feat(KV-DSV4-MULTICACHE): W2 -- DeepSeek-V4's real seven-group cache topology, and a runner that names the group it cannot carry (#1973, #1974) - #1977
Open
localai-bot wants to merge 4 commits into
Conversation
… refuse the group the runner drops in silence (#1973, #1974) W1 declined to publish DeepSeek-V4's cache topology because the runner drops an unrecognised group with no diagnostic. That reading is correct, and tracing it end to end makes it worse rather than better: MakeDeepseekV4KVCache is ModelFactory::make_kv_cache and is reached from LoadedEngine's constructor, and because DeepSeek-V4 has no Mamba group the allocation loop never reaches the by-name path at all -- it allocates one buffer per hidden layer sized from the first group's page. Publishing seven groups without a refusal would allocate 43 buffers of one page for a model that needs 167 of seven, and say nothing. So W2 is resequenced in content rather than in order: it publishes the topology AND makes the drop impossible to take silently, which is the polarity AGENTS.md already requires of an unimplemented arm. This section records the seven groups with the upstream site each value came from, the four details a careless port gets wrong -- the prefix is `attn` and not `self_attn`, kv_quant_mode is passed on two of the four construction sites, the indexer and compressor specs carry no model_version, and the indexer key width defaults to 132 and not 68 -- the page sizes as literals, and the byte-neutrality pins. It also states the cost: a DeepSeek-V4 engine now refuses instead of running a full recompute that has no decode step in it. #1974 is the spec_equal observation W1 named and correctly left alone unverified. It is verified here, filed, and fixed in flow with W2. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: Claude:claude-fable-5 [Claude Code]
…topology, and a runner that names the group it cannot carry (#1973, #1974) MakeDeepseekV4KVCache published ONE placeholder group called "mla", 576 bytes wide, for a model that upstream gives 167 cache entries across 43 layers. It now publishes all of them: 21 C4A and 20 C128A compressed latents plus 21 indexer key caches as MLAAttentionSpec, and 43 SWA caches plus 21 + 21 + 20 compressor states as SlidingWindowMLASpec, at block sizes 256, 64, 4 and 8, with the real module paths as layer names. That could not land alone. GPUModelRunner::initialize_kv_cache selects the first full-attention or MLA group and one Mamba group and has no else, so a kSlidingWindowMla group and a second kMlaAttention group were both dropped with no buffer and no message. Measured on the pre-fix binary: three published groups, constructs without throwing, full_attn_group_id 0, attn_kv().size() 4 -- one buffer per hidden layer off group 0's 1024-byte page -- and zero bytes for the group whose own page is 37440. Publishing seven groups into that would have allocated a subset of the topology in silence, which is a wrong-tokens failure rather than a crash. So the runner now refuses, naming every group it does not allocate, its kind, its first layer and what its page would have cost. The consequence is that a DeepSeek-V4 engine refuses at KV-cache construction instead of running. What it loses is a path with no decode step in it: Forward and ForwardDevice discard attn_kv and recompute the whole prefix per token. examples/deepseek_v4_gen does not go through the runner and is unaffected. docs/FEATURES.md is updated to say so. Four upstream details this port had to get right and a careless one would not: the published prefix is `attn`, not the `self_attn` every other architecture in this tree uses; kv_quant_mode is passed on two of the four construction sites and not the other two, so the SWA and latent specs carry FP8_PER_TENSOR and must still reach the 584-byte branch before W1's quant guard throws; the indexer and compressor specs carry no model_version and take the element formula; and the indexer key width defaults to 132 rather than the MXFP4 68. #1974 rides along: spec_equal's default arm answered false for kMlaAttention and kSlidingWindowMla, so two identical MLA specs never merged into one SpecGroup. Upstream's frozen dataclasses cannot answer that. Latent until this commit published three MLA groups; fixed in flow with both arms comparing the four DeepSeek-V4 fields. NOTHING CONSUMES THE PUBLISHED TOPOLOGY. No runner allocates any of the seven groups and DeepseekV4Model::Forward still discards attn_kv. The wiring is owed to W3 and the consumption to W5, both tracked under #1925 and both listed under `## Owed` in .agents/specs/kv-dsv4-multicache.md, together with the fp8_ds_mla arm being the only one published and the draft slot being tolerated on its kind. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: Claude:claude-fable-5 [Claude Code]
…lkan red found while gating W2 (#1979) Not this branch's defect and not this branch's fix. `windows-msvc-vulkan` stops on `test_openai_api_server.exe exited with status -1073740791` on this pull request and on #1975, #1969 and #1967, whose diffs share no file under src/vllm/entrypoints/. The job is PR-only and never runs on main, so there is no green reference commit to bisect against, which is why four authors could each meet this red without any of them being able to attribute it. It rides here rather than in its own record-only pull request because the alternative to filing it now is not filing it: AGENTS.md requires a bug found during other work to get an issue that names an owner, and this one is owned by ENG-RELEASE-WINDOWS rather than by the row that found it. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: Claude:claude-fable-5 [Claude Code]
origin/main moved 24 commits ahead while this branch was gated, so the diff-scoped gates were measuring against a base that no longer exists. Merged so they run against the real one before the push. Two files are touched by both sides and neither took an automatic three-way merge on trust: `.agents/issue-index.md` is append-only under the union driver, which is known to drop a tail row on a clean merge, and all three rows this branch appends (#1973, #1974, #1979) were checked present and unduplicated afterwards; `docs/FEATURES.md`'s DeepseekV4ForCausalLM row was checked to still carry this branch's edit. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: Claude:claude-fable-5 [Claude Code]
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
MakeDeepseekV4KVCachepublished ONE placeholder group called"mla", 576 byteswide, for a model that upstream gives 167 cache entries across 43 layers. It now
publishes all of them: 21 C4A and 20 C128A compressed latents plus 21 indexer key
caches as
MLAAttentionSpec, and 43 SWA caches plus 21 + 21 + 20 compressorstates as
SlidingWindowMLASpec, at block sizes 256, 64, 4 and 8, with the realmodule paths as layer names.
That could not land alone.
GPUModelRunner::initialize_kv_cacheselects thefirst full-attention or MLA group and one Mamba group and has no
else, so akSlidingWindowMlagroup and a secondkMlaAttentiongroup were both droppedwith no buffer and no message. Measured on the pre-fix binary with a throwaway
probe: three published groups, constructs without throwing,
full_attn_group_id = 0,attn_kv().size() = 4— one buffer per HIDDEN LAYERoff group 0's 1024-byte page — and zero bytes for the group whose own page is
37440. Publishing seven groups into that would have allocated a subset of the
topology in silence, which is a wrong-tokens failure rather than a crash. So the
runner now refuses, naming every group it does not allocate, its kind, its first
layer and what its page would have cost.
The consequence is that a DeepSeek-V4 engine refuses at KV-cache construction
instead of running. What it loses is a path with no decode step in it:
Forwardand
ForwardDevicediscardattn_kvand recompute the whole prefix per token.examples/deepseek_v4_gendoes not go through the runner and is unaffected.docs/FEATURES.mdis updated to say so.Four upstream details this port had to get right and a careless one would not:
the published prefix is
attn, not theself_attnevery other architecture inthis tree uses;
kv_quant_modeis passed on two of the four construction sitesand not the other two, so the SWA and latent specs carry
FP8_PER_TENSORandmust still reach the 584-byte branch before W1's quant guard throws; the indexer
and compressor specs carry no
model_versionand take the element formula; andthe indexer key width defaults to 132 rather than the MXFP4 68.
#1974 rides along:
spec_equal'sdefault:arm answeredfalseforkMlaAttentionandkSlidingWindowMla, so two identical MLA specs never mergedinto one
SpecGroup. Upstream's frozen dataclasses cannot answer that. Latentuntil this change published three MLA groups; fixed in flow with both arms
comparing the four DeepSeek-V4 fields.
Red before
test_deepseek_v4_scaffold.cpp:197: FATAL ERROR: REQUIRE( kv.kv_cache_groups.size() == 7 ) is NOT correct! values: REQUIRE( 1 == 7 )test_runner.cpp:1631: ERROR: CHECK_THROWS_AS( construct(kv), std::runtime_error ) did NOT throw at all!test_kv_cache_coordinator.cpp:318: FATAL ERROR: REQUIRE( hyb->attention_groups.size() == 2 ) is NOT correct! values: REQUIRE( 3 == 2 )Build for the red run:
ninja rc=0at6/6, 0 FAILED.Green after
ninja rc=0at7/7, 0 FAILED.test_runner22 cases / 567 assertions,test_deepseek_v4_scaffold8 / 666,test_kv_cache_coordinator19 / 117 — all0 failed.
Affected suites: 89 built (
ninja rc=0at162/162, 0 FAILED) and run,ctest rc=0, 100% passed. 25 of the 89 report ZERO doctest assertions —they are checkpoint-gated skips wearing a pass, and
test_qwen35_paged_enginedid not run at all (Skipped, no checkpoint). Stated rather than implied: the
64 suites that carry assertions are the evidence, led by
test_dots3_note_scaffold110818,test_nemotron_h_scaffold38308,test_parser_engine_assembly5038,test_nemotron_h_paged_forward3269,test_model_registry941,test_llm_engine493,test_runner567,test_kimi_linear_paged206,test_dflash2_runner_reach144,test_mtp_depth123. Fullctestwas NOT run: the disk stood at 95-96% usedand this box hit ENOSPC once during this work.
Mutations
Ten, each rebuilt from source, each verified to have LANDED before running, each
restored byte-for-byte with sha256 verified after.
VT_CHECKalways true)kSlidingWindowMlaadded to the tolerated kinds.self_attninstead of.attnhead_sizeishead_dim + rope(the old placeholder 576)alignment512 instead of 576block_sizerefusal disabledspec_equalMLA arm ignorescompress_ratiospec_equalkSlidingWindowMlaarm answers false for identical specsTwo of these were first written in forms that FAILED TO BUILD (
rc=1) — a buildfailure re-runs the previous binary and reads as a pass. They were caught by
recording ninja's exit code, rewritten and rerun.
Byte-neutrality
No spec class and no page formula changes, so no existing page size can move.
What could move is the runner's behaviour for every model and
spec_equal'spartition, and both are pinned directly:
test_runnerasserts that all fourgroup shapes shipped today still construct (one full-attention; one MLA; full-
attention + recurrent; full-attention + recurrent +
fa_draft), and every KVfactory in the tree publishes one group or two (
kimi_linear: MLA + KDA), eachof them a tolerated shape. The
fa+gdnshape is executed bytest_nemotron_h_paged_forward(3269) andtest_kimi_linear_paged(206); thefa_draftslot bytest_dflash2_runner_reach(144) andtest_mtp_depth(123).Nothing lands dead
The published topology is UNREACHED and this PR does not claim otherwise. No
runner allocates any of the seven groups and
DeepseekV4Model::Forwardstilldiscards
attn_kv. What IS reached from a production entry point is the refusal:MakeDeepseekV4KVCacheisModelFactory::make_kv_cache, reached fromLoadedEngine's constructor throughMakeKVCacheResolvedandModelRegistry::MakeKVCache, and the runner check sits onGPUModelRunner::initialize_kv_cache, which every engine crosses — the firstmutation deletes it and the gate goes red.
The wiring is owed to W3 (the runner carrying more than one attention group
and more than one cache per layer) and the consumption to W5, both tracked
under #1925 and both listed under
## Owedin.agents/specs/kv-dsv4-multicache.md, together with two more items this waveadds there: only the
fp8_ds_mlaarm of the topology is published (ourParseCacheDTyperefuses the string by name and the factory signature carries nocache dtype), and the draft slot is tolerated on its KIND rather than on
spec_on().CI
gh pr checksreports nothing on this branch and that is not a pass. Thepull_requestrun on0b01c8babwas CANCELLED by the push of the third commit,and GitHub then created no run at all for
fcd4e171e— not after the push, andnot after a close/reopen twenty minutes later, while other pull requests' runs
started normally in the same window. A cancelled run reads as failed and a run
that never starts is not a slow green, so the head was verified another way:
workflow_dispatchrun 32975253972 onfcd4e171e, the exact tree of thispull request. 14 success, 2 failure, 3 skipped:
build-test-cpu,build-test-cpu-arm64,build-test-cpu-arm64-full,build-newest-gcc,build-test-vulkan,cuda-fat-build,cuda-arch-features,sanitize-cpu (address,undefined),sanitize-cpu (thread),macos-metal-mlx,device-leakage,vulkan-spirv-freshness,agent-record,last-gated-commit.pr-size,documentation-checkpoint,commit-protocol-tag— theycarry
pull_request-only conditions and cannot run on a dispatch. All threepassed on the
pull_requestrun at0b01c8bab, andscripts/agent-pr-body.py --pr 1977passes on the body as it stands.windows-msvc-cpuandwindows-msvc-vulkan, below.An operator merging this needs the PR's own checks populated; any fresh push or
a re-run from the forge will do it.
Both
windows-msvc-*jobs are RED and it is not this branch's defect.test_openai_api_server.exefail-fasts with-1073740791(0xC0000409,STATUS_STACK_BUFFER_OVERRUN) on both jobs here and on #1975, #1969 and #1967, whose diffsshare no file under
src/vllm/entrypoints/; the job is PR-only and never runson
main, so nobody has a green reference commit to bisect against. Filed as#1979 against
ENG-RELEASE-WINDOWSwith the four job logs, and the index rowrides in the third commit here rather than in its own record-only pull request,
because the alternative to filing it now was not filing it. Every other check
that has settled is green.
Closes #1973
Closes #1974
FOLLOWING_AGENTS_PROTOCOL
Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: Claude:claude-fable-5 [Claude Code]