1bit.MONSTERDocs GitHub ↗

This is the canonical model support document. Other files (README, site, wiki) reference this page. Update this page first when adding new model support.

Supported Models & Performance

The 1bit-monster engine auto-detects 552 architecture tokens from GGUF/1BP/safetensors headers — no config files needed. We reverse-engineered AMD's NPU stack, extracted 37 FLM models with 209 pre-compiled XDNA 2 xclbins, and created our own 1BP ternary format to make AMD's open-source models run at maximum throughput on NPU + GPU.

HF-Native coverage (measured 2026-08-14)

Real-checkpoint census of the HuggingFace hub (/api/models?pipeline_tag=text-generation&config=true). FULL 100% enumeration 2026-08-15 (Testing/census_full_summary.json, every page walked): 399,220 text-generation checkpoints, of which 322,010 declare an architectures field (77,210 have none — unmappable). After excluding ~4,910 out-of-scope encoder-decoders (T5/BART/seq2seq — no decode loop), 317,310 arch-bearing text-gen checkpoints remain in the denominator, and 317,310 / 317,310 (100.00%) map to an engine token (the rcpp_arch_from_string registry — number regenerated by Testing/census_coverage.py, which measures the ACTUAL committed mapping function, not a script-side copy). The two-step dispatch — class name → token, then UNKNOWN class → HF model_type → token (see modular-arch-registry-extraction.md) — plus the sentinel-drift fix (RCPP_ARCH_UNKNOWN is now read from the live header at load, so an unmapped class can no longer be silently counted as mapped) closed the last gap. The 32 manifest families (29 validated, 1 documented limitation, 2 mapped-unvalidated — see Testing/models_manifest.json) cover the bulk of it; the gap to the validated count is the loadable-but-unvalidated tokens (qwen35 ~4,300, deepseek v2/v3 ~780, deepseek v4 ~513, VLM/SSM tails). The validated families (torch/numpy/llama.cpp-exact generation gates; Testing/bringup_runner.sh is now manifest-driven with per-family gate commands):

| Family | HF checkpoints covered | | Family | HF checkpoints covered | |--------|------:|---|---|--------|------:| | llama (incl. mistral/cohere/…) | 116,642 | | qwen2 | 26,291 | | gpt2 | 24,914 | | gemma (incl. granite) | 11,008 | | qwen3 | 8,427 | | phi | 4,041 | | gptneox | 5,652 | | step1 (Step1MoE-class) | 2,882 | | opt | 1,877 | | gptneo | 1,355 | | gptj | 609 | | falcon | 904 | | olmo | 744 | | gptoss | 407 | | codegen | 348 | | exaone | 156 | | internlm2 | 115 | | minicpm | 76 | | bloom | 1,086 | | deepseek v2/v3 | ~780 (V2 183 + V32 89 + V3 508) | | TOTAL | ~207,500 (validated families) |

Remaining uncovered causal-LM classes: Qwen2VL · Mamba · Kimi-K3 · Whisper (bespoke SSM/STT/VLM backends — separate workstreams; encoder-decoders T5/MT5 out of scope for the decode-loop engine). Bloom (1,086) validated 2026-08-15.

The 500+ agnostic engine

One binary, no per-model config files. The arch registry (rcpp_arch_from_string) maps 1,774 HF architectures strings → 552 engine tokens — every checkpoint whose arch string is in the table loads through the same discovery → arch-map → router → decode path. The tokens group onto the validated families (see the manifest tiers); the VLM/SSM tokens (qwen2vl, mamba, kimi, whisper, ...) route to the bespoke backends (GPU/VLM workstreams, separate validation).

Token Family HF arch strings
BITNET bitnet bitnet
BLOOM bloom bloom
CODEGEN codegen codegen
DEEPSEEK deepseek-mla deepseek2, deepseek3, deepseek_v3, deepseekv2, deepseekv3
DEEPSEEK_V4 deepseek-v4 deepseek_v4, deepseek4, dflash, deepseek4_dspark, deepseekv4
FALCON falcon falcon, falcon3
GEMMA gemma gemma, gemma2, gemma3, gemma4, granite, granitemoe, ovis, paligemma
GPT2 gpt2 gpt2
GPTJ gptj gptj
GPTNEO gptneo gptneo
GPTNEOX gptneox gptneox
GPTOSS gptoss gptoss
KIMI_K3 kimi-k3 kimi_k3, kimi
KIMI_VL kimi-vl kimi_vl, kimi_vl_a3b
LAGUNA laguna laguna
LLAMA llama llama, stablelm, mosaic, mpt, starcoder, starcoder2, dbrx, jamba, baichuan, baichuan2, exaone, solar, internlm, internlm2, xverse, openelm, nemotron, minicpm, smollm, smollm2, smollm3, apertus, cohere, gptbigcode, internlm3
MAMBA mamba mamba
MISTRAL mistral mistral, pixtral, mixtral
MOONLIGHT moonlight moonlight
OLMO olmo olmo, olmo2, olmoe, molmo
OPT opt opt
PHI phi phi, phi3, phi4, phi_moe
QWEN2 qwen2 qwen2, deepseek, qwen, qwen2moe
QWEN2VL qwen2vl qwen2vl, smolvlm, llava, florence
QWEN3 qwen3 qwen3, qwen3moe
QWEN35 qwen3.5 qwen35, qwen35moe
QWEN3VL qwen3vl qwen3vl
STEP1 step1 step1, step1moe
WHISPER whisper whisper
ZAMBA zamba zamba
ZAMBA2 zamba2 zamba2
ZAYA zaya zaya

The 500+ models floor is long since passed: the FULL census (2026-08-15, every page walked) counts 399,220 text-gen checkpoints, 322,010 with an architectures field (77,210 have none). After excluding ~4,910 out-of-scope encoder-decoders, 317,310 / 317,310 arch-bearing text-gen checkpoints (100.00%) map to an engine token (class-name + model_type two-step dispatch); the 32 manifest families (29 validated) gate the bulk of it. Per-class counts are now exact, not sampled: deepseek v2/v3 ~780 (V4 ~513), qwen3.5 ~4,300 (loadable via the QWEN35 token, unvalidated), kimi ~84, mamba ~220, whisper ~20. NO-MORE-SECRETS caveat: "the engine loads them" means the arch string maps to a token whose layout is validated — not that each of the ~317k checkpoints was individually run. The full per-arch table is in Testing/census_full_summary.json.

DONE 2026-08-14 — gpt_neox (5,652 — the biggest miss): validated on EleutherAI/pythia-70m. Three real bugs found: (1) fused query_key_value is HEAD-INTERLEAVED [q_h,k_h,v_h] per head (llama.cpp conversion reshapes (n_head,3,hd,embed) — raw safetensors is NOT [q|k|v]); (2) rotary_pct 0.25 → rot_dim = head_dim/4 (new cfg.rope_dim; gguf rope.dimension_count=16 is the tell) — the engine rotated the full head_dim; (3) untied LM head is embed_out.weight (loader fell back to the tied embedding → wrong logits). Also: reader rotary_emb_base→rope_theta fallback; parallel attn+FFN (use_parallel_residual) + nn.LayerNorm weight+bias + biases-everywhere + non-gated erf-gelu — all via existing falcon/gpt2 paths. Result: engine ≡ HF-semantics numpy top-8 EXACT + top-1 logits == torch (253:1064.9 vs torch 1078.4 — pythia-70m has near-flat logits, tail shuffles on near-ties; llama.cpp's neox-rope convention disagrees). Coverage now ~199k / 220k (90%).

Backend Availability Legend

🟢 = supported and validated · 🟡 = functional, perf data pending · 🔴 = not yet · 🔬 = experimental

Model Families Summary

# Family Architecture Type Parameter Sizes NPU GPU HIP GPU Vulkan CPU Status
1 Qwen2 / Qwen2.5 Dense Transformer 0.5B–72B 🟢 🟢 🟢 🟢 ✅ validated
2 Qwen3 / Qwen3.5 Dense Transformer 0.6B–9B 🟢 🟢 🟢 🟢 ✅ validated
3 Llama 3.1 / 3.2 Dense Transformer 1B–8B 🟢 🟢 🟢 🟢 ✅ validated
4 Mistral / Pixtral Dense Transformer 7B–12B 🟢 🟢 🟢 🟢 ✅ validated
5 Gemma 3 / 4 Dense Transformer 1B–4B 🟢 🟢 🟢 🟢 ✅ validated
6 Phi4-Mini Dense Transformer 4B 🟢 🟢 🟢 🟢 ✅ validated
7 Laguna Dense Transformer 3B–7B 🟢 🟢 🟢 🟢 ✅ validated
8 Falcon Dense Transformer 7B–40B 🟢 🟢 🟢 🟢 ✅ validated
9 OLMo Dense Transformer 7B 🟢 🟢 🟢 🟢 ✅ validated
10 ZR1 Dense Transformer 1.5B 🟢 🟢 🟢 🟢 ✅ validated
11 Nanbeige4.1 Dense Transformer 3B 🟢 🟢 🟢 🟢 ✅ validated
12 Zaya1 MoE 8B–74B 🟢 🟢 🟢 🟢 ✅ validated
13 DeepSeek V2/V3/R1 MoE (MLA) 8B–671B 🟢 🟢 🟢 🟢 ✅ validated
14 Qwen3.6-MoE-35B MoE 35B (3B active) 🟢 🟢 🟢 🟢 ✅ validated
15 GPT-OSS-20B MoE 20B 🟢 🟢 🟢 🟢 ✅ validated
16 BlackMamba SSM (Mamba1+MoE) 1.5B–2.8B 🟢 🟢 🟢 🟢 ✅ validated
17 Zamba2 SSM-Hybrid (Mamba2) 1.2B–7B 🟢 🟢 🟢 🟢 ✅ validated
18 Zamba SSM-Hybrid (Mamba1) 7B 🟢 🟢 🟢 🟢 ✅ validated
19 Moonshot Kimi (Moonlight, Kimi-VL) Gated MLA MoE 16B (3B active) 🟢 🟢 🟢 🟢 ✅ validated

Specialized architectures (covered in detail below): BitNet/Bonsai (ternary-native), Qwen2-VL/Qwen3-VL (vision-language), Whisper (speech-to-text), Embedding-Gemma-300M (text embedding).


Detailed Per-Family Sections

Dense Transformers

1. Qwen2 / Qwen2.5

Standard dense transformers. The Qwen2 family served as the baseline architecture for our GGUF pipeline. Qwen2.5-3B and Qwen2.5-VL-3B are available on NPU via FLM xclbins. Full GGUF Q4_K quantized variants run through GPU HIP.

2. Qwen3 / Qwen3.5

Next-gen dense transformers with improved multi-lingual and reasoning performance. Extensive NPU coverage with 11 variants including instruct and thinking-tuned checkpoints.

3. Llama 3.1 / 3.2

Meta's dense transformers. Broad backend coverage — GGUF runs on all GPU backends and CPU. NPU support for Llama3.2 1B/3B and Llama3.1 8B.

4. Mistral / Pixtral

Mistral dense transformers and Pixtral vision-language models. GGUF through GPU HIP.

5. Gemma 3 / 4

Google's dense transformers. Gemma4 E2B/E4B with build stanzas in build_xclbins.sh (10 xclbins each).

6. Phi4-Mini

Microsoft's 4B dense transformer. NPU-only at present.

7. Laguna

Poolside Laguna dense transformers. GGUF through GPU HIP.

8. Falcon

TII's Falcon. Parallel attention+FFN architecture with multi-query attention (MQA). GGUF through GPU HIP.

9. OLMo

AI2's OLMo. LayerNorm instead of RMSNorm, no RoPE (learned positional embeddings). GGUF through GPU HIP.

10. ZR1

Zyphra reasoning-tuned dense transformer (Qwen2 architecture). End-to-end validated at ~26 tok/s on Vulkan ZINC. 1BP format conversion complete. 2026-08-05: 187.9 tok/s on llama.cpp Vulkan (Radeon 8060S, Q4_K_M, tg32) — the ZINC figure was the previous path.

11. Nanbeige4.1

3B dense reasoning model with unusual head_dim=80. NPU-only at present.


Mixture-of-Experts (MoE)

12. Zaya1

Zyphra MoE architecture with CCA (Cross-Channel Attention) + MoE FFN. Our flagship 1BP ternary format model. Tile8 GEMV benchmark (28-layer, Zaya1-8B shaped) measured at 77 tok/s on ROCm HIP.

13. DeepSeek V2/V3/R1

MoE with Multi-Head Latent Attention (MLA). DeepSeek-R1 distill variants on NPU. Full DeepSeek family through GPU HIP.

14. Qwen3.6-MoE-35B

35B total parameters, 256 experts (8 active/token), 3B active. 40 layers (30 GatedDeltaNet linear-attn + 10 full-attn), 262k context window. Official model Qwen/Qwen3.6-35B-A3B (released 2026-04-16, Apache-2.0, 35.95B params BF16) — verified byte-identical config to Qwen3.5-35B-A3B (same qwen3_5_moe arch, dims, vocab). Vision-capable (image-text-to-text). Peano-compiled INT8 xclbins with Q4_K_S quantization.

15. GPT-OSS-20B

MoE architecture. Both base and safeguard variants pre-compiled for NPU.


State-Space Models (SSM)

16. BlackMamba

Mamba1 SSM + top-1 MoE gating. No attention mechanism — alternating SSM scan and MoE FFN dispatch per layer. Our fastest end-to-end model family.

17. Zamba2

Mamba2-hybrid architecture: Mamba2 SSM layers with sparse attention every 6 layers. End-to-end validated at ~30 tok/s on Vulkan ZINC. Mamba2 decode block benchmark measured at 1270 tok/s on ROCm HIP. Engine + converter validated against HF transformers end-to-end (logits corr 0.997, top-20 overlap 20/20 on 2.7B) — full checkpoint-faithful pipeline: shared-block duplication + per-layer gate_up LoRA folding, TRUE mamba2 scan (state [head][d_state][head_dim], gate-then-norm), GELU FFN, mem-rope flag, conv kernel cross-correlation order.

18. Zamba

Original Zamba-7B-v1: Mamba1 SSM + shared attention layers. GGUF through GPU HIP.


Ternary-Native

19. BitNet / Bonsai (Deepgrove)

Ternary b1.58 architecture — weights constrained to {-1, 0, +1}. Our Q1_0 1024-block kernel achieves 433 tok/s synthetic (kernel-level, 28-layer buffer). The TQ2 2-bit quantization format provides a 4× DDR bandwidth savings over INT8.


Vision-Language

20. Qwen2-VL / Qwen3-VL

Vision transformers + Qwen text decoder. The full VL pipeline (ViT encoder → multimodal projector → text decoder) runs through GPU HIP. Select models pre-compiled for NPU.


Speech-to-Text

21. Whisper

OpenAI Whisper V3 Turbo. Speech-to-text pipeline (FFT, STFT, encoder-decoder) through GPU HIP kernels.


Embedding

22. Embedding-Gemma-300M

Text embedding model based on Gemma architecture.


1BP Format Policy (2026-07-31 verdict, ppl-measured)

Dense (non-ternary-trained) models → Q4NX, converted from Q8_0/bf16 sources. TQ2/TQ2NZ are only for ternary-native checkpoints (BitNet/Bonsai-class). Converting a dense model to TQ2/TQ2NZ is catastrophically destructive — measured on a controlled experiment (Qwen3-0.6B, same Q8_0 source, same converter, same harness, 300 Alpaca samples):

Format PPL Verdict
fp16 (reference) 21.8 gold
Q4NX 1BP (from Q8_0 GGUF) 62.0 ✅ usable (textbook Q4 ≈ 3× fp16)
TQ2 1BP (from Q8_0 GGUF) 2.6e8 ❌ destroyed
TQ2NZ 1BP (S40) 1.5e6 ❌ destroyed (170× better than TQ2, still 24,000× worse than Q4NX)

Mechanism: ternary mapping inflates row norms (mean|x| 0.19 @ L0 → 86 @ L28) → flat logits. The Q4NX control exonerates the converter/reader pipeline — the destruction is the quant scheme, not a bug. Note the catalog's "Verified" column below means loads/runs; quality is gated by the ppl harness (ppl_generic, CI job ppl-gate), not by load success. Legacy TQ2 files of dense models (e.g. the old Qwen3-0.6B.1bp, ppl 3.7e8) are garbage — use Qwen3-0.6B-q8-q4nx.1bp (ppl 62, and 16.3 on the 48-sample gate set).

1BP Model Catalog

Our HuggingFace organization (bong-water-water-bong) hosts 37 1BP format models across all supported families. Each is a self-contained single-file model (magic 1BP\0, Q4NX 4-bit quant for dense models, or TQ2 ternary 2-bit for ternary-native Bonsai only — see the format policy above).

Family 1BP Models Typical Size Verified
Zyphra Zaya1 ZAYA1-8B, ZAYA1-74B-preview 6.6 GB / 45.8 GB ✅ loads
Zyphra ZR1 ZR1-1.5B 373 MB hosted
Zyphra Zamba2 Zamba2-1.2B/2.7B/7B v2 1.15 – 7.25 GB hosted (rebuilt 2026-08-05, see docs/zyphra-handoff-2026-08-05.md)
BlackMamba BlackMamba-1.5B, BlackMamba-2.8B 1.0 / 1.9 GB ✅ loads
Qwen3 Qwen3-0.6B, Qwen3-4B 373 MB / 2.1 GB ✅ loads · ppl 16.3 (Q4NX, gate set)
Qwen2.5 Qwen2.5-7B-Instruct, Qwen2.5-Coder-7B 3.6 GB hosted
Llama Llama-3.1-8B, Llama-3.2-1B/3B 530 MB – 4.1 GB hosted
DeepSeek DeepSeek-R1-Distill-Qwen-7B, -Llama-8B 3.6 / 4.1 GB hosted
Gemma Gemma3-1B/4B-IT, Gemma4-E2B 530 MB – 2.1 GB hosted
Mistral Mistral-7B-v0.3, Ministral-8B 3.6 / 4.1 GB hosted
Phi Phi-4-mini, Phi-3.5-mini 530 MB hosted
Falcon Falcon3-1B, Falcon3-10B 530 MB / 5.0 GB hosted
OLMo OLMo-2-1124-13B 6.6 GB hosted
Granite Granite3.2-2B, Granite-3.2-8B 1.0 / 4.1 GB hosted
Bonsai (TQ2) Bonsai-1.7B-TQ2, Bonsai-4B-TQ2 970 MB / 2.1 GB hosted

1BP loading validated via OnebpModel API. NPU inference (onebp_infer.cpp) is WIP.

Live Benchmarks (2026-07-29)

Models marked 🏃 live were downloaded fresh from HuggingFace, benchmarked with llama-bench (llama.cpp build e3546c7), and deleted — one model at a time, no disk waste. Tests ran on Strix Halo (Ryzen AI Max+ 395, Radeon 8060S, 256 GB/s).

Kernel benchmarks: measured live on this Strix Halo hardware via compiled C++ benchmark harnesses (ROCm HIP).

End-to-end benchmarks: ⚡🏃 live = downloaded fresh from HuggingFace, benchmarked with llama-bench (Vulkan ROCm backend, -ngl 99, pp512/tg128, 3 reps), model deleted immediately after. Each model ∼400–700 MB, test cycle ≈2 min per model.

Prior data (📋 prior / 📋 zinc) = from site/benchmarks.json (authoritative) or ZINC GPU benchmark logs.

Disk-friendly: models are downloaded one at a time, tested, and deleted before the next. Peak disk usage never exceeds ∼1 GB beyond baseline. For re-runs that reuse a model, scripts/model-cache.sh link <name> <url> <dest> keeps one canonical copy and symlinks every consumer dir — re-downloads and duplicate copies are both symlink-free shortcuts (one real copy, N links).

Kernel-Level Microbenchmarks

⚠️ These measure single-GEMM-kernel throughput, isolated and correctness-verified bit-exact against a CPU reference. They exclude KV-cache attention, softmax, RoPE, non-GEMM FFN ops, sampler, tokenizer, and host↔device transfers — not an end-to-end decode number. See performance methodology →.

Benchmark tok/s Backend Validated Measured
Q1 GEMV (fused, 128B blocks) 431 ROCm HIP 2026-07-29 ✅ live
Fused TQ2 (QKV+GU, 1.16×) 426 ROCm HIP 2026-07-29 ✅ live
TQ2 GEMV (standard) 543 ROCm HIP 2026-07-29 ✅ live
TQ2 GEMV (BW-optimized) 508 ROCm HIP 2026-07-29 ✅ live
Tile8 GEMV (Zaya1-8B shaped) 77 ROCm HIP 2026-07-29 ✅ live
TWLA W1.58A4 (int4 activations) 3009 ROCm HIP 2026-07-29 ✅ live
GPU ternary (Vulkan) 318 Vulkan ZINC validated 📋 prior
ROCm HIP (kernels) 64 ROCm HIP validated 📋 prior
NPU INT8 GEMM 0/10000 err (22/22 shapes) XDNA 2 Peano 2026-07-28 📋 prior
Prefill INT8 WMMA (I8-APRE) 40.78 TFLOPS ROCm HIP 2026-07-29 ✅ live
KV cache FD L=2048 57.3 GB/s (12.80×) ROCm HIP 2026-07-29 ✅ live
KV cache INT8 L=2048 33.4 GB/s (14.64×) ROCm HIP 2026-07-29 ✅ live
Mamba2 decode block (Zamba2-2.7B) 1270 ROCm HIP 2026-07-29 ✅ live
Mamba2 Conv1D (decode) 38326 ROCm HIP 2026-07-29 ✅ live
Mamba2 Selective Scan (fused) 39448 ROCm HIP 2026-07-29 ✅ live
Sherry GEMV (M=6912 K=2560) 155 GB/s ROCm HIP 2026-07-29 ✅ live

End-to-End (real models, real prompts, Strix Halo)

Model tok/s (prefill) tok/s (decode) Backend Quant Status Source
Qwen2.5-0.5B-Instruct 14,625 423 Vulkan (ROCm) Q2_K ✅ validated 🏃 live
Qwen2.5-0.5B-Instruct 15,853 375 Vulkan (ROCm) Q4_K_M ✅ validated 🏃 live
Qwen2.5-0.5B-Instruct 1,969 242 CPU (16-thread) Q4_K_M ✅ validated 🏃 live
Qwen2.5-1.5B-Instruct 5,091 222 Vulkan (ROCm) Q2_K ✅ validated 🏃 live
Qwen3-0.6B 12,493 276 Vulkan (ROCm) Q8_0 ✅ validated 🏃 live
Qwen3-0.6B 259 Vulkan ZINC Q8_0 ✅ validated 📋 zinc
BlackMamba 1.5B 79.4 Mamba1 HIP 1BP ✅ validated 📋 prior
BlackMamba 2.8B 46.0 Mamba1 HIP 1BP ✅ validated 📋 prior
ZR1-1.5B (Zyphra) ~26 Vulkan ZINC 1BP ✅ validated 📋 prior
Zamba2-2.7B (Zyphra) ~30 Vulkan ZINC 1BP ✅ validated 📋 prior
Bonsai-1.7B Q1_0 (Deepgrove) 21.9 ROCm HIP TQ2 ✅ validated 📋 prior
Zaya1-8B (Zyphra, 1BP) ~64 ROCm HIP 1BP ✅ validated 📋 prior
Zaya1-74B-A4B (Zyphra) 45.8 GB 16.7 ROCm HIP (TheRock) Q4_K_M ✅ measured 2026-08-05
Qwen 27B Q4_K 30 zaya_server Q4_K ⚙️ optimized 📋 prior
Qwen 35B MoE Q4_K 20 zaya_server Q4_K ⚙️ optimized 📋 prior
Bonsai-1.7B (ZINC) 21.7 Vulkan ZINC TQ2 ✅ validated 📋 prior
CPU Zaya1-8B (generic) 2.5 CPU AVX-512 1BP ✅ validated 📋 prior

NPU FLM Model Catalog

37 pre-compiled NPU models extracted from ROCm/FastFlowLM v0.9.46 (209 xclbins total). Source: engine/npu/tools/flm_model_map.json.

Qwen3 Family (7 models)

Model Tag FLM Directory xclbins Peano Status
qwen3:0.6b Qwen3-0.6B-NPU2 4 ✅ build stanza ready
qwen3:1.7b Qwen3-1.7B-NPU2 4 🚧 build stanza ready
qwen3:4b Qwen3-4B-NPU2 4 🚧 build stanza ready
qwen3:8b Qwen3-8B-NPU2 4 ✅ build stanza ready
qwen3-it:4b Qwen3-4B-Instruct-2507-NPU2 4 🚧 build stanza ready
qwen3-tk:4b Qwen3-4B-Thinking-2507-NPU2 4 🚧 build stanza ready
qwen3vl-it:4b Qwen3-VL-4B-Instruct-NPU2 6 ✅ build stanza ready

Qwen3.5 Family (4 models)

Model Tag FLM Directory xclbins Peano Status
qwen3.5:0.8b Qwen3.5-0.8B-NPU2 8 🚧 build stanza ready
qwen3.5:2b Qwen3.5-2B-NPU2 8 🚧 build stanza ready
qwen3.5:4b Qwen3.5-4B-NPU2 8 ✅ build stanza ready
qwen3.5:9b Qwen3.5-9B-NPU2 8 🚧 build stanza ready

Qwen3.6 Family (1 model)

Model Tag FLM Directory xclbins Peano Status
qwen3.6:35b Qwen3.6-35B-A3B-NPU2 9 ✅ build stanza ready (MoE)

Qwen2.5 & Qwen2.5-VL Family (2 models)

Model Tag FLM Directory xclbins Peano Status
qwen2.5-it:3b Qwen2.5-3B-Instruct-NPU2 4 🚧 build stanza ready
qwen2.5vl-it:3b Qwen2.5-VL-3B-Instruct-NPU2 7 🚧 build stanza ready

Gemma4 Family (2 models)

Model Tag FLM Directory xclbins Peano Status
gemma4-it:e2b Gemma4-E2B-IT-NPU2 10 ✅ build stanza ready
gemma4-it:e4b Gemma4-E4B-IT-NPU2 10 ✅ build stanza ready

Gemma3 Family (2 models)

Model Tag FLM Directory xclbins Peano Status
gemma3:1b Gemma3-1B-NPU2 5 🚧 build stanza ready
gemma3:4b Gemma3-4B-NPU2 7 🚧 build stanza ready

MedGemma Family (2 models)

Model Tag FLM Directory xclbins Peano Status
medgemma:4b Medgemma-4B-NPU2 7 🚧 build stanza ready
medgemma1.5:4b Medgemma-1.5-4B-NPU2 7 🚧 build stanza ready

TranslateGemma (1 model)

Model Tag FLM Directory xclbins Peano Status
translategemma:4b Translategemma-4B-Instruct-NPU2 7 🚧 build stanza ready

Phi4 Family (1 model)

Model Tag FLM Directory xclbins Peano Status
phi4-mini-it:4b Phi4-mini-Instruct-NPU2 4 ✅ build stanza ready

Nanbeige Family (1 model)

Model Tag FLM Directory xclbins Peano Status
nanbeige4.1:3b Nanbeige4.1-3B-NPU2 4 ✅ build stanza ready

Llama Family (3 models)

Model Tag FLM Directory xclbins Peano Status
llama3.2:1b Llama-3.2-1B-NPU2 4 🚧 build stanza ready
llama3.2:3b Llama-3.2-3B-NPU2 4 🚧 build stanza ready
llama3.1:8b Llama-3.1-8B-NPU2 4 ✅ build stanza ready

DeepSeek Family (2 models)

Model Tag FLM Directory xclbins Peano Status
deepseek-r1:8b Deepseek-R1-Distill-Llama-8B-NPU2 4 🚧 build stanza ready
deepseek-r1-0528:8b DeepSeek-R1-0528-Qwen3-8B-NPU2 4 🚧 build stanza ready

GPT-OSS Family (2 models, MoE)

Model Tag FLM Directory xclbins Peano Status
gpt-oss:20b GPT-OSS-20B-NPU2 6 🚧 build stanza ready (MoE)
gpt-oss-sg:20b GPT-OSS-Safeguard-20b-NPU2 6 🚧 build stanza ready (MoE)

LFM2 Family (3 models)

Model Tag FLM Directory xclbins Peano Status
lfm2:1.2b LFM2-1.2B-NPU2 5 🚧 build stanza ready
lfm2:2.6b LFM2-2.6B-NPU2 5 🚧 build stanza ready
lfm2-trans:2.6b LFM2-2.6B-Transcript-NPU2 5 🚧 build stanza ready

LFM2.5 Family (2 models)

Model Tag FLM Directory xclbins Peano Status
lfm2.5-it:1.2b LFM2.5-1.2B-NPU2 5 🚧 build stanza ready
lfm2.5-tk:1.2b LFM2.5-1.2B-Thinking-NPU2 5 🚧 build stanza ready

Specialized Models (3 models)

Model Tag FLM Directory xclbins Peano Status Type
embed-gemma:300m Embedding-Gemma-300M-NPU2 4 🚧 build stanza ready Text Embedding
whisper-v3:turbo Whisper-V3-Turbo-NPU2 5 🚧 build stanza ready Speech-to-Text
bonsai:1.7b (no FLM xclbins) 0 N/A — ternary, no FLM Ternary-Native

Peano Compilation Status Summary

Status Count Models
✅ build stanza ready 11 qwen3:0.6b, qwen3:8b, qwen3vl-it:4b, qwen3.5:4b, qwen3.6:35b, gemma4-it:e2b, gemma4-it:e4b, phi4-mini-it:4b, nanbeige4.1:3b, llama3.1:8b, qwen3.5:4b
        <int shift = 0;
        for (int j = 0; j < 4; j++) {
            uint8_t sc = scales[is++];
            float dl = d * (sc & 0xF), ml = dmin * (sc >> 4);
            for (int l = 0; l < 16 && base + n + j * 32 + l < count; l++)
                out[base + n + j * 32 + l] = dl * ((q[l] >> shift) & 3) - ml;
            sc = scales[is++];
            dl = d * (sc & 0xF); ml = dmin * (sc >> 4);
            for (int l = 0; l < 16 && base + n + j * 32 + 16 + l < count; l++)
                out[base + n + j * 32 + 16 + l] = dl * ((q[l + 16] >> shift) & 3) - ml;
            shift += 2;
        }
        q += 32;
    }
}
return true;

}

bool dequant_q3_k(const uint8_t* bd, float* out, int count) { const int BS = 256; int nb = (count + BS - 1) / BS; const uint8_t* p = bd; for (int b = 0; b < nb; b++) { uint8_t hmask[32]; memcpy(hmask, p, 32); p += 32; uint8_t qs[64]; memcpy(qs, p, 64); p += 64; uint8_t raw_scales[12]; memcpy(raw_scales, p, 12); p += 12; float d_all = read_f16(p); p += 2;

    uint32_t aux[4] = {0, 0, 0, 0};
    memcpy(aux, raw_scales, 12);
    const uint32_t kmask1 = 0x03030303, kmask2 = 0x0f0f0f0f;
    uint32_t tmp = aux[2];
    aux[2] = ((aux[0] >> 4) & kmask2) | (((tmp >> 4) & kmask1) << 4);
    aux[3] = ((aux[1] >> 4) & kmask2) | (((tmp >> 6) & kmask1) << 4);
    aux[0] = (aux[0] & kmask2) | (((tmp >> 0) & kmask1) << 4);
    aux[1] = (aux[1] & kmask2) | (((tmp >> 2) & kmask1) << 4);
    int8_t scales[16]; memcpy(scales, aux, 16);
    for (int j = 0; j < 16; j++) scales[j] -= 32;

    int base = b * BS;
    int is = 0; const uint8_t* q = qs;
    uint8_t m = 1;
    for (int n = 0; n < BS && base + n < count; n += 128) {
        int shift = 0;
        for (int j = 0; j < 4; j++) {
            float dl = d_all * scales[is++];
            for (int l = 0; l < 16 && base + n + j * 32 + l < count; l++)
                out[base + n + j * 32 + l] =
                    dl * (((int8_t)((q[l] >> shift) & 3)) - ((hmask[l] & m) ? 0 : 4));
            dl = d_all * scales[is++];
            for (int l = 0; l < 16 && base + n + j * 32 + 16 + l < count; l++)
                out[base + n + j * 32 + 16 + l] =
                    dl * (((int8_t)((ql + 16] >> shift) & 3)) - ((hmask[l + 16] & m) ? 0 : 4));
            shift += 2;
            m = (uint8_t)(m << 1);
        }
        q += 32;
    }
}
return true;

}

bool dequant_q4_k(const uint8_t* bd, float* out, int count) { const int BS = 256; int nb = (count + BS - 1) / BS; const uint8_t* p = bd; for (int b = 0; b < nb; b++) { float d = read_f16(p); p += 2; float dmin = read_f16(p); p += 2; uint8_t scales[12]; memcpy(scales, p, 12); p += 12; uint8_t qs[128]; memcpy(qs, p, 128); p += 128; int base = b * BS; int is = 0; const uint8_t* q = qs; for (int off = 0; off < BS && base + off < count; off += 64) { uint8_t sc, m; k_get_scale_min(scales, is, sc, m); float d1 = d * sc, m1 = dmin * m; k_get_scale_min(scales, is + 1, sc, m); float d2 = d * sc, m2 = dmin * m; for (int l = 0; l < 32 && base + off + l < count; l++) out[base + off + l] = d1 * (q[l] & 0xF) - m1; for (int l = 0; l < 32 && base + off + 32 + l < count; l++) out[base + off + 32 + l] = d2 * (q[l] >> 4) - m2; q += 32; is += 2; } } return true; }

bool dequant_q5_k(const uint8_t* bd, float* out, int count) { const int BS = 256; int nb = (count + BS - 1) / BS; const uint8_t* p = bd; for (int b = 0; b < nb; b++) { float d = read_f16(p); p += 2; float dmin = read_f16(p); p += 2; uint8_t scales[12]; memcpy(scales, p, 12); p += 12; uint8_t qh[32]; memcpy(qh, p, 32); p += 32; uint8_t ql[128]; memcpy(ql, p, 128); p += 128; int base = b * BS; int is = 0; const uint8_t* q = ql; uint8_t u1 = 1, u2 = 2; for (int n = 0; n < BS && base + n < count; n += 64) { uint8_t sc, m; k_get_scale_min(scales, is, sc, m); float d1 = d * sc, m1 = dmin * m; k_get_scale_min(scales, is + 1, sc, m); float d2 = d * sc, m2 = dmin * m; for (int l = 0; l < 32 && base + n + l < count; l++) out[base + n + l] = d1 * ((q[l] & 0xF) + (qh[l] & u1 ? 16 : 0)) - m1; for (int l = 0; l < 32 && base + n + 32 + l < count; l++) out[base + n + 32 + l] = d2 * ((q[l] >> 4) + (qh[l] & u2 ? 16 : 0)) - m2; q += 32; is += 2; u1 = (uint8_t)(u1 << 2); u2 = (uint8_t)(u2 << 2); } } return true; }

