Skip to content

fix(format): ModelConstraints dropped the gated-DeltaNet shape, so 18 of every 24 Qwen3.5 layers were counted as if their tensors did not exist - #3350

Closed
noahgift wants to merge 8 commits into
mainfrom
PMAT-3346-gdn-shape-keys
Closed

noahgift wants to merge 8 commits into
mainfrom
PMAT-3346-gdn-shape-keys

Conversation

@noahgift

Copy link
Copy Markdown
Contributor

#3346 asked which was wrong, the range or the descriptor. The answer is neither — it was the type that reads the descriptor. Stacked on #3348.

contracts/model-families/qwen3_5.yaml declares inner_size, state_size, conv_kernel, group_count and full_attention_interval. ModelConstraints carried none of them, so every consumer counted a hybrid model with dense/GQA accounting and silently missed the conv, the gates, the state norm and the mixer projections of 18 of every 24 layers.

Ground truth first, and it now matches exactly

The measurement is against a real file, not against the formula's own assumptions: ~/models/Qwen3.5-0.8B-Q4_K_M.gguf (sha256 bd258782…dc517), GGUF header parsed directly — 320 tensors, 752,393,024 parameters.

measured GGUF tensor sum   752,393,024
model_parameter_count      752,393,024      delta 0
  gated-DeltaNet layer      21,555,360  ==  measured blk.0
  full-attention layer      18,352,640  ==  measured

Two shapes in that file contradict dense accounting and are now modelled, both read off the tensors rather than assumed:

  • attn_q is [1024, 4096] = 2·n_h·d_k — the q projection emits the output gate, and attn_output [2048,1024] proves only q is doubled.
  • the file is tied: it has no output.weight.

The mutation: drop the attn_gate term and qwen35_0_8b_config_derived_count_equals_the_measured_gguf_inventory goes RED, short by exactly d·inner_size = 2,097,152.

QE2E-INV-001 is still NOT asserted, and the range was not widened

P(9B) moves 8,208,519,168 → 8,344,907,136, still 0.655 B below [9.0B, 9.2B]. The residual is not arithmetic — the 9b descriptor contradicts itself: it keeps inner_size: 2048 (the value the 0.8B uses at hidden_dim 1024) while quadrupling hidden_dim, and its group_count: 8 fails group_count · state_size == inner_size (8·128 ≠ 2048), a consistency the measured 0.8B satisfies at 16·128.

Only a real Qwen3.5-9B file settles that, and none is on this box, so the obligation stays unproved and the number is pinned by a test instead of asserted. DeltaNetShape::heads_span_the_mixer() now catches the descriptor's self-inconsistency directly.

Two disclosures

  • Outside scope, unavoidable: five one-line deltanet: None, fixture literals in apr-cli tests/oracles. Zero logic change. The enum-payload alternative breaks the same crate, because apr-cli matches AttentionType::HybridGatedDeltaNet in two files.
  • qk_norm is left alone deliberately. The descriptor says the family has none, the real file has attn_q_norm/attn_k_norm on its attention layers, and tensor_expectation.rs asserts the opposite. Both are true of different layer kinds — the repo should settle whether qk_norm is per-family or per-layer-kind. It is worth 4,096 parameters at 9B, so it does not touch the verdict.

The roadmap write pmat work add produced was reverted: #3297 moved entries to fragments, and a monolithic write is exactly the contention that PR removed.

Gate: aprender-core --lib model_arithmetic 28 passed, aprender-contracts --lib 1526 passed, clippy -D warnings clean, fmt clean.

Refs #3346, #3347, #3091, #3114

no-close: #3346 stays open — which side of the 9b descriptor is wrong is undecided until a real Qwen3.5-9B GGUF is measured.

ont-delta: shape ModelConstraints gains inner_size, state_size, conv_kernel, group_count and full_attention_interval, and model_parameter_count gains the gated-DeltaNet layer term; no obligation is added or removed, and QE2E-INV-001 is not discharged.

🤖 Generated with Claude Code

@noahgift noahgift added this to the 0.68.0 milestone Sep 16, 2026
@noahgift
noahgift enabled auto-merge September 16, 2026 07:48
@github-actions

github-actions Bot commented Sep 16, 2026

Copy link
Copy Markdown

§13.11 rung 1 — quorum shadow verdict

S13-SHADOW pr=3350 head=abf8b56acd24be766f3eb806d08216e321926715 verdict=REFUSE class=Q1 arm_rc=1

Shadow mode: this records a verdict and merges nothing. A refusal
to arm is not a block (§13 adds zero rows to §7) — the pull request is
exactly as green as it was.

