Creating a Customized Plugin
This document guides you through the process of creating a customized NVIGI GPT plugin, based on the original GPT plugin from the SDK.
> IMPORTANT: Although the document uses the GPT plugin as an example, the high-level steps also apply to other plugins.
Prerequisites
Familiarity with the basic plugin project setup described in https://github.com/NVIDIA-RTX/NVIGI-Plugins. Experience with successfully building
nvigi.plugin.gpt.ggml.$backend
is a plus.
NOTE:
$backend
can be any of the available backends like for examplecuda
,cpu
etc.
Steps
This section presents one recommended approach to creating a new GPT plugin. Once you understand the project setup process, you can freely make customized changes without strictly following all the steps.
In the following instructions, we follow the directory setup described here.
<SDK_PLUGINS>
should be replaced with the full path to your NVIGI SDK directory (the path of this README).
1. Add a New Plugin Project
Choose a name for your new plugin, such as
mygpt
.Prepare the source files for the new plugin by copying from an existing project.
Duplicate the folder containing the source code of the original plugin (
<SDK_PLUGINS>/source/plugins/nvigi.gpt
) and rename it to<SDK_PLUGINS>/source/plugins/nvigi.mygpt
.(Optional) Enter the new source folder and remove unused backend code (e.g.,
onnxgenai
andrest
).(Optional) Rename the source files with your new plugin name by replacing
gpt
withmygpt
.For example, assuming that we want to modify the GGML backend, the source tree should now have the following structure:
<SDK_PLUGINS>/source/plugins |-- nvigi.mygpt | |-- ggml | | |-- premake.lua | | |-- mygpt.h | | |-- mygpt.cpp | | |-- ... // Other source files | |-- nvigi_mygpt.h | |-- // "onnxgenai" and "rest" are removed |-- ... // Other plugins
Update the setup scripts to include the new source files.
Update
NVIGI-Plugins/source/plugins/nvigi.mygpt/ggml/premake.lua
by renaming it with the new plugin name (e.g., renamegroup "plugins/gpt"
togroup "plugins/mygpt"
andproject "nvigi.plugin.gpt.ggml.$backend"
toproject "nvigi.plugin.mygpt.ggml.$backend"
).Update
NVIGI-Plugins/premake.lua
to include the project premake file by addinginclude("source/plugins/nvigi.mygpt/ggml/premake.lua")
at the end.
Run
setup.bat
to reflect the changes.Open
NVIGI-Plugins/_project/vs2022/nvigi.sln
and check the Solution Explorer - a new projectnvigi.plugin.mygpt.ggml.{$backend}
should be added underplugins.mygpt
.
2. Update Names and GUIDs in Source Files
Find the NVIGI utility
nvigi.tool.utils.exe
located inNVIGI-Core\bin\Release_x64
Open terminal and run
nvigi.tool.utils.exe --plugin nvigi.plugin.mygpt.ggml.$backend
(make sure to replacemygpt
and$backend
accordingly)Open
nvigi_mygpt.h
. This is a public header and will be provided to apps.Remove the namespaces of unused backends (e.g.,
namespace cloud::rest
).Update the plugin GUIDs in this file by pasting the code provided by the NVIGI utilities tool:
// Before // constexpr PluginID kId = { {0x54bbefba, 0x535f, 0x4d77,{0x9c, 0x3f, 0x46, 0x38, 0x39, 0x2d, 0x23, 0xac}}, 0x4b9ee9 }; // {54BBEFBA-535F-4D77-9C3F-4638392D23AC} [nvigi.plugin.gpt.ggml.$backend] // After constexpr PluginID kId = { 0x576e1145, 0xf790, 0x46b4, { 0xbf, 0x9a, 0xde, 0x88, 0x9, 0x46, 0x3a, 0x15 } }; // {576E1145-F790-46B4-BF9A-DE8809463A15} [nvigi.plugin.mygpt.ggml.$backend]
Open
mygpt.cpp
.Include the new headers.
// Before #include "source/plugins/nvigi.gpt/nvigi_gpt.h" #include "source/plugins/nvigi.gpt/ggml/gpt.h" // After #include "source/plugins/nvigi.mygpt/nvigi_mygpt.h" #include "source/plugins/nvigi.mygpt/ggml/mygpt.h"
Update function
getFeatureId()
to return the new plugin IDreturn plugin::mygpt::ggml::$backend::kId;
.
(Optional) Rename namespaces and variables in
mygpt.h/cpp
. These source files will not be visible to apps.
3. (Optional) Update Your App to Use the New Plugin
If you already have an app using the original GPT plugin “nvigi.plugin.gpt.ggml.cuda”, follow these steps to replace it with the new plugin.
Replace the public header
#include "nvigi_gpt.h"
with#include "nvigi_mygpt.h"
.Replace the plugin ID
nvigi::plugin::gpt::ggml::$backend::kId
withnvigi::plugin::mygpt::ggml::$backend::kId
when getting the interface.Rename the model folder from
nvigi.models\nvigi.plugin.gpt.ggml
tonvigi.models\nvigi.plugin.mygpt.ggml
so the new plugin can find and load models.(Troubleshooting) If the new plugin DLL fails to load, start with checking if you are using the correct set of dependent DLLs, like ggml and llama.cpp and CUDA runtime.
4. Customize Your Plugin!
You have completed the setup and can now start modifying the plugin if you have plans in mind. If not, the example below shows how to add a new parameter grammar
to GPTSamplerParameters
.
The example demonstrates the steps to add the grammar
parameter. Note that this parameter may be added in future versions. Similar steps can be applied to other parameters like GPTRuntimeParameters
.
Open
nvigi_mygpt.h
and navigate toGPTSamplerParameters
, then add the parameter at the end and update the version.// Before struct alignas(8) GPTSamplerParameters { NVIGI_UID(UID({ 0xfd183aa9, 0x6e50, 0x4021,{0x9b, 0x0e, 0xa7, 0xae, 0xab, 0x6e, 0xef, 0x49} }), kStructVersion1) // ... v1 parameters //! v2+ members go here, remember to update the kStructVersionN in the above NVIGI_UID macro! }; // After struct alignas(8) GPTSamplerParameters { NVIGI_UID(UID({ 0xfd183aa9, 0x6e50, 0x4021,{0x9b, 0x0e, 0xa7, 0xae, 0xab, 0x6e, 0xef, 0x49} }), kStructVersion2) // ... v1 parameters //! v2+ members go here, remember to update the kStructVersionN in the above NVIGI_UID macro! const char* grammar{}; };
Open
mygpt.cpp
and navigate to functionggmlEvaluate()
.// Before instance->params.sparams.ignore_eos = sampler->ignoreEOS; // After instance->params.sparams.ignore_eos = sampler->ignoreEOS; if (sampler->getVersion() >= 2) { instance->params.sparams.grammar = sampler->grammar ? sampler->grammar : ""; }
Update your app to set the new parameter.
5. Adding New Structures Or Interfaces
If you need to add completely new structure or new interface (structure with functions defining an API) please follow these steps:
Find the NVIGI utility
nvigi.tool.utils.exe
located inNVIGI-Core\bin\Release_x64
Open terminal and run
nvigi.tool.utils.exe --interface MyData
(make sure to replaceMyData
accordingly)Paste the provided code into your
nvigi_mygpt.h
header like for example:
NOTE: Same command is used for data and interfaces since they are simply typed and versioned structures in NVIGI terminology
//! Interface 'MyData'
//!
//! {45DF99CE-F5B8-4D66-90EE-FADEEFBBF713}
struct alignas(8) MyData
{
MyData() { };
NVIGI_UID(UID({0x45df99ce, 0xf5b8, 0x4d66,{0x90, 0xee, 0xfa, 0xde, 0xef, 0xbb, 0xf7, 0x13}}), kStructVersion1)
//! v1 members go here, please do NOT break the C ABI compatibility:
//! * do not use virtual functions, volatile, STL (e.g. std::vector) or any other C++ high level functionality
//! * do not use nested structures, always use pointer members
//! * do not use internal types in _public_ interfaces (like for example 'nvigi::types::vector' etc.)
//! * do not change or move any existing members once interface has shipped
//! v2+ members go here, remember to update the kStructVersionN in the above NVIGI_UID macro!
};
NVIGI_VALIDATE_STRUCT(MyData)
Add new data members to your structure
6. Using New Structures Or Interfaces
6.1 Data Structures
If your new structure contains only data simply chain it to the creation or runtime properties and then use findStruct
in either createInstance
or evaluate
calls.
host app
// Default GPT creation parameters
GPTCreationParameters creationParams{};
// Fill in the creation params
MyData myData{};
// Fill in your data
myData.someData = 1;
// Chain it to the creation parameters
if(NVIGI_FAILED(error,creationParams.chain(myData)))
{
// handle error
}
mygpt.cpp
nvigi::Result ggmlCreateInstance(const nvigi::NVIGIParameter* _params, nvigi::InferenceInstance** _instance)
{
auto myData = findStruct<MyData>(_params);
if(myData)
{
// Do something with v1 data
// If you modify your structure after having shipped your app, make sure to bump the version and check
if(myData->getVersion() >= kStructVersion2)
{
// Do something with v2 data
}
// And so on ...
}
...
}
6.2 Interface Structures
If your new structure contains and API then this new interface must be exported by your modified plugin in order to be used on the host side. Here is an example:
nvigi_mygpt.h
//! Interface 'IMyInterface'
//!
//! {45DF99CE-F5B8-4D66-90EE-FADEEFBBF713}
struct alignas(8) IMyInterface
{
IMyInterface() { };
NVIGI_UID(UID({0x45df99ce, 0xf5b8, 0x4d66,{0x90, 0xee, 0xfa, 0xde, 0xef, 0xbb, 0xf7, 0x13}}), kStructVersion1)
//! v1 members go here, please do NOT break the C ABI compatibility:
nvigi::Result (*someFunction)();
//! * do not use virtual functions, volatile, STL (e.g. std::vector) or any other C++ high level functionality
//! * do not use nested structures, always use pointer members
//! * do not use internal types in _public_ interfaces (like for example 'nvigi::types::vector' etc.)
//! * do not change or move any existing members once interface has shipped
//! v2+ members go here, remember to update the kStructVersionN in the above NVIGI_UID macro!
};
NVIGI_VALIDATE_STRUCT(IMyInterface)
mygpt.cpp
// Add new API to the context
struct GPTContext
{
NVIGI_PLUGIN_CONTEXT_CREATE_DESTROY(GPTContext);
void onCreateContext() {};
void onDestroyContext() {};
IMyInterface myapi{};
// other bits
};
namespace mygpt
{
nvigi::Result someFunction()
{
// Implement your function!
}
}
// Now export the API
Result nvigiPluginRegister(framework::IFramework* framework)
{
if (!plugin::internalPluginSetup(framework)) return kResultInvalidState;
auto& ctx = (*mygpt::getContext());
ctx.feature = mygpt::getFeatureId(nullptr);
ctx.myapi.someFunction = mygpt::someFunction;
framework->addInterface(ctx.feature, &ctx.myapi, 0);
...
}
host app
nvigi::mygpt::IMyInterface* iMyApi{};
if(NVIGI_FAILED(error,nvigiGetInterface("nvigi::plugin::mygpt::ggml::$backend::kId", &iMyApi)))
{
// handle error
}
// use your interface v1
iMyApi->someFunction();
// If interface changed and your app can load both old and new plugins always check the version
if(iMyApi->getVersion() >= kStructureVersion2)
{
// safe to use v2 interface members
iMyApi->someFunction2();
}