/* SPDX-License-Identifier: AGPL-3.0-or-later */ /* Copyright (C) 2026 David (Corey) Herrera */ /** * @file gen_golden_qwen35.c * @brief CPU-reference "golden vector" generator for the qwen35moe GPU kernels. * Produces deterministic input+output golden files under tests/golden/ * so the forthcoming K80 CUDA kernels can be diffed bit-for-bit (routing * indices) / within tolerance (matvec, RoPE) against a trusted CPU * reference. NO model GGUF is loaded — all inputs are synthetic, * generated from a fixed-seed xorshift PRNG. * * Kernels covered (priority order): * 1. Q3_K dequant + Q3_K matvec (history of dequant bugs) * 2. Partial NEOX RoPE (rope_dim 64 of head_dim 256) * 3. QK-RMSNorm (per-head RMSNorm) * 4. MoE routing (softmax + top-8/256 + norm_topk_prob * + shared-expert sigmoid gate) * * Where a PUBLIC Pantheon CPU function exists it is CALLED directly (the * gold standard): pn_dequantize_block_q3_k / pn_qdot for Q3_K, and * pn_vrms_norm_f32 for QK-RMSNorm. The partial-RoPE and MoE-routing math * live in static helpers inside the model .c files; those are replicated * here line-for-line with the source cited inline so the replica is * auditable. * * @status Complete: generates all four golden sets, verifies finiteness, prints * summary sums. CPU-only, no model, runs in well under a second. * @issues None * @todo None */ /* 1. (no own header — standalone tool) */ /* 2. Standard library */ #include #include #include #include #include /* 4. Project headers */ #include "../src/accel/simd.h" #include "../src/core/qweight.h" #include "../src/formats/gguf_dequant.h" /* ============================================================ * Config constants (verified Nex-N2-mini qwen35moe dimensions) * ============================================================ */ #define GOLDEN_SEED 0x9E3779B9u /* fixed xorshift seed (golden ratio) */ #define GOLDEN_DIR "tests/golden" #define QWEN35_N_EMBD 2048 #define QWEN35_N_HEAD 16 #define QWEN35_N_HEAD_KV 2 #define QWEN35_HEAD_DIM 256 #define QWEN35_ROPE_DIM 64 #define QWEN35_N_EXPERT 256 #define QWEN35_EXPERT_USED 8 #define QWEN35_EXPERT_FF 512 #define QWEN35_RMS_NORM_EPS 1e-6f /* attention.layer_norm_rms_epsilon */ #define QWEN35_ROPE_THETA 1e7f /* rope.freq_base */ #define QWEN35_EXPERT_WT_SCALE 1.0f /* expert_weights_scale (GGUF default) */ /* Q3_K matvec test shape: a synthetic weight matrix with cols a multiple of the * 256-element super-block and a handful of output rows. */ #define Q3K_ROWS 24 #define Q3K_COLS 1024 /* 4 Q3_K super-blocks per row */ /* Gated-Delta-Net SSM dimensions (verified Nex-N2-mini qwen35moe). */ #define SSM_N_V_HEADS 32 #define SSM_N_K_HEADS 16 #define SSM_STATE_SIZE 128 /* S == head_k_dim == head_v_dim */ #define SSM_CONV_DIM 8192 #define SSM_CONV_KERNEL 4 /* RoPE test positions. */ static const int ROPE_POSITIONS[] = {0, 1, 7, 100}; #define ROPE_N_POS (int)(sizeof(ROPE_POSITIONS) / sizeof(ROPE_POSITIONS[0])) /* ============================================================ * Deterministic PRNG — xorshift32 (seeded, never rand()) * ============================================================ */ /* A tiny, fully deterministic xorshift32. Same seed -> same stream on every * platform, which is exactly what golden vectors need. */ typedef struct { uint32_t state; } golden_rng; static void golden_rng_init(golden_rng *rng, uint32_t seed) { /* Avoid the zero fixed point; any nonzero seed produces a full-period stream. */ rng->state = seed ? seed : 0xDEADBEEFu; } static uint32_t golden_rng_next_u32(golden_rng *rng) { uint32_t x = rng->state; x ^= x << 13; x ^= x >> 17; x ^= x << 5; rng->state = x; return x; } /* Uniform byte. */ static uint8_t golden_rng_next_u8(golden_rng *rng) { return (uint8_t)(golden_rng_next_u32(rng) & 0xFFu); } /* Uniform float in [-1, 1). */ static float golden_rng_next_signed_unit(golden_rng *rng) { /* 24 mantissa bits -> [0,1), then map to [-1,1). */ uint32_t bits = golden_rng_next_u32(rng) >> 8; float u01 = (float)bits / (float)(1u << 24); return u01 * 2.0f - 1.0f; } /* ============================================================ * FP16 packing (host-side, mirrors IEEE-754 half; round-to-nearest-even) * ============================================================ */ /* Pack a finite, in-range float into an IEEE-754 half. Used only to fill the * synthetic Q3_K super-block scale field (d). Kept small and conservative — the * synthetic d values are O(0.01), comfortably inside the half range. The decode * side is the production pn_half_to_float, so any rounding here is captured in * the golden output (the real CPU kernel reads exactly these bytes). */ static uint16_t float_to_half(float f) { union { float f; uint32_t u; } v; v.f = f; uint32_t x = v.u; uint32_t sign = (x >> 16) & 0x8000u; int32_t exp = (int32_t)((x >> 23) & 0xFF) - 127 + 15; uint32_t mant = x & 0x7FFFFFu; if (exp <= 0) { /* Subnormal/zero half — synthetic d is never this small; flush to 0. */ return (uint16_t)sign; } if (exp >= 0x1F) { /* Overflow — clamp to max finite half. */ return (uint16_t)(sign | 0x7BFFu); } /* Round mantissa to 10 bits, round-to-nearest-even. */ uint32_t mant10 = mant >> 13; uint32_t rem = mant & 0x1FFFu; if (rem > 0x1000u || (rem == 0x1000u && (mant10 & 1u))) mant10++; if (mant10 == 0x400u) { /* mantissa overflow carries into exponent */ mant10 = 0; exp++; if (exp >= 0x1F) return (uint16_t)(sign | 0x7BFFu); } return (uint16_t)(sign | ((uint32_t)exp << 10) | mant10); } /* ============================================================ * Golden file writer — simple self-describing text-prefix format * * Format of every golden file: * line 1: "PNGOLDEN1 seed=" * then raw little-endian values of (binary payload). * dtype is one of: f32, u8, i32. Shapes/semantics are in tests/golden/README.md. * ============================================================ */ static int write_golden(const char *filename, const char *name, const char *dtype, const void *payload, size_t count, size_t elem_size) { char path[512]; int n = snprintf(path, sizeof(path), "%s/%s", GOLDEN_DIR, filename); if (n < 0 || (size_t)n >= sizeof(path)) { fprintf(stderr, "write_golden: path overflow for '%s'\n", filename); return -1; } FILE *fp = fopen(path, "wb"); if (!fp) { fprintf(stderr, "write_golden: cannot open '%s'\n", path); return -1; } int rc = -1; if (fprintf(fp, "PNGOLDEN1 %s %s %zu seed=0x%08X\n", name, dtype, count, (unsigned)GOLDEN_SEED) < 0) goto cleanup; if (count > 0 && fwrite(payload, elem_size, count, fp) != count) goto cleanup; rc = 0; cleanup: if (rc != 0) fprintf(stderr, "write_golden: write failed for '%s'\n", path); fclose(fp); return rc; } /* Finiteness/sanity check + summary print for a float buffer. */ static int report_f32(const char *label, const float *data, size_t count) { double sum = 0.0; for (size_t i = 0; i < count; i++) { if (!isfinite(data[i])) { fprintf(stderr, " [FAIL] %s: non-finite value at index %zu\n", label, i); return -1; } sum += (double)data[i]; } printf(" %-22s count=%-6zu sum=%+.6f first=[%+.5f %+.5f %+.5f]\n", label, count, sum, count > 0 ? data[0] : 0.0f, count > 1 ? data[1] : 0.0f, count > 2 ? data[2] : 0.0f); return 0; } /* ============================================================ * Kernel 1: Q3_K dequant + Q3_K matvec (REAL CPU functions) * ============================================================ */ /* Fill one Q3_K super-block with deterministic pseudo-random bytes that respect * the real block layout (pn_block_q3_k in gguf_dequant.h): hmask[32], qs[64], * scales[12], d (fp16). The decode is done by the production kernel, so every * bit pattern we write here is interpreted exactly as the GPU kernel must. */ static void fill_q3k_block(golden_rng *rng, pn_block_q3_k *block) { for (int i = 0; i < 32; i++) block->hmask[i] = golden_rng_next_u8(rng); for (int i = 0; i < 64; i++) block->qs[i] = golden_rng_next_u8(rng); for (int i = 0; i < 12; i++) block->scales[i] = golden_rng_next_u8(rng); /* Small positive-ish super-block scale, typical of real Q3_K weights. */ float d = 0.005f + 0.02f * (golden_rng_next_signed_unit(rng) * 0.5f + 0.5f); block->d = float_to_half(d); } /* Generate the Q3_K golden set: synthetic quantized weight matrix + F32 input * vector, then run the REAL pn_qmv to produce the golden output. * * Source of golden output: pn_qmv -> pn_qdot -> dot_q3_k -> * pn_dequantize_block_q3_k (src/core/qweight.c:46-57, 161-182; * src/formats/gguf_dequant.c:118-162). All public, all called directly. */ static int gen_q3k_golden(golden_rng *rng) { const int rows = Q3K_ROWS; const int cols = Q3K_COLS; const int blocks_per_row = cols / 256; const size_t total_blocks = (size_t)rows * (size_t)blocks_per_row; pn_block_q3_k *weight = malloc(total_blocks * sizeof(*weight)); float *input = malloc((size_t)cols * sizeof(*input)); float *output = malloc((size_t)rows * sizeof(*output)); int rc = -1; if (!weight || !input || !output) { fprintf(stderr, "gen_q3k_golden: allocation failed\n"); goto cleanup; } for (size_t b = 0; b < total_blocks; b++) fill_q3k_block(rng, &weight[b]); for (int i = 0; i < cols; i++) input[i] = golden_rng_next_signed_unit(rng); /* REAL CPU matvec — the gold standard. row_stride = bytes per quantized row. */ pn_qweight qw = { .data = weight, .type = GGUF_QUANT_Q3_K, .rows = rows, .cols = cols, .row_stride = (size_t)blocks_per_row * sizeof(pn_block_q3_k), .scales = NULL, .group_size = 0, }; pn_qmv(&qw, input, output); if (write_golden("q3k_matvec_weight.bin", "q3k_matvec_weight", "u8", weight, total_blocks * sizeof(pn_block_q3_k), 1) < 0) goto cleanup; if (write_golden("q3k_matvec_input.bin", "q3k_matvec_input", "f32", input, (size_t)cols, sizeof(float)) < 0) goto cleanup; if (write_golden("q3k_matvec_output.bin", "q3k_matvec_output", "f32", output, (size_t)rows, sizeof(float)) < 0) goto cleanup; /* Also emit a single dequantized super-block (block 0) as a standalone * dequant golden — isolates the dequant from the dot-product. */ float dequant0[256]; pn_dequantize_block_q3_k(&weight[0], dequant0); if (write_golden("q3k_dequant_block0.bin", "q3k_dequant_block0", "f32", dequant0, 256, sizeof(float)) < 0) goto cleanup; printf("[1] Q3_K dequant + matvec (REAL pn_qmv / pn_dequantize_block_q3_k)\n"); if (report_f32("q3k_dequant_block0", dequant0, 256) < 0) goto cleanup; if (report_f32("q3k_matvec_output", output, (size_t)rows) < 0) goto cleanup; rc = 0; cleanup: free(weight); free(input); free(output); return rc; } /* ============================================================ * Kernel 2: Partial NEOX RoPE (cross-checked replica) * * Replicates qwen35moe_rope_partial, src/models/qwen35moe_attn.c:57-76. * Only the first rope_dim dims of each head are rotated (NEOX pairing j vs * j+half, half = rope_dim/2); dims [rope_dim, head_dim) pass through. The math * below is identical to the source, term for term. * ============================================================ */ static void rope_partial_replica(float *data, int n_heads, int head_dim, int rope_dim, int pos, float theta_base) { int rot = (rope_dim > 0 && rope_dim < head_dim) ? rope_dim : head_dim; int half = rot / 2; for (int h = 0; h < n_heads; h++) { float *head = data + (size_t)h * head_dim; for (int j = 0; j < half; j++) { float freq = 1.0f / powf(theta_base, (float)(2 * j) / (float)rot); float angle = (float)pos * freq; float cos_a = cosf(angle); float sin_a = sinf(angle); float x0 = head[j]; float x1 = head[j + half]; head[j] = x0 * cos_a - x1 * sin_a; head[j + half] = x0 * sin_a + x1 * cos_a; } } } /* Generate RoPE golden: one synthetic Q tensor [n_head*head_dim] and one * synthetic K tensor [n_head_kv*head_dim], rotated at each test position. The * pre-rotation tensors are shared across positions (written once); each position * gets its own rotated Q and K golden. */ static int gen_rope_golden(golden_rng *rng) { const int q_elems = QWEN35_N_HEAD * QWEN35_HEAD_DIM; const int k_elems = QWEN35_N_HEAD_KV * QWEN35_HEAD_DIM; float *q_in = malloc((size_t)q_elems * sizeof(float)); float *k_in = malloc((size_t)k_elems * sizeof(float)); float *q_work = malloc((size_t)q_elems * sizeof(float)); float *k_work = malloc((size_t)k_elems * sizeof(float)); int rc = -1; if (!q_in || !k_in || !q_work || !k_work) { fprintf(stderr, "gen_rope_golden: allocation failed\n"); goto cleanup; } for (int i = 0; i < q_elems; i++) q_in[i] = golden_rng_next_signed_unit(rng); for (int i = 0; i < k_elems; i++) k_in[i] = golden_rng_next_signed_unit(rng); if (write_golden("rope_q_input.bin", "rope_q_input", "f32", q_in, (size_t)q_elems, sizeof(float)) < 0) goto cleanup; if (write_golden("rope_k_input.bin", "rope_k_input", "f32", k_in, (size_t)k_elems, sizeof(float)) < 0) goto cleanup; printf("[2] Partial NEOX RoPE (replica of qwen35moe_attn.c:57-76)\n"); for (int p = 0; p < ROPE_N_POS; p++) { int pos = ROPE_POSITIONS[p]; memcpy(q_work, q_in, (size_t)q_elems * sizeof(float)); memcpy(k_work, k_in, (size_t)k_elems * sizeof(float)); rope_partial_replica(q_work, QWEN35_N_HEAD, QWEN35_HEAD_DIM, QWEN35_ROPE_DIM, pos, QWEN35_ROPE_THETA); rope_partial_replica(k_work, QWEN35_N_HEAD_KV, QWEN35_HEAD_DIM, QWEN35_ROPE_DIM, pos, QWEN35_ROPE_THETA); char qname[64], kname[64], qfile[64], kfile[64]; snprintf(qname, sizeof(qname), "rope_q_pos%d", pos); snprintf(kname, sizeof(kname), "rope_k_pos%d", pos); snprintf(qfile, sizeof(qfile), "rope_q_pos%d.bin", pos); snprintf(kfile, sizeof(kfile), "rope_k_pos%d.bin", pos); if (write_golden(qfile, qname, "f32", q_work, (size_t)q_elems, sizeof(float)) < 0) goto cleanup; if (write_golden(kfile, kname, "f32", k_work, (size_t)k_elems, sizeof(float)) < 0) goto cleanup; char qlabel[64]; snprintf(qlabel, sizeof(qlabel), "rope_q_pos%d", pos); if (report_f32(qlabel, q_work, (size_t)q_elems) < 0) goto cleanup; } rc = 0; cleanup: free(q_in); free(k_in); free(q_work); free(k_work); return rc; } /* ============================================================ * Kernel 3: QK-RMSNorm (REAL CPU function pn_vrms_norm_f32) * * Mirrors qwen35moe_qk_norm_heads, src/models/qwen35moe_attn.c:39-46, which * applies the public pn_vrms_norm_f32 (src/accel/simd.c:198) once per head with * a shared norm weight. We CALL the real pn_vrms_norm_f32 — gold standard. * ============================================================ */ static int gen_qknorm_golden(golden_rng *rng) { const int n_heads = QWEN35_N_HEAD; const int head_dim = QWEN35_HEAD_DIM; const int total = n_heads * head_dim; float *input = malloc((size_t)total * sizeof(float)); float *weight = malloc((size_t)head_dim * sizeof(float)); float *output = malloc((size_t)total * sizeof(float)); int rc = -1; if (!input || !weight || !output) { fprintf(stderr, "gen_qknorm_golden: allocation failed\n"); goto cleanup; } for (int i = 0; i < total; i++) input[i] = golden_rng_next_signed_unit(rng); /* Norm weight is shared across heads; GGUF bakes it as ~1.0 + small offset. */ for (int i = 0; i < head_dim; i++) weight[i] = 1.0f + 0.1f * golden_rng_next_signed_unit(rng); /* REAL per-head RMSNorm — exactly what qwen35moe_qk_norm_heads does. */ for (int h = 0; h < n_heads; h++) { const float *head_in = input + (size_t)h * head_dim; float *head_out = output + (size_t)h * head_dim; pn_vrms_norm_f32(head_in, weight, head_out, head_dim, QWEN35_RMS_NORM_EPS); } if (write_golden("qknorm_input.bin", "qknorm_input", "f32", input, (size_t)total, sizeof(float)) < 0) goto cleanup; if (write_golden("qknorm_weight.bin", "qknorm_weight", "f32", weight, (size_t)head_dim, sizeof(float)) < 0) goto cleanup; if (write_golden("qknorm_output.bin", "qknorm_output", "f32", output, (size_t)total, sizeof(float)) < 0) goto cleanup; printf("[3] QK-RMSNorm (REAL pn_vrms_norm_f32, per head)\n"); if (report_f32("qknorm_output", output, (size_t)total) < 0) goto cleanup; rc = 0; cleanup: free(input); free(weight); free(output); return rc; } /* ============================================================ * Kernel 4: MoE routing (cross-checked replica) * * Replicates the routed-gating path of qwen35moe_moe.c: * - qwen35moe_router_softmax (lines 47-61) softmax over n_expert logits * - qwen35moe_select_topk (lines 65-89) top-k by probability * - norm_topk_prob + scale (lines 133-136) renormalize selected weights * - shared-expert sigmoid gate (lines 179-183) sigmoid(gate_w . x) scalar * Output: top-k expert indices (i32), normalized+scaled weights (f32), and the * shared-expert gate scalar (f32). Routing indices must match EXACTLY on GPU. * ============================================================ */ /* sigmoid(x) — mirrors qwen35moe_sigmoid, qwen35moe_moe.c:37-40. */ static float moe_sigmoid_replica(float x) { return 1.0f / (1.0f + expf(-x)); } /* In-place softmax — mirrors qwen35moe_router_softmax, qwen35moe_moe.c:47-61. */ static void moe_router_softmax_replica(float *logits, int n_expert) { float max_gl = logits[0]; for (int e = 1; e < n_expert; e++) if (logits[e] > max_gl) max_gl = logits[e]; float sum_exp = 0.0f; for (int e = 0; e < n_expert; e++) { logits[e] = expf(logits[e] - max_gl); sum_exp += logits[e]; } float inv = 1.0f / sum_exp; for (int e = 0; e < n_expert; e++) logits[e] *= inv; } /* Top-k selection — mirrors qwen35moe_select_topk, qwen35moe_moe.c:65-89. The * selection-sort + first-seen tie-break MUST match the GPU bit-for-bit. */ static float moe_select_topk_replica(const float *probs, int n_expert, int k, int *top_idx, float *top_wt) { float wt_sum = 0.0f; for (int s = 0; s < k; s++) { int best = -1; float best_val = -1.0f; for (int e = 0; e < n_expert; e++) { int skip = 0; for (int j = 0; j < s; j++) if (top_idx[j] == e) { skip = 1; break; } if (!skip && probs[e] > best_val) { best = e; best_val = probs[e]; } } top_idx[s] = best; top_wt[s] = best_val; wt_sum += best_val; } return wt_sum; } static int gen_moe_routing_golden(golden_rng *rng) { const int n_expert = QWEN35_N_EXPERT; const int k = QWEN35_EXPERT_USED; const int n_embd = QWEN35_N_EMBD; float *logits = malloc((size_t)n_expert * sizeof(float)); float *probs = malloc((size_t)n_expert * sizeof(float)); float *shared_gate_w = malloc((size_t)n_embd * sizeof(float)); float *hidden_x = malloc((size_t)n_embd * sizeof(float)); int rc = -1; if (!logits || !probs || !shared_gate_w || !hidden_x) { fprintf(stderr, "gen_moe_routing_golden: allocation failed\n"); goto cleanup; } for (int e = 0; e < n_expert; e++) logits[e] = golden_rng_next_signed_unit(rng) * 3.0f; /* router logits */ for (int j = 0; j < n_embd; j++) shared_gate_w[j] = golden_rng_next_signed_unit(rng); for (int j = 0; j < n_embd; j++) hidden_x[j] = golden_rng_next_signed_unit(rng); /* Save raw router logits as the input golden (GPU recomputes from these). */ if (write_golden("moe_router_logits.bin", "moe_router_logits", "f32", logits, (size_t)n_expert, sizeof(float)) < 0) goto cleanup; if (write_golden("moe_shared_gate_weight.bin", "moe_shared_gate_weight", "f32", shared_gate_w, (size_t)n_embd, sizeof(float)) < 0) goto cleanup; if (write_golden("moe_shared_gate_input.bin", "moe_shared_gate_input", "f32", hidden_x, (size_t)n_embd, sizeof(float)) < 0) goto cleanup; /* Routed gating: softmax -> top-k -> norm_topk_prob -> expert_weights_scale. */ memcpy(probs, logits, (size_t)n_expert * sizeof(float)); moe_router_softmax_replica(probs, n_expert); int top_idx[QWEN35_EXPERT_USED]; float top_wt[QWEN35_EXPERT_USED]; float wt_sum = moe_select_topk_replica(probs, n_expert, k, top_idx, top_wt); float inv_wt = (wt_sum > 0.0f) ? (1.0f / wt_sum) : 0.0f; float scale = QWEN35_EXPERT_WT_SCALE * inv_wt; for (int s = 0; s < k; s++) top_wt[s] *= scale; /* Shared-expert scalar sigmoid gate: sigmoid(gate_w . x). */ float gate_logit = 0.0f; for (int j = 0; j < n_embd; j++) gate_logit += shared_gate_w[j] * hidden_x[j]; float shared_gate = moe_sigmoid_replica(gate_logit); if (write_golden("moe_topk_indices.bin", "moe_topk_indices", "i32", top_idx, (size_t)k, sizeof(int32_t)) < 0) goto cleanup; if (write_golden("moe_topk_weights.bin", "moe_topk_weights", "f32", top_wt, (size_t)k, sizeof(float)) < 0) goto cleanup; if (write_golden("moe_shared_gate_scalar.bin", "moe_shared_gate_scalar", "f32", &shared_gate, 1, sizeof(float)) < 0) goto cleanup; printf("[4] MoE routing (replica of qwen35moe_moe.c softmax/top-k/norm/gate)\n"); printf(" top-%d indices:", k); for (int s = 0; s < k; s++) printf(" %d", top_idx[s]); printf("\n"); if (report_f32("moe_topk_weights", top_wt, (size_t)k) < 0) goto cleanup; printf(" shared_gate_scalar = %+.6f (logit %+.6f)\n", shared_gate, gate_logit); { double wsum = 0.0; for (int s = 0; s < k; s++) wsum += top_wt[s]; printf(" weights sum (should be ~%.4f) = %.6f\n", (double)QWEN35_EXPERT_WT_SCALE, wsum); } rc = 0; cleanup: free(logits); free(probs); free(shared_gate_w); free(hidden_x); return rc; } /* ============================================================ * Kernel 5: SSM per-token delta-rule recurrence (cross-checked replica) * * Replicates qwen35moe_compute_recur_heads, src/models/qwen35moe_ssm.c:163-196 * (the static core recurrence). Per V-head h, per row j of the TRANSPOSED state * s[h*S*S + j*S + i] == S_h[i][j]: * (a) row[i] *= exp(g[h]) (ssm.c:180-182) * (b) pred = sum_i row[i]*k[h][i]; * delta = (v[h][j] - pred) * beta[h] (ssm.c:184-186) * (c) row[i] += k[h][i]*delta (ssm.c:188-190) * (d) y[h][j] = scale * sum_i row[i]*q[h][i] (ssm.c:192-193, read-after-write) * q,k are taken AS-IF already L2-normed + GQA-repeated to n_v_heads (this is the * input contract the GPU kernel receives); g is PRE-exp; beta is already sigmoid'd. * Uses the public pn_dot_f32 for the two reductions, exactly as the source does. * ============================================================ */ static void ssm_recur_replica(float *s, const float *q, const float *k, const float *v, const float *g, const float *beta, float *y, int n_v_heads, int S, float scale) { for (int h = 0; h < n_v_heads; h++) { float *sh = s + (size_t)h * S * S; const float *kh = k + (size_t)h * S; const float *qh = q + (size_t)h * S; const float *vh = v + (size_t)h * S; const float decay = expf(g[h]); const float beta_h = beta[h]; float *yh = y + (size_t)h * S; for (int j = 0; j < S; j++) { float *row = sh + (size_t)j * S; /* row j: S[i][j] over i */ for (int i = 0; i < S; i++) /* (a) scalar decay */ row[i] *= decay; float pred = pn_dot_f32(row, kh, S); /* (b) pred + delta */ float delta = (vh[j] - pred) * beta_h; for (int i = 0; i < S; i++) /* (c) rank-1 write */ row[i] += kh[i] * delta; yh[j] = scale * pn_dot_f32(row, qh, S); /* (d) read-after-write */ } } } static int gen_ssm_recur_golden(golden_rng *rng) { const int H = SSM_N_V_HEADS; const int S = SSM_STATE_SIZE; const int hs = H * S; /* q/k/v/y stream length */ const size_t state_n = (size_t)H * S * S; const float scale = 1.0f / sqrtf((float)S); float *q = malloc((size_t)hs * sizeof(float)); float *k = malloc((size_t)hs * sizeof(float)); float *v = malloc((size_t)hs * sizeof(float)); float *g = malloc((size_t)H * sizeof(float)); float *beta = malloc((size_t)H * sizeof(float)); float *s_in = malloc(state_n * sizeof(float)); float *s_out = malloc(state_n * sizeof(float)); float *y = malloc((size_t)hs * sizeof(float)); int rc = -1; if (!q || !k || !v || !g || !beta || !s_in || !s_out || !y) { fprintf(stderr, "gen_ssm_recur_golden: allocation failed\n"); goto cleanup; } /* q/k arrive L2-normed per head; emulate that so magnitudes are realistic. */ for (int h = 0; h < H; h++) { for (int d = 0; d < S; d++) { q[h * S + d] = golden_rng_next_signed_unit(rng); k[h * S + d] = golden_rng_next_signed_unit(rng); v[h * S + d] = golden_rng_next_signed_unit(rng); } /* L2-normalize q,k per head (mirror ssm.c:70-76 divisor form). */ float qsq = 0.0f, ksq = 0.0f; for (int d = 0; d < S; d++) { qsq += q[h*S+d]*q[h*S+d]; ksq += k[h*S+d]*k[h*S+d]; } float qinv = 1.0f / fmaxf(sqrtf(qsq), QWEN35_RMS_NORM_EPS); float kinv = 1.0f / fmaxf(sqrtf(ksq), QWEN35_RMS_NORM_EPS); for (int d = 0; d < S; d++) { q[h*S+d] *= qinv; k[h*S+d] *= kinv; } /* g is PRE-exp: small negative so exp(g) in (0,1), as the decay gate yields. */ g[h] = -0.05f - 0.1f * (golden_rng_next_signed_unit(rng) * 0.5f + 0.5f); beta[h] = moe_sigmoid_replica(golden_rng_next_signed_unit(rng)); } for (size_t i = 0; i < state_n; i++) s_in[i] = 0.1f * golden_rng_next_signed_unit(rng); /* Run the replica on a copy of the input state (state is updated in place). */ memcpy(s_out, s_in, state_n * sizeof(float)); ssm_recur_replica(s_out, q, k, v, g, beta, y, H, S, scale); if (write_golden("ssm_recur_state_in.bin", "ssm_recur_state_in", "f32", s_in, state_n, sizeof(float)) < 0) goto cleanup; if (write_golden("ssm_recur_q.bin", "ssm_recur_q", "f32", q, (size_t)hs, sizeof(float)) < 0) goto cleanup; if (write_golden("ssm_recur_k.bin", "ssm_recur_k", "f32", k, (size_t)hs, sizeof(float)) < 0) goto cleanup; if (write_golden("ssm_recur_v.bin", "ssm_recur_v", "f32", v, (size_t)hs, sizeof(float)) < 0) goto cleanup; if (write_golden("ssm_recur_g.bin", "ssm_recur_g", "f32", g, (size_t)H, sizeof(float)) < 0) goto cleanup; if (write_golden("ssm_recur_beta.bin", "ssm_recur_beta", "f32", beta, (size_t)H, sizeof(float)) < 0) goto cleanup; if (write_golden("ssm_recur_y.bin", "ssm_recur_y", "f32", y, (size_t)hs, sizeof(float)) < 0) goto cleanup; if (write_golden("ssm_recur_state_out.bin", "ssm_recur_state_out", "f32", s_out, state_n, sizeof(float)) < 0) goto cleanup; printf("[5] SSM recurrence (replica of qwen35moe_ssm.c:163-196)\n"); if (report_f32("ssm_recur_y", y, (size_t)hs) < 0) goto cleanup; if (report_f32("ssm_recur_state_out", s_out, state_n) < 0) goto cleanup; rc = 0; cleanup: free(q); free(k); free(v); free(g); free(beta); free(s_in); free(s_out); free(y); return rc; } /* ============================================================ * Kernel 6: SSM causal conv1d (k=4) + SiLU (cross-checked replica) * * Replicates qwen35moe_conv1d_silu, src/models/qwen35moe_ssm.c:110-135. Per * channel c over conv_dim: out[c] = SiLU(history . w[0..k-2] + qkv[c]*w[k-1]), * then roll the per-channel conv_state (drop oldest, append raw qkv[c]). * conv_state layout [conv_dim][k-1], weight row-major [conv_dim, k]. * ============================================================ */ static float ssm_silu_replica(float x) /* mirror qwen35moe_silu (ssm.c:60-63) */ { return x / (1.0f + expf(-x)); } static void ssm_conv1d_replica(const float *qkv, const float *weight, float *conv_state, float *out, int conv_dim, int k) { const int km1 = k - 1; for (int c = 0; c < conv_dim; c++) { const float *cw = weight + (size_t)c * k; float *cs = conv_state + (size_t)c * km1; float acc = qkv[c] * cw[km1]; /* ssm.c:125-127 */ for (int t = 0; t < km1; t++) acc += cs[t] * cw[t]; out[c] = ssm_silu_replica(acc); /* ssm.c:128 */ for (int t = 0; t < km1 - 1; t++) /* ssm.c:131-133 roll */ cs[t] = cs[t + 1]; cs[km1 - 1] = qkv[c]; } } static int gen_ssm_conv_golden(golden_rng *rng) { const int C = SSM_CONV_DIM; const int k = SSM_CONV_KERNEL; const int km1 = k - 1; const size_t st_n = (size_t)C * km1; const size_t w_n = (size_t)C * k; float *qkv = malloc((size_t)C * sizeof(float)); float *weight = malloc(w_n * sizeof(float)); float *st_in = malloc(st_n * sizeof(float)); float *st_out = malloc(st_n * sizeof(float)); float *out = malloc((size_t)C * sizeof(float)); int rc = -1; if (!qkv || !weight || !st_in || !st_out || !out) { fprintf(stderr, "gen_ssm_conv_golden: allocation failed\n"); goto cleanup; } for (int i = 0; i < C; i++) qkv[i] = golden_rng_next_signed_unit(rng); for (size_t i = 0; i < w_n; i++) weight[i] = 0.5f * golden_rng_next_signed_unit(rng); for (size_t i = 0; i < st_n; i++) st_in[i] = golden_rng_next_signed_unit(rng); memcpy(st_out, st_in, st_n * sizeof(float)); /* state rolls in place */ ssm_conv1d_replica(qkv, weight, st_out, out, C, k); if (write_golden("ssm_conv_qkv.bin", "ssm_conv_qkv", "f32", qkv, (size_t)C, sizeof(float)) < 0) goto cleanup; if (write_golden("ssm_conv_weight.bin", "ssm_conv_weight", "f32", weight, w_n, sizeof(float)) < 0) goto cleanup; if (write_golden("ssm_conv_state_in.bin", "ssm_conv_state_in", "f32", st_in, st_n, sizeof(float)) < 0) goto cleanup; if (write_golden("ssm_conv_out.bin", "ssm_conv_out", "f32", out, (size_t)C, sizeof(float)) < 0) goto cleanup; if (write_golden("ssm_conv_state_out.bin", "ssm_conv_state_out", "f32", st_out, st_n, sizeof(float)) < 0) goto cleanup; printf("[6] SSM conv1d+SiLU (replica of qwen35moe_ssm.c:110-135)\n"); if (report_f32("ssm_conv_out", out, (size_t)C) < 0) goto cleanup; if (report_f32("ssm_conv_state_out", st_out, st_n) < 0) goto cleanup; rc = 0; cleanup: free(qkv); free(weight); free(st_in); free(st_out); free(out); return rc; } /* ============================================================ * Kernel 7: SSM gated RMSNorm (REAL pn_vrms_norm_f32 + silu replica) * * Mirrors qwen35moe_gated_norm, src/models/qwen35moe_ssm.c:206-223: * pn_vrms_norm_f32(y_h, w, gated_h, S, eps) then gated_h[d] *= silu(z_h[d]). * Calls the REAL pn_vrms_norm_f32 per head (gold standard); silu mirrors source. * ============================================================ */ static int gen_ssm_gated_golden(golden_rng *rng) { const int H = SSM_N_V_HEADS; const int S = SSM_STATE_SIZE; const int hs = H * S; float *y = malloc((size_t)hs * sizeof(float)); float *weight = malloc((size_t)S * sizeof(float)); float *z = malloc((size_t)hs * sizeof(float)); float *gated = malloc((size_t)hs * sizeof(float)); int rc = -1; if (!y || !weight || !z || !gated) { fprintf(stderr, "gen_ssm_gated_golden: allocation failed\n"); goto cleanup; } for (int i = 0; i < hs; i++) { y[i] = golden_rng_next_signed_unit(rng); z[i] = golden_rng_next_signed_unit(rng); } for (int d = 0; d < S; d++) /* shared norm weight, ~1.0 + offset */ weight[d] = 1.0f + 0.1f * golden_rng_next_signed_unit(rng); for (int h = 0; h < H; h++) { /* mirror ssm.c:214-222 */ const float *yh = y + (size_t)h * S; const float *zh = z + (size_t)h * S; float *gh = gated + (size_t)h * S; pn_vrms_norm_f32(yh, weight, gh, S, QWEN35_RMS_NORM_EPS); for (int d = 0; d < S; d++) gh[d] *= ssm_silu_replica(zh[d]); } if (write_golden("ssm_gated_y.bin", "ssm_gated_y", "f32", y, (size_t)hs, sizeof(float)) < 0) goto cleanup; if (write_golden("ssm_gated_weight.bin", "ssm_gated_weight", "f32", weight, (size_t)S, sizeof(float)) < 0) goto cleanup; if (write_golden("ssm_gated_z.bin", "ssm_gated_z", "f32", z, (size_t)hs, sizeof(float)) < 0) goto cleanup; if (write_golden("ssm_gated_out.bin", "ssm_gated_out", "f32", gated, (size_t)hs, sizeof(float)) < 0) goto cleanup; printf("[7] SSM gated RMSNorm (REAL pn_vrms_norm_f32 * silu(z), per head)\n"); if (report_f32("ssm_gated_out", gated, (size_t)hs) < 0) goto cleanup; rc = 0; cleanup: free(y); free(weight); free(z); free(gated); return rc; } /* ============================================================ * Entry point * ============================================================ */ int main(void) { printf("qwen35moe golden-vector generator (seed=0x%08X)\n", (unsigned)GOLDEN_SEED); printf("output dir: %s/\n\n", GOLDEN_DIR); /* One RNG threaded through all kernels: the full output stream is determined * solely by the seed and the (fixed) order of generation calls below. */ golden_rng rng; golden_rng_init(&rng, GOLDEN_SEED); int rc = 0; rc |= gen_q3k_golden(&rng); printf("\n"); rc |= gen_rope_golden(&rng); printf("\n"); rc |= gen_qknorm_golden(&rng); printf("\n"); rc |= gen_moe_routing_golden(&rng); printf("\n"); rc |= gen_ssm_recur_golden(&rng); printf("\n"); rc |= gen_ssm_conv_golden(&rng); printf("\n"); rc |= gen_ssm_gated_golden(&rng); printf("\n%s\n", rc == 0 ? "ALL GOLDEN VECTORS GENERATED OK" : "GENERATION FAILED — see errors above"); return rc == 0 ? 0 : 1; }