chore: import upstream snapshot with attribution
Docker Image CI / build-ubuntu2004 (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 13:36:55 +08:00
commit c8a779b1bb
1887 changed files with 3245738 additions and 0 deletions
@@ -0,0 +1,276 @@
---
name: trt-cpp-runtime-quickstart
description: >-
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
wants to stream a .plan via IStreamReaderV2. Triggers: TensorRT C++
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.
license: Apache-2.0
metadata:
author: NVIDIA Corporation
version: "1.0"
tags:
- tensorrt
- cpp
- inference
- deployment
- runtime
---
# TensorRT C++ Runtime Deploy
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 17 below.
- `samples/sampleOnnxMNIST/sampleOnnxMNIST.cpp` — end-to-end sample that also builds the engine; the runtime portion shows realistic I/O wiring.
- Public headers: `include/NvInferRuntime.h` — read `IRuntime`, `ICudaEngine`, `IExecutionContext`, `IStreamReaderV2`.
## When to Use
| 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 |
## Prerequisites
1. **TensorRT installed.** Verify `NvInferRuntime.h` is on the include path
and `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).
2. **CUDA toolkit available.** `cuda_runtime_api.h` and `libcudart.so` must
be reachable; `nvcc --version` should match the CUDA version the engine
was built against.
3. **A serialized engine.** A `.plan`/`.engine` file built **on the same
major 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`.
4. **The engine's I/O tensor names.** Inspect with:
```bash
trtexec --loadEngine=model.plan --verbose 2>&1 | grep -E 'Input|Output'
```
5. A C++17 compiler (`g++ >= 9` or `clang++ >= 10`).
## Step 1: Create the IRuntime
The runtime owns engine deserialization and must outlive every
`ICudaEngine` it creates. Construct one per process for typical deployments.
```cpp
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.
## Step 2: Read the Plan into Memory
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:
```cpp
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.
## Step 3 (optional): Use IStreamReaderV2 for Streaming Loads
`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(...)`:
```cpp
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)};
```
## Step 4: Deserialize and Create an Execution Context
`ICudaEngine` is thread-safe for read-only queries; `IExecutionContext`
is **not** - allocate one per inference thread.
```cpp
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");
```
## Step 5: Wire Tensors with setTensorAddress
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.
```cpp
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.
## Step 6: Run enqueueV3
`enqueueV3(stream)` is the only non-deprecated enqueue API;
`enqueueV2`/`execute*` are gone in modern flows.
```cpp
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.
## Step 7: Shutdown Order
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).
## Build
Wire the steps above into your application's build system. For a standalone smoke test, a minimal build is:
```bash
g++ -std=c++17 runtime.cpp -o run -lnvinfer -lcudart # adjust CUDA/TRT include + lib paths
./run model.plan
```
## Common Errors
| 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 |
## Pitfalls
- **Do not use `IStreamReader` v1.** Deprecated in TRT 11.0. Use
`IStreamReaderV2` (note `cudaStream_t` parameter on `read`).
- **Do not use `enqueueV2` / `execute` / binding indices.** These are
legacy paths; the only stable modern path is name-based
`setTensorAddress` + `enqueueV3`.
- **One `IExecutionContext` per thread.** Sharing contexts across threads
is undefined behavior; sharing the engine is fine.
- **Stream lifetime.** The CUDA stream passed to `enqueueV3` must outlive
the inference. Destroying it while work is in flight crashes or corrupts
output.
- **Async vs sync copies.** Mixing synchronous `cudaMemcpy` with
`enqueueV3` on a stream serializes the GPU; always pair `enqueueV3`
with `cudaMemcpyAsync` on the same stream.
- **Engine portability.** A `.plan` is tied to (TRT major version, GPU SM,
CUDA major version). Never check engines into a repo without recording
these three facts.
- **Logger lifetime.** The logger passed to `createInferRuntime` must
outlive the runtime; a stack-local logger in `main` is fine, a function-
scope local is a use-after-free.
- **Refit / weight streaming.** Engines built with refit or weight
streaming enabled need extra setup calls (`setWeightStreamingBudgetV2`,
`IRefitter`); out of scope here.
+343
View File
@@ -0,0 +1,343 @@
---
name: trt-onnx-quickstart
description: >
Build and verify a TensorRT engine from a Hugging Face model ID or ONNX
file, with numerical parity checked against ONNX Runtime. Use when the
user imports a non-LLM model to TensorRT,
needs a verified engine from ONNX, hits trtexec "unsupported operator",
must verify the engine matches ONNX numerically, debugs a polygraphy
parity failure (large max abs diff at FP16), or configures multi-input
dynamic shapes. Triggers: convert ONNX to TensorRT, Hugging Face to
TensorRT, trtexec onnx, trtexec unsupported operator, optimum-cli
export, polygraphy parity check, polygraphy run --trt --onnxrt, parity
check failed, max abs diff, verify engine matches ONNX, --minShapes,
dynamic shapes trtexec, multi-input shape profile, FP16 engine, INT64
warning. Adjacent skills: `trt-torch-quickstart` (PyTorch frontend),
`trt-cpp-runtime-quickstart` (C++ engine load). LLM token generation
belongs in TensorRT-LLM, not here.
license: Apache-2.0
metadata:
author: NVIDIA Corporation
version: "1.0"
tags:
- onnx
- import
- huggingface
- quickstart
- fp16
---
# TensorRT ONNX Quickstart
Take a developer from "I have a Hugging Face model ID" or "an ONNX file" to "a TensorRT engine whose outputs match the source model within tolerance." Follows Path 1 (ONNX → TensorRT) of the [Import Workflows Guide](https://github.com/NVIDIA/TensorRT/blob/main/documents/import_workflows.md), specialized for the most common starting point: a Hugging Face Hub model.
## When to Use
| Scenario | Use this skill? |
|----------|-----------------|
| Has a Hugging Face model ID (`google-bert/bert-base-uncased`) and wants TRT-accelerated inference | Yes |
| Has an `.onnx` file and wants a `.plan` engine with verified parity | Yes |
| Ran `trtexec --onnx=...` and hit a warning/error they don't recognize | Yes |
| Wants LLM token generation (Llama, Mistral, Qwen text generation) | **No** — route to [TensorRT-LLM](https://github.com/NVIDIA/TensorRT-LLM) |
| Has a PyTorch model and wants to stay in PyTorch | No — use `trt-torch-quickstart` |
| Already has a `.plan` engine and wants to run inference from C++ | No — use `trt-cpp-runtime-quickstart` |
| Is migrating a weakly-typed network to strongly-typed | No — use `trt-strong-typing-migration` |
| Has a full diffusion pipeline (SD, FLUX) | Partial — must be component-split first; this skill imports one component at a time |
## Prerequisites
Verify each before continuing. Failures here surface as confusing errors later, so fix them before running the import.
1. **NVIDIA GPU + driver matching TensorRT 11.x.** TRT 11 requires CUDA 13.x. Confirm:
```bash
nvidia-smi # driver + CUDA runtime version
```
2. **Python ≥ 3.10.** TRT 11 dropped 3.9 and earlier.
3. **TensorRT 11 installed — including the `trtexec` CLI**, this skill's primary build tool. **The pip wheel does not ship `trtexec`.** Pick one:
- **NGC container (recommended for first-time users):** `nvcr.io/nvidia/tensorrt:<tag>` — bundles `trtexec`, Python bindings, libraries, and most common dependencies. Run with `--gpus all`.
- **`.tar.gz` or `.deb` from [TensorRT downloads](https://developer.nvidia.com/tensorrt-download):** installs `trtexec` to a system path.
The pip path (`pip install --extra-index-url https://pypi.nvidia.com tensorrt-cu13`) provides Python bindings but not `trtexec`, so it is **insufficient on its own**. For pip-only, route the developer to the alternative Python-builder flow in `import_workflows.md` Path 1 Option B; this skill assumes `trtexec` is available.
Verification:
```bash
python3 -c "import tensorrt; print(tensorrt.__version__)" # expect 11.x.y.z
trtexec --help | head -5 # MUST work; if not, install is incomplete
```
4. **Polygraphy and `optimum-onnx` installed.**
```bash
pip install polygraphy onnx onnxruntime onnx_graphsurgeon onnxsim 'optimum-onnx'
# If the model ID starts with `sentence-transformers/`, also:
pip install sentence-transformers
```
`onnx_graphsurgeon` is required by `polygraphy surgeon sanitize` (Step 3) — Polygraphy does not bundle it. `onnxruntime` is required by `optimum-cli export` (Step 2, to fix dynamic axes) and Polygraphy's `--onnxrt` parity check (Step 5); stock NGC containers do not always ship it.
Note: `optimum-onnx` is the active maintained package — the older `optimum` no longer includes ONNX integration. Do **not** use `optimum-nvidia` (last release 2025-01, unmaintained).
## Step 1 — Choose a model and confirm it's supported
Consult the [Supported Models](https://github.com/NVIDIA/TensorRT/blob/main/documents/supported_models.md) matrix. If listed, the dtype column gives a known-good starting precision. If not, it is still expected to work — file an issue if it doesn't.
If the model is a diffusion pipeline, **stop here** — TRT cannot ingest the whole pipeline object. Split it into components (text encoder, UNet/DiT, VAE) and import each separately. See `supported_models.md` for validated splits, and consider whether `trt-export-rewrite` is a better fit.
This skill's worked example uses `google-bert/bert-base-uncased` from the encoder-NLP section.
## Step 2 — Export to ONNX
The `optimum-cli export onnx` command's `--task` flag selects which head of the model to export. The wrong task yields a valid graph but the wrong output — and Step 5 parity validation won't catch it, because reference and engine are both wrong.
Pick the task from this table based on what the developer wants to do with the model:
| Developer's intent | `--task` | Typical model families |
|--------------------|----------|------------------------|
| Get hidden-state embeddings or pooled output | `feature-extraction` | BERT, RoBERTa, sentence-transformers, CLIP |
| Classify images into N classes | `image-classification` | ResNet, ViT, MobileNet, EfficientNet |
| Classify text into N labels | `text-classification` | BERT for sentiment, DistilBERT, etc. |
| Detect / segment objects | `object-detection`, `image-segmentation` | DETR, Mask R-CNN |
| Transcribe speech | `automatic-speech-recognition` | Whisper, Wav2Vec2 |
| Generate captions / VQA | `image-to-text`, `visual-question-answering` | BLIP, LLaVA |
| Translation / summarization | `text2text-generation` | T5, BART |
For an unknown model, run `optimum-cli export onnx --help` or check the [Optimum supported tasks table](https://huggingface.co/docs/optimum/main/en/exporters/onnx/overview). If unsure, surface the choice to the developer rather than guessing.
### Sentence-transformers models need an extra install
If the model ID starts with `sentence-transformers/` (e.g. `all-MiniLM-L6-v2`, `bge-base-en-v1.5`), install `sentence-transformers` *before* exporting:
```bash
pip install sentence-transformers
```
Without it, `optimum-cli` falls back to plain `transformers` and emits raw token-level `last_hidden_state` instead of the pooled sentence embedding the model is designed to produce. The exporter prints `library name was inferred as sentence_transformers, which is not installed. Falling back to transformers.` — treat that warning as a hard error and install before retrying.
```bash
# Worked example: BERT for embedding extraction
optimum-cli export onnx \
--model google-bert/bert-base-uncased \
--task feature-extraction \
bert_onnx/
```
### Expected warnings (not errors)
The exporter often prints a "max diff between reference and ONNX exported model is not within the set tolerance 1e-05" line (with `max diff` around `1e-5` to `1e-4`). This is its own self-check at a strict tolerance — **the export still succeeded.** Step 5's Polygraphy parity check uses `--atol 1e-2 --rtol 1e-2 --check-error-stat mean`, the correct tolerance for FP16 inference. Don't surface this warning as a failure.
### Authentication for gated models
LLaMA, FLUX, and some Mistral and Qwen variants are gated behind HF agreements and require `huggingface-cli login` before download. If `optimum-cli` exits with a 401, run `huggingface-cli login` with a token from `huggingface.co/settings/tokens` and retry.
### If the export fails
The most common cause is a HF model using patterns `torch.export` / `torch.onnx.export` cannot trace — complex-number arithmetic, data-dependent control flow, non-tensor forward arguments, output dataclasses. **Do not patch the upstream library on disk.** The correct workflow is agentic monkey-patching at runtime: route the developer to the `trt-export-rewrite` skill, which drives it with five concrete patterns from the Qwen-Image case study.
## Step 3 — Sanitize the ONNX
ONNX exporters often produce graphs with constant-folding opportunities, spurious dynamic axes, or shape-inference gaps. Sanitizing cleans these up, but it is a **quality pass, not a correctness gate** — TRT parses many un-sanitized exports fine. Both stages below are therefore optional and **allowed to fail**: each falls through to its input, and Step 3 always leaves a `model.clean.onnx` for the rest of the workflow (worst case, a copy of the exported model). Run:
```bash
# Seed model.clean.onnx with the raw export, then update it in-place
cp bert_onnx/model.onnx bert_onnx/model.clean.onnx
# (1) onnxsim — graph simplification / constant folding.
python3 -m onnxsim bert_onnx/model.clean.onnx bert_onnx/model.simplified.onnx \
&& cp bert_onnx/model.simplified.onnx bert_onnx/model.clean.onnx
# (2) polygraphy surgeon — fold constants, prune dangling nodes.
polygraphy surgeon sanitize bert_onnx/model.clean.onnx \
-o bert_onnx/model.surgeon.onnx --fold-constants \
&& cp bert_onnx/model.surgeon.onnx bert_onnx/model.clean.onnx
```
## Step 3.5 — Inspect the ONNX inputs (parameterize the rest of the workflow)
**Every step from here uses input names and ranks that depend on the model.** Read them out of the sanitized ONNX before building. Do not assume `input_ids` / `attention_mask` — those are BERT-family-specific.
```bash
polygraphy inspect model bert_onnx/model.clean.onnx
```
Example output for `bert-base-uncased` (verified against `optimum-onnx` 0.1.0, opset 18):
```
---- 3 Graph Input(s) ----
{input_ids [dtype=int64, shape=('batch_size', 'sequence_length')],
attention_mask [dtype=int64, shape=('batch_size', 'sequence_length')],
token_type_ids [dtype=int64, shape=('batch_size', 'sequence_length')]}
---- 1 Graph Output(s) ----
{last_hidden_state [dtype=float32, shape=('batch_size', 'sequence_length', 768)]}
```
Inputs and outputs vary per model and per `--task`. Inspect, don't assume.
**Build the shape strings from this output.** Every input with a dynamic (named) axis needs a `--minShapes / --optShapes / --maxShapes` entry; fixed axes are reproduced literally. Use representative values — `--opt` is what the engine optimizes for; `--min` and `--max` define the supported range.
If any input reports an unknown-rank axis (printed as `?`), shape inference failed; reroute through `trt-export-rewrite` to fix the export.
## Step 4 — Bake FP16 into the model (TRT 11) or set the build flag (TRT 10)
Start with FP16 — the safe default for encoder and vision models on modern GPUs. (Use BF16 on Blackwell/Hopper if the developer reports persistent FP16 accuracy issues. Sentence-embedding models sometimes prefer FP32 over lossy FP16 — surface the option.)
**TRT 11 removed the weak-typing builder flags** (`--fp16`, `--int8`, `--bf16`, `BuilderFlag::kFP16`, etc. — see [the migration guide](https://docs.nvidia.com/deeplearning/tensorrt/latest/api/migration/tensorrt-10x-to-11x.html) and the sibling `trt-strong-typing-migration` SKILL). In TRT 11 you bake precision **into the model** before building, and `trtexec` honors the dtypes it finds via **ModelOpt AutoCast**.
Detect the version first:
```bash
TRT_MAJOR=$(python3 -c "import tensorrt as t; print(t.__version__.split('.')[0])")
echo "TensorRT major version: $TRT_MAJOR"
```
### TRT 11: AutoCast the model, then build
```bash
pip install nvidia-modelopt # one-time
# Convert FP32 ONNX to mixed FP16 (ModelOpt picks accuracy-sensitive ops to keep FP32):
python3 -m modelopt.onnx.autocast \
--onnx_path=<MODEL>.clean.onnx \
--output_path=<MODEL>.fp16.onnx
# Build the engine. NO --fp16 flag — strong typing is the default and the model already carries FP16 dtypes.
trtexec \
--onnx=<MODEL>.fp16.onnx \
--saveEngine=<MODEL>.fp16.plan \
--memPoolSize=workspace:4096 \
--minShapes=<INPUT1>:<MINSHAPE>,... \
--optShapes=<INPUT1>:<OPTSHAPE>,... \
--maxShapes=<INPUT1>:<MAXSHAPE>,...
```
### TRT 10: pass the legacy --fp16 flag
```bash
trtexec \
--onnx=<MODEL>.clean.onnx \
--saveEngine=<MODEL>.fp16.plan \
--memPoolSize=workspace:4096 \
--fp16 \
--minShapes=<INPUT1>:<MINSHAPE>,... \
--optShapes=<INPUT1>:<OPTSHAPE>,... \
--maxShapes=<INPUT1>:<MAXSHAPE>,...
```
Worked example for BERT (three inputs, all dynamic on batch and sequence axes), TRT 11 path:
```bash
python3 -m modelopt.onnx.autocast \
--onnx_path=bert_onnx/model.clean.onnx \
--output_path=bert_onnx/model.fp16.onnx
trtexec \
--onnx=bert_onnx/model.fp16.onnx \
--saveEngine=bert.fp16.plan \
--memPoolSize=workspace:4096 \
--minShapes=input_ids:1x32,attention_mask:1x32,token_type_ids:1x32 \
--optShapes=input_ids:8x128,attention_mask:8x128,token_type_ids:8x128 \
--maxShapes=input_ids:16x512,attention_mask:16x512,token_type_ids:16x512
```
Worked example for ResNet-50 (one input, dynamic on batch axis only), TRT 11 path:
```bash
python3 -m modelopt.onnx.autocast \
--onnx_path=resnet50_onnx/model.clean.onnx \
--output_path=resnet50_onnx/model.fp16.onnx
trtexec \
--onnx=resnet50_onnx/model.fp16.onnx \
--saveEngine=resnet50.fp16.plan \
--memPoolSize=workspace:4096 \
--minShapes=pixel_values:1x3x224x224 \
--optShapes=pixel_values:16x3x224x224 \
--maxShapes=pixel_values:32x3x224x224
```
For purely static-shape models, omit all three shape flags.
### Common errors at this step
- **"Engine plan file is generated on an incompatible device"** — plans are not portable across compute capabilities. Rebuild on the deployment GPU.
- **OOM during build** — lower `--memPoolSize=workspace:N`. If still failing, try `--tacticSources=-CUBLAS_LT` to disable expensive tactic sources.
- **Unsupported operator** — `trtexec` names the op. Route to `trt-unsupported-op` for triage (decompose / plugin / switch frontend / file bug).
- **INT64 warnings** — TRT casts to INT32. Usually safe; if values exceed INT32 range, rerun Step 3's `polygraphy surgeon sanitize --fold-constants`.
- **"Input <name> has missing shape information"** — you missed an input in the `--minShapes / --optShapes / --maxShapes` flags. Compare against Step 3.5's output and add the missing entry.
## Step 5 — Verify numerical parity
This is the step most developers skip — and the one that most often surfaces a silent correctness bug.
Construct the parity command from the same input names used in Step 4. Beyond input shapes, three knobs matter; get them right or the result is misleading:
1. **`--val-range`** for any input with constrained semantics. Polygraphy fills with `[0, 1)` floats by default. For BERT-family `input_ids` you must constrain to vocabulary range, set `attention_mask` to all-ones, and `token_type_ids` to all-zeros — otherwise the model sees garbage and FP16 noise amplifies through the attention softmax.
2. **`--check-error-stat mean`** for transformer outputs. FP16 commonly has a few outlier positions with large abs diff; the mean across all positions is the meaningful metric for downstream use (embedding similarity, classification). Default `max` is too pessimistic for FP16 transformers and produces false failures.
3. **`--seed`** for repeatability across runs and CI environments.
Worked example for BERT — run polygraphy against the **same ONNX file you fed to `trtexec`** (the AutoCast-converted `.fp16.onnx` on TRT 11; the `.clean.onnx` on TRT 10):
```bash
# TRT 11: polygraphy reads the model's dtypes (already FP16 from AutoCast)
polygraphy run bert_onnx/model.fp16.onnx \
--trt --onnxrt \
--atol 1e-2 --rtol 1e-2 \
--check-error-stat mean \
--input-shapes input_ids:[8,128] attention_mask:[8,128] token_type_ids:[8,128] \
--val-range input_ids:[0,30000] attention_mask:[1,1] token_type_ids:[0,0] \
--seed 42
# TRT 10: pass --fp16 explicitly (the polygraphy flag maps to BuilderFlag::kFP16)
polygraphy run bert_onnx/model.clean.onnx \
--trt --onnxrt \
--atol 1e-2 --rtol 1e-2 \
--check-error-stat mean \
--input-shapes input_ids:[8,128] attention_mask:[8,128] token_type_ids:[8,128] \
--val-range input_ids:[0,30000] attention_mask:[1,1] token_type_ids:[0,0] \
--seed 42 \
--fp16
```
Expected output: `Pass Rate: 100.0%` with `mean_absdiff ≤ 1e-3` per output. `Pass Rate: 0.0%` with `max_absdiff` near 1e-2 but `mean_absdiff` well below tolerance is the outlier pattern above — use `--check-error-stat mean`.
Worked example for ResNet-50 (same FP16 recipe; TRT-11 path shown — for TRT 10 add `--fp16` and read from `model.clean.onnx`):
```bash
polygraphy run resnet50_onnx/model.fp16.onnx \
--trt --onnxrt \
--atol 1e-2 --rtol 1e-2 \
--check-error-stat mean \
--input-shapes pixel_values:[16,3,224,224] \
--val-range pixel_values:[0,1] \
--seed 42
```
Polygraphy runs the model through ONNX Runtime (reference) and TensorRT (engine) on the seeded inputs and compares outputs per the chosen error stat.
### If parity fails
- **Diverges only at FP16, not FP32**: a few layers are losing precision. Try `--strongly-typed` with an explicit FP32 cast on the offending subgraph (Polygraphy names the layer). For LLM-style models, prefer BF16 over FP16 on Hopper/Blackwell.
- **Diverges at all precisions**: the export is wrong. Re-run Steps 23 fresh and check `polygraphy inspect model` for shape-inference issues. If the model uses patterns covered by `trt-export-rewrite`, reroute.
- **Diverges only for specific input shapes**: dynamic-shape profile is too narrow. Widen `--minShapes` / `--maxShapes` and rebuild.
## Step 6 — Run the engine
```bash
trtexec --loadEngine=bert.fp16.plan \
--shapes=input_ids:8x128,attention_mask:8x128,token_type_ids:8x128 \
--verbose
```
This confirms the saved `.plan` deserializes and runs (Step 5's parity used its own engine, not this file) and reports throughput and per-iteration latency. For production, see the developer guide on `IExecutionContext` and stream-aware execution; the `.plan` is portable to any compatible GPU + TRT runtime.
## Success criteria
The skill is complete when all of the following are true:
- `bert.fp16.plan` exists and `trtexec --loadEngine=...` runs without error.
- `polygraphy run --trt --onnxrt --atol 1e-2 --rtol 1e-2 --check-error-stat mean` reports `PASSED` across all configured input shapes.
- The developer can articulate which precision was used and why.
If any are not true, hand control back with the specific failing diagnostic — do not declare success.
## References
- [Import Workflows Guide (Path 1: ONNX → TensorRT)](https://github.com/NVIDIA/TensorRT/blob/main/documents/import_workflows.md#path-1-onnx--tensorrt)
- [Supported Models matrix](https://github.com/NVIDIA/TensorRT/blob/main/documents/supported_models.md)
- [Polygraphy documentation](https://docs.nvidia.com/deeplearning/tensorrt/polygraphy/index.html)
- [`samples/sampleOnnxMNIST/`](https://github.com/NVIDIA/TensorRT/tree/main/samples/sampleOnnxMNIST) for the C++ equivalent of the build flow
+103
View File
@@ -0,0 +1,103 @@
---
name: trt-perf-analysis
description: Validate and analyze TensorRT performance data from paired layer-info JSON and profile/latency JSON files. Use when asked to inspect TensorRT, TRT, torch-tensorrt, or ONNX-TensorRT perf reports, verify that layer/profile JSON files are valid and from the same model, infer basic model information, find likely fusion or latency optimization opportunities, and produce a concise Markdown performance report or structured JSON data.
---
# TRT Perf Analysis
## Workflow
Use `scripts/run.sh` on Unix-like systems or `scripts\run.cmd` on Windows for Python scripts. These wrappers do best-effort Python 3.8+ discovery; set `SKILL_PYTHON` to a Python executable to override discovery. Replace placeholders with platform-native paths.
1. Choose the input scope.
Use one folder that directly contains `layers_*.json` and/or `profile_*.json`. Do not call the packager on a parent folder that only contains component subfolders. For model suites with separate encoder, transformer, decoder, VAE, or similar components, handle each component folder separately.
2. Infer model identity and components.
Prefer an explicit model name from the user prompt and pass it with `--model-name`. Otherwise rely on the analyzer/packager to inspect likely config files, the input directory name, and layer/profile filenames. Keep the name empty when confidence is low. Serialized JSON records the inferred model identity at the top level and in each successful backend's `model` object.
3. Run deterministic analysis.
Run `scripts/analyze_trt_perf.py` first when you need an integrity gate before interpretation. The script uses only Python built-in modules, extracts structured analysis data, and serializes it as JSON. This JSON is the authoritative analyzer output for validation, packaging, and AI diagnosis.
4. Check validation before interpreting.
The analyzer exits `0` when it can emit structured validation data, even if one or more backends fail validation. Read the generated JSON `validation` object to decide which backend reports are usable. If the analyzer exits nonzero, stop because it could not emit structured data. If the schema validator exits nonzero, stop because the generated JSON contract is invalid. Do not continue into performance interpretation for a backend unless its validation status is `passed`, its analysis mode is `layer_profile`, the layer graph is a DAG, and the layer/profile names match. For `layer_only` backends, graph and layer inspection are available but latency/performance interpretation is not.
5. Understand the model, then write AI diagnosis.
Use the generated JSON as grounding material. Build a clear picture of the model or component scope, backend differences, graph structure, timing distribution, hot layers, fusion clues, and caveats before writing conclusions. The browser `Summary` panel is generated from `analyze-data.json`; do not copy that deterministic summary into `analyze.md`.
Keep the final AI analysis in two sections:
1. `## Summary` - short, high-confidence facts and the most important optimization leads.
2. `## Details` - interpretation, cross-backend comparisons, hypotheses, ranked experiments, and human judgment that is not a mechanical restatement of the analyzer output.
Before sending the final response, write the final AI analysis into `analyze.md` for each report folder you created or updated. Match each file to that report folder's scope. For a single model with multiple backend JSON pairs, one `analyze.md` may compare those backends. For multiple model components packaged as multiple report folders, write distinct component-focused `analyze.md` files; do not copy a suite-level analysis into every component report.
`analyze.md` should not include validation checklists, complete timing tables, top-layer tables, path dumps, or other content already rendered by the report app from `analyze-data.json`. Briefly cite specific metrics or layer names only when they support a new conclusion or next experiment.
6. Package the report.
Run `scripts/package_report.py` through the shared runner. Each invocation creates exactly one final report folder from one input folder that directly contains `layers_*.json` or `profile_*.json` files. The packager writes `analyze-data.json`, validates it against the schema, and copies the browser report template while preserving the generated JSON. Determine the output parent from the user's active workspace, not from a subdirectory of this skill. If the only known directory is the skill directory, omit `--output-parent`; the packaging script will fall back to the system temp directory.
When producing multiple reports for one user request, prefer explicit `--report-dir` values so report folder names include the model/component identity instead of relying on auto-numbered collision suffixes. If a suite-level conclusion is useful, write it as a separate overview Markdown file in the output parent, not as every component report's `analyze.md`.
7. Preview only the final report.
Serve the completed report folder, not the skill directory, when the user wants a browser preview. The Python `visualize_layer_info.py` script remains available for standalone HTML layer-info debugging, but it is not part of the report packaging flow or report app dependency chain.
### Common Commands
Use `<runner>` as `<skill-dir>/scripts/run.sh` on Unix-like systems or `<skill-dir>\scripts\run.cmd` on Windows.
```bash
# Validate or inspect inputs. Folder mode discovers matching backends; repeat --data for explicit backends.
<runner> <skill-dir>/scripts/analyze_trt_perf.py <model-folder> --output <analysis.json>
<runner> <skill-dir>/scripts/analyze_trt_perf.py --data <layers-a.json> <profile-a.json> --data <layers-b.json> --output <analysis.json>
# Create a report. Use --report-dir for component-specific reports and --analyze-md after writing final AI analysis.
<runner> <skill-dir>/scripts/package_report.py <model-folder> --output-parent <workspace-folder> [--model-name <model>] [--analyze-md <final-analysis.md>]
<runner> <skill-dir>/scripts/package_report.py --analyze-data <report-folder>/analyze-data.json [--analyze-md <final-analysis.md>]
# Optional preview.
<runner> -m http.server 8765 --bind 127.0.0.1 --directory <report-folder>
```
Then open `http://127.0.0.1:8765/`.
## Inputs
Expect one layer-info JSON and one profile/latency JSON per engine/report. A folder may contain multiple pairs such as:
- `layers_torch-trt-aot.json` with `profile_torch-trt-aot.json`
- `layers_onnx-tensorrt.json` with `profile_onnx-tensorrt.json`
Layer records are expected to include `Name`, `LayerType`, `Inputs`, and `Outputs`. Profile records are expected to include `name`, `timeMs`, `averageMs`, `medianMs`, and `percentage`; a first record like `{ "count": 20 }` is allowed.
Infer a per-layer `Type` for report output. Default to the raw `LayerType`, then apply narrow special-case overrides when the layer name, tactic, or metadata makes the specialization clear. Known special cases:
- `kgen_mha`: a raw `LayerType` of `kgen` that is actually an MHA/FMHA-style attention layer.
- `misc`: raw `LayerType` values `reshape`, `shape_call`, `signal`, and `wait` are grouped into this category.
## Model Categories
Use this starter category list and revise as evidence warrants:
- Transformer encoder / sentence embedding
- Transformer decoder / LLM
- Vision transformer
- CNN / convolutional vision model
- Diffusion / U-Net style model
- Recommender / ranking model
- Classical ML or feature pipeline
- Unknown DL model
Report category only when the evidence is strong. Good evidence includes Hugging Face config fields, names like `encoder.layer`, attention layers, input names such as `input_ids`, or repeated convolution/GEMM patterns. Say "unknown" instead of over-claiming.
## Analysis Heuristics
Prioritize issues by likely runtime impact and confidence:
- Hot layers: individual layers with high `percentage` or `averageMs`.
- Layer-type concentration: high aggregate time in `kgen`, shape, cast, gather, reshape, or pointwise/reduction layers may indicate fusion or dynamic-shape overhead.
- Fusion clues: for transformer encoders, check whether Q/K/V matmuls are fused per block, whether attention is an obvious fused SDPA/FMHA layer, and whether layer-norm/residual/GELU patterns are fused into larger kernels.
- Engine partitioning: one engine is usually ideal. Use adjacent `.trt`/`.engine` files, `_subgraph`, and `StreamId` as hints; state when this is only inferred.
- GEMM tactics: GEMM layers without Tensor Core or xMMA-like tactic names may deserve inspection.
- Many tiny kernels: if no single layer dominates but many small kernels add up, rank launch/fusion overhead above isolated micro-hotspots.
Keep facts and hypotheses separate. For each optimization opportunity, include evidence from the JSON and a next check or experiment.
@@ -0,0 +1,86 @@
Third Party Notices
===================
This report includes or loads third-party open source software.
These notices are provided for attribution and license-tracking purposes only
and do not modify the license terms of the third-party projects.
Netron
------
Project: Netron
Homepage: https://github.com/lutzroeder/netron
Version included in this report: 9.1.1
License: MIT
License URL: https://github.com/lutzroeder/netron/blob/main/LICENSE
Copyright: Copyright (c) Lutz Roeder
Notes:
- The Netron files are embedded as a trimmed report-specific viewer.
- The embedded files have been modified for use in this TensorRT performance
report, including report-specific loading, iframe integration, and layer
selection behavior.
Dagre
-----
Project: Dagre
Homepage: https://github.com/dagrejs/dagre
License: MIT
License URL: https://github.com/dagrejs/dagre/blob/master/LICENSE
Copyright: Copyright (c) 2012-2014 Chris Pettitt
Notes:
- The bundled graph layout code is included through the report-specific Netron
viewer bundle.
Graphlib
--------
Project: Graphlib
Homepage: https://github.com/dagrejs/graphlib
License: MIT
Package metadata URL: https://github.com/dagrejs/graphlib/blob/master/package.json
Author: Chris Pettitt
Notes:
- Dagre depends on Graphlib. The bundled layout code in dev/report/netron/dagre.js may
include or derive from Graphlib functionality.
DOMPurify
---------
Project: DOMPurify
Homepage: https://github.com/cure53/DOMPurify
Version included in this report: 3.4.9
License: MPL-2.0 OR Apache-2.0
License option used for this notice: Apache-2.0
License URL: https://github.com/cure53/DOMPurify/blob/3.4.9/LICENSE
Package metadata URL: https://github.com/cure53/DOMPurify/blob/3.4.9/package.json
Author: Dr.-Ing. Mario Heiderich, Cure53
Notes:
- DOMPurify is used at runtime to sanitize rendered Markdown content.
Marked
------
Project: marked
Homepage: https://github.com/markedjs/marked
Version included in this report: 18.0.5
License: MIT
License URL: https://github.com/markedjs/marked/blob/v18.0.5/LICENSE.md
Package metadata URL: https://github.com/markedjs/marked/blob/v18.0.5/package.json
Copyright:
- Copyright (c) 2018-2026, MarkedJS
- Copyright (c) 2011-2018, Christopher Jeffrey
Notes:
- marked is used at runtime to render Markdown content.
- The marked license file also includes attribution and license terms for
Markdown by John Gruber. See the upstream license URL above.
File diff suppressed because one or more lines are too long
@@ -0,0 +1,11 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
<title>TRT donut T v3</title>
<rect width="32" height="32" rx="6" fill="#f5f7fb"/>
<g transform="rotate(-90 16 16)" fill="none" stroke-width="6" stroke-linecap="butt">
<circle cx="16" cy="16" r="11" pathLength="100" stroke="#2563eb" stroke-dasharray="50 50" stroke-dashoffset="0"/>
<circle cx="16" cy="16" r="11" pathLength="100" stroke="#16a34a" stroke-dasharray="22 78" stroke-dashoffset="-50"/>
<circle cx="16" cy="16" r="11" pathLength="100" stroke="#d97706" stroke-dasharray="28 72" stroke-dashoffset="-72"/>
</g>
<circle cx="16" cy="16" r="6.5" fill="#f5f7fb"/>
<path d="M11 11h10v3h-3.5v8h-3v-8H11Z" fill="#1d2433"/>
</svg>

After

Width:  |  Height:  |  Size: 716 B

@@ -0,0 +1,183 @@
<!doctype html>
<!-- SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -->
<!-- SPDX-License-Identifier: Apache-2.0 -->
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>TRT Performance Report</title>
<link rel="icon" type="image/svg+xml" href="favicon-donut-t-v3.svg">
<link rel="stylesheet" href="styles.css">
</head>
<body>
<div class="page">
<header class="topbar" id="topbar">
<div class="title-block">
<p class="eyebrow">TensorRT Analyze Report</p>
<div class="title-line">
<h1 id="report-title">TRT Performance Report</h1>
<span class="backend-chip" id="backend-chip">Backend</span>
</div>
<p class="subtitle" id="report-subtitle"></p>
<div class="pinned-summary" id="pinned-summary" aria-label="Pinned basic report information"></div>
</div>
<div class="status-stack">
<div class="status-pill" id="status-pill">
<span class="status-dot"></span>
<span id="status-text">Loading data</span>
</div>
<div class="graph-status" id="graph-status">Loading graph</div>
</div>
</header>
<nav class="result-tabs" id="result-tabs" aria-label="Analysis results"></nav>
<section class="metrics" id="metrics" aria-label="Performance metrics"></section>
<details class="panel summary-panel analyze-panel" id="analyze-panel" aria-labelledby="analyze-title" open hidden>
<summary class="summary-panel-header" aria-label="Toggle AI-generated analysis">
<span class="details-toggle" aria-hidden="true"></span>
<span class="summary-panel-title" id="analyze-title">Analysis (generated by AI)</span>
<button class="panel-help-button" type="button" aria-label="Show help for Analysis generated by AI" aria-expanded="false" aria-controls="panel-help-popover" data-help-title="Analysis (generated by AI)" data-help="Generated by AI from the validated report data.">?</button>
</summary>
<div class="markdown-content" id="analyze-content"></div>
</details>
<details class="panel summary-panel" aria-labelledby="summary-title">
<summary class="summary-panel-header" aria-label="Toggle script-generated analysis">
<span class="details-toggle" aria-hidden="true"></span>
<span class="summary-panel-title" id="summary-title">Analysis (generated by script)</span>
<button class="panel-help-button" type="button" aria-label="Show help for Analysis generated by script" aria-expanded="false" aria-controls="panel-help-popover" data-help-title="Analysis (generated by script)" data-help="Generated by script from the validated report data.">?</button>
</summary>
<div class="markdown-content" id="summary-content"></div>
</details>
<details class="panel details-panel" aria-labelledby="findings-title">
<summary class="details-summary" aria-label="Toggle optimization leads and positive signals">
<span class="details-toggle" aria-hidden="true"></span>
<span class="details-panel-title" id="findings-title">Optimization Leads &amp; Positive Signals</span>
<button class="panel-help-button" type="button" aria-label="Show help for Optimization Leads and Positive Signals" aria-expanded="false" aria-controls="panel-help-popover" data-help-title="Optimization Leads &amp; Positive Signals" data-help="Generated by script from the validated report data.">?</button>
</summary>
<div class="details-grid">
<article class="list-card">
<h2>Optimization Leads</h2>
<ul class="finding-list" id="issues"></ul>
</article>
<article class="list-card">
<h2>Positive Signals</h2>
<ul class="finding-list" id="positives"></ul>
<p class="path-line" id="paths"></p>
</article>
</div>
</details>
<main class="analysis-shell">
<div class="report-column">
<section class="panel workspace" aria-label="Layer performance report">
<section class="chart-panel" aria-labelledby="chart-title">
<div class="panel-header">
<div>
<div class="panel-title-line">
<h2 class="panel-title" id="chart-title">Layers</h2>
<button class="panel-help-button" type="button" aria-label="Show help for Layers" aria-expanded="false" aria-controls="panel-help-popover" data-help-title="Layers" data-help="Layer latency/type breakdown and searchable layer table.">?</button>
</div>
<p class="panel-kicker" id="chart-kicker"></p>
</div>
</div>
<div class="type-summary" id="type-summary"></div>
<div class="chart-body">
<div class="chart-wrap">
<svg class="pie" id="type-pie" viewBox="0 0 320 320" role="img" aria-labelledby="chart-title"></svg>
</div>
<div class="legend" id="legend"></div>
</div>
</section>
<section class="layers-panel" aria-labelledby="layers-title">
<div class="table-toolbar">
<div class="table-title">
<strong id="layers-title">Layers</strong>
<span id="layers-count"></span>
</div>
<label class="sr-only" for="layer-filter">Filter layers</label>
<input class="filter-input" id="layer-filter" type="search" placeholder="Filter layers" autocomplete="off">
</div>
<div class="table-scroll">
<table>
<thead>
<tr>
<th scope="col">
<button class="table-sort-button" type="button" data-layer-sort-key="topo">
<span>Topo</span>
<span class="sort-indicator" aria-hidden="true"></span>
</button>
</th>
<th scope="col">Layer</th>
<th scope="col">
<button class="table-sort-button" type="button" data-layer-sort-key="avg">
<span>Avg ms</span>
<span class="sort-indicator" aria-hidden="true"></span>
</button>
</th>
<th scope="col">
<button class="table-sort-button" type="button" data-layer-sort-key="total">
<span>Total ms</span>
<span class="sort-indicator" aria-hidden="true"></span>
</button>
</th>
<th scope="col">
<button class="table-sort-button" type="button" data-layer-sort-key="share">
<span>Share</span>
<span class="sort-indicator" aria-hidden="true"></span>
</button>
</th>
<th scope="col">Tags</th>
<th scope="col">
<button class="table-sort-button" type="button" data-layer-sort-key="stream">
<span>Stream</span>
<span class="sort-indicator" aria-hidden="true"></span>
</button>
</th>
</tr>
</thead>
<tbody id="layer-rows"></tbody>
</table>
</div>
<div class="empty" id="empty-state" hidden>No matching layers</div>
</section>
</section>
</div>
<aside class="panel graph-panel" aria-labelledby="graph-title">
<div class="panel-header graph-header">
<div>
<div class="panel-title-line">
<h2 class="panel-title" id="graph-title">Model Graph</h2>
<button class="panel-help-button" type="button" aria-label="Show help for Model Graph" aria-expanded="false" aria-controls="panel-help-popover" data-help-title="Model Graph" data-help="Visual model graph linked to selected layers.">?</button>
</div>
<p class="panel-kicker" id="graph-kicker">Rows with matching graph nodes can be selected.</p>
</div>
</div>
<div class="netron-views" id="netron-views"></div>
</aside>
</main>
</div>
<div class="panel-help-popover" id="panel-help-popover" role="tooltip" hidden>
<strong id="panel-help-popover-title"></strong>
<p id="panel-help-popover-text"></p>
</div>
<script>
window.trtReportOptions = {
dataName: 'analyze-data.json',
analyzeName: 'analyze.md',
graphMode: 'onnx-json',
modelName: 'model.trt_inspect.onnx.json',
netronBase: 'netron/',
netronFrameMode: 'srcdoc'
};
</script>
<script src="app.js"></script>
</body>
</html>
File diff suppressed because one or more lines are too long
@@ -0,0 +1,596 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="description" content="Visualizer for neural network, deep learning and machine learning models." />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
<meta http-equiv="Content-Security-Policy" content="script-src 'self'">
<meta name="version" content="9.1.1">
<meta name="date" content="2026-06-05 14:43:12">
<title>Netron</title>
<link rel="stylesheet" type="text/css" href="grapher.css">
<link rel="icon" href="data:,">
<script type="text/javascript" src="netron.js"></script>
<style>
html { touch-action: none; overflow: hidden; width: 100%; height: 100%; -ms-text-size-adjust: 100%; -webkit-text-size-adjust: 100%; text-rendering: optimizeLegibility; -webkit-text-rendering: optimizeLegibility; -moz-text-rendering: optimizeLegibility; -ms-text-rendering: optimizeLegibility; -o-text-rendering: optimizeLegibility; -webkit-font-smoothing: antialiased; -moz-font-smoothing: antialiased; -ms-font-smoothing: antialiased; -o-font-smoothing: antialiased; }
body { touch-action: none; overflow: hidden; width: 100%; height: 100%; margin: 0; font-family: -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Ubuntu", "Droid Sans", sans-serif, "PingFang SC"; font-size: 12px; text-rendering: geometricPrecision; }
button { font-family: -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Ubuntu", "Droid Sans", sans-serif, "PingFang SC"; }
.center { position: absolute; margin: auto; top: 0; right: 0; bottom: 0; left: 0; user-select: none; -webkit-user-select: none; -moz-user-select: none; }
.select { user-select: text; -webkit-user-select: text; -moz-user-select: text; }
.target { display: flex; height: 100%; width: 100%; overflow: auto; outline: none; touch-action: pan-x pan-y; }
.canvas { margin: auto; flex-shrink: 0; text-rendering: geometricPrecision; user-select: none; -webkit-user-select: none; -moz-user-select: none; }
.open-file-dialog { display: none; }
.default { background-color: #ffffff; }
.default .logo { display: none; }
.default .target { display: flex; opacity: 1; }
.default .toolbar { display: flex; }
.toolbar { display: flex; align-items: center; position: absolute; bottom: 10px; left: 10px; padding: 0; margin: 0; user-select: none; -webkit-user-select: none; -moz-user-select: none; }
.toolbar button:focus { outline: 0; }
.toolbar-button { background: None; border-radius: 6px; border: 0; margin: 0; margin-right: 1px; padding: 0; fill: None; stroke: #777; cursor: pointer; width: 32px; height: 32px; user-select: none; }
.toolbar-path { display: flex; align-items: center; }
.toolbar-select { background: transparent; position: relative; width: 170px; margin: 4px 4px 4px 4px; }
.toolbar-select select { width: 100%; appearance: none; -webkit-appearance: none; -moz-appearance: none; font-family: inherit; font-size: 12px; line-height: 16px; border: 1px solid; padding: 3px 18px 3px 10px; border-radius: 6px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; box-sizing: border-box; }
.toolbar-select select { background: #777; border-color: #777; color: #fff; }
.toolbar-select-arrow { position: absolute; top: 50%; right: 8px; transform: translateY(-50%); pointer-events: none; }
.toolbar-select-arrow { fill: #ffffff; }
.toolbar-select select:hover { background: #000000; border-color: #000000; }
.toolbar-select select:focus { outline: 0; }
.toolbar-path-back-button { background: #777; border-top-left-radius: 6px; border-bottom-left-radius: 6px; border: 1px solid; border-color: #777; margin: 4px 0px 4px 4px; padding: 5px 8px 5px 8px; cursor: pointer; color: #ffffff; font-size: 12px; line-height: 12px; transition: 0.1s; }
.toolbar-path-back-button:hover { background: #000000; border-color: #000000; }
.toolbar-path-name-button { background: #777; border: 0px; border-color: #777; color: #ffffff; border-left: 1px; border-left-color: #ffffff; margin: 4px 0 4px 1px; padding: 6px 8px 6px 8px; cursor: pointer; width: auto; font-size: 12px; line-height: 12px; transition: 0.1s; }
.toolbar-path-name-button:hover { background: #000000; border-color: #000000; }
.toolbar-path-name-button:last-child { border-top-right-radius: 6px; border-bottom-right-radius: 6px; }
.toolbar-icon .border { stroke: #fff; }
.toolbar-icon .stroke { stroke: #808080; }
.toolbar-icon .fill { fill: #808080; }
.toolbar-icon:hover .stroke { stroke: #000000; }
.toolbar-icon:hover .fill { fill: #000000; }
.message { display: none; opacity: 0; position: absolute; top: 0; left: 0; width: 100%; height: 100%; flex-direction: column; justify-content: flex-start; }
.message-text { display: inline; text-align: center; width: 562px; font-size: 13px; line-height: 20px; margin-top: 50vh; padding-top: 56px; padding-bottom: 20px; margin-left: auto; margin-right: auto; user-select: text; -webkit-user-select: text; -moz-user-select: text; }
.welcome .message-text a { text-decoration: none; color: #666666; }
.welcome .message-text a:visited { color: inherit; }
.welcome .message-text a:hover { color: #242424; text-decoration: underline; }
.message-button { display: inline; text-align: center; width: 125px; margin-left: auto; margin-right: auto; }
.logo-text { top: -57px; width: 582px; transition: 0.1s; }
.logo-name { top: -170px; width: 582px; transition: 0.1s; }
.logo-icon { left: 248px; top: -18px; width: 106px; height: 106px; transition: 0.1s; }
.logo-spinner { left: 248px; top: -18px; width: 106px; height: 106px; display: none; }
.logo-stroke { stroke: #444444; }
.logo-fill { fill: #444444; }
.logo-border { stroke: #555555; }
.logo-glyph { fill: #444444; }
.logo-button { font-size: 12px; font-weight: bold; line-height: 1.25; text-align: center; vertical-align: middle; min-width: 5em; height: 2.7em; border-radius: 1.3em; transition: 0.1s; user-select: none; -webkit-user-select: none; -moz-user-select: none; color: #444444; background-color: #ececec; border: 1px solid #444444; }
.logo-button:hover { color: #ececec; background-color: #444444; cursor: pointer; transition: 0.2s; }
.logo-button:focus { outline: 0; }
.logo-message { display: none; height: 0px; }
.logo-github { display: none; }
.open-file-button { top: 170px; left: 0px; width: 10.5em; }
.progress { top: 120px; height: 2px; width: 400px; }
.progress-bar { height: 100%; width: 0%; background-color: #444444; }
.notification .logo-name { display: none; }
.notification .open-file-button { display: none; }
.notification .progress { display: none; }
.welcome body { background-color: #ececec; }
.welcome { background-color: #ececec; color: #242424; }
.welcome .message-text { display: none; opacity: 0; }
.welcome .message-button { display: none; opacity: 0; }
.welcome .target { display: none; opacity: 0; }
.welcome .menu { background-color: #ffffff; }
.welcome.spinner .logo-spinner { display: block; -webkit-animation: orbit 0.5s infinite linear; animation: orbit 0.5s infinite linear; cursor: wait; }
.welcome.spinner .menu-button { display: none; }
.welcome.notification .menu-button { display: none; }
.notification body { background-color: #ececec; }
.notification .message { display: flex; opacity: 1; }
.notification .message-text { display: inline; opacity: 1; }
.notification .message-button { display: inline; opacity: 1; }
.alert { background-color: #ececec; color: #242424; }
.alert .target { display: none; opacity: 0; }
.alert .toolbar { display: none; opacity: 0; }
.alert .menu { display: none; opacity: 0; }
.alert .logo { display: none; opacity: 0; }
.alert .message { display: flex; opacity: 1; }
.alert .message-text { display: inline; opacity: 1; width: 50%; padding-top: 0px; }
.alert .message-button { display: inline; opacity: 1; }
.about { overflow: hidden; }
.about .toolbar { display: none; }
.about .logo { display: block; background-color: #ececec; color: #666666; }
.about .logo-message { display: block; top: 132px; font-size: 14px; }
.about .logo-github { display: block; top: 340px; width: 48px; height: 48px; }
.about a { text-decoration: none; color: #666666; }
.about a:visited { color: inherit; }
.about a:hover { color: #242424; }
.about .open-file-button { display: none; }
.about .logo-name { display: none; }
.about .notification { display: none; }
.about .progress { display: none; }
.about .menu-button { display: none; }
.titlebar { color: #aaaaaa; display: none; height: 32px; position: fixed; top: 0; left: 0; right: 0; bottom: 0; z-index: 2; -webkit-app-region: drag; }
.titlebar-visible { display: block; }
.titlebar-content { display: block; padding: 0 142px; height: 100%; text-align: center; font-size: 14px; line-height: 32px; transition: all .1s ease-in-out; user-select: none; }
.titlebar-content-text { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.spinner .titlebar-content { opacity: 0; }
.active .titlebar { color: #464646; transition: all 0.05s ease-in-out; }
.titlebar-control-box { display: none; align-items: center; flex-direction: row-reverse; height: 100%; position: absolute; top: 0; right: 0; width: 138px; }
.titlebar-control-box-visible { display: flex; }
.titlebar-icon { width: 1em; height: 1em; vertical-align: -0.15em; fill: currentColor; overflow: hidden; }
.titlebar-button { display: flex; justify-content: center; align-items: center; width: 46px; height: 32px; user-select: none; -webkit-app-region: no-drag; }
.titlebar-button:hover { color: #000000; background-color: rgba(0, 0, 0, 0.15); }
.titlebar-button-close:hover { color: #ffffff; background-color: #b43029; }
.menu-button { display: flex; justify-content: center; align-items: center; color: #aaaaaa; font-size: 20px; height: 32px; width: 32px; position: fixed; top: 0; left: 0; right: 0; bottom: 0; z-index: 2; -webkit-app-region: no-drag; -webkit-app-region: no-drag; user-select: none; }
.menu-button:hover { color: #000000; }
.menu { display: block; position: absolute; left: -17em; width: 17em; top: 0; height: 100%; z-index: 2; background-color: #ececec; border-right: 1px solid rgba(255, 255, 255, 0.5); padding-top: 40px; padding-bottom: 2px; margin-left: 0; margin-top: 0; overflow: hidden; transition: 0.1s; }
.menu .menu-group { margin-bottom: 12px; }
.menu .menu-group .menu-group-header { display: block; border: none; border-radius: 0; color: black; width: 100%; text-align: left; margin: 4px 12px 5px 12px; white-space: no-wrap; font-size: 11px; font-weight: bold; color: #bbbbbb; white-space: nowrap; }
.menu .menu-group .menu-command { display: block; border: none; border-radius: 0; background-color: transparent; color: black; width: 100%; text-align: left; padding: 4px 12px 5px 12px; font-size: 12px; }
.menu .menu-group .menu-command:focus { color: #ffffff; background-color: #2e6bd2; outline: none; }
.menu .menu-group .menu-command:disabled { color: #888888; }
.menu .menu-group .menu-command .menu-label { display: block; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
.menu .menu-group .menu-command .menu-shortcut { display: block; float: right; margin-left: 25px; color: #888888; }
.menu .menu-group .menu-separator { border-top: 1px; border-bottom: 0; border-style: solid; border-color: #e5e5e5; margin-left: 12px; margin-right: 12px; }
.about .titlebar-visible { opacity: 0; }
@-webkit-keyframes orbit { 0% { -webkit-transform: rotate(0deg); transform: rotate(0deg); } 100% { -webkit-transform: rotate(360deg); transform: rotate(360deg); } }
@keyframes orbit { 0% { -webkit-transform: rotate(0deg); transform: rotate(0deg); } 100% { -webkit-transform: rotate(360deg); transform: rotate(360deg); } }
.welcome.spinner .logo-spinner-stroke { stroke: #ececec; }
.welcome.spinner .logo-name { display: none; }
.welcome.spinner .open-file-button { display: none; }
.welcome.spinner .target { display: flex; opacity: 0; }
.welcome .notification .logo-name { display: none; }
.welcome .toolbar { display: none; }
@media (prefers-color-scheme: dark) {
:root { color-scheme: dark; }
.default { background-color: #404040; }
.target { background-color: #404040; }
.welcome { background-color: #1e1e1e; color: #888888; }
.alert { background-color: #1e1e1e; color: #888888; }
.logo-stroke { stroke: #888888; }
.logo-fill { fill: #888888; }
.logo-border { stroke: #000000; }
.logo-glyph { fill: #888888; }
.logo-spinner-stroke { stroke: #ffffff; }
.logo-button { color: #888888; background-color: #1e1e1e; border-color: #888888; }
.logo-button:hover { color: #1e1e1e; background-color: #888888; }
.welcome .progress-bar { background-color: #888888; }
.welcome .menu { background-color: #2d2d2d }
.about .logo { background-color: #1e1e1e; color: #888888; }
.about a { color: #c6c6c6; }
.about a:hover { color: #565656; }
.welcome .message-text a { color: #c6c6c6; }
.welcome .message-text a:visited { color: #c6c6c6; }
.welcome .message-text a:hover { color: #565656; }
.toolbar-icon .border { stroke: #333333; }
.toolbar-icon .stroke { stroke: #aaaaaa; }
.toolbar-icon .fill { fill: #aaaaaa; }
.toolbar-icon:hover .stroke { stroke: #dfdfdf; }
.toolbar-icon:hover .fill { fill: #dfdfdf; }
.toolbar-path-back-button { background: #aaaaaa; border-color: #aaaaaa; color: #333333; }
.toolbar-path-back-button:hover { background: #dfdfdf; border-color: #dfdfdf; }
.toolbar-path-name-button { background: #aaaaaa ; border-color: #aaaaaa; color: #404040; }
.toolbar-path-name-button:hover { background: #dfdfdf; border-color: #dfdfdf; }
.toolbar-select select { background: #aaaaaa; border-color: #aaaaaa; color: #404040; }
.toolbar-select select:hover { background: #dfdfdf; border-color: #dfdfdf; color: #404040; }
.toolbar-select-arrow { fill: #404040; }
.titlebar { color: #949494; }
.welcome body { background-color: #1e1e1e; }
.default body { background-color: #404040; }
.active .titlebar { color: #c4c4c4; }
.titlebar-button:hover { color: #ffffff; background-color: rgba(0, 0, 0, 0.15); }
.titlebar-button-close:hover { color: #ffffff; background-color: #b43029; }
.menu-button { color: #aaaaaa; }
.menu-button:hover { color: #ffffff; }
.menu { background-color: #2d2d2d; border-color: rgba(0, 0, 0, 0); }
.menu .menu-group .menu-group-header { color: #666666; }
.menu .menu-group .menu-command { color: #ffffff; }
.menu .menu-group .menu-command:focus { color: #ffffff; background-color: #2e6bd2; }
.menu .menu-group .menu-command:disabled { color: #888888; }
.menu .menu-group .menu-command .shortcut { color: #888888; }
.menu .menu-group .menu-separator { border-color: #363636; }
}
@media all and (max-width: 640px) {
.logo { width: 240px; }
.logo-text { opacity: 0; }
.logo-name { opacity: 0; }
.logo-icon { left: 0; width: 128px; height: 128px; }
.logo-spinner { left: 0; width: 128px; height: 128px; }
.logo .open-file-button { top: 204px; left: 0; }
.message-text { padding-top: 68px; width: 320px; }
.progress { top: 160px; height: 2px; width: 100px; }
.about .logo { width: 100%; padding-left: 0; padding-right: 0; }
.about .logo-message { top: 175px; font-size: 12px; }
.about .logo-github { top: 370px; }
}
.sidebar { display: flex; flex-direction: column; font-family: -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Ubuntu", "Droid Sans", sans-serif; font-size: 12px; height: 100%; right: -100%; position: fixed; transition: 0.1s; top: 0; background-color: #ececec; color: #242424; overflow: hidden; border-left: 1px solid rgba(255, 255, 255, 0.5); opacity: 0; }
.sidebar-title { font-weight: bold; font-size: 12px; letter-spacing: 0.5px; text-transform: uppercase; height: 20px; margin: 0; padding: 20px; user-select: none; -webkit-user-select: none; -moz-user-select: none; }
.sidebar-closebutton { padding: 8px 8px 8px 32px; text-decoration: none; font-size: 25px; color: #777777; opacity: 1.0; display: block; transition: 0.2s; position: absolute; top: 0; right: 15px; margin-left: 50px; user-select: none; -webkit-user-select: none; -moz-user-select: none; }
.sidebar-closebutton:hover { color: #242424; }
.sidebar-content { display: flex; flex-direction: column; flex-grow: 1; height: 0; }
.sidebar-header { font-weight: bold; font-size: 12px; letter-spacing: 0.5px; text-transform: uppercase; height: 20px; margin: 0; margin-top: 30px; padding-top: 10px; padding-bottom: 10px; user-select: none; -webkit-user-select: none; -moz-user-select: none; }
.sidebar-section { font-weight: bold; font-size: 11px; text-transform: uppercase; line-height: 1.25; margin-top: 16px; margin-bottom: 16px; display: block; user-select: none; -webkit-user-select: none; -moz-user-select: none; cursor: default; }
.sidebar-object { flex-grow: 1; padding: 0px 20px 20px 20px; overflow-y: auto; }
.sidebar-item { margin-bottom: 0px; display: block; }
.sidebar-item-name { float: left; font-size: 11px; min-width: 95px; max-width: 95px; padding-right: 5px; padding-top: 7px; display: block; }
.sidebar-item-name input { color: #777; font-family: inherit; font-size: inherit; color: inherit; background-color: inherit; width: 100%; text-align: right; margin: 0; padding: 0; border: 0; outline: none; text-overflow: ellipsis; }
.sidebar-item-value-list { margin: 0; margin-left: 105px; overflow: hidden; display: block; padding: 0; }
.sidebar-item-value { font-size: 11px; background-color: #fcfcfc; border-radius: 2px; border: 1px solid #fcfcfc; margin-top: 3px; margin-bottom: 3px; overflow: auto; }
.sidebar-item-value-content { background-color: #f8f8f8; border: 1px solid #f8f8f8; }
.sidebar-item-value b { font-weight: bold; }
.sidebar-item-value code { font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, Courier, monospace; overflow: auto; white-space: pre-wrap; word-wrap: break-word; }
.sidebar-item-value pre { font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, Courier, monospace; margin: 0; overflow: auto; white-space: pre; word-wrap: normal; display: block; }
.sidebar-item-value-line { padding: 4px 6px 4px 6px; overflow-x: auto; white-space: pre; }
.sidebar-item-value-line-wrap { white-space: pre-wrap; overflow-x: hidden; overflow-wrap: break-word; }
.sidebar-item-value-line-break { padding: 4px 6px 4px 6px; overflow-x: auto; white-space: pre; }
.sidebar-item-value-line-link { padding: 4px 6px 4px 6px; cursor: default; overflow-x: auto; white-space: nowrap; }
.sidebar-item-value-line-link:hover { text-decoration: underline; }
.sidebar-item-value-line-border { padding: 4px 6px 4px 6px; border-top: 1px solid rgba(27, 31, 35, 0.05); }
.sidebar-item-value-line-content { white-space: pre; word-wrap: normal; overflow: auto; display: block; }
.sidebar-item-value-expander { font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, Courier, monospace; float: right; color: #aaa; cursor: pointer; user-select: none; -webkit-user-select: none; -moz-user-select: none; padding: 4px 6px 4px 4px; }
.sidebar-item-value-expander:hover { color: #333; }
.sidebar-item-value-button { display: flex; justify-content: center; align-items: center; font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, Courier, monospace; width: 22px; height: 22px; cursor: pointer; user-select: none; -webkit-user-select: none; -moz-user-select: none; color: #aaa; }
.sidebar-item-value-button svg use { fill: #aaa; stroke: #aaa; }
.sidebar-item-value-button:hover svg use { fill: #333; stroke: #333; }
.sidebar-item-value-button-tool { float: right; padding-left: 3px; }
.sidebar-item-value-button-context { float: right; }
.sidebar-item-selector {
font-family: inherit; font-size: 12px;
background-color: #fcfcfc; border: #fcfcfc; color: #333;
border-radius: 2px; width: 100%; height: 23px; padding: 3px 12px 3px 7px;
margin-top: 3px; margin-bottom: 3px; outline: none;
box-sizing: border-box; -moz-box-sizing: border-box;
appearance: none; -webkit-appearance: none; -moz-appearance: none;
background-image: linear-gradient(45deg, transparent 50%, #333 50%), linear-gradient(135deg, #333 50%, transparent 50%);
background-position: calc(100% - 12px) calc(10px), calc(100% - 7px) calc(10px);
background-size: 5px 5px, 5px 5px;
background-repeat: no-repeat;
}
.sidebar-separator { margin-bottom: 20px; }
.sidebar-find-search { display: flex; align-items: center; background: #fff; border-radius: 16px; margin: 0px 20px 8px 20px; padding: 0px 8px 0px 8px; }
.sidebar-find-query { width: 100vw; background: none; font-family: inherit; font-size: 13px; padding: 8px 8px 8px 8px; border: 0; outline: 0; }
.sidebar-find-toggle { margin-left: auto; margin-right: 2px; width: 16px; height: 16px; cursor: pointer; }
.sidebar-find-toggle input[type="checkbox"] { display: none; }
.sidebar-find-toggle input[type="checkbox"]:not(:checked) + svg { fill: #ccc; stroke: #ccc; }
.sidebar-find-toggle input[type="checkbox"]:checked + svg { fill: #555; stroke: #555; }
.sidebar-find-toggle-icon { stroke: #555; fill: #555; width: 16px; height: 16px; }
.sidebar-find-content { flex-grow: 1; padding: 0px 20px 20px 20px; overflow-y: auto; list-style-type: none; margin: 0; outline: 0; }
.sidebar-find-content li *:first-child { margin-right: 2px; }
.sidebar-find-content li { color: #666; font-size: 13px; height: 22px; line-height: 22px; padding: 0 12px 0 12px; outline: 0; white-space: nowrap; border-radius: 3px; user-select: none; -webkit-user-select: none; -moz-user-select: none; }
.sidebar-find-content li.focus { background: #e5e5e5; color: #000; }
.sidebar-find-content-icon { stroke: #555; fill: #555; float: left; width: 16px; height: 16px; padding: 3px; pointer-events: none; }
.sidebar-documentation { flex-grow: 1; padding: 0px 20px 20px 20px; overflow-y: auto; font-size: 13px; line-height: 1.5; margin: 0; }
.sidebar-documentation h1 { font-weight: bold; font-size: 13px; line-height: 1.25; border-bottom: 1px solid #e8e8e8; padding-bottom: 0.3em; margin-top: 0; margin-bottom: 16px; }
.sidebar-documentation h2 { font-weight: bold; font-size: 13px; line-height: 1.25; margin-top: 20px; margin-bottom: 16px; text-transform: uppercase; border: 0; }
.sidebar-documentation h3 { font-weight: bold; font-size: 11px; line-height: 1.25; }
.sidebar-documentation p { margin-top: 4px; margin-bottom: 4px; margin-left: 0px; }
.sidebar-documentation a { color: #237; }
.sidebar-documentation code { font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, Courier, monospace; font-size: 12px; background-color: rgba(27, 31, 35, 0.05); padding: 0.2em 0.4em; margin: 0; border-radius: 3px; }
.sidebar-documentation pre { font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, Courier, monospace; font-size: 12px; padding: 16px; overflow: auto; line-height: 1.45; background-color: rgba(27, 31, 35, 0.05); border-radius: 3px; }
.sidebar-documentation pre code { font-size: 13px; padding: 16px; line-height: 1.45; background-color: transparent; padding: 0; border-radius: 0; }
.sidebar-documentation tt { font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, Courier, monospace; font-weight: bold; font-size: 90%; background-color: rgba(27, 31, 35, 0.05); border-radius: 3px; padding: 0.2em 0.4em; margin: 0; }
.sidebar-documentation dl dt { font-size: 13px; font-weight: bold; padding: 0; margin-top: 16px; margin-left: 0px; }
.sidebar-documentation dd { padding: 0 16px; margin-left: 0; margin-bottom: 16px; }
.sidebar-documentation ul { margin-top: 6px; margin-bottom: 6px; padding-left: 20px; }
.sidebar-documentation blockquote { margin-left: 15px; margin-right: 15px; }
@media (prefers-color-scheme: dark) {
.sidebar html { color: #dfdfdf; }
.sidebar { background-color: #2d2d2d; color: #dfdfdf; border-left: 1px solid rgba(0, 0, 0, 0); }
.sidebar-closebutton { padding: 8px 8px 8px 32px; text-decoration: none; font-size: 25px; color: #777777; opacity: 1.0; display: block; transition: 0.2s; position: absolute; top: 0; right: 15px; margin-left: 50px; user-select: none; -webkit-user-select: none; -moz-user-select: none; }
.sidebar-closebutton:hover { color: #ffffff; }
.sidebar-item-value { background-color: #383838; border-color: #383838; }
.sidebar-item-value-content { background-color: #3e3e3e; border-color: #3e3e3e; }
.sidebar-item-value-line-border { border-color: rgba(0, 0, 0, 0.09); }
.sidebar-item-selector { background-color: #383838; border: #383838; color: #dfdfdf; background-image: linear-gradient(45deg, transparent 50%, #aaa 50%), linear-gradient(135deg, #aaa 50%, transparent 50%); }
.sidebar-item-disable-select { user-select: none; -webkit-user-select: none; -moz-user-select: none; }
.sidebar-header { border-bottom-color: #2d2d2d; color: #dfdfdf; }
.sidebar-documentation h1 { border-bottom: 1px solid #424242; color: #dfdfdf; }
.sidebar-documentation h2 { color: #dfdfdf; }
.sidebar-documentation p { color: #aaaaaa; }
.sidebar-documentation a { color: #6688aa; }
.sidebar-documentation tt { background-color:#1e1e1e; }
.sidebar-documentation code { background-color: #1e1e1e; }
.sidebar-documentation pre { background-color: #1e1e1e; }
.sidebar-find-search { background: #383838; color: #dfdfdf; border-color: #424242; }
.sidebar-find-toggle input[type="checkbox"]:not(:checked) + svg { fill: #555; stroke: #555; }
.sidebar-find-toggle input[type="checkbox"]:checked + svg { fill: #aaa; stroke: #aaa; }
.sidebar-find-content li { color: #aaaaaa; }
.sidebar-find-content li.focus { background: #383838; color: #dfdfdf; }
.sidebar-find-content-icon { stroke: #888888; fill: #888888; }
.sidebar-item-value-expander { color: #888; }
.sidebar-item-value-expander:hover { color: #e5e5e5; }
.sidebar-item-value-button { color: #888; }
.sidebar-item-value-button svg use { fill: #888; stroke: #888; }
.sidebar-item-value-button:hover svg use { fill: #e5e5e5; stroke: #e5e5e5; }
}
@media screen and (prefers-reduced-motion: reduce) {
.menu { transition: none; }
.sidebar { transition: none; }
}
</style>
</head>
<body class="welcome spinner">
<div id="target" class="target" tabindex="0">
</div>
<div id="sidebar" class="sidebar">
<h1 id="sidebar-title" class="sidebar-title"></h1>
<a id="sidebar-closebutton" class="sidebar-closebutton" href="javascript:void(0)" draggable="false">&times;</a>
<div id="sidebar-content" class="sidebar-content"></div>
<svg width="0" height="0" display="none">
<defs>
<symbol id="sidebar-icon-node" viewBox="0 0 20 20">
<circle cx="10" cy="10" r="6" stroke-width="2" fill="none"/>
</symbol>
<symbol id="sidebar-icon-connection" viewBox="0 0 20 20">
<line x1="4" y1="10" x2="15" y2="10" stroke-width="2" />
<polyline points="11,6 15,10 11,14" stroke-width="2" />
</symbol>
<symbol id="sidebar-icon-weight" viewBox="0 0 20 20">
<circle cx="5" cy="5" r="1" />
<circle cx="10" cy="5" r="1" />
<circle cx="15" cy="5" r="1" />
<circle cx="5" cy="10" r="1" />
<circle cx="10" cy="10" r="1" />
<circle cx="15" cy="10" r="1" />
<circle cx="5" cy="15" r="1" />
<circle cx="10" cy="15" r="1" />
<circle cx="15" cy="15" r="1" />
</symbol>
</defs>
</svg>
</div>
<div id="toolbar" class="toolbar">
<button id="zoom-in-button" class="toolbar-button" title="Zoom In">
<svg class="toolbar-icon" viewbox="0 0 100 100">
<circle class="border" cx="50" cy="50" r="35" stroke-width="8" stroke="#fff"></circle>
<line class="border" x1="50" y1="38" x2="50" y2="62" stroke-width="8" stroke-linecap="round" stroke="#fff"></line>
<line class="border" x1="38" y1="50" x2="62" y2="50" stroke-width="8" stroke-linecap="round" stroke="#fff"></line>
<line class="border" x1="78" y1="78" x2="82" y2="82" stroke-width="12" stroke-linecap="square" stroke="#fff"></line>
<circle class="stroke" cx="50" cy="50" r="35" stroke-width="4"></circle>
<line class="stroke" x1="50" y1="38" x2="50" y2="62" stroke-width="4" stroke-linecap="round"></line>
<line class="stroke" x1="38" y1="50" x2="62" y2="50" stroke-width="4" stroke-linecap="round"></line>
<line class="stroke" x1="78" y1="78" x2="82" y2="82" stroke-width="8" stroke-linecap="square"></line>
</svg>
</button>
<button id="zoom-out-button" class="toolbar-button" title="Zoom Out">
<svg class="toolbar-icon" viewbox="0 0 100 100">
<circle class="border" cx="50" cy="50" r="35" stroke-width="8" stroke="#fff"></circle>
<line class="border" x1="38" y1="50" x2="62" y2="50" stroke-width="8" stroke-linecap="round" stroke="#fff"></line>
<line class="border" x1="78" y1="78" x2="82" y2="82" stroke-width="12" stroke-linecap="square" stroke="#fff"></line>
<circle class="stroke" cx="50" cy="50" r="35" stroke-width="4"></circle>
<line class="stroke" x1="38" y1="50" x2="62" y2="50" stroke-width="4" stroke-linecap="round"></line>
<line class="stroke" x1="78" y1="78" x2="82" y2="82" stroke-width="8" stroke-linecap="square"></line>
</svg>
</button>
<button id="sidebar-model-button" class="toolbar-button" title="Model Properties">
<svg class="toolbar-icon" viewbox="0 0 100 100">
<rect class="border" x="12" y="12" width="76" height="76" rx="16" ry="16" stroke-width="8"></rect>
<line class="border" x1="28" y1="37" x2="32" y2="37" stroke-width="8" stroke-linecap="round" stroke="#fff"></line>
<line class="border" x1="28" y1="50" x2="32" y2="50" stroke-width="8" stroke-linecap="round" stroke="#fff"></line>
<line class="border" x1="28" y1="63" x2="32" y2="63" stroke-width="8" stroke-linecap="round" stroke="#fff"></line>
<line class="border" x1="40" y1="37" x2="70" y2="37" stroke-width="8" stroke-linecap="round" stroke="#fff"></line>
<line class="border" x1="40" y1="50" x2="70" y2="50" stroke-width="8" stroke-linecap="round" stroke="#fff"></line>
<line class="border" x1="40" y1="63" x2="70" y2="63" stroke-width="8" stroke-linecap="round" stroke="#fff"></line>
<rect class="stroke" x="12" y="12" width="76" height="76" rx="16" ry="16" stroke-width="4"></rect>
<line class="stroke" x1="28" y1="37" x2="32" y2="37" stroke-width="4" stroke-linecap="round"></line>
<line class="stroke" x1="28" y1="50" x2="32" y2="50" stroke-width="4" stroke-linecap="round"></line>
<line class="stroke" x1="28" y1="63" x2="32" y2="63" stroke-width="4" stroke-linecap="round"></line>
<line class="stroke" x1="40" y1="37" x2="70" y2="37" stroke-width="4" stroke-linecap="round"></line>
<line class="stroke" x1="40" y1="50" x2="70" y2="50" stroke-width="4" stroke-linecap="round"></line>
<line class="stroke" x1="40" y1="63" x2="70" y2="63" stroke-width="4" stroke-linecap="round"></line>
</svg>
</button>
<button id="sidebar-target-button" class="toolbar-button" title="Target Properties">
<svg class="toolbar-icon" viewBox="0 0 100 100">
<rect class="border" x="12" y="12" width="76" height="76" rx="16" ry="16" stroke-width="8"></rect>
<circle class="border" cx="50" cy="50" r="18" stroke-width="8"></circle>
<circle class="border" cx="50" cy="50" r="3" stroke-width="8"></circle>
<rect class="stroke" x="12" y="12" width="76" height="76" rx="16" ry="16" stroke-width="4"></rect>
<circle class="stroke" cx="50" cy="50" r="18" stroke-width="4"></circle>
<circle class="stroke fill" cx="50" cy="50" r="3" stroke-width="4"></circle>
</svg>
</button>
<div id="toolbar-navigator" class="toolbar-select">
<select id="toolbar-target-selector">
<option value="Graphs" disabled="true">&#x2014; Graphs &#x2014;&#x2014;</option>
<option value="add">add</option>
<option value="subtract">subtract</option>
<option value="multiply">multiply</option>
<option value="Functions" disabled="true">&#x2014; Functions &#x2014;&#x2014;</option>
<option value="line">foo_bar.foo</option>
</select>
<svg class="toolbar-select-arrow" viewBox="0 0 10 6" width="10" height="6">
<path d="M0 0L5 6L10 0Z" />
</svg>
</div>
<div id="toolbar-path" class="toolbar-path">
<button id="toolbar-path-back-button" class="toolbar-path-back-button" title="Back">
&#x276E;
</button>
</div>
</div>
<div id="logo" class="center logo">
<a href="https://github.com/lutzroeder/netron" target="blank_">
<svg class="center logo-text" viewbox="0 0 5120 1024">
<g transform="scale(9) translate(-44,-15)">
<g transform="matrix(100,0,0,100,60.9965,126)">
<path class="logo-glyph" d="M0.089,0L0.089,-0.745L0.595,-0.147L0.595,-0.715L0.656,-0.715L0.656,0.021L0.15,-0.578L0.15,0L0.089,0Z" style="fill-rule:nonzero;"/>
</g>
<g transform="matrix(100,0,0,100,164.341,126)">
<path class="logo-glyph" d="M0.089,0L0.089,-0.715L0.443,-0.715L0.443,-0.654L0.154,-0.654L0.154,-0.43L0.443,-0.43L0.443,-0.369L0.154,-0.369L0.154,-0.061L0.443,-0.061L0.443,0L0.089,0Z" style="fill-rule:nonzero;"/>
</g>
<g transform="matrix(100,0,0,100,244.491,126)">
<path class="logo-glyph" d="M0.216,0L0.216,-0.654L0.019,-0.654L0.019,-0.715L0.478,-0.715L0.478,-0.654L0.281,-0.654L0.281,0L0.216,0Z" style="fill-rule:nonzero;"/>
</g>
<g transform="matrix(100,0,0,100,323.031,126)">
<path class="logo-glyph" d="M0.154,-0.658L0.154,-0.394L0.219,-0.394C0.28,-0.394 0.322,-0.404 0.346,-0.423C0.37,-0.442 0.382,-0.475 0.382,-0.522C0.382,-0.571 0.369,-0.606 0.345,-0.627C0.32,-0.648 0.278,-0.658 0.219,-0.658L0.154,-0.658ZM0.523,0L0.444,0L0.193,-0.341L0.154,-0.341L0.154,0L0.089,0L0.089,-0.715L0.22,-0.715C0.298,-0.715 0.356,-0.699 0.394,-0.667C0.433,-0.634 0.452,-0.585 0.452,-0.52C0.452,-0.464 0.436,-0.421 0.403,-0.389C0.37,-0.357 0.324,-0.341 0.266,-0.341L0.523,0Z" style="fill-rule:nonzero;"/>
</g>
<g transform="matrix(100,0,0,100,520.979,126)">
<path class="logo-glyph" d="M0.089,0L0.089,-0.745L0.595,-0.147L0.595,-0.715L0.656,-0.715L0.656,0.021L0.15,-0.578L0.15,0L0.089,0Z" style="fill-rule:nonzero;"/>
</g>
</g>
</svg>
<svg class="center logo-icon" viewbox="0 0 1024 1024">
<circle class="logo-stroke" cx="512" cy="512" r="431" fill="none" stroke-width="32"></circle>
<circle class="logo-border" cx="512" cy="512" r="450" fill="none" stroke-width="6"></circle>
<circle class="logo-border" cx="512" cy="512" r="412" fill="none" stroke-width="6"></circle>
<line class="logo-stroke" x1="296" y1="392" x2="540" y2="280" stroke-width="12"></line>
<line class="logo-stroke" x1="296" y1="632" x2="540" y2="280" stroke-width="12"></line>
<line class="logo-stroke" x1="296" y1="392" x2="540" y2="435" stroke-width="12"></line>
<line class="logo-stroke" x1="296" y1="632" x2="540" y2="435" stroke-width="12"></line>
<line class="logo-stroke" x1="296" y1="392" x2="540" y2="590" stroke-width="12"></line>
<line class="logo-stroke" x1="296" y1="632" x2="540" y2="590" stroke-width="12"></line>
<line class="logo-stroke" x1="296" y1="392" x2="540" y2="744" stroke-width="12"></line>
<line class="logo-stroke" x1="296" y1="632" x2="540" y2="744" stroke-width="12"></line>
<line class="logo-stroke" x1="540" y1="280" x2="785" y2="512" stroke-width="12"></line>
<line class="logo-stroke" x1="540" y1="590" x2="785" y2="512" stroke-width="12"></line>
<line class="logo-stroke" x1="540" y1="435" x2="785" y2="512" stroke-width="12"></line>
<line class="logo-stroke" x1="540" y1="744" x2="785" y2="512" stroke-width="12"></line>
<g transform="translate(296, 392)">
<circle class="logo-fill" cx="0" cy="0" r="51"></circle>
<circle class="logo-border" cx="0" cy="0" r="51" fill="none" stroke-width="6"></circle>
</g>
<g transform="translate(296, 632)">
<circle class="logo-fill" cx="0" cy="0" r="51"></circle>
<circle class="logo-border" cx="0" cy="0" r="51" fill="none" stroke-width="6"></circle>
</g>
<g transform="translate(540, 280)">
<circle class="logo-fill" cx="0" cy="0" r="51"></circle>
<circle class="logo-border" cx="0" cy="0" r="51" fill="none" stroke-width="6"></circle>
</g>
<g transform="translate(540, 435)">
<circle class="logo-fill" cx="0" cy="0" r="51"></circle>
<circle class="logo-border" cx="0" cy="0" r="51" fill="none" stroke-width="6"></circle>
</g>
<g transform="translate(540, 590)">
<circle class="logo-fill" cx="0" cy="0" r="51"></circle>
<circle class="logo-border" cx="0" cy="0" r="51" fill="none" stroke-width="6"></circle>
</g>
<g transform="translate(540, 744)">
<circle class="logo-fill" cx="0" cy="0" r="51"></circle>
<circle class="logo-border" cx="0" cy="0" r="51" fill="none" stroke-width="6"></circle>
</g>
<g transform="translate(785, 512)">
<circle class="logo-fill" cx="0" cy="0" r="51"></circle>
<circle class="logo-border" cx="0" cy="0" r="51" fill="none" stroke-width="6"></circle>
</g>
</svg>
<svg id="logo-spinner" class="center logo-spinner" viewbox="0 0 1024 1024">
<g transform="translate(512, 512)" style="opacity: 1">
<path class="logo-spinner-stroke" d="M-431,0 A-431,-431 0 0,1 0,-431" stroke-width="24" fill="None"></path>
</g>
</svg>
</a>
<a href="https://www.lutzroeder.com" target="blank_">
<svg class="center logo-name" viewbox="0 0 5120 300">
<g transform="scale(5.8) translate(20, 0)">
<g transform="matrix(30,0,0,30,18.9123,38)">
<path class="logo-glyph" d="M0.089,-0L0.089,-0.715L0.154,-0.715L0.154,-0.061L0.399,-0.061L0.399,-0L0.089,-0Z" style="fill-rule:nonzero;"/>
</g>
<g transform="matrix(30,0,0,30,46.7613,38)">
<path class="logo-glyph" d="M0.086,-0.715L0.15,-0.715L0.15,-0.248C0.15,-0.177 0.166,-0.125 0.198,-0.091C0.23,-0.056 0.28,-0.039 0.346,-0.039C0.412,-0.039 0.46,-0.056 0.493,-0.091C0.525,-0.125 0.541,-0.177 0.541,-0.248L0.541,-0.715L0.606,-0.715L0.606,-0.269C0.606,-0.172 0.584,-0.1 0.542,-0.052C0.499,-0.005 0.433,0.019 0.346,0.019C0.259,0.019 0.193,-0.005 0.15,-0.052C0.107,-0.1 0.086,-0.172 0.086,-0.269L0.086,-0.715Z" style="fill-rule:nonzero;"/>
</g>
<g transform="matrix(30,0,0,30,83.5133,38)">
<path class="logo-glyph" d="M0.216,-0L0.216,-0.654L0.019,-0.654L0.019,-0.715L0.478,-0.715L0.478,-0.654L0.281,-0.654L0.281,-0L0.216,-0Z" style="fill-rule:nonzero;"/>
</g>
<g transform="matrix(30,0,0,30,114.421,38)">
<path class="logo-glyph" d="M0.012,-0L0.437,-0.656L0.074,-0.656L0.074,-0.715L0.548,-0.715L0.125,-0.06L0.505,-0.06L0.505,-0L0.012,-0Z" style="fill-rule:nonzero;"/>
</g>
<g transform="matrix(30,0,0,30,171.777,38)">
<path class="logo-glyph" d="M0.154,-0.658L0.154,-0.394L0.219,-0.394C0.28,-0.394 0.322,-0.404 0.346,-0.423C0.37,-0.442 0.382,-0.475 0.382,-0.522C0.382,-0.571 0.369,-0.606 0.345,-0.627C0.32,-0.648 0.278,-0.658 0.219,-0.658L0.154,-0.658ZM0.523,-0L0.444,-0L0.193,-0.341L0.154,-0.341L0.154,-0L0.089,-0L0.089,-0.715L0.22,-0.715C0.298,-0.715 0.356,-0.699 0.394,-0.667C0.433,-0.634 0.452,-0.585 0.452,-0.52C0.452,-0.464 0.436,-0.421 0.403,-0.389C0.37,-0.357 0.324,-0.341 0.266,-0.341L0.523,-0Z" style="fill-rule:nonzero;"/>
</g>
<g transform="matrix(30,0,0,30,203.607,38)">
<path class="logo-glyph" d="M0.437,-0.039C0.479,-0.039 0.519,-0.047 0.557,-0.063C0.595,-0.078 0.629,-0.101 0.659,-0.131C0.689,-0.161 0.712,-0.196 0.727,-0.234C0.743,-0.273 0.751,-0.313 0.751,-0.356C0.751,-0.399 0.743,-0.44 0.728,-0.478C0.712,-0.516 0.689,-0.55 0.659,-0.581C0.63,-0.611 0.596,-0.634 0.558,-0.649C0.52,-0.665 0.48,-0.673 0.437,-0.673C0.395,-0.673 0.355,-0.665 0.317,-0.649C0.28,-0.634 0.246,-0.611 0.216,-0.581C0.186,-0.55 0.163,-0.516 0.147,-0.478C0.132,-0.44 0.124,-0.399 0.124,-0.356C0.124,-0.313 0.132,-0.272 0.147,-0.234C0.163,-0.196 0.186,-0.161 0.216,-0.131C0.246,-0.101 0.279,-0.078 0.316,-0.062C0.354,-0.047 0.394,-0.039 0.437,-0.039ZM0.82,-0.356C0.82,-0.306 0.81,-0.258 0.791,-0.212C0.772,-0.167 0.744,-0.126 0.708,-0.091C0.671,-0.055 0.63,-0.028 0.583,-0.009C0.537,0.01 0.488,0.019 0.437,0.019C0.386,0.019 0.337,0.01 0.291,-0.009C0.245,-0.028 0.203,-0.055 0.167,-0.091C0.131,-0.127 0.103,-0.168 0.084,-0.213C0.065,-0.258 0.055,-0.306 0.055,-0.356C0.055,-0.407 0.065,-0.455 0.084,-0.501C0.103,-0.546 0.131,-0.587 0.167,-0.623C0.203,-0.659 0.244,-0.685 0.29,-0.704C0.335,-0.722 0.385,-0.731 0.437,-0.731C0.49,-0.731 0.539,-0.722 0.585,-0.703C0.631,-0.685 0.672,-0.658 0.708,-0.623C0.744,-0.587 0.772,-0.546 0.791,-0.501C0.81,-0.455 0.82,-0.407 0.82,-0.356Z" style="fill-rule:nonzero;"/>
</g>
<g transform="matrix(30,0,0,30,245.853,38)">
<path class="logo-glyph" d="M0.089,-0L0.089,-0.715L0.443,-0.715L0.443,-0.654L0.154,-0.654L0.154,-0.43L0.443,-0.43L0.443,-0.369L0.154,-0.369L0.154,-0.061L0.443,-0.061L0.443,-0L0.089,-0Z" style="fill-rule:nonzero;"/>
</g>
<g transform="matrix(30,0,0,30,277.243,38)">
<path class="logo-glyph" d="M0.154,-0.056L0.245,-0.056C0.319,-0.056 0.371,-0.06 0.402,-0.069C0.433,-0.077 0.459,-0.091 0.481,-0.111C0.511,-0.139 0.534,-0.174 0.549,-0.215C0.564,-0.257 0.572,-0.305 0.572,-0.358C0.572,-0.413 0.564,-0.461 0.549,-0.502C0.533,-0.544 0.51,-0.578 0.48,-0.605C0.457,-0.625 0.429,-0.64 0.396,-0.648C0.364,-0.657 0.306,-0.661 0.224,-0.661L0.154,-0.661L0.154,-0.056ZM0.089,-0L0.089,-0.715L0.2,-0.715C0.299,-0.715 0.37,-0.71 0.412,-0.7C0.453,-0.69 0.489,-0.674 0.519,-0.65C0.559,-0.618 0.589,-0.578 0.61,-0.528C0.631,-0.478 0.641,-0.421 0.641,-0.357C0.641,-0.293 0.631,-0.236 0.61,-0.186C0.589,-0.136 0.559,-0.096 0.52,-0.066C0.489,-0.042 0.454,-0.025 0.414,-0.015C0.374,-0.005 0.31,-0 0.222,-0L0.089,-0Z" style="fill-rule:nonzero;"/>
</g>
<g transform="matrix(30,0,0,30,314.142,38)">
<path class="logo-glyph" d="M0.089,-0L0.089,-0.715L0.443,-0.715L0.443,-0.654L0.154,-0.654L0.154,-0.43L0.443,-0.43L0.443,-0.369L0.154,-0.369L0.154,-0.061L0.443,-0.061L0.443,-0L0.089,-0Z" style="fill-rule:nonzero;"/>
</g>
<g transform="matrix(30,0,0,30,345.532,38)">
<path class="logo-glyph" d="M0.154,-0.658L0.154,-0.394L0.219,-0.394C0.28,-0.394 0.322,-0.404 0.346,-0.423C0.37,-0.442 0.382,-0.475 0.382,-0.522C0.382,-0.571 0.369,-0.606 0.345,-0.627C0.32,-0.648 0.278,-0.658 0.219,-0.658L0.154,-0.658ZM0.523,-0L0.444,-0L0.193,-0.341L0.154,-0.341L0.154,-0L0.089,-0L0.089,-0.715L0.22,-0.715C0.298,-0.715 0.356,-0.699 0.394,-0.667C0.433,-0.634 0.452,-0.585 0.452,-0.52C0.452,-0.464 0.436,-0.421 0.403,-0.389C0.37,-0.357 0.324,-0.341 0.266,-0.341L0.523,-0Z" style="fill-rule:nonzero;"/>
</g>
<g transform="matrix(30,0,0,30,376.911,38)">
<path class="logo-glyph" d="M0.06,-0.468L0.155,-0.731L0.228,-0.699L0.1,-0.452L0.06,-0.468Z" style="fill-rule:nonzero;"/>
</g>
<g transform="matrix(30,0,0,30,401.549,38)">
<path class="logo-glyph" d="M0.034,-0.12L0.09,-0.15C0.1,-0.115 0.118,-0.087 0.144,-0.068C0.169,-0.049 0.2,-0.039 0.236,-0.039C0.281,-0.039 0.316,-0.052 0.342,-0.079C0.367,-0.106 0.38,-0.143 0.38,-0.19C0.38,-0.224 0.371,-0.253 0.354,-0.276C0.337,-0.299 0.3,-0.325 0.244,-0.355C0.172,-0.393 0.124,-0.427 0.101,-0.456C0.077,-0.485 0.065,-0.519 0.065,-0.56C0.065,-0.611 0.082,-0.652 0.116,-0.684C0.151,-0.716 0.195,-0.732 0.25,-0.732C0.286,-0.732 0.317,-0.724 0.344,-0.709C0.37,-0.694 0.392,-0.671 0.408,-0.641L0.358,-0.611C0.347,-0.631 0.333,-0.647 0.314,-0.658C0.295,-0.668 0.272,-0.674 0.246,-0.674C0.211,-0.674 0.183,-0.663 0.162,-0.643C0.141,-0.622 0.131,-0.594 0.131,-0.559C0.131,-0.509 0.172,-0.462 0.255,-0.419C0.27,-0.411 0.281,-0.405 0.289,-0.401C0.35,-0.367 0.391,-0.336 0.411,-0.306C0.432,-0.276 0.442,-0.237 0.442,-0.19C0.442,-0.126 0.423,-0.075 0.386,-0.037C0.348,0 0.297,0.019 0.233,0.019C0.186,0.019 0.146,0.007 0.113,-0.016C0.079,-0.039 0.053,-0.074 0.034,-0.12Z" style="fill-rule:nonzero;"/>
</g>
</g>
</svg>
</a>
<div class="center logo-message">
<div style="height: 30px; text-align: center;">Version <span id="version" class="select">{version}</span></div>
<div style="height: 30px; text-align: center;">Copyright &copy; <a href="https://www.lutzroeder.com" target="blank_">Lutz Roeder</a></div>
</div>
<a id="logo-github" class="center logo-github" href="https://github.com/lutzroeder/netron" target="blank_">
<svg viewbox="0 0 438.549 438.549">
<path class="logo-fill" d="M409.132,114.573c-19.608-33.596-46.205-60.194-79.798-79.8C295.736,15.166,259.057,5.365,219.271,5.365
c-39.781,0-76.472,9.804-110.063,29.408c-33.596,19.605-60.192,46.204-79.8,79.8C9.803,148.168,0,184.854,0,224.63
c0,47.78,13.94,90.745,41.827,128.906c27.884,38.164,63.906,64.572,108.063,79.227c5.14,0.954,8.945,0.283,11.419-1.996
c2.475-2.282,3.711-5.14,3.711-8.562c0-0.571-0.049-5.708-0.144-15.417c-0.098-9.709-0.144-18.179-0.144-25.406l-6.567,1.136
c-4.187,0.767-9.469,1.092-15.846,1c-6.374-0.089-12.991-0.757-19.842-1.999c-6.854-1.231-13.229-4.086-19.13-8.559
c-5.898-4.473-10.085-10.328-12.56-17.556l-2.855-6.57c-1.903-4.374-4.899-9.233-8.992-14.559
c-4.093-5.331-8.232-8.945-12.419-10.848l-1.999-1.431c-1.332-0.951-2.568-2.098-3.711-3.429c-1.142-1.331-1.997-2.663-2.568-3.997
c-0.572-1.335-0.098-2.43,1.427-3.289c1.525-0.859,4.281-1.276,8.28-1.276l5.708,0.853c3.807,0.763,8.516,3.042,14.133,6.851
c5.614,3.806,10.229,8.754,13.846,14.842c4.38,7.806,9.657,13.754,15.846,17.847c6.184,4.093,12.419,6.136,18.699,6.136
c6.28,0,11.704-0.476,16.274-1.423c4.565-0.952,8.848-2.383,12.847-4.285c1.713-12.758,6.377-22.559,13.988-29.41
c-10.848-1.14-20.601-2.857-29.264-5.14c-8.658-2.286-17.605-5.996-26.835-11.14c-9.235-5.137-16.896-11.516-22.985-19.126
c-6.09-7.614-11.088-17.61-14.987-29.979c-3.901-12.374-5.852-26.648-5.852-42.826c0-23.035,7.52-42.637,22.557-58.817
c-7.044-17.318-6.379-36.732,1.997-58.24c5.52-1.715,13.706-0.428,24.554,3.853c10.85,4.283,18.794,7.952,23.84,10.994
c5.046,3.041,9.089,5.618,12.135,7.708c17.705-4.947,35.976-7.421,54.818-7.421s37.117,2.474,54.823,7.421l10.849-6.849
c7.419-4.57,16.18-8.758,26.262-12.565c10.088-3.805,17.802-4.853,23.134-3.138c8.562,21.509,9.325,40.922,2.279,58.24
c15.036,16.18,22.559,35.787,22.559,58.817c0,16.178-1.958,30.497-5.853,42.966c-3.9,12.471-8.941,22.457-15.125,29.979
c-6.191,7.521-13.901,13.85-23.131,18.986c-9.232,5.14-18.182,8.85-26.84,11.136c-8.662,2.286-18.415,4.004-29.263,5.146
c9.894,8.562,14.842,22.077,14.842,40.539v60.237c0,3.422,1.19,6.279,3.572,8.562c2.379,2.279,6.136,2.95,11.276,1.995
c44.163-14.653,80.185-41.062,108.068-79.226c27.88-38.161,41.825-81.126,41.825-128.906
C438.536,184.851,428.728,148.168,409.132,114.573z"/>
</svg>
</a>
<button id="open-file-button" class="center logo-button open-file-button" tabindex="0">Open Model&hellip;</button>
<div class="center progress">
<div id="progress-bar" class="progress-bar"></div>
</div>
<input type="file" id="open-file-dialog" class="open-file-dialog" multiple="false" accept="">
<!-- Preload fonts to workaround Chrome SVG layout issue -->
<div style="font-weight: normal; color: rgba(0, 0, 0, 0.01); user-select: none;">.</div>
<div style="font-weight: bold; color: rgba(0, 0, 0, 0.01); user-select: none;">.</div>
<div style="font-weight: bold; color: rgba(0, 0, 0, 0.01); user-select: none;">.</div>
</div>
<div id="message" class="message">
<div id="message-text" class="message-text"></div>
<button id="message-button" class="logo-button message-button" tabindex="0">OK</button>
</div>
<div id="titlebar" class="titlebar">
<svg style="position: absolute; width: 0px; height: 0px; overflow: hidden;" aria-hidden="true">
<symbol id="icon-arrow-right" viewBox="0 0 1024 1024">
<path d="M698.75712 565.02272l-191.488 225.4848a81.73568 81.73568 0 0 1-62.48448 28.89728 81.89952 81.89952 0 0 1-62.40256-134.94272l146.432-172.4416-146.432-172.4416a81.92 81.92 0 0 1 124.88704-106.06592l191.488 225.4848a81.87904 81.87904 0 0 1 0 106.02496z"></path>
</symbol>
</svg>
<div id="titlebar-content" class="titlebar-content">
<span id="titlebar-content-text" class="titlebar-content-text"></span>
</div>
<div id="titlebar-control-box" class="titlebar-control-box">
<div id="titlebar-close" class="titlebar-button titlebar-button-close" title="Close">
<svg class="titlebar-icon" aria-hidden="true">
<path d="M 0,0 0,0.7 4.3,5 0,9.3 0,10 0.7,10 5,5.7 9.3,10 10,10 10,9.3 5.7,5 10,0.7 10,0 9.3,0 5,4.3 0.7,0 Z"></path>
</svg>
</div>
<div id="titlebar-toggle" class="titlebar-button" title="Maximize">
<svg id="titlebar-maximize" class="titlebar-icon" aria-hidden="true" style="position: absolute;">
<path d="M 0,0 0,10 10,10 10,0 Z M 1,1 9,1 9,9 1,9 Z"></path>
</svg>
<svg id="titlebar-restore" class="titlebar-icon" aria-hidden="true" style="position: absolute;">
<path d="m 2,1e-5 0,2 -2,0 0,8 8,0 0,-2 2,0 0,-8 z m 1,1 6,0 0,6 -1,0 0,-5 -5,0 z m -2,2 6,0 0,6 -6,0 z"></path>
</svg>
</div>
<div id="titlebar-minimize" class="titlebar-button" title="Minimize">
<svg class="titlebar-icon" aria-hidden="true">
<path d="M 0,5 10,5 10,6 0,6 Z"></path>
</svg>
</div>
</div>
</div>
<div id="menu" class="menu"></div>
<div id="menu-button" class="menu-button">&#8801;</div>
</body>
</html>
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,832 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://codex.local/trt-perf-analysis/analyze-data.schema.json",
"title": "TensorRT Performance Analysis Data",
"description": "Structured JSON emitted by scripts/analyze_trt_perf.py and consumed by the report app.",
"type": "object",
"required": [
"schema_version",
"folder",
"validation",
"results"
],
"additionalProperties": false,
"properties": {
"schema_version": {
"const": "1.0"
},
"folder": {
"type": [
"string",
"null"
]
},
"model_name": {
"type": [
"string",
"null"
]
},
"model_name_confidence": {
"type": "string",
"enum": [
"low",
"medium",
"high"
]
},
"model_name_evidence": {
"$ref": "#/$defs/string_array"
},
"model_components": {
"$ref": "#/$defs/string_array"
},
"validation": {
"$ref": "#/$defs/top_level_validation"
},
"results": {
"type": "array",
"minItems": 1,
"items": {
"$ref": "#/$defs/result"
}
}
},
"$defs": {
"top_level_validation": {
"type": "object",
"required": [
"status",
"backend_count",
"passed_count",
"failed_count",
"warnings"
],
"additionalProperties": false,
"properties": {
"status": {
"type": "string",
"enum": [
"passed",
"partial",
"failed"
]
},
"backend_count": {
"type": "integer",
"minimum": 0
},
"passed_count": {
"type": "integer",
"minimum": 0
},
"failed_count": {
"type": "integer",
"minimum": 0
},
"warnings": {
"$ref": "#/$defs/string_array"
}
}
},
"layer_profile_validation": {
"type": "object",
"required": [
"status",
"analysis_mode",
"messages",
"source_paths",
"report_available"
],
"additionalProperties": false,
"properties": {
"status": {
"const": "passed"
},
"analysis_mode": {
"const": "layer_profile"
},
"messages": {
"$ref": "#/$defs/validation_messages"
},
"source_paths": {
"$ref": "#/$defs/source_paths"
},
"report_available": {
"const": true
}
}
},
"layer_only_validation": {
"type": "object",
"required": [
"status",
"analysis_mode",
"messages",
"source_paths",
"report_available"
],
"additionalProperties": false,
"properties": {
"status": {
"const": "passed"
},
"analysis_mode": {
"const": "layer_only"
},
"messages": {
"$ref": "#/$defs/validation_messages"
},
"source_paths": {
"$ref": "#/$defs/source_paths"
},
"report_available": {
"const": true
}
}
},
"failed_validation": {
"type": "object",
"required": [
"status",
"analysis_mode",
"messages",
"source_paths",
"report_available"
],
"additionalProperties": false,
"properties": {
"status": {
"const": "failed"
},
"analysis_mode": {
"const": "unusable"
},
"messages": {
"$ref": "#/$defs/validation_messages"
},
"source_paths": {
"$ref": "#/$defs/source_paths"
},
"report_available": {
"const": false
}
}
},
"source_paths": {
"type": "object",
"required": [
"layer",
"profile"
],
"additionalProperties": false,
"properties": {
"layer": {
"type": [
"string",
"null"
]
},
"profile": {
"type": [
"string",
"null"
]
}
}
},
"validation_messages": {
"type": "array",
"items": {
"$ref": "#/$defs/validation_message"
}
},
"validation_message": {
"type": "object",
"required": [
"level",
"message"
],
"additionalProperties": false,
"properties": {
"level": {
"type": "string",
"enum": [
"error",
"warning",
"info"
]
},
"message": {
"type": "string"
}
}
},
"result": {
"oneOf": [
{
"$ref": "#/$defs/layer_profile_result"
},
{
"$ref": "#/$defs/layer_only_result"
},
{
"$ref": "#/$defs/failure_result"
}
]
},
"layer_profile_result": {
"type": "object",
"required": [
"label",
"validation",
"layers",
"profile",
"layer_count",
"profile_count",
"graph",
"count",
"count_source",
"timing",
"model",
"engines",
"records",
"type_breakdown",
"tag_breakdown",
"issues",
"positives"
],
"additionalProperties": false,
"properties": {
"label": {
"type": "string"
},
"validation": {
"$ref": "#/$defs/layer_profile_validation"
},
"layers": {
"type": "array",
"items": {
"$ref": "#/$defs/layer"
}
},
"profile": {
"type": "array",
"items": {
"$ref": "#/$defs/profile_entry"
}
},
"layer_count": {
"type": "integer",
"minimum": 0
},
"profile_count": {
"type": "integer",
"minimum": 0
},
"graph": {
"$ref": "#/$defs/graph"
},
"count": {
"type": [
"integer",
"null"
],
"minimum": 0
},
"count_source": {
"type": "string"
},
"timing": {
"$ref": "#/$defs/timing"
},
"model": {
"$ref": "#/$defs/model"
},
"engines": {
"$ref": "#/$defs/engines"
},
"records": {
"type": "array",
"items": {
"$ref": "#/$defs/record"
}
},
"type_breakdown": {
"$ref": "#/$defs/breakdown"
},
"tag_breakdown": {
"$ref": "#/$defs/breakdown"
},
"issues": {
"type": "array",
"items": {
"$ref": "#/$defs/issue"
}
},
"positives": {
"$ref": "#/$defs/string_array"
}
}
},
"layer_only_result": {
"type": "object",
"required": [
"label",
"validation",
"layers",
"layer_count",
"graph",
"model",
"engines"
],
"additionalProperties": false,
"properties": {
"label": {
"type": "string"
},
"validation": {
"$ref": "#/$defs/layer_only_validation"
},
"layers": {
"type": "array",
"items": {
"$ref": "#/$defs/layer"
}
},
"layer_count": {
"type": "integer",
"minimum": 0
},
"graph": {
"$ref": "#/$defs/graph"
},
"model": {
"$ref": "#/$defs/model"
},
"engines": {
"$ref": "#/$defs/engines"
}
}
},
"failure_result": {
"type": "object",
"required": [
"label",
"validation"
],
"additionalProperties": false,
"properties": {
"label": {
"type": "string"
},
"validation": {
"$ref": "#/$defs/failed_validation"
},
"layer_count": {
"type": "integer",
"minimum": 0
},
"profile_count": {
"type": "integer",
"minimum": 0
}
}
},
"layer": {
"type": "object",
"required": [
"Name",
"LayerType",
"Inputs",
"Outputs"
],
"additionalProperties": true,
"properties": {
"Name": {
"type": "string"
},
"LayerType": {
"type": "string"
},
"Inputs": {
"type": "array",
"items": {
"$ref": "#/$defs/tensor"
}
},
"Outputs": {
"type": "array",
"items": {
"$ref": "#/$defs/tensor"
}
},
"Constants": {
"type": "array",
"items": {
"$ref": "#/$defs/tensor"
}
},
"Metadata": {
"type": [
"string",
"object",
"array",
"null"
]
},
"StreamId": {
"type": [
"integer",
"string",
"null"
]
},
"TacticName": {
"type": [
"string",
"null"
]
}
}
},
"tensor": {
"type": "object",
"required": [
"Name"
],
"additionalProperties": true,
"properties": {
"Name": {
"type": "string"
},
"Datatype": {
"type": [
"string",
"null"
]
},
"Dimensions": {
"$ref": "#/$defs/number_array"
},
"Format": {
"type": [
"string",
"null"
]
},
"StrideOrder": {
"$ref": "#/$defs/number_array"
},
"Strides": {
"$ref": "#/$defs/number_array"
}
}
},
"profile_entry": {
"type": "object",
"required": [
"name",
"timeMs",
"averageMs",
"medianMs",
"percentage"
],
"additionalProperties": true,
"properties": {
"name": {
"type": "string"
},
"timeMs": {
"type": "number"
},
"averageMs": {
"type": "number"
},
"medianMs": {
"type": "number"
},
"percentage": {
"type": "number"
}
}
},
"record": {
"type": "object",
"required": [
"name",
"layer_type",
"raw_layer_type",
"tactic",
"time_ms",
"avg_ms",
"median_ms",
"percentage",
"tags",
"layer"
],
"additionalProperties": false,
"properties": {
"name": {
"type": "string"
},
"layer_type": {
"type": "string"
},
"raw_layer_type": {
"type": "string"
},
"tactic": {
"type": [
"string",
"null"
]
},
"time_ms": {
"type": "number"
},
"avg_ms": {
"type": "number"
},
"median_ms": {
"type": "number"
},
"percentage": {
"type": "number"
},
"tags": {
"$ref": "#/$defs/string_array"
},
"layer": {
"$ref": "#/$defs/layer"
}
}
},
"graph": {
"type": "object",
"required": [
"edge_count",
"external_inputs",
"graph_outputs"
],
"additionalProperties": false,
"properties": {
"edge_count": {
"type": "integer",
"minimum": 0
},
"external_inputs": {
"$ref": "#/$defs/string_array"
},
"graph_outputs": {
"$ref": "#/$defs/string_array"
}
}
},
"timing": {
"type": "object",
"required": [
"average_inference_ms",
"total_time_ms",
"total_percentage"
],
"additionalProperties": false,
"properties": {
"average_inference_ms": {
"type": "number"
},
"total_time_ms": {
"type": "number"
},
"total_percentage": {
"type": "number"
}
}
},
"model": {
"type": "object",
"required": [
"category",
"confidence",
"evidence",
"config_path",
"hidden_size",
"intermediate_size",
"num_layers",
"num_heads",
"max_position_embeddings",
"sequence_length",
"dynamic_dims",
"input_shapes"
],
"additionalProperties": false,
"properties": {
"name": {
"type": [
"string",
"null"
]
},
"name_confidence": {
"type": "string",
"enum": [
"low",
"medium",
"high"
]
},
"name_evidence": {
"$ref": "#/$defs/string_array"
},
"components": {
"$ref": "#/$defs/string_array"
},
"category": {
"type": "string"
},
"confidence": {
"type": "string",
"enum": [
"low",
"medium",
"high"
]
},
"evidence": {
"$ref": "#/$defs/string_array"
},
"config_path": {
"type": [
"string",
"null"
]
},
"hidden_size": {
"type": [
"integer",
"null"
]
},
"intermediate_size": {
"type": [
"integer",
"null"
]
},
"num_layers": {
"type": [
"integer",
"null"
]
},
"num_heads": {
"type": [
"integer",
"null"
]
},
"max_position_embeddings": {
"type": [
"integer",
"null"
]
},
"sequence_length": {
"type": [
"integer",
"null"
]
},
"dynamic_dims": {
"type": "boolean"
},
"input_shapes": {
"type": "object",
"additionalProperties": {
"$ref": "#/$defs/number_array"
}
}
}
},
"engines": {
"type": "object",
"required": [
"engine_files",
"subgraphs",
"streams",
"inferred_count",
"source"
],
"additionalProperties": false,
"properties": {
"engine_files": {
"$ref": "#/$defs/string_array"
},
"subgraphs": {
"type": "array",
"items": {
"type": [
"integer",
"string"
]
}
},
"streams": {
"type": "array",
"items": {
"type": [
"integer",
"string"
]
}
},
"inferred_count": {
"type": [
"integer",
"null"
],
"minimum": 0
},
"source": {
"type": "string"
}
}
},
"breakdown": {
"type": "array",
"items": {
"$ref": "#/$defs/breakdown_item"
}
},
"breakdown_item": {
"type": "object",
"required": [
"name",
"count",
"time_ms",
"percentage"
],
"additionalProperties": false,
"properties": {
"name": {
"type": "string"
},
"count": {
"type": "integer",
"minimum": 0
},
"time_ms": {
"type": "number"
},
"percentage": {
"type": "number"
}
}
},
"issue": {
"type": "object",
"required": [
"score",
"title",
"evidence",
"next_check",
"confidence"
],
"additionalProperties": false,
"properties": {
"score": {
"type": "number"
},
"title": {
"type": "string"
},
"evidence": {
"type": "string"
},
"next_check": {
"type": "string"
},
"confidence": {
"type": "string",
"enum": [
"low",
"medium",
"high"
]
}
}
},
"string_array": {
"type": "array",
"items": {
"type": "string"
}
},
"number_array": {
"type": "array",
"items": {
"type": "number"
}
}
}
}
@@ -0,0 +1,70 @@
#!/usr/bin/env python3
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
"""Validate and extract structured TensorRT layer/profile analysis data.
The script intentionally uses only Python built-in modules so it can run in
minimal benchmark environments.
"""
from __future__ import annotations
import argparse
import sys
from typing import Optional, Sequence
from trt_perf.data import DataError, extract_perf_data
from trt_perf.render_json import render_results_json
def parse_args(argv: Optional[Sequence[str]] = None) -> argparse.Namespace:
parser = argparse.ArgumentParser(
description="Validate and analyze TensorRT layer/profile JSON performance data."
)
parser.add_argument(
"path",
nargs="?",
help="Folder directly containing one or more layers_*.json/profile_*.json backend pairs. Omit when using --data.",
)
parser.add_argument(
"--data",
dest="data_specs",
action="append",
nargs="+",
metavar="PATH",
help="Explicit backend input. Use --data layers.json for layer-only analysis or --data layers.json profile.json for a layer/profile pair. Repeat for multiple backends.",
)
parser.add_argument(
"--model-name",
help="Optional model name from the user prompt or caller context. It is cleaned before serialization.",
)
parser.add_argument("--output", help="Optional path to write the generated analysis JSON.")
return parser.parse_args(argv)
def main(argv: Optional[Sequence[str]] = None) -> int:
args = parse_args(argv)
try:
data = extract_perf_data(args.path, args.data_specs, args.model_name)
except DataError as exc:
sys.stderr.write(f"error: {exc}\n")
return 2
report = render_results_json(data)
if args.output:
try:
with open(args.output, "w", encoding="utf-8") as handle:
handle.write(report)
except OSError as exc:
sys.stderr.write(f"error: unable to write {args.output}: {exc}\n")
return 2
else:
sys.stdout.write(report)
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,319 @@
#!/usr/bin/env python3
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
"""Create a standalone TensorRT performance report folder.
This script keeps report packaging deterministic and cross-platform: it runs
the analyzer, validates the generated JSON, then copies the browser report
template without relying on shell tools such as rsync.
"""
from __future__ import annotations
import argparse
import re
import shutil
import subprocess
import sys
import tempfile
from datetime import datetime
from pathlib import Path
from typing import Optional, Sequence
from trt_perf.data import infer_model_name_metadata
def script_dir() -> Path:
return Path(__file__).resolve().parent
def skill_root() -> Path:
return script_dir().parent
def default_template_dir() -> Path:
return skill_root() / "report_template"
def parse_args(argv: Optional[Sequence[str]] = None) -> argparse.Namespace:
parser = argparse.ArgumentParser(
description="Generate and package a standalone TensorRT performance report."
)
parser.add_argument(
"path",
nargs="?",
help="Folder directly containing layers_*.json and/or profile_*.json inputs. Pass one component/report folder per invocation. Omit when using --analyze-data.",
)
parser.add_argument(
"--analyze-data",
help="Existing analyze-data.json to validate and package in place. The file must be named analyze-data.json.",
)
parser.add_argument(
"--output-parent",
help="Directory where an auto-named report folder should be created. Defaults to the current working directory unless that is inside the skill.",
)
parser.add_argument(
"--report-dir",
help="Exact report directory to create. For new analysis this directory must be empty or absent; useful for component-specific reports.",
)
parser.add_argument(
"--model-name",
help="Model name from the user prompt or caller context. It is cleaned and stored in analyze-data.json.",
)
parser.add_argument(
"--timestamp",
help="Timestamp for auto-generated report folder names, formatted as YYYYMMDD_HHMMSS. Defaults to local time.",
)
parser.add_argument(
"--template-dir",
default=str(default_template_dir()),
help="Report template directory. Defaults to report_template in this skill.",
)
parser.add_argument(
"--analyze-md",
help="AI-generated Markdown analysis to copy into the report folder as analyze.md after the final analysis is written.",
)
return parser.parse_args(argv)
def input_source_paths(input_path: Path) -> list[str]:
paths: list[Path] = []
for pattern in ("layer*.json", "profile*.json"):
paths.extend(input_path.glob(pattern))
return [str(path) for path in sorted(paths)]
def inferred_analyzer_model_name(input_path: Path, explicit_name: Optional[str]) -> Optional[str]:
metadata = infer_model_name_metadata(str(input_path), explicit_name, input_source_paths(input_path))
return metadata["name"]
def infer_report_model_name(input_path: Path, explicit_name: Optional[str]) -> str:
metadata = infer_model_name_metadata(str(input_path), explicit_name, input_source_paths(input_path))
return metadata["name"] or "unknown_model"
def is_relative_to(path: Path, parent: Path) -> bool:
try:
path.relative_to(parent)
return True
except ValueError:
return False
def default_output_parent() -> Path:
cwd = Path.cwd().resolve()
root = skill_root().resolve()
if is_relative_to(cwd, root):
return Path(tempfile.gettempdir()).resolve()
return cwd
def parse_timestamp(value: Optional[str]) -> str:
if value is None:
return datetime.now().strftime("%Y%m%d_%H%M%S")
if not re.fullmatch(r"\d{8}_\d{6}", value):
raise ValueError("--timestamp must use YYYYMMDD_HHMMSS format")
return value
def has_files(path: Path) -> bool:
return path.exists() and any(path.iterdir())
def has_input_files(path: Path) -> bool:
return any(path.glob("layers_*.json")) or any(path.glob("profile_*.json"))
def next_available_report_dir(output_parent: Path, model_name: str, timestamp: str) -> Path:
base_name = f"report_{model_name}_{timestamp}"
candidate = output_parent / base_name
if not has_files(candidate):
return candidate
index = 2
while True:
candidate = output_parent / f"{base_name}_{index}"
if not has_files(candidate):
return candidate
index += 1
def resolve_new_report_dir(args: argparse.Namespace, input_path: Path) -> Path:
if args.report_dir:
report_dir = Path(args.report_dir).expanduser()
if has_files(report_dir):
raise ValueError(f"report directory is not empty: {report_dir}")
return report_dir
output_parent = Path(args.output_parent).expanduser() if args.output_parent else default_output_parent()
timestamp = parse_timestamp(args.timestamp)
model_name = infer_report_model_name(input_path, args.model_name)
return next_available_report_dir(output_parent, model_name, timestamp)
def validate_template_dir(template_dir: Path) -> None:
required = [
"index.html",
"app.js",
"styles.css",
"netron/grapher.css",
"netron/index.html",
"netron/netron.js",
"netron/worker.js",
]
missing = [name for name in required if not (template_dir / name).is_file()]
if missing:
raise ValueError(f"report template is missing required file(s): {', '.join(missing)}")
def run_command(command: Sequence[str]) -> int:
process = subprocess.run(command, text=True, check=False)
return process.returncode
def run_analyzer(input_path: Path, output_path: Path, model_name: Optional[str]) -> int:
analyzer = script_dir() / "analyze_trt_perf.py"
command = [
sys.executable,
str(analyzer),
str(input_path),
"--output",
str(output_path),
]
if model_name:
command.extend(["--model-name", model_name])
return run_command(command)
def run_validator(analyze_data_path: Path) -> int:
validator = script_dir() / "validate_analyze_data.py"
return run_command([sys.executable, str(validator), str(analyze_data_path)])
def copy_template(template_dir: Path, report_dir: Path) -> None:
validate_template_dir(template_dir)
report_dir.mkdir(parents=True, exist_ok=True)
for item in template_dir.iterdir():
if item.name in {"analyze-data.json", "analyze.md"}:
continue
destination = report_dir / item.name
if item.is_dir():
shutil.copytree(item, destination, dirs_exist_ok=True)
else:
shutil.copy2(item, destination)
def copy_analyze_markdown(analyze_md: Optional[str], report_dir: Path) -> Optional[Path]:
if not analyze_md:
return None
source = Path(analyze_md).expanduser()
if not source.is_file():
raise ValueError(f"analyze markdown file not found: {source}")
destination = report_dir / "analyze.md"
if source.resolve() == destination.resolve():
return destination
shutil.copy2(source, destination)
return destination
def package_existing_json(args: argparse.Namespace) -> int:
analyze_data_path = Path(args.analyze_data).expanduser()
if not analyze_data_path.is_file():
sys.stderr.write(f"error: analyze-data file not found: {analyze_data_path}\n")
return 2
if analyze_data_path.name != "analyze-data.json":
sys.stderr.write("error: --analyze-data must point to a file named analyze-data.json\n")
return 2
validate_result = run_validator(analyze_data_path)
if validate_result != 0:
return validate_result
try:
copy_template(Path(args.template_dir).expanduser(), analyze_data_path.parent)
analyze_markdown_path = copy_analyze_markdown(args.analyze_md, analyze_data_path.parent)
except (OSError, ValueError) as exc:
sys.stderr.write(f"error: unable to package report template: {exc}\n")
return 2
print(f"report_dir: {analyze_data_path.parent}")
print(f"analyze_data: {analyze_data_path}")
if analyze_markdown_path:
print(f"analyze_md: {analyze_markdown_path}")
return 0
def package_input_folder(args: argparse.Namespace, input_path: Path) -> int:
try:
report_dir = resolve_new_report_dir(args, input_path)
except ValueError as exc:
sys.stderr.write(f"error: {exc}\n")
return 2
report_dir.mkdir(parents=True, exist_ok=True)
analyze_data_path = report_dir / "analyze-data.json"
model_name = inferred_analyzer_model_name(input_path, args.model_name)
analyze_result = run_analyzer(input_path, analyze_data_path, model_name)
if analyze_result != 0:
return analyze_result
validate_result = run_validator(analyze_data_path)
if validate_result != 0:
return validate_result
try:
copy_template(Path(args.template_dir).expanduser(), report_dir)
analyze_markdown_path = copy_analyze_markdown(args.analyze_md, report_dir)
except (OSError, ValueError) as exc:
sys.stderr.write(f"error: unable to package report template: {exc}\n")
return 2
print(f"report_dir: {report_dir}")
print(f"analyze_data: {analyze_data_path}")
if analyze_markdown_path:
print(f"analyze_md: {analyze_markdown_path}")
return 0
def package_new_analysis(args: argparse.Namespace) -> int:
if not args.path:
sys.stderr.write("error: provide an input folder, or use --analyze-data\n")
return 2
input_path = Path(args.path).expanduser()
if not input_path.is_dir():
sys.stderr.write(f"error: input folder not found: {input_path}\n")
return 2
if has_input_files(input_path):
return package_input_folder(args, input_path)
sys.stderr.write(
f"error: no layers_*.json or profile_*.json files found directly in {input_path}. "
"package_report.py creates exactly one report per invocation; pass a single "
"component/report folder instead of a parent folder.\n"
)
return 2
def main(argv: Optional[Sequence[str]] = None) -> int:
args = parse_args(argv)
if args.path and args.analyze_data:
sys.stderr.write("error: use either an input folder or --analyze-data, not both\n")
return 2
if args.analyze_data:
return package_existing_json(args)
return package_new_analysis(args)
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,76 @@
@echo off
:: SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
:: SPDX-License-Identifier: Apache-2.0
setlocal
if "%~1"=="" (
echo usage: run.cmd ^<python-script^> [args...] 1>&2
exit /b 2
)
set "PY_CMD="
set "PY_ARG="
if not defined SKILL_PYTHON goto after_skill_python
call :select_plain "%SKILL_PYTHON%"
if defined PY_CMD goto run_python
:after_skill_python
if not defined PYTHON goto after_python_env
call :select_plain "%PYTHON%"
if defined PY_CMD goto run_python
:after_python_env
if not defined PYTHON3 goto after_python3_env
call :select_plain "%PYTHON3%"
if defined PY_CMD goto run_python
:after_python3_env
call :select_with_arg py -3
if defined PY_CMD goto run_python
call :select_plain python
if defined PY_CMD goto run_python
call :select_plain python3
if defined PY_CMD goto run_python
call :select_plain python3.12
if defined PY_CMD goto run_python
call :select_plain python3.11
if defined PY_CMD goto run_python
call :select_plain python3.10
if defined PY_CMD goto run_python
call :select_plain python3.9
if defined PY_CMD goto run_python
call :select_plain python3.8
if defined PY_CMD goto run_python
echo error: unable to find Python 3.8+ on PATH. Set SKILL_PYTHON to a Python executable. 1>&2
exit /b 127
:run_python
if "%PY_ARG%"=="" (
"%PY_CMD%" %*
) else (
"%PY_CMD%" "%PY_ARG%" %*
)
exit /b %ERRORLEVEL%
:select_plain
set "PY_CMD=%~1"
set "PY_ARG="
if "%PY_CMD%"=="" goto clear_selection
"%PY_CMD%" -c "import sys; raise SystemExit(0 if sys.version_info >= (3, 8) else 1)" >nul 2>nul
if errorlevel 1 goto clear_selection
exit /b 0
:select_with_arg
set "PY_CMD=%~1"
set "PY_ARG=%~2"
if "%PY_CMD%"=="" goto clear_selection
"%PY_CMD%" "%PY_ARG%" -c "import sys; raise SystemExit(0 if sys.version_info >= (3, 8) else 1)" >nul 2>nul
if errorlevel 1 goto clear_selection
exit /b 0
:clear_selection
set "PY_CMD="
set "PY_ARG="
exit /b 1
+56
View File
@@ -0,0 +1,56 @@
#!/usr/bin/env sh
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
set -u
if [ $# -lt 1 ]; then
printf '%s\n' "usage: run.sh <python-script> [args...]" >&2
exit 2
fi
SCRIPT_PATH=$1
shift
try_python() {
candidate=$1
launcher_arg=$2
shift 2
if [ -z "$candidate" ]; then
return 1
fi
if ! command -v "$candidate" >/dev/null 2>&1; then
return 1
fi
if [ -n "$launcher_arg" ]; then
"$candidate" "$launcher_arg" -c 'import sys; raise SystemExit(0 if sys.version_info >= (3, 8) else 1)' >/dev/null 2>&1 || return 1
exec "$candidate" "$launcher_arg" "$SCRIPT_PATH" "$@"
fi
"$candidate" -c 'import sys; raise SystemExit(0 if sys.version_info >= (3, 8) else 1)' >/dev/null 2>&1 || return 1
exec "$candidate" "$SCRIPT_PATH" "$@"
}
if [ -n "${SKILL_PYTHON:-}" ]; then
try_python "$SKILL_PYTHON" "" "$@"
fi
if [ -n "${PYTHON:-}" ]; then
try_python "$PYTHON" "" "$@"
fi
if [ -n "${PYTHON3:-}" ]; then
try_python "$PYTHON3" "" "$@"
fi
try_python python3 "" "$@"
try_python python "" "$@"
try_python py -3 "$@"
try_python python3.12 "" "$@"
try_python python3.11 "" "$@"
try_python python3.10 "" "$@"
try_python python3.9 "" "$@"
try_python python3.8 "" "$@"
printf '%s\n' "error: unable to find Python 3.8+ on PATH. Set SKILL_PYTHON to a Python executable." >&2
exit 127
@@ -0,0 +1,4 @@
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
"""Implementation modules for the TensorRT performance analysis scripts."""
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,536 @@
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
"""Process TensorRT layer-info JSON and write visualization targets."""
from __future__ import annotations
import argparse
import importlib
import json
import re
import sys
from collections import Counter
from dataclasses import dataclass
from pathlib import Path
from typing import Any
STANDARD_LAYER_KEYS = {
"Name",
"LayerType",
"Inputs",
"Constants",
"Outputs",
"TacticName",
"StreamId",
"Metadata",
"_subgraph",
}
@dataclass(frozen=True)
class ProcessedInitializer:
name: str
desc: dict[str, Any]
@dataclass(frozen=True)
class ProcessedNode:
name: str
op_type: str
inputs: list[str]
outputs: list[str]
attrs: dict[str, Any]
@dataclass(frozen=True)
class ProcessedLayerInfo:
source_path: Path
layers: list[dict[str, Any]]
nodes: list[ProcessedNode]
graph_inputs: list[str]
graph_outputs: list[str]
graph_value_info: list[str]
value_descriptors: dict[str, dict[str, Any]]
initializers: list[ProcessedInitializer]
layer_type_counts: Counter[str]
node_op_type_counts: Counter[str]
extra_layer_fields: list[str]
@dataclass(frozen=True)
class TargetSpec:
name: str
module_name: str
default_suffix: str
description: str
aliases: tuple[str, ...] = ()
TARGET_SPECS = (
TargetSpec(
name="html",
module_name="trt_perf.layer_info_html",
default_suffix=".html",
description="Standalone HTML layer graph summary",
),
)
TARGETS_BY_NAME: dict[str, TargetSpec] = {}
for target_spec in TARGET_SPECS:
TARGETS_BY_NAME[target_spec.name] = target_spec
for alias in target_spec.aliases:
TARGETS_BY_NAME[alias] = target_spec
class NameRegistry:
"""Keep target value names valid under SSA while preserving TRT names."""
def __init__(self) -> None:
self.used: set[str] = set()
def reserve(self, preferred: Any, fallback: str) -> str:
base = str(preferred).strip() if preferred is not None else ""
if not base:
base = fallback
candidate = base
suffix = 1
while candidate in self.used:
candidate = f"{base}__trt_dup{suffix}"
suffix += 1
self.used.add(candidate)
return candidate
def compact_json(value: Any) -> str:
return json.dumps(value, ensure_ascii=False, sort_keys=True, separators=(",", ":"))
def load_layers(path: Path) -> list[dict[str, Any]]:
with path.open("r", encoding="utf-8") as handle:
payload = json.load(handle)
if isinstance(payload, list):
layers = payload
elif isinstance(payload, dict):
for key in ("Layers", "layers", "LayerInfo", "layerInfo"):
if isinstance(payload.get(key), list):
layers = payload[key]
break
else:
raise ValueError("JSON object does not contain a layer list")
else:
raise ValueError("top-level JSON value must be a list or object")
bad_indexes = [idx for idx, layer in enumerate(layers) if not isinstance(layer, dict)]
if bad_indexes:
preview = ", ".join(str(idx) for idx in bad_indexes[:5])
raise ValueError(f"layer entries must be objects; invalid indexes: {preview}")
return layers
def as_descriptor_list(value: Any) -> list[dict[str, Any]]:
if not isinstance(value, list):
return []
return [item for item in value if isinstance(item, dict)]
def tensor_name(desc: dict[str, Any], fallback: str) -> str:
name = desc.get("Name")
if name is None or str(name).strip() == "":
return fallback
return str(name)
def safe_dim_param(text: Any, fallback: str) -> str:
value = re.sub(r"[^0-9A-Za-z_]+", "_", str(text)).strip("_")
if not value:
value = fallback
if value[0].isdigit():
value = f"d_{value}"
return value
def tensor_shape(desc: dict[str, Any], tensor: str) -> list[int | str] | None:
dims = desc.get("Dimensions")
if dims is None:
return None
if not isinstance(dims, list):
return None
shape: list[int | str] = []
prefix = safe_dim_param(tensor, "tensor")
for idx, dim in enumerate(dims):
if isinstance(dim, bool):
shape.append(int(dim))
elif isinstance(dim, int) and dim >= 0:
shape.append(dim)
else:
shape.append(f"{prefix}_dim{idx}")
return shape
def sanitize_op_type(layer_type: Any) -> str:
text = str(layer_type).strip() if layer_type is not None else ""
op_type = re.sub(r"[^0-9A-Za-z_]+", "_", text).strip("_")
if not op_type:
op_type = "Layer"
if not re.match(r"[A-Za-z_]", op_type[0]):
op_type = f"_{op_type}"
return op_type[:200]
KGEN_TOKEN_MAP = {
"and": "And",
"add": "Add",
"cast": "Cast",
"div": "Div",
"erf": "Erf",
"gath": "Gather",
"gemm": "Gemm",
"mean": "Mean",
"mha": "MHA",
"move": "Move",
"mul": "Mul",
"resh": "Reshape",
"sele": "Select",
"slic": "Slice",
"sqrt": "Sqrt",
"sub": "Sub",
"tanh": "Tanh",
"tran": "Transpose",
}
def strip_kgen_signature_noise(text: str) -> str:
text = re.sub(r"_0x[0-9A-Fa-f]+$", "", text)
text = re.sub(r"_myl\d+(?:_\d+)?$", "", text)
text = re.sub(r"^__?myl_", "", text)
return text.strip("_")
def split_kgen_signature(signature: str) -> list[str]:
if not signature:
return []
if "_" in signature:
return [part for part in signature.split("_") if part]
return re.findall(r"[A-Z][a-z0-9]*|[a-z0-9]+", signature)
def normalize_kgen_token(token: str) -> str:
lowered = token.lower()
if lowered in KGEN_TOKEN_MAP:
return KGEN_TOKEN_MAP[lowered]
if lowered.startswith("v") and lowered[1:].isdigit():
return lowered.upper()
return token[:1].upper() + token[1:]
def kgen_details(layer: dict[str, Any]) -> tuple[str, list[str], str]:
candidates = [str(layer.get("TacticName", "")), layer_name(layer, 0)]
for candidate in candidates:
signature = strip_kgen_signature_noise(candidate)
tokens = [normalize_kgen_token(token) for token in split_kgen_signature(signature)]
if tokens:
return signature, tokens, f"KGEN_{'_'.join(tokens)}"
return "", [], "kgen"
def node_op_type(layer: dict[str, Any]) -> tuple[str, dict[str, Any]]:
raw_layer_type = layer.get("LayerType", "Layer")
if str(raw_layer_type).lower() != "kgen":
return sanitize_op_type(raw_layer_type), {}
signature, tokens, op_type = kgen_details(layer)
attrs: dict[str, Any] = {}
if signature:
attrs["trt_kgen_signature"] = signature
if tokens:
attrs["trt_kgen_ops_json"] = compact_json(tokens)
attrs["trt_kgen_op_count"] = len(tokens)
return sanitize_op_type(op_type), attrs
def layer_name(layer: dict[str, Any], index: int) -> str:
raw = layer.get("Name")
if raw is None or str(raw).strip() == "":
return f"trt_layer_{index}"
return str(raw)
def int_attr(value: Any, default: int = 0) -> int:
if isinstance(value, bool):
return int(value)
if isinstance(value, int):
return value
try:
return int(str(value))
except (TypeError, ValueError):
return default
def process_layers(layers: list[dict[str, Any]], source_path: Path) -> ProcessedLayerInfo:
registry = NameRegistry()
external_by_original: dict[str, str] = {}
latest_by_original: dict[str, str] = {}
descriptors_by_value: dict[str, dict[str, Any]] = {}
initializers_by_original: dict[str, str] = {}
initializers: list[ProcessedInitializer] = []
produced_values: list[str] = []
consumed_values: set[str] = set()
nodes: list[ProcessedNode] = []
def map_external(desc: dict[str, Any], fallback: str) -> str:
original = tensor_name(desc, fallback)
if original not in external_by_original:
value_name = registry.reserve(original, fallback)
external_by_original[original] = value_name
descriptors_by_value[value_name] = desc
return external_by_original[original]
def map_input(desc: dict[str, Any], fallback: str) -> str:
original = tensor_name(desc, fallback)
value_name = latest_by_original.get(original)
if value_name is None:
value_name = map_external(desc, fallback)
descriptors_by_value.setdefault(value_name, desc)
consumed_values.add(value_name)
return value_name
def map_constant(desc: dict[str, Any], fallback: str) -> str:
original = tensor_name(desc, fallback)
if original not in initializers_by_original:
value_name = registry.reserve(original, fallback)
initializers_by_original[original] = value_name
descriptors_by_value[value_name] = desc
initializers.append(ProcessedInitializer(value_name, desc))
value_name = initializers_by_original[original]
descriptors_by_value.setdefault(value_name, desc)
consumed_values.add(value_name)
return value_name
def map_output(desc: dict[str, Any], fallback: str) -> str:
original = tensor_name(desc, fallback)
value_name = registry.reserve(original, fallback)
latest_by_original[original] = value_name
descriptors_by_value[value_name] = desc
produced_values.append(value_name)
return value_name
layer_type_counts: Counter[str] = Counter()
node_op_type_counts: Counter[str] = Counter()
unmapped_layer_fields: set[str] = set()
for index, layer in enumerate(layers):
inputs = as_descriptor_list(layer.get("Inputs"))
constants = as_descriptor_list(layer.get("Constants"))
outputs = as_descriptor_list(layer.get("Outputs"))
node_inputs = [
map_input(desc, f"trt_layer_{index}_input_{pos}")
for pos, desc in enumerate(inputs)
]
node_inputs.extend(
map_constant(desc, f"trt_layer_{index}_constant_{pos}")
for pos, desc in enumerate(constants)
)
node_outputs = [
map_output(desc, f"trt_layer_{index}_output_{pos}")
for pos, desc in enumerate(outputs)
]
raw_layer_type = layer.get("LayerType", "Layer")
layer_type_counts[str(raw_layer_type)] += 1
op_type, op_type_attrs = node_op_type(layer)
node_op_type_counts[op_type] += 1
extra = {key: value for key, value in layer.items() if key not in STANDARD_LAYER_KEYS}
unmapped_layer_fields.update(extra)
attrs: dict[str, Any] = {
"trt_layer_index": index,
"trt_layer_type": str(raw_layer_type),
"trt_input_count": len(inputs),
"trt_constant_count": len(constants),
"trt_output_count": len(outputs),
"trt_inputs_json": compact_json(inputs),
"trt_constants_json": compact_json(constants),
"trt_outputs_json": compact_json(outputs),
}
attrs.update(op_type_attrs)
if "TacticName" in layer:
attrs["trt_tactic_name"] = str(layer.get("TacticName", ""))
if "StreamId" in layer:
attrs["trt_stream_id"] = int_attr(layer.get("StreamId"))
if "_subgraph" in layer:
attrs["trt_subgraph"] = int_attr(layer.get("_subgraph"))
if "Metadata" in layer:
attrs["trt_metadata"] = str(layer.get("Metadata", ""))
if extra:
attrs["trt_extra_json"] = compact_json(extra)
nodes.append(
ProcessedNode(
name=layer_name(layer, index),
op_type=op_type,
inputs=node_inputs,
outputs=node_outputs,
attrs=attrs,
)
)
terminal_values = [value_name for value_name in produced_values if value_name not in consumed_values]
if not terminal_values and produced_values:
terminal_values = [produced_values[-1]]
graph_output_names = set(terminal_values)
graph_input_names = set(external_by_original.values())
graph_value_info = [
value_name
for value_name in descriptors_by_value
if value_name not in graph_input_names and value_name not in graph_output_names
]
return ProcessedLayerInfo(
source_path=source_path,
layers=layers,
nodes=nodes,
graph_inputs=list(external_by_original.values()),
graph_outputs=terminal_values,
graph_value_info=graph_value_info,
value_descriptors=descriptors_by_value,
initializers=initializers,
layer_type_counts=layer_type_counts,
node_op_type_counts=node_op_type_counts,
extra_layer_fields=sorted(unmapped_layer_fields),
)
def load_and_process_layer_info(path: Path) -> ProcessedLayerInfo:
return process_layers(load_layers(path), path)
def resolve_target_spec(target: str) -> TargetSpec:
try:
return TARGETS_BY_NAME[target]
except KeyError as exc:
valid = ", ".join(sorted(TARGETS_BY_NAME))
raise ValueError(f"unknown target {target!r}; valid targets: {valid}") from exc
def default_output_path(input_path: Path, target: str = "html") -> Path:
spec = resolve_target_spec(target)
return input_path.with_name(f"{input_path.stem}{spec.default_suffix}")
def target_names_help() -> str:
return ", ".join(spec.name for spec in TARGET_SPECS)
def load_target_module(spec: TargetSpec) -> Any:
return importlib.import_module(spec.module_name)
def write_target(processed: ProcessedLayerInfo, spec: TargetSpec, output_path: Path) -> None:
module = load_target_module(spec)
writer = getattr(module, "write", None)
if writer is None:
raise NotImplementedError(
f"target {spec.name!r} is not implemented yet; "
f"expected write(processed, output_path) in {spec.module_name}"
)
writer(processed, output_path)
def parse_args(argv: list[str]) -> argparse.Namespace:
parser = argparse.ArgumentParser(
description="Process TensorRT layer-info JSON and write one or more visualization targets."
)
parser.add_argument("input", type=Path, nargs="?", help="Path to TensorRT layer-info JSON")
parser.add_argument(
"-t",
"--target",
action="append",
dest="targets",
metavar="TARGET",
help=f"Output target; repeat for multiple outputs. Default: html. Available: {target_names_help()}",
)
parser.add_argument(
"-o",
"--output",
type=Path,
help="Output path. Only valid when writing one target.",
)
parser.add_argument(
"--output-dir",
type=Path,
help="Directory for target-specific default filenames.",
)
parser.add_argument(
"--list-targets",
action="store_true",
help="List supported target names and exit.",
)
return parser.parse_args(argv)
def list_targets() -> None:
seen: set[str] = set()
for spec in TARGET_SPECS:
aliases = f" (aliases: {', '.join(spec.aliases)})" if spec.aliases else ""
print(f"{spec.name}{aliases}: {spec.description}")
seen.add(spec.name)
alias_only = sorted(name for name, spec in TARGETS_BY_NAME.items() if spec.name not in seen)
if alias_only:
print(f"aliases: {', '.join(alias_only)}")
def resolve_requested_targets(targets: list[str] | None) -> list[TargetSpec]:
requested = targets or ["html"]
specs: list[TargetSpec] = []
seen: set[str] = set()
for target in requested:
spec = resolve_target_spec(target)
if spec.name in seen:
continue
specs.append(spec)
seen.add(spec.name)
return specs
def main(argv: list[str] | None = None) -> int:
args = parse_args(sys.argv[1:] if argv is None else argv)
if args.list_targets:
list_targets()
return 0
if args.input is None:
print("error: input is required unless --list-targets is used", file=sys.stderr)
return 1
try:
target_specs = resolve_requested_targets(args.targets)
if args.output is not None and len(target_specs) != 1:
raise ValueError("--output can only be used when writing one target")
processed = load_and_process_layer_info(args.input)
written: list[Path] = []
for spec in target_specs:
if args.output is not None:
output_path = args.output
elif args.output_dir is not None:
output_path = args.output_dir / f"{args.input.stem}{spec.default_suffix}"
else:
output_path = default_output_path(args.input, spec.name)
write_target(processed, spec, output_path)
written.append(output_path)
except Exception as exc:
print(f"error: {exc}", file=sys.stderr)
return 1
for output_path in written:
print(f"wrote {output_path}")
return 0
@@ -0,0 +1,220 @@
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
"""Write a standalone HTML visualization target for TensorRT layer-info data."""
from __future__ import annotations
import html
import json
from pathlib import Path
from typing import Any
def escape(value: Any) -> str:
return html.escape(str(value), quote=True)
def compact_json(value: Any) -> str:
return json.dumps(value, ensure_ascii=False, sort_keys=True, separators=(",", ":"))
def short(value: Any, limit: int = 180) -> str:
text = str(value)
if len(text) <= limit:
return text
return f"{text[: limit - 1]}..."
def table_rows(items: list[tuple[str, Any]]) -> str:
rows = []
for key, value in items:
rows.append(
"<tr>"
f"<th>{escape(key)}</th>"
f"<td><code>{escape(value)}</code></td>"
"</tr>"
)
return "\n".join(rows)
def write(processed: Any, output_path: Path) -> None:
layer_rows = []
for node in processed.nodes:
attrs = node.attrs
layer_rows.append(
"<tr>"
f"<td>{escape(attrs.get('trt_layer_index', ''))}</td>"
f"<td><code>{escape(short(node.name, 140))}</code></td>"
f"<td>{escape(attrs.get('trt_layer_type', node.op_type))}</td>"
f"<td>{escape(node.op_type)}</td>"
f"<td>{escape(len(node.inputs))}</td>"
f"<td>{escape(len(node.outputs))}</td>"
f"<td><code>{escape(short(attrs.get('trt_tactic_name', ''), 160))}</code></td>"
"</tr>"
)
summary_rows = table_rows(
[
("source", processed.source_path),
("layers", len(processed.layers)),
("nodes", len(processed.nodes)),
("graph inputs", len(processed.graph_inputs)),
("graph outputs", len(processed.graph_outputs)),
("initializers", len(processed.initializers)),
("layer types", compact_json(dict(processed.layer_type_counts))),
("node op types", compact_json(dict(processed.node_op_type_counts))),
]
)
graph_inputs = "\n".join(f"<li><code>{escape(name)}</code></li>" for name in processed.graph_inputs)
graph_outputs = "\n".join(f"<li><code>{escape(name)}</code></li>" for name in processed.graph_outputs)
document = f"""<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>TensorRT Layer Info: {escape(processed.source_path.name)}</title>
<style>
:root {{
color-scheme: light dark;
--bg: #f8f9fb;
--fg: #17202a;
--muted: #586476;
--line: #d7dce5;
--panel: #ffffff;
--accent: #0f766e;
--code: #0b4a64;
}}
@media (prefers-color-scheme: dark) {{
:root {{
--bg: #111417;
--fg: #edf1f5;
--muted: #aab4c0;
--line: #303942;
--panel: #181d22;
--accent: #5eead4;
--code: #93c5fd;
}}
}}
* {{ box-sizing: border-box; }}
body {{
margin: 0;
background: var(--bg);
color: var(--fg);
font: 14px/1.5 system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
}}
main {{
width: min(1400px, calc(100vw - 32px));
margin: 0 auto;
padding: 28px 0 48px;
}}
h1 {{
margin: 0 0 6px;
font-size: 24px;
letter-spacing: 0;
}}
h2 {{
margin: 28px 0 10px;
font-size: 17px;
letter-spacing: 0;
}}
p {{ margin: 0 0 16px; color: var(--muted); }}
code {{
color: var(--code);
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
font-size: 12px;
word-break: break-word;
}}
table {{
width: 100%;
border-collapse: collapse;
background: var(--panel);
border: 1px solid var(--line);
}}
th, td {{
border-bottom: 1px solid var(--line);
padding: 8px 10px;
text-align: left;
vertical-align: top;
}}
th {{
color: var(--muted);
font-weight: 600;
white-space: nowrap;
}}
thead th {{
position: sticky;
top: 0;
background: var(--panel);
z-index: 1;
}}
tbody tr:last-child td, tbody tr:last-child th {{ border-bottom: 0; }}
.summary th {{ width: 160px; }}
.split {{
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 16px;
}}
.panel {{
background: var(--panel);
border: 1px solid var(--line);
padding: 12px 14px;
}}
ul {{
margin: 0;
padding-left: 18px;
}}
@media (max-width: 780px) {{
main {{ width: min(100vw - 20px, 1400px); padding-top: 18px; }}
.split {{ grid-template-columns: 1fr; }}
th, td {{ padding: 7px 8px; }}
}}
</style>
</head>
<body>
<main>
<h1>TensorRT Layer Info</h1>
<p>{escape(processed.source_path)}</p>
<h2>Summary</h2>
<table class="summary"><tbody>
{summary_rows}
</tbody></table>
<h2>Graph Endpoints</h2>
<div class="split">
<section class="panel">
<h2>Inputs</h2>
<ul>{graph_inputs}</ul>
</section>
<section class="panel">
<h2>Outputs</h2>
<ul>{graph_outputs}</ul>
</section>
</div>
<h2>Layers</h2>
<table>
<thead>
<tr>
<th>#</th>
<th>Name</th>
<th>Layer Type</th>
<th>Op Type</th>
<th>Inputs</th>
<th>Outputs</th>
<th>Tactic</th>
</tr>
</thead>
<tbody>
{chr(10).join(layer_rows)}
</tbody>
</table>
</main>
</body>
</html>
"""
output_path.parent.mkdir(parents=True, exist_ok=True)
output_path.write_text(document, encoding="utf-8")
@@ -0,0 +1,22 @@
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
"""Serialize extracted TensorRT performance data as JSON."""
from __future__ import annotations
import json
from typing import Any, Dict
def json_ready(value: Any) -> Any:
if isinstance(value, dict):
return {str(key): json_ready(item) for key, item in value.items()}
if isinstance(value, (list, tuple)):
return [json_ready(item) for item in value]
if isinstance(value, (str, int, float, bool)) or value is None:
return value
return str(value)
def render_results_json(data: Dict[str, Any], indent: int = 2) -> str:
return json.dumps(json_ready(data), indent=indent, sort_keys=True) + "\n"
@@ -0,0 +1,246 @@
#!/usr/bin/env python3
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
"""Validate analyzer JSON output against schemas/analyze-data.schema.json.
This intentionally implements only the JSON Schema keywords used by the local
schema so it can run without third-party dependencies in benchmark environments.
"""
from __future__ import annotations
import argparse
import json
import os
import sys
from typing import Any, List, Optional, Sequence
class ValidationError(Exception):
"""Raised for schema loading or schema reference errors."""
def default_schema_path() -> str:
script_dir = os.path.dirname(os.path.abspath(__file__))
return os.path.abspath(os.path.join(script_dir, "..", "schemas", "analyze-data.schema.json"))
def load_json(path: str) -> Any:
try:
with open(path, "r", encoding="utf-8") as handle:
return json.load(handle)
except FileNotFoundError as exc:
raise ValidationError(f"file not found: {path}") from exc
except json.JSONDecodeError as exc:
raise ValidationError(f"invalid JSON in {path}: line {exc.lineno}, column {exc.colno}: {exc.msg}") from exc
except OSError as exc:
raise ValidationError(f"unable to read {path}: {exc}") from exc
def parse_args(argv: Optional[Sequence[str]] = None) -> argparse.Namespace:
parser = argparse.ArgumentParser(
description="Validate TensorRT analysis JSON generated by analyze_trt_perf.py."
)
parser.add_argument("input", help="Path to generated analysis JSON.")
parser.add_argument(
"--schema",
default=default_schema_path(),
help="Schema path. Defaults to schemas/analyze-data.schema.json.",
)
parser.add_argument(
"--max-errors",
type=int,
default=40,
help="Maximum validation errors to print. Defaults to 40.",
)
return parser.parse_args(argv)
def decode_ref_token(token: str) -> str:
return token.replace("~1", "/").replace("~0", "~")
def resolve_ref(ref: str, root_schema: Any) -> Any:
if not ref.startswith("#/"):
raise ValidationError(f"only local JSON Schema refs are supported: {ref}")
current = root_schema
for token in ref[2:].split("/"):
key = decode_ref_token(token)
if not isinstance(current, dict) or key not in current:
raise ValidationError(f"unresolved schema ref: {ref}")
current = current[key]
return current
def type_label(instance: Any) -> str:
if instance is None:
return "null"
if isinstance(instance, bool):
return "boolean"
if isinstance(instance, int):
return "integer"
if isinstance(instance, float):
return "number"
if isinstance(instance, str):
return "string"
if isinstance(instance, list):
return "array"
if isinstance(instance, dict):
return "object"
return type(instance).__name__
def matches_type(instance: Any, expected: str) -> bool:
if expected == "null":
return instance is None
if expected == "boolean":
return isinstance(instance, bool)
if expected == "integer":
return isinstance(instance, int) and not isinstance(instance, bool)
if expected == "number":
return isinstance(instance, (int, float)) and not isinstance(instance, bool)
if expected == "string":
return isinstance(instance, str)
if expected == "array":
return isinstance(instance, list)
if expected == "object":
return isinstance(instance, dict)
raise ValidationError(f"unsupported schema type: {expected}")
def matches_any_type(instance: Any, expected: Any) -> bool:
if isinstance(expected, str):
return matches_type(instance, expected)
if isinstance(expected, list):
return any(matches_type(instance, item) for item in expected)
raise ValidationError(f"invalid schema type declaration: {expected!r}")
def append_error(errors: List[str], path: str, message: str) -> None:
errors.append(f"{path}: {message}")
def append_nearest_errors(errors: List[str], matches: List[List[str]], limit: int = 5) -> None:
if not matches:
return
nearest = min(matches, key=lambda item: (sum("expected constant" in error for error in item), len(item)))
for error in nearest[:limit]:
errors.append(f" nearest schema: {error}")
def validate(instance: Any, schema: Any, path: str, root_schema: Any) -> List[str]:
if not isinstance(schema, dict):
raise ValidationError(f"{path}: schema node is not an object")
if "$ref" in schema:
return validate(instance, resolve_ref(str(schema["$ref"]), root_schema), path, root_schema)
errors: List[str] = []
if "allOf" in schema:
for subschema in schema["allOf"]:
errors.extend(validate(instance, subschema, path, root_schema))
if "anyOf" in schema:
matches = [validate(instance, subschema, path, root_schema) for subschema in schema["anyOf"]]
if not any(not match_errors for match_errors in matches):
append_error(errors, path, "does not match any allowed schema")
append_nearest_errors(errors, matches)
if "oneOf" in schema:
matches = [validate(instance, subschema, path, root_schema) for subschema in schema["oneOf"]]
match_count = sum(1 for match_errors in matches if not match_errors)
if match_count != 1:
append_error(errors, path, f"matches {match_count} schemas, expected exactly one")
if match_count == 0:
append_nearest_errors(errors, matches)
if "const" in schema and instance != schema["const"]:
append_error(errors, path, f"expected constant {schema['const']!r}, got {instance!r}")
if "enum" in schema and instance not in schema["enum"]:
append_error(errors, path, f"expected one of {schema['enum']!r}, got {instance!r}")
expected_type = schema.get("type")
if expected_type is not None and not matches_any_type(instance, expected_type):
append_error(errors, path, f"expected type {expected_type!r}, got {type_label(instance)}")
return errors
if isinstance(instance, dict):
validate_object(instance, schema, path, root_schema, errors)
elif isinstance(instance, list):
validate_array(instance, schema, path, root_schema, errors)
elif isinstance(instance, (int, float)) and not isinstance(instance, bool):
validate_number(instance, schema, path, errors)
return errors
def validate_object(instance: dict, schema: dict, path: str, root_schema: Any, errors: List[str]) -> None:
properties = schema.get("properties", {})
required = schema.get("required", [])
additional = schema.get("additionalProperties", True)
for key in required:
if key not in instance:
append_error(errors, path, f"missing required property {key!r}")
for key, value in instance.items():
child_path = f"{path}.{key}"
if key in properties:
errors.extend(validate(value, properties[key], child_path, root_schema))
elif additional is False:
append_error(errors, child_path, "additional property is not allowed")
elif isinstance(additional, dict):
errors.extend(validate(value, additional, child_path, root_schema))
def validate_array(instance: list, schema: dict, path: str, root_schema: Any, errors: List[str]) -> None:
min_items = schema.get("minItems")
if isinstance(min_items, int) and len(instance) < min_items:
append_error(errors, path, f"expected at least {min_items} items, got {len(instance)}")
max_items = schema.get("maxItems")
if isinstance(max_items, int) and len(instance) > max_items:
append_error(errors, path, f"expected at most {max_items} items, got {len(instance)}")
items_schema = schema.get("items")
if isinstance(items_schema, dict):
for index, value in enumerate(instance):
errors.extend(validate(value, items_schema, f"{path}[{index}]", root_schema))
def validate_number(instance: float, schema: dict, path: str, errors: List[str]) -> None:
if "minimum" in schema and instance < schema["minimum"]:
append_error(errors, path, f"expected >= {schema['minimum']}, got {instance}")
if "maximum" in schema and instance > schema["maximum"]:
append_error(errors, path, f"expected <= {schema['maximum']}, got {instance}")
def main(argv: Optional[Sequence[str]] = None) -> int:
args = parse_args(argv)
try:
data = load_json(args.input)
schema = load_json(args.schema)
errors = validate(data, schema, "$", schema)
except ValidationError as exc:
sys.stderr.write(f"error: {exc}\n")
return 2
if errors:
sys.stderr.write(f"{args.input}: FAIL ({len(errors)} schema error(s))\n")
for error in errors[: args.max_errors]:
sys.stderr.write(f"- {error}\n")
remaining = len(errors) - args.max_errors
if remaining > 0:
sys.stderr.write(f"... {remaining} more error(s) omitted\n")
return 1
sys.stdout.write(f"{args.input}: PASS\n")
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,12 @@
#!/usr/bin/env python3
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
"""Standalone TensorRT layer-info HTML visualizer."""
from __future__ import annotations
from trt_perf.layer_info import main
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,418 @@
---
name: trt-strong-typing-migration
description: >-
Migrate a TensorRT build from weak typing (deprecated 10.12, removed 11.0) to
strong typing — across Python INetworkDefinition builders, the trtexec CLI, and
C++ builder code. Use when a TRT 11 upgrade breaks a weakly-typed build. Triggers: weakly typed to strongly typed,
kSTRONGLY_TYPED, weak typing deprecated, kFP16/kINT8 removed, setPrecision rejected,
setComputePrecision deprecated, do I still need --stronglyTyped, how to add the
kSTRONGLY_TYPED flag, ModelOpt autocast, INT8 on TRT 11. NOT for ONNX import
(`trt-onnx-quickstart`), Torch-TRT (`trt-torch-quickstart`), or C++ deploy
(`trt-cpp-runtime-quickstart`).
license: Apache-2.0
metadata:
author: NVIDIA Corporation
version: "1.0"
tags:
- migration
- strong-typing
- autocast
- modelopt
- builder
---
# Migrate Weakly-Typed to Strongly-Typed TensorRT Build
Strong typing is the recommended build mode for TensorRT 11.x; weak typing was
deprecated in 10.12 and is scheduled for removal. This skill migrates a weakly-typed
build flow to a strongly-typed equivalent, one path per entry point (Python network,
`trtexec`, C++ builder), plus the ModelOpt AutoCast pre-step needed when the source
is an ONNX file that must run in mixed precision.
**Version behavior.** On **TensorRT 11.x strong typing is unconditional** — every
network is strongly typed, the `kSTRONGLY_TYPED` creation flag is accepted but
ignored, and `trtexec --stronglyTyped` is a no-op (default-on). On **10.1210.x**
you opt in explicitly via that flag / `--stronglyTyped`. Setting it is therefore
always safe: required on 10.x, harmless on 11.x. Either way the migration is the
same — remove the precision *hints* (gone in 11.x) and move precision into the graph.
## When to Use
| Scenario | Use this skill? |
|----------|-----------------|
| Existing Python build calls `config.set_flag(trt.BuilderFlag.FP16 / INT8 / TF32)` | Yes |
| Existing C++ build calls `config->setFlag(BuilderFlag::kFP16 / kINT8 / kTF32)` | Yes |
| Existing `trtexec` line uses `--fp16`, `--int8`, `--best`, `--bf16` and the developer wants strong typing | Yes |
| Build emits "weak typing is deprecated, use STRONGLY_TYPED" warning at runtime | Yes |
| Build fails with "flag X is incompatible with strongly typed networks" | Yes |
| Developer has a FP32 ONNX file and wants a strongly-typed mixed FP16 engine | Yes — start at Step 1 (AutoCast) |
| Developer is importing a brand-new ONNX file with no existing build flow | No — use `trt-onnx-quickstart` first; come back here to switch the flag |
| Developer's engine produces wrong outputs *after* migration | No — use `trt-quantization-accuracy` |
| Developer is on TensorRT 10.11 or earlier | No — strong typing is fully available from 10.12 onward; upgrade before migrating |
## Prerequisites
1. **TensorRT 10.12 or later (11.x recommended).** Check:
```bash
python3 -c "import tensorrt; print(tensorrt.__version__)"
trtexec --help 2>&1 | grep -i stronglyTyped
```
The version check is the determinant — strong typing is available from 10.12
on. The `trtexec` probe just confirms the CLI flag for the trtexec path; a
missing `--stronglyTyped` means a pre-10.12 install.
2. **ModelOpt installed when the source is an ONNX file requiring mixed precision.**
```bash
pip install nvidia-modelopt onnx onnxruntime
```
If the ONNX file is already strongly typed (contains explicit `Cast` nodes
and FP16 initializers), AutoCast is not needed — skip directly to Step 2.
3. **A working baseline.** Capture the weakly-typed engine's outputs on a known
input set before migrating. Strong typing is stricter, and any silent precision
substitutions weak typing was making will become observable.
4. **The verifier script.** `scripts/verify.sh` runs `migrate.py` against a
committed sample and asserts the rewrite is well-formed. Run it before editing
anything to confirm the helper works.
## Migration paths
There are three migration paths — Python builder, `trtexec`, C++ builder.
**Infer the path from the query when signals are clear; ask only if genuinely
uncertain.** Match the strongest signal:
| Signal in the request | Path | Entry point |
|------------------------|------|-------------|
| Python tokens: `trt.BuilderFlag`, `set_flag(`, `create_network(`, `.py` | **A** | Python `INetworkDefinition` builder |
| Standalone `trtexec --fp16` / `--int8` / `--best` / `--bf16` | **B** | `trtexec` CLI |
| C++ tokens: `BuilderFlag::`, `createNetworkV2`, `->setFlag`, `.cpp` / `.h` | **C** | C++ builder |
| Multiple signals | — | do each, most-specific first |
| No usable signal (generic "how do I migrate") | — | ask which entry point, or proceed with Python and say you assumed it |
The migration preserves existing precisions (kFP16 → FP16 cast, kINT8 → Q/DQ) and is
identical whether the network came from a parser or was hand-built — only Step 1
(AutoCast) is ONNX-specific, so source and precision goal rarely need a question.
Inferring the path is a low-stakes default — it does **not** extend to destructive
operations, which always need confirmation. `migrate.py` defaults to dry-run
(prints a diff); the `--write` flag is the human-in-the-loop confirmation. Treat
any other destructive step (overwriting a `.plan`, replacing an ONNX) the same way:
show, confirm, then act.
For a strongly-typed INT8 model straight from an ONNX file (not a migration of
existing builder code), jump to Step 1's ModelOpt quantization recipe.
## Step 1 — (ONNX source only) AutoCast the model to mixed precision
Skip if the source is not ONNX, or if the ONNX file is already strongly typed.
A FP32 ONNX file built for a weakly-typed `--fp16` flow carries no precision
information — TensorRT was free to pick FP16 or FP32 per layer. Strong typing
requires the graph itself to declare precision via `Cast` operators and typed
initializers. ModelOpt's AutoCast inserts these.
Use the ModelOpt CLI — it's the supported entry point and takes `.npz` calibration
directly. Use `--calibration_data` for both tools (autocast's canonical flag;
quantization's abbreviation of `--calibration_data_path`):
```bash
# FP16 / BF16 mixed-precision AutoCast
python -m modelopt.onnx.autocast \
--onnx_path model.fp32.onnx \
--output_path model.mixed.onnx \
--low_precision_type fp16 \
--calibration_data calib.npz
# INT8 / FP8 strongly-typed quantization (produces ONNX with explicit Q/DQ)
python -m modelopt.onnx.quantization \
--onnx_path model.fp32.onnx \
--output_path model.int8.onnx \
--quantize_mode int8 \
--high_precision_dtype fp16 \
--calibration_data calib.npz
```
Keep accuracy-sensitive ops in FP32 via AutoCast's node/op-type exclusion lists
(e.g. exclude `MatMul`/`LayerNormalization` on transformer-heavy networks), and keep
FP32 graph I/O when downstream stages expect it. See the
[Model Optimizer docs](https://nvidia.github.io/Model-Optimizer) for the exact
module and flags for your ModelOpt version.
Verify the converted model in ONNX Runtime before passing it to TRT:
```python
import onnxruntime, numpy as np
sess = onnxruntime.InferenceSession("model.mixed.onnx", providers=["CPUExecutionProvider"])
# Compare a few outputs against the FP32 model with np.allclose(..., rtol=5e-3, atol=5e-3).
```
If parity fails here, **stop** — the conversion is wrong and no amount of TRT
engine work will recover it. Re-run with tighter exclusion lists.
**Next step — build the strongly-typed plan from the converted ONNX:**
```bash
# For the INT8 case (modelopt.onnx.quantization output)
trtexec --onnx=model.int8.onnx --stronglyTyped --saveEngine=model.int8.plan
# For the mixed-precision case (modelopt.onnx.autocast output)
trtexec --onnx=model.mixed.onnx --stronglyTyped --saveEngine=model.fp16.plan
```
**Critical**: do NOT pass `--int8` or `--fp16` alongside `--stronglyTyped`. Those flags are removed in TRT 11, and on TRT 10.12+ they conflict with strong typing. The precision is already encoded in the ONNX (via explicit Cast / Q / DQ nodes); the builder honors it.
The reference recipe for the end-to-end INT8 flow is `samples/python/strongly_type_autocast/sample.py` in `tensorrt-oss`.
## Step 2A — Migrate a Python `INetworkDefinition` builder
Mechanical replacements:
| Weakly typed | Strongly typed |
|--------------|----------------|
| `builder.create_network(0)` or `builder.create_network(1 << int(NetworkDefinitionCreationFlag.EXPLICIT_BATCH))` | `builder.create_network(1 << int(NetworkDefinitionCreationFlag.STRONGLY_TYPED))` |
| `config.set_flag(trt.BuilderFlag.FP16)` | **Remove.** Types come from the network. |
| `config.set_flag(trt.BuilderFlag.BF16)` | **Remove.** |
| `config.set_flag(trt.BuilderFlag.INT8)` | **Remove.** Quantization expressed via Q/DQ nodes in the graph. |
| `config.set_flag(trt.BuilderFlag.TF32)` | **Keep.** `kTF32` is retained in 11.x — it allows (not requires) TF32 for FP32 tensors and is orthogonal to typing. |
| `layer.precision = trt.float16` | **Remove.** Cast the input tensor instead (`network.add_cast`). |
| `layer.set_output_type(0, trt.float16)` | **Remove.** Output type follows the network; for cast/quantize/dequantize/fill layers use `set_to_type` instead. |
| `builder.platform_has_fast_int8 / has_fast_fp16` checks gating the above | **Remove the gating logic** along with the flag. |
Worked rewrite (the MNIST builder from `samples/python/network_api_pytorch_mnist/sample.py`
already uses the strongly-typed form — note `NetworkDefinitionCreationFlag.STRONGLY_TYPED`
and the absence of any `BuilderFlag.FP16` call):
```python
builder = trt.Builder(TRT_LOGGER)
network = builder.create_network(1 << int(trt.NetworkDefinitionCreationFlag.STRONGLY_TYPED))
config = builder.create_builder_config()
config.set_memory_pool_limit(trt.MemoryPoolType.WORKSPACE, common.GiB(1))
# ... populate network ...
plan = builder.build_serialized_network(network, config)
```
If the network is built via the ONNX parser, the parser respects strong typing
automatically — no code change is needed beyond the flag.
Where a removed `precision` / `set_output_type` hint actually mattered, encode it
in the graph: cast a tensor's dtype with `add_cast`, or insert an explicit
Quantize/Dequantize pair for INT8 (the scale is a build-time constant; ModelOpt
derives it from calibration, or supply it by hand):
```python
# kFP16 hint -> explicit cast on the tensor:
cast = network.add_cast(some_tensor, trt.float16)
# kINT8 hint -> explicit Q/DQ pair (per-tensor scalar scale). NOTE: trt.Weights does
# not copy the numpy buffer, so q_scale must stay alive until the engine is built.
q_scale = np.array(1.0 / 127.0, dtype=np.float32).reshape(()) # rank-0 scalar
scale = network.add_constant(shape=(), weights=trt.Weights(q_scale)).get_output(0)
q = network.add_quantize(some_tensor, scale, trt.int8).get_output(0)
dq = network.add_dequantize(q, scale, trt.float32).get_output(0)
# Feed dq into the consumer; TensorRT fuses the Q/DQ into it so it runs in INT8.
```
Drive the rewrite from `scripts/migrate.py`:
```bash
python3 scripts/migrate.py path/to/build.py # dry-run diff
python3 scripts/migrate.py path/to/build.py --write # rewrite in place
```
The script edits AST nodes (not regexes) — it understands `create_network` and
`set_flag` call shapes, so it is safe on files that mix conventions.
## Step 2B — Migrate a `trtexec` command line
Replacements:
| Weakly typed flag | Strongly typed equivalent |
|-------------------|---------------------------|
| `--fp16` | **Remove.** Precision comes from the ONNX. See the `--stronglyTyped` note below. |
| `--bf16` | **Remove.** Same as `--fp16`. |
| `--int8` | **Remove.** Q/DQ must be present in the ONNX. |
| `--best` | **Remove.** Not allowed with `--stronglyTyped` on 10.1210.x (see Common Errors). |
| `--stronglyTyped` | **Version-gated.** Required on 10.1210.x to opt into strong typing; a no-op (default-on) on 11.x — drop it or leave it, your choice. |
| `--noTF32` | **Keep.** `kTF32` is retained in 11.x; `--noTF32` still disables TF32 and is orthogonal to typing. |
| `--precisionConstraints=obey` / `prefer` | **Remove.** Constraints are expressed in the graph. |
| `--layerPrecisions=...` | **Remove.** Use ONNX `Cast` nodes / AutoCast. |
| `--layerOutputTypes=...` | **Remove.** |
Worked rewrite:
```bash
# Before (weakly typed)
trtexec --onnx=model.onnx --fp16 --saveEngine=model.fp16.plan \
--minShapes=input:1x3x224x224 --optShapes=input:8x3x224x224 \
--maxShapes=input:16x3x224x224
# After (strongly typed; ONNX has been AutoCast'd to mixed FP16)
# On 11.x --stronglyTyped is the default and a no-op — the line below is equivalent
# with or without it. On 10.1210.x it is REQUIRED, or the build stays weakly typed.
trtexec --onnx=model.mixed.onnx --stronglyTyped --saveEngine=model.fp16.plan \
--minShapes=input:1x3x224x224 --optShapes=input:8x3x224x224 \
--maxShapes=input:16x3x224x224
```
The `trtexec` README's [Example 6](https://github.com/NVIDIA/TensorRT/blob/main/samples/trtexec/README.md#example-6-create-a-strongly-typed-plan-file)
is the canonical reference.
## Step 2C — Migrate a C++ builder
Replacements mirror the Python path:
| Weakly typed (C++) | Strongly typed (C++) |
|---|---|
| `builder->createNetworkV2(0)` | `builder->createNetworkV2(1U << static_cast<uint32_t>(NetworkDefinitionCreationFlag::kSTRONGLY_TYPED))` |
| `config->setFlag(BuilderFlag::kFP16)` | **Remove.** |
| `config->setFlag(BuilderFlag::kBF16)` | **Remove.** |
| `config->setFlag(BuilderFlag::kINT8)` | **Remove.** |
| `config->setFlag(BuilderFlag::kTF32)` | **Keep.** `kTF32` is retained in 11.x — orthogonal to typing. |
| `config->setFlag(BuilderFlag::kOBEY_PRECISION_CONSTRAINTS / kPREFER_PRECISION_CONSTRAINTS)` | **Remove.** No per-layer precision exists to constrain. |
| `layer->setPrecision(DataType::kHALF)` | **Remove.** Cast the input tensor instead (`addCast`). |
| `layer->setComputePrecision(...)` | **Remove.** Deprecated 10.16, removed; compute precision follows input dtypes. |
| `layer->setOutputType(0, DataType::kHALF)` | **Remove.** For cast/quantize/dequantize/fill layers use `setToType()` instead. |
Worked rewrite:
```cpp
// Before (10.x weakly typed) — the builder picks precision from these hints:
auto network = std::unique_ptr<INetworkDefinition>(
builder->createNetworkV2(1U << static_cast<uint32_t>(NetworkDefinitionCreationFlag::kEXPLICIT_BATCH)));
auto config = std::unique_ptr<IBuilderConfig>(builder->createBuilderConfig());
config->setFlag(BuilderFlag::kFP16); // hint: builder may use FP16
config->setFlag(BuilderFlag::kINT8); // hint: builder may use INT8 (+ a calibrator)
config->setFlag(BuilderFlag::kOBEY_PRECISION_CONSTRAINTS); // force the per-layer overrides below
someLayer->setPrecision(DataType::kHALF); // per-layer override
someLayer->setOutputType(0, DataType::kHALF); // per-layer override
```
Each hint above maps to a concrete graph edit — there is no longer a "tell the
builder to prefer X" knob, so state the dtype directly:
```cpp
// After (11.x strongly typed) — precision lives in the graph; every line above is gone.
auto network = std::unique_ptr<INetworkDefinition>(
builder->createNetworkV2(1U << static_cast<uint32_t>(NetworkDefinitionCreationFlag::kSTRONGLY_TYPED)));
// kSTRONGLY_TYPED: required on 10.1210.x, accepted-but-ignored on 11.x (0 works too).
auto config = std::unique_ptr<IBuilderConfig>(builder->createBuilderConfig());
// No precision setFlag, no setPrecision / setOutputType.
// kFP16 hint -> cast the tensor's dtype explicitly:
auto* cast = network->addCast(*someTensor, DataType::kHALF);
// feed cast->getOutput(0) into the consumer layer
// kINT8 hint -> insert an explicit Quantize/Dequantize pair. The scale is a
// build-time constant (per-tensor scalar here); ModelOpt derives it from calibration,
// or supply it by hand. NOTE: TensorRT does not copy Weights, so qScale must stay
// alive until the engine is built.
float qScale = 1.0f / 127.0f;
auto* scale = network->addConstant(Dims{0, {}},
Weights{DataType::kFLOAT, &qScale, 1})->getOutput(0);
auto* q = network->addQuantize(*someTensor, *scale, DataType::kINT8)->getOutput(0);
auto* dq = network->addDequantize(*q, *scale, DataType::kFLOAT)->getOutput(0);
// feed dq into the consumer; TensorRT fuses the Q/DQ into it so it runs in INT8.
// kOBEY_PRECISION_CONSTRAINTS + setPrecision/setOutputType -> nothing to port:
// the dtype is now fixed by the cast / Q-DQ above, so there is no constraint to obey.
```
`setToType()` is the only surviving per-layer type control, and is valid **only**
on `ICastLayer`, `IDequantizeLayer`, `IDynamicQuantizeLayer`, `IFillLayer`, and
`IQuantizeLayer` — not on arbitrary layers.
If the build pipeline parses ONNX via `nvonnxparser::IParser`, no parser change is
required — the parser honors the strong typing flag set on the network.
## Step 3 — Rebuild and validate
1. Rebuild the engine with the migrated flow.
2. Run the captured baseline inputs through the new engine.
3. Compare against the weakly-typed baseline:
- **Outputs match within `atol=5e-3, rtol=5e-3`**: migration is complete.
- **Outputs diverge but stay within model accuracy tolerance**: weak typing
was opportunistically using FP16/INT8 where the new graph keeps FP32 (or vice
versa). Acceptable if the downstream metric (top-1, BLEU, WER) is unchanged;
confirm with the original accuracy harness.
- **Outputs diverge beyond tolerance**: see Common Errors below.
If the source was ONNX, also run the AutoCast'd ONNX through ONNX Runtime and
compare against the FP32 ONNX before blaming TRT — graph-level conversion errors
should be caught there.
## Migration Patterns Reference
| Pattern | Action |
|---------|--------|
| Network created with no flags (legacy implicit batch) | First migrate to explicit batch; only then add `STRONGLY_TYPED` |
| Multiple `set_flag` calls (FP16 + INT8 + TF32 + REFIT) | Keep `REFIT`, `SPARSE_WEIGHTS`, `kTF32`; drop the precision-hint flags (FP16/BF16/INT8/FP8) and the removed `kOBEY`/`kPREFER_PRECISION_CONSTRAINTS` |
| `set_calibration_profile` / `IInt8Calibrator` | **Remove.** PTQ calibration is replaced by Q/DQ in the ONNX. Use ModelOpt's PTQ for the model first. |
| Mixed FP16/FP32 per-layer overrides via `layer.precision` | Express the same intent with `Cast` ops in the ONNX, or in the Python network construction code |
| Plugin layers with explicit precision | Plugins must implement `IPluginV3OneBuild::getOutputDataTypes`; the network reads types from the plugin |
| Engine consumed by downstream framework expecting FP32 I/O | Set `keep_io_types=True` in AutoCast, or add explicit `Cast` to FP32 at the network outputs |
## Common Errors
- **`"BuilderFlag::kFP16 is not compatible with a strongly typed network"`** —
a `setFlag`/`set_flag` call survived the migration. Re-run `migrate.py` or grep
for `BuilderFlag\.` / `BuilderFlag::` in the migrated source.
- **`"--best is incompatible with --stronglyTyped"`** — drop `--best` from the
`trtexec` line; precision choices live in the graph now.
- **`"Network has no marked outputs"` immediately after switching the flag** —
some builders only call `mark_output` on the path conditioned by
`platform_has_fast_fp16`. Remove the conditional.
- **AutoCast'd ONNX fails the parser with "If subgraph type mismatch"** — AutoCast
can corrupt certain `If` node subgraphs. Add the affected `If` node names to
`nodes_to_exclude` so they stay FP32.
- **Engine output is FP32 zeros after migration** — `keep_io_types=False` was used
but the host code still feeds FP32 buffers. Either set `keep_io_types=True` or
cast the host input before binding.
- **Accuracy regression of ~1-3% after migration** — known case for
transformer-heavy networks: leftover `Cast(to=FLOAT32)` from AutoCast combined
with FP16 weights causes thrash. Work around by excluding the residual cast op
types in `op_types_to_exclude`.
## Pitfalls
- **Do not partially migrate.** Half-migrated builds (some `set_flag` calls
removed, `STRONGLY_TYPED` not set) silently fall back to weak typing and the
warning is easy to miss. Check the engine build log for `"Strongly typed network
detected"` to confirm the flag took effect.
- **Do not migrate without a baseline.** Weak typing's per-layer precision choices
are non-deterministic across TRT versions; without the old outputs you have no
ground truth and cannot tell whether the new engine is correct.
- **Do not run `--stronglyTyped` against a FP32 ONNX expecting FP16 speedup.**
Strong typing builds exactly what the graph specifies — a FP32 ONNX produces a
FP32 engine. Run AutoCast first.
- **Plugins must declare types.** Old plugins that left output types as "follow
input" will fail to build under strong typing. Update them to the
`IPluginV3OneBuild` interface, the only plugin API that supports strong typing
in 11.x.
- **Refit + strong typing**: refittable strongly-typed engines work, but the refit
weights must match the precision declared in the network. A FP16 layer cannot be
refit with FP32 weights — convert host weights before calling `setNamedWeights`.
- **Don't trust grep alone for completeness.** `migrate.py` uses AST matching, so
it catches the precision flags regardless of how `tensorrt` is imported or
aliased via `as` — something string-level grep misses. It matches direct
attribute access (`trt.BuilderFlag.FP16`); dynamic forms such as
`getattr(trt.BuilderFlag, 'FP16')` are *not* rewritten and need manual review.
Run the AST helper as the source of truth.
## References
- **Migration guide:** [TensorRT 10.x → 11.x](https://docs.nvidia.com/deeplearning/tensorrt/latest/api/migration/tensorrt-10x-to-11x.html)
— the authoritative description of the weak → strong typing change.
- **Operator type rules:** the [TensorRT Operators Reference](https://docs.nvidia.com/deeplearning/tensorrt/latest/_static/operators/)
— each operator's page lists its supported input/output types, i.e. how its output
dtype follows its inputs in a strongly-typed graph.
- **API reference:** the TensorRT C++/Python API docs for `NetworkDefinitionCreationFlag`,
`BuilderFlag`, `ILayer::setToType`, and `INetworkDefinition::addCast` / `addQuantize` /
`addDequantize`.
- **TensorRT Model Optimizer:** <https://nvidia.github.io/Model-Optimizer> — AutoCast and
quantization tooling for getting precision into the model.
- **Sample code** (TensorRT OSS, <https://github.com/NVIDIA/TensorRT>):
[`samples/trtexec/README.md` Example 6](https://github.com/NVIDIA/TensorRT/blob/main/samples/trtexec/README.md#example-6-create-a-strongly-typed-plan-file)
(strongly-typed plan), `samples/python/strongly_type_autocast/sample.py`
(AutoCast → strongly-typed INT8), and `samples/python/network_api_pytorch_mnist/sample.py`
(strongly-typed Python builder).
@@ -0,0 +1,65 @@
# trt-strong-typing-migration helper scripts
Scripts that automate the mechanical parts of the SKILL.md walkthrough.
## `migrate.py`
AST-based rewriter for Python TensorRT builder code. Detects the
weakly-typed builder pattern (`create_network` + `set_flag(BuilderFlag.FP16/...)`)
and rewrites it to the strongly-typed form.
```bash
# Show what would change (dry-run): prints a unified diff; exits 1 if changes are pending, 0 if nothing to do.
python3 migrate.py path/to/build.py
# Rewrite the file in place:
python3 migrate.py path/to/build.py --write
# Process every .py file in a directory tree:
python3 migrate.py path/to/project/ --write
```
The script uses `ast.NodeTransformer` so it understands call shapes, not just
text — it correctly handles `set_flag` accessed via aliased imports, multi-flag
construction in `create_network`, and per-layer `precision`/`set_output_type`
assignments. Unrelated logic and docstrings are left intact, but note: because
the rewrite round-trips through `ast.unparse`, **regular `#` comments and the
original formatting are not preserved**. Review the dry-run diff before
`--write`, and re-add any comments you need afterward.
### What it changes
| Before | After |
|--------|-------|
| `builder.create_network(0)` | `builder.create_network(1 << int(trt.NetworkDefinitionCreationFlag.STRONGLY_TYPED))` |
| `builder.create_network(1 << int(trt.NetworkDefinitionCreationFlag.EXPLICIT_BATCH))` | `builder.create_network(1 << int(trt.NetworkDefinitionCreationFlag.STRONGLY_TYPED))` |
| `config.set_flag(trt.BuilderFlag.FP16 / BF16 / INT8 / FP8)` | (line removed) |
| `layer.precision = trt.float16` | (statement removed) |
| `layer.set_output_type(0, trt.float16)` | (statement removed) |
### What it does NOT change
- `set_flag` calls for non-precision-hint flags (`REFIT`, `SPARSE_WEIGHTS`,
`DISABLE_TIMING_CACHE`, and `TF32`). These are orthogonal to typing — `kTF32`
is kept in TRT 11.
- Calibration setup (`set_calibration_profile`, `IInt8Calibrator`). These must
be removed manually because the correct replacement (Q/DQ in the ONNX) lives
outside the build script.
- Conditional logic gating on `platform_has_fast_fp16` / `platform_has_fast_int8`
— the body of those branches becomes a no-op after the flag is removed, but
the conditional remains. Clean up manually if desired.
## `verify.sh`
End-to-end check that `migrate.py` is doing the right thing. Writes a
representative weakly-typed sample to a temp directory, runs `migrate.py
--write` on it, then asserts the rewritten file matches the strongly-typed
contract (flag present, precision flags absent, valid Python).
```bash
bash verify.sh # run and clean up
bash verify.sh --keep # keep the temp workspace for inspection
```
Exits 0 on success, 1 on any assertion failure. Run this before applying
`migrate.py` to a real codebase.
@@ -0,0 +1,305 @@
#!/usr/bin/env python3
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
"""AST-based migrator for weakly-typed TensorRT Python builders.
Rewrites:
- `builder.create_network(...)` -> uses NetworkDefinitionCreationFlag.STRONGLY_TYPED
- `config.set_flag(trt.BuilderFlag.{FP16,BF16,INT8,FP8})` -> removed
- `layer.precision = ...` and `layer.set_output_type(...)` -> removed
The rewrites are intentionally conservative: ambiguous patterns are skipped and
reported so the user can resolve them manually. Non-precision flags (REFIT,
SPARSE_WEIGHTS, TF32, etc.) are preserved — kTF32 is kept in TRT 11 and is
orthogonal to typing.
Usage:
python3 migrate.py path/to/file.py # dry-run, print unified diff
python3 migrate.py path/to/file.py --write # rewrite in place
python3 migrate.py path/to/dir/ --write # recurse over .py files
"""
from __future__ import annotations
import argparse
import ast
import difflib
import sys
from pathlib import Path
from typing import Iterable
# Precision-hint BuilderFlag attribute names that must be removed. NOTE: TF32 is
# deliberately NOT here — kTF32 is kept in TRT 11 (orthogonal to typing), like REFIT.
PRECISION_FLAGS = frozenset({"FP16", "BF16", "INT8", "FP8"})
# NetworkDefinitionCreationFlag attribute names that map to STRONGLY_TYPED.
WEAK_NETWORK_FLAGS = frozenset({"EXPLICIT_BATCH"})
def _is_attr(node: ast.AST, *, attr_chain: tuple[str, ...]) -> bool:
"""Return True if `node` is an attribute access matching the trailing chain.
Walks `node.attr -> node.value.attr -> ...` and compares to attr_chain
in reverse. The leading element (e.g. module / object name) is not checked,
so this matches `trt.BuilderFlag.FP16`, `tensorrt.BuilderFlag.FP16`, or any
aliased import.
"""
cur = node
for expected in reversed(attr_chain):
if not isinstance(cur, ast.Attribute):
return False
if cur.attr != expected:
return False
cur = cur.value
return True
def _builder_flag_name(node: ast.AST) -> str | None:
"""If `node` is `<anything>.BuilderFlag.<NAME>`, return NAME."""
if isinstance(node, ast.Attribute) and isinstance(node.value, ast.Attribute):
if node.value.attr == "BuilderFlag":
return node.attr
return None
def _network_flag_name(node: ast.AST) -> str | None:
"""If `node` is `<anything>.NetworkDefinitionCreationFlag.<NAME>`, return NAME."""
if isinstance(node, ast.Attribute) and isinstance(node.value, ast.Attribute):
if node.value.attr == "NetworkDefinitionCreationFlag":
return node.attr
return None
def _strongly_typed_arg() -> ast.expr:
"""Build the AST for `1 << int(trt.NetworkDefinitionCreationFlag.STRONGLY_TYPED)`.
Uses bare `trt.` qualifier — matches the canonical import alias. Callers
whose code uses `tensorrt.` directly will get a working but visually
inconsistent line; that is acceptable as a one-line manual cleanup.
"""
return ast.BinOp(
left=ast.Constant(value=1),
op=ast.LShift(),
right=ast.Call(
func=ast.Name(id="int", ctx=ast.Load()),
args=[
ast.Attribute(
value=ast.Attribute(
value=ast.Name(id="trt", ctx=ast.Load()),
attr="NetworkDefinitionCreationFlag",
ctx=ast.Load(),
),
attr="STRONGLY_TYPED",
ctx=ast.Load(),
)
],
keywords=[],
),
)
class Migrator(ast.NodeTransformer):
def __init__(self) -> None:
self.removed_flag_calls = 0
self.removed_precision_assigns = 0
self.removed_set_output_type = 0
self.rewrote_create_network = 0
self.skipped: list[str] = []
# ---- create_network rewrites ----------------------------------------
def visit_Call(self, node: ast.Call) -> ast.AST:
self.generic_visit(node)
func = node.func
if isinstance(func, ast.Attribute) and func.attr == "create_network":
# Replace the single positional argument with the STRONGLY_TYPED flag.
if len(node.args) == 0:
# No positional arg: the flag may be passed as `flags=` keyword.
# Blindly appending a positional arg would collide with it
# (TypeError: got multiple values for argument 'flags').
flags_kw = next((kw for kw in node.keywords if kw.arg == "flags"), None)
if flags_kw is not None:
if self._is_weak_network_arg(flags_kw.value):
flags_kw.value = _strongly_typed_arg()
self.rewrote_create_network += 1
elif self._already_strongly_typed(flags_kw.value):
pass # idempotent
else:
self.skipped.append(
f"create_network at line {node.lineno}: unrecognized flags= "
"argument shape, review manually"
)
elif not node.keywords:
# Truly empty call create_network(): defaults to weak typing.
node.args = [_strongly_typed_arg()]
self.rewrote_create_network += 1
else:
self.skipped.append(
f"create_network at line {node.lineno}: keyword args present "
"without flags=, review manually"
)
elif len(node.args) == 1:
arg = node.args[0]
if self._is_weak_network_arg(arg):
node.args[0] = _strongly_typed_arg()
self.rewrote_create_network += 1
elif self._already_strongly_typed(arg):
pass # idempotent
else:
self.skipped.append(
f"create_network at line {node.lineno}: unrecognized argument shape, "
"review manually"
)
return node
@staticmethod
def _is_weak_network_arg(arg: ast.expr) -> bool:
# Match `0`
if isinstance(arg, ast.Constant) and arg.value == 0:
return True
# Match `1 << int(...EXPLICIT_BATCH)` (any weak-network flag name)
if isinstance(arg, ast.BinOp) and isinstance(arg.op, ast.LShift):
inner = arg.right
if isinstance(inner, ast.Call) and isinstance(inner.func, ast.Name) and inner.func.id == "int":
if inner.args:
name = _network_flag_name(inner.args[0])
if name in WEAK_NETWORK_FLAGS:
return True
return False
@staticmethod
def _already_strongly_typed(arg: ast.expr) -> bool:
if isinstance(arg, ast.BinOp) and isinstance(arg.op, ast.LShift):
inner = arg.right
if isinstance(inner, ast.Call) and isinstance(inner.func, ast.Name) and inner.func.id == "int":
if inner.args and _network_flag_name(inner.args[0]) == "STRONGLY_TYPED":
return True
return False
# ---- set_flag / precision assignment removals -----------------------
def visit_Expr(self, node: ast.Expr) -> ast.AST | None:
# `config.set_flag(trt.BuilderFlag.FP16)` as an Expr statement.
self.generic_visit(node)
if isinstance(node.value, ast.Call):
call = node.value
if isinstance(call.func, ast.Attribute) and call.func.attr == "set_flag":
if call.args:
flag = _builder_flag_name(call.args[0])
if flag in PRECISION_FLAGS:
self.removed_flag_calls += 1
return None
# `layer.set_output_type(0, trt.float16)` — drop unconditionally.
if isinstance(call.func, ast.Attribute) and call.func.attr == "set_output_type":
self.removed_set_output_type += 1
return None
return node
def visit_Assign(self, node: ast.Assign) -> ast.AST | None:
# `something.precision = trt.float16` (or any value) — drop.
self.generic_visit(node)
if len(node.targets) == 1:
tgt = node.targets[0]
if isinstance(tgt, ast.Attribute) and tgt.attr == "precision":
self.removed_precision_assigns += 1
return None
return node
# ---- if-statement cleanup ------------------------------------------
def visit_If(self, node: ast.If) -> ast.AST | None:
"""Drop `if builder.platform_has_fast_{fp16,int8,bf16}:` blocks that become empty."""
self.generic_visit(node)
test = node.test
gating_attrs = {"platform_has_fast_fp16", "platform_has_fast_int8",
"platform_has_fast_bf16", "platform_has_fast_fp8"}
if isinstance(test, ast.Attribute) and test.attr in gating_attrs:
# If body becomes empty (because set_flag was the only statement) and
# there is no else clause, the whole if is dead.
if not node.body and not node.orelse:
return None
if not node.body:
# Body emptied but orelse remains — replace with the orelse block.
return node.orelse if len(node.orelse) > 1 else (
node.orelse[0] if node.orelse else None
)
return node
def _process_source(src: str) -> tuple[str, Migrator]:
tree = ast.parse(src)
m = Migrator()
new_tree = m.visit(tree)
ast.fix_missing_locations(new_tree)
return ast.unparse(new_tree), m
def _iter_files(paths: Iterable[Path]) -> Iterable[Path]:
for p in paths:
if p.is_dir():
yield from sorted(p.rglob("*.py"))
elif p.suffix == ".py":
yield p
def _process_file(path: Path, *, write: bool) -> bool:
"""Return True iff the file needs (or got) a migration.
Keyed on the migrator's transform counts, not on text equality: `ast.unparse`
reformats and drops comments on every round-trip, so a file that needs no
migration would otherwise look "changed" and have its comments stripped.
"""
original = path.read_text()
try:
new_src, m = _process_source(original)
except SyntaxError as e:
print(f"[skip] {path}: syntax error: {e}", file=sys.stderr)
return False
migrated = (m.rewrote_create_network + m.removed_flag_calls
+ m.removed_precision_assigns + m.removed_set_output_type) > 0
if not migrated:
for note in m.skipped:
print(f"[note] {path}: {note}", file=sys.stderr)
return False
if write:
path.write_text(new_src)
print(
f"[write] {path}: create_network={m.rewrote_create_network} "
f"set_flag={m.removed_flag_calls} "
f"precision_assign={m.removed_precision_assigns} "
f"set_output_type={m.removed_set_output_type}"
)
else:
diff = difflib.unified_diff(
original.splitlines(keepends=True),
new_src.splitlines(keepends=True),
fromfile=f"{path} (weakly typed)",
tofile=f"{path} (strongly typed)",
)
sys.stdout.writelines(diff)
for note in m.skipped:
print(f"[note] {path}: {note}", file=sys.stderr)
return True
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("paths", nargs="+", type=Path, help="Files or directories to process.")
parser.add_argument("--write", action="store_true",
help="Rewrite files in place. Default is dry-run with unified diff.")
args = parser.parse_args()
any_changed = False
for path in _iter_files(args.paths):
if _process_file(path, write=args.write):
any_changed = True
# In dry-run, exit non-zero when changes are pending so callers/CI can gate
# on it (like `black --check`). `--write` applies the changes and exits 0.
return 1 if any_changed and not args.write else 0
if __name__ == "__main__":
sys.exit(main())
@@ -0,0 +1,118 @@
#!/usr/bin/env bash
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
#
# Verify the `migrate.py` helper on an inline weakly-typed sample.
# Run from anywhere; resolves paths relative to this script.
#
# Usage:
# bash scripts/verify.sh # run the verification end-to-end
# bash scripts/verify.sh --keep # keep the temp workspace for inspection
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
MIGRATE="${SCRIPT_DIR}/migrate.py"
if [[ ! -f "${MIGRATE}" ]]; then
echo "ERROR: migrate.py not found at ${MIGRATE}" >&2
exit 1
fi
KEEP=0
if [[ "${1:-}" == "--keep" ]]; then
KEEP=1
fi
WORK="$(mktemp -d -t trt-w2s-verify-XXXXXX)"
cleanup_workspace() {
if [[ ${KEEP} -eq 1 ]]; then
echo "Kept workspace at ${WORK}"
else
rm -rf "${WORK}"
fi
}
trap cleanup_workspace EXIT
SAMPLE="${WORK}/sample_build.py"
# A representative weakly-typed Python builder. Contains every transform path
# migrate.py is expected to handle: EXPLICIT_BATCH network flag, set_flag for
# FP16/INT8 (removed) plus TF32/REFIT (preserved), per-layer precision overrides,
# and the platform_has_fast_* gating that becomes dead code after migration.
cat > "${SAMPLE}" <<'PY'
import tensorrt as trt
TRT_LOGGER = trt.Logger(trt.Logger.WARNING)
def build():
builder = trt.Builder(TRT_LOGGER)
network = builder.create_network(1 << int(trt.NetworkDefinitionCreationFlag.EXPLICIT_BATCH))
config = builder.create_builder_config()
if builder.platform_has_fast_fp16:
config.set_flag(trt.BuilderFlag.FP16)
config.set_flag(trt.BuilderFlag.TF32) # kept in TRT 11 (orthogonal to typing); must survive
config.set_flag(trt.BuilderFlag.INT8)
config.set_flag(trt.BuilderFlag.REFIT) # not a precision flag; must survive
parser = trt.OnnxParser(network, TRT_LOGGER)
with open("model.onnx", "rb") as f:
parser.parse(f.read())
last = network.get_layer(network.num_layers - 1)
last.precision = trt.float16
last.set_output_type(0, trt.float16)
plan = builder.build_serialized_network(network, config)
return plan
PY
echo "[verify] sample written to ${SAMPLE}"
echo "[verify] dry-run diff (exit 1 = changes pending, expected here):"
python3 "${MIGRATE}" "${SAMPLE}" || true
echo "[verify] writing rewrite in place:"
python3 "${MIGRATE}" "${SAMPLE}" --write
# Assertions on the rewritten file.
fail=0
assert_present() {
local needle="$1"
if ! grep -qF "${needle}" "${SAMPLE}"; then
echo "ASSERT FAIL: expected to find: ${needle}" >&2
fail=1
fi
}
assert_absent() {
local needle="$1"
if grep -qF "${needle}" "${SAMPLE}"; then
echo "ASSERT FAIL: expected to be removed: ${needle}" >&2
fail=1
fi
}
assert_present "NetworkDefinitionCreationFlag.STRONGLY_TYPED"
assert_absent "NetworkDefinitionCreationFlag.EXPLICIT_BATCH"
assert_absent "BuilderFlag.FP16"
assert_absent "BuilderFlag.INT8"
assert_present "BuilderFlag.TF32" # kTF32 is kept in TRT 11; must NOT be stripped
assert_present "BuilderFlag.REFIT" # non-precision flag must remain
assert_absent ".precision = trt.float16"
assert_absent "set_output_type(0, trt.float16)"
# Syntactic validity.
python3 -c "import ast, sys; ast.parse(open('${SAMPLE}').read())" || {
echo "ASSERT FAIL: rewritten file is not valid Python" >&2
fail=1
}
if [[ ${fail} -ne 0 ]]; then
echo "[verify] FAIL — see assertions above. Workspace: ${WORK}"
KEEP=1
exit 1
fi
echo "[verify] OK — migrate.py produced a well-formed strongly-typed rewrite"
@@ -0,0 +1,266 @@
---
name: trt-torch-quickstart
description: >
Compile a PyTorch model to a TensorRT engine via Torch-TensorRT — AOT or
JIT — under the new strong-typing default. Use when the user
compiles PyTorch to TensorRT without ONNX, hits "enabled_precisions
should not be used when use_explicit_typing=True", sees Dynamo graph
breaks or PyTorch fallback, debugs ABI errors at import torch_tensorrt, or
needs the compatible torch / torch_tensorrt / tensorrt-cu13 version pins for
TensorRT 11. Triggers: torch_tensorrt, torch_tensorrt.dynamo.compile,
torch.compile backend torch_tensorrt, pytorch to tensorrt, ExportedProgram,
Dynamo graph break, use_explicit_typing, enabled_precisions,
torch_tensorrt.Input, min_block_size, truncate_double, tensorrt-cu13,
version pinning, version compatibility. Adjacent skills: `trt-onnx-quickstart`,
`trt-cpp-runtime-quickstart`. LLM token generation belongs in TensorRT-LLM.
license: Apache-2.0
metadata:
author: NVIDIA Corporation
version: "1.0"
tags:
- torch-tensorrt
- dynamo
- pytorch
- quickstart
- fp16
---
# Torch-TensorRT Quickstart
Convert a PyTorch `nn.Module` to a TensorRT engine using `torch_tensorrt` (the [Torch-TensorRT](https://github.com/pytorch/TensorRT) frontend). Covers the AOT path (for production and C++ deploy) and the JIT path (for Python-only inference).
## When to Use
| Scenario | Use this skill? |
|----------|-----------------|
| PyTorch `nn.Module`; want a TensorRT engine without writing an ONNX intermediate | Yes |
| Model uses ops that don't export cleanly to ONNX (custom autograd, dynamic control flow) | Yes |
| Want Torch-TensorRT's automatic fallback of unsupported subgraphs to PyTorch | Yes |
| Need a serialized engine for C++ deploy from a PyTorch source | Yes — use AOT path here, then `trt-cpp-runtime-quickstart` for the C++ load |
| Have a working ONNX file already | No — use `trt-onnx-quickstart` |
| LLM token generation (Llama, Mistral, Qwen text gen) | **No** — route to [TensorRT-LLM](https://github.com/NVIDIA/TensorRT-LLM) |
| Have a `.plan` file already and want to run it from C++ | No — use `trt-cpp-runtime-quickstart` |
| Migrating an existing weakly-typed TRT network to strongly-typed | No — use `trt-strong-typing-migration` |
## Prerequisites
Torch-TensorRT versions are tightly coupled to a (torch, tensorrt-cu13, CUDA) triple; mismatches produce ABI errors at `import torch_tensorrt`. **Pin matrix source of truth:** the [pytorch/TensorRT releases page](https://github.com/pytorch/TensorRT/releases) — each release lists the exact `torch` / `tensorrt` / CUDA versions it was built against. Do not invent pins from memory.
For TensorRT 11.0:
| Component | Required | Notes |
|-----------|----------|-------|
| TensorRT | 11.x (`tensorrt-cu13` wheel) | Mixing `-cu12` and `-cu13` wheels breaks imports. |
| CUDA toolkit | 13.x | Driver R590+. |
| Python | ≥ 3.10 | TRT 11 dropped 3.9 and earlier. |
| `torch` | Match the `torch_tensorrt` release notes for the chosen `torch_tensorrt` version. | |
| `torch_tensorrt` | The release matching the TRT 11.0 RC — see [pytorch/TensorRT releases](https://github.com/pytorch/TensorRT/releases). | |
**Recommended environment**: NGC TensorRT container with PyTorch (`nvcr.io/nvidia/pytorch:<tag>` or `nvcr.io/nvidia/tensorrt:<tag>` with `torch_tensorrt` pip-installed on top). Run with `--gpus all`.
Verification:
```bash
python3 -c "import torch, torch_tensorrt, tensorrt; print(torch.__version__, torch_tensorrt.__version__, tensorrt.__version__)"
```
## Step 1 — Load and prepare the model
Use a `torch.nn.Module` in eval mode. Torch-TensorRT will trace through it.
```python
import torch
import torchvision.models as models
model = models.resnet50(weights=None).eval().cuda()
example = torch.randn(1, 3, 224, 224, device="cuda")
```
Notes:
- `weights=None` skips the download — fine for shape/perf testing. For accuracy work, load real weights.
- Model must be on CUDA before compile. Torch-TensorRT does not move it for you.
- `.eval()` matters: BatchNorm and Dropout behave differently in train mode and can produce different engines.
## Step 2 — Compile to a TensorRT engine (AOT)
Two compile paths — pick by deployment target:
| Goal | Path | API |
|------|------|-----|
| Serialized engine (production, C++ deploy, reuse) | **AOT** — this section | `torch_tensorrt.dynamo.compile``torch_tensorrt.save` |
| In-process Python callable only (no serialized engine) | **JIT** — end of this section | `torch.compile(backend="torch_tensorrt")` |
The AOT path uses `torch_tensorrt.dynamo.compile` and returns a serializable `ExportedProgram`.
**Important — strong typing is the default in torch_tensorrt ≥ 2.12.** With `use_explicit_typing=True` (the default), engine precision is **inferred from the exported model's dtype** — passing `enabled_precisions={torch.float16}` raises `AssertionError`. To compile in FP16, cast the model and example tensor to FP16 before exporting:
```python
import torch
import torch_tensorrt
model = MyModel().eval().cuda().half() # cast to FP16
example = torch.randn(1, 3, 224, 224, device="cuda", dtype=torch.float16)
trt_gm = torch_tensorrt.dynamo.compile(
torch.export.export(model, (example,)),
inputs=[example],
# No enabled_precisions — precision comes from the model dtype.
truncate_double=True,
min_block_size=1,
)
# Serialize for later / C++ loading — use torch_tensorrt.save, NOT raw bytes.
torch_tensorrt.save(trt_gm, "resnet50_trt.ep", inputs=[example])
```
**Do not extract raw engine bytes via `submod.engine` and write them with `open(...).write(...)`.** That bypasses Torch-TensorRT's metadata wrapper: the blob will not round-trip through `torch_tensorrt.load(...)` and silently drops the dispatch graph that handles partial-fallback subgraphs. `torch_tensorrt.save(trt_gm, path, inputs=...)` is the only supported serialization path.
For mixed precision or to override the model dtype, set `use_explicit_typing=False` and `enabled_precisions` applies as before (the weakly-typed path, deprecated in TRT 11):
```python
trt_gm = torch_tensorrt.dynamo.compile(
torch.export.export(model, (example,)),
inputs=[example],
use_explicit_typing=False, # weakly-typed (deprecated)
enabled_precisions={torch.float16, torch.float32},
truncate_double=True,
)
```
Key arguments:
- `truncate_double=True`: silently downcasts FP64 constants. Without it, any FP64 op forces a partition boundary.
- `min_block_size`: smallest subgraph (in node count) worth handing to TRT. `1` is aggressive; default `5` avoids tiny TRT subgraphs that don't pay back their launch overhead.
- `workspace_size`: bytes of scratch memory the TRT builder can use. Leave unset to let TRT decide.
**JIT alternative (Python-only inference):** if you just want a callable and not a serialized engine, use `torch.compile` with the Torch-TensorRT backend. Under the strong-typing default, precision comes from the model dtype — `.half()` the model for FP16 rather than passing `enabled_precisions`:
```python
model = model.half() # FP16 inferred from model dtype (strong typing default)
example = example.half()
trt_model = torch.compile(model, backend="torch_tensorrt")
out = trt_model(example) # compiles lazily on the first call, then runs through TRT in-process
```
This skips `torch.export` and gives a `torch.compile`-wrapped callable. It cannot be loaded from C++ — use the AOT path above if you need that.
## Step 3 — Dynamic shapes
Wrap inputs in `torch_tensorrt.Input` with `min_shape`/`opt_shape`/`max_shape`:
```python
dynamic_input = torch_tensorrt.Input(
min_shape=(1, 3, 224, 224),
opt_shape=(8, 3, 224, 224),
max_shape=(32, 3, 224, 224),
dtype=torch.float16,
)
trt_gm = torch_tensorrt.dynamo.compile(
torch.export.export(model, (example,), dynamic_shapes={"x": {0: torch.export.Dim("batch", min=1, max=32)}}),
inputs=[dynamic_input],
# Strong typing: FP16 comes from the input/model dtype, not enabled_precisions.
)
```
Pitfalls:
- The `dynamic_shapes=` argument to `torch.export.export` and the `Input` ranges must agree. A mismatch produces a builder error 23 minutes into compilation, not at export time.
- `opt_shape` is what TRT tunes kernels for. Set it to the most common runtime shape, not the midpoint.
- Only mark dimensions dynamic that actually vary at runtime; unnecessary dynamism degrades performance.
## Step 4 — Run inference and verify
Raw-tensor comparison (`assert_close`) is fine for FP32 but **misleading for FP16** — a few outlier positions exceed the tolerance even when downstream behavior is identical. Use a **semantic check** appropriate to the model type:
```python
with torch.inference_mode():
torch_out = model(example)
trt_out = trt_gm(example)
```
**Image classification** — top-1 (or top-5) class match, plus mean-softmax-distance:
```python
assert torch.equal(torch_out.argmax(-1), trt_out.argmax(-1)), "top-1 mismatch"
mean_prob_diff = (torch.softmax(torch_out.float(), -1) - torch.softmax(trt_out.float(), -1)).abs().mean()
assert mean_prob_diff < 1e-3
```
**Sentence / token embeddings** — cosine similarity per token, mean across the batch:
```python
cos = torch.nn.functional.cosine_similarity(torch_out.flatten(0, -2), trt_out.flatten(0, -2), dim=-1)
assert cos.mean() > 0.999
```
**Detection / regression heads** — bounded raw-tensor closeness on the meaningful sub-tensor (e.g., bbox coordinates), softmax check on the class logits.
**FP32 sanity** — for any model, an FP32 (no `.half()`) run should pass `torch.testing.assert_close(trt_out, torch_out, rtol=1e-4, atol=1e-4)`. If that fails, the issue is in the compilation, not numeric precision.
## Loading the serialized engine
**From Python**:
```python
import torch_tensorrt
loaded = torch_tensorrt.load("resnet50_trt.ep").module()
out = loaded(example)
```
**From C++**: this skill does NOT cover C++ loading. The `.ep` file `torch_tensorrt.save` produces by default is a `torch.export` archive for the **Python** `torch_tensorrt.load(...)` path — it is *not* directly consumable by the plain TensorRT C++ runtime (`IRuntime` / `deserializeCudaEngine`), which expects a serialized TensorRT engine (`.plan`). Two supported C++ routes:
- Save with `output_format="torchscript"` and deploy the TorchScript module with libtorch + the Torch-TensorRT C++ runtime.
- If you only need the raw TensorRT engine in C++, build it through the ONNX path (`trt-onnx-quickstart`) and load the resulting `.plan` with `trt-cpp-runtime-quickstart` (modern `IRuntime` + `enqueueV3` + `setTensorAddress`).
Do not inline a C++ snippet here — it will diverge from the canonical pattern.
## Common issues
**`RuntimeError: Trying to create tensor with negative dimension`** during export — the model has a shape-dependent control flow path that `torch.export` can't trace. Either rewrite with `torch.cond` / `torch.where`, or fall back to `ir="dynamo"` + `min_block_size=1` so the unsupported region runs in PyTorch.
**`Unsupported operator` warnings** — Torch-TensorRT partitions around them and runs those nodes in PyTorch. Set `require_full_compilation=True` to turn this into an error; use during development to find what's actually falling back.
## Diagnosing excessive PyTorch fallback
If the compile completes but most of the model fell back to PyTorch (only a few subgraphs run on TRT), three knobs in order:
1. **Identify what fell back.** Run with `TORCH_LOGS="graph_breaks"` and `require_full_compilation=True` to escalate fallbacks to errors that name the offending op. Without this you don't know what the autotuner rejected.
```bash
TORCH_LOGS="graph_breaks" python3 your_compile.py
```
2. **Reconsider `min_block_size`.** This is a real trade-off — not just a knob.
- Default `min_block_size=5`: Torch-TensorRT only hands a subgraph to TRT if it has ≥5 nodes. Tiny TRT subgraphs cost more in kernel-launch overhead than they save.
- `min_block_size=1`: aggressive — every supported op goes to TRT, even single-node subgraphs. Useful to see what's *theoretically* supported, but commonly slower at inference due to the per-subgraph launch tax.
- Recommended: leave at default `5` for production; drop to `1` only during diagnosis to see maximum TRT coverage.
3. **Look for shape-dependent control flow.** `torch.export` traces only one branch of `if x.shape[0] > 0:` style code; rewrite with `torch.where`, `torch.cond`, or lift the condition out of the model. The trace error names the offending op.
If fallback persists after all three, you have a genuine unsupported op — write a converter (see Torch-TensorRT upstream docs) or switch to the ONNX path (`trt-onnx-quickstart`).
**Engine builds but output is garbage** — almost always a dtype issue. Check:
1. Model/input dtype matches your pipeline — under strong typing precision follows the model dtype (`.half()` for FP16); `enabled_precisions` applies only under weak typing (`use_explicit_typing=False`).
2. `truncate_double=True` if the model has any FP64 constants (common in positional encodings).
3. The model was `.eval()` before export.
**Compile takes >10 minutes** — turn on the builder log to see what's going on:
```python
import torch_tensorrt.logging as ttlog
ttlog.set_reportable_log_level(ttlog.Level.Info)
```
Usually it's the autotuner exploring kernel variants for a heavy GEMM/conv. To cap exploration, set `workspace_size` smaller (less scratch → fewer candidates).
**`ImportError: cannot import name 'XYZ' from 'torch_tensorrt'`** — version mismatch. Confirm the pin matrix; the `torch_tensorrt` Python API moves between minor versions.
## Numerical debugging
If `assert_close` fails by more than ~5× tolerance:
1. Re-run in FP32 — drop the model's `.half()` (or under weak typing, `enabled_precisions={torch.float32}`). If that matches PyTorch, the issue is FP16 accumulator drift in a specific op — usually a softmax or LayerNorm. The pragmatic fix is to wrap the offending submodule in a module excluded from the compile (`min_block_size` won't help; explicit exclusion is needed). See the [Torch-TensorRT lowering guide](https://pytorch.org/TensorRT/) for the version-specific exclusion API.
2. Sanity-check at FP32 first, then re-introduce FP16 and bisect — almost always reveals one offending op.
3. For FP8: check that the calibration distribution actually covers the runtime distribution. FP8 has no headroom for outliers.
See `trt-strong-typing-migration` for the broader weak-vs-strong typing discussion. By default Torch-TensorRT produces strongly-typed engines in TRT 11.0 — types inferred from the exported program — but you can override.
## What this skill is not
- Not a guide to writing custom Torch-TensorRT converters. Use the upstream Torch-TensorRT docs for that.
- Not for TensorRT-LLM / LLM inference — use the TRT-LLM `examples/` flow instead.
- Not for QAT (quantization-aware training) — Torch-TensorRT consumes the QAT model but the training loop is upstream.
## References
- Upstream Torch-TensorRT: https://pytorch.org/TensorRT/
- Version pin matrix: [pytorch/TensorRT releases](https://github.com/pytorch/TensorRT/releases)
- Related skills: `trt-onnx-quickstart`, `trt-strong-typing-migration`, `trt-cpp-runtime-quickstart`