Skip to content

hy3: Metal decode path + shared gate/up fusion#523

Open
audreyt wants to merge 16 commits into
antirez:mainfrom
audreyt:hy3
Open

hy3: Metal decode path + shared gate/up fusion#523
audreyt wants to merge 16 commits into
antirez:mainfrom
audreyt:hy3

Conversation

@audreyt

@audreyt audreyt commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Hy3 (hy_v3) single-token Metal decode on residual tensors (cur_hc / after_*_hc) instead of per-layer bounce through batch buffers.
  • Fuse shared-expert gate/up/SwiGLU for n_tokens==1 (reuse Flash helper); multi-token prefill unchanged.
  • Dense layer 0 uses batch_routed_* scratch sized for DS4_N_DENSE_FF (13312).
  • Kill switch: DS4_METAL_DISABLE_HY3_SINGLE_TOKEN_DECODE=1 restores legacy batch path.

Also on this branch (prior commits): Hy3 CPU/Metal GQA, split-half RoPE, plain residual, Q4 selected-slots for top-8 MoE, multi-token Q4 selected token-loop under streaming.

Mixed GGUF (not in git)

Built offline for ~128GB machines (~89.8 GiB):
hy3-80layer-Layers74-79Q4KExperts-mixed.gguf — IQ2_XXS gate/up + Q2_K down for layers 1–73, Q4_K experts for layers 74–79.

