Skip to content

Configure MTP heads for paged Engine decoding - #2458

Closed
Tianlei Wu (tianleiwu) wants to merge 3 commits into
tlwu/20260823/gdn-paged-spec-apifrom
tlwu/20260823/gdn-paged-mtp-config
Closed

Configure MTP heads for paged Engine decoding#2458
Tianlei Wu (tianleiwu) wants to merge 3 commits into
tlwu/20260823/gdn-paged-spec-apifrom
tlwu/20260823/gdn-paged-mtp-config

Conversation

@tianleiwu

Copy link
Copy Markdown
Contributor

Summary

  • make the MTP builder follow the main model packed-token layout
  • bind optional hidden-state output from the paged decoder
  • project model.mtp into an internal paged decoder configuration
  • validate the projected MTP configuration

Stack

PR 5/6. Depends on tlwu/20260823/gdn-paged-spec-api.

Validation

  • final stacked branch: engine_unit_tests 322/322 passed
  • MTP config projection and paged hidden-state binding tests are included

The head inherits every build option from the main model, so exporting a paged
model with enable_mtp already built a paged head -- except that its hidden-state
values were still declared as dense [batch, sequence, hidden]. That shape
contradicts the packed [num_tokens, hidden] rows the rest of a paged graph
carries, so the head could not be fed from a paged main model.

Declare them with hidden_state_shape(), which is the helper every other hidden
state in the builder already uses. A dense export is unchanged (the helper
returns the same three dimensions); a paged export now produces an mtp.onnx
whose contract matches text.onnx exactly, plus hidden_states in and
hidden_states_out out. That lets the paged Engine drive the head with the same
varlen decoder and cache machinery as the main model, instead of needing a
separate dense KV cache.
A model exported with include_hidden_states emits a [num_tokens, hidden_size]
row per packed token alongside its logits. The Engine never bound it, so the
paged path had no way to feed an MTP draft head, which consumes exactly that
row for the last accepted token.

Bind it whenever the config names it and the session actually has it, so every
model exported without it runs a byte-identical step. The rows are sized by the
packed token count rather than by the logits row count, because a pruned LM head
emits only one logits row per request while the hidden states stay per token;
PrepareLogits and PrepareHiddenStates now share a TokenCount helper so the two
cannot drift apart. VarlenGraphBuffers gains a matching static buffer so a
captured step keeps a stable output address.

315 engine unit tests pass, and the tiny paged fixture (now exported with
include_hidden_states) still reports exact ragged-batch parity.
The paged Engine should host mtp.onnx directly rather than requiring callers to
rewrite and load a second genai_config.json. CreateMtpDecoderConfig copies the
main config so device, batching and packed-attention input names remain aligned,
then replaces the filename/session options, dimensions, I/O names and shared
initializers from model.mtp.

The head is one full-attention layer, so the projection explicitly drops the
main hybrid model's fixed state groups, convolution metadata and sliding-window
state. A focused test verifies both the inherited paged contract and the removed
main-model state; all six MtpConfigTest cases pass.
@tianleiwu

Copy link
Copy Markdown
Contributor Author

Stack: #2454#2455#2456#24575/6#2459

@tianleiwu

Copy link
Copy Markdown
Contributor Author

Superseded by #2494. The replacement is runtime-only, targets the reconstructed speculative API stack, and excludes the builder/export change covered by #2488.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant