Repository navigation
Conversation
🔗 Helpful Links🧪 See artifacts and rendered test results at hud.pytorch.org/pr/pytorch/executorch/24637
Note: Links to docs will display an error until the docs builds have been completed. ❌ 148 New Failures, 1 Cancelled Job, 7 Unrelated Failures, 27 Unclassified FailuresAs of commit 80c68e9 with merge base c7f7931 ( NEW FAILURES - The following jobs have failed:
UNCLASSIFIED FAILURES - DrCI could not classify the following jobs because the workflow did not run on the merge base. The failures may be pre-existing on trunk or introduced by this PR:
CANCELLED JOB - The following job was cancelled. Please retry:
FLAKY - The following jobs failed but were likely due to flakiness present on trunk:
This comment was automatically generated by Dr. CI and updates every 15 minutes. |
4b5de65 to
0c97fdf
Compare
2f88227 to
ab17a5f
Compare
ab17a5f to
8128a44
Compare
0c97fdf to
4ec9257
Compare
|
@claude /pr-review |
There was a problem hiding this comment.
Most of the Claude review points look like nits to me, but they'd make the design safer and are easy to address such as
- check that positions have one component per token before the consecutive check,
- check can_rewind(start) in checked_offgraph_step and rewind just before run(), so a step that fails before running leaves the length unchanged.
My question is whether start < length should be allowed, and if so, which behavior the backends should share: rewinding to start as this does, or the MLX cache's in-place overwrite, which keeps the length.
| ", past the sequence's length of " + | ||
| std::to_string(kv_cache_->length())); | ||
| } | ||
| if (start < kv_cache_->length() && |
There was a problem hiding this comment.
When should a step be allowed to start before the length? Here the only caller that does it is --warmup_prefill, which re-runs the prompt at 0. If that's the only case, the runner could call kv->control->clear() after warmup, and the engine could require start == length(). DFlash calls rewind() explicitly to drop rejected draft tokens.
If we do want to allow it, should it change the length? MLX overwrites in place and keeps the length. Here the step rewinds to its start, so the length becomes start + tokens. Rewinding to start as this does seems more correct than MLX's in-place overwrite.
The ggml engine now runs graphs exported with an off-graph KV cache. - GgmlSequenceCache is the ggml SequenceCache: the neutral sequence controller plus K/V storage per layer in the engine's backend buffer. Storage starts at the config's initial_capacity rows and doubles toward capacity, so short sequences do not pay for the full context. - register_ggml_cache_builders() registers it with the CacheFactory under backend id NativeGgml, kind single. - A context created with the llm_cache_registry_key option resolves the cache from the CacheRegistry. - update_and_attend lowers to ggml_set_rows into the cache and flash_attn_ext over the filled prefix, with per-step rows and causal mask uploads. Before each call the executor checks positions, grows the cache, rebinds storage (dropping cached graphs on a regrow), and plans every layer; it commits after the call succeeds. Flat layers, batch 1, and fp32/fp16 storage only.
4ec9257 to
b2aebff
Compare
8128a44 to
80c68e9
Compare
Stack
Off-graph KV cache for the native ggml engine, through serving Muse Glimmer on it. Review bottom-up; each PR builds and passes its tests on its own.
Summary
The native ggml engine can now run graphs exported with an off-graph KV cache (
kvcache::update_and_attend).Cache:
GgmlSequenceCache(new,GgmlCache.{h,cpp})SequenceCache, so it uses the neutral sequence controller (SequenceControl/SequencePlanner). It adds K/V storage per layer, laid out[head_dim, rows, n_kv_heads]in the engine backend's default buffer type.initial_capacityrows and doubles towardcapacity, copying existing rows across. Short sequences don't pay for the full context.Storage. An executable holds the storage its graphs point into until it rebinds, so a regrow by another executable never leaves dangling tensors behind.SequenceCache, it isn't thread-safe. Contexts bound to one cache must be serialized;create_context_with_optionsdocuments this.register_ggml_cache_builders()registers it with theCacheFactoryunder backend idNativeGgml, kindsingle. Registration is an explicit call rather than a static initializer, so a static link can't drop it.Engine
GgmlEngineHostimplementscreate_context_with_options. Thellm_cache_registry_keyoption resolves the cache from theCacheRegistry. A missing key or an unknown option throws.update_and_attendlowers toggml_set_rowsinto the layer's cache, a view of the filled prefix, andggml_flash_attn_extwith an F16 causal mask. The rows and mask are per-step uploads shared by all layers.How many cache rows to attend is part of the graph cache key. It's
max(position) + 1, rounded up to 256 and capped at the allocated rows.HostIntEvaluatorlearnsselect.intso it can evaluate HF'sposition_ids[0].Before each call, the executor reads the positions once on the host, then:
--warmup_prefill) does; one starting past the length is rejected, since it would attend rows nothing wrote;It rewinds and commits only after the call succeeds, so a call that fails before its graph runs (a host-side index check, growing the cache, building the graph) leaves the sequence unchanged. One that fails while running may have written rows, so the sequence drops its positions from the step's start.
An F16 step written into an F32 cache is converted first:
ggml_set_rowsdoesn't convert F16 to F32 on every backend (CUDA aborts, Metal falls back to CPU).The cache builder reports a geometry or config the engine can't serve through
CacheFactory::build's error result rather than an exception.Limits: flat layers only (sliding-window layers are rejected), batch 1, fp32 or fp16 storage.
Test plan
New
test_ggml_engine.cpptests. Each runsupdate_and_attendagainst a naive softmax attention over everything the cache should hold:OffGraphAttentionMatchesReferenceAsTheCacheGrows:OffGraphAttentionReplaysFromEmptyAfterClear, including an executable whose binding went stale because another one regrew the cache.OffGraphAttentionContinuesFromARewind.OffGraphStepsStartAtOrBeforeTheLength: re-running a step rewinds; skipping past the length is rejected.FailedOffGraphStepLeavesTheSequence: a step starting before the length that fails on a host-side row check leaves the sequence where it was. It fails if the rewind happens before the graph is built.RejectsOffGraphStepsPastCapacityOrNotConsecutive.OffGraphAttentionNeedsAMatchingCache: no key, an unknown key, an unknown option, a layer out of range, and K/V shaped unlike the cache.Checked that the tests catch real bugs: skipping the row copy on regrow fails 3 of them, dropping the causal mask fails 4, and dropping the start-position check fails 2.
native_ggml_engine_test: 43 passed.GpuUnavailableOnCpuVariantfails as it does onmain, because this build has Metal enabled.