Skip to content

Integrate upstream Flash-Next MTP support - #8

Merged
novkien merged 15 commits into
masterfrom
integrate/flash-next-mtp-20260930
Sep 30, 2026
Merged

novkien merged 15 commits into
masterfrom
integrate/flash-next-mtp-20260930

Conversation

@novkien

@novkien novkien commented Sep 30, 2026

Copy link
Copy Markdown
Owner

Addresses novkien/llama-proxy#470

Integrates ggml-org#28243 at 6fcaa16f4b360649933a54d1f91ad40ed35c0e11, explicitly requested by the owner in Codex on 2026-09-30. Fork base: 345b1c5f26f21b0aed458d846aaedb48565d8490. Integration head: 8c8edf8f6ceb76b41049948f269d33faa5687769.

The true merge is conflict-free and changes exactly the upstream PR's 18 paths (408 additions, 77 deletions). It adds Flash-Next NextN/MTP graph, detached/shared draft tensor loading and conversion support. Generic MTP initialization now loads the actual draft path and limits shared-KV treatment to Gemma assistant. Existing RAM-cache and selective CUDA transport source is unchanged; both immutable input SHAs are ancestors of the integration head. The unrelated custom llama.cpp checkout remains untouched.

Validation before source merge: full delta reviewed; staged diff whitespace check passes; all 10 changed Python files parse; no unmerged entries; owner cache/P2P paths compare byte-identically. Native/RPC rebuild and Flash/Qwen live acceptance follow source merge, as requested, and are not yet claimed complete. Qwen context/speed tuning remains provisional until tested on the rebuilt fork.

AI disclosure: Codex reviewed and integrated the existing upstream PR; no upstream submission is made.

ryanmonsurate and others added 15 commits September 3, 2026 20:04
Adds the MTP head's own hyper-connection mixer tensor names and lists the
NextN tensors under the qwen4exp architecture.
Adds --spec-type draft-mtp support for Qwen3.8-Flash-Next.

The MTP head folds the next token's embedding into the trunk's wide
hyper-connection residual, runs one trunk-style block (dense attention +
MoE) over it, and collapses the result with its own mixer before reusing
the trunk's LM head.

- read nextn_predict_layers so n_layer() excludes the MTP block
- load the trailing block through the existing trunk path: is_recr() and
  is_ple() are already false past the trunk, so it needs no special casing
- eh_proj fuses the checkpoint's fc_embedding and fc_hidden side by side,
  so one matmul computes fc_embedding@e + fc_hidden@h
- the head carries its own hyper-connection mixer, mirroring the trunk's
  hc_head_*, which stands in for the output norm qwen4exp does not have
- export the wide pre-collapse residual as t_h_nextn from both graphs, so
  the driver can feed it back for the next draft step
- route MTP contexts to a plain KV cache filtered to the trailing layer

The draft block attends densely for now: the trunk's QSA only prunes
context past a 2048-token budget, so dense is a numerical superset and
drafts are verified either way. Indexer tensors are still loaded.
The MTP block is one trunk-shaped block (dense attention + MoE wrapped in
hyper-connections) plus a head-level combiner, so once _QwenMtpMixin renames
mtp.layers.0.* to the trailing block index its tensors ride the existing
qwen4exp mappings unchanged. Two head-level pieces need handling:

- fc_embedding and fc_hidden fuse into the eh_proj the shared NextN code
  expects, since W_e@e + W_h@h == [W_e|W_h] @ concat(e, h)
- mtp.hyper_connection_mixer.* is the head's own copy of the trunk's
  hc_head_* output mixer, unindexed in the checkpoint and per-block in the
  GGUF

compress_ratios is read with length block_count, so it gains a trailing 0
for the MTP block, which attends densely.

--no-nextn drops the head; --mtp exports it on its own.
A NextN/MTP draft exported with --mtp carries the token embeddings, output
norm and lm head so it can be loaded as a standalone model. For every current
sidecar those three tensors are most of the file: ggml-org/Qwen3.8-27B-GGUF
mtp-Qwen3.8-27B-Q4_0.gguf is 1.565 GiB, of which 1.332 GiB (85%) is the copy,
against 0.223 GiB for the MTP block itself.

Add an opt-in --mtp-shared-embd that leaves them out and marks the file with
nextn_shared_target_tensors. The loader then resolves those names against the
already loaded target model. The graph side needs no change: the nextn blocks
of twelve archs already fall back to model.tok_embd and model.output.

The borrow is gated on the new key, so a sidecar published before this change
cannot reach it and keeps its current behaviour. Shapes are checked against
the target and a mismatch is refused, as is loading such a file on its own.
_QwenMtpMixin is not a ModelBase subclass, so it re-declares the
attributes it reads off cls for the type checker. filter_tensors reads
cls.mtp_shared_embd without a matching declaration, which ty reports as
unresolved-attribute.

The declaration is a bare annotation, matching no_mtp and mtp_only
above it. That creates no class attribute, so it cannot shadow
ModelBase.mtp_shared_embd even though the mixin precedes the model
class in the MRO; a default value here would have.

Assisted-by: Claude
A draft-only export declares the full block count but ships the MTP
block alone, so the trunk tensors load as null and only the MTP graph
is buildable. Context reservation builds the trunk graph, which walked
those nulls and segfaulted.

A shared-embedding draft is caught earlier by the borrow check, since
it has no token_embd of its own. A self-contained draft keeps one, so
it passed that check and reached here.

Assisted-by: Claude
Reuses the existing llama_context_params::ctx_other plumbing instead of adding a
model_shared load parameter and a nextn_shared_target_tensors metadata key.

A draft-only export now simply omits token_embd/output, and the qwen4exp MTP graph
resolves them against the target context at graph build, following dflash and
gemma4-assistant. Loading such a file on its own reports that it needs -md.

Assisted-by: Claude
A qwen4exp draft that borrows the target's embeddings sets ctx_other but keeps
its own memory, so it must be caught up and rolled back like any other draft.
Treating it as memory-shared skipped the catch-up decode and placed every draft
token at the same position, which the M-RoPE position check rejects.

Assisted-by: Claude
Merge upstream master into the qwen4exp MTP branch.

Every conflict is in src/models/qwen4exp.cpp and comes from 41abbfd,
which moved the hyper-connection gammas to {n_embd, hc} with
TENSOR_ALLOW_RESHAPE for the fused rms_norm+mul path. Each one takes
master's shape and keeps this branch's tensor flags.

layer.nextn.hc_head_norm needs the same shape but never conflicts, since
master has never carried that line. Left flat it aborts at load with
GGML_ASSERT(ggml_can_repeat(b, a)) in build_hc_mix, so it is converted
here as well. nextn.hnorm stays flat because graph_mtp reshapes before
that multiply.
Assisted-by: Claude
Integrate ggml-org/llama.cpp PR 28243 at 6fcaa16. Preserve owner cache and selective CUDA transport changes.

Addresses novkien/llama-proxy#470

Assisted-by: Codex
@novkien

novkien commented Sep 30, 2026

Copy link
Copy Markdown
Owner Author

Pre-merge handoff

Private-fork integration PR #8 implements the owner's explicit Flash-Next MTP merge/rebuild decision. Reviewed head 8c8edf8f6ceb76b41049948f269d33faa5687769, base master 345b1c5f26f21b0aed458d846aaedb48565d8490, upstream PR head 6fcaa16f4b360649933a54d1f91ad40ed35c0e11; exactly 18 upstream paths, true ancestry-preserving merge, no conflicts or owner cache/P2P changes. Python parse and whitespace/ancestry checks pass. No native/RPC rebuild or live MTP acceptance is claimed yet: these are the next delivery stages after source merge, followed by final Qwen tuning and Flash route selector activation. The GPU host's ordinary idle shutdown was confirmed; a normal authorized model load is waking it. A temporary build hold has been requested from the owner and has not been enabled without that separate authorization.

@novkien
novkien merged commit e58b770 into master Sep 30, 2026
18 of 22 checks passed
@novkien

novkien commented Sep 30, 2026

Copy link
Copy Markdown
Owner Author

Native/RPC delivery and Flash-Next acceptance

Fork PR #8 merged as e58b770f3c2346deab036d9e4c3b2ea61ca0bbc5. Native server/core and the RPC executable were rebuilt from that clean published revision. The unchanged GGML tree was verified against installed 95e6565 and its existing backend libraries reused, with native SM70 and guest SM120 kept separate. New core executables/libraries use origin-relative runtime lookup; reused GGML libraries retain their existing production paths. This is an incremental build, not a claim that all optional utilities/CUDA kernels were rebuilt.

Focused checks passed: 10 changed Python files parse, whitespace/merge ancestry checks, native grammar parser, sampling, and one Qwen4Exp CPU architecture/serialization roundtrip case. The exact existing Flash target/draft GGUF metadata and NextN tensor dimensions were inspected for compatibility.

Both hosts staged hash-verified packages with rollback copies. A temporary model-admission fence queued new inference while the normal non-forced unload drained the old model; native/RPC packages were then installed and the admission fence restored to false. The owner's GUI keep-awake hold remains owned by the owner and unchanged. No proxy service restart or agent idle hold was used.

Live /v1/chat/completions through proxy 8082 on Flash-Next passed: 201 generated tokens, 55.87 token/s on this short request, 127 accepted of 222 drafted tokens, coherent Vietnamese completion, finish=stop. This is feature acceptance, not a general speed claim. The child executable hash matches the new manifest and uses pinned UUID order with GGML_CUDA_P2P_PAIRS=0-1,2-3; the active guest RPC manifest has the same e58b770f3…:e3b0… source identity. Flash-Next's route now uses the fork and retains its original MTP draft and other settings. All 14 route transport configurations are now persisted.

Remaining: finish Qwen-27B final profile verification on the new binary, including concurrent prefill, then publish consolidated configuration/performance acceptance under ggml-org#470. Source worktrees on native and guest are /home/jarvis/.local/share/llama-builds/flash-mtp-e58/source; original checkout HEADs remain at 95e6565, while installed runtime manifests identify the rebuilt e58 revision. Preserve this distinction when inspecting source.

@novkien

novkien commented Sep 30, 2026

Copy link
Copy Markdown
Owner Author

Final acceptance completed on rebuilt fork e58b770. Qwen is persistently and actively set to 690k shared KV, b2048/ub32, split29,29,27,15, four slots, MTP n_min1/n_max2/p_min0. Final sequential decode is 46.96/52.94 token/s; four concurrent 7208-token prompts complete without OOM (117.01 s total), and image input correctly returns Red. Original DFlash2 profile on the same binary measures 34.96/56.78 token/s; combined decode is about 15% lower than the selected MTP profile, while DFlash2 remains faster on the code prompt alone. Final MTP settings were restored and read back after comparison.

Flash-Next now uses the rebuilt fork with its original MTP head: 127/222 drafts accepted and a coherent completion. All 14 routes have validated persisted NVLink settings; actual source manifests and child environment match. Model admission is unblocked. The owner-controlled keep-awake state is preserved; no agent hold exists. This completes ggml-org#470's authorized scope. Separate catalog-capacity issue ggml-org#494 and native draft-media issue #6 remain open; neither was silently patched or treated as repaired.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants