Phased plan, ordered by leverage. Each phase is "ship-and-test before moving on".
- Project scaffold (
pyproject.toml,src/xl8r/, CLI dispatcher) - Migrate
nemotron-encode/scripts/export_nemotron_gguf.py→xl8r/safetensor2gguf.py - Generic Llama-3-family path (Llama 3, 3.1, 3.2, 3.3, Nemotron-VL, Hermes, etc.)
- Multimodal-VL split (mmproj + language) for Nemotron-VL pattern
- Tokenizer write (gpt2 model + llama-bpe pre + merges + bos/eos/pad + chat_template)
- Vocab off-by-one handling (matches llama.cpp
convert_hf_to_gguf.pypolicy)
-
gguf2safetensor— migratenemotron-encode/scripts/gguf_to_safetensors.py- Add
--also-pytorch-binflag for legacy HF format - Test round-trip (HF → GGUF → HF) numerical equivalence within quant noise
- Add
-
validate— migratenemotron-encode/scripts/validate_gguf.py- STS-B correlation vs FP32 reference
- Per-quant quality table
-
quantize— migratenemotron-encode/scripts/quantize_model.py- Expose IQ-quants (IQ2_XXS, IQ2_XS, IQ2_S, IQ3_XXS, IQ3_S, IQ3_M, IQ4_NL, IQ4_XS)
- Expose K-quants (Q2_K, Q3_K_S/M/L, Q4_K_S/M, Q5_K_S/M, Q6_K)
- iMatrix calibration + HAWQ-V2 + BRECQ recipes
-
safetensor2safetensor— cross-format conversion- regular ↔ MLX (
mlx_lm.utils.dequantize/quantize) - regular ↔ AWQ (
autoawqunpack/pack) - regular ↔ CompressedTensors (
compressed-tensorsunpack/pack) - regular ↔ GPTQ (via
optimum-quantoorauto-gptq) - regular ↔ BitsAndBytes (8-bit / NF4)
- regular ↔ MLX (
Goal: accept already-quantized HF safetensors and emit GGUF without going through FP32 → quant ourselves.
-
xl8r/formats/awq.py— detect AWQ, dequant viaautoawq.utils.packing.unpack_awq, then re-quant to target GGUF format -
xl8r/formats/mlx.py— detect MLX (group-wise affine), dequant viamlx.core.dequantize -
xl8r/formats/compressed_tensors.py— detect CT (w4a16, w8a8, etc.), dequant viacompressed-tensors -
xl8r/formats/gptq.py— detect GPTQ, dequant viaauto-gptq - Auto interim-F16 pipeline: AWQ/MLX/CT → F16 safetensors → target GGUF quant
- One CLI flag:
safetensor2gguf --auto-formatdetects input format + emits requested quant
- One CLI flag:
- Test models on disk:
Qwen_Qwen2.5-3B-Instruct-AWQ(AWQ)RedHatAI_Qwen3-0.6B-quantized.w4a16(CompressedTensors)- Any MLX model from HF (need to download — small 1B class)
Test the safetensor2gguf path against varied architectures, fix per-arch quirks (xl8r/formats/llama.py, gemma.py, qwen.py, gpt_oss.py, mistral.py).
| Model | Arch | Test plan |
|---|---|---|
google_gemma-4-E2B-it |
gemma3 dense | Convert → llama.cpp loads → matches HF eval |
google_gemma-4-E4B-it |
gemma3 dense | Same as E2B |
Qwen_Qwen3-Embedding-0.6B |
qwen3 (encoder) | Embed test |
Qwen_Qwen3-Embedding-8B |
qwen3 (encoder) | Embed test (large) |
Qwen_Qwen3.5-9B |
qwen3 dense | Generation test |
| gpt-oss 20B / 120B | gpt-oss MoE | Confirm 20b vs 120b packing identical (the user's open question) |
nomic-ai_modernbert-embed-base |
modernbert | Embed test |
BAAI_bge-base-en-v1.5 |
bert | Embed test |
- Audio mmproj — Whisper-style audio encoder + adapter
- Detect via
audio_configin config.json - Test: Qwen2-Audio-7B, Whisper Large-v3
- Detect via
- Video mmproj — Qwen2.5-VL temporal patches, InternVideo
- Detect via temporal patch params
- Diffusion models — SD3, Flux, separate code path (UNet/MMDiT, not LLM)
- Test:
unsloth_FLUX.2-klein-9B-GGUF(already on disk in GGUF — round-trip back?) - Output: GGUF compatible with
stable-diffusion.cpp
- Test:
- ColBERT — late-interaction encoder, custom head
- Test:
colbert-ir_colbertv2.0
- Test:
- ColPali — multimodal ColBERT (vision tokens)
- Test:
vidore_colpali-v1.3
- Test:
- Factcheck — T5-based aggregation
- Test:
Linuss4880_MiniCheck-Flan-T5-Large-Q8_0-GGUF(already GGUF — round-trip)
- Test:
-
xl8r/crossfmt/onnx.py— HF Optimum forward + custom reverse -
xl8r/crossfmt/openvino.py—optimum-intelfor IR conversion + NNCF for INT8/INT4 -
xl8r/crossfmt/coreml.py—coremltoolsfor.mlpackage(Apple ANE-friendly) -
xl8r/crossfmt/pytorch_bin.py— legacytorch.save({}, 'pytorch_model.bin') - Obscure NPU/accelerator formats (research first, implement on demand):
- Qualcomm Snapdragon NPU (QNN)
- Intel NPU (OpenVINO + IR)
- AMD XDNA (XCLBIN)
- Google Edge TPU (
.tflite+edgetpu_compiler) - Hailo (
.hef)
Vendored research lives under _research/turboquant-* (gitignored). Clean re-implementations land here:
-
xl8r/quants/turboquant.py— base TurboQuant compression -
xl8r/quants/turboquant_plus.py— TurboQuant+ variant -
xl8r/quants/back2matching.py— back-to-matching variant -
xl8r/quants/lloyd_max.py— Lloyd-Max scalar quantizer - Integrate into
quantizesubcommand as--recipe turboquant|tq+|b2m|lloydmax
For each conversion class:
- Convert a real HF model from
~/Projects/models/or~/Projects/CLAUDE/ConfluenceSearch2/models/ - Load the artifact into the upstream, unmodified runtime (vanilla
llama.cpp,onnxruntime,openvino,coremltools,mlx_lm) - Compare output (logits / embeddings / generated tokens) to HF reference within quant noise
- Document results in
tests/results/<class>.md - Discard the converted artifact (we have limited disk) and move to the next class
- Keep a tiny canonical set on-hand:
Xenova_all-MiniLM-L6-v2,google_gemma-4-E2B-it,Qwen_Qwen3-Embedding-0.6B,nvidia_llama-nemotron-embed-vl-1b-v2
- No llama.cpp / onnxruntime / openvino native binary build dependency
- No custom CUDA / Metal kernels — pure Python + NumPy + Torch
- No model retraining — converter only, never trains
- No hosted service — this is a library/CLI, not a SaaS
- gpt-oss packing: is the 20B model packed identically to 120B (same MoE expert layout, same
num_experts_per_tok)? Need to inspect both configs side-by-side once a 20B is on disk. - TurboQuant license: vendored repos (
turboquant-pytorch,turboquant_plus,turboquant-back2matching) are MIT/Apache. Clean re-implementation should keep attribution intact in module headers. - HF Optimum vs direct ONNX: Optimum's exports sometimes split into multiple ONNX files (
encoder_model.onnx,decoder_model.onnx). Round-tripping back to safetensors needs to recombine. May skip multi-file export.