516 lines
18 KiB
Python
516 lines
18 KiB
Python
#
|
|
# SPDX-FileCopyrightText: Copyright (c) 1993-2024 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.
|
|
#
|
|
import copy
|
|
import glob
|
|
import os
|
|
import shutil
|
|
from textwrap import dedent
|
|
from typing import List
|
|
|
|
import pytest
|
|
import tensorrt as trt
|
|
from polygraphy import mod, util
|
|
|
|
from tests.helper import ROOT_DIR
|
|
|
|
EXAMPLES_ROOT = os.path.join(ROOT_DIR, "examples")
|
|
|
|
|
|
class Marker:
|
|
def __init__(self, matches_start_func=None, matches_end_func=None):
|
|
self.matches_start = util.default(
|
|
matches_start_func, lambda line: line == self.start
|
|
)
|
|
self.matches_end = util.default(matches_end_func, lambda line: line == self.end)
|
|
|
|
@staticmethod
|
|
def from_name(name):
|
|
return Marker(
|
|
matches_start_func=lambda line: line
|
|
== f"<!-- Polygraphy Test: {name} Start -->",
|
|
matches_end_func=lambda line: line
|
|
== f"<!-- Polygraphy Test: {name} End -->",
|
|
)
|
|
|
|
|
|
VALID_MARKERS = {
|
|
# For command markers, the start marker may be annotated with a language tag, e.g. ```py, so an exact match is too strict.
|
|
"command": Marker(
|
|
matches_start_func=lambda line: line.startswith("```"),
|
|
matches_end_func=lambda line: line == "```",
|
|
),
|
|
# Marks an entire block to be ignored by the tests.
|
|
"ignore": Marker.from_name("Ignore"),
|
|
# Marks an entire block as being expected to fail.
|
|
"xfail": Marker.from_name("XFAIL"),
|
|
}
|
|
|
|
|
|
class MarkerTracker:
|
|
"""
|
|
Keeps track of active markers in the current README on a line-by-line basis.
|
|
"""
|
|
|
|
def __init__(self, readme):
|
|
self.readme = readme
|
|
self.active_markers = set()
|
|
self.entering_markers = set() # The markers that we are currently entering
|
|
self.exiting_markers = set() # The markers that we are currently exiting
|
|
|
|
def __enter__(self):
|
|
self.file = open(self.readme, "r")
|
|
return self
|
|
|
|
def __exit__(self, exc_type, exc_value, traceback):
|
|
self.file.close()
|
|
|
|
def __iter__(self):
|
|
for line in self.file.readlines():
|
|
stripped_line = line.strip()
|
|
self.entering_markers.clear()
|
|
self.exiting_markers.clear()
|
|
|
|
for marker in VALID_MARKERS.values():
|
|
if not self.is_in(marker) and marker.matches_start(stripped_line):
|
|
self.active_markers.add(marker)
|
|
self.entering_markers.add(marker)
|
|
elif marker.matches_end(stripped_line):
|
|
self.active_markers.remove(marker)
|
|
self.exiting_markers.add(marker)
|
|
|
|
yield line.rstrip()
|
|
|
|
def is_in(self, marker):
|
|
"""
|
|
Whether we are currently on a line between the specified start and end marker.
|
|
This will always return False for a line containing the marker itself.
|
|
"""
|
|
return marker in self.active_markers and not (
|
|
self.entering(marker) or self.exiting(marker)
|
|
)
|
|
|
|
def entering(self, marker):
|
|
return marker in self.entering_markers
|
|
|
|
def exiting(self, marker):
|
|
return marker in self.exiting_markers
|
|
|
|
|
|
class CommandBlock:
|
|
def __init__(self, xfail):
|
|
self.xfail = xfail
|
|
self.content = None
|
|
|
|
def add(self, line):
|
|
if self.content is None:
|
|
self.content = line
|
|
else:
|
|
self.content += f"\n{line}"
|
|
|
|
def __str__(self):
|
|
return dedent(self.content)
|
|
|
|
|
|
# Extract any ``` blocks from the README
|
|
# NOTE: This parsing logic is not smart enough to handle multiple separate commands in a single block.
|
|
def load_command_blocks_from_readme(readme) -> List[CommandBlock]:
|
|
with open(readme, "r") as f:
|
|
contents = f.read()
|
|
# Check that the README has all the expected sections.
|
|
assert (
|
|
"## Introduction" in contents
|
|
), "All example READMEs should have an 'Introduction' section!"
|
|
assert (
|
|
"## Running The Example" in contents
|
|
), "All example READMEs should have a 'Running The Example' section!"
|
|
|
|
cmd_blocks = []
|
|
with MarkerTracker(readme) as tracker:
|
|
for line in tracker:
|
|
if tracker.is_in(VALID_MARKERS["ignore"]):
|
|
continue
|
|
|
|
if tracker.entering(VALID_MARKERS["command"]):
|
|
current_block = CommandBlock(
|
|
xfail=tracker.is_in(VALID_MARKERS["xfail"])
|
|
)
|
|
elif tracker.exiting(VALID_MARKERS["command"]):
|
|
cmd_blocks.append(copy.copy(current_block))
|
|
elif tracker.is_in(VALID_MARKERS["command"]):
|
|
current_block.add(line)
|
|
|
|
return cmd_blocks
|
|
|
|
|
|
class Example:
|
|
def __init__(self, path_components, artifact_names=[]):
|
|
self.path = os.path.join(EXAMPLES_ROOT, *path_components)
|
|
self.artifacts = [os.path.join(self.path, name) for name in artifact_names]
|
|
# Ensures no files in addition to the specified artifacts were created.
|
|
self.original_files = []
|
|
|
|
def _get_file_list(self):
|
|
return [
|
|
path
|
|
for path in glob.iglob(os.path.join(self.path, "*"))
|
|
if "__pycache__" not in path
|
|
]
|
|
|
|
def _remove_artifacts(self, must_exist=True):
|
|
for artifact in self.artifacts:
|
|
if must_exist:
|
|
print(f"Checking for the existence of artifact: {artifact}")
|
|
assert os.path.exists(artifact), f"{artifact} does not exist!"
|
|
elif not os.path.exists(artifact):
|
|
continue
|
|
|
|
if os.path.isdir(artifact):
|
|
shutil.rmtree(artifact)
|
|
else:
|
|
os.remove(artifact)
|
|
|
|
def __enter__(self):
|
|
self._remove_artifacts(must_exist=False)
|
|
|
|
self.original_files = self._get_file_list()
|
|
readme = os.path.join(self.path, "README.md")
|
|
return load_command_blocks_from_readme(readme)
|
|
|
|
def run(self, cmd_block, sandboxed_install_run):
|
|
# Remove whitespace args and escaped newlines
|
|
command = [
|
|
arg
|
|
for arg in str(cmd_block).strip().split(" ")
|
|
if arg.strip() and arg != "\\\n"
|
|
]
|
|
status = sandboxed_install_run(command, cwd=self.path)
|
|
|
|
details = f"Note: Command was: {' '.join(command)}.\n==== STDOUT ====\n{status.stdout}\n==== STDERR ====\n{status.stderr}"
|
|
if cmd_block.xfail:
|
|
assert (
|
|
not status.success
|
|
), f"Command that was expected to fail did not fail. {details}"
|
|
else:
|
|
assert (
|
|
status.success
|
|
), f"Command that was expected to succeed did not succeed. {details}"
|
|
return status
|
|
|
|
def __exit__(self, exc_type, exc_value, traceback):
|
|
"""
|
|
Checks for and removes artifacts expected by this example
|
|
"""
|
|
self._remove_artifacts()
|
|
assert (
|
|
self._get_file_list() == self.original_files
|
|
), "Unexpected files were created. If this is the desired behavior, add the file paths to `artifact_names`"
|
|
|
|
def __str__(self):
|
|
return os.path.relpath(self.path, EXAMPLES_ROOT)
|
|
|
|
|
|
API_EXAMPLES = [
|
|
Example(["api", "00_inference_with_tensorrt"], artifact_names=["identity.engine"]),
|
|
Example(
|
|
["api", "01_comparing_frameworks"], artifact_names=["inference_results.json"]
|
|
),
|
|
Example(["api", "02_validating_on_a_dataset"]),
|
|
Example(["api", "03_interoperating_with_tensorrt"]),
|
|
Example(
|
|
["api", "04_int8_calibration_in_tensorrt"],
|
|
artifact_names=["identity-calib.cache"],
|
|
),
|
|
Example(["api", "05_using_tensorrt_network_api"]),
|
|
Example(["api", "06_immediate_eval_api"], artifact_names=["identity.engine"]),
|
|
Example(
|
|
["api", "07_tensorrt_and_dynamic_shapes"],
|
|
artifact_names=["dynamic_identity.engine"],
|
|
),
|
|
Example(
|
|
["api", "08_working_with_run_results_and_saved_inputs_manually"],
|
|
artifact_names=["inputs.json", "outputs.json"],
|
|
),
|
|
Example(["api", "09_working_with_pytorch_tensors"]),
|
|
]
|
|
|
|
|
|
@pytest.mark.parametrize("example", API_EXAMPLES, ids=lambda case: str(case))
|
|
@pytest.mark.script_launch_mode("subprocess")
|
|
def test_api_examples(example, sandboxed_install_run):
|
|
if "07_tensorrt_and_dynamic_shapes" in example.path and (
|
|
mod.version(trt.__version__) >= mod.version("8.6")
|
|
and mod.version(trt.__version__) < mod.version("8.7")
|
|
):
|
|
pytest.skip("Broken on 8.6")
|
|
|
|
with example as commands:
|
|
for command in commands:
|
|
example.run(command, sandboxed_install_run)
|
|
|
|
|
|
CLI_EXAMPLES = [
|
|
# Run
|
|
Example(["cli", "run", "01_comparing_frameworks"]),
|
|
Example(
|
|
["cli", "run", "02_comparing_across_runs"],
|
|
artifact_names=["inputs.json", "run_0_outputs.json", "identity.engine"],
|
|
),
|
|
Example(
|
|
["cli", "run", "03_generating_a_comparison_script"],
|
|
artifact_names=["compare_trt_onnxrt.py"],
|
|
),
|
|
Example(
|
|
["cli", "run", "04_defining_a_tensorrt_network_or_config_manually"],
|
|
artifact_names=["my_define_network.py", "my_create_config.py"],
|
|
),
|
|
Example(
|
|
["cli", "run", "05_comparing_with_custom_input_data"],
|
|
artifact_names=["custom_inputs.json"],
|
|
),
|
|
Example(
|
|
["cli", "run", "06_comparing_with_custom_output_data"],
|
|
artifact_names=["custom_inputs.json", "custom_outputs.json"],
|
|
),
|
|
Example(["cli", "run", "07_checking_nan_inf"]),
|
|
pytest.param(
|
|
Example(
|
|
["cli", "run", "08_adding_precision_constraints"],
|
|
artifact_names=["inputs.json", "golden_outputs.json"],
|
|
),
|
|
marks=[pytest.mark.slow],
|
|
),
|
|
# Convert
|
|
Example(
|
|
["cli", "convert", "01_int8_calibration_in_tensorrt"],
|
|
artifact_names=["identity.engine", "identity_calib.cache"],
|
|
),
|
|
pytest.param(
|
|
Example(
|
|
["cli", "convert", "02_deterministic_engine_builds_in_tensorrt"],
|
|
artifact_names=[
|
|
"0.engine",
|
|
"1.engine",
|
|
"timing.cache",
|
|
"timing.cache.lock",
|
|
],
|
|
),
|
|
marks=[pytest.mark.serial, pytest.mark.flaky(max_runs=2)],
|
|
),
|
|
Example(
|
|
["cli", "convert", "03_dynamic_shapes_in_tensorrt"],
|
|
artifact_names=["dynamic_identity.engine"],
|
|
),
|
|
Example(
|
|
["cli", "convert", "04_converting_models_to_fp16"],
|
|
artifact_names=["identity_fp16.onnx", "inputs.json", "outputs_fp32.json"],
|
|
),
|
|
# Surgeon
|
|
Example(
|
|
["cli", "surgeon", "01_isolating_subgraphs"], artifact_names=["subgraph.onnx"]
|
|
),
|
|
Example(["cli", "surgeon", "02_folding_constants"], artifact_names=["folded.onnx"]),
|
|
Example(
|
|
["cli", "surgeon", "03_modifying_input_shapes"],
|
|
artifact_names=["dynamic_identity.onnx"],
|
|
),
|
|
Example(
|
|
["cli", "surgeon", "04_setting_upper_bounds"],
|
|
artifact_names=["modified.onnx", "folded.onnx"],
|
|
),
|
|
# Debug
|
|
Example(
|
|
["cli", "debug", "01_debugging_flaky_trt_tactics"],
|
|
artifact_names=["replays", "golden.json"],
|
|
),
|
|
pytest.param(
|
|
Example(
|
|
["cli", "debug", "02_reducing_failing_onnx_models"],
|
|
artifact_names=[
|
|
"polygraphy_debug_replay.json",
|
|
"polygraphy_debug_replay_skip_current.json",
|
|
"folded.onnx",
|
|
"inputs.json",
|
|
"layerwise_golden.json",
|
|
"layerwise_inputs.json",
|
|
"initial_reduced.onnx",
|
|
"final_reduced.onnx",
|
|
],
|
|
),
|
|
marks=[pytest.mark.slow],
|
|
),
|
|
# Plugin
|
|
Example(
|
|
["cli", "plugin", "01_match_and_replace_plugin"],
|
|
artifact_names=["config.yaml", "replaced.onnx"]
|
|
),
|
|
]
|
|
|
|
|
|
@pytest.mark.parametrize("example", CLI_EXAMPLES, ids=lambda case: str(case))
|
|
@pytest.mark.script_launch_mode("subprocess")
|
|
def test_cli_examples(example, sandboxed_install_run):
|
|
if "02_deterministic_engine_builds_in_tensorrt" in example.path and mod.version(
|
|
trt.__version__
|
|
) < mod.version("8.7"):
|
|
pytest.skip("Only supported on TensorRT 8.7 and newer")
|
|
|
|
if "03_dynamic_shapes_in_tensorrt" in example.path and (
|
|
mod.version(trt.__version__) >= mod.version("8.6")
|
|
and mod.version(trt.__version__) < mod.version("8.7")
|
|
):
|
|
pytest.skip("Broken on TensorRT 8.6")
|
|
|
|
with example as command_blocks:
|
|
for cmd_block in command_blocks:
|
|
example.run(cmd_block, sandboxed_install_run)
|
|
|
|
|
|
CLI_INSPECT_CHECK_EXAMPLES = [
|
|
Example(["cli", "inspect", "01_inspecting_a_tensorrt_network"]),
|
|
Example(
|
|
["cli", "inspect", "02_inspecting_a_tensorrt_engine"],
|
|
artifact_names=["dynamic_identity.engine"],
|
|
),
|
|
Example(["cli", "inspect", "03_inspecting_an_onnx_model"]),
|
|
Example(
|
|
["cli", "inspect", "05_inspecting_inference_outputs"],
|
|
artifact_names=["outputs.json"],
|
|
),
|
|
Example(
|
|
["cli", "inspect", "06_inspecting_input_data"], artifact_names=["inputs.json"]
|
|
),
|
|
Example(
|
|
["cli", "inspect", "08_inspecting_tensorrt_onnx_support"],
|
|
artifact_names=[
|
|
"polygraphy_capability_dumps/supported_subgraph-nodes-0-1.onnx",
|
|
"polygraphy_capability_dumps/unsupported_subgraph-nodes-2-2.onnx",
|
|
"polygraphy_capability_dumps/supported_subgraph-nodes-3-3.onnx",
|
|
"polygraphy_capability_dumps/results.txt",
|
|
# Remove directory when done
|
|
"polygraphy_capability_dumps",
|
|
],
|
|
),
|
|
Example(
|
|
["cli", "inspect", "07_inspecting_tactic_replays"],
|
|
artifact_names=["replay.json"],
|
|
),
|
|
Example(
|
|
["cli", "check", "01_linting_an_onnx_model"], artifact_names=["report.json"]
|
|
),
|
|
Example(
|
|
["cli", "inspect", "09_inspecting_tensorrt_static_onnx_support"],
|
|
artifact_names=[
|
|
"polygraphy_capability_dumps/results.txt",
|
|
# Remove directory when done
|
|
"polygraphy_capability_dumps",
|
|
],
|
|
),
|
|
]
|
|
|
|
if mod.has_mod("tensorflow"):
|
|
CLI_INSPECT_CHECK_EXAMPLES.append(
|
|
Example(["cli", "inspect", "04_inspecting_a_tensorflow_graph"])
|
|
)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"example", CLI_INSPECT_CHECK_EXAMPLES, ids=lambda case: str(case)
|
|
)
|
|
@pytest.mark.script_launch_mode("subprocess")
|
|
def test_cli_inspect_check_examples(example, sandboxed_install_run):
|
|
if mod.version(trt.__version__) < mod.version("10.0") and (
|
|
"09_inspecting_tensorrt_static_onnx_support" in example.path
|
|
):
|
|
pytest.skip("Parser features not supported in TRT <10.0.")
|
|
|
|
# Last block should be the expected output, and last command should generate it.
|
|
with example as blocks:
|
|
command_blocks, expected_output = blocks[:-1], str(blocks[-1])
|
|
for cmd_block in command_blocks:
|
|
actual_output = example.run(cmd_block, sandboxed_install_run).stdout
|
|
|
|
if mod.version(trt.__version__) >= mod.version("9.0") and (
|
|
"01_inspecting_a_tensorrt_network" in example.path
|
|
or "02_inspecting_a_tensorrt_engine" in example.path
|
|
):
|
|
pytest.skip(
|
|
"Output is different for TRT >=9, this test needs to be updated to account for that. "
|
|
)
|
|
|
|
if mod.version(trt.__version__) < mod.version("10.0") and (
|
|
"08_inspecting_tensorrt_onnx_support" in example.path
|
|
):
|
|
pytest.skip("Output is different for TRT <10.0.")
|
|
|
|
print(actual_output)
|
|
|
|
actual_lines = actual_output.splitlines()
|
|
# The output for lint is expected to have errors and warnings, so we can't filter them out.
|
|
# The rest of the examples can be pruned of unnecessary lines.
|
|
if "01_linting_an_onnx_model" not in example.path:
|
|
include_line = (
|
|
lambda line: "[I] Loading" not in line
|
|
and "[I] Saving" not in line
|
|
and "[W]" not in line
|
|
and "[E]" not in line
|
|
)
|
|
actual_lines = [line for line in actual_lines if include_line(line)]
|
|
|
|
expected_lines = expected_output.splitlines()
|
|
assert len(actual_lines) == len(expected_lines)
|
|
|
|
# Indicates lines that may not match exactly
|
|
NON_EXACT_LINE_MARKERS = [
|
|
"---- ",
|
|
"Layer",
|
|
" Algorithm:",
|
|
"RUNNING",
|
|
"FAILED",
|
|
"[I] Loading ",
|
|
]
|
|
|
|
for index, (actual_line, expected_line) in enumerate(
|
|
zip(actual_lines, expected_lines)
|
|
):
|
|
# Skip whitespace, and lines that include runner names (since those have timestamps)
|
|
if expected_line.strip() and all(
|
|
[marker not in expected_line for marker in NON_EXACT_LINE_MARKERS]
|
|
):
|
|
print(f"Checking line {index}: {expected_line}")
|
|
assert actual_line.strip() == expected_line.strip()
|
|
|
|
|
|
DEV_EXAMPLES = [
|
|
Example(["dev", "01_writing_cli_tools"], artifact_names=["data.json"]),
|
|
Example(
|
|
["dev", "02_extending_polygraphy_run"],
|
|
artifact_names=[
|
|
os.path.join("extension_module", "build"),
|
|
os.path.join("extension_module", "dist"),
|
|
os.path.join("extension_module", "polygraphy_reshape_destroyer.egg-info"),
|
|
],
|
|
),
|
|
]
|
|
|
|
|
|
@pytest.mark.parametrize("example", DEV_EXAMPLES, ids=lambda case: str(case))
|
|
@pytest.mark.script_launch_mode("subprocess")
|
|
def test_dev_examples(example, sandboxed_install_run):
|
|
with example as command_blocks:
|
|
for cmd_block in command_blocks:
|
|
example.run(cmd_block, sandboxed_install_run)
|