NVIDIA、TensorRT エンジン構築を監視・中止可能に
NVIDIA は、TensorRT エンジン構築プロセスの可視化と中止機能を Python および C++ で利用可能にし、開発者の効率向上とリソースの最適化を実現しました。
キーポイント
可視化機能の強化
エンジン構築中の「Building Engine」「Tactic Selection」などの詳細な進捗状況をリアルタイムで確認できるようになり、開発者がプロセスの状態を把握しやすくなりました。
中止(キャンセル)機能の実装
不要になった場合やエラーが発生した場合に、構築プロセスを早期に停止・中止できる機能が追加され、無駄な GPU 時間の消費を防ぎます。
開発ワークフローの改善
長時間実行される自動化エージェントや CI/CD パイプラインにおいて、フリーズしたプロセスへの対応が容易になり、リソースのロスを削減します。
可視化と制御のための IProgressMonitor インターフェース
TensorRT のエンジンビルドプロセスを監視し、進行状況を表示するために `IProgressMonitor` という新しいインターフェースが導入されました。
ネストされた進捗バーによる詳細なステータス表示
このインターフェースを実装することで、「Tactic Selection」や「Kernel Autotune」などの内部フェーズを階層構造(ネスト)で可視化するライブな進行状況バーを実現できます。
ビルドのキャンセル機能の実現
ユーザーが長時間かかるビルドプロセスを必要に応じて中断・キャンセルできるようになり、リソースの無駄遣いを防ぎます。
IProgressMonitor の実装構造
この抽象クラスをサブクラス化して phase_start、step_complete、phase_finish の3 つのメソッドを実装し、ビルド中の進捗ツリーとキャンセル処理(step_complete の戻り値)を制御します。
重要な引用
A TensorRT engine build can take seconds to many minutes.
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
Building Engine, Tactic Selection, Timing Cache Warmup, and Kernel Autotune.
IProgressMonitor is an abstract base class that TensorRT calls during the engine build.
影響分析・編集コメントを表示
影響分析
この機能強化は、TensorRT の実用性を高める重要なステップであり、特に大規模モデルの開発やクラウド環境での自動スケーリングにおいて、開発者のストレスを軽減し、計算リソースの有効活用を可能にします。これにより、AI エンジニアリングのワークフローがより予測可能で制御可能なものへと進化し、生産性が向上することが期待されます。
編集コメント
TensorRT の構築プロセスは、特に大規模モデルや新アーキテクチャ対応時に数十分かかることもあり、開発者の時間を浪費する要因となってきました。今回の可視化と中止機能の追加は、実務的な課題を直接解決する極めて有効なアップデートであり、AI 開発の現場での即効性が高いと言えます。
TensorRT エンジンのビルドには、数秒から数十分までかかる場合があります。大規模な型安全モデルの構築や深い戦術探索、あるいは新しい GPU スキューでのタイミングキャッシュが冷えている状態では、開発者やエンドユーザー、AI エージェントがターミナル画面を凝視し続けることになり、「待つのか、再試行するのか、プロセスを終了させるのか」判断に迷うことになります。現在の NVIDIA TensorRT の統合機能の多くは、ビルド中に何の情報も提供せず、早期中止する方法すら用意されていません。長期実行型のエージェントワークフローにおいて、これは GPU 時間の無駄遣いやセッションの凍結につながります。
image*図 1. IProgressMonitor のサブクラスによって描画される、TensorRT エンジンビルド用のライブ・ネスト型プログレスバー*
この課題を解決するために TensorRT は「IProgressMonitor」という API を提供しており、これは数バージョンにわたって NvInfer.h に含まれています。本チュートリアルでは、Python と C++ 向けの最小限の差し替え実装を紹介し、Ctrl-C や外部イベントループからのプログラムによる停止シグナルに応答するキャンセル経路を実装する方法を解説します。さらに、IDE、サービス、またはエージェントランタイムが利用できるよう、生成された進捗ストリームをどこで表面化(サフェース)すべきかも示します。
本記事のすべてのコードブロックは、NVIDIA がメンテナンスしているオープンソースサンプルから抜粋、あるいはモデル化されています:
- Python: samples/python/simple_progress_monitor/ (ResNet-50、型安全ネットワーク)
- C++: samples/sampleProgressMonitor/ (MNIST)
IProgressMonitor で得られるもの
IProgressMonitor は、TensorRT がエンジン構築中に呼び出す抽象基底クラスです。このクラスをサブクラス化し、3 つのメソッドを上書きすることで実装します。
Python と C++ ではメソッドの形状は同一ですが、記述方法のみが異なります。
| Python メソッド | C++ メソッド | 役割 |
|---|---|---|
phase_start(phase_name, parent_phase, num_steps) | phaseStart(phaseName, parentPhase, nbSteps) | 進捗表示用の行を確保し、num_steps を記録します。 |
step_complete(phase_name, step) -> bool | stepComplete(phaseName, step) -> bool | バーを進めます。False(または false)を返すと構築がキャンセルされます。 |
phase_finish(phase_name) | phaseFinish(phaseName) | 行の表示を終了します。 |
表 1. Python と C++ で mirroring された IProgressMonitor インターフェース。3 つのメソッドは意味論的に同一ですが、ビルダーの動作に影響を与えるのは step_complete の返り値のみです。
親フェーズ(parent_phase)が null 以外であるフェーズはネストされており、モニターは単なるリストではなく進捗のツリー構造として認識します。実装ではスレッドセーフである必要があります。TensorRT は内部のスレッドから複数のスレッドで同じモニターインスタンスを呼び出す可能性があるためです。
このモニターをビルダーに接続するには、IBuilderConfig に設定するだけです。Python と C++ のいずれでも単一の呼び出しで完了します。
config.progress_monitor = MyMonitor() # Pythonconfig->setProgressMonitor(&myMonitor); // C++
image*図 2. TensorRT がビルド中に呼び出すコールバックシーケンス。キャンセルパスは赤色で強調表示されています*。
図を上から順に読んでください。ビルダーはまず phase_start を呼び出して「エンジン構築」フェーズを開始します。その内部にはネストされた「タクト選択」フェーズがあり、親フェーズとして「エンジン構築」を指しています。ビルドが進行するにつれ、ビルダーは step_complete(実線の矢印)を呼び出し、モニター側から Boolean 値(点線の矢印)が返されます。true が返されればビルドは続行され、false が返されればキャンセル要求となります。
ここで示した実行例では、47 ステップ目でモニターが false を返し、これが赤色のキャンセルパスとなります。これによりビルダーは新しいステップの発行を停止し、巻き戻し処理を開始します。そして「タクト選択」フェーズで早期に phase_finish を呼び出し、続いて「エンジン構築」フェーズでも同様に終了させます。こうしてアクティブなすべてのフェーズが逆順で閉じられます。
このチュートリアルで作成するもの
このチュートリアルでは、Python と C++ の両方で IProgressMonitor を実装する方法、step_complete を通じてキャンセル機能を実装する方法、そして進捗更新情報をターミナル、IDE、サービス、あるいはエージェントランタイムへルーティングする方法を解説します。
前提条件
- NVIDIA GPU が 1 台あること。
- TensorRT(最新のオープンソース版)とその Python バインディング、または C++ サンプルのビルド済み環境があること。
- Python 3.10 以降(Python のパスが設定されていること)。
- TensorRT サンプルデータ:Python 用には ResNet-50 ONNX、C++ 用には MNIST ONNX。これらはサンプルデータのアーカイブに含まれているか、公式の NGC コンテナ内では
/usr/src/tensorrt/dataにマウントされています。
ANSI 仮想端末エスケープシーケンスに対応したターミナルが必要です。現代的な Linux シェルであればすべて対応しており、Windows Terminal でも VT(仮想端末)機能を有効にすれば問題なく動作します。
1. Python で IProgressMonitor をサブクラス化する
このサブクラスは非常にシンプルです。アクティブなフェーズと各フェーズのステップ数を追跡するだけのものです。
import tensorrt as trt
from dataclasses import dataclass, field
from threading import Lock
@dataclass
class _PhaseState:
num_steps: int
current_step: int = 0
parent: str | None = None
class RichProgressMonitor(trt.IProgressMonitor):
def __init__(self):
super().__init__()
self._lock = Lock()
self._phases: dict[str, _PhaseState] = {}
self._cancelled = False
self._rendered_lines = 0
def phase_start(self, phase_name, parent_phase, num_steps):
with self._lock:
self._phases[phase_name] = _PhaseState(
num_steps=num_steps, parent=parent_phase
)
self._render()
def step_complete(self, phase_name, step) -> bool:
with self._lock:
if phase_name in self._phases:
self._phases[phase_name].current_step = step
self._render()
return not self._cancelled
def phase_finish(self, phase_name):
with self._lock:
self._phases.pop(phase_name, None)
self._render()2 つの重要な点に注意してください。まず、ロックは必須です。TensorRT は内部スレッドから複数の場所でモニターを呼び出しますが、状態を所有していないスレッドからの描画は表示を破損させる恐れがあります。次に、build を停止できるのは step_complete コールバックのみです。phase_start は None を返すため、フェーズ開始前に拒否することはできません。キャンセル可能な最初の地点は、そのフェーズの最初の step_complete です。
2. 仮想ターミナルエスケープを使用したネストされたプログレスバーの描画
レンダラーは環境によって実装が最も異なる部分です。このセクションでは基本となる形状を示し、本番環境向けの完全な実装については上位サンプルを参照してください。パターンは以下の通りです。
def _render(self):
# 子要素が親要素の下に描画されるよう、ネスト深度に基づいてフェーズをソートする。
rows = sorted(
self._phases.items(),
key=lambda kv: (kv[1].parent or "", kv[0]),
)
# カーソルを移動させるのは現在の行数ではなく、直前の描画で消費された行数です。フェーズはネスト時に追加され、phase_finish で削除されるため、ツリー形状が変化する瞬間にこの 2 つの値は一致しなくなります。
if self._rendered_lines:
print(f"\x1b[{self._rendered_lines}A", end="")
for name, st in rows:
# step は [0, num_steps) の範囲にある 0 ベースのインデックスです。+1 を加算することで完了数を表し、バーが実際に 100% に達するようにします。
done = min(st.current_step + 1, st.num_steps)
pct = done / max(st.num_steps, 1)
bar = "█" * int(40 * pct) + "·" * (40 - int(40 * pct))
indent = " " if st.parent else ""
print(f"\x1b[2K{indent}{name:<28} [{bar}] {done}/{st.num_steps}")
# Clear rows left behind when a phase finishes and the count shrinks.
for _ in range(self._rendered_lines - len(rows)):
print("\x1b[2K")
self._rendered_lines = len(rows)
このアップストリームの simple_progress_monitor.py は、色と幅の処理を改善しつつ、同じ形状の進捗表示を実現しています。エスケープシーケンス \x1b[NA はカーソルを N 行上に移動させ、\x1b[2K は現在の行をクリアします。最初の描画呼び出しでは空白行が書き込まれ、その後の呼び出しでそれらの行が上書きされます。
このモニターを使用している間は、標準出力をファイルやパイプにリダイレクトしないでください。エスケープコードがそのままログに書き込まれてしまい、内容が判読できなくなります。ターミナル以外の出力先では、_render() を構造化されたエミッターに置き換えてください。
3. キャンセル経路の追加
モニターを実装した段階で、キャンセル機能は 3 行のコード追加で実装できます。SIGINT ハンドラーを設定してフラグを切り替え、step_complete でそのフラグを確認するだけです。
import signal
def install_cancel(monitor: RichProgressMonitor):
def handler(signum, frame):
monitor._cancelled = True
print("\nCancelling TensorRT build at next step boundary...")
signal.signal(signal.SIGINT, handler)
モニターを接続してビルダーを実行します。
builder = trt.Builder(TRT_LOGGER)
network = builder.create_network(
1 << int(trt.NetworkDefinitionCreationFlag.STRONGLY_TYPED)
)
parser = trt.OnnxParser(network, TRT_LOGGER)
with open(onnx_path, "rb") as f:
parser.parse(f.read())
config = builder.create_builder_config()
monitor = RichProgressMonitor()
config.progress_monitor = monitor
install_cancel(monitor)
serialized = builder.build_serialized_network(network, config)
if serialized is None:
if monitor._cancelled:
print("Build cancelled cleanly.")
else:
print("Build failed.")
build_serialized_network() はキャンセル時に None を返します。ビルダーは次のステップ境界で巻き戻しを開始しますが、これは通常素早く行われます。ただし、長時間続く戦術探索(tactic-search)の最中などでは、即座に完了しない場合もあります。
アプリケーション側では、このキャンセル待ち時間をユーザーに示すことが重要です。待機中に「キャンセル中…」というメッセージを表示するだけでも、ユーザー体験は大きく改善されます。
このフラグは、シグナル経路以外からでも設定可能です。IDE の停止ボタンやエージェントのタイムアウト、CI キャンセル用ウェブフックなどが該当します。monitor._cancelled に True をセットすれば、ビルドは次のステップ境界で即座に中止されます。
4. C++ でも同じパターンが使える
#include <NvInfer.h>
#include <atomic>
#include <mutex>
#include <unordered_map>
class RichProgressMonitor : public nvinfer1::IProgressMonitor {
public:
void phaseStart(char const* phaseName,
char const* parentPhase,
int32_t nbSteps) noexcept override {
std::lock_guard<std::mutex> g(mu_);
phases_[phaseName] = {nbSteps, 0, parentPhase ? parentPhase : ""};
render();
}
bool stepComplete(char const* phaseName,
int32_t step) noexcept override {
std::lock_guard<std::mutex> g(mu_);
auto it = phases_.find(phaseName);
if (it != phases_.end())
it->second.current = step;
render();
return !cancelled_.load();
}
void phaseFinish(char const* phaseName) noexcept override {
std::lock_guard<std::mutex> g(mu_);
phases_.erase(phaseName);
render();
}
void requestCancel() noexcept {
cancelled_.store(true);
}
private:
struct Phase {
int32_t nbSteps;
int32_t current;
std::string parent;
};
std::mutex mu_;
std::unordered_map<std::string, Phase> phases_;
std::atomic<bool> cancelled_{false};
void render() noexcept;
};
同じようにアタッチします:
auto config =
std::unique_ptr<nvinfer1::IBuilderConfig>(
builder->createBuilderConfig());
RichProgressMonitor monitor;
config->setProgressMonitor(&monitor);
キャンセルフラグに std::atomic<bool> を使うのは、requestCancel() が別のスレッドやシグナルハンドラから呼び出される可能性があるためです。それ以外は Python 版と同じ構成になります。
実際のシステムでどこに接続するか

*図 3. IProgressMonitor は、ビルダーとアプリケーションの各表面を結ぶ唯一の統合ポイントです*
キャンセル矢印は具体例としてエージェントランタイムから描画されていますが、この仕組みはすべての出力先(シンク)に共通して適用されます。ターミナルからの Ctrl-C や IDE の停止ボタン、HTTP キャンセル Webhook、あるいはエージェントのタイムアウトはすべて、同じモニタの _cancelled フラグをオンにし、キャンセル処理は次の step_complete 呼び出しが返された時点で有効になります。
実際のシステムでどこに接続するか
ターミナル経由での連携は比較的簡単です。より興味深いのは、進行状況を別の場所に流し込む統合方法です。
- IDE 拡張機能:_render() をオーバーライドして、Language Server Protocol で $/progress 通知を出力します(またはプロトコル equivalent の window/showProgress)。各フェーズが 1 つの進捗トークンとなり、step_complete() は報告メッセージとして、phase_finish() は完了シグナルとして機能します。
- FastAPI / HTTP サービス:ビルド処理をバックグラウンドスレッドで実行し、_render() が生成したエントリを asyncio.Queue にプッシュします。リクエストハンドラーは Server-Sent Events を通じてこのキューから順次読み取り、クライアントにはリアルタイムストリームとして提供されます。キャンセルフックは POST /builds/{id}/cancel エンドポイントで、ここが monitor.requestCancel() を呼び出す入口となります。
- エージェントツールコール:各フェーズ遷移ごとに構造化されたチャンク({"phase": ..., "step": ..., "total": ...})をツールコールストリームに出力します。エージェントランタイムはこれをユーザーに見えるトレースとしてレンダリングし、ビルドが予算超過になった際に呼び出されるのが、同じ requestCancel() フックです。このパターンはエージェントランタイムにとっても重要です。長時間実行されるビルドは観測可能かつキャンセル可能である必要があり、それによってエージェントは進捗を報告したり、時間予算を厳守したり、安全に処理を終了したりできます。
これら3つのケースすべてにおいて、IProgressMonitor が境界線となります。このインターフェースより上側(レンダリング、ストリーミング、転送など)はアプリケーションレベルの処理であり、下側(戦術的なタイミング調整やカーネル選択など)はビルダー側の責任範囲です。
対処すべきエッジケース
以下の挙動は、統合時にバグの原因になりやすい典型的なケースです:
- ターミナルレンダラーが接続されている間は、標準出力(stdout)をリダイレクトしないでください。エスケープシーケンスがログに混入する原因となります。非対話型のシンクを使用する場合は、レンダラーを構造化されたエミッターに置き換えてください。
- phase_start() メソッドはキャンセルできません。このメソッドは None を返します。キャンセルが可能になるのは、そのフェーズ内の最初の step_complete() 呼び出し時点です。ユーザーが長い phase_start() の実行中にキャンセルした場合でも、ビルド処理は最初のステップ境界に達するまで継続されます。
- phase_finish() が発火するのは、num_steps で指定されたすべてのステップが報告される前になる可能性があります。これはエラー回復時やビルダー内部での短絡処理が行われた場合、あるいは step_complete() が False を返した場合などに発生します。このメソッドはフェーズ終了の権威あるシグナルとして扱ってください。current_step と num_steps が一致すると仮定してはいけません。
- キャンセルの応答時間は上限が決まっていますが、ゼロではありません。ビルダーは現在のステップを完了させてから、戻り値を確認します。戦術探索に時間がかかるステップの場合、この遅延が数秒から数十秒に及ぶこともあります。
- スレッド安全性の確保が必要です。同じモニターインスタンスに対して、複数のビルドスレッドから同時に呼び出しが行われます。_render() 内でインストゥルメントされていない辞書や unordered_map にアクセスすると、最終的にはクラッシュやデータ破損を引き起こします。
始め方
この仕組みをエンドツーエンドで実行する最速の方法は以下の通りです:
これにより、ResNet-50 のエンジン構築プロセスがライブかつアニメーション表示されるようになります。simple_progress_monitor.py に含まれるモニタークラスを上記のバージョンに置き換えるか、既存のクラスを取り囲むようにキャンセルハンドラーを追加してください。C++ 版の実装は samples/sampleProgressMonitor/ ディレクトリにあります。
大規模なシステムでは、次のステップとしてターミナルレンダラーを、アプリケーションがすでに利用している通信手段(Language Server Protocol の通知、サーバー送信イベント、構造化されたツール呼び出しチャンクなど)に置き換えるのが適切です。この際、IProgressMonitor は TensorRT の構築進捗情報をアプリケーション独自の進捗モデルに変換する重要なポイントとなります。
さらに詳しく学ぶ
詳細については、以下のリソースを参照してください:
- IProgressMonitor に関する TensorRT Python API ドキュメント
- nvinfer1::IProgressMonitor に関する TensorRT C++ API ドキュメント
- Python の
simple_progress_monitorサンプル - C++ の
sampleProgressMonitorサンプル - TensorRT の GitHub リリースおよびサンプルデータ
原文を表示
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.

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.
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:
config.progress_monitor = MyMonitor() # Python
config->setProgressMonitor(&myMonitor); // C++

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.
import tensorrt as trt
from dataclasses import dataclass, field
from threading import Lock
@dataclass
class _PhaseState:
num_steps: int
current_step: int = 0
parent: str | None = None
class RichProgressMonitor(trt.IProgressMonitor):
def __init__(self):
super().__init__()
self._lock = Lock()
self._phases: dict[str, _PhaseState] = {}
self._cancelled = False
self._rendered_lines = 0
def phase_start(self, phase_name, parent_phase, num_steps):
with self._lock:
self._phases[phase_name] = _PhaseState(
num_steps=num_steps, parent=parent_phase
)
self._render()
def step_complete(self, phase_name, step) -> bool:
with self._lock:
if phase_name in self._phases:
self._phases[phase_name].current_step = step
self._render()
return not self._cancelled
def phase_finish(self, phase_name):
with self._lock:
self._phases.pop(phase_name, None)
self._render()
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:
def _render(self):
# Order phases by nesting depth so children draw under parents.
rows = sorted(
self._phases.items(),
key=lambda kv: (kv[1].parent or "", kv[0]),
)
# Move the cursor up by the number of lines the PREVIOUS render printed,
# not the current row count — phases are added on nesting and removed on
# phase_finish, so the two differ exactly when the tree changes shape.
if self._rendered_lines:
print(f"\x1b[{self._rendered_lines}A", end="")
for name, st in rows:
# step is a 0-based index in [0, num_steps); +1 turns it into a
# completed count so the bar can actually reach 100%.
done = min(st.current_step + 1, st.num_steps)
pct = done / max(st.num_steps, 1)
bar = "█" * int(40 * pct) + "·" * (40 - int(40 * pct))
indent = " " if st.parent else ""
print(f"\x1b[2K{indent}{name:<28} [{bar}] {done}/{st.num_steps}")
# Clear rows left behind when a phase finishes and the count shrinks.
for _ in range(self._rendered_lines - len(rows)):
print("\x1b[2K")
self._rendered_lines = len(rows)
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.
import signal
def install_cancel(monitor: RichProgressMonitor):
def handler(signum, frame):
monitor._cancelled = True
print("\nCancelling TensorRT build at next step boundary...")
signal.signal(signal.SIGINT, handler)
Wire the monitor and run the builder:
builder = trt.Builder(TRT_LOGGER)
network = builder.create_network(
1 << int(trt.NetworkDefinitionCreationFlag.STRONGLY_TYPED)
)
parser = trt.OnnxParser(network, TRT_LOGGER)
with open(onnx_path, "rb") as f:
parser.parse(f.read())
config = builder.create_builder_config()
monitor = RichProgressMonitor()
config.progress_monitor = monitor
install_cancel(monitor)
serialized = builder.build_serialized_network(network, config)
if serialized is None:
if monitor._cancelled:
print("Build cancelled cleanly.")
else:
print("Build failed.")
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++
#include <NvInfer.h>
#include <atomic>
#include <mutex>
#include <unordered_map>
class RichProgressMonitor : public nvinfer1::IProgressMonitor {
public:
void phaseStart(char const* phaseName,
char const* parentPhase,
int32_t nbSteps) noexcept override {
std::lock_guard<std::mutex> g(mu_);
phases_[phaseName] = {nbSteps, 0, parentPhase ? parentPhase : ""};
render();
}
bool stepComplete(char const* phaseName,
int32_t step) noexcept override {
std::lock_guard<std::mutex> g(mu_);
auto it = phases_.find(phaseName);
if (it != phases_.end())
it->second.current = step;
render();
return !cancelled_.load();
}
void phaseFinish(char const* phaseName) noexcept override {
std::lock_guard<std::mutex> g(mu_);
phases_.erase(phaseName);
render();
}
void requestCancel() noexcept {
cancelled_.store(true);
}
private:
struct Phase {
int32_t nbSteps;
int32_t current;
std::string parent;
};
std::mutex mu_;
std::unordered_map<std::string, Phase> phases_;
std::atomic<bool> cancelled_{false};
void render() noexcept;
};
Attach it the same way:
auto config =
std::unique_ptr<nvinfer1::IBuilderConfig>(
builder->createBuilderConfig());
RichProgressMonitor monitor;
config->setProgressMonitor(&monitor);
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
関連記事
今日のまとめ
AI日報で今日の重要ニュースをまとめ読み