ἀνάμνησις — Parse any format, recover any precision.
A framework-agnostic Rust library and CLI for tensor-file work: parse
.safetensors, .gguf, .npz, and PyTorch .pth; dequantize quantized
weights back to bit-exact BF16 (FP8 · GPTQ · AWQ · BitsAndBytes · all 22 GGUF
block types); inspect any format header-only without loading weights; and convert
between formats — all hardened for untrusted input.
Published on crates.io; pre-1.0, so the API may still evolve — see CHANGELOG and ROADMAP.
- Install
- CLI Commands
- Try it
- Library quick start
- Parsing untrusted input
- Formats & quantization support
- Performance & validation
- Documentation
- What's next
- Used by
- License
- Development
New to anamnesis?
- I have a model file and want to see inside it →
amn inspect, or the Inspect before you parse tutorial — format, tensors, shapes, dtypes, size, header-only.- I want to recover full precision from a quantized model → Dequantize a GGUF model to BF16: a k-quant becomes standard
BF16safetensors, loadable in candle / burn / tch.- I want to move a model to another format → Convert a model between formats: any input →
safetensors/gguf/bnb-nf4in one command, quantized inputs auto-chained throughBF16.- I'm parsing untrusted / user-uploaded files on a server → Parsing untrusted input: the
inspect → check → parse-under-limitsrecipe — typed errors, no panic, noSIGBUS.- I'm calling anamnesis from Rust → Library quick start.
- I'm waiting for Python →
pip install anamnesislands in v0.8.0 (What's next); the interop contract is already frozen.Common questions live in the FAQ; every command and flag is in the CLI Reference.
cargo install anamnesis --features cli,pth,ggufInstalls both anamnesis and amn (short alias). Pick the formats and schemes
you need via feature flags (gptq, awq, bnb, npz, pth, gguf, ollama,
indicatif) — the full list is in the CLI Reference.
| Command | |
|---|---|
amn parse <file> |
Parse and summarize a model file (.safetensors, .pth, .npz, .gguf) |
amn inspect <file> |
Show format, tensor counts, size estimates, dtypes, byte order |
amn remember <file> |
Dequantize to BF16 (safetensors) or convert .pth/.gguf → .safetensors |
amn convert <file> --to <target> |
Convert any input to safetensors / gguf / bnb-nf4 through one dispatch |
Aliases: amn info = amn inspect, amn dequantize = amn remember. Format
detection is automatic (by extension, then magic bytes for .bin). Every flag,
the full convert matrix, output-path rules, and the ollama: URL scheme are in
the CLI Reference.
$ amn inspect model-fp8.safetensors # what precision is in here?
Format: Fine-grained FP8 (E4M3), 128x128 blocks
Quantized: 1 tensors (weights) + 1 scale tensors (F32)
Passthrough: 1 tensors (norms, embeddings)
Size: 96 B (FP8) -> 144 B (BF16)
Lethe took: ~48 B of precision
$ amn remember model-fp8.safetensors # recover it: FP8 → bit-exact BF16
Parsing... 3 tensors, Fine-grained FP8 (E4M3), 128x128 blocks
Recalling... 1 tensors
Output: model-bf16.safetensors (144 B)
$ amn inspect ollama:llama3.2:1b # header-only on a real GGUF — no weights loaded
Format: GGUF v3
Arch: llama
Tensors: 147
Total size: 1.22 GB
Dtypes: F32, Q8_0
Alignment: 32 bytes
$ amn convert model.npz --to safetensors # any input → any target, one dispatch
Converting model.npz -> model-bf16.safetensors (NPZ -> safetensors)
Wrote 5 tensors -> model-bf16.safetensors
The ollama: scheme (built with --features ollama) resolves
ollama:<model>:<tag> straight to the local Ollama cache's GGUF blob — see the
CLI Reference.
[dependencies]
anamnesis = { version = "0.6", features = ["gguf", "pth"] }Inspect any format header-only (no weight data loaded):
let info = anamnesis::parse_gguf("model.gguf")?.inspect();
println!("{} tensors, {} bytes", info.tensor_count, info.total_bytes);Dequantize a quantized model to bit-exact BF16 safetensors, in memory:
use anamnesis::{parse, TargetDtype};
let bytes = parse("model-fp8.safetensors")?.remember_to_bytes(TargetDtype::BF16)?;For untrusted input, prefer the copy-based parse_bytes / parse_*_from_reader
entry points (see below). Full API on docs.rs.
A .safetensors, .npz, .pth, or .gguf file is attacker-controllable — it
can declare arbitrary tensor counts, shapes, lengths, or expansion ratios.
anamnesis is parse-first so a multi-tenant or edge host can reject a hostile
or too-large file against limits it chooses, before committing memory. The
recipe is inspect → check policy → parse under limits:
use anamnesis::{inspect_npz_from_reader, parse_npz_with_limits, ParseLimits};
// 1. Inspect cheaply — header-only, bounded; never materialises tensor data.
let info = inspect_npz_from_reader(&mut reader)?;
// 2. Reject early against YOUR environment's policy — no parse, no allocation.
if info.total_bytes > my_ram_budget || info.tensors.len() > my_tensor_cap {
return Err(/* 413 Too Large */);
}
// 3. Parse under a `ParseLimits` matched to this worker — enforced fail-fast,
// before each allocation.
let limits = ParseLimits::default()
.with_max_total_bytes(my_ram_budget)
.with_max_item_count(my_tensor_cap)
.with_max_decompression_ratio(1000); // reject a DEFLATE entry inflating >1000×
let tensors = parse_npz_with_limits(path, &limits)?;Every parser has a parse_*_with_limits sibling. The four ParseLimits axes
(all opt-in; ParseLimits::default() is unbounded, tighten-only over the
built-in per-format floors):
| Axis | Builder | Bounds |
|---|---|---|
| Single allocation | with_max_single_alloc |
the largest single header-declared buffer |
| Cumulative heap | with_max_total_bytes |
the running sum across the whole file (many-small-items blow-up) |
| Item count | with_max_item_count |
declared tensors / arrays / KV entries / archive members |
| Decompression ratio | with_max_decompression_ratio |
a compressed entry's uncompressed:compressed ratio (.npz zip-bomb cap) |
Error taxonomy. A rejection's kind is its AnamnesisError variant, so a
host branches without string-matching: a budget/cap breach (a ParseLimits axis
or an always-on permanent floor like MAX_PKL_SIZE) is LimitExceeded { limit, message }
(→ 413); a malformed/truncated file is Parse (→ 400); a .pth pickle
referencing a GLOBAL outside the torch.* allowlist is DisallowedGlobal { module, name }
(a security signal); a recognised-but-unimplemented format/dtype is Unsupported;
I/O failures are Io. The v0.8.0 Python bindings map these one-to-one:
AnamnesisError |
Python exception |
|---|---|
Parse |
ParseError |
Unsupported |
UnsupportedError |
LimitExceeded |
LimitExceededError |
DisallowedGlobal |
SecurityError |
Io |
builtin OSError |
No panic, no abort. No public parse/inspect entry point panics or aborts on
any input — a hostile file is always a clean Err, never an unwinding panic and
never a SIGBUS (the copy-based parse_bytes / parse_*_from_reader paths use
no memory map). Pinned in stable CI by tests/no_panic.rs and the cargo fuzz
harness.
Walkthrough: Inspect before you parse. The per-version hardening history is in Validation → robustness timeline.
| Format | Inspect | Parse | Dequantize → BF16 | Quantize | Convert to |
|---|---|---|---|---|---|
| safetensors | ✓ | ✓ | ✓ FP8 · GPTQ · AWQ · BitsAndBytes | ✓ BnB-NF4 (Lethe) | safetensors · gguf · bnb-nf4 |
| GGUF | ✓ | ✓ | ✓ all 22 block types | — | safetensors · gguf · bnb-nf4 |
| NPZ | ✓ | ✓ | — (already full precision) | — | safetensors · gguf · bnb-nf4 |
PyTorch .pth |
✓ | ✓ | — | — | safetensors · gguf · bnb-nf4 |
Every dequant and quant kernel is bit-exact (0 ULP) against the canonical
library's own code (bitsandbytes, AutoAWQ, GPTQModel, PyTorch's fp8 cast,
gguf-py) — not a hand-rolled oracle. Header-only inspection works over any
reader (in-memory, HTTP-range, custom transport): safetensors needs only Read;
the ZIP/GGUF formats need Read + Seek. amn convert routes every
(input × target) pair through an in-memory BF16 hub, so a quantised input
auto-dequantises on the way to any target and gguf → gguf recovers precision in
place (preserving the source metadata KV). Non-safetensors formats and each scheme
are feature-gated (gguf, npz, pth, gptq, awq, bnb).
→ Full tested-model tables, per-kernel speeds, conversion benchmarks, and methodology: Validation & tested models.
Representative measured results (release build, target-cpu=native, best-of-5):
- Dequantization: 2.7–54× faster than the reference Python/PyTorch path, bit-exact (0 ULP) across FP8 / GPTQ / AWQ / BnB / 22 GGUF block types.
- PyTorch
.pthparsing: 11–31× faster thantorch.load()on torchvision models; NPZ at 3.6 GB/s (17.7× thenpyzcrate). - Conversion:
npz → safetensors6.75×,pth → safetensors5.18×,safetensors → BnB-NF42.67× vs the Python ecosystem default.
These are guarded against regression by dev-only tracks — Criterion runtime
benchmarks, dhat-rs peak-heap assertions
that hold each kernel to its documented # Memory ceiling, and an Ollama
cross-validation — none of which ship in the published crate.
Numbers, fixtures, and method: Validation & tested models
and docs/perf-experiments.md.
| Doc | |
|---|---|
| CLI Reference | Every subcommand, flag, the convert matrix, output-path rules, the ollama: scheme |
| FAQ | Common questions — install, feature flags, formats, inspect vs parse, dequantizing/converting, untrusted input |
| Validation & tested models | Cross-validation tables, per-kernel speeds, conversion benchmarks, peak-heap assertions, hardening timeline |
| Tutorial: Inspect before you parse | The inspect → check → parse safety pattern; rejecting a hostile file; bounding memory with ParseLimits |
| Tutorial: Dequantize a GGUF model to BF16 | inspect → remember → verify: a GGUF k-quant becomes standard BF16 safetensors |
| Tutorial: Convert a model between formats | amn convert --to: any input → safetensors / gguf / bnb-nf4 through the BF16 hub, with GGUF-KV stamping |
| Python interop contract | The frozen v0.8.0 binding contract: panic-safety, the NumPy/BF16 ownership model |
| Performance experiments | Measured perf hypotheses, methods, and outcomes (incl. rejected ones) |
- Phase 7 — CPU SIMD pass (v0.7.0): runtime AVX2/NEON dispatch on the shared
BF16writer, so a distributed wheel delivers AVX2 throughput without atarget-cpu=nativebuild. - Phase 8 — Python bindings (v0.8.0):
pip install anamnesis— typed exceptions, owned NumPy arrays,ml_dtypes.bfloat16. The interop contract is already frozen.
Full plan in ROADMAP.md; progress in CHANGELOG.md.
- candle-mi — Mechanistic interpretability toolkit for language models
- hf-fetch-model — Download, inspect, and compare HuggingFace models from Rust; uses anamnesis to parse cached tensor-file metadata (
.safetensors/ GGUF / NPZ / PyTorch.pth) forhf-fm inspect --cached
Licensed under either of Apache License, Version 2.0 or MIT License at your option.
- Exclusively developed with Claude Code (dev) and Augment Code (review)
- Git workflow managed with Fork