CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
299 lines
9.3 KiB
Python
299 lines
9.3 KiB
Python
# Copyright (c) Advanced Micro Devices, Inc.
|
|
# SPDX-License-Identifier: MIT
|
|
|
|
"""ROCTX marker coverage test for ``inject_roctx``.
|
|
|
|
Samples a random subset of ATen operators plus structural entry points,
|
|
runs ``torch.profiler`` as ground truth, runs ``rocprof-compute
|
|
--torch-trace``, and compares marker output per operator. Sampling is
|
|
controlled by ``--coverage-seed`` and ``--coverage-n``. Requires GPU.
|
|
"""
|
|
|
|
import json
|
|
import os
|
|
import random
|
|
import sys
|
|
from pathlib import Path
|
|
from typing import Any, Dict, List, Tuple
|
|
|
|
import common
|
|
import pytest
|
|
from conftest import require_torch
|
|
|
|
# Allow collection on CPU-only hosts.
|
|
try:
|
|
import torch # noqa: E402
|
|
except Exception:
|
|
torch = None
|
|
|
|
COVERAGE_TEST_CONFIG: Dict[str, Any] = {"cleanup": True}
|
|
|
|
# Set default profiler
|
|
os.environ["ROCPROF"] = "rocprofiler-sdk"
|
|
|
|
|
|
@pytest.fixture
|
|
def torch_trace_coverage_sampling(request):
|
|
"""Return ``(seed, sample_budget)`` for the coverage test."""
|
|
seed = request.config.getoption("--coverage-seed")
|
|
n = request.config.getoption("--coverage-n")
|
|
if n < 0:
|
|
pytest.fail("--coverage-n must be non-negative")
|
|
return seed, n
|
|
|
|
|
|
@pytest.mark.torch_trace
|
|
def test_random_operator_kernel_coverage(
|
|
request,
|
|
binary_handler_profile_rocprof_compute,
|
|
torch_trace_coverage_sampling,
|
|
):
|
|
"""Verify ``--torch-trace`` ROCTX output matches ``torch.profiler`` per operator.
|
|
|
|
Per-operator mismatches fail. Per-operator skips are allowed, but at
|
|
least one operator must pass overall.
|
|
"""
|
|
require_torch(gpu=True)
|
|
from collections import Counter, defaultdict
|
|
|
|
from torch_trace_coverage_utils import (
|
|
C_TIER_BACKWARD_SENTINELS,
|
|
categorize_skip_reason,
|
|
compare_single_op,
|
|
detect_cpp_tier_signature,
|
|
discover_operators,
|
|
format_cpp_tier_signature_report,
|
|
format_missing_arg_builder_report,
|
|
format_skip_breakdown_lines,
|
|
multiline_coverage_failure_warning,
|
|
parse_roctx_markers,
|
|
print_torch_trace_coverage_session_header,
|
|
run_ground_truth_torch_profiler_subprocess,
|
|
unique_get_output_param_id,
|
|
write_coverage_workload_artifacts,
|
|
)
|
|
|
|
seed, sample_budget = torch_trace_coverage_sampling
|
|
rng = random.Random(seed)
|
|
|
|
match_verbose = os.getenv("ROCPROF_OPERATOR_MATCH_VERBOSE", "").strip().lower() in {
|
|
"1",
|
|
"true",
|
|
"yes",
|
|
"on",
|
|
}
|
|
|
|
aten_ops, structural_ops, excluded_aten_ops = discover_operators()
|
|
|
|
# The budget caps the ATen sample only; structural entries are always included.
|
|
n_aten = min(
|
|
max(0, sample_budget - len(structural_ops)),
|
|
len(aten_ops),
|
|
)
|
|
sampled = rng.sample(aten_ops, n_aten) + structural_ops
|
|
|
|
print_torch_trace_coverage_session_header(
|
|
seed,
|
|
sample_budget,
|
|
len(sampled),
|
|
len(aten_ops),
|
|
len(structural_ops),
|
|
len(excluded_aten_ops),
|
|
)
|
|
|
|
gt_work_dir = common.get_output_dir(
|
|
param_id=unique_get_output_param_id("torch_trace_gt"),
|
|
suffix="_tmp",
|
|
clean_existing=True,
|
|
)
|
|
workload_dir = common.get_output_dir(
|
|
param_id=unique_get_output_param_id("random_op_coverage"),
|
|
clean_existing=True,
|
|
)
|
|
Path(gt_work_dir).mkdir(parents=True, exist_ok=True)
|
|
Path(workload_dir).mkdir(parents=True, exist_ok=True)
|
|
|
|
ground_truth_path = str(Path(gt_work_dir) / "ground_truth.json")
|
|
workload_script_path = str(Path(gt_work_dir) / "coverage_workload.py")
|
|
ground_truth_runner_script_path = str(
|
|
Path(gt_work_dir) / "coverage_ground_truth_runner.py"
|
|
)
|
|
|
|
try:
|
|
write_coverage_workload_artifacts(
|
|
sampled,
|
|
workload_script_path,
|
|
ground_truth_runner_script_path,
|
|
)
|
|
|
|
# Ground-truth run via torch.profiler.
|
|
run_ground_truth_torch_profiler_subprocess(
|
|
ground_truth_runner_script_path,
|
|
workload_script_path,
|
|
ground_truth_path,
|
|
coverage_seed=seed,
|
|
coverage_sample_budget=sample_budget,
|
|
)
|
|
with open(ground_truth_path) as f:
|
|
ground_truth = json.load(f)
|
|
|
|
# rocprof-compute --torch-trace run on the same workload.
|
|
binary_handler_profile_rocprof_compute(
|
|
{
|
|
**COVERAGE_TEST_CONFIG,
|
|
"coverage_workload": [
|
|
sys.executable,
|
|
workload_script_path,
|
|
],
|
|
},
|
|
workload_dir,
|
|
["--experimental", "--torch-trace", "--iteration-multiplexing"],
|
|
check_success=False,
|
|
app_name="coverage_workload",
|
|
)
|
|
|
|
roctx_kernels_map, roctx_marker_names = parse_roctx_markers(workload_dir)
|
|
|
|
# Sentinel check: the only proof C++ tier ran (PASS works on either).
|
|
cpp_tier_active, cpp_tier_sentinel_counts = detect_cpp_tier_signature(
|
|
workload_dir,
|
|
)
|
|
cpp_tier_backward_active = any(
|
|
cpp_tier_sentinel_counts.get(name, 0) > 0
|
|
for name in C_TIER_BACKWARD_SENTINELS
|
|
)
|
|
|
|
failure_detail: List[Tuple[str, str]] = []
|
|
skip_categories: "Counter[str]" = Counter()
|
|
skip_op_names: Dict[str, List[str]] = defaultdict(list)
|
|
passed = skipped = 0
|
|
for op in sampled:
|
|
outcome = compare_single_op(
|
|
op,
|
|
ground_truth,
|
|
roctx_marker_names,
|
|
roctx_kernels_map,
|
|
match_verbose=match_verbose,
|
|
)
|
|
for line in outcome.log_lines:
|
|
print(line)
|
|
if outcome.status == "pass":
|
|
passed += 1
|
|
elif outcome.status == "fail":
|
|
failure_detail.append((op.name, outcome.reason))
|
|
else:
|
|
skipped += 1
|
|
category = categorize_skip_reason(outcome.reason)
|
|
skip_categories[category] += 1
|
|
skip_op_names[category].append(op.name)
|
|
|
|
print(
|
|
f"\n Summary: {len(sampled)} ops — "
|
|
f"{passed} PASS, {len(failure_detail)} FAIL, {skipped} SKIP"
|
|
)
|
|
breakdown_lines = format_skip_breakdown_lines(
|
|
dict(skip_categories),
|
|
skip_op_names=dict(skip_op_names),
|
|
)
|
|
for line in breakdown_lines:
|
|
print(line)
|
|
print()
|
|
|
|
arg_gap_ops = skip_op_names.get("argument_builder_gap") or []
|
|
if arg_gap_ops:
|
|
for line in format_missing_arg_builder_report(arg_gap_ops):
|
|
print(line)
|
|
print()
|
|
|
|
for line in format_cpp_tier_signature_report(
|
|
cpp_tier_active,
|
|
cpp_tier_sentinel_counts,
|
|
):
|
|
print(line)
|
|
print()
|
|
|
|
if not cpp_tier_active:
|
|
pytest.fail(
|
|
"torch_trace coverage ran without the C++ tier in the "
|
|
"workload subprocess."
|
|
)
|
|
|
|
if failure_detail:
|
|
for line in multiline_coverage_failure_warning(
|
|
failure_detail,
|
|
max_ops=48,
|
|
seed=seed,
|
|
sample_budget=sample_budget,
|
|
).splitlines():
|
|
print(line)
|
|
pytest.fail(
|
|
f"{len(failure_detail)} sampled op(s) failed ROCTX "
|
|
f"coverage (seed={seed}, budget={sample_budget}). "
|
|
f"First: {failure_detail[:5]!r}. "
|
|
f"Summary: {passed} PASS / {len(failure_detail)} FAIL "
|
|
f"/ {skipped} SKIP. Re-run with pytest -s for per-op lines."
|
|
)
|
|
assert passed > 0, (
|
|
f"no operators PASSed ROCTX/kernel coverage "
|
|
f"(sampled={len(sampled)}, FAIL={len(failure_detail)}, SKIP={skipped})"
|
|
)
|
|
assert cpp_tier_backward_active, (
|
|
"no autograd.engine:0 / autograd.bwd:0 sentinels; "
|
|
f"sentinel counts: {cpp_tier_sentinel_counts}"
|
|
)
|
|
finally:
|
|
common.clean_output_dir(
|
|
COVERAGE_TEST_CONFIG["cleanup"],
|
|
workload_dir,
|
|
)
|
|
common.clean_output_dir(
|
|
COVERAGE_TEST_CONFIG["cleanup"],
|
|
gt_work_dir,
|
|
)
|
|
|
|
|
|
@pytest.mark.torch_trace
|
|
def test_function_apply_wrappers_idempotent(monkeypatch):
|
|
"""A grandchild ``Function`` subclass does not get a second ``apply`` wrapper."""
|
|
require_torch()
|
|
|
|
from utils.inject_roctx._backends import torch as _torch_backend
|
|
|
|
if not _torch_backend._resolve_torch():
|
|
pytest.skip("torch could not be resolved for inject_roctx backend")
|
|
|
|
push_counter = {"count": 0}
|
|
|
|
def _count_push(*_args, **_kwargs):
|
|
push_counter["count"] += 1
|
|
|
|
monkeypatch.setattr(_torch_backend, "_push_scope", _count_push)
|
|
monkeypatch.setattr(_torch_backend, "_pop_scope", lambda: None)
|
|
|
|
class Foo(torch.autograd.Function):
|
|
@staticmethod
|
|
def forward(ctx, x):
|
|
return x + 1
|
|
|
|
@staticmethod
|
|
def backward(ctx, grad_out):
|
|
return grad_out
|
|
|
|
class Bar(Foo):
|
|
pass
|
|
|
|
assert _torch_backend.install_function_apply_wrappers() is True
|
|
|
|
assert getattr(
|
|
getattr(Foo.__dict__.get("apply"), "__func__", None),
|
|
"_roctx_wrapped",
|
|
False,
|
|
)
|
|
assert "apply" not in Bar.__dict__
|
|
|
|
x = torch.tensor(1.0, requires_grad=True)
|
|
y = Bar.apply(x)
|
|
y.backward()
|
|
|
|
assert push_counter["count"] == 1, "Bar.apply triggered more than one wrapper push"
|