api: derived codec left context, chunk width hoisted to qt_init

The left context of the buffered chunked decode is no longer a caller
knob: it derives from the codec's own sliding window (2x144 frames),
placing the default decode at the residual floor of the split.
codec_chunk_sec moves from qt_tts_params to qt_init_params, resolved
once to frames at load. The mid-struct removal bumps the ABI to a
closed range [QT_ABI_MIN_VERSION, QT_ABI_VERSION] = [4, 4]; the probe
asserts both bounds reject through the range check.
This commit is contained in:
Pascal
2026-07-25 18:56:28 +02:00
parent 710a52af75
commit d03ffb97f9
8 changed files with 240 additions and 180 deletions
+41 -31
View File
@@ -44,20 +44,28 @@ extern "C" {
# define QT_API
#endif
// Struct ABI version. Incremented every time a public POD struct grows a
// new field at the end. Callers fill `.abi_version = QT_ABI_VERSION`
// (or let qwen_*_default_params set it). Entries that consume those
// structs reject inputs whose abi_version exceeds the build-time
// constant: this guards a binary built against vN from receiving a
// struct laid out for vN+1 by a freshly compiled binding. Adding fields
// stays backward compat because the new tail is zero init in older
// callers and the lib reads only what its abi_version permits.
// Struct ABI version. Incremented every time a public POD struct
// changes layout. Callers fill `.abi_version = QT_ABI_VERSION` (or let
// qwen_*_default_params set it). Entries that consume those structs
// accept the closed range [QT_ABI_MIN_VERSION, QT_ABI_VERSION] and
// reject anything outside it with a diagnostic rather than reading
// fields at offsets the caller never wrote: above the ceiling the
// struct comes from a newer header, below the floor it carries a
// layout this build no longer addresses. Fields appended at the tail
// keep older callers valid down to the floor, since their unwritten
// tail is zero init and the lib gates on abi_version before reading it.
//
// There is no separate semver triple. The runtime build identity is the
// git short hash + commit date string returned by qt_version(); for
// binding compat checks, QT_ABI_VERSION is the only number that
// matters.
#define QT_ABI_VERSION 3
#define QT_ABI_VERSION 4
// Oldest struct layout this build addresses. A v3 or older
// qt_tts_params places its trailing fields at offsets this build does
// not map, so such a struct is unreadable here and its caller rebuilds
// against this header.
#define QT_ABI_MIN_VERSION 4
// Returns a static string of the form "<git-hash> (<date>)" identifying
// the exact commit this binary was built from. Safe to call from any
@@ -134,10 +142,27 @@ struct qt_init_params {
// back into the qwen_* API. qt_synthesize itself stays blocking
// and thread safe in both modes.
int max_batch;
// ABI v4. Chunk width of the buffered codec decode, in seconds of
// audio, resolved to an integer frame count at the codec frame rate
// by qt_init and applied to every synthesis on the handle. A chunk
// shorter than the utterance bounds the peak decode memory; the
// decode window is that chunk plus the left context the decoder
// needs to start warm, which qt_init derives from the codec's own
// sliding window rather than taking from the caller. Peak memory
// therefore floors at that warmup, and driving the chunk below it
// buys no memory while costing one redecode of the context per
// chunk. A chunk covering the whole utterance decodes in a single
// pass and is the exact reference; any split leaves a residual on
// the order of -50 dB. 0 selects the upstream default, 24.0 (300
// frames at 12.5 Hz). The streaming path frames its own chunks
// through the persistent codec stream state and reads none of this.
float codec_chunk_sec;
};
// Initialise to the standard defaults: both paths NULL (caller must set
// them before calling qt_init), use_fa true, clamp_fp16 false.
// them before calling qt_init), use_fa true, clamp_fp16 false,
// max_batch 1, codec_chunk_sec 24.0.
QT_API void qt_init_default_params(struct qt_init_params * p);
// Allocate every module described by params. Returns NULL on any
@@ -203,10 +228,11 @@ typedef bool (*qt_cancel_cb)(void * user_data);
// 24 kHz; valid only for the duration of the call.
// user_data is forwarded verbatim from on_chunk_user_data.
//
// The chunk granularity is driven by chunk_duration_sec in qt_tts_params:
// once the AR loop has produced enough frames to cover that duration,
// the codec decodes that bundle and emits it. The last chunk on EOS /
// max_new flushes whatever frames remain.
// The chunk granularity is a ramp over the persistent codec stream
// state: the first flush covers a single 12.5 Hz frame for the lowest
// time to first audio, then the target width doubles up to 8 frames as
// the stream settles. The last chunk on EOS / max_new flushes whatever
// frames remain.
typedef bool (*qt_audio_chunk_cb)(const float * samples, int n_samples, void * user_data);
// Log severity. Numerically ordered so a callback can filter with a
@@ -311,21 +337,6 @@ struct qt_tts_params {
qt_audio_chunk_cb on_chunk;
void * on_chunk_user_data;
// Codec decode framing. Applied to both the streaming path (chunk
// by chunk emission) and the buffered path (one shot decode at the
// end) : the chunked decode rolls a left context window across the
// codec frames to avoid edge artefacts at chunk boundaries. The
// first chunk has its left context collapsed to whatever is
// available, matching the upstream Qwen3-TTS 12 Hz tokenizer
// chunked_decode rule. Defaults match the upstream reference :
// codec_chunk_sec 24.0 (300 frames at 12.5 Hz) and
// codec_left_context_sec 2.0 (25 frames at 12.5 Hz). Values are
// converted internally to integer frame counts via the codec frame
// rate ; codec_chunk_sec clamps to >= 1 frame, codec_left_context_sec
// clamps to >= 0 frames.
float codec_chunk_sec;
float codec_left_context_sec;
// ABI v2. Pre-encoded voice reference, the latent counterpart of
// ref_audio_24k. ref_spk_emb is the speaker embedding produced by
// the speaker encoder (ref_spk_dim f32 values, must equal the
@@ -343,8 +354,7 @@ struct qt_tts_params {
// Initialise to the standard defaults. Strings NULL, seed -1,
// max_new_tokens 2048, do_sample true, temperature 0.9, top_k 50,
// top_p 1.0, repetition_penalty 1.05, subtalker mirrors talker,
// dump_dir NULL, cancel NULL, on_chunk NULL, codec_chunk_sec 24.0,
// codec_left_context_sec 2.0.
// dump_dir NULL, cancel NULL, on_chunk NULL.
QT_API void qt_tts_default_params(struct qt_tts_params * p);
// Number of RVQ codebooks (K) of the loaded codec. Pre-encoded ICL