Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions crates/aprender-core/src/format/gguf/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,52 @@ fn test_from_bytes_with_one_f32_tensor() {
assert!((extracted[3] - 4.0).abs() < f32::EPSILON);
}

// ========================================================================
// #3601: get_tensor_raw byte-size table must cover every IQ/TQ ggml id,
// not just the K-quants — real Unsloth "UD-*" dynamic quants mix these
// into otherwise-K-quant GGUFs, and `apr inspect` needs a size for every
// tensor it lists, not just the ones it can dequantize.
// ========================================================================

#[test]
fn test_get_tensor_raw_sizes_every_iq_and_tq_id() {
// (dtype, blck_size, type_size, name) — mirrors aprender-serve's vetted
// GGML_TYPES (gguf/ggml_type_table.rs, #3432), which shape.rs's byte_size
// match must agree with.
let cases: [(u32, usize, usize, &str); 11] = [
(16, 256, 66, "IQ2_XXS"),
(17, 256, 74, "IQ2_XS"),
(18, 256, 98, "IQ3_XXS"),
(19, 256, 50, "IQ1_S"),
(20, 32, 18, "IQ4_NL"),
(21, 256, 110, "IQ3_S"),
(22, 256, 82, "IQ2_S"),
(23, 256, 136, "IQ4_XS"),
(29, 256, 56, "IQ1_M"),
(34, 256, 54, "TQ1_0"),
(35, 256, 66, "TQ2_0"),
];

for (dtype, blck_size, type_size, name) in cases {
// One full block's worth of elements, so byte_size is exact.
let tensor_data = vec![0u8; type_size];
let data = build_synthetic_gguf_with_tensor(
"t.weight",
&[blck_size as u64],
dtype,
&tensor_data,
&[],
);
let reader = GgufReader::from_bytes(data).expect("parse GGUF with tensor");
let (raw, shape, got_dtype) = reader
.get_tensor_raw("t.weight")
.unwrap_or_else(|e| panic!("{name} (dtype {dtype}) must be sized, not refused: {e}"));
assert_eq!(raw.len(), type_size, "{name} byte size");
assert_eq!(shape, vec![blck_size]);
assert_eq!(got_dtype, dtype);
}
}

#[test]
fn test_from_bytes_tensor_excessive_dims() {
// n_dims > MAX_DIMS should fail
Expand Down
14 changes: 14 additions & 0 deletions crates/aprender-core/src/format/gguf/shape.rs
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,21 @@ impl GgufReader {
13 => (num_elements / 256).checked_mul(176), // Q5_K: 256 elements = 176 bytes
14 => (num_elements / 256).checked_mul(210), // Q6_K: 256 elements = 210 bytes
15 => (num_elements / 256).checked_mul(292), // Q8_K: 256 elements = 292 bytes
// #3601: IQ-family (codebook/lattice quant) and ternary types, sized from
// aprender-serve's vetted `GGML_TYPES` (gguf/ggml_type_table.rs, #3432) —
// real Unsloth "UD-*" dynamic quants mix these into otherwise-K-quant files.
16 => (num_elements / 256).checked_mul(66), // IQ2_XXS
17 => (num_elements / 256).checked_mul(74), // IQ2_XS
18 => (num_elements / 256).checked_mul(98), // IQ3_XXS
19 => (num_elements / 256).checked_mul(50), // IQ1_S
20 => (num_elements / 32).checked_mul(18), // IQ4_NL
21 => (num_elements / 256).checked_mul(110), // IQ3_S
22 => (num_elements / 256).checked_mul(82), // IQ2_S
23 => (num_elements / 256).checked_mul(136), // IQ4_XS
29 => (num_elements / 256).checked_mul(56), // IQ1_M
30 => num_elements.checked_mul(2), // BF16: 2 bytes per element
34 => (num_elements / 256).checked_mul(54), // TQ1_0
35 => (num_elements / 256).checked_mul(66), // TQ2_0
_ => {
return Err(AprenderError::FormatError {
message: format!("Unsupported dtype {} for raw extraction", meta.dtype),
Expand Down
Loading