Found by the PMAT-3430 plan session on a re-run; verified independently on origin/main (2026-09-17). A sixth instance of the #3418 class: a private per-type size list that disagrees with the spec.
crates/aprender-core/src/format/gguf/types.rs, GgufTensor::byte_size:
GgmlType::Q4_0 | GgmlType::Q4_1 => {
// Q4_0: 2 bytes scale + 16 bytes data = 18 bytes per 32 elements
(elements as usize).div_ceil(32) * 18
}
Q4_1 carries a scale and a min (ggml_half d, m + 16 bytes): 20 bytes per 32 elements — static_assert(sizeof(block_q4_1) == 2*sizeof(ggml_half) + QK4_1/2) in ggml/src/ggml-common.h, and (32, 2 + 2 + 16) in gguf-py, both at llama.cpp 3173a5647 [X]. The comment in the arm describes Q4_0 only. Any Q4_1 tensor sized through this function is short by 2 bytes per block (10 %).
Not measured here: which paths consume this value for a Q4_1 tensor (export offsets, validation, allocation) and whether any real file is mis-written or mis-read today. That is the first task; the defect in the arithmetic is certain, its blast radius is [U].
Fix: split the arm; after #3430 the size comes from the one TRAITS table and this function has no numbers of its own.
Falsifier: a test asserting byte_size for a 32-element tensor of every GgmlType variant equals the C static_assert size at the pinned sha — RED on main today for Q4_1.
Relation to #3430: the plan session found that fixing this inside the enum merge breaks M1's "behaviour-preserving" promise (plan v4 moved it out to land first). Unslotted — milestone is the operator's call.
🤖 Generated with Claude Code
Found by the PMAT-3430 plan session on a re-run; verified independently on
origin/main(2026-09-17). A sixth instance of the #3418 class: a private per-type size list that disagrees with the spec.crates/aprender-core/src/format/gguf/types.rs,GgufTensor::byte_size:Q4_1 carries a scale and a min (
ggml_half d, m+ 16 bytes): 20 bytes per 32 elements —static_assert(sizeof(block_q4_1) == 2*sizeof(ggml_half) + QK4_1/2)inggml/src/ggml-common.h, and(32, 2 + 2 + 16)in gguf-py, both at llama.cpp3173a5647[X]. The comment in the arm describes Q4_0 only. Any Q4_1 tensor sized through this function is short by 2 bytes per block (10 %).Not measured here: which paths consume this value for a Q4_1 tensor (export offsets, validation, allocation) and whether any real file is mis-written or mis-read today. That is the first task; the defect in the arithmetic is certain, its blast radius is
[U].Fix: split the arm; after #3430 the size comes from the one
TRAITStable and this function has no numbers of its own.Falsifier: a test asserting
byte_sizefor a 32-element tensor of everyGgmlTypevariant equals the Cstatic_assertsize at the pinned sha — RED onmaintoday for Q4_1.Relation to #3430: the plan session found that fixing this inside the enum merge breaks M1's "behaviour-preserving" promise (plan v4 moved it out to land first). Unslotted — milestone is the operator's call.
🤖 Generated with Claude Code