Skip to content

feat(qwen4): enforce the tensor contract before any device work - #1149

Merged
FeathBow merged 7 commits into
pegainfer-project:mainfrom
sysuTobo:feat/qwen4-manifest
Oct 14, 2026
Merged

FeathBow merged 7 commits into
pegainfer-project:mainfrom
sysuTobo:feat/qwen4-manifest

Conversation

@sysuTobo

@sysuTobo sysuTobo commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

Description

Lands the tensor contract for the Qwen4 line (qwen4_exp, first checkpoint Qwen/Qwen3.8-Flash-Next), on top of #1130 (already in main); with the merged #1129 this completes slice A of #1103.

The contract names which tensors a qwen4_exp checkpoint must carry, and holds the checkpoint's own shard headers against it before any device work. Ported from pegainfer-gemma4/src/manifest/, with three deltas this checkpoint forces:

  • Untied LM head → both ends required, and no final-norm tensor: the readout is the hyper-connection mixer.
  • Rank-3 shapes → the depthwise conv [channels, 1, kernel] and the fused expert stacks, which carry no .weight suffix.
  • A mandatory skip set (model.visual., mtp.) → 333 vision tensors share shards with text tensors, so a whole-shard loader must classify per tensor.

read.rs has no Gemma 4 counterpart: this checkpoint is 360 GB across 131 shards, so it reports the shards it never opened and refuses to call their tensors missing, while holding every opened tensor to its shape and dtype and cross-checking each header against the index. A shard the index names that is on disk but is not a regular file is its own fault class, not an "absent" one.

