model: add Hy3 (hy_v3) support with MTP speculative decoding - #25395
Conversation
Adds Tencent Hunyuan 3 (HF architecture HYV3ForCausalLM, GGUF arch hy_v3): a MoE decoder stack with per-head Q/K RMSNorm, a sigmoid router with expert selection bias, an always-active ungated shared expert, and leading dense block(s) (first_k_dense_replace). The base implementation is ported from charlie12345's fork (https://github.com/charlie12345/ROCmFPX, src/models/hyv3.cpp), adapted to current mainline APIs (hparams.n_layer(), build_qkv, build_moe_ffn with fused gate_up + scale tensors, output_s). Note: blk.N.exp_probs_b is stored without a .bias suffix for compatibility with existing hy_v3 GGUFs produced by that fork. Implemented by Claude (Anthropic Fable 5), directed by Satinder Grewal (@satindergrewal). Co-Authored-By: charlie12345 <charlie12345@users.noreply.github.com>
Loads the appended NextN/MTP decoder block(s) (blk.<n_layer>..) and adds a LLM_GRAPH_TYPE_DECODER_MTP graph so hy_v3 GGUFs converted with nextn_predict_layers work with --spec-type draft-mtp. Semantics follow vLLM's hy_v3_mtp.py (HYV3MultiTokenPredictorLayer): enorm(embed) + hnorm(prev_hidden) -> concat -> eh_proj -> full hy_v3 decoder block -> final_layernorm (stored as nextn.shared_head_norm) -> shared LM head. The checkpoint carries no MTP embed_tokens or separate shared head; both fall back to the main model's tok_embd/lm_head, matching vLLM's shared-weight loading. Hidden-state chaining is post-final_layernorm, so t_h_nextn is exposed post-norm in both the main graph and the MTP graph (same convention as qwen35moe). The main KV cache excludes the MTP layers and the MTP draft context only contains them (same filter split as step35). Measured on Hy3 299B-A15B IQ1_M (MTP block at Q8_0), RTX 5090, -ngl 12, temp 0, 300 tokens: draft acceptance 85.8% (n_max=3, mean accepted length 2.11, per-position 0.878/0.224/0.010), decode 8.81 tok/s vs 7.89 tok/s baseline (+11.7%) CPU-bound; n_max=1 acceptance 93.3%. Implemented by Claude (Anthropic Fable 5), directed by Satinder Grewal (@satindergrewal).
Registers MODEL_ARCH.HY_V3 in gguf-py (arch name, tensor table incl the NEXTN_* draft-head tensors) and adds HYV3Model to conversion/hunyuan.py. The NextN/MTP block sits at model.layers.<num_hidden_layers>; block_count is extended to include it (Step35Model pattern, including the --mtp / --no-mtp toggles) and its trailing final_layernorm is renamed to shared_head.norm so the existing NEXTN_SHARED_HEAD_NORM mapping picks it up. Everything else (mlp.router.gate, mlp.expert_bias, mlp.shared_mlp.*) resolves through existing tensor_mapping entries. Vocab is GPT-2 BPE with the existing hunyuan-dense pre-tokenizer. Implemented by Claude (Anthropic Fable 5), directed by Satinder Grewal (@satindergrewal).
|
Hi @satindergrewal, thanks for your contribution! Per our contribution guidelines, the automated PR checker found the following issue(s) that need your attention:
Please note that maintainers reserve the right to make final decisions on PRs. If you believe there is a mistake, please comment below. |
|
I'll add parser support in a followup PR. |
|
@pwilkin Parser notes from running these quants in the wild, in case they save you time:
Happy to test any branch against the published GGUFs (IQ2_M with the MTP head is the quickest to exercise). |
|
@satindergrewal I've got my own branch with the parser support already done :) |
|
Need to exclude the MTP arch from |
hy_v3 requires expert_feed_forward_length (the arch is MoE-only, same as step35), so the synthetic Dense variant failed to load: error loading model hyperparameters: key not found in model: hy_v3.expert_feed_forward_length Adding LLM_ARCH_HY_V3 to moe_mandatory() makes the test provide the expert KVs and skip the Dense variant, mirroring the existing STEP35 handling. Addresses the test-archs review note on the hy_v3 PR. The SWA-pattern branch STEP35 also sits in does not apply: hy_v3 never reads LLM_KV_ATTENTION_SLIDING_WINDOW_PATTERN. Implemented by Claude (Anthropic Fable 5), directed by Satinder Grewal (@satindergrewal).
|
@pwilkin Fixed in 97181b7. The actual failure was the synthetic Dense variant: hy_v3's loader requires |
|
Just finished testing the IQ2_M quant on my MacBook Pro M3 Max 128GB, and the results seem worth noting here as an Apple-silicon datapoint. Metal, IQ2_M quant of the 299B, with This supports making |
|
Actually, there's no use delaying stuff, @satindergrewal , could you please cherry-pick the last two commits from https://github.com/pwilkin/llama.cpp/tree/hunyuan-v3 onto here? (the parser fixes) |
Assisted-by: Claude Fable 5
…rser Also disambiguate per-call vs section end markers by comparing their occurrence counts in single vs parallel tool call renders. Assisted-by: Claude Fable 5
|
@pwilkin Done. Both commits cherry-picked onto the branch with your authorship preserved (692d171 jinja str.format, 56142c5 autoparser separator detection + the in-tree Hy3 template). The str.format support is especially welcome: it retires the fixed-template workaround we were shipping alongside the GGUFs. Running our validation battery against the updated branch now (the same chat/tools/streaming probes on the published 299B quants, both CUDA and Metal); will report anything unexpected. |
|
Validation battery on the updated branch (56142c5), Hy3 299B IQ1_M, CPU, temp 0, run against BOTH the in-tree
The headline holds: with the str.format support, the stock template renders and parses unmodified (10,175 chars, One residual observation, not hy_v3-affecting: in the json_native tool path, |
|
Triaged the CI failures before touching any code; the evidence says both are environmental, not the diff. Could a maintainer re-run the failed jobs? ubuntu x64 (5 tests SIGILL): master's own run 28918782275 (commit 4a7ee31, zero PR code) fails with the identical five-test ILLEGAL signature (test-llama-archs, test-thread-safety, test-opt, test-rope, test-col2im-1d; the last was added by master's HEAD). Only ggml-compute tests die, all parser tests pass: runner ISA/ccache mismatch pattern. Locally: all five pass, Release and ASAN+UBSAN. windows x64-openblas (0xc0000409 in test-jinja): all five new str.format tests PASS in the failing job's own log; the fail-fast fires ~600 lines later inside the pre-existing hasher property test at frame exit. The other three Windows jobs were matrix-cancelled, not crashed, so this is a single non-reproduced observation. Local: line audit of the str.format scanner (all accesses bounds-checked), 50 consecutive ASAN+UBSAN runs of test-jinja clean, full sanitizer suite clean. Local gate on the branch as pushed: ctest 52/52 PASSED (Release, x64). If Windows reproduces at the same spot on re-run, I will instrument and dig further. |
|
I generated GGUFs for Hy3 that were published before this PR and have been downloaded by community members — would an arch alias be feasible? Context: the hy_v3 GGUFs already published in the wild were converted with the earlier community implementation, which registered the architecture string as
Everything else is already compatible — I verified against this branch (56142c5):
So a one-line arch alias accepting I remain available to test whatever you need on my hardware. |
|
Nah, we have this problem all the time - the early converters should just reconvert, keeping aliases is messy and breaks conventions. However, the llama.cpp convention is |
|
On the naming: worth noting the tree already has underscore precedents, including in this same family: |
|
so are we discussing about source file name or something else? |
|
No, the architecture name - I was just unsure if we want to enforce one convention or we're fine with either. |
what about:
|
|
Aight, @satindergrewal you're good to go in that case :) |
|
#25395 (comment) is not yet resolved |
Per review: the only special sequence is '{' directly followed by '}'.
Anything else after '{' throws not_implemented_exception, which the
test harness already skips in cpp mode. Deletes brace-escape handling,
field extraction and kwarg filtering.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Fwiw from the GGUF side: the quants already published (mine and the other repos) were converted with |
|
I'll let you decide the dash/underscore naming before merging this |
|
@satindergrewal your final call, then I'll merge either way. |
|
Final call: keeping |
std::stoi with a base outside {0, 2..36} trips the invalid parameter
handler on the MSVC CRT (0xc0000409 fail-fast, not a catchable
exception). The builtin-function fuzzer can generate such calls, e.g.
'"s".int([1, 2, 3], -366)'; adding format() to the string builtins
reshuffled the fuzz pairings and exposed this on the windows CI job.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
The windows x64-openblas CI red is fixed in a00f69a. Root-caused it fully because it turned out to be a latent engine bug, not something in this PR's features:
The remaining ubuntu x64 SIGILL failures are the pre-existing environmental issue triaged earlier in this thread (master and other PRs fail identically). |
|
HF decided to act up, but the previous version already passed most tests, so merging. |
|
Is it possible to remove the MTP when converting to GGUF? When I try Update: I've made a small refactor + enabled for Hy 3 here: #25641 |
…, OpenCL Q6_K/Adreno, CORS, checkpoint min-step, prompt cache refactor, MoE expert API stays) Upstream highlights since 6be7459: - model: DFlash speculative with KV rotation (ggml-org#25823) - model: Hy3 (hy_v3) with MTP speculative decoding (ggml-org#25395) - model: DeepseekV4 with fused hyper-connection ops (ggml-org#25585) - ggml: 0.17.0, LIGHTNING_INDEXER, out_prod, f16 set_rows - vulkan: Q2_0 support, native e2m1/e4m3 conversions, transfer-queue race fix - CUDA: MMQ kernel config refactor (ggml-org#24127), tighter MMQ src1 buffer for fp4 (ggml-org#25613), CUDA graphs on Volta/Turing, MoE gate/up dedup, CUDA Virtual Devices - ROCm: hexagon L2 cache rework, native fp4, FP16/INT8 coopmat on AMD - SYCL: Battlemage flash attention via oneDNN XMX, XIELU op, fp16 conv2d_dw - OpenCL: Q6_K GEMM/GEMV fix, ragged-tile MoE prefill FP16, Adreno vectorized LD/ST, A7x optimizations, ABS op - kleidiai: SME2 f32 kernel, SME vs SME2 dispatch - server: refactor prompt cache state ownership (ggml-org#25649) - new server_prompt_cache_state separates prompt metadata from KV data - server: evict checkpoints within min-step (ggml-org#25472) - server: text-only slot save/restore with mtmd (ggml-org#25076) - server: --cors-* options (ggml-org#25655) - server: refactored server_stream (ggml-org#25541) - server: respect min-step when splitting prompt batches (ggml-org#25420) - server: move chat-template thinking probe inside init try/catch (ggml-org#24093) - common: auto-download dflash/eagle3 HF sidecars (ggml-org#25811), drop --stdin mutual-exclusion, align tokenize usage - conversion: BitNetForCausalLM, dflash tokenizer fix, split MTP export for HY V3 - llama-quant: exclude i32 ffn_gate_tid2eid routing table, allow manual tensor types with --pure - llama-batch: fix allowed decreasing pos in a seq (ggml-org#25449), n_keep_tail in split_equal for recurrent - llama: refactor fused ops (ggml-org#24646), TP fix for Phi3/Bert/Plamo2/3/ChatGLM - ui: agentic content UX, reasoning effort on mobile add sheet, MCP panel fixes, thinking menu fix - vendor: BoringSSL 0.20250713.0 - tests: actually exercise test-recurrent-state-rollback, ds_v4_hc sentinel init, export-graph-ops graceful exit CachyLLama preservation work (conflict resolution): 1. tools/server/server-task.h: Accept upstream's server_prompt refactor (no data member, clear() method). Move our t_last_used field from server_prompt to server_prompt_cache_state (where it now lives after the refactor). server_prompt_cache_state already has the size() method, so our old size() on server_prompt is no longer needed. 2. tools/server/server-context.cpp (create_checkpoint): Take upstream's min-step eviction pre-filter as the FIRST pass, then keep our existing highest-pos_min eviction as the capacity overflow fallback. These are complementary: min-step removes redundant checkpoints from the same task; highest-pos_min keeps the rec-window-friendly checkpoints when at cap. 3. tools/server/server-context.cpp (handle_completions_impl): Keep our std::vector<server_task> tasks batching for multi-prompt requests and per-user concurrency check, AND take upstream's res->set_req(&req) for spipe ownership transfer. 4. tools/server/server-task.cpp: Fix references to entry.tokens -> entry.prompt.tokens, entry.checkpoints -> entry.prompt.checkpoints, entry.n_tokens() -> entry.prompt.n_tokens(). Update find_eviction_candidate return type from list<server_prompt>::iterator to list<server_prompt_cache_state>::iterator. 5. ggml/src/ggml-cuda/mmq.cuh + new mmq-config-rdna3_5.cuh: Upstream's massive MMQ refactor moved per-architecture config into separate files but did NOT add RDNA3.5 (gfx1150/1/2/3, Strix Halo). Create mmq-config-rdna3_5.cuh (231 CASE entries) derived from rdna2 with nthreads=128 (4 warps) and I=48 (smaller X tile) matching our original Strix Halo tuning. Wire into both host and device dispatch paths before the RDNA4 / RDNA2 fallback. 6. README.md and AGENTS.md: Keep CachyLLama-specific links and project context where upstream added parallel content. Verified: - cmake --build builds clean (Release, CPU-only) - llama-server starts, --help shows all CachyLLama flags preserved: --cache-ssd-hot-ram, --cache-ssd-warm-ram, --cache-ssd-system-prompts, --cache-ssd-system-max-days, --cache-ssd-no-fsync, --cache-ssd-max-conversations, --max-concurrent-per-user - /expert-stats and /expert-tracking endpoints preserved - 55/58 tests pass; 3 failures unrelated to merge: - test-tokenizers-ggml-vocabs: missing model downloads - test-jinja-py: missing jinja2 Python module - test-quant-type-selection: snapshot mismatch on upstream's new MXFP4_MOE heuristic Custom CachyLLama files untouched (no upstream conflicts): - common/kv-ssd-cache.{cpp,h}, common/kv-ssd-posix.h, common/kv-ssd-system-cache.{cpp,h} - common/kv_page_manager.{cpp,h} - tools/server/server-context-page-manager.{cpp,h} - tools/server/server-context-ssd-cache.{cpp,h} - test_kv_page_manager.cpp, tests/test-ssd-cache-caps.cpp - STRIX_HALO_NOTES.md, docs/development/user-isolation-design.md - .github/workflows/build-cpu.yml, build-cuda-windows.yml, build-vulkan.yml
…g#25395) * model: add Hy3 (hy_v3) architecture support Adds Tencent Hunyuan 3 (HF architecture HYV3ForCausalLM, GGUF arch hy_v3): a MoE decoder stack with per-head Q/K RMSNorm, a sigmoid router with expert selection bias, an always-active ungated shared expert, and leading dense block(s) (first_k_dense_replace). The base implementation is ported from charlie12345's fork (https://github.com/charlie12345/ROCmFPX, src/models/hyv3.cpp), adapted to current mainline APIs (hparams.n_layer(), build_qkv, build_moe_ffn with fused gate_up + scale tensors, output_s). Note: blk.N.exp_probs_b is stored without a .bias suffix for compatibility with existing hy_v3 GGUFs produced by that fork. Co-Authored-By: charlie12345 <charlie12345@users.noreply.github.com> Co-authored-by: Piotr Wilkin <ilintar@gmail.com> Assisted-by: Claude Fable 5
…g#25395) * model: add Hy3 (hy_v3) architecture support Adds Tencent Hunyuan 3 (HF architecture HYV3ForCausalLM, GGUF arch hy_v3): a MoE decoder stack with per-head Q/K RMSNorm, a sigmoid router with expert selection bias, an always-active ungated shared expert, and leading dense block(s) (first_k_dense_replace). The base implementation is ported from charlie12345's fork (https://github.com/charlie12345/ROCmFPX, src/models/hyv3.cpp), adapted to current mainline APIs (hparams.n_layer(), build_qkv, build_moe_ffn with fused gate_up + scale tensors, output_s). Note: blk.N.exp_probs_b is stored without a .bias suffix for compatibility with existing hy_v3 GGUFs produced by that fork. Co-Authored-By: charlie12345 <charlie12345@users.noreply.github.com> Co-authored-by: Piotr Wilkin <ilintar@gmail.com> Assisted-by: Claude Fable 5
…g#25395) * model: add Hy3 (hy_v3) architecture support Adds Tencent Hunyuan 3 (HF architecture HYV3ForCausalLM, GGUF arch hy_v3): a MoE decoder stack with per-head Q/K RMSNorm, a sigmoid router with expert selection bias, an always-active ungated shared expert, and leading dense block(s) (first_k_dense_replace). The base implementation is ported from charlie12345's fork (https://github.com/charlie12345/ROCmFPX, src/models/hyv3.cpp), adapted to current mainline APIs (hparams.n_layer(), build_qkv, build_moe_ffn with fused gate_up + scale tensors, output_s). Note: blk.N.exp_probs_b is stored without a .bias suffix for compatibility with existing hy_v3 GGUFs produced by that fork. Co-Authored-By: charlie12345 <charlie12345@users.noreply.github.com> Co-authored-by: Piotr Wilkin <ilintar@gmail.com> Assisted-by: Claude Fable 5
…g#25395) * model: add Hy3 (hy_v3) architecture support Adds Tencent Hunyuan 3 (HF architecture HYV3ForCausalLM, GGUF arch hy_v3): a MoE decoder stack with per-head Q/K RMSNorm, a sigmoid router with expert selection bias, an always-active ungated shared expert, and leading dense block(s) (first_k_dense_replace). The base implementation is ported from charlie12345's fork (https://github.com/charlie12345/ROCmFPX, src/models/hyv3.cpp), adapted to current mainline APIs (hparams.n_layer(), build_qkv, build_moe_ffn with fused gate_up + scale tensors, output_s). Note: blk.N.exp_probs_b is stored without a .bias suffix for compatibility with existing hy_v3 GGUFs produced by that fork. Co-Authored-By: charlie12345 <charlie12345@users.noreply.github.com> Co-authored-by: Piotr Wilkin <ilintar@gmail.com> Assisted-by: Claude Fable 5
…g#25395) * model: add Hy3 (hy_v3) architecture support Adds Tencent Hunyuan 3 (HF architecture HYV3ForCausalLM, GGUF arch hy_v3): a MoE decoder stack with per-head Q/K RMSNorm, a sigmoid router with expert selection bias, an always-active ungated shared expert, and leading dense block(s) (first_k_dense_replace). The base implementation is ported from charlie12345's fork (https://github.com/charlie12345/ROCmFPX, src/models/hyv3.cpp), adapted to current mainline APIs (hparams.n_layer(), build_qkv, build_moe_ffn with fused gate_up + scale tensors, output_s). Note: blk.N.exp_probs_b is stored without a .bias suffix for compatibility with existing hy_v3 GGUFs produced by that fork. Co-Authored-By: charlie12345 <charlie12345@users.noreply.github.com> Co-authored-by: Piotr Wilkin <ilintar@gmail.com> Assisted-by: Claude Fable 5
…g#25395) * model: add Hy3 (hy_v3) architecture support Adds Tencent Hunyuan 3 (HF architecture HYV3ForCausalLM, GGUF arch hy_v3): a MoE decoder stack with per-head Q/K RMSNorm, a sigmoid router with expert selection bias, an always-active ungated shared expert, and leading dense block(s) (first_k_dense_replace). The base implementation is ported from charlie12345's fork (https://github.com/charlie12345/ROCmFPX, src/models/hyv3.cpp), adapted to current mainline APIs (hparams.n_layer(), build_qkv, build_moe_ffn with fused gate_up + scale tensors, output_s). Note: blk.N.exp_probs_b is stored without a .bias suffix for compatibility with existing hy_v3 GGUFs produced by that fork. Co-Authored-By: charlie12345 <charlie12345@users.noreply.github.com> Co-authored-by: Piotr Wilkin <ilintar@gmail.com> Assisted-by: Claude Fable 5
Overview
Adds support for Tencent's Hy3 (
hy_v3/HYV3ForCausalLM, 299B MoE, 80 layers + 1 MTP layer), including its multi-token-prediction head as adraft-mtpspeculative target. Addresses #24702 and #22477.LLM_ARCH_HY_V3: arch registration, hparams (incl.n_layer_nextn), tensor tables with the 6 NEXTN entriessrc/models/hy-v3.cpp: base decoder graph (sigmoid router + expert bias MoE, ungated shared expert, q/k norms) plus the MTP/nextn graph wired into the existing step35-style KV filter splitconversion/hunyuan.py(HYV3ForCausalLM) with--mtp/--no-mtp, layer-80 nextn mapping,final_layernorm -> shared_head.norm--spec-type draft-mtpmachinery runs it unchangedValidation (RTX 5090, temp 0, 300-token coding prompt, deterministic across reruns):
Build clean (CUDA 12.8, SM120). Regression: a qwen3.5-MoE MTP model is unaffected. Community hy_v3 GGUFs already in the wild (block_count 81) load unmodified.
Additional information
Reviewer notes, honestly flagged:
--spec-draft-p-min 0.75is effectively required for this model: its MTP head is trained single-depth (per-position acceptance 0.878/0.224/0.010), so the p_min=0 default makes speculation a net slowdown. Open to discussing whether a per-arch default belongs in code.blk.N.exp_probs_bis stored suffix-less for compatibility with published GGUFs; flagging for naming review.llm_typeis leftUNKNOWNfor the 299B config.The
hy_v3base graph is ported from charlie12345's community fork (credited via Co-Authored-By on the base commit); MTP forward semantics were verified against vLLM'shy_v3_mtp.py.Requirements