tiagomonteiro0715 commited on
Commit ·
6f4b39f
0
Parent(s):
initial softmax attention kernel
Browse files- .gitignore +10 -0
- .python-version +1 -0
- README.md +56 -0
- build.toml +14 -0
- flake.lock +95 -0
- flake.nix +17 -0
- main.py +6 -0
- pyproject.toml +16 -0
- tests/test_attention.py +58 -0
- torch-ext/my_softmax_function/__init__.py +8 -0
- torch-ext/my_softmax_function/_ops.py +68 -0
- torch-ext/my_softmax_function/attention_v3.py +98 -0
- uv.lock +0 -0
.gitignore
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Python-generated files
|
| 2 |
+
__pycache__/
|
| 3 |
+
*.py[oc]
|
| 4 |
+
build/
|
| 5 |
+
dist/
|
| 6 |
+
wheels/
|
| 7 |
+
*.egg-info
|
| 8 |
+
|
| 9 |
+
# Virtual environments
|
| 10 |
+
.venv
|
.python-version
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
3.14
|
README.md
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
tags:
|
| 3 |
+
- kernel
|
| 4 |
+
license: apache-2.0
|
| 5 |
+
---
|
| 6 |
+
|
| 7 |
+
# my_softmax_function
|
| 8 |
+
|
| 9 |
+
Fused **softmax-attention** kernel written in the [CUTLASS Python DSL](https://github.com/NVIDIA/cutlass)
|
| 10 |
+
(`cutlass.cute`), packaged for the Hugging Face [`kernels`](https://github.com/huggingface/kernels) Hub.
|
| 11 |
+
|
| 12 |
+
Computes, in a single online pass (max-shifted for numerical stability):
|
| 13 |
+
|
| 14 |
+
```
|
| 15 |
+
out = softmax(scale * Q @ Kᵀ) @ V
|
| 16 |
+
```
|
| 17 |
+
|
| 18 |
+
## Usage
|
| 19 |
+
|
| 20 |
+
```python
|
| 21 |
+
import torch
|
| 22 |
+
from kernels import get_kernel
|
| 23 |
+
|
| 24 |
+
k = get_kernel("monteiro-t/my_softmax_function") # replace with your repo id
|
| 25 |
+
|
| 26 |
+
Q = torch.randn(128, 64, device="cuda", dtype=torch.float32)
|
| 27 |
+
K = torch.randn(256, 64, device="cuda", dtype=torch.float32)
|
| 28 |
+
V = torch.randn(256, 64, device="cuda", dtype=torch.float32)
|
| 29 |
+
|
| 30 |
+
out = k.attention(Q, K, V) # scale defaults to 1/sqrt(d)
|
| 31 |
+
out = k.attention(Q, K, V, scale=0.5)
|
| 32 |
+
```
|
| 33 |
+
|
| 34 |
+
## API
|
| 35 |
+
|
| 36 |
+
| Function | Signature | Notes |
|
| 37 |
+
|---|---|---|
|
| 38 |
+
| `attention(Q, K, V, scale=None)` | `(M,d),(N,d),(N,d) -> (M,d)` | Convenience wrapper; `scale` defaults to `1/sqrt(d)`. |
|
| 39 |
+
| `softmax_attention(Q, K, V, scale)` | registered `torch.ops` op | `scale` required; `torch.compile`-friendly (has a fake/meta impl). |
|
| 40 |
+
|
| 41 |
+
## Supported shapes / dtypes / hardware
|
| 42 |
+
|
| 43 |
+
| Property | Support |
|
| 44 |
+
|---|---|
|
| 45 |
+
| Dtype | `float32` only |
|
| 46 |
+
| Layout | 2-D, contiguous (inputs are forced contiguous) |
|
| 47 |
+
| Shapes | `Q: (M, d)`, `K: (N, d)`, `V: (N, d)` → `out: (M, d)` |
|
| 48 |
+
| Batch / heads | **Not supported** (single 2-D attention; no leading batch/head dims) |
|
| 49 |
+
| Masking | **None** (dense attention only) |
|
| 50 |
+
| Backward pass | **None** (inference / forward only) |
|
| 51 |
+
| Device | CUDA only |
|
| 52 |
+
|
| 53 |
+
## Limitations
|
| 54 |
+
|
| 55 |
+
Naive one-row-per-thread implementation intended as a reference/demo kernel. No
|
| 56 |
+
batching, masking, dropout, or gradients. Not performance-tuned.
|
build.toml
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[general]
|
| 2 |
+
name = "my_softmax_function"
|
| 3 |
+
backends = [
|
| 4 |
+
"cpu",
|
| 5 |
+
"cuda",
|
| 6 |
+
"metal",
|
| 7 |
+
"rocm",
|
| 8 |
+
"xpu",
|
| 9 |
+
]
|
| 10 |
+
|
| 11 |
+
[torch]
|
| 12 |
+
src = []
|
| 13 |
+
|
| 14 |
+
[kernel]
|
flake.lock
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"nodes": {
|
| 3 |
+
"flake-compat": {
|
| 4 |
+
"locked": {
|
| 5 |
+
"lastModified": 1765121682,
|
| 6 |
+
"narHash": "sha256-4VBOP18BFeiPkyhy9o4ssBNQEvfvv1kXkasAYd0+rrA=",
|
| 7 |
+
"owner": "edolstra",
|
| 8 |
+
"repo": "flake-compat",
|
| 9 |
+
"rev": "65f23138d8d09a92e30f1e5c87611b23ef451bf3",
|
| 10 |
+
"type": "github"
|
| 11 |
+
},
|
| 12 |
+
"original": {
|
| 13 |
+
"owner": "edolstra",
|
| 14 |
+
"repo": "flake-compat",
|
| 15 |
+
"type": "github"
|
| 16 |
+
}
|
| 17 |
+
},
|
| 18 |
+
"flake-utils": {
|
| 19 |
+
"inputs": {
|
| 20 |
+
"systems": "systems"
|
| 21 |
+
},
|
| 22 |
+
"locked": {
|
| 23 |
+
"lastModified": 1731533236,
|
| 24 |
+
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
| 25 |
+
"owner": "numtide",
|
| 26 |
+
"repo": "flake-utils",
|
| 27 |
+
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
| 28 |
+
"type": "github"
|
| 29 |
+
},
|
| 30 |
+
"original": {
|
| 31 |
+
"owner": "numtide",
|
| 32 |
+
"repo": "flake-utils",
|
| 33 |
+
"type": "github"
|
| 34 |
+
}
|
| 35 |
+
},
|
| 36 |
+
"kernel-builder": {
|
| 37 |
+
"inputs": {
|
| 38 |
+
"flake-compat": "flake-compat",
|
| 39 |
+
"flake-utils": "flake-utils",
|
| 40 |
+
"nixpkgs": "nixpkgs"
|
| 41 |
+
},
|
| 42 |
+
"locked": {
|
| 43 |
+
"lastModified": 1775482375,
|
| 44 |
+
"narHash": "sha256-RUbxfJGs96jwnwSci3+8h08GB2/katOI67yZTCaV+aE=",
|
| 45 |
+
"owner": "huggingface",
|
| 46 |
+
"repo": "kernel-builder",
|
| 47 |
+
"rev": "dffbce5a048648febb96bbed79f3811ab6d7a577",
|
| 48 |
+
"type": "github"
|
| 49 |
+
},
|
| 50 |
+
"original": {
|
| 51 |
+
"owner": "huggingface",
|
| 52 |
+
"repo": "kernel-builder",
|
| 53 |
+
"type": "github"
|
| 54 |
+
}
|
| 55 |
+
},
|
| 56 |
+
"nixpkgs": {
|
| 57 |
+
"locked": {
|
| 58 |
+
"lastModified": 1766341660,
|
| 59 |
+
"narHash": "sha256-4yG6vx7Dddk9/zh45Y2KM82OaRD4jO3HA9r98ORzysA=",
|
| 60 |
+
"owner": "NixOS",
|
| 61 |
+
"repo": "nixpkgs",
|
| 62 |
+
"rev": "26861f5606e3e4d1400771b513cc63e5f70151a6",
|
| 63 |
+
"type": "github"
|
| 64 |
+
},
|
| 65 |
+
"original": {
|
| 66 |
+
"owner": "NixOS",
|
| 67 |
+
"ref": "nixos-unstable-small",
|
| 68 |
+
"repo": "nixpkgs",
|
| 69 |
+
"type": "github"
|
| 70 |
+
}
|
| 71 |
+
},
|
| 72 |
+
"root": {
|
| 73 |
+
"inputs": {
|
| 74 |
+
"kernel-builder": "kernel-builder"
|
| 75 |
+
}
|
| 76 |
+
},
|
| 77 |
+
"systems": {
|
| 78 |
+
"locked": {
|
| 79 |
+
"lastModified": 1681028828,
|
| 80 |
+
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
| 81 |
+
"owner": "nix-systems",
|
| 82 |
+
"repo": "default",
|
| 83 |
+
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
| 84 |
+
"type": "github"
|
| 85 |
+
},
|
| 86 |
+
"original": {
|
| 87 |
+
"owner": "nix-systems",
|
| 88 |
+
"repo": "default",
|
| 89 |
+
"type": "github"
|
| 90 |
+
}
|
| 91 |
+
}
|
| 92 |
+
},
|
| 93 |
+
"root": "root",
|
| 94 |
+
"version": 7
|
| 95 |
+
}
|
flake.nix
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
description = "my_softmax_function — fused softmax-attention kernel";
|
| 3 |
+
|
| 4 |
+
inputs = {
|
| 5 |
+
kernel-builder.url = "github:huggingface/kernel-builder";
|
| 6 |
+
};
|
| 7 |
+
|
| 8 |
+
outputs =
|
| 9 |
+
{
|
| 10 |
+
self,
|
| 11 |
+
kernel-builder,
|
| 12 |
+
}:
|
| 13 |
+
kernel-builder.lib.genFlakeOutputs {
|
| 14 |
+
inherit self;
|
| 15 |
+
path = ./.;
|
| 16 |
+
};
|
| 17 |
+
}
|
main.py
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
def main():
|
| 2 |
+
print("Hello from my-softmax-function!")
|
| 3 |
+
|
| 4 |
+
|
| 5 |
+
if __name__ == "__main__":
|
| 6 |
+
main()
|
pyproject.toml
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[project]
|
| 2 |
+
name = "my-softmax-function"
|
| 3 |
+
version = "0.1.0"
|
| 4 |
+
description = "Add your description here"
|
| 5 |
+
readme = "README.md"
|
| 6 |
+
requires-python = ">=3.14"
|
| 7 |
+
dependencies = [
|
| 8 |
+
"nvidia-cutlass-dsl>=4.6.1",
|
| 9 |
+
"torch>=2.13.0",
|
| 10 |
+
]
|
| 11 |
+
|
| 12 |
+
[dependency-groups]
|
| 13 |
+
dev = [
|
| 14 |
+
"kernels",
|
| 15 |
+
"pytest",
|
| 16 |
+
]
|
tests/test_attention.py
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Test the built kernel through the `kernels` loader against a torch reference.
|
| 2 |
+
|
| 3 |
+
Run AFTER building (so build/torch-universal/my_softmax_function exists):
|
| 4 |
+
|
| 5 |
+
pytest tests/ -v
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
import math
|
| 9 |
+
from pathlib import Path
|
| 10 |
+
|
| 11 |
+
import pytest
|
| 12 |
+
import torch
|
| 13 |
+
|
| 14 |
+
from kernels import get_local_kernel
|
| 15 |
+
|
| 16 |
+
REPO_ROOT = Path(__file__).resolve().parent.parent
|
| 17 |
+
|
| 18 |
+
pytestmark = pytest.mark.skipif(
|
| 19 |
+
not torch.cuda.is_available(), reason="kernel requires CUDA"
|
| 20 |
+
)
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
def _load():
|
| 24 |
+
# Loads build/torch-universal/my_softmax_function from the local repo,
|
| 25 |
+
# exactly the way kernels.get_kernel(...) would load it from the Hub.
|
| 26 |
+
return get_local_kernel(REPO_ROOT, "my_softmax_function")
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
def _reference(Q, K, V, scale):
|
| 30 |
+
scores = (Q @ K.transpose(-1, -2)) * scale
|
| 31 |
+
probs = torch.softmax(scores, dim=-1)
|
| 32 |
+
return probs @ V
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
@pytest.mark.parametrize("M,N,d", [(1, 1, 1), (17, 33, 8), (128, 256, 64)])
|
| 36 |
+
def test_matches_reference(M, N, d):
|
| 37 |
+
kernel = _load()
|
| 38 |
+
torch.manual_seed(0)
|
| 39 |
+
Q = torch.randn(M, d, device="cuda", dtype=torch.float32)
|
| 40 |
+
K = torch.randn(N, d, device="cuda", dtype=torch.float32)
|
| 41 |
+
V = torch.randn(N, d, device="cuda", dtype=torch.float32)
|
| 42 |
+
scale = 1.0 / math.sqrt(d)
|
| 43 |
+
|
| 44 |
+
out = kernel.attention(Q, K, V)
|
| 45 |
+
ref = _reference(Q, K, V, scale)
|
| 46 |
+
|
| 47 |
+
torch.testing.assert_close(out, ref, atol=1e-3, rtol=1e-3)
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
def test_custom_scale():
|
| 51 |
+
kernel = _load()
|
| 52 |
+
Q = torch.randn(8, 16, device="cuda", dtype=torch.float32)
|
| 53 |
+
K = torch.randn(12, 16, device="cuda", dtype=torch.float32)
|
| 54 |
+
V = torch.randn(12, 16, device="cuda", dtype=torch.float32)
|
| 55 |
+
|
| 56 |
+
out = kernel.attention(Q, K, V, scale=0.5)
|
| 57 |
+
ref = _reference(Q, K, V, 0.5)
|
| 58 |
+
torch.testing.assert_close(out, ref, atol=1e-3, rtol=1e-3)
|
torch-ext/my_softmax_function/__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 ._ops import attention, softmax_attention
|
| 7 |
+
|
| 8 |
+
__all__ = ["attention", "softmax_attention"]
|
torch-ext/my_softmax_function/_ops.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)
|
torch-ext/my_softmax_function/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 |
+
)
|
uv.lock
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|