Skip to content

[feature] support pp>1 for Kimi-K3 - #2365

Merged
valarLip merged 52 commits into
mainfrom
guanbao/k3-pp2tp4
Nov 5, 2026
Merged

valarLip merged 52 commits into
mainfrom
guanbao/k3-pp2tp4

Conversation

@gbyu-amd

@gbyu-amd gbyu-amd commented Sep 23, 2026 •

Copy link
Copy Markdown
Contributor

Motivation

Kimi-K3 only ran at TP8. This PR makes it serve at --pipeline-parallel-size 2 --tensor-parallel-size 4 on MI355X ×8, covering plain decode, prefix caching (KDA state checkpoints), LMCache offload and DSpark speculative decoding. None of these paths had been exercised under PP for this model.

The first commit's message lists every original change; the review: commits on top each address one review point.

Technical Details

Model and transport

  • K3's block_residual (AttnRes) now crosses the stage boundary with the hidden states. It used to be dropped there, and stage 1 raised KeyError during warmup.
  • The DSpark draft loads its own checkpoint embedding on the last stage (587 MiB/rank at TP4), where the target's embed_tokens is a PPMissingLayer.
  • PPAuxRelay (atom/spec_decode/pp_aux_relay.py) relays DSpark's aux hidden states across stages. K3 taps layers 2 and 23, which live on stage 0, so without it the drafter read zero buffers. The embedding tap (-1) resolves as Drafter.arm_aux_capture resolves it.
  • MLA's query-head padding rounds up to a width aiter dispatches (24 → 32 at TP4). TP8 already padded 12 → 16, and head counts that already dispatch are unchanged.

Speculative decoding under PP

  • A PP engine now verifies its own drafts. PP output is undeferred, so:

    • the sampler reports this step's rejection and bonus counts on the non-deferred path;
    • every stage publishes those counts to its attention backends, so the stages that don't sample still roll back KDA state;
    • postprocess leaves exactly one run of mtp_k + 1 placeholders after every step, whatever was rejected.
  • The attention backends rebuild the verify window from tokenIDProcessor.verify_ctx_lens:

    • deferred output shifts by num_rejected (unchanged);
    • undeferred output shifts by 1.

    The scheduler stages the undeferred window from the recorded seq.num_placeholder_tokens.

  • The verify window is gated and sized on the run's speculative config, not on whether this stage drafts. Stage 0 used to allocate 3 conv rows per KDA slot while the kernel addressed 10.

  • A P/D producer still never speculates, with or without PP. Under PP a producer keeps any drafter; the other architectures and spec methods allowed under PP are declared in registry.pp_speculative_methods.

  • TBO is refused under PP (UBatchWrapper.forward takes no intermediate_tensors).

  • On the last stage the drafter reads the relayed aux block in place (Drafter.take_relayed_aux), with no per-tap split and concatenation.

KDA state and prefix caching under PP

  • Checkpoint hold. A chunk that ends on a checkpoint rung holds the next chunk until the rung's checkpoint is filed, because progress advances at schedule time. Otherwise the archived state ran ahead of its hash (filed as 768 tokens while holding 884).
  • State fork on decode. The checkpoint is filed after the prompt, so its state fork lands on the first decode. That decode now publishes the source slot and moves conv and recurrent state onto the write slot before the kernels run.
    • With DSpark, that first decode is a verify step, so the spec path does the same; every column of a forked row's spec read table holds the source slot. ReplaySSM seeds forked slots from host-known (write, source) pairs, with no host sync.
    • Only a backend whose model does that copy may take a fork on the spec path (GDNStateMixin.spec_honours_state_fork, which K3 sets). Every other GDN backend keeps the assert.

CI

  • A new accuracy case, Kimi-K3 DSpark PP2, runs pp2 × tp4 with DSpark, prefix caching and ptpc_fp8 online quant on. It is the only accuracy job that reaches the PP paths.

Known limits (follow-ups)

  • PP still requires --enforce-eager: PP decode has no CUDA graphs yet. This is the existing forward_vars ring assert, not something this PR adds. CUDA-graph support for PP decode is follow-up work.
  • The checkpoint hold costs prefill latency: in K3 PP8 prefill it accounts for about 3.3 s of a roughly 7 s fixed latency. feat(kimi-k3): PP8 P/D prefill 1/4 — PAGE-backed state checkpoints under PP #2709, stacked on this PR, files a PAGE-backed checkpoint as soon as its chunk is scheduled and keeps the hold only for fork-class requests.
  • Aux-relay traffic grows with PP depth. At PP8 the 6 → 7 boundary carries 4 taps. Relaying a running context_proj partial sum would keep it at one fp32 column.
  • LMCache offload under PP is validated on the store path only: GSM8K has no repeated prefix, so loads from the CPU tier are not exercised yet. The KDA state tier stays off under PP (state_offload.py, CacheEngineKey has no PP component).

Dependency

Test Plan

Unit tests

  • Command: python -m pytest <file> for every file under tests/, one process per file. Compared with main at the same commit, on the same host.
  • New tests:
    • test_verify_ctx_lens.py: the verify window's positions, deferred and undeferred. It imports model_runner (where tokenIDProcessor.verify_ctx_lens lives, per @valarLip's review), so it needs aiter and skips on the non-GPU runner.
    • test_pp_draft_input_ids.py: the vectorized PP verify-draft scatter
    • test_pp_aux_relay.py: tap split at pp = 2/4/8, the -1 tap, the round trip through every stage, the send buffer sized per forward, and the last stage arming only its own taps
    • test_gdn_state_fork.py: a fork on the decode and spec paths
    • undeferred placeholder algebra in test_scheduler_mtp_max_tokens.py
    • checkpoint-hold lifecycle in test_pp.py, including the flush on the ack path
    • K3 mono refuses a forked step, spec and plain, in models/test_kimi_k3_mono_plan.py

Accuracy

  • GSM8K, full 1319 questions, lm_eval as in recipes/Kimi-K3.md, num_concurrent=32.
  • Server flags are the new CI case's: -tp 4 --pipeline-parallel-size 2 --enforce-eager --block-size 128 --enable_prefix_caching --method dspark --num-speculative-tokens 7 --max-num-seqs 64, plus the recipe's ptpc_fp8 online quant.
  • Image: rocm/atom-dev:nightly_202610251529, with its own aiter.

Test Result

Unit tests

  • At 5ab3d68f7 (this branch merged with main 99ebf633a), every test file was run, one process per file, against main itself in the same container. No file does worse. At head b62bf7ac6, every file the PR affects was re-run against the merge base a8946ff73: no regressions (the one failure, test_model_runner_exit::test_a_failed_megamoe_close_still_releases_everything, fails at the merge base too). Every file that differs does so by gaining tests:
    • new files: test_verify_ctx_lens (26), test_pp_aux_relay (24), test_pp_draft_input_ids (1), test_gdn_state_fork (5);
    • added tests: test_pp +7, test_scheduler_mtp_max_tokens +6, models/test_kimi_k3_mono_plan +2.
  • test_verify_ctx_lens, test_pp_aux_relay and test_gdn_state_fork import aiter, so the non-GPU pre-checkin runner skips them; they pass in the rocm/atom-dev nightly image.

GSM8K, pp2 × tp4, DSpark, prefix caching, ptpc_fp8, 1319 questions

Run Build flexible / strict DSpark acceptance
this PR f34cb8a8e, nightly_202610310756 0.9704 / 0.9704 (±0.0047) 53.7%, 4.76 tok/fwd
this PR this commit on main 713eb0614 (70dc6d37c), nightly_202610251529 0.9553 / 0.9560 (±0.0057) 53.4%, 4.74 tok/fwd
main CI, TP8, Kimi-K3 DSpark 2026-10-24 / 2026-10-26 0.9568 / 0.9545 (flexible) —

pp2 × tp4 scores the same as main's TP8 DSpark case from the same week, and its acceptance matches TP8 (53.7–55.2% on earlier same-host runs). Earlier numbers for this PR (0.9666 on nightly_202609220547) were measured on an older image, so they are not comparable.

GSM8K, pp2 × tp4, no DSpark, prefix caching + LMCache offload, ptpc_fp8, 1319 questions

flexible 0.9674 ± 0.0049 / strict 0.9682 ± 0.0048 (e1c80ceae, nightly_202610310756)

Submission Checklist

@gbyu-amd
gbyu-amd marked this pull request as draft September 23, 2026 02:35
@github-actions

Copy link
Copy Markdown
Contributor

🏷️ CI Guide

Runs automatically on every eligible PR before approval:

  • ✅ Pre Checkin: Black, Ruff, catalog schema validation, non-GPU unit tests

Heavy model tests:

  • ✅ Run after the PR is approved and Pre Checkin passes
  • ✅ Run immediately when an approval review is submitted
  • ✅ Can be requested before approval with labels
Label Tests
ci:full Run all heavy PR model tests: native ATOM, vLLM, and SGLang
ci:atom Run native ATOM model accuracy tests
ci:vllm Run ATOM vLLM OOT model accuracy tests
ci:sglang Run ATOM SGLang model accuracy tests

Heavy jobs are skipped when the PR is not approved and no matching ci:* label is present.
Add labels via the sidebar or gh pr edit 2365 --add-label <label>

@wanzhenchn

Copy link
Copy Markdown
Contributor