It pins exactly the frozen text set — 1658 keys − 333 model.visual.* − 31 mtp.* = 1294 — the 1-D lengths (259 vectors, which the shared loader does not check, #1068), and the n-gram partition by value against the two I64[16] vectors the checkpoint stores.

Evidence

  • Real bytes: the contract has been exercised against real shards of the frozen revision, and every opened tensor matched the derived shape and dtype. Which shards a run touched is printed by the ignored gate itself rather than recorded here.
  • CPU only: cargo test --release --locked -p pegainfer-qwen4 --lib → 68 passed, 3 ignored; clippy -D warnings and fmt --check clean.
  • Not in CI, by decision — a known bound, not an oversight: feat(qwen4): detect the line and validate its geometry #1130 keeps the line out of the matrix until slice B, so the commands above are the gate.

Refs #1104.

Type of Change

  • New feature (non-breaking change which adds functionality)
  • Documentation update

Checklist

  • Style guidelines
  • Self-reviewed
  • Commitizen commits
  • Local test suite passes

Test plan

The commands below are the whole gate for this PR (see "Not in CI" above), and none of them
needs a device. The first three were run on this head (efbbbd43); the last needs a
checkpoint on disk and was not run here.

  • cargo test --release --locked -p pegainfer-qwen4 --lib → 68 passed, 0 failed, 3 ignored.
  • cargo clippy --release --locked -p pegainfer-qwen4 --all-targets -- -D warnings → exit 0.
  • cargo fmt --all --check → clean.
  • PEGAINFER_TEST_MODEL_PATH=<dir> plus --ignored — the three tests that read the real shard headers, the checkpoint index, and the stored n-gram partition vectors. Not run on this head: it needs a checkpoint on disk.

@github-actions github-actions Bot added qwen4 Qwen4-architecture model crate (pegainfer-qwen4) enhancement New feature or request labels Oct 12, 2026

@sysuTobo sysuTobo left a comment •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code audit — self-review of this PR

Audited 2b39def3 against 4b1472e4. The forward path is correct: the contract is self-consistent with the frozen checkpoint (1294 tensors, 102.4 GB table, 259 1-Ds, GQA group 12, indexer 640 / 512 micro-blocks), every figure recomputed from the real config.json; scope is one crate plus one docs row; no reachable bug. 0 H, 2 M, 6 L — all evidence / wording / guard-strength level.

ID Lv One line Evidence
R1 M Tests are not wired into CI, so #1104 criterion 1's "running" clause is hand-only the diff has no ci.yml; main has no qwen4 row
R2 M The gate prints 446 but not 111, so the doc's headline number can't be replayed from it manifest/read.rs:622-628; bring-up.md:54,196-197
R3 L Fault message hardcodes the first checkpoint while the line serves any smaller qwen4_exp manifest/validate.rs:123
R4 L "three that read real shard bytes" — one of them reads the index bring-up.md:54; validate.rs:516
R5 L "Every probe, config and manifest test runs against it" — two build their own input bring-up.md:49
R6 L Duplicate-name guard is debug_assert!, compiled out in the --release test profile manifest/schema.rs:97
R7 L dtype_size exists twice and they disagree: production returns 0 for an unknown dtype schema.rs:174 vs read.rs:311 (D1)
R8 L Three dead_code reasons still say "the tensor contract is the first reader" — stale as of this PR config.rs:111,449,525

Verified ✓: every geometry quantity against the real config; all five fault classes + 1-D lengths + anti-shadowing; partial-download semantics (excuse missing, not the rest); a truncated shard is rejected before any weight read; the ple_layer conversion has a single source of truth; from_config's signature change has exhausted call sites; zero CUDA dependencies (67 tests pass on a box with no nvcc).

No code changed by this comment; remediation follows.

@sysuTobo

sysuTobo commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor Author

Remediation

Follows the audit above. Applied in 3326c7f4; R1 is the only one left open.

ID Disposition Where
R1 Open, by decision. #1130 keeps the line out of CI until slice B, so the tests run by hand. Recorded as a known bound in the PR description. PR description
R2 Fixed: the gate prints the required/skipped split (446 checked, 111 required, 335 skipped). manifest/read.rs
R3 Fixed: names the architecture (crate::MODEL_TYPE) instead of the first checkpoint. manifest/validate.rs
R4 Fixed: "three ignored ones that read a real checkpoint on demand". bring-up.md
R5 Fixed: "only the identity tests build their own input". bring-up.md
R6 Fixed: debug_assert! → assert!. manifest/schema.rs:98
R7 Fixed: one dtype_size in manifest/mod.rs, which panics on an unknown dtype. mod.rs, schema.rs, read.rs
R8 Fixed: the reasons name the real first reader (slice B's loader; the contract test for bf16_bytes). config.rs:113,454,533

Evidence: 67 passed / 3 ignored, clippy -D warnings exit 0, fmt --check clean; no derived number moved. The 111 is now computed and printed by the gate; re-measuring its exact value needs a checkpoint plus --ignored, which this PR does not do.

Comment thread docs/models/qwen4/bring-up.md Outdated
this.
- **GQA group 12 is not a compiled decode group.** `SUPPORTED_GQA_GROUP_SIZES` is
`[1, 2, 3, 4, 8]`, so `decode_group_is_compiled()` is false here and captured-graph
decode is off the table — the same corner Qwen3.5-27B is in at group 6. Slice B should

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

qwen3.5-27B的问题应该已经修复了,你rebase一下,看看这里的文档是否需要更新

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done — rebased onto a3b51b4a (so this branch now sits on the group-6 fix) and the bullet is updated:

  • the set reads [1, 2, 3, 4, 6, 8] now;
  • the stale "same corner Qwen3.5-27B is in at group 6" clause is gone — it isn't in that corner any more;
  • the conclusion is unchanged: 12 is still absent, so captured-graph decode stays off the table for this line. The bullet now states the mechanism instead of the comparison (a group joins the set when it earns a compiled kernel; fix(qwen35): route GQA group 6 to the compiled batch-decode kernel #1145 added 6).

The same commit trims the comments across the module and drops the run's internal provenance from the bring-up doc (which shards, how much was fetched, the checksum procedure, the tensor counts) — the claim and the two derived conclusions stay, and the ignored gate still prints what it opened.

@sysuTobo

sysuTobo commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor Author

Follow-up — the audit's one uncovered case, closed

a541298b closes the case the audit listed as uncovered: an indexed path that exists but is not a regular file was counted as "not downloaded", so a download tool that left a directory (or any other non-file) behind passed as an ordinary partial fetch.

  • The scan gains unreadable_shards, filled only when the indexed path exists and is not a file; absent_shards keeps its narrower meaning.
  • check names it as its own fault class, and forgives missing for it as well — a shard we cannot open is one we cannot testify about, so its tensors are not a wall of "missing" either.
  • The ignored real-shard gate counts it in its denominator, so "opened X of Y" stays honest.
  • The two ignored tests that read the real checkpoint index now use read::INDEX_FILE instead of repeating the literal, so the published filename has one source.

New test: 68 passed, 3 ignored; clippy -D warnings exit 0; fmt --check clean.

The detection PR (pegainfer-project#1130) landed the probe and the validated config, so the
checkpoint's tensors were implied by the geometry but never named. This lands the
contract that says exactly which tensors the line must read, and holds the
checkpoint's own headers against it before any H2D.

Ported from `pegainfer-gemma4/src/manifest/`, with three deltas this checkpoint
forces. The LM head is **untied**, so both ends are required and there is no
final-norm tensor — the readout is the hyper-connection mixer. Shapes include
**rank-3** tensors: the depthwise conv and the fused expert stacks, which carry
no `.weight` suffix. And the skip set (`model.visual.`, `mtp.`) is mandatory
rather than optional, because all 333 vision tensors share a shard with 16 text
tensors, so a loader reading whole shards must classify per tensor.

`read.rs` has no Gemma 4 counterpart: that line's checkpoint is small enough to
hold whole, while this one is 360 GB across 131 shards, so the reader reports the
shards it never opened and refuses to call their tensors missing, while still
holding every opened tensor to its shape and dtype. It also cross-checks each
shard's header against the names the index attributes to it, so a stale index is
named instead of surfacing as a wall of spurious "missing".

The contract derives exactly the frozen checkpoint's text set — 1658 keys less
333 `model.visual.*` and 31 `mtp.*` = 1294 tensors, `lm_head.weight` the only one
outside the text prefix — and has been held against eight real, sha256-verified
shards (446 tensors opened, 111 of them required text tensors): every shape and
dtype matched. The n-gram partition is confirmed **by value** against the two
`I64[16]` vectors the checkpoint stores, not only by shape.

CPU only, like the rest of the crate: 67 tests run with no device, no weights and
no Triton-equipped build, and three more read real shard bytes on demand.

Refs pegainfer-project#1104

Signed-off-by: bohaotu <bohaotu@pony.ai>
An audit of this PR found two issues that matter and six worth tightening; all
eight sit in the gap between what the contract claims and what it makes
verifiable. None of them changes a derived number.

- The ignored gate printed how many tensors it opened but not how many of them
  the contract requires, so the "446 opened / 111 required" figure in the
  bring-up doc could not be reproduced from the gate's own output. It prints the
  required/skipped split now.
- The fault message named "Qwen3.8-Flash-Next" while this line's probe and config
  deliberately serve *any* smaller `qwen4_exp` checkpoint, so a failure on a
  second checkpoint would name the wrong model. It names the architecture
  (`MODEL_TYPE`) now.
- `dtype_size` existed twice and the two disagreed: the production copy returned
  `0` for an unknown dtype, silently shrinking `weight_bytes`, while the test copy
  panicked. There is one copy in `manifest/mod.rs` now, and it panics for both.
- `schema.rs` guarded duplicate derivations with `debug_assert!`, which the
  `--release` profile these tests run under compiles out. It is an `assert!` now.
- The three `dead_code` allows still said "the tensor contract is the first
  reader", which this PR makes stale; they name the actual first reader (slice B's
  loader, and the contract test for `bf16_bytes`).
- Two doc sentences over-claimed: "every probe, config and manifest test runs
  against it" (two of them build their own input) and "three that read real shard
  bytes" (one reads the index).

Not addressed, by decision: the line is not wired into CI (pegainfer-project#1130 kept it out until
slice B), so these tests still run by hand only. Recorded in the PR description as
a known bound rather than fixed here.

Signed-off-by: bohaotu <bohaotu@pony.ai>
The contract audit left one case uncovered: an indexed path that exists but is not
a regular file was counted as "not downloaded", so a download tool that left a
directory (or any other non-file) behind passed as an ordinary partial fetch. On a
360 GB checkpoint fetched shard by shard that is a plausible accident.

- The scan gains `unreadable_shards`, filled only when the indexed path exists and
  is not a file. `absent_shards` keeps its narrower meaning.
- `check` names it as its own fault class, and forgives `missing` for it as well:
  a shard we cannot open is one we cannot testify about, so its tensors must not be
  reported as a wall of "missing" either.
- The ignored real-shard gate counts it in its denominator, so "opened X of Y"
  stays honest.
- The two ignored tests that read the real checkpoint index now use
  `read::INDEX_FILE` instead of repeating the literal, so the published filename
  has one source.

New test: 68 passed, 3 ignored; clippy `-D warnings` and `fmt --check` clean.

Signed-off-by: bohaotu <bohaotu@pony.ai>
Review feedback on the GQA trap was right: `SUPPORTED_GQA_GROUP_SIZES` is
`[1, 2, 3, 4, 6, 8]` on `main` now — pegainfer-project#1145 routed group 6 — so Qwen3.5-27B is no
longer "in the same corner". Group 12 is still absent, so the conclusion holds; the
bullet now states the set and the mechanism (it grows when a group earns a compiled
kernel) instead of the stale comparison.

Also, per review:

- The verification section recorded the internal provenance of the run — which
  shards, how much was fetched, the checksum procedure, the tensor counts. That
  belongs in the local working notes, not the repository: it keeps the claim and the
  two derived conclusions, and the ignored gate still prints what it opened.
- The ignored n-gram test no longer names a specific shard of the frozen revision,
  and the comments across the module are cut to a line or two where they ran to
  paragraphs.

Comments and docs only: no derived number, name, shape, test or behaviour changed.

Signed-off-by: bohaotu <bohaotu@pony.ai>
@sysuTobo
sysuTobo force-pushed the feat/qwen4-manifest branch from a541298 to efbbbd4 Compare October 12, 2026 12:57
@sysuTobo
sysuTobo marked this pull request as ready for review October 12, 2026 12:59
Signed-off-by: Feathbow <feathbow@gmail.com>
… to slice B

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

Copy link
Copy Markdown
Collaborator

We pushed three commits on top of yours. The contract is now tested against every real shard header, the code that tolerated partial downloads is gone, and the manifest compiles for tests only until a loader calls it.

The contract itself was right. We built a sparse copy of the frozen revision from its real shard headers and ran your three ignored gates against all 131 shards. Every one of the 1294 text tensors matched in name, shape and dtype, and the stored partition vectors matched the derivation. The same setup showed where the partial download path went wrong. When one unrelated shard was missing from disk, the check stopped reporting a required tensor that no shard carried. A shard behind a directory the process could not read was treated as not downloaded. With only the first shard present, the gate passed after checking 16 of the 1294 text tensors.

The headers alone answer whether the checkpoint matches. tests/frozen_tensors.json now holds the name, dtype and shape of all 1658 tensors plus the two partition vectors, 148 KB in total, written by tools/accuracy/dump_qwen4_frozen_tensors.py from HTTP range reads of the frozen revision. the_contract_is_the_frozen_checkpoint classifies every tensor against the contract, and the partition test compares the stored vectors with the derivation. Both run as ordinary tests, so read.rs, its tests, the three ignored gates and the memmap2 and tempfile dependencies are gone.

Nothing outside the tests calls the contract yet, so the module is #[cfg(test)] until slice B's loader does. The widths the schema needs are computed in schema.rs, which leaves config.rs as it is on main apart from its tests. dtype_size, weight_bytes and bf16_bytes went with that change.

We removed the tests that restated values schema.rs writes out, since the snapshot test checks all of them against the real checkpoint, and the fault tests that repeated another fault class. The n-gram table test keeps only the two totals the other tests do not reach. ExpectedShape now has one dims method that both the comparison and the fault message use, and comments that repeated each other are shorter. The comment on the PLE block pointed at a function that does not exist and now names Config::ple_layer.

In bring-up.md the GQA trap no longer concludes that decode stays eager. The compiled group set belongs to the FlashInfer batch decode path, and QSA needs kernels of its own, so whether its decode is captured is slice B's decision. The Still open table lists enforcement before the first H2D again, blocked on the loader. The three deltas now sit in the Manifest entry, and the section restating shapes the snapshot already holds is gone, with its one design point, that attention writes back at hidden_size, moved to the traps. The note on the GDN scalars now says what the Qwen3.5 loader checks. A_log is held to BF16 or F32 but never to its length, and on a single rank dt_bias is checked for neither. docs/index.md is back to its main version, as its drift policy asks.

Checked on one sm_89 box. cargo fmt --all --check, Clippy with -D warnings on the library and on all targets, and the crate's tests (48 passed, none ignored) all pass.

@github-actions

Copy link
Copy Markdown

Commit attribution notice

This pull request contains commits attributed to an account other than the PR author @sysuTobo without a verified signature from that account:

If you are mentioned above and did not take part in this pull request, please say so here. Attributing a commit to someone else normally requires that person to be involved; maintainers should treat unacknowledged attribution as a review blocker.

This check never fails the build.

@FeathBow
FeathBow merged commit da39fbc into pegainfer-project:main Oct 14, 2026
14 checks passed
@sysuTobo
sysuTobo deleted the feat/qwen4-manifest branch October 14, 2026 08:00
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.

2 participants