Ashiedu commited on
Commit
e14d114
·
verified ·
1 Parent(s): 40ef767

Initial kernel bundle upload: selective-update

Browse files
.gitattributes CHANGED
@@ -1,35 +1,3 @@
1
- *.7z filter=lfs diff=lfs merge=lfs -text
2
- *.arrow filter=lfs diff=lfs merge=lfs -text
3
- *.bin filter=lfs diff=lfs merge=lfs -text
4
- *.bz2 filter=lfs diff=lfs merge=lfs -text
5
- *.ckpt filter=lfs diff=lfs merge=lfs -text
6
- *.ftz filter=lfs diff=lfs merge=lfs -text
7
- *.gz filter=lfs diff=lfs merge=lfs -text
8
- *.h5 filter=lfs diff=lfs merge=lfs -text
9
- *.joblib filter=lfs diff=lfs merge=lfs -text
10
- *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
- *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
- *.model filter=lfs diff=lfs merge=lfs -text
13
- *.msgpack filter=lfs diff=lfs merge=lfs -text
14
- *.npy filter=lfs diff=lfs merge=lfs -text
15
- *.npz filter=lfs diff=lfs merge=lfs -text
16
- *.onnx filter=lfs diff=lfs merge=lfs -text
17
- *.ot filter=lfs diff=lfs merge=lfs -text
18
- *.parquet filter=lfs diff=lfs merge=lfs -text
19
- *.pb filter=lfs diff=lfs merge=lfs -text
20
- *.pickle filter=lfs diff=lfs merge=lfs -text
21
- *.pkl filter=lfs diff=lfs merge=lfs -text
22
- *.pt filter=lfs diff=lfs merge=lfs -text
23
  *.pth filter=lfs diff=lfs merge=lfs -text
24
- *.rar filter=lfs diff=lfs merge=lfs -text
25
- *.safetensors filter=lfs diff=lfs merge=lfs -text
26
- saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
- *.tar.* filter=lfs diff=lfs merge=lfs -text
28
- *.tar filter=lfs diff=lfs merge=lfs -text
29
- *.tflite filter=lfs diff=lfs merge=lfs -text
30
- *.tgz filter=lfs diff=lfs merge=lfs -text
31
- *.wasm filter=lfs diff=lfs merge=lfs -text
32
- *.xz filter=lfs diff=lfs merge=lfs -text
33
- *.zip filter=lfs diff=lfs merge=lfs -text
34
- *.zst filter=lfs diff=lfs merge=lfs -text
35
- *tfevents* filter=lfs diff=lfs merge=lfs -text
 
1
+ *.so filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
  *.pth filter=lfs diff=lfs merge=lfs -text
3
+ *.bin filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
 
 
 
 
 
.gitignore ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ build
2
+ *pyc
CARD.md CHANGED
@@ -1,26 +1,61 @@
1
  ---
2
  library_name: kernels
3
- license: apache-2.0
4
- ---
5
 