HF upload blocked here: local token is read-only (role=read). Need a write token to publish under audreyt/*.

Test plan

  • make ds4 clean build
  • Smoke: ./ds4 -m …mixed.gguf --metal --ctx 2048 --tokens 64 --temp 0 --nothink -p '…2+2?' → correct answer, ~12 t/s gen
  • Kill switch path smoke
  • CPU/reference numeric equivalence for rewrite (not done)
  • Broader Hy3 quality suite

audreyt added 16 commits June 20, 2026 13:12
M1 - GGUF production tooling:
- gguf-tools/glm_template/build_glm_template.py: header-only GLM template
  builder. Emits the GLM-5.2 layout (98 layers incl. dens/MLP, attention standard
  MLA with separate kv_b/o_proj, optional IndexShare disabled for first port),
  glm.* metadata (qk_nope/rope, v_head, kv_lora, dense_ff, router_scoring,
  index_*), DeepSeek-reused keys for dimensions ds4 already reads, GLM tokenizer
  (BPE 154820 base + 36 added tokens).
- gguf-tools/glm-quantize.c: DeepSeek4-quantize.c cloned; adapted for GLM:
  - F8_E4M3 + F32 weight_scale_inv dequant (DeepSeek used e8m0 .scale); partial-
    block tolerant for the kv_a_proj 576x6144 edge case.
  - Expert path uses GLM per-expert gate_proj/up_proj/down_proj (FP8+scale_inv);
    no FP4 path.
  - GLM tensor map: attn_kv_b / attn_output (new, GLM is standard MLA, no
    factored output_a/b); ffn_gate/up/down (dense MLP); exp_probs_b.bias
    (e_score_correction_bias) named to match ds4's binder.
  - MTP (layer 78) deferred: standard model loaded as mtp.0.* via --mtp.
- ggt-tools/Makefile: build glm-quantize alongside deepseek4-quantize.

M3 - Loader support in ds4.c:
- Raise DS4_MAX_LAYER 61->80, EX4_VOCAB->154880, EX4_LORA_Q->2048,
  EX4_EXPERT_USED->8, ADD DS4_MAX_DENSE_FF=12288, INDEXER_TOP_K->2048.
- Add DS4_VARIANT_GLM=2 + DS4_SHAPE_GLM (78L, 6144 embd, 64 heads, qk=256,
  v=256, kv_lora=512, 256 experts@top-8, 3 dense, 2048 dense-ff, sigmoid/noaux,
  8em6 rope base, no HC/SWA/compression/indexer).
- Extend ds4_shape with GLM-only fields: n_qk_nope/n_qk_rope/n_v_head_dim/
  n_kv_lora/n_dense_layers/n_dense_ff/router_scoring/index_topk_freq.
- config_validate_model: arch-tag dispatch (glm_moe_dsa) to a new
  config_validate_model_glm() that reads glm.* keys; legacy DeepSeek path
  keeps strict rejection.
- Expected compress ratio: 0 for GLM (no SWA).
- Weights struct: add GLM-only ffn_gate/up/down + attn_kv_b + attn_output
  pointers; weights_layer_has_required/weights_have_output_head gated on
  DS4_IS_GLM(); weights_bind_output / weights_bind_layer / weights_validate_layout
  branch on variant to bind GLM's standard MLA + (dense | MoE-FFN) layout with the
  ds4 convention that d0=in, d1=out (matching DeepSeek tensor_nbytes semantics).
- model_open / parse_tensors gain an allow_short_file flag gated on inspect_only
  so --inspect on the header-only template doesn't trigger the tensor-outside-GGUF
  guard; real loads keep the safety check.

Verified: --inspect on the GLM template prints 78 layers / 256 experts / top-8
with q8_0/q2_k/iq2_xxs/f16/f32 tensors; DeepSeek model-free tests still pass
(test_q4k_dot, ds4-agent_test, ds4-eval --self-test-extractors).
Add a self-contained, correct CPU forward path used as the Gate-1 parity
oracle for the Metal graph port.  GLM has no hyper-connection, so the top-
level decode driver (forward_token_raw_swa_cpu_decode_scratch) dispatches
to a plain-[n_embd] residual path: forward_glm_cpu_decode_scratch -> per-
layer layer_forward_glm_decode_one -> glm_mla_attention_one + (dense-MLP |
top-8 MoE + shared) + output_logits_one_decode_scratch_glm.

- K4786: kv_cache_init now allocates DS4_N_KV_LORA + DS4_N_QK_ROPE (576)
  per row for GLM (was DS4_N_HEAD_DIM=256) — the cache stores the full
  kv_a latent + rotated rope tail.

- glm_mla_attention_one:
  * K_nope per-row = first 192 of kv_b(raw_kv[r][:512]) — each cached row's
    own kv_b expansion (the linear map to n_head*448).  K_rope per-row =
    raw_kv[r][512:576] (that row's already-rotated 64-dim tail).
  * Score = q_h · K over qk_nope+qk_rope=256, single softmax across the
    n_raw+1 candidate rows, weighted sum of V_h = kv_b(row[:512])[h*448+192:+256].
  * No inverse-rope on the output (GLM is standard MLA; V_h is unroted).
  * Cost is O(seq*kv_b) per layer — fine for the CPU oracle; the GPU path
    (M5) is the optimized production target.

- glm_dense_ffn_one: standard SwiGLU MLP for layers 0..n_dense_layers-1
  using ffn_gate/ffn_up/ffn_down (12288-wide intermediate).

- glm_moe_one + glm_moe_router: top-8 router scoring sigmoid + e_score_corr
  ection_bias for selection only; weights = sigmoid(logits)*scale/sum, runs
  the IQ2_XXS/Q2_K expert matvecs and reuses the Q8_0 shared-FFN helper.

Forward declarations for both *-decode_scratch functions added so the
DeepSeek output_logits_one_decode_scratch (defined later in the file) is
visible to the GLM dispatch branch.

Verified: build clean (no warnings); the model-free subset (Q4_K dot,
extractors, agent test) still passes; --inspect still loads the GLM template.
- layer_forward_glm_decode_one: q_a and q_a_n must be sized with
  DS4_N_LORA_Q=2048 (the GLM Q lora rank), not kv_lora=512, which only
  applies to the kv_a latent. Fixes an OOB write through matvec_q8_0
  (attn_q_a's output dim is 2048).
- prefill_layer_major_cpu: add a GLM branch that runs the decode path
  token-by-token over the prompt on a plain [n_embd] residual, so the
  CPU oracle path covers M4's full-prompt reference too. The GPU-resident
  prefill graph (M5) is the optimized production path.

Verified: build clean, DeepSeek model-free tests still pass
(Q4_K dot, extractors, agent test).
Make Hy3 Metal runnable on M5 Max: plain-residual graph sizing when
n_hc==0, LLaMA-style split-half RoPE for Q/K, GQA attention kernel,
and a CLI guard that refuses oversized Metal diagnostics so a 159 GiB
GGUF cannot wedge the machine during CPU/GPU compare paths.
Hy3 routes 8 experts. The Metal slots8 Q4 kernels already existed, but
the C-side and batch entry still required top-6, so Q4 streaming fell
back to full expert maps. Allow DS4_MAX_EXPERT_USED (8) and require the
slots8 pipelines on that path so M5 Max SSD streaming can stay selected-only.
Default short-prompt prefill batches many tokens. Q4 selected-slots only
existed for n_tokens==1, so multi-token SSD streaming would wrap full
multi-GiB expert tensors and wedge 128 GiB machines.

When SSD streaming + Q4_K + top-6/8 selected slots are available, loop
the one-token slots path over batch rows. Also refuse full-expert batch
wraps under streaming unless explicitly opted in, and print all 8
selected expert IDs in the profile line.

Verified on M5 Max with hy3-80layer-q4k: --ssd-streaming --prefill-chunk 8
--ctx 256 -n 2, path=q4/q4 mode=stream-cache n_expert=8, ~8 GiB RSS.
Route Hy3 decode through residual single-token tensors instead of
bouncing each layer through batch buffers. Fuse shared expert
gate/up/SwiGLU on n_tokens==1 (same helper as Flash decode). Dense
layer 0 uses batch MoE scratch sized for DS4_N_DENSE_FF.

Kill switch: DS4_METAL_DISABLE_HY3_SINGLE_TOKEN_DECODE=1 restores the
legacy batch bounce path. Smoke-only validation on M5 Max (~12 t/s at
ctx 2048); no CPU/reference numeric equivalence yet.
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