Skip to content

PP-QUANT-001 M1: one #[repr(u32)] GgmlType (43 rows: 35 live + 8 Removed) + TRAITS[43] below core/compute/serve — ggml enums 3 → 1 #3430

Closed

Description

@noahgift

⚠ FALSIFIER SUPERSEDED (operator ruling 2026-09-20, amended same day). The falsifier below — agreement with existing core/compute/serve values — is unsatisfiable (core Q4_1 18 bytes/block vs upstream 20; Q8_1 36 upstream by static_assert vs 40 in-tree). Replaced by: for every live row i, TRAITS[i].{blck_size,type_size} equals the value extracted from ggml at the pin resolved by reference (scripts/llama_pin.toml build_commit, resolved at run time — never a literal sha anywhere, including in a strike-through), the extraction output is a checked-in fixture recording the sha it read, a test asserts fixture.sha == build_commit, and mutation of any one row turns the test RED. Upstream ggml is the oracle; in-tree enums are the defendant.

Title numbers verified 2026-09-20 by re-extraction at whatever scripts/llama_pin.toml build_commit resolves to: 35 live + 8 Removed = 43 slots (GGML_TYPE_COUNT = 43). No longer [U]; no title correction needed. Full ruling, amendment and extraction method in the comments.


Parent: #3421 · Slot: 0.69.0 must-carry.
Mechanism: three independent enums name ggml tensor types and disagree — serve GgmlQuantType 16 ids, core GgmlType 12 (the import path; no Q5_0/Q5_1/Q2_K/Q3_K/Q5_K), compute GgmlType 15 — against 35 live ids at llama.cpp 3173a5647. #3405 added a fourth private table (QTYPE_LABELS, 13 rows).
Fix: one closed enum + const TRAITS: [QuantTraits; 43] {name, blck_size, type_size, family} in the lowest common workspace dependency, or a new zero-dep leaf crate if none exists (then it joins clean-room + publish order). The three enums become re-exports. GGUF metadata value-type ids (UINT32/STRING/INT32/ARRAY) are a separate enum — a different id space.
Falsifier: exactly 1 ggml tensor-type enum definition under crates/*/src; TRAITS (block, bytes) equals gguf-py GGML_QUANT_SIZES for every shared id.

Ĵ: 24 turns [A] — borrowed; estimate.sh aprender pools 0 of 47 ledger rows today, so there is no measured basis.
Plan + evidence: #3427 · design discussion: #3418 · spec PR: #3420

🤖 Generated with Claude Code


Carried from the PMAT-3427 receipt (operator decision 2026-09-17) — PLAN PHASE FIRST, no code until the plan quorum is 3/3.
The design quorum for Q1 (one #[repr(u32)] GgmlType, 43 rows, TRAITS[43], homed in the lowest common workspace dep or a zero-dep leaf crate) and X1 (ordering Q1 → T1/T2 → Q2 → Q3) runs inside this ticket's own plan phase. Inputs, all in docs/audits/impl-PMAT-3427-receipt.md (draft PR #3447), measured on origin/main @ 7eb81a531:

Activity

  1. added this to the 0.69.0 milestone on Sep 17, 2026
  2. noahgift commented on Sep 17, 2026

    @noahgift
    MemberAuthor

    Falsifier correction (my wording was unmeetable) — 2026-09-17. This ticket's body says TRAITS (block, bytes) must equal gguf-py GGML_QUANT_SIZES for every shared id. That cannot be met: at llama.cpp 3173a5647 gguf-py gives Q8_1 = 40 (4 + 4 + 32) while ggml's C struct is 36 (static_assert(sizeof(block_q8_1) == 2*sizeof(ggml_half) + QK8_1), ggml/src/ggml-common.h) [X], verified. Found by this ticket's own plan session.

    Read the falsifier as: TRAITS (block, bytes) equals the C static_assert sizes in ggml-common.h at the pinned sha for every live id; gguf-py is a second source, and every id where the two disagree is listed by name with both values (today: Q8_1) rather than silently resolved. Table S in the PMAT-3427 receipt took its type_size column from gguf-py and is corrected for Q8_1 only — the other 34 rows are not yet re-checked against C; that is Phase 2 of this ticket.

    This does not touch the open design ruling on objection classes A/B (posted on #3421).

  3. noahgift commented on Sep 20, 2026

    @noahgift
    MemberAuthor

    RULING (operator, 2026-09-20) — PMAT-3430 M1

    Recorded verbatim. Lane A is unblocked; no fifth quorum round.

    1. Settled, no further quorum: target (one #[repr(u32)] GgmlType, 43 rows: 35 live + 8 Removed), home crate aprender-quant, Table E, X1 ordering. Undisputed in rounds 1-4; they are closed.

    2. Oracle: upstream ggml at the pinned llama.cpp sha (3173a5647) is the reference [X]. In-tree enums are the defendant, never the reference. Where they disagree, in-tree is a defect.

    3. Falsifier, re-worded. Old wording (agreement with the existing core/compute/serve values) is unsatisfiable: core Q4_1 = 18 bytes/block vs upstream 20 (2xf16 + 16); Q8_1 upstream is 36 by static_assert vs 40 in-tree (pre-f16 layout).
      New: for every live row i, TRAITS[i].{blck_size,type_size} equals the value extracted from ggml at the pinned sha by a committed command; the extraction output is a checked-in fixture; mutation of any one row turns the test RED (shown in the PR body).

    4. Every in-tree disagreement is a row in the reconciliation table (PMAT-3431), each with: upstream value, in-tree value, call sites, and a verdict of NEVER-WORKED or REGRESSION, measured on the v0.68.1 binary - not inferred.

    5. Fix-or-refuse per type: a type whose in-tree size was wrong ships in M1 with the upstream value AND a one-line refusal + exit code on load, until a round-trip fixture for that type is green. No silent behaviour change for Q4_1/Q8_1 consumers.

    6. Remaining disputed D-rows: one amendment round, then HRQ. No fifth open round.

    One clause needs a correction before it can be executed — clause 2's sha

    The ruling makes the pinned llama.cpp sha the oracle, so every number in M1's fixture is extracted from whatever that sha resolves to. It does not resolve here. Measured on the release tree:

    sha status
    d1d3c3396 the pin of record — scripts/llama_pin.toml build_commit, pinned_on = 2026-09-15, pin_expiry = 2026-12-01, 216 references in-tree
    39173bcac an older comparator pin, still referenced 124 times (e.g. the CPU-leg parity basis in evidence/parity/thresholds.yaml)
    3173a5647 (the ruling's) appears nowhere in the tree — grep -rl over the repo returns nothing

    I am not substituting a sha into the ruling by inference: the whole point of clause 2 is that the oracle is named, and an oracle resolved by guessing is not an oracle. The implementer needs one line back: is the oracle d1d3c3396 (the pin of record), or a deliberate new pin? If it is a new pin, note that bumping the comparator invalidates every ratio measured against the old one — llama_pin.toml says so itself, and that is a decision with a named cost rather than a bump.

    Second-order finding, independent of the answer: two comparator pins are live in-tree at once — 216 references to d1d3c3396 and 124 to 39173bcac. Some committed artifacts are therefore measured against a superseded oracle. That deserves its own row regardless of how clause 2 resolves.

    What proceeds now, unblocked

    Refs #3080, #3430, #3431, #3421, #3562

  4. noahgift commented on Sep 20, 2026

    @noahgift
    MemberAuthor

    AMENDMENT (operator, 2026-09-20) — clause 2 of the M1 ruling, recorded verbatim

    2'. The oracle is scripts/llama_pin.toml build_commit, by reference, never a literal sha. Today that resolves to d1d3c3396. No pin bump is ordered by this ruling. "3173a5647" in the original clause is struck: it was the checkout the 43/35/8 count was first measured at, not a pin.

    2a. The TRAITS fixture records the sha it was extracted from. A test asserts fixture.sha == llama_pin.toml build_commit. A pin bump therefore turns the fixture RED until it is regenerated by the committed extraction command — the bump's cost is visible in the bump PR, not discovered later.

    2b. Row counts are derived, not ruled. Re-extract at d1d3c3396; if live/Removed differs from 35/8, the extraction wins and the PMAT-3430 title is corrected. "43" is [U] until then.

    2b is now measured — the ticket's numbers hold. [U] cleared.

    Extracted from the pinned checkout at /home/noah/src/llama.cpp-d1d3c3396 (git rev-parse HEAD → d1d3c3396aa1…, matching llama_pin.toml build_commit), from ggml/include/ggml.h:

    count ids
    live 35 0–3, 6–30, 34, 35, 39–42
    Removed (commented out) 8 4 Q4_2, 5 Q4_3, 31 Q4_0_4_4, 32 Q4_0_4_8, 33 Q4_0_8_8, 36 IQ4_NL_4_4, 37 IQ4_NL_4_8, 38 IQ4_NL_8_8
    total slots 43 GGML_TYPE_COUNT = 43, ids 0…42, and 35 + 8 = 43 ✓

    So 43 rows: 35 live + 8 Removed is confirmed at the pin. No title correction is needed and the count can stop being [U].

    Method note, because it nearly went the other way. A first pass with a one-line grep returned 44/41/3 and looked like a disagreement. It was wrong twice: it counted GGML_TYPE_COUNT as a type, and it classified a commented row as live whenever the comment did not contain the word "removed" (// GGML_TYPE_Q4_0_4_8 = 32, says nothing). Reading the enum settled it. Worth stating because clause 3's fixture will be generated by a command, and a sloppy extractor produces a fixture that is confidently wrong on exactly these rows — the commented block at 31–33 and 36–38 is the trap.

    Four live types are recent — MXFP4 (39), NVFP4 (40), Q1_0 (41), Q2_0 (42). Whether in-tree carries them at all is part of clause 4's reconciliation, and a NEVER-WORKED verdict is the likely shape there rather than REGRESSION.

    What clause 3's committed extraction command must do

    1. Parse enum ggml_type from ggml/include/ggml.h at the resolved pin, taking commented rows as Removed by position, not by comment wording.
    2. Take blck_size / type_size from ggml's own type-traits table, not from the header comment, and not from any in-tree value.
    3. Emit the fixture with the sha it read, so 2a's assertion has something to compare.
    4. Prove it can fail: mutate one row, expect RED (in the PR body, per clause 3).

    2a generalises, and there is already a ticket for the general case

    2a — an artifact derived from the pin records the pin, and a test asserts they match, so a bump's cost surfaces in the bump PR — is exactly the mechanism #3563 asks for repo-wide, where two comparator pins are live at once (216 references to d1d3c3396, 124 to the earlier 39173bcac). The TRAITS fixture should be the first artifact built to this shape, and #3563's guard should be written as the general form of it rather than as a separate idea.

    Refs #3080, #3430, #3431, #3452, #3563

  5. noahgift commented on Sep 20, 2026

    @noahgift
    MemberAuthor

    done_when

    (Fixed heading, written by the cop for tab 2 — see #3452 for the full chain definition, which governs this row.)

    This is the implementation half of the ruled M1 plan. Its done_when is #3452's, in full — the falsifier, the oracle-by-reference rule, the derived row counts (35 live + 8 Removed = 43, verified at d1d3c3396, no longer [U]), the by-position Removed classification, and fix-or-refuse per type.

    Two things specific to this ticket:

    • The superseded falsifier banner at the top of this issue body stays accurate — the original wording (agreement with existing core/compute/serve values) is unsatisfiable and must not be implemented.
    • TRAITS[43] lives in aprender-quant (settled, clause 1), and the fixture + its sha assertion ship in the same PR as the table — not as a follow-up. A table without its falsifier is the thing the four quorum rounds were about.

    STOP conditions and the fresh-worktree rule: as #3452.

  6. noahgift commented on Sep 20, 2026

    @noahgift
    MemberAuthor

    M1's leaf is up: #3581 — the ruling executed, with the extraction and the mutation proof

    Clauses 1–6 with amendment 2'/2a/2b. trueno_quant::GgmlType (35 live variants) + TRAITS[43], every number extracted from ggml at the pin by a committed command, fixture recording its sha, six mutations RED and every control GREEN. Behaviour unchanged: nothing imports the leaf yet, and serve's 16 strings / compute's 15 sizes are asserted identical.

    Two corrections to the ruling's premises, both measured

    1. Clause 3 says "Q8_1 upstream is 36 by static_assert vs 40 in-tree (pre-f16 layout)". There is no 40 in-tree. Every in-tree Q8_1 size is already 36 — aprender-compute's block_bytes, aprender-core's shape.rs:230, and the GPU dp4a kernels. The 40 came from gguf-py, via Table S of the PMAT-3427 receipt, which is a document, not code. So clause 5's fix-or-refuse has no Q8_1 case at all, and the extraction (which reads the C table) simply gives 36.

    2. Clause 5 has no Q4_1 case either, for a different reason. GgufTensor::byte_size really does size Q4_1 at 18 where upstream says 20 — but it has no non-test caller in the workspace: export_tensors_to_gguf sizes tensors with tensor.data.len(). No GGUF was ever written wrong by it, and there is no load path for a refusal to sit on. Core's other size table (shape.rs) has always said 20, so the defect is two tables in one crate disagreeing rather than a wrong loader.

    Clause 5 therefore ships with no refusal, stated rather than silently skipped. If #3431's reconciliation finds a wrong size on a real load path, that type gets clause 5's treatment there.

    The defect that matters, for #3431's table (clause 4)

    aprender-core::ggml_dtype_element_size (format/safetensors.rs) is wrong in 10 of its 31 rows. From index 16 the table is written in the order of its own comment — … I8, I16, BF16, I32, I64, F64, IQ1_M — instead of ggml id order (24 I8, 25 I16, 26 I32, 27 I64, 28 F64, 29 IQ1_M, 30 BF16), and the IQ rows carry values belonging to no type at all.

    id type in-tree B/elem upstream (type_size/blck_size)
    16 IQ2_XXS 0.5625 0.257_812_5 (66/256)
    17 IQ2_XS 0.625 0.289_062_5 (74/256)
    18 IQ3_XXS 0.6875 0.382_812_5 (98/256)
    19 IQ1_S 0.4375 0.195_312_5 (50/256)
    21 IQ3_S 0.4375 0.429_687_5 (110/256)
    22 IQ2_S 0.625 0.320_312_5 (82/256)
    23 IQ4_XS 0.5 0.531_25 (136/256)
    26 I32 2.0 4.0
    27 I64 4.0 8.0
    29 IQ1_M 8.0 0.218_75 (56/256)
    30 BF16 0.375 2.0
    34, 35, 39–42 TQ1_0, TQ2_0, MXFP4, NVFP4, Q1_0, Q2_0 absent (unwrap_or(4.0)) 0.210_937_5, 0.257_812_5, 0.531_25, 0.5625, 0.140_625, 0.281_25

    Verdict NEVER-WORKED, with a user-visible consequence. list_tensors_gguf uses this table for the #2569 truncation check: BF16 at 0.375 instead of 2.0 is a 5.3× under-estimate, so a truncated BF16 GGUF passes the extent check — fail-open — and apr tensors prints a size 5.3× too small. The IQ rows err the other way and would reject an intact file as truncated.

    PMAT-869 corrected four K-quant rows of this same table in 2026-06 and stopped there. The audit-lags-main pattern, and the reason the whole table is being re-derived rather than patched row by row.

    It is not fixed in #3581: the fix is a lookup into TRAITS, which is what #3581 creates, and doing both at once would make the leaf's "changes no behaviour" claim unverifiable. It lands next, with the Q4_1 row, before Phases 3–4.

    Disposition of #3452

    Its plan (v4) and the four-round quorum history are the provenance for this work and are cited by the receipt; nothing in them is contradicted except where the ruling superseded the falsifier and where §6 above corrects a premise. It carries no code and can merge or close on tab 1's call — #3581 does not depend on it.

    Refs #3430, #3421, #3431, #3452, #3563

  7. noahgift commented on Sep 20, 2026

    @noahgift
    MemberAuthor

    Note for tab 2's first read — two literal shas remain below the fold, in the pre-existing ticket text: 3173a5647 and 7eb81a531. They are in the original body written before the oracle-by-reference rule, not in the falsifier or the banner, and I have left them rather than silently rewriting someone else's ticket text.

    Neither is authority. The oracle is scripts/llama_pin.toml build_commit, resolved at run time. 3173a5647 is the struck literal from the original ruling — it resolves nowhere in this tree. If the implementation grep finds either string, that is the literal-a-grep-will-find problem the rule exists to prevent; delete them in the M1 PR rather than working from them.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    P1High priorityenhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions