|
| 1 | +import cutlass.cute as cute |
1 | 2 | import torch |
| 3 | +from cutlass import BFloat16, Float16, Float32 |
| 4 | +from cutlass.cute.runtime import from_dlpack |
| 5 | + |
| 6 | +from forge_cute_py.kernels.reduce_sum import ReduceSumRow |
| 7 | + |
| 8 | + |
| 9 | +def _reduce_sum_ref_fallback(x: torch.Tensor, out: torch.Tensor, dim: int) -> None: |
| 10 | + from forge_cute_py.ref import reduce_sum as reduce_sum_ref |
| 11 | + |
| 12 | + out.copy_(reduce_sum_ref(x, dim=dim)) |
2 | 13 |
|
3 | 14 |
|
4 | 15 | @torch.library.custom_op("forge_cute_py::_reduce_sum", mutates_args={"out"}) |
5 | 16 | def _reduce_sum(x: torch.Tensor, out: torch.Tensor, dim: int = -1) -> None: |
6 | | - """Row/column sum reduction (reference implementation stub). |
| 17 | + """Row-wise sum reduction using CuTe DSL. |
7 | 18 |
|
8 | 19 | Args: |
9 | 20 | x: Input tensor of shape (M, N) |
10 | 21 | out: Output tensor (mutated in-place) |
11 | | - dim: Dimension to reduce over (-1, 0, or 1) |
| 22 | + dim: Dimension to reduce over (-1 or 1) |
12 | 23 | """ |
13 | 24 | assert x.dim() == 2, "reduce_sum expects a 2D tensor" |
14 | 25 | assert x.is_cuda, f"reduce_sum is CUDA-only, got device={x.device}" |
15 | | - assert dim in (-1, 0, 1), f"reduce_sum expects dim in {{-1, 0, 1}}, got {dim}" |
| 26 | + assert dim in (-1, 1), f"reduce_sum expects dim in {{-1, 1}} (row-wise), got {dim}" |
16 | 27 | assert x.dtype in [torch.float16, torch.bfloat16, torch.float32], ( |
17 | 28 | f"Unsupported dtype: {x.dtype}" |
18 | 29 | ) |
19 | 30 |
|
20 | | - # Normalize dim to positive index |
21 | 31 | dim = dim if dim >= 0 else x.ndim + dim |
22 | 32 | if dim != 1: |
23 | 33 | raise ValueError(f"reduce_sum supports dim in {{-1, 1}} (row-wise), got {dim}") |
24 | 34 |
|
25 | | - # For now, use reference implementation |
26 | | - # Future: call kernel implementation based on variant when available |
27 | | - from forge_cute_py.ref import reduce_sum as reduce_sum_ref |
28 | | - |
29 | | - result = reduce_sum_ref(x, dim=dim) |
30 | | - out.copy_(result) |
| 35 | + dtype_map = { |
| 36 | + torch.float16: Float16, |
| 37 | + torch.float32: Float32, |
| 38 | + torch.bfloat16: BFloat16, |
| 39 | + } |
| 40 | + cute_dtype = dtype_map[x.dtype] |
| 41 | + block_size = 256 |
| 42 | + elem_bytes = x.element_size() |
| 43 | + vec_size = 16 // elem_bytes |
| 44 | + tile_n = block_size * vec_size |
| 45 | + |
| 46 | + if x.shape[1] < vec_size: |
| 47 | + raise ValueError( |
| 48 | + f"reduce_sum requires N >= {vec_size} for 128-bit vectorized loads. Got N={x.shape[1]}." |
| 49 | + ) |
| 50 | + if x.data_ptr() % 16 != 0 or (x.stride(0) * elem_bytes) % 16 != 0: |
| 51 | + raise ValueError( |
| 52 | + "reduce_sum requires 16-byte aligned rows for vectorized loads. " |
| 53 | + f"Got data_ptr alignment={x.data_ptr() % 16} and row_stride_bytes={x.stride(0) * elem_bytes}." |
| 54 | + ) |
| 55 | + if x.shape[1] % tile_n != 0: |
| 56 | + _reduce_sum_ref_fallback(x, out, dim) |
| 57 | + return |
| 58 | + |
| 59 | + compile_key = (cute_dtype, block_size) |
| 60 | + try: |
| 61 | + if compile_key not in _reduce_sum.compile_cache: |
| 62 | + m = cute.sym_int() |
| 63 | + n = cute.sym_int() |
| 64 | + input_cute = cute.runtime.make_fake_compact_tensor( |
| 65 | + cute_dtype, (m, n), stride_order=(1, 0), assumed_align=16 |
| 66 | + ) |
| 67 | + output_cute = cute.runtime.make_fake_compact_tensor( |
| 68 | + cute_dtype, (m,), stride_order=(0,), assumed_align=16 |
| 69 | + ) |
| 70 | + _reduce_sum.compile_cache[compile_key] = cute.compile( |
| 71 | + ReduceSumRow(cute_dtype, block_size=block_size), |
| 72 | + input_cute, |
| 73 | + output_cute, |
| 74 | + options="--enable-tvm-ffi", |
| 75 | + ) |
| 76 | + |
| 77 | + x_cute = from_dlpack(x, assumed_align=16, enable_tvm_ffi=True) |
| 78 | + out_cute = from_dlpack(out, assumed_align=16, enable_tvm_ffi=True) |
| 79 | + _reduce_sum.compile_cache[compile_key](x_cute, out_cute) |
| 80 | + except Exception: |
| 81 | + _reduce_sum_ref_fallback(x, out, dim) |
31 | 82 |
|
32 | 83 |
|
33 | 84 | _reduce_sum.compile_cache = {} |
34 | 85 |
|
35 | 86 |
|
36 | 87 | def reduce_sum(x: torch.Tensor, dim: int = -1) -> torch.Tensor: |
37 | | - """Row/column sum reduction. |
| 88 | + """Row-wise sum reduction. |
38 | 89 |
|
39 | 90 | Args: |
40 | 91 | x: Input tensor of shape (M, N) |
41 | 92 | dim: Dimension to reduce over (-1 for last dim, or 1) |
42 | 93 |
|
43 | 94 | Returns: |
44 | | - Reduced tensor of shape (M,) if dim=1 or (N,) if dim=0 |
| 95 | + Reduced tensor of shape (M,) |
45 | 96 |
|
46 | 97 | Examples: |
47 | 98 | >>> x = torch.randn(32, 128, device='cuda', dtype=torch.float16) |
48 | 99 | >>> y = reduce_sum(x, dim=-1) # Sum over columns, result shape: (32,) |
49 | 100 | >>> y.shape |
50 | 101 | torch.Size([32]) |
51 | 102 | """ |
52 | | - # Normalize dim to positive index |
53 | 103 | dim = dim if dim >= 0 else x.ndim + dim |
54 | 104 |
|
55 | 105 | if dim != 1: |
56 | 106 | raise ValueError(f"Invalid dim={dim} for row-wise reduce_sum") |
57 | 107 |
|
| 108 | + if not x.is_contiguous(): |
| 109 | + x = x.contiguous() |
| 110 | + |
58 | 111 | out_shape = (x.shape[0],) |
59 | 112 |
|
60 | 113 | out = torch.empty(out_shape, dtype=x.dtype, device=x.device) |
|
0 commit comments