@noahgift noahgift modified the milestones: 0.68.0, 0.70.0 Sep 16, 2026
@noahgift
noahgift added this pull request to the merge queue Sep 16, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to a conflict with the base branch Sep 16, 2026
@noahgift noahgift modified the milestones: 0.70.0, 0.71.0 Sep 17, 2026
@noahgift

Copy link
Copy Markdown
Contributor Author

stale — APR-RELEASE-001 §6, applied mechanically 2026-09-20.

No activity since 2026-09-17 13:44Z; 2 release trains have been cut since (v0.68.2, v0.68.1). Age is the only input to this sweep — it is not a judgement on the work, and a train label such as pp-066/carry-* is not an exemption.

What happens next: if one more train passes while this is still labelled stale, §6 closes it. The branch is kept — closing a PR here discards a review thread, never commits.

To clear it: push, rebase, or say on the thread what it is waiting for. Any of the three removes the label at the next sweep. If it is blocked on something external, name that here — a blocker with an owner is not sprawl, and it stops the clock.

@noahgift

Copy link
Copy Markdown
Contributor Author

Adopting this PR (2026-09-21 sweep; author session is not live — branch cut 09-16, no session on the box predates it). Plan, before touching anything: the DIRTY state is the squash-of-the-stack-base class — c5b7bce70 on this branch is byte-identical (git patch-id) to main's dfd3d5f96 (#3348's squash), so every hunk built on it conflicts. Fix is git rebase --onto origin/main c5b7bce70: replay only the three commits that are this PR's own, no content change, then run the model_family / model_arithmetic tests and the roadmap guards on the rebased head before a --force-with-lease push. If a real conflict appears in the replay I stop and report it here rather than resolve it by guessing. Refs #3346.

noahgift and others added 4 commits September 21, 2026 00:34
…annot reproduce

QE2E-INV-001 could not be judged because nothing in the tree held a MEASURED
Qwen3.5 tensor inventory to judge against. This adds one: the 320 tensors of
~/models/Qwen3.5-0.8B-Q4_K_M.gguf (sha256 bd258782...dc517), read straight from
the GGUF header rather than from a model card.

It already falsifies the current arithmetic. Dense/GQA accounting applied to
that file gives 644,400,128 against a measured 752,393,024 — short by
107,992,896, 14.4% of the model, because 18 of the 24 layers are Gated DeltaNet
and no term here counts their conv, gate, state or output projections.

Two shapes in the file are also not what dense accounting predicts, and both are
pinned: attn_q is [1024, 4096] = 2 * num_heads * head_dim (the q projection
emits the attention output gate alongside the query; attn_output [2048, 1024]
confirms num_heads * head_dim = 2048), and attn_q_norm/attn_k_norm are present
at head_dim. The file is also TIED — it has no output.weight.

Refs #3346

Pmat-Ticket: PMAT-3346
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… a hybrid model can be counted

contracts/model-families/qwen3_5.yaml declares inner_size, state_size,
conv_kernel, group_count and full_attention_interval under constraints:, and
ModelConstraints carried none of them. A Gated DeltaNet layer's parameters live
entirely in those dimensions, so every consumer of the descriptor counted
Qwen3.5 as if three quarters of its layers did not exist.

Carried through as ModelConstraints::deltanet: Option<DeltaNetShape> — the
runtime YAML loader (parsing.rs) and the compiled-in registry (build_parsing.rs
+ build_codegen.rs) both populate it, and FALSIFY-MF-QWEN35-010 pins that the
declared values survive the trip and that no other family acquires a shape it
never declared. qwen3_5.yaml is the only descriptor with these keys, so every
other family keeps byte-identical accounting.

model_arithmetic gains gated_deltanet_layer_params (one term per GGUF tensor:
attn_qkv, attn_gate, ssm_conv1d, ssm_alpha/beta, ssm_a, ssm_dt.bias, ssm_norm,
ssm_out) and hybrid_layers (the interleaved schedule). attention_layer_params
gained two terms the real file has and dense accounting did not model: the
gated q projection (2*n_h*d_k) and the q/k norm vectors.

Falsified against a real model, not against itself: fed the 0.8B configuration,
the equation now reproduces the 320-tensor inventory of
Qwen3.5-0.8B-Q4_K_M.gguf EXACTLY — 752,393,024, both layer kinds matching
tensor for tensor.

QE2E-INV-001 is still NOT asserted, and no range was widened to make it pass.
The 9b descriptor now yields 8,344,907,136, up from 8,208,519,168 but still
0.655B below [9.0B, 9.2B]. The remaining gap looks like descriptor drift rather
than missing arithmetic: 9b keeps inner_size 2048 — the value the 0.8B uses at
hidden_dim 1024 — while quadrupling hidden_dim, and its group_count 8 fails
8 * 128 == 2048, a consistency the measured 0.8B satisfies at 16 * 128. Only a
real Qwen3.5-9B file can settle it; none is on this box.

Refs #3346, #3347

Pmat-Ticket: PMAT-3346
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…osed

The note said the obligation was undischarged because ModelConstraints does not carry the DeltaNet shape keys. It does now, and the 0.8B count reproduces the real GGUF exactly. What actually blocks the obligation is descriptor drift at the 9b variant.

Pmat-Ticket: PMAT-3346
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…nd pv extract shrank the graph by 356 triples instead of refusing

d3cc76f rewrote the note on the QE2E-INV-001 binding and dropped the
trailing `"` — contracts/binding.yaml stopped being valid YAML at line 764
(`found unexpected end of stream`). Nothing in the PR noticed because
`pv extract contracts` does not refuse a binding registry that will not
parse: it emitted a graph with 15,244 triples where main has 15,600 —
every bound symbol AFTER the broken entry (prune::run, distill::run,
harness_ir::*, ptx_explain::run, …) silently gone — and `--check`
would have agreed with itself. Found while regenerating the derivative
for this adoption, by the drop, not by any gate.

One character. With it, binding.yaml parses (156 entries, same as main)
and the extraction is byte-identical to main's committed contracts.nt, so
this PR owes no graph change after all.

Refs #3346, #3350

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@noahgift
noahgift force-pushed the PMAT-3346-gdn-shape-keys branch from d3cc76f to da1ee15 Compare September 20, 2026 22:41
@noahgift

Copy link
Copy Markdown
Contributor Author

Adopted and rebased — and the rebase found a real defect in the branch, now fixed.

What was done. git rebase --onto origin/main c5b7bce70 with the by-id roadmap driver bound: the first branch commit was byte-identical (git patch-id) to main's squash of #3348, so only this PR's own three commits were replayed. No conflicts. New head da1ee1506.

What the rebase found. Regenerating contracts/contracts.nt on the rebased head (the R-18 derivative rule) produced a graph 356 triples smaller than main — and the missing nodes were prune::run, distill::run, harness_ir::*, symbols this PR never touches. Cause: d3cc76f1f rewrote the QE2E-INV-001 note in contracts/binding.yaml and dropped the closing ", so the file stopped being valid YAML at line 764 (found unexpected end of stream). Every bound symbol after that entry vanished from the extraction. pv extract did not refuse; it emitted the smaller graph, and --check would have agreed with itself. One character, fixed in da1ee1506; binding.yaml parses again (156 entries, same as main) and the extraction is now byte-identical to main's committed graph, so this PR owes no contracts.nt change after all.

Verified on the new head: format::model_family + format::model_arithmetic 114 passed; apr-cli oracle 218 passed; ont4b_shapes_gate 11 passed; roadmap additive deleted=0 and fragment guard PASS; fmt and clippy -p aprender-core -D warnings clean; census unchanged. Measured with a pv built from this tree under a pinned target dir, and main's own graph re-derived with the same binary as the control.

Not armed: per the board ruling every PR needs a quorum receipt first; that runs next. Refs #3346.

…t to judge against

Acceptance transcribed from issue #3346 as this PR answers it (the type that
reads the descriptor was wrong, not the range or the descriptor), with the 9B
range instantiation explicitly out of scope until a real 9B GGUF exists.

Refs #3346

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@noahgift

Copy link
Copy Markdown
Contributor Author

quorum-review (AD-04): NOT agreed (auto_merge: checked=true was_armed=false disarmed=false)

{
 "ticket": "PMAT-3346",
 "head": "1ffdccf2500116a4dd39af6a66a697f7bb841136",
 "width": 3,
 "executor": "agy",
 "agreed": false,
 "auto_merge": {
  "checked": true,
  "was_armed": false,
  "disarmed": false,
  "note": "auto-merge not armed"
 },
 "lanes": [
  {
   "lane": 1,
   "verdict": "FAIL",
   "findings": 2
  },
  {
   "lane": 2,
   "verdict": "NO-VERDICT",
   "findings": 0
  },
  {
   "lane": 3,
   "verdict": "PASS",
   "findings": 4
  }
 ]
}

…iases

Found by the AD-04 quorum on #3350 (lane 1, gemini-3.1-pro-high, cited
model_arithmetic.rs:144): the projection term used q_out for a gated
family's q matrix (2*n_h*d_k — attn_q emits the output gate, MEASURED in
Qwen3.5-0.8B) while the bias term still used q_dim. A bias narrower than its
projection is not a model. One token: q_dim -> q_out in the bias sum.

