Qwen3-Omni-30B-A3B-DynQuant-3bit
Speech-to-speech fine-tune of Qwen3-Omni-30B-A3B, quantized to 3.00 average bits per weight with DynQuant.
Fine-tuned from
Qwen/Qwen3-Omni-30B-A3B-Instruct on spoken dialogue, then quantized to a 3.00-bit target with DynQuant.
What was actually trained
Qwen3-Omni ships the codec decoder (code2wav) but no codec encoder or quantizer,
so target speech cannot be turned back into the discrete Talker labels a speech-out
loss would need. This run therefore trains the Thinker -- the half that listens and
decides what to say -- inside the full speech-in/speech-out pipeline, with the Talker
and code2wav frozen and bit-identical to the base checkpoint. That is also the only
half DynQuant quantizes here: all 96 expert banks are Thinker banks.
| |
|---|
| Trained stage | thinker (31.72 B params) |
| Frozen stages | talker, code2wav (3.54 B params, bit-identical to base) |
| Method | QLoRA, rank 16, on attention and dense-MLP projections |
| Steps | 500 optimizer steps at effective batch 16 (2 x GPU) |
| Audio seen | 10.0 hours |
| Wall clock | 30 min |
Size
| Artifact | On disk |
|---|
| QLoRA adapter | 0.10 GiB (112,207,855 B) |
| bf16 merge | 65.69 GiB (70,537,517,111 B) |
| DynQuant 4-bit | 21.38 GiB (22,953,262,961 B) |
| DynQuant 3-bit | 17.68 GiB (18,988,492,249 B) |
Honest limits
There is no accuracy metric. No benchmark, no A/B against the base checkpoint, no
listening study -- that was accepted going in. What did run is a smoke probe:
4 held-out clips per arm, seed pinned, each answered end to end from audio
in to audio out, with the loaded module tree counted afterwards so that a directory
which silently loaded unquantized could not pass as a working quantized model.
| Arm | Packed modules | Clips | Silent | Median new tokens | Speech emitted |
|---|
| bf16 merge | none (bf16) | 4 | 0 | 39 | 45.4 s |
| DynQuant 4-bit | 602 | 4 | 0 | 21 | 37.6 s |
| DynQuant 3-bit | 602 | 4 | 0 | 28 | 30.5 s |
That checks for the destroyed-model failure -- silence, fluent nonsense, a load path
that quietly fell back -- and all three arms pass it. It is not a measure of quality.
A handful of replies read by eye cannot separate "as good as bf16" from "noticeably
worse but still coherent", and nothing in this campaign does.
A 3.00-bit target is below this architecture's floor budget. The role floors
alone cost 3.4210 average bits, so a 3.00 budget cannot pay for them and the soft
floors bind: 394 of 650 modules are breached and 125
are cut to 2 bits.
The breaches land where they hurt a speech model most. The narrowest modules in the
entire allocation are the audio tower's own attention projections --
audio_tower.layers.0.self_attn.{q,k,v}_proj at 2 bits against a 4-bit floor -- which
is the path the model hears through. On a sibling arm of this same architecture a 3-bit
budget cost 61.80 accuracy points and produced a destroyed model rather than a degraded
one.
This artifact is published because it was asked for and because its size claim is real.
The expectation going in was that it would be destroyed. The probe does not show
that. Every reply came back fluent, on topic and grammatical, including one that
correctly answered a spoken question about Plato's Republic -- which it could only do
by hearing the question through those 2-bit projections.
It does show one behavioural difference. On the clip where the reference answer and
both other arms decline a request for a person's home address, this arm offered to help
find it. One clip is not a rate. But it is the kind of divergence that a fluency check
is blind to by construction: instruction-following and refusal behaviour degrade before
fluency does, so "it still speaks well" is the last thing to break and the weakest
possible evidence that nothing else has.
So: not destroyed, not measured, and the weakest of the three. Listen before relying on
it, and do not deploy it anywhere a refusal matters.
Use
1import torch, transformers
2import dynquant
3
4dynquant.register_hf_quantizer()
5
6model = transformers.Qwen3OmniMoeForConditionalGeneration.from_pretrained(
7 "VikramPal/Qwen3-Omni-30B-A3B-DynQuant-3bit", dtype=torch.bfloat16, device_map="auto"
8)
9processor = transformers.AutoProcessor.from_pretrained("VikramPal/Qwen3-Omni-30B-A3B-DynQuant-3bit")
10
11conversation = [
12 {"role": "system", "content": [{"type": "text", "text": "You are a helpful voice assistant. Reply naturally and briefly, as if speaking aloud."}]},
13 {"role": "user", "content": [{"type": "audio", "audio": wave_16khz}]},
14]
15inputs = processor.apply_chat_template(
16 conversation, add_generation_prompt=True, tokenize=True,
17 return_dict=True, return_tensors="pt", sampling_rate=16000,
18).to(model.device).to(model.dtype)
19
20text_ids, audio = model.generate(**inputs, speaker="Ethan", return_audio=True)
21# audio comes back at 24 kHz
Provenance
Produced with
DynQuant v0.5.0. The
allocation is the signal map the fine-tune itself recorded -- per-module gradient
variance and activation RMS -- consumed by a role-aware ROI allocator. It was computed
over the Thinker and applied to the full model, which is why the other two stages are
still bf16.