@@ -112,6 +112,7 @@ using ::executorch::extension::Module;
112112using ::executorch::extension::llm::check_vocab_size;
113113using ::executorch::extension::llm::LogitsToKeepMode;
114114using ::executorch::extension::llm::read_activation_dtype;
115+ using ::executorch::extension::llm::read_cache_geometry;
115116using ::executorch::extension::llm::read_logits_to_keep_mode;
116117using ::executorch::extension::llm::read_max_seq_len;
117118using ::executorch::extension::llm::read_vocab_size;
@@ -154,17 +155,6 @@ bool parse_int_list(
154155 return true ;
155156}
156157
157- // Constant methods the export publishes (get_n_caches and friends). They carry
158- // no delegate, so reading them only needs the program loaded -- which is what
159- // lets the cache be built before forward's backend init consumes its key.
160- std::optional<int64_t > const_int (Module& module , const char * name) {
161- const auto r = module .execute (name);
162- if (!r.ok () || r->empty () || !r->at (0 ).isInt ()) {
163- return std::nullopt ;
164- }
165- return r->at (0 ).toInt ();
166- }
167-
168158// The sampler (sample_from_logits) fatally aborts on any other dtype, so an
169159// unsupported logits type must be rejected at startup rather than at inference.
170160bool is_supported_logits_type (::executorch::aten::ScalarType type) {
@@ -212,54 +202,6 @@ bool validate_forward_abi(
212202 return true ;
213203}
214204
215- std::optional<std::vector<int >> const_ints (Module& module , const char * name) {
216- const auto r = module .execute (name);
217- if (!r.ok () || r->empty () || !r->at (0 ).isTensor ()) {
218- return std::nullopt ;
219- }
220- const auto t = r->at (0 ).toTensor ();
221- if (t.scalar_type () != ::executorch::aten::ScalarType::Int) {
222- return std::nullopt ;
223- }
224- const int32_t * p = t.const_data_ptr <int32_t >();
225- return std::vector<int >(p, p + t.numel ());
226- }
227-
228- // Fill in the cache geometry the export published: get_n_caches, then one
229- // entry per cache in get_kv_heads / get_head_dims / get_windows (0 = flat).
230- // Capacity and dtype stay with the flags. False means this is not an off-graph
231- // model.
232- bool read_kv_layout (
233- Module& module ,
234- int prefill_chunk,
235- cache::CacheConfig& cfg) {
236- const auto n_caches = const_int (module , " get_n_caches" );
237- const auto kv_heads = const_ints (module , " get_kv_heads" );
238- const auto head_dims = const_ints (module , " get_head_dims" );
239- const auto windows = const_ints (module , " get_windows" );
240- if (!n_caches || !kv_heads || !head_dims || !windows) {
241- return false ;
242- }
243- cfg.max_write = prefill_chunk;
244- const size_t n = static_cast <size_t >(*n_caches);
245- if (kv_heads->size () != n || head_dims->size () != n || windows->size () != n) {
246- return false ;
247- }
248- cfg.n_layers = static_cast <int >(n);
249- cfg.layers .clear ();
250- cfg.layers .reserve (n);
251- for (size_t l = 0 ; l < n; ++l) {
252- cache::LayerConfig lc{};
253- lc.n_kv_heads = (*kv_heads)[l];
254- lc.head_dim = (*head_dims)[l];
255- lc.policy = (*windows)[l] > 0
256- ? cache::LayerPolicy{cache::LayerPolicy::Kind::Ring, (*windows)[l]}
257- : cache::LayerPolicy{cache::LayerPolicy::Kind::Flat, 0 };
258- cfg.layers .push_back (lc);
259- }
260- return true ;
261- }
262-
263205// Replace the model's own attention pattern with `spec`, a comma-separated list
264206// of windows repeating over the caches (0 = flat). One entry makes every layer
265207// sliding. Only the policy changes; each cache keeps the geometry the .pte
@@ -268,14 +210,17 @@ bool read_kv_layout(
268210// The export sizes the chunk to the model's own window; narrowing the window
269211// here would leave the ring (window + max_write - 1) sized by the chunk
270212// instead, so the chunk follows the window down.
271- bool apply_window_override (const std::string& spec, cache::CacheConfig& cfg) {
213+ bool apply_window_override (
214+ const std::string& spec,
215+ cache::CacheGeometry& geometry,
216+ cache::CacheConfig& cfg) {
272217 std::vector<int > pattern;
273218 if (!parse_int_list (spec, ' ,' , pattern) || pattern.empty ()) {
274219 return false ;
275220 }
276- for (size_t l = 0 ; l < cfg .layers .size (); ++l) {
221+ for (size_t l = 0 ; l < geometry .layers .size (); ++l) {
277222 const int w = pattern[l % pattern.size ()];
278- cfg .layers [l].policy = w > 0
223+ geometry .layers [l].policy = w > 0
279224 ? cache::LayerPolicy{cache::LayerPolicy::Kind::Ring, w}
280225 : cache::LayerPolicy{cache::LayerPolicy::Kind::Flat, 0 };
281226 }
@@ -288,7 +233,7 @@ bool apply_window_override(const std::string& spec, cache::CacheConfig& cfg) {
288233 if (cfg.max_write && narrowest > 0 && narrowest < *cfg.max_write ) {
289234 cfg.max_write = narrowest;
290235 }
291- return cache::valid (cfg);
236+ return cache::valid (geometry, cfg);
292237}
293238
294239// Human-readable name for a kv_dtype (an ET ScalarType int). Only the
@@ -310,14 +255,14 @@ std::string dtype_name(int st) {
310255// Announce the cache shape: the same .pte runs under whatever config this
311256// invocation asks for -- capacity, storage dtype, flat/ring layers -- with no
312257// re-export. The footprint lines printed later then show it growing at runtime.
313- void print_cache_summary (const cache::CacheConfig& cfg) {
258+ void print_cache_summary (
259+ const cache::CacheGeometry& geometry,
260+ const cache::CacheConfig& cfg) {
314261 // Ring layers grouped by window: --kv-windows can give each layer its own,
315262 // and the pools are sized per layer, so a single number would misreport them.
316263 std::map<int , int > ring;
317264 int flat = 0 ;
318- for (int l = 0 ; l < cfg.n_layers ; ++l) {
319- const cache::LayerConfig& lc =
320- cfg.layers .size () == 1 ? cfg.layers .front () : cfg.layers [l];
265+ for (const cache::LayerGeometry& lc : geometry.layers ) {
321266 if (lc.policy .kind == cache::LayerPolicy::Kind::Ring) {
322267 ++ring[lc.policy .window ];
323268 } else {
@@ -330,7 +275,8 @@ void print_cache_summary(const cache::CacheConfig& cfg) {
330275 if (cfg.max_write ) {
331276 std::cout << " max_write=" << *cfg.max_write ;
332277 }
333- std::cout << " \n " << cfg.n_layers << " layers: " << flat << " flat" ;
278+ std::cout << " \n " << geometry.layers .size () << " layers: " << flat
279+ << " flat" ;
334280 for (const auto & [window, n] : ring) {
335281 std::cout << " + " << n << " ring(window " << window << " )" ;
336282 }
@@ -743,16 +689,19 @@ int main(int argc, char** argv) {
743689 << " (bf16|fp16|fp32)" << std::endl;
744690 return 1 ;
745691 }
746- if (!read_kv_layout (module , prefill_chunk, cfg)) {
747- std::cerr << " No KV cache layout in " << pte
692+ auto geometry = read_cache_geometry (module );
693+ if (!geometry.ok ()) {
694+ std::cerr << " No valid KV cache geometry in " << pte
748695 << " ; re-export with --use-offgraph-cache" << std::endl;
749696 return 1 ;
750697 }
751- if (!kv_windows.empty () && !apply_window_override (kv_windows, cfg)) {
698+ cfg.max_write = prefill_chunk;
699+ if (!kv_windows.empty () &&
700+ !apply_window_override (kv_windows, *geometry, cfg)) {
752701 std::cerr << " Invalid --kv-windows: " << kv_windows << std::endl;
753702 return 1 ;
754703 }
755- if (!cache::valid (cfg)) {
704+ if (!cache::valid (*geometry, cfg)) {
756705 std::cerr << " Invalid cache config" << std::endl;
757706 return 1 ;
758707 }
@@ -762,7 +711,7 @@ int main(int argc, char** argv) {
762711
763712 const char * const cache_kind = cache::kind::kSingle ;
764713 auto built = cache::CacheFactory::global ().build (
765- ::executorch::backends::mlx::kMLXBackendId , cache_kind, cfg);
714+ ::executorch::backends::mlx::kMLXBackendId , cache_kind, *geometry, cfg);
766715 if (!built.ok ()) {
767716 std::cerr << " Failed to build cache: " << static_cast <int >(built.error ())
768717 << std::endl;
@@ -775,7 +724,7 @@ int main(int argc, char** argv) {
775724 // load_method() inside it.
776725 const cache::InstallGuard guard{kv};
777726
778- print_cache_summary (cfg);
727+ print_cache_summary (*geometry, cfg);
779728 if (guard.set_option (mlx_opts) != Error::Ok ||
780729 options_map.set_options (
781730 ::executorch::backends::mlx::kMLXBackendId , mlx_opts.view ()) !=
0 commit comments