Why a delta-0 measurement did not catch it: no shipped family exercises the
case. Qwen3.5 has no attention bias; Qwen2.5 has biases but is not gated, so
q_out == q_dim there. The test that pinned 12 (a q_dim bias under a q_out
matrix) now asserts 16 and says why, and a second test holds the other
polarity — a non-gated family with biases is unchanged at 12.

115 model_arithmetic + model_family tests pass; oracle 218; clippy clean.

Refs #3346, #3350

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@noahgift

Copy link
Copy Markdown
Contributor Author

Quorum round 0: 1 FAIL / 1 no-verdict / 1 PASS — and the FAIL was a real finding, now fixed in e060c7e2c. Lane 1 (gemini-3.1-pro-high, cited model_arithmetic.rs:144): the projection term used q_out for a gated family's q matrix (2·n_h·d_k, measured in Qwen3.5-0.8B) while the bias term still used q_dim — a bias vector narrower than the projection it biases. One token (q_dimq_out in the bias sum). It changed no shipped model's count — Qwen3.5 has no attention bias, Qwen2.5 has biases but is not gated — which is exactly how it survived a delta-0 measurement. The test that pinned 12 now asserts 16 and says why; a second test holds the other polarity (a non-gated family with biases stays at 12). 115 tests pass, oracle 218, clippy clean. Re-running the quorum on the new head. Refs #3346.

@noahgift

Copy link
Copy Markdown
Contributor Author

quorum-review (AD-04): three PASS — agreed (auto_merge: checked=true was_armed=false disarmed=false)

{
 "ticket": "PMAT-3346",
 "head": "e060c7e2c22bc221b5e454a9b677d09d2b27c2e2",
 "width": 3,
 "executor": "agy",
 "agreed": true,
 "auto_merge": {
  "checked": true,
  "was_armed": false,
  "disarmed": false,
  "note": "auto-merge not armed"
 },
 "lanes": [
  {
   "lane": 1,
   "verdict": "PASS",
   "findings": 5
  },
  {
   "lane": 2,
   "verdict": "PASS",
   "findings": 0
  },
  {
   "lane": 3,
   "verdict": "PASS",
   "findings": 2
  }
 ]
}

Round 0 was 1 FAIL / 1 no-verdict / 1 PASS and the FAIL was real (the bias
width, fixed in e060c7e). Round 1 on the fixed head: 3/3 PASS,
gemini-3.1-pro-high / pro-low / 3.6-flash-high, each measured, no dissent.

Refs #3346, #3350

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@noahgift
noahgift enabled auto-merge September 20, 2026 23:40
@noahgift

Copy link
Copy Markdown
Contributor Author

Cross-inspection of docs/audits/quorum-PMAT-3346.json lanes (non-author: aprender-04, 23:46Z)

lane conversation status verdict findings refs to sibling lanes / receipts / $WORK duration
lane 1 f3ea1f1c SUCCESS PASS (structured_output) 5 (2 cited) 0 644 s
lane 2 3f01ebe3 SUCCESS PASS (structured_output) 0 (0 cited) 0 55 s
lane 3 81760200 SUCCESS PASS (structured_output) 2 (0 cited) 0 394 s

Distinct agy conversation ids: 3/3; no lane references a sibling lane, another receipt or $WORK. Read from /tmp/claude-1000/-home-noah-src-aprender/51506a7a-265a-4e5c-bc53-50274de4a757/scratchpad/lanes-3346-round1 on this box. Note: round 0's pro lane found the gated-family bias-term defect (model_arithmetic.rs:144), fixed in e060c7e; this is round 1 on the new head; d4 armed via pmat-merge on the parent rule and this inspection is confirmatory.

Verdict line: 3/3 PASS, independent. Arm confirmed.

@noahgift

Copy link
Copy Markdown
Contributor Author

Folded into the 0.69 release batch #3669 by the cop (08:05Z, operator: "most PRs can be batched"). One CI run and one queue slot for all of them, and the generated files (roadmap.yaml, census, graph, shapes, README count) regenerated once. This PR's receipt is in the batch tree unchanged, and its closing keywords are carried in #3669's body. Disarmed here so the queue doesn't take it twice. It closes as landed-in-#3669 when the batch merges. Don't push here; changes go to release/0.69-batch.

@noahgift

Copy link
Copy Markdown
Contributor Author

Landed in #3669 (squash a877fa056, merged 2026-09-21T10:50:29Z). This PR's receipt stands as the constituent review; the batch folded its commits verbatim. — cop

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

Labels

stale APR-RELEASE-001 §6: >=2 release trains with no activity

Projects

None yet

Development

Successfully merging this pull request may close these issues.

QE2E-INV-001 asserts Qwen3.5-9B ∈ [9.0B, 9.2B] but the repo's own 9b descriptor sums to 8.209B — the descriptor declares no DeltaNet tensors

1 participant