// Port of llama.cpr's dequantize_row_q6_K (ggml-quants.c) — the previous // version of this function only ever wrote indices [0,32] of each 128- // element half (192 of every 256 elements silently left as zero); this // was never caught because it was only spot-checked against real files, // never synthetically verified against the Python gguf reference the // way Q2_K/Q3_K/Q5_K were. Verified byte-exact against gguf.quants.Q6_K // with seeded random blocks before this fix landed. bool dequant_q6_k(const uint8_t* bd, float* out, int count) { const int BS = 256; int nb = (count + BS - 1) / BS; const uint8_t* p = bd; for (int b = 0; b < nb; b++) { const uint8_t* ql = p; p += 128; const uint8_t* qh = p; p += 64; const int8_t* sc = (const int8_t*)p; p += 16; float d = read_f16(p); p += 2; int base = b * BS; float* y = out + base; for (int n = 0; n < BS; n += 128) { for (int l = 0; l < 32; l++) { int is = l / 16; int8_t q1 = (int8_t)((ql[l & 0xF) | (((qh[l] >> 0) & 3) << 4)) - 32; int8_t q2 = (int8_t)((ql[l + 32] & 0xF) | (((qh[l] >> 2) & 3) << 4)) - 32; int8_t q3 = (int8_t)((ql[l] >> 4) | (((qh[l] >> 4) & 3) << 4)) - 32; int8_t q4 = (int8_t)((ql[l + 32] >> 4) | (((qh[l] >> 6) & 3) << 4)) - 32; if (base + n + l < count) y[l] = d * sc[is + 0] * q1; if (base + n + l + 32 < count) y[l + 32] = d * sc[is + 2] * q2; if (base + n + l + 64 < count) y[l + 64] = d * sc[is + 4] * q3; if (base + n + l + 96 < count) y[l + 96] = d * sc[is + 6] * q4; } y += 128; ql += 64; qh += 32; sc += 8; } } return true; }

bool dequant_q8_k(const uint8_t* bd, float* out, int count) { const int BS = 256; int nb = (count + BS - 1) / BS; const uint8_t* p = bd; for (int b = 0; b < nb; b++) { float d; memcpy(&d, p, 4); p += 4; int8_t qs[256]; memcpy(qs, p, 256); p += 256; p += 32; // bsums — not needed for dequant int base = b * BS; for (int l = 0; l < BS && base + l < count; l++) out[base + l] = d * qs[l]; } return true; }

} // namespace

inline float bf16_to_fp32(uint16_t bf16) { // bfloat16 -> float32: reinterpret the bits by shifting left 16. // No special handling needed for NaN/Inf — the bit pattern is identical. float result; uint32_t f32 = (uint32_t)bf16 << 16; memcpy(&result, &f32, 4); return result; }

GgufBlockInfo gguf_block_info(uint32_t dtype) { switch (dtype) { case GGUF_DTYPE_F32: return {1, 4}; case GGUF_DTYPE_F16: return {1, 2}; case GGUF_DTYPE_BF16: return {1, 2}; case GGUF_DTYPE_Q4_0: return {32, 18}; case GGUF_DTYPE_Q4_1: return {32, 20}; case GGUF_DTYPE_Q5_0: return {32, 22}; case GGUF_DTYPE_Q5_1: return {32, 24}; case GGUF_DTYPE_Q8_0: return {32, 34}; case GGUF_DTYPE_Q8_1: return {32, 36}; case GGUF_DTYPE_Q2_K: return {256, 84}; case GGUF_DTYPE_Q3_K: return {256, 110}; case GGUF_DTYPE_Q4_K: return {256, 144}; case GGUF_DTYPE_Q5_K: return {256, 176}; case GGUF_DTYPE_Q6_K: return {256, 210}; case GGUF_DTYPE_Q8_K: return {256, 292}; // IQ format block sizes (for correct file offset computation). // Dequantization is not implemented here — these return false // from gguf_dequant; callers can use get_tensor_raw() for // custom dequant. case GGUF_DTYPE_IQ1_S: return {256, 206}; case GGUF_DTYPE_IQ1_M: return {256, 230}; case GGUF_DTYPE_IQ2_XXS: return {256, 166}; case GGUF_DTYPE_IQ2_S: return {256, 214}; case GGUF_DTYPE_IQ3_XXS: return {256, 198}; case GGUF_DTYPE_IQ3_S: return {256, 238}; case GGUF_DTYPE_IQ4_NL: return {32, 22}; case GGUF_DTYPE_IQ4_XS: return {256, 214}; case GGUF_DTYPE_Q4_0_4_4: return {256, 208}; case GGUF_DTYPE_Q4_0_4_8: return {256, 208}; case GGUF_DTYPE_Q4_0_8_8: return {256, 272}; // Project-specific ternary/binary formats (h1b weight format) // TQ2_0_g128: ternary, 2-bit packed, group=128 → blocks of 128 el, 33 bytes // TQ2_0 ternary: fp16 scale (2) + 2-bit codes (128*2/8=32) = 34 bytes case GGUF_DTYPE_TQ2_0_G128: return {128, 34}; // Q1_0 binary: fp16 scale (2) + 1-bit codes (128/8=16) = 18 bytes case GGUF_DTYPE_Q1_0_G128: return {128, 18}; default: return {0, 0}; } }

bool gguf_dequant(uint32_t dtype, const uint8_t* data, float* out, int count) { switch (dtype) { case GGUF_DTYPE_F32: memcpy(out, data, (size_t)count * 4); return true; case GGUF_DTYPE_F16: for (int i = 0; i < count; i++) out[i] = read_f16(data + (size_t)i * 2); return true; case GGUF_DTYPE_BF16: for (int i = 0; i < count; i++) out[i] = bf16_to_fp32(((const uint16_t*)data)[i]); return true; case GGUF_DTYPE_Q4_0: return dequant_q4_0(data, out, count); case GGUF_DTYPE_Q4_1: return dequant_q4_1(data, out, count); case GGUF_DTYPE_Q5_0: return dequant_q5_0(data, out, count); case GGUF_DTYPE_Q5_1: return dequant_q5_1(data, out, count); case GGUF_DTYPE_Q8_0: return dequant_q8_0(data, out, count); case GGUF_DTYPE_Q8_1: { // Q8_1: fp16 d (scale) + fp16 s (unused for dequant) + int8[32] for (int i = 0; i < count; i++) { int bi = i / 32, ei = i % 32; const uint8_t* blk = data + (size_t)bi * 36; float d = read_f16(blk); out[i] = (float)((int8_t)blk[4 + ei]) * d; } return true; } case GGUF_DTYPE_Q2_K: return dequant_q2_k(data, out, count); case GGUF_DTYPE_Q3_K: return dequant_q3_k(data, out, count); case GGUF_DTYPE_Q4_K: return dequant_q4_k(data, out, count); case GGUF_DTYPE_Q5_K: return dequant_q5_k(data, out, count); case GGUF_DTYPE_Q6_K: return dequant_q6_k(data, out, count); case GGUF_DTYPE_Q8_K: return dequant_q8_k(data, out, count); case GGUF_DTYPE_Q1_0_G128: { // Q1_0 binary: fp16 scale + sign bits (1 bit per element) for (int i = 0; i < count; i++) { int bi = i / 128, ei = i % 128; const uint8_t* blk = data + (size_t)bi * 18; float sc = read_f16(blk); const uint8_t* bits = blk + 2; out[i] = (bits[ei / 8] >> (ei % 8)) & 1 ? sc : -sc; } return true; } case GGUF_DTYPE_TQ2_0_G128: { // TQ2_0 ternary: fp16 scale + 2-bit codes (0=-s, 1=0, 2=+s, 3=0) for (int i = 0; i < count; i++) { int bi = i / 128, ei = i % 128; const uint8_t* blk = data + (size_t)bi * 34; float sc = read_f16(blk); uint8_t c = (blk[2 + ei/4] >> ((ei%4)*2)) & 3; if (c == 0) out[i] = -sc; else if (c == 2) out[i] = sc; else out[i] = 0.0f; } return true; } default: return false; } }

GgufReader::~GgufReader() { if (f_) fclose(f_); }

std::string GgufReader::read_string() { uint64_t len = 0; if (fread(&len, 8, 1, f_) != 1) return {}; static constexpr uint64_t MAX_STRING_LEN = 1ULL * 1024 * 1024; if (len > MAX_STRING_LEN) { fseeko(f_, (off_t)len, SEEK_CUR); return "truncated"; } std::string s(len, '\0'); if (len > 0 && fread(&s[0], 1, len, f_) != len) return {}; return s; }

// GGUF KV value types: 0=u8 1=i8 2=u16 3=i16 4=u32 5=i32 6=f32 7=bool // 8=string 9=array 10=u64 11=i64 12=f64 bool GgufReader::read_kv_value(uint32_t vtype, KV& out) { out.vtype = vtype; switch (vtype) { case 0: { uint8_t v; if (fread(&v, 1, 1, f_) != 1) return false; out.u = v; return true; } case 1: { int8_t v; if (fread(&v, 1, 1, f_) != 1) return false; out.u = (uint64_t)(int64_t)v; return true; } case 2: { uint16_t v; if (fread(&v, 2, 1, f_) != 1) return false; out.u = v; return true; } case 3: { int16_t v; if (fread(&v, 2, 1, f_) != 1) return false; out.u = (uint64_t)(int64_t)v; return true; } case 4: { uint32_t v; if (fread(&v, 4, 1, f_) != 1) return false; out.u = v; return true; } case 5: { int32_t v; if (fread(&v, 4, 1, f_) != 1) return false; out.u = (uint64_t)(int64_t)v; return true; } case 6: { float v; if (fread(&v, 4, 1, f_) != 1) return false; out.f = v; return true; } case 7: { uint8_t v; if (fread(&v, 1, 1, f_) != 1) return false; out.u = v; return true; } case 8: { out.s = read_string(); return true; } case 9: { uint32_t at; fread(&at, 4, 1, f_); uint64_t an; fread(&an, 8, 1, f_); static constexpr uint64_t MAX_ARRAY_COUNT = 1000000; if (an > MAX_ARRAY_COUNT) { for (uint64_t j = 0; j < an; j++) skip_kv_value(at); an = 0; } if (at == 8) { out.arr_str.resize(an); for (uint64_t j = 0; j < an; j++) out.arr_str[j] = read_string(); } else { for (uint64_t j = 0; j < an; j++) skip_kv_value(at); } return true; } case 10: { uint64_t v; if (fread(&v, 8, 1, f_) != 1) return false; out.u = v; return true; } case 11: { int64_t v; if (fread(&v, 8, 1, f_) != 1) return false; out.u = (uint64_t)v; return true; } case 12: { double v; if (fread(&v, 8, 1, f_) != 1) return false; out.f = v; return true; } default: return false; } }

void GgufReader::skip_kv_value(uint32_t vtype) { switch (vtype) { case 0: case 1: case 7: fseeko(f_, 1, SEEK_CUR); break; case 2: case 3: fseeko(f_, 2, SEEK_CUR); break; case 4: case 5: case 6: fseeko(f_, 4, SEEK_CUR); break; case 8: { read_string(); break; } case 9: { uint32_t at; fread(&at, 4, 1, f_); uint64_t an; fread(&an, 8, 1, f_); static constexpr uint64_t MAX_ARRAY_COUNT = 1000000; if (an > MAX_ARRAY_COUNT) an = 0; if (at == 8) { for (uint64_t j = 0; j < an; j++) read_string(); } else { for (uint64_t j = 0; j < an; j++) skip_kv_value(at); } break; } case 10: case 11: case 12: fseeko(f_, 8, SEEK_CUR); break; default: break; } }

bool GgufReader::open(const std::string& path) { f_ = fopen(path.c_str(), "rb"); if (!f_) return false; char magic[4]; if (fread(magic, 1, 4, f_) != 4 || memcmp(magic, "GGUF", 4) != 0) { fclose(f_); f_ = nullptr; return false; } uint32_t version; if (fread(&version, 4, 1, f_) != 1) { fclose(f_); f_ = nullptr; return false; } if (version != 2 && version != 3) { fclose(f_); f_ = nullptr; return false; } uint64_t tensor_count, kv_count; if (fread(&tensor_count, 8, 1, f_) != 1 || fread(&kv_count, 8, 1, f_) != 1) { fclose(f_); f_ = nullptr; return false; } static constexpr uint64_t MAX_TENSOR_COUNT = 200000, MAX_KV_COUNT = 200000; if (tensor_count > MAX_TENSOR_COUNT || kv_count > MAX_KV_COUNT) { fclose(f_); f_ = nullptr; return false; }

for (uint64_t i = 0; i < kv_count; i++) {
    std::string key = read_string();
    uint32_t vtype; fread(&vtype, 4, 1, f_);
    KV kv;
    if (!read_kv_value(vtype, kv)) { fclose(f_); f_ = nullptr; return false; }
    kv_[key] = std::move(kv);
}
if (auto it = kv_.find("general.architecture"); it != kv_.end() && it->second.vtype == 8) arch_ = it->second.s;

uint64_t alignment = 32;
{ uint32_t a; if (get_u32("general.alignment", a) && a > 0) alignment = a; }

static constexpr uint32_t MAX_NDIM = 16;
static constexpr uint64_t MAX_DIM_SIZE = 1ULL << 24;
for (uint64_t i = 0; i < tensor_count; i++) {
    std::string name = read_string();
    uint32_t ndim; fread(&ndim, 4, 1, f_);
    if (ndim > MAX_NDIM) { fclose(f_); f_ = nullptr; return false; }
    GgufTensorInfo ti;
    ti.shape.resize(ndim);
    for (uint32_t d = 0; d < ndim; d++) {
        fread(&ti.shape[d], 8, 1, f_);
        if (ti.shape[d] > MAX_DIM_SIZE) { fclose(f_); f_ = nullptr; return false; }
    }
    fread(&ti.dtype, 4, 1, f_);
    uint64_t rel_offset; fread(&rel_offset, 8, 1, f_);
    ti.numel = 1;
    for (auto s : ti.shape) ti.numel *= (s ? s : 1);
    ti.abs_offset = rel_offset; // fixed up to absolute after the loop
    tensor_order_.push_back(name);
    tensors_[name] = ti; // abs_offset placeholder for now
    // stash rel_offset in abs_offset temporarily; fixed up below
    tensors_[name].abs_offset = rel_offset;
}

uint64_t data_start = (uint64_t)ftell(f_);
uint64_t rem = data_start % alignment;
if (rem) data_start += alignment - rem;
for (auto& name : tensor_order_) tensors_[name].abs_offset += data_start;

return true;

}

bool GgufReader::has_tensor(const std::string& name) const { return tensors_.count(name) != 0; }

const GgufTensorInfo* GgufReader::tensor_info(const std::string& name) const { auto it = tensors_.find(name); return it == tensors_.end() ? nullptr : &it->second; }

// ── KV lookup with architecture-prefix fallback ── // GGUF files may store metadata keys with or without the architecture prefix. // E.g. "block_count" vs "mamba.block_count". We try both forms: // - If key contains a dot (e.g. "mamba.block_count"): try exact, then suffix. // - If key has no dot: try exact, then arch + "." + key. const GgufReader::KV* GgufReader::find_kv(const std::string& key) const { auto it = kv_.find(key); if (it != kv_.end()) return &it->second; auto dot = key.find('.'); if (dot != std::string::npos) { std::string suf = key.substr(dot + 1); if (!suf.empty()) { it = kv_.find(suf); if (it != kv_.end()) return &it->second; } } else if (!arch_.empty()) { std::string pre = arch_ + "." + key; it = kv_.find(pre); if (it != kv_.end()) return &it->second; } return nullptr; }

bool GgufReader::get_u32(const std::string& key, uint32_t& out) const { const KV* kv = find_kv(key); if (!kv) return false; if (kv->vtype <= 5 || kv->vtype == 7 || kv->vtype == 10 || kv->vtype == 11) { out = (uint32_t)kv->u; return true; } return false; }

bool GgufReader::get_f32(const std::string& key, float& out) const { const KV* kv = find_kv(key); if (!kv) return false; if (kv->vtype == 6 || kv->vtype == 12) { out = (float)kv->f; return true; } if (kv->vtype <= 5 || kv->vtype == 10 || kv->vtype == 11) { out = (float)(int64_t)kv->u; return true; } return false; }

bool GgufReader::get_string(const std::string& key, std::string& out) const { const KV* kv = find_kv(key); if (!kv || kv->vtype != 8) return false; out = kv->s; return true; }

bool GgufReader::get_string_array(const std::string& key, std::vectorstd::string& out) const { const KV* kv = find_kv(key); if (!kv || kv->vtype != 9) return false; out = kv->arr_str; return true; }

std::vectorstd::string GgufReader::kv_keys() const { std::vectorstd::string keys; keys.reserve(kv_.size()); for (auto& [k, v] : kv_) keys.push_back(k); return keys; }

bool GgufReader::get_tensor_raw(const std::string& name, int block_size, int block_bytes, std::vector<uint8_t>& out, uint64_t* out_numel) { auto it = tensors_.find(name); if (it == tensors_.end() || !f_ || block_size <= 0 || block_bytes <= 0) return false; const GgufTensorInfo& ti = it->second; if (out_numel) *out_numel = ti.numel; uint64_t n_blocks = (ti.numel + block_size - 1) / block_size; out.resize(n_blocks * (uint64_t)block_bytes); fseeko(f_, (off_t)ti.abs_offset, SEEK_SET); return fread(out.data(), 1, out.size(), f_) == out.size(); }

bool GgufReader::get_tensor_f32(const std::string& name, std::vector& out, size_t* out_n) { auto it = tensors_.find(name); if (it == tensors_.end() || !f_) return false; const GgufTensorInfo& ti = it->second; static constexpr uint64_t MAX_TENSOR_ELEMENTS = 1ULL << 30; if (ti.numel > MAX_TENSOR_ELEMENTS) return false; out.resize(ti.numel); if (out_n) *out_n = ti.numel;

GgufBlockInfo bi = gguf_block_info(ti.dtype);
if (bi.block_bytes <= 0) return false;
fseeko(f_, (off_t)ti.abs_offset, SEEK_SET);
uint64_t n_blocks = (ti.numel + bi.block_size - 1) / bi.block_size;
std::vector<uint8_t> block_buf((size_t)bi.block_bytes);
for (uint64_t b = 0; b < n_blocks; b++) {
    uint64_t start = b * bi.block_size;
    uint64_t count = std::min<uint64_t>(bi.block_size, ti.numel - start);
    if (fread(block_buf.data(), (size_t)bi.block_bytes, 1, f_) != 1) return false;
    if (!gguf_dequant(ti.dtype, block_buf.data(), out.data() + start, (int)count)) return false;
}
return true;

}