NVIGI - Programming Guide
This guide primarily focuses on the general use of the In-Game Inferencing, including how to initialize it and utilize its interfaces.
IMPORTANT: This guide might contain pseudo code, for the up to date implementation and source code which can be copy pasted please see the basic sample
Version 1.1.0 Release
Table of Contents
INTRODUCTION
NVIGI functions as a plugin manager that provides secure plugin loading
and an explicit
API, giving the host application complete control over every aspect of NVIGI’s usage.
Key Concepts
Typed and version structures are used for all data and C-style interfaces.
Structures can be chained together as needed
Structures are ABI and backwards compatible
Structures can contain C-style API (aka interfaces) or just plain data (aka parameters or properties)
Each plugin implements at least one C-style interface which is provided to the host application and other plugins to use
Interfaces are completely custom and it is up to the plugins to decide what functionality is needed in them
Different plugins can implement identical interfaces if needed, normally they would use different
backends
to provide same functionalityPlugins are developed independently and can be updated independently (as needed) in any application (even after it has shipped)
Plugins have unique identifiers in the following namespace format
nvigi::plugin::$name{::$backend::$api}::kId
where backend and API are optionalThe core component
nvigi.core.framework
enumerates all available plugins, determines which plugins can run on user’s system and what interfaces do they exportNot all plugin locations need to be known when
nvigi.core.framework
is initializedHowever, all shared dependencies must be known and stored in a single location
Typed And Versioned Structures
Typed and versioned structures can be used to provide input data, obtain output results and call custom C-style APIs.
Input Data
In this case, the host application provides information to NVIGI. Depending on scenario, multiple inputs (structures) can be chained together and later sought for with the findStruct
API. Here are some examples:
//! We are creating local GPT instance while providing common and D3D12 related information
//! Common
nvigi::CommonCreationParameters common{};
common.numThreads = myNumCPUThreads; // How many CPU threads is instance allowed to use
common.vramBudgetMB = myVRAMBudget; // How much VRAM is instance allowed to occupy
common.utf8PathToModels = myPathToNVIGIModelRepository; // Path to provided NVIGI model repository (using UTF-8 encoding)
common.modelGUID = "{175C5C5D-E978-41AF-8F11-880D0517C524}"; // Model GUID, for details please see NVIGI models repository
//! GPT specific
nvigi::GPTCreationParameters params{};
params.maxNumTokensToPredict = 200;
params.contextSize = 512;
params.seed = -1;
Note that we are chaining common properties to our main properties like this:
if(NVIGI_FAILED(params.chain(common)))
{
// Handle error
}
Now we provide additional information about D3D12 context, assuming the host application is using D3D graphics API:
//! D3D12 specific
nvigi::D3D12Parameters d3d12Params{};
d3d12Params.device = myDevice;
d3d12Params.queue = myQueue;
Then we chain it together like this:
if(NVIGI_FAILED(params.chain(d3d12Parameters)))
{
// Handle error
}
Finaly, we end up with the following chained input structure which can be provided to NVIGI inteface(s) for processing:
nvigi::GPTCreationParameters -> nvigi::D3D12Parameters -> nvigi::CommonCreationParameters
NVIGI plugins, in this case GPT, will use findStruct
API to find all mandatory and optional inputs which are chained together.
Output Data
When calling various NVIGI APIs it is a common case that they provide specific output(s) as typed and versioned structures. Here is one example:
//! Obtain interface, note that interface is just a special typed and version structure
nvigi::IGeneralPurposeTransformer* igpt{};
nvigiGetInterface(nvigi::plugin::gpt::ggml::cuda::kId, &igpt, params.nvigiLoadInterface);
//! Tell our GPT interface where to find models
nvigi::GPTCreationParameters params{};
common.utf8PathToModels = myPathToModelRepo;
//! Chain it together so GPT interface can find it
if(NVIGI_FAILED(params.chain(common)))
{
// Handle error
}
//! Now we obtain capabilities and requirements for this instance, again as typed and versioned structure but containing just data
nvigi::CommonCapabilitiesAndRequirements* caps{};
getCapsAndRequirements(igpt, params, &caps);
By design, NVIGI will chain together any additional output(s) so the requester can use the findStruct
function to check if they are present like this:
//! Check if optional output is chained with caps
auto sampler = findStruct<nvigi::GPTSamplerParameters>(*caps);
if (sampler)
{
//! This interface supports sampler parameters
//!
//! They can be modified as needed and chained to the nvigi::GPTCreationParameters when creating an instance
sampler->penalizeNewLine = true;
if(NVIGI_FAILED(params.chain(sampler)))
{
// Handle error
}
}
NOTE: In this example, since interface in question supports
nvigi::GPTSamplerParameters
, sampler parameters can be chained together with thenvigi::GPTCreationParameters
and serve as input (see the above section) if it is required to change any sampler related options when creating the instance.
Thread Safety
By default, any API in NVIGI is NOT thread safe unless stated otherwise in the comment above the API declaration. For example:
//! ....
//!
//! This method is NOT thread safe.
nvigi::Result(*createInstance)(const nvigi::NVIGIParameter* params, nvigi::InferenceInstance** instance);
Note that the line immediately above the declaration explicitly states that this method is NOT thread safe. It is host’s responsibility to provide synchronization is such scenarios.
Security
Digital Signatures
When running on Windows and in production configuration, NVIGI core framework will enforce digital signature check on all NVIGI plugin libraries but NOT on their dependencies. For example:
nvigi.plugin.$name.$backend.$api.dll # signed by NVDA, signature checked by `nvigi.core.framework.dll`
├── cudaRt_12.dll # signed by NVDA, signature NOT checked by `nvigi.core.framework.dll`
├── tensorRT_10.dll # signed by NVDA, signature NOT checked by `nvigi.core.framework.dll`
├── libprotobuf.dll # not signed, up to the host app to enforce security
├── zlib.dll # not signed, up to the host app to enforce security
└── kernel32.dll # system library, ignored since it is signed by the OS and in the secure location
To prevent hackers from replacing nvigi.core.framework.dll
with potentially malicious code it is expected that the host application uses API located in nvigi_security.h
to ensure that digital signature is valid before loading any NVIGI libraries. Here is an example:
//! IMPORTANT: Always use absolute path to DLL
auto pathToNVIGICore = std::filesystem::path(myPathToNVIGISDK) / L"nvigi.core.framework.dll";
if(nvigi::security::verifyEmbeddedSignature(pathToNVIGICore.wstring().c_str()))
{
//! Safe to load core and initialize NVIGI
//! IMPORTANT: Always use absolute path to DLL
HMODULE lib = LoadLibraryW(pathToNVIGICore.wstring().c_str());
}
IMPORTANT: Failure to check for digital signature on
nvigi.core.framework.dll
can result in executing potentially dangerous code with wide range of consequences.
As mentioned above, nvigi.core.framework.dll
with NOT check any signatures on 3rd party dependencies. This is a security vulnerability and must be addressed by the host application either by enforcing CRC checks on all DLLs or some other method which ensures that libraries haven’t been tampered with before loading them.
Elevated Privileges
If the host process is running with elevated privileges NVIGI core will attempt to downgrade some of them to mitigate security risks. If this behavior is not desirable it can be overridden by setting PreferenceFlags::eDisablePrivilegeDowngrade
flag.
IMPORTANT: Please note that when opting out from the privilege downgrade the host application is taking over the responsibility for any security breaches that might occur and in this scenario, it is highly recommended to install the NVIGI bits in secure location which can be modified only by admin users.
Here is the list of privileges in question:
SE_LOAD_DRIVER_NAME
SE_DEBUG_NAME
SE_TCB_NAME
SE_ASSIGNPRIMARYTOKEN_NAME
SE_SHUTDOWN_NAME
SE_BACKUP_NAME
SE_RESTORE_NAME
SE_TAKE_OWNERSHIP_NAME
SE_IMPERSONATE_NAME
NOTE: This is related only to Windows platform
Core API
The core API is rather minimalistic and located in the nvigi.h
header.
VERY IMPORTANT: Always make sure to securely load
nvigi.core.framework.dll
before calling any NVIGI APIs (see the security section above)
Initialization
//! Initializes the NVIGI framework
//!
//! Call this method when your application is initializing
//!
//! @param pref Specifies preferred behavior for the NVIGI framework (NVIGI will keep a copy)
//! @param pluginInfo Optional pointer to data structure containing information about plugins, user system
//! @param sdkVersion Current SDK version
//! @returns nvigi::kResultOk if successful, error code otherwise (see nvigi_result.h for details)
//!
//! This method is NOT thread safe.
NVIGI_API nvigi::Result nvigiInit(const nvigi::Preferences &pref, nvigi::PluginAndSystemInformation** pluginInfo = nullptr, uint64_t sdkVersion = nvigi::kSDKVersion);
This function initializes NVIGI SDK based on the provided preferences. It enumerates available plugins and, if requested, can report back detailed information on what plugins are available, which are compatible with the user’s system and which are not, what are the basic requirements for each plugin etc. Here is an example:
nvigi::Preferences pref{};
pref.logLevel = nvigi::LogLevel::eDefault; // eVerbose for more detailed log
pref.showConsole = true; // false when shipping
pref.numPathsToPlugins = count(myPathsToNVIGIPlugins);
//! NOTE: Plugins and their custom dependencies can be split up across different locations. Shared dependencies must be in one location (see below)
pref.utf8PathsToPlugins = myPathsToNVIGIPlugins;
//! NOTE: Location for shared dependencies, if not provided NVIGI will assume that there are NO shared dependencies only custom ones and they are all next to their respective plugins.
pref.utf8PathToDependencies = myPathToNVIGISharedPluginDependencies;
pref.logMessageCallback = myCallback; // OPTIONAL
pref.utf8PathToLogsAndData = myPathToLogs; // OPTIONAL but highly recommended, much easier to track down any issues
// Optional info about system and plugins
nvigi::PluginAndSystemInformation* info{};
if(NVIGI_FAILED(result, nvigiInit(pref, &info, nvigi::kSDKVersion)))
{
// Handle error
}
Note that not all plugins need to be enumerated here, later on when requesting interfaces it is possible to provide additional paths to search.
IMPORTANT: NVIGI SDK can come in various configurations (debug, release, production etc.) which can contain
different 3rd party dependencies
. To avoid runtime issues it is absolutely essential to use correct set of NVIGI plugins combined with matching set of 3rd party dependencies - do NOT mix debug, release etc.
Shutdown
Once NVIGI is no longer needed the following API needs to be called to release any used resources and unload any plugins which might be resident in memory:
//! Shuts down the NVIGI module
//!
//! Call this method when your application is shutting down.
//!
//! @returns nvigi::kResultOk if successful, error code otherwise (see nvigi_result.h for details)
//!
//! This method is NOT thread safe.
NVIGI_API nvigi::Result nvigiShutdown();
Interfaces
As mentioned in the introduction, each plugin is assigned a unique identifier and it must implement at least one interface which is represented as a typed and versioned structure. Host application requests an interface which is needed to perform specific tasks and releases it once it is no longer needed. Here is the API which loads an interface:
//! Loads an interface for a specific NVIGI feature
//!
//! Call this method when specific interface is needed.
//!
//! NOTE: Interfaces are reference counted so they all must be released before underlying plugin is released.
//!
//! @param feature Specifies feature which needs to provide the requested interface
//! @param interfaceType Type of the interface to obtain
//! @param interfaceVersion Minimal version of the interface to obtain
//! @param interface Pointer to the interface
//! @param utf8PathToPlugin Optional path to a new plugin which provides this interface
//! @returns nvigi::kResultOk if successful, error code otherwise (see nvigi_result.h for details)
//!
//! This method is NOT thread safe.
NVIGI_API nvigi::Result nvigiLoadInterface(nvigi::PluginID feature, const nvigi::UID& interfaceType, uint32_t interfaceVersion, void** _interface, const char* utf8PathToPlugin);
It is important to emphasize that this API allows host to provide an additional path to a plugin which implements this interface. In other words, not all plugin locations need to be known when nvigiInit
is called.
NOTE: This API will load and make resident in memory the shared library which matches the
PluginID
(unless it is already loaded due to earlier request for an interface it implements)
It is highly recommended to use the templated helpers nvigiGetInterface
or nvigiGetInterfaceDynamic
depending on the linking option used in the host application:
//! Helper method when statically linking NVIGI framework
//!
template<typename T>
inline nvigi::Result nvigiGetInterface(nvigi::PluginID feature, T** _interface)
//! Helper method when dynamically loading NVIGI framework
//!
template<typename T>
inline nvigi::Result nvigiGetInterfaceDynamic(nvigi::PluginID feature, T** _interface, PFun_nvigiLoadInterface* func)
For example:
nvigi::IGeneralPurposeTransformer* igpt{};
// Static linking, as an example requesting interface from ggml cuda backend
if(NVIGI_FAILED(result, nvigiGetInterface(nvigi::plugin::gpt::ggml::cuda::kId, &igpt)))
{
// Handle error
}
// Dynamic `nvigi.core.framework` loading, again as an example requesting interface from ggml cuda backend
if(NVIGI_FAILED(result, nvigiGetInterfaceDynamic(nvigi::plugin::gpt::ggml::cuda::kId, &igpt, nvigiLoadInterfaceFunction)))
{
// Handle error
}
When certain interface is no longer needed, it should to be unloaded so that the underlying shared library which implements it can be released. This is achieved with the following API:
//! Unloads an interface for a specific NVIGI feature
//!
//! Call this method when specific interface is no longer needed
//!
//! @param feature Specifies feature which provided the interface
//! @param interface Pointer to the interface
//! @returns IResult interface with code nvigi::kResultOk if successful, error code otherwise (see nvigi_result.h for details)
//!
//! This method is NOT thread safe.
NVIGI_API nvigi::Result nvigiUnloadInterface(nvigi::PluginID feature, void* _interface);
NOTE: Interfaces are reference counted so the underlying shared library will be released only when ALL references to the requested interface(s) are released.
Validation
Once successfully initialized the optional nvigi::PluginAndSystemInformation
, if provided as shown in the above section when calling nvigiInit
, contains useful information which can be used to determine if specific plugin and or interface is available. NVIGI comes with various helpers which can be used as shown below:
Check If Specific Plugin Is Supported
//! Replace $name::$backend::$api as appropriate
if(NVIGI_FAILED(result, nvigi::getPluginStatus(info, nvigi::plugin::$name::$backend::$api::kId)))
{
// Not supported, check the following sections to find out how to detect min spec for OS, drivers etc.
}
If plugin in question is not supportedt the following functions can be used to obtain more details:
Check For Minimum Required OS Version
//! Replace $name::$backend::$api as appropriate
nvigi::Version version;
if(NVIGI_FAILED(result, nvigi::getPluginRequiredOSVersion(info, nvigi::plugin::$name::$backend::$api::kId, version)))
{
// Not supported, check the version
}
Check For Minimum Required Adapter Driver Version
//! Replace $name::$backend::$api as appropriate
nvigi::Version version;
if(NVIGI_FAILED(result, nvigi::getPluginRequiredAdapterDriverVersion(info, nvigi::plugin::$name::$backend::$api::kId, version)))
{
// Not supported, check the version
}
Check For Minimum Required Adapter Vendor
//! Replace $name::$backend::$api as appropriate
nvigi::VendorId vendor;
if(NVIGI_FAILED(result, nvigi::getPluginRequiredAdapterVendor(info, nvigi::plugin::$name::$backend::$api::kId, vendor)))
{
// Not supported, check the vendor
}
Check For Minimum Required Adapter Architecture
//! Replace $name::$backend::$api as appropriate
uint32_t arch; // 0 indicates any architecture is fine
if(NVIGI_FAILED(result, nvigi::getPluginRequiredAdapterArchitecture(info, nvigi::plugin::$name::$backend::$api::kId, arch)))
{
// Not supported, check the architecture number
}
If plugin in question is supported, next we can query if specific interface we are interested in is supported/implemented by the plugin:
Check If Specific Plugin Exports An Interface
//! Replace $name::$backend::$api and $some_interface_name as appropriate
if(NVIGI_FAILED(result, nvigi::isPluginExportingInterface(info, nvigi::plugin::$name::$backend::$api::kId, nvigi::$some_interface_name)))
{
// Not supported, check the result
}
IMPORTANT: At this point NO SHARED LIBRARIES (PLUGINS) ARE LOADED NOR RESIDENT IN MEMORY, only upon an explicit request for an interface implemented by a specific plugin will that plugin be loaded.
NVIGI and D3D Wrappers (e.g. Streamline)
Care should be taken when integrating NVIGI into an existing application that is also using a D3D object wrapper like Streamline. The queue/device parameters passed to NVIGI must be the native objects, not the app-level wrappers. In the case of Streamline, this means using slGetNativeInterface
to retrieve the base interface object before passing it to NVIGI.
3rd Party Dependencies
EXTREMELY IMPORTANT PLEASE READ: If host application is using the exact same dependency as NVIGI but different version this can lead to runtime issues and random crashes. Please contact NVIDIA do discuss possible solutions.
NVIGI SDK can come in various configurations (debug, release, production) which can contain different 3rd party dependencies.
To avoid runtime issues please make sure that:
NVIGI shared dependencies (CUDA, tensorRT etc.) are stored in one location pointed by the
nvigi::Preferences::utf8PathToDependencies
parameter.Correct set of NVIGI plugins is combined with matching set of 3rd party dependencies (debug and release dependencies are NOT mixed up etc.)
nvigi::Preferences::utf8PathsToPlugins
andnvigi::Preferences::utf8PathToDependencies
always point to the appropriate locations based on the build configuration.On Windows,
AddDLLDirectory
or similar APIs are NOT used to manage NVIGI paths implicitly - this prevents NVIGI plugins from obtaining the location of their dependencies.On Linux,
LD_LIBRARY_PATH
includes path(s) to NVIGI dependencies before host application starts
In addition to the above, please note the following:
nvigiInit
andnvigiLoadInterface
are NOT thread safe and they will TEMPORARILY modify DLL search path based on the information provided by the host.Host application should NOT do any DLL related operations while the above mentioned APIs are running without an explicit synchronization
If
nvigi::Preferences::utf8PathToDependencies
is NOT provided NVIGI will assume that there are NO shared dependencies and any plugin specific dependencies all MUST be next to their respective plugin(s).Individual plugins might need the
nvigi::Preferences::utf8PathToDependencies
to dynamically load their own dependencies and validate digital signatures on themIf plugin dependency is NOT located in
nvigi::Preferences::utf8PathsToPlugins
ornvigi::Preferences::utf8PathToDependencies
that plugin will fail to load to avoid DLL ABI issues.
Runtime Scenarios With Dependencies
NVIGI and host application do NOT share any dependencies or share a subset of identical dependencies - same name(s), compatible ABI(s)
No runtime issues but keep in mind that NVIGI expects to load its dependencies from
nvigi::Preferences::utf8PathsToPlugins
ornvigi::Preferences::utf8PathToDependencies
NVIGI and host application share a subset of dependencies with the same name(s) but incompatible ABI(s)
Unless each API exported by the dependencies in question is accessed explicitly using
HMODULE
andGetProcAddress
there can be random crashes and instabilityPreferably, the host application should rename the dependencies in question to avoid ABI collision(s)
Alternatively, either NVIGI plugin(s) or the host app should be recompiled to use the same version of shared dependencies
Plugin And Dependencies Deployment Examples
Everything In One Location:
// This is a very common and valid way to distribute NVIGI with your application
$some_path/nvigi
│
├──nvigi.core.framework.dll
├──nvigi.pugin.gpt.ggml.cuda.dll
├──nvigi.pugin.asr.ggml.cuda.dll
├──cudart64_12.dll
├──ggml_40f25557_cuda.dll
└──zlib1.dll
NOTE: In this scenario both
nvigi::Preferences::utf8PathsToPlugins
andnvigi::Preferences::utf8PathToDependencies
point to the same location -$some_path/nvigi
Split In Multiple Locations
Core and shared dependencies together
// Care must be taken not to duplicate plugins or dependencies in this setup
$some_path/nvigi/core
│
├──nvigi.core.framework.dll
└──cudart64_12.dll // Shared by many plugins so must be in one location
NOTE: Here
nvigi::Preferences::utf8PathToDependencies
points to$some_path/nvigi/core
butnvigi::Preferences::utf8PathsToPlugins
points to different locations (see below)
GPT plugin(s)
// Care must be taken not to duplicate plugins or dependencies in this setup
$some_path/nvigi/gpt
│
├──nvigi.pugin.gpt.ggml.cuda.dll
└──tensorRT_10.dll // This is OK as long as other plugins don't use it, this is version 10
ASR plugin(s)
// Care must be taken not to duplicate plugins or dependencies in this setup
$some_path/nvigi/asr
│
├──nvigi.pugin.asr.ggml.cuda.dll
└──tensorRT_11.dll // This is OK as long as other plugins don't use it, this is version 11
NOTE: In the above example, if a new plugin is introduced and uses
tensorRT_10.dll
for example, this shared library now must move to$some_path/nvigi/core
otherwise we have invalid configuration with duplicated dependency.
Invalid Configurations
IMPORTANT: Note that in this scenario shared library is duplicated across different locations which is NOT allowed.
$some_path/nvigi/core
│
├──nvigi.core.framework.dll
└──cudart64_12.dll
GPT plugin(s)
$some_path/nvigi/gpt
│
├──nvigi.pugin.gpt.ggml.cuda.dll
└──cudart64_12.dll // !!! duplicates NOT allowed
ASR plugin(s)
$some_path/nvigi/asr
│
├──nvigi.pugin.asr.ggml.cuda.dll
└──cudart64_12.dll // !!! duplicates NOT allowed
Collisions With The Host
In this example, host uses AddDllDirectory
to include $some_path/my_app_dependencies
in the OS search path or loads libz.dll
prior to initializing NVIGI which then results in NVIGI using incorrect libz.dll
which can lead to instability or incorrect runtime behavior.
$some_path/nvigi/core
│
├──nvigi.core.framework.dll
├──cudart64_12.dll
└──libz.dll // !!! NVIGI version
$some_path/my_app_dependencies
│
└──libz.dll // !!! HOST version
NOTE: If similar issue occurs while deploying your application please contact NVIDIA