Skip to content

feat(qwen4): add Qwen3.8-Flash-Next PLE n-gram golden - #1155

Draft
sysuTobo wants to merge 13 commits into
pegainfer-project:mainfrom
sysuTobo:feat/qwen38fn-ple-golden
Draft

sysuTobo wants to merge 13 commits into
pegainfer-project:mainfrom
sysuTobo:feat/qwen38fn-ple-golden

Conversation

@sysuTobo

@sysuTobo sysuTobo commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Reference golden for the PLE n-gram block of Qwen3.8-Flash-Next (#1103).

  • tools/accuracy/dump_qwen38fn_ple_golden.py — dumps Qwen4ExpTextPLELayer + Qwen4ExpTextNGramEmbedding (transformers 5.16.1 qwen4_exp): the real hashed n-gram ids, the projection activations, and the injected block output (grouped (1+w) norms, signed-sqrt gate, dilated conv).
  • test_data/qwen38fn-ple-golden.safetensors — 2 cases: no EOS, and a mid-sequence EOS that exercises the segment reset. 24 tensors / 1.41 MiB; eos_token_id is stored so consumers need not hardcode it, and metadata is a single JSON key (format_version 2).
  • Geometry: real frozen-config geometry — hidden 2560, hc_count 4 (10240-wide stream), ple_embed_dim 2560, ngram_size 3, 16 heads × 160, and the real per-head prime vocabularies/offsets/multipliers. Moved boundaries: (a) the 95 GiB n-gram table is stubbed (not storable), so only the hash→id mapping is real; the embedding activation is a lookup into that stub table and is not stored (the projection outputs it feeds are); (b) the key_proj [10240,2560] (~52 MB) and value_proj [2560,2560] (~13 MB) weights are not stored — their outputs are recorded; (c) every other shape is real (there is no final GEMM).

Consumer

pegainfer-qwen4/tests/ple_ngram_replay.rs reads the fixture: a device-free parity gate in a crate with no CUDA dependency, so it needs no device, no weights and no Python. It runs in CI — cpu-unit-tests now runs -p pegainfer-qwen4 --tests and cpu-clippy covers the crate with --all-targets -D warnings — which is what makes "the golden has a reader" enforceable rather than aspirational.

$ cargo test --release --locked -p pegainfer-qwen4 --test ple_ngram_replay -- --nocapture
case0: S=8 eos_at=[] heads=16 ids=128/128 exact
case1: S=12 eos_at=[5] heads=16 ids=192/192 exact
case0: S=8 out max_abs=0.000e0 mean_abs=0.000e0 bit_identical=81920/81920 tol=1.6e-2 dilation=3 signed_sqrt=true
case1: S=12 out max_abs=0.000e0 mean_abs=0.000e0 bit_identical=122880/122880 tol=1.6e-2 dilation=3 signed_sqrt=true
test result: ok. 6 passed; 0 failed

What it proves. Two ports of modeling_qwen4_exp.py, both driven from stored inputs only:

  • Exact n-gram hash replay (Qwen4ExpTextNGramEmbedding): the EOS-padded context window with the cummax segment reset, the per-position layer_multipliers XOR fold, and the per-head remainder(ngram_heads_vocab_sizes[h]) + ngram_heads_offsets[h], with heads laid out (order, head) — bigram block first, then trigram. Compared with i64 equality at zero tolerance: 320/320 ids exact. Integer math, so a deviation is a bug, not noise.
  • PLE block replay (Qwen4ExpTextPLELayer) from the recorded key/value projections and hidden: grouped (1+w) RMSNorm over 4×2560, dot/sqrt(2560) gate, signed sqrt, sigmoid × value broadcast over the 4 streams, (1+w) norm, then silu(causal dilated depthwise conv(kernel 4, dilation 3, 9-slot zero state)) added back to the gated value. Bit-identical on every stored output element (81920/81920 and 122880/122880).

Tolerance. The measured residual is 0.0, so OUT_MAX_ABS = 1.6e-2 is headroom for the one non-portable op in the chain, expf (inside sigmoid/silu); sqrt, division and multiplication are IEEE correctly-rounded and the f64 reductions are the test's own. Injecting a whole bf16 ulp into every sigmoid and every silu output — far more than any libm disagreement — moves out by at most 7.8e-3, i.e. 2 bf16 ulps at the largest stored |out| (0.8438). The bound is 2× that and still 17× below the weakest negative control. Reaching bit-exactness required emulating the gate's double rounding: the reference rounds the group sum to bf16 before the 1/sqrt(hidden) divide and again after it — collapsing the two is worth ~1 ulp on every gate and 3.9e-3 on out.

Negative controls (each its own test, each asserting the comparison fails):

control case0 (S=8) case1 (S=12, EOS at 5) margin
drop the EOS window reset 0/128 ids change (no EOS to reset) 8/192 ids change only the trigram heads of the row right after the EOS — a targeted control, not a blanket break
swapped bigram/trigram head order 128/128 ids change 192/192 ids change every id, by construction: disjoint offset ranges, each remainder smaller than the gap
unsigned sqrt (drop the sign restore) max_abs 2.686e-1 max_abs 3.320e-1 21× the tolerance
dilation 1 instead of ngram_size max_abs 7.881e-1 max_abs 7.891e-1 49× the tolerance

Provenance. Every load parses the qwen4_ple_golden manifest, asserts format_version == 2, and asserts config_sha256 equals the sha256 of pegainfer-qwen4/tests/frozen_config.json — whose own hash src/config.rs::the_committed_fixture_is_the_frozen_revision pins to the frozen revision, so the chain is fixture → config → checkpoint. A missing fixture panics: no skip path, no env override. The loader also cross-checks the config's eos_token_id against the stored scalar and every tensor width against the config geometry, and requires caseN_rows to be the identity (this replay is whole-sequence).

Still ungated on the host — the n-gram table lookup/gather that turns ngram_ids into 160-dim head vectors needs the 95 GiB table's loader (#1106). The GPU dilated depthwise conv now runs on the device (below). The decode-side states (the 2-token id window and the 9-slot conv state) stay out of scope for both halves, since the recording is whole-sequence only.

Device path (commits 6115dd89 → 39cf30f0 → f1ebd3d8)

  • The hash is production host code. pegainfer_qwen4::ngram::NgramHasher (pegainfer-qwen4/src/ngram.rs) owns the EOS-padded history, the cummax segment reset (returned as data, so a resumed decode can carry it), the mul-xor fold, the per-head prime remainder + offset, and the order-major head layout. The exact-i64 gate calls it directly, so the zero-tolerance comparison pins production code rather than a test-local copy; the two hash controls compose the same primitives with one knob each and are asserted to reproduce the production ids at the reference settings, so a control cannot drift onto a private copy of the hash and still pass. The crate stays CUDA-free — this is integer host math; the device gather that consumes it is what needs a GPU.
  • The block runs on the device. Three kernels in pegainfer-kernels/csrc/qwen4/qwen4_ple_block.cu — the branch-major split, the per-branch gate (every bf16 rounding point of the reference: the double rounding around the 1 / sqrt(hidden_size) divide, the signed sqrt's clamp_min floor and torch.sign at zero, the sigmoid), and the dilated causal depthwise conv with its residual add — behind the line's qwen4 feature on pegainfer-kernels (default builds are unchanged: cargo check -p pegainfer-kernels is green and reports the feature disabled; the new .cu is not compiled). The gate drives ops::ple_block_into, the production entry; ops::ple_block_variant_into's only callers are the gate's three controls, which turn one scalar of the same launches rather than running a second copy of the kernels.
  • The grouped norms reuse the existing batched op. Each of the three grouped (1+w) RMSNorms is 4 per-branch calls of the existing rms_norm_batched_offset_cuda (twelve launches total, plus the branch-major split in front of them), because that entry point's row stride is its hidden_dim and this stream's four groups are interleaved row-major. If/when test(qwen4): pin the grouped (1+w) RMSNorm to a grouped weight slice #1158's single-launch grouped entry lands, production switches to it; bit-identical on that degenerate path.
$ ./docker/dev.sh run cargo test --release --locked -p pegainfer-kernels --features qwen4 --test qwen4_ple_block_parity -- --nocapture
reference case 0: S=8  stream=4x2560 out max_abs=0.000e0 bit_identical=81920/81920  bound fraction 0.0000 of 1.6e-2
reference case 1: S=12 stream=4x2560 out max_abs=1.221e-4 bit_identical=122879/122880 bound fraction 0.0076 of 1.6e-2
parity over 2 cases and 204800 block outputs: max abs 1.221e-4, worst bound fraction 0.0076 of 1.6e-2,
  204799 of 204800 bit-identical to the golden
test result: ok. 6 passed; 0 failed

Measured per case (sm_120, CUDA 12.9.1, re-run after the final commit): case 0 (S=8) is bit-identical on all 81920/81920 stored outputs, max_abs 0.000e0; case 1 (S=12) is bit-identical on 122879/122880, the single deviation 1.221e-4 — one bf16 ulp at that element's magnitude (0.024292 against 0.024414), 0.0076 of the bound.

Device tolerance. The bound is the same 1.6e-2 as the host gate's, so both halves of this consumer read against one scale. Derivation: one flipped bf16 anywhere upstream reaches out as at most about one bf16 ulp of out at that element (the gate is a single scalar per (row, branch); the conv, its silu — derivative bounded by ~1.1 — and the residual add cannot amplify further); one ulp at the largest stored element (0.8438) is 2^-8 = 3.9e-3, and the bound is four of those. It covers the three places the device path cannot be the host path: rsqrt.approx.ftz.f32 in the existing batched norm (~1.4e-7 relative error flips the bf16 rounding of roughly one normed element in 30000 — an instrumented bring-up run found exactly two across case 0's three norms, 245760 outputs, each one bf16 ulp of itself and the gated stream bit-identical), a different reduction tree (f32 butterflies vs the reference's f32 tree vs the host's f64 exact sum — within an f32 ulp or two, four orders below the bf16 step the result rounds to), and expf (already budgeted on the host as a whole bf16 ulp in every sigmoid/silu, worth ≤ 7.8e-3). It cannot be tightened towards the measured 1.2e-4: rsqrt.approx, expf and reduction order are architecture- and libm-defined, so another GPU flips a different set of elements, possibly including one near 0.8438 where a single ulp is 3.9e-3 — the flip count is a discrete random variable, not a constant.

Device negative controls — each turns exactly one scalar of the same launches, and every case is asserted to leave the bound (a control that passed would mean the gate cannot see that fault):

control worst deviation margin
full-stream gate dot (10240-wide instead of per-branch 2560) 2.749e-1 17× the bound
unsigned sqrt (drop the sign restore) 3.320e-1 21× the bound
dilation 1 instead of ngram_size 7.891e-1 49× the bound

Two more device-gate tests never skip and need no GPU: provenance — panics, not skips, so a fixture from another checkpoint or another semantics fails — and the entry points' refusal contract, with every compiled-constant guard pinned and zero-extent positive controls first so a refuse-everything entry point cannot make the refusal matrix vacuous.

What remains ungated. The 102.4 GB n-gram table lookup/gather itself — and the UVA / in-graph question of feeding a table that size to a kernel — is #1106; both gates start at the block's post-projection inputs. The decode-side states (the 2-token id window and the 9-slot dilated conv state) stay ungated because the recording is whole-sequence. No CI job enables the kernels' qwen4 feature yet: the device gate runs only under the manual command quoted above, while the device-free half (the hash gate and CPU replay in pegainfer-qwen4) is in the CPU CI jobs. Recorded as inert in the gate's header rather than papered over: the gate's clamp_min(1e-6) floor and sign(0) branch are inert at the smallest |pre_gate| either case produces (0.0232), seq_len 1 is compared against nothing, and the ops layer's extent guards need a device to reach.

Branch state. The branch was rebased onto current main (base 77d78cae); the conflicts described in the earlier "Branch state note" comment are resolved — that comment predates the rebase. GitHub reports the PR MERGEABLE/CLEAN.

Validation

The dump asserts nothing. Every stored value is the reference's own output at the real geometry — the hashed ids and the key/value projections captured through forward hooks, plus the module's own returned block output. There is nothing to re-check in the dumper: the fixture is the reference, and it is the consumer gate that compares a PLE kernel against it.

$ python tools/accuracy/dump_qwen38fn_ple_golden.py --out test_data/qwen38fn-ple-golden.safetensors   # venv: torch 2.14.1+cpu, transformers 5.16.1
case0: S=8 eos=[] rows=8 heads=16
case1: S=12 eos=[5] rows=12 heads=16
wrote test_data/qwen38fn-ple-golden.safetensors (1.41 MiB, 24 tensors)

Regenerating the committed fixture is byte-identical.

Fixture + dump script, plus the golden's two readers: the device-free consumer gate (pegainfer-qwen4/tests/ple_ngram_replay.rs) and its CI wiring, the production host hash module (pegainfer-qwen4/src/ngram.rs), the PLE block kernels/FFI/ops behind pegainfer-kernels' additive qwen4 feature with their gate (pegainfer-kernels/tests/qwen4_ple_block_parity.rs), and the bring-up doc rows. No serving path calls any of it yet — the line still refuses to launch.

Refs #1103

@sysuTobo

Copy link
Copy Markdown
Contributor Author

Code review

范围:2 文件、无运行时数据流(dump 脚本 + fixture),逐文件认领:tools/accuracy/dump_qwen38fn_ple_golden.py(191 行,新增)、test_data/qwen38fn-ple-golden.safetensors(23 张量,BF16/I32/I64/U8)。
正向路径(已实测确认):fixture header 与脚本写入表逐一相符(23 张量,provenance 三件套齐,ref_transformers=5.16.1);本机重跑与 committed fixture 逐字节相同;缩几何下素数词表仍走真实公式(_find_nth_prime_after(199, h+1) → 16 个素数 211..293,sum 4048 → pad 4096,head_dim 32,与 table=(4096,32) 读数一致);dtype 语义正确——layer_multipliers 实测 4.3e15/8.4e15/1.3e15 量级,I64 必需且已用 I64,input_ids I32 安全(值 <1024;真实 vocab 248320 亦 <2^31),哈希中间值不入库;rederive 对注入块组合(gate/norm/conv 合成)的守卫有效:把参考的 signed-sqrt 门突变成 plain sqrt,重推导断言实测 RED。

突变测试(把参考实现性质改坏,看断言红不红;A/C/D 突变后 fixture 输出实测已改变,但脚本全绿放行):

ID 级别 问题 证据
H1 H EOS 段重置断言空转:扰动落在 token 0,而 n-gram 窗口半径=3(ngram_size),token 0 只影响位置 0–2 的 embedding;断言区却从 eos_pos+1=6 起。无论重置语义在不在,位置 ≥6 都不含 token 0——实测把 _shift_right_ignore_eos 突变成无重置的 plain shift 后全流程 GREEN,且 case1_ngram_emb/case1_out 已静默变坏写出。PR body 声称的"asserts the EOS segment reset"目前不成立 tools/accuracy/dump_qwen38fn_ple_golden.py:159-171
M1 M dilated conv 未钉住(headline trap 无守卫):short_conv/rederive 直接读 module.conv1d 与 module.short_conv_state_len,与被测模块同源;参考若一致性地把 dilation 改成 1(state_len 同步变 3),所有断言实测 GREEN,而 fixture 的 conv 语义已变。无处断言 dilation == ngram_size tools/accuracy/dump_qwen38fn_ple_golden.py:92-98,110
M2 M mul-xor 哈希无独立重推导:embedding 直接取自 ng(input_ids)(参考自身),再喂给 rederive——同源比较。实测把哈希突变成 mul-add 后全流程 GREEN、golden 已变。姊妹约定(#1150)是内部量必须在脚本内重推导并逐位断言 tools/accuracy/dump_qwen38fn_ple_golden.py:152,156
M3 M config_digest 漏掉影响输出的常量:Config.seed=1234(经 _build_layer_multipliers 直接决定哈希乘子)、EOS_TOKEN_ID(重置与窗口填充)、RMS_EPS 均不在 canon 串里——改任何一个,golden 全变而 digest 不变,provenance 断言形同虚设。(家族级备注:#1150 的 canon 同样漏 eps,不在本 PR 修) tools/accuracy/dump_qwen38fn_ple_golden.py:74-75,79-85
L1 L eos_token_id 未入 fixture,消费者只能硬编码 0;#1150 约定是可推导标量也入库(per-case budget/ratio) tools/accuracy/dump_qwen38fn_ple_golden.py:127-141
L2 L EOS case 覆盖单薄:仅 1 个 mid-EOS。开头 EOS 数值上等价于 EOS-padded 初始上下文(可不测);结尾 EOS 无 post-EOS 位置、不练重置(可不测);连续/双 EOS(第二次重置边界、cummax 语义)确实缺 tools/accuracy/dump_qwen38fn_ple_golden.py:58
L3 L 仅覆盖 cache=None 全序列路径:两个解码续推状态(conv_states[2] 前 2-token id 窗口、conv_states[1] 长度 (K−1)×dil=9 的 dilated conv 状态)未入 fixture,decode gate 需另行 dump——建议 docstring 注明边界 tools/accuracy/dump_qwen38fn_ple_golden.py:152-153
L4 L 无构造性哈希碰撞 case:16 头 × 素数 211–293、每 case 8–12 个位置,碰撞按生日悖论必然已发生于 golden 值中(by design),且 gate 回放逐位值时任何哈希错误都会红——构造碰撞增量≈0,不修 tools/accuracy/dump_qwen38fn_ple_golden.py:143-147
L5 L FORMAT_VERSION 缺姊妹脚本的"layout 变更时 bump、gate 加载即断言"注释 tools/accuracy/dump_qwen38fn_ple_golden.py:55
L6 L Config.layer_types 不被 PLE/NGram 任何代码读取(仅 decoder layer 用),与 docstring "exactly the attributes the PLE block reads" 不符 tools/accuracy/dump_qwen38fn_ple_golden.py:62,76

最小加固:修 H1(扰动点改到 eos_pos-1——它落在 post-EOS 位置的重置窗口半径内,才能区分有无重置;并加正对照断言"pre-EOS embedding 必须变",防空转)、M1(断言 conv1d.dilation == (ngram_size,) 与 short_conv_state_len == (K-1)*ngram_size)、M2(纯 Python 独立重推导哈希——EOS 重置窗口 → 逐 order mul-xor → 逐头 remainder+offset,逐位断言 embedding)、M3(canon 补 seed/eos/eps)、L1、L5、L6;L2 随 H1 修复补一个双 EOS case;L3/L4 记为已知边界写入 docstring。修复将改变 config_sha256 与张量集,fixture 需重生成(本机可逐字节复现,重生成安全)。

@sysuTobo

Copy link
Copy Markdown
Contributor Author

修改措施

Pushed 27139252。

  • H1 已修:EOS 重置属性断言的扰动点从 token 0 改为 eos_pos-1(落在 post-EOS 位置的重置窗口半径内,有无重置语义在此处可区分),并加正对照断言"扰动必须改变 pre-EOS embedding"防空转;eos_pos=0 显式跳过(与 EOS-padded 初始上下文数值等价)。tools/accuracy/dump_qwen38fn_ple_golden.py:219-241
  • M1 已修:构造后断言 conv1d.dilation == (ngram_size,) 与 short_conv_state_len == (kernel-1)*dilation,一致性 dilation=1 突变现在会红。tools/accuracy/dump_qwen38fn_ple_golden.py:172-179
  • M2 已修:新增 rederive_embeddings —— 纯 Python 独立重推导哈希(EOS 重置窗口 → 逐 order mul-xor → 逐头 remainder+offset,order o 持有 heads [(o-2)*8,(o-1)*8)、混 shifts 0..o-1),逐位断言与模块 embedding 相等;xor→add 突变现在会红。tools/accuracy/dump_qwen38fn_ple_golden.py:125-156,213-215
  • M3 已修:digest canon 补 eos / eps / seed(config.seed=1234 经 splitmix 决定 layer_multipliers,提为具名常量 NGRAM_SEED)。tools/accuracy/dump_qwen38fn_ple_golden.py:61,90-97
  • L1 已修:fixture 新增 eos_token_id I32 标量,消费者无需硬编码。tools/accuracy/dump_qwen38fn_ple_golden.py:186
  • L2 部分已修:CASES 增加双 EOS case (14, eos 4/9),覆盖第二重置边界与 cummax 跨段语义;开头/结尾 EOS 仍不测——开头 EOS 与 EOS-padded 初始上下文数值不可区分,结尾 EOS 无 post-EOS 位置、不练重置,理由已写入注释。tools/accuracy/dump_qwen38fn_ple_golden.py:68-70,225-226
  • L3 接受为已知边界:仅 cache=None 全序列路径;两个解码续推状态(前 2-token id 窗口、长度 9 的 dilated conv 状态)不入本 fixture,docstring 已注明"需另行 dump"。tools/accuracy/dump_qwen38fn_ple_golden.py:25-27
  • L4 不修:16 头 × 素数 211–293、每 case 8–14 个位置,碰撞按生日悖论必然已在 golden 值中(by design);gate 回放逐位值时任何哈希错误都会红,构造性碰撞 case 无增量。
  • L5 已修:FORMAT_VERSION 补姊妹约定的 bump/assert 注释。tools/accuracy/dump_qwen38fn_ple_golden.py:65-66
  • L6 已修:删除 PLE 块不读取的 Config.layer_types,docstring "exactly the attributes" 恢复成立。tools/accuracy/dump_qwen38fn_ple_golden.py:73-87

复验:fixture 重生成于 torch 2.14.1+cpu / transformers 5.16.1 / CPU——29 张量(3.08 MiB),provenance = 5.16.1 / config_sha256=5acc59a2… / format_version=1;3 case 重推导逐位自洽(hash_ok=True rederivation_ok=True):case0 S=8 eos=[]、case1 S=12 eos=[5]、case2 S=14 eos=[4,9],table=(4096,32);连跑两次逐字节相同。突变复验:去掉 EOS 重置 → RED、signed-sqrt→sqrt → RED、一致性 dilation=1 → RED、xor→add → RED;另在中性化哈希重推导后单独验证 H1 属性断言,去掉 EOS 重置仍 RED(属性断言不再依赖第一道守卫)。

家族级备注(不改兄弟 PR):#1150 的 config_digest canon 同样未含 rms_norm_eps,后续姊妹脚本(#1151–#1154)如涉及 norm eps 建议一并纳入 digest。

sysuTobo added a commit to sysuTobo/pegainfer that referenced this pull request Oct 12, 2026
Address the family-level finding from the pegainfer-project#1153/pegainfer-project#1155 reviews: RMS_EPS
feeds Config.rms_norm_eps but was missing from the config_digest canon,
so editing eps changed the golden while the stored provenance stayed
green. Canon now carries eps like the sibling scripts.

Fixture regenerated (66 tensors): config_sha256 b9fd359a -> 20cb29e2;
59 tensors bitwise stable; the six case*_scores carry bf16 ULP-level
drift (<=4.8e-4) from re-dumping under torch 2.14.1+cpu / transformers
5.16.1 instead of the original torch 2.5.1+cu121 environment. Second
same-environment run is byte-identical; all six cases pass mask_ok and
the reload assertions.

Signed-off-by: bohaotu <bohaotu@pony.ai>
FeathBow added a commit that referenced this pull request Oct 13, 2026
* feat(accuracy): add Qwen3.8-Flash-Next QSA indexer golden

Dump the QSA token indexer reference (transformers 5.16.1 `qwen4_exp`) into a
fixture the Stage 1 kernel gate replays: per-block scores + selected token
indices for 6 cases, covering short/medium inputs, the budget-boundary
truncation (block_topk reached with a shrunk budget) and the always-kept tail.
The re-derivation is validated against the reference module's own mask in-dump.

Signed-off-by: bohaotu <bohaotu@pony.ai>

* fix(accuracy): stamp QSA indexer golden provenance + explicit score validity

Address PR review: add `format_version` / `config_sha256` / `ref_transformers`
so a consumer can assert the oracle's origin, and an explicit `scores_valid`
mask so the NaN-padded block scores cannot be mis-consumed (no silent
nan_to_num pass). L1 (real-budget truncation) kept as a known bound.

Signed-off-by: bohaotu <bohaotu@pony.ai>

* docs(accuracy): trim QSA indexer golden dump comments

Drop references to local-only planning docs and tighten the docstring/comments.
No behavior change; fixture is unaffected.

Signed-off-by: bohaotu <bohaotu@pony.ai>

* fix(accuracy): include norm eps in the QSA indexer golden config digest

Address the family-level finding from the #1153/#1155 reviews: RMS_EPS
feeds Config.rms_norm_eps but was missing from the config_digest canon,
so editing eps changed the golden while the stored provenance stayed
green. Canon now carries eps like the sibling scripts.

Fixture regenerated (66 tensors): config_sha256 b9fd359a -> 20cb29e2;
59 tensors bitwise stable; the six case*_scores carry bf16 ULP-level
drift (<=4.8e-4) from re-dumping under torch 2.14.1+cpu / transformers
5.16.1 instead of the original torch 2.5.1+cu121 environment. Second
same-environment run is byte-identical; all six cases pass mask_ok and
the reload assertions.

Signed-off-by: bohaotu <bohaotu@pony.ai>

* fix(accuracy): record the QSA indexer golden from the reference itself

Signed-off-by: Feathbow <feathbow@gmail.com>

* fix(accuracy): cover the real QSA budget boundary in the indexer golden

Signed-off-by: Feathbow <feathbow@gmail.com>

---------

Signed-off-by: bohaotu <bohaotu@pony.ai>
Signed-off-by: Feathbow <feathbow@gmail.com>
Co-authored-by: Feathbow <feathbow@gmail.com>
sysuTobo added a commit to sysuTobo/pegainfer that referenced this pull request Oct 16, 2026
The golden recorded in pegainfer-project#1155 had no reader, so it proved nothing on its own:
issue pegainfer-project#1103's rule is that a fixture lands with the code that checks it. Add
that consumer as a device-free integration test, keeping this crate's
no-CUDA property so it runs with no device, no weights and no Python.

Two halves, matching the two things a PLE port can get wrong:

- The mul-xor n-gram hash is recomputed from `input_ids` and compared with i64
  equality, zero tolerance: the EOS-resetting context window, the bigram/
  trigram head layout (16 heads = 2 orders x 8) and the per-head prime modulus
  and table offset all have to be right for one id to land. 128/128 and 192/192
  ids exact on the S=8 and S=12 cases.
- The block is replayed from the recorded `key`/`value` projections and is
  bit-identical on all 81920 + 122880 stored bf16 outputs, because the port
  emulates each bf16 rounding point of the reference chain - including the
  gate's double rounding, where the group sum is rounded before the
  1/sqrt(hidden) divide and again after it.

`OUT_MAX_ABS` is sized from measurement, not guessed: the residual is 0.0, and
injecting a whole bf16 ulp into every `sigmoid` and `silu` output - the chain's
only libm-dependent op, far more than any platform disagreement - moves `out`
by at most 7.8e-3. 1.6e-2 is 2x that and still 17x below the weakest negative
control. Four controls are each asserted to fail: dropping the EOS window reset
(moves only the 8 trigram heads of the row after the EOS, and leaves the
EOS-free case untouched), dilation 1 (0.79), unsigned sqrt (0.27) and a swapped
bigram/trigram head order (every id).

Provenance is checked on every load and never skipped: the manifest must parse,
carry format_version 2, and name the frozen config with the sha256 that file
hashes to. The config's eos_token_id and every tensor width are cross-checked
against the stored scalars and shapes, and `caseN_rows` must be the identity -
this replay is whole-sequence, so a row selection would fail loudly rather than
replay against the wrong conv state.

Still ungated, both landing with the device side in pegainfer-project#1106: the n-gram table
gather that turns `ngram_ids` into head vectors, and the GPU dilated conv.

Signed-off-by: bohaotu <bohaotu@pony.ai>
sysuTobo added a commit to sysuTobo/pegainfer that referenced this pull request Oct 16, 2026
The bring-up doc's TL;DR and "What landed" still described the crate as an
identity probe plus one config validation, and never mentioned the PLE golden
pegainfer-project#1155 records or the gate that now reads it - so a reader could not tell that
the block's hash and arithmetic are already pinned on the host, or what the
fixture deliberately leaves out.

Add the landed bullet with the two boundaries the fixture cannot carry (the
102.4 GB table is not stored, so the replay starts at the block's
post-projection inputs; the recording is whole-sequence, so the decode-side
states stay ungated), the note that the hash reads `layer_multipliers` rather
than recomputing them - which is what keeps `seed` unmodelled - and the CI
coverage that makes the no-device property enforceable. Refresh the TL;DR and
the index row to match.

Signed-off-by: bohaotu <bohaotu@pony.ai>
@github-actions github-actions Bot added qwen4 Qwen4-architecture model crate (pegainfer-qwen4) needs-rebase Conflicts with main, needs a rebase labels Oct 16, 2026
@github-actions

Copy link
Copy Markdown

This pull request has merge conflicts with main. Please rebase it so it can be reviewed and merged.

@sysuTobo

Copy link
Copy Markdown
Contributor Author

Code review

自审对象:本次追加到 #1155 的三个 commit(df13d063 消费端测试、799ba946 CI 接线、e53a83e5 文档),基线 fe3e7dda。审计方式:把 replay 当成一条数据流逐段对着参考实现(transformers 5.16.1 models/qwen4_exp 的 Qwen4ExpTextNGramEmbedding / Qwen4ExpTextPLELayer)模拟,并对每个 claim 用真实数字或搜索取证。结论:正向路径全部符合预期,共 12 条发现(4 条 M、8 条 L),9 条已修,3 条明确保留。

ID 级别 一句话 证据(文件:行)
R1 M rem_euclid 的注释断言"折叠值可能为负",但实测 40/40 折叠值恒非负(乘积按构造不溢出 + XOR 保号),注释描述了一个数据永不触发的条件 pegainfer-qwen4/tests/ple_ngram_replay.rs:525(改前);实测 negative=0 / non-negative=40
R2 M 头注声称 replay 用的 per-head 素数/偏移"就是 NgramTable::derive 推出的那份",但该单测只钉了首/末素数、单调性与总量;config 模块私有,没有任何测试把 fixture 里 16 个模数与派生值逐一比对 → claim 强于证据 pegainfer-qwen4/src/config.rs:599-615 vs ple_ngram_replay.rs:28-30(改前)
R3 M 门落地了却在 CI 里零覆盖:pegainfer-qwen4 既不在 cpu-clippy 的包列表,CPU 测试作业也没有它的步骤,新增 gate 与既有 40 个单测都只在开发机上跑过 .github/workflows/ci.yml:142-157,186-192(改前);grep -n qwen4 .github/workflows/ci.yml 改前 0 命中
R4 M 文档未同步:bring-up 的 TL;DR 仍写"Landed so far: an identity probe and one typed config validation",What landed 三条 bullet 与 docs/index.md 路由行都没提 #1155 的 golden 和本次 reader docs/models/qwen4/bring-up.md:6-8,48-108;docs/index.md:68(均为改前)
R5 L assert!(cases.len() >= 2, "expected the S=8 and S=12 cases") 的文案声称钉住了序列长度,实际只数了组数 ple_ngram_replay.rs:368(改前文案)
R6 L loader 断言文案写 "increasing primes",但代码只查了 >1、严格递增、偏移连续,没查素性 ple_ngram_replay.rs:258(改前文案)
R7 L gate 原写成"乘 1/sqrt(hidden) 的倒数",与参考的除法舍入不同;本 fixture 上恰好同样 bit-exact,属潜在偏差 ple_ngram_replay.rs:106,613(改前为 * inv_sqrt_hidden)
R8 L .expect("{name}_seq_len must be positive") 里 {name} 在 expect 中不会插值,且 seq_len == 0 会被当成合法值放过 ple_ngram_replay.rs:334(改前)
R9 L hash 比对里 .enumerate() 的 index 未被使用,纯噪声 ple_ngram_replay.rs:687-691(改前)
R10 L knob 结构体命名 Hash / Block 与 std::hash::Hash 同名,易误读 ple_ngram_replay.rs:406,424
R11 L Fixture.eos 与 Geometry.eos 是同一量的两个真相源(D4 形状) ple_ngram_replay.rs:233,266
R12 L 是否在测试里重算 16 个素数来交叉校验 fixture?会把 nth_prime_after 复制成第二份实现(D1) pegainfer-qwen4/src/config.rs:500-528

已逐项核对为符合预期的部分(附取证方式,均为直接搜索/实测,非外推):

  • fixture 的唯一读者就是本测试:grep -rn "qwen38fn-ple-golden" .(排除 worktree 元数据与 target)只命中 ple_ngram_replay.rs:1,217;dump 脚本走 --out 不写死路径。✓ 无重复实现(D1)。
  • 没有可复用的宿主端 grouped RMSNorm:grep -rln "rmsnorm|rms_norm" --include=*.rs . 命中 pegainfer-core/src/ops.rs 等,全是设备侧算子封装;本 crate 按设计无 CUDA 依赖(src/lib.rs:5-11),不能用。✓ 自建 grouped_norm(:543)不是重复。
  • 缓冲区全覆盖:三处 vec![bf16::ZERO; n] 都被完整写入 —— gated 由 row×group×j 覆盖 seq_len×hc×hidden = seq_len×width,out 由 channel×row 覆盖,grouped_norm 由 chunks(width)×step_by(hidden)×j 覆盖(width % hidden == 0 由 width = hc*hidden 保证)。✓
  • 无整数下溢/溢出:state - dilation*tap,tap ≤ kernel-1 ⇒ dilation*tap ≤ state;token*multiplier 最大实测 5.09e18 < i64 max 9.22e18。✓
  • Cargo.lock 加法式:仅 +2 行(half、safetensors 挂到 pegainfer-qwen4 的依赖列表),无新增包(half 2.7.1、safetensors 0.7.0 早已在锁文件里);--locked 通过。✓
  • 永不 skip:fixture 缺失走 panic!(:221-226),没有 env 覆盖、没有"找不到就 ok"的假绿路径;provenance 三查(manifest 可解析 / format_version == 2 / config_sha256 等于 frozen_config.json 实算 sha256)在每次 Fixture::load() 里执行,6 个测试各跑一遍。✓
  • 容差是量出来的,不是拍的:残差实测 0.0;对链路里唯一依赖 libm 的 expf(sigmoid/silu)做扰动实验——给每一个 sigmoid 与 silu 输出注入整整 1 个 bf16 ulp——out 最大偏移 7.8e-3(= 最大 |out| 0.8438 处的 2 个 bf16 ulp)。取 1.6e-2 = 该上界的 2 倍,同时仍比最弱的负控(unsigned sqrt,0.27)低 17 倍。✓
  • 红线扫描:对全部改动文件(测试、两个 manifest、CI workflow、两份文档)跑仓库规定的红线正则(个人绝对路径、内网网段、内部工具与私有目录名等)→ 0 命中。✓

修改措施

已修(9 条)

  • R1(ple_ngram_replay.rs:514-527):注释改为陈述真实不变量——每个 token * multiplier 按构造非负(参考用 (2^63-1)/vocab_size 限制乘数),非负 i64 的 XOR 保持符号位为 0,故折叠值恒非负(实测 0/40 为负);wrapping_mul 是忠实而非承重,rem_euclid 的理由改为"torch.remainder 是向下取整余数,只在负折叠时才与 Rust 的截断余数不同"。
  • R2(ple_ngram_replay.rs:26-30):claim 收窄到实际断言的范围——本测试用 fixture 自带的模数/偏移,并在此检查其为"严格递增 + 偏移连续";同时点明 NgramTable::derive 建的是同一张表,其端点与总量由 the_ngram_table_derivation_matches_the_checkpoint 钉住。
  • R3(.github/workflows/ci.yml):cpu-clippy 包列表加 -p pegainfer-qwen4;cpu-unit-tests 新增一步 cargo test --release --locked -p pegainfer-qwen4 --tests(沿用同一 steps.deps.outcome 门禁)。两条命令按 CI 原样在本地跑通:clippy --all-targets -- -D warnings 0 error,测试 40 + 6 全绿。
  • R4(docs/models/qwen4/bring-up.md、docs/index.md):What landed 新增 PLE golden + 宿主 reader 一条(含实测数字、两个 fixture 载不动的边界、"读 layer_multipliers 而非重算,所以 seed 仍不建模"这条与既有决策的互链、CI 覆盖);TL;DR 与 index 路由行同步。Last touched 已是 2026-10,无需改。
  • R5(:367-372):文案改为"dump 记录两组 case(S=8 无 EOS、S=12 带序列中 EOS),但只解出 N 组 —— 有组缺失或命名变了",不再声称钉住长度;长度相关的真实不变量由负控 (a) 的 broke > 0 与各 shape 断言承担。
  • R6(:258):文案改为 "an increasing run with contiguous offsets",与代码实际检查一致。
  • R7(:106,613):inv_sqrt_hidden() 改为 sqrt_hidden(),gate 用 sum / sqrt(hidden),与参考的除法逐位一致(改后仍 81920/81920、122880/122880 全 bit-identical)。
  • R8(:330-334):改用 unwrap_or_else(|| panic!("{name}_seq_len must be a positive row count")),并 .filter(|&len| len > 0),seq_len == 0 现在会响亮失败。
  • R9(:694-698):删掉未使用的 .enumerate()。

保留(3 条,附理由)

  • R10:保留 Hash / Block。调用点读作 Hash { eos_reset: false, ..Hash::reference() },本文件从未 use std::hash::Hash,不存在真实歧义;改名要动 12 处而无行为收益。
  • R11:保留两个 eos 字段。这里的价值恰恰是那条断言本身——fixture 记录的 EOS 必须等于冻结 config 的 EOS(:232-235),否则一份用别的 config 录的 fixture 会静默地拿 config 的值去 replay;断言通过后 replay 刻意使用 fixture 自己的值,是"校验后单一使用",不是并行真相源。
  • R12:不在测试里重算素数。那会把 nth_prime_after 复制成第二份实现(D1),而 config_sha256 已把 fixture 绑到派生所读的那份文件上,链条 fixture → config → checkpoint 闭合;loader 只做结构性检查(>1、递增、偏移连续)。

复跑回执(修改后,非改前数字)

$ cargo fmt --all -- --check                                   # 无输出
$ cargo clippy --release --locked -p pegainfer-qwen4 --all-targets -- -D warnings   # 0 error
$ cargo test --release --locked -p pegainfer-qwen4 --tests
test result: ok. 40 passed; 0 failed     # 单测
test result: ok. 6 passed; 0 failed      # ple_ngram_replay
case0: ids=128/128 exact   out max_abs=0.000e0 bit_identical=81920/81920
case1: ids=192/192 exact   out max_abs=0.000e0 bit_identical=122880/122880
负控:EOS reset 去掉 → 0/128 与 8/192;头序交换 → 128/128 与 192/192;
      unsigned sqrt → 2.686e-1 / 3.320e-1(21×容差);dilation 1 → 7.881e-1 / 7.891e-1(49×容差)

@sysuTobo

Copy link
Copy Markdown
Contributor Author

Branch state note (after the three commits above)

main moved while this branch was being worked (da39fbc8 #1149, 2bc01a0c #1150), and GitHub now reports the PR as conflicting. The conflicts are not in the fixture, the dump script, the pinned frozen_config.json, or ci.yml — main has no qwen4 entry in the CI workflows, so the new cpu-clippy / cpu-unit-tests steps merge without duplication (verified with git merge-tree). Only two files need a hand:

file why resolution
pegainfer-qwen4/Cargo.toml both sides add a dev-dep on the same line: main added safetensors, this branch added half + safetensors keep one safetensors line and add half, alphabetically: half, safetensors, sha2
docs/models/qwen4/bring-up.md main reflowed the doc to one paragraph per line and added the Manifest bullet re-apply this branch's What landed bullet (PLE golden + host reader) after the Registration bullet, and re-blend the TL;DR clause — main's TL;DR already reads "…plus a tensor manifest…", so the replay-gate clause goes alongside it, not in place of it

Fixing that means rebasing this branch, which needs a force-push — deliberately not done here.

Two things that make the rebase safe: main did not touch pegainfer-qwen4/tests/frozen_config.json, so the manifest's config_sha256 still matches the file the gate hashes; and the test names this branch's comments cite all still exist on main (the_committed_fixture_is_the_frozen_revision at src/config.rs:561, the_ngram_table_derivation_matches_the_checkpoint at :599). main additionally gained the_ngram_partition_equals_the_stored_integer_vectors (src/config.rs:870), which compares the checkpoint's stored ngram_heads_vocab_sizes / ngram_heads_offsets against the derived primes — the same buffers this gate replays with, so after the rebase the header comment may cite it for a stronger chain.

sysuTobo added a commit to sysuTobo/pegainfer that referenced this pull request Oct 16, 2026
The golden recorded in pegainfer-project#1155 had no reader, so it proved nothing on its own:
issue pegainfer-project#1103's rule is that a fixture lands with the code that checks it. Add
that consumer as a device-free integration test, keeping this crate's
no-CUDA property so it runs with no device, no weights and no Python.

Two halves, matching the two things a PLE port can get wrong:

- The mul-xor n-gram hash is recomputed from `input_ids` and compared with i64
  equality, zero tolerance: the EOS-resetting context window, the bigram/
  trigram head layout (16 heads = 2 orders x 8) and the per-head prime modulus
  and table offset all have to be right for one id to land. 128/128 and 192/192
  ids exact on the S=8 and S=12 cases.
- The block is replayed from the recorded `key`/`value` projections and is
  bit-identical on all 81920 + 122880 stored bf16 outputs, because the port
  emulates each bf16 rounding point of the reference chain - including the
  gate's double rounding, where the group sum is rounded before the
  1/sqrt(hidden) divide and again after it.

`OUT_MAX_ABS` is sized from measurement, not guessed: the residual is 0.0, and
injecting a whole bf16 ulp into every `sigmoid` and `silu` output - the chain's
only libm-dependent op, far more than any platform disagreement - moves `out`
by at most 7.8e-3. 1.6e-2 is 2x that and still 17x below the weakest negative
control. Four controls are each asserted to fail: dropping the EOS window reset
(moves only the 8 trigram heads of the row after the EOS, and leaves the
EOS-free case untouched), dilation 1 (0.79), unsigned sqrt (0.27) and a swapped
bigram/trigram head order (every id).

Provenance is checked on every load and never skipped: the manifest must parse,
carry format_version 2, and name the frozen config with the sha256 that file
hashes to. The config's eos_token_id and every tensor width are cross-checked
against the stored scalars and shapes, and `caseN_rows` must be the identity -
this replay is whole-sequence, so a row selection would fail loudly rather than
replay against the wrong conv state.

Still ungated, both landing with the device side in pegainfer-project#1106: the n-gram table
gather that turns `ngram_ids` into head vectors, and the GPU dilated conv.

Signed-off-by: bohaotu <bohaotu@pony.ai>
@sysuTobo
sysuTobo force-pushed the feat/qwen38fn-ple-golden branch from e53a83e to 77d78ca Compare October 16, 2026 16:21
@github-actions github-actions Bot removed the needs-rebase Conflicts with main, needs a rebase label Oct 16, 2026
@sysuTobo

Copy link
Copy Markdown
Contributor Author

Code review

自审对象:#1155 追加的三个 device-path commit(6115dd89 生产 host hash、39cf30f0 device kernels + parity gate、f1ebd3d8 审计后修复),基线 77d78cae(分支已 rebase 到当前 main)。审计方式:第一轮把 replay 链当数据流对着参考实现(transformers 5.16.1 qwen4_exp 的 Qwen4ExpTextNGramEmbedding / Qwen4ExpTextPLELayer)逐段模拟;本轮为复核 pass——逐条验证 f1ebd3d8 声称的修复在 diff 中为真,并专查 tolerance 论证、control 空转、provenance panic-not-skip、.qoder 未跟踪卫生四项。结论:正向路径全部符合预期;第一轮 6 条发现已全部在 f1ebd3d8 修复且复核证实;复核 pass 5 条均为确认项,无需新 commit。

门禁重跑(本轮实测,非引用旧数字):

  • device-free:cargo test --release --locked -p pegainfer-qwen4 --test ple_ngram_replay → 6/6 通过;ids 128/128 与 192/192 精确(i64 零容差),block CPU replay 81920/81920 与 122880/122880 位相同。
  • device(CUDA 12.9.1,sm_120):./docker/dev.sh run cargo test --release --locked -p pegainfer-kernels --features qwen4 --test qwen38fn_ple_block_parity → 6/6 通过;204800 个存储输出中 204799 位相同,唯一偏差 1.221e-4(0.024292 对 0.024414,该量级下 1 个 bf16 ulp),为 bound 1.6e-2 的 0.0076;三个 device control 分别在界外 17× / 21× / 49×。
  • ./docker/dev.sh run cargo check --locked -p pegainfer-kernels(默认 features)绿,并带 "Qwen4 PLE block kernels disabled" 警告——新 .cu 未编译;cargo fmt --all -- --check 干净;pegainfer-qwen4 --lib 48/48;pegainfer-kernels --features qwen4 --lib 21+2 通过(含新增的两个 PLE 单测 only_the_compiled_geometry_passes、every_control_turns_one_knob)。

第一轮审计(A 系列,均已在 f1ebd3d8 修复,本轮复核证实修复为真)

ID 级别 一句话 证据(文件:行)
A1 L NgramHasher 带着四个无调用者的 accessor 出厂——gather 要的是 ids,不是被 hash 的向量——新生产模块上的死 API 面 pegainfer-qwen4/src/ngram.rs(f1ebd3d8 删除;本轮 grep 逐个确认剩余 accessor 均有调用者,含 shifted_context 由 windows 内部调用)
A2 M table_head_of 原按 slot 而非 order 取参,换头序 control 只能自行重推布局——control 可能漂移到私有布局副本上仍然绿 pegainfer-qwen4/src/ngram.rs:194-196(现签名为 (order, head));pegainfer-qwen4/tests/ple_ngram_replay.rs:492-500(换序 control 直接调生产 order_of_slot / table_head_of)
A3 M 拒绝矩阵每个入口各有一个洞:gate 从不拒绝错误 branch count,conv 从不拒绝错误 group 或 branch count——编译常量守卫零覆盖 pegainfer-kernels/tests/qwen38fn_ple_block_parity.rs:672-688(gate branch count)、765-780(conv group)、782-797(conv branch count),现全部拒绝并断言 CUDA_ERROR_INVALID_VALUE
A4 M 输出 sentinel 先于 finiteness 比较:kernel 自己产出的 NaN 会被误报成"未写入元素",掩盖真实故障形态 pegainfer-kernels/tests/qwen38fn_ple_block_parity.rs:434-447(finiteness 先行,报错信息区分 sentinel 与 kernel 产 NaN 两种形态)
A5 L kernel 头注释与代码不符:block 有三个 grouped norm 却写 "both grouped norms";把 ple_block_into 写成唯一相关入口,而 variant 入口同样是公开的 pegainfer-kernels/csrc/qwen38fn/qwen38fn_ple_block.cu:36-42(现为 "All three grouped norms … twelve launches")、53-56(现点名 ops::ple_block_variant_into 且 "sole callers are the gate's three controls",与 ops.rs / gate 的实际调用面一致,本轮 grep 证实)
A6 L 惰性覆盖未记录:gate 的 clamp_min 地板与 sign(0) 分支在两 case 的最小 |pre_gate|(0.0232)下不可触发,错误地板常量也会通过;seq_len 1 无对照;ops 层 extent 守卫需 device 才可达 pegainfer-kernels/tests/qwen38fn_ple_block_parity.rs:34-51("What this fixture cannot reach",记录而非掩饰)

复核 pass(B 系列,均为确认项,未改代码)

ID 级别 一句话 证据(文件:行)
B1 ✓ f1ebd3d8 声称的每项修复在 diff 中逐条为真(见 A 系列各行证据);NgramHasher 无残留死 accessor A1–A6 各行;pegainfer-qwen4/src/ngram.rs:153-196
B2 ✓ 容差论证成立:1.6e-2 = 最大存储元素 0.8438 处 4 个 bf16 ulp(2^-8 = 3.9e-3),覆盖三个非可移植点——rsqrt.approx.ftz.f32(bring-up 期插桩实测 245760 个 norm 输出中恰好 2 个翻转,各为自身 1 ulp,gated 流位相同)、归约树差异(f32 butterfly vs 参考 f32 树 vs host f64 精确和,相距一两个 f32 ulp,低于 bf16 步长四个数量级)、expf(host 侧已按每个 sigmoid/silu 注入整 ulp 预算,≤7.8e-3);与 host gate 同数,两半读同一把尺;且写明了为何不能向实测 1.2e-4 收紧——翻转集合由架构/libm 决定,是离散随机变量而非常量 pegainfer-kernels/tests/qwen38fn_ple_block_parity.rs:78-126;pegainfer-qwen4/tests/ple_ngram_replay.rs:87(同为 1.6e-2)
B3 ✓ control 非空转:拒绝矩阵以每入口的零 extent 正对照开头(必须放行且不形成 launch),"拒绝一切"无法让下游拒绝断言变绿;三个负 control 断言每个 case 都出界而非只一个;host 侧 hash control 组合在参考设置下被断言与生产 ids 逐元素相等,control 无法带着私有 hash 副本通过 pegainfer-kernels/tests/qwen38fn_ple_block_parity.rs:566-571, 592-634, 1042-1049;pegainfer-qwen4/tests/ple_ngram_replay.rs:665-673
B4 ✓ provenance 断言 panic 而非 skip:assert_provenance 全部 assert/expect,由 device-free 测试 fixture_records_the_frozen_reference 不经 device_or_skip 直接调用;device 测试在任何比较前先过 provenance;device_or_skip 仅在确无设备时跳过,且 PEGAINFER_REQUIRE_GPU=1 会把跳过变成失败——"另一 checkpoint 的 fixture 在这里失败而不是被跳过"为真 pegainfer-kernels/tests/qwen38fn_ple_block_parity.rs:160-243, 535-562, 949-956;pegainfer-kernels/tests/common/mod.rs:12-36
B5 ✓ .qoder 未跟踪卫生:git diff --name-only 77d78cae..HEAD 共 14 个文件,无一为 .qoder 路径;.qoder/ 保持 untracked(未被 ignore),后续提交必须逐文件 add、绝不 git add -A。红线扫描该范围仅一处误报:kGateClampMin 标识符恰好包含子串 "mpm",与任何内部工具无关 pegainfer-kernels/csrc/qwen38fn/qwen38fn_ple_block.cu:72;git diff --name-only 77d78cae..HEAD

修改措施

  • A1–A6:已修,全部落在 f1ebd3d8;本轮复核逐条对照 diff 证实为真(行号见上表),无新 commit。
  • B1–B4:确认项,无需修改。B5 的红线命中为标识符子串误报,就地更正该扫描结论,不改动任何提交内容。
  • 明确留给 [Model] Qwen3.8-Flash-Next: n-gram embedding #1106:102.4 GB n-gram 表的 lookup + gather 本身,以及这么大一张表如何喂进 kernel 的 UVA / in-graph 问题——两个 gate 都从 block 的 post-projection 输入开始,表不在 fixture 里;decode 侧状态(2-token id 窗口 + 9-slot dilated conv 状态)——录制为整序列、无 cache 状态,同样留给 [Model] Qwen3.8-Flash-Next: n-gram embedding #1106 的 decode 接线。
  • 明确留给 test(qwen4): pin the grouped (1+w) RMSNorm to a grouped weight slice #1158:grouped norm 的单 launch 入口。落地后生产切换过去,十二次 per-branch 调用收敛为三次、branch-major split 随之消失;在那个退化路径上按构造位相同(同一段算术,仅 launch 形状变化)。
  • 明确留给"把 kernels qwen4 feature 接进 CI 的人":当前没有任何 CI job 启用该 feature,device gate 只在其头注的手动命令下运行(device-free 一半已在 CPU CI job 里);A6 第三条的 ops 层 extent 守卫需要 device + 故意短 buffer 才可达,随那个 job 一起补。

@github-actions github-actions Bot added the needs-rebase Conflicts with main, needs a rebase label Oct 20, 2026
@github-actions

Copy link
Copy Markdown

This pull request has merge conflicts with main. Please rebase it so it can be reviewed and merged.

Signed-off-by: bohaotu <bohaotu@pony.ai>
…overage

Review follow-up for the PLE golden dump:

- EOS reset property assert was vacuous: it perturbed token 0, whose
  3-token window radius never reaches the asserted post-EOS region
  (positions >= eos_pos+1 = 6), so removing the segment reset from the
  reference still passed. Perturb the token right before each EOS
  (inside the post-EOS window radius) and add a positive control
  asserting the perturbation moves the pre-EOS embeddings.
- Re-derive the mul-xor n-gram hash in plain Python (EOS-reset window,
  per-order mix, per-head remainder+offset) and assert it reproduces the
  module embedding bit-for-bit; a xor->add reference mutation previously
  sailed through.
- Pin the dilated conv: assert conv1d.dilation == ngram_size and
  short_conv_state_len == (kernel-1)*dilation; a consistent dilation-1
  reference mutation previously sailed through.
- config_digest now covers seed / eos_token_id / rms_norm_eps (all
  output-affecting constants); store eos_token_id in the fixture.
- Add a double-EOS case (14, eos at 4 and 9) covering the second reset
  boundary; drop the unused Config.layer_types; document the cache=None
  scope boundary.

Fixture regenerated (29 tensors, config_sha256=5acc59a2...) with
torch 2.14.1+cpu / transformers 5.16.1; re-run is byte-stable.

Signed-off-by: bohaotu <bohaotu@pony.ai>
Read the frozen checkpoint config instead of a hand-written reduced one:
hidden 2560, hc_count 4 (10240-wide stream), ple_embed_dim 2560, ngram_size 3,
16 heads of 160. Capture the reference's own outputs with forward hooks (the
real hashed n-gram ids, the key/value projections, the n-gram embedding and the
block output) rather than recomputing them, and store the real per-head
primes/offsets/vocab sizes and layer multipliers.

The 95 GiB n-gram table is stubbed so the module builds; the hash math never
touches it, so the captured ids stay real. key_proj/value_proj weights are not
stored - their outputs are recorded. The dump asserts the captured ids equal an
independent hash re-derivation and that the captured projections reproduce the
block output, and asserts the EOS segment reset with a positive control.

Signed-off-by: bohaotu <bohaotu@pony.ai>
case*_key_proj/value_proj held the captured projection outputs, not weights;
rename them key/value (matching the GDN golden) so they are not read as the
weights the golden deliberately does not store.

Signed-off-by: bohaotu <bohaotu@pony.ai>
`verify()` reloaded the fixture and reproduced the hashed ids and the block
output by re-implementing the reference math by hand (`grouped_norm` /
`short_conv`, plus a second copy of the mul-xor hash), purely to show the
fixture reproduces itself. Every stored value is captured from the reference
module through forward hooks, so the oracle never depended on that pass: the
fixture is byte-identical without it (26 tensors, same metadata). `safe_open`
(that pass's only reader) goes with it.

No tensor is dropped. The n-gram block is table/embedding-driven: the hash
buffers (`layer_multipliers`, `ngram_heads_offsets`/`_vocab_sizes`,
`eos_token_id`) are the lookup a consumer must perform to re-derive the ids,
and `case*_ngram_emb` is the reference's own recorded n-gram-embedding
activation for the stored ids -- the fixture's only record of the embedding
stage, since the 95 GiB table cannot be committed.

The in-run invariants stay, since they guard the oracle's semantics: the
captured ids equal an independent plain-Python mul-xor re-derivation, the
captured key/value projections reproduce the module's returned block output
bit-for-bit, the EOS segment reset with its positive control, and the
dilation / short-conv-state-length pins.

Signed-off-by: bohaotu <bohaotu@pony.ai>
…tion

`case*_ngram_emb` was the n-gram lookup into the capped stub table, whose weights
are not in the fixture, so no consumer could reproduce it; the projection outputs
it feeds (`key`/`value`) are stored instead. The verifiable part of the lookup --
the real mul-xor hash output -- is stored as `ngram_ids` and cross-checked by an
independent re-derivation. The activation stays in-run, where it asserts the EOS
segment reset. The 24 surviving tensors are bit-identical and the metadata JSON is
unchanged.

Signed-off-by: bohaotu <bohaotu@pony.ai>
The dump stored the reference's own output at the real geometry; every
assert re-checked a property the model architecture already guarantees
(hash re-derivation, block re-derivation, EOS segment reset, dilated-conv
pins) or merely tested the capture. Delete the asserts and the code that
existed only to serve them (rederive_ngram_ids, block_forward and its
grouped_norm/short_conv helpers, _perturb, the structural pins, the dead
math/F imports and the now-unused eps local, plus the dead embedding hook),
so the dumper is a pure recorder. The fixture tensor set is unchanged and
regenerates byte-for-byte.

Signed-off-by: bohaotu <bohaotu@pony.ai>
The golden recorded in pegainfer-project#1155 had no reader, so it proved nothing on its own:
issue pegainfer-project#1103's rule is that a fixture lands with the code that checks it. Add
that consumer as a device-free integration test, keeping this crate's
no-CUDA property so it runs with no device, no weights and no Python.

Two halves, matching the two things a PLE port can get wrong:

- The mul-xor n-gram hash is recomputed from `input_ids` and compared with i64
  equality, zero tolerance: the EOS-resetting context window, the bigram/
  trigram head layout (16 heads = 2 orders x 8) and the per-head prime modulus
  and table offset all have to be right for one id to land. 128/128 and 192/192
  ids exact on the S=8 and S=12 cases.
- The block is replayed from the recorded `key`/`value` projections and is
  bit-identical on all 81920 + 122880 stored bf16 outputs, because the port
  emulates each bf16 rounding point of the reference chain - including the
  gate's double rounding, where the group sum is rounded before the
  1/sqrt(hidden) divide and again after it.

`OUT_MAX_ABS` is sized from measurement, not guessed: the residual is 0.0, and
injecting a whole bf16 ulp into every `sigmoid` and `silu` output - the chain's
only libm-dependent op, far more than any platform disagreement - moves `out`
by at most 7.8e-3. 1.6e-2 is 2x that and still 17x below the weakest negative
control. Four controls are each asserted to fail: dropping the EOS window reset
(moves only the 8 trigram heads of the row after the EOS, and leaves the
EOS-free case untouched), dilation 1 (0.79), unsigned sqrt (0.27) and a swapped
bigram/trigram head order (every id).

Provenance is checked on every load and never skipped: the manifest must parse,
carry format_version 2, and name the frozen config with the sha256 that file
hashes to. The config's eos_token_id and every tensor width are cross-checked
against the stored scalars and shapes, and `caseN_rows` must be the identity -
this replay is whole-sequence, so a row selection would fail loudly rather than
replay against the wrong conv state.

Still ungated, both landing with the device side in pegainfer-project#1106: the n-gram table
gather that turns `ngram_ids` into head vectors, and the GPU dilated conv.

Signed-off-by: bohaotu <bohaotu@pony.ai>
`pegainfer-qwen4` was in no CI job: the CPU clippy list covers build/frontend/
sim/kvbm-logical, and the CUDA matrix lists the model packages that need a
device. So the crate's 40 config/probe unit tests and the new PLE golden replay
ran only on a developer box - a gate nobody runs is not evidence.

The crate carries no CUDA dependency by design, so it belongs with the CPU jobs:
add it to `cpu-clippy` (--all-targets -D warnings, pedantic) and run
`-p pegainfer-qwen4 --tests` in `cpu-unit-tests`, which picks up the unit tests
and the golden replay together. Both commands verified locally as written.

Signed-off-by: bohaotu <bohaotu@pony.ai>
The bring-up doc's TL;DR and "What landed" still described the crate as an
identity probe plus one config validation, and never mentioned the PLE golden
the block's hash and arithmetic are already pinned on the host, or what the
fixture deliberately leaves out.

Add the landed bullet with the two boundaries the fixture cannot carry (the
102.4 GB table is not stored, so the replay starts at the block's
post-projection inputs; the recording is whole-sequence, so the decode-side
states stay ungated), the note that the hash reads `layer_multipliers` rather
than recomputing them - which is what keeps `seed` unmodelled - and the CI
coverage that makes the no-device property enforceable. Refresh the TL;DR and
the index row to match.

Signed-off-by: bohaotu <bohaotu@pony.ai>
The hash that addresses the 95 GiB n-gram table was test-local, so the
exact-i64 golden gate pinned a copy rather than the code a loader will
call. `pegainfer_qwen4::ngram::NgramHasher` now owns it: the EOS-padded
history, the cummax segment reset as data the decode path can carry,
the mul-xor fold, the per-head prime remainder plus offset, and the
order-major head layout.

The gate calls it directly for the zero-tolerance comparison. Its two
hash controls compose the same primitives with one knob each — a
segment start of 0 everywhere, and the order blocks walked in reverse —
and the exact test now asserts that composition reproduces `ids` at the
reference settings, so a control cannot drift onto its own copy of the
hash and still pass.

The crate stays CUDA-free: this is integer host math, and the device
gather that consumes it is what needs a GPU.

Signed-off-by: bohaotu <bohaotu@pony.ai>
The PLE golden had a host reader and no device consumer, so the block's
arithmetic was pinned only by a CPU oracle. Three kernels in
`csrc/qwen4/qwen4_ple_block.cu` now compute it behind the line's additive
`qwen4` feature on `pegainfer-kernels`: the branch-major split that lets
the grouped norms run on contiguous groups, the per-branch gate, and the
dilated causal depthwise conv with its residual add.

Every bf16 rounding point of the reference is reproduced — the gate's
double rounding around the `1 / sqrt(hidden_size)` divide, the signed
sqrt's `clamp_min` floor and `torch.sign` at zero, the conv's rounding
before the silu — and the three grouped `(1 + w)` norms are twelve calls
to the existing batched entry point rather than a new kernel, because
that entry point's row stride is its `hidden_dim` and this stream's four
groups are interleaved row-major.

The gate drives `ops::ple_block_into` and measures 204799 of the
golden's 204800 stored outputs bit-for-bit, the remaining one a single
bf16 ulp, against a bound of 1.6e-2 — the host gate's own number, so
both halves of this consumer read against one scale. Three device-side
controls (unsigned sqrt, a full-stream gate dot, dilation 1) are each
asserted to leave that bound, at 21x, 17x and 49x outside it, and they
turn one scalar of the same launches rather than running a second copy
of the kernels. Provenance and the entry points' refusal contract are
the two device-free halves, so neither can skip.

Signed-off-by: bohaotu <bohaotu@pony.ai>
@sysuTobo
sysuTobo force-pushed the feat/qwen38fn-ple-golden branch from f1ebd3d to 89a2092 Compare October 21, 2026 03:15
`NgramHasher` shipped four accessors with no caller — the gather needs
the ids, not the vectors they were hashed from — so they are gone, and
`table_head_of` now takes the order rather than the slot, which lets the
swapped-head-order control call it instead of re-deriving the layout.

The parity gate's refusal matrix had a hole per entry point: the gate
never refused a wrong branch count and the conv never refused a wrong
group or branch count, so two of the six compiled-constant guards were
unexercised. It also compared the output sentinel before finiteness,
which would have reported a NaN the kernels produced as an unwritten
element, and it now records what the fixture cannot reach — the gate's
`clamp_min` floor and `sign(0)` branch are inert at the smallest
`|pre_gate|` either case produces (0.0232), `seq_len` 1 is compared
against nothing, and the ops layer's extent guards need a device.

The kernel header said "both grouped norms" where the block has three,
and named `ple_block_into` the only caller where the variant entry is
public too.

Signed-off-by: bohaotu <bohaotu@pony.ai>
@sysuTobo
sysuTobo force-pushed the feat/qwen38fn-ple-golden branch from 89a2092 to cc346a4 Compare October 21, 2026 03:16
@github-actions github-actions Bot removed the needs-rebase Conflicts with main, needs a rebase label Oct 21, 2026
@sysuTobo

Copy link
Copy Markdown
Contributor Author

Rebase

已 rebase 到 main @ 1f17ddb3(含 #1160 QSA indexer、#1176 FlashInfer v0.7.2),并统一到 #1160 合入后的 kernels 布局:

  • 核文件 csrc/qwen38fn/qwen38fn_ple_block.cu → csrc/qwen4/qwen4_ple_block.cu,三个 C 入口改为 qwen4_ple_*_cuda
  • FFI/ops 追加进 main 已有的 src/ffi/qwen4.rs / src/ops/qwen4.rs;is_qwen38fn_source 与 feature 行删除,复用 main 的 is_qwen4_source
  • gate 改名 tests/qwen4_ple_block_parity.rs;fixture、dump 脚本与 pegainfer-qwen4 侧(ngram.rs、ple_ngram_replay.rs、CI 接线)名不变

bring-up.md 三处冲突取本 PR 的新版本(#1158 分组 norm 的实测结论、PLE 三核与双读者),只把 main 的 QSA 段保留在前面。重跑:device gate 6 passed,host replay 6 passed,pegainfer-qwen4 lib 48 passed,默认 feature 的 cargo check --all-targets 绿,cargo fmt --check 干净。

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

Labels

enhancement New feature or request qwen4 Qwen4-architecture model crate (pegainfer-qwen4)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant