Quant Menu II (v0.53.0)

The full advanced-quantization surface. v0.53.0 shipped the closed allowlists and validators; v0.53.1 lit up every writer — 3-stage llama.cpp imatrix pipeline, TorchAO PTQ export, and single-shot BNB-4bit merge. See v0.53.1 live writers for the live-wiring detail.

Unsloth Dynamic 2.0 GGUF ladder

14-entry closed allowlist:

UD-Q8_K_XL · UD-Q6_K_XL · UD-Q5_K_XL · UD-Q4_K_XL · UD-Q3_K_XL · UD-Q2_K_XL · UD-IQ4_XS · UD-IQ3_M · UD-IQ3_XXS · UD-IQ2_M · UD-IQ2_XS · UD-IQ2_XXS · UD-IQ1_M · UD-IQ1_S

validate_ud_gguf_format is case-insensitive with canonical normalisation.

bash
soup export --model ./output --format gguf-ud --gguf-flavour UD-Q4_K_XL --calibration-data calib.jsonl --output ./model.gguf

Every UD rung and the low-bit IQ rungs (IQ1_S, IQ1_M, IQ2_XXS, IQ2_XS, IQ2_S, IQ2_M, IQ3_XXS, IQ3_XS) need --calibration-data, because the quantizer builds an importance matrix from it; without it the export fails with flavour ... requires --calibration-data <jsonl>. The Apple/ARM flavours and the other four IQ rungs (IQ3_S, IQ3_M, IQ4_NL, IQ4_XS) do not.

IQ + Apple/ARM GGUF flavours

  • 12-entry IQ family (IQ1/2/3/4 — including IQ4_NL non-linear)
  • 10-entry Apple/ARM-friendly set (Q4_0_4_4, Q4_NL, Q5_K_M, etc.)

Both wrapped in MappingProxyType metadata.

KV cache types

yaml
training:
  kv_cache_type: q8_0   # q8_0 | bf16 | f16 are servable; fp8 is accepted here and refused at serve time

training.kv_cache_type is accepted by the schema, but nothing in v0.76.0 reads it. The cache type is chosen when you serve, with soup serve --model ./output --kv-cache-type q8_0 (q8_0 needs the hqq package; bf16 and f16 set the cache dtype).

FP8 is accepted by the schema and unreachable at serve time: the transformers backend has no fp8 KV-cache path and raises, and the vLLM / SGLang routing that would provide one is not wired, so it raises earlier still. The Hopper capability check only decides which of the two error messages you get; a Hopper card does not make it work today.

FP8 attention, NVFP4, native unsloth_bnb_4bit

Three separate configs, because the requirements in those comments are enforced at config load and no single config satisfies all three.

yaml
training:
  quantization_aware: fp8   # required, or fp8_attention is refused
  fp8_attention: true       # not on mlx, unsloth or stream_layers
yaml
training:
  nvfp4: true               # CUDA + text only; Blackwell (SM 10.0 datacenter or 12.0 consumer), checked at runtime

