A TensorRT engine build can take seconds to many minutes. Large strongly typed models, deep tactic search, and a cold timing cache on a brand-new GPU SKU can leave developers, end users, or AI agents staring at a frozen terminal with no idea whether to wait, retry, or kill the process. Most NVIDIA TensorRT integrations report nothing during a build or provide no way to abort early. In a long-running agent workflow, this turns into wasted GPU-hours and stuck sessions.
Figure 1. Live, nested progress bars for a TensorRT engine build, rendered by an IProgressMonitor subclass
TensorRT provides IProgressMonitor, an API for fixing this issue, and it has been in NvInfer.h for several releases. This tutorial walks through a minimal drop-in implementation for Python and C++, adds a cancel path that responds to Ctrl-C or a programmatic stop signal from an outer event loop, and shows where to surface the resulting progress stream so an IDE, a service, or an agent runtime can use it.
Every code block in this post is lifted from or modeled on two NVIDIA-maintained OSS samples:
Python: samples/python/simple_progress_monitor/ (ResNet-50, strongly typed network) C++: samples/sampleProgressMonitor/ (MNIST)What IProgressMonitor gives you
IProgressMonitor is an abstract base class that TensorRT calls during the engine build. You subclass it and override three methods. The shape is identical in Python and C++; only the spelling differs.
| Phase entered | phase_start(phase_name, parent_phase, num_steps) | phaseStart(phaseName, parentPhase, nbSteps) | Reserve a progress row and record num_steps. |
| Step within phase complete | step_complete(phase_name, step) -> bool | stepComplete(phaseName, step) -> bool | Advance the bar. Return False/false to cancel the build. |
| Phase exited | phase_finish(phase_name) | phaseFinish(phaseName) | Tear down the row. |
A phase whose parent_phase is non-null is nested inside another phase, so the monitor sees a tree of progress rather than a flat list. The implementation must be thread-safe because TensorRT can call the same monitor instance from multiple internal threads.
Wire the monitor to the builder by setting it on the IBuilderConfig. It is a single call in either language:
Figure 2. The callback sequence TensorRT drives during a build, with the cancel path highlighted in red
Read the diagram from top to bottom. The builder opens the Building Engine phase with phase_start, then opens Tactic Selection nested inside it with its parent_phase pointing back at Building Engine. As the build proceeds, the builder calls step_complete (the solid arrows) and your monitor returns a Boolean (the dashed arrows): true lets the build continue and false requests cancellation. In the run shown here, the monitor returns false at step 47, which is the red cancel path, and the builder stops issuing new steps and unwinds. It calls phase_finish early on Tactic Selection and then on Building Engine, closing every active phase in reverse order.
What this tutorial builds
This tutorial shows how to implement IProgressMonitor in Python and C++, add cancellation through step_complete, and route progress updates to a terminal, IDE, service, or agent runtime.
Prerequisites
One NVIDIA GPU. TensorRT (current OSS release) and its Python bindings, or a build of the C++ samples. Python 3.10 or newer (Python path). The TensorRT sample data: ResNet-50 ONNX for Python and MNIST ONNX for C++. Both ship with the sample-data archive or are mounted under /usr/src/tensorrt/data in the official NGC containers. A terminal that supports ANSI virtual-terminal escapes. Any modern Linux shell qualifies; Windows Terminal works if VT is enabled.1. Subclass IProgressMonitor in Python
The subclass is small. It only tracks which phases are active and how many steps each phase contains.
Two things to notice. First, the Lock is not optional. TensorRT will call into the monitor from multiple internal threads, and rendering from a thread that doesn’t own the state will tear the display. Second, step_complete is the only callback that can stop the build. phase_start returns None, so you cannot reject a phase before it begins. The earliest cancellation point is the first step_complete of that phase.
2. Render nested progress bars with virtual-terminal escapes
The renderer is the part that varies most by environment, so this section gives the shape and points to the upstream sample for the production-grade implementation. The pattern is:
The upstream simple_progress_monitor.py renders the same shape with improved color and width handling. The escape sequence \x1b[NA moves the cursor up N lines, and \x1b[2K clears a line. The first render call writes blank rows; subsequent calls overwrite them in place.
When this monitor is attached, do not redirect stdout to a file or pipe. The escape codes will be written verbatim into the log and make it unreadable. For non-terminal sinks, replace _render() with a structured emitter.
3. Add a cancel path
Cancellation is a three-line addition once the monitor exists. Install a SIGINT handler that flips the flag, then let step_complete honor it.
Wire the monitor and run the builder:
build_serialized_network() returns None on cancellation. The builder unwinds at the next step boundary, usually quickly, but not instantaneously, especially inside a long tactic-search step.
Applications should surface cancellation latency to users. A simple “Cancelling…” message during the unwind window goes a long way.
The same flag can be set from any non-signal path, such as an IDE Stop button, an agent timeout, or a CI cancel webhook. Set monitor._cancelled = True, and the build aborts at the next step boundary.
4. The same pattern in C++
Attach it the same way:
std::atomic<bool> for the cancel flag matters because requestCancel() may be called from another thread or a signal handler. Everything else mirrors the Python version.
Where to wire it in real systems
Figure 3. IProgressMonitor is the single integration point between the builder and an application’s surfaces
The cancel arrow is drawn from the agent runtime for concreteness, but the same mechanism applies to every sink. A Ctrl-C from the terminal, an IDE Stop button, an HTTP cancel webhook, or an agent timeout all flip the same monitor._cancelled flag, and the cancel takes effect at the next step_complete return.Where to wire it in real systems
The terminal is the easy case. The interesting integrations route progress somewhere else:
IDE extension: Override _render() to emit $/progress notifications in the Language Server Protocol, or equivalent window/showProgress in protocol. Each phase becomes one progress token; step_complete() becomes a report message; phase_finish() becomes end. FastAPI / HTTP service: Run the build on a background thread, and have _render() push entries into an asyncio.Queue that the request handler drains via Server-Sent Events. The client gets a live stream; the cancel hook is just a POST /builds/{id}/cancel that calls monitor.requestCancel(). Agent tool call: Emit one structured chunk per phase transition ({"phase": ..., "step": ..., "total": ...}) into the tool-call stream. The agent runtime renders it in the user-visible trace, and the same requestCancel() hook is what an agent timeout calls when the build exceeds the budget. This pattern also matters for agent runtimes. Long-running builds need to be observable and cancelable so agents can report progress, enforce time budgets, and stop cleanly.In all three cases, IProgressMonitor is the right boundary. Anything above it (rendering, streaming, transport) is application-level; anything below it (tactic timing, kernel selection) is the builder’s business.
Edge cases to handle
These behaviors are common sources of integration bugs:
Do not redirect stdout while the terminal renderer is attached. The escape sequences will pollute the log. For non-interactive sinks, swap the renderer for a structured emitter. phase_start() can’t cancel. It returns None. The earliest cancel point is the first step_complete() of that phase. If the user cancels during a long phase_start(), the build will continue until the first step boundary. phase_finish() may fire before all num_steps are reported. This can happen during error recovery, builder-internal short-circuits, or when step_complete() returns False. Treat it as the authoritative end-of-phase signal; do not assume current_step == num_steps. Cancel latency is bounded but not zero. The builder finishes the current step before checking the return value. Long tactic-search steps can push this into the seconds-to-tens-of-seconds range. Thread safety is required. The same monitor instance is called from multiple builder threads; uninstrumented dict or unordered_map access from _render() will eventually crash or tear.Get started
The fastest way to run this end to end is:
This starts a live, animated build of a ResNet-50 engine. Replace simple_progress_monitor.py‘s monitor class with the version above or attach a cancel handler around the existing class. C++ equivalent is available in samples/sampleProgressMonitor/.
For larger systems, the right next step is replacing the terminal renderer with the transport the application already uses such as Language Server Protocol notifications, server-sent events, or structured tool-call chunks. IProgressMonitor becomes the point where TensorRT build progress is translated into the application’s progress model.
Learn more
Refer to the following resources for more information:
TensorRT Python API documentation for IProgressMonitor TensorRT C++ API documentation for nvinfer1::IProgressMonitor Python simple_progress_monitor sample C++ sampleProgressMonitor sample TensorRT GitHub releases and sample data.png)
1 day ago
English (United States) ·
French (France) ·