Audio version — Estimated duration: 7 min 28 sec
File: md_to_audio_custom.py
Usage
python md_to_audio_custom.py <input_md> [options]Arguments
input_md (positional, required)
Path to the input Markdown file. Text is cleaned of formatting and non-ASCII characters before synthesis.
python md_to_audio_custom.py sample.md --speaker "Serena"--model-path
| Type | Default |
|---|---|
str | ./Qwen3-TTS-12Hz-0.6B-CustomVoice |
Path to the CustomVoice model directory. Contains speaker embeddings and model weights downloaded from Hugging Face.
python md_to_audio_custom.py sample.md \
--model-path /models/CustomVoice \
--speaker "Aiden"--output
| Type | Default |
|---|---|
str | output_custom.wav |
Output WAV file path. 24 kHz mono 32-bit float. Overwrites existing files without warning.
--speaker
| Type | Default |
|---|---|
str | Aiden |
One of 9 preset speaker names. Case-insensitive — the script does a lowercase comparison and resolves the correct casing automatically.
Valid values: Serena, Vivian, Uncle_Fu, Ryan, Aiden, Ono_Anna, Sohee, Eric, Dylan
If the name doesn’t match any speaker, the script falls back to "serena" and prints the available list.
--instruct
| Type | Default |
|---|---|
str | Speak naturally. |
Style instruction for the speaker. This is a short text prompt that guides delivery. Keep it to one sentence — longer instructions may confuse the model.
python md_to_audio_custom.py sample.md \
--speaker "Ryan" \
--instruct "Read this like a dramatic movie trailer."See the Preset Voices guide for a table of instruction examples.
--lang
| Type | Default |
|---|---|
str | English |
Language for generation. Should match the input text. The model supports English, Chinese, Japanese, Korean, and others depending on training.
--batch-size
| Type | Default | Range |
|---|---|---|
int | 1 | 1–8 |
Chunks per GPU call. Default is 1 — tuned for 6 GB VRAM cards. Increase to 2–4 on 8 GB+ GPUs.
Unlike the Base script, this script does not have a sequential fallback if a batch fails. If the batch OOMs, you need to restart with a lower batch size.
--max-chars
| Type | Default | Range |
|---|---|---|
int | 150 | 50–400 |
Maximum characters per chunk. Tighter than the Base script (200) because the CustomVoice script targets low-VRAM GPUs. Smaller chunks generate fewer audio tokens per call, which is the biggest speed factor.
| Value | Effect |
|---|---|
| 100 | Fastest, but sentence fragmentation likely |
| 150 (default) | Good for 6 GB VRAM |
| 250 | Better prosody, more VRAM per chunk |
--dtype
| Type | Default | Choices |
|---|---|---|
str | bfloat16 | bfloat16, float16, float32 |
Model precision. Affects speed and VRAM usage:
| dtype | Speed vs float32 | Ampere+ | VRAM |
|---|---|---|---|
bfloat16 | ~2× faster | Yes (native) | Half |
float16 | ~1.8× faster | Yes | Half |
float32 | 1× | Yes | Full |
bfloat16 is recommended on Ampere GPUs (RTX 30xx) — it’s faster than float32 and avoids the underflow issues that float16 can have with denormalised numbers.
Behaviour details
Model loading
Uses a progressive attention backend loader:
for attn_impl in ("sdpa", "eager"):
try:
model = Qwen3TTSModel.from_pretrained(..., attn_implementation=attn_impl)
except:
continueThis allows the script to work on Windows (where flash-attn is unavailable) and systems without flash-attn installed. sdpa (PyTorch’s built-in scaled dot-product attention) is preferred — it’s significantly faster than eager. On Windows native the fallback may drop all the way to eager, making inference 2–6× slower than Linux. See the Windows performance note for details.
The model is loaded with:
local_files_only=True— no internet fallbacklow_cpu_mem_usage=True— minimise RAM overhead during loading
Text preprocessing
In addition to Markdown cleaning (links → text, stripping *_~# etc.), this script removes non-ASCII characters:
text = text.encode('ascii', 'ignore').decode('ascii')This prevents tokenizer crashes that commonly occur with emoji, CJK characters, mathematical symbols, and other non-ASCII glyphs in the Base script.
Adaptive token budget
Each chunk gets a custom max_new_tokens instead of a fixed budget (see Architecture for the formula). This is the single largest speed optimisation — short chunks don’t waste time generating padding tokens.
Sampling
Generation uses nucleus sampling with these hardcoded parameters:
do_sample=True
top_k=50
top_p=0.95
temperature=0.8These balance consistency (low temperature, narrow top-k) with natural variation (top_p 0.95 allows some diversity).
Timing
The script prints per-chunk timing breakdown at the end:
Done in 24.3s | avg 2.4s/chunk | 10 chunks synthesised
This helps diagnose whether a specific chunk is slow (e.g., because its token budget hit 1024).
See Preset Voices Guide for usage examples.