fp8_attention asks the same card gate as FP8 training: compute capability 8.9 or higher (Ada, Hopper, Blackwell), checked at setup before the model loads. A card or torch build that cannot run it, a missing torchao, or a conversion that fails stops the run (v0.76.0, #835); through v0.75.2 it printed a yellow advisory and trained in bf16, and a pre-Hopper GPU degraded to an advisory rather than an error. nvfp4 is different: it still degrades. When no Blackwell device is detected, or torchao lacks the class, Soup prints a yellow NVFP4: ... line and the run continues in bf16, so look for NVFP4 quantisation applied in the log. When it does apply, nvfp4: true uses torchao's NVFP4TrainingConfig (the linears become NVFP4Linear and the forward and backward GEMMs are quantised), which needs torchao>=0.18.0, the [qat] extra's floor since v0.76.0; a training step additionally needs triton and M, K and N divisible by 128, which Soup does not check. Through v0.75.2 Soup asked torchao for NVFP4Config, a class torchao has never exported, so on Blackwell the flag always fell back to the advisory and a bf16 run (#826).

On task: sft, fp8_attention and nvfp4 were not applied at all until v0.76.0 (#800): the SFT trainer only handled quantization_aware, so those two keys were accepted and did nothing there. If you set either on an SFT run on v0.75.2 or earlier, that run trained without it. They are refused at config load on backend: unsloth and with stream_layers: true (#1124), and on tasks outside the 13-task allowlist.

yaml
backend: unsloth
training:
  quantization: 4bit
  unsloth_bnb_4bit: true    # both of the above are required

LF / Axolotl parity

yaml
task: ppo
training:
  quantization: 4bit
  bnb_4bit_use_double_quant: true   # explicit true requires quantization='4bit'
  quantize_reward_model: true       # PPO + reward_model tasks

quantize_ref_model is accepted by the schema (it requires a task that has a reference model) but nothing in v0.76.0 reads it, so it does not quantize the reference model: the DPO trainer builds no reference model and the PPO trainer passes ref_model=None, rather than loading a second model. Set to true, it prints Warning: training.quantize_ref_model is accepted but read by nothing; v0.77 will refuse it at load (#808); the warning says v0.77 will refuse the config (upstream's stated deadline), so delete the key. quantize_reward_model is honoured by the PPO and reward-model trainers.

yaml
training:
  quantization: 8bit
  llm_int8: true                    # asserts quantization='8bit', so it cannot
                                    # share a config with the 4-bit block above

bnb_4bit_use_double_quant was read by nothing until v0.73.3. Every place Soup built a 4-bit config hardcoded double-quantization on, so setting the key to false changed your config fingerprint and changed nothing about the run. It is honoured now at the three call sites Soup owns: the resident loader, the layer-streaming path (which reads it once and hands the same value to both the sharder and the meta skeleton, so streamed-versus-resident bit-exactness cannot drift), and the 4-bit save path. Two things travel with that. The field is now tri-state: unset means the shipped default, which is double-quant on, so a config that never set it trains identically; only an explicit true triggers the "requires quantization: 4bit" refusal. And because the resolved config now carries the field, a previously-unset config shows a one-time fingerprint drift in soup ship --evidence provenance and soup lock check with the numerics unchanged. The Unsloth loader cannot honour it — FastLanguageModel builds its own quantization config internally with double-quant hardcoded on and exposes no override.

Advanced save formats

bash
soup merge --adapter ./output --save-format 4bit          # | 4bit_forced
soup merge --adapter ./output --save-format 4bit --no-double-quant   # v0.73.3; ignored for fp16
soup export --model ./output --format torchao --quant-config quant.yaml

save-format 4bit_forced writes a single BNB-4bit merged checkpoint without a dequant / merge / requant cycle. It quantizes all linear layers including lm_head, so a model with tied embeddings (tie_word_embeddings: true) cannot use it: the tied head shares weights with the embeddings, which a 4-bit linear cannot hold, and the command refuses by name (after the fp16 merge has been staged and before the 4-bit reload: Merge failed: ..., exit 1). Use --save-format 4bit for such a model.

--quant-config accepts a closed TorchAO allowlist: Int4WeightOnly / Int8DynActInt4 / Float8DynActFloat8 / NVFP4. These are Soup's own scheme names, case-sensitive, and since v0.76.0 each is mapped to the torchao class that implements it (three of the four names Soup used to look up did not exist in torchao, #826). Int4WeightOnly and Int8DynActInt4 accept group_size; inner_k_tiles was removed from the allowlist because torchao's Int4WeightOnlyConfig raises TypeError for it.

Stats

  • Net +157 tests (7,453 → 7,610 across 179 files)
  • 154 tests in test_v0530.py
  • 5 review agents ran in parallel; every CRITICAL / HIGH / MEDIUM / LOW finding fixed or documented

See also

Soup is free and Apache-2.0. If it saved you a training run, starring the repo costs nothing and helps most. You can also fund the GPU time behind the work a 4 GB laptop cannot reach.