Skip to content
Open
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
14 changes: 9 additions & 5 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,17 @@ All notable changes to this project are documented in this file.
crates: `[dependencies]` stays empty for this feature; the upstream
`safetensors` crate, `serde_json`, and `corinth-canal` are not used. Does
not include payload mmap or Hugging Face `config.json` policy.
- **Optional `mmap` feature (#45 option 1):** `load_gguf_mmap` maps a GGUF with
`memmap2` 0.9.11 instead of `fs::read` into a `Vec<u8>`. Default builds stay
zero-dep (`default = []`). Packed CPU dequant for **Q8_0**, **Q5_K**,
**Q6_K**, and the internal **IQ3_M block** layout
- **Optional `mmap` feature (#45 option 1 / RM-367):** `load_gguf_mmap` maps a
GGUF with `memmap2` 0.9.11 instead of `fs::read` into a `Vec<u8>`. Default
builds stay zero-dep (`default = []`). Packed CPU dequant for **Q8_0**,
**Q5_K**, **Q6_K**, and the internal **IQ3_M block** layout
(`GGML_TYPE_IQ3_M_BLOCK = 0x4949334D`, 111 bytes / 256 values). Wire type
**31** remains historical **Q4_0_4_4** (`DType::Other(31)`). CUDA
host-register is still out of scope.
host-register is still out of scope. `GgufLayoutMmap::directory_matches`
compares the full tensor directory (not just count/architecture). CI covers
mmap↔owned packed dequant, a tensor past the first OS page, and a **sparse
2 GiB** mapping that never calls `fs::read`. Real on-disk multi-GB pilots
remain `#[ignore]` behind `ENGRAM_GGUF`.

### Changed

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@ cargo test --all-features
cargo llvm-cov --all-targets --all-features --locked --lcov --output-path lcov.info
```

`load_gguf` reads and retains the complete file. For multi-GB checkpoints use `cargo test --features mmap` / `load_gguf_mmap`. Real GGUF pilots require local files (`ENGRAM_GGUF`) and are `#[ignore]` so CI stays green without them. CUDA host-register is still out of scope.
`load_gguf` reads and retains the complete file. For multi-GB checkpoints use `cargo test --features mmap` / `load_gguf_mmap`. CI mmap tests include packed Q8_0/Q5_K/Q6_K/IQ3_M dequant from the mapping and a sparse 2 GiB file that is mapped without `fs::read`. Real on-disk GGUF pilots still require local files (`ENGRAM_GGUF`) and are `#[ignore]`. CUDA host-register is still out of scope.

```bash
ENGRAM_GGUF=~/.models/gguf/.../model.gguf ENGRAM_EXPECT_MOE=1 \
Expand Down
7 changes: 7 additions & 0 deletions src/gguf/dequant.rs
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ fn fail_closed_dequant(dtype: DType) -> ParserError {

pub(crate) fn dequantize_row_q8_0(row: &[u8], width: usize) -> Result<Vec<f32>> {
require_multiple(width, Q8_0_BLOCK, "Q8_0")?;
expect_row_len(row, width, Q8_0_BLOCK, Q8_0_BYTES, "Q8_0")?;
let mut out = Vec::with_capacity(width);
let (blocks, _) = row.as_chunks::<Q8_0_BYTES>();
for block in blocks {
Expand All @@ -159,6 +160,7 @@ pub(crate) fn dequantize_row_q8_0(row: &[u8], width: usize) -> Result<Vec<f32>>

pub(crate) fn dequantize_row_q5_k(row: &[u8], width: usize) -> Result<Vec<f32>> {
require_multiple(width, K_BLOCK, "Q5_K")?;
expect_row_len(row, width, K_BLOCK, Q5_K_BYTES, "Q5_K")?;
let mut out = Vec::with_capacity(width);
let (blocks, _) = row.as_chunks::<Q5_K_BYTES>();
for block in blocks {
Expand Down Expand Up @@ -537,12 +539,17 @@ mod tests {
fn dequant_error_paths() {
assert!(dequantize_q8_0(&[0u8; 34], &[31]).is_err());
assert!(dequantize_q8_0(&[0u8; 33], &[32]).is_err());
assert!(dequantize_row_q8_0(&[0u8; 33], 32).is_err());
assert!(dequantize_row_q5_k(&[0u8; 175], 256).is_err());
assert!(dequantize_iq3_m(&[0u8; 111], &[255]).is_err());
assert!(dequantize_iq3_m(&[0u8; 110], &[256]).is_err());
assert!(dequantize_q5_k(&[0u8; 176], &[]).is_err());
assert!(dequantize_q8_0(&[], &[0]).is_err());
assert!(dequantize_packed(DType::F32, &[0u8; 4], &[1]).is_err());
assert!(dequantize_packed(DType::Other(31), &[0u8; 18], &[32]).is_err());
assert!(dequantize_packed(DType::Q5_K, &[0u8; 176], &[256]).is_ok());
assert!(dequantize_packed(DType::Q6_K, &[0u8; 210], &[256]).is_ok());
assert!(dequantize_packed(DType::IQ3_M_BLOCK, &[0u8; 111], &[256]).is_ok());
}

#[test]
Expand Down
16 changes: 16 additions & 0 deletions src/gguf/map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -183,11 +183,27 @@ impl GgufLayoutMmap {
}

/// Compare directory metadata with an owned [`GgufLayout`] of the same file.
///
/// Checks alignment, tensor-data offset, architecture, and every tensor's
/// name, shape, dtype, packed length, and offsets. Does not compare
/// payload bytes (callers that need that should use
/// [`Self::tensor_bytes`] against [`GgufLayout::tensor_bytes`]).
pub fn directory_matches(&self, owned: &GgufLayout) -> bool {
self.alignment == owned.alignment
&& self.tensor_data_offset == owned.tensor_data_offset
&& self.tensors.len() == owned.tensors.len()
&& self.metadata.architecture() == owned.metadata.architecture()
&& self.tensors.iter().all(|(name, mapped)| {
owned.tensors.get(name).is_some_and(|owned_t| {
mapped.dims == owned_t.dims
&& mapped.dtype == owned_t.dtype
&& mapped.ggml_type == owned_t.ggml_type
&& mapped.n_elements == owned_t.n_elements
&& mapped.byte_len == owned_t.byte_len
&& mapped.relative_offset == owned_t.relative_offset
&& mapped.absolute_offset == owned_t.absolute_offset
})
})
}
}

Expand Down
4 changes: 4 additions & 0 deletions tests/common/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,11 @@ pub const VT_STRING: u32 = 8;
pub const GGML_F32: u32 = 0;
pub const GGML_Q8_0: u32 = 8;
pub const GGML_Q4_K: u32 = 12;
pub const GGML_Q5_K: u32 = 13;
pub const GGML_Q6_K: u32 = 14;
pub const GGML_IQ3_S: u32 = 21;
/// Internal 111-byte IQ3_M block id (not GGUF wire type 31).
pub const GGML_IQ3_M_BLOCK: u32 = 0x4949_334D;

pub enum KvValue {
U32(u32),
Expand Down
Loading