mirror of
https://github.com/pytorch/pytorch.git
synced 2026-10-28 18:56:51 +08:00
Human Notes: We finally get there! Cut over the existing inner-tree implementations to the new stack. Codex: Route the existing ordered `aten::sum` and `aten::prod` overrides through the shared fixed-DAG kernel. Eligibility, TensorIterator canonicalization, keepdim, and direct/out overload handling remain in the adapter. Unsupported dtypes and geometry continue to fall through to ATen. Nonzero input row strides and unit-stride outputs use the shared kernel. Noncompact rows use a dynamic leading stride; compactness and alignment key the plan, with underaligned rows selecting the bit-neutral unstaged path. Strided outputs retain the legacy same-order implementation. H100 uses a targeted native-suite list; B200's existing dynamic discovery covers the same suites without scheduling them twice. Labeling covers both implementation and test-only changes. End-to-end tests assert both reference bits and that the ordered route actually ran. Test Plan: ``` spin quicklint PYTORCH_SUM_INNER_TREE=1 python test/python_native/test_sum_cutedsl.py python test/python_native/test_sum_inner_tree_plan.py python test/python_native/test_inner_tree_order.py ``` Authored with assistance from an AI coding agent. Pull Request resolved: https://github.com/pytorch/pytorch/pull/195062 Approved by: https://github.com/ngimel
114 lines
4.9 KiB
Python
114 lines
4.9 KiB
Python
# Owner(s): ["module: dsl-native-ops"]
|
|
# Smoke-test K0 compilation and dispatch on a middle-dimension reduction, which forces
|
|
# the general path. Override OpInfo suites provide full numerical coverage.
|
|
|
|
import pathlib
|
|
import re
|
|
import sys
|
|
import unittest
|
|
|
|
import torch
|
|
from torch.testing._internal.common_cuda import SM90OrLater, TEST_CUDA
|
|
from torch.testing._internal.common_utils import run_tests, TEST_CUTEDSL, TestCase
|
|
|
|
|
|
# Guard before importing cutlass-dependent kernels to avoid collection errors.
|
|
if not TEST_CUTEDSL:
|
|
sys.stderr.write("CuTeDSL not available\n")
|
|
if __name__ == "__main__":
|
|
sys.exit(0)
|
|
raise unittest.SkipTest("CuTeDSL not available")
|
|
|
|
import cutlass
|
|
|
|
from torch._native.ops import reductions
|
|
from torch._native.ops.reductions import (
|
|
kernel_general as kg,
|
|
kernel_xcta as xc,
|
|
traits as T,
|
|
)
|
|
|
|
|
|
@unittest.skipUnless(TEST_CUDA, "CUDA required")
|
|
@unittest.skipUnless(SM90OrLater, "Hopper+ required")
|
|
class TestKernelGeneral(TestCase):
|
|
def test_reduce_dim_general_path(self):
|
|
x = torch.randn(8, 16, 32, device="cuda")
|
|
out = kg.reduce_dim(
|
|
T.SumOps(acc=cutlass.Float32), "smoke", x, [1], torch.float32
|
|
)
|
|
torch.testing.assert_close(out, x.sum(dim=1), atol=1e-2, rtol=1e-2)
|
|
|
|
def test_two_stage_row_ragged_split(self):
|
|
# A prime row requires ragged stage-1 chunks that stop at row boundaries.
|
|
x = torch.randn(8, 65537, device="cuda")
|
|
(out,) = kg._two_stage_row(
|
|
T.SumOps(acc=cutlass.Float32), "smoke_rag", x, [torch.float32], 1
|
|
)
|
|
torch.testing.assert_close(out, x.sum(dim=1), atol=1e-1, rtol=1e-3)
|
|
|
|
def test_two_stage_row_index_is_global(self):
|
|
# Chunk-local reductions must report global columns and preserve first-wins ties.
|
|
x = torch.zeros(8, 65537, device="cuda")
|
|
x[:, 40000] = 1.0
|
|
x[:, 50000] = 1.0 # tie with the above -> the lower column must win
|
|
(idx,) = kg._two_stage_row(
|
|
T.ArgMaxOps(acc=cutlass.Float32), "smoke_ragidx", x, [torch.int32], 1
|
|
)
|
|
self.assertEqual(idx, torch.full((8,), 40000, device="cuda", dtype=torch.int32))
|
|
|
|
def test_family_has_exactly_one_cute_kernel(self):
|
|
# Assert every axis still shares one kernel. Glob files and match qualified or
|
|
# annotated decorators so new drivers and spellings cannot evade the check.
|
|
# Runtime introspection cannot distinguish @cute.kernel from @cute.jit wrappers.
|
|
root = pathlib.Path(reductions.__file__).parent
|
|
deco = re.compile(r"@(?:\w+\.)*cute\.kernel\b")
|
|
found = [
|
|
f"{path.name}:{i}"
|
|
for path in sorted(root.glob("*.py"))
|
|
for i, line in enumerate(path.read_text().splitlines(), 1)
|
|
if deco.match(line.strip())
|
|
]
|
|
self.assertEqual(
|
|
len(found), 1, f"expected one kernel in the family, got {found}"
|
|
)
|
|
self.assertTrue(found[0].startswith("tile.py"), f"the body moved: {found}")
|
|
|
|
def test_internal_invariants_raise(self):
|
|
# Each invariant must raise explicitly because python -O strips asserts. Exercise every
|
|
# check on its documented invalid input.
|
|
trait = T.SumOps(acc=cutlass.Float32)
|
|
# reduce-all needs a flat view, so a transposed input has to be refused, not reshaped.
|
|
xt = torch.randn(64, 128, device="cuda").t()
|
|
with self.assertRaisesRegex(AssertionError, "contiguous CUDA input"):
|
|
kg._reduce_all(trait, "inv", xt, [torch.float32], 1, 128, 4)
|
|
# The general path needs a CUDA input; a CPU tensor is refused, not silently run.
|
|
with self.assertRaisesRegex(AssertionError, "need a CUDA input"):
|
|
kg._reduce(trait, "inv", torch.randn(8, 8), [1], [torch.float32], 1)
|
|
# The magic-division decode is exact only below 2^31.
|
|
with self.assertRaisesRegex(AssertionError, "count and num_o"):
|
|
kg.ReduceBlock(
|
|
trait, count=2**31, num_o=1, red_pairs=((2**31, 1),), kept_pairs=()
|
|
)
|
|
# A missing reduced run would index vals[-1]; empty kept runs remain valid.
|
|
with self.assertRaisesRegex(AssertionError, "at least one reduced run"):
|
|
kg.ReduceBlock(trait, count=1, num_o=1, red_pairs=(), kept_pairs=())
|
|
# The reshaping cross-CTA path also requires contiguous CUDA input.
|
|
with self.assertRaisesRegex(AssertionError, "CUDA"):
|
|
xc.reduce_row_xcta(trait, "inv_xcta", xt, torch.float32)
|
|
|
|
def test_no_suppressed_asserts_survive(self):
|
|
# Reject suppressed asserts directly because lint itself can be silenced.
|
|
root = pathlib.Path(reductions.__file__).parent
|
|
offenders = [
|
|
f"{path.name}:{i}"
|
|
for path in sorted(root.glob("*.py"))
|
|
for i, line in enumerate(path.read_text().splitlines(), 1)
|
|
if "noqa: S101" in line
|
|
]
|
|
self.assertEqual(offenders, [])
|
|
|
|
|
|
if __name__ == "__main__":
|
|
run_tests()
|