Skip to content

Commit 82d1dca

Browse files
authored
TensorRT 11.1 OSS Release (#4808)
Signed-off-by: Kevin Chen <kevinch@nvidia.com>
1 parent b8b46f2 commit 82d1dca

242 files changed

Lines changed: 13644 additions & 72396 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 276 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,276 @@
1+
---
2+
name: trt-cpp-runtime-quickstart
3+
description: >-
4+
Load and run a TensorRT engine (.plan / .engine) from C++ using the
5+
TensorRT 11 / 10.x **modern Runtime API**, avoiding the deprecated TRT
6+
8.x binding-index APIs that older guidance still promotes. Use whenever
7+
the user asks about loading
8+
or running a TensorRT .plan/.engine from C++, even on "minimal example"
9+
requests — without this skill the default reply uses deprecated
10+
enqueueV2-style code. Also use when the user hits "Engine plan file
11+
is generated on an incompatible device", deserializeCudaEngine returns
12+
nullptr, gets an enqueueV2 / IStreamReader deprecation warning, or
13+
wants to stream a .plan via IStreamReaderV2. Triggers: TensorRT C++
14+
inference, load TensorRT plan C++, run .plan from C++, IRuntime
15+
example, deserializeCudaEngine, enqueueV3, enqueueV2 deprecated,
16+
setTensorAddress, getBindingIndex, IStreamReaderV2, libnvinfer C++.
17+
NOT for building engines (`trt-onnx-quickstart`), Python deploy,
18+
plugins, multi-GPU.
19+
license: Apache-2.0
20+
metadata:
21+
author: NVIDIA Corporation
22+
version: "1.0"
23+
tags:
24+
- tensorrt
25+
- cpp
26+
- inference
27+
- deployment
28+
- runtime
29+
---
30+
31+
# TensorRT C++ Runtime Deploy
32+
33+
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.
34+
35+
Reference samples to open before writing new code:
36+
37+
- `quickstart/SemanticSegmentation/tutorial-runtime.cpp` — cleanest minimal load-and-run example. Mirrors Steps 1–7 below.
38+
- `samples/sampleOnnxMNIST/sampleOnnxMNIST.cpp` — end-to-end sample that also builds the engine; the runtime portion shows realistic I/O wiring.
39+
- Public headers: `include/NvInferRuntime.h` — read `IRuntime`, `ICudaEngine`, `IExecutionContext`, `IStreamReaderV2`.
40+
41+
## When to Use
42+
43+
| Situation | Use this skill? |
44+
|-------------------------------------------------------------------------------------------|-----------------|
45+
| You have a `.plan`/`.engine` and need to run it from a C++ binary | Yes |
46+
| You need a minimal harness that uses `enqueueV3` + `setTensorAddress` | Yes |
47+
| You want to load an engine from a `std::istream` or large file via `IStreamReaderV2` | Yes |
48+
| You need to wire dynamic shapes (`setInputShape`) before inference | Yes |
49+
| You are *building* / optimizing the engine (calibration, INT8, sparsity, builder configs) | No - use trtexec or `IBuilder` directly |
50+
| You are deploying in Python | No - use `tensorrt` Python bindings |
51+
| You are writing a plugin (`IPluginV3`) or custom layer | No - separate plugin skill |
52+
| You need multi-GPU, MPS, MIG, or process-level orchestration | No - out of scope |
53+
54+
## Prerequisites
55+
56+
1. **TensorRT installed.** Verify `NvInferRuntime.h` is on the include path
57+
and `libnvinfer.so` is on the link path. On a TRT dev container these are
58+
in `/usr/include/x86_64-linux-gnu/` and `/usr/lib/x86_64-linux-gnu/` (or
59+
`/opt/tensorrt/...` for tarball installs).
60+
2. **CUDA toolkit available.** `cuda_runtime_api.h` and `libcudart.so` must
61+
be reachable; `nvcc --version` should match the CUDA version the engine
62+
was built against.
63+
3. **A serialized engine.** A `.plan`/`.engine` file built **on the same
64+
major TRT version and the same GPU architecture (compute capability) you
65+
will deploy on**. Engines are not portable across major TRT versions or
66+
across SMs unless the builder was given `--hardwareCompatibilityLevel`.
67+
4. **The engine's I/O tensor names.** Inspect with:
68+
```bash
69+
trtexec --loadEngine=model.plan --verbose 2>&1 | grep -E 'Input|Output'
70+
```
71+
5. A C++17 compiler (`g++ >= 9` or `clang++ >= 10`).
72+
73+
## Step 1: Create the IRuntime
74+
75+
The runtime owns engine deserialization and must outlive every
76+
`ICudaEngine` it creates. Construct one per process for typical deployments.
77+
78+
```cpp
79+
class Logger : public nvinfer1::ILogger {
80+
public:
81+
void log(Severity severity, char const* msg) noexcept override {
82+
if (severity <= Severity::kWARNING) {
83+
std::cerr << msg << std::endl;
84+
}
85+
}
86+
};
87+
88+
Logger gLogger;
89+
std::unique_ptr<nvinfer1::IRuntime> runtime{
90+
nvinfer1::createInferRuntime(gLogger)};
91+
if (!runtime) throw std::runtime_error("createInferRuntime failed");
92+
```
93+
94+
A custom logger is mandatory - TensorRT does not log internally. Keep it
95+
process-global so deserialization warnings (version skew, calibrator
96+
mismatch) are not lost.
97+
98+
## Step 2: Read the Plan into Memory
99+
100+
For small/medium engines (< ~1 GiB) read the whole file into a
101+
`std::vector<char>` and hand the pointer to
102+
`IRuntime::deserializeCudaEngine(blob, size)`. This is what the
103+
`SemanticSegmentation` tutorial does and the simplest correct path:
104+
105+
```cpp
106+
std::ifstream f(planPath, std::ios::binary);
107+
if (!f) throw std::runtime_error("cannot open " + planPath);
108+
f.seekg(0, std::ios::end);
109+
auto size = static_cast<size_t>(f.tellg());
110+
f.seekg(0, std::ios::beg);
111+
std::vector<char> blob(size);
112+
if (!f.read(blob.data(), size))
113+
throw std::runtime_error("short read on " + planPath);
114+
```
115+
116+
For very large engines, or when the bytes live behind a stream (HTTP,
117+
mmap'd archive, encrypted store), implement an `IStreamReaderV2` - see
118+
Step 3.
119+
120+
## Step 3 (optional): Use IStreamReaderV2 for Streaming Loads
121+
122+
`IStreamReader` (v1) is **deprecated in TensorRT 11.0**. Always use
123+
`IStreamReaderV2`: it reads into both host and device memory and is the
124+
only stream-reader form guaranteed for new code. Subclass and implement
125+
`read(...)` and `seek(...)`:
126+
127+
```cpp
128+
class FileStreamReader : public nvinfer1::IStreamReaderV2 {
129+
public:
130+
explicit FileStreamReader(std::string const& path)
131+
: mFile(path, std::ios::binary) {
132+
if (!mFile) throw std::runtime_error("open failed: " + path);
133+
}
134+
int64_t read(void* dst, int64_t n,
135+
cudaStream_t /*stream*/) noexcept override {
136+
mFile.read(static_cast<char*>(dst), n);
137+
return mFile.gcount();
138+
}
139+
bool seek(int64_t off, nvinfer1::SeekPosition where) noexcept override {
140+
auto dir = (where == nvinfer1::SeekPosition::kSET) ? std::ios::beg
141+
: (where == nvinfer1::SeekPosition::kCUR) ? std::ios::cur
142+
: std::ios::end;
143+
mFile.clear();
144+
mFile.seekg(off, dir);
145+
return static_cast<bool>(mFile);
146+
}
147+
private:
148+
std::ifstream mFile;
149+
};
150+
151+
FileStreamReader rd{planPath};
152+
std::unique_ptr<nvinfer1::ICudaEngine> engine{
153+
runtime->deserializeCudaEngine(rd)};
154+
```
155+
156+
## Step 4: Deserialize and Create an Execution Context
157+
158+
`ICudaEngine` is thread-safe for read-only queries; `IExecutionContext`
159+
is **not** - allocate one per inference thread.
160+
161+
```cpp
162+
std::unique_ptr<nvinfer1::ICudaEngine> engine{
163+
runtime->deserializeCudaEngine(blob.data(), blob.size())};
164+
if (!engine) throw std::runtime_error("deserializeCudaEngine failed");
165+
166+
std::unique_ptr<nvinfer1::IExecutionContext> ctx{
167+
engine->createExecutionContext()};
168+
if (!ctx) throw std::runtime_error("createExecutionContext failed");
169+
```
170+
171+
## Step 5: Wire Tensors with setTensorAddress
172+
173+
Enumerate I/O tensors via `getNbIOTensors()` + `getIOTensorName(i)`. Use
174+
`getTensorIOMode`, `getTensorDataType`, and `getTensorShape` to size and
175+
allocate buffers. **Set every tensor address before `enqueueV3`** - the
176+
modern API has no implicit binding-index map.
177+
178+
```cpp
179+
for (int i = 0; i < engine->getNbIOTensors(); ++i) {
180+
char const* name = engine->getIOTensorName(i);
181+
auto mode = engine->getTensorIOMode(name);
182+
auto shape = engine->getTensorShape(name); // -1 = dynamic dim
183+
if (mode == nvinfer1::TensorIOMode::kINPUT && hasDynamic(shape)) {
184+
// Fill in concrete shape, e.g. batch=1
185+
shape.d[0] = 1;
186+
ctx->setInputShape(name, shape);
187+
}
188+
}
189+
// After setInputShape on all dynamic inputs, query output shapes.
190+
for (int i = 0; i < engine->getNbIOTensors(); ++i) {
191+
char const* name = engine->getIOTensorName(i);
192+
auto bytes = elementCount(ctx->getTensorShape(name))
193+
* dtypeSize(engine->getTensorDataType(name));
194+
void* dev = nullptr;
195+
cudaMalloc(&dev, bytes);
196+
ctx->setTensorAddress(name, dev);
197+
}
198+
```
199+
200+
Always call `setInputShape` for dynamic inputs **before** querying output
201+
shapes - the latter depends on the former.
202+
203+
## Step 6: Run enqueueV3
204+
205+
`enqueueV3(stream)` is the only non-deprecated enqueue API;
206+
`enqueueV2`/`execute*` are gone in modern flows.
207+
208+
```cpp
209+
cudaStream_t stream{};
210+
cudaStreamCreate(&stream);
211+
212+
cudaMemcpyAsync(devInput, hostInput, inBytes,
213+
cudaMemcpyHostToDevice, stream);
214+
if (!ctx->enqueueV3(stream))
215+
throw std::runtime_error("enqueueV3 failed");
216+
cudaMemcpyAsync(hostOutput, devOutput, outBytes,
217+
cudaMemcpyDeviceToHost, stream);
218+
cudaStreamSynchronize(stream);
219+
```
220+
221+
If you reuse buffers across iterations, skip the per-call
222+
`setTensorAddress` - addresses persist on the context until overwritten.
223+
224+
## Step 7: Shutdown Order
225+
226+
Destroy in reverse construction order: contexts -> engines -> runtime,
227+
then free CUDA memory and destroy the stream. With `std::unique_ptr` this
228+
is automatic as long as the context is declared *after* the engine, and
229+
the engine *after* the runtime. Free `cudaMalloc` allocations explicitly
230+
(RAII wrapper recommended).
231+
232+
## Build
233+
234+
Wire the steps above into your application's build system. For a standalone smoke test, a minimal build is:
235+
236+
```bash
237+
g++ -std=c++17 runtime.cpp -o run -lnvinfer -lcudart # adjust CUDA/TRT include + lib paths
238+
./run model.plan
239+
```
240+
241+
## Common Errors
242+
243+
| Symptom | Likely cause |
244+
|----------------------------------------------------------------------|------------------------------------------------------------------------------|
245+
| `deserializeCudaEngine` returns `nullptr`, log says "version tag" | Engine built on a different TRT major version. Rebuild on the deploy version |
246+
| `nullptr` with "engine plan file is generated on an incompatible device" | SM mismatch. Rebuild on the target SM or use `--hardwareCompatibilityLevel` |
247+
| `enqueueV3` returns false, log mentions "Tensor X has no address" | Forgot `setTensorAddress` for one of the I/O tensors |
248+
| `enqueueV3` false, "shape" in message | Forgot `setInputShape` for a dynamic input, or supplied an out-of-profile shape |
249+
| `cudaErrorIllegalAddress` on H->D / D->H copy | Mismatched element count / dtype between host buffer and engine tensor |
250+
| Process crashes inside TRT during destruction | Wrong destruction order - context outlived engine, or engine outlived runtime |
251+
| `cudaErrorMemoryAllocation` during context creation | Workspace too big for the device; rebuild with smaller workspace |
252+
253+
## Pitfalls
254+
255+
- **Do not use `IStreamReader` v1.** Deprecated in TRT 11.0. Use
256+
`IStreamReaderV2` (note `cudaStream_t` parameter on `read`).
257+
- **Do not use `enqueueV2` / `execute` / binding indices.** These are
258+
legacy paths; the only stable modern path is name-based
259+
`setTensorAddress` + `enqueueV3`.
260+
- **One `IExecutionContext` per thread.** Sharing contexts across threads
261+
is undefined behavior; sharing the engine is fine.
262+
- **Stream lifetime.** The CUDA stream passed to `enqueueV3` must outlive
263+
the inference. Destroying it while work is in flight crashes or corrupts
264+
output.
265+
- **Async vs sync copies.** Mixing synchronous `cudaMemcpy` with
266+
`enqueueV3` on a stream serializes the GPU; always pair `enqueueV3`
267+
with `cudaMemcpyAsync` on the same stream.
268+
- **Engine portability.** A `.plan` is tied to (TRT major version, GPU SM,
269+
CUDA major version). Never check engines into a repo without recording
270+
these three facts.
271+
- **Logger lifetime.** The logger passed to `createInferRuntime` must
272+
outlive the runtime; a stack-local logger in `main` is fine, a function-
273+
scope local is a use-after-free.
274+
- **Refit / weight streaming.** Engines built with refit or weight
275+
streaming enabled need extra setup calls (`setWeightStreamingBudgetV2`,
276+
`IRefitter`); out of scope here.

0 commit comments

Comments
 (0)