huggingface/candle · error
gguf: header declares
Error message
gguf: header declares {tensor_count} tensors and {metadata_kv_count} metadata entries, needs at least {needed} bytes, only {remaining} remaining What it means
candle cross-checks the header-declared counts against the actual remaining file size. Each tensor and metadata entry has a minimum byte size, and if the declared minimum total exceeds the bytes left in the file, the file cannot possibly be valid, so it is rejected before any allocation.
Solutions
- Re-download the GGUF file completely and compare file size/checksum against the published value
- Check that the download tool finished (no partial .part files)
- Verify storage is not returning placeholder/sparse data (e.g. OneDrive/Dropbox on-demand files)
Example fix
// before
let gguf = GgufFile::from_reader(&mut BufReader::new(File::open("model.gguf")?))?;
// after
let meta = std::fs::metadata("model.gguf")?;
if meta.len() < EXPECTED_MIN_SIZE {
return Err(anyhow!("model.gguf is truncated: {} bytes, expected at least {}", meta.len(), EXPECTED_MIN_SIZE));
}
let gguf = GgufFile::from_reader(&mut BufReader::new(File::open("model.gguf")?))?; Defensive patterns
Strategy: validation
Validate before calling
let meta = std::fs::metadata("model.gguf")?;
if meta.len() < EXPECTED_FILE_SIZE {
return Err(anyhow!("truncated: {} < {} bytes", meta.len(), EXPECTED_FILE_SIZE));
} Try / catch
match GgufFile::from_reader(&mut reader) {
Ok(g) => g,
Err(e) if e.to_string().contains("needs at least") => {
eprintln!("gguf file is truncated relative to its header; re-download");
return Err(e.into());
}
Err(e) => return Err(e.into()),
} Prevention
- Compare downloaded file size to the published size before loading
- Disable on-demand/cloud placeholder file modes for model directories
- Re-download interrupted transfers
When it happens
Trigger: GgufFile::read where tensor_count * min_tensor_size + metadata_kv_count * min_kv_size > remaining file bytes — a truncated or fabricated GGUF file.
Common situations: Interrupted downloads; cloud sync placeholders not fully fetched; sparse/corrupt files; deliberately crafted files with inflated header counts.
Related errors
- gguf: array of elements needs at least bytes, only remaining
- gguf: metadata_kv_count
- gguf: tensor_count exceeds max
- gguf: tensor ' ' has dimensions, max is
- unrecognized value-type
AI-assisted analysis of huggingface/candle@d5fee525bf (2026-09-02).
Data as JSON: /api/errors/c8b8c3819fc3219f.
Report an issue: GitHub.
Appendix: source
Thrown at candle-core/src/quantized/gguf_file.rs:501
if metadata_kv_count > GGUF_MAX_ARRAY_ELEMENTS {
crate::bail!(
"gguf: metadata_kv_count {metadata_kv_count} exceeds max {GGUF_MAX_ARRAY_ELEMENTS}"
)
}
// Reject header-declared counts that can't fit in the file at minimum size.
// Per-entry minima: a metadata kv is at least `key_len_prefix + u32 value_type
// + 1 byte value`; a tensor info is at least `name_len_prefix + u32 n_dims
// + u32 dtype + u64 offset`.
let prefix = magic.length_prefix_size();
let min_per_kv = prefix + 4 + 1;
let min_per_tensor = prefix + 4 + 4 + 8;
let needed = metadata_kv_count
.saturating_mul(min_per_kv)
.saturating_add(tensor_count.saturating_mul(min_per_tensor));
let remaining = remaining_bytes(reader, file_size)?;
if needed > remaining {
crate::bail!(
"gguf: header declares {tensor_count} tensors and {metadata_kv_count} metadata entries, needs at least {needed} bytes, only {remaining} remaining"
)
}
let mut metadata = HashMap::new();
for _idx in 0..metadata_kv_count {
let key = read_string(reader, &magic, file_size)?;
let value_type = reader.read_u32::<LittleEndian>()?;
let value_type = ValueType::from_u32(value_type)?;
let value = Value::read(reader, value_type, &magic, 0, file_size)?;
metadata.insert(key, value);
}
let mut tensor_infos = HashMap::new();
for _idx in 0..tensor_count {
let tensor_name = read_string(reader, &magic, file_size)?;
let n_dimensions = reader.read_u32::<LittleEndian>()?;
if n_dimensions > GGUF_MAX_TENSOR_DIMS {
crate::bail!(View on GitHub (pinned to d5fee525bf)