Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions modelopt/onnx/quantization/autotune/benchmark.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@
import os
import re
import shutil
import subprocess # nosec B404
import tempfile
import time
from abc import ABC, abstractmethod
Expand All @@ -42,7 +41,7 @@
import torch

from modelopt.onnx.logging_config import logger
from modelopt.onnx.quantization.ort_utils import _check_for_trtexec
from modelopt.onnx.quantization.ort_utils import _check_for_trtexec, _run_trtexec

Comment on lines +44 to 45
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Preserve the caller-supplied trtexec_path when delegating here.

TrtExecBenchmark still accepts and documents trtexec_path, but _run_trtexec() hardcodes "trtexec", so this call now ignores custom binary paths entirely. That breaks existing callers using a non-PATH install and makes the FileNotFoundError branch misleading.

Possible direction
- from modelopt.onnx.quantization.ort_utils import _check_for_trtexec, _run_trtexec
+ from modelopt.onnx.quantization.ort_utils import _check_for_trtexec, _run_trtexec
@@
-            result = _run_trtexec(cmd)
+            result = _run_trtexec(cmd, executable=self.trtexec_path)
def _run_trtexec(
    args: Sequence[str] | None = None,
    *,
    executable: str = "trtexec",
    timeout: float | None = None,
) -> subprocess.CompletedProcess[str]:
    cmd = [executable, *(args or [])]
    ...

Also applies to: 270-270

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@modelopt/onnx/quantization/autotune/benchmark.py` around lines 44 - 45, The
code in TrtExecBenchmark currently delegates to _run_trtexec but ignores the
caller-supplied trtexec_path; update the call sites in TrtExecBenchmark (and any
other callers noted) to pass the provided trtexec_path into _run_trtexec (or
into _check_for_trtexec if that helper resolves the executable) so custom binary
locations are honored; if _run_trtexec does not accept an executable parameter,
add one (e.g., executable or trtexec_path) and use it when building the command,
and ensure the FileNotFoundError branch and logging still reflect the actual
executable attempted (reference TrtExecBenchmark, _run_trtexec, and
_check_for_trtexec).

TRT_AVAILABLE = importlib.util.find_spec("tensorrt") is not None
if TRT_AVAILABLE:
Expand Down Expand Up @@ -186,7 +185,6 @@ def __init__(
self.latency_pattern = r"\[I\]\s+Latency:.*?median\s*=\s*([\d.]+)\s*ms"

self._base_cmd = [
self.trtexec_path,
f"--avgRuns={self.timing_runs}",
f"--iterations={self.timing_runs}",
f"--warmUp={self.warmup_runs}",
Expand Down Expand Up @@ -269,7 +267,7 @@ def run(

cmd = [*self._base_cmd, f"--onnx={model_path}"]
self.logger.debug(f"Running: {' '.join(cmd)}")
result = subprocess.run(cmd, capture_output=True, text=True) # nosec B603
result = _run_trtexec(cmd)
self._write_log_file(
log_file,
"\n".join(
Expand Down
18 changes: 17 additions & 1 deletion modelopt/onnx/quantization/ort_utils.py
Comment thread
kevalmorabia97 marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,22 @@ def _check_lib_in_ld_library_path(ld_library_path, lib_pattern):
return False, None


def _run_trtexec(
args: list[str] | None = None, timeout: float | None = None
) -> subprocess.CompletedProcess:
"""Run a 'trtexec' command via subprocess.

Args:
args: Arguments to pass to trtexec (without the 'trtexec' command itself).
timeout: Optional subprocess timeout in seconds.

Returns:
The completed subprocess result.
"""
cmd = ["trtexec", *(args or [])]
return subprocess.run(cmd, capture_output=True, text=True, timeout=timeout) # nosec B603


def _check_for_trtexec(min_version: str = "10.0") -> str:
"""Check if the `trtexec` CLI tool is available in PATH and is >= min_version.

Expand Down Expand Up @@ -87,7 +103,7 @@ def _parse_version_from_string(version_str: str) -> str | None:
)

try:
result = subprocess.run([trtexec_path], capture_output=True, text=True, timeout=5) # nosec B603
result = _run_trtexec(timeout=5)
banner_output = result.stdout + result.stderr
parsed_version = _parse_version_from_string(banner_output)

Expand Down
4 changes: 0 additions & 4 deletions modelopt/torch/_deploy/_runtime/tensorrt/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,6 @@
ONE_GIBI_IN_BYTES = 1 << 30

# TensorRT conversion tool names
TRTEXEC = "trtexec"

# trtexec path within docker
TRTEXEC_PATH = "trtexec"
DEFAULT_ARTIFACT_DIR = "modelopt_build/trt_artifacts"

# Default conversion params
Expand Down
35 changes: 19 additions & 16 deletions modelopt/torch/_deploy/_runtime/tensorrt/engine_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@
DEFAULT_NUM_INFERENCE_PER_RUN,
SHA_256_HASH_LENGTH,
TRT_MODE_FLAGS,
TRTEXEC_PATH,
WARMUP_TIME_MS,
TRTMode,
)
Expand All @@ -41,25 +40,29 @@
)


# TODO: Get rid of this function or get approval for `# nosec` usage if we want to include this
# as a non-compiled python file in the release.
def _run_command(cmd: list[str], cwd: Path | None = None) -> tuple[int, bytes]:
"""Util function to execute a command.
def _run_trtexec_streamed(args: list[str], cwd: Path | None = None) -> tuple[int, bytes]:
Comment thread
kevalmorabia97 marked this conversation as resolved.
"""Run a 'trtexec' command via subprocess, streaming stdout/stderr to a temp file.

