Programming Guide: Text-to-Speech with Chatterbox TTS using GGML/llama.cpp.#
Overview#
The NVIDIA In-Game Inferencing (NVIGI) Chatterbox TTS plugin provides high-quality text-to-speech using GGML/llama.cpp. The plugin supports multiple backends (CUDA, Vulkan, D3D12) and integrates with NVIGI’s plugin architecture for easy application integration.
Key features:
Multi-backend support: CUDA, Vulkan or D3D12
Two model types: Chatterbox Turbo (English, faster, no CFG) and Chatterbox Multilingual (23 languages, CFG-controlled pacing). Some features are exclusive to one model — see Model Feature Comparison.
Voice cloning: Voice cloning script to save speaker embeddings as JSON via a separate script.
Long-text handling: Built-in text chunking (word-based by default; character-based for
zh/jain the Multilingual model)CIG support: CUDA In Graphics for efficient GPU sharing for CUDA backend.
Paralinguistic tags (Turbo only): Support for
[clear throat],[sigh],[shush],[cough],[groan],[sniff],[gasp],[chuckle],[laugh]. The Multilingual model’s tokenizer does not include these tags — they are silently dropped or split into letters and will not produce the intended non-speech sound.Languages supported: English (Turbo); Arabic, Danish, German, Greek, English, Spanish, Finnish, French, Hebrew, Hindi, Italian, Japanese, Korean, Malay, Dutch, Norwegian, Polish, Portuguese, Russian, Swedish, Swahili, Turkish, Chinese (Multilingual)
Model Feature Comparison#
Many features behave differently — or only exist — for one of the two model types. The table below is the single source of truth for the rest of this document; per-feature sections below repeat the same restrictions where they apply.
Feature |
Turbo ( |
Multilingual ( |
|---|---|---|
Languages |
English only |
23 languages (ar, da, de, el, en, es, fi, fr, he, hi, it, ja, ko, ms, nl, no, pl, pt, ru, sv, sw, tr, zh) |
|
Ignored (always English) |
Required — the model uses a |
|
Not used — value is ignored, internally forced to 0 |
Always on, default 0.5; tune at runtime per utterance |
Paralinguistic tags |
Supported — emitted in the trained voice |
Not supported — tags are not in the multilingual vocabulary; do not use |
Emotion |
Ignored — bake-in is no-op for the Turbo speaker JSON |
Applied — captured in the speaker embedding |
Speaker embedding JSON |
Must be extracted with |
Must be extracted with |
Language-specific preprocessing (lowercase, NFKD, Korean Jamo, Chinese Cangjie, Japanese Kanji→Hiragana, Hebrew diacritization) |
N/A |
Applied automatically based on |
Text chunking ( |
Word-based, default 40 |
Word-based by default; switches to character-based (30 chars per chunk) for |
Hallucination protection (EOS-rank alignment analyzer) |
Not used |
Active — suppresses early EOS and forces termination on degenerate / runaway outputs |
S3Gen decoder |
Single-step Meanflow (1 ODE step) |
Standard 5-step ODE |
LLaMA backbone |
GPT-2 ( |
LLaMA 520M ( |
Text tokenizer |
GPT-2 byte-level BPE (50,276 vocab) |
Standard BPE with explicit |
Model GUID |
|
|
Required model files |
|
|
Recommended |
≥ 2,048 MB |
≥ 3,584 MB |
Output audio |
24 kHz, 16-bit PCM mono |
24 kHz, 16-bit PCM mono |
Available Plugins#
Plugin |
Backend |
Plugin ID |
Use Case |
|---|---|---|---|
|
GGML CUDA |
|
NVIDIA GPUs with CUDA, best performance |
|
GGML Vulkan |
|
Any Vulkan-capable GPU |
|
GGML D3D12 |
|
Windows D3D12-capable GPU |
Key Concepts#
Output Audio Format#
The plugin generates 24 KHz output audio.
Tips for Good Generation#
Paralinguistic tags work for the Turbo model only. When using
eTurbo, include tags such as[laugh],[sigh]or[gasp]in surrounding text that conveys the corresponding emotion (e.g. surround[gasp]with text that conveys surprise or shock) for natural results. Do not use paralinguistic tags with the Multilingual model — they are not in its tokenizer vocabulary and will either be dropped or split into individual letters that read aloud as nonsense.For voice cloning, we recommend at least 5 seconds of audio for optimal performance, and no more than 10 seconds as .wav file.
For the Multilingual model, always set
language_idto one of the 23 supported ISO codes; an empty / null value falls back to no language token, which can produce wrong-language pronunciation.
Known Limitations: Input Text Quality#
The Chatterbox model produces the best results with grammatically complete sentences and standard punctuation. The following input patterns may result in reduced quality or garbled audio:
Incomplete words or sentence fragments such as “the”, “that”, “hundred”, or “a” on their own. The model may not recognize these as complete utterances and can generate extra, unintelligible audio after the spoken word.
Very short utterances of fewer than ~5 words such as
"Yes","Help!", or"Over here!"— common in game dialogue, but more likely to produce audio artifacts because the model has too little context to anchor prosody and end-of-utterance detection. See Mitigations for short game dialogue below.Repeated or excessive punctuation such as
Hello!!!,Really?!, orWait...may produce incorrect audio.
Recommendations:
Always provide complete, grammatically correct sentences with proper punctuation.
Use single punctuation marks at the end of sentences (e.g.,
"Hello!"instead of"Hello!!!").For best results, provide sentences of moderate to long length (>5 words). Very short inputs (1–2 words) are more likely to produce artifacts.
Mitigations for short game dialogue#
Action lines like "Yes", "Help!", or "Over here!" are unavoidable in interactive dialogue. To keep them reliable:
Pad with a complete frame. Send
"Yes, I will."or"Yes, sir."instead of bare"Yes."— the surrounding words give the model enough context to terminate cleanly. The extra word is cheap and rarely audible as filler in game audio.Use a paralinguistic anchor. Tags like
[laugh],[sigh], or[gasp]placed before or after a short line stabilise the prosody, e.g."[gasp] Help!".Pre-render canned lines. For a fixed roster of short barks (
"Reloading.","Take cover!", etc.), generate the WAVs once at build time or first-launch and play them back as ordinary audio assets — short utterances don’t need to be re-synthesised every time the line plays.Concatenate consecutive short lines. If two short lines from the same speaker fire back-to-back (
"Yes.""On it."), send them as one utterance ("Yes, on it.") so the model has more material to work with.
Data Slots#
Data slots are the named bindings that NVIGI uses to pass inputs and outputs to an inference call.
For Chatterbox TTS, you provide the prompt under the "text" slot and receive audio from the "audio" slot.
Slot Name |
Constant |
Direction |
Type |
Description |
|---|---|---|---|---|
Prompt |
|
Input |
|
Input text to speak |
Generated Audio |
|
Output |
|
Output PCM audio (24 kHz) |
Model#
Two model types are available. See Model Feature Comparison for the full per-feature breakdown.
Turbo (
eTurbo): English-only, ~2× faster than Multilingual; supports paralinguistic tags; ignorescfg_weightandlanguage_id.Files:
t3_turbo_text_emb.gguf,S3Gen-Meanflow-266M-F32.gguf,turbo_gpt2_backbone_v2.ggufGUID:
{019BD494-0D97-7223-B9D5-C9286933B8B7}
Multilingual (
eMultilingual): 23-language model with always-on CFG, language-specific preprocessing, and a built-in alignment analyzer that suppresses early EOS / runaway generation.Files:
multilingual_t3_text_emb.gguf,S3Gen-Multilingual-F16.gguf,multilingual_chatterbox_llama_backbone_tts_q4_0.ggufAuxiliary preprocessing assets auto-discovered from the model directory:
cangjie_mapping.json(Chinese),kanji_readings.json(Japanese),dicta-bert-q8.gguf+dicta-bert-q8-heads.gguf(Hebrew diacritization). The plugin probes for these files atcreateInstancetime; missing files only disable the corresponding language’s preprocessing — they do not fail instance creation.Supported languages: ar, da, de, el, en, es, fi, fr, he, hi, it, ja, ko, ms, nl, no, pl, pt, ru, sv, sw, tr, zh
language_idin runtime parameters is required (e.g."en","fr","zh"). When set, the plugin prepends a[xx]language token, applies lowercase + NFKD normalization, and runs language-specific transforms (Korean Jamo decomposition forko, Cangjie encoding forzh, Kanji→Hiragana conversion forja, DictaBERT diacritization forhe). Other languages pass through with only the lowercase/NFKD normalization.Does not support paralinguistic tags — the multilingual tokenizer does not contain
[laugh],[sigh], etc. Sending such tags produces no expressive effect.
Select the model type during instance creation using TTSChatterboxCreationParameters::modelType.
Speaker Embeddings#
The runtime requires a speaker embedding JSON file that contains speaker conditioning data. Provide it through:
TTSChatterboxRuntimeParameters::speaker_json_path
Sample embeddings are included in data/nvigi.test/spk_emb/:
Turbo (English):
aaron_turbo.json,lucy_turbo.jsonMultilingual (per-language):
arabic.json,chinese.json,dutch.json,english.json,finnish.json,french.json,german.json,greek.json,hebrew.json,hindi.json,italian.json,japanese.json,korean.json,polish.json,portuguese.json,russian.json,spanish.json,turkish.jsonAdditional multilingual voices:
ethan_multilingual.json,meera_multilingual.json,speaker_base.json
They can also be created via voice cloning script for other speakers. Multilingual embeddings must be produced with --model-type multilingual so the stored cond_emb matches what the multilingual T3 consumes.
Speech Pacing (CFG Weight) — Multilingual only#
The cfg_weight parameter controls speech pacing for the Multilingual model via Classifier-Free Guidance:
Lower values (0.3–0.5): Slower, more natural speech with pauses and prosody
Higher values (1.0–3.0): Faster, more text-aligned speech
Default:
0.5(matches PyTorch reference)Only applies to the Multilingual model (
eMultilingual). The Turbo model does not use CFG: the plugin forcescfg_weightto 0 internally, and any value passed in is silently ignored.
Set via TTSChatterboxRuntimeParameters::cfg_weight.
Text Chunking#
Long text is split into chunks for smoother output:
Controlled by
TTSChatterboxRuntimeParameters::maxWordsPerChunkDefault:
40Set to
0to disable chunking
Chunking uses punctuation-aware splitting and adds small fade-in trims to reduce audio artifacts when stitching.
For the Multilingual model with language_id set to "zh" or "ja", the plugin automatically switches to character-based chunking (~30 characters per chunk) because CJK text has no whitespace between words. The maxWordsPerChunk value still gates whether chunking is enabled (> 0) but its numeric magnitude is not used for these languages.
Hallucination Protection — Multilingual only#
The Multilingual model includes an alignment analyzer that monitors the EOS token’s logit rank during generation and steers the sampler to prevent two failure modes that arise from the larger, more permissive vocabulary:
Premature stop: EOS is suppressed for at least 8 generation steps so very short prompts cannot collapse to silence.
Runaway generation: when EOS is consistently in the top-5 logits for 3 sustained steps, EOS is forced so the model terminates instead of looping.
There is no API knob for this — it is always on when modelType == eMultilingual and inactive when modelType == eTurbo. The Turbo model uses simple eos_token_id-based termination because its smaller GPT-2 vocabulary makes runaway generation less likely.
Backends and Graphics Integration#
Backend options: cuda, vulkan, d3d12
CUDA backend: Supports CUDA In Graphics (CIG) for efficient GPU sharing with graphics.
Vulkan backend: Uses GGML’s Vulkan context for GPU inference.
Note: When attempting to share the Vulkan device between the graphics pipeline and the Chatterbox TTS plugin, the generated output may be incorrect. The plugin creates its own separate Vulkan device (without sharing) to ensure correct output.
D3D12 backend: Uses GGML’s D3D12 compute shaders for GPU inference on Windows. Requires providing a D3D12 device, command queues, and memory allocation callbacks via
D3D12Parameters.Note: First inference may take approximately 6 seconds. Changing speaker embeddings may add approximately 1 second delay to the first inference after the change. Application developers should handle these delays appropriately.
Scheduling Mode Options#
When sharing the GPU between graphics and TTS inference, choose a scheduling mode based on your application’s priorities:
Mode |
When to Use |
Trade-off |
|---|---|---|
Prioritize Graphics |
Frame rate is critical and higher TTS latency can be tolerated (e.g., visually intensive scenes with background narration) |
Smooth rendering, slower TTS response |
Prioritize Inference |
Low TTS latency is critical and some frame drops can be tolerated (e.g., real-time conversational AI where responsiveness matters most) |
Fast TTS response, potential frame drops |
Balanced |
Good default for most applications |
Reasonable frame rates and TTS latency without heavily favoring either workload |
Getting Started#
Important Note: The code examples in this guide are illustrative and may not run as-is. They are designed to show the API structure and usage patterns. For complete, working implementations, please refer to:
Standalone CLI TTS:
source/samples/nvigi.tts.chatterbox/sample_tts_chatterbox.cppGraphics Integration:
source/samples/nvigi.3d/src/nvigi/NVIGIContext.cpp
Step 1: Initialize NVIGI#
#include <nvigi.h>
#include <nvigi_ai.h> // ITextToSpeech, InferenceExecutionContext, kInferenceFlagDataPending, ...
#include "nvigi_tts_chatterbox.h"
#ifndef NVIGI_WINDOWS
#include <dlfcn.h>
#define LoadLibraryA(lib) dlopen(lib, RTLD_LAZY)
#define GetProcAddress dlsym
#define FreeLibrary dlclose
#endif
const char* sdkPath = "C:\\path\\to\\sdk"; // Path to SDK root directory (= bin\x64\<config> in the pack)
#ifdef NVIGI_WINDOWS
// One-time, process-wide DLL search-path setup for the host application.
//
// This is REQUIRED whenever the host EXE lives outside the package's
// bin\x64\<config> directory. Without it, NVIGI's own plugin discovery via
// `Preferences.utf8PathsToPlugins` (below) succeeds, but the plugin DLLs in
// turn dynamically load CUDA/cuBLAS/cuDNN/ggml at first inference and those
// loads use the *process-wide* DLL search order -- not the special flags we
// pass to LoadLibraryExA for the initial core framework load. If the EXE's
// directory and PATH don't already contain cublas64_12.dll, cublasLt64_12.dll,
// etc., the first inference crashes with `STATUS_DLL_NOT_FOUND`.
//
// Three equivalent options; pick one. (1) is recommended for shipping apps:
//
// (1) Programmatic, no env changes:
// Call SetDefaultDllDirectories(...) + AddDllDirectory(packageBinDir)
// once at startup, BEFORE any LoadLibrary* on NVIGI or its plugins.
// (2) Launch the EXE from inside bin\x64\<config> so it inherits a working
// current directory -- fragile, especially for double-click launches.
// (3) Prepend bin\x64\<config> to %PATH% in the parent shell -- works but
// leaks into the user environment and is brittle across configs.
SetDefaultDllDirectories(LOAD_LIBRARY_SEARCH_DEFAULT_DIRS |
LOAD_LIBRARY_SEARCH_USER_DIRS);
std::wstring packageBinDirW(/* widened sdkPath */);
AddDllDirectory(packageBinDirW.c_str());
#endif
// Load the NVIGI core library from SDK path.
// LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR is required so Windows resolves the core
// library's own dependencies (CUDA, HWI, amd_ags_x64.dll) from the same
// directory as nvigi.core.framework.dll rather than only the PATH.
std::string coreLibPath = std::string(sdkPath) + "\\nvigi.core.framework.dll";
HMODULE coreLib = LoadLibraryExA(
coreLibPath.c_str(), NULL,
LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR | LOAD_LIBRARY_SEARCH_DEFAULT_DIRS);
// Get function pointers
auto nvigiInit = (PFun_nvigiInit*)GetProcAddress(coreLib, "nvigiInit");
auto nvigiShutdown = (PFun_nvigiShutdown*)GetProcAddress(coreLib, "nvigiShutdown");
auto nvigiLoadInterface = (PFun_nvigiLoadInterface*)GetProcAddress(coreLib, "nvigiLoadInterface");
auto nvigiUnloadInterface = (PFun_nvigiUnloadInterface*)GetProcAddress(coreLib, "nvigiUnloadInterface");
// Set up preferences
const char* pluginPaths[] = { sdkPath };
nvigi::Preferences pref{};
pref.logLevel = nvigi::LogLevel::eDefault; // Options: eOff, eDefault, eVerbose, eCount
pref.showConsole = false;
pref.numPathsToPlugins = 1;
pref.utf8PathsToPlugins = pluginPaths;
pref.utf8PathToLogsAndData = sdkPath;
// Initialize
nvigi::Result result{};
if (NVIGI_FAILED(result, nvigiInit(pref, nullptr, nvigi::kSDKVersion)))
{
// Handle error
}
Step 2: Load the TTS Interface#
Choose a backend plugin ID:
nvigi::PluginID pluginId = nvigi::plugin::tts::chatterbox::cuda::kId; // CUDA
// nvigi::PluginID pluginId = nvigi::plugin::tts::chatterbox::vulkan::kId; // Vulkan
// nvigi::PluginID pluginId = nvigi::plugin::tts::chatterbox::d3d12::kId; // D3D12 (Windows only)
nvigi::ITextToSpeech* itts{};
if (NVIGI_FAILED(result, nvigiGetInterfaceDynamic(pluginId, &itts, nvigiLoadInterface)))
{
// Handle error
}
Step 3: Create a TTS Instance#
nvigi::CommonCreationParameters common{};
common.utf8PathToModels = "C:\\path\\to\\models";
// Turbo: "{019BD494-0D97-7223-B9D5-C9286933B8B7}"
// Multilingual: "{A60EB5CF-9551-4B86-865B-CDC3CDBE61C4}"
common.modelGUID = "{019BD494-0D97-7223-B9D5-C9286933B8B7}";
common.vramBudgetMB = 2048; // Maximum VRAM the plugin can use (will error if plugin needs more)
nvigi::TTSChatterboxCreationParameters ttsParams{};
ttsParams.modelType = nvigi::TTSChatterboxModelType::eTurbo; // or eMultilingual
ttsParams.chain(common);
nvigi::InferenceInstance* instance{};
if (NVIGI_FAILED(result, itts->createInstance(ttsParams, &instance)))
{
// Handle error
}
D3D12 Backend Setup#
When using the D3D12 backend, you must create a D3D12 device and command queues, then pass them
via D3D12Parameters chained to the creation parameters. You also need to provide memory allocation
callbacks and export the D3D12 Agility SDK version:
#include <nvigi_d3d12.h>
#include <d3d12.h>
#include <dxgi1_6.h>
#include <wrl/client.h>
// D3D12 Agility SDK exports (required at global scope)
extern "C" __declspec(dllexport) UINT D3D12SDKVersion = 615;
extern "C" __declspec(dllexport) const char* D3D12SDKPath = ".\\D3D12\\";
// Create D3D12 device and queues
Microsoft::WRL::ComPtr<ID3D12Device> d3d12Device;
Microsoft::WRL::ComPtr<ID3D12CommandQueue> directQueue, computeQueue, copyQueue;
// ... (create device via D3D12CreateDevice, create queues via CreateCommandQueue) ...
// Memory allocation callbacks
ID3D12Resource* myCreateCommittedResource(
ID3D12Device* device, const D3D12_HEAP_PROPERTIES* pHeapProperties,
D3D12_HEAP_FLAGS HeapFlags, const D3D12_RESOURCE_DESC* pDesc,
D3D12_RESOURCE_STATES InitialResourceState, const D3D12_CLEAR_VALUE* pOptimizedClearValue,
void* userContext)
{
ID3D12Resource* resource = nullptr;
device->CreateCommittedResource(pHeapProperties, HeapFlags, pDesc,
InitialResourceState, pOptimizedClearValue, IID_PPV_ARGS(&resource));
return resource;
}
void myDestroyResource(ID3D12Resource* pResource, void* userContext)
{
if (pResource) pResource->Release();
}
// Chain D3D12Parameters into creation parameters
nvigi::D3D12Parameters d3d12Params{};
d3d12Params.device = d3d12Device.Get();
d3d12Params.queue = directQueue.Get(); // Direct (graphics) queue
d3d12Params.queueCompute = computeQueue.Get(); // Compute queue
d3d12Params.queueCopy = copyQueue.Get(); // Copy queue
d3d12Params.createCommittedResourceCallback = myCreateCommittedResource;
d3d12Params.destroyResourceCallback = myDestroyResource;
ttsParams.chain(d3d12Params);
// Then call createInstance as usual
itts->createInstance(ttsParams, &instance);
Note: The D3D12 device and queues must remain valid for the lifetime of the TTS instance. See
source/samples/nvigi.tts.chatterbox/sample_tts_chatterbox.cppfor a complete working example.D3D12 Backend Performance Notes:
First inference may take approximately 6 seconds. Application developers should handle this delay appropriately.
Changing speaker embeddings may add approximately 1 second delay to the first inference after the change. Application developers should handle this delay appropriately.
Step 4: Run Inference#
// Input text
nvigi::CpuData textBuffer{};
const char* text = "Hello from Chatterbox Turbo TTS.";
textBuffer.buffer = (void*)text;
textBuffer.sizeInBytes = (uint64_t)strlen(text) + 1;
nvigi::InferenceDataText prompt{textBuffer};
std::vector<nvigi::InferenceDataSlot> inputSlots = {
{ nvigi::kTTSDataSlotPrompt, prompt }
};
nvigi::InferenceDataSlotArray inputs{ inputSlots.size(), inputSlots.data() };
// Runtime parameters
nvigi::TTSChatterboxRuntimeParameters runtime{};
runtime.speaker_json_path = "C:\\path\\to\\speaker.json";
runtime.max_new_tokens = 768;
runtime.temperature = 0.8f;
runtime.cfg_weight = 0.5f; // Speech pacing: 0.3-0.5 = natural, 1.0-3.0 = faster (multilingual only)
// Streaming callback to receive results.
//
// The TTS plugin emits audio in chunks. For multi-chunk utterances you must
// drain `kInferenceExecutionStateDataPending` callbacks AS THEY ARRIVE -- the
// buffer pointed to by `audio->audio` is reused for the next chunk, so
// `Done` alone will only give you the final chunk's audio. Apps that only
// handle `Done` (the original snippet style) work for very short inputs
// that fit in a single chunk but silently truncate longer ones.
auto callback = [](const nvigi::InferenceExecutionContext* ctx,
nvigi::InferenceExecutionState state,
void* userData)
{
// Process audio on both DataPending (intermediate chunk) and Done (last chunk).
if ((state == nvigi::kInferenceExecutionStateDataPending ||
state == nvigi::kInferenceExecutionStateDone) && ctx && ctx->outputs)
{
const nvigi::InferenceDataAudio* audio{};
ctx->outputs->findAndValidateSlot(nvigi::kTTSDataSlotGeneratedAudio, &audio);
if (audio)
{
// Copy audio->audio (CpuData) to your own buffer / queue NOW;
// do NOT retain the pointer across the callback return -- the
// plugin may reuse the storage for the next chunk.
const bool isLast = (state == nvigi::kInferenceExecutionStateDone);
// appendPcm(audio->audio.buffer, audio->audio.sizeInBytes, isLast);
}
}
return state;
};
nvigi::InferenceExecutionContext execCtx{};
execCtx.instance = instance;
execCtx.inputs = &inputs;
execCtx.outputs = nullptr; // Let the plugin provide output slots for the callback
execCtx.runtimeParameters = runtime;
execCtx.callback = callback;
execCtx.callbackUserData = nullptr;
if (NVIGI_FAILED(result, instance->evaluate(&execCtx)))
{
// Handle error
}
Step 5: Cleanup#
itts->destroyInstance(instance);
nvigiUnloadInterface(pluginId, itts);
nvigiShutdown();
FreeLibrary(coreLib);
API Reference#
TTSChatterboxCreationParameters#
Field |
Type |
Default |
Description |
|---|---|---|---|
|
|
|
|
TTSChatterboxRuntimeParameters#
Field |
Type |
Default |
Description |
|---|---|---|---|
|
|
|
Max tokens generated by the model |
|
|
|
GPU device index |
|
|
|
GPU layers for GGML/llama.cpp |
|
|
|
Sampling temperature |
|
|
|
Path to speaker embeddings JSON (required). Must match the runtime |
|
|
|
Words per chunk, |
|
|
|
Model type (must match creation params) |
|
|
|
Multilingual: required. ISO language code (e.g. |
|
|
|
CFG weight for speech pacing (v2). Lower = slower/natural, higher = faster. Multilingual only — ignored by Turbo. |
TTSChatterboxCapabilitiesAndRequirements#
Field |
Type |
Description |
|---|---|---|
|
|
Standard model list and budgets |
|
|
Language strings from model configs |
|
|
Model types from configs |
|
|
Number of entries in supportedLanguages array |
|
|
Number of entries in modelTypes array |
Error Handling#
Always check return values with NVIGI_FAILED. Common errors:
Invalid model path or GUID: Verify
utf8PathToModelsandmodelGUIDVRAM budget too small: Increase
vramBudgetMBMissing speaker JSON:
speaker_json_pathis requiredBackend mismatch: Ensure CUDA/Vulkan/D3D12 plugin matches your hardware
Missing D3D12 parameters: D3D12 backend requires
D3D12Parameterswith a valid device and queues
Best Practices#
Set model type at creation: Use
TTSChatterboxCreationParameters::modelTypeReuse instances: Avoid recreating per request
Provide a matching speaker JSON: Turbo speakers (e.g.
aaron_turbo.json) only witheTurbo; multilingual speakers only witheMultilingual. The plugin rejects mismatches withkResultInvalidParameteratcreateInstancetime when the JSON has amodel_typetag, or warns at runtime if the tag is missing.Chunk long text: Keep
maxWordsPerChunkaround 40 for longer prompts. For Multilingualzh/ja, character-based chunking is used automatically.Use CUDA when available: Best performance on NVIDIA GPUs
Tune
cfg_weightfor your use case (Multilingual only): Use 0.3–0.5 for natural narration, 1.0+ for fast UI prompts. Can be changed per-utterance without reinitializing. Has no effect on Turbo.Use paralinguistic tags only on Turbo: Tags like
[laugh],[sigh],[gasp]are part of the Turbo vocabulary and produce in-voice non-speech sounds. The Multilingual model’s vocabulary does not contain them — sending such tags produces no expressive effect and may degrade prosody.Always set
language_idon Multilingual: Pass an explicit ISO code ("en","fr","zh", etc.). An empty / null value skips the[xx]language token and language-specific preprocessing, which can produce wrong-language pronunciation.
Performance Benchmarks#
Benchmarking Methodology#
The benchmarks below use the Chatterbox Turbo model with a representative set of 31 sentences ranging from 14 to 50 words, covering a variety of conversational styles and lengths. All tests were conducted on:
GPU: NVIDIA GeForce RTX 4090
Driver: 591.86
Key metrics:
RTF (Real-Time Factor): Ratio of inference time to generated audio duration. RTF < 1 means faster than real-time. Lower is better.
Speed-up: Equal to 1/RTF, showing how many times faster than real-time the system generates audio.
TTFA (Time to First Audio): Latency from inference start until the first audio chunk is available for playback (3D sample only).
Backend Comparison#
CLI Sample Performance#
Standalone inference without any graphics workload:
Backend |
Avg Inference Time |
Avg Audio Duration |
Avg RTF |
Speed-up |
|---|---|---|---|---|
CUDA |
564 ms |
9.5 s |
0.061 |
16.4x |
Vulkan |
626 ms |
9.5 s |
0.066 |
15.2x |
D3D12 |
1,525 ms |
9.5 s |
0.164 |
6.1x |
3D Sample Performance#
Inference running alongside a 3D rendering workload:
Backend |
Avg Inference Time |
Avg RTF |
Speed-up |
|---|---|---|---|
CUDA |
1,281 ms |
0.135 |
7.4x |
D3D12 |
1,414 ms |
0.147 |
6.8x |
Vulkan |
1,653 ms |
0.175 |
5.7x |
Note: 3D sample RTFs are higher than CLI because the GPU is shared between rendering and inference. The CUDA backend shows the largest difference between CLI and 3D modes because CIG scheduling introduces overhead to balance both workloads. D3D12 shows the smallest gap as it natively shares the GPU via D3D12 command queues.
Latency by Audio Duration (CLI)#
Average inference time grouped by generated audio duration:
Audio Duration |
CUDA (ms) |
Vulkan (ms) |
D3D12 (ms) |
|---|---|---|---|
< 7 s |
426 |
411 |
951 |
7–9 s |
522 |
556 |
1,362 |
9–11 s |
597 |
688 |
1,664 |
11–13 s |
613 |
698 |
1,716 |
13 s+ |
615 |
686 |
1,685 |
CUDA and Vulkan inference times remain relatively flat as audio duration increases, producing better (lower) RTF for longer outputs. D3D12 shows more variation but also benefits from longer outputs.
Time to First Audio (3D Sample)#
TTFA measures how quickly the first audio chunk is available for playback, which directly affects perceived responsiveness in interactive applications:
Backend |
TTFA (ms) |
|---|---|
CUDA |
~970 |
D3D12 |
~1,200 |
Vulkan |
~1,140 |
Measured during sequential 3D sample inference with sentences of 41–50 words.
VRAM Usage#
Per-backend peak VRAM recorded during sequential cli sample inference of 9–25 s of audio:
Backend |
VRAM Usage |
|---|---|
CUDA |
1,938 MB |
D3D12 |
1,691 MB |
Vulkan |
1,519 MB |
Set CommonCreationParameters::vramBudgetMB to at least 2,048 MB for Turbo and at least 3,584 MB for Multilingual. If the plugin requires more VRAM than the budget allows, instance creation will fail.
D3D12 Warmup Latency#
The D3D12 backend has additional first-run overhead:
Event |
Additional Latency |
|---|---|
First inference after plugin load |
~6 seconds (shader compilation) |
First inference after changing speaker embeddings |
~1 second |
Subsequent inferences run at steady-state speeds. These costs cannot be eliminated, but they can be moved off the user-visible critical path so the first piece of in-game dialogue still meets your latency target.
Mitigation: pre-warm during loading screens#
Run a one-off dummy inference (any short text, output discarded) after the plugin instance is created but before any user-facing audio is requested. The ~6 s shader-compilation cost is then paid on the loading screen instead of on the first in-game line.
If your application uses multiple speaker embeddings during gameplay, pre-warm each one on the loading screen as well to amortise the additional ~1 s per-speaker cost.
Other strategies for latency-sensitive applications#
Group dialogue by speaker. In interactive cutscenes or scripted exchanges, schedule consecutive lines from the same speaker together so the per-switch ~1 s cost is paid less often.
Use one fixed voice per ambient context. Reserve dynamic speaker switching for scripted scenes that already absorb a small load time; let ambient NPCs share a single pre-warmed voice.
Maintain a small pool of pre-warmed instances. For highly dynamic dialogue with many concurrent voices, create N TTS instances at level-load time, each pre-warmed with one of the expected speakers, and dispatch lines to whichever instance is idle.
These strategies are additive — for most applications, the loading-screen warmup alone is sufficient.
Performance Recommendations#
CUDA is recommended for the best inference performance on NVIDIA GPUs (16.4x real-time in CLI)
Vulkan provides comparable CLI performance (15.2x) and is a good alternative for broader GPU compatibility
D3D12 is slower for standalone inference (6.1x) but shows the smallest performance gap in the 3D sample, making it a reasonable choice for D3D12-based applications
For interactive applications, target sentences of moderate length (20–40 words) for a good balance between natural-sounding output and consistent latency (~500–700 ms on CUDA)
Warmup: Run a dummy inference during application startup to avoid cold-start latency affecting the user experience
Working Code Examples#
For production-ready integration examples, see:
source/samples/nvigi.3d/src/nvigi/NVIGIContext.cpp(TTS integration and callbacks)source/samples/nvigi.tts.chatterbox/sample_tts_chatterbox.cpp(standalone CLI sample)
Example CLI usage:
nvigi.tts.chatterbox.exe --models C:\path\to\models --speaker C:\path\to\speaker.json --text "Hello from Chatterbox Turbo." --output output.wav --backend cuda --max_new_tokens 768 --gpu_device 0 --n_gpu_layers 99 --temperature 0.8 --cfg_weight 0.5 --max_words_per_chunk 40
Interactive mode (Windows only):
nvigi.tts.chatterbox.exe --models C:\path\to\models --speaker C:\path\to\speaker.json --interactive --backend cuda
Support#
For issues or feedback, refer to the NVIGI Developer Pack documentation or contact NVIDIA Developer Support.