Download Isaac-GR00T/tests/scripts/deployment/test_build_tensorrt_engine.py from Timsty/groot_deployment: direct link, hf CLI and curl.
- Browser
- Download file 16.3 kB
-
https://huggingface.co/Timsty/groot_deployment/resolve/main/Isaac-GR00T/tests/scripts/deployment/test_build_tensorrt_engine.py
- Command line
-
hf download hf://Timsty/groot_deployment/Isaac-GR00T/tests/scripts/deployment/test_build_tensorrt_engine.py
-
curl -L -o test_build_tensorrt_engine.py https://huggingface.co/Timsty/groot_deployment/resolve/main/Isaac-GR00T/tests/scripts/deployment/test_build_tensorrt_engine.py
16.3 kB
| # SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. | |
| # SPDX-License-Identifier: Apache-2.0 | |
| # | |
| # Licensed under the Apache License, Version 2.0 (the "License"); | |
| # you may not use this file except in compliance with the License. | |
| # You may obtain a copy of the License at | |
| # | |
| # http://www.apache.org/licenses/LICENSE-2.0 | |
| # | |
| # Unless required by applicable law or agreed to in writing, software | |
| # distributed under the License is distributed on an "AS IS" BASIS, | |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| # See the License for the specific language governing permissions and | |
| # limitations under the License. | |
| """CPU-only regression tests for build_tensorrt_engine.build_full_pipeline. | |
| The full TRT build path is exercised by tests/scripts/deployment/test_trt_pipeline.py | |
| under @pytest.mark.gpu. These tests cover the orchestration layer only: shape | |
| inference, engine compilation, and the tensorrt / onnx imports themselves are | |
| stubbed so the assertions run on any CPU host. | |
| Keep the stubs and the build_tensorrt_engine import inside the | |
| build_full_pipeline fixture. Installing them at module top-level replaces | |
| sys.modules["onnx"] for every pytest-xdist worker that collects this file, | |
| including GPU workers, where the empty stub then crashes torch.onnx.export | |
| inside the unrelated test_trt_full_pipeline. | |
| """ | |
| from __future__ import annotations | |
| import os | |
| import sys | |
| import types | |
| from unittest.mock import patch | |
| import pytest | |
| _PIPELINE_ONNX_FILES = [ | |
| # The full_pipeline exporter writes vit_fp32.onnx (ViT is FP32 for accuracy); | |
| # build_full_pipeline prefers it over vit_bf16.onnx when both exist. Mirror | |
| # that here so the per-component precision override is exercised. | |
| "vit_fp32.onnx", | |
| "llm_bf16.onnx", | |
| "vl_self_attention.onnx", | |
| "state_encoder.onnx", | |
| "action_encoder.onnx", | |
| "dit_bf16.onnx", | |
| "action_decoder.onnx", | |
| ] | |
| def build_full_pipeline(monkeypatch): | |
| """Yield build_full_pipeline with tensorrt/onnx stubbed in sys.modules. | |
| Every side effect goes through monkeypatch so it is reverted at teardown | |
| and never leaks across tests collected by the same pytest-xdist worker. | |
| """ | |
| if "tensorrt" not in sys.modules: | |
| trt_stub = types.ModuleType("tensorrt") | |
| trt_stub.Logger = types.SimpleNamespace(WARNING=0, ERROR=1, INFO=2, VERBOSE=3) | |
| monkeypatch.setitem(sys.modules, "tensorrt", trt_stub) | |
| if "onnx" not in sys.modules: | |
| monkeypatch.setitem(sys.modules, "onnx", types.ModuleType("onnx")) | |
| # scripts/deployment/ is not a package; mirror the pattern used by | |
| # test_trt_pipeline.py so build_tensorrt_engine is importable. | |
| deploy_dir = os.path.abspath( | |
| os.path.join(os.path.dirname(__file__), "../../../scripts/deployment") | |
| ) | |
| monkeypatch.syspath_prepend(deploy_dir) | |
| from build_tensorrt_engine import build_full_pipeline as fn | |
| yield fn | |
| def _seed_dummy_onnx_dir(onnx_dir): | |
| """Touch every ONNX file build_full_pipeline iterates over.""" | |
| onnx_dir.mkdir(parents=True, exist_ok=True) | |
| for fname in _PIPELINE_ONNX_FILES: | |
| (onnx_dir / fname).touch() | |
| def _fake_build_engine_success(onnx_path, engine_path, **kwargs): | |
| with open(engine_path, "wb"): | |
| pass | |
| def test_build_full_pipeline_raises_when_any_engine_fails(tmp_path, build_full_pipeline): | |
| """Regression: a single sub-engine failure must not silently exit 0. | |
| Before the fix, build_full_pipeline caught all build_engine exceptions, logged | |
| them into a results list, and returned without raising. main() therefore | |
| exited 0 even though the engine directory was incomplete, and downstream | |
| verify/benchmark steps were the first to notice. | |
| """ | |
| onnx_dir = tmp_path / "onnx" | |
| engine_dir = tmp_path / "engines" | |
| _seed_dummy_onnx_dir(onnx_dir) | |
| def fake_build_engine(onnx_path, engine_path, **kwargs): | |
| if "llm" in os.path.basename(onnx_path): | |
| raise RuntimeError("simulated TRT failure") | |
| _fake_build_engine_success(onnx_path, engine_path) | |
| with ( | |
| patch("build_tensorrt_engine.derive_shapes_with_hint", return_value=({}, {}, {})), | |
| patch("build_tensorrt_engine.build_engine", side_effect=fake_build_engine), | |
| pytest.raises(RuntimeError, match=r"1/\d+ engine\(s\) failed"), | |
| ): | |
| build_full_pipeline( | |
| onnx_dir=str(onnx_dir), | |
| engine_dir=str(engine_dir), | |
| precision="bf16", | |
| allow_default_hints=True, # orchestration test; metadata contract covered separately | |
| ) | |
| def test_build_full_pipeline_returns_normally_when_all_engines_build(tmp_path, build_full_pipeline): | |
| """Happy path: every engine builds → no exception.""" | |
| onnx_dir = tmp_path / "onnx" | |
| engine_dir = tmp_path / "engines" | |
| _seed_dummy_onnx_dir(onnx_dir) | |
| with ( | |
| patch("build_tensorrt_engine.derive_shapes_with_hint", return_value=({}, {}, {})), | |
| patch("build_tensorrt_engine.build_engine", side_effect=_fake_build_engine_success), | |
| ): | |
| build_full_pipeline( | |
| onnx_dir=str(onnx_dir), | |
| engine_dir=str(engine_dir), | |
| precision="bf16", | |
| allow_default_hints=True, # orchestration test; metadata contract covered separately | |
| ) | |
| def test_build_full_pipeline_raises_when_all_onnx_inputs_missing(tmp_path, build_full_pipeline): | |
| """Empty ONNX dir must raise instead of producing zero engines and exiting 0.""" | |
| onnx_dir = tmp_path / "onnx_missing" | |
| onnx_dir.mkdir() | |
| engine_dir = tmp_path / "engines" | |
| with ( | |
| patch("build_tensorrt_engine.derive_shapes_with_hint", return_value=({}, {}, {})), | |
| patch("build_tensorrt_engine.build_engine", side_effect=_fake_build_engine_success), | |
| pytest.raises( | |
| RuntimeError, | |
| match=rf"{len(_PIPELINE_ONNX_FILES)}/{len(_PIPELINE_ONNX_FILES)} component\(s\) had no ONNX input", | |
| ), | |
| ): | |
| build_full_pipeline( | |
| onnx_dir=str(onnx_dir), | |
| engine_dir=str(engine_dir), | |
| precision="bf16", | |
| allow_default_hints=True, # orchestration test; metadata contract covered separately | |
| ) | |
| assert not engine_dir.exists() or not list(engine_dir.iterdir()) | |
| def test_build_full_pipeline_raises_when_some_onnx_inputs_missing(tmp_path, build_full_pipeline): | |
| """Partially-populated ONNX dir must raise; "full pipeline" means full.""" | |
| onnx_dir = tmp_path / "onnx_partial" | |
| onnx_dir.mkdir() | |
| seeded = _PIPELINE_ONNX_FILES[:5] | |
| missing = _PIPELINE_ONNX_FILES[5:] | |
| for fname in seeded: | |
| (onnx_dir / fname).touch() | |
| engine_dir = tmp_path / "engines" | |
| with ( | |
| patch("build_tensorrt_engine.derive_shapes_with_hint", return_value=({}, {}, {})), | |
| patch("build_tensorrt_engine.build_engine", side_effect=_fake_build_engine_success), | |
| pytest.raises( | |
| RuntimeError, | |
| match=rf"{len(missing)}/{len(_PIPELINE_ONNX_FILES)} component\(s\) had no ONNX input", | |
| ), | |
| ): | |
| build_full_pipeline( | |
| onnx_dir=str(onnx_dir), | |
| engine_dir=str(engine_dir), | |
| precision="bf16", | |
| allow_default_hints=True, # orchestration test; metadata contract covered separately | |
| ) | |
| def test_build_full_pipeline_error_mentions_both_skips_and_failures(tmp_path, build_full_pipeline): | |
| """When components both skip AND fail, the exception message lists both reasons.""" | |
| onnx_dir = tmp_path / "onnx" | |
| onnx_dir.mkdir() | |
| skipped_file = "llm_bf16.onnx" | |
| failing_file = "dit_bf16.onnx" | |
| for fname in _PIPELINE_ONNX_FILES: | |
| if fname == skipped_file: | |
| continue | |
| (onnx_dir / fname).touch() | |
| engine_dir = tmp_path / "engines" | |
| def fake_build(onnx_path, engine_path, **kwargs): | |
| if os.path.basename(onnx_path) == failing_file: | |
| raise RuntimeError("simulated TRT failure") | |
| _fake_build_engine_success(onnx_path, engine_path) | |
| with ( | |
| patch("build_tensorrt_engine.derive_shapes_with_hint", return_value=({}, {}, {})), | |
| patch("build_tensorrt_engine.build_engine", side_effect=fake_build), | |
| ): | |
| with pytest.raises(RuntimeError) as exc_info: | |
| build_full_pipeline( | |
| onnx_dir=str(onnx_dir), | |
| engine_dir=str(engine_dir), | |
| precision="bf16", | |
| allow_default_hints=True, # orchestration test; metadata contract covered separately | |
| ) | |
| message = str(exc_info.value) | |
| assert "engine(s) failed" in message, message | |
| assert "had no ONNX input" in message, message | |
| # --------------------------------------------------------------------------- | |
| # STRONGLY_TYPED precision sanity check | |
| # --------------------------------------------------------------------------- | |
| # | |
| # Under STRONGLY_TYPED (TRT 10+), precision is read from the ONNX tensor | |
| # types and --precision builder flags are ignored. These tests verify that | |
| # _check_strongly_typed_precision_match fails fast when --precision cannot be | |
| # honored by the network instead of silently building a mismatched engine. | |
| def check_precision_match(build_full_pipeline): # noqa: ARG001 — share stub setup | |
| """Return the _check_strongly_typed_precision_match helper from the module.""" | |
| from build_tensorrt_engine import _check_strongly_typed_precision_match | |
| return _check_strongly_typed_precision_match | |
| def test_strongly_typed_precision_match_passes_for_pure_match(check_precision_match): | |
| """bf16 request against a pure-BF16 network is fine.""" | |
| check_precision_match({"BF16"}, "bf16") | |
| def test_strongly_typed_precision_match_passes_for_mixed_network(check_precision_match): | |
| """The real exporter produces a mixed graph (ViT FP32, rest BF16); bf16 still matches.""" | |
| check_precision_match({"BF16", "FLOAT"}, "bf16") | |
| def test_strongly_typed_precision_match_raises_on_mismatch( | |
| check_precision_match, network_dtypes, requested | |
| ): | |
| """A --precision the STRONGLY_TYPED network cannot honor must raise, not pass silently.""" | |
| with pytest.raises(ValueError, match="cannot be honored"): | |
| check_precision_match(network_dtypes, requested) | |
| def test_strongly_typed_precision_match_rejects_unknown_token(check_precision_match): | |
| """A typo like --precision=int8 should fail loudly, not be treated as 'no match'.""" | |
| with pytest.raises(ValueError, match="Unknown precision"): | |
| check_precision_match({"BF16"}, "int8") | |
| # --------------------------------------------------------------------------- | |
| # Per-component precision override | |
| # --------------------------------------------------------------------------- | |
| # | |
| # build_full_pipeline must mirror the export's mixed-precision layout | |
| # (ViT FP32, every other component BF16) instead of forwarding the | |
| # pipeline-wide --precision to all engines. Without this, building the | |
| # ViT engine from vit_fp32.onnx with --precision=bf16 trips the | |
| # STRONGLY_TYPED sanity check above and the whole pipeline fails. | |
| def precision_from_onnx_path(build_full_pipeline): # noqa: ARG001 — share stub setup | |
| """Return the _precision_from_onnx_path helper from the module.""" | |
| from build_tensorrt_engine import _precision_from_onnx_path | |
| return _precision_from_onnx_path | |
| def test_precision_from_onnx_path_reads_filename_suffix(precision_from_onnx_path, path, expected): | |
| """Recognized suffix overrides the pipeline default.""" | |
| assert precision_from_onnx_path(path, default="bf16") == expected | |
| def test_precision_from_onnx_path_falls_back_to_default(precision_from_onnx_path, path): | |
| """No recognized suffix → return the pipeline default unchanged.""" | |
| assert precision_from_onnx_path(path, default="bf16") == "bf16" | |
| assert precision_from_onnx_path(path, default="fp32") == "fp32" | |
| def test_build_full_pipeline_passes_per_component_precision(tmp_path, build_full_pipeline): | |
| """ViT is built FP32, every other component inherits the pipeline default. | |
| The real exporter writes vit_fp32.onnx (FLOAT IO), so the ViT engine must | |
| be built with precision=fp32 to satisfy the STRONGLY_TYPED sanity check, | |
| while every other component uses the pipeline-wide default (bf16). | |
| """ | |
| onnx_dir = tmp_path / "onnx" | |
| engine_dir = tmp_path / "engines" | |
| _seed_dummy_onnx_dir(onnx_dir) | |
| seen_precisions: dict[str, str] = {} | |
| def fake_build_engine(onnx_path, engine_path, precision, **kwargs): | |
| seen_precisions[os.path.basename(onnx_path)] = precision | |
| _fake_build_engine_success(onnx_path, engine_path) | |
| with ( | |
| patch("build_tensorrt_engine.derive_shapes_with_hint", return_value=({}, {}, {})), | |
| patch("build_tensorrt_engine.build_engine", side_effect=fake_build_engine), | |
| ): | |
| build_full_pipeline( | |
| onnx_dir=str(onnx_dir), | |
| engine_dir=str(engine_dir), | |
| precision="bf16", | |
| allow_default_hints=True, # orchestration test; metadata contract covered separately | |
| ) | |
| assert seen_precisions["vit_fp32.onnx"] == "fp32", ( | |
| f"ViT must be built as fp32 to match its STRONGLY_TYPED FLOAT IO " | |
| f"(saw precision={seen_precisions['vit_fp32.onnx']!r})" | |
| ) | |
| for fname in ( | |
| "llm_bf16.onnx", | |
| "dit_bf16.onnx", | |
| "vl_self_attention.onnx", | |
| "state_encoder.onnx", | |
| "action_encoder.onnx", | |
| "action_decoder.onnx", | |
| ): | |
| assert seen_precisions[fname] == "bf16", ( | |
| f"{fname} should inherit the pipeline default precision=bf16 " | |
| f"(saw {seen_precisions[fname]!r})" | |
| ) | |
| def test_build_full_pipeline_falls_back_to_vit_bf16_when_fp32_missing( | |
| tmp_path, build_full_pipeline | |
| ): | |
| """If only vit_bf16.onnx is on disk, ViT picks up the pipeline default (bf16).""" | |
| onnx_dir = tmp_path / "onnx" | |
| engine_dir = tmp_path / "engines" | |
| onnx_dir.mkdir(parents=True) | |
| # No vit_fp32.onnx — exercise the build_full_pipeline fallback branch. | |
| fallback_files = [ | |
| "vit_bf16.onnx", | |
| "llm_bf16.onnx", | |
| "vl_self_attention.onnx", | |
| "state_encoder.onnx", | |
| "action_encoder.onnx", | |
| "dit_bf16.onnx", | |
| "action_decoder.onnx", | |
| ] | |
| for fname in fallback_files: | |
| (onnx_dir / fname).touch() | |
| seen_precisions: dict[str, str] = {} | |
| def fake_build_engine(onnx_path, engine_path, precision, **kwargs): | |
| seen_precisions[os.path.basename(onnx_path)] = precision | |
| _fake_build_engine_success(onnx_path, engine_path) | |
| with ( | |
| patch("build_tensorrt_engine.derive_shapes_with_hint", return_value=({}, {}, {})), | |
| patch("build_tensorrt_engine.build_engine", side_effect=fake_build_engine), | |
| ): | |
| build_full_pipeline( | |
| onnx_dir=str(onnx_dir), | |
| engine_dir=str(engine_dir), | |
| precision="bf16", | |
| allow_default_hints=True, # orchestration test; metadata contract covered separately | |
| ) | |
| assert seen_precisions["vit_bf16.onnx"] == "bf16" | |