tiagomonteiro0715 commited on
Commit
490d9fe
·
1 Parent(s): f5ebe93

add built softmax attention kernel

Browse files
.gitignore CHANGED
@@ -1,10 +1,13 @@
1
  # Python-generated files
2
  __pycache__/
3
  *.py[oc]
4
- build/
5
  dist/
6
  wheels/
7
  *.egg-info
8
 
9
  # Virtual environments
10
  .venv
 
 
 
 
 
1
  # Python-generated files
2
  __pycache__/
3
  *.py[oc]
 
4
  dist/
5
  wheels/
6
  *.egg-info
7
 
8
  # Virtual environments
9
  .venv
10
+
11
+ # Nix build symlink (do NOT commit; build/ is the real artifact and IS tracked)
12
+ result
13
+ result-*
build/torch-cuda/__init__.py ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ """my_softmax_function — fused softmax-attention kernel (CUTLASS Python DSL).
2
+
3
+ Public API exposed to `kernels` consumers.
4
+ """
5
+
6
+ from ._kernel import attention, softmax_attention
7
+
8
+ __all__ = ["attention", "softmax_attention"]
build/torch-cuda/_kernel.py ADDED
@@ -0,0 +1,68 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Public torch custom-op wrapper around the CUTLASS-DSL softmax-attention kernel.
2
+
3
+ This is the API a Hugging Face `kernels` consumer sees: a normal, registered
4
+ `torch.ops` op that works with autograd tracing / `torch.compile`, not the raw
5
+ `@cute.kernel`.
6
+ """
7
+
8
+ import math
9
+
10
+ import torch
11
+ from cutlass.cute.runtime import from_dlpack
12
+
13
+ from .attention_v3 import solve
14
+
15
+ _OP_NAME = "my_softmax_function::softmax_attention"
16
+
17
+
18
+ @torch.library.custom_op(_OP_NAME, mutates_args=())
19
+ def softmax_attention(
20
+ Q: torch.Tensor, # (M, d)
21
+ K: torch.Tensor, # (N, d)
22
+ V: torch.Tensor, # (N, d)
23
+ scale: float,
24
+ ) -> torch.Tensor: # (M, d)
25
+ """Fused, max-shifted softmax attention: softmax(scale * Q @ K^T) @ V.
26
+
27
+ Q, K, V must be contiguous, 2-D, float32, and on the same CUDA device.
28
+ """
29
+ if not (Q.is_cuda and K.is_cuda and V.is_cuda):
30
+ raise ValueError("Q, K, V must be CUDA tensors")
31
+ if not (Q.dim() == K.dim() == V.dim() == 2):
32
+ raise ValueError("Q, K, V must be 2-D (M,d)/(N,d)/(N,d)")
33
+
34
+ M, d = Q.shape
35
+ N = K.shape[0]
36
+ if K.shape[1] != d or V.shape[1] != d or V.shape[0] != N:
37
+ raise ValueError("shape mismatch between Q/K/V")
38
+
39
+ Q = Q.contiguous()
40
+ K = K.contiguous()
41
+ V = V.contiguous()
42
+ output = torch.empty((M, d), dtype=torch.float32, device=Q.device)
43
+
44
+ solve(
45
+ from_dlpack(Q),
46
+ from_dlpack(K),
47
+ from_dlpack(V),
48
+ from_dlpack(output),
49
+ M,
50
+ N,
51
+ d,
52
+ float(scale),
53
+ )
54
+ return output
55
+
56
+
57
+ @softmax_attention.register_fake
58
+ def _(Q, K, V, scale):
59
+ # Shape/dtype/device metadata only — no compute. Lets torch.compile trace.
60
+ M, d = Q.shape
61
+ return Q.new_empty((M, d))
62
+
63
+
64
+ def attention(Q, K, V, scale=None):
65
+ """Convenience entry point with a default 1/sqrt(d) scale."""
66
+ if scale is None:
67
+ scale = 1.0 / math.sqrt(Q.shape[-1])
68
+ return torch.ops.my_softmax_function.softmax_attention(Q, K, V, scale)
build/torch-cuda/_ops.py ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ ops = torch.ops._my_softmax_function_2af76fc
3
+
4
+ def add_op_namespace_prefix(op_name: str):
5
+ """
6
+ Prefix op by namespace.
7
+ """
8
+ return f"_my_softmax_function_2af76fc::{op_name}"
build/torch-cuda/attention_v3.py ADDED
@@ -0,0 +1,98 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import cutlass
2
+ import cutlass.cute as cute
3
+ from cutlass.cute.runtime import from_dlpack
4
+
5
+ NEG_BIG = -3.0e38 # finite stand-in for -inf
6
+
7
+
8
+ def transpose_matrix(T: cute.Tensor, i, j):
9
+ """Element (i, j) of T-transpose: a pure index swap, no data movement."""
10
+ return T[j, i]
11
+
12
+
13
+ def matrix_multiplication(acc, A: cute.Tensor, B: cute.Tensor, row, col, k,
14
+ transpose_b=False):
15
+ """One MAC step of C[row, col] = sum_k A[row, k] * B[k, col].
16
+ transpose_b is a Python bool, resolved at trace time."""
17
+ b = transpose_matrix(B, k, col) if transpose_b else B[k, col]
18
+ return acc + A[row, k] * b
19
+
20
+
21
+ def softmax_computation(score, row_max, denom):
22
+ """One softmax probability, max-shifted for stability. Pass denom=1.0 for the
23
+ unnormalized numerator (online softmax normalizes once at the end)."""
24
+ return cute.math.exp(score - row_max) / denom
25
+
26
+
27
+ @cute.kernel
28
+ def softmax_attention(
29
+ Q: cute.Tensor, # (M, d)
30
+ K: cute.Tensor, # (N, d)
31
+ V: cute.Tensor, # (N, d)
32
+ output: cute.Tensor, # (M, d)
33
+ M: cutlass.Int32,
34
+ N: cutlass.Int32,
35
+ d: cutlass.Int32,
36
+ host_scale: cutlass.Float32,
37
+ ):
38
+ bx, _, _ = cute.arch.block_idx()
39
+ bdx, _, _ = cute.arch.block_dim()
40
+ tx, _, _ = cute.arch.thread_idx()
41
+
42
+ row = bx * bdx + tx
43
+
44
+ if row < M:
45
+ scale = host_scale
46
+
47
+ # output[row, :] is the running accumulator
48
+ for k in cutlass.range(d):
49
+ output[row, k] = cutlass.Float32(0.0)
50
+
51
+ running_max = cutlass.Float32(NEG_BIG)
52
+ running_sum = cutlass.Float32(0.0)
53
+
54
+ # single pass: score, max, denominator and V-accumulation together
55
+ for n in cutlass.range(N):
56
+ s = cutlass.Float32(0.0)
57
+ for k in cutlass.range(d):
58
+ s = matrix_multiplication(s, Q, K, row, n, k, transpose_b=True)
59
+ s = s * scale
60
+
61
+ new_max = running_max
62
+ if s > new_max:
63
+ new_max = s
64
+
65
+ # rescales everything accumulated under the old max
66
+ correction = softmax_computation(running_max, new_max, 1.0)
67
+ p = softmax_computation(s, new_max, 1.0)
68
+
69
+ running_sum = running_sum * correction + p
70
+ running_max = new_max
71
+
72
+ for k in cutlass.range(d):
73
+ output[row, k] = output[row, k] * correction + p * V[n, k]
74
+
75
+ # normalize once, with a reciprocal instead of d divisions
76
+ inv_sum = 1.0 / running_sum
77
+ for k in cutlass.range(d):
78
+ output[row, k] = output[row, k] * inv_sum
79
+
80
+
81
+ @cute.jit
82
+ def solve(
83
+ Q: cute.Tensor,
84
+ K: cute.Tensor,
85
+ V: cute.Tensor,
86
+ output: cute.Tensor,
87
+ M: cutlass.Int32,
88
+ N: cutlass.Int32,
89
+ d: cutlass.Int32,
90
+ host_scale: cutlass.Float32,
91
+ ):
92
+ block_size = 256
93
+ grid_size = (M + block_size - 1) // block_size
94
+
95
+ softmax_attention(Q, K, V, output, M, N, d, host_scale).launch(
96
+ grid=(grid_size, 1, 1),
97
+ block=(block_size, 1, 1),
98
+ )
build/torch-cuda/metadata.json ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ {
2
+ "python-depends": [
3
+ "nvidia-cutlass-dsl"
4
+ ]
5
+ }
build/torch-cuda/my_softmax_function/__init__.py ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import ctypes
2
+ import sys
3
+
4
+ import importlib
5
+ from pathlib import Path
6
+ from types import ModuleType
7
+
8
+ def _import_from_path(file_path: Path) -> ModuleType:
9
+ # We cannot use the module name as-is, after adding it to `sys.modules`,
10
+ # it would also be used for other imports. So, we make a module name that
11
+ # depends on the path for it to be unique using the hex-encoded hash of
12
+ # the path.
13
+ path_hash = "{:x}".format(ctypes.c_size_t(hash(file_path.absolute())).value)
14
+ module_name = path_hash
15
+ spec = importlib.util.spec_from_file_location(module_name, file_path)
16
+ if spec is None:
17
+ raise ImportError(f"Cannot load spec for {module_name} from {file_path}")
18
+ module = importlib.util.module_from_spec(spec)
19
+ if module is None:
20
+ raise ImportError(f"Cannot load module {module_name} from spec")
21
+ sys.modules[module_name] = module
22
+ spec.loader.exec_module(module) # type: ignore
23
+ return module
24
+
25
+
26
+ globals().update(vars(_import_from_path(Path(__file__).parent.parent / "__init__.py")))
result DELETED
@@ -1 +0,0 @@
1
- /nix/store/4mkpzj01sp4ixa9wbig2hiqfaavhxqck-torch-ext-bundle