This util will not direct stdout and stderr to console if the cmd succeeds.
The 'trtexec' binary is hardcoded as the executable; only its arguments may be supplied
by the caller. This restricts the function to trtexec invocations.

Output handling: stdout and stderr are captured to a temp file and returned as bytes.
On failure (non-zero returncode), the captured output is also logged at ERROR level;
on success, this function emits nothing to the console.

Args:
cmd: the command line list
cwd: current working directory
args: Arguments to pass to trtexec (without the 'trtexec' command itself).
cwd: Optional working directory for the subprocess.

Returns:
return code: 0 means successful, otherwise means failed
log_string: the stdout and stderr output as a string

A tuple of (returncode, output) where output is the combined stdout/stderr bytes.
"""
cmd = ["trtexec", *args]
logging.info(" ".join(cmd))
with NamedTemporaryFile("w+b") as log:
p = subprocess.Popen(cmd, stdout=log, stderr=log, cwd=str(cwd) if cwd else None) # nosec
p = subprocess.Popen( # nosec B603 - cmd[0] is hardcoded "trtexec"
cmd, stdout=log, stderr=log, cwd=str(cwd) if cwd else None
)
p.wait()
Comment on lines +43 to 66
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical | 🏗️ Heavy lift

This streamed wrapper still bypasses Bandit and can hang indefinitely.

p.wait() has no timeout, so a stuck trtexec process can block engine build/profile forever, and the new # nosec comment is blocked by repo policy.

Possible direction
-def _run_trtexec_streamed(args: list[str], cwd: Path | None = None) -> tuple[int, bytes]:
+def _run_trtexec_streamed(
+    args: list[str],
+    cwd: Path | None = None,
+    timeout: float = 300.0,
+) -> tuple[int, bytes]:
@@
-        p = subprocess.Popen(  # nosec B603 - cmd[0] is hardcoded "trtexec"
-            cmd, stdout=log, stderr=log, cwd=str(cwd) if cwd else None
-        )
-        p.wait()
+        p = subprocess.Popen(cmd, stdout=log, stderr=log, cwd=str(cwd) if cwd else None)
+        try:
+            p.wait(timeout=timeout)
+        except subprocess.TimeoutExpired:
+            p.kill()
+            p.wait()
+            raise

As per coding guidelines, "Bandit security checks must pass without exceptions. # nosec comments are not allowed as a bypass for security checks," and subprocess execution should enforce limits "by enforcing limits (e.g., timeouts) for external execution."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@modelopt/torch/_deploy/_runtime/tensorrt/engine_builder.py` around lines 43 -
66, The _run_trtexec_streamed function uses p.wait() with no timeout (and a "#
nosec" bypass) which can hang; change it to enforce a timeout and handle
TimeoutExpired: either replace Popen + wait with subprocess.run(cmd, stdout=log,
stderr=log, cwd=..., timeout=TRTEXEC_TIMEOUT) or if using Popen keep
p.communicate(timeout=TRTEXEC_TIMEOUT) and on TimeoutExpired call p.kill(), read
remaining output, and return a failure code and the captured bytes; remove the
"# nosec" bypass and ensure you flush/rewind the temp file (NamedTemporaryFile)
before reading its contents to return combined stdout/stderr and log the output
on non-zero/timeout return.

log.seek(0)
output = log.read()
Expand Down Expand Up @@ -181,7 +184,7 @@ def _build_command(
calib_cache_path: Path | None = None,
timing_cache_path: Path | None = None,
) -> list[str]:
cmd = [TRTEXEC_PATH, f"--onnx={onnx_path}"]
cmd = [f"--onnx={onnx_path}"]
cmd.extend(TRT_MODE_FLAGS[trt_mode])

if trt_mode == TRTMode.INT8 and calib_cache and calib_cache_path:
Expand Down Expand Up @@ -235,7 +238,7 @@ def _setup_files_and_paths(
cmd = _build_command(onnx_path, engine_path, calib_cache_path, timing_cache_path)

try:
ret_code, out = _run_command(cmd)
ret_code, out = _run_trtexec_streamed(cmd)
if ret_code != 0:
return None, out

Expand Down Expand Up @@ -284,7 +287,7 @@ def profile_engine(
"""

def _build_command(engine_path: Path, profile_path: Path, layer_info_path: Path) -> list[str]:
cmd = [TRTEXEC_PATH, f"--loadEngine={engine_path}"]
cmd = [f"--loadEngine={engine_path}"]
cmd += _get_profiling_params(profiling_runs)

if enable_layerwise_profiling:
Expand Down Expand Up @@ -320,7 +323,7 @@ def _setup_files_and_paths(tmp_dir_path: Path, engine_hash: str) -> tuple[Path,
cmd = _build_command(engine_path, profile_path, layer_info_path)

try:
ret_code, out = _run_command(cmd)
ret_code, out = _run_trtexec_streamed(cmd)
if ret_code != 0:
return None, out

Expand Down
Loading