Programming Guide: Automatic Speech Recognition with Riva ASR using ONNX Runtime#
1. Overview#
The NVIDIA In-Game Inferencing (NVIGI) ASR (Automatic Speech Recognition) Riva ONNX Runtime plugins provide high-performance speech-to-text capabilities for applications and games. These plugins leverage ONNX Runtime with optimized backends to deliver accurate transcription of audio input in real-time.
The Riva ASR plugins support:
Multilingual recognition: 8 languages – English, French, German, Italian, Spanish, Korean, Japanese, and Mandarin Chinese
Multiple backend options: CPU, TensorRT-RTX GPU, and DirectML (DML) for broad hardware compatibility
Streaming mode: Real-time transcription with partial results during recording
Offline mode: Process pre-recorded audio files
Word boosting: Emphasize or suppress specific words in recognition
Silence detection: Detect silence periods in streaming audio
Auto punctuation: Optional automatic punctuation insertion and capitalization
Flexible decoding: Greedy or beam search strategies
CIG support: CUDA In Graphics integration for efficient GPU sharing (D3D12/Vulkan, TensorRT-RTX only)
2. Available Plugins#
The SDK provides two ASR Riva ORT plugins:
Plugin |
Backend |
Plugin ID |
Use Case |
|---|---|---|---|
|
ONNX Runtime (CPU) |
|
CPU-only systems, development |
|
ONNX Runtime (TensorRT-RTX/DirectML) |
|
GPU-accelerated (TensorRT-RTX on NVIDIA GPUs, falls back to DirectML otherwise) |
3. Key Concepts#
3.1 Audio Requirements#
The Riva ASR plugins expect audio in the following format:
Sample rate: 16,000 Hz (16 kHz)
Bit depth: 16-bit PCM
Channels: Mono (single channel)
If your audio is in a different format, you must convert it before passing it to the plugin.
3.2 Operating Modes#
Offline Mode (Non-Streaming)#
Process entire audio files at once. Best for:
Post-processing of recorded audio
Batch transcription
Non-interactive applications
Streaming Mode#
Process audio in real-time as it arrives. Best for:
Live microphone input
Interactive conversations
Real-time captioning
Streaming mode provides:
Partial results: Incremental transcription as speech progresses
Lower latency: Results available before complete utterance finishes
Accumulated results: Complete transcription built up over time
3.3 Data Slots#
The ASR plugin uses named data slots for input and output:
Slot Name |
Constant |
Direction |
Type |
Description |
|---|---|---|---|---|
Audio |
|
Input |
|
Raw audio data (16kHz, 16-bit, mono) |
Transcribed Text |
|
Output |
|
Accumulated transcription with enough context for accuracy (offline: final result, streaming: accumulated so far) |
Partial Text |
|
Output |
|
Intermediate transcription of recent audio without enough context (streaming only, less accurate) |
Understanding Partial vs Transcribed Text in Streaming:
Transcribed Text (Accumulated): Uses enough past and future context for maximum accuracy. This is the high-quality transcription built up over time as the model receives sufficient context.
Partial Text: Real-time transcription of the most recent audio without enough context. Useful for immediate feedback but may be less accurate and can change as more context becomes available.
3.4 Benchmarking Methodology#
Throughout the programming guide, wherever possible, we have provided metrics that we hope are useful to the developer in making specific choices about the runtime and creation time parameters. For this purpose, we’ve chosen the Librispeech test-clean split dataset from https://www.openslr.org/12. We converted the .flac audio files to .wav files before running various tests. The clean test-split had 2620 audio samples.
The metrics below establish baseline performance characteristics across key dimensions (accuracy, and speed). These metrics are intended to inform your parameter selection and trade-off analysis. We acknowledge that specific applications may benefit from additional or alternative metrics tailored to their unique requirements.
Avg. WER |
Corpus WER |
Perfect WER % |
High WER % |
Avg RTF |
Speed up |
|---|
WER (Word Error Rate): The percentage of words incorrectly transcribed, calculated as
(insertions + deletions + substitutions) / total_reference_words.Avg. WER: The average of per-utterance WER values. Each audio sample’s WER is calculated independently, then averaged across all samples. This gives equal weight to each utterance regardless of length.
Corpus WER: The WER calculated over the entire corpus by summing all errors and dividing by total words across all samples. This is weighted by utterance length, so longer utterances have more impact on the final score. This is the standard metric used in ASR research papers.
Perfect WER %: Percentage of test instances where WER was exactly 0.0 (perfect transcription).
High WER % (>=50%): Percentage of test instances where WER was 50% or higher (significantly degraded transcription).
RTF (Real Time Factor): Ratio of processing time to audio length. 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 performs.
The GPU tests have been carried out on NVIDIA RTX 5090 Blackwell Generation GPU. CPU tests were conducted on Intel Core i9-14900K processor.
4. 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:
Streaming ASR:
source/samples/nvigi.asr.sample/sample_asr.cppGraphics Integration:
source/samples/nvigi.3d/src/nvigi/NVIGIContext.cpp
4.1 Initialize NVIGI#
Before using any ASR plugin, initialize the NVIGI framework:
#include <nvigi.h>
#include "nvigi_asr_riva.h"
#ifndef NVIGI_WINDOWS
#include <dlfcn.h>
#define LoadLibraryA(lib) dlopen(lib, RTLD_LAZY)
#define GetProcAddress dlsym
#define FreeLibrary dlclose
#endif
// Load the NVIGI core library from SDK path
const char* sdkPath = "C:\\path\\to\\sdk"; // Path to SDK root directory
std::string coreLibPath = std::string(sdkPath) + "\\nvigi.core.framework.dll";
HMODULE coreLib = LoadLibraryA(coreLibPath.c_str());
// 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::eOff; // Options: eOff, eDefault, eVerbose, eCount
pref.showConsole = false;
pref.numPathsToPlugins = 1;
pref.utf8PathsToPlugins = pluginPaths;
pref.utf8PathToLogsAndData = sdkPath;
// Initialize
if (NVIGI_FAILED(result, nvigiInit(pref, nullptr, nvigi::kSDKVersion)))
{
// Handle error
}
4.2 Load the ASR Interface#
Choose the appropriate backend (CPU or GPU):
// Select plugin ID based on your backend preference
nvigi::PluginID pluginId = nvigi::plugin::asr::riva_ort::cpu::kId; // CPU backend
// OR
nvigi::PluginID pluginId = nvigi::plugin::asr::riva_ort::gpu::kId; // GPU backend (TensorRT-RTX or DirectML)
// Load the interface
nvigi::IAutoSpeechRecognition* iasr{};
if (NVIGI_FAILED(result, nvigiGetInterfaceDynamic(pluginId, &iasr, nvigiLoadInterface)))
{
// Handle error
}
4.3 Create an ASR Instance#
Configure and create an instance with your desired settings:
// Common parameters
nvigi::CommonCreationParameters common{};
common.utf8PathToModels = "C:\\path\\to\\models";
common.modelGUID = "{B5D4F28E-3DB9-4FDA-A78B-A569AA22FCD0}"; // English Conformer-CTC (see Multilingual Support for other languages)
common.vramBudgetMB = 2048; // Maximum VRAM the plugin can use (will error if plugin needs more)
// ASR-specific parameters
nvigi::ASRRivaORTCreationParameters asrParams{};
asrParams.chain(common);
// Configure for offline mode (default)
asrParams.enableStreamingMode = false;
asrParams.enableAutoPunctuation = false;
// GPU backend options
asrParams.preferDML = false; // false = TensorRT-RTX if possible (default on NVIDIA GPUs), true = DirectML
// Create instance
nvigi::InferenceInstance* instance{};
if (NVIGI_FAILED(result, iasr->createInstance(asrParams, &instance)))
{
// Handle error
}
4.4 Run Inference (Offline Mode)#
Process a complete audio file:
// Define a callback to receive results
struct TranscriptionContext {
std::string transcribedText;
std::atomic<bool> audioPending{true};
};
nvigi::InferenceExecutionState transcriptionCallback(
const nvigi::InferenceExecutionContext* ctx,
nvigi::InferenceExecutionState state,
void* userData)
{
auto* myCtx = static_cast<TranscriptionContext*>(userData);
if (ctx && ctx->outputs)
{
// Get transcribed text from output slot
const nvigi::InferenceDataText* textOutput{};
if (ctx->outputs->findAndValidateSlot(nvigi::kASRDataSlotTranscribedText, &textOutput))
{
myCtx->transcribedText = textOutput->getUTF8Text();
}
}
if (state == nvigi::kInferenceExecutionStateDone)
{
myCtx->audioPending = false;
}
return state;
}
// Load your audio data (must be 16kHz, 16-bit, mono)
std::vector<int16_t> audioSamples = loadAudioFile("audio.wav");
// Set up audio data
nvigi::CpuData audioData{};
audioData.buffer = reinterpret_cast<uint8_t*>(audioSamples.data());
audioData.sizeInBytes = audioSamples.size() * sizeof(int16_t);
nvigi::InferenceDataAudio audio{audioData};
audio.bitsPerSample = 16;
audio.samplingRate = 16000;
audio.channels = 1;
// Create input slot
std::vector<nvigi::InferenceDataSlot> slots = {
{nvigi::kASRDataSlotAudio, audio}
};
nvigi::InferenceDataSlotArray inputs{slots.size(), slots.data()};
// Set up runtime parameters
nvigi::ASRRivaORTRuntimeParameters runtime{};
runtime.sampling = nvigi::ASRRivaSamplingStrategy::eBeamSearch; // or eGreedy for offline mode
runtime.streamingMode = false;
// Create callback context
TranscriptionContext ctx{};
// Create execution context
nvigi::InferenceExecutionContext execCtx{};
execCtx.instance = instance;
execCtx.inputs = &inputs;
execCtx.runtimeParameters = runtime;
execCtx.callback = transcriptionCallback;
execCtx.callbackUserData = &ctx;
// Run inference
if (NVIGI_FAILED(result, instance->evaluate(&execCtx)))
{
// Handle error
}
// Wait for completion
while (ctx.audioPending)
{
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
// Use the transcription result
std::cout << "Transcription: " << ctx.transcribedText << std::endl;
4.5 Cleanup#
Always clean up resources:
// Destroy instance
iasr->destroyInstance(instance);
// Unload interface
nvigiUnloadInterface(pluginId, iasr);
// Shutdown NVIGI
nvigiShutdown();
// Free library
FreeLibrary(coreLib);
5. Streaming Mode#
Streaming mode enables real-time transcription with partial results. This is ideal for live microphone input or interactive applications.
5.1 Configuring Streaming Mode#
Enable streaming when creating the instance:
nvigi::ASRRivaORTCreationParameters asrParams{};
asrParams.chain(common);
// Enable streaming mode
asrParams.enableStreamingMode = true;
// Streaming configuration (optional, these are defaults)
// Chunk size: Recommanded 380ms for Conformer models (default), Recommanded to 900ms for Parakeet models (needs more context)
asrParams.streamingChunkSizeMs = 380; // Process audio in chunks
asrParams.streamingBeamSize = 32; // Beam size for decoder
asrParams.streamingLmWeight = 0.8f; // Language model weight
asrParams.streamingWordScore = 1.0f; // Word insertion score
asrParams.streamingContextSizeLeftRightMs = 2500; // Context padding in milliseconds. Higher values delay accumulated text but partial text remains available.
5.2 Streaming Parameters Explained#
streamingChunkSizeMs: Size of audio chunks to process internally (milliseconds). Default: 380ms, 900ms recommended for Parakeet models (needs more context for optimal accuracy).streamingBeamSize: Beam search width. Larger values improve accuracy but increase computation. Default: 32.streamingLmWeight: Weight for language model scores. Range [0, 1]. Default: 0.8.streamingWordScore: Word insertion score. Helps control output length. Default: 1.0.streamingContextSizeLeftRightMs: Context padding size (left and right) in milliseconds for providing context to the acoustic model. Larger values provide more historical context but increase memory usage. Default: 2500ms.Important: Higher values delay when accumulated transcription results are finalized and sent, as the model waits for more context. However, partial transcription results (if
enablePartialDecodingis enabled) will still be available in real-time during this delay, allowing users to see preliminary results while the accumulated text is being processed.
5.3 Streaming Workflow#
Streaming uses a callback-based approach with three signals: START, DATA, and STOP.
1. Define a Callback Function#
// Context structure to track streaming results
struct StreamingContext {
std::string transcribedText; // Final accumulated text
std::string currentAccumulated; // Current accumulated text during streaming
std::string mostRecentPartial; // Most recent partial transcription
std::atomic<bool> audioPending{true};
int updateCount = 0; // Count of updates received
};
nvigi::InferenceExecutionState streamingCallback (
const nvigi::InferenceExecutionContext* ctx,
nvigi::InferenceExecutionState state,
void* userData)
{
auto* myCtx = static_cast<StreamingContext*>(userData);
if (ctx && ctx->outputs)
{
// Get accumulated text (high-quality, uses enough context)
const nvigi::InferenceDataText* accumulatedText{};
ctx->outputs->findAndValidateSlot(nvigi::kASRDataSlotTranscribedText, &accumulatedText);
std::string accumulated = accumulatedText ? accumulatedText->getUTF8Text() : "";
// Get partial text (real-time, recent audio only, less accurate)
const nvigi::InferenceDataText* partialText{};
bool hasPartial = ctx->outputs->findAndValidateSlot(nvigi::kASRDataSlotPartialText, &partialText);
std::string partial = hasPartial && partialText ? partialText->getUTF8Text() : "";
if (state == nvigi::kInferenceExecutionStateDataPartial)
{
// Partial result during streaming
// - 'accumulated': high-quality transcription with enough past/future context
// - 'partial': real-time preview of recent audio, may change with more context
myCtx->currentAccumulated = accumulated;
myCtx->mostRecentPartial = partial;
myCtx->updateCount++;
std::cout << "Update " << myCtx->updateCount
<< " - Accumulated: '" << accumulated << "'";
if (!partial.empty())
{
std::cout << ", Partial: '" << partial << "'";
}
std::cout << std::endl;
}
if (state == nvigi::kInferenceExecutionStateDataPending ||
state == nvigi::kInferenceExecutionStateDone)
{
// Final or pending result
myCtx->transcribedText = accumulated;
myCtx->currentAccumulated = accumulated;
}
}
if (state == nvigi::kInferenceExecutionStateDone)
{
std::cout << "Final: " << myCtx->transcribedText << std::endl;
myCtx->audioPending = false;
}
return state;
}
2. Initialize Streaming#
// Create context
StreamingContext streamCtx{};
// Set up audio data structure
nvigi::CpuData audioData{};
nvigi::InferenceDataAudio audio{audioData};
audio.bitsPerSample = 16;
audio.samplingRate = 16000;
audio.channels = 1;
std::vector<nvigi::InferenceDataSlot> slots = {{nvigi::kASRDataSlotAudio, audio}};
nvigi::InferenceDataSlotArray inputs{slots.size(), slots.data()};
// Set up runtime parameters
nvigi::ASRRivaORTRuntimeParameters runtime{};
nvigi::StreamingParameters stream{};
runtime.streamingMode = true;
runtime.sampling = nvigi::ASRRivaSamplingStrategy::eBeamSearch;
stream.mode = nvigi::StreamingMode::eStreamingModeInputOutput; // Only mode available (parameter not used currently)
stream.signal = nvigi::StreamSignal::eStreamSignalStart;
runtime.chain(stream);
// Create execution context
nvigi::InferenceExecutionContext execCtx{};
execCtx.instance = instance;
execCtx.inputs = &inputs;
execCtx.runtimeParameters = runtime;
execCtx.callback = streamingCallback;
execCtx.callbackUserData = &streamCtx;
// Send START signal with empty audio
audioData.buffer = nullptr;
audioData.sizeInBytes = 0;
instance->evaluateAsync(&execCtx);
3. Send Audio DATA#
// Load your audio samples (16kHz, 16-bit, mono)
std::vector<int16_t> audioSamples = loadAudioFile("speech.wav");
// Stream audio in chunks (e.g., 5KB chunks)
const uint64_t chunkSizeBytes = 5 * 1024; // 5KB chunks
uint64_t offset = 0;
const uint64_t totalBytes = audioSamples.size() * sizeof(int16_t);
// After sending START, switch to DATA signal
stream.signal = nvigi::StreamSignal::eStreamSignalData;
while (offset < totalBytes)
{
// Calculate chunk size for this iteration
uint64_t sizeToSend = std::min(chunkSizeBytes, totalBytes - offset);
// Update audio buffer pointer and size
audioData.buffer = reinterpret_cast<uint8_t*>(audioSamples.data()) + offset;
audioData.sizeInBytes = sizeToSend;
// Send audio chunk
instance->evaluateAsync(&execCtx);
offset += sizeToSend;
// Optional: add delay to simulate real-time streaming
if (offset < totalBytes)
{
std::this_thread::sleep_for(std::chrono::milliseconds(50));
}
}
4. Send STOP Signal and Wait for Completion#
// Signal end of stream
stream.signal = nvigi::StreamSignal::eStreamSignalStop;
audioData.buffer = nullptr;
audioData.sizeInBytes = 0;
instance->evaluateAsync(&execCtx);
// Wait for final callback with state == kInferenceExecutionStateDone
while (streamCtx.audioPending)
{
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
// Use final transcription
std::cout << "Complete transcription: " << streamCtx.transcribedText << std::endl;
6. Features#
6.1 Auto Punctuation#
Enable automatic punctuation insertion and capitalization:
asrParams.enableAutoPunctuation = true;
When enabled, the ASR will:
Automatically insert periods, commas, question marks, and other punctuation
Capitalize proper nouns and sentence beginnings
Improve overall readability of transcriptions
Note: Auto punctuation is currently available for English models only. Enabling it with non-English models will have no effect.
6.2 Multilingual Support#
The ASR plugin supports speech recognition in 8 languages. Each language has a dedicated Conformer-CTC model identified by a unique GUID. To use a specific language, set the modelGUID to the corresponding value when creating the ASR instance:
Language |
Model Name |
GUID |
|---|---|---|
English (en-US) |
Conformer-CTC |
|
English (en-US) |
Parakeet-0.6B |
|
French (fr-FR) |
Conformer-CTC-French |
|
German (de-DE) |
Conformer-CTC-German |
|
Italian (it-IT) |
Conformer-CTC-Italian |
|
Spanish (es-ES) |
Conformer-CTC-Spanish |
|
Korean (ko-KR) |
Conformer-CTC-Korean |
|
Japanese (ja-JP) |
Conformer-CTC-Japanese |
|
Mandarin (zh-CN) |
Conformer-CTC-Mandarin |
|
Selecting a Language#
To switch languages, change the modelGUID in your creation parameters:
// French ASR
common.modelGUID = "{7B9ED4D0-D15C-450F-BCC8-8FA7B460BFC0}";
// Japanese ASR
common.modelGUID = "{29A8FAFA-A9BB-485C-AEEF-BFF4512DFE91}";
// Spanish ASR
common.modelGUID = "{20AD4848-5CB4-49CA-B48F-AF1AA61B4EB2}";
All multilingual models use the same plugin (nvigi.plugin.asr.riva-ort) and the same API – only the GUID changes. Streaming mode, offline mode, backend selection, word boosting, and silence detection all work identically across languages.
Multilingual Considerations#
Audio format: All languages require the same 16kHz, 16-bit, mono PCM audio input.
Auto punctuation: Currently available for English models only.
Word boosting: Works across all languages. Provide boosted words in the target language’s script (e.g., Kanji/Hiragana for Japanese, Hangul for Korean, Hanzi for Mandarin).
Switching languages: Requires destroying the current instance and creating a new one with the desired language’s GUID. Language cannot be changed at runtime without re-creating the instance.
6.3 Word Boosting#
Improve recognition of specific words by providing boost scores. Words with higher scores are more likely to be recognized, while negative scores suppress words.
// Define word boosting entries
static const nvigi::WordBoostingEntry wordBoostingEntries[] = {
{"fugatto", 10.0f}, // Boost recognition of "fugatto"
{"nvidia", 5.0f}, // Boost recognition of "nvidia"
{"um", -100.0f} // Suppress filler word "um"
};
// Apply word boosting
asrParams.wordBoostingEntries = wordBoostingEntries;
asrParams.wordBoostingEntriesCount = sizeof(wordBoostingEntries) / sizeof(wordBoostingEntries[0]);
Use cases for word boosting:
Domain-specific vocabulary (technical terms, product names)
User-specific vocabulary (usernames, place names)
Suppressing common misrecognitions
Score recommendations:
Keep boost scores between -50 and +50 for optimal results
Start with moderate scores (±10) and adjust based on results
Scores outside this range may reduce overall accuracy
Troubleshooting word boosting:
If word boosting does not seem to work for a specific word, it is most likely because the tokens predicted by the model differ from the tokens obtained when tokenizing the boosted word. The ASR model uses subword tokenization, so a word may be split into multiple tokens that don’t match the boosted word exactly.
Solution: Try different orthographic variations of the word:
Phonetic spellings (e.g., “fugatto” vs “fugato” vs “fougatto”)
Common misspellings that the model might predict Runtime Updates:
Word boosting can be updated at runtime by providing updated wordBoostingEntries in the runtime parameters when calling instance->evaluate() (offline) or instance->evaluateAsync() (streaming). When word boosting is updated at runtime, expect approximately 400ms additional latency for the first inference after the update. This is because the system needs to rebuild internal data structures (trie and decoders) to reflect the new boosting configuration.
To avoid this latency cost during critical inference calls, consider performing a warmup inference immediately after updating word boosting:
// Update word boosting in runtime parameters
runtime.wordBoostingEntries = newWordBoostingEntries;
runtime.wordBoostingEntriesCount = newWordBoostingEntriesCount;
// Perform warmup inference with minimal audio to absorb the rebuild cost
// This ensures subsequent inferences don't incur the 400ms penalty
instance->evaluate(&execCtx); // or evaluateAsync() for streaming
// Now subsequent inferences will have normal latency
instance->evaluate(&execCtx);
6.4 Silence Detection (Streaming Only)#
In streaming mode, you can enable silence detection to identify pauses in speech:
// Enable silence detection with 1000ms window
runtime.silenceDetectionTimeMs = 1000; // Detect 1-second silences
runtime.silenceSensitivityThreshold = 0.85f; // Sensitivity (0.0 - 1.0, default 0.85)
When silence is detected, the accumulated text will include <Silence> markers. This is useful for:
Detecting natural breaks in conversation
Identifying when a user has stopped speaking
Important: The <Silence> marker is temporary and will disappear from the accumulated text as soon as the speaker starts talking again. This allows you to detect silence periods in real-time during streaming.
Note: Silence detection uses a rolling average window, so the exact number of <Silence> markers may vary slightly based on speech patterns.
6.5 Sampling Strategies#
Two sampling strategies are available:
// Greedy sampling (faster, may be less accurate) - OFFLINE MODE ONLY
runtime.sampling = nvigi::ASRRivaSamplingStrategy::eGreedy;
// Beam search (slower, more accurate) - Available for both offline and streaming
runtime.sampling = nvigi::ASRRivaSamplingStrategy::eBeamSearch;
Important Notes:
Streaming mode: Only beam search is supported. Use the
streamingBeamSizeparameter to control beam width.Offline mode: Both greedy and beam search are available.
Use beam search for production quality transcriptions
Use greedy for faster processing when accuracy is less critical
The following benchmarks were conducted on NVIDIA RTX 5090 GPU using the LibriSpeech test-clean dataset (2620 samples). See Benchmarking Methodology for metric definitions.
Conformer Model:
Backend |
Sampling Strategy |
Avg. WER |
Corpus WER |
Perfect WER % |
High WER % |
Avg RTF |
Speed up |
|---|---|---|---|---|---|---|---|
GPU |
|
2.39% |
2.16% |
74.5 |
0.34 |
0.00253 |
395.2x |
GPU |
|
2.60% |
2.33% |
72.1 |
0.42 |
0.00211 |
474.6x |
Parakeet Model:
Backend |
Sampling Strategy |
Avg. WER |
Corpus WER |
Perfect WER % |
High WER % |
Avg RTF |
Speed up |
|---|---|---|---|---|---|---|---|
GPU |
|
2.23% |
1.95% |
76.0 |
0.31 |
0.00284 |
352.2x |
GPU |
|
2.27% |
1.94% |
75.8 |
0.34 |
0.00259 |
386.5x |
Note: Greedy sampling is only available in offline mode. Beam search is always used for streaming mode. Word boosting is not available with Greedy decoding.
7. Performance Optimization#
7.1 Backend Selection#
The SDK provides two plugins with different backend support:
CPU Backend (
nvigi::plugin::asr::riva_ort::cpu::kId):Uses ONNX Runtime CPU execution provider
Supports all modern x64 CPUs
Best for: CPU-only systems, limited amount of VRAM
GPU Backend (
nvigi::plugin::asr::riva_ort::gpu::kId):Supports both TensorRT-RTX and DirectML execution providers
TensorRT-RTX (default on NVIDIA GPUs): Best performance for NVIDIA GPUS
DirectML (via
preferDML = trueor fallback): Compatible with any DirectX 12 GPU (NVIDIA, AMD, Intel)Automatically falls back to DirectML if TensorRT-RTX is unavailable
// Use GPU backend with TensorRT-RTX (default on NVIDIA GPUs)
nvigi::PluginID pluginId = nvigi::plugin::asr::riva_ort::gpu::kId;
asrParams.preferDML = false; // Use TensorRT-RTX (falls back to DirectML if unavailable)
// Use GPU backend with DirectML (broader GPU compatibility)
nvigi::PluginID pluginId = nvigi::plugin::asr::riva_ort::gpu::kId;
asrParams.preferDML = true; // Force DirectML
Preventing Backend Fallback#
By default, the GPU backend will automatically fall back to a slower backend if the preferred one fails (TRT-RTX → DML → CPU). For testing or production scenarios where you want to ensure a specific backend is used, you can enable crash-on-fallback:
// Fail instead of falling back to a slower backend
asrParams.crashOnFallback = true;
When crashOnFallback is enabled, instance creation will fail with an error if the expected backend cannot be used.
TensorRT-RTX Engine Compilation: When using TensorRT-RTX for the first time, the engine will take approximately 30 seconds to compile. This compiled engine is then cached in the same folder specified by utf8PathToLogsAndData, so subsequent runs will not experience this overhead. The cache is reused across application restarts, ensuring optimal performance after the initial compilation.
CPU Thread Configuration#
For CPU backend, you can configure the number of threads used by the ONNX Runtime execution provider:
// Configure CPU thread count (default: 16)
asrParams.cpuNumThreads = 8; // Capped at hardware concurrency
The thread count is automatically capped at the system’s hardware concurrency (number of logical CPU cores). A global thread pool is used across all ONNX Runtime sessions for efficient resource sharing.
Offline Performance#
Backend Comparison (Conformer, librispeech test-clean):
Backend |
Avg RTF |
Speed up |
|---|---|---|
TensorRT-RTX |
0.00245 |
408.8x |
DirectML |
0.00511 |
195.8x |
CPU |
0.0198 |
50.4x |
Backend Comparison (Parakeet, librispeech test-clean):
Backend |
Avg RTF |
Speed up |
|---|---|---|
TensorRT-RTX |
0.00290 |
345.4x |
DirectML |
0.00642 |
155.7x |
CPU |
0.0255 |
39.2x |
Latency by Audio Duration (Conformer, Offline):
Audio Duration |
TensorRT-RTX (ms) |
DirectML (ms) |
CPU (ms) |
|---|---|---|---|
1-3s |
10.53 |
19.60 |
73.05 |
3-6s |
13.79 |
27.98 |
95.97 |
6-9s |
17.98 |
37.67 |
138.55 |
9-12s |
22.41 |
47.56 |
184.73 |
12-15s |
27.75 |
60.27 |
238.69 |
15-18s |
31.43 |
68.91 |
292.27 |
18-21s |
36.02 |
79.30 |
361.80 |
21-24s |
39.77 |
86.09 |
435.79 |
24s+ |
47.31 |
106.70 |
626.61 |
Latency by Audio Duration (Parakeet, Offline):
Audio Duration |
TensorRT-RTX (ms) |
DirectML (ms) |
CPU (ms) |
|---|---|---|---|
1-3s |
12.16 |
23.82 |
109.19 |
3-6s |
16.26 |
34.74 |
141.16 |
6-9s |
21.27 |
47.05 |
185.40 |
9-12s |
26.51 |
59.96 |
229.06 |
12-15s |
33.10 |
76.36 |
280.23 |
15-18s |
37.64 |
88.69 |
334.21 |
18-21s |
43.08 |
102.93 |
398.53 |
21-24s |
47.28 |
111.48 |
446.10 |
24s+ |
57.72 |
140.34 |
595.87 |
Streaming Performance#
Backend Comparison (Conformer, librispeech test-clean):
Backend |
Avg RTF |
Speed up |
|---|---|---|
TensorRT-RTX |
0.0164 |
61.1x |
DirectML |
0.0380 |
26.3x |
CPU |
0.196 |
5.1x |
Backend Comparison (Parakeet, librispeech test-clean):
Backend |
Avg RTF |
Speed up |
|---|---|---|
TensorRT-RTX |
0.0179 |
55.9x |
DirectML |
0.0493 |
20.3x |
CPU |
0.326 |
3.1x |
7.2 Memory Requirements#
Specify the maximum VRAM budget for GPU backends:
common.vramBudgetMB = 2048; // Maximum VRAM the plugin can use (in MB)
Actual VRAM usage can range from 500 MB to 1.6 GB depending on the model selected, user-defined parameters, and the graphics card in use.
Important: This parameter specifies the maximum amount of VRAM that the plugin can use. If the plugin requires more VRAM than this budget allows, it will raise an error. Consider:
The model being used (e.g., Parakeet-0.6B requires more VRAM than Conformer-CTC)
The graphics card and its available VRAM
Other GPU workloads running simultaneously
Setting a sufficient budget to avoid runtime errors
7.3 Streaming Chunk Size#
The internal chunk size affects processing characteristics. Larger chunk sizes provide more acoustic context, improving accuracy (lower WER), and improves RTF because there is lesser workload that runs on GPU. So the metrics are highly scenario specific.
// Recommended chunk sizes based on model type:
asrParams.streamingChunkSizeMs = 380; // For Conformer models (default)
// OR
asrParams.streamingChunkSizeMs = 900; // For Parakeet models (needs more context)
Model-Specific Recommendations:
Conformer models: 380ms (default) - Efficient with less context, balanced latency and accuracy
Parakeet models: 900ms - Requires more context for optimal accuracy due to architectural differences
Note: You can send audio chunks of any size to
evaluateAsync()regardless of this setting
Parakeet Model Streaming Performance (librispeech test-clean):
Chunk size (ms) |
Avg. WER |
Corpus WER |
Perfect WER % |
High WER % |
Avg RTF |
Speed up |
|---|---|---|---|---|---|---|
380 |
3.33% |
2.72% |
67.2 |
0.84 |
0.0180 |
55.5x |
900 |
3.08% |
2.48% |
69.8 |
0.80 |
0.0146 |
68.5x |
Streaming Performance on Challenging Audio (librispeech test-other):
Model |
Chunk (ms) |
Avg. WER |
Corpus WER |
Perfect WER % |
High WER % |
Avg RTF |
Speed up |
|---|---|---|---|---|---|---|---|
Conformer |
380 |
6.66% |
5.53% |
52.8 |
1.84 |
0.0181 |
55.2x |
Conformer |
900 |
6.37% |
5.18% |
54.4 |
1.77 |
0.0162 |
61.9x |
Parakeet |
380 |
6.91% |
5.31% |
54.1 |
2.35 |
0.0190 |
52.6x |
Parakeet |
700 |
6.45% |
4.97% |
55.3 |
2.14 |
0.0166 |
60.2x |
Parakeet |
900 |
6.37% |
4.82% |
56.9 |
2.21 |
0.0162 |
61.7x |
The test-other dataset contains more challenging audio with accents, background noise, and varied acoustic conditions.
7.4 Streaming Context size#
This parameter is available through the streamingContextSizeLeftRightMs parameter for the streaming use case. Larger values provide more historical context but turns out to be a bit slower. Default: 2500 ms
Context Size (ms) |
Avg. WER |
Corpus WER |
Perfect WER % |
High WER % |
Avg RTF |
Speed up |
|---|---|---|---|---|---|---|
1920 |
3.19% |
2.89% |
66.7 |
0.50 |
0.0166 |
60.3x |
2500 |
2.87% |
2.58% |
69.6 |
0.50 |
0.0169 |
59.1x |
4000 |
2.68% |
2.34% |
71.7 |
0.46 |
0.0167 |
59.8x |
8. Graphics Integration#
For game applications, the ASR plugin supports graphics API integration through D3D12 and Vulkan parameters:
With TensorRT-RTX: Enables CUDA In Graphics (CIG) for efficient GPU resource sharing between AI inference and rendering
With DirectML: Uses your game’s D3D12 device to create command queues for proper integration
Note: CIG is only available with TensorRT-RTX backend.
8.1 D3D12Parameters Structure#
The D3D12Parameters structure provides the necessary D3D12 context for both CUDA In Graphics (CIG) and DirectML backends.
Field |
Type |
Required |
Description |
|---|---|---|---|
|
|
Yes |
Your game’s D3D12 device |
|
|
Yes (for CIG) |
Used by CIG to create a shared CUDA context |
|
|
Optional |
Asynchronous compute queue ( |
|
Function pointer |
Optional |
Custom callback for creating D3D12 committed resources |
|
Function pointer |
Optional |
Custom callback for destroying D3D12 resources |
|
|
Optional |
User context passed to |
|
|
Optional |
User context passed to |
|
|
Optional |
Flags controlling D3D12 behavior (see below) |
D3D12ParametersFlags:
eNone: No flags (default)eDisableReBAR: Disable use of ReBAR (D3D12_HEAP_TYPE_GPU_UPLOAD) - may degrade performanceeComputeQueueSharedWithFrame: Indicates the provided compute queue is also used for main rendering
8.2 D3D12 Integration with CIG (TensorRT-RTX)#
For CUDA In Graphics integration, provide your D3D12 device and queue:
Important: The compute queue provided to CIG must be part of the same TSG (Timeslice Group) as the graphics queue of the game. This ensures proper synchronization and resource sharing between AI inference and rendering operations.
Recommendation: For CIG, it is recommended to provide only a compute queue in the queue parameter for optimal performance and resource scheduling.
#ifdef NVIGI_WINDOWS
// Initialize D3D12 parameters for CIG
nvigi::D3D12Parameters d3d12Params{};
d3d12Params.device = myD3D12Device; // Your ID3D12Device*
d3d12Params.queue = myComputeQueue; // Recommended: Use compute queue (D3D12_COMMAND_LIST_TYPE_COMPUTE) for CIG
// Chain D3D12 parameters with ASR parameters
nvigi::ASRRivaORTCreationParameters asrParams{};
asrParams.chain(common);
asrParams.chain(d3d12Params); // Chain D3D12 params for CIG
asrParams.preferDML = false; // Use TensorRT-RTX (default)
// Create instance
nvigi::InferenceInstance* instance{};
iasr->createInstance(asrParams, &instance);
#endif
8.3 D3D12 Integration with DirectML#
For DirectML, provide at minimum the device and optionally a compute queue:
#ifdef NVIGI_WINDOWS
// Initialize D3D12 parameters for DirectML
nvigi::D3D12Parameters d3d12Params{};
d3d12Params.device = myD3D12Device; // Your ID3D12Device*
// Option 1: Use your game's queues
d3d12Params.queue = myGraphicsQueue; // Optional for DML, but recommended
d3d12Params.queueCompute = myComputeQueue; // Used for DirectML inference operations
// Option 2: Create a dedicated compute queue for AI
D3D12_COMMAND_QUEUE_DESC queueDesc = {};
queueDesc.Type = D3D12_COMMAND_LIST_TYPE_COMPUTE;
queueDesc.Priority = D3D12_COMMAND_QUEUE_PRIORITY_NORMAL;
queueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE;
ID3D12CommandQueue* aiComputeQueue = nullptr;
myD3D12Device->CreateCommandQueue(&queueDesc, IID_PPV_ARGS(&aiComputeQueue));
d3d12Params.queueCompute = aiComputeQueue;
// Chain D3D12 parameters with ASR parameters
nvigi::ASRRivaORTCreationParameters asrParams{};
asrParams.chain(common);
asrParams.chain(d3d12Params); // Chain D3D12 params for DirectML
asrParams.preferDML = true; // Use DirectML
// Create instance
nvigi::InferenceInstance* instance{};
iasr->createInstance(asrParams, &instance);
#endif
8.4 Custom Resource Allocation Callbacks#
For games that need fine-grained control over D3D12 resource allocation, you can provide custom callbacks:
// Custom resource creation callback
ID3D12Resource* createCommittedResource(
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;
HRESULT hr = device->CreateCommittedResource(
pHeapProperties, HeapFlags, pDesc,
InitialResourceState, pOptimizedClearValue,
IID_PPV_ARGS(&resource));
if (FAILED(hr))
{
// Handle error - log, track allocation failure, etc.
return nullptr;
}
// Optional: Track allocation in your resource manager
// MyResourceManager* mgr = static_cast<MyResourceManager*>(userContext);
// mgr->TrackAllocation(resource, pDesc->Width);
return resource;
}
// Custom resource destruction callback
void destroyResource(ID3D12Resource* pResource, void* userContext)
{
// Optional: Untrack allocation
// MyResourceManager* mgr = static_cast<MyResourceManager*>(userContext);
// mgr->UntrackAllocation(pResource);
pResource->Release();
}
// Setup D3D12 parameters with callbacks
nvigi::D3D12Parameters d3d12Params{};
d3d12Params.device = myD3D12Device;
d3d12Params.queue = myGraphicsQueue;
d3d12Params.createCommittedResourceCallback = createCommittedResource;
d3d12Params.destroyResourceCallback = destroyResource;
d3d12Params.createCommitResourceUserContext = &myResourceManager; // Optional
d3d12Params.destroyResourceUserContext = &myResourceManager; // Optional
8.5 VulkanParameters Structure#
The VulkanParameters structure provides the necessary Vulkan context for CUDA In Graphics (CIG) integration.
Field |
Type |
Required |
Description |
|---|---|---|---|
|
|
Yes |
Your game’s Vulkan physical device |
|
|
Yes |
Your game’s Vulkan logical device |
|
|
Yes |
Your game’s Vulkan instance |
|
|
Yes (for CIG) |
Graphics queue ( |
|
|
Optional |
Compute queue ( |
|
Function pointer |
Optional |
Custom callback for allocating Vulkan memory |
|
Function pointer |
Optional |
Custom callback for freeing Vulkan memory |
|
|
Optional |
User context passed to |
|
|
Optional |
User context passed to |
Note: All VulkanParameters members are optional. If not provided, NVIGI will create internal device, instance, and queues. However, for proper CIG integration with your game, you should provide your game’s Vulkan context.
8.6 Vulkan Integration#
#ifdef NVIGI_WINDOWS
// Initialize Vulkan parameters with your game's Vulkan context
nvigi::VulkanParameters vulkanParams{};
vulkanParams.physicalDevice = myVkPhysicalDevice; // Your VkPhysicalDevice
vulkanParams.device = myVkDevice; // Your VkDevice
vulkanParams.instance = myVkInstance; // Your VkInstance
vulkanParams.queue = myGraphicsQueue; // Graphics queue (VK_QUEUE_GRAPHICS_BIT)
// Optionally provide compute queue for better scheduling
vulkanParams.queueCompute = myComputeQueue; // VK_QUEUE_COMPUTE_BIT
// Chain Vulkan parameters with ASR parameters
nvigi::ASRRivaORTCreationParameters asrParams{};
asrParams.chain(common);
asrParams.chain(vulkanParams); // Chain Vulkan params for CIG
asrParams.preferDML = false; // Use TensorRT-RTX
// Create instance
nvigi::InferenceInstance* instance{};
iasr->createInstance(asrParams, &instance);
#endif
Important for Vulkan games: Provide VulkanParameters with your game’s Vulkan device and queue for CUDA In Graphics (CIG) integration with TensorRT-RTX backend.
8.7 When to Chain Graphics Parameters#
Backend |
Use Case |
Chain D3D12Parameters? |
Chain VulkanParameters? |
|---|---|---|---|
TensorRT-RTX |
D3D12 game with CIG |
Yes - Required for CIG |
No |
TensorRT-RTX |
Vulkan game with CIG |
No |
Yes - Required for CIG |
TensorRT-RTX |
No graphics (standalone) |
Optional |
Optional |
DirectML |
D3D12 game |
Yes - Recommended for proper queue integration |
N/A (DML is D3D12 only) |
DirectML |
No graphics (standalone) |
Optional (NVIGI creates internal queues) |
N/A |
CPU |
Any |
No (ignored) |
No (ignored) |
8.8 GPU Scheduling Modes#
When using CIG, you can control GPU scheduling priorities:
#ifdef NVIGI_WINDOWS
// Get HWI (Hardware Interface) for scheduling control
nvigi::IHWICommon* ihwiCommon{};
nvigiGetInterfaceDynamic(plugin::hwi::common::kId, &ihwiCommon, nvigiLoadInterface);
// Set scheduling mode
ihwiCommon->SetGpuInferenceSchedulingMode(SchedulingMode::kBalance);
// ... Run inference ...
// Clean up
nvigiUnloadInterface(plugin::hwi::common::kId, ihwiCommon);
#endif
Scheduling Mode Options:
Mode |
Description |
Use When |
|---|---|---|
|
Gives GPU priority to 3D rendering over inference |
Frame rate is critical and you can tolerate higher ASR latency |
|
Gives GPU priority to inference over rendering |
Low ASR latency is critical (e.g., real-time voice commands) and you can tolerate some frame drops |
|
Balanced split between graphics and compute |
Good default for most applications; provides reasonable frame rates and ASR latency without tuning |
Note: For games with low graphics intensity (e.g., 2D games, simple 3D scenes, or applications running well below GPU capacity), the difference between scheduling modes will be minimal since the GPU has enough headroom to handle both workloads without contention.
Note: CUDA In Graphics (CIG) is only available with the TensorRT-RTX backend on NVIDIA GPUs with driver version 575 or higher. CIG is not available when using DirectML.
9. Error Handling#
Always check return values using the NVIGI_FAILED macro:
nvigi::Result result;
if (NVIGI_FAILED(result, iasr->createInstance(asrParams, &instance)))
{
// Log error code and message
std::cerr << "Error code: " << result.error << std::endl;
// Handle error appropriately
}
Common error scenarios:
Model not found: Check
utf8PathToModelsand model GUIDVRAM budget exceeded: Increase
vramBudgetMB, ensure GPU has enough memory, or use CPU backendInvalid audio format: Ensure 16kHz, 16-bit, mono
Plugin not found: Check plugin path and DLL availability
10. Complete Examples#
Note: These examples are illustrative to demonstrate API usage patterns. For complete, working code, see the Working Code Examples section below.
10.1 Example 1: Offline Processing with Word Boosting#
#include <nvigi.h>
#include "nvigi_asr_riva.h"
#include <iostream>
#include <vector>
#include <atomic>
// Callback context
struct TranscriptionContext {
std::string transcribedText;
std::atomic<bool> audioPending{true};
};
// Callback function
nvigi::InferenceExecutionState transcriptionCallback(
const nvigi::InferenceExecutionContext* ctx,
nvigi::InferenceExecutionState state,
void* userData)
{
auto* myCtx = static_cast<TranscriptionContext*>(userData);
if (ctx && ctx->outputs)
{
const nvigi::InferenceDataText* textOutput{};
if (ctx->outputs->findAndValidateSlot(nvigi::kASRDataSlotTranscribedText, &textOutput))
{
myCtx->transcribedText = textOutput->getUTF8Text();
}
}
if (state == nvigi::kInferenceExecutionStateDone)
{
myCtx->audioPending = false;
}
return state;
}
int main()
{
// Initialize NVIGI (see Step 1)
// Load interface (see Step 2)
nvigi::IAutoSpeechRecognition* iasr{};
// ... (initialization code)
// Create instance for offline mode with word boosting
nvigi::CommonCreationParameters common{};
common.utf8PathToModels = "C:\\models";
common.modelGUID = "{B5D4F28E-3DB9-4FDA-A78B-A569AA22FCD0}";
// Define word boosting
static const nvigi::WordBoostingEntry wordBoostingEntries[] = {
{"nvidia", 10.0f},
{"fugatto", 10.0f}
};
nvigi::ASRRivaORTCreationParameters asrParams{};
asrParams.chain(common);
asrParams.enableStreamingMode = false;
asrParams.enableAutoPunctuation = true;
asrParams.wordBoostingEntries = wordBoostingEntries;
asrParams.wordBoostingEntriesCount = 2;
nvigi::InferenceInstance* instance{};
iasr->createInstance(asrParams, &instance);
// Load audio (must be 16kHz, 16-bit, mono)
std::vector<int16_t> audioSamples = loadAudioFile("speech.wav");
// Set up audio data
nvigi::CpuData audioData{};
audioData.buffer = reinterpret_cast<uint8_t*>(audioSamples.data());
audioData.sizeInBytes = audioSamples.size() * sizeof(int16_t);
nvigi::InferenceDataAudio audio{audioData};
audio.bitsPerSample = 16;
audio.samplingRate = 16000;
audio.channels = 1;
std::vector<nvigi::InferenceDataSlot> slots = {{nvigi::kASRDataSlotAudio, audio}};
nvigi::InferenceDataSlotArray inputs{slots.size(), slots.data()};
// Set up runtime parameters
nvigi::ASRRivaORTRuntimeParameters runtime{};
runtime.sampling = nvigi::ASRRivaSamplingStrategy::eBeamSearch;
runtime.streamingMode = false;
// Create callback context
TranscriptionContext ctx{};
// Set up execution context
nvigi::InferenceExecutionContext execCtx{};
execCtx.instance = instance;
execCtx.inputs = &inputs;
execCtx.runtimeParameters = runtime;
execCtx.callback = transcriptionCallback;
execCtx.callbackUserData = &ctx;
// Run inference
instance->evaluate(&execCtx);
// Wait for completion
while (ctx.audioPending)
{
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
// Use result
std::cout << "Transcription: " << ctx.transcribedText << std::endl;
// Cleanup
iasr->destroyInstance(instance);
// Shutdown (see Step 5)
return 0;
}
10.2 Example 2: Real-Time Streaming with Silence Detection#
#include <nvigi.h>
#include "nvigi_asr_riva.h"
#include <iostream>
#include <vector>
#include <atomic>
// Streaming context
struct StreamingContext {
std::string transcribedText;
std::string currentAccumulated;
std::string mostRecentPartial;
std::atomic<bool> audioPending{true};
int updateCount = 0;
bool lastHadSilence = false;
int silenceCount = 0;
};
// Streaming callback
nvigi::InferenceExecutionState streamingCallback(
const nvigi::InferenceExecutionContext* ctx,
nvigi::InferenceExecutionState state,
void* userData)
{
auto* myCtx = static_cast<StreamingContext*>(userData);
if (ctx && ctx->outputs)
{
// Get accumulated text (high-quality, uses enough context)
const nvigi::InferenceDataText* accumulatedText{};
ctx->outputs->findAndValidateSlot(nvigi::kASRDataSlotTranscribedText, &accumulatedText);
std::string accumulated = accumulatedText ? accumulatedText->getUTF8Text() : "";
// Get partial text (real-time, recent audio only, less accurate)
const nvigi::InferenceDataText* partialText{};
bool hasPartial = ctx->outputs->findAndValidateSlot(nvigi::kASRDataSlotPartialText, &partialText);
std::string partial = hasPartial && partialText ? partialText->getUTF8Text() : "";
if (state == nvigi::kInferenceExecutionStateDataPartial)
{
// Check for silence detection in accumulated text
bool currentHasSilence = (accumulated.find("<Silence>") != std::string::npos);
if (!myCtx->lastHadSilence && currentHasSilence)
{
myCtx->silenceCount++;
std::cout << "Silence detected (count: " << myCtx->silenceCount << ")" << std::endl;
}
myCtx->lastHadSilence = currentHasSilence;
myCtx->currentAccumulated = accumulated;
myCtx->mostRecentPartial = partial;
myCtx->updateCount++;
std::cout << "Update " << myCtx->updateCount << " - Accumulated: '" << accumulated << "'";
if (!partial.empty())
{
std::cout << ", Partial: '" << partial << "'";
}
std::cout << std::endl;
}
if (state == nvigi::kInferenceExecutionStateDataPending ||
state == nvigi::kInferenceExecutionStateDone)
{
myCtx->transcribedText = accumulated;
myCtx->currentAccumulated = accumulated;
}
}
if (state == nvigi::kInferenceExecutionStateDone)
{
std::cout << "Final: " << myCtx->transcribedText << std::endl;
myCtx->audioPending = false;
}
return state;
}
int main()
{
// Initialize NVIGI and load interface
nvigi::IAutoSpeechRecognition* iasr{};
// ... (initialization code)
// Create instance for streaming mode
nvigi::CommonCreationParameters common{};
common.utf8PathToModels = "C:\\models";
common.modelGUID = "{B5D4F28E-3DB9-4FDA-A78B-A569AA22FCD0}";
nvigi::ASRRivaORTCreationParameters asrParams{};
asrParams.chain(common);
asrParams.enableStreamingMode = true;
asrParams.streamingChunkSizeMs = 380; // 380ms for Conformer, 900ms for Parakeet
asrParams.streamingBeamSize = 32;
nvigi::InferenceInstance* instance{};
iasr->createInstance(asrParams, &instance);
// Load audio
std::vector<int16_t> audioSamples = loadAudioFile("speech.wav");
// Set up audio data
nvigi::CpuData audioData{};
nvigi::InferenceDataAudio audio{audioData};
audio.bitsPerSample = 16;
audio.samplingRate = 16000;
audio.channels = 1;
std::vector<nvigi::InferenceDataSlot> slots = {{nvigi::kASRDataSlotAudio, audio}};
nvigi::InferenceDataSlotArray inputs{slots.size(), slots.data()};
// Set up runtime parameters with silence detection
nvigi::ASRRivaORTRuntimeParameters runtime{};
nvigi::StreamingParameters stream{};
runtime.streamingMode = true;
runtime.sampling = nvigi::ASRRivaSamplingStrategy::eBeamSearch;
runtime.silenceDetectionTimeMs = 1000; // Detect 1-second silences
runtime.silenceSensitivityThreshold = 0.85f;
stream.mode = nvigi::StreamingMode::eStreamingModeInputOutput; // Only mode available (parameter not used currently)
stream.signal = nvigi::StreamSignal::eStreamSignalStart;
runtime.chain(stream);
// Create context
StreamingContext streamCtx{};
// Set up execution context
nvigi::InferenceExecutionContext execCtx{};
execCtx.instance = instance;
execCtx.inputs = &inputs;
execCtx.runtimeParameters = runtime;
execCtx.callback = streamingCallback;
execCtx.callbackUserData = &streamCtx;
// Send START signal
audioData.buffer = nullptr;
audioData.sizeInBytes = 0;
instance->evaluateAsync(&execCtx);
// Send audio data in chunks
const uint64_t chunkSizeBytes = 5 * 1024;
uint64_t offset = 0;
const uint64_t totalBytes = audioSamples.size() * sizeof(int16_t);
stream.signal = nvigi::StreamSignal::eStreamSignalData;
while (offset < totalBytes)
{
uint64_t sizeToSend = std::min(chunkSizeBytes, totalBytes - offset);
audioData.buffer = reinterpret_cast<uint8_t*>(audioSamples.data()) + offset;
audioData.sizeInBytes = sizeToSend;
instance->evaluateAsync(&execCtx);
offset += sizeToSend;
if (offset < totalBytes)
{
std::this_thread::sleep_for(std::chrono::milliseconds(50));
}
}
// Send STOP signal
stream.signal = nvigi::StreamSignal::eStreamSignalStop;
audioData.buffer = nullptr;
audioData.sizeInBytes = 0;
instance->evaluateAsync(&execCtx);
// Wait for completion
while (streamCtx.audioPending)
{
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
std::cout << "Complete transcription: " << streamCtx.transcribedText << std::endl;
std::cout << "Total silences detected: " << streamCtx.silenceCount << std::endl;
// Cleanup
iasr->destroyInstance(instance);
// Shutdown
return 0;
}
11. Working Code Examples#
For complete, production-ready implementations, refer to these sample files:
Streaming ASR Implementation:
source/samples/nvigi.asr.sample/sample_asr.cppComplete working example of streaming mode with callbacks, audio chunking, and proper initialization
Graphics Integration (D3D12/Vulkan):
source/samples/nvigi.3d/src/nvigi/NVIGIContext.cppReal-world implementation showing graphics API integration for games
These samples demonstrate proper error handling, resource management, and integration patterns that can be used in production code.
12. Troubleshooting#
12.1 Audio Format Issues#
Problem: Poor transcription quality or errors
Solution: Ensure audio is exactly 16kHz, 16-bit, mono. Convert if necessary:
// Convert stereo to mono
if (numChannels == 2)
{
std::vector<int16_t> monoData(wavData.size() / 2);
for (size_t i = 0; i < monoData.size(); i++)
{
int32_t left = wavData[2 * i];
int32_t right = wavData[2 * i + 1];
monoData[i] = static_cast<int16_t>((left + right) / 2);
}
wavData = std::move(monoData);
}
12.2 Streaming Timeouts#
Problem: No final callback received
Solution:
Ensure STOP signal is sent after all audio
Check that callback function is properly set
Increase timeout duration for very long audio
12.3 Memory Issues#
Problem: Instance creation or inference fails due to VRAM budget exceeded
Solution:
Increase
vramBudgetMBto provide more VRAM budget for the pluginEnsure your GPU has sufficient VRAM available (check GPU specifications)
Use CPU backend instead of GPU backend if VRAM is limited
12.4 Poor Accuracy#
Problem: Incorrect transcriptions
Solution:
Use
eBeamSearchsampling instead ofeGreedyIncrease
streamingBeamSizefor streaming modeEnable
enableAutoPunctuationEnsure audio is clear and noise-free
Use word boosting for domain-specific vocabulary
13. API Reference#
13.1 ASRRivaORTCreationParameters#
Field |
Type |
Default |
Description |
|---|---|---|---|
|
|
|
Array of word boosting entries |
|
|
|
Number of word boosting entries |
|
|
|
Enable automatic punctuation and capitalization |
|
|
|
Enable streaming mode |
|
|
|
Prefer DirectML over TensorRT-RTX (GPU backend only) |
|
|
|
Internal chunk size in milliseconds (Recommanded to set it to 900ms for Parakeet) |
|
|
|
Beam search width for streaming |
|
|
|
Language model weight (0.0 - 1.0) |
|
|
|
Word insertion score |
|
|
|
Context padding size (left and right) in milliseconds |
|
|
|
If true, fail instead of falling back to a slower backend (useful for testing) |
|
|
|
Number of threads for CPU execution provider (capped at hardware concurrency) |
13.2 WordBoostingEntry#
Field |
Type |
Description |
|---|---|---|
|
|
Word to boost or suppress |
|
|
Boost score (positive = emphasize, negative = suppress) |
13.3 ASRRivaORTRuntimeParameters#
Field |
Type |
Default |
Description |
|---|---|---|---|
|
|
|
Sampling strategy (eGreedy for offline only, eBeamSearch for both) |
|
|
|
Enable streaming mode for this evaluation |
|
|
|
Silence detection window in ms (streaming only, -1 = disabled) |
|
|
|
Silence sensitivity threshold (0.0 - 1.0) |
13.4 StreamingParameters#
Field |
Type |
Description |
|---|---|---|
|
|
Streaming mode - only |
|
|
Stream signal: |
13.5 ASRRivaSamplingStrategy#
Value |
Description |
|---|---|
|
Greedy decoding (faster, less accurate, offline mode only) |
|
Beam search decoding (slower, more accurate, both offline and streaming) |
13.6 InferenceExecutionState#
Value |
Description |
|---|---|
|
Partial result available (streaming) |
|
Data is pending processing |
|
Processing complete, final result available |
13.7 Data Slots#
Slot Constant |
Direction |
Type |
Description |
|---|---|---|---|
|
Input |
|
Raw audio data (16kHz, 16-bit, mono) |
|
Output |
|
Accumulated transcription with enough past/future context (high accuracy) |
|
Output |
|
Real-time transcription of recent audio without enough context (streaming only, less accurate) |
14. Best Practices#
Initialize once: Call
nvigiInitonce at application startup, not for each inferenceReuse instances: Create instances once and reuse for multiple inferences to avoid overhead
Match audio format: Always provide 16kHz, 16-bit, mono PCM audio
Use beam search: Prefer
eBeamSearchfor production quality transcriptionsEnable auto punctuation: Improves readability and capitalizes proper nouns
Choose appropriate backend:
Use GPU backend with TensorRT-RTX when possible for best performance on NVIDIA GPUs (default)
Use CPU backend for limited VRAM scenarios, development, and testing
Use callbacks: Always implement callbacks to receive results asynchronously
Handle errors: Always check return values with
NVIGI_FAILEDClean up: Destroy instances and shutdown NVIGI on exit
Thread safety: Each instance should be used by only one thread at a time
Streaming signals: Always send START, DATA, and STOP in order
Word boosting: Use positive scores to emphasize domain-specific vocabulary, negative scores to suppress
Silence detection: Enable in streaming mode to detect natural pauses (1000-2000ms windows work well)
Multilingual: Select the correct model GUID for your target language; all other API usage remains the same across languages
15. Additional Resources#
Getting Started Guide - Setup and initial configuration
Samples Documentation - Detailed sample walkthroughs
NVIGI Core Documentation - Core framework documentation
16. Support#
For issues, questions, or feedback, please contact NVIDIA Developer Support or refer to the NVIGI Developer Pack documentation.