2828
2929#ifdef EXECUTORCH_BUILD_CUDA
3030#include < cuda_runtime.h>
31+ #include < executorch/backends/cuda/runtime/backend_options.h>
32+ #include < executorch/extension/llm/runner/constants.h>
33+ #include < executorch/extension/llm/runner/model_metadata.h>
3134#include < nlohmann/json.hpp>
3235#else
3336#include < executorch/extension/llm/sampler/util.h>
@@ -276,11 +279,23 @@ Result<uint64_t> read_sampled_token(
276279#endif
277280}
278281
282+ // The off-graph KV cache is CUDA-only, and so are its headers; elsewhere the
283+ // module is built without one and the guard is always null.
284+ #ifdef EXECUTORCH_BUILD_CUDA
285+ using OffGraphInstallGuard = ::executorch::extension::llm::cache::InstallGuard;
286+ #else
287+ struct OffGraphInstallGuard ;
288+ #endif
289+
279290Result<std::unique_ptr<Module>> build_muse_glimmer_module (
280291 const MuseGlimmerConfig& config,
281292 MuseGlimmerArtifactMode artifact_mode,
282293 bool has_vision,
283- bool multi_session) {
294+ bool multi_session,
295+ const OffGraphInstallGuard* offgraph_guard) {
296+ #ifndef EXECUTORCH_BUILD_CUDA
297+ (void )offgraph_guard;
298+ #endif
284299 std::vector<std::string> data_files;
285300 if (!config.data_path .empty ()) {
286301 data_files.push_back (config.data_path );
@@ -315,6 +330,20 @@ Result<std::unique_ptr<Module>> build_muse_glimmer_module(
315330#endif
316331
317332 const executorch::runtime::LoadBackendOptionsMap* load_options = nullptr ;
333+ #ifdef EXECUTORCH_BUILD_CUDA
334+ // Name the installed off-graph cache so CudaBackend::init() can resolve it
335+ // from the registry. Must outlive the load_method calls below, which read it
336+ // during init. Absent for an in-graph model, and its absence is exactly how
337+ // the delegate tells the two apart.
338+ executorch::runtime::BackendOptions<1 > cuda_cache_opts;
339+ executorch::runtime::LoadBackendOptionsMap cuda_options_map;
340+ if (offgraph_guard != nullptr ) {
341+ ET_CHECK_OK_OR_RETURN_ERROR (offgraph_guard->set_option (cuda_cache_opts));
342+ ET_CHECK_OK_OR_RETURN_ERROR (cuda_options_map.set_options (
343+ ::executorch::backends::cuda::kCudaBackendId , cuda_cache_opts.view ()));
344+ load_options = &cuda_options_map;
345+ }
346+ #endif
318347#ifdef EXECUTORCH_BUILD_MLX
319348 // Per-model MLX runtime specs, delivered to MLXBackend::init(). Must outlive
320349 // the load_method calls below (they read it during init).
@@ -456,6 +485,65 @@ Error register_mutable_fqns(
456485 return Error::Ok;
457486}
458487
488+ struct OffGraphKVPlan {
489+ cache::CacheGeometry geometry;
490+ cache::CacheConfig config;
491+ };
492+
493+ Result<OffGraphKVPlan> read_offgraph_kv_plan (
494+ Module& module ,
495+ const std::unordered_map<std::string, int64_t >& metadata,
496+ int64_t initial_capacity) {
497+ // Geometry comes from the neutral constant methods the MLX off-graph path
498+ // also reads. The two sizes are already in the engine's metadata:
499+ // kMaxContextLen is the cache ceiling (get_llm_metadata derives it from
500+ // get_max_seq_len when the model does not publish it), and
501+ // kMaxPrefillChunk is the largest single step, which is what a ring layer
502+ // sizes its slots from.
503+ ET_ASSIGN_OR_RETURN (
504+ geometry, ::executorch::extension::llm::read_cache_geometry (module ));
505+
506+ const auto capacity_it = metadata.find (kMaxContextLen );
507+ const auto chunk_it = metadata.find (kMaxPrefillChunk );
508+ ET_CHECK_OR_RETURN_ERROR (
509+ capacity_it != metadata.end () && chunk_it != metadata.end (),
510+ InvalidProgram,
511+ " off-graph KV cache needs %s and %s metadata" ,
512+ kMaxContextLen ,
513+ kMaxPrefillChunk );
514+
515+ // CacheConfig counts in int; a value that does not survive the narrowing
516+ // would otherwise be validated after truncation.
517+ constexpr int64_t kMaxCacheInt = std::numeric_limits<int >::max ();
518+ for (const auto & [name, value] :
519+ {std::pair{kMaxContextLen , capacity_it->second },
520+ std::pair{kMaxPrefillChunk , chunk_it->second },
521+ std::pair{" offgraph_initial_capacity" , initial_capacity}}) {
522+ ET_CHECK_OR_RETURN_ERROR (
523+ value > 0 && value <= kMaxCacheInt ,
524+ InvalidProgram,
525+ " off-graph KV cache: %s is %" PRId64 " , outside [1, %" PRId64 " ]" ,
526+ name,
527+ value,
528+ kMaxCacheInt );
529+ }
530+
531+ OffGraphKVPlan plan;
532+ plan.geometry = std::move (geometry);
533+ plan.config .capacity = static_cast <int >(capacity_it->second );
534+ plan.config .kv_dtype =
535+ static_cast <int >(executorch::aten::ScalarType::BFloat16);
536+ plan.config .initial_capacity = static_cast <int >(initial_capacity);
537+ plan.config .max_write = static_cast <int >(chunk_it->second );
538+ ET_CHECK_OR_RETURN_ERROR (
539+ cache::valid (plan.geometry , plan.config ) &&
540+ plan.config .initial_capacity > 0 &&
541+ plan.config .initial_capacity <= plan.config .capacity ,
542+ InvalidProgram,
543+ " invalid off-graph KV cache configuration" );
544+ return plan;
545+ }
546+
459547TensorPtr build_decode_pos_table (
460548 const std::unordered_map<std::string, int64_t >& metadata) {
461549 auto ctx_it = metadata.find (kMaxContextLen );
@@ -489,6 +577,9 @@ class MuseGlimmerSession : public LLMSession,
489577 int64_t min_prefill_chunk,
490578 TensorPtr decode_pos_table_dev,
491579 MuseGlimmerMutableStateContextOwner* mutable_state,
580+ #ifdef EXECUTORCH_BUILD_CUDA
581+ ::executorch::extension::llm::cache::SequenceControl* offgraph_control,
582+ #endif
492583 int session_token)
493584 : module_(module ),
494585 exec_mutex_ (exec_mutex),
@@ -502,6 +593,9 @@ class MuseGlimmerSession : public LLMSession,
502593 decode_pos_table_dev_ (std::move(decode_pos_table_dev)),
503594#endif
504595 mutable_state_ (mutable_state),
596+ #ifdef EXECUTORCH_BUILD_CUDA
597+ offgraph_control_ (offgraph_control),
598+ #endif
505599 session_token_ (session_token) {
506600 if (auto it = metadata_.find (kUseSampling ); it != metadata_.end ()) {
507601 use_sampling_ = it->second != 0 ;
@@ -752,6 +846,11 @@ class MuseGlimmerSession : public LLMSession,
752846
753847 Error reset () override {
754848 pos_ = 0 ;
849+ #ifdef EXECUTORCH_BUILD_CUDA
850+ if (offgraph_control_ != nullptr ) {
851+ offgraph_control_->clear ();
852+ }
853+ #endif
755854 pending_.reset ();
756855 prev_decode_token_.reset ();
757856 if (preserve_staged_image_on_next_reset_) {
@@ -1114,6 +1213,10 @@ class MuseGlimmerSession : public LLMSession,
11141213 TensorPtr decode_pos_table_dev_;
11151214#endif
11161215 MuseGlimmerMutableStateContextOwner* mutable_state_ = nullptr ;
1216+ #ifdef EXECUTORCH_BUILD_CUDA
1217+ ::executorch::extension::llm::cache::SequenceControl* offgraph_control_ =
1218+ nullptr ;
1219+ #endif
11171220 int session_token_ = kMuseGlimmerNoMutableSession ;
11181221#ifdef EXECUTORCH_BUILD_CUDA
11191222 float temp_val_ = 1e-6f ;
@@ -1399,8 +1502,47 @@ Result<std::unique_ptr<MuseGlimmerEngine>> MuseGlimmerEngine::create(
13991502 }
14001503
14011504 std::unique_ptr<MuseGlimmerMutableStateContextOwner> mutable_state;
1505+ const OffGraphInstallGuard* offgraph_install = nullptr ;
14021506#ifdef EXECUTORCH_BUILD_CUDA
1403- if (config.enable_cuda_graph ) {
1507+ std::shared_ptr<::executorch::extension::llm::cache::Cache> offgraph_cache;
1508+ std::unique_ptr<::executorch::extension::llm::cache::InstallGuard>
1509+ offgraph_guard;
1510+ ::executorch::extension::llm::cache::SequenceControl* offgraph_control =
1511+ nullptr ;
1512+ if (method_names.count (kNumCaches ) != 0 ) {
1513+ ET_CHECK_OR_RETURN_ERROR (
1514+ !config.enable_cuda_graph ,
1515+ NotSupported,
1516+ " off-graph KV cache does not support CUDA graph" );
1517+ ET_CHECK_OR_RETURN_ERROR (
1518+ artifact_mode == MuseGlimmerArtifactMode::Autoregressive,
1519+ NotSupported,
1520+ " off-graph KV cache currently supports autoregressive artifacts only" );
1521+ ET_CHECK_OR_RETURN_ERROR (
1522+ config.max_sessions == 1 ,
1523+ NotSupported,
1524+ " off-graph KV cache currently supports one session" );
1525+ auto plan = read_offgraph_kv_plan (
1526+ *meta_module, metadata, config.offgraph_initial_capacity );
1527+ ET_CHECK_OK_OR_RETURN_ERROR (plan.error ());
1528+ // The factory hands back a neutral cache; the guard publishes it under a
1529+ // registry key that build_muse_glimmer_module passes to the delegate.
1530+ auto built = cache::CacheFactory::global ().build (
1531+ ::executorch::backends::cuda::kCudaBackendId ,
1532+ cache::kind::kSingle ,
1533+ plan.get ().geometry ,
1534+ plan.get ().config );
1535+ ET_CHECK_OK_OR_RETURN_ERROR (built.error ());
1536+ offgraph_cache = built.get ();
1537+ offgraph_guard = std::make_unique<cache::InstallGuard>(offgraph_cache);
1538+ offgraph_install = offgraph_guard.get ();
1539+ offgraph_control = offgraph_cache->as <cache::SequenceControl>();
1540+ ET_CHECK_OR_RETURN_ERROR (
1541+ offgraph_control != nullptr ,
1542+ Internal,
1543+ " off-graph KV cache is missing a face the engine needs" );
1544+ ET_LOG (Info, " MuseGlimmerEngine: dynamic off-graph KV cache enabled" );
1545+ } else if (config.enable_cuda_graph ) {
14041546 ET_LOG (
14051547 Info,
14061548 " MuseGlimmerEngine: CUDA graph requested; per-session rebinding "
@@ -1433,18 +1575,18 @@ Result<std::unique_ptr<MuseGlimmerEngine>> MuseGlimmerEngine::create(
14331575 // skip_mutable_buffer_init, so the skip flag can never diverge from the
14341576 // owner.
14351577 const bool multi_session = mutable_state != nullptr ;
1436- auto module_res = multi_session
1437- ? mutable_state->with_load_scope ([&]() {
1438- return build_muse_glimmer_module (
1439- config, artifact_mode, has_vision, multi_session);
1440- })
1441- : build_muse_glimmer_module (
1442- config, artifact_mode, has_vision, multi_session);
1578+ auto build_module = [&]() {
1579+ return build_muse_glimmer_module (
1580+ config, artifact_mode, has_vision, multi_session, offgraph_install);
1581+ };
1582+ // The in-graph path still scopes its load, because mutable-buffer rebinding
1583+ // has no registry key to travel on.
1584+ auto module_res = multi_session ? mutable_state->with_load_scope (build_module)
1585+ : build_module ();
14431586 if (module_res.error () != Error::Ok) {
14441587 return module_res.error ();
14451588 }
14461589 std::unique_ptr<Module> shared_module = std::move (module_res.get ());
1447-
14481590 bool rebind_available = false ;
14491591 rebind_available = mutable_state != nullptr && mutable_state->available ();
14501592 if (rebind_available && mutable_state->validate_coverage () != Error::Ok) {
@@ -1493,6 +1635,12 @@ Result<std::unique_ptr<MuseGlimmerEngine>> MuseGlimmerEngine::create(
14931635 /* vision_runtime=*/ nullptr ,
14941636 rebind_available,
14951637 std::move (mutable_state)));
1638+ #ifdef EXECUTORCH_BUILD_CUDA
1639+ // Handed over after construction: the cache has to be installed before the
1640+ // module loads, which happens above.
1641+ engine->offgraph_guard_ = std::move (offgraph_guard);
1642+ engine->offgraph_control_ = offgraph_control;
1643+ #endif
14961644 if (has_vision) {
14971645 MuseGlimmerVisionRuntimeConfig vision_config;
14981646 vision_config.module = engine->shared_module_ .get ();
@@ -1623,6 +1771,9 @@ Result<std::unique_ptr<LLMSession>> MuseGlimmerEngine::create_session() {
16231771 min_prefill_chunk_,
16241772 decode_pos_table_dev_,
16251773 rebind_available_ ? mutable_state_.get () : nullptr ,
1774+ #ifdef EXECUTORCH_BUILD_CUDA
1775+ offgraph_control_,
1776+ #endif
16261777 token));
16271778}
16281779
0 commit comments