|
|
||
|---|---|---|
| .. | ||
| README.md | ||
| moe_router_logits.bin | ||
| moe_shared_gate_input.bin | ||
| moe_shared_gate_scalar.bin | ||
| moe_shared_gate_weight.bin | ||
| moe_topk_indices.bin | ||
| moe_topk_weights.bin | ||
| q3k_dequant_block0.bin | ||
| q3k_matvec_input.bin | ||
| q3k_matvec_output.bin | ||
| q3k_matvec_weight.bin | ||
| qknorm_input.bin | ||
| qknorm_output.bin | ||
| qknorm_weight.bin | ||
| rope_k_input.bin | ||
| rope_k_pos0.bin | ||
| rope_k_pos1.bin | ||
| rope_k_pos7.bin | ||
| rope_k_pos100.bin | ||
| rope_q_input.bin | ||
| rope_q_pos0.bin | ||
| rope_q_pos1.bin | ||
| rope_q_pos7.bin | ||
| rope_q_pos100.bin | ||
| ssm_conv_out.bin | ||
| ssm_conv_qkv.bin | ||
| ssm_conv_state_in.bin | ||
| ssm_conv_state_out.bin | ||
| ssm_conv_weight.bin | ||
| ssm_gated_out.bin | ||
| ssm_gated_weight.bin | ||
| ssm_gated_y.bin | ||
| ssm_gated_z.bin | ||
| ssm_recur_beta.bin | ||
| ssm_recur_g.bin | ||
| ssm_recur_k.bin | ||
| ssm_recur_q.bin | ||
| ssm_recur_state_in.bin | ||
| ssm_recur_state_out.bin | ||
| ssm_recur_v.bin | ||
| ssm_recur_y.bin | ||
README.md
qwen35moe CPU-reference golden vectors
Trusted CPU-reference input/output vectors for the four qwen35moe GPU kernels being written for a K80 host. The CUDA kernels must reproduce these outputs from the same inputs (within the tolerances below) — the same isolation-test discipline that already caught the Q3_K dequant high-mask bug and the SSM head-tiling bug.
- Generator:
tests/gen_golden_qwen35.c - Regenerate:
make USE_BLAS=1 golden(from the Pantheon repo root) - No model GGUF is involved. All inputs are synthetic, produced by a
fixed-seed xorshift32 PRNG seeded with
0x9E3779B9. A single RNG is threaded through all four kernels in the order they appear below, so the entire output set is fully determined by that seed and the generation order.
File format
Every *.bin file is self-describing:
line 1 (ASCII, newline-terminated): PNGOLDEN1 <name> <dtype> <count> seed=0x9E3779B9
remainder: <count> raw little-endian <dtype> values
<dtype> is one of f32 (4-byte IEEE-754 little-endian), i32 (4-byte signed
little-endian), or u8 (raw bytes). Read past the first newline, then read
count * sizeof(dtype) bytes of payload. Shapes/semantics are documented
per-file below (the header carries the flat element count only).
Config constants used
| Constant | Value | Source |
|---|---|---|
n_embd |
2048 | qwen35moe config |
n_head |
16 | qwen35moe config |
n_head_kv |
2 | qwen35moe config |
head_dim |
256 | qwen35moe config |
rope_dim (partial) |
64 | qwen35moe config |
rope_theta (freq base) |
1e7 | model facts (rope.freq_base) |
rms_norm_eps |
1e-6 | model facts (attention.layer_norm_rms_epsilon) |
n_expert |
256 | qwen35moe config |
expert_used (top-k) |
8 | qwen35moe config |
expert_ff |
512 | qwen35moe config |
expert_weights_scale |
1.0 | GGUF default (no key) |
PRNG seed: 0x9E3779B9.
Kernel 1 — Q3_K dequant + Q3_K matvec (HIGHEST VALUE)
Golden output produced by the REAL CPU functions: pn_qmv →
pn_qdot → dot_q3_k → pn_dequantize_block_q3_k
(src/core/qweight.c, src/formats/gguf_dequant.c). The synthetic weight
matrix is built by filling the real pn_block_q3_k struct
(hmask[32], qs[64], scales[12], fp16 d) with deterministic
pseudo-random bytes, so every bit pattern is decoded exactly as a GPU kernel
must decode it. Matrix shape: rows=24, cols=1024 (4 Q3_K super-blocks/row).
| File | dtype | count | shape / meaning |
|---|---|---|---|
q3k_matvec_weight.bin |
u8 | 10560 | raw Q3_K blocks, 24 rows × 4 blocks × 110 bytes (row-major) |
q3k_matvec_input.bin |
f32 | 1024 | input activation vector (cols) |
q3k_matvec_output.bin |
f32 | 24 | out[r] = W[r,:] · input (rows) — golden |
q3k_dequant_block0.bin |
f32 | 256 | dequantized super-block 0 of the weight (dequant-only isolation) |
Tolerance: matvec/dequant on Kepler fp32 — ~1e-4 .. 1e-3 relative (per output element). Use the dequant-only file first to localize a mismatch to the dequant vs. the dot-product.
Kernel 2 — Partial NEOX RoPE
Golden produced by a cross-checked replica of the static helper
qwen35moe_rope_partial (src/models/qwen35moe_attn.c:57-76) — replicated
term-for-term (cited inline in the generator). Only the first rope_dim=64
dims of each head_dim=256 head are rotated (NEOX pairing j vs j+32); dims
[64,256) pass through unchanged. theta_base=1e7. Verified that pos 0 is an
exact identity (angle 0).
Pre-rotation inputs (shared across all positions):
| File | dtype | count | shape |
|---|---|---|---|
rope_q_input.bin |
f32 | 4096 | Q, n_head(16) × head_dim(256) |
rope_k_input.bin |
f32 | 512 | K, n_head_kv(2) × head_dim(256) |
Rotated goldens, one pair per position (pos ∈ {0, 1, 7, 100}):
| File | dtype | count | meaning |
|---|---|---|---|
rope_q_pos<P>.bin |
f32 | 4096 | Q rotated at position P |
rope_k_pos<P>.bin |
f32 | 512 | K rotated at position P |
Tolerance: ~1e-4 .. 1e-3 relative (trig in fp32 on Kepler). Note GPU
sinf/cosf/powf may differ slightly from the host libm; pos 0 should match
to ~0 (identity).
Kernel 3 — QK-RMSNorm
Golden produced by the REAL CPU function pn_vrms_norm_f32
(src/accel/simd.c:198), applied per head exactly as qwen35moe_qk_norm_heads
(src/models/qwen35moe_attn.c:39-46) does — one shared head_dim norm weight,
eps=1e-6. Input is the 16-head Q layout [n_head(16) × head_dim(256)].
| File | dtype | count | shape / meaning |
|---|---|---|---|
qknorm_input.bin |
f32 | 4096 | per-head input, 16 × 256 |
qknorm_weight.bin |
f32 | 256 | shared per-head norm weight (≈1 ± 0.1) |
qknorm_output.bin |
f32 | 4096 | RMSNorm'd output, 16 × 256 — golden |
Tolerance: ~1e-4 .. 1e-3 relative. RMS uses 1/sqrt(mean(x²)+eps); the
sum-of-squares reduction order may differ on GPU.
Kernel 4 — MoE routing
Golden produced by a cross-checked replica of the routed-gating path of
src/models/qwen35moe_moe.c:
qwen35moe_router_softmax (lines 47-61), qwen35moe_select_topk (lines 65-89),
norm_topk_prob + expert_weights_scale (lines 133-136), and the shared-expert
scalar sigmoid gate sigmoid(gate_w · x) (lines 179-183). Replicated
term-for-term (cited inline). n_expert=256, top-k=8, expert_weights_scale=1.0.
Top-k selection is a selection sort with first-seen tie-break — the indices must match exactly on GPU; any GPU implementation must use the same tie-break to be bit-identical here.
Inputs:
| File | dtype | count | meaning |
|---|---|---|---|
moe_router_logits.bin |
f32 | 256 | raw router logits (pre-softmax) |
moe_shared_gate_weight.bin |
f32 | 2048 | shared-expert gate weight (n_embd) |
moe_shared_gate_input.bin |
f32 | 2048 | hidden state x for the gate dot (n_embd) |
Goldens:
| File | dtype | count | meaning |
|---|---|---|---|
moe_topk_indices.bin |
i32 | 8 | selected expert indices, in selection order |
moe_topk_weights.bin |
f32 | 8 | renormalized + scaled weights (sum ≈ 1.0) |
moe_shared_gate_scalar.bin |
f32 | 1 | sigmoid(gate_w · x) scalar gate |
Reference values from this seed: indices [1, 93, 212, 144, 170, 76, 180, 203],
weights sum 1.000000, shared gate 0.051038.
Tolerance: top-k indices must match EXACTLY (integer). Weights and the gate scalar: ~1e-5 .. 1e-4 relative (softmax/sigmoid in fp32).
Kernel 5 — SSM per-token delta-rule recurrence (HIGHEST VALUE, the centerpiece)
Golden produced by a cross-checked replica of qwen35moe_compute_recur_heads
(src/models/qwen35moe_ssm.c:163-196) — the static core recurrence, replicated
term-for-term with the source cited inline (the two length-S dots call the public
pn_dot_f32, exactly as the source does). Single-token (decode) path.
Config: n_v_heads=32, state_size S=128, scale=1/sqrt(128). The state is
transposed: s[h*S*S + j*S + i] == S_h[i][j] (row j contiguous over i). For
each V-head h, each row j:
- (a)
row[i] *= exp(g[h])— scalar decay of the whole row (ssm.c:180-182) - (b)
pred = Σ_i row[i]·k[h][i];delta = (v[h][j] − pred)·beta[h](ssm.c:184-186) - (c)
row[i] += k[h][i]·delta— rank-1 write, in place (ssm.c:188-190) - (d)
y[h][j] = scale · Σ_i row[i]·q[h][i]— read-after-write (ssm.c:192-193)
q/k are supplied already L2-normed + GQA-repeated to n_v_heads (the GPU
kernel's input contract; the 16→32 repeat happens on the host before the kernel).
g is pre-exp (the kernel applies exp); beta is already sigmoid'd.
Inputs:
| File | dtype | count | shape / meaning |
|---|---|---|---|
ssm_recur_state_in.bin |
f32 | 524288 | initial transposed state, 32×128×128 (h,j,i) |
ssm_recur_q.bin |
f32 | 4096 | q, 32×128 (L2-normed + GQA-repeated) |
ssm_recur_k.bin |
f32 | 4096 | k, 32×128 (L2-normed + GQA-repeated) |
ssm_recur_v.bin |
f32 | 4096 | v, 32×128 |
ssm_recur_g.bin |
f32 | 32 | per-V-head decay (pre-exp; kernel does exp(g)) |
ssm_recur_beta.bin |
f32 | 32 | per-V-head beta (already sigmoid'd) |
Goldens:
| File | dtype | count | meaning |
|---|---|---|---|
ssm_recur_y.bin |
f32 | 4096 | recurrence output y, 32×128 |
ssm_recur_state_out.bin |
f32 | 524288 | updated transposed state (decayed + rank-1 write) |
Tolerance: two 128-wide dot reductions per (h,j) in fp32 on Kepler — ~1e-4 .. 1e-3 relative. The harness diffs both y and the updated state.
Kernel 6 — SSM causal conv1d (k=4) + SiLU
Golden produced by a cross-checked replica of qwen35moe_conv1d_silu
(src/models/qwen35moe_ssm.c:110-135), replicated term-for-term. Depthwise
causal conv over the full conv_dim=8192 stream with conv_kernel=4:
out[c] = SiLU(history·w[0..k-2] + qkv[c]·w[k-1]), then the per-channel
conv_state is rolled (drop oldest, append raw qkv[c]). conv_state layout is
[conv_dim][k-1] (oldest..newest); weight is row-major [conv_dim, k].
Inputs:
| File | dtype | count | shape / meaning |
|---|---|---|---|
ssm_conv_qkv.bin |
f32 | 8192 | raw projected stream this token (conv_dim) |
ssm_conv_weight.bin |
f32 | 32768 | depthwise conv weights, 8192×4 row-major |
ssm_conv_state_in.bin |
f32 | 24576 | conv history, 8192×3 ([conv_dim][k-1]) |
Goldens:
| File | dtype | count | meaning |
|---|---|---|---|
ssm_conv_out.bin |
f32 | 8192 | SiLU'd convolved stream (conv_dim) |
ssm_conv_state_out.bin |
f32 | 24576 | rolled conv_state, 8192×3 |
Tolerance: small fixed-tap dot + SiLU in fp32 — ~1e-4 .. 1e-3 relative. The harness diffs both the output and the rolled conv_state.
Kernel 7 — SSM gated RMSNorm
Golden produced by the REAL CPU function pn_vrms_norm_f32 per head plus a
SiLU replica, mirroring qwen35moe_gated_norm
(src/models/qwen35moe_ssm.c:206-223):
gated[h*S+d] = RMSNorm(y_h)[d] · weight[d] · silu(z[h*S+d]). The norm weight is
shared across heads; the trailing ssm_out projection is out of scope (the
GPU reuses fc_matvec_q3k). n_v_heads=32, state_size S=128.
Inputs:
| File | dtype | count | shape / meaning |
|---|---|---|---|
ssm_gated_y.bin |
f32 | 4096 | recurrence output y, 32×128 |
ssm_gated_weight.bin |
f32 | 128 | shared ssm_norm weight (state_size) |
ssm_gated_z.bin |
f32 | 4096 | attn_gate projection z (pre-silu), 32×128 |
Golden:
| File | dtype | count | meaning |
|---|---|---|---|
ssm_gated_out.bin |
f32 | 4096 | RMSNorm(y)·w·silu(z), 32×128 |
Tolerance: per-head RMS reduction (128 elems) + silu in fp32 on Kepler — ~1e-4 .. 1e-3 relative.