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

TypeDefault
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

TypeDefault
stroutput_custom.wav

Output WAV file path. 24 kHz mono 32-bit float. Overwrites existing files without warning.

--speaker

TypeDefault
strAiden

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

TypeDefault
strSpeak 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

TypeDefault
strEnglish

Language for generation. Should match the input text. The model supports English, Chinese, Japanese, Korean, and others depending on training.

--batch-size

TypeDefaultRange
int11–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

TypeDefaultRange
int15050–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.

ValueEffect
100Fastest, but sentence fragmentation likely
150 (default)Good for 6 GB VRAM
250Better prosody, more VRAM per chunk

--dtype

TypeDefaultChoices
strbfloat16bfloat16, float16, float32

Model precision. Affects speed and VRAM usage:

dtypeSpeed vs float32Ampere+VRAM
bfloat16~2× fasterYes (native)Half
float16~1.8× fasterYesHalf
float32YesFull

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:
        continue

This 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 fallback
  • low_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.8

These 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.