A unified, self-contained repository for stable single-speaker voice adaptation on Alibaba's Qwen3-TTS 12Hz series. This studio supports both full-parameter supervised fine-tuning (Full SFT) and parameter-efficient adapter training (PEFT LoRA) on consumer-grade hardware, with direct deployment to a unified inference and audio auditing server.
- Repository Path:
https://github.com/jromal/qwen3-tts-voice-sft
All notebooks, patched scripts, and deployment configurations are consolidated inside this monorepo, allowing you to clone the codebase and begin execution immediately:
qwen3-tts-voice-sft/
βββ README.md
βββ WtB_Qwen3_TTS_Finetuning.ipynb # Native Full-Parameter SFT Notebook
βββ WtB_Qwen3_TTS_LoRA_Training.ipynb # Native Parameter-Efficient LoRA SFT Notebook
βββ WtB_Qwen3_TTS_and_Whisper.ipynb # Native Unified Inference & Audit Notebook
βββ full_sft/
β βββ sft_12hz.py # Patched Full-Parameter SFT script
βββ lora_sft/
βββ sft_12hz_lora.py # Patched PEFT LoRA SFT script
βββ infer_lora_custom_voice.py # Patched PEFT LoRA Inference script
The full-parameter SFT training engine has been updated with several critical performance and stability patches:
- The Issue: On the 1.7B parameter base model, the text embedding dimension (2048) matches the transformer hidden dimension (2048). Consequently, the 1.7B model bypasses the text projection layer entirely during standard inference.
- The Problem: Forcing text embeddings through
model.talker.text_projection()during 1.7B SFT training introduces an extra linear transformation that is completely absent during inference. This mismatch causes the language model to read unaligned features during generation, resulting in a totally scrambled, distorted, robotic sound (resembling "aliens underwater"). - The Fix: We have introduced a dynamic, dimension-based check inside the forward pass:
This automatically applies the projection layer on the 0.6B variant (to map 2048 dimensions to 1024), but bypasses it on the 1.7B model (dimensions 2048 vs 2048), maintaining alignment with the inference pipeline.
raw_text_embedding = model.talker.model.text_embedding(input_text_ids) if raw_text_embedding.shape[-1] != model.talker.model.codec_embedding.weight.shape[-1]: input_text_embedding = model.talker.text_projection(raw_text_embedding) * text_embedding_mask else: input_text_embedding = raw_text_embedding * text_embedding_mask
- The Problem: Standard SFT pipelines often call
model.train()on the top-level class, putting all modulesβincluding the pre-trainedspeaker_encoderandspeech_tokenizer(VQ vocoder)βinto training mode. Allowing the vocoder's batch normalization statistics and weights to float during SFT destroys its acoustic reconstruction capabilities, producing severe voice scrambling. - The Fix: The training script explicitly freezes the speaker encoder and vocoder parameters (
requires_grad = False), and enforces.eval()mode on thespeaker_encoderthroughout the SFT loop. Fine-tuning is strictly isolated to themodel.talker(the semantic language model).
- The Problem: Manoevering target labels before they reach the Hugging Face causal loss module can cause double-shifting. This causes the model to learn a temporal compression error, meaning the synthesized voice accelerates progressively over successive training epochs until it is completely fast-forwarded and unintelligible.
- The Fix: Rather than pre-shifting, unshifted labels are passed directly to
ForCausalLMLoss. Slicing is performed strictly on aligning the hidden states[:-1]with target codec indices[1:]through a contiguous mask:hidden_states = outputs.hidden_states[0][-1] target_codec_mask = codec_mask[:, 1:] talker_hidden_states = hidden_states[:, :-1, :][target_codec_mask] talker_codec_ids = codec_ids[:, 1:][target_codec_mask]
- The Problem: In standard Accelerate loops with
gradient_accumulation_steps > 1, executingoptimizer.step()andoptimizer.zero_grad()outside thesync_gradientscheck forces parameter resets on every micro-batch, corrupting accumulated gradient math. - The Fix: Both operations are wrapped strictly inside the
sync_gradientsblock:if accelerator.sync_gradients: accelerator.clip_grad_norm_(model.parameters(), 1.0) optimizer.step() optimizer.zero_grad()
The Parameter-Efficient Fine-Tuning (PEFT) pipeline allows you to compile highly expressive adapters (averaging ~58 MB instead of 3.83 GB), making them ideal for integration with consumer audiobook rendering platforms such as Alexandria Audiobook Studio:
- Description-Based Voice Design: Alexandria's "Voice Design" interface takes plain-text descriptions (e.g., "An elderly, low-register female narrator with a warm, steady cadence") to synthesize a 30 to 50 sentence training dataset.
- Rapid Adapter Training: These synthetic wav clips are compiled into a dataset and trained using
sft_12hz_lora.py. This targets strictly the Qwen3-TTS attention projection layers (q_proj,k_proj,v_proj,o_proj), preserving overall linguistic pronunciation while reshaping vocal timbre and expressive patterns.
Training PEFT models on top of a multi-component neural speech model requires targeted adaptations to prevent gradient corruption:
- The PEFT Unpeeler: Wrapping the inner
talkermodule in a PEFT framework turns it into aPeftModel. When the training loop accesses embedding parameters, resolving attributes on the wrapped model throws a fatalAttributeError. SFT scripts use a custom unpeeler to access the rawQwen2Modellayers cleanly:talker = model.talker if hasattr(talker, "base_model") and hasattr(talker.base_model, "model"): raw_talker = talker.base_model.model else: raw_talker = talker
- Frozen Sub-Talker Loss Isolation: Under a PEFT configuration, the auxiliary code predictors (layers 1-15) are frozen. Backpropagating auxiliary sub-talker losses through frozen parameters into active attention adapters pollutes the gradient landscape, warping speech outputs. The SFT script sets the sub-talker loss scale strictly to
0.0during LoRA runs, focusing the adapters 100% on the primary codebook predictions.
When loading trained adapters onto the unified Gradio server, the model dynamically incorporates the learned features:
- Active PEFT Injection: The server uses
PeftModel.from_pretrainedto load the low-rank weights directly onto the base model's innertalkermodule, applying a scaled context window. - Static/Dynamic Embedding Mapping: SFT weights are mapped to index
3000of the model's vocal embeddings. The server loads the speaker tensor fromspeaker_embedding.safetensorsor extracts it dynamically on-the-fly from a 9-secondref_sample.wavon disk. It injects this tensor into the model's active parameters, enabling you to hot-swap and generate dozens of custom voices.
To create a voice that supports both your speaker's identity and natural language style/emotion control (e.g., "whisper", "sad tone"), you must train on top of the CustomVoice model (which possesses the instruction-following attention weights) rather than the Base model.
- The Obstacle:
CustomVoiceis configured withtts_model_type = "custom_voice"on initialization, which forcesself.speaker_encoder = Noneto conserve memory. Attempting to run standard SFT on it causes a fatalNoneTypeAttributeError during the forward pass. - The Fix: During training startup, if
sft_12hz.pydetects aCustomVoicevariant, it automatically:- Instantiates a standard
Qwen3TTSSpeakerEncoder. - Downloads the matching configuration from the
Basemodel cache (dynamically resolving to1.7B-Baseor0.6B-Basedepending on scale) to define the correct 2048-dimensional projection layers, preventing dimension mismatch errors. - Downloads and extracts the speaker encoder weights, loads them into the active module, and casts them to the correct hardware device and precision dtype (
float16orbfloat16).
- Instantiates a standard
Trained checkpoints are uploaded directly to the Hugging Face Hub. To prevent storage-quota and memory-exhaustion failures on public notebook environments, two designed optimizations are implemented:
- The Problem: Standard training containers (such as Kaggle) enforce a strict 20 GB local disk space limit. Waiting until after the new checkpoint is saved to delete the previous epoch's files forces the disk to temporarily hold multiple 4.52 GB checkpoints at the same time, exceeding the 20 GB ceiling and leading to silent file-writing failures.
- The Fix: The script executes Pre-Pruning. It scans and deletes the previous epoch's checkpoint folder before starting the new save block, keeping peak disk usage strictly under ~9.12 GB.
Hugging Face enforces a strict 100 GB private storage cap on free tier accounts. To support the storage of multiple different voices inside a single private repository, the notebooks implement two distinct upload strategies depending on the model footprint:
- Full-Parameter SFT Checkpoints (Requires Zero-History Branching): Full-parameter models are extremely large (~4.52 GB). Overwriting files in Git LFS appends new files to the Git history instead of replacing them, causing a single repository to quickly exceed the 100 GB limit. The Full SFT notebook resolves this by using an independent Git branch per speaker. During Step 5, it calls
api.delete_branchto wipe out historical LFS caches, re-initializes a fresh branch viaapi.create_branch, and uploads the directory as a single clean commit usingapi.upload_folder. This resets LFS history and keeps storage at exactly ~4.52 GB per voice, leaving other voices untouched. - PEFT LoRA Adapters (Standard Folder-Based Upload on
mainbranch): PEFT adapters are highly compact (~58 MB) and hot-swappable. Because of their tiny size, they do not risk depleting your 100 GB private storage quota. The LoRA notebook uploads checkpoints directly to subfolders on themainbranch using standard folder uploads. This allows you to host and organize dozens of different hot-swappable voices inside a single, clean directory structure.
To train either a Full SFT model or a LoRA adapter, construct your single-speaker dataset directory structure as follows:
Your-Voice-Dataset/
βββ train_raw.jsonl (Your transcription manifest)
βββ ref.wav (Your raw target voice reference)
βββ ref.txt (The transcription text of your ref.wav)
βββ wavs/ (Folder containing training clips)
βββ utt0001.wav
βββ utt0002.wav
βββ ...
- The Resampling Rule (Extremely Important): Every
.wavfile insidewavs/must be pre-resampled to exactly 24,000 Hz (24 kHz) Mono prior to running the training pipeline. Standard 16 kHz or 48 kHz recordings will cause the tokenizer to extract incorrect codec tokens, causing the model to learn distorted, static sound. - Temporal Sizing: Aim for clean audio clips cut to lengths between 2 and 10 seconds.
- Vocal Quality: Ensure background noise is removed (recommended Signal-to-Noise Ratio: SNR > 20dB).
- Identity Lock: Each line of your JSONL manifest should contain a
"language": "en"key (or match your target speaker locale) to prevent identity drift during multi-turn script rendering.
{"audio": "./wavs/utt0001.wav", "text": "This is a clean, resampled training sentence.", "ref_audio": "./ref.wav", "language": "en"}To train inside Kaggle's free Tesla T4 GPU environments, you must package, upload, and attach your dataset correctly so that the notebook's dynamic scanner can ingest it.
- Verify that your audio clips are resampled to 24,000 Hz Mono.
- Structure your files locally to match the
Your-Voice-Dataset/directory layout shown in the section above. - Compress the root folder into a standard
.ziparchive (e.g.,david_attenborough_dataset.zip).- Note: Ensure the zip contains the files directly (e.g., opening the zip should show
train_raw.jsonl,ref.wav, and thewavs/directory), without any double-nested directories.
- Note: Ensure the zip contains the files directly (e.g., opening the zip should show
- Log in to Kaggle.
- On the left-hand navigation sidebar, click on + Create and select New Dataset.
- Enter a clear, hyphenated Dataset Title (e.g.,
david-attenborough-voice-sft). - Drag and drop your
david_attenborough_dataset.ziparchive into the upload window. - Click Create at the bottom-right. Kaggle will unzip and mount the dataset in the cloud.
- Open your SFT training notebook (
WtB_Qwen3_TTS_Finetuning.ipynborWtB_Qwen3_TTS_LoRA_Training.ipynb) inside your Kaggle workspace. - On the right-hand panel, locate the Input configuration sidebar.
- Click on + Add Input.
- In the search bar, select Your Work (or search for the title you entered in Step 2, such as
david-attenborough-voice-sft). - Click the Add button next to your dataset. It will be mounted at
/kaggle/input/david-attenborough-voice-sft/.
The code executed inside Step 4 of your SFT notebook uses an automated recursive search:
- It scans the entire
/kaggle/inputpath on disk to dynamically locate yourtrain_raw.jsonl,ref.wav, andref.txtfiles. - Once located, it automatically identifies your custom directory names, replicates the exact structure inside your local workspace, and copies your WAV clips.
- This means the pipeline will execute successfully even if you rename your ZIP file, change your Kaggle dataset title, or if Kaggle mounts the files inside nested subdirectories.
Your execution notebooks clone this repository and inject the patched scripts directly into the upstream training environment, bypassing standard git merge and path resolution errors.
Clones this repository and executes the following setup command to overwrite the training file:
!cp qwen3-tts-voice-sft/full_sft/sft_12hz.py Qwen3-TTS/finetuning/sft_12hz.py-
Three-Tier Epoch Scaling: Replaced the legacy single auto-epoch logic with a granular, 3-tier adaptive scaling system selectable directly from your configuration dashboard:
-
'full'(Rule of 720):$\text{Epochs} = \text{round}(720 / N)$ , capped cleanly between 4 and 20. Optimized for larger, highly diverse datasets. -
'medium'(Rule of 540):$\text{Epochs} = \text{round}(540 / N)$ , capped cleanly between 3 and 15. The recommended baseline default (providing the sweet spot of phonetic detailing and style flexibility). -
'half'(Rule of 360):$\text{Epochs} = \text{round}(360 / N)$ , capped cleanly between 3 and 10. Prevents overfitting and vocal burning on tiny, highly uniform datasets.
-
-
Dashboard-Exposed Learning Rate: The SFT learning rate is now fully exposed as a configurable parameter on the dashboard (Step 1), defaulting to
1e-6. It supports:-
2e-6(Aggressive / standard; best for very large, multi-hour voice pools). -
1e-6(Recommended standard baseline for typical 15-30 minute SFT runs). -
5e-7(Conservative / delicate; designed to prevent metallic distortions on small, pristine datasets under 15 minutes).
-
-
Dual-Naming Reference Copies: During Step 5, the notebook automatically copies both
ref.wavandref.txtto the final epoch folder using dual names (ref.wav/ref_sample.wavandref.txt/ref_sample.txt). -
Standalone Checkpoint Preservation: The export script preserves the foundational text processor configurations (
vocab.json,merges.txt,tokenizer_config.json, andpreprocessor_config.json) inside the final saved directory. This ensures the output checkpoint remains fully loadable via standardQwen3TTSModel.from_pretrained(...).
Clones this repository and copies both the PEFT training script and the corresponding inference adapter logic:
!cp qwen3-tts-voice-sft/lora_sft/sft_12hz_lora.py Qwen3-TTS/finetuning/sft_12hz_lora.py
!cp qwen3-tts-voice-sft/lora_sft/infer_lora_custom_voice.py Qwen3-TTS/finetuning/infer_lora_custom_voice.py-
Rule of 1200: Epochs scale dynamically via:
$$\text{Epochs} = \text{round}\left(\frac{1200}{N}\right) \quad \text{[Bounded between 6 and 30]}$$ -
Metadata Bypass: Automatically deletes
README.mdright before uploading checkpoints to Hugging Face, bypassing standard server-side metadata parser rejections.
Clones this repository to set up and host the Gradio production generation and post-production auditing server:
- Hugging Face Variable Injection: Seamlessly logs in and dynamically scans available branches on your model hub repository to compile custom speaker character choices.
- Physical Hardware Acceleration Casting: Overrides wrapper loading limitations by explicitly executing
current_tts_model.model.to(DEVICE)to cast all model layers onto active GPU memory, reducing execution times from minutes to under 3 seconds. - Dynamic safe ceiling limiter: Replaces standard static generation length constraints with a text-proportional mathematical safe token ceiling to eliminate infinite autoregressive loop hangs.
Trained models are deployed utilizing the unified inference server (WtB_Qwen3_TTS_and_Whisper.ipynb). The production deployment architecture relies on several designed optimizations:
To prevent massive Git LFS file history bloat inside centralized Hugging Face repositories, each voice model is saved on its own independent, zero-history branch named after the target_speaker_name.
- Dynamic Scanning: The server queries branches dynamically (
api.list_repo_refs) to populate character choices instead of scanning repo files. - On-Demand Downloads: Checked models are loaded by setting the target branch as the snapshot revision (
revision=voice_name/subfolder=None). This completely isolates SFT weight downloads and prevents downloading unrelated models.
- SFT Custom Voice Mode (Timbre SFT + Active Style Controls): SFT models trained on top of
1.7B-CustomVoiceinherit the pre-trained, instruction-following attention maps. At load-time, the server forces"tts_model_type": "custom_voice". This disables the speaker encoder and routes generation natively throughmodel.generate_custom_voice()using SFT index3000. This maps style instructions cleanly as a conditioning latent (instruct), preventing style prompts from being read aloud. - SFT + ICL Hybrid Mode (For Low-Resource Datasets): For short datasets (under 30 minutes of voice data), index-based embedding can experience mathematical collapse. The server dynamically keeps the model in
"base"mode, activating thespeaker_encoderto extract a stable timbre from yourref.wavon disk while using the SFT-trained attention layers to guide expressive cadence.
To prevent infinite generation loops (EOS token failures), the inference engine computes a dynamic, text-proportional safe ceiling based on character count:
- 20-Second Floor (250 tokens): Guarantees a minimum headroom of 20 seconds, preventing the truncation of short sentences with long expressive pauses.
- Context Safety Window (4096 tokens): Caps absolute sequence boundaries to prevent attention matrix memory overflow (OOM) crashes on large text passages.
- Explicit EOS Boundaries: Explicitly passes the validated Qwen3-TTS tokenizer EOS array (
[2150, 2157, 151670, 151673, 151645, 151643]) to all generation loops, providing explicit targets for clean stops.
- Turing GPU Precision Stability: Turing-architecture GPUs (such as the Tesla T4) suffer from PyTorch SDPA attention stability issues under native
float16precision. ForcingDTYPE_TTS = torch.bfloat16with"attn_implementation": "sdpa"completely prevents underflow NaNs and process-halting crashes on T4 cloud VMs. - Physical Hardware Casting: High-level wrapper pipelines can cause weights to remain partially on CPU memory, spike system CPU to 100%, and hang inference. Explicitly casting the model via
current_tts_model.model.to(DEVICE)right after load-time moves all weight matrices onto GPU memory, accelerating execution speeds.
The Quality Audit tab hosts a high-precision post-production aligner powered by Stable-TS (Whisper large-v3).
- Word-Level Forced Alignment: Accepts any input audio along with a manuscript text prompt to output aligned, millisecond-accurate word-level timestamp coordinates.
- Onset Correction Scan: Autoregressive Whisper models suffer from an origin-snapping alignment bug where silent audio buffers at the start of a clip cause word timings to snap falsely to
0.00seconds. The audit engine runs an active RMS energy scan to find the physical sound onset threshold (dB > -40) and applies a matching mathematical shift, ensuring aligned timestamp outputs are accurate.
This training repository builds upon contributions from the following open-source frameworks and community developers:
- Alibaba Qwen Team for the base Qwen3-TTS architecture and streaming engines.
- vspeech/Qwen3-TTS-Train for community research regarding model synchronization, training behavior analyses, and dataset size guidelines.
- Finrandojin/alexandria-audiobook for the audiobook rendering pipelines and custom UI application components.