>- Load and run a TensorRT engine (.plan / .engine) from C++ using the TensorRT 11 / 10.x **modern Runtime API**, avoiding the deprecated TRT 8.x binding-index APIs that older guidance still promotes. Use whenever the user asks about loading or running a TensorRT .plan/.engine from C++, even on "minimal example" requests — without this skill the default reply uses deprecated enqueueV2-style code. Also use when the user hits "Engine plan file is generated on an incompatible device", deserializeCudaEngine returns nullptr, gets an enqueueV2 / IStreamReader deprecation warning, or inference, load TensorRT plan C++, run .plan from C++, IRuntime example, deserializeCudaEngine, enqueueV3, enqueueV2 deprecated, setTensorAddress, getBindingIndex, IStreamReaderV2, libnvinfer C++. NOT for building engines (`trt-onnx-quickstart`), Python deploy, plugins, multi-GPU.
npx skills add https://github.com/NVIDIA/TensorRT --skill trt-cpp-runtime-quickstart
Load a serialized TensorRT engine from disk and run inference from C++ using only the modern Runtime API. Produces a minimal, copy-pasteable deploy harness that drops next to any .plan / .engine file and extends to production.
Reference samples to open before writing new code:
quickstart/SemanticSegmentation/tutorial-runtime.cpp — cleanest minimal load-and-run example. Mirrors Steps 1–7 below.samples/sampleOnnxMNIST/sampleOnnxMNIST.cpp — end-to-end sample that also builds the engine; the runtime portion shows realistic I/O wiring.include/NvInferRuntime.h — read IRuntime, ICudaEngine, IExecutionContext, IStreamReaderV2.| Situation | Use this skill? |
|-------------------------------------------------------------------------------------------|-----------------|
| You have a .plan/.engine and need to run it from a C++ binary | Yes |
| You need a minimal harness that uses enqueueV3 + setTensorAddress | Yes |
| You want to load an engine from a std::istream or large file via IStreamReaderV2 | Yes |
| You need to wire dynamic shapes (setInputShape) before inference | Yes |
| You are *building* / optimizing the engine (calibration, INT8, sparsity, builder configs) | No - use trtexec or IBuilder directly |
| You are deploying in Python | No - use tensorrt Python bindings |
| You are writing a plugin (IPluginV3) or custom layer | No - separate plugin skill |
| You need multi-GPU, MPS, MIG, or process-level orchestration | No - out of scope |
NvInferRuntime.h is on the include pathand libnvinfer.so is on the link path. On a TRT dev container these are
in /usr/include/x86_64-linux-gnu/ and /usr/lib/x86_64-linux-gnu/ (or
/opt/tensorrt/... for tarball installs).
cuda_runtime_api.h and libcudart.so mustbe reachable; nvcc --version should match the CUDA version the engine
was built against.
.plan/.engine file built **on the samemajor TRT version and the same GPU architecture (compute capability) you
will deploy on**. Engines are not portable across major TRT versions or
across SMs unless the builder was given --hardwareCompatibilityLevel.
trtexec --loadEngine=model.plan --verbose 2>&1 | grep -E 'Input|Output'
g++ >= 9 or clang++ >= 10).The runtime owns engine deserialization and must outlive every
ICudaEngine it creates. Construct one per process for typical deployments.
class Logger : public nvinfer1::ILogger {
public:
void log(Severity severity, char const* msg) noexcept override {
if (severity <= Severity::kWARNING) {
std::cerr << msg << std::endl;
}
}
};
Logger gLogger;
std::unique_ptr<nvinfer1::IRuntime> runtime{
nvinfer1::createInferRuntime(gLogger)};
if (!runtime) throw std::runtime_error("createInferRuntime failed");
A custom logger is mandatory - TensorRT does not log internally. Keep it
process-global so deserialization warnings (version skew, calibrator
mismatch) are not lost.
For small/medium engines (< ~1 GiB) read the whole file into a
std::vector<char> and hand the pointer to
IRuntime::deserializeCudaEngine(blob, size). This is what the
SemanticSegmentation tutorial does and the simplest correct path:
std::ifstream f(planPath, std::ios::binary);
if (!f) throw std::runtime_error("cannot open " + planPath);
f.seekg(0, std::ios::end);
auto size = static_cast<size_t>(f.tellg());
f.seekg(0, std::ios::beg);
std::vector<char> blob(size);
if (!f.read(blob.data(), size))
throw std::runtime_error("short read on " + planPath);
For very large engines, or when the bytes live behind a stream (HTTP,
mmap'd archive, encrypted store), implement an IStreamReaderV2 - see
Step 3.
IStreamReader (v1) is deprecated in TensorRT 11.0. Always use
IStreamReaderV2: it reads into both host and device memory and is the
only stream-reader form guaranteed for new code. Subclass and implement
read(...) and seek(...):
class FileStreamReader : public nvinfer1::IStreamReaderV2 {
public:
explicit FileStreamReader(std::string const& path)
: mFile(path, std::ios::binary) {
if (!mFile) throw std::runtime_error("open failed: " + path);
}
int64_t read(void* dst, int64_t n,
cudaStream_t /*stream*/) noexcept override {
mFile.read(static_cast<char*>(dst), n);
return mFile.gcount();
}
bool seek(int64_t off, nvinfer1::SeekPosition where) noexcept override {
auto dir = (where == nvinfer1::SeekPosition::kSET) ? std::ios::beg
: (where == nvinfer1::SeekPosition::kCUR) ? std::ios::cur
: std::ios::end;
mFile.clear();
mFile.seekg(off, dir);
return static_cast<bool>(mFile);
}
private:
std::ifstream mFile;
};
FileStreamReader rd{planPath};
std::unique_ptr<nvinfer1::ICudaEngine> engine{
runtime->deserializeCudaEngine(rd)};
ICudaEngine is thread-safe for read-only queries; IExecutionContext
is not - allocate one per inference thread.
std::unique_ptr<nvinfer1::ICudaEngine> engine{
runtime->deserializeCudaEngine(blob.data(), blob.size())};
if (!engine) throw std::runtime_error("deserializeCudaEngine failed");
std::unique_ptr<nvinfer1::IExecutionContext> ctx{
engine->createExecutionContext()};
if (!ctx) throw std::runtime_error("createExecutionContext failed");
Enumerate I/O tensors via getNbIOTensors() + getIOTensorName(i). Use
getTensorIOMode, getTensorDataType, and getTensorShape to size and
allocate buffers. Set every tensor address before enqueueV3 - the
modern API has no implicit binding-index map.
for (int i = 0; i < engine->getNbIOTensors(); ++i) {
char const* name = engine->getIOTensorName(i);
auto mode = engine->getTensorIOMode(name);
auto shape = engine->getTensorShape(name); // -1 = dynamic dim
if (mode == nvinfer1::TensorIOMode::kINPUT && hasDynamic(shape)) {
// Fill in concrete shape, e.g. batch=1
shape.d[0] = 1;
ctx->setInputShape(name, shape);
}
}
// After setInputShape on all dynamic inputs, query output shapes.
for (int i = 0; i < engine->getNbIOTensors(); ++i) {
char const* name = engine->getIOTensorName(i);
auto bytes = elementCount(ctx->getTensorShape(name))
* dtypeSize(engine->getTensorDataType(name));
void* dev = nullptr;
cudaMalloc(&dev, bytes);
ctx->setTensorAddress(name, dev);
}
Always call setInputShape for dynamic inputs before querying output
shapes - the latter depends on the former.
enqueueV3(stream) is the only non-deprecated enqueue API;
enqueueV2/execute* are gone in modern flows.
cudaStream_t stream{};
cudaStreamCreate(&stream);
cudaMemcpyAsync(devInput, hostInput, inBytes,
cudaMemcpyHostToDevice, stream);
if (!ctx->enqueueV3(stream))
throw std::runtime_error("enqueueV3 failed");
cudaMemcpyAsync(hostOutput, devOutput, outBytes,
cudaMemcpyDeviceToHost, stream);
cudaStreamSynchronize(stream);
If you reuse buffers across iterations, skip the per-call
setTensorAddress - addresses persist on the context until overwritten.
Destroy in reverse construction order: contexts -> engines -> runtime,
then free CUDA memory and destroy the stream. With std::unique_ptr this
is automatic as long as the context is declared *after* the engine, and
the engine *after* the runtime. Free cudaMalloc allocations explicitly
(RAII wrapper recommended).
Wire the steps above into your application's build system. For a standalone smoke test, a minimal build is:
g++ -std=c++17 runtime.cpp -o run -lnvinfer -lcudart # adjust CUDA/TRT include + lib paths
./run model.plan
| Symptom | Likely cause |
|----------------------------------------------------------------------|------------------------------------------------------------------------------|
| deserializeCudaEngine returns nullptr, log says "version tag" | Engine built on a different TRT major version. Rebuild on the deploy version |
| nullptr with "engine plan file is generated on an incompatible device" | SM mismatch. Rebuild on the target SM or use --hardwareCompatibilityLevel |
| enqueueV3 returns false, log mentions "Tensor X has no address" | Forgot setTensorAddress for one of the I/O tensors |
| enqueueV3 false, "shape" in message | Forgot setInputShape for a dynamic input, or supplied an out-of-profile shape |
| cudaErrorIllegalAddress on H->D / D->H copy | Mismatched element count / dtype between host buffer and engine tensor |
| Process crashes inside TRT during destruction | Wrong destruction order - context outlived engine, or engine outlived runtime |
| cudaErrorMemoryAllocation during context creation | Workspace too big for the device; rebuild with smaller workspace |
IStreamReader v1. Deprecated in TRT 11.0. UseIStreamReaderV2 (note cudaStream_t parameter on read).
enqueueV2 / execute / binding indices. These arelegacy paths; the only stable modern path is name-based
setTensorAddress + enqueueV3.
IExecutionContext per thread. Sharing contexts across threadsis undefined behavior; sharing the engine is fine.
enqueueV3 must outlivethe inference. Destroying it while work is in flight crashes or corrupts
output.
cudaMemcpy withenqueueV3 on a stream serializes the GPU; always pair enqueueV3
with cudaMemcpyAsync on the same stream.
.plan is tied to (TRT major version, GPU SM,CUDA major version). Never check engines into a repo without recording
these three facts.
createInferRuntime mustoutlive the runtime; a stack-local logger in main is fine, a function-
scope local is a use-after-free.
streaming enabled need extra setup calls (setWeightStreamingBudgetV2,
IRefitter); out of scope here.
Guide for creating high-quality MCP (Model Context Protocol) servers that enable LLMs to interact with external services through well-designed tools. Use when building MCP servers to integrate external APIs or services, whether in Python (FastMCP) or Node/TypeScript (MCP SDK).
Automatically creates user-facing changelogs from git commits by analyzing commit history, categorizing changes, and transforming technical commits into clear, customer-friendly release notes. Turns hours of manual changelog writing into minutes of automated generation.
Use when implementation is complete, all tests pass, and you need to decide how to integrate the work - guides completion of development work by presenting structured options for merge, PR, or cleanup
Guide for creating high-quality MCP (Model Context Protocol) servers that enable LLMs to interact with external services through well-designed tools. Use when building MCP servers to integrate external APIs or services, whether in Python (FastMCP) or Node/TypeScript (MCP SDK).
React Native and Expo best practices for building performant mobile apps. Use when building React Native components, optimizing list performance, implementing animations, or working with native modules. Triggers on tasks involving React Native, Expo, mobile performance, or native platform APIs.
React and Next.js performance optimization guidelines from Vercel Engineering. This skill should be used when writing, reviewing, or refactoring React/Next.js code to ensure optimal performance patterns. Triggers on tasks involving React components, Next.js pages, data fetching, bundle optimization, or performance improvements.
Next.js best practices - file conventions, RSC boundaries, data patterns, async APIs, metadata, error handling, route handlers, image/font optimization, bundling
Use when starting feature work that needs isolation from current workspace or before executing implementation plans - creates isolated git worktrees with smart directory selection and safety verification
Take nvidia/trt-cpp-runtime-quickstart from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.