Files
TheRock/rocm-systems/projects/rocprofiler-compute/tests/torch_trace_coverage_utils.py
T
derek.holloway 7502bf64a4
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
Flatten subprojects into a single base monorepo
2026-09-02 16:34:47 -07:00

4164 lines
136 KiB
Python

# Copyright (c) Advanced Micro Devices, Inc.
# SPDX-License-Identifier: MIT
"""Torch-dependent helpers and ``OP_SPECS`` for the coverage test.
``OP_SPECS`` maps each ATen operator to an :class:`OpSpec` that either
builds arguments or records a skip reason.
"""
import os
import shutil
import signal
import subprocess
import sys
import textwrap
import threading
import uuid
import warnings
from dataclasses import dataclass
from pathlib import Path
from typing import Any, Callable, Dict, List, NamedTuple, Optional, Set, Tuple
import pandas as pd
import pytest
import torch
class CoverageTensorArg(NamedTuple):
"""Tensor slot with a specific factory for structurally-constrained inputs."""
shape: Tuple[int, ...]
emit: str
scale: float = 4.0
ArgBuilder = Callable[[str], Tuple[List[Any], Dict[str, Any]]]
@dataclass(frozen=True)
class OpSpec:
"""One operator: a builder that produces ``(args, kwargs)`` or a skip reason."""
build: Optional[ArgBuilder] = None
skip: Optional[str] = None
# Compact tensor factories used by builders.
def f(device: str, *shape: int, dtype: torch.dtype = torch.float32) -> torch.Tensor:
return torch.randn(shape, device=device, dtype=dtype)
def i(device: str, *shape: int, low: int = 0, high: int = 8) -> torch.Tensor:
return torch.randint(low, high, shape, device=device, dtype=torch.int64)
def i1(device: str, n: int = 8) -> torch.Tensor:
return torch.randint(0, 4, (n,), device=device, dtype=torch.int64)
def b(device: str, shape: Tuple[int, ...] = (4, 4)) -> torch.Tensor:
return torch.ones(shape, device=device, dtype=torch.bool)
def r01(device: str, *shape: int) -> torch.Tensor:
return torch.rand(shape, device=device)
# Symmetric positive-definite, for Cholesky-family ops.
def spd(device: str, n: int = 4) -> torch.Tensor:
a = torch.randn(n, n, device=device)
return a @ a.mT + torch.eye(n, device=device) * 0.1
# Multi-line builder helpers.
def linalg_householder_product_args(device: str) -> Tuple[List[Any], Dict[str, Any]]:
"""Build ``(input, tau)`` for ``linalg_householder_product``."""
a = torch.randn(6, 4, device=device)
for geqrf in (getattr(torch.linalg, "geqrf", None), getattr(torch, "geqrf", None)):
if geqrf is None:
continue
try:
qr_tau = geqrf(a)
return [qr_tau[0], qr_tau[1]], {}
except Exception:
pass
try:
qr_tau = geqrf(torch.randn(6, 4))
except Exception:
continue
return [qr_tau[0].to(device), qr_tau[1].to(device)], {}
inp = torch.randn(6, 4, device=device)
tau = torch.randn(4, device=device)
return [inp, tau], {}
def lu_unpack_args(device: str) -> Tuple[List[Any], Dict[str, Any]]:
# Schema: (LU_data, LU_pivots, unpack_data, unpack_pivots).
return [
torch.randn(4, 4, device=device),
CoverageTensorArg(shape=(4,), emit="pivots_1based"),
True,
True,
], {}
def lu_solve_args(device: str) -> Tuple[List[Any], Dict[str, Any]]:
# Schema: (LU, pivots, B, *, left, adjoint).
return [
torch.randn(4, 4, device=device),
CoverageTensorArg(shape=(4,), emit="pivots_1based"),
torch.randn(4, 2, device=device),
], {}
def ldl_solve_args(device: str) -> Tuple[List[Any], Dict[str, Any]]:
ld, pivots, _ = torch.linalg.ldl_factor_ex(spd(device))
return [ld, pivots.to(dtype=torch.int64), torch.randn(4, 2, device=device)], {}
def solve_triangular_args(device: str) -> Tuple[List[Any], Dict[str, Any]]:
# Schema: linalg_solve_triangular(A, B, *, upper, left, unitriangular).
a = torch.randn(4, 4, device=device).tril()
b = torch.randn(4, 2, device=device)
return [a, b], {"upper": False, "left": True, "unitriangular": False}
def ormqr_args(device: str) -> Tuple[List[Any], Dict[str, Any]]:
# Schema: (self[m,k], tau[k], other[m,n], left, transpose).
return (
[
torch.randn(4, 4, device=device),
torch.randn(4, device=device),
torch.randn(4, 2, device=device),
True,
],
{},
)
def convolution_backward_args(device: str) -> Tuple[List[Any], Dict[str, Any]]:
# Schema (11 positionals):
# grad_output, input, weight, bias_sizes?, stride, padding, dilation,
# transposed, output_padding, groups, output_mask[3]
return (
[
torch.randn(1, 1, 6, 6, device=device), # grad_output (N,C_out,H,W)
torch.randn(1, 1, 8, 8, device=device), # input
torch.randn(1, 1, 3, 3, device=device), # weight
None, # bias_sizes (no bias)
[1, 1], # stride
[0, 0], # padding
[1, 1], # dilation
False, # transposed
[0, 0], # output_padding
1, # groups
[True, True, False], # output_mask
],
{},
)
def native_batch_norm_backward_args(
device: str,
) -> Tuple[List[Any], Dict[str, Any]]:
# Schema: (grad_out, input, weight?, running_mean?, running_var?,
# save_mean?, save_invstd?, train, eps, output_mask[3]).
return (
[
torch.randn(2, 3, 4, 4, device=device), # grad_out
torch.randn(2, 3, 4, 4, device=device), # input
torch.ones(3, device=device), # weight
torch.zeros(3, device=device), # running_mean
torch.ones(3, device=device), # running_var
torch.zeros(3, device=device), # save_mean
torch.ones(3, device=device), # save_invstd
True, # train
1e-5, # eps
[True, True, True], # output_mask
],
{},
)
def batch_norm_backward_args(device: str) -> Tuple[List[Any], Dict[str, Any]]:
args, kwargs = native_batch_norm_backward_args(device)
args.append(torch.empty(0, device=device))
return args, kwargs
def native_group_norm_backward_args(device: str) -> Tuple[List[Any], Dict[str, Any]]:
# Schema: (grad_out, input, mean, rstd, weight?, N, C, HxW, group,
# output_mask[3]). mean/rstd shape is (N, groups).
return (
[
torch.randn(2, 6, 4, 4, device=device), # grad_out
torch.randn(2, 6, 4, 4, device=device), # input
torch.zeros(2, 2, device=device), # mean (N, groups)
torch.ones(2, 2, device=device), # rstd (N, groups)
torch.ones(6, device=device), # weight
2, # N
6, # C
16, # HxW
2, # group
[True, True, True], # output_mask
],
{},
)
def fused_adam_args(device: str) -> Tuple[List[Any], Dict[str, Any]]:
"""Build positional list-of-tensor arguments for ``_fused_adam_``."""
params = [torch.randn(4, device=device)]
grads = [torch.randn(4, device=device)]
exp_avgs = [torch.zeros(4, device=device)]
exp_avg_sqs = [torch.zeros(4, device=device)]
max_exp_avg_sqs: List[torch.Tensor] = []
state_steps = [torch.tensor(1.0, device=device)]
return (
[params, grads, exp_avgs, exp_avg_sqs, max_exp_avg_sqs, state_steps],
{
"lr": 1e-3,
"beta1": 0.9,
"beta2": 0.999,
"weight_decay": 0.0,
"eps": 1e-8,
"amsgrad": False,
"maximize": False,
},
)
def native_layer_norm_backward_args(device: str) -> Tuple[List[Any], Dict[str, Any]]:
# Schema: (grad_out, input, normalized_shape, mean, rstd, weight?, bias?,
# output_mask[3]). mean/rstd broadcast across non-normalized dims.
return (
[
torch.randn(2, 4, 4, device=device), # grad_out
torch.randn(2, 4, 4, device=device), # input
[4], # normalized_shape
torch.randn(2, 4, 1, device=device), # mean
torch.randn(2, 4, 1, device=device), # rstd
torch.ones(4, device=device), # weight
torch.zeros(4, device=device), # bias
[True, True, True], # output_mask
],
{},
)
# Operator table keyed by ATen short name. Each entry is an OpSpec
# carrying either a builder or a skip reason.
OP_SPECS: Dict[str, OpSpec] = {
"_sparse_semi_structured_mm": OpSpec(skip="CUTLASS 2:4 sparse; NVIDIA-only."),
"_fft_r2c": OpSpec(skip="SIGSEGV under torch.profiler on ROCm."),
"_fft_c2c": OpSpec(skip="SIGSEGV under torch.profiler on ROCm."),
"_fft_c2r": OpSpec(skip="SIGSEGV under torch.profiler on ROCm."),
"_cudnn_rnn": OpSpec(skip="cuDNN-only; ROCm uses miopen_rnn instead."),
"_cdist_backward": OpSpec(
skip="Requires cdist output tied to x1/x2; serialized workload cannot "
"preserve that correlation.",
),
"igamma": OpSpec(skip="ROCm kernel: HIP 719 illegal memory access."),
"igammac": OpSpec(skip="ROCm kernel: HIP 719 illegal memory access."),
"cudnn_batch_norm_backward": OpSpec(
skip="Needs save_mean / save_var / reserveSpace from a matching forward.",
),
"cudnn_batch_norm": OpSpec(skip="cuDNN-only; ROCm uses miopen_batch_norm."),
"_weight_int4pack_mm": OpSpec(
skip="Requires packed int4 weights + matching qScaleAndZeros layout.",
),
"_weight_int8pack_mm": OpSpec(
skip="Requires int8 packed weights + matching scales on supported builds.",
),
"_native_multi_head_attention": OpSpec(
skip="Requires specific QKV/mask layout + matching projection weights.",
),
# ``observer_on``, ``fake_quant_on``, ``zero_point`` must be int32.
"_fused_moving_avg_obs_fq_helper": OpSpec(
build=lambda d: (
[
torch.randn(4, 4, device=d),
torch.ones(1, device=d, dtype=torch.int32),
torch.ones(1, device=d, dtype=torch.int32),
torch.zeros(1, device=d),
torch.zeros(1, device=d),
torch.ones(1, device=d),
torch.zeros(1, device=d, dtype=torch.int32),
0.01,
0,
255,
-1,
False,
False,
],
{},
),
),
"_scaled_grouped_mm": OpSpec(
skip="Requires mat2 to be transposed with scale tensors; backend-specific.",
),
"_jagged_to_padded_dense_forward": OpSpec(skip="shape contract TBD."),
"_padded_dense_to_jagged_forward": OpSpec(skip="shape contract TBD."),
# ---------------------------------------------------------------
# matmul / batched matmul / conv
# ---------------------------------------------------------------
"bmm": OpSpec(build=lambda d: ([f(d, 2, 4, 4), f(d, 2, 4, 4)], {})),
"conv1d": OpSpec(build=lambda d: ([f(d, 1, 3, 16), f(d, 6, 3, 3)], {})),
"conv2d": OpSpec(build=lambda d: ([f(d, 1, 3, 8, 8), f(d, 6, 3, 3, 3)], {})),
"conv3d": OpSpec(
build=lambda d: ([f(d, 1, 3, 4, 4, 4), f(d, 6, 3, 3, 3, 3)], {}),
),
"conv_transpose1d": OpSpec(
build=lambda d: ([f(d, 1, 3, 16), f(d, 3, 6, 3)], {}),
),
"conv_transpose2d": OpSpec(
build=lambda d: ([f(d, 1, 3, 8, 8), f(d, 3, 6, 3, 3)], {}),
),
"conv_transpose3d": OpSpec(
build=lambda d: ([f(d, 1, 3, 4, 4, 4), f(d, 3, 6, 3, 3, 3)], {}),
),
"addmm": OpSpec(build=lambda d: ([f(d, 4), f(d, 4, 4), f(d, 4, 4)], {})),
"addbmm": OpSpec(
build=lambda d: ([f(d, 4, 4), f(d, 2, 4, 4), f(d, 2, 4, 4)], {}),
),
"addbmm_": OpSpec(
build=lambda d: ([f(d, 4, 4), f(d, 2, 4, 4), f(d, 2, 4, 4)], {}),
),
"baddbmm": OpSpec(
build=lambda d: ([f(d, 2, 4, 4), f(d, 2, 4, 4), f(d, 2, 4, 4)], {}),
),
"baddbmm_": OpSpec(
build=lambda d: ([f(d, 2, 4, 4), f(d, 2, 4, 4), f(d, 2, 4, 4)], {}),
),
"addmv": OpSpec(build=lambda d: ([f(d, 4), f(d, 4, 4), f(d, 4)], {})),
"addmv_": OpSpec(build=lambda d: ([f(d, 4), f(d, 4, 4), f(d, 4)], {})),
"addr": OpSpec(build=lambda d: ([f(d, 4, 4), f(d, 4), f(d, 4)], {})),
"dot": OpSpec(build=lambda d: ([f(d, 4), f(d, 4)], {})),
"vdot": OpSpec(build=lambda d: ([f(d, 4), f(d, 4)], {})),
# ---------------------------------------------------------------
# embedding / embedding_bag
# ---------------------------------------------------------------
"embedding": OpSpec(
build=lambda d: ([f(d, 10, 8), torch.randint(0, 10, (4,), device=d)], {}),
),
"embedding_dense_backward": OpSpec(
build=lambda d: (
[f(d, 8, 16), torch.randint(0, 32, (8,), device=d), 32, -1, False],
{},
),
),
# Schema: (weight, indices, offsets, scale_grad_by_freq, mode, sparse,
# per_sample_weights, include_last_offset, padding_idx).
"_embedding_bag": OpSpec(
build=lambda d: (
[
f(d, 10, 8),
i1(d, 6),
torch.tensor([0, 3], device=d, dtype=torch.int64),
],
{},
),
),
"_embedding_bag_forward_only": OpSpec(
build=lambda d: (
[
f(d, 10, 8),
i1(d, 6),
torch.tensor([0, 3], device=d, dtype=torch.int64),
False,
0,
False,
],
{},
),
),
"_embedding_bag_backward": OpSpec(
skip="Requires offset2bag/bag_size from matched forward run.",
),
"_embedding_bag_dense_backward": OpSpec(
skip="Requires offset2bag/bag_size from matched forward run.",
),
"_embedding_bag_per_sample_weights_backward": OpSpec(
skip="Requires offset2bag from matched forward run.",
),
# ---------------------------------------------------------------
# Linalg / Cholesky (need structured input)
# ---------------------------------------------------------------
"linalg_householder_product": OpSpec(build=linalg_householder_product_args),
"cholesky": OpSpec(
build=lambda d: ([CoverageTensorArg(shape=(4, 4), emit="spd")], {}),
),
"linalg_cholesky_ex": OpSpec(
build=lambda d: ([CoverageTensorArg(shape=(4, 4), emit="spd")], {}),
),
"cholesky_inverse": OpSpec(
build=lambda d: ([CoverageTensorArg(shape=(4, 4), emit="spd")], {}),
),
# Schema: _cholesky_solve_helper(self, A, bool upper).
"_cholesky_solve_helper": OpSpec(
build=lambda d: ([f(d, 4, 2), spd(d).tril(), False], {}),
),
"linalg_cross": OpSpec(build=lambda d: ([f(d, 2, 3), f(d, 2, 3)], {})),
"linalg_ldl_solve": OpSpec(
skip="Requires PyTorch built against the MAGMA library.",
),
"linalg_lu_solve": OpSpec(build=lu_solve_args),
"linalg_solve_triangular": OpSpec(build=solve_triangular_args),
"lu_unpack": OpSpec(build=lu_unpack_args),
"ormqr": OpSpec(build=ormqr_args),
# ---------------------------------------------------------------
# Loss functions
# ---------------------------------------------------------------
"cross_entropy_loss": OpSpec(
build=lambda d: ([f(d, 4, 10), torch.randint(0, 10, (4,), device=d)], {}),
),
"binary_cross_entropy": OpSpec(
build=lambda _d: (
[CoverageTensorArg((4, 4), "rand"), CoverageTensorArg((4, 4), "rand")],
{},
),
),
"binary_cross_entropy_backward": OpSpec(
build=lambda d: (
[
f(d, 4, 4),
CoverageTensorArg((4, 4), "rand"),
CoverageTensorArg((4, 4), "rand"),
],
{},
),
),
"nll_loss_forward": OpSpec(
build=lambda d: (
[
f(d, 4, 10).log_softmax(1),
torch.randint(0, 10, (4,), device=d),
torch.ones(10, device=d),
0,
-100,
],
{},
),
),
# Schema: nll_loss_backward(grad_output, self, target, weight?, reduction,
# ignore_index, total_weight).
"nll_loss_backward": OpSpec(
build=lambda d: (
[
torch.tensor(1.0, device=d), # grad_output
f(d, 4, 5).log_softmax(1), # self
torch.randint(0, 5, (4,), device=d), # target
None, # weight
1, # reduction (Mean)
-100, # ignore_index
torch.tensor(4.0, device=d), # total_weight
],
{},
),
),
"nll_loss2d_forward": OpSpec(
build=lambda d: (
[
f(d, 2, 5, 4, 4).log_softmax(1),
torch.randint(0, 5, (2, 4, 4), device=d),
torch.ones(5, device=d),
0,
-100,
],
{},
),
),
"nll_loss2d_backward": OpSpec(
build=lambda d: (
[
torch.tensor(1.0, device=d), # grad_output
f(d, 2, 5, 4, 4).log_softmax(1), # self
torch.randint(0, 5, (2, 4, 4), device=d), # target
None, # weight
1, # reduction (Mean)
-100, # ignore_index
torch.tensor(32.0, device=d), # total_weight
],
{},
),
),
# Schema: multi_margin_loss(self, target, p, margin, weight?, reduction).
"multi_margin_loss": OpSpec(
build=lambda d: (
[
f(d, 4, 5),
torch.randint(0, 5, (4,), device=d),
1.0,
1.0,
None,
],
{},
),
),
"multilabel_margin_loss_forward": OpSpec(
build=lambda d: (
[f(d, 4, 5), torch.randint(0, 5, (4, 5), device=d), 0],
{},
),
),
# ---------------------------------------------------------------
# Norm / batch / group / layer
# ---------------------------------------------------------------
"batch_norm": OpSpec(
build=lambda d: (
[
f(d, 2, 3, 4, 4),
f(d, 3),
f(d, 3),
f(d, 3),
f(d, 3),
True,
0.1,
1e-5,
False,
],
{},
),
),
"native_batch_norm": OpSpec(
build=lambda d: (
[
f(d, 2, 3, 4, 4),
f(d, 3),
f(d, 3),
f(d, 3),
f(d, 3),
True,
0.1,
1e-5,
],
{},
),
),
"miopen_batch_norm": OpSpec(
build=lambda d: (
[
f(d, 2, 3, 4, 4),
torch.ones(3, device=d),
torch.zeros(3, device=d),
torch.ones(3, device=d),
torch.zeros(3, device=d),
True,
0.1,
1e-5,
],
{},
),
),
# Schema: (input, grad_output, weight, running_mean?, running_var?,
# save_mean?, save_var?, epsilon).
"miopen_batch_norm_backward": OpSpec(
build=lambda d: (
[
f(d, 2, 3, 4, 4), # input
f(d, 2, 3, 4, 4), # grad_output
torch.ones(3, device=d), # weight
torch.zeros(3, device=d), # running_mean
torch.ones(3, device=d), # running_var
torch.zeros(3, device=d), # save_mean
torch.ones(3, device=d), # save_var
1e-5, # epsilon
],
{},
),
),
"_batch_norm_with_update": OpSpec(
build=lambda d: (
[
f(d, 2, 3, 4, 4),
torch.ones(3, device=d),
torch.zeros(3, device=d),
torch.ones(3, device=d),
torch.zeros(3, device=d),
0.1,
1e-5,
],
{},
),
),
"native_group_norm": OpSpec(
build=lambda d: (
[
f(d, 2, 6, 4, 4),
torch.ones(6, device=d),
torch.zeros(6, device=d),
2,
6,
16,
2,
1e-5,
],
{},
),
),
"native_layer_norm": OpSpec(
build=lambda d: (
[
f(d, 2, 4, 4),
[4],
torch.ones(4, device=d),
torch.zeros(4, device=d),
1e-5,
],
{},
),
),
"_fused_rms_norm": OpSpec(
build=lambda d: ([f(d, 2, 4, 4), [4], torch.ones(4, device=d), 1e-5], {}),
),
# Schema: (grad_out, input, normalized_shape, rstd, weight?, bool[2] output_mask).
"_fused_rms_norm_backward": OpSpec(
build=lambda d: (
[
f(d, 2, 4, 4), # grad_out
f(d, 2, 4, 4), # input
[4], # normalized_shape
torch.ones(2, 4, 1, device=d), # rstd
torch.ones(4, device=d), # weight
[True, True], # output_mask
],
{},
),
),
"batch_norm_backward": OpSpec(build=batch_norm_backward_args),
"native_batch_norm_backward": OpSpec(build=native_batch_norm_backward_args),
"native_group_norm_backward": OpSpec(build=native_group_norm_backward_args),
"native_layer_norm_backward": OpSpec(build=native_layer_norm_backward_args),
# ---------------------------------------------------------------
# Pooling + unpooling (strict shape requirements)
# ---------------------------------------------------------------
"_adaptive_avg_pool2d": OpSpec(
build=lambda d: ([f(d, 1, 3, 8, 8), [4, 4]], {}),
),
"_adaptive_avg_pool2d_backward": OpSpec(
build=lambda d: ([f(d, 1, 3, 4, 4), f(d, 1, 3, 8, 8)], {}),
),
"_adaptive_avg_pool3d": OpSpec(
build=lambda d: ([f(d, 1, 1, 8, 8, 8), [4, 4, 4]], {}),
),
"_adaptive_avg_pool3d_backward": OpSpec(
build=lambda d: ([f(d, 1, 1, 4, 4, 4), f(d, 1, 1, 8, 8, 8)], {}),
),
"adaptive_max_pool2d": OpSpec(build=lambda d: ([f(d, 1, 3, 8, 8), [2, 2]], {})),
"adaptive_max_pool2d_backward": OpSpec(
build=lambda d: (
[f(d, 1, 3, 2, 2), f(d, 1, 3, 8, 8), i(d, 1, 3, 2, 2)],
{},
),
),
"adaptive_max_pool3d": OpSpec(
build=lambda d: ([f(d, 1, 2, 6, 6, 6), [2, 2, 2]], {}),
),
"adaptive_max_pool3d_backward": OpSpec(
build=lambda d: (
[f(d, 1, 2, 2, 2, 2), f(d, 1, 2, 6, 6, 6), i(d, 1, 2, 2, 2, 2)],
{},
),
),
"avg_pool2d": OpSpec(build=lambda d: ([f(d, 1, 1, 8, 8), [2, 2]], {})),
"avg_pool2d_backward": OpSpec(
build=lambda d: (
[
f(d, 1, 1, 4, 4),
f(d, 1, 1, 8, 8),
[2, 2],
[2, 2],
[0, 0],
True,
True,
None,
],
{},
),
),
"avg_pool3d": OpSpec(build=lambda d: ([f(d, 1, 1, 8, 8, 8), [2, 2, 2]], {})),
"avg_pool3d_backward": OpSpec(
build=lambda d: (
[
f(d, 1, 1, 4, 4, 4),
f(d, 1, 1, 8, 8, 8),
[2, 2, 2],
[2, 2, 2],
[0, 0, 0],
False,
True,
None,
],
{},
),
),
# Schema: (self, kernel_size, output_size, random_samples[N,C,2]).
"fractional_max_pool2d": OpSpec(
build=lambda d: (
[
f(d, 1, 3, 8, 8),
[2, 2],
[4, 4],
torch.rand(1, 3, 2, device=d),
],
{},
),
),
# Schema: (self, kernel_size, stride, padding, dilation, ceil_mode).
"max_pool2d_with_indices": OpSpec(
build=lambda d: (
[f(d, 1, 1, 8, 8), [2, 2], [2, 2], [0, 0], [1, 1], False],
{},
),
),
"max_pool2d_with_indices_backward": OpSpec(
build=lambda d: (
[
f(d, 1, 1, 4, 4), # grad_output
f(d, 1, 1, 8, 8), # self
[2, 2],
[2, 2],
[0, 0],
[1, 1], # kernel, stride, padding, dilation
False, # ceil_mode
i(d, 1, 1, 4, 4), # indices
],
{},
),
),
"max_pool3d_with_indices": OpSpec(
build=lambda d: (
[
f(d, 1, 1, 8, 8, 8),
[2, 2, 2],
[2, 2, 2],
[0, 0, 0],
[1, 1, 1],
False,
],
{},
),
),
"max_pool3d_with_indices_backward": OpSpec(
build=lambda d: (
[
f(d, 1, 1, 4, 4, 4), # grad_output
f(d, 1, 1, 8, 8, 8), # self
[2, 2, 2],
[2, 2, 2],
[0, 0, 0],
[1, 1, 1],
False,
i(d, 1, 1, 4, 4, 4), # indices
],
{},
),
),
"max_unpool2d": OpSpec(
build=lambda d: ([f(d, 1, 1, 4, 4), i(d, 1, 1, 4, 4), [8, 8]], {}),
),
"max_unpool3d": OpSpec(
build=lambda d: (
[
f(d, 1, 1, 2, 2, 2),
i(d, 1, 1, 2, 2, 2),
[4, 4, 4],
[2, 2, 2],
[0, 0, 0],
],
{},
),
),
# ---------------------------------------------------------------
# Upsample
# ---------------------------------------------------------------
"upsample_nearest2d": OpSpec(
build=lambda d: ([f(d, 1, 3, 8, 8), [4, 4], None, None], {}),
),
"upsample_nearest3d": OpSpec(
build=lambda d: ([f(d, 1, 1, 4, 4, 4), [8, 8, 8]], {}),
),
"upsample_nearest1d": OpSpec(build=lambda d: ([f(d, 1, 3, 8), [16], None], {})),
# Schema: (grad_output, output_size, input_size, scales_h?, scales_w?).
"upsample_nearest2d_backward": OpSpec(
build=lambda d: (
[f(d, 1, 3, 16, 16), [16, 16], [1, 3, 8, 8], None, None],
{},
),
),
"upsample_linear1d": OpSpec(
build=lambda d: ([f(d, 1, 3, 8), [16], False, False], {}),
),
"upsample_bilinear2d": OpSpec(
build=lambda d: ([f(d, 1, 3, 8, 8), [16, 16], False], {}),
),
"upsample_bicubic2d": OpSpec(
build=lambda d: ([f(d, 1, 3, 8, 8), [16, 16], False], {}),
),
"upsample_trilinear3d": OpSpec(
build=lambda d: ([f(d, 1, 2, 4, 4, 4), [8, 8, 8], False], {}),
),
"_upsample_nearest_exact1d": OpSpec(
build=lambda d: ([f(d, 1, 3, 8), [16]], {}),
),
"_upsample_nearest_exact2d": OpSpec(
build=lambda d: ([f(d, 1, 3, 8, 8), [16, 16]], {}),
),
"_upsample_nearest_exact2d_backward": OpSpec(
build=lambda d: (
[f(d, 1, 3, 16, 16), [16, 16], [1, 3, 8, 8], None, None],
{},
),
),
"_upsample_nearest_exact3d": OpSpec(
build=lambda d: ([f(d, 1, 2, 4, 4, 4), [8, 8, 8]], {}),
),
"_upsample_bilinear2d_aa": OpSpec(
build=lambda d: ([f(d, 1, 3, 8, 8), [16, 16], False, None], {}),
),
"_upsample_bicubic2d_aa": OpSpec(
build=lambda d: ([f(d, 1, 3, 8, 8), [16, 16], False, None], {}),
),
# Schema: (grad_output, output_size, input_size, align_corners, scales_h?,
# scales_w?).
"_upsample_bilinear2d_aa_backward": OpSpec(
build=lambda d: (
[f(d, 1, 3, 16, 16), [16, 16], [1, 3, 8, 8], False, None, None],
{},
),
),
# ---------------------------------------------------------------
# Grid sampler / im2col
# ---------------------------------------------------------------
"grid_sampler_2d": OpSpec(
build=lambda d: (
[f(d, 1, 1, 8, 8), torch.zeros(1, 8, 8, 2, device=d), 0, 0, False],
{},
),
),
# Schema: (grad_output, input, grid, interp, padding, align_corners,
# bool[2] output_mask).
"grid_sampler_2d_backward": OpSpec(
build=lambda d: (
[
f(d, 1, 1, 8, 8), # grad_output
f(d, 1, 1, 8, 8), # input
torch.zeros(1, 8, 8, 2, device=d), # grid
0,
0,
False,
[True, True], # output_mask
],
{},
),
),
"grid_sampler_3d": OpSpec(
build=lambda d: (
[f(d, 1, 1, 4, 4, 4), torch.zeros(1, 4, 4, 4, 3, device=d), 0, 0, False],
{},
),
),
"grid_sampler_3d_backward": OpSpec(
build=lambda d: (
[
f(d, 1, 1, 4, 4, 4), # grad_output
f(d, 1, 1, 4, 4, 4), # input
torch.zeros(1, 4, 4, 4, 3, device=d), # grid
0,
0,
False,
[True, True],
],
{},
),
),
# Schema: im2col(self, kernel_size, dilation, padding, stride).
"im2col": OpSpec(
build=lambda d: ([f(d, 1, 1, 8, 8), [3, 3], [1, 1], [0, 0], [1, 1]], {}),
),
# Schema: col2im(self, output_size, kernel_size, dilation, padding, stride).
"col2im": OpSpec(
build=lambda d: (
[f(d, 1, 4, 9), [4, 4], [2, 2], [1, 1], [0, 0], [1, 1]],
{},
),
),
# ---------------------------------------------------------------
# Padding
# ---------------------------------------------------------------
"reflection_pad1d_backward": OpSpec(
build=lambda d: ([f(d, 1, 1, 8), f(d, 1, 1, 6), [1, 1]], {}),
),
"reflection_pad2d": OpSpec(
build=lambda d: ([f(d, 1, 1, 6, 6), [1, 1, 1, 1]], {}),
),
"reflection_pad2d_backward": OpSpec(
build=lambda d: ([f(d, 1, 1, 8, 8), f(d, 1, 1, 6, 6), [1, 1, 1, 1]], {}),
),
"reflection_pad3d": OpSpec(
build=lambda d: ([f(d, 1, 1, 4, 4, 4), [1, 1, 1, 1, 1, 1]], {}),
),
"reflection_pad3d_backward": OpSpec(
build=lambda d: (
[f(d, 2, 3, 8, 8, 8), f(d, 2, 3, 6, 6, 6), [1, 1, 1, 1, 1, 1]],
{},
),
),
"replication_pad1d_backward": OpSpec(
build=lambda d: ([f(d, 1, 1, 8), f(d, 1, 1, 6), [1, 1]], {}),
),
"replication_pad2d": OpSpec(
build=lambda d: ([f(d, 1, 1, 6, 6), [1, 1, 1, 1]], {}),
),
"replication_pad2d_backward": OpSpec(
build=lambda d: ([f(d, 1, 1, 8, 8), f(d, 1, 1, 6, 6), [1, 1, 1, 1]], {}),
),
"replication_pad3d": OpSpec(
build=lambda d: ([f(d, 1, 1, 4, 4, 4), [1, 1, 1, 1, 1, 1]], {}),
),
"replication_pad3d_backward": OpSpec(
build=lambda d: (
[f(d, 1, 1, 6, 6, 6), f(d, 1, 1, 4, 4, 4), [1, 1, 1, 1, 1, 1]],
{},
),
),
# Integer / bitwise.
"__ilshift__": OpSpec(build=lambda d: ([i(d, 4, 4), i(d, 4, 4, high=4)], {})),
"__irshift__": OpSpec(build=lambda d: ([i(d, 4, 4), i(d, 4, 4)], {})),
"__rshift__": OpSpec(build=lambda d: ([i(d, 4, 4), i(d, 4, 4)], {})),
"__lshift__": OpSpec(build=lambda d: ([i(d, 4, 4), i(d, 4, 4)], {})),
"bitwise_and": OpSpec(build=lambda d: ([i(d, 4, 4), i(d, 4, 4)], {})),
"bitwise_and_": OpSpec(build=lambda d: ([i(d, 4, 4), i(d, 4, 4)], {})),
"bitwise_or": OpSpec(build=lambda d: ([i(d, 4, 4), i(d, 4, 4)], {})),
"bitwise_or_": OpSpec(build=lambda d: ([i(d, 4, 4), i(d, 4, 4)], {})),
"bitwise_xor": OpSpec(build=lambda d: ([i(d, 4, 4), i(d, 4, 4)], {})),
"bitwise_xor_": OpSpec(build=lambda d: ([i(d, 4, 4), i(d, 4, 4)], {})),
"bitwise_left_shift": OpSpec(build=lambda d: ([i(d, 4, 4), i(d, 4, 4)], {})),
"bitwise_right_shift": OpSpec(build=lambda d: ([i(d, 4, 4), i(d, 4, 4)], {})),
"bitwise_not": OpSpec(
build=lambda d: (
[torch.randint(0, 256, (4, 4), device=d, dtype=torch.int64)],
{},
),
),
"bitwise_not_": OpSpec(build=lambda d: ([i(d, 4, 4)], {})),
"gcd": OpSpec(build=lambda d: ([i(d, 4, 4), i(d, 4, 4)], {})),
"gcd_": OpSpec(build=lambda d: ([i(d, 4, 4), i(d, 4, 4)], {})),
"lcm": OpSpec(
build=lambda d: (
[
torch.randint(1, 100, (4, 4), device=d, dtype=torch.int64),
torch.randint(1, 100, (4, 4), device=d, dtype=torch.int64),
],
{},
),
),
"lcm_": OpSpec(build=lambda d: ([i(d, 4, 4), i(d, 4, 4)], {})),
# ---------------------------------------------------------------
# Random distributions
# ---------------------------------------------------------------
"one_hot": OpSpec(
build=lambda d: ([torch.randint(0, 5, (4,), device=d)], {}),
),
"poisson": OpSpec(
build=lambda _d: ([CoverageTensorArg((4, 4), "rand_uniform", 4.0), None], {}),
),
"bernoulli_": OpSpec(
build=lambda d: ([f(d, 4, 4), CoverageTensorArg((4, 4), "rand")], {}),
),
"multinomial": OpSpec(
skip="SIGSEGV under torch.profiler on ROCm.",
),
"geometric_": OpSpec(build=lambda d: ([f(d, 4, 4), 0.5], {})),
"repeat_interleave": OpSpec(build=lambda d: ([i1(d, 16)], {})),
"bincount": OpSpec(build=lambda d: ([i1(d, 32).clamp_min(0)], {})),
# ---------------------------------------------------------------
# Masks / clamp
# ---------------------------------------------------------------
"clamp": OpSpec(build=lambda d: ([f(d, 4, 4), 0.0, 1.0], {})),
"clamp_": OpSpec(build=lambda d: ([f(d, 4, 4), 0.0, 1.0], {})),
"masked_fill_": OpSpec(build=lambda d: ([f(d, 4, 4), b(d), 0.0], {})),
"masked_scatter_": OpSpec(
build=lambda d: ([f(d, 4, 4), b(d), f(d, 4, 4)], {}),
),
"masked_select": OpSpec(build=lambda d: ([f(d, 4, 4), b(d)], {})),
"_masked_scale": OpSpec(
build=lambda d: (
[
f(d, 4, 4),
torch.randint(0, 2, (4, 4), device=d, dtype=torch.uint8),
1.0,
],
{},
),
),
# Schema: native_dropout_backward(grad_output, mask, scale).
"native_dropout_backward": OpSpec(
build=lambda d: ([f(d, 4, 4), b(d), 0.5], {}),
),
# ---------------------------------------------------------------
# Indexing / scatter / gather
# ---------------------------------------------------------------
"index": OpSpec(
build=lambda d: (
[
f(d, 4, 4),
[None, torch.randint(0, 4, (2, 2), device=d, dtype=torch.int64)],
],
{},
),
),
"flip": OpSpec(build=lambda d: ([f(d, 4, 4), [0]], {})),
"index_copy": OpSpec(
build=lambda d: ([f(d, 4, 4), 0, i1(d, 2), f(d, 2, 4)], {}),
),
"index_copy_": OpSpec(
build=lambda d: ([f(d, 4, 4), 0, i1(d, 2), f(d, 2, 4)], {}),
),
"index_add": OpSpec(
build=lambda d: ([f(d, 4, 4), 0, i1(d, 2), f(d, 2, 4)], {}),
),
"index_add_": OpSpec(
build=lambda d: ([f(d, 4, 4), 0, i1(d, 2), f(d, 2, 4)], {}),
),
"index_reduce": OpSpec(
build=lambda d: (
[f(d, 4, 4), 0, i1(d, 4), f(d, 4, 4), "prod"],
{"include_self": False},
),
),
"index_reduce_": OpSpec(
build=lambda d: (
[f(d, 4, 4), 0, i1(d, 4), f(d, 4, 4), "prod"],
{"include_self": False},
),
),
"index_fill_": OpSpec(
build=lambda d: ([f(d, 4, 4), 0, i1(d, 2), torch.tensor(0.0, device=d)], {}),
),
"index_select": OpSpec(build=lambda d: ([f(d, 4, 4), 0, i1(d, 2)], {})),
"gather": OpSpec(
build=lambda d: ([f(d, 4, 4), 0, i(d, 4, 4, low=0, high=4)], {}),
),
"scatter": OpSpec(
build=lambda d: ([f(d, 4, 4), 0, i(d, 4, 4), f(d, 4, 4)], {}),
),
"scatter_": OpSpec(
build=lambda d: ([f(d, 4, 4), 0, i(d, 4, 4), f(d, 4, 4)], {}),
),
"scatter_add": OpSpec(
build=lambda d: ([f(d, 4, 4), 0, i(d, 4, 4), f(d, 4, 4)], {}),
),
"scatter_add_": OpSpec(
build=lambda d: ([f(d, 4, 4), 0, i(d, 4, 4), f(d, 4, 4)], {}),
),
"scatter_reduce": OpSpec(
build=lambda d: (
[f(d, 4, 4), 0, i(d, 4, 4), f(d, 4, 4), "sum"],
{"include_self": False},
),
),
"scatter_reduce_": OpSpec(
build=lambda d: (
[f(d, 4, 4), 0, i(d, 4, 4), f(d, 4, 4), "sum"],
{"include_self": False},
),
),
"take": OpSpec(build=lambda d: ([f(d, 4, 4), i1(d, 8)], {})),
"segment_reduce": OpSpec(
build=lambda d: (
[f(d, 8), "sum"],
{
"lengths": torch.tensor([4, 4], device=d, dtype=torch.int64),
"unsafe": True,
},
),
),
"_segment_reduce_backward": OpSpec(
build=lambda d: (
[
torch.randn(2, device=d), # grad (output shape)
torch.randn(2, device=d), # output
torch.randn(8, device=d), # data
"sum", # reduce
],
{"lengths": torch.tensor([4, 4], device=d, dtype=torch.int64)},
),
),
# ---------------------------------------------------------------
# Misc shape / dtype / layout
# ---------------------------------------------------------------
"_chunk_cat": OpSpec(
build=lambda d: ([[f(d, 2, 4), f(d, 2, 4)], 0, 2], {}),
),
"_cdist_forward": OpSpec(
build=lambda d: ([f(d, 2, 4, 8), f(d, 2, 5, 8), 2.0, None], {}),
),
"_thnn_fused_lstm_cell": OpSpec(
build=lambda d: (
[f(d, 2, 16), f(d, 2, 16), f(d, 2, 4), None, None],
{},
),
),
"channel_shuffle": OpSpec(build=lambda d: ([f(d, 1, 8, 4, 4), 2], {})),
"bucketize": OpSpec(build=lambda d: ([f(d, 8), f(d, 5)], {})),
"roll": OpSpec(build=lambda d: ([f(d, 4, 4), [1], [0]], {})),
"unfold": OpSpec(build=lambda d: ([f(d, 2, 8), 1, 4, 2], {})),
# Schema: unfold_backward(grad_in, input_sizes, dim, size, step).
"unfold_backward": OpSpec(
build=lambda d: ([f(d, 2, 4, 4), [2, 10], 1, 4, 2], {}),
),
"view_as_complex": OpSpec(build=lambda d: ([f(d, 4, 2)], {})),
"view_as_real": OpSpec(
build=lambda d: ([torch.randn(4, 2, device=d, dtype=torch.complex64)], {}),
),
"view": OpSpec(build=lambda d: ([f(d, 4, 4), [8, 2]], {})),
"glu_backward": OpSpec(build=lambda d: ([f(d, 2, 4), f(d, 2, 8), 1], {})),
"_int_mm": OpSpec(
build=lambda d: (
[
torch.randint(-2, 2, (32, 32), device=d, dtype=torch.int8),
torch.randint(-2, 2, (32, 32), device=d, dtype=torch.int8),
],
{},
),
),
"_convert_weight_to_int4pack": OpSpec(
build=lambda d: (
[torch.randint(0, 255, (128, 64), device=d, dtype=torch.uint8), 8],
{},
),
),
# ---------------------------------------------------------------
# Softmax backward (mixed-dtype accumulation)
# ---------------------------------------------------------------
"_log_softmax_backward_data": OpSpec(
build=lambda d: (
[
f(d, 2, 8, dtype=torch.float16),
f(d, 2, 8, dtype=torch.float16),
1,
torch.float16,
],
{},
),
),
"_softmax_backward_data": OpSpec(
build=lambda d: (
[
f(d, 2, 8, dtype=torch.float16),
f(d, 2, 8, dtype=torch.float16),
1,
torch.float16,
],
{},
),
),
# ---------------------------------------------------------------
# Misc / AMP / assertions
# ---------------------------------------------------------------
# Schema: (Tensor[] self, Tensor found_inf, Tensor inv_scale) -> ().
"_amp_foreach_non_finite_check_and_unscale_": OpSpec(
build=lambda d: (
[
[f(d, 4), f(d, 4)],
torch.zeros(1, device=d), # found_inf
torch.ones(1, device=d), # inv_scale
],
{},
),
),
# _assert_async traps (SIGABRT) on any False element, so the input must
# be all-True; default randint(bool) would abort the ground-truth subprocess.
"_assert_async": OpSpec(
build=lambda _d: (
[CoverageTensorArg(shape=(), emit="bool_all_true")],
{},
),
),
"nonzero_static": OpSpec(
build=lambda d: ([f(d, 4, 4)], {"size": 8}),
),
# ---------------------------------------------------------------
# Convolution backward (needs explicit output_sizes + masks)
# ---------------------------------------------------------------
"convolution_backward": OpSpec(build=convolution_backward_args),
# ---------------------------------------------------------------
# Fused optimizers (list-of-tensors + scalar state_steps)
# ---------------------------------------------------------------
"_fused_adam_": OpSpec(build=fused_adam_args),
"_fused_adamw_": OpSpec(build=fused_adam_args),
"_scaled_dot_product_cudnn_attention": OpSpec(skip="cuDNN-only."),
"_scaled_dot_product_cudnn_attention_backward": OpSpec(skip="cuDNN-only."),
"_scaled_dot_product_efficient_attention": OpSpec(skip="xFormers CUDA-only."),
"_scaled_dot_product_efficient_attention_backward": OpSpec(
skip="xFormers CUDA-only."
),
"_scaled_dot_product_flash_attention": OpSpec(
skip="FlashAttention forward; schema-driven synthesis raises IndexError "
"from broadcast/mask dims, needs real 4D QKV + optional mask.",
),
"_scaled_dot_product_flash_attention_backward": OpSpec(
skip="FlashAttention backward; requires matching fwd output + softmax_lse.",
),
"_flash_attention_forward": OpSpec(
skip="FlashAttention forward; requires bf16/fp16 QKV with specific layout.",
),
"_flash_attention_backward": OpSpec(
skip="FlashAttention backward; requires matching fwd output + softmax_lse.",
),
"_efficient_attention_forward": OpSpec(skip="xFormers CUDA-only kernel."),
"_efficient_attention_backward": OpSpec(skip="xFormers CUDA-only kernel."),
"_scaled_mm": OpSpec(
skip="fp8 gemm; requires fp8 tensors + scale tensors on supported hardware.",
),
"_scaled_mm_v2": OpSpec(
skip="fp8 gemm v2; requires fp8 tensors + scale tensors on supported hardware.",
),
"_grouped_mm": OpSpec(
skip="grouped matmul; requires per-group offset metadata on supported hw.",
),
"_cslt_sparse_mm": OpSpec(skip="cuSPARSELt; no ROCm equivalent."),
"_sparse_semi_structured_linear": OpSpec(skip="CUTLASS 2:4 sparse; NVIDIA-only."),
"_sparse_semi_structured_addmm": OpSpec(skip="CUTLASS 2:4 sparse; NVIDIA-only."),
"_nested_view_from_buffer": OpSpec(
skip="Nested-tensor constructor; requires matching offsets/lengths buffers.",
),
}
# Bulk-registered builders. ``dict.setdefault`` preserves explicit
# per-operator overrides in ``OP_SPECS``.
def _register_bulk_unary_float_builders() -> None:
"""Register builders for unary float ``Tensor -> Tensor`` operators."""
def template(d: str) -> Tuple[List[Any], Dict[str, Any]]:
return ([f(d, 8, 8)], {})
# fmt: off
bulk_short_names = (
# trig / inverse trig / hyperbolic
"acos", "acosh", "asin", "asinh", "atan", "atanh",
"cos", "cosh", "sin", "sinh", "tan", "tanh", "sinc",
# exponentials / logarithms / power
"exp", "exp2", "expm1", "log", "log10", "log1p", "log2",
"logit",
"reciprocal", "rsqrt", "sqrt", "square",
# rounding / sign / fractional
"ceil", "floor", "trunc", "round", "neg", "frac", "sgn", "sign",
"positive",
# gamma / Bessel-ish
"digamma", "erf", "erfc", "erfinv", "i0", "lgamma",
# complex / angle helpers
"angle", "conj_physical",
# angle conversion
"deg2rad", "rad2deg",
# activations
"elu", "gelu", "hardshrink", "hardsigmoid", "hardswish",
"hardtanh", "leaky_relu", "mish", "relu", "sigmoid", "silu",
"softplus", "softshrink",
)
# fmt: on
for name in bulk_short_names:
OP_SPECS.setdefault(name, OpSpec(build=template))
def _register_bulk_whole_tensor_reduction_builders() -> None:
"""Register builders for whole-tensor reduction operators."""
def template(d: str) -> Tuple[List[Any], Dict[str, Any]]:
return ([f(d, 8, 8)], {})
bulk_short_names = (
"all",
"any",
"amax",
"amin",
"aminmax",
"max",
"min",
"median",
"mode",
"nanmedian",
"prod",
"sum",
"mean",
"nansum",
"trace",
"std",
"var",
"std_mean",
"var_mean",
)
for name in bulk_short_names:
OP_SPECS.setdefault(name, OpSpec(build=template))
def _register_bulk_unary_special_builders() -> None:
"""Register builders for unary ``special_*`` functions."""
def template(d: str) -> Tuple[List[Any], Dict[str, Any]]:
return ([torch.rand(8, 8, device=d) * 2.0], {})
bulk_short_names = (
"special_airy_ai",
"special_bessel_j0",
"special_bessel_j1",
"special_bessel_y0",
"special_bessel_y1",
"special_modified_bessel_i0",
"special_modified_bessel_i1",
"special_modified_bessel_k0",
"special_modified_bessel_k1",
"special_scaled_modified_bessel_k0",
"special_scaled_modified_bessel_k1",
"special_spherical_bessel_j0",
"special_entr",
"special_erfcx",
"special_ndtri",
"special_i0e",
"special_i1e",
)
for name in bulk_short_names:
OP_SPECS.setdefault(name, OpSpec(build=template))
def _register_bulk_binary_tensor_elementwise_builders() -> None:
"""Register builders for binary elementwise operators."""
def template(d: str) -> Tuple[List[Any], Dict[str, Any]]:
return ([f(d, 8, 8), f(d, 8, 8)], {})
bulk_short_names = (
"add",
"sub",
"mul",
"div",
"maximum",
"minimum",
"fmax",
"fmin",
"copysign",
"heaviside",
"hypot",
"nextafter",
"logaddexp",
"logaddexp2",
"floor_divide",
"xlogy",
"atan2",
"clamp_max",
"clamp_min",
)
for name in bulk_short_names:
OP_SPECS.setdefault(name, OpSpec(build=template))
def _register_high_impact_individual_builders() -> None:
"""Register individual builders for high-impact operators."""
individual = {
# (input, value, value) — fused multiply-add variants
"addcmul": OpSpec(
build=lambda d: ([f(d, 4, 4), f(d, 4, 4), f(d, 4, 4)], {}),
),
"addcdiv": OpSpec(
build=lambda d: ([f(d, 4, 4), f(d, 4, 4), f(d, 4, 4)], {}),
),
# matmul: 2D x 2D
"mm": OpSpec(build=lambda d: ([f(d, 4, 4), f(d, 4, 4)], {})),
# cat: list of tensors + dim
"cat": OpSpec(
build=lambda d: ([[f(d, 2, 4), f(d, 2, 4)], 0], {}),
),
# where.self: (condition Bool, self, other) - same shapes
"where": OpSpec(
build=lambda d: (
[
torch.zeros(4, 4, dtype=torch.bool, device=d),
f(d, 4, 4),
f(d, 4, 4),
],
{},
),
),
# softmax / log_softmax forward — (input, dim, half_to_float=False)
"_softmax": OpSpec(
build=lambda d: ([f(d, 4, 8), -1, False], {}),
),
"_log_softmax": OpSpec(
build=lambda d: ([f(d, 4, 8), -1, False], {}),
),
# dim-reduction helpers — (input, dim, keepdim?)
"all.dim": OpSpec(build=lambda d: ([f(d, 4, 4), -1], {})),
"any.dim": OpSpec(build=lambda d: ([f(d, 4, 4), -1], {})),
"max.dim": OpSpec(build=lambda d: ([f(d, 4, 4), -1], {})),
"min.dim": OpSpec(build=lambda d: ([f(d, 4, 4), -1], {})),
"mean.dim": OpSpec(build=lambda d: ([f(d, 4, 4), [-1]], {})),
"prod.dim_int": OpSpec(
build=lambda d: ([f(d, 4, 4), -1], {}),
),
"count_nonzero.dim_IntList": OpSpec(
build=lambda d: ([f(d, 4, 4), [-1]], {}),
),
# cumulative reductions
"cumsum": OpSpec(build=lambda d: ([f(d, 4, 4), -1], {})),
"cumprod": OpSpec(build=lambda d: ([f(d, 4, 4), -1], {})),
# losses: (input, target, reduction='mean')
"mse_loss": OpSpec(
build=lambda d: ([f(d, 4, 4), f(d, 4, 4)], {}),
),
"smooth_l1_loss": OpSpec(
build=lambda d: ([f(d, 4, 4), f(d, 4, 4)], {}),
),
"huber_loss": OpSpec(
build=lambda d: ([f(d, 4, 4), f(d, 4, 4)], {}),
),
# padding (1d): (input, pad)
"reflection_pad1d": OpSpec(
build=lambda d: ([f(d, 1, 4, 8), [1, 1]], {}),
),
"replication_pad1d": OpSpec(
build=lambda d: ([f(d, 1, 4, 8), [1, 1]], {}),
),
# threshold: (self, threshold, value)
"threshold": OpSpec(
build=lambda d: ([f(d, 4, 4), 0.0, 0.0], {}),
),
# histc: (input, bins, min, max)
"histc": OpSpec(
build=lambda d: ([f(d, 64), 10, -3.0, 3.0], {}),
),
# native_dropout: (input, p, train) → (Tensor, Bool mask)
"native_dropout": OpSpec(
build=lambda d: ([f(d, 4, 4), 0.5, True], {}),
),
# tril / triu / *_indices: (n, k=0)
"tril": OpSpec(build=lambda d: ([f(d, 4, 4)], {})),
"triu": OpSpec(build=lambda d: ([f(d, 4, 4)], {})),
"tril_indices": OpSpec(build=lambda _d: ([4, 4], {})),
"triu_indices": OpSpec(build=lambda _d: ([4, 4], {})),
# comparison with explicit Scalar overload short names
"eq": OpSpec(build=lambda d: ([f(d, 4, 4), f(d, 4, 4)], {})),
"ne": OpSpec(build=lambda d: ([f(d, 4, 4), f(d, 4, 4)], {})),
"lt": OpSpec(build=lambda d: ([f(d, 4, 4), f(d, 4, 4)], {})),
"gt": OpSpec(build=lambda d: ([f(d, 4, 4), f(d, 4, 4)], {})),
"ge": OpSpec(build=lambda d: ([f(d, 4, 4), f(d, 4, 4)], {})),
"le": OpSpec(build=lambda d: ([f(d, 4, 4), f(d, 4, 4)], {})),
# isin: needs (elements, test_elements). Short name "isin"
# covers both .Tensor_Tensor and .Tensor_Scalar overloads.
"isin": OpSpec(
build=lambda d: (
[
torch.tensor(
[0.0, 1.0, 2.0, 3.0],
device=d,
dtype=torch.float32,
),
torch.tensor(
[1.0, 3.0],
device=d,
dtype=torch.float32,
),
],
{},
),
),
# logical_*: bool inputs
"logical_and": OpSpec(
build=lambda d: (
[b(d, (4, 4)), b(d, (4, 4))],
{},
),
),
"logical_or": OpSpec(
build=lambda d: (
[b(d, (4, 4)), b(d, (4, 4))],
{},
),
),
"logical_xor": OpSpec(
build=lambda d: (
[b(d, (4, 4)), b(d, (4, 4))],
{},
),
),
"logical_not": OpSpec(
build=lambda d: ([b(d, (4, 4))], {}),
),
# argmax / argmin: (input, dim?, keepdim?)
"argmax": OpSpec(
build=lambda d: ([f(d, 4, 4)], {"dim": -1}),
),
"argmin": OpSpec(
build=lambda d: ([f(d, 4, 4)], {"dim": -1}),
),
# topk: (input, k)
"topk": OpSpec(build=lambda d: ([f(d, 16), 4], {})),
# sort: (input, dim?, descending?)
"sort": OpSpec(build=lambda d: ([f(d, 16)], {})),
# nonzero: (input,) → indices
"nonzero": OpSpec(build=lambda d: ([f(d, 4, 4)], {})),
# unique_consecutive / unique_dim
"unique_consecutive": OpSpec(
build=lambda d: (
[torch.tensor([1, 1, 2, 2, 3, 1], device=d)],
{},
),
),
# renorm: (input, p, dim, maxnorm)
"renorm": OpSpec(
build=lambda d: ([f(d, 4, 4), 2.0, 0, 5.0], {}),
),
}
for name, spec in individual.items():
OP_SPECS.setdefault(name, spec)
_register_bulk_unary_float_builders()
_register_bulk_whole_tensor_reduction_builders()
_register_bulk_unary_special_builders()
_register_bulk_binary_tensor_elementwise_builders()
_register_high_impact_individual_builders()
# Zero-copy view ops that launch no GPU kernels. A correct ROCTX trace
# shows the marker with an empty kernel-correlation set.
KNOWN_KERNEL_FREE_ATEN_OPS: Set[str] = {
"view",
"view_as_real",
"view_as_complex",
"unfold",
}
# CUDA-only / NVIDIA-only backends that cannot run on ROCm. Ops whose
# OpSpec.skip text contains any of these keywords are filtered out at
# discovery time.
_HARDWARE_INCOMPATIBLE_CUDA_KEYWORDS: Tuple[str, ...] = (
"cuda-only",
"cuda only",
"nvidia-only",
"nvidia only",
"cudnn-only",
"cudnn only",
"cutlass",
"cusparselt",
"xformers",
"flashattention",
"flash attention",
"fp8 gemm",
"fp8 tensors",
"packed int4",
"int8 packed",
"grouped matmul",
"mat2 to be transposed",
)
def is_hardware_incompatible_cuda_only(text: Optional[str]) -> bool:
"""Return True when ``text`` references a CUDA-only or NVIDIA-only backend."""
if not text:
return False
lowered = text.lower()
return any(k in lowered for k in _HARDWARE_INCOMPATIBLE_CUDA_KEYWORDS)
# One row in the coverage sample. ``category`` is "aten" or
# "structural"; ``schema`` is a ``FunctionSchema`` for ATen rows.
class OpEntry(NamedTuple):
name: str
category: str
schema: object
# status is "pass" / "skip" / "fail"; reason is empty on pass, otherwise the
class OpCompareOutcome(NamedTuple):
status: str
reason: str
log_lines: Tuple[str, ...]
# Subprocess runner that records one ``torch.profiler`` window per
# operator and dumps results to JSON.
COVERAGE_GROUND_TRUTH_RUNNER_SOURCE = textwrap.dedent(
"""
import importlib.util
import json
import os
import sys
# Synchronous dispatch attributes faults to the launching op.
os.environ.setdefault("AMD_SERIALIZE_KERNEL", "3")
import torch
from torch.profiler import profile, ProfilerActivity
def probe_device() -> None:
\"\"\"Launch a small kernel and synchronise to surface device faults.\"\"\"
t = torch.empty(1, device=\"cuda\")
t.add_(1)
torch.cuda.synchronize()
def main() -> None:
workload_path, output_path = sys.argv[1], sys.argv[2]
spec = importlib.util.spec_from_file_location(
"_torch_trace_coverage_workload",
workload_path,
)
wl = importlib.util.module_from_spec(spec)
assert spec.loader is not None
spec.loader.exec_module(wl)
results: dict = {}
for op_name, run_fn in zip(wl.OP_NAMES, wl.ALL_OPS):
print(f"[torch-trace-cov] START {op_name}", file=sys.stderr, flush=True)
try:
with profile(
activities=[
ProfilerActivity.CPU,
ProfilerActivity.CUDA,
],
acc_events=True,
) as prof:
run_fn()
torch.cuda.synchronize()
results[op_name] = {
"aten_ops": [
e.name
for e in prof.events()
if e.name.startswith("aten::")
],
"cuda_kernels": [
e.name
for e in prof.events()
if e.device_type == torch.autograd.DeviceType.CUDA
],
}
except Exception as exc:
results[op_name] = {
"error": f"{type(exc).__name__}: {exc}",
}
print(
f"[torch-trace-cov] FAIL {op_name} {type(exc).__name__}: {exc}",
file=sys.stderr,
flush=True,
)
try:
probe_device()
except Exception as exc2:
print(
f"[torch-trace-cov] DEVICE_DEAD after {op_name} "
f"{type(exc2).__name__}: {exc2}",
file=sys.stderr,
flush=True,
)
break
continue
print(f"[torch-trace-cov] OK {op_name}", file=sys.stderr, flush=True)
with open(output_path, "w", encoding="utf-8") as out_f:
json.dump(results, out_f)
if __name__ == "__main__":
main()
"""
).strip()
def unique_get_output_param_id(prefix: str) -> str:
"""Unique param_id (xdist worker + pid + tid + uuid) to avoid path races."""
worker = os.environ.get("PYTEST_XDIST_WORKER", "main")
return f"{prefix}_{worker}_{os.getpid()}_{threading.get_ident()}_{uuid.uuid4().hex}"
# Sample labels: ``torch.ops.aten.<op>`` for the default overload and
# ``torch.ops.aten.<op>.<overload>`` for named overloads. The workload
# always calls the packet and lets the dispatcher pick the overload.
def aten_op_short_name(op_name: str) -> str:
"""Return the ATen op name without any overload suffix."""
parts = op_name.split(".")
return parts[3] if len(parts) >= 4 and parts[:3] == ["torch", "ops", "aten"] else ""
def aten_overload_key(op_name: str) -> Optional[str]:
"""Return ``<op>.<overload>`` for non-default overloads, else ``None``."""
parts = op_name.split(".")
if len(parts) >= 5 and parts[:3] == ["torch", "ops", "aten"]:
return ".".join(parts[3:])
return None
def aten_packet_call_path(op_name: str) -> str:
"""Return the packet call path for ``op_name`` without any overload suffix."""
parts = op_name.split(".")
if len(parts) >= 5 and parts[:3] == ["torch", "ops", "aten"]:
return ".".join(parts[:4])
return op_name
# Argument synthesis. An ATen operator is emitted only when ``OP_SPECS``
# holds a builder for the overload key or the packet key. ``.out`` and
# in-place overloads reuse the out-of-place builder when guards pass.
_SINGLE_TENSOR_OUT_OVERLOAD_SUFFIXES: frozenset = frozenset({
"out",
"Tensor_out",
"OutTensor",
"m_out",
"out_mode",
})
# Short names excluded from generic .out injection because the output
# dtype does not follow the input dtype.
_OUT_MECHANISM_EXCLUDED_SHORT_NAMES: frozenset = frozenset({
"eq",
"ne",
"lt",
"gt",
"ge",
"le",
"isnan",
"isinf",
"isfinite",
"isneginf",
"isposinf",
"logical_and",
"logical_or",
"logical_xor",
"logical_not",
"isclose",
"isin",
"argmax",
"argmin",
"argsort",
"all",
"any",
"nonzero",
"tril_indices",
"triu_indices",
})
def _first_tensor_dtype(args: List[Any]) -> Optional[torch.dtype]:
"""Return the dtype of the first Tensor in ``args``, or ``None``."""
for arg in args:
if isinstance(arg, torch.Tensor):
return arg.dtype
if isinstance(arg, (list, tuple)) and arg:
first = arg[0]
if isinstance(first, torch.Tensor):
return first.dtype
return None
def _schema_returns_single_tensor(schema: object) -> bool:
"""Return True when ``schema`` declares exactly one return value."""
if schema is None:
return False
returns = getattr(schema, "returns", None)
if returns is None:
return False
try:
return len(returns) == 1
except TypeError:
return False
def build_args_for_op(
op: OpEntry,
) -> Optional[Tuple[List[Any], dict]]:
"""Return ``(args, kwargs)`` for ``op`` on CUDA, or ``None`` to skip."""
device = "cuda"
if op.category == "structural":
return [], {}
overload_key = aten_overload_key(op.name)
short_name = aten_op_short_name(op.name) or op.name.rsplit(".", 1)[-1]
if overload_key is not None:
spec = OP_SPECS.get(overload_key)
if spec is not None:
if spec.skip is not None:
return None
if spec.build is not None:
return spec.build(device)
if overload_key is not None and "." in overload_key:
overload_suffix = overload_key.split(".", 1)[1]
if (
overload_suffix in _SINGLE_TENSOR_OUT_OVERLOAD_SUFFIXES
and short_name not in _OUT_MECHANISM_EXCLUDED_SHORT_NAMES
and _schema_returns_single_tensor(op.schema)
):
base = OP_SPECS.get(short_name)
if base is not None and base.build is not None:
base_args, base_kwargs = base.build(device)
out_dtype = _first_tensor_dtype(base_args)
if out_dtype is None:
out_dtype = torch.get_default_dtype()
out_kwargs = dict(base_kwargs)
out_kwargs["out"] = torch.empty(
0,
device=device,
dtype=out_dtype,
)
return base_args, out_kwargs
spec = OP_SPECS.get(short_name)
if spec is not None:
if spec.skip is not None:
return None
if spec.build is not None:
return spec.build(device)
if (
short_name.endswith("_")
and not short_name.endswith("__")
and not short_name.startswith("_")
):
out_of_place = OP_SPECS.get(short_name[:-1])
if out_of_place is not None and out_of_place.build is not None:
return out_of_place.build(device)
return None
# -- Operator discovery --
def _op_skip_text(op: OpEntry) -> Optional[str]:
"""OpSpec.skip text for op (overload-key first, short-name second), else None."""
overload_key = aten_overload_key(op.name)
spec = None
if overload_key is not None:
spec = OP_SPECS.get(overload_key)
if spec is None:
spec = OP_SPECS.get(aten_op_short_name(op.name) or op.name.rsplit(".", 1)[-1])
return spec.skip if spec is not None else None
def discover_operators() -> Tuple[List[OpEntry], List[OpEntry], List[OpEntry]]:
"""Enumerate ATen and structural operators for the coverage sample.
Returns ``(kept_aten_ops, structural_ops, excluded_aten_ops)``;
excluded ops are CUDA-only backends incompatible with ROCm.
"""
aten_ops: List[OpEntry] = []
seen: Set[str] = set()
for ns_name in ("aten",):
ns = getattr(torch.ops, ns_name, None)
if ns is None:
continue
for op_name in dir(ns):
packet = getattr(ns, op_name, None)
if packet is None or not hasattr(packet, "overloads"):
continue
try:
overload_names = list(packet.overloads())
except Exception:
continue
for ov_name in overload_names:
try:
overload = getattr(packet, ov_name)
except AttributeError:
continue
try:
has_cuda = torch._C._dispatch_has_kernel_for_dispatch_key(
overload.name(),
torch._C.DispatchKey.CUDA,
)
except Exception:
continue
if not has_cuda:
continue
if ov_name == "default":
full = f"torch.ops.{ns_name}.{op_name}"
else:
full = f"torch.ops.{ns_name}.{op_name}.{ov_name}"
if full in seen:
continue
seen.add(full)
schema = getattr(overload, "_schema", None)
aten_ops.append(OpEntry(full, "aten", schema))
kept_aten_ops: List[OpEntry] = []
excluded_aten_ops: List[OpEntry] = []
for op in aten_ops:
if is_hardware_incompatible_cuda_only(_op_skip_text(op)):
excluded_aten_ops.append(op)
else:
kept_aten_ops.append(op)
structural_ops = [
OpEntry(name, "structural", None) for name in sorted(STRUCTURAL_BUILDERS.keys())
]
return kept_aten_ops, structural_ops, excluded_aten_ops
# -- Marker matching --
def marker_matches_op(op_name: str, marker_leaf: str) -> bool:
"""Return True when ``marker_leaf`` counts as coverage for ``op_name``."""
if op_name == marker_leaf:
return True
if (
op_name.startswith("nn.Module.")
and op_name.endswith(".__call__")
and op_name != "nn.Module.__call__"
):
cls = op_name[len("nn.Module.") : -len(".__call__")]
if marker_leaf.endswith(f".{cls}.forward") or marker_leaf == (
f"nn.Module.{cls}.forward"
):
return True
return False
if (
op_name.startswith("Optimizer.")
and op_name.endswith(".step")
and op_name != "Optimizer.step"
):
cls = op_name[len("Optimizer.") : -len(".step")]
if cls and marker_leaf.endswith(f".{cls}.step"):
return True
return False
if op_name.startswith("torch.ops.aten."):
op_short = aten_op_short_name(op_name)
if op_short:
packet = aten_packet_call_path(op_name)
if marker_leaf == packet:
return True
marker_norm = marker_leaf.rsplit("::", 1)[-1].rsplit(".", 1)[-1]
if marker_norm == op_short:
return True
if marker_leaf.endswith(f".{op_short}"):
return True
return False
op_leaf = op_name.rsplit("::", 1)[-1].rsplit(".", 1)[-1]
marker_norm = marker_leaf.rsplit("::", 1)[-1].rsplit(".", 1)[-1]
if marker_norm == op_leaf:
return True
if marker_leaf.endswith(f".{op_leaf}"):
return True
if op_name == "Optimizer.step" and marker_leaf.endswith(".step"):
return True
if (
op_name == "nn.Module.__call__"
and ".forward" in marker_leaf
and marker_leaf.startswith("nn.Module.")
):
return True
return False
# -- Workload script generation --
# Maximum content width for emitted workload bodies (88 - 4 indent).
WORKLOAD_EMIT_BODY_CONTENT_MAX = 84
def workload_emit_tensor_rand_uniform_setup(
vname: str,
shape: Tuple[Any, ...],
scale: float,
) -> List[str]:
"""Emit torch.rand times scale (non-negative rates, etc.)."""
scale_repr = repr(scale)
single = f"{vname} = torch.rand({shape}, device=device) * {scale_repr}"
if len(single) <= WORKLOAD_EMIT_BODY_CONTENT_MAX:
return [single]
return [
f"{vname} = torch.rand(",
f" {shape},",
" device=device,",
f") * {scale_repr}",
]
def workload_emit_tensor_rand_setup(
vname: str,
shape: Tuple[Any, ...],
) -> List[str]:
"""Emit torch.rand in [0, 1) (Bernoulli probabilities, etc.)."""
single = f"{vname} = torch.rand({shape}, device=device)"
if len(single) <= WORKLOAD_EMIT_BODY_CONTENT_MAX:
return [single]
return [
f"{vname} = torch.rand(",
f" {shape},",
" device=device,",
")",
]
def factory_expr_for_dtype(shape: Tuple[Any, ...], dtype: torch.dtype) -> str:
"""Return a random-tensor factory expression that preserves ``dtype``."""
if dtype == torch.bool:
return f"torch.randint(0, 2, {shape}, device=device, dtype=torch.bool)"
if dtype.is_floating_point or dtype.is_complex:
return f"torch.randn({shape}, device=device, dtype={dtype!r})"
low, high = (-4, 4) if dtype == torch.int8 else (0, 4)
return f"torch.randint({low}, {high}, {shape}, device=device, dtype={dtype!r})"
def workload_emit_assignment(vname: str, rhs: str) -> List[str]:
"""Emit vname = rhs on one line, or wrap across lines for Ruff E501."""
single = f"{vname} = {rhs}"
if len(single) <= WORKLOAD_EMIT_BODY_CONTENT_MAX:
return [single]
return [f"{vname} = (", f" {rhs}", ")"]
def workload_emit_tensor_random_setup(
vname: str,
shape: Tuple[Any, ...],
dtype: torch.dtype,
) -> List[str]:
"""Emit a dtype-preserving random tensor construction."""
return workload_emit_assignment(vname, factory_expr_for_dtype(shape, dtype))
def workload_emit_tensor_spd_setup(
vname: str,
shape: Tuple[Any, ...],
) -> List[str]:
"""Emit an SPD matrix (a @ a.mT + n*I) for cholesky et al."""
if len(shape) != 2 or shape[0] != shape[1]:
raise ValueError(
f"CoverageTensorArg(emit='spd') expects a square shape, got {shape!r}"
)
n = shape[0]
return [
f"{vname}_a = torch.randn({shape}, device=device)",
(f"{vname} = {vname}_a @ {vname}_a.mT + {n} * torch.eye({n}, device=device)"),
]
def workload_emit_tensor_pivots_1based_setup(
vname: str,
shape: Tuple[Any, ...],
) -> List[str]:
"""Emit a 1-D, 1-based identity int32 permutation for LU pivots."""
if len(shape) != 1:
raise ValueError(
f"CoverageTensorArg(emit='pivots_1based') expects a 1-D shape, "
f"got {shape!r}"
)
n = shape[0]
return [
f"{vname} = torch.arange(1, {n + 1}, dtype=torch.int32, device=device)",
]
def workload_emit_tensor_cumsum_offsets_setup(
vname: str,
shape: Tuple[Any, ...],
segment_length: int,
) -> List[str]:
"""Emit int64 offsets ``[0, k, ..., n*k]`` for nested/jagged ops."""
if len(shape) != 1:
raise ValueError(
f"CoverageTensorArg(emit='cumsum_offsets') expects a 1-D shape, "
f"got {shape!r}"
)
n = shape[0]
return [
(
f"{vname} = torch.arange(0, {n * segment_length + 1}, "
f"{segment_length}, dtype=torch.int64, device=device)"
),
]
def workload_emit_tensor_bool_all_true_setup(
vname: str,
shape: Tuple[Any, ...],
) -> List[str]:
"""Emit an all-True bool tensor of ``shape``."""
return [
f"{vname} = torch.ones({shape}, device=device, dtype=torch.bool)",
]
def workload_emit_multiline_call(
call_expr: str,
arg_expression_strings: List[str],
) -> List[str]:
"""Emit call_expr(...) on one line or split across lines for Ruff E501."""
parts = list(arg_expression_strings)
if not parts:
single = f"{call_expr}()"
if len(single) <= WORKLOAD_EMIT_BODY_CONTENT_MAX:
return [single]
return [
f"{call_expr}(",
")",
]
one_line = f"{call_expr}(" + ", ".join(parts) + ")"
if len(one_line) <= WORKLOAD_EMIT_BODY_CONTENT_MAX:
return [one_line]
out_lines = [f"{call_expr}("]
for index, part in enumerate(parts):
last = index == len(parts) - 1
suffix = "" if last else ","
out_lines.append(f" {part}{suffix}")
out_lines.append(")")
return out_lines
COVERAGE_TENSOR_ARG_EMITTERS: Dict[
str,
Callable[["CoverageTensorArg", str], List[str]],
] = {
"rand": lambda a, v: workload_emit_tensor_rand_setup(v, a.shape),
"rand_uniform": lambda a, v: workload_emit_tensor_rand_uniform_setup(
v,
a.shape,
a.scale,
),
"spd": lambda a, v: workload_emit_tensor_spd_setup(v, a.shape),
"pivots_1based": lambda a, v: workload_emit_tensor_pivots_1based_setup(
v,
a.shape,
),
"cumsum_offsets": lambda a, v: workload_emit_tensor_cumsum_offsets_setup(
v,
a.shape,
int(a.scale),
),
"bool_all_true": lambda a, v: workload_emit_tensor_bool_all_true_setup(
v,
a.shape,
),
}
def serialize_arg(argument_value: Any, vname: str) -> Tuple[List[str], str]:
"""Return ``(setup_lines, expr)`` defining ``vname`` and the call expression.
For plain ``torch.Tensor`` inputs only shape and dtype are
preserved; values are re-randomized at emit time. Use
``CoverageTensorArg`` when the builder requires specific values.
"""
if isinstance(argument_value, CoverageTensorArg):
emitter = COVERAGE_TENSOR_ARG_EMITTERS.get(argument_value.emit)
if emitter is None:
raise ValueError(
f"unsupported CoverageTensorArg.emit: {argument_value.emit!r}"
)
return emitter(argument_value, vname), vname
if isinstance(argument_value, torch.Tensor):
return (
workload_emit_tensor_random_setup(
vname,
tuple(argument_value.shape),
argument_value.dtype,
),
vname,
)
if isinstance(argument_value, (list, tuple)):
open_b, close_b = ("[", "]") if isinstance(argument_value, list) else ("(", ")")
if not argument_value:
return [], f"{open_b}{close_b}" if open_b == "[" else "()"
stmts: List[str] = []
parts: List[str] = []
for index, elem in enumerate(argument_value):
sub_stmts, sub_expr = serialize_arg(elem, f"{vname}_sub{index}")
stmts.extend(sub_stmts)
parts.append(sub_expr)
body = ", ".join(parts)
if isinstance(argument_value, tuple) and len(argument_value) == 1:
body += ","
return stmts, f"{open_b}{body}{close_b}"
if argument_value is None:
return [], "None"
return [], repr(argument_value)
# Structural builder registry. A builder maps ``safe_var`` to
# ``(setup_lines, call_expr, call_args)``: statements placed inside
# ``def _run_<safe_var>()`` before the call, the callable expression,
# and the comma-separated argument substring.
StructuralBuilder = Callable[[str], Tuple[List[str], str, str]]
def builder_nn_module_call(
ctor_expr: str,
input_expr: str,
*,
extra_setup: Tuple[str, ...] = (),
) -> StructuralBuilder:
"""Instantiate ``ctor_expr`` on CUDA and call it with ``input_expr``."""
def build(safe_var: str) -> Tuple[List[str], str, str]:
setup: List[str] = ["import torch.nn as nn"]
setup.extend(extra_setup)
setup.append(f"_mod_{safe_var} = ({ctor_expr}).cuda()")
return setup, f"_mod_{safe_var}", input_expr
return build
def builder_optimizer_step(optimizer_ctor: str) -> StructuralBuilder:
"""Run forward and backward on a small ``Linear``, then ``optim.step()``.
``optimizer_ctor`` must reference ``PARAMS`` where the Linear
parameters should be substituted.
"""
def build(safe_var: str) -> Tuple[List[str], str, str]:
params_expr = f"_m_{safe_var}.parameters()"
optim_src = optimizer_ctor.replace("PARAMS", params_expr)
return (
[
"import torch.nn as nn",
f"_m_{safe_var} = nn.Linear(4, 4).cuda()",
f"_opt_{safe_var} = {optim_src}",
(f"_m_{safe_var}(torch.randn(2, 4, device=device)).sum().backward()"),
],
f"_opt_{safe_var}.step",
"",
)
return build
# Single-GPU process-group bootstrap inlined by distributed builders.
# Uses a per-process ``file://`` rendezvous and destroys the group at
# interpreter shutdown.
DISTRIBUTED_BOOTSTRAP_SETUP: Tuple[str, ...] = (
"import atexit as _atexit",
"import tempfile as _tempfile",
"import torch.distributed as _dist",
"if not _dist.is_initialized():",
" _dist_init_dir = _tempfile.mkdtemp(prefix='torch_trace_cov_dist_')",
" _dist.init_process_group(",
' backend="nccl",',
' init_method=f"file://{_dist_init_dir}/init",',
" rank=0,",
" world_size=1,",
" )",
" def _shutdown_dist():",
" try:",
" if _dist.is_initialized():",
" _dist.destroy_process_group()",
" except Exception:",
" pass",
" _atexit.register(_shutdown_dist)",
)
def builder_distributed(
call_expr: str,
*,
tensor_setup: Tuple[str, ...] = ("_t = torch.randn(4, 4, device=device)",),
call_args: str = "_t",
) -> StructuralBuilder:
"""Run a ``torch.distributed.*`` collective on a single-GPU group."""
def build(_safe_var: str) -> Tuple[List[str], str, str]:
setup = list(DISTRIBUTED_BOOTSTRAP_SETUP) + list(tensor_setup)
return setup, call_expr, call_args
return build
# Per-module builders: name -> ``(ctor_expr, input_expr, extra_setup)``.
NN_MODULE_BUILDERS: Dict[str, Tuple[str, str, Tuple[str, ...]]] = {
# Dense / linear layers
"nn.Module.Linear.__call__": (
"nn.Linear(4, 4)",
"torch.randn(2, 4, device=device)",
(),
),
"nn.Module.Bilinear.__call__": (
"nn.Bilinear(4, 4, 4)",
"torch.randn(2, 4, device=device), torch.randn(2, 4, device=device)",
(),
),
# Convolutional layers
"nn.Module.Conv1d.__call__": (
"nn.Conv1d(3, 8, 3, padding=1)",
"torch.randn(1, 3, 16, device=device)",
(),
),
"nn.Module.Conv2d.__call__": (
"nn.Conv2d(3, 8, 3, padding=1)",
"torch.randn(1, 3, 16, 16, device=device)",
(),
),
"nn.Module.Conv3d.__call__": (
"nn.Conv3d(3, 8, 3, padding=1)",
"torch.randn(1, 3, 4, 8, 8, device=device)",
(),
),
"nn.Module.ConvTranspose2d.__call__": (
"nn.ConvTranspose2d(3, 8, 3, padding=1)",
"torch.randn(1, 3, 16, 16, device=device)",
(),
),
# Normalization
"nn.Module.BatchNorm1d.__call__": (
"nn.BatchNorm1d(4)",
"torch.randn(8, 4, device=device)",
(),
),
"nn.Module.BatchNorm2d.__call__": (
"nn.BatchNorm2d(3)",
"torch.randn(2, 3, 8, 8, device=device)",
(),
),
"nn.Module.LayerNorm.__call__": (
"nn.LayerNorm(8)",
"torch.randn(2, 8, device=device)",
(),
),
"nn.Module.GroupNorm.__call__": (
"nn.GroupNorm(2, 4)",
"torch.randn(2, 4, 8, 8, device=device)",
(),
),
"nn.Module.InstanceNorm2d.__call__": (
"nn.InstanceNorm2d(3)",
"torch.randn(2, 3, 8, 8, device=device)",
(),
),
# Embedding
"nn.Module.Embedding.__call__": (
"nn.Embedding(16, 4)",
"torch.randint(0, 16, (2, 4), device=device)",
(),
),
"nn.Module.EmbeddingBag.__call__": (
"nn.EmbeddingBag(16, 4, mode='mean')",
"torch.randint(0, 16, (8,), device=device), "
"torch.tensor([0, 4], device=device)",
(),
),
# Attention / transformer
"nn.Module.MultiheadAttention.__call__": (
"nn.MultiheadAttention(8, 2, batch_first=True)",
"_q, _q, _q",
("_q = torch.randn(2, 4, 8, device=device)",),
),
"nn.Module.TransformerEncoderLayer.__call__": (
"nn.TransformerEncoderLayer(d_model=8, nhead=2, batch_first=True)",
"torch.randn(2, 4, 8, device=device)",
(),
),
# Recurrent
"nn.Module.RNN.__call__": (
"nn.RNN(4, 4, batch_first=True)",
"torch.randn(2, 3, 4, device=device)",
(),
),
"nn.Module.LSTM.__call__": (
"nn.LSTM(4, 4, batch_first=True)",
"torch.randn(2, 3, 4, device=device)",
(),
),
"nn.Module.GRU.__call__": (
"nn.GRU(4, 4, batch_first=True)",
"torch.randn(2, 3, 4, device=device)",
(),
),
# Activations (trainable / parameter-free)
"nn.Module.ReLU.__call__": (
"nn.ReLU()",
"torch.randn(4, 4, device=device)",
(),
),
"nn.Module.GELU.__call__": (
"nn.GELU()",
"torch.randn(4, 4, device=device)",
(),
),
"nn.Module.SiLU.__call__": (
"nn.SiLU()",
"torch.randn(4, 4, device=device)",
(),
),
"nn.Module.Softmax.__call__": (
"nn.Softmax(dim=-1)",
"torch.randn(4, 4, device=device)",
(),
),
"nn.Module.LogSoftmax.__call__": (
"nn.LogSoftmax(dim=-1)",
"torch.randn(4, 4, device=device)",
(),
),
# Dropout
"nn.Module.Dropout.__call__": (
"nn.Dropout(p=0.5)",
"torch.randn(4, 4, device=device)",
(),
),
# Pooling
"nn.Module.MaxPool2d.__call__": (
"nn.MaxPool2d(2)",
"torch.randn(1, 3, 8, 8, device=device)",
(),
),
"nn.Module.AvgPool2d.__call__": (
"nn.AvgPool2d(2)",
"torch.randn(1, 3, 8, 8, device=device)",
(),
),
"nn.Module.AdaptiveAvgPool2d.__call__": (
"nn.AdaptiveAvgPool2d((4, 4))",
"torch.randn(1, 3, 8, 8, device=device)",
(),
),
# Upsample / pixel shuffle / reshape
"nn.Module.Upsample.__call__": (
"nn.Upsample(scale_factor=2, mode='nearest')",
"torch.randn(1, 3, 4, 4, device=device)",
(),
),
"nn.Module.PixelShuffle.__call__": (
"nn.PixelShuffle(2)",
"torch.randn(1, 4, 4, 4, device=device)",
(),
),
"nn.Module.Flatten.__call__": (
"nn.Flatten()",
"torch.randn(2, 3, 4, 4, device=device)",
(),
),
# Containers (just to exercise the wrapper forward)
"nn.Module.Sequential.__call__": (
"nn.Sequential(nn.Linear(4, 4), nn.ReLU(), nn.Linear(4, 4))",
"torch.randn(2, 4, device=device)",
(),
),
}
# Per-optimizer builders.
OPTIMIZER_BUILDERS: Dict[str, str] = {
"Optimizer.SGD.step": "torch.optim.SGD(PARAMS, lr=0.01)",
"Optimizer.Adam.step": "torch.optim.Adam(PARAMS, lr=1e-3)",
"Optimizer.AdamW.step": "torch.optim.AdamW(PARAMS, lr=1e-3)",
"Optimizer.RMSprop.step": "torch.optim.RMSprop(PARAMS, lr=1e-3)",
"Optimizer.Adagrad.step": "torch.optim.Adagrad(PARAMS, lr=1e-2)",
"Optimizer.Adadelta.step": "torch.optim.Adadelta(PARAMS, lr=1.0)",
"Optimizer.NAdam.step": "torch.optim.NAdam(PARAMS, lr=1e-3)",
"Optimizer.RAdam.step": "torch.optim.RAdam(PARAMS, lr=1e-3)",
"Optimizer.Adamax.step": "torch.optim.Adamax(PARAMS, lr=1e-3)",
"Optimizer.ASGD.step": "torch.optim.ASGD(PARAMS, lr=1e-3)",
}
# Autograd builders.
def builder_autograd_grad(safe_var: str) -> Tuple[List[str], str, str]:
"""torch.autograd.grad(loss, params) on a 1-layer MLP."""
return (
[
"import torch.nn as nn",
f"_m_{safe_var} = nn.Linear(4, 4).cuda()",
(
f"_loss_{safe_var} = _m_{safe_var}("
"torch.randn(2, 4, device=device)).sum()"
),
],
"torch.autograd.grad",
f"_loss_{safe_var}, list(_m_{safe_var}.parameters())",
)
def builder_autograd_backward(safe_var: str) -> Tuple[List[str], str, str]:
"""torch.autograd.backward([loss]) — functional form of .backward()."""
return (
[
"import torch.nn as nn",
f"_m_{safe_var} = nn.Linear(4, 4).cuda()",
(
f"_loss_{safe_var} = _m_{safe_var}("
"torch.randn(2, 4, device=device)).sum()"
),
],
"torch.autograd.backward",
f"[_loss_{safe_var}]",
)
def builder_autograd_function_apply(safe_var: str) -> Tuple[List[str], str, str]:
"""Define a tiny torch.autograd.Function and call its .apply."""
return (
[
f"class _Fn_{safe_var}(torch.autograd.Function):",
" @staticmethod",
" def forward(ctx, x):",
" ctx.save_for_backward(x)",
" return x * 2.0 + 1.0",
" @staticmethod",
" def backward(ctx, grad_output):",
" (x,) = ctx.saved_tensors",
" return grad_output * 2.0",
(f"_x_{safe_var} = torch.randn(4, 4, device=device, requires_grad=True)"),
],
f"_Fn_{safe_var}.apply",
f"_x_{safe_var}",
)
def builder_autograd_functional_jacobian(
safe_var: str,
) -> Tuple[List[str], str, str]:
"""torch.autograd.functional.jacobian(f, x) for a 1-layer MLP wrapper."""
return (
[
"import torch.nn as nn",
f"_m_{safe_var} = nn.Linear(4, 4).cuda()",
f"_x_{safe_var} = torch.randn(4, device=device)",
],
"torch.autograd.functional.jacobian",
f"lambda z: _m_{safe_var}(z).sum(), _x_{safe_var}",
)
def builder_autograd_functional_hessian(
safe_var: str,
) -> Tuple[List[str], str, str]:
"""torch.autograd.functional.hessian(f, x) of a quadratic form."""
return (
[f"_x_{safe_var} = torch.randn(4, device=device)"],
"torch.autograd.functional.hessian",
f"lambda z: (z * z).sum(), _x_{safe_var}",
)
def builder_autograd_functional_vjp(safe_var: str) -> Tuple[List[str], str, str]:
"""torch.autograd.functional.vjp(f, x, v) for a tiny MLP."""
return (
[
"import torch.nn as nn",
f"_m_{safe_var} = nn.Linear(4, 4).cuda()",
f"_x_{safe_var} = torch.randn(4, device=device)",
f"_v_{safe_var} = torch.randn(4, device=device)",
],
"torch.autograd.functional.vjp",
f"lambda z: _m_{safe_var}(z), _x_{safe_var}, _v_{safe_var}",
)
def builder_autograd_functional_jvp(safe_var: str) -> Tuple[List[str], str, str]:
"""torch.autograd.functional.jvp(f, x, v) for a tiny MLP."""
return (
[
"import torch.nn as nn",
f"_m_{safe_var} = nn.Linear(4, 4).cuda()",
f"_x_{safe_var} = torch.randn(4, device=device)",
f"_v_{safe_var} = torch.randn(4, device=device)",
],
"torch.autograd.functional.jvp",
f"lambda z: _m_{safe_var}(z), _x_{safe_var}, _v_{safe_var}",
)
# Compile / JIT builders.
def builder_torch_compile(safe_var: str) -> Tuple[List[str], str, str]:
"""Compile a trivial fn with torch.compile and invoke it once."""
return (
[
f"def _fn_{safe_var}(x):",
" return (x * 2.0 + 1.0).relu().sum()",
f"_cfn_{safe_var} = torch.compile(_fn_{safe_var})",
f"_x_{safe_var} = torch.randn(64, device=device)",
],
f"_cfn_{safe_var}",
f"_x_{safe_var}",
)
def builder_torch_jit_trace(safe_var: str) -> Tuple[List[str], str, str]:
"""Trace a tiny Linear with torch.jit.trace and run the traced module."""
return (
[
"import torch.nn as nn",
f"_m_{safe_var} = nn.Linear(4, 4).cuda().eval()",
f"_ex_{safe_var} = torch.randn(2, 4, device=device)",
(f"_tr_{safe_var} = torch.jit.trace(_m_{safe_var}, _ex_{safe_var})"),
],
f"_tr_{safe_var}",
f"_ex_{safe_var}",
)
def builder_torch_jit_script(safe_var: str) -> Tuple[List[str], str, str]:
"""Script a pure-python fn with torch.jit.script and run it on the GPU."""
return (
[
f"def _fn_{safe_var}(x):",
" return x * 2.0 + 1.0",
f"_sc_{safe_var} = torch.jit.script(_fn_{safe_var})",
f"_x_{safe_var} = torch.randn(4, 4, device=device)",
],
f"_sc_{safe_var}",
f"_x_{safe_var}",
)
# CUDA utility builders.
def builder_cuda_synchronize(_safe_var: str) -> Tuple[List[str], str, str]:
return [], "torch.cuda.synchronize", ""
def builder_cuda_current_device(_safe_var: str) -> Tuple[List[str], str, str]:
return [], "torch.cuda.current_device", ""
def builder_cuda_device_count(_safe_var: str) -> Tuple[List[str], str, str]:
return [], "torch.cuda.device_count", ""
def builder_cuda_empty_cache(_safe_var: str) -> Tuple[List[str], str, str]:
return [], "torch.cuda.empty_cache", ""
def builder_cuda_memory_allocated(_safe_var: str) -> Tuple[List[str], str, str]:
return [], "torch.cuda.memory_allocated", ""
def builder_cuda_reset_peak_memory_stats(
_safe_var: str,
) -> Tuple[List[str], str, str]:
return [], "torch.cuda.reset_peak_memory_stats", ""
def builder_cuda_manual_seed(_safe_var: str) -> Tuple[List[str], str, str]:
return [], "torch.cuda.manual_seed", "0"
def builder_cuda_set_device(_safe_var: str) -> Tuple[List[str], str, str]:
return [], "torch.cuda.set_device", "0"
def builder_cuda_stream(safe_var: str) -> Tuple[List[str], str, str]:
"""Create a torch.cuda.Stream and submit one op on it.
The stream context, kernel, and sync run in setup; the emitted call
is a no-op.
"""
return (
[
f"_stream_{safe_var} = torch.cuda.Stream()",
f"with torch.cuda.stream(_stream_{safe_var}):",
f" _y_{safe_var} = torch.randn(8, 8, device=device) * 2.0",
f"_stream_{safe_var}.synchronize()",
],
"(lambda: None)",
"",
)
def builder_cuda_event(safe_var: str) -> Tuple[List[str], str, str]:
"""Record and query a torch.cuda.Event around a small op."""
return (
[
f"_ev_start_{safe_var} = torch.cuda.Event(enable_timing=True)",
f"_ev_end_{safe_var} = torch.cuda.Event(enable_timing=True)",
f"_ev_start_{safe_var}.record()",
f"_y_{safe_var} = torch.randn(8, 8, device=device) * 2.0",
f"_ev_end_{safe_var}.record()",
f"_ev_end_{safe_var}.synchronize()",
],
"(lambda: None)",
"",
)
# Registry assembly.
def build_structural_builder_registry() -> Dict[str, StructuralBuilder]:
"""Flat name -> StructuralBuilder map. Duplicate names raise."""
registry: Dict[str, StructuralBuilder] = {}
# Generic fallbacks used when no class-specific builder applies.
def _generic_nn_call(safe_var: str) -> Tuple[List[str], str, str]:
return (
[
"import torch.nn as nn",
f"_model_{safe_var} = nn.Linear(4, 4).cuda()",
],
f"_model_{safe_var}",
"torch.randn(2, 4, device=device)",
)
def _generic_optimizer_step(safe_var: str) -> Tuple[List[str], str, str]:
return (
[
"import torch.nn as nn",
f"_m_{safe_var} = nn.Linear(4, 4).cuda()",
f"_opt_{safe_var} = torch.optim.SGD(",
f" _m_{safe_var}.parameters(),",
" lr=0.01,",
")",
f"_m_{safe_var}(",
" torch.randn(2, 4, device=device),",
").sum().backward()",
],
f"_opt_{safe_var}.step",
"",
)
def _generic_tensor_backward(safe_var: str) -> Tuple[List[str], str, str]:
return (
[
f"_lin_{safe_var} = torch.nn.Linear(4, 4).cuda()",
f"_loss_{safe_var} = _lin_{safe_var}(",
" torch.randn(2, 4, device=device),",
").sum()",
],
f"_loss_{safe_var}.backward",
"",
)
registry["nn.Module.__call__"] = _generic_nn_call
registry["Optimizer.step"] = _generic_optimizer_step
registry["torch.Tensor.backward"] = _generic_tensor_backward
for name, (ctor, input_expr, extra_setup) in NN_MODULE_BUILDERS.items():
if name in registry:
raise AssertionError(f"duplicate structural builder: {name!r}")
registry[name] = builder_nn_module_call(
ctor,
input_expr,
extra_setup=extra_setup,
)
for name, optim_ctor in OPTIMIZER_BUILDERS.items():
if name in registry:
raise AssertionError(f"duplicate structural builder: {name!r}")
registry[name] = builder_optimizer_step(optim_ctor)
for name, fn in (
("torch.autograd.grad", builder_autograd_grad),
("torch.autograd.backward", builder_autograd_backward),
("torch.autograd.Function.apply", builder_autograd_function_apply),
(
"torch.autograd.functional.jacobian",
builder_autograd_functional_jacobian,
),
(
"torch.autograd.functional.hessian",
builder_autograd_functional_hessian,
),
("torch.autograd.functional.vjp", builder_autograd_functional_vjp),
("torch.autograd.functional.jvp", builder_autograd_functional_jvp),
("torch.compile", builder_torch_compile),
("torch.jit.trace", builder_torch_jit_trace),
("torch.jit.script", builder_torch_jit_script),
("torch.distributed.all_reduce", builder_distributed("_dist.all_reduce")),
(
"torch.distributed.broadcast",
builder_distributed(
"_dist.broadcast",
call_args="_t, src=0",
),
),
(
"torch.distributed.reduce",
builder_distributed(
"_dist.reduce",
call_args="_t, dst=0",
),
),
(
"torch.distributed.all_gather",
builder_distributed(
"_dist.all_gather",
tensor_setup=(
"_t = torch.randn(4, 4, device=device)",
"_out = [torch.empty_like(_t)]",
),
call_args="_out, _t",
),
),
(
"torch.distributed.reduce_scatter",
builder_distributed(
"_dist.reduce_scatter",
tensor_setup=(
"_t = torch.randn(4, 4, device=device)",
"_inp = [torch.randn(4, 4, device=device)]",
),
call_args="_t, _inp",
),
),
(
"torch.distributed.barrier",
builder_distributed(
"_dist.barrier",
tensor_setup=(),
call_args="",
),
),
("torch.cuda.synchronize", builder_cuda_synchronize),
("torch.cuda.current_device", builder_cuda_current_device),
("torch.cuda.device_count", builder_cuda_device_count),
("torch.cuda.empty_cache", builder_cuda_empty_cache),
("torch.cuda.memory_allocated", builder_cuda_memory_allocated),
(
"torch.cuda.reset_peak_memory_stats",
builder_cuda_reset_peak_memory_stats,
),
("torch.cuda.manual_seed", builder_cuda_manual_seed),
("torch.cuda.set_device", builder_cuda_set_device),
("torch.cuda.Stream", builder_cuda_stream),
("torch.cuda.Event", builder_cuda_event),
):
if name in registry:
raise AssertionError(f"duplicate structural builder: {name!r}")
registry[name] = fn
return registry
STRUCTURAL_BUILDERS: Dict[str, StructuralBuilder] = build_structural_builder_registry()
def emit_structural_preamble(
op_name: str,
safe_var: str,
) -> Tuple[List[str], str, str]:
"""Dispatch into ``STRUCTURAL_BUILDERS`` for one structural workload."""
builder = STRUCTURAL_BUILDERS.get(op_name)
if builder is None:
return [], op_name, ""
return builder(safe_var)
def build_workload_module_lines(operators: List[OpEntry]) -> List[str]:
"""Emit source lines for a minimal ``coverage_workload.py`` module."""
lines = [
"import os",
"import sys",
# Set before ``import torch``; HIP reads it at runtime init.
'os.environ.setdefault("AMD_SERIALIZE_KERNEL", "3")',
"import torch",
"",
'device = "cuda"',
"",
]
op_name_literals: List[str] = []
runner_fn_names: List[str] = []
for op in operators:
build_result = build_args_for_op(op)
if build_result is None:
continue
args, kwargs = build_result
safe_var = op.name.replace(".", "_").replace("::", "_")
op_setup: List[str] = []
arg_strs = []
for arg_index, arg_value in enumerate(args):
vname = f"_arg_{safe_var}_{arg_index}"
stmts, expr = serialize_arg(arg_value, vname)
op_setup.extend(stmts)
arg_strs.append(expr)
kwarg_strs: List[str] = []
for keyword, value in kwargs.items():
vname = f"_kwarg_{safe_var}_{keyword}"
stmts, expr = serialize_arg(value, vname)
op_setup.extend(stmts)
kwarg_strs.append(f"{keyword}={expr}")
call_args = ", ".join(arg_strs + kwarg_strs)
if op.name.startswith("torch.ops."):
call_expr = aten_packet_call_path(op.name)
elif op.category == "structural":
extra_setup, call_expr, call_args = emit_structural_preamble(
op.name,
safe_var,
)
op_setup.extend(extra_setup)
else:
call_expr = op.name
if op.category == "structural" and not call_args.strip():
call_segments: List[str] = []
elif op.category == "structural":
call_segments = [call_args]
else:
call_segments = arg_strs + kwarg_strs
call_body_lines = workload_emit_multiline_call(call_expr, call_segments)
fn_name = f"_run_{safe_var}"
lines.append(f"def {fn_name}():")
for setup_line in op_setup:
lines.append(f" {setup_line}")
for call_body_line in call_body_lines:
lines.append(f" {call_body_line}")
lines.append("")
op_name_literals.append(repr(op.name))
runner_fn_names.append(fn_name)
lines.append("OP_NAMES = [")
for lit in op_name_literals:
lines.append(f" {lit},")
lines.append("]")
lines.append("")
lines.append("ALL_OPS = [")
for fn in runner_fn_names:
lines.append(f" {fn},")
lines.append("]")
lines.append("")
lines.append("def probe_device():")
lines.append(" t = torch.empty(1, device=device)")
lines.append(" t.add_(1)")
lines.append(" torch.cuda.synchronize()")
lines.append("")
lines.append("def run_all():")
lines.append(" for op_label, fn in zip(OP_NAMES, ALL_OPS):")
lines.append(
' print(f"[torch-trace-cov] START {op_label}", '
"file=sys.stderr, flush=True)"
)
lines.append(" try:")
lines.append(" fn()")
lines.append(" torch.cuda.synchronize()")
lines.append(" except Exception as exc:")
lines.append(
' print(f"[torch-trace-cov] FAIL {op_label} '
'{type(exc).__name__}: {exc}", file=sys.stderr, flush=True)'
)
lines.append(" try:")
lines.append(" probe_device()")
lines.append(" except Exception as exc2:")
lines.append(
' print(f"[torch-trace-cov] DEVICE_DEAD after {op_label} '
'{type(exc2).__name__}: {exc2}", file=sys.stderr, flush=True)'
)
lines.append(" break")
lines.append(" continue")
lines.append(
' print(f"[torch-trace-cov] OK {op_label}", '
"file=sys.stderr, flush=True)"
)
lines.append("")
lines.append('if __name__ == "__main__":')
lines.append(" run_all()")
lines.append("")
return lines
def write_coverage_ground_truth_runner_script(path: str) -> None:
"""Write the static torch.profiler runner next to coverage_workload.py."""
Path(path).write_text(
COVERAGE_GROUND_TRUTH_RUNNER_SOURCE + "\n",
encoding="utf-8",
)
def write_coverage_workload_artifacts(
operators: List[OpEntry],
workload_script_path: str,
ground_truth_runner_script_path: str,
) -> None:
"""Write coverage_workload.py and coverage_ground_truth_runner.py."""
Path(workload_script_path).write_text(
"\n".join(build_workload_module_lines(operators)),
encoding="utf-8",
)
write_coverage_ground_truth_runner_script(ground_truth_runner_script_path)
# -- ROCTX marker CSV parsing --
def with_correlation_id_standard_name(marker_df: pd.DataFrame) -> pd.DataFrame:
"""Normalise the correlation-id column name across rocprof-compute versions."""
if "Correlation_Id" not in marker_df.columns:
return marker_df
return marker_df.rename(columns={"Correlation_Id": "Correlation_ID"})
def segments_from_function_cell(func: object) -> List[str]:
"""Return chain segments of a ROCTX ``Function`` cell, in order."""
if not isinstance(func, str):
return []
op_path = func.split(":#")[0] if ":#" in func else func
return [seg for seg in (s.strip() for s in op_path.split("/")) if seg]
def leaf_from_function_cell(func: object) -> Optional[str]:
"""Return the last path segment of a ROCTX Function cell, or None."""
segments = segments_from_function_cell(func)
return segments[-1] if segments else None
def collect_marker_ops_and_correlations(
marker_df: pd.DataFrame,
) -> tuple[set[str], dict[str, set]]:
"""Return (marker_leaves, segment -> correlation_ids).
The correlation map attributes each row's Correlation_ID to every
segment of its chain so a kernel inside a nested marker is visible
when the harness probes the enclosing op. Monotonic: only adds
correlations the outer marker genuinely enclosed.
"""
marker_ops: set[str] = set()
op_to_corr: dict[str, set] = {}
func_col = marker_df.get("Function")
corr_col = marker_df.get("Correlation_ID")
if func_col is None:
return marker_ops, op_to_corr
for row_index, function_cell in enumerate(func_col):
segments = segments_from_function_cell(function_cell)
if not segments:
continue
marker_ops.add(segments[-1])
if corr_col is None:
continue
correlation_id = corr_col.iloc[row_index]
if not pd.notna(correlation_id):
continue
for segment in segments:
op_to_corr.setdefault(segment, set()).add(correlation_id)
return marker_ops, op_to_corr
def merge_kernel_names_for_correlation_ids(
correlation_ids: set,
correlation_id_to_kernels: dict,
) -> set[str]:
"""Union the kernel-name sets for every correlation id in correlation_ids."""
kernels: set[str] = set()
for correlation_id in correlation_ids:
kernels |= correlation_id_to_kernels.get(correlation_id, set())
return kernels
def kernels_by_marker_leaf(
op_to_corr: dict[str, set],
counter_files: list[Path],
) -> dict[str, set[str]]:
"""Return leaf -> {kernel_name} by joining marker correlations to counters."""
counter_df = pd.concat(
[pd.read_csv(f) for f in counter_files],
ignore_index=True,
)
counter_df = with_correlation_id_standard_name(counter_df)
if "Kernel_Name" not in counter_df.columns:
return {}
all_corr_ids: set = set()
for ids in op_to_corr.values():
all_corr_ids |= ids
matched = counter_df[counter_df["Correlation_ID"].isin(all_corr_ids)]
correlation_id_to_kernels: dict = {}
for correlation_id, kernel_name in zip(
matched["Correlation_ID"],
matched["Kernel_Name"],
):
if not pd.notna(kernel_name):
continue
correlation_id_to_kernels.setdefault(correlation_id, set()).add(kernel_name)
op_to_kernels: dict[str, set[str]] = {}
for leaf, correlation_ids_for_leaf in op_to_corr.items():
kernels = merge_kernel_names_for_correlation_ids(
correlation_ids_for_leaf,
correlation_id_to_kernels,
)
if kernels:
op_to_kernels[leaf] = kernels
return op_to_kernels
def _read_roctx_marker_dataframe(workload_dir: str) -> Optional[pd.DataFrame]:
"""Concatenate ``marker_api_trace.csv`` files under ``workload_dir``."""
marker_files = sorted(Path(workload_dir).glob("**/*marker_api_trace.csv"))
if not marker_files:
return None
marker_df = pd.concat(
[pd.read_csv(f) for f in marker_files],
ignore_index=True,
)
return with_correlation_id_standard_name(marker_df)
def parse_roctx_markers(
workload_dir: str,
) -> Tuple[Dict[str, Set[str]], Set[str]]:
"""Parse rocprof-compute marker and counter CSVs under ``workload_dir``.
Returns ``(op_to_kernels, marker_ops)`` where ``marker_ops`` lists
every distinct marker leaf and ``op_to_kernels`` maps a leaf to its
correlated GPU kernel names.
"""
marker_df = _read_roctx_marker_dataframe(workload_dir)
if marker_df is None:
return {}, set()
counter_files = sorted(Path(workload_dir).glob("**/*counter_collection.csv"))
marker_ops, op_to_corr = collect_marker_ops_and_correlations(marker_df)
op_to_kernels: Dict[str, Set[str]] = {}
if counter_files and op_to_corr:
op_to_kernels = kernels_by_marker_leaf(op_to_corr, counter_files)
return op_to_kernels, marker_ops
# Sentinel leaf-context labels emitted by the C++ RecordFunction tier.
# Source of truth: roctx_recordfn.cpp ``default_leaf_context``.
C_TIER_LEAF_CONTEXT_SENTINELS: Tuple[str, ...] = (
"aten:0",
"aten.nested:0",
"autograd.engine:0",
"autograd.bwd:0",
)
# Backward-pass sentinels.
C_TIER_BACKWARD_SENTINELS: Tuple[str, ...] = (
"autograd.engine:0",
"autograd.bwd:0",
)
def cpp_tier_signature_in_markers(
marker_df: pd.DataFrame,
) -> Tuple[bool, Dict[str, int]]:
"""Scan ``marker_df["Function"]`` for ``@<sentinel>`` tokens.
Returns ``(cpp_tier_active, sentinel_counts)``. The ``@`` prefix
avoids false positives against file paths. A missing column returns
``(False, {})``.
"""
counts: Dict[str, int] = {}
if marker_df is None or marker_df.empty:
return False, counts
func_col = marker_df.get("Function")
if func_col is None:
return False, counts
tokens = tuple(f"@{sentinel}" for sentinel in C_TIER_LEAF_CONTEXT_SENTINELS)
for cell in func_col:
if not isinstance(cell, str):
continue
for sentinel, token in zip(C_TIER_LEAF_CONTEXT_SENTINELS, tokens):
n = cell.count(token)
if n:
counts[sentinel] = counts.get(sentinel, 0) + n
return bool(counts), counts
def detect_cpp_tier_signature(
workload_dir: str,
) -> Tuple[bool, Dict[str, int]]:
"""Workload-dir entry point for :func:`cpp_tier_signature_in_markers`."""
marker_df = _read_roctx_marker_dataframe(workload_dir)
if marker_df is None:
return False, {}
return cpp_tier_signature_in_markers(marker_df)
def format_cpp_tier_signature_report(
cpp_tier_active: bool,
sentinel_counts: Dict[str, int],
) -> Tuple[str, ...]:
"""Render the C++ tier outcome as ``NONE``, ``FORWARD-ONLY``, or ``FULL``."""
if not cpp_tier_active:
return (
"C++ tier signature: NONE; workload ran on the Python "
"TorchDispatchMode fallback.",
)
backward_observed = any(
sentinel_counts.get(name, 0) > 0 for name in C_TIER_BACKWARD_SENTINELS
)
forward_observed = any(
sentinel_counts.get(name, 0) > 0 for name in ("aten:0", "aten.nested:0")
)
parts = []
if forward_observed:
parts.append(
f"forward (aten:0={sentinel_counts.get('aten:0', 0)}, "
f"aten.nested:0={sentinel_counts.get('aten.nested:0', 0)})"
)
if backward_observed:
parts.append(
f"backward (autograd.engine:0="
f"{sentinel_counts.get('autograd.engine:0', 0)}, "
f"autograd.bwd:0={sentinel_counts.get('autograd.bwd:0', 0)})"
)
summary = " ".join(parts) if parts else "(no sentinels counted)"
if backward_observed:
return (f"C++ tier signature: FULL — {summary}.",)
return (
f"C++ tier signature: FORWARD-ONLY — {summary}.",
" No autograd.engine:0 / autograd.bwd:0 sentinels observed.",
)
# -- Per-operator comparison --
def torch_trace_coverage_color_enabled() -> bool:
"""True when ANSI colors are allowed for coverage report lines (stdout)."""
if os.environ.get("NO_COLOR", "").strip():
return False
force = os.environ.get("FORCE_COLOR", "").strip().lower()
if force in ("1", "true", "yes"):
return True
return sys.stdout.isatty()
def torch_trace_coverage_red(text: str) -> str:
if not torch_trace_coverage_color_enabled():
return text
return f"\033[31m{text}\033[0m"
def coverage_log_pass(op_name: str, *, note: str = "") -> Tuple[str, ...]:
"""Stdout lines for a passing operator (pytest -s).
note may contain newlines; each line is rendered as a separate
indented stdout line so multi-line rationales remain readable.
"""
if not note:
return (f"PASS {op_name}",)
body = note.splitlines() or [note]
lines = [f"PASS {op_name}"]
lines.extend(f" {ln}" for ln in body)
return tuple(lines)
def coverage_log_fail(op_name: str, reason: str) -> Tuple[str, ...]:
"""Stdout lines for a failing operator; reason is red when color is allowed."""
body = reason.splitlines() or [reason]
first = f"FAIL {op_name}"
if len(body) == 1:
return (first, f" {torch_trace_coverage_red(body[0])}")
lines = [first]
for ln in body:
lines.append(f" {torch_trace_coverage_red(ln)}")
return tuple(lines)
def coverage_log_skip(op_name: str, reason: str) -> Tuple[str, ...]:
"""Stdout lines for a skipped operator."""
body = reason.splitlines() or [reason]
lines = [f"SKIP {op_name}"]
lines.extend(f" {ln}" for ln in body)
return tuple(lines)
def describe_missing_or_errored_op(
op: OpEntry,
ground_truth_entry: Optional[Dict[str, Any]],
) -> str:
"""Human-readable SKIP reason: workload error, OpSpec skip, or missing builder."""
if ground_truth_entry is not None and "error" in ground_truth_entry:
return f"workload raised at runtime: {ground_truth_entry['error']}"
if op.category == "structural":
return "structural op missing from generated workload"
overload_key = aten_overload_key(op.name)
short_name = aten_op_short_name(op.name) or op.name.rsplit(".", 1)[-1]
spec = None
if overload_key is not None:
spec = OP_SPECS.get(overload_key)
if spec is None:
spec = OP_SPECS.get(short_name)
if spec is not None and spec.skip:
return f"OpSpec skip: {spec.skip}"
if spec is not None and spec.build is not None:
return "OpSpec builder exists but op was not emitted into the workload"
return (
"no OpSpec.build entry in torch_trace_coverage_utils.OP_SPECS; "
"argument synthesis is restricted to per-operator builders"
)
# Skip-reason categorization. Each bucket is identified by a stable
# keyword in the outcome reason string.
SKIP_CATEGORY_LABELS: Dict[str, str] = {
"argument_builder_gap": (
"argument-builder coverage gap "
"(no OpSpec.build registered for this op in OP_SPECS — "
"register a per-operator builder to enable coverage)"
),
"workload_runtime_error": (
"workload runtime error (synthetic workload raised before profiling completed)"
),
"upstream_rocm_bug": (
"upstream ROCm bug (SIGSEGV / HIP illegal memory access under torch.profiler)"
),
"hardware_incompatible_cuda_only": (
"hardware-incompatible "
"(CUDA-only feature: CUTLASS / cuDNN / xFormers / cuSPARSELt / "
"FlashAttention / fp8 / packed int4-int8 / grouped matmul)"
),
"stateful_op_dependency": (
"stateful-op dependency "
"(backward op needs state / output from a matched forward run)"
),
"external_library_required": (
"external library required (e.g. MAGMA) — not present in this build"
),
"incomplete_op_spec": (
"OpSpec pending (builder declared but shape / argument contract TBD)"
),
"no_kernel_in_ground_truth": (
"no GPU kernels in ground truth "
"(torch.profiler reported zero kernels for this op)"
),
"structural_marker_gap": (
"structural marker gap "
"(structural pattern not exercised or no ROCTX marker emitted)"
),
"opspec_builder_present_but_not_emitted": (
"OpSpec builder present but op was not emitted into the workload"
),
"other": "other / uncategorized",
}
def categorize_skip_reason(reason: str) -> str:
"""Return the SKIP-category key for an ``outcome.reason`` string."""
r = reason.lower()
if "no opspec.build entry" in r:
return "argument_builder_gap"
if "workload raised at runtime" in r:
return "workload_runtime_error"
if "no gpu kernels in ground truth" in r:
return "no_kernel_in_ground_truth"
if "no roctx marker" in r or "structural op missing" in r:
return "structural_marker_gap"
if "opspec builder exists but op was not emitted" in r:
return "opspec_builder_present_but_not_emitted"
if any(
k in r
for k in (
"sigsegv",
"hip 719",
"illegal memory access",
"kernel race",
)
):
return "upstream_rocm_bug"
if is_hardware_incompatible_cuda_only(r):
return "hardware_incompatible_cuda_only"
if any(
k in r
for k in (
"matching forward",
"matched forward",
"matched fwd",
"offset2bag",
"save_mean",
"save_var",
"reservespace",
"cdist output",
"nested-tensor constructor",
"matching offsets/lengths",
)
):
return "stateful_op_dependency"
if "magma" in r or "libmagma" in r:
return "external_library_required"
if "shape contract" in r:
return "incomplete_op_spec"
return "other"
def _short_op_name(op_name: str) -> str:
"""Strip the torch.ops.aten. prefix for compact breakdown listings."""
prefix = "torch.ops.aten."
return op_name[len(prefix) :] if op_name.startswith(prefix) else op_name
# Family classifier for the ``argument_builder_gap`` bucket. Maps each
# op name to a structural family and a short fix-pattern hint.
_MISSING_BUILDER_FAMILY_HINTS: Dict[str, str] = {
"out / output-buffer overload (.out / .grad_input)": (
"wrap the default-overload builder and pass out=torch.empty_like(...)"
),
"autograd backward (*_backward)": (
"synthesize grad_output + the forward inputs/outputs the schema needs"
),
"in-place (trailing underscore)": (
"same args as the out-of-place sibling; just call the in-place variant"
),
"foreach / vectorized list ops": (
"wrap inputs in [t1, t2, t3] lists; lengths must match across args"
),
"special functions (Bessel / Chebyshev / Hermite / ...)": (
"single Tensor → Tensor; randn(d, N) works for most "
"(some need x ∈ [-1, 1] or x > 0)"
),
"linear algebra (linalg_*)": (
"well-conditioned square matrices (A = randn(N,N) + N*eye(N))"
),
"softmax / log_softmax family": (
"(input, dim, half_to_float=False) for forward; backward needs "
"grad_output + output (not input)"
),
"batch normalization": (
"input (N,C,*) + weight/bias (C,) + running_mean/var (C,) + "
"training flag; backward also needs save_mean / save_invstd"
),
"layer / group / instance normalization": (
"input (N,C,*) + normalized_shape + weight/bias matching the normalized dims"
),
"FFT family": ("complex input, or real input with appropriate signal_ndim"),
"sparse tensor ops": ("explicit indices/values constructor; never randn"),
"quantization": ("needs scale + zero_point tensors matching the quantized dtype"),
"distribution / random sampler": (
"shape arg + optional generator; some take param tensors (mean, std)"
),
"convolution": ("input (N,C,H,W) + weight (C_out,C_in,Kh,Kw) + optional bias"),
"pooling": ("input (N,C,H,W) + kernel_size + stride/padding"),
"loss function": ("input + target with matching shapes / dtypes"),
"indexing / gather / scatter": (
"source tensor + int64 index tensor; bounds must lie inside source"
),
"cumulative reduction": ("input tensor + dim; dtype kwarg optional"),
"reduction along dim": ("input tensor + dim (int or list-of-int) + keepdim kwarg"),
"elementwise — Tensor overload": ("two same-shape tensors"),
"elementwise — Scalar overload": ("tensor + python scalar"),
"elementwise — unary float Tensor → Tensor": (
"single template: lambda d: ([f(d, N, N)], {}) — most accept any "
"float tensor (a few need x ∈ [-1, 1] or x > 0)"
),
"reduction (whole-tensor)": (
"single template: lambda d: ([f(d, N, N)], {}) — operates over "
"all elements (some have .dim variants which fall in the "
"'reduction along dim' bucket)"
),
"other / uncategorized": ("inspect the schema; no shared template applies"),
}
def missing_builder_family(op_name: str) -> str:
"""Return a family bucket key for an op missing an ``OpSpec.build`` entry."""
short = aten_op_short_name(op_name) or op_name.rsplit(".", 1)[-1]
overload_key = aten_overload_key(op_name)
suffix = (
overload_key.split(".", 1)[1] if overload_key and "." in overload_key else ""
)
# Highest-priority structural buckets: same builder pattern fits the
# entire family regardless of which op short name the variant belongs
# to.
if suffix == "out" or suffix.endswith("_out") or suffix == "grad_input":
return "out / output-buffer overload (.out / .grad_input)"
if short.endswith("_backward"):
return "autograd backward (*_backward)"
# Trailing-underscore in-place ops, e.g. add_, sin_, log_normal_.
# Filter out leading-underscore private ops to avoid mis-bucketing
# things like _fft_r2c.
if short.endswith("_") and not short.startswith("_"):
return "in-place (trailing underscore)"
if short.startswith("_foreach_"):
return "foreach / vectorized list ops"
# Domain-specific families. Match private (_underscored) variants to
# the same family as their public sibling so e.g. _linalg_det groups
# with linalg_*.
if short.startswith("special_"):
return "special functions (Bessel / Chebyshev / Hermite / ...)"
if short.startswith("linalg_") or short.startswith("_linalg_"):
return "linear algebra (linalg_*)"
if "softmax" in short:
return "softmax / log_softmax family"
if "batch_norm" in short:
return "batch normalization"
if "layer_norm" in short or "group_norm" in short or "instance_norm" in short:
return "layer / group / instance normalization"
if "fft" in short:
return "FFT family"
if "sparse" in short:
return "sparse tensor ops"
if (
short.startswith("quantize")
or "quantized" in short
or "_pack" in short
or "_unpack" in short
or "dequantize" in short
):
return "quantization"
if any(
k in short
for k in (
"normal",
"uniform",
"cauchy",
"exponential",
"geometric",
"bernoulli",
"multinomial",
"random",
"poisson",
)
):
return "distribution / random sampler"
if "conv" in short or "convolution" in short:
return "convolution"
if "pool" in short:
return "pooling"
if short.endswith("_loss") or short.endswith("_loss_backward"):
return "loss function"
if (
short.startswith("index_")
or short.startswith("_index_")
or "gather" in short
or "scatter" in short
):
return "indexing / gather / scatter"
if (
short.startswith("cumsum")
or short.startswith("cumprod")
or short.startswith("cummax")
or short.startswith("cummin")
or short.startswith("logcumsumexp")
):
return "cumulative reduction"
if suffix in ("dim", "dim_IntList", "dim_int", "dim_values"):
return "reduction along dim"
if suffix in ("Tensor", "Tensor_Tensor"):
return "elementwise — Tensor overload"
if suffix in ("Scalar", "Tensor_Scalar", "Scalar_Tensor"):
return "elementwise — Scalar overload"
if short in _KNOWN_UNARY_FLOAT_ELEMENTWISE:
return "elementwise — unary float Tensor → Tensor"
if short in _KNOWN_WHOLE_TENSOR_REDUCTIONS:
return "reduction (whole-tensor)"
return "other / uncategorized"
# Curated short-name allowlists for the family classifier.
_KNOWN_UNARY_FLOAT_ELEMENTWISE: frozenset = frozenset({
# trig / hyperbolic
"acos",
"acosh",
"asin",
"asinh",
"atan",
"atanh",
"cos",
"cosh",
"sin",
"sinc",
"sinh",
"tan",
"tanh",
# exponentials / logs
"exp",
"exp2",
"expm1",
"log",
"log10",
"log1p",
"log2",
"logit",
# roots, powers, reciprocals
"reciprocal",
"rsqrt",
"sqrt",
"square",
# rounding / sign
"ceil",
"floor",
"frac",
"neg",
"round",
"sgn",
"sign",
"signbit",
"trunc",
"positive",
# gamma / bessel-ish
"digamma",
"erf",
"erfc",
"erfinv",
"i0",
"lgamma",
"polygamma",
# complex / angle
"angle",
"conj_physical",
# predicates → BoolTensor
"isfinite",
"isinf",
"isnan",
"isneginf",
"isposinf",
"isreal",
# angle conversion
"deg2rad",
"rad2deg",
# activations
"elu",
"gelu",
"glu",
"hardshrink",
"hardsigmoid",
"hardswish",
"hardtanh",
"leaky_relu",
"mish",
"relu",
"sigmoid",
"silu",
"softplus",
"softshrink",
"threshold",
# other
"nonzero",
})
_KNOWN_WHOLE_TENSOR_REDUCTIONS: frozenset = frozenset({
"all",
"any",
"amax",
"amin",
"aminmax",
"argmax",
"argmin",
"max",
"min",
"median",
"mode",
"nanmedian",
"prod",
"sum",
"mean",
"nansum",
"trace",
"std",
"var",
"std_mean",
"var_mean",
"norm",
})
def format_missing_arg_builder_report(
op_names: List[str],
*,
max_examples: int = 5,
include_hints: bool = True,
indent: str = " ",
) -> List[str]:
"""Render a family Pareto for ops missing an ``OpSpec.build`` entry."""
if not op_names:
return []
by_family: Dict[str, List[str]] = {}
for n in op_names:
by_family.setdefault(missing_builder_family(n), []).append(
_short_op_name(n),
)
rows = sorted(by_family.items(), key=lambda kv: (-len(kv[1]), kv[0]))
width = max(len(str(len(ops))) for _, ops in rows)
sub_indent = f"{indent}{' ' * width} "
lines = [f" argument-builder gap — by family ({len(op_names)} ops):"]
for family, names in rows:
sample = sorted(names)[:max_examples]
suffix = (
f", … (+{len(names) - max_examples} more)"
if len(names) > max_examples
else ""
)
lines.append(f"{indent}{len(names):>{width}} {family}")
lines.append(f"{sub_indent}e.g. {', '.join(sample)}{suffix}")
if include_hints:
hint = _MISSING_BUILDER_FAMILY_HINTS.get(family)
if hint:
lines.append(f"{sub_indent}hint: {hint}")
return lines
def format_skip_breakdown_lines(
skip_counts: Dict[str, int],
*,
skip_op_names: Optional[Dict[str, List[str]]] = None,
max_names_per_category: int = 12,
indent: str = " ",
) -> List[str]:
"""Render a SKIP breakdown table sorted by count then label."""
if not skip_counts:
return []
rows = sorted(
skip_counts.items(),
key=lambda kv: (-kv[1], SKIP_CATEGORY_LABELS.get(kv[0], kv[0])),
)
width = max(len(str(count)) for _, count in rows)
sub_indent = f"{indent}{' ' * width} "
lines = [" SKIP breakdown:"]
for category, count in rows:
label = SKIP_CATEGORY_LABELS.get(category, category)
lines.append(f"{indent}{count:>{width}} {label}")
if skip_op_names:
names = skip_op_names.get(category) or []
if names:
short_names = sorted(_short_op_name(n) for n in names)
if len(short_names) <= max_names_per_category:
listed = ", ".join(short_names)
suffix = ""
else:
listed = ", ".join(short_names[:max_names_per_category])
suffix = f", … (+{len(short_names) - max_names_per_category} more)"
lines.append(f"{sub_indent}ops: {listed}{suffix}")
total = sum(skip_counts.values())
lines.append(f"{indent}{total:>{width}} total")
return lines
def multiline_coverage_failure_warning(
failure_detail: List[Tuple[str, str]],
*,
max_ops: int,
seed: int,
sample_budget: int,
) -> str:
"""Bounded multiline text for warnings.warn when stdout is captured."""
lines = [
f"{len(failure_detail)} operator(s) failed ROCTX/kernel coverage "
"(report only; test still passes).",
f"Re-run with -s: pytest tests/test_torch_trace_coverage.py -m "
f"torch_trace --coverage-seed={seed} --coverage-n={sample_budget} -s",
"",
]
shown = failure_detail[:max_ops]
for name, reason in shown:
r = reason.replace("\n", " ")[:500]
lines.append(f"FAIL {name}")
lines.append(f" {r}")
lines.append("")
rest = len(failure_detail) - len(shown)
if rest > 0:
lines.append(f"… and {rest} more (see pytest -s for full list).")
return "\n".join(lines)
def compare_single_op(
op: OpEntry,
ground_truth: Dict[str, Any],
roctx_marker_names: Set[str],
roctx_kernels_map: Dict[str, Set[str]],
*,
match_verbose: bool = False,
) -> OpCompareOutcome:
"""Compare one op's profiler entry against ROCTX markers and kernels.
Structural ops pass when a matching marker leaf is present. ATen
ops with profiler kernels also require a non-empty intersection of
profiler ``cuda_kernels`` and ROCTX-correlated kernels.
"""
ground_truth_entry = ground_truth.get(op.name)
if ground_truth_entry is None or "error" in ground_truth_entry:
err_msg = describe_missing_or_errored_op(op, ground_truth_entry)
reason = err_msg if len(err_msg) <= 4000 else f"{err_msg[:4000]}…"
log_lines = coverage_log_skip(op.name, reason)
if match_verbose:
log_lines = log_lines + (
" [match] no ground-truth entry (op skipped before "
"marker/kernel comparison)",
)
return OpCompareOutcome("skip", reason, log_lines)
profiler_kernels = ground_truth_entry.get("cuda_kernels", [])
profiler_kernel_set = set(profiler_kernels)
matched_marker_leaves: List[str] = []
roctx_kernels: Set[str] = set()
for observed_marker_leaf in roctx_marker_names:
if marker_matches_op(op.name, observed_marker_leaf):
matched_marker_leaves.append(observed_marker_leaf)
roctx_kernels |= roctx_kernels_map.get(observed_marker_leaf, set())
marker_found = bool(matched_marker_leaves)
def _match_verbose_lines() -> Tuple[str, ...]:
"""Indented per-op match diagnostic, truncated at 8 entries."""
if not match_verbose:
return ()
def _trunc(label: str, items) -> str:
items_sorted = sorted(items)
head = items_sorted[:8]
ellipsis = "…" if len(items_sorted) > 8 else ""
return f" [match] {label}: {head}{ellipsis}"
return (
_trunc("matched_marker_leaves", matched_marker_leaves),
_trunc("profiler_kernels", profiler_kernel_set),
_trunc("roctx_kernels", roctx_kernels),
_trunc(
"kernel_overlap",
profiler_kernel_set & roctx_kernels,
),
)
verbose_tail = _match_verbose_lines()
if op.category == "structural":
if marker_found:
return OpCompareOutcome(
"pass",
"",
coverage_log_pass(op.name, note="structural: marker present")
+ verbose_tail,
)
skip_msg = "no ROCTX marker — inject_roctx instrumentation gap"
return OpCompareOutcome(
"skip",
skip_msg,
coverage_log_skip(op.name, skip_msg) + verbose_tail,
)
if not profiler_kernel_set:
if marker_found:
return OpCompareOutcome(
"pass",
"",
coverage_log_pass(
op.name,
note="marker present; no kernels in ground truth",
)
+ verbose_tail,
)
skip_msg = "no GPU kernels in ground truth"
return OpCompareOutcome(
"skip",
skip_msg,
coverage_log_skip(op.name, skip_msg) + verbose_tail,
)
if marker_found and roctx_kernels:
overlap = profiler_kernel_set & roctx_kernels
if overlap:
return OpCompareOutcome(
"pass",
"",
coverage_log_pass(op.name) + verbose_tail,
)
reason = (
"marker found and ROCTX correlated kernels, but none overlap "
f"torch.profiler cuda_kernels "
f"(profiler={sorted(profiler_kernel_set)[:6]}"
f"{'…' if len(profiler_kernel_set) > 6 else ''}, "
f"roctx={sorted(roctx_kernels)[:6]}"
f"{'…' if len(roctx_kernels) > 6 else ''})"
)
return OpCompareOutcome(
"fail",
reason,
coverage_log_fail(op.name, reason) + verbose_tail,
)
if marker_found:
short_name = aten_op_short_name(op.name)
if short_name and short_name in KNOWN_KERNEL_FREE_ATEN_OPS:
note_lines = ("kernel-free aten op (zero-copy view).",)
return OpCompareOutcome(
"pass",
"",
coverage_log_pass(op.name, note="\n".join(note_lines)) + verbose_tail,
)
reason = "marker found but no correlated kernels"
return OpCompareOutcome(
"fail",
reason,
coverage_log_fail(op.name, reason) + verbose_tail,
)
reason = "marker not found"
return OpCompareOutcome(
"fail",
reason,
coverage_log_fail(op.name, reason) + verbose_tail,
)
def print_torch_trace_coverage_session_header(
seed: int,
sample_budget: int,
sampled_operator_count: int,
aten_operator_count: int,
structural_operator_count: int,
excluded_operator_count: int = 0,
) -> None:
"""Print the seed and sampling summary for a coverage session."""
print(
f"\n Seed: {seed} | {sampled_operator_count} operators"
f" selected from {aten_operator_count} CUDA ATen ops"
f" + {structural_operator_count} structural"
f" (budget={sample_budget})"
)
if excluded_operator_count:
print(
f" Excluded {excluded_operator_count} CUDA-only ATen ops "
"(hardware-incompatible)."
)
print()
reproduce_cmd = (
"pytest tests/test_torch_trace_coverage.py -m torch_trace "
f"--coverage-seed={seed} --coverage-n={sample_budget}"
)
warnings.warn(
f"torch_trace_coverage RNG: seed={seed}, n={sample_budget}. "
f"Re-run: {reproduce_cmd}",
UserWarning,
stacklevel=2,
)
def signal_name(returncode: int) -> str:
"""SIGSEGV / SIGABRT / ... for negative returncodes; '' otherwise."""
if returncode >= 0:
return ""
try:
return signal.Signals(-returncode).name
except ValueError:
return ""
def describe_subprocess_exit_code(returncode: int) -> str:
"""Return a human-readable description of a subprocess return code."""
if returncode < 0:
name = signal_name(returncode)
name_part = f" ({name})" if name else ""
return (
f"Child process terminated by signal {-returncode}{name_part} "
f"(exit {returncode})."
)
return f"Child process exited with status {returncode}."
def op_workload_failure_due_to(
*,
timed_out: bool,
returncode: Optional[int],
) -> str:
"""Short phrase for pytest.fail lead-in."""
if timed_out:
return "ground-truth subprocess exceeded 120s timeout"
assert returncode is not None
if returncode < 0:
name = signal_name(returncode)
name_part = f", {name}" if name else ""
return (
f"ground-truth subprocess killed by signal {-returncode}"
f"{name_part} (exit {returncode})"
)
return f"ground-truth subprocess exited with status {returncode}"
def stderr_tail_collapsed(
stderr: str,
*,
max_lines: int = 32,
max_chars: int = 6000,
) -> str:
"""Return a shorter stderr view: drop leading duplicate spam, collapse repeats."""
if not stderr or not stderr.strip():
return "(no stderr)"
lines = stderr.splitlines()
collapsed: List[str] = []
index = 0
while index < len(lines):
line = lines[index]
run_end = index + 1
while run_end < len(lines) and lines[run_end] == line:
run_end += 1
repeat_count = run_end - index
if repeat_count > 1:
collapsed.append(f"{line} [repeated {repeat_count} times]")
else:
collapsed.append(line)
index = run_end
if len(collapsed) <= max_lines:
body_lines = collapsed
else:
head_n = min(10, max_lines // 3)
tail_n = max_lines - head_n - 1
head = collapsed[:head_n]
tail = collapsed[-tail_n:]
omitted = len(collapsed) - head_n - tail_n
body_lines = head + [f"... ({omitted} line(s) omitted) ..."] + tail
body = "\n".join(body_lines)
if len(body) > max_chars:
body = body[-max_chars:]
body = f"... (stderr tail, {max_chars} char cap) ...\n{body}"
return body
def copy_failed_coverage_artifacts_to_cwd(
workload_script_path: str,
runner_script_path: str,
) -> str:
"""Copy workload + profiler runner from the failed run into pytest cwd."""
workload_dest = Path.cwd() / "failed_torch_trace_coverage_workload.py"
runner_dest = Path.cwd() / "failed_torch_trace_coverage_runner.py"
notes: List[str] = []
for src, dest in (
(workload_script_path, workload_dest),
(runner_script_path, runner_dest),
):
try:
shutil.copy2(src, dest)
notes.append(str(dest.resolve()))
except OSError as exc:
return f"\n\nCould not copy {src} to {dest}: {exc}"
return (
f"\n\nSaved workload to {notes[0]} and runner to {notes[1]}. "
f"Re-run: python {runner_dest.name} {workload_dest.name} ground_truth.json"
)
def extract_device_fault_banner(stderr_text: str) -> str:
"""Summarise device-fault markers emitted by the generated workload."""
if not stderr_text:
return ""
dead: List[str] = []
last_start: Optional[str] = None
last_terminal_label: Optional[str] = None
for line in stderr_text.splitlines():
if "[torch-trace-cov] DEVICE_DEAD" in line:
dead.append(line.strip())
continue
if "[torch-trace-cov] START " in line:
last_start = line.split("[torch-trace-cov] START ", 1)[1].strip()
continue
for tag in ("[torch-trace-cov] OK ", "[torch-trace-cov] FAIL "):
if tag in line:
last_terminal_label = line.split(tag, 1)[1].split(" ", 1)[0]
break
hints: List[str] = []
if dead:
hints.append("Device-dead banner(s):")
hints.extend(f" {d}" for d in dead)
if last_start is not None and last_start != last_terminal_label:
hints.append(f"Last START without matching OK/FAIL: {last_start}")
return "\n".join(hints)
def run_ground_truth_torch_profiler_subprocess(
runner_script_path: str,
workload_script_path: str,
ground_truth_json_path: str,
*,
coverage_seed: Optional[int] = None,
coverage_sample_budget: Optional[int] = None,
) -> None:
"""Run coverage_ground_truth_runner.py (torch.profiler + JSON write)."""
repro = ""
if coverage_seed is not None and coverage_sample_budget is not None:
repro = f" (seed={coverage_seed}, n={coverage_sample_budget})"
argv = [
sys.executable,
str(Path(runner_script_path).resolve()),
str(Path(workload_script_path).resolve()),
str(Path(ground_truth_json_path).resolve()),
]
try:
completed = subprocess.run(
argv,
capture_output=True,
text=True,
timeout=120,
)
except subprocess.TimeoutExpired as exc:
copy_note = copy_failed_coverage_artifacts_to_cwd(
workload_script_path,
runner_script_path,
)
due = op_workload_failure_due_to(timed_out=True, returncode=None)
out_tail = stderr_tail_collapsed(exc.stdout or "", max_lines=8)
err_tail = stderr_tail_collapsed(exc.stderr or "")
fault_hint = extract_device_fault_banner(exc.stderr or "")
hint_block = f"--- device fault ---\n{fault_hint}\n\n" if fault_hint else ""
pytest.fail(
f"Op workload failed: {due}.{repro}{copy_note}\n\n"
f"{hint_block}"
f"--- stdout (tail) ---\n{out_tail}\n\n"
f"--- stderr (tail) ---\n{err_tail}"
)
if completed.returncode != 0:
copy_note = copy_failed_coverage_artifacts_to_cwd(
workload_script_path,
runner_script_path,
)
due = op_workload_failure_due_to(
timed_out=False,
returncode=completed.returncode,
)
exit_expl = describe_subprocess_exit_code(completed.returncode)
err_tail = stderr_tail_collapsed(completed.stderr or "")
out_tail = stderr_tail_collapsed(completed.stdout or "", max_lines=12)
cmdline = subprocess.list2cmdline(argv)
fault_hint = extract_device_fault_banner(completed.stderr or "")
hint_block = f"--- device fault ---\n{fault_hint}\n\n" if fault_hint else ""
pytest.fail(
f"Op workload failed: {due}.{repro}{copy_note}\n\n"
f"{exit_expl}\n\n"
f"{hint_block}"
f"--- command ---\n{cmdline}\n\n"
f"--- stdout (tail) ---\n{out_tail}\n\n"
f"--- stderr (tail) ---\n{err_tail}"
)