Summary
conversion/registry.py allows eight weight types for ASR and two for TTS and codec. I would like to understand whether that gap is a measured quality decision or an unimplemented path, and I am proposing a specific experiment that would settle it either way.
This follows a LinkedIn thread where I was asked to bring the question here.
What the code does today
_normalized_outtype in conversion/registry.py sets the defaults and the allowed sets:
| architecture |
default |
supported |
asr |
q8_0 |
f16, bf16, q8_0, q4_k, q5_k, q6_k, nvfp4, mxfp4 |
pnc |
q8_0 |
f16, bf16, q8_0 |
diarization |
f32 |
f32, f16, bf16, q8_0, q4_k, q5_k, q6_k, nvfp4, mxfp4 |
tts |
f16 |
f16, f32 |
codec |
f16 |
f16, f32 |
So python3 convert_model.py nvidia/magpie_tts_multilingual_357m --outfile m.gguf --outtype q8_0 raises ValueError, and the same holds for the NanoCodec checkpoint.
The restriction appears to track the converter rather than a quality finding. conversion/tts.py does not import conversion/quantization.py, and tensor_to_numpy only ever emits torch.float16 or torch.float32. conversion/codec.py is in the same position. There is no code path that could produce a quantized TTS tensor, so as far as I can tell no q8_0 MagpieTTS GGUF has been measured.
Why the TTS side looks like a candidate
Three things in the existing converter already point the right way.
should_store_f32 in conversion/tts.py keeps rank-1 tensors and every .bias at f32 while letting rank-2 weights drop to f16. That is the same keep-the-norms-high convention a q8_0 path needs, already written and already applied.
add_tensors splits the Conv1d weights *.pos_ff.proj.conv.weight and *.pos_ff.o_net.conv.weight into per-kernel rank-2 slices .k0 through .kN. Those land in ggml_mul_mat through src/runtime/ggml/nn.cpp, which is the same call that makes Q8_0 and the k-quants compute natively on the ASR side. The stored layout is already the shape a quantized matmul consumes.
--local-transformer-outtype already exists and already carries a per-subgraph precision override, with is_local_transformer_tensor scoping it to audio_embeddings., local_transformer., local_transformer_in_projection. and local_transformer_out_projections.. Widening its choices is a smaller change than introducing the concept.
Finally, MagpieTTS emits discrete NanoCodec tokens rather than a waveform or a mel, so a weight perturbation either changes the selected token or it does not survive to the output at all.
The counter-argument I cannot dismiss
That last point is weaker than it first looks, and it is the reason I am opening a question rather than a request.
Decoding is not argmax. The converter writes magpietts.inference.topk at 80, magpietts.inference.temperature at 0.6 and magpietts.inference.cfg_scale at 2.5. Under top-k sampling with classifier-free guidance, small logit shifts do change the sampled token, and errors accumulate over up to max_decoder_steps autoregressive frames with the attention prior reacting to the drift. NanoCodec at 21.5 fps also has no downstream stage that rounds an error away, which cuts against quantization here in a way it does not for an ASR decoder.
So the argument that discrete output makes 8-bit cheap holds for a greedy decoder and is not established for this one.
Proposed test
Fixed seed, a fixed prompt set across the supported languages, both models converted from the same checkpoint, comparing at the token level before anything reaches the codec:
- Convert
nvidia/magpie_tts_multilingual_357m at f16 and at q8_0.
- Generate with identical seed, text, speaker and sampling parameters.
- Compare the emitted NanoCodec token streams frame by frame, and record exact-match rate, first divergence frame and generated length.
- Repeat at
temperature=0 to separate weight error from sampling noise.
Two outcomes, both useful. If the token streams agree at greedy decode and diverge only marginally under sampling, f16 on the TTS side is over-provisioning and the ASR precedent carries, so a q8_0 path is worth adding. If they diverge materially at greedy decode, the current default is correct and the interesting artifact is the number, which belongs in docs/model-conversion.md next to the table so the question stops coming up.
A --local-transformer-outtype q8_0 variant is worth a row too, since the local transformer and audio embeddings are the cheapest subgraph to move and the plumbing is already there.
Questions
- Has a quantized MagpieTTS or NanoCodec conversion been measured internally, and if so is the number publishable?
- Is the
{f16, f32} restriction a deliberate quality floor, or is it recording that the converter has no quantization path yet?
- If the experiment above lands on the favorable side, would a
q8_0 path in conversion/tts.py plus the matching entry in tests/conversion/converter_contract_test.py be in scope for a PR?
I am happy to run the experiment and post the table here before writing any converter code.
Environment
Observations are from the default branch at 5be7bfb104802131e61fe679b3f1401b27270216.
Summary
conversion/registry.pyallows eight weight types for ASR and two for TTS and codec. I would like to understand whether that gap is a measured quality decision or an unimplemented path, and I am proposing a specific experiment that would settle it either way.This follows a LinkedIn thread where I was asked to bring the question here.
What the code does today
_normalized_outtypeinconversion/registry.pysets the defaults and the allowed sets:asrq8_0f16, bf16, q8_0, q4_k, q5_k, q6_k, nvfp4, mxfp4pncq8_0f16, bf16, q8_0diarizationf32f32, f16, bf16, q8_0, q4_k, q5_k, q6_k, nvfp4, mxfp4ttsf16f16, f32codecf16f16, f32So
python3 convert_model.py nvidia/magpie_tts_multilingual_357m --outfile m.gguf --outtype q8_0raisesValueError, and the same holds for the NanoCodec checkpoint.The restriction appears to track the converter rather than a quality finding.
conversion/tts.pydoes not importconversion/quantization.py, andtensor_to_numpyonly ever emitstorch.float16ortorch.float32.conversion/codec.pyis in the same position. There is no code path that could produce a quantized TTS tensor, so as far as I can tell no q8_0 MagpieTTS GGUF has been measured.Why the TTS side looks like a candidate
Three things in the existing converter already point the right way.
should_store_f32inconversion/tts.pykeeps rank-1 tensors and every.biasat f32 while letting rank-2 weights drop to f16. That is the same keep-the-norms-high convention a q8_0 path needs, already written and already applied.add_tensorssplits the Conv1d weights*.pos_ff.proj.conv.weightand*.pos_ff.o_net.conv.weightinto per-kernel rank-2 slices.k0through.kN. Those land inggml_mul_matthroughsrc/runtime/ggml/nn.cpp, which is the same call that makes Q8_0 and the k-quants compute natively on the ASR side. The stored layout is already the shape a quantized matmul consumes.--local-transformer-outtypealready exists and already carries a per-subgraph precision override, withis_local_transformer_tensorscoping it toaudio_embeddings.,local_transformer.,local_transformer_in_projection.andlocal_transformer_out_projections.. Widening itschoicesis a smaller change than introducing the concept.Finally, MagpieTTS emits discrete NanoCodec tokens rather than a waveform or a mel, so a weight perturbation either changes the selected token or it does not survive to the output at all.
The counter-argument I cannot dismiss
That last point is weaker than it first looks, and it is the reason I am opening a question rather than a request.
Decoding is not argmax. The converter writes
magpietts.inference.topkat 80,magpietts.inference.temperatureat 0.6 andmagpietts.inference.cfg_scaleat 2.5. Under top-k sampling with classifier-free guidance, small logit shifts do change the sampled token, and errors accumulate over up tomax_decoder_stepsautoregressive frames with the attention prior reacting to the drift. NanoCodec at 21.5 fps also has no downstream stage that rounds an error away, which cuts against quantization here in a way it does not for an ASR decoder.So the argument that discrete output makes 8-bit cheap holds for a greedy decoder and is not established for this one.
Proposed test
Fixed seed, a fixed prompt set across the supported languages, both models converted from the same checkpoint, comparing at the token level before anything reaches the codec:
nvidia/magpie_tts_multilingual_357matf16and atq8_0.temperature=0to separate weight error from sampling noise.Two outcomes, both useful. If the token streams agree at greedy decode and diverge only marginally under sampling,
f16on the TTS side is over-provisioning and the ASR precedent carries, so aq8_0path is worth adding. If they diverge materially at greedy decode, the current default is correct and the interesting artifact is the number, which belongs indocs/model-conversion.mdnext to the table so the question stops coming up.A
--local-transformer-outtype q8_0variant is worth a row too, since the local transformer and audio embeddings are the cheapest subgraph to move and the plumbing is already there.Questions
{f16, f32}restriction a deliberate quality floor, or is it recording that the converter has no quantization path yet?q8_0path inconversion/tts.pyplus the matching entry intests/conversion/converter_contract_test.pybe in scope for a PR?I am happy to run the experiment and post the table here before writing any converter code.
Environment
Observations are from the default branch at
5be7bfb104802131e61fe679b3f1401b27270216.