6
- This is the repository card of Ashiedu/selective-update that has been pushed on the Hub. It was built to be used with the [`kernels` library](https://github.com/huggingface/kernels). This card was automatically generated.
7
 
8
  ## How to use
 
9
 
10
  ```python
11
  # make sure `kernels` is installed: `pip install -U kernels`
12
  from kernels import get_kernel
13
 
14
- kernel_module = get_kernel("Ashiedu/selective-update", version=1)
15
- selective_update = kernel_module.selective_update
16
 
17
- selective_update(...)
18
  ```
 
 
 
 
19
 
20
  ## Available functions
21
- - `selective_update`
22
- - `selective_state_update`
 
 
 
 
 
 
 
 
 
 
 
 
 
23
 
24
  ## Benchmarks
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
 
26
- Benchmarking script is available for this kernel. Run `kernels benchmark Ashiedu/selective-update --version 1`.
 
 
1
  ---
2
  library_name: kernels
3
+ {% if license %}license: {{ license }}
4
+ {% endif %}---
5
 
6
+ This is the repository card of {{ repo_id }} that has been pushed on the Hub. It was built to be used with the [`kernels` library](https://github.com/huggingface/kernels). This card was automatically generated.
7
 
8
  ## How to use
9
+ {% if functions %}
10
 
11
  ```python
12
  # make sure `kernels` is installed: `pip install -U kernels`
13
  from kernels import get_kernel
14
 
15
+ kernel_module = get_kernel("{{ repo_id }}", version={{ version }})
16
+ {{ functions[0] }} = kernel_module.{{ functions[0] }}
17
 
18
+ {{ functions[0] }}(...)
19
  ```
20
+ {% else %}
21
+
22
+ Usage example not available.
23
+ {% endif %}
24
 
25
  ## Available functions
26
+ {% if functions %}
27
+ {% for func in functions %}
28
+ - `{{ func }}`
29
+ {% endfor %}
30
+ {% else %}
31
+
32
+ Function list not available.
33
+ {% endif %}
34
+ {% if layers %}
35
+
36
+ ## Available layers
37
+ {% for layer in layers %}
38
+ - `{{ layer }}`
39
+ {% endfor %}
40
+ {% endif %}
41
 
42
  ## Benchmarks
43
+ {% if has_benchmark %}
44
+
45
+ Benchmarking script is available for this kernel. Run `kernels benchmark {{ repo_id }} --version {{ version }}`.
46
+ {% else %}
47
+
48
+ No benchmark available yet.
49
+ {% endif %}
50
+ {% if upstream %}
51
+
52
+ ## Upstream
53
+
54
+ The original source code for this kernel comes from {{ upstream }}.
55
+ {% endif %}
56
+ {% if source %}
57
+
58
+ ## Source
59
 
60
+ The kernel-builder formatted source for this kernel is available at {{ source }}.
61
+ {% endif %}
build.toml ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [general]
2
+ name = "selective-update"
3
+ license = "Apache-2.0"
4
+ backends = [
5
+ "rocm",
6
+ ]
7
+ version = 1
8
+ edition = 5
9
+
10
+ [general.hub]
11
+ repo-id = "Ashiedu/selective-update"
12
+
13
+ [torch-noarch]
example.py ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # /// script
2
+ # requires-python = ">=3.13"
3
+ # dependencies = [
4
+ # "kernels",
5
+ # "numpy",
6
+ # "torch",
7
+ # ]
8
+ # ///
9
+
10
+ import platform
11
+ from pathlib import Path
12
+
13
+ import kernels
14
+ import torch
15
+
16
+ # Load the locally built kernel
17
+ kernel = kernels.get_local_kernel(Path("build"), "selective_update")
18
+
19
+ # Select device
20
+ if platform.system() == "Darwin":
21
+ device = torch.device("mps")
22
+ elif hasattr(torch, "xpu") and torch.xpu.is_available():
23
+ device = torch.device("xpu")
24
+ elif torch.version.cuda is not None and torch.cuda.is_available():
25
+ device = torch.device("cuda")
26
+ else:
27
+ device = torch.device("cpu")
28
+
29
+ print(f"Using device: {device}")
30
+
31
+ # Create input tensor
32
+ x = torch.tensor([1.0, 2.0, 3.0], device=device)
33
+ print(f"Input: {x}")
34
+
35
+ # Run kernel (adds 1 to each element)
36
+ result = kernel.selective_update(x)
37
+ print(f"Output: {result}")
38
+
39
+ # Verify result
40
+ expected = x + 1.0
41
+ assert torch.allclose(result, expected), "Kernel output doesn't match expected!"
42
+ print("Success!")
flake.lock ADDED
@@ -0,0 +1,117 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "nodes": {
3
+ "flake-compat": {
4
+ "locked": {
5
+ "lastModified": 1767039857,
6
+ "narHash": "sha256-vNpUSpF5Nuw8xvDLj2KCwwksIbjua2LZCqhV1LNRDns=",
7
+ "owner": "edolstra",
8
+ "repo": "flake-compat",
9
+ "rev": "5edf11c44bc78a0d334f6334cdaf7d60d732daab",
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
+ "rust-overlay": "rust-overlay"
42
+ },
43
+ "locked": {
44
+ "lastModified": 1783738448,
45
+ "narHash": "sha256-vNb0C3LOAPlDPJH2OHbrVND5KrfvNXcl66nQaVp8rRU=",
46
+ "owner": "huggingface",
47
+ "repo": "kernels",
48
+ "rev": "570dcf4a810e6bd9d2433716c2bc6962c6ae46bb",
49
+ "type": "github"
50
+ },
51
+ "original": {
52
+ "owner": "huggingface",
53
+ "repo": "kernels",
54
+ "type": "github"
55
+ }
56
+ },
57
+ "nixpkgs": {
58
+ "locked": {
59
+ "lastModified": 1783284758,
60
+ "narHash": "sha256-tiQ8/qi8I45OOaBBYlVbXoAVkeQzvvTQOv5I45rMw5o=",
61
+ "owner": "NixOS",
62
+ "repo": "nixpkgs",
63
+ "rev": "ec1a11210589d294f0ac99d3290a27e6c73dfa1d",
64
+ "type": "github"
65
+ },
66
+ "original": {
67
+ "owner": "NixOS",
68
+ "repo": "nixpkgs",
69
+ "rev": "ec1a11210589d294f0ac99d3290a27e6c73dfa1d",
70
+ "type": "github"
71
+ }
72
+ },
73
+ "root": {
74
+ "inputs": {
75
+ "kernel-builder": "kernel-builder"
76
+ }
77
+ },
78
+ "rust-overlay": {
79
+ "inputs": {
80
+ "nixpkgs": [
81
+ "kernel-builder",
82
+ "nixpkgs"
83
+ ]
84
+ },
85
+ "locked": {
86
+ "lastModified": 1783320166,
87
+ "narHash": "sha256-l7C/OsjcnWDOk2K3ssj+SBduwL67LashjBqis9+t468=",
88
+ "owner": "oxalica",
89
+ "repo": "rust-overlay",
90
+ "rev": "20ee15370c9256669d66968b89ee20a4b0a4e673",
91
+ "type": "github"
92
+ },
93
+ "original": {
94
+ "owner": "oxalica",
95
+ "repo": "rust-overlay",
96
+ "type": "github"
97
+ }
98
+ },
99
+ "systems": {
100
+ "locked": {
101
+ "lastModified": 1681028828,
102
+ "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
103
+ "owner": "nix-systems",
104
+ "repo": "default",
105
+ "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
106
+ "type": "github"
107
+ },
108
+ "original": {
109
+ "owner": "nix-systems",
110
+ "repo": "default",
111
+ "type": "github"
112
+ }
113
+ }
114
+ },
115
+ "root": "root",
116
+ "version": 7
117
+ }
flake.nix ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ inputs = {
3
+ kernel-builder.url = "github:huggingface/kernels";
4
+ };
5
+ outputs =
6
+ { self, kernel-builder, ... }:
7
+ kernel-builder.lib.genKernelFlakeOutputs {
8
+ inherit self;
9
+ path = ./.;
10
+ };
11
+ }
selective_update_cuda/selective_update.cu ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #include <ATen/cuda/CUDAContext.h>
2
+ #include <c10/cuda/CUDAGuard.h>
3
+ #include <torch/all.h>
4
+
5
+ __global__ void selective_update_kernel(float *__restrict__ out,
6
+ float const *__restrict__ input, const int n) {
7
+ int idx = blockIdx.x * blockDim.x + threadIdx.x;
8
+ if (idx < n) {
9
+ out[idx] = input[idx] + 1.0f;
10
+ }
11
+ }
12
+
13
+ void selective_update(torch::Tensor &out, torch::Tensor const &input) {
14
+ TORCH_CHECK(input.device().is_cuda(), "input must be a CUDA tensor");
15
+ TORCH_CHECK(input.is_contiguous(), "input must be contiguous");
16
+ TORCH_CHECK(input.scalar_type() == at::ScalarType::Float,
17
+ "selective_update only supports float32");
18
+ TORCH_CHECK(input.sizes() == out.sizes(),
19
+ "Tensors must have the same shape");
20
+ TORCH_CHECK(input.scalar_type() == out.scalar_type(),
21
+ "Tensors must have the same dtype");
22
+ TORCH_CHECK(input.device() == out.device(),
23
+ "Tensors must be on the same device");
24
+
25
+ int n = input.numel();
26
+ int threads = 256;
27
+ int blocks = (n + threads - 1) / threads;
28
+
29
+ const at::cuda::OptionalCUDAGuard device_guard(device_of(input));
30
+ const cudaStream_t stream = at::cuda::getCurrentCUDAStream();
31
+ selective_update_kernel<<<blocks, threads, 0, stream>>>(
32
+ out.data_ptr<float>(), input.data_ptr<float>(), n);
33
+ }
tests/__init__.py ADDED
File without changes
tests/test_selective_update.py ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import platform
2
+
3
+ import torch
4
+
5
+ import selective_update
6
+
7
+
8
+ def test_selective_update():
9
+ if platform.system() == "Darwin":
10
+ device = torch.device("mps")
11
+ elif hasattr(torch, "xpu") and torch.xpu.is_available():
12
+ device = torch.device("xpu")
13
+ elif torch.version.cuda is not None and torch.cuda.is_available():
14
+ device = torch.device("cuda")
15
+ else:
16
+ device = torch.device("cpu")
17
+
18
+ x = torch.randn(1024, 1024, dtype=torch.float32, device=device)
19
+ expected = x + 1.0
20
+ result = selective_update.selective_update(x)
21
+ torch.testing.assert_close(result, expected)
torch-ext/selective_update/__init__.py ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Optional
2
+
3
+ import torch
4
+
5
+ from .functional import selective_state_update
6
+
7
+
8
+ def selective_update(x: torch.Tensor, out: Optional[torch.Tensor] = None) -> torch.Tensor:
9
+ """Legacy scaffold kernel — adds 1.0 to each element."""
10
+ if out is None:
11
+ out = torch.empty_like(x)
12
+ out.copy_(x + 1.0)
13
+ return out
14
+
15
+
16
+ __all__ = ["selective_update", "selective_state_update"]
torch-ext/selective_update/_autotune_configs.py ADDED
@@ -0,0 +1,101 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import warnings
3
+ from packaging import version
4
+
5
+ import torch
6
+
7
+ try:
8
+ import triton
9
+ TRITON_VERSION = version.parse(triton.__version__)
10
+ except ImportError:
11
+ TRITON_VERSION = version.parse("0.0.0")
12
+
13
+ TRITON_HAS_CACHE_RESULTS = TRITON_VERSION >= version.parse("3.4.0")
14
+ _autotune_warning_issued = False
15
+ _deterministic_override = None
16
+
17
+
18
+ def use_deterministic_mode():
19
+ if _deterministic_override is not None:
20
+ return _deterministic_override
21
+ env = os.environ.get("MAMBA_DETERMINISTIC")
22
+ if env:
23
+ return env[0] == "1"
24
+ return torch.are_deterministic_algorithms_enabled()
25
+
26
+
27
+ def set_deterministic_mode(value):
28
+ global _deterministic_override
29
+ _deterministic_override = value
30
+
31
+
32
+ def is_gfx1030():
33
+ """Identifies the HIP gfx1030 hardware."""
34
+ if not torch.cuda.is_available():
35
+ return False
36
+ try:
37
+ device_cap = torch.cuda.get_device_capability()
38
+ return device_cap == (10, 3)
39
+ except Exception:
40
+ return False
41
+
42
+
43
+ def gfx1030_selective_update_configs(configs):
44
+ """Pin safe tile configs for selective-update on gfx1030."""
45
+ if not is_gfx1030():
46
+ return configs
47
+
48
+ expected = {
49
+ "BLOCK_SIZE_M": 32,
50
+ "BLOCK_SIZE_DSTATE": 64,
51
+ }
52
+ selected = [
53
+ config
54
+ for config in configs
55
+ if all(config.kwargs.get(key) == value for key, value in expected.items())
56
+ ]
57
+ if not selected:
58
+ return [configs[0]]
59
+ return selected[:1]
60
+
61
+
62
+ def _estimate_config_cost(cfg):
63
+ block_product = 1
64
+ for key, val in cfg.kwargs.items():
65
+ if key.startswith("BLOCK_SIZE_"):
66
+ block_product *= val
67
+ return block_product * (getattr(cfg, "num_stages", 1) or 1)
68
+
69
+
70
+ def _filter_configs_by_block_sizes(configs):
71
+ env_filters = {}
72
+ for suffix in ("M", "N", "K", "DSTATE"):
73
+ env_val = os.environ.get(f"TRITON_AUTOTUNE_BLOCK_SIZE_{suffix}")
74
+ if env_val is not None:
75
+ env_filters[f"BLOCK_SIZE_{suffix}"] = int(env_val)
76
+ if not env_filters:
77
+ return None
78
+ matching = configs
79
+ for key, target in env_filters.items():
80
+ matching = [c for c in matching if c.kwargs.get(key) == target]
81
+ return matching[:1] if matching else None
82
+
83
+
84
+ def autotune_configs(configs):
85
+ if not configs or not use_deterministic_mode():
86
+ return configs
87
+ if TRITON_HAS_CACHE_RESULTS and os.environ.get("TRITON_CACHE_AUTOTUNING") == "1":
88
+ return configs
89
+ global _autotune_warning_issued
90
+ if not _autotune_warning_issued:
91
+ _autotune_warning_issued = True
92
+ msg = (
93
+ "Deterministic mode: set TRITON_CACHE_AUTOTUNING=1 for cached autotuning."
94
+ if TRITON_HAS_CACHE_RESULTS
95
+ else "Deterministic mode: upgrade to Triton >= 3.4.0 for cached autotuning."
96
+ )
97
+ warnings.warn(msg)
98
+ filtered = _filter_configs_by_block_sizes(configs)
99
+ if filtered:
100
+ return filtered
101
+ return [min(configs, key=_estimate_config_cost)]
torch-ext/selective_update/functional.py ADDED
@@ -0,0 +1,165 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Python autograd wrapper for selective-update Triton kernel.
2
+
3
+ Matches upstream ``mamba_ssm.ops.triton.selective_state_update`` ABI exactly:
4
+ state: (B, D, N) or (B, H, D, N)
5
+ x: (B, D) or (B, H, D)
6
+ dt: (B, D) or (B, H, D)
7
+ A: (D, N) or (H, D, N)
8
+ B: (B, N) or (B, G, N)
9
+ C: (B, N) or (B, G, N)
10
+ D: (D,) or (H, D) or None
11
+ z: (B, D) or (B, H, D) or None
12
+ dt_bias: (D,) or (H, D) or None
13
+ state_batch_indices: (B,) or None
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ import importlib.util
19
+ from pathlib import Path
20
+
21
+ import torch
22
+ import triton
23
+
24
+ _TRITON_KERNELS_PATH = (
25
+ Path(__file__).resolve().parent / "triton_kernels.py"
26
+ )
27
+
28
+
29
+ def _load_triton_kernel():
30
+ spec = importlib.util.spec_from_file_location(
31
+ "selective_update.triton_kernels",
32
+ _TRITON_KERNELS_PATH,
33
+ )
34
+ module = importlib.util.module_from_spec(spec)
35
+ spec.loader.exec_module(module)
36
+ return module.selective_state_update_kernel
37
+
38
+
39
+ selective_state_update_kernel = None # populated on first call
40
+
41
+
42
+ def _get_kernel():
43
+ global selective_state_update_kernel
44
+ if selective_state_update_kernel is None:
45
+ selective_state_update_kernel = _load_triton_kernel()
46
+ return selective_state_update_kernel
47
+
48
+
49
+ def _resolve_tie_hdim(A, dt, dt_bias):
50
+ if A.dim() == 2:
51
+ a0 = A[0]
52
+ return a0.stride(0) == 0 and a0.stride(1) == 0 and dt.stride(-1) == 0 and (dt_bias.stride(-1) == 0 if dt_bias is not None else True)
53
+ return A.stride(-1) == 0 and A.stride(-2) == 0 and dt.stride(-1) == 0 and (dt_bias.stride(-1) == 0 if dt_bias is not None else True)
54
+
55
+
56
+ class SelectiveUpdateFunction(torch.autograd.Function):
57
+ """Differentiable single-token selective state update.
58
+
59
+ Mirrors upstream ``selective_state_update`` shape contract exactly.
60
+ Backward raises NotImplementedError — training uses the patched
61
+ selective_scan reverse path instead.
62
+ """
63
+
64
+ @staticmethod
65
+ def forward(ctx, state, x, dt, A, B, C, D=None, z=None, dt_bias=None, dt_softplus=False, state_batch_indices=None):
66
+ orig_state = state
67
+ has_heads = state.dim() > 3
68
+ if state.dim() == 3:
69
+ state = state.unsqueeze(1)
70
+ if x.dim() == 2:
71
+ x = x.unsqueeze(1)
72
+ if dt.dim() == 2:
73
+ dt = dt.unsqueeze(1)
74
+ if A.dim() == 2:
75
+ A = A.unsqueeze(0)
76
+ if B.dim() == 2:
77
+ B = B.unsqueeze(1)
78
+ if C.dim() == 2:
79
+ C = C.unsqueeze(1)
80
+ if D is not None and D.dim() == 1:
81
+ D = D.unsqueeze(0)
82
+ if z is not None and z.dim() == 2:
83
+ z = z.unsqueeze(1)
84
+ if dt_bias is not None and dt_bias.dim() == 1:
85
+ dt_bias = dt_bias.unsqueeze(0)
86
+
87
+ B_sz, H, D_sz, N_sz = state.shape
88
+ x_sz = x.shape[0]
89
+ if x.shape != (x_sz, H, D_sz):
90
+ raise ValueError(f"x shape {x.shape} does not match state batch/heads/dim")
91
+ if dt.shape != (x_sz, H, D_sz):
92
+ raise ValueError(f"dt shape {dt.shape} does not match x shape")
93
+ if A.shape != (H, D_sz, N_sz):
94
+ raise ValueError(f"A shape {A.shape} does not match (H, D, N)")
95
+ ngroups = B.shape[1]
96
+ if H % ngroups != 0:
97
+ raise ValueError(f"nheads {H} must be divisible by ngroups {ngroups}")
98
+ if B.shape[0] != x_sz or B.shape[2] != N_sz or C.shape[0] != x_sz or C.shape[2] != N_sz:
99
+ raise ValueError(f"B/C batch or dstate mismatch")
100
+ if D is not None and D.shape not in {(H, D_sz), (H,)}:
101
+ raise ValueError(f"D shape {D.shape} does not match (H, D) or (H,)")
102
+ if z is not None and z.shape != (x_sz, H, D_sz):
103
+ raise ValueError(f"z shape {z.shape} does not match x shape")
104
+ if dt_bias is not None and dt_bias.shape not in {(H, D_sz), (H,)}:
105
+ raise ValueError(f"dt_bias shape {dt_bias.shape} does not match (H, D) or (H,)")
106
+ if state_batch_indices is not None and state_batch_indices.shape != (x_sz,):
107
+ raise ValueError(f"state_batch_indices shape {state_batch_indices.shape} does not match (B,)")
108
+
109
+ tie_hdim = _resolve_tie_hdim(A, dt, dt_bias)
110
+ nheads_ratio = H // ngroups
111
+
112
+ out = torch.empty_like(x)
113
+ grid = lambda META: (triton.cdiv(D_sz, META["BLOCK_SIZE_M"]), x_sz, H)
114
+
115
+ BLOCK_M = 32 if N_sz <= 16 else (16 if N_sz <= 32 else (8 if N_sz <= 64 else (4 if N_sz <= 128 else 4)))
116
+ num_warps = 4 if N_sz <= 64 else 8
117
+
118
+ z_strides = ((z.stride(0), z.stride(1), z.stride(2)) if z is not None else (0, 0, 0))
119
+
120
+ _get_kernel()[grid](
121
+ state, x, dt, dt_bias, A, B, C, D, z, out, state_batch_indices,
122
+ x_sz, H, D_sz, N_sz, nheads_ratio,
123
+ state.stride(0), state.stride(1), state.stride(2), state.stride(3),
124
+ x.stride(0), x.stride(1), x.stride(2),
125
+ dt.stride(0), dt.stride(1), dt.stride(2),
126
+ dt_bias.stride(0) if dt_bias is not None else 0, dt_bias.stride(1) if dt_bias is not None else 0,
127
+ A.stride(0), A.stride(1), A.stride(2),
128
+ B.stride(0), B.stride(1), B.stride(2),
129
+ C.stride(0), C.stride(1), C.stride(2),
130
+ D.stride(0) if D is not None else 0, D.stride(1) if D is not None else 0,
131
+ z_strides[0], z_strides[1], z_strides[2],
132
+ out.stride(0), out.stride(1), out.stride(2),
133
+ dt_softplus,
134
+ tie_hdim,
135
+ BLOCK_M,
136
+ num_warps=num_warps,
137
+ num_stages=2,
138
+ )
139
+
140
+ if not has_heads:
141
+ out = out.squeeze(1)
142
+ state = state.squeeze(1)
143
+ ctx.save_for_backward(orig_state, x, dt, A, B, C, D, z, dt_bias, state, state_batch_indices)
144
+ ctx.tie_hdim = tie_hdim
145
+ ctx.dt_softplus = dt_softplus
146
+ ctx.has_heads = has_heads
147
+ ctx.nheads_ratio = nheads_ratio
148
+ return out
149
+
150
+ @staticmethod
151
+ def backward(ctx, grad_out):
152
+ raise NotImplementedError(
153
+ "SelectiveUpdateFunction backward is not implemented. "
154
+ "Use autograd through the patched selective_scan path for training."
155
+ )
156
+
157
+
158
+ def selective_state_update(state, x, dt, A, B, C, D=None, z=None, dt_bias=None, dt_softplus=False, state_batch_indices=None, **kwargs):
159
+ """Dispatches single-token state updates to the custom Triton backend.
160
+
161
+ Shapes mirror upstream ``mamba_ssm.ops.triton.selective_state_update`` exactly.
162
+ """
163
+ return SelectiveUpdateFunction.apply(
164
+ state, x, dt, A, B, C, D, z, dt_bias, dt_softplus, state_batch_indices
165
+ )
torch-ext/selective_update/triton_kernels.py ADDED
@@ -0,0 +1,127 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Triton kernels for Mamba-2 single-token selective state updates.
2
+
3
+ Matches upstream ``mamba_ssm.ops.triton.selective_state_update`` ABI exactly:
4
+ state: (B, H, D, N) x/dt: (B, H, D) A: (H, D, N) B/C: (B, G, N)
5
+ """
6
+
7
+ import triton
8
+ import triton.language as tl
9
+
10
+
11
+ @triton.jit
12
+ def _softplus(dt):
13
+ return tl.math.log(tl.math.exp(dt) + 1)
14
+
15
+
16
+ @triton.heuristics({"HAS_DT_BIAS": lambda args: args["dt_bias_ptr"] is not None})
17
+ @triton.heuristics({"HAS_D": lambda args: args["D_ptr"] is not None})
18
+ @triton.heuristics({"HAS_Z": lambda args: args["z_ptr"] is not None})
19
+ @triton.heuristics({"HAS_STATE_BATCH_INDICES": lambda args: args["state_batch_indices_ptr"] is not None})
20
+ @triton.heuristics({"BLOCK_SIZE_DSTATE": lambda args: triton.next_power_of_2(args["dstate"])})
21
+ @triton.jit
22
+ def selective_state_update_kernel(
23
+ state_ptr, x_ptr, dt_ptr, dt_bias_ptr, A_ptr, B_ptr, C_ptr, D_ptr, z_ptr, out_ptr, state_batch_indices_ptr,
24
+ batch, nheads, dim, dstate, nheads_ngroups_ratio,
25
+ s_state_batch, s_state_head, s_state_dim, s_state_dstate,
26
+ s_x_batch, s_x_head, s_x_dim,
27
+ s_dt_batch, s_dt_head, s_dt_dim,
28
+ s_dt_bias_head, s_dt_bias_dim,
29
+ s_A_head, s_A_dim, s_A_dstate,
30
+ s_B_batch, s_B_group, s_B_dstate,
31
+ s_C_batch, s_C_group, s_C_dstate,
32
+ s_D_head, s_D_dim,
33
+ s_z_batch, s_z_head, s_z_dim,
34
+ s_out_batch, s_out_head, s_out_dim,
35
+ DT_SOFTPLUS: tl.constexpr,
36
+ TIE_HDIM: tl.constexpr,
37
+ BLOCK_SIZE_M: tl.constexpr,
38
+ HAS_DT_BIAS: tl.constexpr,
39
+ HAS_D: tl.constexpr,
40
+ HAS_Z: tl.constexpr,
41
+ HAS_STATE_BATCH_INDICES: tl.constexpr,
42
+ BLOCK_SIZE_DSTATE: tl.constexpr,
43
+ ):
44
+ pid_m = tl.program_id(axis=0)
45
+ pid_b = tl.program_id(axis=1)
46
+ pid_h = tl.program_id(axis=2)
47
+
48
+ offs_m = pid_m * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M)
49
+ out_ptr += pid_b * s_out_batch + pid_h * s_out_head
50
+ out_ptrs = out_ptr + offs_m * s_out_dim
51
+
52
+ if HAS_STATE_BATCH_INDICES:
53
+ state_batch_indices_ptr += pid_b
54
+ state_batch_idx = tl.load(state_batch_indices_ptr)
55
+ if state_batch_idx < 0:
56
+ tl.store(out_ptrs, 0.0, mask=offs_m < dim)
57
+ return
58
+ state_ptr += state_batch_idx * s_state_batch + pid_h * s_state_head
59
+ else:
60
+ state_ptr += pid_b * s_state_batch + pid_h * s_state_head
61
+
62
+ x_ptr += pid_b * s_x_batch + pid_h * s_x_head
63
+ dt_ptr += pid_b * s_dt_batch + pid_h * s_dt_head
64
+ if HAS_DT_BIAS:
65
+ dt_bias_ptr += pid_h * s_dt_bias_head
66
+ A_ptr += pid_h * s_A_head
67
+ B_ptr += pid_b * s_B_batch + (pid_h // nheads_ngroups_ratio) * s_B_group
68
+ C_ptr += pid_b * s_C_batch + (pid_h // nheads_ngroups_ratio) * s_C_group
69
+ if HAS_Z:
70
+ z_ptr += pid_b * s_z_batch + pid_h * s_z_head
71
+
72
+ offs_n = tl.arange(0, BLOCK_SIZE_DSTATE)
73
+ state_ptrs = state_ptr + (offs_m[:, None] * s_state_dim + offs_n[None, :] * s_state_dstate)
74
+ x_ptrs = x_ptr + offs_m * s_x_dim
75
+ dt_ptrs = dt_ptr + offs_m * s_dt_dim
76
+ if HAS_DT_BIAS:
77
+ dt_bias_ptrs = dt_bias_ptr + offs_m * s_dt_bias_dim
78
+ if HAS_D:
79
+ D_ptr += pid_h * s_D_head
80
+ A_ptrs = A_ptr + (offs_m[:, None] * s_A_dim + offs_n[None, :] * s_A_dstate)
81
+ B_ptrs = B_ptr + offs_n * s_B_dstate
82
+ C_ptrs = C_ptr + offs_n * s_C_dstate
83
+ if HAS_D:
84
+ D_ptrs = D_ptr + offs_m * s_D_dim
85
+ if HAS_Z:
86
+ z_ptrs = z_ptr + offs_m * s_z_dim
87
+
88
+ state = tl.load(state_ptrs, mask=(offs_m[:, None] < dim) & (offs_n[None, :] < dstate), other=0.0)
89
+ x = tl.load(x_ptrs, mask=offs_m < dim, other=0.0).to(tl.float32)
90
+ if not TIE_HDIM:
91
+ dt = tl.load(dt_ptrs, mask=offs_m < dim, other=0.0).to(tl.float32)
92
+ if HAS_DT_BIAS:
93
+ dt += tl.load(dt_bias_ptrs, mask=offs_m < dim, other=0.0).to(tl.float32)
94
+ if DT_SOFTPLUS:
95
+ dt = tl.where(dt <= 20.0, _softplus(dt), dt)
96
+ A = tl.load(A_ptrs, mask=(offs_m[:, None] < dim) & (offs_n[None, :] < dstate), other=0.0).to(tl.float32)
97
+ dA = tl.exp(A * dt[:, None])
98
+ else:
99
+ dt = tl.load(dt_ptr).to(tl.float32)
100
+ if HAS_DT_BIAS:
101
+ dt += tl.load(dt_bias_ptr).to(tl.float32)
102
+ if DT_SOFTPLUS:
103
+ dt = tl.where(dt <= 20.0, _softplus(dt), dt)
104
+ A = tl.load(A_ptr).to(tl.float32)
105
+ dA = tl.exp(A * dt)
106
+
107
+ B = tl.load(B_ptrs, mask=offs_n < dstate, other=0.0).to(tl.float32)
108
+ C = tl.load(C_ptrs, mask=offs_n < dstate, other=0.0).to(tl.float32)
109
+ if HAS_D:
110
+ D = tl.load(D_ptrs, mask=offs_m < dim, other=0.0).to(tl.float32)
111
+ if HAS_Z:
112
+ z = tl.load(z_ptrs, mask=offs_m < dim, other=0.0).to(tl.float32)
113
+
114
+ if not TIE_HDIM:
115
+ dB = B[None, :] * dt[:, None]
116
+ else:
117
+ dB = B * dt
118
+
119
+ state = state * dA + dB * x[:, None]
120
+ tl.store(state_ptrs, state, mask=(offs_m[:, None] < dim) & (offs_n[None, :] < dstate))
121
+ out = tl.sum(state * C[None, :], axis=1)
122
+ if HAS_D:
123
+ out += x * D
124
+ if HAS_Z:
125
+ out *= z * tl.sigmoid(z)
126
+
127
+ tl.store(out_ptrs, out, mask=offs_m < dim)
torch-ext/torch_binding.cpp ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #include <torch/library.h>
2
+
3
+ #include "registration.h"
4
+ #include "torch_binding.h"
5
+
6
+ TORCH_LIBRARY_EXPAND(TORCH_EXTENSION_NAME, ops) {
7
+ ops.def("selective_update(Tensor! out, Tensor input) -> ()");
8
+ #if defined(CPU_KERNEL)
9
+ ops.impl("selective_update", torch::kCPU, &selective_update);
10
+ #elif defined(CUDA_KERNEL) || defined(ROCM_KERNEL)
11
+ ops.impl("selective_update", torch::kCUDA, &selective_update);
12
+ #elif defined(METAL_KERNEL)
13
+ ops.impl("selective_update", torch::kMPS, selective_update);
14
+ #elif defined(XPU_KERNEL)
15
+ ops.impl("selective_update", torch::kXPU, &selective_update);
16
+ #endif
17
+ }
18
+
19
+ REGISTER_EXTENSION(TORCH_EXTENSION_NAME)
torch-ext/torch_binding.h ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ #pragma once
2
+
3
+ #include <torch/torch.h>
4
+
5
+ void selective_update(torch::Tensor &out, torch::Tensor const &input);