Update: I went ahead and stacked the three PRs (main ← #2576 ← #2365 ← #2709). guanbao/k3-pp2tp4 is now at 501364cf4, the 15 commits rebased onto #2576 and current main, and this PR's base is Jasen/atomesh-agentx-harness. If you have local work on the old head 5c811df1d, git fetch && git rebase --onto origin/guanbao/k3-pp2tp4 5c811df1d moves it over. If you'd rather keep this PR out of the stack, say so and I'll restore 5c811df1d and unstack it.

@wanzhenchn wanzhenchn left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review summary

Thanks for this PR. It adds pp>1 support for Kimi-K3, including DSpark speculative decoding under PP, the aux hidden-state relay, and KDA state and checkpoint handling. The approach looks right and the main fixes look sound.

  • CI is green: black, ruff, and all 20 accuracy jobs.
  • Unit tests pass locally at 501364cf4: 285 passed, 60 skipped.

Before merge, I'd like to see four things addressed:

  • one latent bug,
  • one assert path that needs confirmation,
  • test coverage,
  • the commit history and the PR description.

The performance items at the end are fine as follow-ups.

1. Correctness

1.1 [Must fix] PPAuxRelay mishandles the embedding tap (-1)

# The embedding tap (-1) is resident on the first stage.
if -1 in spec.layer_ids and pp.is_first_rank:
resident.insert(0, -1)

for lid in self.own_ids:
module = layers[lid]
hook = self._make_hook(
self.buffers[lid], self.spec.extract, is_draft_forward
)

  • What's wrong:
    • On the first stage, -1 is added to own_ids. arm() then hooks layers[-1], which is the last decoder layer and a PPMissingLayer on that stage.
    • It also uses spec.extract, whereas the base drafter resolves -1 with _resolve_embedding and _identity_extract.
    • The hook never fires, so downstream stages receive an all-zero buffer and nothing reports an error.
  • Impact: K3's taps are [2, 23, 47, 71, 89], so this doesn't trigger today. But the relay is the generic DSpark path.
  • Ask: Please mirror the base drafter's handling of -1, or reject -1 explicitly under PP.

1.2 [Please confirm] A fork landing on a spec-verify decode step hits an assert

assert not (with_spec and any(s >= 0 for s in fork_srcs)), (
"state fork on the spec-decode path: spec_state_indices_tensor has "
"no read-side counterpart (BlockManager only forks onto prefill)"
)

  • Concern:
    • 838ac51 notes that under PP the checkpoint can land after the prompt, so a fork can reach decode. It only handles the non-spec KDA decode branch.
    • With DSpark enabled, the first decode after the prompt is a spec-verify step. If that step carries a FORK checkpoint, this assert fires.
  • Ask: Can this ordering happen under PP + DSpark + prefix caching? If it can, the spec branch needs handling. If it can't, please add a comment or a test that explains why.

1.3 [Minor] The PP path drops the producer's num_spec_step == 0 guard

  • The description says the P/D producer keeps this assert, but the code only checks it on the non-PP path (model_runner.py L558-L567).
  • It's safe today, because the producer's scheduler sets use_spec = has_spec and not is_pd_producer.
  • Please either keep the assert for producers on the PP path or adjust the description.

2. Tests

2.1 [Must fix] The new test doesn't run in CI

tests/test_verify_context_shift.py imports model_runner, which needs AITER, so pytest.importorskip skips it on the non-GPU runner. Consider moving verify_context_shift into a pure helper (e.g. in token_layout/decode.py) so it can be tested on CPU.

2.2 [Suggested] The new logic has no unit coverage

The existing is_deferred_out=False cases are all non-spec. CPU tests worth adding:

  • Placeholder algebra for undeferred spec output: rejected counts from 0 to k, steps with no drafts, and the max_tokens boundary.
  • The _pp_checkpoint_hold lifecycle: a held sequence is skipped by both scan loops and is released on retire, preempt and abort.
  • The PPAuxRelay split into incoming, own and outgoing taps for pp=2/4/8, using fake layers and including -1.
  • has_state_fork on the decode path.

2.3 [Suggested] Add a PP accuracy CI case

Every fix here is PP-only, but all 20 accuracy jobs run TP8 or TP4. A Kimi-K3 pp2tp4 GSM8K job with DSpark and prefix caching would cover it.

3. Commit history and description

3.1 [Suggested] Bisectability

  • 43f5aa7 removes the NotImplementedError and enables spec decode under PP, but the following 10 commits are needed for correct output.
  • So any intermediate commit silently produces garbage with PP + DSpark. The commit messages themselves say "decodes noise" and "still broken".
  • Please move the enabling change to the end of the series, or squash the chain into a few commits.

3.2 [Suggested] Stale references in commit messages

Commit Stale reference
43f5aa7, d7fdb2e, 21034f8 k3_pp2tp4/K3_PP2TP4.md (not in the repo)
4547d54 9ba0d6a6 (pre-rebase SHA of 54b724f)
6fc1fde 0ac00173 (pre-rebase SHA of 21034f8)
924147e 3d7ff8a5 (not found)

Please also:

  • drop the debugging narrative ("not sufficient", "still broken", "decodes noise") from 43f5aa7 and d7fdb2e;
  • fold "Trim the comments this PR adds" back into the commits it touches.

3.3 [Suggested] Update the PR description

  • The GSM8K table predates the rebases, and its last three rows predate the verify-window fixes. Please re-run at the current head.
  • ROCm/aiter#5783 merged on 2026-09-30, so the dependency note is out of date.
  • There is no performance data. A pp2tp4 vs tp8 comparison of throughput and TPOT would help reviewers judge where PP decode is worth using.

4. Performance (fine as follow-ups)

4.1 The checkpoint hold is the largest cost.

  • In our Kimi-K3 PP8 prefill runs, _pp_checkpoint_hold accounts for about 3.3 s of a roughly 7 s fixed prefill latency.
  • In #2709, which is stacked on this PR, b81cf24 and 7d55402 file a PAGE-backed checkpoint as soon as its chunk is scheduled, and keep the hold only for fork-class requests.
  • Either note this as a known cost that #2709 addresses, or pull those two commits into this PR.

4.2 PP decode still requires --enforce-eager, so it has no CUDA graphs. This decides whether PP decode is practical. Please list it as follow-up work in the description.

4.3 Aux relay traffic grows with PP depth.

  • Current cost: At PP8, the stage 6 → 7 boundary carries 4 taps. At hidden size 7168 in bf16, that is about 470 MB per 8192-token chunk. The torch.cat in pack plus the clone before the send copy the payload twice.
  • Alternative: context_proj (weight [7168, 35840]) is linear over the concatenated taps. Each stage could apply its own weight slice and relay a single running sum in fp32.
  • Effect: Each boundary carries one fp32 column regardless of PP depth.
  • Trade-offs: Each stage stores its own weight slice (about 103 MB per tap), and the summation order changes.

4.4 Small items:

  • The per-row Python loop that stages draft columns in prepare_input_ids (model_runner.py L578) can be vectorized.
  • _build_output does two .cpu() syncs (model_runner.py L3438-L3439); stacking the tensors makes it one.
  • The KDA fork state copy (kimi_k3/model.py L1352) copies every decode row; it could copy only the fork rows. Forks are rare, so the gain is small.

5. Checked, no issues

  • The relay's async send is safe: async_send_intermediate_tensors clones before isend, so there is no race.
  • Enabling spec decode under PP doesn't affect P/D producers: see 1.3.
  • K3 tap split at PP8 (12×7+9) is correct: each stage receives and forwards the expected taps.
  • The MLA head padding in e39bcb6 (24 → 32 at TP4) is not a new cost: it is aiter's dispatch constraint, and TP8 already pads 12 → 16.

@ganyi1996ppo

ganyi1996ppo commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

@wanzhenchn Thanks for the review. All points are addressed, except the performance items you marked as follow-ups. The PR is now one commit, d064068f5, on main (rebased after #2576 merged), so the SHAs in your review no longer resolve. The pre-squash history is kept locally (tip 513b13346); I can push it if you want to diff against it.

1. Correctness

1.1 PPAuxRelay and the -1 tap: fixed. arm() now resolves (module, extract) the same way Drafter.arm_aux_capture does: -1 maps to _resolve_embedding + _identity_extract, and any other id to layers[lid] + spec.extract. -1 also stays on the post-hook when the spec captures inputs, as on the base path. tests/test_pp_aux_relay.py covers it. Two of its tests fail with the fix reverted: the embedding is not hooked, and the buffer stays zero.

1.2 A fork on a spec-verify decode: it can happen, and it is now handled. Under PP + DSpark + prefix caching, three things combine:

  • a decode batch carries forks (_consume_state_forks also runs on the decode return path);
  • with spec decode on, every decode takes the spec branch, and nothing exempts the first one;
  • the checkpoint is filed after the prompt, so the fork lands on that first decode.

The fix uses the same approach as the plain decode path:

  • prepare_state_indices publishes the read slot on the spec path as well. prepare_gdn_metadata ships it to the device only when the step carries a fork; every other spec step still ships None.
  • K3's KDA spec branch copies conv state and recurrent state from that slot onto spec_state_indices[:, 0] before the kernels run. Neither spec kernel takes a read-side index.
  • The assert stays for every other GDN backend. Only a builder that sets GDNStateMixin.spec_honours_state_fork may take a fork on the spec path, and only K3's does, so Qwen and GLM keep the guard.
  • K3's mono plan refuses a forked step, because a mono step replaces the branch that does the copy. Mono already refuses PP > 1, so this guard only matters if pp=1 ever puts a fork on a decode.

tests/test_gdn_state_fork.py covers both paths and the refusal. Three of its tests fail against the old prepare_state_indices.

1.3 Producer guard on the PP path: fixed in code. num_spec_step == 0 is now asserted for every P/D producer, and for every non-PP engine as before. The description no longer claims more than the code does.

2. Tests

2.1 The new test now runs in CI. verify_context_shift is a pure function in token_layout/decode.py. tokenIDProcessor.verify_context_shift() forwards to it, so the four backend call sites are unchanged. The test passes with aiter blocked from import.

2.2 Added:

  • Undeferred placeholder run (test_scheduler_mtp_max_tokens.py): exactly one run of mtp_k + 1 placeholders after every reject count from 0 to k, across mixed steps, from a prefill step that scores no drafts, and at a max_tokens cap crossed by an all-accepted step.
  • _pp_checkpoint_hold (test_pp.py): a held sequence is skipped by both prefill scans and is released when its batch files its hashes. It is also released, through either filer, when the sequence left running first (preempted, aborted or retired).
  • PPAuxRelay tap split at pp=2/4/8, including -1, plus has_state_fork on decode (see 1.1 and 1.2).

2.3 Added the CI case Kimi-K3 DSpark PP2: pp2 × tp4, DSpark, prefix caching and ptpc_fp8, with test_level: pr. If maintainers want to spare every PR an 8-GPU eager job, it can move to main.

3. History and description

3.1, 3.2 Squashed into a single commit. This removes the bisect hazard, the stale references, the debugging narrative and the separate "Trim the comments" commit in one step.

3.3 Description updated:

  • GSM8K re-run on nightly_202610251529 with this same commit applied on main 713eb0614 (70dc6d37c), full 1319 questions with the CI case's flags: 0.9553 / 0.9560, DSpark acceptance 53.4%. main's TP8 Kimi-K3 DSpark CI case scored 0.9568 and 0.9545 this week.
  • aiter#5783 is now noted as merged.
  • Performance comparison (pp2 × tp4 vs tp8): not added; that is a follow-up together with 4.x.

4. Performance

Left as follow-ups and listed under "Known limits" in the description: the hold's prefill cost and #2709's fix for it (4.1), --enforce-eager (4.2), and relay traffic (4.3). 4.4 is untouched.

@ganyi1996ppo
ganyi1996ppo force-pushed the guanbao/k3-pp2tp4 branch 3 times, most recently from 65ebed6 to d102bb5 Compare October 27, 2026 03:32
Base automatically changed from Jasen/atomesh-agentx-harness to main October 27, 2026 06:48
wanzhenchn
wanzhenchn previously approved these changes Oct 28, 2026

@valar-cop valar-cop left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a few small things..

ci Atomesh Accuracy Validation failed — ci script/config bug, not the pr (high): The workflow triggers on .github/benchmark/models_accuracy.json (touched by this PR), but on a pull_request its whitelist selects zero models, so models_json is [] and the atomesh-test matrix include expands to nothing; unlike atom-test.yaml there is no placeholder fallback and GitHub fails an empty matrix. Kimi-K3 is not in atomesh_levels.

include: ${{ fromJson(needs.load-test-models.outputs.models_json) }} # atomesh-accuracy-validation.yaml:138, no fallback
include: ${{ fromJson(needs.load-test-models.outputs.models_json || '[{"runner":"ubuntu-latest","model_name":"skip"}]') }} # atom-test.yaml:265
atomesh_levels = {"DeepSeek-R1-0528": "main", "gpt-oss-120b": "nightly",}
next: Not a PR defect: add atom-test's placeholder-entry fallback to the atomesh matrix (or skip the workflow on catalog-only PRs), then rerun; no job log was attached. https://github.com/ROCm/ATOM/actions/runs/41762102572

Comment thread atom/spec_decode/drafter.py Outdated
def relayed(lid):
# On an earlier PP stage: `take_relayed_aux` serves it instead.
# Out-of-range ids are left for install_aux_taps to reject.
if lid == -1:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

relayed() uses its own residency rule while pp_aux_relay uses is_first_rank, can we share one helper?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in b62bf7a: drafter.tap_is_resident is the one rule now (is the tap's module a PPMissingLayer, the embedding included), and both arm_aux_capture and PPAuxRelay use it.

Both now ask whether the tap's module is a PPMissingLayer, the embedding
included, instead of the relay keying the -1 tap on is_first_rank.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
wanzhenchn added a commit that referenced this pull request Nov 4, 2026
Brings #2365's review commit a1436cd, which allocates the drafter's aux
buffers only for this stage's own taps.

No conflicts.

Co-authored-by: Cursor <cursoragent@cursor.com>
wanzhenchn added a commit that referenced this pull request Nov 4, 2026
Brings #2365's review commit b62bf7a, which shares one tap residency
rule between the drafter and the PP aux relay.

No conflicts.

Co-authored-by: Cursor <cursoragent@cursor.com>

@valar-cop valar-cop left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

ci Atomesh Accuracy Validation failed — ci script/config bug, not the pr (high): The workflow triggers on .github/benchmark/models_accuracy.json, which this PR touches, but on a pull_request its whitelist selects zero models, so models_json is [] and the atomesh matrix include expands to nothing. Unlike atom-test.yaml this workflow has no placeholder fallback, and GitHub fails a job whose matrix resolves empty. Kimi-K3 is not in atomesh_levels, so no atomesh job can exist for this change.

    include: ${{ fromJson(needs.load-test-models.outputs.models_json) }}   # atomesh-accuracy-validation.yaml:138, no fallback
    include: ${{ fromJson(needs.load-test-models.outputs.models_json || '[{"runner":"ubuntu-latest","model_name":"skip"}]') }}   # atom-test.yaml:265, with fallback
      filtered = [m for m in models if atomesh_levels.get(m["model_name"], "skip") in allowed]   # atomesh_levels has no Kimi-K3 entry

next: Not a PR defect: add atom-test's placeholder-entry fallback to the atomesh matrix (or skip the workflow on catalog-only PRs), then rerun. No job log was attached to this run. https://github.com/ROCm/ATOM/actions/runs/41766392543

@gbyu-amd

gbyu-amd commented Nov 4, 2026

Copy link
Copy Markdown
Contributor Author

@valarLip would you review again at the latest head?

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
wanzhenchn added a commit that referenced this pull request Nov 5, 2026
Brings #2365's merge of main ab4ada4, which adds Kimi-K3.1 day-0
support (#3003) and resolves its overlap with #2365 in
models_accuracy.json and kimi_k3_dspark.py.

No conflicts.

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
wanzhenchn added a commit that referenced this pull request Nov 5, 2026
Brings #2365's merge of main 058c219, which adds Chat token logprobs
(#2856) and resolves its overlap with #2365 in
tests/test_scheduler_mtp_max_tokens.py.

No conflicts.

Co-authored-by: Cursor <cursoragent@cursor.com>
@valarLip
valarLip merged commit 64412f1 into main Nov 5, 2026
37 checks passed
@valarLip
valarLip deleted the guanbao/k3-pp2tp4 branch November 5, 2026 12:06
wanzhenchn added a commit that referenced this pull request Nov 5, 2026
#2365 has landed on main as the squash 64412f1, so this PR now targets
main. 64412f1's tree is #2365's head 9fb45ff, which this branch
already merges, merged with main d8525dd (#2913). The only new change
this merge brings is #2913.

scheduler.py and tests/test_pp.py conflicted only because the squash
repeats #2365's changes that this branch edits further. They keep this
branch's versions: the result is this branch merged with d8525dd,
which merges cleanly, and its diff against main is this PR's diff
against 9fb45ff unchanged.

Co-authored-by: Cursor <cursoragent@cursor.com>
rbrugaro-amd added a commit to rbrugaro-amd/ATOM that referenced this pull request Nov 5, 2026
main's ROCm#2365 brings PP > 1 to Kimi-K3, so 'such an engine never verifies'
no longer holds. The refusal stands: PP rings forward_vars per in-flight
step while bind_target binds the draft to one slot's filters. Message,
comment and guide row now say that.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
rbrugaro-amd added a commit to rbrugaro-amd/ATOM that referenced this pull request Nov 5, 2026
…tn now reads

main's ROCm#2365 has GDNStateMixin._init_gdn_state read
model_runner.num_spec_tokens (every PP stage runs the verify window), but
this GPU-only test's ModelRunner stub never set it, so all 19 cases fail
with AttributeError before reaching what they test. The stub sets it to
num_spec, the value its drafter stub already carries as mtp_k.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Jasen2201 added a commit that referenced this pull request Nov 6, 2026
A P/D producer never drafts, but it must write the draft's context K/V for
the prompt, which the decode drafts from. Under PP4 the drafter is built on
the last stage only:

- Its taps (layers 3/17/31/45/59) mostly run on earlier stages; main's
  `PPAuxRelay` (#2365) carries DSpark's to the last stage. A PP drafter that
  taps target layers without a config-only aux spec (EAGLE3) is refused at
  startup: nothing relays its taps, and a producer would write wrong
  context K/V.
- A block drafter warms up on at least two sequences. That floor is read off
  the config, not the stage's drafter, so every stage warms up on the rows
  the last stage does.
- The draft pool's units say how they split by KV head (DSpark's binding
  reads and writes page-16: eight runs a block), so a TP1 stage's draft K/V
  is cut per decode rank like the target's. `merge_pages` numbers regions
  appended after a mapped contributor from the consumer's end, where the
  consumer appends them too; the producer resolves them against the
  consumer's region count at handshake.
- Any P/D producer whose drafter writes its context K/V apart from propose
  (DSpark: M3.1, K3, V4.1) skips `propose()` and its draft graph warmup, on
  the deferred output path as on the direct one, PP or not, and leaves the
  DP draft lockstep (every rank skips alike). Its drafts were discarded
  anyway (`mtp_k` 0). EAGLE still proposes, since it leaves a final chunk's
  context K/V to propose's first step.
- So the M3.1 draft may run below TP4 on a producer. Its one-KV-head check
  runs at the top of the block pass, before anything is written. The last
  stage's embedding is a placeholder, so the vocab check reads the LM head.
- Adds a recipe section with the prefill at `--max-num-batched-tokens 8192`:
  the sparse prefill launches one `pa_fwd` row per (token, KV head), HIP caps
  a launch at 65535 rows, and a TP1 stage holds all four KV heads.

Co-authored-by: Mengqing Cao <cmq0113@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PerryZhang01 pushed a commit that referenced this pull request Nov 10, 2026
ATOM's shared Kimi-K3 KDA forward now also reads
spec_state_indices_in_tensor on a verify step (#2365, pp>1 checkpoint
forks). vLLM's KimiK3KDAMetadata has no such field, so any plugin model
on that forward with speculative decoding (GLM-5.3-Flash MTP, Kimi-K3
DSpark) raised AttributeError during CUDA graph capture. Default it to
None, as the non-speculative index already is: vLLM has no state fork,
and None keeps the read on the write slot.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PerryZhang01 pushed a commit that referenced this pull request Nov 11, 2026
#2365 made the shared K3 KDA forward read
`kda_metadata.spec_state_indices_in_tensor` on its speculative-decode
branch. vLLM's KimiK3KDAMetadata has no such field, and the plugin only
defaulted its non-spec twin, so K3 on the plugin dies in profiling under
DSpark (AttributeError on the first verify-shaped forward). None keeps the
forward on the write slot, as the non-spec default does.

The same hunk as #3137's, so the two apply cleanly in either order. The
K3 TP8 LMCache MP runs on this PR ran with it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
valarLip pushed a commit that referenced this pull request Nov 11, 2026
…#3137)

* vllm-atom: serve GLM-5.3-Flash on the plugin backend

Register Glm5NextForConditionalGeneration with the vLLM plugin, so
GLM-5.3-Flash runs ATOM's model and kernels under vLLM 0.31 -- including the
pooled (kpool) indexer -- instead of falling through to vLLM's own Glm5Next.

It is the first plugin model with three kinds of KV state at once: 11 MLA
layers, 34 KDA recurrent states and 11 DSA indexer caches.

* KDA: reuse Kimi-K3's vLLM adapter (Glm5NextKDAAttentionVllm), adapter first
  in the MRO so its overrides win over GLM's native layer.
* MLA width: vLLM sizes the cache at kv_lora_rank + qk_rope_head_dim = 512,
  because the checkpoint keeps the rope dim at 0 for its NoPE indexer, while
  ATOM's MLA entry carries the 64-wide zero rope pad. Patch get_head_size to
  576 for glm5_next_text only (glm5_next_mla_head_size.py). vLLM's own config
  classes are used as is; they carry every checkpoint field ATOM reads.
* Indexer row width 132 -> 144 (aligned_index_cache_row), so the indexer page
  divides the MLA page and vLLM scales its block instead of padding it.
* kpool: the indexer spec declares tokens_per_state = index_kpool. The sparse
  backends keep their 64-token kernel block, so a block holds 16 pooled rows,
  the preshuffled layout the decode scorer computes correctly in (checked in
  the indexer builder). The pooled op (glm5_kpool_indexer.py) drives the
  native kpool kernels from vLLM's metadata. Its per-slot tail buffer is
  allocated when the KDA state is bound, before warmup and graph capture: a
  buffer first created inside a capture has its zero-fill replayed every step.
* The pure-decode sparse-MLA metadata cache keys on the pooled sparse length
  (pools * kpool + seq_len % kpool). min(seq_len, topk) stops changing past
  topk while the open tail keeps moving, so a hit would replay a stale split.
* PCP, DCP, speculative decoding, prefix caching and KV transfer are refused.

GSM8K chat 16-shot (every prompt past index_topk, so pooled selection decides),
TP4, bf16 KV, MI355X, vLLM 0.31.1.dev0+gdb9527a46, aiter 47b4e0869,
flexible-extract: 0.9697 and 0.9666 with the plugin CI serve flags, 0.9704
with vLLM's defaults; native ATOM 0.9666.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* ci(vllm-plugin): add a GLM-5.3-Flash kpool cell to the PR accuracy matrix

TP4, chat 16-shot gsm8k so the pooled indexer is what selects, bf16 KV.
Gate 0.94, the same as the native GLM-5.3-Flash-kpool-16shot entry; this
exact serve command measured 0.9697 and 0.9666 flexible-extract. The
evaluation takes about four minutes once the server is up.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* ci(vllm-plugin): drop the GLM-5.2-MXFP4-MTP cell from the PR accuracy matrix

The plugin PR matrix keeps six cells, with GLM-5.3-Flash added. GLM-5.2-MXFP4
with MTP stays covered by the nightly vLLM accuracy run
(oot_models_accuracy.json, "GLM-5.2-MXFP4 MTP TP4").

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* vllm-atom: keep the GLM-5.3-Flash plugin changes out of the native model

Move the indexer hooks added to atom/models/glm5_next.py into a plugin
subclass, Glm5NextIndexerVllm, swapped in while the model is built the same
way as the KDA layer, and declare the indexer row width with native
aiter_mla.aligned_index_cache_dim instead of a new geometry helper.
atom/models and atom/model_ops are now unchanged by this PR.

GSM8K chat 16-shot through the plugin CI harness on this tree: 0.9651
flexible-extract (gate 0.94); earlier runs of the same computation 0.9697,
0.9666, 0.9727.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* vllm-atom: GLM-5.3-Flash plugin review fixes

* Leave the shared sparse-MLA builders reading index_topk and
  num_hidden_layers from hf_config, as on main: vLLM 0.31's Glm5NextConfig
  carries both at the top level, so GLM-5.3-Flash needs no change there.
* Drop the indexer builder's ChunkK=256 block-size check. It assumed the
  kernel block equals the manager block, which stopped holding when the
  sparse backends kept their 64-token kernel block; the builders get the
  kernel block, so it could only reject a working configuration.
* Clear the pooled tail buffer in place when the KDA state is re-bound with
  the same shape, so CUDA graphs captured against it keep a valid pointer.
* Refuse, instead of silently re-viewing, a pooled index cache whose row count
  disagrees with the builder's rows_per_block.
* Fix the dense indexer path (ATOM_GLM5_KPOOL=0): the shared indexer's
  nope-cache fusion hands the op a BF16 query, which the plugin's dense op
  cannot take (fp8_mqa_logits fails to compile). Glm5NextIndexerVllm turns
  the fusion off, so the query is quantized first, as for GLM-5.2.
* List GLM-5.3-Flash in the vLLM plugin backend guide.

On this tree, TP4, MI355X: the plugin CI harness gives 0.9727 GSM8K chat
16-shot (gate 0.94); the dense path gives 0.9666 3-shot at max-model-len 2048
(native ATOM 0.9689). Kpool geometry checked under ATOM_GLM5_KPOOL_CHECK over a
full 16-shot run: 16 rows per block, state slots and block ids in range.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* vllm-atom: test GLM-5.3-Flash prefill chunks shorter than index_kpool

* Unit tests for the pooled indexer's prefill-chunk metadata: whole prompts
  shorter than one pool, a prompt of exactly one pool, chunks that resume an
  earlier chunk and end mid-pool, and a chunk covering a later request range.
  Only complete pools are counted, over each request's context so far.
* Under ATOM_GLM5_KPOOL_CHECK=1, log per prefill chunk how many requests add
  fewer rows than one pool and how many resume mid-pool, so a run can show
  those cases were exercised.

TP4, MI355X, --max-num-batched-tokens 1027 so chunk boundaries fall
mid-pool: GSM8K chat 16-shot 0.9682. During the run, mid-pool resumes were
logged in about 1,700 scheduler steps and sub-pool chunks in 2 (one of them
also mid-pool), with no check failing. Prompts of 1 to 7 tokens continue
coherently.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* vllm-atom: expand the kpool tail for prefill rows shorter than a pool

A prefill chunk whose requests have not closed a single pool yet
(total_pools == 0, e.g. a 1 to 3 token prompt with index_kpool=4) skipped
expand_pools_and_append_tail along with the scoring, so every row kept
topk = -1 and attended slot 0 instead of its own tokens. The chunk now
builds an all -1 pool_topk and still appends the tail, so each row
attends positions 0..pos.

Review follow-ups on the same op and its metadata:
- Size scoring row chunks with the shared sparse_indexer_row_chunk
  helper instead of a copy that dropped its 2 GiB bound.
- Drop the ATOM_GLM5_KPOOL_CHECK debug block.
- Drop the pooled slot_mapping recompute in the indexer metadata
  builder; the pooled op computes its own write slots.
- Return instead of raising when vLLM has no Glm5NextTextConfig or no
  get_head_size hook; the head-size patch runs for every model.
- Restate why _check_unsupported exists: plugin mode builds an ATOM
  Config, but vLLM's PCP and DCP sizes are never forwarded into it.

Adds a GPU test that runs the prefill chunk op with total_pools == 0
and checks each row attends exactly its own positions.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* ci: run the GLM-5.3-Flash plugin cell on fp8 KV

Drop the cell's bfloat16 override so it runs the harness's fp8 KV cache:
GSM8K chat 16-shot scores 0.9727 / 0.9682 over two runs of the cell's
serve command. Correct the prompt-length note: the 16-shot prompts run
1939-3591 tokens, all but one past index_topk=2048.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* vllm-atom: keep GLM-5.3-Flash's open kpool in a vLLM-managed ring

The pooled indexer kept each request's open pool (the tail) in a side
buffer keyed by the KDA state slot. That ties the tail to a slot an MTP
draft layer does not have (the draft layer is MLA plus an indexer, no
KDA), and that vLLM moves under prefix caching.

Declare the tail as vLLM's own GLM-5.3-Flash tail cache instead
(KpoolTailSpec: one block per request for its lifetime, never
prefix-cached), with native ATOM's ring depth so native's verification
kernels stay correct: one pool without speculation,
speculative_kpool_history_size with it (16 rows for MTP3, where vLLM
sizes 8). vLLM aliases each ring into the indexer page, so the block
stride is padded; the op gathers the batch's rings into a contiguous
buffer for the native kernels and scatters them back.

The heterogeneous MLA/MHA EAGLE3 KV pool check treated the ring spec as
an MHA draft layer; skip it there.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* vllm-atom: GLM-5.3-Flash MTP speculative decoding

Register the checkpoint's MTP layer (Glm5NextMTPModel) as ATOM's
Glm5NextMTP with the plugin indexer swapped in during construction,
under the DeepSeek-MTP draft contract (position-0 embedding mask,
spec_step_idx, (sample, recycle) hidden). vLLM passes the draft the
target's positions, which is what native's MTP indexer derives with
positions - 1, so the plugin indexer takes them as-is.

The pooled op verifies drafts with native ATOM's kernels:
build_speculative_pool_candidates forms the pool ending at each row from
the step's rows and the ring, update_speculative_kpool_history writes the
rows into the ring, and scoring reuses the per-row decode path the
builder already flattens a verification into. A test checks that a
four-row verification leaves the pooled keys and the ring exactly as
four single-row decodes do.

The KDA state shape takes vLLM's num_speculative_tokens, as Kimi-K3's
does. Speculative methods other than mtp are refused. Prefix caching
stays refused on the pooled path here; the next commit lifts it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* vllm-atom: serve GLM-5.3-Flash with prefix caching

The refusal blamed align-mode KDA state migration. The real cause is
aiter's bf16 MLA decode misreading KV past 4 GiB inside a layer
(#3120): prefix caching only made vLLM hand out higher block
ids. With fp8 KV, prefix caching scores 0.9727 then 0.9682 (cache hits
on the second pass) on GSM8K 16-shot, so drop the refusal.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* vllm-atom: default the KDA verify-row state read index to the write slot

ATOM's shared Kimi-K3 KDA forward now also reads
spec_state_indices_in_tensor on a verify step (#2365, pp>1 checkpoint
forks). vLLM's KimiK3KDAMetadata has no such field, so any plugin model
on that forward with speculative decoding (GLM-5.3-Flash MTP, Kimi-K3
DSpark) raised AttributeError during CUDA graph capture. Default it to
None, as the non-speculative index already is: vLLM has no state fork,
and None keeps the read on the write slot.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* ci: gate GLM-5.3-Flash prefix caching and MTP in the plugin PR CI

Add a TP4 cell serving GLM-5.3-Flash on fp8 KV with prefix caching and
MTP3. lm_eval draws each question's exemplars separately, so nothing
hits within one pass, and with MTP3 a prompt hits only past two
2048-token blocks: the client runs GSM8K 30-shot (4167-6039 token
prompts) twice on one server, gates the second pass, and fails if the
server counted no prefix-cache hit. Two runs through atom_oot_test.sh:
0.9659 / 0.9682, MTP acceptance 0.827 / 0.828, every second-pass
request hitting one cached block.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* ci: keep only the GLM-5.3-Flash MTP prefix-cache cell in the plugin PR CI

The plain GLM-5.3-Flash cell runs a subset of what the MTP prefix-cache
cell runs, so drop it and gate the model with the one cell.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* ci: run the native GLM-5.3-Flash kpool entry on fp8 KV

aiter's bf16 MLA decode misreads KV past 4 GiB inside a layer
(#3120), which a long-running server reaches; fp8 reads
correctly. Switch the entry's KV cache to fp8.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* vllm-atom: address review on GLM-5.3-Flash prefix caching and MTP

- Register Glm5NextMTPModel's predictor (DeepSeekMultiTokenPredictor,
  which Glm5NextMTP inherits) in the MTP input-mask contract test.
- Narrow the AttentionSpec import guard to ImportError.
- The kpool indexer op returns nothing, like the dense one: its caller
  never read the float32 copy of the weights it returned.
- Restore the GLM-5.2-MXFP4-MTP cell: it is the plugin CI's only Model
  Runner V2 MTP gate.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* ci: set the native GLM-5.3-Flash kpool baseline from its fp8 runs

Two runs of the entry's exact command on fp8 KV (TP8): flexible-extract
0.9682 and 0.9697. Baseline 0.9682, threshold unchanged at 0.94.
Correct the note's prompt lengths: the 16-shot prompts run 1939-3591
tokens, all but one past index_topk=2048.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* vllm-atom: refuse MTP on GLM-5.3-Flash's dense indexer

With ATOM_GLM5_KPOOL=0 and MTP, the dense indexer's metadata builder
flattens the verify rows into a block-table buffer sized from the
indexer's 64-token kernel block (32 columns at max-model-len 2048),
while the hybrid model's block table has 128, and the engine fails at
graph capture. Refuse the combination at startup.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* vllm-atom: fit the sparse indexer's expanded block table to its buffer

MTP verify rows on the sparse MLA indexer are flattened into a
block-table buffer sized for max_model_len in kernel blocks. vLLM rounds
each request's table up to whole manager blocks, and on a hybrid model
the indexer group's manager block can be several times the attention
one: GLM-5.3-Flash's dense indexer (ATOM_GLM5_KPOOL=0) gets 8192-token
manager blocks, 128 kernel blocks per row at max_model_len 2048 against
a 32-column buffer, and the engine failed at graph capture. Copy only
the columns the buffer holds; the rest address no position below
max_model_len. This replaces the startup refusal from 07bc99e.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: perzhang <perzhang@amd.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
valarLip pushed a commit that referenced this pull request Nov 11, 2026
… object (#3117)

* Kimi-K3 plugin: ride the KDA state on the PAGE object through the LMCache MP server

Kimi-K3 on the MP backend was refused ("recurrent (mamba) state offloads
in-process only"). It now takes the path DeepSeek-V4.1's checkpoints take
(#2598): the KDA state at a boundary rides the PAGE object as a native-state
image, so KV and state commit and restore as one LMCache object.

No copy is needed. vLLM lays every KV cache group out layer-outer from byte 0
of one allocation, so mamba group layer j aliases MLA plane j. On K3 the three
mamba groups of 23 KDA layers sit on the first 23 of the 29 MLA planes, and the
state a group holds in block b is PAGE unit b of those planes. A boundary's
image is one unit per mamba group (3 x 23 x 884,736 B).

- kda_ride.py: KdaRidePlanner (caps hits to the chunk grid, names the load
  destination row (n - 1) // block in every mamba group or none),
  kda_ride_geometry (the checkpoint spec both halves derive from vLLM's
  KV cache config), publish_kda_ride_state (checks on the registered tensors
  that every KDA layer starts at, and strides with, one of the leading PAGE
  planes before declaring the image), and the startup refusals (align mode,
  prefix caching, kv_load_failure_policy=recompute).
- V41StateMPScheduler had nothing V4.1-specific; renamed StateRideMPScheduler
  (ride_mp_scheduler.py) and shared by both models. _v41_request_of is now
  _request_of.
- The KDA state is per rank (TP-sharded heads); the in-process K3 leg is
  unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Kimi-K3 plugin: size the KDA image by attention layer, and ride only K3

Review of #3117:

- The image covered the leading N PAGE planes with N = mamba layers per
  group, which counts layers, not planes. An attention layer with a DSA
  index cache publishes two planes, so the count fell short. The spec now
  sums each leading attention layer's movable bytes from vLLM's specs (an
  index cache folded into the layer that owns it, as build_kv_cache_tensors
  publishes it), and the worker counts the region planes off the published
  regions' layer names before it checks the aliasing.
- Only Kimi-K3 rides on MP. Its KDA layers report GDN_ATTN, as Qwen's GDN
  layers do, so the gate is the kimi_k3 offload layout; any other mamba model
  on MP is refused as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Kimi-K3 plugin: put the layers the KDA state aliases first in the PAGE order

The first K3 TP8 boot over mp refused at registration: mamba group 0
layer 18 started on PAGE plane 23, outside the image's leading 23 planes.
ATOM publishes PAGE planes by layer name, while vLLM aliases mamba layer j
onto the j-th attention layer it allocates, and with DSpark the two orders
differ (29 attention layers, five sort ahead of the first aliased one).

kda_ride_page_order moves the attention layers the mamba layers alias to the
front, in mamba-layer order, matched on addresses, and the scheduler sizes
the image from vLLM's allocation order. The end-to-end test gains a layer
that sorts first without being aliased; without the reorder it raises the
same refusal the boot did.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Kimi-K3 plugin: default spec_state_indices_in_tensor so DSpark starts

#2365 made the shared K3 KDA forward read
`kda_metadata.spec_state_indices_in_tensor` on its speculative-decode
branch. vLLM's KimiK3KDAMetadata has no such field, and the plugin only
defaulted its non-spec twin, so K3 on the plugin dies in profiling under
DSpark (AttributeError on the first verify-shaped forward). None keeps the
forward on the write slot, as the non-spec default does.

The same hunk as #3137's, so the two apply cleanly in either order. The
K3 TP8 LMCache MP runs on this PR ran with it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: perzhang <perzhang@amd.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants