kernels-bot commited on
Commit
89181fc
·
verified ·
1 Parent(s): 6508fac

Uploaded using `kernel-builder`.

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. build/torch-rocm/__init__.py +96 -0
  2. build/torch-rocm/_aiter_compat/__init__.py +30 -0
  3. build/torch-rocm/_aiter_compat/chip_info.py +48 -0
  4. build/torch-rocm/_aiter_compat/dtypes.py +45 -0
  5. build/torch-rocm/_aiter_compat/torch_guard.py +30 -0
  6. build/torch-rocm/_aiter_compat/triton_metadata_redirect.py +165 -0
  7. build/torch-rocm/_gluon_kernels/__init__.py +2 -0
  8. build/torch-rocm/_gluon_kernels/gfx1250/fusions/__init__.py +0 -0
  9. build/torch-rocm/_gluon_kernels/gfx1250/fusions/fused_kv_cache.py +680 -0
  10. build/torch-rocm/_gluon_kernels/gfx1250/gemm/basic/__init__.py +0 -0
  11. build/torch-rocm/_gluon_kernels/gfx1250/gemm/basic/gemm_a16w16.py +703 -0
  12. build/torch-rocm/_gluon_kernels/gfx1250/gemm/basic/gemm_mxfp4.py +401 -0
  13. build/torch-rocm/_gluon_kernels/gfx1250/moe/__init__.py +0 -0
  14. build/torch-rocm/_gluon_kernels/gfx1250/moe/moe_op_gemm_a8w4.py +1170 -0
  15. build/torch-rocm/_gluon_kernels/gfx1250/norm/__init__.py +0 -0
  16. build/torch-rocm/_gluon_kernels/gfx1250/norm/fused_rmsnorm_add.py +122 -0
  17. build/torch-rocm/_gluon_kernels/gfx942/__init__.py +0 -0
  18. build/torch-rocm/_gluon_kernels/gfx942/moe/__init__.py +0 -0
  19. build/torch-rocm/_gluon_kernels/gfx942/moe/moe_op_gemm_int8_smoothquant.py +273 -0
  20. build/torch-rocm/_ops.py +38 -0
  21. build/torch-rocm/_triton_kernels/__init__.py +0 -0
  22. build/torch-rocm/_triton_kernels/activation.py +317 -0
  23. build/torch-rocm/_triton_kernels/causal_conv1d.py +631 -0
  24. build/torch-rocm/_triton_kernels/causal_conv1d_update_single_token.py +554 -0
  25. build/torch-rocm/_triton_kernels/common/__init__.py +0 -0
  26. build/torch-rocm/_triton_kernels/common/splitk_reduce.py +89 -0
  27. build/torch-rocm/_triton_kernels/fusions/__init__.py +20 -0
  28. build/torch-rocm/_triton_kernels/fusions/fused_bmm_rope_kv_cache.py +1043 -0
  29. build/torch-rocm/_triton_kernels/fusions/fused_clamp_act_mul.py +173 -0
  30. build/torch-rocm/_triton_kernels/fusions/fused_kv_cache.py +1124 -0
  31. build/torch-rocm/_triton_kernels/fusions/fused_mul_add.py +47 -0
  32. build/torch-rocm/_triton_kernels/fusions/fused_qk_concat.py +285 -0
  33. build/torch-rocm/_triton_kernels/fusions/fused_reduce_qk_norm_rope_swa_write.py +289 -0
  34. build/torch-rocm/_triton_kernels/fusions/fused_routing_from_topk.py +175 -0
  35. build/torch-rocm/_triton_kernels/fusions/mhc.py +1277 -0
  36. build/torch-rocm/_triton_kernels/gated_delta_rule/__init__.py +50 -0
  37. build/torch-rocm/_triton_kernels/gated_delta_rule/decode/__init__.py +21 -0
  38. build/torch-rocm/_triton_kernels/gated_delta_rule/decode/causal_conv1d_split_qkv.py +1098 -0
  39. build/torch-rocm/_triton_kernels/gated_delta_rule/decode/fused_rearrange_sigmoid_gdr.py +165 -0
  40. build/torch-rocm/_triton_kernels/gated_delta_rule/decode/fused_recurrent.py +191 -0
  41. build/torch-rocm/_triton_kernels/gated_delta_rule/decode/fused_sigmoid_gating_recurrent.py +266 -0
  42. build/torch-rocm/_triton_kernels/gated_delta_rule/fused_qkvzba_split.py +580 -0
  43. build/torch-rocm/_triton_kernels/gated_delta_rule/gated_delta_rule_utils.py +580 -0
  44. build/torch-rocm/_triton_kernels/gated_delta_rule/prefill/__init__.py +43 -0
  45. build/torch-rocm/_triton_kernels/gated_delta_rule/prefill/causal_conv1d_fwd_split_qkv.py +399 -0
  46. build/torch-rocm/_triton_kernels/gated_delta_rule/prefill/chunk.py +374 -0
  47. build/torch-rocm/_triton_kernels/gated_delta_rule/prefill/chunk_delta_h.py +1455 -0
  48. build/torch-rocm/_triton_kernels/gated_delta_rule/prefill/chunk_o.py +1197 -0
  49. build/torch-rocm/_triton_kernels/gated_delta_rule/prefill/fused_cumsum_kkt.py +339 -0
  50. build/torch-rocm/_triton_kernels/gated_delta_rule/prefill/fused_gdn_gating_prefill.py +80 -0
build/torch-rocm/__init__.py ADDED
@@ -0,0 +1,96 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (C) 2024-2026, Advanced Micro Devices, Inc. All rights reserved.
3
+ """AITER Triton kernels for AMD ROCm.
4
+
5
+ Repackaged from the ``aiter/ops/triton/**`` subtree of the
6
+ `ROCm/aiter <https://github.com/ROCm/aiter>`_ project as a self-contained
7
+ Hugging Face Hub kernel. Each subpackage under :mod:`aiter_kernels` maps 1:1
8
+ to the equivalent upstream module under ``aiter.ops.triton``.
9
+
10
+ Flash Attention is **not** included here — it lives in
11
+ ``kernels-community/aiter-flash-attn`` and is synced separately.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ from . import _aiter_compat # noqa: F401 (must be importable before ops)
17
+
18
+ from . import activation
19
+ from . import causal_conv1d
20
+ from . import causal_conv1d_update_single_token
21
+ from . import fusions
22
+ from . import gated_delta_net
23
+ from . import gather_kv_b_proj
24
+ from . import gemm
25
+ from . import gluon
26
+ from . import gmm
27
+ from . import kv_cache
28
+ from . import moe
29
+ from . import normalization
30
+ from . import quant
31
+ from . import rope
32
+ # Top-level re-exports for drop-in parity with the standalone ``aiter-rope`` repo.
33
+ from .rope import RotateStyle, apply_rotary_transformers
34
+ from . import softmax
35
+ from . import topk
36
+ from . import utils
37
+
38
+ # ``comms`` pulls in iris and is optional — make it import-safe.
39
+ try:
40
+ from . import comms
41
+
42
+ # Re-export communication primitives at this level for convenience
43
+ from .comms import (
44
+ IrisCommContext,
45
+ reduce_scatter,
46
+ all_gather,
47
+ reduce_scatter_rmsnorm_quant_all_gather,
48
+ IRIS_COMM_AVAILABLE,
49
+ )
50
+
51
+ _COMMS_AVAILABLE = True
52
+ except ImportError:
53
+ _COMMS_AVAILABLE = False
54
+ IRIS_COMM_AVAILABLE = False
55
+ comms = None # type: ignore[assignment]
56
+
57
+
58
+ __kernel_metadata__ = {
59
+ "license": "mit",
60
+ }
61
+
62
+
63
+ __all__ = [
64
+ "__kernel_metadata__",
65
+ "RotateStyle",
66
+ "apply_rotary_transformers",
67
+ "activation",
68
+ "causal_conv1d",
69
+ "causal_conv1d_update_single_token",
70
+ "comms",
71
+ "fusions",
72
+ "gated_delta_net",
73
+ "gather_kv_b_proj",
74
+ "gemm",
75
+ "gluon",
76
+ "gmm",
77
+ "kv_cache",
78
+ "moe",
79
+ "normalization",
80
+ "quant",
81
+ "rope",
82
+ "softmax",
83
+ "topk",
84
+ "utils",
85
+ ]
86
+
87
+ if _COMMS_AVAILABLE:
88
+ __all__.extend(
89
+ [
90
+ "IrisCommContext",
91
+ "reduce_scatter",
92
+ "all_gather",
93
+ "reduce_scatter_rmsnorm_quant_all_gather",
94
+ "IRIS_COMM_AVAILABLE",
95
+ ]
96
+ )
build/torch-rocm/_aiter_compat/__init__.py ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Compatibility shim layer for aiter cross-tree dependencies.
2
+
3
+ The upstream ``aiter/ops/triton/**`` Triton ops import a handful of helpers
4
+ from outside the triton subtree:
5
+
6
+ - ``aiter.dtypes`` / ``aiter.utility.dtypes`` — dtype constants keyed by GPU arch.
7
+ - ``aiter.jit.utils.torch_guard.torch_compile_guard`` — torch.compile/torch.library
8
+ registration helper.
9
+ - ``aiter.utility.triton.triton_metadata_redirect.AOTMetadataContext`` — AOT
10
+ metadata-path redirector.
11
+ - ``aiter.jit.utils.chip_info.get_gfx`` — GPU arch query.
12
+
13
+ In a Hub-kernel build we don't ship the rest of upstream aiter — we vendor
14
+ just enough here that the Triton ops import cleanly. The runtime hot paths
15
+ (the actual Triton kernels) do not rely on these helpers; they're used for
16
+ torch.library schema registration, AOT caching, and dtype lookups that we
17
+ re-derive from torch / Triton directly.
18
+ """
19
+
20
+ from . import dtypes
21
+ from . import chip_info
22
+ from . import torch_guard
23
+ from . import triton_metadata_redirect
24
+
25
+ __all__ = [
26
+ "dtypes",
27
+ "chip_info",
28
+ "torch_guard",
29
+ "triton_metadata_redirect",
30
+ ]
build/torch-rocm/_aiter_compat/chip_info.py ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Minimal ``get_gfx()`` replacement.
2
+
3
+ Upstream lives in ``aiter/jit/utils/chip_info.py`` and pulls in the JIT C++
4
+ build machinery. We only need the chip-arch string here.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import functools
10
+ import os
11
+
12
+
13
+ @functools.lru_cache(maxsize=1)
14
+ def get_gfx() -> str:
15
+ """Return the active GPU arch string (e.g. ``"gfx942"``).
16
+
17
+ Resolution order:
18
+ 1. ``GFX_ARCH`` env var (explicit override).
19
+ 2. Triton's active driver target.
20
+ 3. ``torch.cuda.get_device_properties(0).gcnArchName`` (ROCm/HIP only).
21
+ 4. ``""`` if none are available — callers using this for fast-path
22
+ selection should treat that as "unknown arch, use safe defaults".
23
+ """
24
+
25
+ env = os.environ.get("GFX_ARCH")
26
+ if env:
27
+ return env
28
+
29
+ try:
30
+ import triton
31
+
32
+ target = triton.runtime.driver.active.get_current_target()
33
+ arch = getattr(target, "arch", None)
34
+ if isinstance(arch, str) and arch.startswith("gfx"):
35
+ return arch
36
+ except Exception:
37
+ pass
38
+
39
+ try:
40
+ import torch
41
+
42
+ if torch.cuda.is_available():
43
+ name = torch.cuda.get_device_properties(0).gcnArchName
44
+ return name.split(":")[0] if name else ""
45
+ except Exception:
46
+ pass
47
+
48
+ return ""
build/torch-rocm/_aiter_compat/dtypes.py ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Minimal local replacement for ``aiter.utility.dtypes``.
2
+
3
+ Upstream parses a generated C header (``csrc/include/aiter_enum.h``) to derive
4
+ its dtype table and depends on ``aiter.ops.enum`` (which in turn pulls the
5
+ JIT C++ extension). For a Triton-only Hub kernel we only need the dtype
6
+ constants the Triton ops actually reference — none of the enum / header
7
+ machinery is required.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import torch
13
+
14
+ from .chip_info import get_gfx
15
+
16
+
17
+ _FP8_BY_ARCH = {
18
+ "gfx942": getattr(torch, "float8_e4m3fnuz", None),
19
+ "gfx950": getattr(torch, "float8_e4m3fn", None),
20
+ "gfx1200": getattr(torch, "float8_e4m3fn", None),
21
+ "gfx1201": getattr(torch, "float8_e4m3fn", None),
22
+ "gfx1250": getattr(torch, "float8_e4m3fn", None),
23
+ }
24
+
25
+ _8BIT_FALLBACK = torch.uint8
26
+
27
+
28
+ def get_dtype_fp8() -> torch.dtype:
29
+ return _FP8_BY_ARCH.get(get_gfx()) or _8BIT_FALLBACK
30
+
31
+
32
+ i4x2 = getattr(torch, "int4", _8BIT_FALLBACK)
33
+ fp4x2 = getattr(torch, "float4_e2m1fn_x2", _8BIT_FALLBACK)
34
+ fp8 = get_dtype_fp8()
35
+ fp8_e8m0 = getattr(torch, "float8_e8m0fnu", _8BIT_FALLBACK)
36
+ fp16 = torch.float16
37
+ bf16 = torch.bfloat16
38
+ fp32 = torch.float32
39
+ u32 = torch.uint32
40
+ i32 = torch.int32
41
+ i16 = torch.int16
42
+ i8 = torch.int8
43
+ u8 = torch.uint8
44
+ i64 = torch.int64
45
+ u64 = torch.uint64
build/torch-rocm/_aiter_compat/torch_guard.py ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """No-op replacement for ``aiter.jit.utils.torch_guard``.
2
+
3
+ Upstream's ``torch_compile_guard`` registers each decorated function with
4
+ ``torch.library`` under the global ``"aiter"`` namespace and routes calls
5
+ through ``torch.ops.aiter.<name>``. That's appropriate for the full aiter
6
+ install (which ships a C++ extension that also registers ops there) but
7
+ would clash with a parallel ``import aiter`` in the same process and is
8
+ unnecessary for Triton-only kernels.
9
+
10
+ This stub returns the decorated function unmodified — the Triton ops still
11
+ run, just without the ``torch.ops.aiter.*`` indirection.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ from typing import Any, Callable, Optional, Union
17
+
18
+
19
+ def torch_compile_guard(
20
+ mutates_args: Union[list, str] = "unknown",
21
+ device: str = "cpu",
22
+ calling_func_: Optional[Callable[..., Any]] = None,
23
+ gen_fake: Optional[Callable[..., Any]] = None,
24
+ ):
25
+ """No-op decorator factory: returns the function unchanged."""
26
+
27
+ def decorator(func):
28
+ return func
29
+
30
+ return decorator
build/torch-rocm/_aiter_compat/triton_metadata_redirect.py ADDED
@@ -0,0 +1,165 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (C) 2025, Advanced Micro Devices, Inc. All rights reserved.
3
+
4
+ """
5
+ Triton Metadata Redirect Module
6
+
7
+ This module provides decorators and utilities for customizing Triton kernel
8
+ metadata file paths during compilation. It allows redirecting .json and
9
+ .hsaco files to custom directories.
10
+
11
+ Example usage0 for jit:
12
+ from aiter.utility.triton_metadata_redirect import with_custom_metadata_path
13
+
14
+ @with_custom_metadata_path("/custom/path")
15
+ @triton.jit
16
+ def my_kernel(...):
17
+ ...
18
+
19
+ Example usage1 for aot:
20
+ from aiter.utility.triton_metadata_redirect import AOTMetadataContext
21
+
22
+ with AOTMetadataContext("kernel_name", "/custom/path"):
23
+ kernel = compile_kernel(...)
24
+ """
25
+
26
+ import functools
27
+ import os
28
+ from typing import Callable, Dict, Optional
29
+ import threading
30
+ import triton.compiler.compiler as triton_compiler
31
+
32
+ # Use thread-local storage to avoid multi-threading race conditions
33
+ _thread_local = threading.local()
34
+
35
+
36
+ def _get_thread_registry():
37
+ """Get the registry for the current thread"""
38
+ if not hasattr(_thread_local, "replacement_registry"):
39
+ _thread_local.replacement_registry = {}
40
+ return _thread_local.replacement_registry
41
+
42
+
43
+ # Lock to ensure patching happens only once
44
+ _patch_lock = threading.Lock()
45
+ # Flag indicating whether patching has been performed
46
+ _patched = False
47
+
48
+
49
+ def _ensure_patched():
50
+ """Ensure CompiledKernel.__init__ method is patched only once"""
51
+ global _patched
52
+ with _patch_lock:
53
+ if not _patched:
54
+ # Save the original __init__ method
55
+ _original_compiled_kernel_init = triton_compiler.CompiledKernel.__init__
56
+
57
+ def _replacement_init(self, src, metadata_group, hash):
58
+ # Find kernel name from metadata group
59
+ kernel_name = None
60
+ for key in metadata_group:
61
+ if key.endswith(".json"):
62
+ kernel_name = key[:-5] # Remove '.json' suffix
63
+ break
64
+
65
+ # Replace metadata paths using thread-local registry
66
+ if kernel_name:
67
+ registry = _get_thread_registry()
68
+ if kernel_name in registry:
69
+ dir = registry[kernel_name]
70
+ metadata_group[kernel_name + ".json"] = os.path.join(
71
+ dir, f"{kernel_name}.json"
72
+ )
73
+ metadata_group[kernel_name + ".hsaco"] = os.path.join(
74
+ dir, f"{kernel_name}.hsaco"
75
+ )
76
+
77
+ # Call the original initialization method
78
+ _original_compiled_kernel_init(self, src, metadata_group, hash)
79
+
80
+ # Replace the original __init__ method with our patched version
81
+ triton_compiler.CompiledKernel.__init__ = _replacement_init
82
+ _patched = True
83
+
84
+
85
+ # Ensure patching is done when module is loaded
86
+ _ensure_patched()
87
+
88
+
89
+ class AOTMetadataContext:
90
+ """
91
+ Context manager for AOT compilation with custom metadata paths
92
+
93
+ Uses thread-local storage to avoid multi-threading race conditions.
94
+
95
+ Example usage:
96
+ with AOTMetadataContext("kernel_name", "/custom/path"):
97
+ kernel = compile_kernel(...)
98
+ """
99
+
100
+ def __init__(self, kernel_name: str, dir: str):
101
+ self.kernel_name = kernel_name
102
+ self.dir = dir
103
+ self._previously_registered = False
104
+ self._previous_dir: Optional[str] = None
105
+
106
+ def __enter__(self):
107
+ registry = _get_thread_registry()
108
+
109
+ # Save previous registration if it exists
110
+ if self.kernel_name in registry:
111
+ self._previously_registered = True
112
+ self._previous_dir = registry[self.kernel_name]
113
+
114
+ # Register the new path
115
+ registry[self.kernel_name] = self.dir
116
+
117
+ return self
118
+
119
+ def __exit__(self, exc_type, exc_val, exc_tb):
120
+ registry = _get_thread_registry()
121
+
122
+ # Restore previous registration or remove current one
123
+ if self._previously_registered:
124
+ registry[self.kernel_name] = self._previous_dir
125
+ else:
126
+ # Remove our registration if it wasn't previously registered
127
+ registry.pop(self.kernel_name, None)
128
+
129
+ # Don't suppress exceptions
130
+ return False
131
+
132
+
133
+ def with_custom_metadata_path(dir):
134
+ """
135
+ Decorator to register a kernel for metadata path replacement
136
+
137
+ This decorator uses thread-local storage to ensure consistency
138
+ with the context manager approach.
139
+
140
+ Args:
141
+ dir: Directory path where kernel metadata files should be stored
142
+ """
143
+
144
+ def decorator(func: Callable) -> Callable:
145
+ # Save the original function
146
+ original_func = func
147
+
148
+ # Create a wrapper function
149
+ # Currently does nothing but maintains signature
150
+ @functools.wraps(func)
151
+ def wrapper(*args, **kwargs):
152
+ return original_func(*args, **kwargs)
153
+
154
+ # Register the kernel in the thread-local registry
155
+ registry = _get_thread_registry()
156
+ registry[original_func.__name__] = dir
157
+
158
+ # Add decorator markers
159
+ wrapper._with_custom_metadata_path_applied = True
160
+ wrapper._metadata_directory = dir
161
+
162
+ # Return the original function to avoid interfering with @triton.jit
163
+ return original_func
164
+
165
+ return decorator
build/torch-rocm/_gluon_kernels/__init__.py ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (C) 2026, Advanced Micro Devices, Inc. All rights reserved.
build/torch-rocm/_gluon_kernels/gfx1250/fusions/__init__.py ADDED
File without changes
build/torch-rocm/_gluon_kernels/gfx1250/fusions/fused_kv_cache.py ADDED
@@ -0,0 +1,680 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (C) 2024-2026, Advanced Micro Devices, Inc. All rights reserved.
3
+ """
4
+ Gluon (gfx1250) port of ``_fused_qk_rope_cat_and_cache_mla_kernel``.
5
+
6
+ This mirrors the Triton kernel in
7
+ ``aiter/ops/triton/_triton_kernels/fusions/fused_kv_cache.py`` but is written in
8
+ Gluon for explicit control over layouts and load scheduling.
9
+
10
+ The contiguous input tiles (q_nope / q_pe / k_nope / k_pe) are streamed through
11
+ LDS with the gfx1250 TDM engine: ``tdm.async_load`` is issued as early as
12
+ possible (right after the per-program offsets are known, before the scalar
13
+ ``pos`` load / cos-sin gather), and the shared->register ``load`` uses the exact
14
+ downstream distributed layout (``L_NOPE`` / ``L_PE``) so no ``convert_layout`` is
15
+ needed. ``tdm.async_wait`` drains the loads just before the values are consumed,
16
+ overlapping the global-memory latency with the index math and the cos/sin load.
17
+
18
+ ``cos`` / ``sin`` stay on ``buffer_load``: with ``reuse_freqs_front_part`` they are
19
+ a gather (64 positions mapped onto 32 cached freqs), which the contiguous TDM
20
+ tile load cannot express.
21
+
22
+ The RoPE rotation (``_get_neox_rotated_x_1D`` / ``_get_gptj_rotated_x_1D``) and the
23
+ NVFP4 quantizer (``_nvfp4_quant_op``) are reused from the Triton ``@triton.jit``
24
+ helpers.
25
+ """
26
+
27
+ from triton.experimental import gluon
28
+ from triton.experimental.gluon import language as gl
29
+
30
+ from ...._triton_kernels.rope.rope import (
31
+ _get_neox_rotated_x_1D,
32
+ _get_gptj_rotated_x_1D,
33
+ )
34
+ from ...._triton_kernels.quant.quant import _nvfp4_quant_op
35
+
36
+
37
+ @gluon.constexpr_function
38
+ def _store_blocked_layout(R, C):
39
+ """Pick a wave32 blocked layout for an (R, C) shuffled store tile.
40
+
41
+ Lanes are spread over the row dim first (one row per lane, contiguous
42
+ ``C``-chunk per thread), matching the Triton-generated layouts:
43
+ (64, 8) -> [1,8]/[32,1] (8, 8) -> [1,2]/[8,4]
44
+ """
45
+ lanes_row = min(R, 32)
46
+ lanes_col = 32 // lanes_row
47
+ spt_col = max(1, C // lanes_col)
48
+ return gl.BlockedLayout(
49
+ size_per_thread=[1, spt_col],
50
+ threads_per_warp=[lanes_row, lanes_col],
51
+ warps_per_cta=[1, 1],
52
+ order=[1, 0],
53
+ )
54
+
55
+
56
+ @gluon.jit
57
+ def _make_tdm_desc_1d(base_ptr, stride, N: gl.constexpr, layout: gl.constexpr):
58
+ """Issue an async TDM load of a contiguous 1D tile (base already offset)."""
59
+ desc = gl.amd.gfx1250.tdm.make_tensor_descriptor(
60
+ base=base_ptr,
61
+ shape=[N],
62
+ strides=[stride],
63
+ block_shape=[N],
64
+ layout=layout,
65
+ )
66
+ return desc
67
+
68
+
69
+ @gluon.jit
70
+ def _issue_tdm_load_1d(desc, offset, smem):
71
+ """Issue an async TDM load of a contiguous 1D tile (base already offset)."""
72
+ gl.amd.gfx1250.tdm.async_load(desc, [offset], smem)
73
+
74
+
75
+ @gluon.jit
76
+ def _store_mla_kv_cache(
77
+ kv_cache_ptr,
78
+ pid_t_slot,
79
+ pid_hk,
80
+ pid_blk,
81
+ d_nope_offs,
82
+ d_pe_offs,
83
+ kv_cache_stride_b,
84
+ kv_cache_stride_h,
85
+ kv_cache_stride_d,
86
+ k_nope,
87
+ k_pe,
88
+ BLOCK_D_nope: gl.constexpr,
89
+ BLOCK_D_pe: gl.constexpr,
90
+ BLOCK_SIZE: gl.constexpr,
91
+ SHUFFLED_KV_CACHE: gl.constexpr,
92
+ SCALE_K_WIDTH_NOPE: gl.constexpr,
93
+ SCALE_K_WIDTH_ROPE: gl.constexpr,
94
+ L_NOPE: gl.constexpr,
95
+ L_PE: gl.constexpr,
96
+ ):
97
+ if SHUFFLED_KV_CACHE:
98
+ if kv_cache_ptr.dtype.element_ty == gl.bfloat16:
99
+ # BF16
100
+ K_WIDTH: gl.constexpr = 8
101
+ else:
102
+ # FP8 E4M3 or packed FP4 E2M1
103
+ K_WIDTH: gl.constexpr = 16
104
+
105
+ if kv_cache_ptr.dtype.element_ty == gl.uint8:
106
+ NVFP4_QUANT_BLOCK_SIZE: gl.constexpr = 16
107
+ k_nope, k_nope_scales = _nvfp4_quant_op(
108
+ k_nope, BLOCK_D_nope, 1, NVFP4_QUANT_BLOCK_SIZE
109
+ )
110
+ k_pe, k_pe_scales = _nvfp4_quant_op(
111
+ k_pe, BLOCK_D_pe, 1, NVFP4_QUANT_BLOCK_SIZE
112
+ )
113
+ BLOCK_D_nope_STORE: gl.constexpr = BLOCK_D_nope // 2
114
+ BLOCK_D_pe_STORE: gl.constexpr = BLOCK_D_pe // 2
115
+ else:
116
+ BLOCK_D_nope_STORE: gl.constexpr = BLOCK_D_nope
117
+ BLOCK_D_pe_STORE: gl.constexpr = BLOCK_D_pe
118
+
119
+ R_nope: gl.constexpr = BLOCK_D_nope_STORE // K_WIDTH
120
+ R_pe: gl.constexpr = BLOCK_D_pe_STORE // K_WIDTH
121
+ PARENT_NOPE: gl.constexpr = _store_blocked_layout(R_nope, K_WIDTH)
122
+ PARENT_PE: gl.constexpr = _store_blocked_layout(R_pe, K_WIDTH)
123
+
124
+ d_nope_offs_shfl = gl.arange(0, R_nope, layout=gl.SliceLayout(1, PARENT_NOPE))
125
+ d_pe_offs_shfl = gl.arange(0, R_pe, layout=gl.SliceLayout(1, PARENT_PE))
126
+ k_width_shfl_nope = gl.arange(0, K_WIDTH, layout=gl.SliceLayout(0, PARENT_NOPE))
127
+ k_width_shfl_pe = gl.arange(0, K_WIDTH, layout=gl.SliceLayout(0, PARENT_PE))
128
+
129
+ k_nope = gl.convert_layout(gl.reshape(k_nope, [R_nope, K_WIDTH]), PARENT_NOPE)
130
+ k_pe = gl.convert_layout(gl.reshape(k_pe, [R_pe, K_WIDTH]), PARENT_PE)
131
+
132
+ kv_cache_base = (
133
+ kv_cache_ptr + pid_t_slot * kv_cache_stride_b + pid_hk * kv_cache_stride_h
134
+ )
135
+
136
+ kv_cache_nope_offs = (
137
+ (pid_blk // 16) * BLOCK_D_nope_STORE * 16
138
+ + (pid_blk % 16) * K_WIDTH
139
+ + d_nope_offs_shfl[:, None] * K_WIDTH * 16
140
+ + k_width_shfl_nope[None, :]
141
+ ) * kv_cache_stride_d
142
+
143
+ if kv_cache_ptr.dtype.element_ty == gl.uint8:
144
+ nope_scale_offset: gl.constexpr = BLOCK_D_nope // NVFP4_QUANT_BLOCK_SIZE
145
+ else:
146
+ nope_scale_offset: gl.constexpr = 0
147
+ kv_cache_pe_offs = (
148
+ BLOCK_SIZE * (BLOCK_D_nope_STORE + nope_scale_offset)
149
+ + (pid_blk // 16) * BLOCK_D_pe_STORE * 16
150
+ + (pid_blk % 16) * K_WIDTH
151
+ + d_pe_offs_shfl[:, None] * K_WIDTH * 16
152
+ + k_width_shfl_pe[None, :]
153
+ ) * kv_cache_stride_d
154
+
155
+ gl.amd.cdna4.buffer_store(
156
+ k_nope.to(kv_cache_ptr.dtype.element_ty),
157
+ ptr=kv_cache_base,
158
+ offsets=kv_cache_nope_offs.to(gl.int32),
159
+ )
160
+ gl.amd.cdna4.buffer_store(
161
+ k_pe.to(kv_cache_ptr.dtype.element_ty),
162
+ ptr=kv_cache_base,
163
+ offsets=kv_cache_pe_offs.to(gl.int32),
164
+ )
165
+
166
+ if kv_cache_ptr.dtype.element_ty == gl.uint8:
167
+ BLOCK_D_nope_scales: gl.constexpr = BLOCK_D_nope // NVFP4_QUANT_BLOCK_SIZE
168
+ BLOCK_D_pe_scales: gl.constexpr = BLOCK_D_pe // NVFP4_QUANT_BLOCK_SIZE
169
+ R_ns: gl.constexpr = BLOCK_D_nope_scales // SCALE_K_WIDTH_NOPE
170
+ R_ps: gl.constexpr = BLOCK_D_pe_scales // SCALE_K_WIDTH_ROPE
171
+ PARENT_NS: gl.constexpr = _store_blocked_layout(R_ns, SCALE_K_WIDTH_NOPE)
172
+ PARENT_PS: gl.constexpr = _store_blocked_layout(R_ps, SCALE_K_WIDTH_ROPE)
173
+
174
+ d_nope_scales_shfl = gl.arange(0, R_ns, layout=gl.SliceLayout(1, PARENT_NS))
175
+ d_pe_scales_shfl = gl.arange(0, R_ps, layout=gl.SliceLayout(1, PARENT_PS))
176
+ k_nope_width_shfl = gl.arange(
177
+ 0, SCALE_K_WIDTH_NOPE, layout=gl.SliceLayout(0, PARENT_NS)
178
+ )
179
+ k_pe_width_shfl = gl.arange(
180
+ 0, SCALE_K_WIDTH_ROPE, layout=gl.SliceLayout(0, PARENT_PS)
181
+ )
182
+ k_nope_scales = gl.convert_layout(
183
+ gl.reshape(k_nope_scales, [R_ns, SCALE_K_WIDTH_NOPE]), PARENT_NS
184
+ )
185
+ k_pe_scales = gl.convert_layout(
186
+ gl.reshape(k_pe_scales, [R_ps, SCALE_K_WIDTH_ROPE]), PARENT_PS
187
+ )
188
+ pid_sub_blk = pid_blk % 128
189
+ kv_cache_nope_scales_offs = (
190
+ BLOCK_SIZE * BLOCK_D_nope_STORE
191
+ + (pid_blk // 128) * BLOCK_D_nope_scales * 128
192
+ + d_nope_scales_shfl[:, None] * SCALE_K_WIDTH_NOPE * 128
193
+ + (pid_sub_blk % 32) * 4 * SCALE_K_WIDTH_NOPE
194
+ + (pid_sub_blk // 32) * SCALE_K_WIDTH_NOPE
195
+ + k_nope_width_shfl[None, :]
196
+ ) * kv_cache_stride_d
197
+ kv_cache_pe_scales_offs = (
198
+ BLOCK_SIZE
199
+ * (BLOCK_D_nope_STORE + BLOCK_D_nope_scales + BLOCK_D_pe_STORE)
200
+ + (pid_blk // 128) * BLOCK_D_pe_scales * 128
201
+ + d_pe_scales_shfl[:, None] * SCALE_K_WIDTH_ROPE * 128
202
+ + (pid_sub_blk % 32) * 4 * SCALE_K_WIDTH_ROPE
203
+ + (pid_sub_blk // 32) * SCALE_K_WIDTH_ROPE
204
+ + k_pe_width_shfl[None, :]
205
+ ) * kv_cache_stride_d
206
+ e4m3_dtype: gl.constexpr = gl.float8e4nv
207
+ gl.amd.cdna4.buffer_store(
208
+ k_nope_scales.to(e4m3_dtype).to(
209
+ kv_cache_ptr.dtype.element_ty, bitcast=True
210
+ ),
211
+ ptr=kv_cache_base,
212
+ offsets=kv_cache_nope_scales_offs.to(gl.int32),
213
+ )
214
+ gl.amd.cdna4.buffer_store(
215
+ k_pe_scales.to(e4m3_dtype).to(
216
+ kv_cache_ptr.dtype.element_ty, bitcast=True
217
+ ),
218
+ ptr=kv_cache_base,
219
+ offsets=kv_cache_pe_scales_offs.to(gl.int32),
220
+ )
221
+ else:
222
+ # non-shuffled KV cache
223
+ kv_cache_base = (
224
+ kv_cache_ptr + pid_t_slot * kv_cache_stride_b + pid_hk * kv_cache_stride_h
225
+ )
226
+ kv_cache_nope_offs = d_nope_offs * kv_cache_stride_d
227
+ kv_cache_pe_offs = (d_pe_offs + BLOCK_D_nope) * kv_cache_stride_d
228
+ gl.amd.cdna4.buffer_store(
229
+ k_nope.to(kv_cache_ptr.dtype.element_ty),
230
+ ptr=kv_cache_base,
231
+ offsets=kv_cache_nope_offs.to(gl.int32),
232
+ )
233
+ gl.amd.cdna4.buffer_store(
234
+ k_pe.to(kv_cache_ptr.dtype.element_ty),
235
+ ptr=kv_cache_base,
236
+ offsets=kv_cache_pe_offs.to(gl.int32),
237
+ )
238
+
239
+ # Note: the async_store drain (tdm.async_wait) is done by the CALLER after
240
+ # any downstream ops, so the async_store latency can overlap with the
241
+ # post-helper work (decode_q_pe / zeros buffer_stores, etc.) instead of
242
+ # being exposed right at the helper return.
243
+
244
+
245
+ @gluon.jit
246
+ def _freq_from_shared(
247
+ smem,
248
+ REUSE_FREQS_FRONT_PART: gl.constexpr,
249
+ IS_NEOX: gl.constexpr,
250
+ BLOCK_D_pe: gl.constexpr,
251
+ L_PE: gl.constexpr,
252
+ L_FREQ: gl.constexpr,
253
+ ):
254
+ """Rebuild the BLOCK_D_pe cos/sin vector from a contiguous freq slice in LDS.
255
+
256
+ The cached freq buffer is gathered by ``d_cos_offs`` in the Triton kernel; here
257
+ we TDM-load the contiguous slice and rebuild the gather in registers:
258
+
259
+ * REUSE & NEOX -> concat(f, f) = reshape(trans(join(f, f)))
260
+ * REUSE & GPTJ -> interleave(f, f) = reshape(join(f, f))
261
+ * not REUSE -> already the full BLOCK_D_pe vector
262
+ """
263
+ if REUSE_FREQS_FRONT_PART:
264
+ f = smem.load(L_FREQ)
265
+ j = gl.join(f, f)
266
+ if IS_NEOX:
267
+ out = gl.reshape(gl.permute(j, [1, 0]), [BLOCK_D_pe])
268
+ else:
269
+ out = gl.reshape(j, [BLOCK_D_pe])
270
+ return gl.convert_layout(out, L_PE)
271
+ else:
272
+ return smem.load(L_PE)
273
+
274
+
275
+ @gluon.jit
276
+ def _rope_pe(
277
+ x_pe,
278
+ cos,
279
+ sin,
280
+ d_pe_offs,
281
+ IS_NEOX: gl.constexpr,
282
+ BLOCK_D_pe: gl.constexpr,
283
+ BLOCK_D_HALF_pe: gl.constexpr,
284
+ ):
285
+ """RoPE on an already-loaded 1D pe vector. Reuses the Triton rotation helper."""
286
+ if IS_NEOX:
287
+ x_rotated_mask = d_pe_offs < BLOCK_D_HALF_pe
288
+ x_pe_rotated = _get_neox_rotated_x_1D(
289
+ x_pe, x_rotated_mask, BLOCK_D_pe, BLOCK_D_HALF_pe
290
+ )
291
+ else:
292
+ x_rotated_mask = d_pe_offs % 2 == 0
293
+ x_pe_rotated = _get_gptj_rotated_x_1D(
294
+ x_pe, x_rotated_mask, BLOCK_D_pe, BLOCK_D_HALF_pe
295
+ )
296
+ return x_pe * cos + x_pe_rotated * sin
297
+
298
+
299
+ @gluon.jit
300
+ def _fused_qk_rope_cat_and_cache_mla_kernel(
301
+ q_nope_ptr,
302
+ q_pe_ptr,
303
+ k_nope_ptr,
304
+ k_pe_ptr,
305
+ pos_ptr,
306
+ cos_ptr,
307
+ sin_ptr,
308
+ q_out_ptr,
309
+ decode_q_pe_out_ptr,
310
+ k_pe_out_ptr,
311
+ q_nope_zeros_out_ptr,
312
+ kv_cache_ptr,
313
+ slot_mapping_ptr,
314
+ B,
315
+ B_slot,
316
+ num_decode_toks_for_zeros,
317
+ q_nope_stride_b,
318
+ q_nope_stride_h,
319
+ q_nope_stride_d,
320
+ q_pe_stride_b,
321
+ q_pe_stride_h,
322
+ q_pe_stride_d,
323
+ k_nope_stride_b,
324
+ k_nope_stride_h,
325
+ k_nope_stride_d,
326
+ k_pe_stride_b,
327
+ k_pe_stride_h,
328
+ k_pe_stride_d,
329
+ pos_stride_b,
330
+ cos_stride_b,
331
+ cos_stride_d,
332
+ q_out_stride_b,
333
+ q_out_stride_h,
334
+ q_out_stride_d,
335
+ decode_q_pe_out_stride_b,
336
+ decode_q_pe_out_stride_h,
337
+ decode_q_pe_out_stride_d,
338
+ k_pe_out_stride_b,
339
+ k_pe_out_stride_h,
340
+ k_pe_out_stride_d,
341
+ q_nope_zeros_out_stride_b,
342
+ q_nope_zeros_out_stride_h,
343
+ q_nope_zeros_out_stride_d,
344
+ kv_cache_stride_b,
345
+ kv_cache_stride_h,
346
+ kv_cache_stride_d,
347
+ k_scale_ptr,
348
+ QH_PER_KH: gl.constexpr,
349
+ QH: gl.constexpr,
350
+ KH: gl.constexpr,
351
+ REUSE_FREQS_FRONT_PART: gl.constexpr,
352
+ IS_NEOX: gl.constexpr,
353
+ BLOCK_D_nope: gl.constexpr,
354
+ BLOCK_D_pe: gl.constexpr,
355
+ BLOCK_D_HALF_pe: gl.constexpr,
356
+ BLOCK_SIZE: gl.constexpr = 1,
357
+ SHUFFLED_KV_CACHE: gl.constexpr = False,
358
+ SCALE_K_WIDTH_NOPE: gl.constexpr = 4,
359
+ SCALE_K_WIDTH_ROPE: gl.constexpr = 4,
360
+ OUTPUT_Q_NOPE_ZEROS_AND_Q_PE: gl.constexpr = False,
361
+ HAVE_K_SCALE: gl.constexpr = False,
362
+ UPCAST_OPERAND: gl.constexpr = False,
363
+ ):
364
+ # 1-warp (wave32) blocked layouts matching the Triton-generated ttgir.
365
+ L_NOPE: gl.constexpr = gl.BlockedLayout(
366
+ size_per_thread=[8], threads_per_warp=[32], warps_per_cta=[1], order=[0]
367
+ )
368
+ L_PE: gl.constexpr = gl.BlockedLayout(
369
+ size_per_thread=[2], threads_per_warp=[32], warps_per_cta=[1], order=[0]
370
+ )
371
+ # Identity (un-swizzled) shared layout for the 1D TDM staging buffers.
372
+ SH: gl.constexpr = gl.SwizzledSharedLayout(1, 1, 1, order=[0])
373
+
374
+ # cos/sin: TDM-load the contiguous freq slice, then rebuild in registers.
375
+ FREQ_W: gl.constexpr = BLOCK_D_HALF_pe if REUSE_FREQS_FRONT_PART else BLOCK_D_pe
376
+ FREQ_SPT: gl.constexpr = BLOCK_D_HALF_pe // 32 if BLOCK_D_HALF_pe >= 32 else 1
377
+ L_FREQ: gl.constexpr = gl.BlockedLayout(
378
+ size_per_thread=[FREQ_SPT], threads_per_warp=[32], warps_per_cta=[1], order=[0]
379
+ )
380
+
381
+ pid = gl.program_id(0)
382
+
383
+ d_nope_offs = gl.arange(0, BLOCK_D_nope, layout=L_NOPE).to(gl.int64)
384
+ d_pe_offs = gl.arange(0, BLOCK_D_pe, layout=L_PE).to(gl.int64)
385
+
386
+ # When q_out has the same dtype as q_nope/q_pe we can stage the passthrough
387
+ # q_nope straight from its load buffer (no cast). When it differs we need
388
+ # separate q_out-dtype staging buffers and an explicit cast on store.
389
+ Q_OUT_MATCHES: gl.constexpr = (
390
+ q_out_ptr.dtype.element_ty == q_nope_ptr.dtype.element_ty
391
+ )
392
+
393
+ # Shared staging buffers (static allocation; only a subset is used per pid).
394
+ qn_smem = gl.allocate_shared_memory(q_nope_ptr.dtype.element_ty, [BLOCK_D_nope], SH)
395
+ qpe_smem = gl.allocate_shared_memory(q_pe_ptr.dtype.element_ty, [BLOCK_D_pe], SH)
396
+ kn_smem = gl.allocate_shared_memory(k_nope_ptr.dtype.element_ty, [BLOCK_D_nope], SH)
397
+ kpe_smem = gl.allocate_shared_memory(k_pe_ptr.dtype.element_ty, [BLOCK_D_pe], SH)
398
+ cos_smem = gl.allocate_shared_memory(cos_ptr.dtype.element_ty, [FREQ_W], SH)
399
+ sin_smem = gl.allocate_shared_memory(sin_ptr.dtype.element_ty, [FREQ_W], SH)
400
+ if not Q_OUT_MATCHES:
401
+ # q_out-dtype staging buffers for the cast path.
402
+ qn_smem_out = gl.allocate_shared_memory(
403
+ q_out_ptr.dtype.element_ty, [BLOCK_D_nope], SH
404
+ )
405
+ qpe_smem_out = gl.allocate_shared_memory(
406
+ q_out_ptr.dtype.element_ty, [BLOCK_D_pe], SH
407
+ )
408
+
409
+ if pid < B * QH:
410
+ # pid_b = pid // QH
411
+ # pid_hq = pid % QH
412
+ # This is a new optimization that prioritized heavy workload WGs first
413
+ pid_hq = pid // B
414
+ pid_b = pid % B
415
+
416
+ # Issue ``pos`` first — it's used immediately by the cos/sin TDM
417
+ # descriptors. pid_slot / k_scale are only consumed later in the
418
+ # k-store path, so they sit behind pos in the issue stream.
419
+ pos = gl.load(pos_ptr + pid_b * pos_stride_b)
420
+ pid_slot = gl.load(slot_mapping_ptr + pid_b).to(gl.int64)
421
+
422
+ q_nope_desc = _make_tdm_desc_1d(
423
+ q_nope_ptr + pid_b * q_nope_stride_b + pid_hq * q_nope_stride_h,
424
+ q_nope_stride_d,
425
+ BLOCK_D_nope,
426
+ SH,
427
+ )
428
+ _issue_tdm_load_1d(q_nope_desc, 0, qn_smem)
429
+ if HAVE_K_SCALE:
430
+ k_scale = gl.load(k_scale_ptr)
431
+ else:
432
+ k_scale = 1.0
433
+
434
+ # cos/sin: TDM-load the contiguous freq slice (base depends on pos),
435
+ # rebuilt into the BLOCK_D_pe vector after the wait. The slice is
436
+ # contiguous (no d_cos_offs gather), so it streams through LDS like the
437
+ # other inputs. Empirically faster than the buffer_load gather despite
438
+ # adding 2 to the TDM-load FIFO depth (the [FIFO full] stall on the
439
+ # 6th issue is an overlap stall — kernel keeps doing useful work).
440
+ cos_desc = _make_tdm_desc_1d(
441
+ cos_ptr + pos * cos_stride_b, cos_stride_d, FREQ_W, SH
442
+ )
443
+ sin_desc = _make_tdm_desc_1d(
444
+ sin_ptr + pos * cos_stride_b, cos_stride_d, FREQ_W, SH
445
+ )
446
+ _issue_tdm_load_1d(cos_desc, 0, cos_smem)
447
+ _issue_tdm_load_1d(sin_desc, 0, sin_smem)
448
+
449
+ # --- Issue all TDM loads as early as possible ---
450
+ q_pe_desc = _make_tdm_desc_1d(
451
+ q_pe_ptr + pid_b * q_pe_stride_b + pid_hq * q_pe_stride_h,
452
+ q_pe_stride_d,
453
+ BLOCK_D_pe,
454
+ SH,
455
+ )
456
+ _issue_tdm_load_1d(q_pe_desc, 0, qpe_smem)
457
+
458
+ # pid_hk = pid_hq // QH_PER_KH
459
+ # is_kv = pid_hq % QH_PER_KH == 0
460
+ # This is a new optimization that prioritized heavy workload WGs first
461
+ pid_hk = pid_hq
462
+ is_kv = pid_hk < KH
463
+
464
+ q_out_base = pid_b * q_out_stride_b + pid_hq * q_out_stride_h
465
+
466
+ if is_kv:
467
+ k_nope_desc = _make_tdm_desc_1d(
468
+ k_nope_ptr + pid_b * k_nope_stride_b + pid_hk * k_nope_stride_h,
469
+ k_nope_stride_d,
470
+ BLOCK_D_nope,
471
+ SH,
472
+ )
473
+ _issue_tdm_load_1d(k_nope_desc, 0, kn_smem)
474
+ k_pe_desc = _make_tdm_desc_1d(
475
+ k_pe_ptr + pid_b * k_pe_stride_b + pid_hk * k_pe_stride_h,
476
+ k_pe_stride_d,
477
+ BLOCK_D_pe,
478
+ SH,
479
+ )
480
+ _issue_tdm_load_1d(k_pe_desc, 0, kpe_smem)
481
+
482
+ gl.amd.gfx1250.tdm.async_wait(0)
483
+ # Rebuild the BLOCK_D_pe cos/sin from the contiguous freq slice in LDS.
484
+ cos = _freq_from_shared(
485
+ cos_smem, REUSE_FREQS_FRONT_PART, IS_NEOX, BLOCK_D_pe, L_PE, L_FREQ
486
+ )
487
+ sin = _freq_from_shared(
488
+ sin_smem, REUSE_FREQS_FRONT_PART, IS_NEOX, BLOCK_D_pe, L_PE, L_FREQ
489
+ )
490
+ if UPCAST_OPERAND:
491
+ cos = cos.to(gl.float32)
492
+ sin = sin.to(gl.float32)
493
+
494
+ q_pe_in = qpe_smem.load(L_PE)
495
+ q_pe = _rope_pe(
496
+ q_pe_in, cos, sin, d_pe_offs, IS_NEOX, BLOCK_D_pe, BLOCK_D_HALF_pe
497
+ )
498
+
499
+ q_out_nope_desc = _make_tdm_desc_1d(
500
+ q_out_ptr + q_out_base,
501
+ q_out_stride_d,
502
+ BLOCK_D_nope,
503
+ SH,
504
+ )
505
+ q_out_pe_desc = _make_tdm_desc_1d(
506
+ q_out_ptr + q_out_base + BLOCK_D_nope * q_out_stride_d,
507
+ q_out_stride_d,
508
+ BLOCK_D_pe,
509
+ SH,
510
+ )
511
+ if Q_OUT_MATCHES:
512
+ # Same dtype: qn_smem already holds the bit-identical q_nope from the
513
+ # async_load, so TDM-store directly (skip the LDS round-trip).
514
+ qpe_smem.store(q_pe.to(q_out_ptr.dtype.element_ty))
515
+ gl.amd.gfx1250.tdm.async_store(q_out_nope_desc, [0], qn_smem)
516
+ gl.amd.gfx1250.tdm.async_store(q_out_pe_desc, [0], qpe_smem)
517
+ else:
518
+ # Differing dtype: load q_nope to registers, cast to the q_out dtype
519
+ # and stage into the q_out-dtype buffers before the TDM-store.
520
+ q_nope = qn_smem.load(L_NOPE)
521
+ qn_smem_out.store(q_nope.to(q_out_ptr.dtype.element_ty))
522
+ qpe_smem_out.store(q_pe.to(q_out_ptr.dtype.element_ty))
523
+ gl.amd.gfx1250.tdm.async_store(q_out_nope_desc, [0], qn_smem_out)
524
+ gl.amd.gfx1250.tdm.async_store(q_out_pe_desc, [0], qpe_smem_out)
525
+
526
+ if is_kv:
527
+ if pid_slot >= 0:
528
+ if BLOCK_SIZE > 1:
529
+ pid_t_slot = pid_slot // BLOCK_SIZE
530
+ pid_blk = pid_slot % BLOCK_SIZE
531
+ else:
532
+ pid_t_slot = pid_slot
533
+ pid_blk = 0
534
+
535
+ k_nope = kn_smem.load(L_NOPE)
536
+ k_pe_in = kpe_smem.load(L_PE)
537
+ k_pe = _rope_pe(
538
+ k_pe_in, cos, sin, d_pe_offs, IS_NEOX, BLOCK_D_pe, BLOCK_D_HALF_pe
539
+ )
540
+ k_pe_out_base = pid_b * k_pe_out_stride_b + pid_hk * k_pe_out_stride_h
541
+ gl.amd.cdna4.buffer_store(
542
+ k_pe.to(k_pe_out_ptr.dtype.element_ty),
543
+ ptr=k_pe_out_ptr,
544
+ offsets=(k_pe_out_base + d_pe_offs * k_pe_out_stride_d).to(
545
+ gl.int32
546
+ ),
547
+ )
548
+ k_scale_rcprl = (1 / k_scale).to(gl.float32)
549
+ k_nope = k_nope.to(gl.float32) * k_scale_rcprl
550
+ k_pe = k_pe.to(gl.float32) * k_scale_rcprl
551
+
552
+ _store_mla_kv_cache(
553
+ kv_cache_ptr,
554
+ pid_t_slot,
555
+ pid_hk,
556
+ pid_blk,
557
+ d_nope_offs,
558
+ d_pe_offs,
559
+ kv_cache_stride_b,
560
+ kv_cache_stride_h,
561
+ kv_cache_stride_d,
562
+ k_nope,
563
+ k_pe,
564
+ BLOCK_D_nope,
565
+ BLOCK_D_pe,
566
+ BLOCK_SIZE,
567
+ SHUFFLED_KV_CACHE,
568
+ SCALE_K_WIDTH_NOPE,
569
+ SCALE_K_WIDTH_ROPE,
570
+ L_NOPE,
571
+ L_PE,
572
+ )
573
+
574
+ # OUTPUT block at tail (after the kv-store path): both stores via
575
+ # buffer_store. Empirically beats moving the block earlier or putting
576
+ # decode_q_pe on TDM async_store — those alternatives lower per-WGP
577
+ # SIMD-instruction count but degrade IPC enough that wall-clock
578
+ # dispatch time grows.
579
+ if OUTPUT_Q_NOPE_ZEROS_AND_Q_PE:
580
+ if pid < num_decode_toks_for_zeros * QH:
581
+ decode_q_pe_base = (
582
+ pid_b * decode_q_pe_out_stride_b + pid_hq * decode_q_pe_out_stride_h
583
+ )
584
+ gl.amd.cdna4.buffer_store(
585
+ q_pe.to(decode_q_pe_out_ptr.dtype.element_ty),
586
+ ptr=decode_q_pe_out_ptr,
587
+ offsets=(
588
+ decode_q_pe_base + d_pe_offs * decode_q_pe_out_stride_d
589
+ ).to(gl.int32),
590
+ )
591
+ z = gl.zeros(
592
+ [BLOCK_D_nope],
593
+ dtype=q_nope_zeros_out_ptr.dtype.element_ty,
594
+ layout=L_NOPE,
595
+ )
596
+ zeros_base = (
597
+ pid_b * q_nope_zeros_out_stride_b
598
+ + pid_hq * q_nope_zeros_out_stride_h
599
+ )
600
+ gl.amd.cdna4.buffer_store(
601
+ z,
602
+ ptr=q_nope_zeros_out_ptr,
603
+ offsets=(zeros_base + d_nope_offs * q_nope_zeros_out_stride_d).to(
604
+ gl.int32
605
+ ),
606
+ )
607
+
608
+ # Drain the in-flight q_out async_stores.
609
+ gl.amd.gfx1250.tdm.async_wait(0)
610
+ else:
611
+ pid = pid - B * QH + B * KH
612
+ if pid < B_slot * KH:
613
+ pid_b = pid // KH
614
+ pid_hk = pid % KH
615
+
616
+ k_nope_desc = _make_tdm_desc_1d(
617
+ k_nope_ptr + pid_b * k_nope_stride_b + pid_hk * k_nope_stride_h,
618
+ k_nope_stride_d,
619
+ BLOCK_D_nope,
620
+ SH,
621
+ )
622
+ _issue_tdm_load_1d(k_nope_desc, 0, kn_smem)
623
+ k_pe_desc = _make_tdm_desc_1d(
624
+ k_pe_ptr + pid_b * k_pe_stride_b + pid_hk * k_pe_stride_h,
625
+ k_pe_stride_d,
626
+ BLOCK_D_pe,
627
+ SH,
628
+ )
629
+ _issue_tdm_load_1d(k_pe_desc, 0, kpe_smem)
630
+
631
+ pid_slot = gl.load(slot_mapping_ptr + pid_b).to(gl.int64)
632
+ if pid_slot >= 0:
633
+ if BLOCK_SIZE > 1:
634
+ pid_t_slot = pid_slot // BLOCK_SIZE
635
+ pid_blk = pid_slot % BLOCK_SIZE
636
+ else:
637
+ pid_t_slot = pid_slot
638
+ pid_blk = 0
639
+ if HAVE_K_SCALE:
640
+ k_scale = gl.load(k_scale_ptr)
641
+ else:
642
+ k_scale = 1.0
643
+
644
+ k_pe_out_base = pid_b * k_pe_out_stride_b + pid_hk * k_pe_out_stride_h
645
+
646
+ gl.amd.gfx1250.tdm.async_wait(0)
647
+ k_nope = kn_smem.load(L_NOPE)
648
+ k_pe = kpe_smem.load(L_PE)
649
+ gl.amd.cdna4.buffer_store(
650
+ k_pe.to(k_pe_out_ptr.dtype.element_ty),
651
+ ptr=k_pe_out_ptr,
652
+ offsets=(k_pe_out_base + d_pe_offs * k_pe_out_stride_d).to(
653
+ gl.int32
654
+ ),
655
+ )
656
+ k_scale_rcprl = (1 / k_scale).to(gl.float32)
657
+ k_nope = k_nope.to(gl.float32) * k_scale_rcprl
658
+ k_pe = k_pe.to(gl.float32) * k_scale_rcprl
659
+
660
+ _store_mla_kv_cache(
661
+ kv_cache_ptr,
662
+ pid_t_slot,
663
+ pid_hk,
664
+ pid_blk,
665
+ d_nope_offs,
666
+ d_pe_offs,
667
+ kv_cache_stride_b,
668
+ kv_cache_stride_h,
669
+ kv_cache_stride_d,
670
+ k_nope,
671
+ k_pe,
672
+ BLOCK_D_nope,
673
+ BLOCK_D_pe,
674
+ BLOCK_SIZE,
675
+ SHUFFLED_KV_CACHE,
676
+ SCALE_K_WIDTH_NOPE,
677
+ SCALE_K_WIDTH_ROPE,
678
+ L_NOPE,
679
+ L_PE,
680
+ )
build/torch-rocm/_gluon_kernels/gfx1250/gemm/basic/__init__.py ADDED
File without changes
build/torch-rocm/_gluon_kernels/gfx1250/gemm/basic/gemm_a16w16.py ADDED
@@ -0,0 +1,703 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (C) 2026, Advanced Micro Devices, Inc. All rights reserved.
3
+
4
+ import math
5
+ from triton.experimental import gluon
6
+ import triton.experimental.gluon.language as gl
7
+ from .....utils._triton.kernel_repr import make_kernel_repr
8
+
9
+ _GLUON_REPR_KEYS = [
10
+ "BLOCK_M",
11
+ "BLOCK_N",
12
+ "BLOCK_K",
13
+ "NUM_BUFFERS",
14
+ "LAYOUT",
15
+ "USE_ACTIVATION",
16
+ "ADD_BIAS",
17
+ ]
18
+
19
+ _gemm_a16w16_bandwidth_bound_repr = make_kernel_repr(
20
+ "_gemm_a16w16_gfx1250_bandwidth_bound_kernel", _GLUON_REPR_KEYS
21
+ )
22
+
23
+ _gemm_a16w16_compute_bound_repr = make_kernel_repr(
24
+ "_gemm_a16w16_gfx1250_compute_bound_kernel", _GLUON_REPR_KEYS
25
+ )
26
+
27
+
28
+ def create_shared_layouts(
29
+ BLOCK_M: gl.constexpr,
30
+ BLOCK_N: gl.constexpr,
31
+ BLOCK_K: gl.constexpr,
32
+ LAYOUT: gl.constexpr,
33
+ ):
34
+ if LAYOUT[0] == "T":
35
+ SHARED_LAYOUT_A: gl.constexpr = gl.PaddedSharedLayout.with_identity_for(
36
+ [[BLOCK_K, 8]], [BLOCK_M, BLOCK_K], [1, 0]
37
+ )
38
+ else:
39
+ SHARED_LAYOUT_A: gl.constexpr = gl.PaddedSharedLayout.with_identity_for(
40
+ [[BLOCK_M, 8]], [BLOCK_K, BLOCK_M], [1, 0]
41
+ )
42
+
43
+ if LAYOUT[1] == "T":
44
+ SHARED_LAYOUT_B: gl.constexpr = gl.PaddedSharedLayout.with_identity_for(
45
+ [[BLOCK_N, 16]], [BLOCK_K, BLOCK_N], [1, 0]
46
+ )
47
+ else:
48
+ SHARED_LAYOUT_B: gl.constexpr = gl.PaddedSharedLayout.with_identity_for(
49
+ [[BLOCK_K, 8]], [BLOCK_N, BLOCK_K], [1, 0]
50
+ )
51
+
52
+ return (SHARED_LAYOUT_A, SHARED_LAYOUT_B)
53
+
54
+
55
+ def create_wmma_layouts(num_warps):
56
+ warp_bases = [(0, 1)]
57
+ for i in range(int(math.log2(num_warps // 2))):
58
+ warp_bases.append((1 << i, 0))
59
+ warp_bases = tuple(warp_bases)
60
+
61
+ wmma_layout = gl.amd.AMDWMMALayout(
62
+ version=3, transposed=True, warp_bases=warp_bases, instr_shape=[16, 16, 32]
63
+ )
64
+ operand_a = gl.DotOperandLayout(operand_index=0, parent=wmma_layout, k_width=8)
65
+ operand_b = gl.DotOperandLayout(operand_index=1, parent=wmma_layout, k_width=8)
66
+ return (wmma_layout, operand_a, operand_b)
67
+
68
+
69
+ @gluon.jit(repr=_gemm_a16w16_bandwidth_bound_repr)
70
+ def _gemm_a16w16_bandwidth_bound_kernel(
71
+ a_ptr,
72
+ b_ptr,
73
+ c_ptr,
74
+ bias_ptr,
75
+ M,
76
+ N,
77
+ K,
78
+ stride_am,
79
+ stride_ak,
80
+ stride_bk,
81
+ stride_bn,
82
+ stride_cm,
83
+ stride_cn,
84
+ BLOCK_M: gl.constexpr,
85
+ BLOCK_N: gl.constexpr,
86
+ BLOCK_K: gl.constexpr,
87
+ NUM_BUFFERS: gl.constexpr,
88
+ LAYOUT: gl.constexpr,
89
+ SHARED_LAYOUT_A: gl.constexpr,
90
+ SHARED_LAYOUT_B: gl.constexpr,
91
+ WMMA_LAYOUT: gl.constexpr,
92
+ OPERAND_LAYOUT_A: gl.constexpr,
93
+ OPERAND_LAYOUT_B: gl.constexpr,
94
+ activation: gl.constexpr,
95
+ USE_ACTIVATION: gl.constexpr,
96
+ ADD_BIAS: gl.constexpr,
97
+ ):
98
+ pid = gl.program_id(axis=0)
99
+ num_pid_m = gl.cdiv(M, BLOCK_M)
100
+ pid_m = pid % num_pid_m
101
+ pid_n = pid // num_pid_m
102
+
103
+ # Descriptors start at this block's (M, N) offset by biasing the base
104
+ # pointer — subsequent async_loads use [0, 0] and step only along K.
105
+ a_base = a_ptr + pid_m * BLOCK_M * stride_am
106
+ b_base = b_ptr + pid_n * BLOCK_N * stride_bn
107
+
108
+ if LAYOUT[0] == "T":
109
+ a_desc = gl.amd.gfx1250.tdm.make_tensor_descriptor(
110
+ base=a_base,
111
+ shape=(M - pid_m * BLOCK_M, K),
112
+ strides=(stride_am, stride_ak),
113
+ block_shape=(BLOCK_M, BLOCK_K),
114
+ layout=SHARED_LAYOUT_A,
115
+ )
116
+ else:
117
+ a_desc = gl.amd.gfx1250.tdm.make_tensor_descriptor(
118
+ base=a_base,
119
+ shape=(K, M - pid_m * BLOCK_M),
120
+ strides=(stride_ak, stride_am),
121
+ block_shape=(BLOCK_K, BLOCK_M),
122
+ layout=SHARED_LAYOUT_A,
123
+ )
124
+
125
+ if LAYOUT[1] == "T":
126
+ b_desc = gl.amd.gfx1250.tdm.make_tensor_descriptor(
127
+ base=b_base,
128
+ shape=(K, N - pid_n * BLOCK_N),
129
+ strides=(stride_bk, stride_bn),
130
+ block_shape=(BLOCK_K, BLOCK_N),
131
+ layout=SHARED_LAYOUT_B,
132
+ )
133
+ else:
134
+ b_desc = gl.amd.gfx1250.tdm.make_tensor_descriptor(
135
+ base=b_base,
136
+ shape=(N - pid_n * BLOCK_N, K),
137
+ strides=(stride_bn, stride_bk),
138
+ block_shape=(BLOCK_N, BLOCK_K),
139
+ layout=SHARED_LAYOUT_B,
140
+ )
141
+
142
+ if LAYOUT[0] == "T":
143
+ a_buffer = gl.allocate_shared_memory(
144
+ a_ptr.type.element_ty,
145
+ shape=[NUM_BUFFERS, BLOCK_M, BLOCK_K],
146
+ layout=SHARED_LAYOUT_A,
147
+ )
148
+ else:
149
+ a_buffer = gl.allocate_shared_memory(
150
+ a_ptr.type.element_ty,
151
+ shape=[NUM_BUFFERS, BLOCK_K, BLOCK_M],
152
+ layout=SHARED_LAYOUT_A,
153
+ )
154
+
155
+ if LAYOUT[1] == "T":
156
+ b_buffer = gl.allocate_shared_memory(
157
+ b_ptr.type.element_ty,
158
+ shape=[NUM_BUFFERS, BLOCK_K, BLOCK_N],
159
+ layout=SHARED_LAYOUT_B,
160
+ )
161
+ else:
162
+ b_buffer = gl.allocate_shared_memory(
163
+ b_ptr.type.element_ty,
164
+ shape=[NUM_BUFFERS, BLOCK_N, BLOCK_K],
165
+ layout=SHARED_LAYOUT_B,
166
+ )
167
+
168
+ load_idx = 0
169
+ compute_idx = 0
170
+
171
+ accumulator = gl.zeros((BLOCK_M, BLOCK_N), dtype=gl.float32, layout=WMMA_LAYOUT)
172
+
173
+ # Fill the pipeline
174
+ for _ in gl.static_range(NUM_BUFFERS - 1):
175
+ gl.amd.gfx1250.tdm.async_load(
176
+ a_desc, [0, 0], a_buffer.index(load_idx % NUM_BUFFERS)
177
+ )
178
+ gl.amd.gfx1250.tdm.async_load(
179
+ b_desc, [0, 0], b_buffer.index(load_idx % NUM_BUFFERS)
180
+ )
181
+
182
+ # Walk the descriptors forward one K tile.
183
+ if LAYOUT[0] == "T":
184
+ a_desc = gl.amd.gfx1250.tdm.update_tensor_descriptor(
185
+ a_desc, add_offsets=[0, BLOCK_K]
186
+ )
187
+ else:
188
+ a_desc = gl.amd.gfx1250.tdm.update_tensor_descriptor(
189
+ a_desc, add_offsets=[BLOCK_K, 0]
190
+ )
191
+
192
+ if LAYOUT[1] == "T":
193
+ b_desc = gl.amd.gfx1250.tdm.update_tensor_descriptor(
194
+ b_desc, add_offsets=[BLOCK_K, 0]
195
+ )
196
+ else:
197
+ b_desc = gl.amd.gfx1250.tdm.update_tensor_descriptor(
198
+ b_desc, add_offsets=[0, BLOCK_K]
199
+ )
200
+
201
+ load_idx += 1
202
+
203
+ # Main pipeline loop
204
+ num_k_tiles = gl.cdiv(K, BLOCK_K)
205
+
206
+ for _ in range(num_k_tiles - (NUM_BUFFERS - 1)):
207
+ gl.amd.gfx1250.tdm.async_load(
208
+ a_desc, [0, 0], a_buffer.index(load_idx % NUM_BUFFERS)
209
+ )
210
+ gl.amd.gfx1250.tdm.async_load(
211
+ b_desc, [0, 0], b_buffer.index(load_idx % NUM_BUFFERS)
212
+ )
213
+
214
+ gl.amd.gfx1250.tdm.async_wait((NUM_BUFFERS - 1) * 2)
215
+
216
+ # Walk the descriptors forward one K tile.
217
+ if LAYOUT[0] == "T":
218
+ a_desc = gl.amd.gfx1250.tdm.update_tensor_descriptor(
219
+ a_desc, add_offsets=[0, BLOCK_K]
220
+ )
221
+ else:
222
+ a_desc = gl.amd.gfx1250.tdm.update_tensor_descriptor(
223
+ a_desc, add_offsets=[BLOCK_K, 0]
224
+ )
225
+
226
+ if LAYOUT[1] == "T":
227
+ b_desc = gl.amd.gfx1250.tdm.update_tensor_descriptor(
228
+ b_desc, add_offsets=[BLOCK_K, 0]
229
+ )
230
+ else:
231
+ b_desc = gl.amd.gfx1250.tdm.update_tensor_descriptor(
232
+ b_desc, add_offsets=[0, BLOCK_K]
233
+ )
234
+
235
+ load_idx += 1
236
+
237
+ if LAYOUT[0] == "T":
238
+ cur_a = gl.amd.cdna4.async_copy.load_shared_relaxed(
239
+ a_buffer.index(compute_idx % NUM_BUFFERS), OPERAND_LAYOUT_A
240
+ )
241
+ else:
242
+ cur_a = gl.amd.cdna4.async_copy.load_shared_relaxed(
243
+ a_buffer.index(compute_idx % NUM_BUFFERS).permute([1, 0]),
244
+ OPERAND_LAYOUT_A,
245
+ )
246
+
247
+ if LAYOUT[1] == "T":
248
+ cur_b = gl.amd.cdna4.async_copy.load_shared_relaxed(
249
+ b_buffer.index(compute_idx % NUM_BUFFERS), OPERAND_LAYOUT_B
250
+ )
251
+ else:
252
+ cur_b = gl.amd.cdna4.async_copy.load_shared_relaxed(
253
+ b_buffer.index(compute_idx % NUM_BUFFERS).permute([1, 0]),
254
+ OPERAND_LAYOUT_B,
255
+ )
256
+
257
+ accumulator = gl.amd.gfx1250.wmma(cur_a, cur_b, accumulator)
258
+
259
+ compute_idx += 1
260
+
261
+ # Epilogue: no more loads
262
+ for i in gl.static_range(NUM_BUFFERS - 1):
263
+ gl.amd.gfx1250.tdm.async_wait((NUM_BUFFERS - 2 - i) * 2)
264
+
265
+ if LAYOUT[0] == "T":
266
+ cur_a = gl.amd.cdna4.async_copy.load_shared_relaxed(
267
+ a_buffer.index(compute_idx % NUM_BUFFERS), OPERAND_LAYOUT_A
268
+ )
269
+ else:
270
+ cur_a = gl.amd.cdna4.async_copy.load_shared_relaxed(
271
+ a_buffer.index(compute_idx % NUM_BUFFERS).permute([1, 0]),
272
+ OPERAND_LAYOUT_A,
273
+ )
274
+
275
+ if LAYOUT[1] == "T":
276
+ cur_b = gl.amd.cdna4.async_copy.load_shared_relaxed(
277
+ b_buffer.index(compute_idx % NUM_BUFFERS), OPERAND_LAYOUT_B
278
+ )
279
+ else:
280
+ cur_b = gl.amd.cdna4.async_copy.load_shared_relaxed(
281
+ b_buffer.index(compute_idx % NUM_BUFFERS).permute([1, 0]),
282
+ OPERAND_LAYOUT_B,
283
+ )
284
+
285
+ accumulator = gl.amd.gfx1250.wmma(cur_a, cur_b, accumulator)
286
+ compute_idx += 1
287
+
288
+ # Bias
289
+ if ADD_BIAS:
290
+ offs_bias = pid_n * BLOCK_N + gl.arange(
291
+ 0, BLOCK_N, layout=gl.SliceLayout(0, WMMA_LAYOUT)
292
+ )
293
+ bias_vals = gl.load(bias_ptr + offs_bias, mask=offs_bias < N, other=0.0)
294
+ accumulator = accumulator + bias_vals[None, :]
295
+
296
+ # Activation
297
+ if USE_ACTIVATION:
298
+ accumulator = activation(accumulator)
299
+
300
+ offs_cm = pid_m * BLOCK_M + gl.arange(
301
+ 0, BLOCK_M, layout=gl.SliceLayout(1, WMMA_LAYOUT)
302
+ )
303
+ offs_cn = pid_n * BLOCK_N + gl.arange(
304
+ 0, BLOCK_N, layout=gl.SliceLayout(0, WMMA_LAYOUT)
305
+ )
306
+
307
+ offs_c = stride_cm * offs_cm[:, None] + stride_cn * offs_cn[None, :]
308
+
309
+ mask_c = (offs_cm[:, None] < M) & (offs_cn[None, :] < N)
310
+
311
+ # Store
312
+ gl.amd.gfx1250.buffer_store(
313
+ accumulator.to(c_ptr.type.element_ty), c_ptr, offs_c, mask=mask_c
314
+ )
315
+
316
+
317
+ @gluon.jit(repr=_gemm_a16w16_compute_bound_repr)
318
+ def _gemm_a16w16_compute_bound_kernel(
319
+ a_ptr,
320
+ b_ptr,
321
+ c_ptr,
322
+ bias_ptr,
323
+ M,
324
+ N,
325
+ K,
326
+ stride_am,
327
+ stride_ak,
328
+ stride_bk,
329
+ stride_bn,
330
+ stride_cm,
331
+ stride_cn,
332
+ BLOCK_M: gl.constexpr,
333
+ BLOCK_N: gl.constexpr,
334
+ BLOCK_K: gl.constexpr,
335
+ NUM_BUFFERS: gl.constexpr,
336
+ LAYOUT: gl.constexpr,
337
+ SHARED_LAYOUT_A: gl.constexpr,
338
+ SHARED_LAYOUT_B: gl.constexpr,
339
+ WMMA_LAYOUT: gl.constexpr,
340
+ OPERAND_LAYOUT_A: gl.constexpr,
341
+ OPERAND_LAYOUT_B: gl.constexpr,
342
+ activation: gl.constexpr,
343
+ USE_ACTIVATION: gl.constexpr,
344
+ ADD_BIAS: gl.constexpr,
345
+ ):
346
+ """Local-load pipelining across K-tiles.
347
+
348
+ Manually places load_shared_relaxed for tile i+1 *before* the wmma for
349
+ tile i so the hardware LDS unit and matrix unit can run in parallel.
350
+ LLVM fails to schedule this reordering on its own in the bandwidth_bound kernel.
351
+
352
+ Requires NUM_BUFFERS >= 2. With NUM_BUFFERS == 2 the TDM must complete
353
+ fully before each ds_read batch (async_wait(0)), but the ds_read/wmma
354
+ overlap is still preserved. NUM_BUFFERS >= 3 is recommended.
355
+ """
356
+ gl.static_assert(NUM_BUFFERS >= 2, "compute_bound kernel requires NUM_BUFFERS >= 2")
357
+
358
+ pid = gl.program_id(axis=0)
359
+ num_pid_m = gl.cdiv(M, BLOCK_M)
360
+ pid_m = pid % num_pid_m
361
+ pid_n = pid // num_pid_m
362
+
363
+ # Descriptors start at this block's (M, N) offset by biasing the base
364
+ # pointer — subsequent async_loads use [0, 0] and step only along K.
365
+ a_base = a_ptr + pid_m * BLOCK_M * stride_am
366
+ b_base = b_ptr + pid_n * BLOCK_N * stride_bn
367
+
368
+ if LAYOUT[0] == "T":
369
+ a_desc = gl.amd.gfx1250.tdm.make_tensor_descriptor(
370
+ base=a_base,
371
+ shape=(M - pid_m * BLOCK_M, K),
372
+ strides=(stride_am, stride_ak),
373
+ block_shape=(BLOCK_M, BLOCK_K),
374
+ layout=SHARED_LAYOUT_A,
375
+ )
376
+ else:
377
+ a_desc = gl.amd.gfx1250.tdm.make_tensor_descriptor(
378
+ base=a_base,
379
+ shape=(K, M - pid_m * BLOCK_M),
380
+ strides=(stride_ak, stride_am),
381
+ block_shape=(BLOCK_K, BLOCK_M),
382
+ layout=SHARED_LAYOUT_A,
383
+ )
384
+
385
+ if LAYOUT[1] == "T":
386
+ b_desc = gl.amd.gfx1250.tdm.make_tensor_descriptor(
387
+ base=b_base,
388
+ shape=(K, N - pid_n * BLOCK_N),
389
+ strides=(stride_bk, stride_bn),
390
+ block_shape=(BLOCK_K, BLOCK_N),
391
+ layout=SHARED_LAYOUT_B,
392
+ )
393
+ else:
394
+ b_desc = gl.amd.gfx1250.tdm.make_tensor_descriptor(
395
+ base=b_base,
396
+ shape=(N - pid_n * BLOCK_N, K),
397
+ strides=(stride_bn, stride_bk),
398
+ block_shape=(BLOCK_N, BLOCK_K),
399
+ layout=SHARED_LAYOUT_B,
400
+ )
401
+
402
+ if LAYOUT[0] == "T":
403
+ a_buffer = gl.allocate_shared_memory(
404
+ a_ptr.type.element_ty,
405
+ shape=[NUM_BUFFERS, BLOCK_M, BLOCK_K],
406
+ layout=SHARED_LAYOUT_A,
407
+ )
408
+ else:
409
+ a_buffer = gl.allocate_shared_memory(
410
+ a_ptr.type.element_ty,
411
+ shape=[NUM_BUFFERS, BLOCK_K, BLOCK_M],
412
+ layout=SHARED_LAYOUT_A,
413
+ )
414
+
415
+ if LAYOUT[1] == "T":
416
+ b_buffer = gl.allocate_shared_memory(
417
+ b_ptr.type.element_ty,
418
+ shape=[NUM_BUFFERS, BLOCK_K, BLOCK_N],
419
+ layout=SHARED_LAYOUT_B,
420
+ )
421
+ else:
422
+ b_buffer = gl.allocate_shared_memory(
423
+ b_ptr.type.element_ty,
424
+ shape=[NUM_BUFFERS, BLOCK_N, BLOCK_K],
425
+ layout=SHARED_LAYOUT_B,
426
+ )
427
+
428
+ load_idx = 0
429
+ compute_idx = 0
430
+
431
+ accumulator = gl.zeros((BLOCK_M, BLOCK_N), dtype=gl.float32, layout=WMMA_LAYOUT)
432
+
433
+ # TDM prologue: fill the pipeline with NUM_BUFFERS-1 tiles
434
+ for _ in gl.static_range(NUM_BUFFERS):
435
+ gl.amd.gfx1250.tdm.async_load(
436
+ a_desc, [0, 0], a_buffer.index(load_idx % NUM_BUFFERS)
437
+ )
438
+ gl.amd.gfx1250.tdm.async_load(
439
+ b_desc, [0, 0], b_buffer.index(load_idx % NUM_BUFFERS)
440
+ )
441
+
442
+ # Walk the descriptors forward one K tile.
443
+ if LAYOUT[0] == "T":
444
+ a_desc = gl.amd.gfx1250.tdm.update_tensor_descriptor(
445
+ a_desc, add_offsets=[0, BLOCK_K]
446
+ )
447
+ else:
448
+ a_desc = gl.amd.gfx1250.tdm.update_tensor_descriptor(
449
+ a_desc, add_offsets=[BLOCK_K, 0]
450
+ )
451
+
452
+ if LAYOUT[1] == "T":
453
+ b_desc = gl.amd.gfx1250.tdm.update_tensor_descriptor(
454
+ b_desc, add_offsets=[BLOCK_K, 0]
455
+ )
456
+ else:
457
+ b_desc = gl.amd.gfx1250.tdm.update_tensor_descriptor(
458
+ b_desc, add_offsets=[0, BLOCK_K]
459
+ )
460
+
461
+ load_idx += 1
462
+
463
+ num_k_tiles = gl.cdiv(K, BLOCK_K)
464
+
465
+ # Register pre-load prologue: wait for tile 0 then read it into cur_a/cur_b.
466
+ # After TDM prologue there are (NUM_BUFFERS-1)*2 ops in-flight; waiting for
467
+ # (NUM_BUFFERS-2)*2 lets exactly one tile (tile 0) complete.
468
+ gl.amd.gfx1250.tdm.async_wait((NUM_BUFFERS - 1) * 2)
469
+
470
+ if LAYOUT[0] == "T":
471
+ cur_a = gl.amd.cdna4.async_copy.load_shared_relaxed(
472
+ a_buffer.index(compute_idx % NUM_BUFFERS), OPERAND_LAYOUT_A
473
+ )
474
+ else:
475
+ cur_a = gl.amd.cdna4.async_copy.load_shared_relaxed(
476
+ a_buffer.index(compute_idx % NUM_BUFFERS).permute([1, 0]),
477
+ OPERAND_LAYOUT_A,
478
+ )
479
+
480
+ if LAYOUT[1] == "T":
481
+ cur_b = gl.amd.cdna4.async_copy.load_shared_relaxed(
482
+ b_buffer.index(compute_idx % NUM_BUFFERS), OPERAND_LAYOUT_B
483
+ )
484
+ else:
485
+ cur_b = gl.amd.cdna4.async_copy.load_shared_relaxed(
486
+ b_buffer.index(compute_idx % NUM_BUFFERS).permute([1, 0]),
487
+ OPERAND_LAYOUT_B,
488
+ )
489
+
490
+ # Main pipeline loop — first iteration peeled out below, then loop runs
491
+ # for (num_k_tiles - (NUM_BUFFERS - 1) - 1) remaining iterations.
492
+
493
+ # ---- Peeled first iteration ----
494
+ # WMMA for the current tile — uses operands pre-loaded in the
495
+ # *previous* iteration so no ds_read stall before the matrix op.
496
+ accumulator = gl.amd.gfx1250.wmma(cur_a, cur_b, accumulator)
497
+
498
+ # Issue TDM for the tile that is (NUM_BUFFERS-1) steps ahead
499
+ gl.amd.gfx1250.tdm.async_load(
500
+ a_desc, [0, 0], a_buffer.index(load_idx % NUM_BUFFERS)
501
+ )
502
+ gl.amd.gfx1250.tdm.async_load(
503
+ b_desc, [0, 0], b_buffer.index(load_idx % NUM_BUFFERS)
504
+ )
505
+
506
+ # Walk the descriptors forward one K tile.
507
+ if LAYOUT[0] == "T":
508
+ a_desc = gl.amd.gfx1250.tdm.update_tensor_descriptor(
509
+ a_desc, add_offsets=[0, BLOCK_K]
510
+ )
511
+ else:
512
+ a_desc = gl.amd.gfx1250.tdm.update_tensor_descriptor(
513
+ a_desc, add_offsets=[BLOCK_K, 0]
514
+ )
515
+
516
+ if LAYOUT[1] == "T":
517
+ b_desc = gl.amd.gfx1250.tdm.update_tensor_descriptor(
518
+ b_desc, add_offsets=[BLOCK_K, 0]
519
+ )
520
+ else:
521
+ b_desc = gl.amd.gfx1250.tdm.update_tensor_descriptor(
522
+ b_desc, add_offsets=[0, BLOCK_K]
523
+ )
524
+
525
+ # Tighter wait: after issuing the new TDM there are (NUM_BUFFERS-1)*2
526
+ # ops in-flight. Waiting for (NUM_BUFFERS-2)*2 guarantees that tile
527
+ # compute_idx+1 has landed in LDS.
528
+ gl.amd.gfx1250.tdm.async_wait((NUM_BUFFERS - 1) * 2)
529
+
530
+ load_idx += 1
531
+
532
+ # Pre-load the NEXT tile's operands into registers *before* the WMMA
533
+ # below. The hardware can run LDS reads and the matrix unit in
534
+ # parallel, hiding the ds_read latency inside the WMMA execution.
535
+ if LAYOUT[0] == "T":
536
+ next_a = gl.amd.cdna4.async_copy.load_shared_relaxed(
537
+ a_buffer.index((compute_idx + 1) % NUM_BUFFERS), OPERAND_LAYOUT_A
538
+ )
539
+ else:
540
+ next_a = gl.amd.cdna4.async_copy.load_shared_relaxed(
541
+ a_buffer.index((compute_idx + 1) % NUM_BUFFERS).permute([1, 0]),
542
+ OPERAND_LAYOUT_A,
543
+ )
544
+
545
+ if LAYOUT[1] == "T":
546
+ next_b = gl.amd.cdna4.async_copy.load_shared_relaxed(
547
+ b_buffer.index((compute_idx + 1) % NUM_BUFFERS), OPERAND_LAYOUT_B
548
+ )
549
+ else:
550
+ next_b = gl.amd.cdna4.async_copy.load_shared_relaxed(
551
+ b_buffer.index((compute_idx + 1) % NUM_BUFFERS).permute([1, 0]),
552
+ OPERAND_LAYOUT_B,
553
+ )
554
+
555
+ cur_a = next_a
556
+ cur_b = next_b
557
+ compute_idx += 1
558
+
559
+ # ---- Remaining main-loop iterations ----
560
+ for _ in range(num_k_tiles - NUM_BUFFERS - 1):
561
+
562
+ # WMMA for the current tile — uses operands pre-loaded in the
563
+ # *previous* iteration so no ds_read stall before the matrix op.
564
+ accumulator = gl.amd.gfx1250.wmma(cur_a, cur_b, accumulator)
565
+
566
+ # Issue TDM for the tile that is (NUM_BUFFERS-1) steps ahead
567
+ gl.amd.gfx1250.tdm.async_load(
568
+ a_desc, [0, 0], a_buffer.index(load_idx % NUM_BUFFERS)
569
+ )
570
+ gl.amd.gfx1250.tdm.async_load(
571
+ b_desc, [0, 0], b_buffer.index(load_idx % NUM_BUFFERS)
572
+ )
573
+
574
+ # Walk the descriptors forward one K tile.
575
+ if LAYOUT[0] == "T":
576
+ a_desc = gl.amd.gfx1250.tdm.update_tensor_descriptor(
577
+ a_desc, add_offsets=[0, BLOCK_K]
578
+ )
579
+ else:
580
+ a_desc = gl.amd.gfx1250.tdm.update_tensor_descriptor(
581
+ a_desc, add_offsets=[BLOCK_K, 0]
582
+ )
583
+
584
+ if LAYOUT[1] == "T":
585
+ b_desc = gl.amd.gfx1250.tdm.update_tensor_descriptor(
586
+ b_desc, add_offsets=[BLOCK_K, 0]
587
+ )
588
+ else:
589
+ b_desc = gl.amd.gfx1250.tdm.update_tensor_descriptor(
590
+ b_desc, add_offsets=[0, BLOCK_K]
591
+ )
592
+
593
+ # Tighter wait: after issuing the new TDM there are (NUM_BUFFERS-1)*2
594
+ # ops in-flight. Waiting for (NUM_BUFFERS-2)*2 guarantees that tile
595
+ # compute_idx+1 has landed in LDS.
596
+ gl.amd.gfx1250.tdm.async_wait((NUM_BUFFERS - 1) * 2)
597
+
598
+ load_idx += 1
599
+
600
+ # Pre-load the NEXT tile's operands into registers *before* the WMMA
601
+ # below. The hardware can run LDS reads and the matrix unit in
602
+ # parallel, hiding the ds_read latency inside the WMMA execution.
603
+ if LAYOUT[0] == "T":
604
+ next_a = gl.amd.cdna4.async_copy.load_shared_relaxed(
605
+ a_buffer.index((compute_idx + 1) % NUM_BUFFERS), OPERAND_LAYOUT_A
606
+ )
607
+ else:
608
+ next_a = gl.amd.cdna4.async_copy.load_shared_relaxed(
609
+ a_buffer.index((compute_idx + 1) % NUM_BUFFERS).permute([1, 0]),
610
+ OPERAND_LAYOUT_A,
611
+ )
612
+
613
+ if LAYOUT[1] == "T":
614
+ next_b = gl.amd.cdna4.async_copy.load_shared_relaxed(
615
+ b_buffer.index((compute_idx + 1) % NUM_BUFFERS), OPERAND_LAYOUT_B
616
+ )
617
+ else:
618
+ next_b = gl.amd.cdna4.async_copy.load_shared_relaxed(
619
+ b_buffer.index((compute_idx + 1) % NUM_BUFFERS).permute([1, 0]),
620
+ OPERAND_LAYOUT_B,
621
+ )
622
+
623
+ cur_a = next_a
624
+ cur_b = next_b
625
+ compute_idx += 1
626
+
627
+ # Epilogue: no more TDM loads; drain the remaining NUM_BUFFERS-1 tiles.
628
+ # The first NUM_BUFFERS-2 iterations still use the pre-load / WMMA pattern.
629
+ for i in gl.static_range(NUM_BUFFERS - 1):
630
+ gl.amd.gfx1250.tdm.async_wait((NUM_BUFFERS - 2 - i) * 2)
631
+
632
+ if LAYOUT[0] == "T":
633
+ next_a = gl.amd.cdna4.async_copy.load_shared_relaxed(
634
+ a_buffer.index((compute_idx + 1) % NUM_BUFFERS), OPERAND_LAYOUT_A
635
+ )
636
+ else:
637
+ next_a = gl.amd.cdna4.async_copy.load_shared_relaxed(
638
+ a_buffer.index((compute_idx + 1) % NUM_BUFFERS).permute([1, 0]),
639
+ OPERAND_LAYOUT_A,
640
+ )
641
+
642
+ if LAYOUT[1] == "T":
643
+ next_b = gl.amd.cdna4.async_copy.load_shared_relaxed(
644
+ b_buffer.index((compute_idx + 1) % NUM_BUFFERS), OPERAND_LAYOUT_B
645
+ )
646
+ else:
647
+ next_b = gl.amd.cdna4.async_copy.load_shared_relaxed(
648
+ b_buffer.index((compute_idx + 1) % NUM_BUFFERS).permute([1, 0]),
649
+ OPERAND_LAYOUT_B,
650
+ )
651
+ accumulator = gl.amd.gfx1250.wmma(cur_a, cur_b, accumulator)
652
+
653
+ cur_a = next_a
654
+ cur_b = next_b
655
+ compute_idx += 1
656
+
657
+ # Final WMMA for the last pre-loaded tile
658
+ accumulator = gl.amd.gfx1250.wmma(cur_a, cur_b, accumulator)
659
+
660
+ # if NUM_BUFFERS > 2:
661
+ # gl.amd.sched_barrier(0)
662
+
663
+ # Bias
664
+ if ADD_BIAS:
665
+ offs_bias = pid_n * BLOCK_N + gl.arange(
666
+ 0, BLOCK_N, layout=gl.SliceLayout(0, WMMA_LAYOUT)
667
+ )
668
+ bias_vals = gl.load(bias_ptr + offs_bias, mask=offs_bias < N, other=0.0)
669
+ accumulator = accumulator + bias_vals[None, :]
670
+
671
+ # Activation
672
+ if USE_ACTIVATION:
673
+ accumulator = activation(accumulator)
674
+
675
+ # TDM Store: accumulator → shared memory → global memory
676
+ SHARED_LAYOUT_C: gl.constexpr = gl.PaddedSharedLayout.with_identity_for(
677
+ [[BLOCK_N, 8]], [BLOCK_M, BLOCK_N], [1, 0]
678
+ )
679
+ c_buffer = gl.allocate_shared_memory(
680
+ c_ptr.type.element_ty,
681
+ shape=[BLOCK_M, BLOCK_N],
682
+ layout=SHARED_LAYOUT_C,
683
+ )
684
+ c_buffer.store(accumulator.to(c_ptr.type.element_ty))
685
+
686
+ # Ensure all wavefronts have finished writing to LDS before TDM reads it.
687
+ gl.barrier()
688
+
689
+ c_desc = gl.amd.gfx1250.tdm.make_tensor_descriptor(
690
+ base=c_ptr,
691
+ shape=(M, N),
692
+ strides=(stride_cm, stride_cn),
693
+ block_shape=(BLOCK_M, BLOCK_N),
694
+ layout=SHARED_LAYOUT_C,
695
+ )
696
+ gl.amd.gfx1250.tdm.async_store(c_desc, [pid_m * BLOCK_M, pid_n * BLOCK_N], c_buffer)
697
+ gl.amd.gfx1250.tdm.async_wait(0)
698
+
699
+
700
+ _KERNEL_MAP = {
701
+ "bandwidth_bound": _gemm_a16w16_bandwidth_bound_kernel,
702
+ "compute_bound": _gemm_a16w16_compute_bound_kernel,
703
+ }
build/torch-rocm/_gluon_kernels/gfx1250/gemm/basic/gemm_mxfp4.py ADDED
@@ -0,0 +1,401 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from triton.experimental import gluon
2
+ import triton.experimental.gluon.language as gl
3
+ from .....utils._triton.kernel_repr import make_kernel_repr
4
+
5
+ SCALE_GROUP_ELEMS = 32
6
+ PRESHUFFLE_FACTOR = 16 # rows packed per scale-preshuffle stripe
7
+
8
+
9
+ def get_gemm_afp4wfp4_preshuffle_layouts(num_warps, BLOCK_M, BLOCK_N, BLOCK_K):
10
+ K_GROUPS = BLOCK_K // SCALE_GROUP_ELEMS
11
+ BLOCK_K_BYTES = BLOCK_K // 2
12
+
13
+ # Warp/register layout bases depend on warp count
14
+ if num_warps == 2:
15
+ warp_bases = [[1, 0]]
16
+ reg_bases = []
17
+ elif num_warps == 4:
18
+ warp_bases = [[0, 1], [2, 0]]
19
+ reg_bases = [[1, 0]]
20
+ else:
21
+ warp_bases = [[1, 0], [0, 1], [2, 0]]
22
+ reg_bases = []
23
+
24
+ # e2m1 uses instr_shape [16,16,64] for operands
25
+ wmma_layout = gl.amd.AMDWMMALayout(
26
+ version=3,
27
+ transposed=True,
28
+ warp_bases=warp_bases,
29
+ reg_bases=reg_bases,
30
+ instr_shape=[32, 16, 64],
31
+ )
32
+
33
+ wmma_acc_layout = gl.amd.AMDWMMALayout(
34
+ version=3,
35
+ transposed=True,
36
+ warp_bases=warp_bases,
37
+ reg_bases=reg_bases,
38
+ instr_shape=[32, 16, 128],
39
+ )
40
+
41
+ # Shared memory layouts
42
+ PAD_INTERVAL_A = 256 if BLOCK_K_BYTES <= 256 else BLOCK_K_BYTES
43
+ shared_A = gl.PaddedSharedLayout.with_identity_for(
44
+ [[PAD_INTERVAL_A, 16]], [BLOCK_M, BLOCK_K_BYTES], [1, 0]
45
+ )
46
+ shared_B = gl.SwizzledSharedLayout(vec=1, per_phase=1, max_phase=1, order=[1, 0])
47
+ shared_S = gl.SwizzledSharedLayout(vec=1, per_phase=1, max_phase=1, order=[1, 0])
48
+
49
+ # Output staging layout for the TDM store (acc -> LDS -> HBM)
50
+ shared_C = gl.SwizzledSharedLayout(vec=1, per_phase=1, max_phase=1, order=[1, 0])
51
+
52
+ # Register layouts for WMMA operands
53
+ dot_a = gl.DotOperandLayout(operand_index=0, parent=wmma_layout, k_width=16)
54
+ dot_b = gl.DotOperandLayout(operand_index=1, parent=wmma_layout, k_width=16)
55
+
56
+ # Register layouts for WMMA scale operands
57
+ scale_a = gl.amd.gfx1250.get_wmma_scale_layout(
58
+ dot_a, [BLOCK_M, K_GROUPS], scale_factor=SCALE_GROUP_ELEMS
59
+ )
60
+ scale_b = gl.amd.gfx1250.get_wmma_scale_layout(
61
+ dot_b, [BLOCK_N, K_GROUPS], scale_factor=SCALE_GROUP_ELEMS
62
+ )
63
+
64
+ return {
65
+ "wmma_layout": wmma_layout,
66
+ "wmma_acc_layout": wmma_acc_layout,
67
+ "shared_A": shared_A,
68
+ "shared_B": shared_B,
69
+ "shared_S": shared_S,
70
+ "shared_C": shared_C,
71
+ "dot_a_layout": dot_a,
72
+ "dot_b_layout": dot_b,
73
+ "a_scale_layout": scale_a,
74
+ "b_scale_layout": scale_b,
75
+ }
76
+
77
+
78
+ # ---------------------------------------------------------------------------
79
+ # View transforms for preshuffled data in LDS
80
+ # These are zero-cost (no data movement) — they just reindex the LDS view
81
+ # so load_shared_relaxed reads bytes in the order WMMA expects.
82
+ # ---------------------------------------------------------------------------
83
+
84
+
85
+ @gluon.jit
86
+ def depreshuffle_scales(
87
+ smem_scales,
88
+ BLOCK_M: gl.constexpr,
89
+ K_GROUPS: gl.constexpr,
90
+ ):
91
+ # Inverse of host shuffle_scales_gfx1250: PRESHUFFLE_FACTOR rows are packed
92
+ # per stripe, SCALE_KWIDTH scale-groups contiguous per row.
93
+ PRESHUFFLE_FACTOR: gl.constexpr = 16
94
+ SCALE_KWIDTH: gl.constexpr = 4
95
+ NUM_STRIPES: gl.constexpr = K_GROUPS // SCALE_KWIDTH
96
+ return (
97
+ smem_scales.reshape(
98
+ (BLOCK_M // PRESHUFFLE_FACTOR, NUM_STRIPES, PRESHUFFLE_FACTOR, SCALE_KWIDTH)
99
+ )
100
+ .permute((0, 2, 1, 3))
101
+ .reshape((BLOCK_M, K_GROUPS))
102
+ )
103
+
104
+
105
+ @gluon.jit
106
+ def depreshuffle_b_raw_to_kn(
107
+ b_raw,
108
+ BLOCK_N: gl.constexpr,
109
+ BLOCK_K_BYTES: gl.constexpr,
110
+ ):
111
+ # raw -> logical [BLOCK_K_BYTES, BLOCK_N]
112
+ return (
113
+ b_raw.reshape((BLOCK_N // 16, BLOCK_K_BYTES // 32, 2, 16, 16))
114
+ .permute((0, 3, 1, 2, 4))
115
+ .reshape((BLOCK_N, BLOCK_K_BYTES))
116
+ .permute((1, 0))
117
+ )
118
+
119
+
120
+ _gemm_mxfp4_preshuffle_gfx1250_repr = make_kernel_repr(
121
+ "_gemm_mxfp4_preshuffle_gfx1250_kernel",
122
+ [
123
+ "BLOCK_SIZE_M",
124
+ "BLOCK_SIZE_N",
125
+ "BLOCK_SIZE_K",
126
+ "num_warps",
127
+ "NUM_BUFFERS",
128
+ ],
129
+ )
130
+
131
+
132
+ @gluon.jit(repr=_gemm_mxfp4_preshuffle_gfx1250_repr)
133
+ def gemm_mxfp4_preshuffle_gfx1250(
134
+ a_fp4_ptr,
135
+ b_preshuf_ptr,
136
+ c_ptr,
137
+ a_scale_ptr,
138
+ b_scale_ptr,
139
+ M,
140
+ N,
141
+ K_elems,
142
+ stride_a_m,
143
+ stride_a_kbytes,
144
+ stride_b_n16,
145
+ stride_b_kshuf,
146
+ stride_c_k,
147
+ stride_c_m,
148
+ stride_c_n,
149
+ stride_as_m,
150
+ stride_as_k,
151
+ stride_bs_n,
152
+ stride_bs_k,
153
+ BLOCK_SIZE_M: gl.constexpr,
154
+ BLOCK_SIZE_N: gl.constexpr,
155
+ BLOCK_SIZE_K: gl.constexpr,
156
+ num_warps: gl.constexpr,
157
+ NUM_BUFFERS: gl.constexpr,
158
+ wmma_layout: gl.constexpr,
159
+ wmma_acc_layout: gl.constexpr,
160
+ shared_A: gl.constexpr,
161
+ shared_B: gl.constexpr,
162
+ shared_S: gl.constexpr,
163
+ shared_C: gl.constexpr,
164
+ dot_a_layout: gl.constexpr,
165
+ dot_b_layout: gl.constexpr,
166
+ a_scale_layout: gl.constexpr,
167
+ b_scale_layout: gl.constexpr,
168
+ ):
169
+ # Compile-time constants
170
+ FP4_ELEMS_PER_BYTE: gl.constexpr = 2
171
+ SCALE_GROUP_ELEMS: gl.constexpr = 32
172
+
173
+ BLOCK_K_BYTES: gl.constexpr = BLOCK_SIZE_K // FP4_ELEMS_PER_BYTE
174
+ K_GROUPS: gl.constexpr = BLOCK_SIZE_K // SCALE_GROUP_ELEMS
175
+ # Scale preshuffle: PRESHUFFLE_FACTOR rows packed per stripe, SCALE_KWIDTH
176
+ # scale-groups contiguous per row (must match the host shuffle_scales_gfx1250).
177
+ PRESHUFFLE_FACTOR: gl.constexpr = 16
178
+ SCALE_KWIDTH: gl.constexpr = 4
179
+
180
+ gl.static_assert(K_GROUPS * 32 == BLOCK_SIZE_K)
181
+
182
+ gl.static_assert(BLOCK_SIZE_K % 32 == 0)
183
+ gl.static_assert(K_GROUPS % SCALE_KWIDTH == 0) # K_GROUPS divisible by SCALE_KWIDTH
184
+ gl.static_assert(BLOCK_SIZE_M % PRESHUFFLE_FACTOR == 0)
185
+ gl.static_assert(BLOCK_SIZE_N % PRESHUFFLE_FACTOR == 0)
186
+
187
+ pid = gl.program_id(axis=0)
188
+ tiles_n = gl.cdiv(N, BLOCK_SIZE_N)
189
+
190
+ tile_linear = pid
191
+ tile_m = tile_linear // tiles_n
192
+ tile_n = tile_linear - tile_m * tiles_n
193
+
194
+ K_bytes = K_elems // FP4_ELEMS_PER_BYTE
195
+ k_tiles = gl.cdiv(K_bytes, BLOCK_K_BYTES)
196
+
197
+ # =====================================================================
198
+ # TDM descriptors (HBM tensor layout for async loads)
199
+ # =====================================================================
200
+ a_desc = gl.amd.gfx1250.tdm.make_tensor_descriptor(
201
+ base=a_fp4_ptr + tile_m * BLOCK_SIZE_M * stride_a_m,
202
+ shape=(M - tile_m * BLOCK_SIZE_M, K_bytes),
203
+ strides=(stride_a_m, stride_a_kbytes),
204
+ block_shape=(BLOCK_SIZE_M, BLOCK_K_BYTES),
205
+ layout=shared_A,
206
+ )
207
+
208
+ b_desc = gl.amd.gfx1250.tdm.make_tensor_descriptor(
209
+ base=b_preshuf_ptr + tile_n * (BLOCK_SIZE_N // 16) * stride_b_n16,
210
+ shape=(gl.cdiv(N, 16) - tile_n * (BLOCK_SIZE_N // 16), K_bytes * 16),
211
+ strides=(stride_b_n16, stride_b_kshuf),
212
+ block_shape=(BLOCK_SIZE_N // 16, BLOCK_K_BYTES * 16),
213
+ layout=shared_B,
214
+ )
215
+
216
+ k_scale_cols = K_elems // SCALE_GROUP_ELEMS
217
+
218
+ as_desc = gl.amd.gfx1250.tdm.make_tensor_descriptor(
219
+ base=a_scale_ptr + tile_m * (BLOCK_SIZE_M // PRESHUFFLE_FACTOR) * stride_as_m,
220
+ shape=(
221
+ gl.cdiv(M, PRESHUFFLE_FACTOR)
222
+ - tile_m * (BLOCK_SIZE_M // PRESHUFFLE_FACTOR),
223
+ k_scale_cols * PRESHUFFLE_FACTOR,
224
+ ),
225
+ strides=(stride_as_m, stride_as_k),
226
+ block_shape=(BLOCK_SIZE_M // PRESHUFFLE_FACTOR, K_GROUPS * PRESHUFFLE_FACTOR),
227
+ layout=shared_S,
228
+ )
229
+
230
+ bs_desc = gl.amd.gfx1250.tdm.make_tensor_descriptor(
231
+ base=b_scale_ptr + tile_n * (BLOCK_SIZE_N // PRESHUFFLE_FACTOR) * stride_bs_n,
232
+ shape=(
233
+ gl.cdiv(N, PRESHUFFLE_FACTOR)
234
+ - tile_n * (BLOCK_SIZE_N // PRESHUFFLE_FACTOR),
235
+ k_scale_cols * PRESHUFFLE_FACTOR,
236
+ ),
237
+ strides=(stride_bs_n, stride_bs_k),
238
+ block_shape=(BLOCK_SIZE_N // PRESHUFFLE_FACTOR, K_GROUPS * PRESHUFFLE_FACTOR),
239
+ layout=shared_S,
240
+ )
241
+
242
+ # =====================================================================
243
+ # Allocate shared memory
244
+ # =====================================================================
245
+ smem_A = gl.allocate_shared_memory(
246
+ a_fp4_ptr.type.element_ty,
247
+ [NUM_BUFFERS, BLOCK_SIZE_M, BLOCK_K_BYTES],
248
+ layout=shared_A,
249
+ )
250
+
251
+ smem_B = gl.allocate_shared_memory(
252
+ b_preshuf_ptr.type.element_ty,
253
+ [NUM_BUFFERS, BLOCK_SIZE_N // 16, BLOCK_K_BYTES * 16],
254
+ layout=shared_B,
255
+ )
256
+
257
+ smem_AS = gl.allocate_shared_memory(
258
+ a_scale_ptr.type.element_ty,
259
+ [NUM_BUFFERS, BLOCK_SIZE_M // PRESHUFFLE_FACTOR, K_GROUPS * PRESHUFFLE_FACTOR],
260
+ layout=shared_S,
261
+ )
262
+
263
+ smem_BS = gl.allocate_shared_memory(
264
+ b_scale_ptr.type.element_ty,
265
+ [NUM_BUFFERS, BLOCK_SIZE_N // PRESHUFFLE_FACTOR, K_GROUPS * PRESHUFFLE_FACTOR],
266
+ layout=shared_S,
267
+ )
268
+
269
+ # Pipelining start
270
+ load_idx = 0
271
+ compute_idx = 0
272
+ acc = gl.zeros(
273
+ (BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=gl.float32, layout=wmma_acc_layout
274
+ )
275
+
276
+ # --- 1. Prologue: fill NUM_BUFFERS-1 LDS slots via TDM ---
277
+ # Load-then-advance: each iter consumes the descriptor's current K
278
+ # position, then steps it forward for the next load (prologue or main).
279
+ for _ in gl.static_range(NUM_BUFFERS):
280
+ slot = load_idx % NUM_BUFFERS
281
+ # slot index math (arith.muli) ahead of the copies so the four tdm async_loads emit back-to-back and the compiler can merge them.
282
+ a_slot = smem_A.index(slot)
283
+ b_slot = smem_B.index(slot)
284
+ as_slot = smem_AS.index(slot)
285
+ bs_slot = smem_BS.index(slot)
286
+ off_a = load_idx * BLOCK_K_BYTES
287
+ off_b = load_idx * BLOCK_K_BYTES * 16
288
+ off_s = load_idx * K_GROUPS * PRESHUFFLE_FACTOR
289
+ gl.amd.gfx1250.tdm.async_load(a_desc, [0, off_a], a_slot)
290
+ gl.amd.gfx1250.tdm.async_load(b_desc, [0, off_b], b_slot)
291
+ gl.amd.gfx1250.tdm.async_load(as_desc, [0, off_s], as_slot)
292
+ gl.amd.gfx1250.tdm.async_load(bs_desc, [0, off_s], bs_slot)
293
+ load_idx += 1
294
+
295
+ # --- 2. Pre-load tile 0 from LDS into registers ---
296
+ gl.amd.gfx1250.tdm.async_wait((NUM_BUFFERS - 1) * 4)
297
+
298
+ slot_c = compute_idx % NUM_BUFFERS
299
+ cur_A = smem_A.index(slot_c).load(layout=dot_a_layout)
300
+ cur_B = depreshuffle_b_raw_to_kn(
301
+ smem_B.index(slot_c), BLOCK_N=BLOCK_SIZE_N, BLOCK_K_BYTES=BLOCK_K_BYTES
302
+ ).load(layout=dot_b_layout)
303
+ cur_AS = depreshuffle_scales(smem_AS.index(slot_c), BLOCK_SIZE_M, K_GROUPS).load(
304
+ layout=a_scale_layout
305
+ )
306
+ cur_BS = depreshuffle_scales(smem_BS.index(slot_c), BLOCK_SIZE_N, K_GROUPS).load(
307
+ layout=b_scale_layout
308
+ )
309
+
310
+ # --- 3. Main loop: WMMA(cur) → TDM(future) → wait → pre-load(next) ---
311
+ main_iters = k_tiles - (NUM_BUFFERS)
312
+ for _ in range(main_iters):
313
+ acc = gl.amd.gfx1250.wmma_scaled(
314
+ cur_A, cur_AS, "e2m1", cur_B, cur_BS, "e2m1", acc
315
+ )
316
+
317
+ # TDM load next tile (descriptors are already positioned by
318
+ # the previous iter's / prologue's trailing update_tensor_descriptor)
319
+ slot = load_idx % NUM_BUFFERS
320
+
321
+ a_slot = smem_A.index(slot)
322
+ b_slot = smem_B.index(slot)
323
+ as_slot = smem_AS.index(slot)
324
+ bs_slot = smem_BS.index(slot)
325
+ off_a = load_idx * BLOCK_K_BYTES
326
+ off_b = load_idx * BLOCK_K_BYTES * 16
327
+ off_s = load_idx * K_GROUPS * PRESHUFFLE_FACTOR
328
+ gl.amd.gfx1250.tdm.async_load(a_desc, [0, off_a], a_slot)
329
+ gl.amd.gfx1250.tdm.async_load(b_desc, [0, off_b], b_slot)
330
+ gl.amd.gfx1250.tdm.async_load(as_desc, [0, off_s], as_slot)
331
+ gl.amd.gfx1250.tdm.async_load(bs_desc, [0, off_s], bs_slot)
332
+
333
+ gl.amd.gfx1250.tdm.async_wait((NUM_BUFFERS - 1) * 4)
334
+ load_idx += 1
335
+
336
+ # Pre-load next tile from LDS into registers
337
+ next_slot = (compute_idx + 1) % NUM_BUFFERS
338
+ cur_A = smem_A.index(next_slot).load(layout=dot_a_layout)
339
+ cur_B = depreshuffle_b_raw_to_kn(
340
+ smem_B.index(next_slot),
341
+ BLOCK_N=BLOCK_SIZE_N,
342
+ BLOCK_K_BYTES=BLOCK_K_BYTES,
343
+ ).load(layout=dot_b_layout)
344
+ cur_AS = depreshuffle_scales(
345
+ smem_AS.index(next_slot), BLOCK_SIZE_M, K_GROUPS
346
+ ).load(layout=a_scale_layout)
347
+ cur_BS = depreshuffle_scales(
348
+ smem_BS.index(next_slot), BLOCK_SIZE_N, K_GROUPS
349
+ ).load(layout=b_scale_layout)
350
+ compute_idx += 1
351
+
352
+ # --- 4. Epilogue: drain remaining tiles (no new TDM loads) ---
353
+ for i in gl.static_range(NUM_BUFFERS - 1):
354
+ acc = gl.amd.gfx1250.wmma_scaled(
355
+ cur_A, cur_AS, "e2m1", cur_B, cur_BS, "e2m1", acc
356
+ )
357
+
358
+ gl.amd.gfx1250.tdm.async_wait((NUM_BUFFERS - 2 - i) * 4)
359
+
360
+ next_slot = (compute_idx + 1) % NUM_BUFFERS
361
+ cur_A = smem_A.index(next_slot).load(layout=dot_a_layout)
362
+ cur_B = depreshuffle_b_raw_to_kn(
363
+ smem_B.index(next_slot),
364
+ BLOCK_N=BLOCK_SIZE_N,
365
+ BLOCK_K_BYTES=BLOCK_K_BYTES,
366
+ ).load(layout=dot_b_layout)
367
+ cur_AS = depreshuffle_scales(
368
+ smem_AS.index(next_slot), BLOCK_SIZE_M, K_GROUPS
369
+ ).load(layout=a_scale_layout)
370
+ cur_BS = depreshuffle_scales(
371
+ smem_BS.index(next_slot), BLOCK_SIZE_N, K_GROUPS
372
+ ).load(layout=b_scale_layout)
373
+ compute_idx += 1
374
+
375
+ # --- 5. Final WMMA ---
376
+ acc = gl.amd.gfx1250.wmma_scaled(cur_A, cur_AS, "e2m1", cur_B, cur_BS, "e2m1", acc)
377
+
378
+ # =====================================================================
379
+ # Store output via TDM: accumulator → shared memory → global memory.
380
+ # =====================================================================
381
+
382
+ c_buffer = gl.allocate_shared_memory(
383
+ c_ptr.type.element_ty,
384
+ shape=[BLOCK_SIZE_M, BLOCK_SIZE_N],
385
+ layout=shared_C,
386
+ )
387
+ c_desc = gl.amd.gfx1250.tdm.make_tensor_descriptor(
388
+ base=c_ptr,
389
+ shape=(M, N),
390
+ strides=(stride_c_m, stride_c_n),
391
+ block_shape=(BLOCK_SIZE_M, BLOCK_SIZE_N),
392
+ layout=shared_C,
393
+ )
394
+
395
+ c_buffer.store(acc.to(c_ptr.type.element_ty))
396
+
397
+ gl.amd.gfx1250.tdm.async_store(
398
+ c_desc, [tile_m * BLOCK_SIZE_M, tile_n * BLOCK_SIZE_N], c_buffer
399
+ )
400
+
401
+ gl.amd.gfx1250.tdm.async_wait(0)
build/torch-rocm/_gluon_kernels/gfx1250/moe/__init__.py ADDED
File without changes
build/torch-rocm/_gluon_kernels/gfx1250/moe/moe_op_gemm_a8w4.py ADDED
@@ -0,0 +1,1170 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import triton.language as tl
3
+ from triton.experimental import gluon
4
+ import triton.experimental.gluon.language as gl
5
+ from ....utils._triton.pid_preprocessing import remap_xcd, pid_grid
6
+ from ...._triton_kernels.moe.quant_moe import _compute_static_fp8_quant
7
+ from ...._triton_kernels.moe.activations import _swiglu
8
+
9
+
10
+ def matmul_launch_metadata(grid, kernel, args):
11
+ ret = dict()
12
+ M, N, K = None, args["N"], args["K"]
13
+ Y, X, W = args["Y"], args["X"], args["W"]
14
+ hist = args["ExptHist"]
15
+ if hist is not None:
16
+ n_rows = int(hist.float().mean())
17
+ n_tokens = float(hist.sum())
18
+ n_w_bytes = (W.numel() * W.element_size() // hist.numel()) * (hist > 0).sum()
19
+ else:
20
+ n_tokens = None
21
+ n_w_bytes = W.numel() * W.element_size()
22
+
23
+ def repr(s, x):
24
+ return f"{s}={x}" if x is not None else f"E_{len(hist)}({s})={n_rows}"
25
+
26
+ nbits = X.dtype.itemsize * 8
27
+ ret["name"] = f"{kernel.name} [{repr('M', M)}, {repr('N', N)}, {repr('K', K)}]"
28
+ gindx = args.get("GatherIndx", None)
29
+ if gindx is not None:
30
+ gindx = gindx.to(torch.int32)
31
+ ret["name"] += "_layer1"
32
+ else:
33
+ ret["name"] += "_layer2"
34
+ if args["B"] is not None:
35
+ ret["name"] += "_bias"
36
+ if args["APPLY_SWIGLU"]:
37
+ ret["name"] += "_swiglu"
38
+ if args["Quant_static_scale"] is not None:
39
+ ret["name"] += "_quant"
40
+
41
+ fM = n_tokens
42
+ fK = K if K is not None else n_tokens
43
+ ret[f"flops{nbits}"] = 2.0 * fM * N * fK
44
+
45
+ n_x_bytes = X.numel() * X.element_size()
46
+ n_y_bytes = Y.numel() * Y.element_size()
47
+ if hist is not None:
48
+ assert n_tokens is not None
49
+ n_expts_act = args["N_EXPTS_ACT"]
50
+
51
+ if gindx is not None:
52
+ # recreate inverse GatherIndx.
53
+ dst = torch.full_like(gindx, -1)
54
+ idx = torch.arange(len(gindx), device=gindx.device, dtype=torch.int32)
55
+ mask = gindx != -1
56
+ dst[gindx[mask]] = idx[mask]
57
+ n_read_rows = (dst.view((-1, n_expts_act)) != -1).any(dim=1).sum()
58
+ else:
59
+ n_read_rows = n_tokens
60
+ n_x_bytes = n_read_rows * X.shape[-1] * X.element_size()
61
+ n_y_bytes = n_tokens * Y.shape[-1] * Y.element_size()
62
+ ret["bytes"] = int(n_x_bytes + n_y_bytes + n_w_bytes)
63
+
64
+ return ret
65
+
66
+
67
+ @gluon.jit
68
+ def unswizzle_mx_scale_gfx1250(
69
+ scale_buffer_slice, BLOCK_N, MX_SCALE_BLOCK_K, PRESHUFFLE_FACTOR, SCALE_KWIDTH
70
+ ):
71
+ scale_buffer_slice = (
72
+ scale_buffer_slice.reshape(
73
+ (
74
+ BLOCK_N // PRESHUFFLE_FACTOR,
75
+ MX_SCALE_BLOCK_K // SCALE_KWIDTH,
76
+ PRESHUFFLE_FACTOR,
77
+ SCALE_KWIDTH,
78
+ )
79
+ )
80
+ .permute((0, 2, 1, 3))
81
+ .reshape((BLOCK_N, MX_SCALE_BLOCK_K))
82
+ )
83
+
84
+ return scale_buffer_slice
85
+
86
+
87
+ @gluon.jit(launch_metadata=matmul_launch_metadata, do_not_specialize=["num_tokens"])
88
+ def _moe_gemm_a8w4_decode(
89
+ Y,
90
+ stride_y_m,
91
+ stride_y_n,
92
+ X,
93
+ stride_x_m,
94
+ stride_x_k,
95
+ XMxScale,
96
+ stride_x_mx_m,
97
+ stride_x_mx_k,
98
+ W,
99
+ stride_w_e,
100
+ stride_w_n,
101
+ stride_w_k,
102
+ WMxScale,
103
+ stride_w_mx_e,
104
+ stride_w_mx_n,
105
+ stride_w_mx_k,
106
+ X_static_scale,
107
+ Quant_static_scale,
108
+ B,
109
+ stride_b_e, # Bias
110
+ Gammas,
111
+ num_tokens,
112
+ N,
113
+ K, # shapes
114
+ # expt data
115
+ GatherIndx,
116
+ ExptHist,
117
+ ExptOffs,
118
+ ExptOffsSum,
119
+ ExptData,
120
+ # true grid size
121
+ grid_m,
122
+ grid_n,
123
+ # fused activation function
124
+ APPLY_SWIGLU: gl.constexpr,
125
+ alpha,
126
+ limit,
127
+ ACTIVATION_REDUCTION_N: gl.constexpr,
128
+ SWIGLU_ADD_RESIDUAL: gl.constexpr,
129
+ # MoE config
130
+ N_EXPTS_ACT: gl.constexpr,
131
+ # optimization config
132
+ BLOCK_M: gl.constexpr,
133
+ BLOCK_N: gl.constexpr,
134
+ BLOCK_K: gl.constexpr,
135
+ XCD_SWIZZLE: gl.constexpr,
136
+ NUM_BUFFERS: gl.constexpr,
137
+ # One of ["GFX1250", None]
138
+ SWIZZLE_MX_SCALE: gl.constexpr,
139
+ MASK_K_LIMIT: gl.constexpr,
140
+ W_CACHE_MODIFIER: gl.constexpr,
141
+ num_warps: gl.constexpr,
142
+ UPCAST_INDICES: gl.constexpr = False,
143
+ ):
144
+
145
+ is_x_microscaled: gl.constexpr = XMxScale is not None
146
+ MX_PACK_DIVISOR: gl.constexpr = 32
147
+ NUM_TDM_OPS: gl.constexpr = 4 if is_x_microscaled else 3
148
+ w_type: gl.constexpr = W.dtype.element_ty
149
+ gl.static_assert(w_type == gl.uint8, "mx_weight_ptr must be uint8 or fp8")
150
+ gl.static_assert(
151
+ WMxScale.dtype.element_ty == gl.uint8, "mx_scale_ptr must be uint8"
152
+ )
153
+ gl.static_assert(
154
+ BLOCK_K % MX_PACK_DIVISOR == 0, "BLOCK_K must be a multiple of MX_PACK_DIVISOR"
155
+ )
156
+ x_type: gl.constexpr = X.dtype.element_ty
157
+ if is_x_microscaled:
158
+ gl.static_assert(x_type == gl.float8e4nv, "mx_act_ptr must be float8e4nv")
159
+ gl.static_assert(
160
+ XMxScale.dtype.element_ty == gl.uint8, "mx_scale_ptr must be uint8"
161
+ )
162
+
163
+ OUT_BLOCK_N: tl.constexpr = BLOCK_N // ACTIVATION_REDUCTION_N
164
+ yN = N // ACTIVATION_REDUCTION_N
165
+
166
+ pid = gl.program_id(0)
167
+
168
+ index_type: tl.constexpr = gl.int64 if UPCAST_INDICES else gl.int32
169
+
170
+ if XCD_SWIZZLE != 1:
171
+ padding_m = grid_m - gl.load(ExptOffsSum)
172
+ unpadded_m = grid_m - padding_m
173
+ total_actual_tiles = unpadded_m * grid_n
174
+ if padding_m > 0 and pid >= total_actual_tiles:
175
+ return
176
+ pid = remap_xcd(pid, total_actual_tiles, XCD_SWIZZLE)
177
+ else:
178
+ unpadded_m = grid_m
179
+ pid_m, pid_n = pid_grid(pid, unpadded_m, grid_n, 1)
180
+ # unpack expert data
181
+ expt_data = gl.load(ExptData + pid_m)
182
+ if XCD_SWIZZLE == 1 and expt_data == -1:
183
+ return
184
+ expt_id = expt_data & 0x0000FFFF
185
+ block_id = expt_data >> 16
186
+ M = gl.load(ExptHist + expt_id)
187
+ start_m = gl.load(ExptOffs + expt_id)
188
+ expt_id, block_id = expt_id.to(index_type), block_id.to(index_type)
189
+ start_m = start_m.to(index_type)
190
+ pid_n = pid_n.to(index_type)
191
+
192
+ # A pointers
193
+ off_x_m = BLOCK_M * block_id
194
+ if GatherIndx is None:
195
+ X += start_m * stride_x_m
196
+ else:
197
+ if GatherIndx.dtype.element_ty == gl.uint16:
198
+ IDX_LAYOUT: gl.constexpr = gl.SliceLayout(
199
+ 0, gl.BlockedLayout([1, 16], [32, 1], [1, num_warps], [0, 1])
200
+ )
201
+ oob_idx = (num_tokens).to(gl.uint16)
202
+ else:
203
+ gl.static_assert(
204
+ GatherIndx.dtype.element_ty == gl.int32,
205
+ "Gather index datatype should be uint16 or int32",
206
+ )
207
+ IDX_LAYOUT: gl.constexpr = gl.SliceLayout(
208
+ 0, gl.BlockedLayout([1, 8], [32, 1], [1, num_warps], [0, 1])
209
+ )
210
+ oob_idx = num_tokens
211
+ offs_x_m = BLOCK_M * block_id + gl.arange(0, BLOCK_M, layout=IDX_LAYOUT)
212
+ mask_idx = offs_x_m < M
213
+ offs_x_m = offs_x_m % M
214
+ GatherIndx += start_m
215
+ offs_x_m = gl.load(GatherIndx + offs_x_m) // N_EXPTS_ACT
216
+ offs_x_m = gl.where(mask_idx, offs_x_m, oob_idx)
217
+
218
+ W_K_DIVISOR: gl.constexpr = 2
219
+ PACKED_BLOCK_K_W: gl.constexpr = BLOCK_K // W_K_DIVISOR
220
+ PACKED_BLOCK_N_W: gl.constexpr = BLOCK_N
221
+ MX_SCALE_BLOCK_K: gl.constexpr = BLOCK_K // MX_PACK_DIVISOR
222
+
223
+ WMxScale += expt_id * stride_w_mx_e
224
+ if SWIZZLE_MX_SCALE == "GFX1250_SCALE":
225
+ gl.static_assert(stride_w_mx_k is not None)
226
+ gl.static_assert(stride_w_mx_n is not None)
227
+ PRESHUFFLE_FACTOR: gl.constexpr = 32
228
+ PACKED_MX_BLOCK: gl.constexpr = MX_SCALE_BLOCK_K * PRESHUFFLE_FACTOR
229
+ SCALE_BLOCK_N: gl.constexpr = BLOCK_N // PRESHUFFLE_FACTOR
230
+ SCALE_KWIDTH: gl.constexpr = 8
231
+ else:
232
+ PRESHUFFLE_FACTOR: gl.constexpr = 1
233
+ PACKED_MX_BLOCK: gl.constexpr = MX_SCALE_BLOCK_K
234
+ SCALE_BLOCK_N: gl.constexpr = BLOCK_N
235
+
236
+ # B pointers
237
+ off_w_n_scale = pid_n * SCALE_BLOCK_N
238
+ off_w_n = pid_n * PACKED_BLOCK_N_W
239
+ W += expt_id * stride_w_e
240
+
241
+ SHARED_LAYOUT_X: gl.constexpr = gl.PaddedSharedLayout.with_identity_for(
242
+ [[BLOCK_K, 16]], [BLOCK_M, BLOCK_K], [1, 0]
243
+ )
244
+ if BLOCK_K <= 256:
245
+ SHARED_LAYOUT_W: gl.constexpr = gl.PaddedSharedLayout.with_identity_for(
246
+ [[256, 16]], [BLOCK_N, PACKED_BLOCK_K_W], [1, 0]
247
+ )
248
+ else:
249
+ SHARED_LAYOUT_W: gl.constexpr = gl.PaddedSharedLayout.with_identity_for(
250
+ [[PACKED_BLOCK_K_W, 16]], [BLOCK_N, PACKED_BLOCK_K_W], [1, 0]
251
+ )
252
+ SHARED_LAYOUT_W_SCALES: gl.constexpr = gl.PaddedSharedLayout.with_identity_for(
253
+ [[256, 16]], [SCALE_BLOCK_N, PACKED_MX_BLOCK], [1, 0]
254
+ )
255
+ if is_x_microscaled:
256
+ SHARED_LAYOUT_X_SCALES: gl.constexpr = gl.PaddedSharedLayout.with_identity_for(
257
+ [[256, 16]], [BLOCK_M, MX_SCALE_BLOCK_K], [1, 0]
258
+ )
259
+ if Quant_static_scale is not None:
260
+ SHARED_LAYOUT_Y: gl.constexpr = gl.PaddedSharedLayout.with_identity_for(
261
+ [[OUT_BLOCK_N, 16]], [BLOCK_M, OUT_BLOCK_N], [1, 0]
262
+ )
263
+ else:
264
+ SHARED_LAYOUT_Y: gl.constexpr = gl.PaddedSharedLayout.with_identity_for(
265
+ [[OUT_BLOCK_N, 8]], [BLOCK_M, OUT_BLOCK_N], [1, 0]
266
+ )
267
+
268
+ if GatherIndx is None:
269
+ x_desc = gl.amd.gfx1250.tdm.make_tensor_descriptor(
270
+ base=X,
271
+ shape=(M, K),
272
+ strides=(stride_x_m, stride_x_k),
273
+ block_shape=(BLOCK_M, BLOCK_K),
274
+ layout=SHARED_LAYOUT_X,
275
+ )
276
+ else:
277
+ x_desc = gl.amd.gfx1250.tdm.make_tensor_descriptor(
278
+ base=X,
279
+ shape=(num_tokens, K),
280
+ strides=(stride_x_m, stride_x_k),
281
+ block_shape=(BLOCK_M, BLOCK_K),
282
+ layout=SHARED_LAYOUT_X,
283
+ )
284
+ w_desc = gl.amd.gfx1250.tdm.make_tensor_descriptor(
285
+ base=W,
286
+ shape=(N, K // W_K_DIVISOR),
287
+ strides=(
288
+ stride_w_n,
289
+ stride_w_k,
290
+ ),
291
+ block_shape=(
292
+ BLOCK_N,
293
+ PACKED_BLOCK_K_W,
294
+ ),
295
+ layout=SHARED_LAYOUT_W,
296
+ )
297
+ w_scales_desc = gl.amd.gfx1250.tdm.make_tensor_descriptor(
298
+ base=WMxScale,
299
+ shape=(N // PRESHUFFLE_FACTOR, tl.cdiv(K, MX_PACK_DIVISOR) * PRESHUFFLE_FACTOR),
300
+ strides=(stride_w_mx_n, stride_w_mx_k),
301
+ block_shape=(SCALE_BLOCK_N, PACKED_MX_BLOCK),
302
+ layout=SHARED_LAYOUT_W_SCALES,
303
+ )
304
+
305
+ if is_x_microscaled:
306
+ if GatherIndx is None:
307
+ XMxScale += start_m * stride_x_mx_m
308
+ x_scales_desc = gl.amd.gfx1250.tdm.make_tensor_descriptor(
309
+ base=XMxScale,
310
+ shape=(M, tl.cdiv(K, MX_PACK_DIVISOR)),
311
+ strides=(stride_x_mx_m, stride_x_mx_k),
312
+ block_shape=(BLOCK_M, MX_SCALE_BLOCK_K),
313
+ layout=SHARED_LAYOUT_X_SCALES,
314
+ )
315
+ else:
316
+ x_scales_desc = gl.amd.gfx1250.tdm.make_tensor_descriptor(
317
+ base=XMxScale,
318
+ shape=(num_tokens, tl.cdiv(K, MX_PACK_DIVISOR)),
319
+ strides=(stride_x_mx_m, stride_x_mx_k),
320
+ block_shape=(BLOCK_M, MX_SCALE_BLOCK_K),
321
+ layout=SHARED_LAYOUT_X_SCALES,
322
+ )
323
+
324
+ WMMA_LAYOUT: gl.constexpr = gl.amd.AMDWMMALayout(
325
+ 3,
326
+ transposed=True,
327
+ warp_bases=[[0, 1], [0, 2]],
328
+ reg_bases=[],
329
+ instr_shape=[16, 16, 128],
330
+ )
331
+ WMMA_LAYOUT_PACKED: gl.constexpr = gl.amd.AMDWMMALayout(
332
+ 3,
333
+ transposed=True,
334
+ warp_bases=[[0, 1], [0, 2]],
335
+ reg_bases=[],
336
+ instr_shape=[16, 16, 64],
337
+ )
338
+
339
+ DOT_LAYOUT_X: gl.constexpr = gl.DotOperandLayout(0, WMMA_LAYOUT, k_width=16)
340
+ DOT_LAYOUT_W: gl.constexpr = gl.DotOperandLayout(1, WMMA_LAYOUT_PACKED, k_width=16)
341
+ DOT_LAYOUT_W_SCALES: gl.constexpr = gl.amd.gfx1250.get_wmma_scale_layout(
342
+ DOT_LAYOUT_W, [BLOCK_N, MX_SCALE_BLOCK_K]
343
+ )
344
+ if is_x_microscaled:
345
+ DOT_LAYOUT_X_SCALES: gl.constexpr = gl.amd.gfx1250.get_wmma_scale_layout(
346
+ DOT_LAYOUT_X, [BLOCK_M, MX_SCALE_BLOCK_K]
347
+ )
348
+
349
+ x_buffer = gl.allocate_shared_memory(
350
+ x_desc.dtype, shape=[NUM_BUFFERS] + x_desc.block_shape, layout=x_desc.layout
351
+ )
352
+ w_buffer = gl.allocate_shared_memory(
353
+ w_desc.dtype, shape=[NUM_BUFFERS] + w_desc.block_shape, layout=w_desc.layout
354
+ )
355
+ w_scales_buffer = gl.allocate_shared_memory(
356
+ w_scales_desc.dtype,
357
+ shape=[NUM_BUFFERS] + w_scales_desc.block_shape,
358
+ layout=w_scales_desc.layout,
359
+ )
360
+ if is_x_microscaled:
361
+ x_scales_buffer = gl.allocate_shared_memory(
362
+ x_scales_desc.dtype,
363
+ shape=[NUM_BUFFERS] + x_scales_desc.block_shape,
364
+ layout=x_scales_desc.layout,
365
+ )
366
+
367
+ read_idx = 0
368
+ write_idx = 0
369
+ for _ in gl.static_range(NUM_BUFFERS - 1):
370
+ gl.amd.gfx1250.tdm.async_load(
371
+ w_desc,
372
+ [off_w_n, write_idx * PACKED_BLOCK_K_W],
373
+ w_buffer.index(write_idx % NUM_BUFFERS),
374
+ )
375
+ if GatherIndx is None:
376
+ gl.amd.gfx1250.tdm.async_load(
377
+ x_desc,
378
+ [off_x_m, write_idx * BLOCK_K],
379
+ x_buffer.index(write_idx % NUM_BUFFERS),
380
+ )
381
+ else:
382
+ gl.amd.gfx1250.tdm.async_gather(
383
+ x_desc,
384
+ offs_x_m,
385
+ write_idx * BLOCK_K,
386
+ x_buffer.index(write_idx % NUM_BUFFERS),
387
+ )
388
+ gl.amd.gfx1250.tdm.async_load(
389
+ w_scales_desc,
390
+ [off_w_n_scale, write_idx * PACKED_MX_BLOCK],
391
+ w_scales_buffer.index(write_idx % NUM_BUFFERS),
392
+ )
393
+ if is_x_microscaled:
394
+ if GatherIndx is None:
395
+ gl.amd.gfx1250.tdm.async_load(
396
+ x_scales_desc,
397
+ [off_x_m, write_idx * MX_SCALE_BLOCK_K],
398
+ x_scales_buffer.index(write_idx % NUM_BUFFERS),
399
+ )
400
+ else:
401
+ gl.amd.gfx1250.tdm.async_gather(
402
+ x_scales_desc,
403
+ offs_x_m,
404
+ write_idx * MX_SCALE_BLOCK_K,
405
+ x_scales_buffer.index(write_idx % NUM_BUFFERS),
406
+ )
407
+ write_idx += 1
408
+
409
+ num_k_iter = tl.cdiv(K, BLOCK_K)
410
+ acc = gl.zeros((BLOCK_M, BLOCK_N), dtype=gl.float32, layout=WMMA_LAYOUT)
411
+ for k in range(num_k_iter - (NUM_BUFFERS - 1)):
412
+
413
+ gl.amd.gfx1250.tdm.async_load(
414
+ w_desc,
415
+ [off_w_n, write_idx * PACKED_BLOCK_K_W],
416
+ w_buffer.index(write_idx % NUM_BUFFERS),
417
+ )
418
+ if GatherIndx is None:
419
+ gl.amd.gfx1250.tdm.async_load(
420
+ x_desc,
421
+ [off_x_m, write_idx * BLOCK_K],
422
+ x_buffer.index(write_idx % NUM_BUFFERS),
423
+ )
424
+ else:
425
+ gl.amd.gfx1250.tdm.async_gather(
426
+ x_desc,
427
+ offs_x_m,
428
+ write_idx * BLOCK_K,
429
+ x_buffer.index(write_idx % NUM_BUFFERS),
430
+ )
431
+ gl.amd.gfx1250.tdm.async_load(
432
+ w_scales_desc,
433
+ [off_w_n_scale, write_idx * PACKED_MX_BLOCK],
434
+ w_scales_buffer.index(write_idx % NUM_BUFFERS),
435
+ )
436
+ if is_x_microscaled:
437
+ if GatherIndx is None:
438
+ gl.amd.gfx1250.tdm.async_load(
439
+ x_scales_desc,
440
+ [off_x_m, write_idx * MX_SCALE_BLOCK_K],
441
+ x_scales_buffer.index(write_idx % NUM_BUFFERS),
442
+ )
443
+ else:
444
+ gl.amd.gfx1250.tdm.async_gather(
445
+ x_scales_desc,
446
+ offs_x_m,
447
+ write_idx * MX_SCALE_BLOCK_K,
448
+ x_scales_buffer.index(write_idx % NUM_BUFFERS),
449
+ )
450
+ write_idx += 1
451
+
452
+ gl.amd.gfx1250.tdm.async_wait(NUM_BUFFERS * NUM_TDM_OPS - 1)
453
+ cur_w = (
454
+ w_buffer.index(read_idx % NUM_BUFFERS)
455
+ .permute((1, 0))
456
+ .load(layout=DOT_LAYOUT_W)
457
+ )
458
+
459
+ gl.amd.gfx1250.tdm.async_wait((NUM_BUFFERS - 1) * NUM_TDM_OPS)
460
+ cur_x = x_buffer.index(read_idx % NUM_BUFFERS).load(layout=DOT_LAYOUT_X)
461
+ w_scales_buffer_slice = w_scales_buffer.index(read_idx % NUM_BUFFERS)
462
+ if SWIZZLE_MX_SCALE == "GFX1250_SCALE":
463
+ w_scales_buffer_slice = unswizzle_mx_scale_gfx1250(
464
+ w_scales_buffer_slice,
465
+ BLOCK_N,
466
+ MX_SCALE_BLOCK_K,
467
+ PRESHUFFLE_FACTOR,
468
+ SCALE_KWIDTH,
469
+ )
470
+ cur_w_scales = w_scales_buffer_slice.load(layout=DOT_LAYOUT_W_SCALES)
471
+ if is_x_microscaled:
472
+ cur_x_scales = x_scales_buffer.index(read_idx % NUM_BUFFERS).load(
473
+ layout=DOT_LAYOUT_X_SCALES
474
+ )
475
+
476
+ read_idx += 1
477
+
478
+ if is_x_microscaled:
479
+ acc = gl.amd.gfx1250.wmma_scaled(
480
+ cur_x, cur_x_scales, "e4m3", cur_w, cur_w_scales, "e2m1", acc
481
+ )
482
+ else:
483
+ acc = gl.amd.gfx1250.wmma_scaled(
484
+ cur_x, 0, "e4m3", cur_w, cur_w_scales, "e2m1", acc
485
+ )
486
+
487
+ # bias
488
+ if B is not None:
489
+ BPtrs = B + expt_id * stride_b_e
490
+ SHARED_LAYOUT_BIAS: gl.constexpr = gl.SwizzledSharedLayout(1, 1, 1, [1, 0])
491
+ bias_desc = gl.amd.gfx1250.tdm.make_tensor_descriptor(
492
+ base=BPtrs,
493
+ shape=(1, N),
494
+ strides=(N, 1),
495
+ block_shape=(1, BLOCK_N),
496
+ layout=SHARED_LAYOUT_BIAS,
497
+ )
498
+ bias_buffer = gl.allocate_shared_memory(
499
+ bias_desc.dtype, shape=[1, BLOCK_N], layout=bias_desc.layout
500
+ )
501
+ gl.amd.gfx1250.tdm.async_load(
502
+ bias_desc,
503
+ [0, pid_n * BLOCK_N],
504
+ bias_buffer,
505
+ )
506
+ TDM_BIAS_WAIT: gl.constexpr = 1
507
+ else:
508
+ TDM_BIAS_WAIT: gl.constexpr = 0
509
+
510
+ # Epilogue: drain remaining pipeline stages (no new TDM loads).
511
+ # The first NUM_BUFFERS-1 iterations still use the pre-load / WMMA pattern.
512
+ for k_ep in gl.static_range(NUM_BUFFERS - 1):
513
+
514
+ gl.amd.gfx1250.tdm.async_wait(
515
+ (NUM_BUFFERS - 1 - k_ep) * NUM_TDM_OPS - 1 + TDM_BIAS_WAIT
516
+ )
517
+
518
+ cur_w = (
519
+ w_buffer.index(read_idx % NUM_BUFFERS)
520
+ .permute((1, 0))
521
+ .load(layout=DOT_LAYOUT_W)
522
+ )
523
+
524
+ gl.amd.gfx1250.tdm.async_wait(
525
+ (NUM_BUFFERS - 2 - k_ep) * NUM_TDM_OPS + TDM_BIAS_WAIT
526
+ )
527
+
528
+ cur_x = x_buffer.index(read_idx % NUM_BUFFERS).load(layout=DOT_LAYOUT_X)
529
+ w_scales_buffer_slice = w_scales_buffer.index(read_idx % NUM_BUFFERS)
530
+ if SWIZZLE_MX_SCALE == "GFX1250_SCALE":
531
+ w_scales_buffer_slice = unswizzle_mx_scale_gfx1250(
532
+ w_scales_buffer_slice,
533
+ BLOCK_N,
534
+ MX_SCALE_BLOCK_K,
535
+ PRESHUFFLE_FACTOR,
536
+ SCALE_KWIDTH,
537
+ )
538
+ cur_w_scales = w_scales_buffer_slice.load(layout=DOT_LAYOUT_W_SCALES)
539
+ if is_x_microscaled:
540
+ cur_x_scales = x_scales_buffer.index(read_idx % NUM_BUFFERS).load(
541
+ layout=DOT_LAYOUT_X_SCALES
542
+ )
543
+
544
+ read_idx += 1
545
+
546
+ if is_x_microscaled:
547
+ acc = gl.amd.gfx1250.wmma_scaled(
548
+ cur_x, cur_x_scales, "e4m3", cur_w, cur_w_scales, "e2m1", acc
549
+ )
550
+ else:
551
+ acc = gl.amd.gfx1250.wmma_scaled(
552
+ cur_x, 0, "e4m3", cur_w, cur_w_scales, "e2m1", acc
553
+ )
554
+
555
+ # scalar fp8 scale
556
+ if X_static_scale is not None:
557
+ acc = acc * gl.load(X_static_scale)
558
+
559
+ if B is not None:
560
+ gl.amd.gfx1250.tdm.async_wait(0)
561
+ bias = bias_buffer.reshape((BLOCK_N,)).load(
562
+ layout=gl.SliceLayout(0, WMMA_LAYOUT)
563
+ )
564
+ acc = acc + bias[None, :]
565
+
566
+ if APPLY_SWIGLU:
567
+ out = _swiglu(acc, alpha, limit, ADD_RESIDUAL=SWIGLU_ADD_RESIDUAL)
568
+ tl.static_assert(
569
+ out.shape[1] == OUT_BLOCK_N,
570
+ f"Activation fn out.shape[1] ({out.shape[1]}) doesn't match computed OUT_BLOCK_N ({OUT_BLOCK_N})",
571
+ )
572
+ else:
573
+ tl.static_assert(
574
+ ACTIVATION_REDUCTION_N == 1,
575
+ "Activation reduction must be 1 if no activation fn is provided",
576
+ )
577
+ out = acc
578
+
579
+ if Gammas is not None:
580
+ offs_m = BLOCK_M * block_id + gl.arange(0, BLOCK_M)
581
+ mask_m = offs_m < M
582
+ gammas = gl.amd.gfx1250.buffer_load(
583
+ Gammas + start_m, offs_m, mask=mask_m, other=0.0
584
+ )
585
+ out *= gammas[:, None]
586
+
587
+ # quant
588
+ if Quant_static_scale is not None:
589
+ out = _compute_static_fp8_quant(out, gl.load(Quant_static_scale))
590
+ else:
591
+ out = out.to(tl.bfloat16)
592
+
593
+ # TDM Store: accumulator → shared memory → global memory
594
+ Y += start_m * stride_y_m
595
+ y_buffer = gl.allocate_shared_memory(
596
+ Y.type.element_ty,
597
+ shape=[BLOCK_M, OUT_BLOCK_N],
598
+ layout=SHARED_LAYOUT_Y,
599
+ )
600
+ y_desc = gl.amd.gfx1250.tdm.make_tensor_descriptor(
601
+ base=Y,
602
+ shape=(M, yN),
603
+ strides=(stride_y_m, stride_y_n),
604
+ block_shape=(BLOCK_M, OUT_BLOCK_N),
605
+ layout=SHARED_LAYOUT_Y,
606
+ )
607
+ y_buffer.store(out)
608
+ gl.amd.gfx1250.tdm.async_store(
609
+ y_desc, [block_id * BLOCK_M, pid_n * OUT_BLOCK_N], y_buffer
610
+ )
611
+ gl.amd.gfx1250.tdm.async_wait(0)
612
+
613
+
614
+ @gluon.jit(launch_metadata=matmul_launch_metadata)
615
+ def _moe_gemm_a8w4_prefill(
616
+ Y,
617
+ stride_y_m,
618
+ stride_y_n,
619
+ X,
620
+ stride_x_m,
621
+ stride_x_k,
622
+ XMxScale,
623
+ stride_x_mx_m,
624
+ stride_x_mx_k,
625
+ W,
626
+ stride_w_e,
627
+ stride_w_n,
628
+ stride_w_k,
629
+ WMxScale,
630
+ stride_w_mx_e,
631
+ stride_w_mx_n,
632
+ stride_w_mx_k,
633
+ X_static_scale,
634
+ Quant_static_scale,
635
+ B,
636
+ stride_b_e, # Bias
637
+ Gammas,
638
+ num_tokens,
639
+ N,
640
+ K, # shapes
641
+ # expt data
642
+ GatherIndx,
643
+ ExptHist,
644
+ ExptOffs,
645
+ ExptOffsSum,
646
+ ExptData,
647
+ # true grid size
648
+ grid_m,
649
+ grid_n,
650
+ # fused activation function
651
+ APPLY_SWIGLU: gl.constexpr,
652
+ alpha,
653
+ limit,
654
+ ACTIVATION_REDUCTION_N: gl.constexpr,
655
+ SWIGLU_ADD_RESIDUAL: gl.constexpr,
656
+ # MoE config
657
+ N_EXPTS_ACT: gl.constexpr,
658
+ # optimization config
659
+ BLOCK_M: gl.constexpr,
660
+ BLOCK_N: gl.constexpr,
661
+ BLOCK_K: gl.constexpr,
662
+ XCD_SWIZZLE: gl.constexpr,
663
+ NUM_BUFFERS: gl.constexpr,
664
+ # One of ["GFX1250", None]
665
+ SWIZZLE_MX_SCALE: gl.constexpr,
666
+ MASK_K_LIMIT: gl.constexpr,
667
+ W_CACHE_MODIFIER: gl.constexpr,
668
+ num_warps: gl.constexpr,
669
+ UPCAST_INDICES: gl.constexpr = False,
670
+ ):
671
+
672
+ is_x_microscaled: gl.constexpr = XMxScale is not None
673
+ MX_PACK_DIVISOR: gl.constexpr = 32
674
+ NUM_TDM_OPS: gl.constexpr = 4 if is_x_microscaled else 3
675
+ w_type: gl.constexpr = W.dtype.element_ty
676
+ gl.static_assert(w_type == gl.uint8, "mx_weight_ptr must be uint8 or fp8")
677
+ gl.static_assert(
678
+ WMxScale.dtype.element_ty == gl.uint8, "mx_scale_ptr must be uint8"
679
+ )
680
+ gl.static_assert(
681
+ BLOCK_K % MX_PACK_DIVISOR == 0, "BLOCK_K must be a multiple of MX_PACK_DIVISOR"
682
+ )
683
+ x_type: gl.constexpr = X.dtype.element_ty
684
+ if is_x_microscaled:
685
+ gl.static_assert(x_type == gl.float8e4nv, "mx_act_ptr must be float8e4nv")
686
+ gl.static_assert(
687
+ XMxScale.dtype.element_ty == gl.uint8, "mx_scale_ptr must be uint8"
688
+ )
689
+
690
+ OUT_BLOCK_N: tl.constexpr = BLOCK_N // ACTIVATION_REDUCTION_N
691
+ yN = N // ACTIVATION_REDUCTION_N
692
+
693
+ pid = gl.program_id(0)
694
+
695
+ index_type: tl.constexpr = gl.int64 if UPCAST_INDICES else gl.int32
696
+
697
+ if XCD_SWIZZLE != 1:
698
+ padding_m = grid_m - gl.load(ExptOffsSum)
699
+ unpadded_m = grid_m - padding_m
700
+ total_actual_tiles = unpadded_m * grid_n
701
+ if padding_m > 0 and pid >= total_actual_tiles:
702
+ return
703
+ pid = remap_xcd(pid, total_actual_tiles, XCD_SWIZZLE)
704
+ else:
705
+ unpadded_m = grid_m
706
+ pid_m, pid_n = pid_grid(pid, unpadded_m, grid_n, 1)
707
+ # unpack expert data
708
+ expt_data = gl.load(ExptData + pid_m)
709
+ if XCD_SWIZZLE == 1 and expt_data == -1:
710
+ return
711
+ expt_id = expt_data & 0x0000FFFF
712
+ block_id = expt_data >> 16
713
+ M = gl.load(ExptHist + expt_id)
714
+ start_m = gl.load(ExptOffs + expt_id)
715
+ expt_id, block_id = expt_id.to(index_type), block_id.to(index_type)
716
+ start_m = start_m.to(index_type)
717
+ pid_n = pid_n.to(index_type)
718
+
719
+ # A pointers
720
+ off_x_m = BLOCK_M * block_id
721
+ if GatherIndx is None:
722
+ X += start_m * stride_x_m
723
+ else:
724
+ if GatherIndx.dtype.element_ty == gl.uint16:
725
+ IDX_LAYOUT: gl.constexpr = gl.SliceLayout(
726
+ 0, gl.BlockedLayout([1, 16], [32, 1], [1, num_warps], [0, 1])
727
+ )
728
+ else:
729
+ gl.static_assert(
730
+ GatherIndx.dtype.element_ty == gl.int32,
731
+ "Gather index datatype should be uint16 or int32",
732
+ )
733
+ IDX_LAYOUT: gl.constexpr = gl.SliceLayout(
734
+ 0, gl.BlockedLayout([1, 8], [32, 1], [1, num_warps], [0, 1])
735
+ )
736
+ offs_x_m = BLOCK_M * block_id + gl.arange(0, BLOCK_M, layout=IDX_LAYOUT)
737
+ offs_x_m = offs_x_m % M
738
+ GatherIndx += start_m
739
+ offs_x_m = gl.load(GatherIndx + offs_x_m) // N_EXPTS_ACT
740
+
741
+ W_K_DIVISOR: gl.constexpr = 2
742
+ PACKED_BLOCK_K_W: gl.constexpr = BLOCK_K // W_K_DIVISOR
743
+ PACKED_BLOCK_N_W: gl.constexpr = BLOCK_N
744
+ MX_SCALE_BLOCK_K: gl.constexpr = BLOCK_K // MX_PACK_DIVISOR
745
+
746
+ WMxScale += expt_id * stride_w_mx_e
747
+ if SWIZZLE_MX_SCALE == "GFX1250_SCALE":
748
+ gl.static_assert(stride_w_mx_k is not None)
749
+ gl.static_assert(stride_w_mx_n is not None)
750
+ PRESHUFFLE_FACTOR: gl.constexpr = 32
751
+ PACKED_MX_BLOCK: gl.constexpr = MX_SCALE_BLOCK_K * PRESHUFFLE_FACTOR
752
+ SCALE_BLOCK_N: gl.constexpr = BLOCK_N // PRESHUFFLE_FACTOR
753
+ SCALE_KWIDTH: gl.constexpr = 8
754
+ else:
755
+ PRESHUFFLE_FACTOR: gl.constexpr = 1
756
+ PACKED_MX_BLOCK: gl.constexpr = MX_SCALE_BLOCK_K
757
+ SCALE_BLOCK_N: gl.constexpr = BLOCK_N
758
+
759
+ # B pointers
760
+ off_w_n_scale = pid_n * SCALE_BLOCK_N
761
+ off_w_n = pid_n * PACKED_BLOCK_N_W
762
+ W += expt_id * stride_w_e
763
+
764
+ SHARED_LAYOUT_X: gl.constexpr = gl.PaddedSharedLayout.with_identity_for(
765
+ [[BLOCK_K, 16]], [BLOCK_M, BLOCK_K], [1, 0]
766
+ )
767
+ if BLOCK_K <= 256:
768
+ SHARED_LAYOUT_W: gl.constexpr = gl.PaddedSharedLayout.with_identity_for(
769
+ [[256, 16]], [BLOCK_N, PACKED_BLOCK_K_W], [1, 0]
770
+ )
771
+ else:
772
+ SHARED_LAYOUT_W: gl.constexpr = gl.PaddedSharedLayout.with_identity_for(
773
+ [[PACKED_BLOCK_K_W, 16]], [BLOCK_N, PACKED_BLOCK_K_W], [1, 0]
774
+ )
775
+ SHARED_LAYOUT_W_SCALES: gl.constexpr = gl.PaddedSharedLayout.with_identity_for(
776
+ [[256, 16]], [SCALE_BLOCK_N, PACKED_MX_BLOCK], [1, 0]
777
+ )
778
+ if is_x_microscaled:
779
+ SHARED_LAYOUT_X_SCALES: gl.constexpr = gl.PaddedSharedLayout.with_identity_for(
780
+ [[256, 16]], [BLOCK_M, MX_SCALE_BLOCK_K], [1, 0]
781
+ )
782
+ if Quant_static_scale is not None:
783
+ SHARED_LAYOUT_Y: gl.constexpr = gl.PaddedSharedLayout.with_identity_for(
784
+ [[OUT_BLOCK_N, 16]], [BLOCK_M, OUT_BLOCK_N], [1, 0]
785
+ )
786
+ else:
787
+ SHARED_LAYOUT_Y: gl.constexpr = gl.PaddedSharedLayout.with_identity_for(
788
+ [[OUT_BLOCK_N, 8]], [BLOCK_M, OUT_BLOCK_N], [1, 0]
789
+ )
790
+
791
+ if GatherIndx is None:
792
+ x_desc = gl.amd.gfx1250.tdm.make_tensor_descriptor(
793
+ base=X,
794
+ shape=(M, K),
795
+ strides=(stride_x_m, stride_x_k),
796
+ block_shape=(BLOCK_M, BLOCK_K),
797
+ layout=SHARED_LAYOUT_X,
798
+ )
799
+ else:
800
+ x_desc = gl.amd.gfx1250.tdm.make_tensor_descriptor(
801
+ base=X,
802
+ shape=(num_tokens, K),
803
+ strides=(stride_x_m, stride_x_k),
804
+ block_shape=(BLOCK_M, BLOCK_K),
805
+ layout=SHARED_LAYOUT_X,
806
+ )
807
+ w_desc = gl.amd.gfx1250.tdm.make_tensor_descriptor(
808
+ base=W,
809
+ shape=(N, K // W_K_DIVISOR),
810
+ strides=(
811
+ stride_w_n,
812
+ stride_w_k,
813
+ ),
814
+ block_shape=(
815
+ BLOCK_N,
816
+ PACKED_BLOCK_K_W,
817
+ ),
818
+ layout=SHARED_LAYOUT_W,
819
+ )
820
+ w_scales_desc = gl.amd.gfx1250.tdm.make_tensor_descriptor(
821
+ base=WMxScale,
822
+ shape=(N // PRESHUFFLE_FACTOR, tl.cdiv(K, MX_PACK_DIVISOR) * PRESHUFFLE_FACTOR),
823
+ strides=(stride_w_mx_n, stride_w_mx_k),
824
+ block_shape=(SCALE_BLOCK_N, PACKED_MX_BLOCK),
825
+ layout=SHARED_LAYOUT_W_SCALES,
826
+ )
827
+
828
+ if is_x_microscaled:
829
+ if GatherIndx is None:
830
+ XMxScale += start_m * stride_x_mx_m
831
+ x_scales_desc = gl.amd.gfx1250.tdm.make_tensor_descriptor(
832
+ base=XMxScale,
833
+ shape=(M, tl.cdiv(K, MX_PACK_DIVISOR)),
834
+ strides=(stride_x_mx_m, stride_x_mx_k),
835
+ block_shape=(BLOCK_M, MX_SCALE_BLOCK_K),
836
+ layout=SHARED_LAYOUT_X_SCALES,
837
+ )
838
+ else:
839
+ x_scales_desc = gl.amd.gfx1250.tdm.make_tensor_descriptor(
840
+ base=XMxScale,
841
+ shape=(num_tokens, tl.cdiv(K, MX_PACK_DIVISOR)),
842
+ strides=(stride_x_mx_m, stride_x_mx_k),
843
+ block_shape=(BLOCK_M, MX_SCALE_BLOCK_K),
844
+ layout=SHARED_LAYOUT_X_SCALES,
845
+ )
846
+
847
+ WMMA_LAYOUT: gl.constexpr = gl.amd.AMDWMMALayout(
848
+ 3,
849
+ transposed=True,
850
+ warp_bases=[[0, 1], [1, 0]],
851
+ reg_bases=[],
852
+ instr_shape=[16, 16, 128],
853
+ )
854
+ WMMA_LAYOUT_PACKED: gl.constexpr = gl.amd.AMDWMMALayout(
855
+ 3,
856
+ transposed=True,
857
+ warp_bases=[[0, 1], [1, 0]],
858
+ reg_bases=[],
859
+ instr_shape=[16, 16, 64],
860
+ )
861
+ DOT_LAYOUT_X: gl.constexpr = gl.DotOperandLayout(0, WMMA_LAYOUT, k_width=16)
862
+ DOT_LAYOUT_W: gl.constexpr = gl.DotOperandLayout(1, WMMA_LAYOUT_PACKED, k_width=16)
863
+ DOT_LAYOUT_W_SCALES: gl.constexpr = gl.amd.gfx1250.get_wmma_scale_layout(
864
+ DOT_LAYOUT_W, [BLOCK_N, MX_SCALE_BLOCK_K]
865
+ )
866
+ if is_x_microscaled:
867
+ DOT_LAYOUT_X_SCALES: gl.constexpr = gl.amd.gfx1250.get_wmma_scale_layout(
868
+ DOT_LAYOUT_X, [BLOCK_M, MX_SCALE_BLOCK_K]
869
+ )
870
+
871
+ x_buffer = gl.allocate_shared_memory(
872
+ x_desc.dtype, shape=[NUM_BUFFERS] + x_desc.block_shape, layout=x_desc.layout
873
+ )
874
+ w_buffer = gl.allocate_shared_memory(
875
+ w_desc.dtype, shape=[NUM_BUFFERS] + w_desc.block_shape, layout=w_desc.layout
876
+ )
877
+ w_scales_buffer = gl.allocate_shared_memory(
878
+ w_scales_desc.dtype,
879
+ shape=[NUM_BUFFERS] + w_scales_desc.block_shape,
880
+ layout=w_scales_desc.layout,
881
+ )
882
+ if is_x_microscaled:
883
+ x_scales_buffer = gl.allocate_shared_memory(
884
+ x_scales_desc.dtype,
885
+ shape=[NUM_BUFFERS] + x_scales_desc.block_shape,
886
+ layout=x_scales_desc.layout,
887
+ )
888
+
889
+ read_idx = 0
890
+ write_idx = 0
891
+ for _ in gl.static_range(NUM_BUFFERS):
892
+ if GatherIndx is None:
893
+ gl.amd.gfx1250.tdm.async_load(
894
+ x_desc,
895
+ [off_x_m, write_idx * BLOCK_K],
896
+ x_buffer.index(write_idx % NUM_BUFFERS),
897
+ )
898
+ else:
899
+ gl.amd.gfx1250.tdm.async_gather(
900
+ x_desc,
901
+ offs_x_m,
902
+ write_idx * BLOCK_K,
903
+ x_buffer.index(write_idx % NUM_BUFFERS),
904
+ )
905
+ gl.amd.gfx1250.tdm.async_load(
906
+ w_desc,
907
+ [off_w_n, write_idx * PACKED_BLOCK_K_W],
908
+ w_buffer.index(write_idx % NUM_BUFFERS),
909
+ )
910
+ gl.amd.gfx1250.tdm.async_load(
911
+ w_scales_desc,
912
+ [off_w_n_scale, write_idx * PACKED_MX_BLOCK],
913
+ w_scales_buffer.index(write_idx % NUM_BUFFERS),
914
+ )
915
+ if is_x_microscaled:
916
+ if GatherIndx is None:
917
+ gl.amd.gfx1250.tdm.async_load(
918
+ x_scales_desc,
919
+ [off_x_m, write_idx * MX_SCALE_BLOCK_K],
920
+ x_scales_buffer.index(write_idx % NUM_BUFFERS),
921
+ )
922
+ else:
923
+ gl.amd.gfx1250.tdm.async_gather(
924
+ x_scales_desc,
925
+ offs_x_m,
926
+ write_idx * MX_SCALE_BLOCK_K,
927
+ x_scales_buffer.index(write_idx % NUM_BUFFERS),
928
+ )
929
+ write_idx += 1
930
+
931
+ num_k_iter = tl.cdiv(K, BLOCK_K)
932
+
933
+ # After TDM prologue there are NUM_BUFFERS*3 ops in-flight; waiting for
934
+ # (NUM_BUFFERS-1)*3 lets exactly one tile (tile 0) complete.
935
+ gl.amd.gfx1250.tdm.async_wait((NUM_BUFFERS - 1) * NUM_TDM_OPS)
936
+
937
+ # Register pre-load prologue: wait for tile 0 then read it into cur_x/cur_w/cur_w_scales.
938
+ cur_x = x_buffer.index(read_idx % NUM_BUFFERS).load(layout=DOT_LAYOUT_X)
939
+ cur_w = (
940
+ w_buffer.index(read_idx % NUM_BUFFERS).permute((1, 0)).load(layout=DOT_LAYOUT_W)
941
+ )
942
+ w_scales_buffer_slice = w_scales_buffer.index(read_idx % NUM_BUFFERS)
943
+ if SWIZZLE_MX_SCALE == "GFX1250_SCALE":
944
+ w_scales_buffer_slice = unswizzle_mx_scale_gfx1250(
945
+ w_scales_buffer_slice,
946
+ BLOCK_N,
947
+ MX_SCALE_BLOCK_K,
948
+ PRESHUFFLE_FACTOR,
949
+ SCALE_KWIDTH,
950
+ )
951
+ cur_w_scales = w_scales_buffer_slice.load(layout=DOT_LAYOUT_W_SCALES)
952
+ if is_x_microscaled:
953
+ cur_x_scales = x_scales_buffer.index(read_idx % NUM_BUFFERS).load(
954
+ layout=DOT_LAYOUT_X_SCALES
955
+ )
956
+ read_idx += 1
957
+
958
+ acc = gl.zeros((BLOCK_M, BLOCK_N), dtype=gl.float32, layout=WMMA_LAYOUT)
959
+ for k in range(num_k_iter - NUM_BUFFERS):
960
+ if is_x_microscaled:
961
+ acc = gl.amd.gfx1250.wmma_scaled(
962
+ cur_x, cur_x_scales, "e4m3", cur_w, cur_w_scales, "e2m1", acc
963
+ )
964
+ else:
965
+ acc = gl.amd.gfx1250.wmma_scaled(
966
+ cur_x, 0, "e4m3", cur_w, cur_w_scales, "e2m1", acc
967
+ )
968
+
969
+ if GatherIndx is None:
970
+ gl.amd.gfx1250.tdm.async_load(
971
+ x_desc,
972
+ [off_x_m, write_idx * BLOCK_K],
973
+ x_buffer.index(write_idx % NUM_BUFFERS),
974
+ )
975
+ else:
976
+ gl.amd.gfx1250.tdm.async_gather(
977
+ x_desc,
978
+ offs_x_m,
979
+ write_idx * BLOCK_K,
980
+ x_buffer.index(write_idx % NUM_BUFFERS),
981
+ )
982
+ gl.amd.gfx1250.tdm.async_load(
983
+ w_desc,
984
+ [off_w_n, write_idx * PACKED_BLOCK_K_W],
985
+ w_buffer.index(write_idx % NUM_BUFFERS),
986
+ )
987
+ gl.amd.gfx1250.tdm.async_load(
988
+ w_scales_desc,
989
+ [off_w_n_scale, write_idx * PACKED_MX_BLOCK],
990
+ w_scales_buffer.index(write_idx % NUM_BUFFERS),
991
+ )
992
+ if is_x_microscaled:
993
+ if GatherIndx is None:
994
+ gl.amd.gfx1250.tdm.async_load(
995
+ x_scales_desc,
996
+ [off_x_m, write_idx * MX_SCALE_BLOCK_K],
997
+ x_scales_buffer.index(write_idx % NUM_BUFFERS),
998
+ )
999
+ else:
1000
+ gl.amd.gfx1250.tdm.async_gather(
1001
+ x_scales_desc,
1002
+ offs_x_m,
1003
+ write_idx * MX_SCALE_BLOCK_K,
1004
+ x_scales_buffer.index(write_idx % NUM_BUFFERS),
1005
+ )
1006
+ write_idx += 1
1007
+
1008
+ gl.amd.gfx1250.tdm.async_wait((NUM_BUFFERS - 1) * NUM_TDM_OPS)
1009
+
1010
+ next_x = x_buffer.index(read_idx % NUM_BUFFERS).load(layout=DOT_LAYOUT_X)
1011
+ next_w = (
1012
+ w_buffer.index(read_idx % NUM_BUFFERS)
1013
+ .permute((1, 0))
1014
+ .load(layout=DOT_LAYOUT_W)
1015
+ )
1016
+ w_scales_buffer_slice = w_scales_buffer.index(read_idx % NUM_BUFFERS)
1017
+ if SWIZZLE_MX_SCALE == "GFX1250_SCALE":
1018
+ w_scales_buffer_slice = unswizzle_mx_scale_gfx1250(
1019
+ w_scales_buffer_slice,
1020
+ BLOCK_N,
1021
+ MX_SCALE_BLOCK_K,
1022
+ PRESHUFFLE_FACTOR,
1023
+ SCALE_KWIDTH,
1024
+ )
1025
+ next_w_scales = w_scales_buffer_slice.load(layout=DOT_LAYOUT_W_SCALES)
1026
+ if is_x_microscaled:
1027
+ next_x_scales = x_scales_buffer.index(read_idx % NUM_BUFFERS).load(
1028
+ layout=DOT_LAYOUT_X_SCALES
1029
+ )
1030
+
1031
+ cur_x = next_x
1032
+ cur_w = next_w
1033
+ cur_w_scales = next_w_scales
1034
+ if is_x_microscaled:
1035
+ cur_x_scales = next_x_scales
1036
+ read_idx += 1
1037
+
1038
+ # bias
1039
+ if B is not None:
1040
+ BPtrs = B + expt_id * stride_b_e
1041
+ SHARED_LAYOUT_BIAS: gl.constexpr = gl.SwizzledSharedLayout(1, 1, 1, [1, 0])
1042
+ bias_desc = gl.amd.gfx1250.tdm.make_tensor_descriptor(
1043
+ base=BPtrs,
1044
+ shape=(1, N),
1045
+ strides=(N, 1),
1046
+ block_shape=(1, BLOCK_N),
1047
+ layout=SHARED_LAYOUT_BIAS,
1048
+ )
1049
+ bias_buffer = gl.allocate_shared_memory(
1050
+ bias_desc.dtype, shape=[1, BLOCK_N], layout=bias_desc.layout
1051
+ )
1052
+ gl.amd.gfx1250.tdm.async_load(
1053
+ bias_desc,
1054
+ [0, pid_n * BLOCK_N],
1055
+ bias_buffer,
1056
+ )
1057
+ TDM_BIAS_WAIT: gl.constexpr = 1
1058
+ else:
1059
+ TDM_BIAS_WAIT: gl.constexpr = 0
1060
+
1061
+ # Epilogue: drain remaining pipeline stages (no new TDM loads).
1062
+ # The first NUM_BUFFERS-1 iterations still use the pre-load / WMMA pattern.
1063
+ for k_ep in gl.static_range(NUM_BUFFERS - 1):
1064
+ if is_x_microscaled:
1065
+ acc = gl.amd.gfx1250.wmma_scaled(
1066
+ cur_x, cur_x_scales, "e4m3", cur_w, cur_w_scales, "e2m1", acc
1067
+ )
1068
+ else:
1069
+ acc = gl.amd.gfx1250.wmma_scaled(
1070
+ cur_x, 0, "e4m3", cur_w, cur_w_scales, "e2m1", acc
1071
+ )
1072
+
1073
+ gl.amd.gfx1250.tdm.async_wait(
1074
+ (NUM_BUFFERS - 2 - k_ep) * NUM_TDM_OPS + TDM_BIAS_WAIT
1075
+ )
1076
+
1077
+ next_x = x_buffer.index(read_idx % NUM_BUFFERS).load(layout=DOT_LAYOUT_X)
1078
+ next_w = (
1079
+ w_buffer.index(read_idx % NUM_BUFFERS)
1080
+ .permute((1, 0))
1081
+ .load(layout=DOT_LAYOUT_W)
1082
+ )
1083
+ w_scales_buffer_slice = w_scales_buffer.index(read_idx % NUM_BUFFERS)
1084
+ if SWIZZLE_MX_SCALE == "GFX1250_SCALE":
1085
+ w_scales_buffer_slice = unswizzle_mx_scale_gfx1250(
1086
+ w_scales_buffer_slice,
1087
+ BLOCK_N,
1088
+ MX_SCALE_BLOCK_K,
1089
+ PRESHUFFLE_FACTOR,
1090
+ SCALE_KWIDTH,
1091
+ )
1092
+ next_w_scales = w_scales_buffer_slice.load(layout=DOT_LAYOUT_W_SCALES)
1093
+ if is_x_microscaled:
1094
+ next_x_scales = x_scales_buffer.index(read_idx % NUM_BUFFERS).load(
1095
+ layout=DOT_LAYOUT_X_SCALES
1096
+ )
1097
+
1098
+ cur_x = next_x
1099
+ cur_w = next_w
1100
+ cur_w_scales = next_w_scales
1101
+ if is_x_microscaled:
1102
+ cur_x_scales = next_x_scales
1103
+ read_idx += 1
1104
+
1105
+ if is_x_microscaled:
1106
+ acc = gl.amd.gfx1250.wmma_scaled(
1107
+ cur_x, cur_x_scales, "e4m3", cur_w, cur_w_scales, "e2m1", acc
1108
+ )
1109
+ else:
1110
+ acc = gl.amd.gfx1250.wmma_scaled(
1111
+ cur_x, 0, "e4m3", cur_w, cur_w_scales, "e2m1", acc
1112
+ )
1113
+
1114
+ # scalar fp8 scale
1115
+ if X_static_scale is not None:
1116
+ acc = acc * gl.load(X_static_scale)
1117
+
1118
+ if B is not None:
1119
+ gl.amd.gfx1250.tdm.async_wait(0)
1120
+ bias = bias_buffer.reshape((BLOCK_N,)).load(
1121
+ layout=gl.SliceLayout(0, WMMA_LAYOUT)
1122
+ )
1123
+ acc = acc + bias[None, :]
1124
+
1125
+ if APPLY_SWIGLU:
1126
+ out = _swiglu(acc, alpha, limit, ADD_RESIDUAL=SWIGLU_ADD_RESIDUAL)
1127
+ tl.static_assert(
1128
+ out.shape[1] == OUT_BLOCK_N,
1129
+ f"Activation fn out.shape[1] ({out.shape[1]}) doesn't match computed OUT_BLOCK_N ({OUT_BLOCK_N})",
1130
+ )
1131
+ else:
1132
+ tl.static_assert(
1133
+ ACTIVATION_REDUCTION_N == 1,
1134
+ "Activation reduction must be 1 if no activation fn is provided",
1135
+ )
1136
+ out = acc
1137
+
1138
+ if Gammas is not None:
1139
+ offs_m = BLOCK_M * block_id + gl.arange(0, BLOCK_M)
1140
+ mask_m = offs_m < M
1141
+ gammas = gl.amd.gfx1250.buffer_load(
1142
+ Gammas + start_m, offs_m, mask=mask_m, other=0.0
1143
+ )
1144
+ out *= gammas[:, None]
1145
+
1146
+ # quant
1147
+ if Quant_static_scale is not None:
1148
+ out = _compute_static_fp8_quant(out, gl.load(Quant_static_scale))
1149
+ else:
1150
+ out = out.to(tl.bfloat16)
1151
+
1152
+ # TDM Store: accumulator → shared memory → global memory
1153
+ Y += start_m * stride_y_m
1154
+ y_buffer = gl.allocate_shared_memory(
1155
+ Y.type.element_ty,
1156
+ shape=[BLOCK_M, OUT_BLOCK_N],
1157
+ layout=SHARED_LAYOUT_Y,
1158
+ )
1159
+ y_desc = gl.amd.gfx1250.tdm.make_tensor_descriptor(
1160
+ base=Y,
1161
+ shape=(M, yN),
1162
+ strides=(stride_y_m, stride_y_n),
1163
+ block_shape=(BLOCK_M, OUT_BLOCK_N),
1164
+ layout=SHARED_LAYOUT_Y,
1165
+ )
1166
+ y_buffer.store(out)
1167
+ gl.amd.gfx1250.tdm.async_store(
1168
+ y_desc, [block_id * BLOCK_M, pid_n * OUT_BLOCK_N], y_buffer
1169
+ )
1170
+ gl.amd.gfx1250.tdm.async_wait(0)
build/torch-rocm/_gluon_kernels/gfx1250/norm/__init__.py ADDED
File without changes
build/torch-rocm/_gluon_kernels/gfx1250/norm/fused_rmsnorm_add.py ADDED
@@ -0,0 +1,122 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (C) 2024-2026, Advanced Micro Devices, Inc. All rights reserved.
3
+
4
+ # RMSNorm + residual add.
5
+ from triton.experimental import gluon
6
+ from triton.experimental.gluon import language as gl
7
+
8
+
9
+ @gluon.jit
10
+ def _rmsnorm_op(row, weights, n_cols, epsilon):
11
+ row_norm = row * row
12
+ row_norm = gl.sum(row_norm, axis=-1, keep_dims=True)
13
+ norm_factor = gl.rsqrt((row_norm / n_cols) + epsilon)
14
+ return row * norm_factor * weights
15
+
16
+
17
+ @gluon.jit
18
+ def _gluon_fused_rms_kernel(
19
+ x1_ptr,
20
+ w1_ptr,
21
+ res1_ptr,
22
+ out1_ptr,
23
+ out_res1_ptr,
24
+ eps1,
25
+ M,
26
+ N,
27
+ x1_stride_m,
28
+ res1_stride_m,
29
+ out1_stride_m,
30
+ out_res1_stride_m,
31
+ BLOCK_SIZE_M: gl.constexpr,
32
+ BLOCK_SIZE_N: gl.constexpr,
33
+ FIRST_INPUT_RES: gl.constexpr,
34
+ ):
35
+ start_pid = gl.program_id(0)
36
+
37
+ gLayout2D: gl.constexpr = gl.BlockedLayout([1, 8], [1, 32], [1, 4], [1, 0])
38
+ gLayoutN: gl.constexpr = gl.SliceLayout(0, gLayout2D)
39
+ sharedLayout2D: gl.constexpr = gl.SwizzledSharedLayout(1, 1, 1, order=[1, 0])
40
+ sharedLayoutN: gl.constexpr = gl.SwizzledSharedLayout(1, 1, 1, order=[0])
41
+
42
+ # descriptors + smem for first input and its weight
43
+ x1_desc = gl.amd.gfx1250.tdm.make_tensor_descriptor(
44
+ x1_ptr, [M, N], [x1_stride_m, 1], [BLOCK_SIZE_M, BLOCK_SIZE_N], sharedLayout2D
45
+ )
46
+ w1_desc = gl.amd.gfx1250.tdm.make_tensor_descriptor(
47
+ w1_ptr, [N], [1], [BLOCK_SIZE_N], sharedLayoutN
48
+ )
49
+ smemX1 = gl.allocate_shared_memory(
50
+ x1_ptr.dtype.element_ty, [BLOCK_SIZE_M, BLOCK_SIZE_N], sharedLayout2D
51
+ )
52
+ smemW1 = gl.allocate_shared_memory(
53
+ w1_ptr.dtype.element_ty, [BLOCK_SIZE_N], sharedLayoutN
54
+ )
55
+
56
+ # x1 load issued first for early latency hiding
57
+ gl.amd.gfx1250.tdm.async_load(x1_desc, [start_pid * BLOCK_SIZE_M, 0], smemX1)
58
+
59
+ # optional residual input
60
+ if FIRST_INPUT_RES:
61
+ res1_desc = gl.amd.gfx1250.tdm.make_tensor_descriptor(
62
+ res1_ptr,
63
+ [M, N],
64
+ [res1_stride_m, 1],
65
+ [BLOCK_SIZE_M, BLOCK_SIZE_N],
66
+ sharedLayout2D,
67
+ )
68
+ smemRes1 = gl.allocate_shared_memory(
69
+ res1_ptr.dtype.element_ty, [BLOCK_SIZE_M, BLOCK_SIZE_N], sharedLayout2D
70
+ )
71
+ gl.amd.gfx1250.tdm.async_load(
72
+ res1_desc, [start_pid * BLOCK_SIZE_M, 0], smemRes1
73
+ )
74
+ out_res1_desc = gl.amd.gfx1250.tdm.make_tensor_descriptor(
75
+ out_res1_ptr,
76
+ [M, N],
77
+ [out_res1_stride_m, 1],
78
+ [BLOCK_SIZE_M, BLOCK_SIZE_N],
79
+ sharedLayout2D,
80
+ )
81
+ smemOutRes1 = gl.allocate_shared_memory(
82
+ out_res1_ptr.dtype.element_ty, [BLOCK_SIZE_M, BLOCK_SIZE_N], sharedLayout2D
83
+ )
84
+
85
+ gl.amd.gfx1250.tdm.async_load(w1_desc, [0], smemW1)
86
+
87
+ # output descriptor + smem (static alloc; placement before wait is free)
88
+ out1_desc = gl.amd.gfx1250.tdm.make_tensor_descriptor(
89
+ out1_ptr,
90
+ [M, N],
91
+ [out1_stride_m, 1],
92
+ [BLOCK_SIZE_M, BLOCK_SIZE_N],
93
+ sharedLayout2D,
94
+ )
95
+ smemOut1 = gl.allocate_shared_memory(
96
+ out1_ptr.dtype.element_ty, [BLOCK_SIZE_M, BLOCK_SIZE_N], sharedLayout2D
97
+ )
98
+
99
+ gl.amd.gfx1250.tdm.async_wait(1)
100
+
101
+ x1 = smemX1.load(gLayout2D).to(gl.float32)
102
+
103
+ if FIRST_INPUT_RES:
104
+ res1_loaded = smemRes1.load(gLayout2D).to(gl.float32)
105
+ x1 = x1 + res1_loaded
106
+ smemOutRes1.store(x1.to(out_res1_ptr.dtype.element_ty))
107
+ gl.amd.gfx1250.tdm.async_store(
108
+ out_res1_desc, [start_pid * BLOCK_SIZE_M, 0], smemOutRes1
109
+ )
110
+ gl.amd.gfx1250.tdm.async_wait(1)
111
+ else:
112
+ gl.amd.gfx1250.tdm.async_wait(0)
113
+
114
+ w1 = smemW1.load(gLayoutN).to(gl.float32)
115
+ w1 = w1.reshape(1, BLOCK_SIZE_N)
116
+ w1 = gl.convert_layout(w1, gLayout2D)
117
+ norm1 = _rmsnorm_op(x1, w1, N, eps1)
118
+
119
+ smemOut1.store(norm1.to(out1_ptr.dtype.element_ty))
120
+ gl.amd.gfx1250.tdm.async_store(out1_desc, [start_pid * BLOCK_SIZE_M, 0], smemOut1)
121
+
122
+ gl.amd.gfx1250.tdm.async_wait(0)
build/torch-rocm/_gluon_kernels/gfx942/__init__.py ADDED
File without changes
build/torch-rocm/_gluon_kernels/gfx942/moe/__init__.py ADDED
File without changes
build/torch-rocm/_gluon_kernels/gfx942/moe/moe_op_gemm_int8_smoothquant.py ADDED
@@ -0,0 +1,273 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import triton
2
+ from triton.experimental import gluon
3
+ from triton.experimental.gluon import language as gl
4
+
5
+
6
+ @triton.heuristics(
7
+ {
8
+ "UNROLL_TIMES": lambda args: triton.cdiv(args["K"], args["BLOCK_K"]),
9
+ }
10
+ )
11
+ @gluon.jit
12
+ def _gluon_moe_gemm_int8_smoothquant(
13
+ Y,
14
+ stride_y_k,
15
+ stride_y_m,
16
+ stride_y_n,
17
+ X,
18
+ stride_x_m,
19
+ stride_x_k,
20
+ XScale,
21
+ stride_x_scale,
22
+ W,
23
+ stride_w_e,
24
+ stride_w_k,
25
+ stride_w_n,
26
+ WScale,
27
+ stride_w_scale_e,
28
+ stride_w_scale_n,
29
+ B,
30
+ stride_b_e, # Bias
31
+ Gammas,
32
+ N,
33
+ K,
34
+ # expt data
35
+ GatherIndx,
36
+ ExptHist,
37
+ ExptOffs,
38
+ ExptOffsSum,
39
+ ExptData,
40
+ # true grid size
41
+ grid_m,
42
+ grid_n,
43
+ alpha,
44
+ limit,
45
+ ACTIVATION_REDUCTION_N: gl.constexpr,
46
+ APPLY_ACTIVATION: gl.constexpr,
47
+ SWIGLU_ADD_RESIDUAL: gl.constexpr,
48
+ # MoE config
49
+ N_EXPTS_ACT: gl.constexpr,
50
+ # optimization config
51
+ BLOCK_M: gl.constexpr,
52
+ BLOCK_N: gl.constexpr,
53
+ BLOCK_K: gl.constexpr,
54
+ GROUP_M: gl.constexpr,
55
+ EVEN_K: gl.constexpr,
56
+ MASK_K_LIMIT: gl.constexpr,
57
+ # Gluon-specific
58
+ UNROLL_TIMES: gl.constexpr,
59
+ num_warps: gl.constexpr,
60
+ ):
61
+ """
62
+ Gluon-optimized Int8 MoE GEMM with SmoothQuant for small K dimensions.
63
+
64
+ Key optimizations over the standard _moe_gemm_int8_smoothquant:
65
+ - Manual LICM: A matrix, x_scale, and gammas pre-loaded outside N loop
66
+ - K-dimension unrolling via gl.static_range eliminates loop overhead
67
+ - Explicit BlockedLayout + MFMA instructions for optimal register usage
68
+ - SUB_BLOCK_SIZE_N inner loop processes large BLOCK_N in 64-wide chunks
69
+ """
70
+ SUB_BLOCK_SIZE_N: gl.constexpr = 64
71
+
72
+ # -- Layouts --
73
+ # INT8 on CDNA3 uses v_mfma_i32_32x32x16_i8 instruction
74
+ blocked_a: gl.constexpr = gl.BlockedLayout(
75
+ size_per_thread=[1, 16],
76
+ threads_per_warp=[32, 2],
77
+ warps_per_cta=[num_warps, 1],
78
+ order=[1, 0],
79
+ )
80
+ blocked_b: gl.constexpr = gl.BlockedLayout(
81
+ size_per_thread=[16, 1],
82
+ threads_per_warp=[4, 16],
83
+ warps_per_cta=[1, num_warps],
84
+ order=[0, 1],
85
+ )
86
+ mfma_layout: gl.constexpr = gl.amd.AMDMFMALayout(
87
+ version=3,
88
+ instr_shape=[32, 32, 16],
89
+ transposed=True,
90
+ warps_per_cta=[num_warps, 1],
91
+ )
92
+ mfma_a_layout: gl.constexpr = gl.DotOperandLayout(
93
+ operand_index=0, parent=mfma_layout, k_width=16
94
+ )
95
+ mfma_b_layout: gl.constexpr = gl.DotOperandLayout(
96
+ operand_index=1, parent=mfma_layout, k_width=16
97
+ )
98
+ blocked_d: gl.constexpr = gl.BlockedLayout(
99
+ size_per_thread=[1, 8],
100
+ threads_per_warp=[16, 4],
101
+ warps_per_cta=[num_warps, 1],
102
+ order=[1, 0],
103
+ )
104
+
105
+ # -- PID mapping (new path style) --
106
+ pid = gl.program_id(axis=0)
107
+
108
+ pid_m = pid // grid_n
109
+ pid_n = pid % grid_n
110
+
111
+ # Unpack expert data
112
+ expt_data = gl.load(ExptData + pid_m)
113
+ if expt_data == -1:
114
+ return
115
+ expt_id = expt_data & 0x0000FFFF
116
+ block_id = expt_data >> 16
117
+ M = gl.load(ExptHist + expt_id)
118
+ start_m = gl.load(ExptOffs + expt_id)
119
+
120
+ # -- A row offsets --
121
+ offs_x_m_raw = block_id * BLOCK_M + gl.arange(
122
+ 0, BLOCK_M, layout=gl.SliceLayout(1, blocked_a)
123
+ )
124
+ offs_x_m = offs_x_m_raw % M
125
+ mask_m = offs_x_m_raw < M
126
+
127
+ if GatherIndx is not None:
128
+ # Indirect indexing via gather
129
+ offs_x_m = (
130
+ gl.amd.cdna3.buffer_load(
131
+ GatherIndx + start_m, offs_x_m, mask=mask_m, other=0
132
+ )
133
+ // N_EXPTS_ACT
134
+ )
135
+ else:
136
+ X += start_m * stride_x_m
137
+ XScale += start_m * stride_x_scale
138
+
139
+ offs_ak = gl.arange(0, BLOCK_K, layout=gl.SliceLayout(0, blocked_a))
140
+ offs_bk = gl.arange(0, BLOCK_K, layout=gl.SliceLayout(1, blocked_b))
141
+
142
+ # =====================================================
143
+ # LICM: Pre-load all A matrix K-blocks outside N loop
144
+ # =====================================================
145
+ a_converted = ()
146
+ for k in gl.static_range(UNROLL_TIMES):
147
+ if EVEN_K:
148
+ a = gl.amd.cdna3.buffer_load(
149
+ X + k * BLOCK_K * stride_x_k,
150
+ offs_x_m[:, None] * stride_x_m + offs_ak[None, :] * stride_x_k,
151
+ mask=mask_m[:, None],
152
+ other=0.0,
153
+ )
154
+ else:
155
+ a = gl.amd.cdna3.buffer_load(
156
+ X + k * BLOCK_K * stride_x_k,
157
+ offs_x_m[:, None] * stride_x_m + offs_ak[None, :] * stride_x_k,
158
+ mask=mask_m[:, None] & (offs_ak[None, :] < K - k * BLOCK_K),
159
+ other=0.0,
160
+ )
161
+ a_converted = a_converted + (gl.convert_layout(a, mfma_a_layout),)
162
+
163
+ # =====================================================
164
+ # LICM: Pre-load per-token x_scale outside N loop
165
+ # =====================================================
166
+ x_scale = gl.amd.cdna3.buffer_load(
167
+ XScale, offs_x_m * stride_x_scale, mask=mask_m, other=1.0
168
+ )
169
+ x_scale_converted = gl.convert_layout(x_scale, gl.SliceLayout(1, mfma_layout))
170
+
171
+ # =====================================================
172
+ # LICM: Pre-load gammas outside N loop
173
+ # =====================================================
174
+ if Gammas is not None:
175
+ offs_gamma = block_id * BLOCK_M + gl.arange(
176
+ 0, BLOCK_M, layout=gl.SliceLayout(1, mfma_layout)
177
+ )
178
+ gamma_mask = offs_gamma < M
179
+ gamma_vals = gl.amd.cdna3.buffer_load(
180
+ Gammas + start_m,
181
+ offs_gamma,
182
+ mask=gamma_mask,
183
+ other=0.0,
184
+ )
185
+
186
+ # =====================================================
187
+ # N-dimension loop (SUB_BLOCK_SIZE_N chunks)
188
+ # =====================================================
189
+ W_base = W + expt_id * stride_w_e
190
+
191
+ for n_start in range(0, BLOCK_N, SUB_BLOCK_SIZE_N):
192
+ offs_bn = (
193
+ pid_n * BLOCK_N
194
+ + n_start
195
+ + gl.arange(0, SUB_BLOCK_SIZE_N, layout=gl.SliceLayout(0, blocked_b))
196
+ ) % N
197
+
198
+ # Accumulator in int32 for int8 x int8
199
+ accumulator = gl.zeros(
200
+ (BLOCK_M, SUB_BLOCK_SIZE_N), dtype=gl.int32, layout=mfma_layout
201
+ )
202
+
203
+ # -- Load B and compute MFMA (unrolled K via static_range) --
204
+ for k in gl.static_range(UNROLL_TIMES):
205
+ if EVEN_K:
206
+ b = gl.amd.cdna3.buffer_load(
207
+ W_base + k * BLOCK_K * stride_w_k,
208
+ offs_bk[:, None] * stride_w_k + offs_bn[None, :] * stride_w_n,
209
+ )
210
+ else:
211
+ b = gl.amd.cdna3.buffer_load(
212
+ W_base + k * BLOCK_K * stride_w_k,
213
+ offs_bk[:, None] * stride_w_k + offs_bn[None, :] * stride_w_n,
214
+ mask=offs_bk[:, None] < K - k * BLOCK_K,
215
+ other=0.0,
216
+ )
217
+ b_converted = gl.convert_layout(b, mfma_b_layout)
218
+ accumulator = gl.amd.cdna3.mfma(a_converted[k], b_converted, accumulator)
219
+
220
+ # -- Apply SmoothQuant scales: acc_fp32 = acc_int32 * x_scale * w_scale --
221
+ # Load per-channel weight scale for this N sub-block
222
+ offs_wscale_n = (
223
+ pid_n * BLOCK_N
224
+ + n_start
225
+ + gl.arange(0, SUB_BLOCK_SIZE_N, layout=gl.SliceLayout(0, mfma_layout))
226
+ )
227
+ w_scale = gl.amd.cdna3.buffer_load(
228
+ WScale + expt_id * stride_w_scale_e,
229
+ offs_wscale_n * stride_w_scale_n,
230
+ mask=offs_wscale_n < N,
231
+ other=1.0,
232
+ )
233
+ w_scale_converted = gl.convert_layout(w_scale, gl.SliceLayout(0, mfma_layout))
234
+ acc_fp32 = (
235
+ accumulator.to(gl.float32)
236
+ * x_scale_converted[:, None]
237
+ * w_scale_converted[None, :]
238
+ )
239
+
240
+ # -- Bias --
241
+ if B is not None:
242
+ bias = gl.amd.cdna3.buffer_load(
243
+ B + expt_id * stride_b_e,
244
+ offs_wscale_n,
245
+ mask=offs_wscale_n < N,
246
+ other=0.0,
247
+ )
248
+ bias_converted = gl.convert_layout(bias, gl.SliceLayout(0, mfma_layout))
249
+ acc_fp32 = acc_fp32 + bias_converted[None, :]
250
+
251
+ # -- Apply gammas (pre-loaded outside N loop) --
252
+ if Gammas is not None:
253
+ acc_fp32 = acc_fp32 * gamma_vals[:, None]
254
+
255
+ # -- Write back --
256
+ offs_cn = (
257
+ pid_n * BLOCK_N
258
+ + n_start
259
+ + gl.arange(0, SUB_BLOCK_SIZE_N, layout=gl.SliceLayout(0, blocked_d))
260
+ )
261
+ offs_ym = block_id * BLOCK_M + gl.arange(
262
+ 0, BLOCK_M, layout=gl.SliceLayout(1, blocked_d)
263
+ )
264
+ ym_mask = offs_ym < M
265
+ cn_mask = offs_cn < N
266
+
267
+ out = gl.convert_layout(acc_fp32, blocked_d).to(Y.dtype.element_ty)
268
+ gl.amd.cdna3.buffer_store(
269
+ out,
270
+ Y + start_m * stride_y_m,
271
+ offs_ym[:, None] * stride_y_m + offs_cn[None, :] * stride_y_n,
272
+ mask=ym_mask[:, None] & cn_mask[None, :],
273
+ )
build/torch-rocm/_ops.py ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+
3
+ def get_backend() -> str:
4
+ """Detect the backend by inspecting torch."""
5
+ import torch
6
+
7
+ if hasattr(torch, "neuron"):
8
+ # Needs to be sorted before specific Torch builds, since Neuron
9
+ # extension can be loaded into e.g. CUDA Torch builds.
10
+ return "neuron"
11
+ elif torch.version.cuda is not None:
12
+ return "cuda"
13
+ elif torch.version.hip is not None:
14
+ return "rocm"
15
+ elif torch.backends.mps.is_available():
16
+ return "metal"
17
+ elif hasattr(torch.version, "xpu") and torch.version.xpu is not None:
18
+ return "xpu"
19
+ else:
20
+ return "cpu"
21
+
22
+
23
+ def _find_ops_name() -> str:
24
+ kernel_name = "aiter_kernels"
25
+ unique_id = "a7b6068"
26
+ backend = get_backend()
27
+ return f"_{kernel_name}_{backend}_{unique_id}"
28
+
29
+
30
+ _OPS_NAME = _find_ops_name()
31
+
32
+ ops = getattr(torch.ops, _OPS_NAME)
33
+
34
+ def add_op_namespace_prefix(op_name: str) -> str:
35
+ """
36
+ Prefix op by namespace.
37
+ """
38
+ return f"{_OPS_NAME}::{op_name}"
build/torch-rocm/_triton_kernels/__init__.py ADDED
File without changes
build/torch-rocm/_triton_kernels/activation.py ADDED
@@ -0,0 +1,317 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from .quant.quant import _mxfp4_quant_op
2
+ from .quant.fused_fp8_quant import _fp8_quant_op
3
+ import triton
4
+ import triton.language as tl
5
+
6
+
7
+ @triton.jit
8
+ def _silu_exp2(x):
9
+ return x / (1.0 + tl.exp2(-(x * 1.44269504089)))
10
+
11
+
12
+ @triton.jit
13
+ def _silu(x):
14
+ return _silu_exp2(x)
15
+
16
+
17
+ @triton.jit
18
+ def fused_silu_mul_kernel(
19
+ inp_ptr,
20
+ out_ptr,
21
+ n_rows,
22
+ n_cols,
23
+ row_stride_in,
24
+ col_stride_in,
25
+ row_stride_out,
26
+ col_stride_out,
27
+ BLOCK_M: tl.constexpr,
28
+ BLOCK_N: tl.constexpr,
29
+ ):
30
+ """
31
+ SiLU on the first half of the last dimension, multiply by the second half.
32
+ Each row has 2 * n_cols input elements; writes n_cols outputs.
33
+ 2D grid: axis 0 tiles rows (BLOCK_M), axis 1 tiles columns (BLOCK_N).
34
+ """
35
+ m_pid = tl.program_id(0)
36
+ n_pid = tl.program_id(1)
37
+ m_offs = tl.arange(0, BLOCK_M)
38
+ n_offs = tl.arange(0, BLOCK_N)
39
+ row_idx = m_pid * BLOCK_M + m_offs
40
+ col_idx = n_pid * BLOCK_N + n_offs
41
+
42
+ row_in = row_idx * row_stride_in
43
+ row_out = row_idx * row_stride_out
44
+
45
+ first_half_ptrs = inp_ptr + row_in[:, None] + col_idx[None, :] * col_stride_in
46
+ second_half_ptrs = (
47
+ inp_ptr + row_in[:, None] + (n_cols + col_idx)[None, :] * col_stride_in
48
+ )
49
+ out_ptrs = out_ptr + row_out[:, None] + col_idx[None, :] * col_stride_out
50
+
51
+ mask = (row_idx < n_rows)[:, None] & (col_idx < n_cols)[None, :]
52
+ a = tl.load(first_half_ptrs, mask=mask, other=0.0).to(tl.float32)
53
+ silu_a = _silu_exp2(a).to(inp_ptr.dtype.element_ty)
54
+ b = tl.load(second_half_ptrs, mask=mask, other=0.0)
55
+ o = (silu_a * b).to(out_ptr.dtype.element_ty)
56
+ tl.store(out_ptrs, o, mask=mask)
57
+
58
+
59
+ @triton.jit
60
+ def _tanh(x):
61
+ return 2 * tl.sigmoid(2 * x) - 1
62
+
63
+
64
+ @triton.jit
65
+ def _gelu(x):
66
+ M_SQRT1_2 = 0.70710678118654752440
67
+ ALPHA = M_SQRT1_2
68
+ return 0.5 * x * (1.0 + tl.erf(x * ALPHA))
69
+
70
+
71
+ @triton.jit
72
+ def _gelu_tanh(x):
73
+ M_SQRT2 = 1.41421356237309504880
74
+ M_2_SQRTPI = 1.12837916709551257390
75
+ BETA = M_SQRT2 * M_2_SQRTPI * 0.5
76
+ KAPPA = 0.044715
77
+ x_cube = x * x * x
78
+ inner = BETA * (x + KAPPA * x_cube)
79
+ return 0.5 * x * (1.0 + _tanh(inner))
80
+
81
+
82
+ @triton.jit
83
+ def _relu(x):
84
+ return tl.maximum(0.0, x)
85
+
86
+
87
+ def _get_activation_from_str(activation: str):
88
+ mapping = {
89
+ "gelu": _gelu,
90
+ "gelu_tanh": _gelu_tanh,
91
+ "silu": _silu,
92
+ "silu_exp2": _silu_exp2,
93
+ "relu": _relu,
94
+ }
95
+ return mapping[activation]
96
+
97
+
98
+ @triton.jit
99
+ def _apply_activation_from_str(x, activation: tl.constexpr):
100
+ if activation == "gelu":
101
+ return _gelu(x)
102
+ elif activation == "gelu_tanh":
103
+ return _gelu_tanh(x)
104
+ elif activation == "silu":
105
+ return _silu(x)
106
+ elif activation == "silu_exp2":
107
+ return _silu_exp2(x)
108
+ elif activation == "relu":
109
+ return _relu(x)
110
+ else:
111
+ return x # No activation if it is not recognized
112
+
113
+
114
+ @triton.heuristics(
115
+ {
116
+ "EVEN_M_N": lambda args: args["M"] % args["BLOCK_SIZE_M"] == 0
117
+ and args["N"] % (args["BLOCK_SIZE_N"] * args["NUM_ITER"]) == 0,
118
+ }
119
+ )
120
+ @triton.jit
121
+ def _act_mul_and_dynamic_mxfp4_quant_kernel(
122
+ x_ptr,
123
+ x_fp4_ptr,
124
+ bs_ptr,
125
+ stride_x_m_in,
126
+ stride_x_n_in,
127
+ stride_x_fp4_m_in,
128
+ stride_x_fp4_n_in,
129
+ stride_bs_m_in,
130
+ stride_bs_n_in,
131
+ M,
132
+ N,
133
+ BLOCK_SIZE_M: tl.constexpr,
134
+ BLOCK_SIZE_N: tl.constexpr,
135
+ NUM_ITER: tl.constexpr,
136
+ NUM_STAGES: tl.constexpr,
137
+ MXFP4_QUANT_BLOCK_SIZE: tl.constexpr,
138
+ EVEN_M_N: tl.constexpr,
139
+ SCALING_MODE: tl.constexpr,
140
+ ACTIVATION: tl.constexpr,
141
+ scaleN: tl.constexpr,
142
+ scaleM_pad: tl.constexpr,
143
+ scaleN_pad: tl.constexpr,
144
+ SHUFFLE: tl.constexpr,
145
+ ):
146
+ pid_m = tl.program_id(0)
147
+ start_n = tl.program_id(1) * NUM_ITER
148
+ # cast strides to int64, in case M*N > max int32
149
+ stride_x_m = tl.cast(stride_x_m_in, tl.int64)
150
+ stride_x_n = tl.cast(stride_x_n_in, tl.int64)
151
+ stride_x_fp4_m = tl.cast(stride_x_fp4_m_in, tl.int64)
152
+ stride_x_fp4_n = tl.cast(stride_x_fp4_n_in, tl.int64)
153
+ stride_bs_m = tl.cast(stride_bs_m_in, tl.int64)
154
+ stride_bs_n = tl.cast(stride_bs_n_in, tl.int64)
155
+
156
+ NUM_QUANT_BLOCKS: tl.constexpr = BLOCK_SIZE_N // MXFP4_QUANT_BLOCK_SIZE
157
+
158
+ for pid_n in tl.range(start_n, min(start_n + NUM_ITER, N), num_stages=NUM_STAGES):
159
+ x_offs_m = pid_m * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M)
160
+ x_offs_n = pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)
161
+ x_offs = x_offs_m[:, None] * stride_x_m + x_offs_n[None, :] * stride_x_n
162
+
163
+ if EVEN_M_N:
164
+ a = tl.load(x_ptr + x_offs, cache_modifier=".cg").to(tl.float32)
165
+ b = tl.load(x_ptr + x_offs + stride_x_n * N, cache_modifier=".cg").to(
166
+ tl.float32
167
+ )
168
+ else:
169
+ x_mask = (x_offs_m < M)[:, None] & (x_offs_n < N)[None, :]
170
+ a = tl.load(x_ptr + x_offs, mask=x_mask, cache_modifier=".cg").to(
171
+ tl.float32
172
+ )
173
+ # a and b can share the same mask
174
+ b = tl.load(
175
+ x_ptr + x_offs + stride_x_n * N, mask=x_mask, cache_modifier=".cg"
176
+ ).to(tl.float32)
177
+
178
+ x = _apply_activation_from_str(a, ACTIVATION) * b
179
+
180
+ out_tensor, bs_e8m0 = _mxfp4_quant_op(
181
+ x, BLOCK_SIZE_N, BLOCK_SIZE_M, MXFP4_QUANT_BLOCK_SIZE
182
+ )
183
+
184
+ out_offs_m = pid_m * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M)
185
+ out_offs_n = pid_n * BLOCK_SIZE_N // 2 + tl.arange(0, BLOCK_SIZE_N // 2)
186
+ out_offs = (
187
+ out_offs_m[:, None] * stride_x_fp4_m + out_offs_n[None, :] * stride_x_fp4_n
188
+ )
189
+
190
+ if EVEN_M_N:
191
+ tl.store(x_fp4_ptr + out_offs, out_tensor)
192
+ else:
193
+ out_mask = (out_offs_m < M)[:, None] & (out_offs_n < (N // 2))[None, :]
194
+ tl.store(x_fp4_ptr + out_offs, out_tensor, mask=out_mask)
195
+
196
+ bs_offs_m = pid_m * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M)
197
+ bs_offs_n = pid_n * NUM_QUANT_BLOCKS + tl.arange(0, NUM_QUANT_BLOCKS)
198
+ if SHUFFLE:
199
+ bs_offs_0 = bs_offs_m[:, None] // 32
200
+ bs_offs_1 = bs_offs_m[:, None] % 32
201
+ bs_offs_2 = bs_offs_1 % 16
202
+ bs_offs_1 = bs_offs_1 // 16
203
+ bs_offs_3 = bs_offs_n[None, :] // 8
204
+ bs_offs_4 = bs_offs_n[None, :] % 8
205
+ bs_offs_5 = bs_offs_4 % 4
206
+ bs_offs_4 = bs_offs_4 // 4
207
+ bs_offs = (
208
+ bs_offs_1
209
+ + bs_offs_4 * 2
210
+ + bs_offs_2 * 2 * 2
211
+ + bs_offs_5 * 2 * 2 * 16
212
+ + bs_offs_3 * 2 * 2 * 16 * 4
213
+ + bs_offs_0 * 2 * 16 * scaleN
214
+ )
215
+ bs_mask1 = (bs_offs_m < M)[:, None] & (bs_offs_n < scaleN)[None, :]
216
+ bs_mask = (bs_offs_m < scaleM_pad)[:, None] & (bs_offs_n < scaleN_pad)[
217
+ None, :
218
+ ]
219
+ bs_e8m0 = tl.where(bs_mask1, bs_e8m0, 127)
220
+ else:
221
+ bs_offs = (
222
+ bs_offs_m[:, None] * stride_bs_m + bs_offs_n[None, :] * stride_bs_n
223
+ )
224
+ bs_mask = (bs_offs_m < M)[:, None] & (bs_offs_n < scaleN)[None, :]
225
+ if EVEN_M_N:
226
+ tl.store(bs_ptr + bs_offs, bs_e8m0)
227
+ else:
228
+
229
+ tl.store(
230
+ bs_ptr + bs_offs,
231
+ bs_e8m0,
232
+ mask=bs_mask,
233
+ )
234
+
235
+
236
+ @triton.heuristics(
237
+ {
238
+ "EVEN_N": lambda args: args["N"] % args["BLOCK_SIZE_N"] == 0,
239
+ }
240
+ )
241
+ @triton.jit
242
+ def _act_mul_and_dynamic_fp8_group_quant_kernel(
243
+ x_ptr,
244
+ x_fp8_ptr,
245
+ x_bs_ptr,
246
+ stride_x_m_in,
247
+ stride_x_n_in,
248
+ stride_x_fp8_m_in,
249
+ stride_x_fp8_n_in,
250
+ stride_bs_m_in,
251
+ stride_bs_n_in,
252
+ N,
253
+ ACTIVATION: tl.constexpr,
254
+ scaleN: tl.constexpr,
255
+ BLOCK_SIZE_N: tl.constexpr,
256
+ QUANT_BLOCK_SIZE: tl.constexpr,
257
+ DTYPE_MAX: tl.constexpr,
258
+ DTYPE_MIN: tl.constexpr,
259
+ EVEN_N: tl.constexpr,
260
+ ):
261
+ pid_m = tl.program_id(0)
262
+ pid_n = tl.program_id(1)
263
+ # cast strides to int64, in case M*N > max int32
264
+ stride_x_m = tl.cast(stride_x_m_in, tl.int64)
265
+ stride_x_n = tl.cast(stride_x_n_in, tl.int64)
266
+ stride_x_fp8_m = tl.cast(stride_x_fp8_m_in, tl.int64)
267
+ stride_x_fp8_n = tl.cast(stride_x_fp8_n_in, tl.int64)
268
+ stride_bs_m = tl.cast(stride_bs_m_in, tl.int64)
269
+ stride_bs_n = tl.cast(stride_bs_n_in, tl.int64)
270
+ NUM_QUANT_BLOCKS: tl.constexpr = BLOCK_SIZE_N // QUANT_BLOCK_SIZE
271
+
272
+ x_offs_n = pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)
273
+ x_offs = pid_m * stride_x_m + x_offs_n * stride_x_n
274
+
275
+ if EVEN_N:
276
+ a = tl.load(x_ptr + x_offs, cache_modifier=".cg").to(tl.float32)
277
+ b = tl.load(x_ptr + x_offs + stride_x_n * N, cache_modifier=".cg").to(
278
+ tl.float32
279
+ )
280
+ else:
281
+ x_mask = x_offs_n < N
282
+ a = tl.load(x_ptr + x_offs, mask=x_mask, cache_modifier=".cg").to(tl.float32)
283
+ # a and b can share the same mask
284
+ b = tl.load(
285
+ x_ptr + x_offs + stride_x_n * N, mask=x_mask, cache_modifier=".cg"
286
+ ).to(tl.float32)
287
+
288
+ x = _apply_activation_from_str(a, ACTIVATION) * b
289
+
290
+ x_fp8, x_bs = _fp8_quant_op(
291
+ x, 1, BLOCK_SIZE_N, QUANT_BLOCK_SIZE, DTYPE_MAX, DTYPE_MIN
292
+ )
293
+ x_fp8 = tl.ravel(x_fp8)
294
+ x_bs = tl.ravel(x_bs)
295
+
296
+ out_offs_n = pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)
297
+ out_offs = pid_m * stride_x_fp8_m + out_offs_n * stride_x_fp8_n
298
+
299
+ if EVEN_N:
300
+ tl.store(x_fp8_ptr + out_offs, x_fp8.to(x_fp8_ptr.dtype.element_ty))
301
+ else:
302
+ out_mask = out_offs_n < N
303
+ tl.store(
304
+ x_fp8_ptr + out_offs, x_fp8.to(x_fp8_ptr.dtype.element_ty), mask=out_mask
305
+ )
306
+
307
+ bs_offs_n = pid_n * NUM_QUANT_BLOCKS + tl.arange(0, NUM_QUANT_BLOCKS)
308
+ bs_offs = pid_m * stride_bs_m + bs_offs_n * stride_bs_n
309
+ if EVEN_N:
310
+ tl.store(x_bs_ptr + bs_offs, x_bs.to(x_bs_ptr.dtype.element_ty))
311
+ else:
312
+ bs_mask = bs_offs_n < scaleN
313
+ tl.store(
314
+ x_bs_ptr + bs_offs,
315
+ x_bs.to(x_bs_ptr.dtype.element_ty),
316
+ mask=bs_mask,
317
+ )
build/torch-rocm/_triton_kernels/causal_conv1d.py ADDED
@@ -0,0 +1,631 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import triton
2
+ import triton.language as tl
3
+
4
+ PAD_SLOT_ID = -1
5
+
6
+
7
+ @triton.jit()
8
+ def _causal_conv1d_fwd_kernel( # continuous batching
9
+ # Pointers to matrices
10
+ x_ptr, # (dim, cu_seqlen) holding `batch` of actual sequences + padded sequences
11
+ w_ptr, # (dim, width)
12
+ bias_ptr,
13
+ initial_states_ptr, # conv_states_ptr
14
+ cache_indices_ptr, # conv_state_indices_ptr
15
+ has_initial_states_ptr,
16
+ query_start_loc_ptr,
17
+ o_ptr, # (dim, seqlen) - actually pointing to x_ptr
18
+ # Matrix dimensions
19
+ dim: tl.constexpr,
20
+ seqlen: tl.int32, # cu_seqlen
21
+ num_cache_lines: tl.constexpr, # added to support vLLM larger cache lines
22
+ # Strides
23
+ stride_x_seq: tl.constexpr, # stride to get to next sequence,
24
+ stride_x_dim: tl.constexpr, # stride to get to next feature-value,
25
+ stride_x_token: tl.constexpr, # stride to get to next token (same feature-index, same sequence-index)
26
+ stride_w_dim: tl.constexpr, # stride to get to next dim-axis value
27
+ stride_w_width: tl.constexpr, # stride to get to next width-axis value
28
+ stride_istate_seq: tl.constexpr,
29
+ stride_istate_dim: tl.constexpr,
30
+ stride_istate_token: tl.constexpr,
31
+ stride_o_seq: tl.constexpr,
32
+ stride_o_dim: tl.constexpr,
33
+ stride_o_token: tl.constexpr,
34
+ # others
35
+ pad_slot_id: tl.constexpr,
36
+ # Meta-parameters
37
+ HAS_BIAS: tl.constexpr,
38
+ KERNEL_WIDTH: tl.constexpr,
39
+ SILU_ACTIVATION: tl.constexpr,
40
+ HAS_INITIAL_STATES: tl.constexpr,
41
+ HAS_CACHE: tl.constexpr,
42
+ IS_CONTINUOUS_BATCHING: tl.constexpr,
43
+ USE_PAD_SLOT: tl.constexpr,
44
+ NP2_STATELEN: tl.constexpr,
45
+ BLOCK_M: tl.constexpr,
46
+ BLOCK_N: tl.constexpr,
47
+ ):
48
+ conv_states_ptr = initial_states_ptr
49
+ conv_state_indices_ptr = cache_indices_ptr
50
+ stride_conv_state_seq = stride_istate_seq
51
+ stride_conv_state_dim = stride_istate_dim
52
+ stride_conv_state_tok = stride_istate_token
53
+ state_len = (
54
+ KERNEL_WIDTH - 1
55
+ ) # can be passed via argument if it's not the same as this value
56
+
57
+ # one program handles one chunk in a single sequence
58
+ # rather than mixing sequences - to make updating initial_states across sequences efficiently
59
+
60
+ # single-sequence id
61
+ idx_seq = tl.program_id(0)
62
+ chunk_offset = tl.program_id(1)
63
+
64
+ # BLOCK_N elements along the feature-dimension (channel)
65
+ idx_feats = tl.program_id(2) * BLOCK_N + tl.arange(0, BLOCK_N)
66
+
67
+ if idx_seq == pad_slot_id:
68
+ return
69
+
70
+ sequence_start_index = tl.load(query_start_loc_ptr + idx_seq)
71
+ sequence_end_index = tl.load(query_start_loc_ptr + idx_seq + 1)
72
+ # find the actual sequence length
73
+ seqlen = sequence_end_index - sequence_start_index
74
+
75
+ token_offset = BLOCK_M * chunk_offset
76
+ segment_len = min(BLOCK_M, seqlen - token_offset)
77
+
78
+ if segment_len <= 0:
79
+ return
80
+
81
+ # base of the sequence
82
+ x_base = (
83
+ x_ptr + sequence_start_index * stride_x_token + idx_feats * stride_x_dim
84
+ ) # [BLOCK_N,]
85
+
86
+ if IS_CONTINUOUS_BATCHING:
87
+ # cache_idx
88
+ conv_state_batch_coord = tl.load(conv_state_indices_ptr + idx_seq).to(tl.int64)
89
+ else:
90
+ # cache_idx
91
+ conv_state_batch_coord = idx_seq
92
+ if USE_PAD_SLOT: # noqa
93
+ if conv_state_batch_coord == pad_slot_id:
94
+ # not processing as this is not the actual sequence
95
+ return
96
+ conv_states_base = (
97
+ conv_states_ptr
98
+ + (conv_state_batch_coord * stride_conv_state_seq)
99
+ + (idx_feats * stride_conv_state_dim)
100
+ ) # [BLOCK_N,]
101
+
102
+ w_base = w_ptr + (idx_feats * stride_w_dim) # [BLOCK_N,]
103
+
104
+ # Does 2 things:
105
+ # 1. READ prior-block init-state data - [done by every Triton programs]
106
+ # 2. update conv_state with new data [only by the Triton program handles chunk_offset=0]
107
+ if chunk_offset == 0:
108
+ # read from conv_states
109
+ load_init_state = False
110
+ if HAS_INITIAL_STATES: # the new HAS_INITIAL_STATES
111
+ load_init_state = tl.load(has_initial_states_ptr + idx_seq).to(tl.int1)
112
+ if load_init_state:
113
+ # load from conv_states
114
+ prior_tokens = conv_states_base + (state_len - 1) * stride_conv_state_tok
115
+ mask_w = idx_feats < dim
116
+ if KERNEL_WIDTH == 2:
117
+ conv_states_ptrs = prior_tokens # [BLOCK_N]
118
+ col0 = tl.load(conv_states_ptrs, mask_w, 0.0)
119
+ if KERNEL_WIDTH == 3:
120
+ conv_states_ptrs = prior_tokens # [BLOCK_N]
121
+ col1 = tl.load(conv_states_ptrs, mask_w, 0.0)
122
+ conv_states_ptrs = prior_tokens - 1 * stride_conv_state_tok # [BLOCK_N]
123
+ col0 = tl.load(conv_states_ptrs, mask_w, 0.0)
124
+ if KERNEL_WIDTH == 4:
125
+ conv_states_ptrs = prior_tokens # [BLOCK_N]
126
+ col2 = tl.load(conv_states_ptrs, mask_w, 0.0)
127
+ conv_states_ptrs = prior_tokens - 1 * stride_conv_state_tok # [BLOCK_N]
128
+ col1 = tl.load(conv_states_ptrs, mask_w, 0.0)
129
+ conv_states_ptrs = prior_tokens - 2 * stride_conv_state_tok # [BLOCK_N]
130
+ col0 = tl.load(conv_states_ptrs, mask_w, 0.0)
131
+ if KERNEL_WIDTH == 5:
132
+ conv_states_ptrs = prior_tokens # [BLOCK_N]
133
+ col3 = tl.load(conv_states_ptrs, mask_w, 0.0)
134
+ conv_states_ptrs = prior_tokens - 1 * stride_conv_state_tok # [BLOCK_N]
135
+ col2 = tl.load(conv_states_ptrs, mask_w, 0.0)
136
+ conv_states_ptrs = prior_tokens - 2 * stride_conv_state_tok # [BLOCK_N]
137
+ col1 = tl.load(conv_states_ptrs, mask_w, 0.0)
138
+ conv_states_ptrs = prior_tokens - 3 * stride_conv_state_tok # [BLOCK_N]
139
+ col0 = tl.load(conv_states_ptrs, mask_w, 0.0)
140
+ else:
141
+ # prior-tokens are zeros
142
+ if KERNEL_WIDTH >= 2: # STRATEGY1
143
+ # first chunk and does not have prior-token, so just set to 0
144
+ col0 = tl.zeros((BLOCK_N,), dtype=x_ptr.dtype.element_ty)
145
+ if KERNEL_WIDTH >= 3: # STRATEGY1
146
+ col1 = tl.zeros((BLOCK_N,), dtype=x_ptr.dtype.element_ty)
147
+ if KERNEL_WIDTH >= 4: # STRATEGY1
148
+ col2 = tl.zeros((BLOCK_N,), dtype=x_ptr.dtype.element_ty)
149
+ if KERNEL_WIDTH >= 5: # STRATEGY1
150
+ col3 = tl.zeros((BLOCK_N,), dtype=x_ptr.dtype.element_ty)
151
+
152
+ # STEP 2:
153
+ # here prepare data for updating conv_state
154
+ if (
155
+ state_len <= seqlen
156
+ ): # SMALL_CACHE=True (only move part of 'x' into conv_state cache)
157
+ # just read from 'x'
158
+ # copy 'x' data to conv_state
159
+ # load only 'x' data (and set 0 before 'x' if seqlen < state_len)
160
+ idx_tokens_last = (seqlen - state_len) + tl.arange(
161
+ 0, NP2_STATELEN
162
+ ) # [BLOCK_M]
163
+ x_ptrs = (
164
+ x_ptr
165
+ + ((sequence_start_index + idx_tokens_last) * stride_x_token)[:, None]
166
+ + (idx_feats * stride_x_dim)[None, :]
167
+ ) # [BLOCK_M,BLOCK_N,]
168
+ mask_x = (
169
+ (idx_tokens_last >= 0)[:, None]
170
+ & (idx_tokens_last < seqlen)[:, None]
171
+ & (idx_feats < dim)[None, :]
172
+ ) # token-index # token-index # feature-index
173
+ loaded_x = tl.load(x_ptrs, mask_x, 0.0)
174
+ new_conv_state = tl.load(x_ptrs, mask_x, 0.0)
175
+ idx_tokens_conv = tl.arange(0, NP2_STATELEN) # [BLOCK_M]
176
+ conv_states_ptrs_target = (
177
+ conv_states_base[None, :]
178
+ + (idx_tokens_conv * stride_conv_state_tok)[:, None]
179
+ )
180
+
181
+ mask = (idx_tokens_conv < state_len)[:, None] & (idx_feats < dim)[None, :]
182
+ tl.debug_barrier() # NOTE: use this due to bug in Triton compiler
183
+ tl.store(conv_states_ptrs_target, new_conv_state, mask)
184
+
185
+ else:
186
+ if load_init_state:
187
+ # update conv_state by shifting left, i.e. take last few cols from conv_state + cols from 'x'
188
+ idx_tokens_conv = tl.arange(0, NP2_STATELEN) # [BLOCK_M]
189
+
190
+ conv_states_ptrs_source = (
191
+ conv_states_ptr
192
+ + (conv_state_batch_coord * stride_conv_state_seq)
193
+ + (idx_feats * stride_conv_state_dim)[None, :]
194
+ + ((idx_tokens_conv + seqlen) * stride_conv_state_tok)[:, None]
195
+ ) # [BLOCK_M, BLOCK_N]
196
+ mask = (
197
+ (conv_state_batch_coord < num_cache_lines)
198
+ & ((idx_tokens_conv + seqlen) < state_len)[:, None]
199
+ & (idx_feats < dim)[None, :]
200
+ )
201
+ conv_state = tl.load(conv_states_ptrs_source, mask, other=0.0)
202
+
203
+ VAL = state_len - seqlen
204
+
205
+ x_ptrs = (
206
+ x_base[None, :]
207
+ + ((idx_tokens_conv - VAL) * stride_x_token)[:, None]
208
+ ) # [BLOCK_M, BLOCK_N]
209
+
210
+ mask_x = (
211
+ (idx_tokens_conv - VAL >= 0)[:, None]
212
+ & (idx_tokens_conv - VAL < seqlen)[:, None]
213
+ & (idx_feats < dim)[None, :]
214
+ ) # token-index # token-index # feature-index
215
+ loaded_x = tl.load(x_ptrs, mask_x, 0.0)
216
+
217
+ tl.debug_barrier() # need this due to the bug in tl.where not enforcing this when data is the result of another tl.load
218
+ new_conv_state = tl.where(
219
+ mask, conv_state, loaded_x
220
+ ) # BUG in 'tl.where' which requires a barrier before this
221
+ conv_states_ptrs_target = (
222
+ conv_states_base
223
+ + (idx_tokens_conv * stride_conv_state_tok)[:, None]
224
+ ) # [BLOCK_M, BLOCK_N]
225
+ mask = (idx_tokens_conv < state_len)[:, None] & (idx_feats < dim)[
226
+ None, :
227
+ ]
228
+ tl.store(conv_states_ptrs_target, new_conv_state, mask)
229
+ else: # load_init_state == False
230
+ # update conv_state by shifting left, BUT
231
+ # set cols prior to 'x' as zeros + cols from 'x'
232
+ idx_tokens_conv = tl.arange(0, NP2_STATELEN) # [BLOCK_M]
233
+
234
+ VAL = state_len - seqlen
235
+
236
+ x_ptrs = (
237
+ x_base[None, :]
238
+ + ((idx_tokens_conv - VAL) * stride_x_token)[:, None]
239
+ ) # [BLOCK_M, BLOCK_N]
240
+
241
+ mask_x = (
242
+ (idx_tokens_conv - VAL >= 0)[:, None]
243
+ & (idx_tokens_conv - VAL < seqlen)[:, None]
244
+ & (idx_feats < dim)[None, :]
245
+ ) # token-index # token-index # feature-index
246
+ new_conv_state = tl.load(x_ptrs, mask_x, 0.0)
247
+
248
+ conv_states_ptrs_target = (
249
+ conv_states_base
250
+ + (idx_tokens_conv * stride_conv_state_tok)[:, None]
251
+ ) # [BLOCK_M, BLOCK_N]
252
+ mask = (idx_tokens_conv < state_len)[:, None] & (idx_feats < dim)[
253
+ None, :
254
+ ]
255
+ tl.store(conv_states_ptrs_target, new_conv_state, mask)
256
+
257
+ else: # chunk_offset > 0
258
+ # read prior-token data from `x`
259
+ load_init_state = True
260
+ prior_tokens = x_base + (token_offset - 1) * stride_x_token
261
+ mask_w = idx_feats < dim
262
+ if KERNEL_WIDTH == 2:
263
+ conv_states_ptrs = prior_tokens # [BLOCK_N]
264
+ col0 = tl.load(conv_states_ptrs, mask_w, 0.0, cache_modifier=".ca")
265
+ if KERNEL_WIDTH == 3:
266
+ conv_states_ptrs = prior_tokens # [BLOCK_N]
267
+ col1 = tl.load(conv_states_ptrs, mask_w, 0.0, cache_modifier=".ca")
268
+ conv_states_ptrs = prior_tokens - 1 * stride_x_token # [BLOCK_N]
269
+ col0 = tl.load(conv_states_ptrs, mask_w, 0.0, cache_modifier=".ca")
270
+ if KERNEL_WIDTH == 4:
271
+ conv_states_ptrs = prior_tokens # [BLOCK_N]
272
+ col2 = tl.load(conv_states_ptrs, mask_w, 0.0, cache_modifier=".ca")
273
+ conv_states_ptrs = prior_tokens - 1 * stride_x_token # [BLOCK_N]
274
+ col1 = tl.load(conv_states_ptrs, mask_w, 0.0, cache_modifier=".ca")
275
+ conv_states_ptrs = prior_tokens - 2 * stride_x_token # [BLOCK_N]
276
+ col0 = tl.load(conv_states_ptrs, mask_w, 0.0, cache_modifier=".ca")
277
+ if KERNEL_WIDTH == 5:
278
+ # ruff: noqa: F841
279
+ conv_states_ptrs = prior_tokens # [BLOCK_N]
280
+ col3 = tl.load(conv_states_ptrs, mask_w, 0.0, cache_modifier=".ca")
281
+ conv_states_ptrs = prior_tokens - 1 * stride_x_token # [BLOCK_N]
282
+ col2 = tl.load(conv_states_ptrs, mask_w, 0.0, cache_modifier=".ca")
283
+ conv_states_ptrs = prior_tokens - 2 * stride_x_token # [BLOCK_N]
284
+ col1 = tl.load(conv_states_ptrs, mask_w, 0.0, cache_modifier=".ca")
285
+ conv_states_ptrs = prior_tokens - 3 * stride_x_token # [BLOCK_N]
286
+ col0 = tl.load(conv_states_ptrs, mask_w, 0.0, cache_modifier=".ca")
287
+
288
+ if HAS_BIAS:
289
+ bias = bias_ptr + idx_feats
290
+ mask_bias = idx_feats < dim
291
+ acc_preload = tl.load(bias, mask=mask_bias, other=0.0).to(
292
+ tl.float32
293
+ ) # [BLOCK_N]
294
+ else:
295
+ acc_preload = tl.zeros((BLOCK_N,), dtype=tl.float32)
296
+
297
+ x_base_1d = x_base + token_offset * stride_x_token # starting of chunk
298
+
299
+ # PRE-LOAD WEIGHTS
300
+ mask_w = idx_feats < dim
301
+ if KERNEL_WIDTH >= 2:
302
+ w_ptrs = w_base + (0 * stride_w_width) # [BLOCK_N] tensor
303
+ w_col0 = tl.load(w_ptrs, mask_w, other=0.0)
304
+ w_ptrs = w_base + (1 * stride_w_width) # [BLOCK_N] tensor
305
+ w_col1 = tl.load(w_ptrs, mask_w, other=0.0)
306
+ if KERNEL_WIDTH >= 3:
307
+ w_ptrs = w_base + (2 * stride_w_width) # [BLOCK_N] tensor
308
+ w_col2 = tl.load(w_ptrs, mask_w, other=0.0)
309
+ if KERNEL_WIDTH >= 4:
310
+ w_ptrs = w_base + (3 * stride_w_width) # [BLOCK_N] tensor
311
+ w_col3 = tl.load(w_ptrs, mask_w, other=0.0)
312
+ mask_x_1d = idx_feats < dim
313
+ for idx_token in range(segment_len):
314
+ acc = acc_preload
315
+
316
+ matrix_w = w_col0
317
+ matrix_x = col0
318
+ for j in tl.static_range(KERNEL_WIDTH):
319
+
320
+ if KERNEL_WIDTH == 2:
321
+ if j == 1: # KERNEL_WIDTH-1:
322
+ matrix_w = w_col1
323
+ x_ptrs_1d = x_base_1d + idx_token * stride_x_token # [BLOCK_N]
324
+ matrix_x = tl.load(x_ptrs_1d, mask=mask_x_1d)
325
+ elif KERNEL_WIDTH == 3:
326
+ if j == 1:
327
+ matrix_w = w_col1
328
+ matrix_x = col1
329
+ elif j == 2:
330
+ matrix_w = w_col2
331
+ x_ptrs_1d = x_base_1d + idx_token * stride_x_token # [BLOCK_N]
332
+ matrix_x = tl.load(x_ptrs_1d, mask=mask_x_1d)
333
+ elif KERNEL_WIDTH == 4:
334
+ if j == 1:
335
+ matrix_w = w_col1
336
+ matrix_x = col1
337
+ elif j == 2:
338
+ matrix_w = w_col2
339
+ matrix_x = col2
340
+ elif j == 3:
341
+ matrix_w = w_col3
342
+ x_ptrs_1d = x_base_1d + idx_token * stride_x_token # [BLOCK_N]
343
+ matrix_x = tl.load(x_ptrs_1d, mask=mask_x_1d)
344
+
345
+ acc += matrix_x * matrix_w # [BLOCK_N]
346
+
347
+ if KERNEL_WIDTH == 2:
348
+ col0 = matrix_x
349
+ elif KERNEL_WIDTH == 3:
350
+ col0 = col1
351
+ col1 = matrix_x
352
+ elif KERNEL_WIDTH == 4:
353
+ col0 = col1
354
+ col1 = col2
355
+ col2 = matrix_x
356
+
357
+ if SILU_ACTIVATION:
358
+ acc = acc / (1 + tl.exp(-acc))
359
+ mask_1d = (idx_token < segment_len) & (
360
+ idx_feats < dim
361
+ ) # token-index # feature-index
362
+ o_ptrs = (
363
+ o_ptr
364
+ + (sequence_start_index + token_offset + idx_token) * stride_o_token
365
+ + (idx_feats * stride_o_dim)
366
+ )
367
+
368
+ tl.store(o_ptrs, acc, mask=mask_1d)
369
+
370
+
371
+ @triton.jit()
372
+ def _causal_conv1d_update_kernel(
373
+ # Pointers to matrices
374
+ x_ptr, # (batch, dim, seqlen)
375
+ w_ptr, # (dim, width)
376
+ bias_ptr,
377
+ conv_state_ptr,
378
+ cache_seqlens_ptr, # circular buffer
379
+ conv_state_indices_ptr,
380
+ num_accepted_tokens_ptr,
381
+ intermediate_conv_window_ptr,
382
+ o_ptr, # (batch, dim, seqlen)
383
+ # Matrix dimensions
384
+ batch: int,
385
+ dim: tl.constexpr,
386
+ seqlen: tl.constexpr,
387
+ state_len: tl.constexpr,
388
+ num_cache_lines: tl.constexpr, # added to support vLLM larger cache lines
389
+ # Strides
390
+ stride_x_seq: tl.constexpr,
391
+ stride_x_dim: tl.constexpr,
392
+ stride_x_token: tl.constexpr,
393
+ stride_w_dim: tl.constexpr,
394
+ stride_w_width: tl.constexpr,
395
+ stride_conv_state_seq: tl.constexpr,
396
+ stride_conv_state_dim: tl.constexpr,
397
+ stride_conv_state_tok: tl.constexpr,
398
+ stride_state_indices: tl.constexpr,
399
+ stride_inter_seq: tl.constexpr,
400
+ stride_inter_step: tl.constexpr,
401
+ stride_inter_dim: tl.constexpr,
402
+ stride_inter_win: tl.constexpr,
403
+ stride_o_seq: tl.constexpr,
404
+ stride_o_dim: tl.constexpr,
405
+ stride_o_token: tl.constexpr,
406
+ # others
407
+ pad_slot_id: tl.constexpr,
408
+ # Meta-parameters
409
+ HAS_BIAS: tl.constexpr,
410
+ KERNEL_WIDTH: tl.constexpr,
411
+ SILU_ACTIVATION: tl.constexpr,
412
+ IS_CONTINUOUS_BATCHING: tl.constexpr,
413
+ IS_SPEC_DECODING: tl.constexpr,
414
+ NP2_STATELEN: tl.constexpr,
415
+ USE_PAD_SLOT: tl.constexpr,
416
+ BLOCK_N: tl.constexpr,
417
+ SAVE_INTERMEDIATE: tl.constexpr,
418
+ ):
419
+ # ruff: noqa: E501
420
+ idx_seq = tl.program_id(0)
421
+ if idx_seq >= batch:
422
+ return
423
+
424
+ # [BLOCK_N,] elements along the feature-dimension (channel)
425
+ idx_feats = tl.program_id(1) * BLOCK_N + tl.arange(0, BLOCK_N)
426
+
427
+ if IS_CONTINUOUS_BATCHING:
428
+ # mask = idx_seq < batch
429
+ conv_state_batch_coord = tl.load(
430
+ conv_state_indices_ptr + idx_seq * stride_state_indices
431
+ ).to(tl.int64)
432
+ else:
433
+ conv_state_batch_coord = idx_seq
434
+ if USE_PAD_SLOT: # noqa
435
+ if conv_state_batch_coord == pad_slot_id:
436
+ # not processing as this is not the actual sequence
437
+ return
438
+
439
+ if IS_SPEC_DECODING:
440
+ # The rolling of conv state:
441
+ #
442
+ # Before forward, the conv_state is:
443
+ # [history1, history2, ..., historyM].
444
+ #
445
+ # After forward, the conv_state becomes:
446
+ # [history2, ..., historyM, draft1, draft2, ..., draftN].
447
+ #
448
+ # After acceptance, it becomes:
449
+ #
450
+ # - accept 1 tokens: [history2, ..., historyM, draft1]
451
+ # - accept 2 tokens: [history3, ..., historyM, draft1, draft2]
452
+ # - and so on.
453
+ conv_state_token_offset = tl.load(num_accepted_tokens_ptr + idx_seq) - 1
454
+ else:
455
+ conv_state_token_offset = 0
456
+
457
+ # STEP 1: READ init_state data
458
+ conv_states_base = (
459
+ conv_state_ptr
460
+ + (conv_state_batch_coord * stride_conv_state_seq)
461
+ + (idx_feats * stride_conv_state_dim)
462
+ )
463
+ mask_w = idx_feats < dim
464
+
465
+ prior_tokens = conv_states_base + conv_state_token_offset * stride_conv_state_tok
466
+ if KERNEL_WIDTH >= 2:
467
+ conv_states_ptrs = prior_tokens # [BLOCK_N]
468
+ col0 = tl.load(conv_states_ptrs, mask_w, 0.0)
469
+ if KERNEL_WIDTH >= 3:
470
+ conv_states_ptrs = prior_tokens + 1 * stride_conv_state_tok # [BLOCK_N]
471
+ col1 = tl.load(conv_states_ptrs, mask_w, 0.0)
472
+ if KERNEL_WIDTH >= 4:
473
+ conv_states_ptrs = prior_tokens + 2 * stride_conv_state_tok # [BLOCK_N]
474
+ col2 = tl.load(conv_states_ptrs, mask_w, 0.0)
475
+ if KERNEL_WIDTH == 5:
476
+ conv_states_ptrs = prior_tokens + 3 * stride_conv_state_tok # [BLOCK_N]
477
+ col3 = tl.load(conv_states_ptrs, mask_w, 0.0)
478
+
479
+ # STEP 2: assume state_len > seqlen
480
+ idx_tokens = tl.arange(0, NP2_STATELEN) # [BLOCK_M]
481
+
482
+ # The conv_state updates works in a sliding window manner,
483
+ # at each forward pass, the tokens are shift by 1, so we
484
+ # load since idx_tokens + 1.
485
+ conv_state_ptrs_source = (
486
+ conv_state_ptr
487
+ + (conv_state_batch_coord * stride_conv_state_seq)
488
+ + conv_state_token_offset * stride_conv_state_tok
489
+ + (idx_feats * stride_conv_state_dim)[None, :]
490
+ + ((idx_tokens + (1 if IS_SPEC_DECODING else seqlen)) * stride_conv_state_tok)[
491
+ :, None
492
+ ]
493
+ ) # [BLOCK_M, BLOCK_N]
494
+ mask = (
495
+ (conv_state_batch_coord < num_cache_lines)
496
+ & ((idx_tokens + seqlen) < state_len)[:, None]
497
+ & (idx_feats < dim)[None, :]
498
+ )
499
+ conv_state = tl.load(conv_state_ptrs_source, mask, other=0.0)
500
+
501
+ VAL = state_len - seqlen
502
+ x_base = x_ptr + (idx_seq * stride_x_seq) + (idx_feats * stride_x_dim) # [BLOCK_N]
503
+
504
+ x_ptrs = (
505
+ x_base[None, :] + ((idx_tokens - VAL) * stride_x_token)[:, None]
506
+ ) # [BLOCK_M, BLOCK_N]
507
+
508
+ mask_x = (
509
+ (idx_tokens - VAL >= 0)[:, None]
510
+ & (idx_tokens - VAL < seqlen)[:, None]
511
+ & (idx_feats < dim)[None, :]
512
+ ) # token-index # token-index # feature-index
513
+ loaded_x = tl.load(x_ptrs, mask_x, 0.0)
514
+ tl.debug_barrier()
515
+
516
+ new_conv_state = tl.where(mask, conv_state, loaded_x)
517
+
518
+ conv_state_base = (
519
+ conv_state_ptr
520
+ + (conv_state_batch_coord * stride_conv_state_seq)
521
+ + (idx_feats * stride_conv_state_dim)
522
+ ) # [BLOCK_N,]
523
+ conv_state_ptrs_target = (
524
+ conv_state_base + (idx_tokens * stride_conv_state_tok)[:, None]
525
+ ) # [BLOCK_M, BLOCK_N]
526
+ mask = (idx_tokens < state_len)[:, None] & (idx_feats < dim)[None, :]
527
+ tl.store(conv_state_ptrs_target, new_conv_state, mask)
528
+
529
+ # STEP 3: init accumulator
530
+ if HAS_BIAS:
531
+ bias = bias_ptr + idx_feats
532
+ mask_bias = idx_feats < dim
533
+ acc_preload = tl.load(bias, mask=mask_bias, other=0.0).to(
534
+ tl.float32
535
+ ) # [BLOCK_N]
536
+ else:
537
+ acc_preload = tl.zeros((BLOCK_N,), dtype=tl.float32)
538
+
539
+ # STEP 4:
540
+ # PRE-LOAD WEIGHTS
541
+ # first kernel column, configured for weights to handle BLOCK_N features in range
542
+ w_base = w_ptr + (idx_feats * stride_w_dim) # [BLOCK_N,]
543
+ mask_w = idx_feats < dim
544
+ if KERNEL_WIDTH >= 2:
545
+ w_ptrs = w_base + (0 * stride_w_width) # [BLOCK_N] tensor
546
+ w_col0 = tl.load(w_ptrs, mask_w, other=0.0)
547
+ w_ptrs = w_base + (1 * stride_w_width) # [BLOCK_N] tensor
548
+ w_col1 = tl.load(w_ptrs, mask_w, other=0.0)
549
+ if KERNEL_WIDTH >= 3:
550
+ w_ptrs = w_base + (2 * stride_w_width) # [BLOCK_N] tensor
551
+ w_col2 = tl.load(w_ptrs, mask_w, other=0.0)
552
+ if KERNEL_WIDTH >= 4:
553
+ w_ptrs = w_base + (3 * stride_w_width) # [BLOCK_N] tensor
554
+ w_col3 = tl.load(w_ptrs, mask_w, other=0.0)
555
+
556
+ x_base_1d = x_base # starting of chunk [BLOCK_N]
557
+ mask_x_1d = idx_feats < dim
558
+
559
+ # STEP 5: compute each token
560
+ for idx_token in tl.static_range(seqlen):
561
+ acc = acc_preload
562
+
563
+ matrix_w = w_col0
564
+ matrix_x = col0
565
+ for j in tl.static_range(KERNEL_WIDTH):
566
+ if KERNEL_WIDTH == 2:
567
+ if j == 1: # KERNEL_WIDTH-1:
568
+ matrix_w = w_col1
569
+ x_ptrs_1d = x_base_1d + idx_token * stride_x_token # [BLOCK_N]
570
+ matrix_x = tl.load(x_ptrs_1d, mask=mask_x_1d)
571
+ elif KERNEL_WIDTH == 3:
572
+ if j == 1:
573
+ matrix_w = w_col1
574
+ matrix_x = col1
575
+ elif j == 2:
576
+ matrix_w = w_col2
577
+ x_ptrs_1d = x_base_1d + idx_token * stride_x_token # [BLOCK_N]
578
+ matrix_x = tl.load(x_ptrs_1d, mask=mask_x_1d)
579
+ elif KERNEL_WIDTH == 4:
580
+ if j == 1:
581
+ matrix_w = w_col1
582
+ matrix_x = col1
583
+ elif j == 2:
584
+ matrix_w = w_col2
585
+ matrix_x = col2
586
+ elif j == 3:
587
+ matrix_w = w_col3
588
+ x_ptrs_1d = x_base_1d + idx_token * stride_x_token # [BLOCK_N]
589
+ matrix_x = tl.load(x_ptrs_1d, mask=mask_x_1d)
590
+
591
+ acc += matrix_x * matrix_w # [BLOCK_N]
592
+
593
+ if KERNEL_WIDTH == 2:
594
+ col0 = matrix_x
595
+ elif KERNEL_WIDTH == 3:
596
+ col0 = col1
597
+ col1 = matrix_x
598
+ elif KERNEL_WIDTH == 4:
599
+ col0 = col1
600
+ col1 = col2
601
+ col2 = matrix_x
602
+
603
+ if SILU_ACTIVATION:
604
+ acc = acc / (1 + tl.exp(-acc))
605
+ mask_1d = (idx_token < seqlen) & (
606
+ idx_feats < dim
607
+ ) # token-index # feature-index
608
+ o_ptrs = (
609
+ o_ptr
610
+ + (idx_seq) * stride_o_seq
611
+ + idx_token * stride_o_token
612
+ + (idx_feats * stride_o_dim)
613
+ )
614
+
615
+ tl.store(o_ptrs, acc, mask=mask_1d)
616
+
617
+ if SAVE_INTERMEDIATE:
618
+ # Save the window state after consuming this token
619
+ # Layout: [seq(cache line), step, dim, win(K-1)]
620
+ base_ptr = (
621
+ intermediate_conv_window_ptr
622
+ + conv_state_batch_coord * stride_inter_seq
623
+ + idx_token * stride_inter_step
624
+ + idx_feats * stride_inter_dim
625
+ )
626
+ if KERNEL_WIDTH >= 2:
627
+ tl.store(base_ptr + 0 * stride_inter_win, col0, mask=mask_w)
628
+ if KERNEL_WIDTH >= 3:
629
+ tl.store(base_ptr + 1 * stride_inter_win, col1, mask=mask_w)
630
+ if KERNEL_WIDTH >= 4:
631
+ tl.store(base_ptr + 2 * stride_inter_win, col2, mask=mask_w)
build/torch-rocm/_triton_kernels/causal_conv1d_update_single_token.py ADDED
@@ -0,0 +1,554 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
+ # Copyright (c) 2024, Tri Dao.
4
+ # Adapted from https://github.com/Dao-AILab/causal-conv1d/blob/main/causal_conv1d/causal_conv1d_interface.py
5
+ # Copyright (C) 2024-2026, Advanced Micro Devices, Inc. All rights reserved.
6
+ #
7
+ # Kernels for causal_conv1d **update** single-token paths: ``conv_state`` is updated in place.
8
+
9
+ import triton
10
+ import triton.language as tl
11
+
12
+
13
+ @triton.jit
14
+ def _ba_source_offsets(
15
+ idx_hv, num_v_heads, num_k_heads, INTERLEAVED_QKVZ: tl.constexpr
16
+ ):
17
+ """Return (b_off, a_off) into the packed ba tensor for one v-head index."""
18
+ if INTERLEAVED_QKVZ:
19
+ G = num_v_heads // num_k_heads
20
+ idx_h = idx_hv // G
21
+ idx_v = idx_hv % G
22
+ b_off = idx_h * (2 * G) + idx_v
23
+ a_off = idx_h * (2 * G) + G + idx_v
24
+ else:
25
+ b_off = idx_hv
26
+ a_off = num_v_heads + idx_hv
27
+ return b_off, a_off
28
+
29
+
30
+ @triton.jit
31
+ def _z_source_idx(
32
+ idx_z,
33
+ num_k_heads,
34
+ num_v_heads,
35
+ head_k_dim,
36
+ head_v_dim,
37
+ head_qkvz_dim,
38
+ INTERLEAVED_QKVZ: tl.constexpr,
39
+ ):
40
+ """Map flat z index to source column in the packed x tensor."""
41
+ if INTERLEAVED_QKVZ:
42
+ G = num_v_heads // num_k_heads
43
+ gs = G * head_v_dim
44
+ return idx_z // gs * head_qkvz_dim + 2 * head_k_dim + gs + idx_z % gs
45
+ else:
46
+ return 2 * num_k_heads * head_k_dim + num_v_heads * head_v_dim + idx_z
47
+
48
+
49
+ @triton.jit
50
+ def _feat_source_idx(
51
+ idx_feats,
52
+ num_k_heads,
53
+ head_k_dim,
54
+ head_v_dim,
55
+ head_qkvz_dim,
56
+ num_v_heads,
57
+ INTERLEAVED_QKVZ: tl.constexpr,
58
+ ):
59
+ """Map logical conv-output feature index to source column in packed x."""
60
+ if INTERLEAVED_QKVZ:
61
+ nk = num_k_heads
62
+ hk = head_k_dim
63
+ gs = (num_v_heads // nk) * head_v_dim
64
+ in_q = (idx_feats < nk * hk).to(tl.int64)
65
+ in_k = ((idx_feats >= nk * hk) & (idx_feats < nk * hk * 2)).to(tl.int64)
66
+ in_v = (idx_feats >= nk * hk * 2).to(tl.int64)
67
+ q_idx = idx_feats // hk * head_qkvz_dim + idx_feats % hk
68
+ rel_k = idx_feats - nk * hk
69
+ k_idx = rel_k // hk * head_qkvz_dim + hk + rel_k % hk
70
+ rel_v = idx_feats - nk * hk * 2
71
+ v_idx = rel_v // gs * head_qkvz_dim + 2 * hk + rel_v % gs
72
+ return in_q * q_idx + in_k * k_idx + in_v * v_idx
73
+ else:
74
+ return idx_feats.to(tl.int64)
75
+
76
+
77
+ @triton.jit()
78
+ def _causal_conv1d_update_single_token_kernel(
79
+ # Pointers to matrices
80
+ x_ptr, # (batch, dim, seqlen)
81
+ w_ptr, # (dim, width)
82
+ bias_ptr,
83
+ conv_state_ptr,
84
+ conv_state_indices_ptr,
85
+ block_idx_last_scheduled_token, # (batch,)
86
+ initial_state_idx, # (batch,)
87
+ o_ptr, # (batch, dim, seqlen)
88
+ # Matrix dimensions
89
+ batch: int,
90
+ dim: tl.constexpr,
91
+ seqlen: tl.constexpr,
92
+ state_len: tl.constexpr,
93
+ num_cache_lines: tl.constexpr, # added to support vLLM larger cache lines
94
+ # Strides
95
+ stride_x_seq: tl.constexpr,
96
+ stride_x_dim: tl.constexpr,
97
+ stride_x_token: tl.constexpr,
98
+ stride_w_dim: tl.constexpr,
99
+ stride_w_width: tl.constexpr,
100
+ stride_conv_state_seq: tl.constexpr,
101
+ stride_conv_state_dim: tl.constexpr,
102
+ stride_conv_state_tok: tl.constexpr,
103
+ stride_state_indices: tl.constexpr,
104
+ stride_o_seq: tl.constexpr,
105
+ stride_o_dim: tl.constexpr,
106
+ stride_o_token: tl.constexpr,
107
+ # others
108
+ pad_slot_id: tl.constexpr,
109
+ # Meta-parameters
110
+ HAS_BIAS: tl.constexpr,
111
+ KERNEL_WIDTH: tl.constexpr,
112
+ SILU_ACTIVATION: tl.constexpr,
113
+ IS_APC_ENABLED: tl.constexpr,
114
+ NP2_STATELEN: tl.constexpr,
115
+ USE_PAD_SLOT: tl.constexpr,
116
+ BLOCK_N: tl.constexpr,
117
+ ):
118
+ # ruff: noqa: E501
119
+ idx_seq = tl.program_id(0)
120
+ if idx_seq >= batch:
121
+ return
122
+
123
+ # [BLOCK_N,] elements along the feature-dimension (channel)
124
+ idx_feats = tl.program_id(1) * BLOCK_N + tl.arange(0, BLOCK_N)
125
+
126
+ if IS_APC_ENABLED:
127
+ # Get the state from the initial_state_idx
128
+ conv_state_init = tl.load(initial_state_idx + idx_seq)
129
+ current_last_index = tl.load(block_idx_last_scheduled_token + idx_seq)
130
+ else:
131
+ conv_state_init = 0
132
+ current_last_index = 0
133
+
134
+ # cache_idx
135
+ conv_states_input_coord = tl.load(
136
+ conv_state_indices_ptr + idx_seq * stride_state_indices + conv_state_init
137
+ ).to(tl.int64)
138
+
139
+ if USE_PAD_SLOT: # noqa
140
+ if conv_states_input_coord == pad_slot_id:
141
+ # not processing as this is not the actual sequence
142
+ return
143
+
144
+ # IS_VARLEN is False
145
+ query_start_index = idx_seq * seqlen
146
+ query_end_index = query_start_index + seqlen
147
+ x_offset = idx_seq * stride_x_seq
148
+ o_offset = idx_seq * stride_o_seq
149
+
150
+ if query_start_index == query_end_index:
151
+ return
152
+
153
+ # IS_SPEC_DECODING is False
154
+ conv_state_token_offset = 0
155
+
156
+ # STEP 1: READ init_state data
157
+ # note: NP2_STATELEN = triton.next_power_of_2(KERNEL_WIDTH - 1)
158
+ idx_cols = tl.arange(0, NP2_STATELEN)
159
+ conv_state_ptrs_cols = (
160
+ conv_state_ptr
161
+ + (conv_states_input_coord * stride_conv_state_seq)
162
+ + conv_state_token_offset * stride_conv_state_tok
163
+ + (idx_feats * stride_conv_state_dim)[:, None]
164
+ + (idx_cols * stride_conv_state_tok)[None, :]
165
+ ) # [BLOCK_N, NP2_STATELEN]
166
+ mask_cols = (
167
+ (conv_states_input_coord < num_cache_lines)
168
+ & (idx_feats < dim)[:, None]
169
+ & (idx_cols < KERNEL_WIDTH - 1)[None, :]
170
+ )
171
+ cols = tl.load(conv_state_ptrs_cols, mask_cols, other=0.0)
172
+
173
+ # STEP 2: assume state_len > seqlen
174
+ idx_tokens = tl.arange(0, NP2_STATELEN) # [BLOCK_M]
175
+
176
+ # With speculative decoding, the conv_state updates works in a sliding
177
+ # window manner, at each forward pass, the tokens are shift by 1, so we
178
+ # load since idx_tokens + 1.
179
+ conv_state_ptrs_source = (
180
+ conv_state_ptr
181
+ + (conv_states_input_coord * stride_conv_state_seq)
182
+ + conv_state_token_offset * stride_conv_state_tok
183
+ + (idx_feats * stride_conv_state_dim)[None, :]
184
+ + ((idx_tokens + seqlen) * stride_conv_state_tok)[:, None]
185
+ ) # [BLOCK_M, BLOCK_N]
186
+ mask = (
187
+ (conv_states_input_coord < num_cache_lines)
188
+ & ((idx_tokens + seqlen) < state_len)[:, None]
189
+ & (idx_feats < dim)[None, :]
190
+ )
191
+ conv_state = tl.load(conv_state_ptrs_source, mask, other=0.0)
192
+
193
+ VAL = state_len - seqlen
194
+ x_base = x_ptr + x_offset + (idx_feats * stride_x_dim) # [BLOCK_N]
195
+
196
+ x_ptrs = (
197
+ x_base[None, :] + ((idx_tokens - VAL) * stride_x_token)[:, None]
198
+ ) # [BLOCK_M, BLOCK_N]
199
+
200
+ mask_x = (
201
+ (idx_tokens - VAL >= 0)[:, None]
202
+ & (idx_tokens - VAL < seqlen)[:, None]
203
+ & (idx_feats < dim)[None, :]
204
+ ) # token-index # token-index # feature-index
205
+ loaded_x = tl.load(x_ptrs, mask_x, 0.0)
206
+ tl.debug_barrier()
207
+
208
+ new_conv_state = tl.where(mask, conv_state, loaded_x)
209
+
210
+ # Get the state from the initial_state_idx
211
+ # cache_idx
212
+ conv_states_offset = tl.load(
213
+ conv_state_indices_ptr + idx_seq * stride_state_indices + current_last_index
214
+ ).to(tl.int64)
215
+ conv_state_ptrs_target = (
216
+ conv_state_ptr
217
+ + (conv_states_offset * stride_conv_state_seq) # Offset from seq
218
+ + (idx_feats * stride_conv_state_dim)
219
+ )[
220
+ None, :
221
+ ] + ( # [BLOCK_N,]
222
+ idx_tokens * stride_conv_state_tok
223
+ )[
224
+ :, None
225
+ ]
226
+ mask = (idx_tokens < state_len)[:, None] & (idx_feats < dim)[None, :]
227
+ tl.store(conv_state_ptrs_target, new_conv_state, mask)
228
+
229
+ # STEP 3: init accumulator, not necessary
230
+ # if HAS_BIAS:
231
+ # bias = bias_ptr + idx_feats
232
+ # mask_bias = idx_feats < dim
233
+ # acc_preload = tl.load(bias, mask=mask_bias, other=0.0).to(
234
+ # tl.float32
235
+ # ) # [BLOCK_N]
236
+ # else:
237
+ # acc_preload = tl.zeros((BLOCK_N,), dtype=tl.float32)
238
+
239
+ # STEP 4:
240
+ # LOAD WEIGHTS and compute
241
+ w_cols_ptrs = (
242
+ w_ptr
243
+ + (idx_feats * stride_w_dim)[:, None]
244
+ + (idx_cols * stride_w_width)[None, :]
245
+ )
246
+ mask_w_cols = (idx_feats < dim)[:, None] & (idx_cols < KERNEL_WIDTH - 1)[None, :]
247
+ w_cols = tl.load(w_cols_ptrs, mask_w_cols, other=0.0) # [BLOCK_N, NP2_STATELEN]
248
+
249
+ w_last_ptrs = (
250
+ w_ptr + (idx_feats * stride_w_dim) + (KERNEL_WIDTH - 1) * stride_w_width
251
+ )
252
+ w_last = tl.load(w_last_ptrs, idx_feats < dim, other=0.0) # [BLOCK_N]
253
+
254
+ # For the convolution output: dot(weights, [state_cols | x])
255
+ # cols is [BLOCK_N, NP2_STATELEN] = conv_state history
256
+ # We need x as 1D [BLOCK_N] for the last weight column
257
+ x_1d = tl.load(x_base, mask=(idx_feats < dim), other=0.0) # [BLOCK_N], reload as 1D
258
+ acc = tl.sum((w_cols * cols).to(tl.float32), axis=1) + (w_last * x_1d).to(
259
+ tl.float32
260
+ )
261
+
262
+ if HAS_BIAS:
263
+ bias = bias_ptr + idx_feats
264
+ acc += tl.load(bias, idx_feats < dim, other=0.0).to(tl.float32) # [BLOCK_N]
265
+
266
+ if SILU_ACTIVATION:
267
+ acc = acc / (1 + tl.exp(-acc))
268
+ mask_1d = idx_feats < dim
269
+ o_ptrs = o_ptr + o_offset + (idx_feats * stride_o_dim)
270
+
271
+ tl.store(o_ptrs, acc, mask=mask_1d)
272
+
273
+
274
+ @triton.jit()
275
+ def _reshape_causal_conv1d_update_single_token_kernel(
276
+ # Pointers to matrices
277
+ x_ptr, # (num_tokens, dim+z_dim, seqlen) where seqlen=1
278
+ ba_ptr,
279
+ z_ptr, # (num_tokens, num_v_heads, head_v_dim)
280
+ core_attn_out_ptr, # (num_tokens, num_v_heads, head_v_dim)
281
+ b_ptr, # (num_accepted_tokens, num_v_heads)
282
+ a_ptr, # (num_accepted_tokens, num_v_heads)
283
+ w_ptr, # (dim, width)
284
+ bias_ptr,
285
+ conv_state_ptr,
286
+ conv_state_indices_ptr,
287
+ block_idx_last_scheduled_token, # (batch,)
288
+ initial_state_idx, # (batch,)
289
+ o_ptr, # (num_accepted_tokens, dim, seqlen)
290
+ # Matrix dimensions
291
+ batch: int,
292
+ num_tokens: int,
293
+ num_k_heads: tl.constexpr,
294
+ num_v_heads: tl.constexpr,
295
+ head_k_dim: tl.constexpr,
296
+ head_v_dim: tl.constexpr,
297
+ dim: tl.constexpr,
298
+ head_qkvz_dim: tl.constexpr,
299
+ seqlen: tl.constexpr,
300
+ state_len: tl.constexpr,
301
+ num_cache_lines: tl.constexpr, # added to support vLLM larger cache lines
302
+ # Strides
303
+ stride_x_seq: tl.constexpr,
304
+ stride_x_dim: tl.constexpr,
305
+ stride_x_token: tl.constexpr,
306
+ stride_w_dim: tl.constexpr,
307
+ stride_w_width: tl.constexpr,
308
+ stride_conv_state_seq: tl.constexpr,
309
+ stride_conv_state_dim: tl.constexpr,
310
+ stride_conv_state_tok: tl.constexpr,
311
+ stride_state_indices: tl.constexpr,
312
+ stride_o_seq: tl.constexpr,
313
+ stride_o_dim: tl.constexpr,
314
+ stride_o_token: tl.constexpr,
315
+ stride_z_seq: tl.constexpr,
316
+ stride_ba_seq: tl.constexpr,
317
+ stride_ba_token: tl.constexpr,
318
+ stride_b_seq: tl.constexpr,
319
+ # others
320
+ pad_slot_id: tl.constexpr,
321
+ num_program_write_z: tl.constexpr,
322
+ BLOCK_Z: tl.constexpr,
323
+ HV: tl.constexpr,
324
+ # Meta-parameters
325
+ HAS_BIAS: tl.constexpr,
326
+ KERNEL_WIDTH: tl.constexpr,
327
+ SILU_ACTIVATION: tl.constexpr,
328
+ IS_APC_ENABLED: tl.constexpr,
329
+ NP2_STATELEN: tl.constexpr,
330
+ USE_PAD_SLOT: tl.constexpr,
331
+ BLOCK_N: tl.constexpr,
332
+ INTERLEAVED_QKVZ: tl.constexpr,
333
+ ):
334
+ # ruff: noqa: E501
335
+ idx_seq = tl.program_id(0)
336
+ if idx_seq >= batch:
337
+ return
338
+
339
+ ## write b, a
340
+ if tl.program_id(1) == 0:
341
+ ## HV = triton.next_power_of_2(num_v_heads)
342
+ idx_hv = tl.arange(0, HV)
343
+ b_source_offset, a_source_offset = _ba_source_offsets(
344
+ idx_hv, num_v_heads, num_k_heads, INTERLEAVED_QKVZ
345
+ )
346
+
347
+ b_source_ptrs = (
348
+ ba_ptr + idx_seq * stride_ba_seq + b_source_offset * stride_ba_token
349
+ )
350
+ a_source_ptrs = (
351
+ ba_ptr + idx_seq * stride_ba_seq + a_source_offset * stride_ba_token
352
+ )
353
+ mask_ba = idx_hv < num_v_heads
354
+ b = tl.load(b_source_ptrs, mask=mask_ba, other=0.0)
355
+ a = tl.load(a_source_ptrs, mask=mask_ba, other=0.0)
356
+ ## b, a should be contiguous so the last stride is 1
357
+ b_ptrs = b_ptr + idx_seq * stride_b_seq + idx_hv
358
+ a_ptrs = a_ptr + idx_seq * stride_b_seq + idx_hv
359
+ tl.store(b_ptrs, b, mask_ba)
360
+ tl.store(a_ptrs, a, mask_ba)
361
+ ## write z
362
+ elif tl.program_id(1) < 1 + num_program_write_z:
363
+ idx_z = (tl.program_id(1) - 1) * BLOCK_Z + tl.arange(0, BLOCK_Z)
364
+ idx_z_x = _z_source_idx(
365
+ idx_z,
366
+ num_k_heads,
367
+ num_v_heads,
368
+ head_k_dim,
369
+ head_v_dim,
370
+ head_qkvz_dim,
371
+ INTERLEAVED_QKVZ,
372
+ )
373
+ z_source_ptrs = x_ptr + idx_seq * stride_x_seq + idx_z_x * stride_x_dim
374
+ mask_z = idx_z < num_v_heads * head_v_dim
375
+ z = tl.load(z_source_ptrs, mask=mask_z, other=0.0)
376
+ z_ptrs = z_ptr + idx_seq * stride_z_seq + idx_z
377
+ tl.store(z_ptrs, z, mask=mask_z)
378
+
379
+ ## zero-fill core_attn_out
380
+ # first, zero_fill [0, batch) for core_attn_out
381
+ core_attn_out_ptrs = core_attn_out_ptr + idx_seq * stride_z_seq + idx_z
382
+ tl.store(core_attn_out_ptrs, 0.0, mask=mask_z)
383
+ # second, zero_fill [batch, num_tokens) for both z and core_attn_out
384
+ n_repeat = (num_tokens - 1) // batch
385
+ for idx_repeat in tl.range(n_repeat):
386
+ idx_seq_remain = batch * (1 + idx_repeat) + idx_seq
387
+ z_ptrs = z_ptr + idx_seq_remain * stride_z_seq + idx_z
388
+ core_attn_out_ptrs = (
389
+ core_attn_out_ptr + idx_seq_remain * stride_z_seq + idx_z
390
+ )
391
+ mask_remain = (idx_seq_remain < num_tokens) & mask_z
392
+ tl.store(z_ptrs, 0.0, mask=mask_remain)
393
+ tl.store(core_attn_out_ptrs, 0.0, mask=mask_remain)
394
+ ## do regular causal conv1d update
395
+ else:
396
+ # [BLOCK_N,] elements along the feature-dimension (channel)
397
+ idx_feats = (tl.program_id(1) - 1 - num_program_write_z) * BLOCK_N + tl.arange(
398
+ 0, BLOCK_N
399
+ )
400
+ idx_feats_x = _feat_source_idx(
401
+ idx_feats,
402
+ num_k_heads,
403
+ head_k_dim,
404
+ head_v_dim,
405
+ head_qkvz_dim,
406
+ num_v_heads,
407
+ INTERLEAVED_QKVZ,
408
+ )
409
+
410
+ if IS_APC_ENABLED:
411
+ # Get the state from the initial_state_idx
412
+ conv_state_init = tl.load(initial_state_idx + idx_seq)
413
+ current_last_index = tl.load(block_idx_last_scheduled_token + idx_seq)
414
+ else:
415
+ conv_state_init = 0
416
+ current_last_index = 0
417
+
418
+ # cache_idx
419
+ conv_states_input_coord = tl.load(
420
+ conv_state_indices_ptr + idx_seq * stride_state_indices + conv_state_init
421
+ ).to(tl.int64)
422
+
423
+ if USE_PAD_SLOT: # noqa
424
+ if conv_states_input_coord == pad_slot_id:
425
+ # not processing as this is not the actual sequence
426
+ return
427
+
428
+ # IS_VARLEN is False
429
+ query_start_index = idx_seq * seqlen
430
+ query_end_index = query_start_index + seqlen
431
+ x_offset = idx_seq * stride_x_seq
432
+ o_offset = idx_seq * stride_o_seq
433
+
434
+ if query_start_index == query_end_index:
435
+ return
436
+
437
+ # STEP 1: READ init_state data
438
+ # note: NP2_STATELEN = triton.next_power_of_2(KERNEL_WIDTH - 1)
439
+ idx_cols = tl.arange(0, NP2_STATELEN)
440
+ conv_state_ptrs_cols = (
441
+ conv_state_ptr
442
+ + (conv_states_input_coord * stride_conv_state_seq)
443
+ + (idx_feats * stride_conv_state_dim)[:, None]
444
+ + (idx_cols * stride_conv_state_tok)[None, :]
445
+ ) # [BLOCK_N, NP2_STATELEN]
446
+ mask_cols = (
447
+ (conv_states_input_coord < num_cache_lines)
448
+ & (idx_feats < dim)[:, None]
449
+ & (idx_cols < KERNEL_WIDTH - 1)[None, :]
450
+ )
451
+ cols = tl.load(conv_state_ptrs_cols, mask_cols, other=0.0)
452
+
453
+ # STEP 2: assume state_len > seqlen
454
+ idx_tokens = tl.arange(0, NP2_STATELEN) # [BLOCK_M]
455
+
456
+ # With speculative decoding, the conv_state updates works in a sliding
457
+ # window manner, at each forward pass, the tokens are shift by 1, so we
458
+ # load since idx_tokens + 1.
459
+ conv_state_ptrs_source = (
460
+ conv_state_ptr
461
+ + (conv_states_input_coord * stride_conv_state_seq)
462
+ + (idx_feats * stride_conv_state_dim)[None, :]
463
+ + ((idx_tokens + seqlen) * stride_conv_state_tok)[:, None]
464
+ ) # [BLOCK_M, BLOCK_N]
465
+ mask = (
466
+ (conv_states_input_coord < num_cache_lines)
467
+ & ((idx_tokens + seqlen) < state_len)[:, None]
468
+ & (idx_feats < dim)[None, :]
469
+ )
470
+ conv_state = tl.load(conv_state_ptrs_source, mask, other=0.0)
471
+
472
+ VAL = state_len - seqlen
473
+ x_base = x_ptr + x_offset + (idx_feats_x * stride_x_dim) # [BLOCK_N]
474
+
475
+ x_ptrs = (
476
+ x_base[None, :] + ((idx_tokens - VAL) * stride_x_token)[:, None]
477
+ ) # [BLOCK_M, BLOCK_N]
478
+
479
+ mask_x = (
480
+ (idx_tokens - VAL >= 0)[:, None]
481
+ & (idx_tokens - VAL < seqlen)[:, None]
482
+ & (idx_feats < dim)[None, :]
483
+ ) # token-index # token-index # feature-index
484
+ loaded_x = tl.load(x_ptrs, mask_x, 0.0)
485
+ tl.debug_barrier()
486
+
487
+ new_conv_state = tl.where(mask, conv_state, loaded_x)
488
+
489
+ # Get the state from the initial_state_idx
490
+ # cache_idx
491
+ conv_states_offset = tl.load(
492
+ conv_state_indices_ptr + idx_seq * stride_state_indices + current_last_index
493
+ ).to(tl.int64)
494
+ conv_state_ptrs_target = (
495
+ conv_state_ptr
496
+ + (conv_states_offset * stride_conv_state_seq) # Offset from seq
497
+ + (idx_feats * stride_conv_state_dim)
498
+ )[
499
+ None, :
500
+ ] + ( # [BLOCK_N,]
501
+ idx_tokens * stride_conv_state_tok
502
+ )[
503
+ :, None
504
+ ]
505
+ mask = (idx_tokens < state_len)[:, None] & (idx_feats < dim)[None, :]
506
+ tl.store(conv_state_ptrs_target, new_conv_state, mask)
507
+
508
+ # STEP 3: init accumulator, not necessary
509
+ # if HAS_BIAS:
510
+ # bias = bias_ptr + idx_feats
511
+ # mask_bias = idx_feats < dim
512
+ # acc_preload = tl.load(bias, mask=mask_bias, other=0.0).to(
513
+ # tl.float32
514
+ # ) # [BLOCK_N]
515
+ # else:
516
+ # acc_preload = tl.zeros((BLOCK_N,), dtype=tl.float32)
517
+
518
+ # STEP 4:
519
+ # LOAD WEIGHTS and compute
520
+ w_cols_ptrs = (
521
+ w_ptr
522
+ + (idx_feats * stride_w_dim)[:, None]
523
+ + (idx_cols * stride_w_width)[None, :]
524
+ )
525
+ mask_w_cols = (idx_feats < dim)[:, None] & (idx_cols < KERNEL_WIDTH - 1)[
526
+ None, :
527
+ ]
528
+ w_cols = tl.load(w_cols_ptrs, mask_w_cols, other=0.0) # [BLOCK_N, NP2_STATELEN]
529
+
530
+ w_last_ptrs = (
531
+ w_ptr + (idx_feats * stride_w_dim) + (KERNEL_WIDTH - 1) * stride_w_width
532
+ )
533
+ w_last = tl.load(w_last_ptrs, idx_feats < dim, other=0.0) # [BLOCK_N]
534
+
535
+ # For the convolution output: dot(weights, [state_cols | x])
536
+ # cols is [BLOCK_N, NP2_STATELEN] = conv_state history
537
+ # We need x as 1D [BLOCK_N] for the last weight column
538
+ x_1d = tl.load(
539
+ x_base, mask=(idx_feats < dim), other=0.0
540
+ ) # [BLOCK_N], reload as 1D
541
+ acc = tl.sum((w_cols * cols).to(tl.float32), axis=1) + (w_last * x_1d).to(
542
+ tl.float32
543
+ )
544
+
545
+ if HAS_BIAS:
546
+ bias = bias_ptr + idx_feats
547
+ acc += tl.load(bias, idx_feats < dim, other=0.0).to(tl.float32) # [BLOCK_N]
548
+
549
+ if SILU_ACTIVATION:
550
+ acc = acc / (1 + tl.exp(-acc))
551
+ mask_1d = idx_feats < dim
552
+ o_ptrs = o_ptr + o_offset + (idx_feats * stride_o_dim)
553
+
554
+ tl.store(o_ptrs, acc, mask=mask_1d)
build/torch-rocm/_triton_kernels/common/__init__.py ADDED
File without changes
build/torch-rocm/_triton_kernels/common/splitk_reduce.py ADDED
@@ -0,0 +1,89 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import triton
2
+ import triton.language as tl
3
+
4
+ from ...utils._triton.kernel_repr import make_kernel_repr
5
+
6
+ _gemm_splitk_reduce_repr = make_kernel_repr(
7
+ "_gemm_splitk_reduce_kernel",
8
+ [
9
+ "BLOCK_SIZE_M",
10
+ "BLOCK_SIZE_N",
11
+ "ACTUAL_KSPLIT",
12
+ "MAX_KSPLIT",
13
+ "ADD_BIAS",
14
+ "activation",
15
+ "use_activation",
16
+ ],
17
+ name_key="KERNEL_NAME",
18
+ )
19
+
20
+
21
+ @triton.jit(repr=_gemm_splitk_reduce_repr)
22
+ def _gemm_splitk_reduce_kernel(
23
+ c_in_ptr,
24
+ c_out_ptr,
25
+ bias_ptr,
26
+ M,
27
+ N,
28
+ stride_c_in_k,
29
+ stride_c_in_m,
30
+ stride_c_in_n,
31
+ stride_c_out_m,
32
+ stride_c_out_n,
33
+ BLOCK_SIZE_M: tl.constexpr,
34
+ BLOCK_SIZE_N: tl.constexpr,
35
+ ACTUAL_KSPLIT: tl.constexpr,
36
+ MAX_KSPLIT: tl.constexpr,
37
+ ADD_BIAS: tl.constexpr,
38
+ activation: tl.constexpr,
39
+ use_activation: tl.constexpr,
40
+ KERNEL_NAME: tl.constexpr = "_gemm_splitk_reduce_kernel",
41
+ ):
42
+ tl.assume(stride_c_in_k > 0)
43
+ tl.assume(stride_c_in_m > 0)
44
+ tl.assume(stride_c_in_n > 0)
45
+ tl.assume(stride_c_out_m > 0)
46
+ tl.assume(stride_c_out_n > 0)
47
+
48
+ pid_m = tl.program_id(axis=0)
49
+ pid_n = tl.program_id(axis=1)
50
+
51
+ # Tell the AMD backend pid * stride stays non-negative so it can lower
52
+ # the loads/stores to buffer ops instead of generic global ops.
53
+ tl.assume(pid_m >= 0)
54
+ tl.assume(pid_n >= 0)
55
+
56
+ offs_m = (pid_m * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M)) % M
57
+ offs_n = (pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)) % N
58
+ offs_k = tl.arange(0, MAX_KSPLIT)
59
+ c_in_ptrs = (
60
+ c_in_ptr
61
+ + (offs_k[:, None, None] * stride_c_in_k)
62
+ + (offs_m[None, :, None] * stride_c_in_m)
63
+ + (offs_n[None, None, :] * stride_c_in_n)
64
+ )
65
+
66
+ if ACTUAL_KSPLIT == MAX_KSPLIT:
67
+ c = tl.load(c_in_ptrs)
68
+ else:
69
+ c = tl.load(c_in_ptrs, mask=offs_k[:, None, None] < ACTUAL_KSPLIT, other=0.0)
70
+ c = tl.sum(c, axis=0)
71
+
72
+ if ADD_BIAS:
73
+ acc_dtype = tl.float32 if c_in_ptr.type.element_ty != tl.int8 else tl.int32
74
+ bias = tl.load(bias_ptr + offs_n).to(dtype=acc_dtype)
75
+ bias = tl.broadcast_to(bias[None, :], (BLOCK_SIZE_M, BLOCK_SIZE_N))
76
+ c += bias
77
+
78
+ if use_activation:
79
+ c = activation(c)
80
+
81
+ c = c.to(c_out_ptr.type.element_ty)
82
+
83
+ c_out_ptrs = (
84
+ c_out_ptr
85
+ + (offs_m[:, None] * stride_c_out_m)
86
+ + (offs_n[None, :] * stride_c_out_n)
87
+ )
88
+
89
+ tl.store(c_out_ptrs, c)
build/torch-rocm/_triton_kernels/fusions/__init__.py ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (C) 2024-2026, Advanced Micro Devices, Inc. All rights reserved.
3
+
4
+ from ..._triton_kernels.fusions.mhc import (
5
+ _mhc_fused_kernel,
6
+ _mhc_fused_split_kernel,
7
+ _mhc_reduce_apply_kernel,
8
+ _mhc_post_kernel,
9
+ _mhc_post_pre_split_kernel,
10
+ _mhc_post_pre_reduce_apply_kernel,
11
+ )
12
+
13
+ __all__ = [
14
+ "_mhc_fused_kernel",
15
+ "_mhc_fused_split_kernel",
16
+ "_mhc_reduce_apply_kernel",
17
+ "_mhc_post_kernel",
18
+ "_mhc_post_pre_split_kernel",
19
+ "_mhc_post_pre_reduce_apply_kernel",
20
+ ]
build/torch-rocm/_triton_kernels/fusions/fused_bmm_rope_kv_cache.py ADDED
@@ -0,0 +1,1043 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (C) 2026, Advanced Micro Devices, Inc. All rights reserved.
3
+
4
+ import triton
5
+ import triton.language as tl
6
+
7
+ from ..._triton_kernels.quant.quant import _mxfp4_quant_op
8
+ from ...rope.rope import _get_gptj_rotated_x_1D, _get_neox_rotated_x_1D
9
+ from ...utils._triton.kernel_repr import make_kernel_repr
10
+ from ...utils._triton.pid_preprocessing import pid_grid
11
+
12
+ _fused_fp4_bmm_rope_cat_and_cache_mla_repr = make_kernel_repr(
13
+ "_fused_fp4_bmm_rope_cat_and_cache_mla_kernel",
14
+ [
15
+ "BLOCK_SIZE_M",
16
+ "BLOCK_SIZE_N",
17
+ "BLOCK_SIZE_K",
18
+ "GROUP_SIZE_M",
19
+ "NUM_KSPLIT",
20
+ "SPLITK_BLOCK_SIZE",
21
+ "QH_PER_KH",
22
+ "REUSE_FREQS_FRONT_PART",
23
+ "IS_NEOX",
24
+ "BLOCK_D_nope",
25
+ "BLOCK_DK_nope",
26
+ "BLOCK_D_pe",
27
+ "BLOCK_D_HALF_pe",
28
+ "PRE_QUANT",
29
+ "TRANSPOSE_BM",
30
+ "OUTPUT_Q_NOPE_ZEROS",
31
+ "HAVE_Y_SCALE",
32
+ "HAVE_K_SCALE",
33
+ "EVEN_K",
34
+ ],
35
+ )
36
+
37
+
38
+ _fused_fp4_bmm_reduce_repr = make_kernel_repr(
39
+ "_fused_fp4_bmm_reduce_kernel",
40
+ [
41
+ "BLOCK_SIZE_M",
42
+ "BLOCK_SIZE_N",
43
+ "ACTUAL_KSPLIT",
44
+ "MAX_KSPLIT",
45
+ ],
46
+ )
47
+
48
+
49
+ _fused_fp8_bmm_rope_cat_and_cache_mla_repr = make_kernel_repr(
50
+ "_fused_fp8_bmm_rope_cat_and_cache_mla_kernel",
51
+ [
52
+ "BLOCK_SIZE_M",
53
+ "BLOCK_SIZE_N",
54
+ "BLOCK_SIZE_K",
55
+ "GROUP_SIZE_M",
56
+ "QH_PER_KH",
57
+ "REUSE_FREQS_FRONT_PART",
58
+ "IS_NEOX",
59
+ "BLOCK_D_nope",
60
+ "BLOCK_DK_nope",
61
+ "BLOCK_D_pe",
62
+ "BLOCK_D_HALF_pe",
63
+ "TRANSPOSE_BM",
64
+ "OUTPUT_Q_NOPE_ZEROS",
65
+ "HAVE_K_SCALE",
66
+ "EVEN_K",
67
+ ],
68
+ )
69
+
70
+
71
+ @triton.jit
72
+ def _unit_rope(
73
+ x_ptrs,
74
+ cos,
75
+ sin,
76
+ d_pe_offs,
77
+ IS_NEOX: tl.constexpr,
78
+ BLOCK_D_pe: tl.constexpr,
79
+ BLOCK_D_HALF_pe: tl.constexpr,
80
+ ):
81
+ """Apply RoPE to a vector. Copied from fused_kv_cache.py"""
82
+ x_pe = tl.load(x_ptrs)
83
+
84
+ if IS_NEOX:
85
+ x_rotated_mask = d_pe_offs < BLOCK_D_HALF_pe
86
+ x_pe_rotated = _get_neox_rotated_x_1D(
87
+ x_pe, x_rotated_mask, BLOCK_D_pe, BLOCK_D_HALF_pe
88
+ )
89
+ else:
90
+ x_rotated_mask = d_pe_offs % 2 == 0
91
+ x_pe_rotated = _get_gptj_rotated_x_1D(
92
+ x_pe, x_rotated_mask, BLOCK_D_pe, BLOCK_D_HALF_pe
93
+ )
94
+
95
+ x_pe = x_pe * cos + x_pe_rotated * sin
96
+ return x_pe
97
+
98
+
99
+ @triton.heuristics(
100
+ {
101
+ "EVEN_K": lambda args: (args["K"] % (args["BLOCK_SIZE_K"] // 2) == 0)
102
+ and (args["SPLITK_BLOCK_SIZE"] % args["BLOCK_SIZE_K"] == 0)
103
+ and (args["K"] % (args["SPLITK_BLOCK_SIZE"] // 2) == 0),
104
+ "GRID_MN": lambda args: triton.cdiv(args["M"], args["BLOCK_SIZE_M"])
105
+ * triton.cdiv(args["N"], args["BLOCK_SIZE_N"]),
106
+ }
107
+ )
108
+ @triton.jit(repr=_fused_fp4_bmm_rope_cat_and_cache_mla_repr)
109
+ def _fused_fp4_bmm_rope_cat_and_cache_mla_kernel(
110
+ a_ptr,
111
+ b_ptr,
112
+ b_scales_ptr,
113
+ c_ptr,
114
+ c_scale_ptr,
115
+ q_pe_ptr,
116
+ k_nope_ptr,
117
+ k_pe_ptr,
118
+ pos_ptr,
119
+ cos_ptr,
120
+ sin_ptr,
121
+ q_out_ptr,
122
+ decode_q_pe_out_ptr,
123
+ k_pe_out_ptr,
124
+ q_nope_zeros_out_ptr,
125
+ kv_cache_ptr,
126
+ slot_mapping_ptr,
127
+ M,
128
+ N,
129
+ K,
130
+ B,
131
+ B_slot,
132
+ num_decode_toks_for_zeros,
133
+ QH,
134
+ KH,
135
+ bmm_programs,
136
+ grid_mn,
137
+ num_pid_m,
138
+ num_pid_n,
139
+ stride_ab,
140
+ stride_am,
141
+ stride_ak,
142
+ stride_bb,
143
+ stride_bn,
144
+ stride_bk,
145
+ stride_bsb,
146
+ stride_bsn,
147
+ stride_bsk,
148
+ stride_cb,
149
+ stride_ck,
150
+ stride_cm,
151
+ stride_cn,
152
+ q_pe_stride_b,
153
+ q_pe_stride_h,
154
+ q_pe_stride_d,
155
+ k_nope_stride_b,
156
+ k_nope_stride_h,
157
+ k_nope_stride_d,
158
+ k_pe_stride_b,
159
+ k_pe_stride_h,
160
+ k_pe_stride_d,
161
+ pos_stride_b,
162
+ cos_stride_b,
163
+ cos_stride_d,
164
+ q_out_stride_b,
165
+ q_out_stride_h,
166
+ q_out_stride_d,
167
+ decode_q_pe_out_stride_b,
168
+ decode_q_pe_out_stride_h,
169
+ decode_q_pe_out_stride_d,
170
+ k_pe_out_stride_b,
171
+ k_pe_out_stride_h,
172
+ k_pe_out_stride_d,
173
+ q_nope_zeros_out_stride_b,
174
+ q_nope_zeros_out_stride_h,
175
+ q_nope_zeros_out_stride_d,
176
+ kv_cache_stride_b,
177
+ kv_cache_stride_h,
178
+ kv_cache_stride_d,
179
+ k_scale_ptr,
180
+ BLOCK_SIZE_M: tl.constexpr,
181
+ BLOCK_SIZE_N: tl.constexpr,
182
+ BLOCK_SIZE_K: tl.constexpr,
183
+ GROUP_SIZE_M: tl.constexpr,
184
+ NUM_KSPLIT: tl.constexpr,
185
+ SPLITK_BLOCK_SIZE: tl.constexpr,
186
+ QH_PER_KH: tl.constexpr,
187
+ REUSE_FREQS_FRONT_PART: tl.constexpr,
188
+ IS_NEOX: tl.constexpr,
189
+ BLOCK_D_nope: tl.constexpr,
190
+ BLOCK_DK_nope: tl.constexpr,
191
+ BLOCK_D_pe: tl.constexpr,
192
+ BLOCK_D_HALF_pe: tl.constexpr,
193
+ PRE_QUANT: tl.constexpr,
194
+ OUTPUT_Q_NOPE_ZEROS: tl.constexpr,
195
+ HAVE_Y_SCALE: tl.constexpr,
196
+ HAVE_K_SCALE: tl.constexpr,
197
+ EVEN_K: tl.constexpr,
198
+ GRID_MN: tl.constexpr,
199
+ cache_modifier: tl.constexpr = ".ca",
200
+ ):
201
+ """
202
+ Fused kernel for FP4 BMM + RoPE + KV cache write.
203
+
204
+ These are INDEPENDENT operations fused to reduce kernel launch overhead:
205
+ - BMM writes to c_ptr (either q_out[:, :, :kv_lora_rank] or y_pp for split-K)
206
+ - RoPE writes to q_out[:, :, kv_lora_rank:] and handles KV cache
207
+
208
+ Grid structure:
209
+ - Phase 1: pid < bmm_programs → BMM (tiled over heads, K-splits, M, N)
210
+ - Phase 2: pid in [bmm_programs, bmm_programs + B*QH) → RoPE + KV cache for decode
211
+ - Phase 3: pid >= bmm_programs + B*QH → KV cache only for prefill tokens
212
+ """
213
+
214
+ pid = tl.program_id(0)
215
+ tl.assume(pid >= 0)
216
+
217
+ rope_start = bmm_programs
218
+ prefill_start = bmm_programs + B * QH
219
+
220
+ if pid < bmm_programs:
221
+ tl.assume(stride_ab > 0)
222
+ tl.assume(stride_am > 0)
223
+ tl.assume(stride_ak > 0)
224
+ tl.assume(stride_bb > 0)
225
+ tl.assume(stride_bk > 0)
226
+ tl.assume(stride_bn > 0)
227
+ tl.assume(stride_cb > 0)
228
+ tl.assume(stride_cm > 0)
229
+ tl.assume(stride_cn > 0)
230
+ tl.assume(stride_bsb > 0)
231
+ tl.assume(stride_bsk > 0)
232
+ tl.assume(stride_bsn > 0)
233
+
234
+ stride_ab_i64 = tl.cast(stride_ab, tl.int64)
235
+ stride_bb_i64 = tl.cast(stride_bb, tl.int64)
236
+ tl.cast(stride_cb, tl.int64)
237
+ stride_bsb_i64 = tl.cast(stride_bsb, tl.int64)
238
+
239
+ SCALE_GROUP_SIZE: tl.constexpr = 32
240
+
241
+ if HAVE_Y_SCALE:
242
+ c_scale = tl.load(c_scale_ptr)
243
+ else:
244
+ c_scale = 1
245
+ c_scale_rcprl = (1 / c_scale).to(tl.float32)
246
+
247
+ pid_head = pid // (NUM_KSPLIT * GRID_MN)
248
+ pid_unified = pid % (NUM_KSPLIT * GRID_MN)
249
+ pid_k = pid_unified % NUM_KSPLIT
250
+ pid_tile = pid_unified // NUM_KSPLIT
251
+
252
+ pid_head_i64 = tl.cast(pid_head, tl.int64)
253
+
254
+ if NUM_KSPLIT == 1:
255
+ # remap_xcd(pid_tile, GRID_MN)
256
+ pid_m, pid_n = pid_grid(
257
+ pid_tile, num_pid_m, num_pid_n, GROUP_SIZE_M=GROUP_SIZE_M
258
+ )
259
+ else:
260
+ pid_m = pid_tile // num_pid_n
261
+ pid_n = pid_tile % num_pid_n
262
+
263
+ tl.assume(pid_head >= 0)
264
+ tl.assume(pid_m >= 0)
265
+ tl.assume(pid_n >= 0)
266
+
267
+ if (pid_k * SPLITK_BLOCK_SIZE // 2) < K:
268
+ num_k_iter = tl.cdiv(SPLITK_BLOCK_SIZE // 2, BLOCK_SIZE_K // 2)
269
+
270
+ offs_k_bf16 = tl.arange(0, BLOCK_SIZE_K)
271
+ offs_k_split_bf16 = pid_k * SPLITK_BLOCK_SIZE + offs_k_bf16
272
+ offs_am = (pid_m * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M)) % M
273
+ a_ptrs = a_ptr + (
274
+ pid_head_i64 * stride_ab_i64
275
+ + offs_am[:, None] * stride_am
276
+ + offs_k_split_bf16[None, :] * stride_ak
277
+ )
278
+
279
+ offs_k = tl.arange(0, BLOCK_SIZE_K // 2)
280
+ offs_k_split = pid_k * (SPLITK_BLOCK_SIZE // 2) + offs_k
281
+ offs_bn = (pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)) % N
282
+ b_ptrs = b_ptr + (
283
+ pid_head_i64 * stride_bb_i64
284
+ + offs_k_split[:, None] * stride_bk
285
+ + offs_bn[None, :] * stride_bn
286
+ )
287
+
288
+ offs_ks = (pid_k * (SPLITK_BLOCK_SIZE // SCALE_GROUP_SIZE)) + tl.arange(
289
+ 0, BLOCK_SIZE_K // SCALE_GROUP_SIZE
290
+ )
291
+ b_scale_ptrs = (
292
+ b_scales_ptr
293
+ + pid_head_i64 * stride_bsb_i64
294
+ + offs_bn[:, None] * stride_bsn
295
+ + offs_ks[None, :] * stride_bsk
296
+ )
297
+
298
+ accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)
299
+
300
+ for k_idx in range(pid_k * num_k_iter, (pid_k + 1) * num_k_iter):
301
+ b_scales = tl.load(b_scale_ptrs)
302
+
303
+ if EVEN_K:
304
+ a_bf16 = tl.load(a_ptrs)
305
+ b = tl.load(b_ptrs, cache_modifier=cache_modifier)
306
+ else:
307
+ a_bf16 = tl.load(
308
+ a_ptrs,
309
+ mask=offs_k_bf16[None, :] < K - k_idx * BLOCK_SIZE_K,
310
+ other=0,
311
+ )
312
+ b = tl.load(
313
+ b_ptrs,
314
+ mask=offs_k[:, None] < K - k_idx * (BLOCK_SIZE_K // 2),
315
+ other=0,
316
+ )
317
+
318
+ if PRE_QUANT:
319
+ a, a_scales = _mxfp4_quant_op(
320
+ a_bf16, BLOCK_SIZE_K, BLOCK_SIZE_M, SCALE_GROUP_SIZE
321
+ )
322
+
323
+ accumulator = tl.dot_scaled(
324
+ a, a_scales, "e2m1", b, b_scales, "e2m1", acc=accumulator
325
+ )
326
+
327
+ a_ptrs += BLOCK_SIZE_K * stride_ak
328
+ b_ptrs += (BLOCK_SIZE_K // 2) * stride_bk
329
+ b_scale_ptrs += (BLOCK_SIZE_K // SCALE_GROUP_SIZE) * stride_bsk
330
+
331
+ if HAVE_Y_SCALE:
332
+ accumulator = accumulator * c_scale_rcprl
333
+
334
+ c = accumulator.to(c_ptr.type.element_ty)
335
+
336
+ offs_cm = pid_m * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M).to(tl.int64)
337
+ offs_cn = pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N).to(tl.int64)
338
+
339
+ if NUM_KSPLIT == 1:
340
+ c_ptrs = (
341
+ c_ptr
342
+ + pid_head_i64 * stride_cb
343
+ + offs_cm[:, None] * stride_cm
344
+ + offs_cn[None, :] * stride_cn
345
+ )
346
+ else:
347
+ c_ptrs = (
348
+ c_ptr
349
+ + pid_head_i64 * stride_cb
350
+ + pid_k * stride_ck
351
+ + offs_cm[:, None] * stride_cm
352
+ + offs_cn[None, :] * stride_cn
353
+ )
354
+
355
+ c_mask = (offs_cm[:, None] < M) & (offs_cn[None, :] < N)
356
+ tl.store(c_ptrs, c, mask=c_mask)
357
+
358
+ elif pid < prefill_start:
359
+ pid_adjusted = pid - rope_start
360
+ pid_b = pid_adjusted // QH
361
+ pid_hq = pid_adjusted % QH
362
+
363
+ tl.assume(pid_b >= 0)
364
+ tl.assume(pid_hq >= 0)
365
+
366
+ dk_nope_offs = tl.arange(0, BLOCK_DK_nope).to(tl.int64)
367
+ d_pe_offs = tl.arange(0, BLOCK_D_pe).to(tl.int64)
368
+
369
+ if REUSE_FREQS_FRONT_PART:
370
+ if IS_NEOX:
371
+ d_cos_offs = d_pe_offs
372
+ d_cos_offs = tl.where(
373
+ (d_cos_offs >= BLOCK_D_HALF_pe) & (d_cos_offs < BLOCK_D_pe),
374
+ d_cos_offs - BLOCK_D_HALF_pe,
375
+ d_cos_offs,
376
+ ).to(d_cos_offs.dtype)
377
+ else:
378
+ d_cos_offs = d_pe_offs // 2
379
+ else:
380
+ d_cos_offs = d_pe_offs
381
+
382
+ pos = tl.load(pos_ptr + pid_b * pos_stride_b)
383
+ cos_offs = pos * cos_stride_b + d_cos_offs * cos_stride_d
384
+ cos = tl.load(cos_ptr + cos_offs)
385
+ sin = tl.load(sin_ptr + cos_offs)
386
+
387
+ q_pe_ptrs = (
388
+ q_pe_ptr
389
+ + pid_b * q_pe_stride_b
390
+ + pid_hq * q_pe_stride_h
391
+ + d_pe_offs * q_pe_stride_d
392
+ )
393
+ q_pe = _unit_rope(
394
+ q_pe_ptrs,
395
+ cos,
396
+ sin,
397
+ d_pe_offs,
398
+ IS_NEOX,
399
+ BLOCK_D_pe,
400
+ BLOCK_D_HALF_pe,
401
+ )
402
+
403
+ q_out_base = q_out_ptr + pid_b * q_out_stride_b + pid_hq * q_out_stride_h
404
+ tl.store(
405
+ q_out_base + (d_pe_offs + BLOCK_D_nope) * q_out_stride_d,
406
+ q_pe.to(q_out_ptr.dtype.element_ty),
407
+ )
408
+
409
+ if pid_adjusted < num_decode_toks_for_zeros * QH:
410
+ decode_q_pe_out_ptrs = (
411
+ decode_q_pe_out_ptr
412
+ + pid_b * decode_q_pe_out_stride_b
413
+ + pid_hq * decode_q_pe_out_stride_h
414
+ + d_pe_offs * decode_q_pe_out_stride_d
415
+ )
416
+ tl.store(
417
+ decode_q_pe_out_ptrs, q_pe.to(decode_q_pe_out_ptr.dtype.element_ty)
418
+ )
419
+
420
+ if OUTPUT_Q_NOPE_ZEROS:
421
+ if pid_adjusted < num_decode_toks_for_zeros * QH:
422
+ z = tl.zeros(
423
+ (BLOCK_DK_nope,), dtype=q_nope_zeros_out_ptr.dtype.element_ty
424
+ )
425
+ tl.store(
426
+ q_nope_zeros_out_ptr
427
+ + pid_b * q_nope_zeros_out_stride_b
428
+ + pid_hq * q_nope_zeros_out_stride_h
429
+ + dk_nope_offs * q_nope_zeros_out_stride_d,
430
+ z,
431
+ )
432
+
433
+ if pid_hq % QH_PER_KH == 0:
434
+ pid_slot = tl.load(slot_mapping_ptr + pid_b).to(tl.int64)
435
+ if pid_slot >= 0:
436
+ if HAVE_K_SCALE:
437
+ k_scale = tl.load(k_scale_ptr)
438
+ else:
439
+ k_scale = 1
440
+
441
+ pid_hk = pid_hq // QH_PER_KH
442
+
443
+ k_nope_ptrs = (
444
+ k_nope_ptr
445
+ + pid_b * k_nope_stride_b
446
+ + pid_hk * k_nope_stride_h
447
+ + dk_nope_offs * k_nope_stride_d
448
+ )
449
+ k_nope = tl.load(k_nope_ptrs)
450
+
451
+ k_pe_load_ptrs = (
452
+ k_pe_ptr
453
+ + pid_b * k_pe_stride_b
454
+ + pid_hk * k_pe_stride_h
455
+ + d_pe_offs * k_pe_stride_d
456
+ )
457
+ k_pe = _unit_rope(
458
+ k_pe_load_ptrs,
459
+ cos,
460
+ sin,
461
+ d_pe_offs,
462
+ IS_NEOX,
463
+ BLOCK_D_pe,
464
+ BLOCK_D_HALF_pe,
465
+ )
466
+
467
+ k_pe_out_ptrs = (
468
+ k_pe_out_ptr
469
+ + pid_b * k_pe_out_stride_b
470
+ + pid_hk * k_pe_out_stride_h
471
+ + d_pe_offs * k_pe_out_stride_d
472
+ )
473
+ tl.store(k_pe_out_ptrs, k_pe.to(k_pe_out_ptr.dtype.element_ty))
474
+
475
+ k_scale_rcprl = (1 / k_scale).to(tl.float32)
476
+ k_nope_scaled = (k_nope.to(tl.float32) * k_scale_rcprl).to(
477
+ kv_cache_ptr.dtype.element_ty
478
+ )
479
+ k_pe_scaled = (k_pe.to(tl.float32) * k_scale_rcprl).to(
480
+ kv_cache_ptr.dtype.element_ty
481
+ )
482
+
483
+ kv_cache_ptrs = (
484
+ kv_cache_ptr
485
+ + pid_slot * kv_cache_stride_b
486
+ + pid_hk * kv_cache_stride_h
487
+ )
488
+
489
+ tl.store(
490
+ kv_cache_ptrs + dk_nope_offs * kv_cache_stride_d, k_nope_scaled
491
+ )
492
+ tl.store(
493
+ kv_cache_ptrs + (d_pe_offs + BLOCK_DK_nope) * kv_cache_stride_d,
494
+ k_pe_scaled,
495
+ )
496
+
497
+ else:
498
+ pid_adjusted = pid - prefill_start
499
+ pid_b = pid_adjusted // KH + B
500
+ pid_hk = pid_adjusted % KH
501
+
502
+ if pid_b < B_slot:
503
+ pid_slot = tl.load(slot_mapping_ptr + pid_b).to(tl.int64)
504
+
505
+ if pid_slot >= 0:
506
+ if HAVE_K_SCALE:
507
+ k_scale = tl.load(k_scale_ptr)
508
+ else:
509
+ k_scale = 1
510
+
511
+ dk_nope_offs = tl.arange(0, BLOCK_DK_nope).to(tl.int64)
512
+ d_pe_offs = tl.arange(0, BLOCK_D_pe).to(tl.int64)
513
+
514
+ k_nope_ptrs = (
515
+ k_nope_ptr
516
+ + pid_b * k_nope_stride_b
517
+ + pid_hk * k_nope_stride_h
518
+ + dk_nope_offs * k_nope_stride_d
519
+ )
520
+ k_nope = tl.load(k_nope_ptrs)
521
+
522
+ k_pe_load_ptrs = (
523
+ k_pe_ptr
524
+ + pid_b * k_pe_stride_b
525
+ + pid_hk * k_pe_stride_h
526
+ + d_pe_offs * k_pe_stride_d
527
+ )
528
+ k_pe = tl.load(k_pe_load_ptrs)
529
+
530
+ k_pe_out_ptrs = (
531
+ k_pe_out_ptr
532
+ + pid_b * k_pe_out_stride_b
533
+ + pid_hk * k_pe_out_stride_h
534
+ + d_pe_offs * k_pe_out_stride_d
535
+ )
536
+ tl.store(k_pe_out_ptrs, k_pe.to(k_pe_out_ptr.dtype.element_ty))
537
+
538
+ k_scale_rcprl = (1 / k_scale).to(tl.float32)
539
+ k_nope_scaled = (k_nope.to(tl.float32) * k_scale_rcprl).to(
540
+ kv_cache_ptr.dtype.element_ty
541
+ )
542
+ k_pe_scaled = (k_pe.to(tl.float32) * k_scale_rcprl).to(
543
+ kv_cache_ptr.dtype.element_ty
544
+ )
545
+
546
+ kv_cache_ptrs = (
547
+ kv_cache_ptr
548
+ + pid_slot * kv_cache_stride_b
549
+ + pid_hk * kv_cache_stride_h
550
+ )
551
+
552
+ tl.store(
553
+ kv_cache_ptrs + dk_nope_offs * kv_cache_stride_d, k_nope_scaled
554
+ )
555
+ tl.store(
556
+ kv_cache_ptrs + (d_pe_offs + BLOCK_DK_nope) * kv_cache_stride_d,
557
+ k_pe_scaled,
558
+ )
559
+
560
+
561
+ @triton.jit(repr=_fused_fp4_bmm_reduce_repr)
562
+ def _fused_fp4_bmm_reduce_kernel(
563
+ c_in_ptr,
564
+ c_out_ptr,
565
+ M,
566
+ N,
567
+ QH,
568
+ stride_c_in_b,
569
+ stride_c_in_k,
570
+ stride_c_in_m,
571
+ stride_c_in_n,
572
+ stride_c_out_b,
573
+ stride_c_out_h,
574
+ stride_c_out_d,
575
+ BLOCK_SIZE_M: tl.constexpr,
576
+ BLOCK_SIZE_N: tl.constexpr,
577
+ ACTUAL_KSPLIT: tl.constexpr,
578
+ MAX_KSPLIT: tl.constexpr,
579
+ TRANSPOSE_BM: tl.constexpr,
580
+ ):
581
+ """Reduce kernel for split-K BMM results."""
582
+ pid_head = tl.program_id(axis=0)
583
+ pid_m = tl.program_id(axis=1)
584
+ pid_n = tl.program_id(axis=2)
585
+
586
+ offs_m = (pid_m * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M)) % M
587
+ offs_n = (pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)) % N
588
+ offs_k = tl.arange(0, MAX_KSPLIT)
589
+
590
+ c_in_ptrs = (
591
+ c_in_ptr
592
+ + pid_head * stride_c_in_b
593
+ + (offs_k[:, None, None] * stride_c_in_k)
594
+ + (offs_m[None, :, None] * stride_c_in_m)
595
+ + (offs_n[None, None, :] * stride_c_in_n)
596
+ )
597
+
598
+ if ACTUAL_KSPLIT == MAX_KSPLIT:
599
+ c = tl.load(c_in_ptrs)
600
+ else:
601
+ c = tl.load(c_in_ptrs, mask=offs_k[:, None, None] < ACTUAL_KSPLIT)
602
+
603
+ c = tl.sum(c, axis=0)
604
+ c = c.to(c_out_ptr.type.element_ty)
605
+
606
+ if TRANSPOSE_BM:
607
+ c_out_ptrs = (
608
+ c_out_ptr
609
+ + (offs_m[:, None] * stride_c_out_b)
610
+ + (pid_head * stride_c_out_h)
611
+ + (offs_n[None, :] * stride_c_out_d)
612
+ )
613
+ else:
614
+ c_out_ptrs = (
615
+ c_out_ptr
616
+ + (pid_head * stride_c_out_b)
617
+ + (offs_m[:, None] * stride_c_out_h)
618
+ + (offs_n[None, :] * stride_c_out_d)
619
+ )
620
+
621
+ c_mask = (offs_m[:, None] < M) & (offs_n[None, :] < N)
622
+ tl.store(c_out_ptrs, c, mask=c_mask)
623
+
624
+
625
+ @triton.heuristics(
626
+ {
627
+ "EVEN_K": lambda args: args["K"] % args["BLOCK_SIZE_K"] == 0,
628
+ "GRID_MN": lambda args: triton.cdiv(args["M"], args["BLOCK_SIZE_M"])
629
+ * triton.cdiv(args["N"], args["BLOCK_SIZE_N"]),
630
+ }
631
+ )
632
+ @triton.jit(repr=_fused_fp8_bmm_rope_cat_and_cache_mla_repr)
633
+ def _fused_fp8_bmm_rope_cat_and_cache_mla_kernel(
634
+ a_ptr,
635
+ b_ptr,
636
+ b_scale_ptr,
637
+ c_ptr,
638
+ q_pe_ptr,
639
+ k_nope_ptr,
640
+ k_pe_ptr,
641
+ pos_ptr,
642
+ cos_ptr,
643
+ sin_ptr,
644
+ decode_q_pe_out_ptr,
645
+ k_pe_out_ptr,
646
+ q_nope_zeros_out_ptr,
647
+ kv_cache_ptr,
648
+ slot_mapping_ptr,
649
+ M,
650
+ N,
651
+ K,
652
+ B,
653
+ B_slot,
654
+ num_decode_toks_for_zeros,
655
+ QH,
656
+ KH,
657
+ bmm_programs,
658
+ grid_mn,
659
+ num_pid_m,
660
+ num_pid_n,
661
+ stride_ab,
662
+ stride_am,
663
+ stride_ak,
664
+ stride_bb,
665
+ stride_bk,
666
+ stride_bn,
667
+ stride_cb,
668
+ stride_cm,
669
+ stride_cn,
670
+ q_pe_stride_b,
671
+ q_pe_stride_h,
672
+ q_pe_stride_d,
673
+ k_nope_stride_b,
674
+ k_nope_stride_h,
675
+ k_nope_stride_d,
676
+ k_pe_stride_b,
677
+ k_pe_stride_h,
678
+ k_pe_stride_d,
679
+ pos_stride_b,
680
+ cos_stride_b,
681
+ cos_stride_d,
682
+ q_out_stride_b,
683
+ q_out_stride_h,
684
+ q_out_stride_d,
685
+ decode_q_pe_out_stride_b,
686
+ decode_q_pe_out_stride_h,
687
+ decode_q_pe_out_stride_d,
688
+ k_pe_out_stride_b,
689
+ k_pe_out_stride_h,
690
+ k_pe_out_stride_d,
691
+ q_nope_zeros_out_stride_b,
692
+ q_nope_zeros_out_stride_h,
693
+ q_nope_zeros_out_stride_d,
694
+ kv_cache_stride_b,
695
+ kv_cache_stride_h,
696
+ kv_cache_stride_d,
697
+ k_scale_ptr,
698
+ BLOCK_SIZE_M: tl.constexpr,
699
+ BLOCK_SIZE_N: tl.constexpr,
700
+ BLOCK_SIZE_K: tl.constexpr,
701
+ GROUP_SIZE_M: tl.constexpr,
702
+ QH_PER_KH: tl.constexpr,
703
+ REUSE_FREQS_FRONT_PART: tl.constexpr,
704
+ IS_NEOX: tl.constexpr,
705
+ BLOCK_D_nope: tl.constexpr,
706
+ BLOCK_DK_nope: tl.constexpr,
707
+ BLOCK_D_pe: tl.constexpr,
708
+ BLOCK_D_HALF_pe: tl.constexpr,
709
+ OUTPUT_Q_NOPE_ZEROS: tl.constexpr,
710
+ HAVE_K_SCALE: tl.constexpr,
711
+ DTYPE_MAX: tl.constexpr,
712
+ EVEN_K: tl.constexpr,
713
+ GRID_MN: tl.constexpr,
714
+ cache_modifier: tl.constexpr = ".ca",
715
+ ):
716
+ """
717
+ Fused kernel for FP8 BMM + RoPE + KV cache write.
718
+
719
+ These are INDEPENDENT operations fused to reduce kernel launch overhead:
720
+ - BMM writes to q_out[:, :, :kv_lora_rank]
721
+ - RoPE writes to q_out[:, :, kv_lora_rank:] and handles KV cache
722
+
723
+ Note: FP8 does not support split-K.
724
+
725
+ Grid structure:
726
+ - Phase 1: pid < bmm_programs → BMM (tiled over heads, M, N)
727
+ - Phase 2: pid in [bmm_programs, bmm_programs + B*QH) → RoPE + KV cache for decode
728
+ - Phase 3: pid >= bmm_programs + B*QH → KV cache only for prefill tokens
729
+ """
730
+
731
+ pid = tl.program_id(0)
732
+ tl.assume(pid >= 0)
733
+
734
+ rope_start = bmm_programs
735
+ prefill_start = bmm_programs + B * QH
736
+
737
+ if pid < bmm_programs:
738
+ stride_ab_i64 = tl.cast(stride_ab, tl.int64)
739
+ stride_am_i64 = tl.cast(stride_am, tl.int64)
740
+ stride_ak_i64 = tl.cast(stride_ak, tl.int64)
741
+ stride_bb_i64 = tl.cast(stride_bb, tl.int64)
742
+ stride_bk_i64 = tl.cast(stride_bk, tl.int64)
743
+ stride_bn_i64 = tl.cast(stride_bn, tl.int64)
744
+ stride_cb_i64 = tl.cast(stride_cb, tl.int64)
745
+ stride_cm_i64 = tl.cast(stride_cm, tl.int64)
746
+ stride_cn_i64 = tl.cast(stride_cn, tl.int64)
747
+
748
+ tl.assume(stride_ab_i64 > 0)
749
+ tl.assume(stride_am_i64 > 0)
750
+ tl.assume(stride_ak_i64 > 0)
751
+ tl.assume(stride_bb_i64 > 0)
752
+ tl.assume(stride_bk_i64 > 0)
753
+ tl.assume(stride_bn_i64 > 0)
754
+ tl.assume(stride_cb_i64 > 0)
755
+ tl.assume(stride_cm_i64 > 0)
756
+ tl.assume(stride_cn_i64 > 0)
757
+
758
+ pid_head = pid // GRID_MN
759
+ pid_tile = pid % GRID_MN
760
+
761
+ pid_head_i64 = tl.cast(pid_head, tl.int64)
762
+
763
+ if GROUP_SIZE_M == 1:
764
+ pid_m = pid_tile // num_pid_n
765
+ pid_n = pid_tile % num_pid_n
766
+ else:
767
+ num_pid_in_group = GROUP_SIZE_M * num_pid_n
768
+ group_id = pid_tile // num_pid_in_group
769
+ first_pid_m = group_id * GROUP_SIZE_M
770
+ group_size_m = tl.minimum(num_pid_m - first_pid_m, GROUP_SIZE_M)
771
+ pid_m = first_pid_m + (pid_tile % group_size_m)
772
+ pid_n = (pid_tile % num_pid_in_group) // group_size_m
773
+
774
+ pid_m_i64 = tl.cast(pid_m, tl.int64)
775
+ pid_n_i64 = tl.cast(pid_n, tl.int64)
776
+
777
+ tl.assume(pid_m_i64 >= 0)
778
+ tl.assume(pid_n_i64 >= 0)
779
+ tl.assume(pid_head_i64 >= 0)
780
+
781
+ offs_k = tl.arange(0, BLOCK_SIZE_K)
782
+ offs_am = (pid_m * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M)) % M
783
+ offs_bn = (pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)) % N
784
+
785
+ a_ptrs = a_ptr + (
786
+ pid_head_i64 * stride_ab_i64
787
+ + offs_am[:, None] * stride_am_i64
788
+ + offs_k[None, :] * stride_ak_i64
789
+ )
790
+ b_ptrs = b_ptr + (
791
+ pid_head_i64 * stride_bb_i64
792
+ + offs_k[:, None] * stride_bk_i64
793
+ + offs_bn[None, :] * stride_bn_i64
794
+ )
795
+
796
+ one_over_DTYPE_MAX = 1.0 / DTYPE_MAX
797
+ b_scale = tl.load(b_scale_ptr)
798
+
799
+ accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)
800
+
801
+ for k_idx in range(0, tl.cdiv(K, BLOCK_SIZE_K)):
802
+ if EVEN_K:
803
+ a = tl.load(a_ptrs)
804
+ b = tl.load(b_ptrs, cache_modifier=cache_modifier)
805
+ else:
806
+ a = tl.load(
807
+ a_ptrs, mask=offs_k[None, :] < K - k_idx * BLOCK_SIZE_K, other=0.0
808
+ )
809
+ b = tl.load(
810
+ b_ptrs, mask=offs_k[:, None] < K - k_idx * BLOCK_SIZE_K, other=0.0
811
+ )
812
+
813
+ # Per-token group quantization
814
+ m = tl.maximum(tl.max(tl.abs(a), axis=-1), 1e-10)[:, None]
815
+ a_scale = m.to(tl.float32) * one_over_DTYPE_MAX
816
+ a_scale_recip = 1.0 / a_scale
817
+ a = tl.clamp(a * a_scale_recip, -DTYPE_MAX, DTYPE_MAX).to(
818
+ b_ptr.dtype.element_ty
819
+ )
820
+
821
+ accumulator += tl.dot(a, b) * a_scale
822
+
823
+ a_ptrs += BLOCK_SIZE_K * stride_ak_i64
824
+ b_ptrs += BLOCK_SIZE_K * stride_bk_i64
825
+
826
+ accumulator *= b_scale
827
+
828
+ c = accumulator.to(c_ptr.type.element_ty)
829
+
830
+ offs_cm = pid_m * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M)
831
+ offs_cn = pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)
832
+
833
+ c_ptrs = (
834
+ c_ptr
835
+ + pid_head_i64 * stride_cb_i64
836
+ + offs_cm[:, None] * stride_cm_i64
837
+ + offs_cn[None, :] * stride_cn_i64
838
+ )
839
+
840
+ c_mask = (offs_cm[:, None] < M) & (offs_cn[None, :] < N)
841
+ tl.store(c_ptrs, c, mask=c_mask)
842
+
843
+ elif pid < prefill_start:
844
+ pid_adjusted = pid - rope_start
845
+ pid_b = pid_adjusted // QH
846
+ pid_hq = pid_adjusted % QH
847
+
848
+ tl.assume(pid_b >= 0)
849
+ tl.assume(pid_hq >= 0)
850
+
851
+ dk_nope_offs = tl.arange(0, BLOCK_DK_nope).to(tl.int64)
852
+ d_pe_offs = tl.arange(0, BLOCK_D_pe).to(tl.int64)
853
+
854
+ if REUSE_FREQS_FRONT_PART:
855
+ if IS_NEOX:
856
+ d_cos_offs = d_pe_offs
857
+ d_cos_offs = tl.where(
858
+ (d_cos_offs >= BLOCK_D_HALF_pe) & (d_cos_offs < BLOCK_D_pe),
859
+ d_cos_offs - BLOCK_D_HALF_pe,
860
+ d_cos_offs,
861
+ ).to(d_cos_offs.dtype)
862
+ else:
863
+ d_cos_offs = d_pe_offs // 2
864
+ else:
865
+ d_cos_offs = d_pe_offs
866
+
867
+ pos = tl.load(pos_ptr + pid_b * pos_stride_b)
868
+ cos_offs = pos * cos_stride_b + d_cos_offs * cos_stride_d
869
+ cos = tl.load(cos_ptr + cos_offs)
870
+ sin = tl.load(sin_ptr + cos_offs)
871
+
872
+ q_pe_ptrs = (
873
+ q_pe_ptr
874
+ + pid_b * q_pe_stride_b
875
+ + pid_hq * q_pe_stride_h
876
+ + d_pe_offs * q_pe_stride_d
877
+ )
878
+ q_pe = _unit_rope(
879
+ q_pe_ptrs,
880
+ cos,
881
+ sin,
882
+ d_pe_offs,
883
+ IS_NEOX,
884
+ BLOCK_D_pe,
885
+ BLOCK_D_HALF_pe,
886
+ )
887
+
888
+ q_out_base = c_ptr + pid_b * q_out_stride_b + pid_hq * q_out_stride_h
889
+ tl.store(
890
+ q_out_base + (d_pe_offs + BLOCK_D_nope) * q_out_stride_d,
891
+ q_pe.to(c_ptr.dtype.element_ty),
892
+ )
893
+
894
+ if pid_adjusted < num_decode_toks_for_zeros * QH:
895
+ decode_q_pe_out_ptrs = (
896
+ decode_q_pe_out_ptr
897
+ + pid_b * decode_q_pe_out_stride_b
898
+ + pid_hq * decode_q_pe_out_stride_h
899
+ + d_pe_offs * decode_q_pe_out_stride_d
900
+ )
901
+ tl.store(
902
+ decode_q_pe_out_ptrs, q_pe.to(decode_q_pe_out_ptr.dtype.element_ty)
903
+ )
904
+
905
+ if OUTPUT_Q_NOPE_ZEROS:
906
+ if pid_adjusted < num_decode_toks_for_zeros * QH:
907
+ z = tl.zeros(
908
+ (BLOCK_DK_nope,), dtype=q_nope_zeros_out_ptr.dtype.element_ty
909
+ )
910
+ tl.store(
911
+ q_nope_zeros_out_ptr
912
+ + pid_b * q_nope_zeros_out_stride_b
913
+ + pid_hq * q_nope_zeros_out_stride_h
914
+ + dk_nope_offs * q_nope_zeros_out_stride_d,
915
+ z,
916
+ )
917
+
918
+ if pid_hq % QH_PER_KH == 0:
919
+ pid_slot = tl.load(slot_mapping_ptr + pid_b).to(tl.int64)
920
+ if pid_slot >= 0:
921
+ if HAVE_K_SCALE:
922
+ k_scale = tl.load(k_scale_ptr)
923
+ else:
924
+ k_scale = 1
925
+
926
+ pid_hk = pid_hq // QH_PER_KH
927
+
928
+ k_nope_ptrs = (
929
+ k_nope_ptr
930
+ + pid_b * k_nope_stride_b
931
+ + pid_hk * k_nope_stride_h
932
+ + dk_nope_offs * k_nope_stride_d
933
+ )
934
+ k_nope = tl.load(k_nope_ptrs)
935
+
936
+ k_pe_load_ptrs = (
937
+ k_pe_ptr
938
+ + pid_b * k_pe_stride_b
939
+ + pid_hk * k_pe_stride_h
940
+ + d_pe_offs * k_pe_stride_d
941
+ )
942
+ k_pe = _unit_rope(
943
+ k_pe_load_ptrs,
944
+ cos,
945
+ sin,
946
+ d_pe_offs,
947
+ IS_NEOX,
948
+ BLOCK_D_pe,
949
+ BLOCK_D_HALF_pe,
950
+ )
951
+
952
+ k_pe_out_ptrs = (
953
+ k_pe_out_ptr
954
+ + pid_b * k_pe_out_stride_b
955
+ + pid_hk * k_pe_out_stride_h
956
+ + d_pe_offs * k_pe_out_stride_d
957
+ )
958
+ tl.store(k_pe_out_ptrs, k_pe.to(k_pe_out_ptr.dtype.element_ty))
959
+
960
+ k_scale_rcprl = (1 / k_scale).to(tl.float32)
961
+ k_nope_scaled = (k_nope.to(tl.float32) * k_scale_rcprl).to(
962
+ kv_cache_ptr.dtype.element_ty
963
+ )
964
+ k_pe_scaled = (k_pe.to(tl.float32) * k_scale_rcprl).to(
965
+ kv_cache_ptr.dtype.element_ty
966
+ )
967
+
968
+ kv_cache_ptrs = (
969
+ kv_cache_ptr
970
+ + pid_slot * kv_cache_stride_b
971
+ + pid_hk * kv_cache_stride_h
972
+ )
973
+
974
+ tl.store(
975
+ kv_cache_ptrs + dk_nope_offs * kv_cache_stride_d, k_nope_scaled
976
+ )
977
+ tl.store(
978
+ kv_cache_ptrs + (d_pe_offs + BLOCK_DK_nope) * kv_cache_stride_d,
979
+ k_pe_scaled,
980
+ )
981
+
982
+ else:
983
+ pid_adjusted = pid - prefill_start
984
+ pid_b = pid_adjusted // KH + B
985
+ pid_hk = pid_adjusted % KH
986
+
987
+ if pid_b < B_slot:
988
+ pid_slot = tl.load(slot_mapping_ptr + pid_b).to(tl.int64)
989
+
990
+ if pid_slot >= 0:
991
+ if HAVE_K_SCALE:
992
+ k_scale = tl.load(k_scale_ptr)
993
+ else:
994
+ k_scale = 1
995
+
996
+ dk_nope_offs = tl.arange(0, BLOCK_DK_nope).to(tl.int64)
997
+ d_pe_offs = tl.arange(0, BLOCK_D_pe).to(tl.int64)
998
+
999
+ k_nope_ptrs = (
1000
+ k_nope_ptr
1001
+ + pid_b * k_nope_stride_b
1002
+ + pid_hk * k_nope_stride_h
1003
+ + dk_nope_offs * k_nope_stride_d
1004
+ )
1005
+ k_nope = tl.load(k_nope_ptrs)
1006
+
1007
+ k_pe_load_ptrs = (
1008
+ k_pe_ptr
1009
+ + pid_b * k_pe_stride_b
1010
+ + pid_hk * k_pe_stride_h
1011
+ + d_pe_offs * k_pe_stride_d
1012
+ )
1013
+ k_pe = tl.load(k_pe_load_ptrs)
1014
+
1015
+ k_pe_out_ptrs = (
1016
+ k_pe_out_ptr
1017
+ + pid_b * k_pe_out_stride_b
1018
+ + pid_hk * k_pe_out_stride_h
1019
+ + d_pe_offs * k_pe_out_stride_d
1020
+ )
1021
+ tl.store(k_pe_out_ptrs, k_pe.to(k_pe_out_ptr.dtype.element_ty))
1022
+
1023
+ k_scale_rcprl = (1 / k_scale).to(tl.float32)
1024
+ k_nope_scaled = (k_nope.to(tl.float32) * k_scale_rcprl).to(
1025
+ kv_cache_ptr.dtype.element_ty
1026
+ )
1027
+ k_pe_scaled = (k_pe.to(tl.float32) * k_scale_rcprl).to(
1028
+ kv_cache_ptr.dtype.element_ty
1029
+ )
1030
+
1031
+ kv_cache_ptrs = (
1032
+ kv_cache_ptr
1033
+ + pid_slot * kv_cache_stride_b
1034
+ + pid_hk * kv_cache_stride_h
1035
+ )
1036
+
1037
+ tl.store(
1038
+ kv_cache_ptrs + dk_nope_offs * kv_cache_stride_d, k_nope_scaled
1039
+ )
1040
+ tl.store(
1041
+ kv_cache_ptrs + (d_pe_offs + BLOCK_DK_nope) * kv_cache_stride_d,
1042
+ k_pe_scaled,
1043
+ )
build/torch-rocm/_triton_kernels/fusions/fused_clamp_act_mul.py ADDED
@@ -0,0 +1,173 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (C) 2024-2026, Advanced Micro Devices, Inc. All rights reserved.
3
+
4
+ """Fused SwiGLU clamp + SiLU * up + optional token weights + optional per-row FP8 group quant (128).
5
+
6
+ Each program handles one row (token). ``inp`` is ``[M, 2 * N]`` with gate in the first
7
+ ``N`` columns and up in the second ``N`` (same layout as ``torch.chunk(2, dim=-1)``).
8
+ Gate clamp matches DeepSeek-V4 reference: ``clamp(gate, max=limit)`` only; up uses
9
+ ``clamp(up, min=-limit, max=limit)``. When ``HAS_QUANT`` is False the result is written
10
+ directly to ``out`` in its native dtype and no scales are produced.
11
+ """
12
+
13
+ import triton
14
+ import triton.language as tl
15
+
16
+ from ..._triton_kernels.activation import _apply_activation_from_str
17
+
18
+ from ..._triton_kernels.quant.fused_fp8_quant import _fp8_quant_op
19
+ from ...utils._triton.kernel_repr import make_kernel_repr
20
+
21
+ _fused_clamp_silu_mul_repr = make_kernel_repr(
22
+ "_fused_clamp_silu_mul_kernel",
23
+ [
24
+ "BLOCK_SIZE_N",
25
+ "QUANT_BLOCK_SIZE",
26
+ "SCALE_FMT",
27
+ "HAVE_WEIGHTS",
28
+ "WEIGHT_BROADCAST",
29
+ "HAVE_SWIGLU_CLAMP",
30
+ "HAS_QUANT",
31
+ ],
32
+ )
33
+
34
+
35
+ @triton.jit(repr=_fused_clamp_silu_mul_repr)
36
+ def _fused_clamp_silu_mul_kernel(
37
+ inp_ptr,
38
+ out_ptr,
39
+ scale_ptr,
40
+ weights_ptr,
41
+ M,
42
+ n_half,
43
+ inp_stride_m,
44
+ inp_stride_n,
45
+ out_stride_m,
46
+ out_stride_n,
47
+ scale_stride_m,
48
+ scale_stride_n,
49
+ weights_stride_m,
50
+ weights_stride_n,
51
+ swiglu_limit,
52
+ BLOCK_SIZE_N: tl.constexpr,
53
+ QUANT_BLOCK_SIZE: tl.constexpr,
54
+ SCALE_FMT: tl.constexpr,
55
+ DTYPE_MAX: tl.constexpr,
56
+ DTYPE_MIN: tl.constexpr,
57
+ HAVE_WEIGHTS: tl.constexpr,
58
+ WEIGHT_BROADCAST: tl.constexpr,
59
+ HAVE_SWIGLU_CLAMP: tl.constexpr,
60
+ HAS_QUANT: tl.constexpr,
61
+ ACTIVATION: tl.constexpr,
62
+ SHUFFLE: tl.constexpr,
63
+ SCALE_N_PAD: tl.constexpr,
64
+ ):
65
+ m_pid = tl.program_id(0)
66
+ n_offs = tl.arange(0, BLOCK_SIZE_N)
67
+ mask = n_offs < n_half
68
+
69
+ gate = tl.load(
70
+ inp_ptr + m_pid * inp_stride_m + n_offs * inp_stride_n,
71
+ mask=mask,
72
+ other=0.0,
73
+ cache_modifier=".cg",
74
+ ).to(tl.float32)
75
+ up = tl.load(
76
+ inp_ptr + m_pid * inp_stride_m + (n_half + n_offs) * inp_stride_n,
77
+ mask=mask,
78
+ other=0.0,
79
+ cache_modifier=".cg",
80
+ ).to(tl.float32)
81
+
82
+ if HAVE_SWIGLU_CLAMP:
83
+ up = tl.clamp(up, -swiglu_limit, swiglu_limit)
84
+ gate = tl.minimum(gate, swiglu_limit)
85
+
86
+ out = _apply_activation_from_str(gate, ACTIVATION) * up
87
+
88
+ if HAVE_WEIGHTS:
89
+ if WEIGHT_BROADCAST:
90
+ w = tl.load(weights_ptr + m_pid * weights_stride_m).to(tl.float32)
91
+ out = out * w
92
+ else:
93
+ w = tl.load(
94
+ weights_ptr + m_pid * weights_stride_m + n_offs * weights_stride_n,
95
+ mask=mask,
96
+ other=0.0,
97
+ cache_modifier=".cg",
98
+ ).to(tl.float32)
99
+ out = out * w
100
+
101
+ if HAS_QUANT:
102
+ if SCALE_FMT == "ue8m0":
103
+ # Per-1×QUANT_BLOCK_SIZE MXFP8 emit: fp8 e4m3 values + uint8 ue8m0
104
+ # biased-exponent scales. Mirrors the ue8m0 path used by moe_gemm_a8w4.
105
+ NUM_QB: tl.constexpr = BLOCK_SIZE_N // QUANT_BLOCK_SIZE
106
+ out_3d = tl.reshape(out, [1, NUM_QB, QUANT_BLOCK_SIZE])
107
+ abs_3d = tl.abs(out_3d)
108
+ max_val = tl.max(abs_3d, axis=2, keep_dims=True)
109
+ dequant_scale = max_val / DTYPE_MAX
110
+ # ROUND_UP via exponent: 2 ** ceil(log2(dequant_scale))
111
+ dequant_scale_exp = (
112
+ dequant_scale.to(tl.uint32, bitcast=True) + 0x007FFFFF
113
+ ) & 0x7F800000
114
+ dequant_scale_rounded = dequant_scale_exp.to(tl.float32, bitcast=True)
115
+ quant_scale = tl.where(
116
+ dequant_scale_rounded == 0, 0.0, 1.0 / dequant_scale_rounded
117
+ )
118
+ quant_tensor = out_3d * quant_scale
119
+ quant_2d = tl.reshape(quant_tensor, [1, BLOCK_SIZE_N])
120
+ out_q = tl.ravel(quant_2d)
121
+ scale_exp = (dequant_scale_exp >> 23).to(tl.uint8)
122
+ scale_exp_2d = tl.reshape(scale_exp, [1, NUM_QB])
123
+ block_scales = tl.ravel(scale_exp_2d)
124
+ else:
125
+ out_q, block_scales = _fp8_quant_op(
126
+ out, 1, BLOCK_SIZE_N, QUANT_BLOCK_SIZE, DTYPE_MAX, DTYPE_MIN
127
+ )
128
+ out_q = tl.ravel(out_q)
129
+ block_scales = tl.ravel(block_scales)
130
+
131
+ tl.store(
132
+ out_ptr + m_pid * out_stride_m + n_offs * out_stride_n,
133
+ out_q.to(out_ptr.dtype.element_ty),
134
+ mask=mask,
135
+ )
136
+
137
+ num_bs = tl.cdiv(n_half, QUANT_BLOCK_SIZE)
138
+ NUM_QB_S: tl.constexpr = BLOCK_SIZE_N // QUANT_BLOCK_SIZE
139
+ g_offs = tl.arange(0, NUM_QB_S)
140
+ if SHUFFLE:
141
+ bs_offs_0 = m_pid // 32
142
+ bs_offs_1 = m_pid % 32
143
+ bs_offs_2 = bs_offs_1 % 16
144
+ bs_offs_1 = bs_offs_1 // 16
145
+ bs_offs_3 = g_offs // 8
146
+ bs_offs_4 = g_offs % 8
147
+ bs_offs_5 = bs_offs_4 % 4
148
+ bs_offs_4 = bs_offs_4 // 4
149
+ bs_offs = (
150
+ bs_offs_1
151
+ + bs_offs_4 * 2
152
+ + bs_offs_2 * 2 * 2
153
+ + bs_offs_5 * 2 * 2 * 16
154
+ + bs_offs_3 * 2 * 2 * 16 * 4
155
+ + bs_offs_0 * 2 * 16 * SCALE_N_PAD
156
+ )
157
+ tl.store(
158
+ scale_ptr + bs_offs,
159
+ block_scales.to(scale_ptr.dtype.element_ty),
160
+ mask=g_offs < num_bs,
161
+ )
162
+ else:
163
+ tl.store(
164
+ scale_ptr + m_pid * scale_stride_m + g_offs * scale_stride_n,
165
+ block_scales.to(scale_ptr.dtype.element_ty),
166
+ mask=g_offs < num_bs,
167
+ )
168
+ else:
169
+ tl.store(
170
+ out_ptr + m_pid * out_stride_m + n_offs * out_stride_n,
171
+ out.to(out_ptr.dtype.element_ty),
172
+ mask=mask,
173
+ )
build/torch-rocm/_triton_kernels/fusions/fused_kv_cache.py ADDED
@@ -0,0 +1,1124 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import triton
2
+ import triton.language as tl
3
+ from ...rope.rope import _get_gptj_rotated_x_1D, _get_neox_rotated_x_1D
4
+ from ..._triton_kernels.kv_cache import _store_mla_kv_cache
5
+ from ..._triton_kernels.quant.quant import _nvfp4_quant_op
6
+
7
+
8
+ @triton.jit
9
+ def _store_kv_cache_kernel(
10
+ key_cache_ptr,
11
+ value_cache_ptr,
12
+ pid_t_slot,
13
+ pid_hk,
14
+ pid_b,
15
+ d_pe_offs,
16
+ k_pe,
17
+ v,
18
+ key_cache_stride_t,
19
+ key_cache_stride_h,
20
+ key_cache_stride_d,
21
+ key_cache_stride_b,
22
+ key_cache_stride_x,
23
+ value_cache_stride_t,
24
+ value_cache_stride_h,
25
+ value_cache_stride_d,
26
+ value_cache_stride_b,
27
+ value_cache_stride_x,
28
+ value_cache_stride_slot_chunk,
29
+ BLOCK_D_pe: tl.constexpr,
30
+ BLOCK_SIZE: tl.constexpr,
31
+ X_SIZE: tl.constexpr,
32
+ FLASH_LAYOUT: tl.constexpr,
33
+ VALUE_SHUFFLE_LAYOUT: tl.constexpr,
34
+ SCALE_K_WIDTH: tl.constexpr,
35
+ ):
36
+ if key_cache_ptr.dtype.element_ty == tl.uint8:
37
+ K_WIDTH: tl.constexpr = 16
38
+ NVFP4_QUANT_BLOCK_SIZE: tl.constexpr = 16
39
+ BLOCK_D_pe_STORE: tl.constexpr = BLOCK_D_pe // 2
40
+ BLOCK_D_pe_scales: tl.constexpr = BLOCK_D_pe // NVFP4_QUANT_BLOCK_SIZE
41
+
42
+ k_pe, k_pe_scales = _nvfp4_quant_op(k_pe, BLOCK_D_pe, 1, NVFP4_QUANT_BLOCK_SIZE)
43
+ v, v_scales = _nvfp4_quant_op(v, BLOCK_D_pe, 1, NVFP4_QUANT_BLOCK_SIZE)
44
+
45
+ d_pe_offs_shfl = tl.arange(0, BLOCK_D_pe_STORE // K_WIDTH).to(tl.int64)
46
+ k_width_shfl = tl.arange(0, K_WIDTH).to(tl.int64)
47
+ k_pe = k_pe.reshape((BLOCK_D_pe_STORE // K_WIDTH, K_WIDTH))
48
+ v = v.reshape((BLOCK_D_pe_STORE // K_WIDTH, K_WIDTH))
49
+
50
+ key_cache_ptrs = (
51
+ key_cache_ptr
52
+ + pid_t_slot * key_cache_stride_t
53
+ + pid_hk * key_cache_stride_h
54
+ )
55
+ value_cache_ptrs = (
56
+ value_cache_ptr
57
+ + pid_t_slot * value_cache_stride_t
58
+ + pid_hk * value_cache_stride_h
59
+ )
60
+
61
+ key_value_cache_offs = (
62
+ (pid_b // 16) * BLOCK_D_pe_STORE * 16
63
+ + (pid_b % 16) * K_WIDTH
64
+ + d_pe_offs_shfl[:, None] * K_WIDTH * 16
65
+ + k_width_shfl[None, :]
66
+ ) * key_cache_stride_d
67
+
68
+ tl.store(
69
+ key_cache_ptrs + key_value_cache_offs,
70
+ k_pe.to(key_cache_ptr.dtype.element_ty),
71
+ )
72
+ tl.store(
73
+ value_cache_ptrs + key_value_cache_offs,
74
+ v.to(value_cache_ptr.dtype.element_ty),
75
+ )
76
+
77
+ d_pe_offs_shfl = tl.arange(0, BLOCK_D_pe_scales // SCALE_K_WIDTH).to(tl.int64)
78
+ k_pe_width_shfl = tl.arange(0, SCALE_K_WIDTH).to(tl.int64)
79
+ k_pe_scales = k_pe_scales.reshape(
80
+ (BLOCK_D_pe_scales // SCALE_K_WIDTH, SCALE_K_WIDTH)
81
+ )
82
+ v_scales = v_scales.reshape((BLOCK_D_pe_scales // SCALE_K_WIDTH, SCALE_K_WIDTH))
83
+ pid_sub_blk = pid_b % 128
84
+ key_cache_pe_scales_offs = (
85
+ BLOCK_SIZE * BLOCK_D_pe_STORE
86
+ + (pid_b // 128) * BLOCK_D_pe_scales * 128
87
+ + d_pe_offs_shfl[:, None] * SCALE_K_WIDTH * 128
88
+ + (pid_sub_blk % 32) * 4 * SCALE_K_WIDTH
89
+ + (pid_sub_blk // 32) * SCALE_K_WIDTH
90
+ + k_pe_width_shfl[None, :]
91
+ ) * key_cache_stride_d
92
+ e4m3_dtype = tl.float8e4nv
93
+ tl.store(
94
+ key_cache_ptrs + key_cache_pe_scales_offs,
95
+ k_pe_scales.to(e4m3_dtype).to(key_cache_ptr.dtype.element_ty, bitcast=True),
96
+ )
97
+ tl.store(
98
+ value_cache_ptrs + key_cache_pe_scales_offs,
99
+ v_scales.to(e4m3_dtype).to(value_cache_ptr.dtype.element_ty, bitcast=True),
100
+ )
101
+ else:
102
+
103
+ if FLASH_LAYOUT:
104
+ k_out_ptrs = (
105
+ key_cache_ptr
106
+ + pid_t_slot * key_cache_stride_t
107
+ + d_pe_offs * key_cache_stride_d
108
+ + pid_b * key_cache_stride_b
109
+ + pid_hk * key_cache_stride_h
110
+ )
111
+ else:
112
+ k_pe = tl.reshape(k_pe, (BLOCK_D_pe // X_SIZE, X_SIZE))
113
+ dx_offs = tl.arange(0, BLOCK_D_pe // X_SIZE).to(tl.int64)
114
+ x_offs = tl.arange(0, X_SIZE).to(tl.int64)
115
+ k_out_ptrs = (
116
+ key_cache_ptr
117
+ + pid_t_slot * key_cache_stride_t
118
+ + pid_hk * key_cache_stride_h
119
+ + dx_offs[:, None] * key_cache_stride_d
120
+ + pid_b * key_cache_stride_b
121
+ + x_offs[None, :] * key_cache_stride_x
122
+ )
123
+
124
+ if VALUE_SHUFFLE_LAYOUT:
125
+ slot_chunk = pid_b // X_SIZE
126
+ x_off = pid_b % X_SIZE
127
+ v_out_ptrs = (
128
+ value_cache_ptr
129
+ + pid_t_slot * value_cache_stride_t
130
+ + pid_hk * value_cache_stride_h
131
+ + slot_chunk * value_cache_stride_slot_chunk
132
+ + d_pe_offs * value_cache_stride_d
133
+ + x_off * value_cache_stride_x
134
+ )
135
+ else:
136
+ v_out_ptrs = (
137
+ value_cache_ptr
138
+ + pid_t_slot * value_cache_stride_t
139
+ + pid_hk * value_cache_stride_h
140
+ + d_pe_offs * value_cache_stride_d
141
+ + pid_b * value_cache_stride_b
142
+ )
143
+
144
+ tl.store(k_out_ptrs, k_pe.to(key_cache_ptr.dtype.element_ty))
145
+ tl.store(v_out_ptrs, v.to(value_cache_ptr.dtype.element_ty))
146
+
147
+
148
+ @triton.jit
149
+ def _unit_cat(
150
+ x1_ptr,
151
+ x2_ptr,
152
+ x_out_ptr,
153
+ b_in,
154
+ b_out,
155
+ h,
156
+ d1_offs,
157
+ d2_offs,
158
+ x1_stride_b,
159
+ x1_stride_h,
160
+ x1_stride_d,
161
+ x2_stride_b,
162
+ x2_stride_h,
163
+ x2_stride_d,
164
+ x_out_stride_b,
165
+ x_out_stride_h,
166
+ x_out_stride_d,
167
+ k_scale,
168
+ BLOCK_D1: tl.constexpr,
169
+ ):
170
+ x1_offs = b_in * x1_stride_b + h * x1_stride_h + d1_offs * x1_stride_d
171
+ x2_offs = b_in * x2_stride_b + h * x2_stride_h + d2_offs * x2_stride_d
172
+ x_out_offs = b_out * x_out_stride_b + h * x_out_stride_h
173
+
174
+ x1 = tl.load(x1_ptr + x1_offs)
175
+ x2 = tl.load(x2_ptr + x2_offs)
176
+
177
+ x1 = (x1 / k_scale).to(x_out_ptr.dtype.element_ty)
178
+ x2 = (x2 / k_scale).to(x_out_ptr.dtype.element_ty)
179
+ tl.store(x_out_ptr + x_out_offs + d1_offs * x_out_stride_d, x1)
180
+ tl.store(x_out_ptr + x_out_offs + (d2_offs + BLOCK_D1) * x_out_stride_d, x2)
181
+
182
+
183
+ @triton.jit
184
+ def _unit_rope_cat(
185
+ x_nope_ptr,
186
+ x_pe_ptr,
187
+ cos,
188
+ sin,
189
+ x_out_ptr,
190
+ b_in,
191
+ b_out,
192
+ h,
193
+ d_nope_offs,
194
+ d_pe_offs,
195
+ x_nope_stride_b,
196
+ x_nope_stride_h,
197
+ x_nope_stride_d,
198
+ x_pe_stride_b,
199
+ x_pe_stride_h,
200
+ x_pe_stride_d,
201
+ x_out_stride_b,
202
+ x_out_stride_h,
203
+ x_out_stride_d,
204
+ k_scale,
205
+ IS_NEOX: tl.constexpr,
206
+ BLOCK_D_nope: tl.constexpr,
207
+ BLOCK_D_pe: tl.constexpr,
208
+ BLOCK_D_HALF_pe: tl.constexpr,
209
+ ):
210
+ x_nope_offs = (
211
+ b_in * x_nope_stride_b + h * x_nope_stride_h + d_nope_offs * x_nope_stride_d
212
+ )
213
+ x_pe_offs = b_in * x_pe_stride_b + h * x_pe_stride_h + d_pe_offs * x_pe_stride_d
214
+ x_out_offs = b_out * x_out_stride_b + h * x_out_stride_h
215
+
216
+ x_nope = tl.load(x_nope_ptr + x_nope_offs)
217
+ x_pe = tl.load(x_pe_ptr + x_pe_offs)
218
+
219
+ if IS_NEOX:
220
+ x_rotated_mask = d_pe_offs < BLOCK_D_HALF_pe
221
+ x_pe_rotated = _get_neox_rotated_x_1D(
222
+ x_pe, x_rotated_mask, BLOCK_D_pe, BLOCK_D_HALF_pe
223
+ )
224
+ else:
225
+ x_rotated_mask = d_pe_offs % 2 == 0
226
+ x_pe_rotated = _get_gptj_rotated_x_1D(
227
+ x_pe, x_rotated_mask, BLOCK_D_pe, BLOCK_D_HALF_pe
228
+ )
229
+
230
+ x_pe = x_pe * cos + x_pe_rotated * sin
231
+ x_pe = x_pe / k_scale
232
+ x_nope = x_nope / k_scale
233
+ x_nope = x_nope.to(x_out_ptr.dtype.element_ty)
234
+ x_pe = x_pe.to(x_out_ptr.dtype.element_ty)
235
+
236
+ tl.store(x_out_ptr + x_out_offs + d_nope_offs * x_out_stride_d, x_nope)
237
+ tl.store(x_out_ptr + x_out_offs + (d_pe_offs + BLOCK_D_nope) * x_out_stride_d, x_pe)
238
+
239
+
240
+ @triton.jit
241
+ def _fused_qk_rope_cat_and_cache_mla_kernel(
242
+ q_nope_ptr,
243
+ q_pe_ptr,
244
+ k_nope_ptr,
245
+ k_pe_ptr,
246
+ pos_ptr,
247
+ cos_ptr,
248
+ sin_ptr,
249
+ q_out_ptr,
250
+ decode_q_pe_out_ptr,
251
+ k_pe_out_ptr,
252
+ q_nope_zeros_out_ptr,
253
+ kv_cache_ptr,
254
+ slot_mapping_ptr,
255
+ B,
256
+ B_slot,
257
+ num_decode_toks_for_zeros,
258
+ q_nope_stride_b,
259
+ q_nope_stride_h,
260
+ q_nope_stride_d,
261
+ q_pe_stride_b,
262
+ q_pe_stride_h,
263
+ q_pe_stride_d,
264
+ k_nope_stride_b,
265
+ k_nope_stride_h,
266
+ k_nope_stride_d,
267
+ k_pe_stride_b,
268
+ k_pe_stride_h,
269
+ k_pe_stride_d,
270
+ pos_stride_b,
271
+ cos_stride_b,
272
+ cos_stride_d,
273
+ q_out_stride_b,
274
+ q_out_stride_h,
275
+ q_out_stride_d,
276
+ decode_q_pe_out_stride_b,
277
+ decode_q_pe_out_stride_h,
278
+ decode_q_pe_out_stride_d,
279
+ k_pe_out_stride_b,
280
+ k_pe_out_stride_h,
281
+ k_pe_out_stride_d,
282
+ q_nope_zeros_out_stride_b,
283
+ q_nope_zeros_out_stride_h,
284
+ q_nope_zeros_out_stride_d,
285
+ kv_cache_stride_b,
286
+ kv_cache_stride_h,
287
+ kv_cache_stride_d,
288
+ k_scale_ptr,
289
+ QH_PER_KH: tl.constexpr,
290
+ QH: tl.constexpr,
291
+ KH: tl.constexpr,
292
+ REUSE_FREQS_FRONT_PART: tl.constexpr,
293
+ IS_NEOX: tl.constexpr,
294
+ BLOCK_D_nope: tl.constexpr,
295
+ BLOCK_D_pe: tl.constexpr,
296
+ BLOCK_D_HALF_pe: tl.constexpr,
297
+ BLOCK_SIZE: tl.constexpr = 1,
298
+ SHUFFLED_KV_CACHE: tl.constexpr = False,
299
+ SCALE_K_WIDTH_NOPE: tl.constexpr = 4,
300
+ SCALE_K_WIDTH_ROPE: tl.constexpr = 4,
301
+ OUTPUT_Q_NOPE_ZEROS_AND_Q_PE: tl.constexpr = False,
302
+ HAVE_K_SCALE: tl.constexpr = False,
303
+ UPCAST_OPERAND: tl.constexpr = False,
304
+ ):
305
+ pid = tl.program_id(0)
306
+
307
+ d_nope_offs = tl.arange(0, BLOCK_D_nope).to(tl.int64)
308
+ d_pe_offs = tl.arange(0, BLOCK_D_pe).to(tl.int64)
309
+
310
+ if pid < B * QH:
311
+ # pid_b = pid // QH
312
+ # pid_hq = pid % QH
313
+ # This is a new optimization that prioritized heavy workload WGs first
314
+ pid_hq = pid // B
315
+ pid_b = pid % B
316
+
317
+ if REUSE_FREQS_FRONT_PART:
318
+ if IS_NEOX:
319
+ d_cos_offs = d_pe_offs
320
+ d_cos_offs = tl.where(
321
+ (d_cos_offs >= BLOCK_D_HALF_pe) & (d_cos_offs < BLOCK_D_pe),
322
+ d_cos_offs - BLOCK_D_HALF_pe,
323
+ d_cos_offs,
324
+ ).to(d_cos_offs.dtype)
325
+ else:
326
+ d_cos_offs = d_pe_offs // 2
327
+ else:
328
+ d_cos_offs = d_pe_offs
329
+
330
+ pos = tl.load(pos_ptr + pid_b * pos_stride_b)
331
+ cos_offs = pos * cos_stride_b + d_cos_offs * cos_stride_d
332
+ cos = tl.load(cos_ptr + cos_offs)
333
+ sin = tl.load(sin_ptr + cos_offs)
334
+ if UPCAST_OPERAND:
335
+ cos = cos.to(tl.float32)
336
+ sin = sin.to(tl.float32)
337
+
338
+ q_nope_ptrs = (
339
+ q_nope_ptr
340
+ + pid_b * q_nope_stride_b
341
+ + pid_hq * q_nope_stride_h
342
+ + d_nope_offs * q_nope_stride_d
343
+ )
344
+ q_pe_ptrs = (
345
+ q_pe_ptr
346
+ + pid_b * q_pe_stride_b
347
+ + pid_hq * q_pe_stride_h
348
+ + d_pe_offs * q_pe_stride_d
349
+ )
350
+ q_out_ptrs = q_out_ptr + pid_b * q_out_stride_b + pid_hq * q_out_stride_h
351
+ q_nope = tl.load(q_nope_ptrs)
352
+ q_pe = _unit_rope(
353
+ q_pe_ptrs,
354
+ cos,
355
+ sin,
356
+ d_pe_offs,
357
+ IS_NEOX,
358
+ BLOCK_D_pe,
359
+ BLOCK_D_HALF_pe,
360
+ )
361
+ tl.store(
362
+ q_out_ptrs + d_nope_offs * q_out_stride_d,
363
+ q_nope.to(q_out_ptr.dtype.element_ty),
364
+ )
365
+ tl.store(
366
+ q_out_ptrs + (d_pe_offs + BLOCK_D_nope) * q_out_stride_d,
367
+ q_pe.to(q_out_ptr.dtype.element_ty),
368
+ )
369
+
370
+ if OUTPUT_Q_NOPE_ZEROS_AND_Q_PE:
371
+ if pid < num_decode_toks_for_zeros * QH:
372
+ decode_q_pe_out_ptrs = (
373
+ decode_q_pe_out_ptr
374
+ + pid_b * decode_q_pe_out_stride_b
375
+ + pid_hq * decode_q_pe_out_stride_h
376
+ )
377
+ tl.store(
378
+ decode_q_pe_out_ptrs + d_pe_offs * decode_q_pe_out_stride_d,
379
+ q_pe.to(decode_q_pe_out_ptr.dtype.element_ty),
380
+ )
381
+ z = tl.zeros(
382
+ (BLOCK_D_nope,), dtype=q_nope_zeros_out_ptr.dtype.element_ty
383
+ )
384
+ tl.store(
385
+ q_nope_zeros_out_ptr
386
+ + pid_b * q_nope_zeros_out_stride_b
387
+ + pid_hq * q_nope_zeros_out_stride_h
388
+ + d_nope_offs * q_nope_zeros_out_stride_d,
389
+ z,
390
+ )
391
+
392
+ # pid_hk = pid_hq // QH_PER_KH
393
+ # is_kv = pid_hq % QH_PER_KH == 0
394
+ # This is a new optimization that prioritized heavy workload WGs first
395
+ pid_hk = pid_hq
396
+ is_kv = pid_hk < KH
397
+ if is_kv:
398
+ pid_slot = tl.load(slot_mapping_ptr + pid_b).to(tl.int64)
399
+ if pid_slot >= 0:
400
+ if BLOCK_SIZE > 1:
401
+ pid_t_slot = pid_slot // BLOCK_SIZE
402
+ pid_blk = pid_slot % BLOCK_SIZE
403
+ else:
404
+ pid_t_slot = pid_slot
405
+ pid_blk = 0
406
+ if BLOCK_SIZE > 1:
407
+ pid_t_slot = pid_slot // BLOCK_SIZE
408
+ pid_blk = pid_slot % BLOCK_SIZE
409
+ else:
410
+ pid_t_slot = pid_slot
411
+ pid_blk = 0
412
+ if HAVE_K_SCALE:
413
+ k_scale = tl.load(k_scale_ptr)
414
+ else:
415
+ k_scale = 1
416
+
417
+ k_nope_ptrs = (
418
+ k_nope_ptr
419
+ + pid_b * k_nope_stride_b
420
+ + pid_hk * k_nope_stride_h
421
+ + d_nope_offs * k_nope_stride_d
422
+ )
423
+ k_pe_ptrs = (
424
+ k_pe_ptr
425
+ + pid_b * k_pe_stride_b
426
+ + pid_hk * k_pe_stride_h
427
+ + d_pe_offs * k_pe_stride_d
428
+ )
429
+ k_pe_out_ptrs = (
430
+ k_pe_out_ptr
431
+ + pid_b * k_pe_out_stride_b
432
+ + pid_hk * k_pe_out_stride_h
433
+ + d_pe_offs * k_pe_out_stride_d
434
+ )
435
+ k_nope = tl.load(k_nope_ptrs)
436
+ k_pe = _unit_rope(
437
+ k_pe_ptrs,
438
+ cos,
439
+ sin,
440
+ d_pe_offs,
441
+ IS_NEOX,
442
+ BLOCK_D_pe,
443
+ BLOCK_D_HALF_pe,
444
+ )
445
+ tl.store(k_pe_out_ptrs, k_pe.to(k_pe_out_ptr.dtype.element_ty))
446
+ k_scale_rcprl = (1 / k_scale).to(tl.float32)
447
+ k_nope = k_nope.to(tl.float32) * k_scale_rcprl
448
+ k_pe = k_pe.to(tl.float32) * k_scale_rcprl
449
+
450
+ _store_mla_kv_cache(
451
+ kv_cache_ptr,
452
+ pid_t_slot,
453
+ pid_hk,
454
+ pid_blk,
455
+ d_nope_offs,
456
+ d_pe_offs,
457
+ kv_cache_stride_b,
458
+ kv_cache_stride_h,
459
+ kv_cache_stride_d,
460
+ k_nope,
461
+ k_pe,
462
+ BLOCK_D_nope,
463
+ BLOCK_D_pe,
464
+ BLOCK_SIZE,
465
+ SHUFFLED_KV_CACHE,
466
+ SCALE_K_WIDTH_NOPE,
467
+ SCALE_K_WIDTH_ROPE,
468
+ )
469
+ else:
470
+ pid = pid - B * QH + B * KH
471
+ if pid < B_slot * KH:
472
+ pid_b = pid // KH
473
+ pid_hk = pid % KH
474
+ pid_slot = tl.load(slot_mapping_ptr + pid_b).to(tl.int64)
475
+ if pid_slot >= 0:
476
+ if BLOCK_SIZE > 1:
477
+ pid_t_slot = pid_slot // BLOCK_SIZE
478
+ pid_blk = pid_slot % BLOCK_SIZE
479
+ else:
480
+ pid_t_slot = pid_slot
481
+ pid_blk = 0
482
+ if BLOCK_SIZE > 1:
483
+ pid_t_slot = pid_slot // BLOCK_SIZE
484
+ pid_blk = pid_slot % BLOCK_SIZE
485
+ else:
486
+ pid_t_slot = pid_slot
487
+ pid_blk = 0
488
+ if HAVE_K_SCALE:
489
+ k_scale = tl.load(k_scale_ptr)
490
+ else:
491
+ k_scale = 1
492
+
493
+ k_nope_ptrs = (
494
+ k_nope_ptr
495
+ + pid_b * k_nope_stride_b
496
+ + pid_hk * k_nope_stride_h
497
+ + d_nope_offs * k_nope_stride_d
498
+ )
499
+ k_pe_ptrs = (
500
+ k_pe_ptr
501
+ + pid_b * k_pe_stride_b
502
+ + pid_hk * k_pe_stride_h
503
+ + d_pe_offs * k_pe_stride_d
504
+ )
505
+ k_pe_out_ptrs = (
506
+ k_pe_out_ptr
507
+ + pid_b * k_pe_out_stride_b
508
+ + pid_hk * k_pe_out_stride_h
509
+ + d_pe_offs * k_pe_out_stride_d
510
+ )
511
+ k_nope = tl.load(k_nope_ptrs)
512
+ k_pe = tl.load(k_pe_ptrs)
513
+ tl.store(k_pe_out_ptrs, k_pe.to(k_pe_out_ptr.dtype.element_ty))
514
+ k_scale_rcprl = (1 / k_scale).to(tl.float32)
515
+ k_nope = k_nope.to(tl.float32) * k_scale_rcprl
516
+ k_pe = k_pe.to(tl.float32) * k_scale_rcprl
517
+
518
+ _store_mla_kv_cache(
519
+ kv_cache_ptr,
520
+ pid_t_slot,
521
+ pid_hk,
522
+ pid_blk,
523
+ d_nope_offs,
524
+ d_pe_offs,
525
+ kv_cache_stride_b,
526
+ kv_cache_stride_h,
527
+ kv_cache_stride_d,
528
+ k_nope,
529
+ k_pe,
530
+ BLOCK_D_nope,
531
+ BLOCK_D_pe,
532
+ BLOCK_SIZE,
533
+ SHUFFLED_KV_CACHE,
534
+ SCALE_K_WIDTH_NOPE,
535
+ SCALE_K_WIDTH_ROPE,
536
+ )
537
+
538
+
539
+ @triton.jit
540
+ def _unit_rope(
541
+ x_ptrs,
542
+ cos,
543
+ sin,
544
+ d_pe_offs,
545
+ IS_NEOX: tl.constexpr,
546
+ BLOCK_D_pe: tl.constexpr,
547
+ BLOCK_D_HALF_pe: tl.constexpr,
548
+ ):
549
+ x_pe = tl.load(x_ptrs)
550
+
551
+ if IS_NEOX:
552
+ x_rotated_mask = d_pe_offs < BLOCK_D_HALF_pe
553
+ x_pe_rotated = _get_neox_rotated_x_1D(
554
+ x_pe, x_rotated_mask, BLOCK_D_pe, BLOCK_D_HALF_pe
555
+ )
556
+ else:
557
+ x_rotated_mask = d_pe_offs % 2 == 0
558
+ x_pe_rotated = _get_gptj_rotated_x_1D(
559
+ x_pe, x_rotated_mask, BLOCK_D_pe, BLOCK_D_HALF_pe
560
+ )
561
+
562
+ x_pe = x_pe * cos + x_pe_rotated * sin
563
+
564
+ return x_pe
565
+
566
+
567
+ @triton.jit
568
+ def _fused_qk_rope_reshape_and_cache_kernel(
569
+ q_ptr,
570
+ k_ptr,
571
+ v_ptr,
572
+ pos_ptr,
573
+ cos_ptr,
574
+ sin_ptr,
575
+ offs_ptr,
576
+ key_cache_ptr,
577
+ value_cache_ptr,
578
+ slot_mapping_ptr,
579
+ q_out_ptr,
580
+ k_out_ptr,
581
+ zeros_out_ptr,
582
+ T,
583
+ T_slot,
584
+ q_stride_t,
585
+ q_stride_h,
586
+ q_stride_d,
587
+ k_stride_t,
588
+ k_stride_h,
589
+ k_stride_d,
590
+ v_stride_t,
591
+ v_stride_h,
592
+ v_stride_d,
593
+ cos_stride_t,
594
+ cos_stride_d,
595
+ q_out_stride_t,
596
+ q_out_stride_h,
597
+ q_out_stride_d,
598
+ k_out_stride_t,
599
+ k_out_stride_h,
600
+ k_out_stride_d,
601
+ key_cache_stride_t,
602
+ key_cache_stride_h,
603
+ key_cache_stride_d,
604
+ key_cache_stride_b,
605
+ key_cache_stride_x,
606
+ value_cache_stride_t,
607
+ value_cache_stride_h,
608
+ value_cache_stride_d,
609
+ value_cache_stride_b,
610
+ value_cache_stride_slot_chunk,
611
+ value_cache_stride_x,
612
+ zeros_out_stride_t,
613
+ zeros_out_stride_h,
614
+ zeros_out_stride_d,
615
+ k_scale_ptr,
616
+ v_scale_ptr,
617
+ QH_PER_KH: tl.constexpr,
618
+ QH: tl.constexpr,
619
+ KH: tl.constexpr,
620
+ REUSE_FREQS_FRONT_PART: tl.constexpr,
621
+ IS_NEOX: tl.constexpr,
622
+ BLOCK_D_pe: tl.constexpr,
623
+ BLOCK_D_HALF_pe: tl.constexpr,
624
+ BLOCK_SIZE: tl.constexpr,
625
+ X_SIZE: tl.constexpr,
626
+ SCALE_K_WIDTH: tl.constexpr,
627
+ FLASH_LAYOUT: tl.constexpr,
628
+ VALUE_SHUFFLE_LAYOUT: tl.constexpr = False,
629
+ HAVE_POS: tl.constexpr = False,
630
+ HAVE_K_SCALE: tl.constexpr = False,
631
+ HAVE_V_SCALE: tl.constexpr = False,
632
+ HAVE_ZEROS: tl.constexpr = False,
633
+ UPCAST_OPERAND: tl.constexpr = False,
634
+ ):
635
+
636
+ tl.assume(q_stride_t >= 0)
637
+ tl.assume(q_stride_h >= 0)
638
+ tl.assume(q_stride_d >= 0)
639
+ tl.assume(k_stride_t >= 0)
640
+ tl.assume(k_stride_h >= 0)
641
+ tl.assume(k_stride_d >= 0)
642
+ tl.assume(v_stride_t >= 0)
643
+ tl.assume(v_stride_h >= 0)
644
+ tl.assume(v_stride_d >= 0)
645
+ tl.assume(cos_stride_t >= 0)
646
+ tl.assume(cos_stride_d >= 0)
647
+ tl.assume(q_out_stride_t >= 0)
648
+ tl.assume(q_out_stride_h >= 0)
649
+ tl.assume(q_out_stride_d >= 0)
650
+ tl.assume(k_out_stride_t >= 0)
651
+ tl.assume(k_out_stride_h >= 0)
652
+ tl.assume(k_out_stride_d >= 0)
653
+ tl.assume(key_cache_stride_t >= 0)
654
+ tl.assume(key_cache_stride_h >= 0)
655
+ tl.assume(key_cache_stride_d >= 0)
656
+ tl.assume(key_cache_stride_b >= 0)
657
+ tl.assume(key_cache_stride_x >= 0)
658
+ tl.assume(value_cache_stride_t >= 0)
659
+ tl.assume(value_cache_stride_h >= 0)
660
+ tl.assume(value_cache_stride_d >= 0)
661
+ tl.assume(value_cache_stride_b >= 0)
662
+ tl.assume(value_cache_stride_slot_chunk >= 0)
663
+ tl.assume(value_cache_stride_x >= 0)
664
+ tl.assume(zeros_out_stride_t >= 0)
665
+ tl.assume(zeros_out_stride_h >= 0)
666
+ tl.assume(zeros_out_stride_d >= 0)
667
+
668
+ pid = tl.program_id(0)
669
+ tl.assume(pid >= 0)
670
+
671
+ d_pe_offs = tl.arange(0, BLOCK_D_pe).to(tl.int64)
672
+
673
+ if pid < T * QH:
674
+ pid_t = pid // QH
675
+ pid_hq = pid % QH
676
+ if REUSE_FREQS_FRONT_PART:
677
+ if IS_NEOX:
678
+ d_cos_offs = d_pe_offs
679
+ d_cos_offs = tl.where(
680
+ (d_cos_offs >= BLOCK_D_HALF_pe) & (d_cos_offs < BLOCK_D_pe),
681
+ d_cos_offs - BLOCK_D_HALF_pe,
682
+ d_cos_offs,
683
+ ).to(d_cos_offs.dtype)
684
+ else:
685
+ d_cos_offs = d_pe_offs // 2
686
+ else:
687
+ d_cos_offs = d_pe_offs
688
+
689
+ pos = tl.load(pos_ptr + pid_t)
690
+ if HAVE_POS:
691
+ offset = tl.load(offs_ptr + pid_t)
692
+ pos = pos + offset
693
+ cos_offs = pos * cos_stride_t + d_cos_offs * cos_stride_d
694
+ cos = tl.load(cos_ptr + cos_offs)
695
+ sin = tl.load(sin_ptr + cos_offs)
696
+ if UPCAST_OPERAND:
697
+ cos = cos.to(tl.float32)
698
+ sin = sin.to(tl.float32)
699
+
700
+ q_ptrs = (
701
+ q_ptr + pid_t * q_stride_t + pid_hq * q_stride_h + d_pe_offs * q_stride_d
702
+ )
703
+ q_pe = _unit_rope(
704
+ q_ptrs,
705
+ cos,
706
+ sin,
707
+ d_pe_offs,
708
+ IS_NEOX,
709
+ BLOCK_D_pe,
710
+ BLOCK_D_HALF_pe,
711
+ )
712
+ q_out_ptrs = (
713
+ q_out_ptr
714
+ + pid_t * q_out_stride_t
715
+ + pid_hq * q_out_stride_h
716
+ + d_pe_offs * q_out_stride_d
717
+ )
718
+ tl.store(q_out_ptrs, q_pe.to(q_out_ptr.dtype.element_ty))
719
+
720
+ if HAVE_ZEROS:
721
+ z = tl.zeros((BLOCK_D_pe,), dtype=zeros_out_ptr.dtype.element_ty)
722
+ zeros_out_ptrs = (
723
+ zeros_out_ptr
724
+ + pid_t * zeros_out_stride_t
725
+ + pid_hq * zeros_out_stride_h
726
+ + d_pe_offs * zeros_out_stride_d
727
+ )
728
+ tl.store(zeros_out_ptrs, z)
729
+
730
+ if pid_hq % QH_PER_KH == 0:
731
+ pid_slot = tl.load(slot_mapping_ptr + pid_t).to(tl.int64)
732
+ if pid_slot >= 0:
733
+ pid_t_slot = pid_slot // BLOCK_SIZE
734
+ pid_b = pid_slot % BLOCK_SIZE
735
+ pid_hk = pid_hq // QH_PER_KH
736
+ if HAVE_K_SCALE:
737
+ k_scale = tl.load(k_scale_ptr)
738
+ else:
739
+ k_scale = 1
740
+ k_ptrs = (
741
+ k_ptr
742
+ + pid_t * k_stride_t
743
+ + pid_hk * k_stride_h
744
+ + d_pe_offs * k_stride_d
745
+ )
746
+ k_pe = _unit_rope(
747
+ k_ptrs,
748
+ cos,
749
+ sin,
750
+ d_pe_offs,
751
+ IS_NEOX,
752
+ BLOCK_D_pe,
753
+ BLOCK_D_HALF_pe,
754
+ )
755
+
756
+ k_out_ptrs = (
757
+ k_out_ptr
758
+ + pid_t * k_out_stride_t
759
+ + pid_hk * k_out_stride_h
760
+ + d_pe_offs * k_out_stride_d
761
+ )
762
+ tl.store(k_out_ptrs, k_pe.to(k_out_ptr.dtype.element_ty))
763
+
764
+ k_scale_rcprl = 1 / k_scale
765
+ k_pe = k_pe * k_scale_rcprl
766
+
767
+ v_ptrs = (
768
+ v_ptr
769
+ + pid_t * v_stride_t
770
+ + pid_hk * v_stride_h
771
+ + d_pe_offs * v_stride_d
772
+ )
773
+ if HAVE_V_SCALE:
774
+ v_scale = tl.load(v_scale_ptr)
775
+ else:
776
+ v_scale = 1
777
+ v_scale_rcprl = 1 / v_scale
778
+ v = tl.load(v_ptrs) * v_scale_rcprl
779
+
780
+ _store_kv_cache_kernel(
781
+ key_cache_ptr,
782
+ value_cache_ptr,
783
+ pid_t_slot,
784
+ pid_hk,
785
+ pid_b,
786
+ d_pe_offs,
787
+ k_pe,
788
+ v,
789
+ key_cache_stride_t,
790
+ key_cache_stride_h,
791
+ key_cache_stride_d,
792
+ key_cache_stride_b,
793
+ key_cache_stride_x,
794
+ value_cache_stride_t,
795
+ value_cache_stride_h,
796
+ value_cache_stride_d,
797
+ value_cache_stride_b,
798
+ value_cache_stride_x,
799
+ value_cache_stride_slot_chunk,
800
+ BLOCK_D_pe,
801
+ BLOCK_SIZE,
802
+ X_SIZE,
803
+ FLASH_LAYOUT,
804
+ VALUE_SHUFFLE_LAYOUT,
805
+ SCALE_K_WIDTH,
806
+ )
807
+ else:
808
+ pid = pid - T * QH + T * KH
809
+ if pid < T_slot * KH:
810
+ pid_t = pid // KH
811
+ pid_hk = pid % KH
812
+ pid_slot = tl.load(slot_mapping_ptr + pid_t).to(tl.int64)
813
+ if pid_slot >= 0:
814
+ pid_t_slot = pid_slot // BLOCK_SIZE
815
+ pid_b = pid_slot % BLOCK_SIZE
816
+ if HAVE_K_SCALE:
817
+ k_scale = tl.load(k_scale_ptr)
818
+ else:
819
+ k_scale = 1
820
+ k_ptrs = (
821
+ k_ptr
822
+ + pid_t * k_stride_t
823
+ + pid_hk * k_stride_h
824
+ + d_pe_offs * k_stride_d
825
+ )
826
+
827
+ k_pe = tl.load(k_ptrs)
828
+
829
+ k_out_ptrs = (
830
+ k_out_ptr
831
+ + pid_t * k_out_stride_t
832
+ + pid_hk * k_out_stride_h
833
+ + d_pe_offs * k_out_stride_d
834
+ )
835
+ tl.store(k_out_ptrs, k_pe.to(k_out_ptr.dtype.element_ty))
836
+
837
+ k_scale_rcprl = 1 / k_scale
838
+ k_pe = k_pe * k_scale_rcprl
839
+
840
+ v_ptrs = (
841
+ v_ptr
842
+ + pid_t * v_stride_t
843
+ + pid_hk * v_stride_h
844
+ + d_pe_offs * v_stride_d
845
+ )
846
+ if HAVE_V_SCALE:
847
+ v_scale = tl.load(v_scale_ptr)
848
+ else:
849
+ v_scale = 1
850
+ v_scale_rcprl = 1 / v_scale
851
+ v = tl.load(v_ptrs) * v_scale_rcprl
852
+
853
+ _store_kv_cache_kernel(
854
+ key_cache_ptr,
855
+ value_cache_ptr,
856
+ pid_t_slot,
857
+ pid_hk,
858
+ pid_b,
859
+ d_pe_offs,
860
+ k_pe,
861
+ v,
862
+ key_cache_stride_t,
863
+ key_cache_stride_h,
864
+ key_cache_stride_d,
865
+ key_cache_stride_b,
866
+ key_cache_stride_x,
867
+ value_cache_stride_t,
868
+ value_cache_stride_h,
869
+ value_cache_stride_d,
870
+ value_cache_stride_b,
871
+ value_cache_stride_x,
872
+ value_cache_stride_slot_chunk,
873
+ BLOCK_D_pe,
874
+ BLOCK_SIZE,
875
+ X_SIZE,
876
+ FLASH_LAYOUT,
877
+ VALUE_SHUFFLE_LAYOUT,
878
+ SCALE_K_WIDTH,
879
+ )
880
+
881
+
882
+ @triton.jit
883
+ def _fused_qk_rope_cosine_cache_llama_kernel(
884
+ q_ptr,
885
+ k_ptr,
886
+ v_ptr,
887
+ pos_ptr,
888
+ cos_ptr,
889
+ sin_ptr,
890
+ offs_ptr,
891
+ key_cache_ptr,
892
+ value_cache_ptr,
893
+ slot_mapping_ptr,
894
+ q_out_ptr,
895
+ T,
896
+ T_slot,
897
+ q_stride_t,
898
+ q_stride_h,
899
+ q_stride_d,
900
+ k_stride_t,
901
+ k_stride_h,
902
+ k_stride_d,
903
+ v_stride_t,
904
+ v_stride_h,
905
+ v_stride_d,
906
+ cos_stride_t,
907
+ cos_stride_d,
908
+ q_out_stride_t,
909
+ q_out_stride_h,
910
+ q_out_stride_d,
911
+ key_cache_stride_t,
912
+ key_cache_stride_h,
913
+ key_cache_stride_d,
914
+ key_cache_stride_b,
915
+ key_cache_stride_x,
916
+ value_cache_stride_t,
917
+ value_cache_stride_h,
918
+ value_cache_stride_d,
919
+ value_cache_stride_b,
920
+ k_scale_ptr,
921
+ v_scale_ptr,
922
+ QH_PER_KH: tl.constexpr,
923
+ QH: tl.constexpr,
924
+ KH: tl.constexpr,
925
+ REUSE_FREQS_FRONT_PART: tl.constexpr,
926
+ IS_NEOX: tl.constexpr,
927
+ BLOCK_D_pe: tl.constexpr,
928
+ BLOCK_D_HALF_pe: tl.constexpr,
929
+ BLOCK_SIZE: tl.constexpr,
930
+ X_SIZE: tl.constexpr,
931
+ FLASH_LAYOUT: tl.constexpr,
932
+ HAVE_POS: tl.constexpr = False,
933
+ HAVE_K_SCALE: tl.constexpr = False,
934
+ HAVE_V_SCALE: tl.constexpr = False,
935
+ ):
936
+ pid = tl.program_id(0)
937
+
938
+ d_pe_offs = tl.arange(0, BLOCK_D_pe).to(tl.int64)
939
+
940
+ if pid < T * QH:
941
+ pid_t = pid // QH
942
+ pid_hq = pid % QH
943
+ if REUSE_FREQS_FRONT_PART:
944
+ if IS_NEOX:
945
+ d_cos_offs = d_pe_offs
946
+ d_cos_offs = tl.where(
947
+ (d_cos_offs >= BLOCK_D_HALF_pe) & (d_cos_offs < BLOCK_D_pe),
948
+ d_cos_offs - BLOCK_D_HALF_pe,
949
+ d_cos_offs,
950
+ ).to(d_cos_offs.dtype)
951
+ else:
952
+ d_cos_offs = d_pe_offs // 2
953
+ d_cos_mask = d_cos_offs < BLOCK_D_HALF_pe
954
+
955
+ else:
956
+ d_cos_offs = d_pe_offs
957
+
958
+ pos = tl.load(pos_ptr + pid_t)
959
+ if HAVE_POS:
960
+ offset = tl.load(offs_ptr + pid_t)
961
+ pos = pos + offset
962
+ cos_offs = pos * cos_stride_t + d_cos_offs * cos_stride_d
963
+ cos = tl.load(cos_ptr + cos_offs).to(tl.float64)
964
+ sin = tl.load(sin_ptr + cos_offs).to(tl.float64)
965
+
966
+ q_ptrs = (
967
+ q_ptr + pid_t * q_stride_t + pid_hq * q_stride_h + d_pe_offs * q_stride_d
968
+ )
969
+ q_pe = _unit_rope(
970
+ q_ptrs,
971
+ cos,
972
+ sin,
973
+ d_pe_offs,
974
+ IS_NEOX,
975
+ BLOCK_D_pe,
976
+ BLOCK_D_HALF_pe,
977
+ )
978
+ q_out_ptrs = (
979
+ q_out_ptr
980
+ + pid_t * q_out_stride_t
981
+ + pid_hq * q_out_stride_h
982
+ + d_pe_offs * q_out_stride_d
983
+ )
984
+ tl.store(q_out_ptrs, q_pe.to(q_out_ptr.dtype.element_ty))
985
+
986
+ if pid_hq % QH_PER_KH == 0:
987
+ pid_slot = tl.load(slot_mapping_ptr + pid_t).to(tl.int64)
988
+ if pid_slot >= 0:
989
+ pid_t_slot = pid_t
990
+ pid_b = pid_slot
991
+ pid_hk = pid_hq // QH_PER_KH
992
+ if HAVE_K_SCALE:
993
+ k_scale = tl.load(k_scale_ptr)
994
+ else:
995
+ k_scale = 1
996
+ k_ptrs = (
997
+ k_ptr
998
+ + pid_t * k_stride_t
999
+ + pid_hk * k_stride_h
1000
+ + d_pe_offs * k_stride_d
1001
+ )
1002
+ k_pe = _unit_rope(
1003
+ k_ptrs,
1004
+ cos,
1005
+ sin,
1006
+ d_pe_offs,
1007
+ IS_NEOX,
1008
+ BLOCK_D_pe,
1009
+ BLOCK_D_HALF_pe,
1010
+ )
1011
+
1012
+ k_scale_rcprl = 1 / k_scale
1013
+ k_pe = k_pe * k_scale_rcprl
1014
+
1015
+ if FLASH_LAYOUT:
1016
+ k_out_ptrs = (
1017
+ key_cache_ptr
1018
+ + pid_t_slot * key_cache_stride_t
1019
+ + pid_b * key_cache_stride_b
1020
+ + pid_hk * key_cache_stride_h
1021
+ + d_pe_offs * key_cache_stride_d
1022
+ )
1023
+ else:
1024
+ k_pe = tl.reshape(k_pe, (BLOCK_D_pe // X_SIZE, X_SIZE))
1025
+ dx_offs = tl.arange(0, BLOCK_D_pe // X_SIZE).to(tl.int64)
1026
+ x_offs = tl.arange(0, X_SIZE).to(tl.int64)
1027
+ k_out_ptrs = (
1028
+ key_cache_ptr
1029
+ + pid_t_slot * key_cache_stride_t
1030
+ + pid_hk * key_cache_stride_h
1031
+ + dx_offs[:, None] * key_cache_stride_d
1032
+ + pid_b * key_cache_stride_b
1033
+ + x_offs[None, :] * key_cache_stride_x
1034
+ )
1035
+
1036
+ tl.store(k_out_ptrs, k_pe.to(key_cache_ptr.dtype.element_ty))
1037
+
1038
+ v_ptrs = (
1039
+ v_ptr
1040
+ + pid_t * v_stride_t
1041
+ + pid_hk * v_stride_h
1042
+ + d_pe_offs * v_stride_d
1043
+ )
1044
+ if HAVE_V_SCALE:
1045
+ v_scale = tl.load(v_scale_ptr)
1046
+ else:
1047
+ v_scale = 1
1048
+ v_scale_rcprl = 1 / v_scale
1049
+ v = tl.load(v_ptrs) * v_scale_rcprl
1050
+ v_out_ptrs = (
1051
+ value_cache_ptr
1052
+ + pid_t_slot * value_cache_stride_t
1053
+ + pid_hk * value_cache_stride_h
1054
+ + d_pe_offs * value_cache_stride_d
1055
+ + pid_b * value_cache_stride_b
1056
+ )
1057
+ tl.store(v_out_ptrs, v.to(value_cache_ptr.dtype.element_ty))
1058
+ else:
1059
+ pid = pid - T * QH + T * KH
1060
+ if pid < T_slot * KH:
1061
+ pid_t = pid // KH
1062
+ pid_hk = pid % KH
1063
+ pid_slot = tl.load(slot_mapping_ptr + pid_t).to(tl.int64)
1064
+ if pid_slot >= 0:
1065
+ pid_t_slot = pid_t
1066
+ pid_b = pid_slot
1067
+ if HAVE_K_SCALE:
1068
+ k_scale = tl.load(k_scale_ptr)
1069
+ else:
1070
+ k_scale = 1
1071
+ k_ptrs = (
1072
+ k_ptr
1073
+ + pid_t * k_stride_t
1074
+ + pid_hk * k_stride_h
1075
+ + d_pe_offs * k_stride_d
1076
+ )
1077
+
1078
+ k_pe = tl.load(k_ptrs)
1079
+
1080
+ k_scale_rcprl = 1 / k_scale
1081
+ k_pe = k_pe * k_scale_rcprl
1082
+
1083
+ if FLASH_LAYOUT:
1084
+ k_out_ptrs = (
1085
+ key_cache_ptr
1086
+ + pid_t_slot * key_cache_stride_t
1087
+ + d_pe_offs * key_cache_stride_d
1088
+ + pid_b * key_cache_stride_b
1089
+ + pid_hk * key_cache_stride_h
1090
+ )
1091
+ else:
1092
+ k_pe = tl.reshape(k_pe, (BLOCK_D_pe // X_SIZE, X_SIZE))
1093
+ dx_offs = tl.arange(0, BLOCK_D_pe // X_SIZE).to(tl.int64)
1094
+ x_offs = tl.arange(0, X_SIZE).to(tl.int64)
1095
+ k_out_ptrs = (
1096
+ key_cache_ptr
1097
+ + pid_t_slot * key_cache_stride_t
1098
+ + pid_hk * key_cache_stride_h
1099
+ + dx_offs[:, None] * key_cache_stride_d
1100
+ + pid_b * key_cache_stride_b
1101
+ + x_offs[None, :] * key_cache_stride_x
1102
+ )
1103
+ tl.store(k_out_ptrs, k_pe.to(key_cache_ptr.dtype.element_ty))
1104
+
1105
+ v_ptrs = (
1106
+ v_ptr
1107
+ + pid_t * v_stride_t
1108
+ + pid_hk * v_stride_h
1109
+ + d_pe_offs * v_stride_d
1110
+ )
1111
+ if HAVE_V_SCALE:
1112
+ v_scale = tl.load(v_scale_ptr)
1113
+ else:
1114
+ v_scale = 1
1115
+ v_scale_rcprl = 1 / v_scale
1116
+ v = tl.load(v_ptrs) * v_scale_rcprl
1117
+ v_out_ptrs = (
1118
+ value_cache_ptr
1119
+ + pid_t_slot * value_cache_stride_t
1120
+ + pid_hk * value_cache_stride_h
1121
+ + d_pe_offs * value_cache_stride_d
1122
+ + pid_b * value_cache_stride_b
1123
+ )
1124
+ tl.store(v_out_ptrs, v.to(value_cache_ptr.dtype.element_ty))
build/torch-rocm/_triton_kernels/fusions/fused_mul_add.py ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import triton
2
+ import triton.language as tl
3
+
4
+
5
+ @triton.jit
6
+ def _fused_mul_add_kernel(
7
+ x_ptr,
8
+ a_ptr,
9
+ b_ptr,
10
+ out_ptr,
11
+ N,
12
+ BLOCK_SIZE_N: tl.constexpr,
13
+ NEED_MASK: tl.constexpr,
14
+ IS_A_SCALAR: tl.constexpr,
15
+ IS_B_SCALAR: tl.constexpr,
16
+ IS_A_TENSOR: tl.constexpr,
17
+ IS_B_TENSOR: tl.constexpr,
18
+ ):
19
+ pid = tl.program_id(0)
20
+
21
+ x_offs = pid * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)
22
+
23
+ x_mask = None
24
+ if NEED_MASK:
25
+ x_mask = x_offs < N
26
+
27
+ x = tl.load(x_ptr + x_offs, mask=x_mask).to(tl.float32)
28
+
29
+ if IS_A_SCALAR and IS_A_TENSOR:
30
+ a = tl.load(a_ptr)
31
+ elif IS_A_SCALAR:
32
+ a = a_ptr
33
+ else:
34
+ a = tl.load(a_ptr + x_offs, mask=x_mask)
35
+ a = a.to(tl.float32)
36
+
37
+ if IS_B_SCALAR and IS_B_TENSOR:
38
+ b = tl.load(b_ptr)
39
+ elif IS_B_SCALAR:
40
+ b = b_ptr
41
+ else:
42
+ b = tl.load(b_ptr + x_offs, mask=x_mask)
43
+ b = b.to(tl.float32)
44
+
45
+ out = a * x + b
46
+ out = out.to(out_ptr.dtype.element_ty)
47
+ out = tl.store(out_ptr + x_offs, out, mask=x_mask)
build/torch-rocm/_triton_kernels/fusions/fused_qk_concat.py ADDED
@@ -0,0 +1,285 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import triton
2
+ import triton.language as tl
3
+ from ..._triton_kernels.rope.rope import (
4
+ _get_gptj_rotated_x_1D,
5
+ _get_neox_rotated_x_1D,
6
+ )
7
+
8
+
9
+ @triton.jit
10
+ def _unit_cat(
11
+ x1_ptr,
12
+ x2_ptr,
13
+ x_out_ptr,
14
+ b,
15
+ h,
16
+ d1_offs,
17
+ d2_offs,
18
+ x1_stride_b,
19
+ x1_stride_h,
20
+ x1_stride_d,
21
+ x2_stride_b,
22
+ x2_stride_h,
23
+ x2_stride_d,
24
+ x_out_stride_b,
25
+ x_out_stride_h,
26
+ x_out_stride_d,
27
+ BLOCK_D1: tl.constexpr,
28
+ ):
29
+ x1_offs = b * x1_stride_b + h * x1_stride_h + d1_offs * x1_stride_d
30
+ x2_offs = b * x2_stride_b + h * x2_stride_h + d2_offs * x2_stride_d
31
+ x_out_offs = b * x_out_stride_b + h * x_out_stride_h
32
+
33
+ x1 = tl.load(x1_ptr + x1_offs)
34
+ x2 = tl.load(x2_ptr + x2_offs)
35
+
36
+ tl.store(x_out_ptr + x_out_offs + d1_offs * x_out_stride_d, x1)
37
+ tl.store(x_out_ptr + x_out_offs + (d2_offs + BLOCK_D1) * x_out_stride_d, x2)
38
+
39
+
40
+ @triton.jit
41
+ def _qk_cat_kernel(
42
+ q1_ptr,
43
+ q2_ptr,
44
+ k1_ptr,
45
+ k2_ptr,
46
+ q_out_ptr,
47
+ k_out_ptr,
48
+ q1_stride_b,
49
+ q1_stride_h,
50
+ q1_stride_d,
51
+ q2_stride_b,
52
+ q2_stride_h,
53
+ q2_stride_d,
54
+ k1_stride_b,
55
+ k1_stride_h,
56
+ k1_stride_d,
57
+ k2_stride_b,
58
+ k2_stride_h,
59
+ k2_stride_d,
60
+ q_out_stride_b,
61
+ q_out_stride_h,
62
+ q_out_stride_d,
63
+ k_out_stride_b,
64
+ k_out_stride_h,
65
+ k_out_stride_d,
66
+ QH_PER_KH: tl.constexpr,
67
+ BLOCK_D1: tl.constexpr,
68
+ BLOCK_D2: tl.constexpr,
69
+ ):
70
+ pid_b = tl.program_id(0)
71
+ pid_hq = tl.program_id(1)
72
+
73
+ d1_offs = tl.arange(0, BLOCK_D1)
74
+ d2_offs = tl.arange(0, BLOCK_D2)
75
+
76
+ _unit_cat(
77
+ q1_ptr,
78
+ q2_ptr,
79
+ q_out_ptr,
80
+ pid_b,
81
+ pid_hq,
82
+ d1_offs,
83
+ d2_offs,
84
+ q1_stride_b,
85
+ q1_stride_h,
86
+ q1_stride_d,
87
+ q2_stride_b,
88
+ q2_stride_h,
89
+ q2_stride_d,
90
+ q_out_stride_b,
91
+ q_out_stride_h,
92
+ q_out_stride_d,
93
+ BLOCK_D1,
94
+ )
95
+
96
+ if pid_hq % QH_PER_KH == 0:
97
+ _unit_cat(
98
+ k1_ptr,
99
+ k2_ptr,
100
+ k_out_ptr,
101
+ pid_b,
102
+ pid_hq // QH_PER_KH,
103
+ d1_offs,
104
+ d2_offs,
105
+ k1_stride_b,
106
+ k1_stride_h,
107
+ k1_stride_d,
108
+ k2_stride_b,
109
+ k2_stride_h,
110
+ k2_stride_d,
111
+ k_out_stride_b,
112
+ k_out_stride_h,
113
+ k_out_stride_d,
114
+ BLOCK_D1,
115
+ )
116
+
117
+
118
+ @triton.jit
119
+ def _unit_rope_cat(
120
+ x_nope_ptr,
121
+ x_pe_ptr,
122
+ cos,
123
+ sin,
124
+ x_out_ptr,
125
+ b,
126
+ h,
127
+ d_nope_offs,
128
+ d_pe_offs,
129
+ x_nope_stride_b,
130
+ x_nope_stride_h,
131
+ x_nope_stride_d,
132
+ x_pe_stride_b,
133
+ x_pe_stride_h,
134
+ x_pe_stride_d,
135
+ x_out_stride_b,
136
+ x_out_stride_h,
137
+ x_out_stride_d,
138
+ IS_NEOX: tl.constexpr,
139
+ BLOCK_D_nope: tl.constexpr,
140
+ BLOCK_D_pe: tl.constexpr,
141
+ BLOCK_D_HALF_pe: tl.constexpr,
142
+ ):
143
+ x_nope_offs = (
144
+ b * x_nope_stride_b + h * x_nope_stride_h + d_nope_offs * x_nope_stride_d
145
+ )
146
+ x_pe_offs = b * x_pe_stride_b + h * x_pe_stride_h + d_pe_offs * x_pe_stride_d
147
+ x_out_offs = b * x_out_stride_b + h * x_out_stride_h
148
+
149
+ x_nope = tl.load(x_nope_ptr + x_nope_offs)
150
+ x_pe = tl.load(x_pe_ptr + x_pe_offs)
151
+
152
+ if IS_NEOX:
153
+ x_rotated_mask = d_pe_offs < BLOCK_D_HALF_pe
154
+ x_pe_rotated = _get_neox_rotated_x_1D(
155
+ x_pe, x_rotated_mask, BLOCK_D_pe, BLOCK_D_HALF_pe
156
+ )
157
+ else:
158
+ x_rotated_mask = d_pe_offs % 2 == 0
159
+ x_pe_rotated = _get_gptj_rotated_x_1D(
160
+ x_pe, x_rotated_mask, BLOCK_D_pe, BLOCK_D_HALF_pe
161
+ )
162
+
163
+ x_pe = x_pe * cos + x_pe_rotated * sin
164
+ x_pe = x_pe.to(x_pe_ptr.dtype.element_ty)
165
+
166
+ tl.store(x_out_ptr + x_out_offs + d_nope_offs * x_out_stride_d, x_nope)
167
+ tl.store(x_out_ptr + x_out_offs + (d_pe_offs + BLOCK_D_nope) * x_out_stride_d, x_pe)
168
+
169
+
170
+ @triton.jit
171
+ def _qk_rope_cat_kernel(
172
+ q_nope_ptr,
173
+ q_pe_ptr,
174
+ k_nope_ptr,
175
+ k_pe_ptr,
176
+ pos_ptr,
177
+ cos_ptr,
178
+ sin_ptr,
179
+ q_out_ptr,
180
+ k_out_ptr,
181
+ q_nope_stride_b,
182
+ q_nope_stride_h,
183
+ q_nope_stride_d,
184
+ q_pe_stride_b,
185
+ q_pe_stride_h,
186
+ q_pe_stride_d,
187
+ k_nope_stride_b,
188
+ k_nope_stride_h,
189
+ k_nope_stride_d,
190
+ k_pe_stride_b,
191
+ k_pe_stride_h,
192
+ k_pe_stride_d,
193
+ pos_stride_b,
194
+ cos_stride_b,
195
+ cos_stride_d,
196
+ q_out_stride_b,
197
+ q_out_stride_h,
198
+ q_out_stride_d,
199
+ k_out_stride_b,
200
+ k_out_stride_h,
201
+ k_out_stride_d,
202
+ QH_PER_KH: tl.constexpr,
203
+ REUSE_FREQS_FRONT_PART: tl.constexpr,
204
+ IS_NEOX: tl.constexpr,
205
+ BLOCK_D_nope: tl.constexpr,
206
+ BLOCK_D_pe: tl.constexpr,
207
+ BLOCK_D_HALF_pe: tl.constexpr,
208
+ ):
209
+ pid_b = tl.program_id(0)
210
+ pid_hq = tl.program_id(1)
211
+
212
+ d_nope_offs = tl.arange(0, BLOCK_D_nope)
213
+ d_pe_offs = tl.arange(0, BLOCK_D_pe)
214
+
215
+ if REUSE_FREQS_FRONT_PART:
216
+ if IS_NEOX:
217
+ d_cos_offs = d_pe_offs
218
+ d_cos_offs = tl.where(
219
+ (d_cos_offs >= BLOCK_D_HALF_pe) & (d_cos_offs < BLOCK_D_pe),
220
+ d_cos_offs - BLOCK_D_HALF_pe,
221
+ d_cos_offs,
222
+ ).to(d_cos_offs.dtype)
223
+ # d_cos_mask = d_cos_offs < BLOCK_D_pe
224
+ else:
225
+ d_cos_offs = d_pe_offs // 2
226
+ # d_cos_mask = d_cos_offs < BLOCK_D_HALF_pe
227
+ else:
228
+ d_cos_offs = d_pe_offs
229
+ # d_cos_mask = d_cos_offs < BLOCK_D_pe
230
+
231
+ pos = tl.load(pos_ptr + pid_b * pos_stride_b)
232
+ cos_offs = pos * cos_stride_b + d_cos_offs * cos_stride_d
233
+ cos = tl.load(cos_ptr + cos_offs)
234
+ sin = tl.load(sin_ptr + cos_offs)
235
+
236
+ _unit_rope_cat(
237
+ q_nope_ptr,
238
+ q_pe_ptr,
239
+ cos,
240
+ sin,
241
+ q_out_ptr,
242
+ pid_b,
243
+ pid_hq,
244
+ d_nope_offs,
245
+ d_pe_offs,
246
+ q_nope_stride_b,
247
+ q_nope_stride_h,
248
+ q_nope_stride_d,
249
+ q_pe_stride_b,
250
+ q_pe_stride_h,
251
+ q_pe_stride_d,
252
+ q_out_stride_b,
253
+ q_out_stride_h,
254
+ q_out_stride_d,
255
+ IS_NEOX,
256
+ BLOCK_D_nope,
257
+ BLOCK_D_pe,
258
+ BLOCK_D_HALF_pe,
259
+ )
260
+
261
+ if pid_hq % QH_PER_KH == 0:
262
+ _unit_rope_cat(
263
+ k_nope_ptr,
264
+ k_pe_ptr,
265
+ cos,
266
+ sin,
267
+ k_out_ptr,
268
+ pid_b,
269
+ pid_hq // QH_PER_KH,
270
+ d_nope_offs,
271
+ d_pe_offs,
272
+ k_nope_stride_b,
273
+ k_nope_stride_h,
274
+ k_nope_stride_d,
275
+ k_pe_stride_b,
276
+ k_pe_stride_h,
277
+ k_pe_stride_d,
278
+ k_out_stride_b,
279
+ k_out_stride_h,
280
+ k_out_stride_d,
281
+ IS_NEOX,
282
+ BLOCK_D_nope,
283
+ BLOCK_D_pe,
284
+ BLOCK_D_HALF_pe,
285
+ )
build/torch-rocm/_triton_kernels/fusions/fused_reduce_qk_norm_rope_swa_write.py ADDED
@@ -0,0 +1,289 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (C) 2024-2026, Advanced Micro Devices, Inc. All rights reserved.
3
+
4
+ """Fused split-K reduce + per-head weighted RMSNorm + RoPE (tail) on Q,
5
+ per-row weighted RMSNorm + RoPE (tail) on KV (+ optional SWA KV write).
6
+
7
+ Grid: ``(cdiv(M, BLOCK_SIZE_M), num_local_heads + 1)``. Each program tile
8
+ handles ``BLOCK_SIZE_M`` tokens. Programs with ``pid_h < num_local_heads``
9
+ load a query head tile ``[NUM_SPLITK, BLOCK_SIZE_M, HEAD_DIM]`` from
10
+ ``q_in``, reduce over the split-K axis, apply per-head weighted batched
11
+ RMSNorm, store the (pre-RoPE) head into ``q_out``, then call the batched
12
+ RoPE on the last ``rope_head_dim`` elements. Programs with
13
+ ``pid_h == num_local_heads`` load the full ``[BLOCK_SIZE_M, HEAD_DIM]``
14
+ kv tile, apply weighted batched RMSNorm over ``head_dim``, store the
15
+ normed nope part back into ``kv``, then extract the tail with the same
16
+ reshape+sum trick used for q, apply RoPE, write the result to the kv
17
+ tail, and optionally scatter both parts into ``swa_kv``.
18
+
19
+ ``q_in`` layout (driven by API helper):
20
+ - 2D: ``[M, N]`` — ``q_in_splitk_stride`` = 0, ``NUM_SPLITK`` = 1.
21
+ - 3D: ``[num_splitk, M, N]`` — ``q_in_splitk_stride`` = ``q_in.stride(0)``,
22
+ ``NUM_SPLITK`` = ``num_splitk``.
23
+ """
24
+
25
+ import triton
26
+ import triton.language as tl
27
+
28
+ from ...utils._triton.kernel_repr import make_kernel_repr
29
+ from ...rope.rope import _get_neox_rotated_x, _get_gptj_rotated_x
30
+
31
+
32
+ @triton.jit
33
+ def _batched_rmsnorm_op(row, weight, n_cols, epsilon):
34
+ """Per-row RMSNorm over the last axis of a [BLOCK_M, N] tile (row in fp32)."""
35
+ row_norm = row * row
36
+ row_norm = tl.sum(row_norm, axis=-1)
37
+ norm_factor = tl.math.rsqrt((row_norm / n_cols) + epsilon)
38
+ if weight is not None:
39
+ rms_norm = row * norm_factor[:, None] * weight[None, :]
40
+ else:
41
+ rms_norm = row * norm_factor[:, None]
42
+ return rms_norm
43
+
44
+
45
+ @triton.jit
46
+ def _batched_unit_rope(
47
+ x_pe,
48
+ cos,
49
+ sin,
50
+ d_pe_offs,
51
+ IS_NEOX: tl.constexpr,
52
+ BLOCK_M: tl.constexpr,
53
+ BLOCK_D_pe: tl.constexpr,
54
+ BLOCK_D_HALF_pe: tl.constexpr,
55
+ ):
56
+ """RoPE on a [BLOCK_M, BLOCK_D_pe] tile; cos/sin are [BLOCK_M, BLOCK_D_pe]."""
57
+ if IS_NEOX:
58
+ x_rotated_mask = (d_pe_offs < BLOCK_D_HALF_pe)[None, :]
59
+ x_pe_rotated = _get_neox_rotated_x(
60
+ x_pe, x_rotated_mask, BLOCK_M, BLOCK_D_pe, BLOCK_D_HALF_pe
61
+ )
62
+ else:
63
+ x_rotated_mask = (d_pe_offs % 2 == 0)[None, :]
64
+ x_pe_rotated = _get_gptj_rotated_x(
65
+ x_pe, x_rotated_mask, BLOCK_M, BLOCK_D_pe, BLOCK_D_HALF_pe
66
+ )
67
+
68
+ return x_pe * cos + x_pe_rotated * sin
69
+
70
+
71
+ _fused_reduce_qk_norm_rope_swa_write_repr = make_kernel_repr(
72
+ "_fused_reduce_qk_norm_rope_swa_write_kernel",
73
+ [
74
+ "BLOCK_SIZE_M",
75
+ "HEAD_DIM",
76
+ "ROPE_DIM",
77
+ "NUM_LOCAL_HEADS",
78
+ "NUM_SPLITK",
79
+ "HAS_SWA",
80
+ "IS_NEOX",
81
+ "REUSE_FREQS_FRONT_PART",
82
+ ],
83
+ )
84
+
85
+
86
+ @triton.jit(repr=_fused_reduce_qk_norm_rope_swa_write_repr)
87
+ def _fused_reduce_qk_norm_rope_swa_write_kernel(
88
+ q_in_ptr,
89
+ q_out_ptr,
90
+ kv_ptr,
91
+ q_norm_weight_ptr,
92
+ kv_norm_weight_ptr,
93
+ positions_ptr,
94
+ cos_ptr,
95
+ sin_ptr,
96
+ swa_write_active_ptr,
97
+ batch_id_per_token_ptr,
98
+ state_slot_per_seq_ptr,
99
+ swa_kv_ptr,
100
+ M,
101
+ q_in_splitk_stride,
102
+ q_in_m_stride,
103
+ q_in_d_stride,
104
+ stride_qm,
105
+ stride_qh,
106
+ stride_qd,
107
+ stride_kv_m,
108
+ stride_kv_d,
109
+ cos_stride_t,
110
+ cos_stride_d,
111
+ swa_kv_slot_stride,
112
+ swa_kv_pos_stride,
113
+ win,
114
+ q_eps,
115
+ kv_eps,
116
+ BLOCK_SIZE_M: tl.constexpr,
117
+ HEAD_DIM: tl.constexpr,
118
+ ROPE_DIM: tl.constexpr,
119
+ NUM_LOCAL_HEADS: tl.constexpr,
120
+ NUM_SPLITK: tl.constexpr,
121
+ HAS_SWA: tl.constexpr,
122
+ IS_NEOX: tl.constexpr,
123
+ REUSE_FREQS_FRONT_PART: tl.constexpr,
124
+ ):
125
+ pid_m = tl.program_id(0).to(tl.int64)
126
+ pid_h = tl.program_id(1).to(tl.int64)
127
+ NOPE_DIM: tl.constexpr = HEAD_DIM - ROPE_DIM
128
+ NUM_PE_CHUNKS: tl.constexpr = HEAD_DIM // ROPE_DIM
129
+
130
+ m_offs = pid_m * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M).to(tl.int64)
131
+ m_mask = m_offs < M
132
+
133
+ offs_d_full = tl.arange(0, HEAD_DIM)
134
+ nope_d_mask = offs_d_full < NOPE_DIM
135
+
136
+ d_pe_offs = tl.arange(0, ROPE_DIM).to(tl.int64)
137
+ if REUSE_FREQS_FRONT_PART:
138
+ if IS_NEOX:
139
+ d_cos_offs = d_pe_offs
140
+ d_cos_offs = tl.where(
141
+ (d_cos_offs >= (ROPE_DIM // 2)) & (d_cos_offs < ROPE_DIM),
142
+ d_cos_offs - (ROPE_DIM // 2),
143
+ d_cos_offs,
144
+ ).to(d_cos_offs.dtype)
145
+ else:
146
+ d_cos_offs = d_pe_offs // 2
147
+ else:
148
+ d_cos_offs = d_pe_offs
149
+
150
+ if pid_h < NUM_LOCAL_HEADS:
151
+ head_id = pid_h.to(tl.int32)
152
+ offs_n = head_id * HEAD_DIM + offs_d_full
153
+
154
+ splitk_offs = tl.arange(0, NUM_SPLITK).to(tl.int64)
155
+ q_ptrs = (
156
+ q_in_ptr
157
+ + splitk_offs[:, None, None] * q_in_splitk_stride
158
+ + m_offs[None, :, None] * q_in_m_stride
159
+ + offs_n[None, None, :] * q_in_d_stride
160
+ )
161
+ q_tile = tl.load(
162
+ q_ptrs,
163
+ mask=m_mask[None, :, None],
164
+ other=0.0,
165
+ ).to(
166
+ tl.float32
167
+ ) # [NUM_SPLITK, BLOCK_SIZE_M, HEAD_DIM]
168
+ q_acc = tl.sum(q_tile, axis=0) # [BLOCK_SIZE_M, HEAD_DIM]
169
+
170
+ if q_norm_weight_ptr is not None:
171
+ w_q = tl.load(q_norm_weight_ptr + offs_d_full).to(tl.float32)
172
+ else:
173
+ w_q = None
174
+ q_out_normed = _batched_rmsnorm_op(q_acc, w_q, HEAD_DIM, q_eps)
175
+
176
+ q_base_ptrs = q_out_ptr + m_offs[:, None] * stride_qm + pid_h * stride_qh
177
+ tl.store(
178
+ q_base_ptrs + offs_d_full[None, :] * stride_qd,
179
+ q_out_normed.to(q_out_ptr.dtype.element_ty),
180
+ mask=m_mask[:, None] & nope_d_mask[None, :],
181
+ )
182
+
183
+ # Slice the trailing ROPE_DIM elements: only the last chunk is nonzero.
184
+ q_pe = tl.where(
185
+ (offs_d_full >= NOPE_DIM)[None, :], q_out_normed, 0.0
186
+ ) # [BLOCK_SIZE_M, HEAD_DIM]
187
+ q_pe = q_pe.reshape(BLOCK_SIZE_M, NUM_PE_CHUNKS, ROPE_DIM)
188
+ q_pe = tl.sum(q_pe, axis=1) # [BLOCK_SIZE_M, ROPE_DIM]
189
+
190
+ pos = tl.load(positions_ptr + m_offs, mask=m_mask, other=0) # [BLOCK_SIZE_M]
191
+ cos_offs = pos[:, None] * cos_stride_t + d_cos_offs[None, :] * cos_stride_d
192
+ cos = tl.load(cos_ptr + cos_offs, mask=m_mask[:, None], other=0)
193
+ sin = tl.load(sin_ptr + cos_offs, mask=m_mask[:, None], other=0)
194
+
195
+ q_pe_ptrs = q_base_ptrs + (NOPE_DIM + d_pe_offs[None, :]) * stride_qd
196
+ q_pe = _batched_unit_rope(
197
+ q_pe,
198
+ cos,
199
+ sin,
200
+ d_pe_offs,
201
+ IS_NEOX,
202
+ BLOCK_SIZE_M,
203
+ ROPE_DIM,
204
+ ROPE_DIM // 2,
205
+ )
206
+ tl.store(
207
+ q_pe_ptrs,
208
+ q_pe.to(q_out_ptr.dtype.element_ty),
209
+ mask=m_mask[:, None],
210
+ )
211
+ return
212
+
213
+ if HAS_SWA:
214
+ src_id = tl.load(
215
+ swa_write_active_ptr + m_offs, mask=m_mask, other=-1
216
+ ) # [BLOCK_SIZE_M]
217
+ else:
218
+ src_id = m_offs.to(tl.int32)
219
+ src_mask = m_mask & (src_id >= 0)
220
+
221
+ pos = tl.load(positions_ptr + src_id, mask=src_mask, other=0)
222
+ cos_offs = pos[:, None] * cos_stride_t + d_cos_offs[None, :] * cos_stride_d
223
+ cos = tl.load(cos_ptr + cos_offs, mask=src_mask[:, None], other=0)
224
+ sin = tl.load(sin_ptr + cos_offs, mask=src_mask[:, None], other=0)
225
+
226
+ kv_base_ptrs = kv_ptr + src_id[:, None].to(tl.int64) * stride_kv_m
227
+ kv_full_ptrs = kv_base_ptrs + offs_d_full[None, :] * stride_kv_d
228
+ kv_pe_ptrs = kv_base_ptrs + (NOPE_DIM + d_pe_offs[None, :]) * stride_kv_d
229
+
230
+ # Load the entire kv row (nope + pe) so we can RMSNorm over head_dim.
231
+ kv_full = tl.load(kv_full_ptrs, mask=src_mask[:, None], other=0.0).to(tl.float32)
232
+
233
+ if kv_norm_weight_ptr is not None:
234
+ w_kv = tl.load(kv_norm_weight_ptr + offs_d_full).to(tl.float32)
235
+ else:
236
+ w_kv = None
237
+ kv_normed = _batched_rmsnorm_op(
238
+ kv_full, w_kv, HEAD_DIM, kv_eps
239
+ ) # [BLOCK_SIZE_M, HEAD_DIM]
240
+
241
+ # Store the normed nope portion back into kv.
242
+ tl.store(
243
+ kv_full_ptrs,
244
+ kv_normed.to(kv_ptr.dtype.element_ty),
245
+ mask=src_mask[:, None] & nope_d_mask[None, :],
246
+ )
247
+
248
+ # Extract pe via the same reshape+sum trick used for q.
249
+ kv_pe = tl.where(
250
+ (offs_d_full >= NOPE_DIM)[None, :], kv_normed, 0.0
251
+ ) # [BLOCK_SIZE_M, HEAD_DIM]
252
+ kv_pe = kv_pe.reshape(BLOCK_SIZE_M, NUM_PE_CHUNKS, ROPE_DIM)
253
+ kv_pe = tl.sum(kv_pe, axis=1) # [BLOCK_SIZE_M, ROPE_DIM]
254
+
255
+ kv_pe = _batched_unit_rope(
256
+ kv_pe,
257
+ cos,
258
+ sin,
259
+ d_pe_offs,
260
+ IS_NEOX,
261
+ BLOCK_SIZE_M,
262
+ ROPE_DIM,
263
+ ROPE_DIM // 2,
264
+ )
265
+ tl.store(
266
+ kv_pe_ptrs,
267
+ kv_pe.to(kv_ptr.dtype.element_ty),
268
+ mask=src_mask[:, None],
269
+ )
270
+
271
+ if HAS_SWA:
272
+ bid = tl.load(batch_id_per_token_ptr + src_id, mask=src_mask, other=0)
273
+ slot = tl.load(state_slot_per_seq_ptr + bid, mask=src_mask, other=0)
274
+ ring_idx = pos % win
275
+ swa_kv_ptrs = (
276
+ swa_kv_ptr
277
+ + slot[:, None].to(tl.int64) * swa_kv_slot_stride
278
+ + ring_idx[:, None].to(tl.int64) * swa_kv_pos_stride
279
+ )
280
+ tl.store(
281
+ swa_kv_ptrs + offs_d_full[None, :],
282
+ kv_normed.to(swa_kv_ptr.dtype.element_ty),
283
+ mask=src_mask[:, None] & nope_d_mask[None, :],
284
+ )
285
+ tl.store(
286
+ swa_kv_ptrs + NOPE_DIM + d_pe_offs[None, :],
287
+ kv_pe.to(swa_kv_ptr.dtype.element_ty),
288
+ mask=src_mask[:, None],
289
+ )
build/torch-rocm/_triton_kernels/fusions/fused_routing_from_topk.py ADDED
@@ -0,0 +1,175 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (C) 2025-2026, Advanced Micro Devices, Inc. All rights reserved.
3
+
4
+ # Triton kernels that convert FusedMoE topk outputs (topk_weights, topk_ids)
5
+ # into the (gather_indx, scatter_indx, gate_scal, hist) routing data consumed
6
+ # by triton_kernels.matmul_ogs. Three single-CTA kernels implement a counting
7
+ # sort over (token, slot) pairs by their expert id; replaces ~12 small torch
8
+ # ops (per-row sort, gather, two stable argsorts, advanced indexing, fp32
9
+ # histc, plus dtype casts) with three kernel launches.
10
+ import triton
11
+ import triton.language as tl
12
+ from ...utils._triton.kernel_repr import make_kernel_repr
13
+
14
+ _fused_routing_from_topk_hist_kernel_repr = make_kernel_repr(
15
+ "_fused_routing_from_topk_hist_kernel",
16
+ [
17
+ "E",
18
+ "HAS_EXPERT_MAP",
19
+ "BLOCK_NK",
20
+ "BLOCK_E",
21
+ ],
22
+ )
23
+
24
+ _fused_routing_from_topk_offset_kernel_repr = make_kernel_repr(
25
+ "_fused_routing_from_topk_offset_kernel",
26
+ [
27
+ "E",
28
+ "BLOCK_E",
29
+ ],
30
+ )
31
+
32
+ _fused_routing_from_topk_place_kernel_repr = make_kernel_repr(
33
+ "_fused_routing_from_topk_place_kernel",
34
+ [
35
+ "HAS_EXPERT_MAP",
36
+ "BLOCK_NK",
37
+ ],
38
+ )
39
+
40
+
41
+ @triton.jit(repr=_fused_routing_from_topk_hist_kernel_repr)
42
+ def _fused_routing_from_topk_hist_kernel(
43
+ # inputs
44
+ topk_ids_ptr, # [NK] int32 — flattened topk_ids
45
+ expert_map_ptr, # [N_EXPERTS_GLOBAL] int32 or identity map fallback
46
+ expert_map_numel, # runtime int — bounds for expert_map_ptr
47
+ # outputs
48
+ hist_ptr, # [E] int32 — tokens-per-expert histogram
49
+ # shapes
50
+ NK, # runtime int — actual valid item count (≤ BLOCK_NK)
51
+ E: tl.constexpr,
52
+ HAS_EXPERT_MAP: tl.constexpr,
53
+ BLOCK_NK: tl.constexpr, # padded to next pow2 of NK
54
+ BLOCK_E: tl.constexpr, # padded to next pow2 of E (tl.histogram needs pow2)
55
+ ):
56
+ """Phase A: histogram via tl.histogram (warp-local shared-memory reduction).
57
+
58
+ No global atomics, no debug barrier — the reduction completes within a
59
+ single wave/CTA and the result is written with a plain tl.store.
60
+ ``tl.histogram`` requires ``num_bins`` to be a power of two, so the
61
+ reduction is over BLOCK_E bins; bins ``>= E`` are unreachable because
62
+ expert ids are in ``[0, E)`` and the trailing entries are dropped via
63
+ a masked store.
64
+ """
65
+ item_offs = tl.arange(0, BLOCK_NK)
66
+ item_mask = item_offs < NK
67
+ # Clamp the offset for masked-out lanes to 0 so the pointer arithmetic
68
+ # below stays within the allocated buffers.
69
+ safe_item = tl.where(item_mask, item_offs, 0)
70
+ global_expt = tl.load(topk_ids_ptr + safe_item, mask=item_mask, other=0).to(
71
+ tl.int32
72
+ )
73
+ if HAS_EXPERT_MAP:
74
+ map_mask = item_mask & (global_expt >= 0) & (global_expt < expert_map_numel)
75
+ safe_global_expt = tl.where(map_mask, global_expt, 0)
76
+ local_expt = tl.load(
77
+ expert_map_ptr + safe_global_expt, mask=map_mask, other=-1
78
+ ).to(tl.int32)
79
+ # Match reference semantics: invalid experts are redirected to bucket 0
80
+ # and later zeroed in gate_scal.
81
+ expt = tl.where(local_expt >= 0, local_expt, 0)
82
+ else:
83
+ expt = global_expt
84
+
85
+ hist = tl.histogram(expt, BLOCK_E, mask=item_mask)
86
+
87
+ e_offs = tl.arange(0, BLOCK_E)
88
+ tl.store(hist_ptr + e_offs, hist, mask=e_offs < E)
89
+
90
+
91
+ @triton.jit(repr=_fused_routing_from_topk_offset_kernel_repr)
92
+ def _fused_routing_from_topk_offset_kernel(
93
+ # inputs
94
+ hist_ptr, # [E] int32 — published by the hist kernel
95
+ # outputs
96
+ offset_ptr, # [E] int32 — exclusive prefix sum of hist
97
+ # shapes
98
+ E: tl.constexpr,
99
+ BLOCK_E: tl.constexpr, # padded to next pow2 of E
100
+ ):
101
+ """Phase B: exclusive prefix-sum hist → offset.
102
+
103
+ The previous kernel's exit publishes hist, so this kernel observes
104
+ them on entry without an explicit fence.
105
+ """
106
+ e_offs = tl.arange(0, BLOCK_E)
107
+ e_mask = e_offs < E
108
+ safe_e = tl.where(e_mask, e_offs, 0)
109
+ h = tl.load(hist_ptr + safe_e, mask=e_mask, other=0)
110
+ incl = tl.cumsum(h, axis=0)
111
+ excl = incl - h
112
+ tl.store(offset_ptr + safe_e, excl, mask=e_mask)
113
+
114
+
115
+ @triton.jit(repr=_fused_routing_from_topk_place_kernel_repr)
116
+ def _fused_routing_from_topk_place_kernel(
117
+ # inputs
118
+ topk_ids_ptr, # [NK] int32 — flattened topk_ids
119
+ topk_weights_ptr, # [NK] (any float dtype) — flattened topk_weights
120
+ expert_map_ptr, # [N_EXPERTS_GLOBAL] int32 or identity map fallback
121
+ expert_map_numel, # runtime int — bounds for expert_map_ptr
122
+ offset_ptr, # [E] int32 — exclusive prefix sums from the offset kernel
123
+ # outputs
124
+ topk_indx_ptr, # [NK] int32 — output gather_indx.src_indx
125
+ gate_indx_ptr, # [NK] int32 — output gather_indx.dst_indx
126
+ gate_scal_ptr, # [NK] same dtype as topk_weights
127
+ # shapes
128
+ NK, # runtime int — actual valid item count (≤ BLOCK_NK)
129
+ HAS_EXPERT_MAP: tl.constexpr,
130
+ BLOCK_NK: tl.constexpr, # padded to next pow2 of NK
131
+ ):
132
+ """Phase C: place items.
133
+
134
+ For each valid item, atomic_add on offset[expert] returns its
135
+ expert-sorted position; write topk_indx, gate_indx, gate_scal.
136
+
137
+ The kernel does NOT pre-sort each token's K experts. The resulting
138
+ topk_indx / gate_indx differ from a stable-argsort reference at
139
+ intra-expert ordering, but they form a valid inverse permutation pair
140
+ and matmul_ogs produces the same per-token aggregation (gather +
141
+ weighted scatter sum are both commutative over a per-expert slice).
142
+ """
143
+ item_offs = tl.arange(0, BLOCK_NK)
144
+ item_mask = item_offs < NK
145
+ safe_item = tl.where(item_mask, item_offs, 0)
146
+ global_expt = tl.load(topk_ids_ptr + safe_item, mask=item_mask, other=0).to(
147
+ tl.int32
148
+ )
149
+ weights = tl.load(topk_weights_ptr + safe_item, mask=item_mask, other=0.0)
150
+ if HAS_EXPERT_MAP:
151
+ map_mask = item_mask & (global_expt >= 0) & (global_expt < expert_map_numel)
152
+ safe_global_expt = tl.where(map_mask, global_expt, 0)
153
+ local_expt = tl.load(
154
+ expert_map_ptr + safe_global_expt, mask=map_mask, other=-1
155
+ ).to(tl.int32)
156
+ invalid = local_expt < 0
157
+ expt = tl.where(invalid, 0, local_expt)
158
+ weights = tl.where(invalid, 0.0, weights)
159
+ else:
160
+ expt = global_expt
161
+
162
+ pos = tl.atomic_add(offset_ptr + expt, 1, mask=item_mask)
163
+
164
+ # Clamp pos for masked-out lanes — `pos` is undefined there, and
165
+ # `topk_indx_ptr + pos` / `gate_scal_ptr + pos` would otherwise be
166
+ # arbitrary addresses. The mask=False store doesn't write, but the
167
+ # address calc is still evaluated and may fault on OOB pages.
168
+ safe_pos = tl.where(item_mask, pos, 0)
169
+
170
+ # gate_indx[i] = pos (original_flat → expert_sorted_pos)
171
+ tl.store(gate_indx_ptr + safe_item, pos, mask=item_mask)
172
+ # topk_indx[pos] = i (expert_sorted_pos → original_flat)
173
+ tl.store(topk_indx_ptr + safe_pos, item_offs.to(tl.int32), mask=item_mask)
174
+ # gate_scal[pos] = weight at the original flat item
175
+ tl.store(gate_scal_ptr + safe_pos, weights, mask=item_mask)
build/torch-rocm/_triton_kernels/fusions/mhc.py ADDED
@@ -0,0 +1,1277 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (C) 2024-2026, Advanced Micro Devices, Inc. All rights reserved.
3
+
4
+ """Triton kernel for mHC (manifold-constrained Hyper Connection) operations."""
5
+
6
+ import triton
7
+ import triton.language as tl
8
+ from ...utils._triton.kernel_repr import make_kernel_repr
9
+
10
+
11
+ @triton.jit
12
+ def _mhc_apply_pre_mix_tile(
13
+ x_ptr,
14
+ out_ptr,
15
+ pre_mix_2d, # (BLOCK_M, N_POW2) fp32, caller-supplied
16
+ rm,
17
+ rc,
18
+ i_n,
19
+ M,
20
+ C: tl.constexpr,
21
+ n: tl.constexpr,
22
+ stride_xm,
23
+ stride_xk,
24
+ stride_om,
25
+ stride_oc,
26
+ ):
27
+ """Compute one (M-tile, C-tile) of the pre-stream apply step:
28
+
29
+ out[rm, rc] = sum_{i in [0, n)} pre_mix_2d[rm, i] * x[rm, i*C + rc]
30
+
31
+ `pre_mix_2d` must already be padded to width `N_POW2` along the n-axis
32
+ (entries with `i_n >= n` masked to 0 by the caller).
33
+ """
34
+ x_tile = tl.load(
35
+ x_ptr
36
+ + rm[:, None, None] * stride_xm
37
+ + (i_n[None, :, None] * C + rc[None, None, :]) * stride_xk,
38
+ mask=(rm[:, None, None] < M)
39
+ & (i_n[None, :, None] < n)
40
+ & (rc[None, None, :] < C),
41
+ other=0.0,
42
+ ).to(tl.float32)
43
+ li_acc = tl.sum(pre_mix_2d[:, :, None] * x_tile, axis=1)
44
+ tl.store(
45
+ out_ptr + rm[:, None] * stride_om + rc[None, :] * stride_oc,
46
+ li_acc.to(out_ptr.dtype.element_ty),
47
+ mask=(rm[:, None] < M) & (rc[None, :] < C),
48
+ )
49
+
50
+
51
+ @triton.jit
52
+ def _mhc_fused_kernel(
53
+ x_ptr,
54
+ phi_ptr, # Unified phi: (K, n + n + n_res), layout [pre | post | res]
55
+ alpha_pre,
56
+ alpha_post,
57
+ alpha_res,
58
+ bias_ptr,
59
+ out_ptr, # Shrunk output: (M, n + n_squared), layout [post | res]
60
+ layer_input_ptr, # (M, C); written directly via the inline apply step
61
+ M: tl.constexpr,
62
+ K: tl.constexpr,
63
+ N: tl.constexpr,
64
+ n: tl.constexpr,
65
+ n_squared: tl.constexpr,
66
+ C: tl.constexpr,
67
+ eps: tl.constexpr,
68
+ hc_pre_eps: tl.constexpr,
69
+ hc_post_mult_value: tl.constexpr,
70
+ stride_xm,
71
+ stride_xk,
72
+ stride_phi_k, # Stride for K dimension
73
+ stride_phi_n, # Stride for N dimension (total_cols)
74
+ stride_out_m, # Stride for M dimension
75
+ stride_out_n, # Stride for N dimension (post + res)
76
+ stride_li_m, # Stride for M dimension of layer_input
77
+ stride_li_c, # Stride for C dimension of layer_input
78
+ BLOCK_M: tl.constexpr,
79
+ BLOCK_N: tl.constexpr,
80
+ BLOCK_K: tl.constexpr,
81
+ BLOCK_C: tl.constexpr,
82
+ N_POW2: tl.constexpr,
83
+ NUM_SINKHORN_ITERS: tl.constexpr,
84
+ ):
85
+ """
86
+ Fused kernel for mHC equations 14-18 + the apply step (non-split-K path).
87
+
88
+ Computes three separate outputs:
89
+ - H^pre: (M, n) - sigmoid activation (Eq 17). The pre-stream program runs
90
+ the inline 3D-broadcast apply directly to `layer_input_ptr`, producing
91
+ ``layer_input[m, c] = sum_i (sigmoid(H_pre[m, i]) + hc_pre_eps) * x[m, i*C + c]``.
92
+ - H^post: (M, n) with hc_post_mult_value * sigmoid activation (Eq 18)
93
+ - H^res: (M, n, n) doubly-stochastic Sinkhorn-Knopp output when
94
+ NUM_SINKHORN_ITERS > 0 (Eq 19), or raw logits when 0.
95
+
96
+ Post and res streams write to a unified `(M, n + n_squared)` tensor following
97
+ `[post | res]`. phi/bias indexing follows `[pre | post | res]` layout. When
98
+ NUM_SINKHORN_ITERS > 0, the res branch reshapes its `(BLOCK_M, BLOCK_N)`
99
+ tile to `(BLOCK_M, n, n)` and runs log-domain Sinkhorn-Knopp inline before
100
+ the store; this requires `BLOCK_N == n_squared` (enforced by the wrapper).
101
+
102
+ Grid structure:
103
+ - The grid is organized per-stream so each program processes exactly one stream
104
+ - pid_n maps to: [0, n_blocks_pre) = pre, [n_blocks_pre, n_blocks_pre+post) = post, rest = res
105
+ """
106
+ pid_m = tl.program_id(0)
107
+ pid_n = tl.program_id(1)
108
+
109
+ rm = pid_m * BLOCK_M + tl.arange(0, BLOCK_M)
110
+
111
+ n_blocks_pre = tl.cdiv(n, BLOCK_N)
112
+ n_blocks_post = n_blocks_pre
113
+
114
+ # Determine stream type from pid_n, each program processes exactly one stream
115
+ is_pre_program = pid_n < n_blocks_pre
116
+ is_post_program = (pid_n >= n_blocks_pre) & (pid_n < n_blocks_pre + n_blocks_post)
117
+ is_res_program = ~is_pre_program & ~is_post_program
118
+ is_post_i32 = is_post_program.to(tl.int32)
119
+ is_res_i32 = is_res_program.to(tl.int32)
120
+
121
+ stream_offset = is_post_i32 * n_blocks_pre + is_res_i32 * (
122
+ n_blocks_pre + n_blocks_post
123
+ )
124
+ local_pid_n = pid_n - stream_offset
125
+
126
+ rn_local = local_pid_n * BLOCK_N + tl.arange(0, BLOCK_N)
127
+
128
+ n_out = n + (n_squared - n) * is_res_i32
129
+
130
+ acc = tl.zeros([BLOCK_M, BLOCK_N], dtype=tl.float32)
131
+ acc_sq = tl.zeros([BLOCK_M], dtype=tl.float32)
132
+
133
+ # Compute phi column offset in unified tensor layout: [pre: 0..n-1, post: n..2n-1, res: 2n..2n+n_res-1]
134
+ # phi/bias indexing keeps the original [pre | post | res] layout
135
+ phi_col_start = tl.where(is_pre_program, 0, tl.where(is_post_program, n, 2 * n))
136
+ rn_global = rn_local + phi_col_start
137
+
138
+ # Unified phi tensor - strides are the same for all streams
139
+ for k in range(0, K, BLOCK_K):
140
+ rk = k + tl.arange(0, BLOCK_K)
141
+
142
+ x_tile = tl.load(
143
+ x_ptr + rm[:, None] * stride_xm + rk[None, :] * stride_xk,
144
+ mask=(rm[:, None] < M) & (rk[None, :] < K),
145
+ other=0.0,
146
+ )
147
+
148
+ phi_col_offset = phi_col_start + rn_local
149
+ phi_tile = tl.load(
150
+ phi_ptr
151
+ + rk[:, None] * stride_phi_k
152
+ + phi_col_offset[None, :] * stride_phi_n,
153
+ mask=(rk[:, None] < K) & (rn_local[None, :] < n_out),
154
+ other=0.0,
155
+ )
156
+
157
+ acc = tl.dot(x_tile, phi_tile, acc=acc)
158
+ x_tile_f32 = x_tile.to(tl.float32)
159
+ acc_sq += tl.sum(x_tile_f32 * x_tile_f32, axis=1)
160
+
161
+ rms = tl.sqrt(acc_sq / K + eps)
162
+ rsigma = 1.0 / rms
163
+
164
+ bias = tl.load(bias_ptr + rn_global, mask=rn_global < N, other=0.0).to(tl.float32)
165
+ alpha_val = tl.where(
166
+ is_pre_program, alpha_pre, tl.where(is_post_program, alpha_post, alpha_res)
167
+ )
168
+
169
+ out = rsigma[:, None] * alpha_val * acc + bias[None, :]
170
+
171
+ if is_pre_program:
172
+ pre_mix = tl.sigmoid(out) + hc_pre_eps # (BLOCK_M, BLOCK_N)
173
+ # Run the apply step inline via a 3D-broadcast reduction
174
+ i_n = tl.arange(0, N_POW2)
175
+ pre_mix_2d = tl.sum(
176
+ tl.where(
177
+ rn_local[None, None, :] == i_n[None, :, None],
178
+ pre_mix[:, None, :],
179
+ 0.0,
180
+ ),
181
+ axis=2,
182
+ ) # (BLOCK_M, N_POW2)
183
+ for c0 in range(0, C, BLOCK_C):
184
+ rc = c0 + tl.arange(0, BLOCK_C)
185
+ _mhc_apply_pre_mix_tile(
186
+ x_ptr,
187
+ layer_input_ptr,
188
+ pre_mix_2d,
189
+ rm,
190
+ rc,
191
+ i_n,
192
+ M,
193
+ C,
194
+ n,
195
+ stride_xm,
196
+ stride_xk,
197
+ stride_li_m,
198
+ stride_li_c,
199
+ )
200
+ else:
201
+ # Post or Res branch.
202
+ if is_post_program:
203
+ out_activated = tl.sigmoid(out) * hc_post_mult_value
204
+ out_col_start = 0
205
+ else:
206
+ # Res branch: log-domain Sinkhorn-Knopp on (BLOCK_M, n, n) sub-tile,
207
+ # or raw logits when NUM_SINKHORN_ITERS == 0. Requires BLOCK_N == n_squared.
208
+ if NUM_SINKHORN_ITERS > 0:
209
+ LOG2_E: tl.constexpr = 1.4426950408889634
210
+
211
+ log2_A = tl.reshape(out, (BLOCK_M, n, n)) * LOG2_E
212
+
213
+ log2_u = tl.zeros((BLOCK_M, n), dtype=tl.float32)
214
+ log2_v = tl.zeros((BLOCK_M, n), dtype=tl.float32)
215
+
216
+ for _ in range(NUM_SINKHORN_ITERS):
217
+ scaled_row = log2_A + log2_v[:, None, :]
218
+ row_max = tl.max(scaled_row, axis=2)
219
+ exp_shifted = tl.exp2(scaled_row - row_max[:, :, None])
220
+ row_sum_exp = tl.sum(exp_shifted, axis=2)
221
+ log2_row_sums = row_max + tl.log2(row_sum_exp)
222
+ log2_u = -log2_row_sums
223
+
224
+ scaled_col = log2_A + log2_u[:, :, None]
225
+ col_max = tl.max(scaled_col, axis=1)
226
+ exp_shifted = tl.exp2(scaled_col - col_max[:, None, :])
227
+ col_sum_exp = tl.sum(exp_shifted, axis=1)
228
+ log2_col_sums = col_max + tl.log2(col_sum_exp)
229
+ log2_v = -log2_col_sums
230
+
231
+ log2_P = log2_A + log2_u[:, :, None] + log2_v[:, None, :]
232
+ P = tl.exp2(log2_P)
233
+ out_activated = tl.reshape(P, (BLOCK_M, n_squared))
234
+ else:
235
+ out_activated = out
236
+ out_col_start = n
237
+ out_col_offset = out_col_start + rn_local
238
+ tl.store(
239
+ out_ptr
240
+ + rm[:, None] * stride_out_m
241
+ + out_col_offset[None, :] * stride_out_n,
242
+ out_activated,
243
+ mask=(rm[:, None] < M) & (rn_local[None, :] < n_out),
244
+ )
245
+
246
+
247
+ @triton.jit
248
+ def _mhc_fused_split_kernel(
249
+ x_ptr,
250
+ phi_ptr, # Unified phi: (K, n + n + n_squared)
251
+ acc_ptr, # Single unified output: (NUM_KSPLIT, M, n + n + n_squared)
252
+ acc_sq_ptr,
253
+ M: tl.constexpr,
254
+ K: tl.constexpr,
255
+ N: tl.constexpr, # = 2*n + n_squared (logical width of unified phi)
256
+ n: tl.constexpr,
257
+ n_squared: tl.constexpr,
258
+ stride_xm,
259
+ stride_xk,
260
+ stride_phi_k, # Stride for K dimension
261
+ stride_phi_n, # Stride for N dimension (total_cols)
262
+ stride_acc_k, # Stride for NUM_KSPLIT dimension
263
+ stride_acc_m, # Stride for M dimension
264
+ stride_acc_n, # Stride for N dimension (total_cols)
265
+ stride_acc_sq_k,
266
+ stride_acc_sq_m,
267
+ BLOCK_M: tl.constexpr,
268
+ N_TOTAL_POW2: tl.constexpr, # = next_pow2(N), full N-tile per program
269
+ BLOCK_K: tl.constexpr,
270
+ SPLITK_BLOCK_SIZE: tl.constexpr,
271
+ ):
272
+ """
273
+ Split-K kernel for mHC - computes partial results for equations 14-15.
274
+
275
+ Each program owns the *full* (BLOCK_M, N_TOTAL_POW2) tile for one
276
+ `(pid_m, pid_k)` pair: load each x-tile once, dot it against the unified
277
+ phi covering all 3 streams in a single MFMA, and write the entire output
278
+ row in one store. Compared to the old per-stream layout this drops the 3x
279
+ redundant x re-read and lifts MFMA utilization (the pre/post partial
280
+ columns are now subsumed by the same dot as the res columns).
281
+
282
+ Writes all streams to unified contiguous tensor: (NUM_KSPLIT, M, N_total)
283
+ Memory layout: [pre_0..pre_{n-1}, post_0..post_{n-1}, res_0..res_{n_squared-1}]
284
+
285
+ Grid structure: (M_blocks, NUM_KSPLIT).
286
+ """
287
+ pid_m = tl.program_id(0)
288
+ pid_k = tl.program_id(1)
289
+
290
+ rm = pid_m * BLOCK_M + tl.arange(0, BLOCK_M)
291
+ rn = tl.arange(0, N_TOTAL_POW2)
292
+
293
+ split_k_start = pid_k * SPLITK_BLOCK_SIZE
294
+ split_k_end = tl.minimum(split_k_start + SPLITK_BLOCK_SIZE, K)
295
+
296
+ if split_k_start >= K:
297
+ return
298
+
299
+ acc = tl.zeros([BLOCK_M, N_TOTAL_POW2], dtype=tl.float32)
300
+ acc_sq = tl.zeros([BLOCK_M], dtype=tl.float32)
301
+
302
+ k_span = split_k_end - split_k_start
303
+ num_k_iter = tl.cdiv(k_span, BLOCK_K)
304
+
305
+ for k_idx in range(num_k_iter):
306
+ k = split_k_start + k_idx * BLOCK_K
307
+ rk = k + tl.arange(0, BLOCK_K)
308
+
309
+ x_tile = tl.load(
310
+ x_ptr + rm[:, None] * stride_xm + rk[None, :] * stride_xk,
311
+ mask=(rm[:, None] < M) & (rk[None, :] < split_k_end),
312
+ other=0.0,
313
+ )
314
+ phi_tile = tl.load(
315
+ phi_ptr + rk[:, None] * stride_phi_k + rn[None, :] * stride_phi_n,
316
+ mask=(rk[:, None] < split_k_end) & (rn[None, :] < N),
317
+ other=0.0,
318
+ )
319
+
320
+ acc = tl.dot(x_tile, phi_tile, acc=acc)
321
+ x_tile_f32 = x_tile.to(tl.float32)
322
+ acc_sq += tl.sum(x_tile_f32 * x_tile_f32, axis=1)
323
+
324
+ tl.store(
325
+ acc_ptr
326
+ + pid_k * stride_acc_k
327
+ + rm[:, None] * stride_acc_m
328
+ + rn[None, :] * stride_acc_n,
329
+ acc,
330
+ mask=(rm[:, None] < M) & (rn[None, :] < N),
331
+ )
332
+ tl.store(
333
+ acc_sq_ptr + pid_k * stride_acc_sq_k + rm * stride_acc_sq_m,
334
+ acc_sq,
335
+ mask=rm < M,
336
+ )
337
+
338
+
339
+ @triton.jit
340
+ def _mhc_reduce_apply_res_block(
341
+ acc_res, # (BLOCK_M, N_POW2_RES) fp32, already reduced over ks
342
+ rsigma, # (BLOCK_M,) fp32
343
+ rm,
344
+ rn_res_local,
345
+ rn_res_global,
346
+ alpha_res,
347
+ bias_ptr,
348
+ out_ptr,
349
+ M,
350
+ n: tl.constexpr,
351
+ n_squared: tl.constexpr,
352
+ N_POW2_RES: tl.constexpr,
353
+ stride_out_m,
354
+ stride_out_n,
355
+ BLOCK_M: tl.constexpr,
356
+ NUM_SINKHORN_ITERS: tl.constexpr,
357
+ ):
358
+ """Compute h_res = rsigma * alpha_res * acc_res + bias_res, optionally run
359
+ log-domain Sinkhorn-Knopp, and store to ``out[:, n:n+n_squared]``.
360
+
361
+ Shared between the merged-CTA path (`RES_PID_C == 0`, fused with post on the
362
+ same `for-ks` loop) and the split-CTA path (`RES_PID_C != 0`).
363
+ """
364
+ bias_res = tl.load(
365
+ bias_ptr + rn_res_global,
366
+ mask=rn_res_local < n_squared,
367
+ other=0.0,
368
+ ).to(tl.float32)
369
+ h_res = rsigma[:, None] * alpha_res * acc_res + bias_res[None, :]
370
+
371
+ if NUM_SINKHORN_ITERS > 0:
372
+ LOG2_E: tl.constexpr = 1.4426950408889634
373
+
374
+ log2_A = tl.reshape(h_res, (BLOCK_M, n, n)) * LOG2_E
375
+ log2_u = tl.zeros((BLOCK_M, n), dtype=tl.float32)
376
+ log2_v = tl.zeros((BLOCK_M, n), dtype=tl.float32)
377
+
378
+ for _ in range(NUM_SINKHORN_ITERS):
379
+ scaled_row = log2_A + log2_v[:, None, :]
380
+ row_max = tl.max(scaled_row, axis=2)
381
+ exp_shifted = tl.exp2(scaled_row - row_max[:, :, None])
382
+ row_sum_exp = tl.sum(exp_shifted, axis=2)
383
+ log2_row_sums = row_max + tl.log2(row_sum_exp)
384
+ log2_u = -log2_row_sums
385
+
386
+ scaled_col = log2_A + log2_u[:, :, None]
387
+ col_max = tl.max(scaled_col, axis=1)
388
+ exp_shifted = tl.exp2(scaled_col - col_max[:, None, :])
389
+ col_sum_exp = tl.sum(exp_shifted, axis=1)
390
+ log2_col_sums = col_max + tl.log2(col_sum_exp)
391
+ log2_v = -log2_col_sums
392
+
393
+ log2_P = log2_A + log2_u[:, :, None] + log2_v[:, None, :]
394
+ P = tl.exp2(log2_P)
395
+ out_res = tl.reshape(P, (BLOCK_M, n_squared))
396
+ else:
397
+ out_res = h_res
398
+
399
+ tl.store(
400
+ out_ptr
401
+ + rm[:, None] * stride_out_m
402
+ + (n + rn_res_local[None, :]) * stride_out_n,
403
+ out_res,
404
+ mask=(rm[:, None] < M) & (rn_res_local[None, :] < n_squared),
405
+ )
406
+
407
+
408
+ @triton.jit
409
+ def _mhc_reduce_apply_kernel(
410
+ acc_ptr, # Unified split-K partials: (NUM_KSPLIT, M, n + n + n_squared), layout [pre | post | res]
411
+ acc_sq_ptr, # Sum-of-squares partials: (NUM_KSPLIT, M)
412
+ alpha_pre,
413
+ alpha_post,
414
+ alpha_res,
415
+ bias_ptr, # (n + n + n_squared,) fp32
416
+ x_ptr, # (M, n*C)
417
+ out_ptr, # Unified output: (M, n + n_squared), layout [post | res]
418
+ layer_input_ptr, # (M, C) in x.dtype
419
+ M,
420
+ K: tl.constexpr,
421
+ n: tl.constexpr,
422
+ n_squared: tl.constexpr,
423
+ C: tl.constexpr,
424
+ eps: tl.constexpr,
425
+ hc_pre_eps: tl.constexpr,
426
+ hc_post_mult_value: tl.constexpr,
427
+ stride_acc_k,
428
+ stride_acc_m,
429
+ stride_acc_n,
430
+ stride_acc_sq_k,
431
+ stride_acc_sq_m,
432
+ stride_xm,
433
+ stride_xk,
434
+ stride_out_m,
435
+ stride_out_n,
436
+ stride_li_m,
437
+ stride_li_c,
438
+ BLOCK_M: tl.constexpr,
439
+ BLOCK_C: tl.constexpr,
440
+ N_POW2: tl.constexpr,
441
+ N_POW2_RES: tl.constexpr,
442
+ ACTUAL_KSPLIT: tl.constexpr,
443
+ NUM_SINKHORN_ITERS: tl.constexpr,
444
+ RES_PID_C: tl.constexpr,
445
+ ):
446
+ """
447
+ Reduce-and-apply kernel for the split-K mHC pipeline (Eq 15-19 + apply).
448
+
449
+ Grid: ``(cdiv(M, BLOCK_M), cdiv(C, BLOCK_C))``.
450
+
451
+ Each program reads its M-slice of split-K partials once and computes:
452
+
453
+ - All pids: pre stream (RMS + bias + alpha + sigmoid + hc_pre_eps) and
454
+ the apply step ``layer_input[m, c] = sum_i pre_mix[m, i] * x[m, i*C + c]``
455
+ restricted to this pid's BLOCK_C slice of the hidden dimension.
456
+ - ``pid_c == 0``: post stream (``hc_post_mult_value * sigmoid``), writes to
457
+ ``out[:, :n]``.
458
+ - ``pid_c == RES_PID_C``: res stream (in-kernel log-domain Sinkhorn-Knopp
459
+ when ``NUM_SINKHORN_ITERS > 0``, else raw logits), writes to
460
+ ``out[:, n:n+n_squared]``.
461
+
462
+ Sinkhorn requires ``n_squared is`` a power of two; the wrapper enforces
463
+ this when ``NUM_SINKHORN_ITERS > 0``.
464
+ """
465
+ pid_m = tl.program_id(0)
466
+ pid_c = tl.program_id(1)
467
+
468
+ rm = pid_m * BLOCK_M + tl.arange(0, BLOCK_M)
469
+ rc = pid_c * BLOCK_C + tl.arange(0, BLOCK_C)
470
+ rn_pre = tl.arange(0, N_POW2)
471
+
472
+ # --- 1) Reduce split-K partials: PRE columns + acc_sq ---
473
+ acc_pre = tl.zeros([BLOCK_M, N_POW2], dtype=tl.float32)
474
+ acc_sq = tl.zeros([BLOCK_M], dtype=tl.float32)
475
+ for ks in range(ACTUAL_KSPLIT):
476
+ acc_pre += tl.load(
477
+ acc_ptr
478
+ + ks * stride_acc_k
479
+ + rm[:, None] * stride_acc_m
480
+ + rn_pre[None, :] * stride_acc_n,
481
+ mask=(rm[:, None] < M) & (rn_pre[None, :] < n),
482
+ other=0.0,
483
+ )
484
+ acc_sq += tl.load(
485
+ acc_sq_ptr + ks * stride_acc_sq_k + rm * stride_acc_sq_m,
486
+ mask=rm < M,
487
+ other=0.0,
488
+ )
489
+
490
+ # --- 2) RMS normalization (Eq 15) ---
491
+ rms = tl.sqrt(acc_sq / K + eps)
492
+ rsigma = 1.0 / rms
493
+
494
+ # --- 3) Pre stream: bias + alpha + sigmoid + hc_pre_eps (Eq 16-17) ---
495
+ bias_pre = tl.load(bias_ptr + rn_pre, mask=rn_pre < n, other=0.0).to(tl.float32)
496
+ h_pre = rsigma[:, None] * alpha_pre * acc_pre + bias_pre[None, :]
497
+ pre_mix_2d = tl.sigmoid(h_pre) + hc_pre_eps
498
+
499
+ # --- 4) Apply step for this pid's BLOCK_C slice ---
500
+ _mhc_apply_pre_mix_tile(
501
+ x_ptr,
502
+ layer_input_ptr,
503
+ pre_mix_2d,
504
+ rm,
505
+ rc,
506
+ rn_pre,
507
+ M,
508
+ C,
509
+ n,
510
+ stride_xm,
511
+ stride_xk,
512
+ stride_li_m,
513
+ stride_li_c,
514
+ )
515
+
516
+ # --- 5) Post stream on pid_c == 0; Res stream on pid_c == RES_PID_C ---
517
+ # Two compile-time layouts:
518
+ # RES_PID_C == 0 (single C-tile, shared CTA): one for-ks loop loads both
519
+ # post and res partials, then post and res are computed back-to-back.
520
+ # RES_PID_C != 0 (multi C-tile, separate CTAs): each CTA runs its own
521
+ # for-ks loop. The res body is factored into _mhc_reduce_apply_res_block
522
+ # to avoid duplication with the shared-CTA branch.
523
+ if RES_PID_C == 0:
524
+ if pid_c == 0:
525
+ rn_post_local = tl.arange(0, N_POW2)
526
+ rn_post_global = n + rn_post_local
527
+ rn_res_local = tl.arange(0, N_POW2_RES)
528
+ rn_res_global = 2 * n + rn_res_local
529
+
530
+ acc_post = tl.zeros([BLOCK_M, N_POW2], dtype=tl.float32)
531
+ acc_res = tl.zeros([BLOCK_M, N_POW2_RES], dtype=tl.float32)
532
+ for ks in range(ACTUAL_KSPLIT):
533
+ acc_post += tl.load(
534
+ acc_ptr
535
+ + ks * stride_acc_k
536
+ + rm[:, None] * stride_acc_m
537
+ + rn_post_global[None, :] * stride_acc_n,
538
+ mask=(rm[:, None] < M) & (rn_post_local[None, :] < n),
539
+ other=0.0,
540
+ )
541
+ acc_res += tl.load(
542
+ acc_ptr
543
+ + ks * stride_acc_k
544
+ + rm[:, None] * stride_acc_m
545
+ + rn_res_global[None, :] * stride_acc_n,
546
+ mask=(rm[:, None] < M) & (rn_res_local[None, :] < n_squared),
547
+ other=0.0,
548
+ )
549
+
550
+ bias_post = tl.load(
551
+ bias_ptr + rn_post_global,
552
+ mask=rn_post_local < n,
553
+ other=0.0,
554
+ ).to(tl.float32)
555
+ h_post = rsigma[:, None] * alpha_post * acc_post + bias_post[None, :]
556
+ out_post = tl.sigmoid(h_post) * hc_post_mult_value
557
+ tl.store(
558
+ out_ptr
559
+ + rm[:, None] * stride_out_m
560
+ + rn_post_local[None, :] * stride_out_n,
561
+ out_post,
562
+ mask=(rm[:, None] < M) & (rn_post_local[None, :] < n),
563
+ )
564
+
565
+ _mhc_reduce_apply_res_block(
566
+ acc_res,
567
+ rsigma,
568
+ rm,
569
+ rn_res_local,
570
+ rn_res_global,
571
+ alpha_res,
572
+ bias_ptr,
573
+ out_ptr,
574
+ M,
575
+ n,
576
+ n_squared,
577
+ N_POW2_RES,
578
+ stride_out_m,
579
+ stride_out_n,
580
+ BLOCK_M,
581
+ NUM_SINKHORN_ITERS,
582
+ )
583
+ else:
584
+ if pid_c == 0:
585
+ rn_post_local = tl.arange(0, N_POW2)
586
+ rn_post_global = n + rn_post_local
587
+ acc_post = tl.zeros([BLOCK_M, N_POW2], dtype=tl.float32)
588
+ for ks in range(ACTUAL_KSPLIT):
589
+ acc_post += tl.load(
590
+ acc_ptr
591
+ + ks * stride_acc_k
592
+ + rm[:, None] * stride_acc_m
593
+ + rn_post_global[None, :] * stride_acc_n,
594
+ mask=(rm[:, None] < M) & (rn_post_local[None, :] < n),
595
+ other=0.0,
596
+ )
597
+ bias_post = tl.load(
598
+ bias_ptr + rn_post_global,
599
+ mask=rn_post_local < n,
600
+ other=0.0,
601
+ ).to(tl.float32)
602
+ h_post = rsigma[:, None] * alpha_post * acc_post + bias_post[None, :]
603
+ out_post = tl.sigmoid(h_post) * hc_post_mult_value
604
+ tl.store(
605
+ out_ptr
606
+ + rm[:, None] * stride_out_m
607
+ + rn_post_local[None, :] * stride_out_n,
608
+ out_post,
609
+ mask=(rm[:, None] < M) & (rn_post_local[None, :] < n),
610
+ )
611
+
612
+ if pid_c == RES_PID_C:
613
+ rn_res_local = tl.arange(0, N_POW2_RES)
614
+ rn_res_global = 2 * n + rn_res_local
615
+ acc_res = tl.zeros([BLOCK_M, N_POW2_RES], dtype=tl.float32)
616
+ for ks in range(ACTUAL_KSPLIT):
617
+ acc_res += tl.load(
618
+ acc_ptr
619
+ + ks * stride_acc_k
620
+ + rm[:, None] * stride_acc_m
621
+ + rn_res_global[None, :] * stride_acc_n,
622
+ mask=(rm[:, None] < M) & (rn_res_local[None, :] < n_squared),
623
+ other=0.0,
624
+ )
625
+ _mhc_reduce_apply_res_block(
626
+ acc_res,
627
+ rsigma,
628
+ rm,
629
+ rn_res_local,
630
+ rn_res_global,
631
+ alpha_res,
632
+ bias_ptr,
633
+ out_ptr,
634
+ M,
635
+ n,
636
+ n_squared,
637
+ N_POW2_RES,
638
+ stride_out_m,
639
+ stride_out_n,
640
+ BLOCK_M,
641
+ NUM_SINKHORN_ITERS,
642
+ )
643
+
644
+
645
+ @triton.jit
646
+ def _mhc_post_kernel(
647
+ out_ptr, # (M, n, C) bf16 / fp16
648
+ x_ptr, # (M, C) bf16 / fp16 (layer_input from mhc())
649
+ residual_ptr, # (M, n, C) bf16 / fp16
650
+ post_mix_ptr, # (M, n) fp32 (mhc()'s h_post)
651
+ comb_mix_ptr, # (M, n, n) fp32 [src, dst] (mhc()'s h_res)
652
+ M,
653
+ C,
654
+ stride_x_m,
655
+ stride_x_c,
656
+ stride_res_m,
657
+ stride_res_n,
658
+ stride_res_c,
659
+ stride_out_m,
660
+ stride_out_n,
661
+ stride_out_c,
662
+ stride_post_m,
663
+ stride_post_n,
664
+ stride_comb_m,
665
+ stride_comb_src,
666
+ stride_comb_dst,
667
+ n: tl.constexpr,
668
+ BLOCK_M: tl.constexpr,
669
+ BLOCK_C: tl.constexpr,
670
+ ):
671
+ """Fused mhc_post kernel: compute one M-tile across all `n` output
672
+ streams and the full hidden dim.
673
+
674
+ out[m, j, c] = post_mix[m, j] * x[m, c]
675
+ + sum_h comb_mix[m, h, j] * residual[m, h, c]
676
+
677
+ Grid: ``(cdiv(M, BLOCK_M),)``. Each program loads ``post_mix``
678
+ (BLOCK_M, n) and ``comb_mix`` (BLOCK_M, n, n) once and reuses them
679
+ across the persistent loop over ``BLOCK_C``-sized C-tiles. The
680
+ ``n``-source-head contraction inside each C-tile is unrolled via
681
+ ``tl.static_range``: each iteration loads a 2-D ``residual`` slice and
682
+ a 1-D ``comb_mix`` row and accumulates ``comb_h * res_h`` into
683
+ ``out_tile``. This avoids materializing a (BLOCK_M, n, n, BLOCK_C)
684
+ outer-product intermediate. Requires ``n`` to be a power of 2 so
685
+ ``tl.arange(0, n)`` compiles.
686
+ """
687
+ pid_m = tl.program_id(0)
688
+
689
+ rm = pid_m * BLOCK_M + tl.arange(0, BLOCK_M)
690
+ i_n = tl.arange(0, n)
691
+
692
+ m_mask = rm < M
693
+
694
+ post_mix_tile = tl.load(
695
+ post_mix_ptr + rm[:, None] * stride_post_m + i_n[None, :] * stride_post_n,
696
+ mask=m_mask[:, None],
697
+ other=0.0,
698
+ )
699
+
700
+ # Pre-load each row of the per-token (n_src, n_dst) comb_mix matrix into
701
+ # a tuple of 2-D tiles. Each ``comb_rows[h]`` has shape (BLOCK_M, n_dst)
702
+ # and lives in registers across the C-loop, eliminating per-C-tile
703
+ # reloads of the small per-token coefficients.
704
+ comb_rows = ()
705
+ for h in tl.static_range(n):
706
+ comb_rows = comb_rows + (
707
+ tl.load(
708
+ comb_mix_ptr
709
+ + rm[:, None] * stride_comb_m
710
+ + h * stride_comb_src
711
+ + i_n[None, :] * stride_comb_dst,
712
+ mask=m_mask[:, None],
713
+ other=0.0,
714
+ ),
715
+ )
716
+
717
+ for c_start in range(0, C, BLOCK_C):
718
+ rc = c_start + tl.arange(0, BLOCK_C)
719
+ c_mask = rc < C
720
+
721
+ # ``x`` and ``residual`` are bf16 / fp16 in production. Keeping them
722
+ # in their native dtype halves on-chip footprint vs an upfront fp32
723
+ # promotion; mixed-dtype multiply with fp32 ``post_mix`` / ``comb``
724
+ # is auto-promoted by Triton with an fp32 accumulator.
725
+ x_tile = tl.load(
726
+ x_ptr + rm[:, None] * stride_x_m + rc[None, :] * stride_x_c,
727
+ mask=m_mask[:, None] & c_mask[None, :],
728
+ other=0.0,
729
+ cache_modifier=".cg",
730
+ )
731
+
732
+ out_tile = post_mix_tile[:, :, None] * x_tile[:, None, :].to(tl.float32)
733
+
734
+ for h in tl.static_range(n):
735
+ res_h = tl.load(
736
+ residual_ptr
737
+ + rm[:, None] * stride_res_m
738
+ + h * stride_res_n
739
+ + rc[None, :] * stride_res_c,
740
+ mask=m_mask[:, None] & c_mask[None, :],
741
+ other=0.0,
742
+ cache_modifier=".cg",
743
+ )
744
+ comb_h = comb_rows[h] # (BLOCK_M, n_dst), pre-loaded 2-D tile
745
+ out_tile += comb_h[:, :, None] * res_h[:, None, :].to(tl.float32)
746
+
747
+ tl.store(
748
+ out_ptr
749
+ + rm[:, None, None] * stride_out_m
750
+ + i_n[None, :, None] * stride_out_n
751
+ + rc[None, None, :] * stride_out_c,
752
+ out_tile.to(out_ptr.dtype.element_ty),
753
+ mask=m_mask[:, None, None] & c_mask[None, None, :],
754
+ cache_modifier=".cs",
755
+ )
756
+
757
+
758
+ _mhc_post_pre_split_kernel_repr = make_kernel_repr(
759
+ "_mhc_post_pre_split_kernel",
760
+ [
761
+ "n",
762
+ "C",
763
+ "stride_phi_k",
764
+ "stride_phi_n",
765
+ "BLOCK_M",
766
+ "BLOCK_C",
767
+ "N_TOTAL_POW2",
768
+ ],
769
+ )
770
+
771
+
772
+ @triton.jit(repr=_mhc_post_pre_split_kernel_repr)
773
+ def _mhc_post_pre_split_kernel(
774
+ # mhc_post inputs
775
+ layer_input_ptr, # (M, C) x.dtype - attn/ffn output
776
+ residual_in_ptr, # (M, n, C) x.dtype - prev-layer multi-stream residual
777
+ post_mix_ptr, # (M, n) fp32 - h_post from preceding mhc_pre
778
+ comb_mix_ptr, # (M, n, n) fp32 - h_res from preceding mhc_pre
779
+ # mhc_post output (also the next mhc_pre's flattened x input)
780
+ residual_out_ptr, # (M, n, C) x.dtype - new residual; consumed by next layer's hc_post
781
+ # next mhc_pre's split-K partials
782
+ phi_ptr, # (n*C, N=2n+n^2) x.dtype - projection matrix, cols [pre|post|res]
783
+ acc_ptr, # (NUM_KSPLIT=cdiv(C, BLOCK_C), M, N) fp32 - GEMM partials
784
+ acc_sq_ptr, # (NUM_KSPLIT, M) fp32 - sum-of-squares partials
785
+ M,
786
+ N: tl.constexpr, # = 2*n + n_squared
787
+ n: tl.constexpr,
788
+ C: tl.constexpr,
789
+ stride_x_m,
790
+ stride_x_c,
791
+ stride_resin_m,
792
+ stride_resin_n,
793
+ stride_resin_c,
794
+ stride_post_m,
795
+ stride_post_n,
796
+ stride_comb_m,
797
+ stride_comb_src,
798
+ stride_comb_dst,
799
+ stride_resout_m,
800
+ stride_resout_n,
801
+ stride_resout_c,
802
+ stride_phi_k: tl.constexpr,
803
+ stride_phi_n: tl.constexpr,
804
+ stride_acc_k,
805
+ stride_acc_m,
806
+ stride_acc_n,
807
+ stride_acc_sq_k,
808
+ stride_acc_sq_m,
809
+ BLOCK_M: tl.constexpr,
810
+ BLOCK_C: tl.constexpr,
811
+ N_TOTAL_POW2: tl.constexpr,
812
+ ):
813
+ """Fused mhc_post + (next) mhc_pre split-K kernel.
814
+
815
+ Per (M-tile, C-tile) — n streams unrolled via tl.static_range — this CTA:
816
+ 1. Computes the new mHC residual stream (mhc_post step):
817
+ residual_out[m, j, c] = post_mix[m, j] * layer_input[m, c]
818
+ + sum_h comb_mix[m, h, j] * residual_in[m, h, c]
819
+ 2. With that residual tile still live in registers, contributes the next
820
+ mhc_pre's split-K GEMM partials over the same C-tile, treating the
821
+ residual as the next pre's flattened x = (M, n*C):
822
+ acc[pid_c, m, :N] += sum_j x_j(:, c_block:+BLOCK_C)
823
+ @ phi[j*C+c_block:+BLOCK_C, :N]
824
+ acc_sq[pid_c, m] += sum_j ||x_j(:, c_block:+BLOCK_C)||^2
825
+ where ``x_j`` is the j-th stream of residual_out (= the "h" index of
826
+ the flattened pre input ``x[m, h*C+c]``).
827
+
828
+ The C-tile axis IS the pre's split-K axis: each CTA owns one of
829
+ ``cdiv(C, BLOCK_C)`` non-overlapping K-splits of the next-pre GEMM. The
830
+ remaining apply / RMS / Sinkhorn work is finished by a separate launch
831
+ of ``_mhc_reduce_apply_kernel``, which re-reads ``residual_out`` as its
832
+ ``x`` operand for the apply-pre step.
833
+
834
+ The post output (``residual_out``) is still written to HBM because the
835
+ next layer's ``hc_post`` consumes it as its own ``residual_in``. The
836
+ HBM saving vs the unfused chain comes from not re-reading it as the
837
+ next-pre's GEMM operand.
838
+
839
+ Grid: ``(cdiv(M, BLOCK_M), cdiv(C, BLOCK_C))``.
840
+ """
841
+ pid_m = tl.program_id(0)
842
+ pid_c = tl.program_id(1)
843
+
844
+ rm = pid_m * BLOCK_M + tl.arange(0, BLOCK_M)
845
+ rc = pid_c * BLOCK_C + tl.arange(0, BLOCK_C)
846
+ rn = tl.arange(0, N_TOTAL_POW2)
847
+ i_n = tl.arange(0, n)
848
+
849
+ m_mask = rm < M
850
+ c_mask = rc < C
851
+
852
+ out_dtype = residual_out_ptr.dtype.element_ty
853
+
854
+ # --- 1) Load small post-step operands once. ---
855
+ post_mix_tile = tl.load(
856
+ post_mix_ptr + rm[:, None] * stride_post_m + i_n[None, :] * stride_post_n,
857
+ mask=m_mask[:, None],
858
+ other=0.0,
859
+ ) # (BLOCK_M, n) fp32
860
+
861
+ # comb_mix as a single (BLOCK_M, n_src, n_dst) 3D tile.
862
+ comb_3d = tl.load(
863
+ comb_mix_ptr
864
+ + rm[:, None, None] * stride_comb_m
865
+ + i_n[None, :, None] * stride_comb_src
866
+ + i_n[None, None, :] * stride_comb_dst,
867
+ mask=m_mask[:, None, None],
868
+ other=0.0,
869
+ ) # (BLOCK_M, n_src, n_dst) fp32
870
+
871
+ x_tile = tl.load(
872
+ layer_input_ptr + rm[:, None] * stride_x_m + rc[None, :] * stride_x_c,
873
+ mask=m_mask[:, None] & c_mask[None, :],
874
+ other=0.0,
875
+ cache_modifier=".cg",
876
+ ) # (BLOCK_M, BLOCK_C) x.dtype
877
+
878
+ # All n source streams of residual_in as one (BLOCK_M, n_src, BLOCK_C) tile.
879
+ res_3d = tl.load(
880
+ residual_in_ptr
881
+ + rm[:, None, None] * stride_resin_m
882
+ + i_n[None, :, None] * stride_resin_n
883
+ + rc[None, None, :] * stride_resin_c,
884
+ mask=m_mask[:, None, None] & c_mask[None, None, :],
885
+ other=0.0,
886
+ cache_modifier=".cg",
887
+ ) # (BLOCK_M, n_src, BLOCK_C) x.dtype
888
+
889
+ # --- 2) Post step: build (BLOCK_M, n_dst, BLOCK_C) residual_out via tl.sum.
890
+ # Conceptually: out[m, j, c] = post[m, j] * x[m, c]
891
+ # + sum_h_src comb[m, h_src, j] * res[m, h_src, c].
892
+ # The 4D outer product `comb[:, :, :, None] * res[:, :, None, :]` of shape
893
+ # (BLOCK_M, n_src, n_dst, BLOCK_C) is folded into tl.sum(axis=1); Triton
894
+ # fuses the broadcast + sum into a register-resident reduction without
895
+ # materializing the full 4D intermediate. Measured ~25-40% faster than the
896
+ # manual static_range(n) per-h load+accumulate at M ∈ {1..256}, hc=4,
897
+ # C=4096.
898
+ out_tile = post_mix_tile[:, :, None] * x_tile[:, None, :].to(tl.float32)
899
+ out_tile += tl.sum(
900
+ comb_3d[:, :, :, None] * res_3d[:, :, None, :].to(tl.float32),
901
+ axis=1,
902
+ )
903
+
904
+ # Store residual_out for next layer's hc_post (separate from the dot below).
905
+ tl.store(
906
+ residual_out_ptr
907
+ + rm[:, None, None] * stride_resout_m
908
+ + i_n[None, :, None] * stride_resout_n
909
+ + rc[None, None, :] * stride_resout_c,
910
+ out_tile.to(out_dtype),
911
+ mask=m_mask[:, None, None] & c_mask[None, None, :],
912
+ cache_modifier=".cs",
913
+ )
914
+
915
+ # --- 3) Next-pre split-K partial GEMM + sqrsum, sharing out_tile in registers. ---
916
+ # Reshape (BLOCK_M, n, BLOCK_C) → (BLOCK_M, n*BLOCK_C) as the next pre's
917
+ # flattened x for this K-split. The matching phi rows are (n, BLOCK_C, N)
918
+ # tiled at offsets ``h*C + c`` for h in [0, n), c in [c_block, c_block+BLOCK_C);
919
+ # we 3D-load and reshape to (n*BLOCK_C, N_TOTAL_POW2) so a single tl.dot
920
+ # handles all n streams' contribution to this K-split.
921
+ out_flat = tl.reshape(out_tile, [BLOCK_M, n * BLOCK_C])
922
+ out_flat_cast = out_flat.to(out_dtype)
923
+
924
+ k_offset_2d = i_n[:, None] * C + rc[None, :] # (n, BLOCK_C)
925
+ phi_3d = tl.load(
926
+ phi_ptr
927
+ + k_offset_2d[:, :, None] * stride_phi_k
928
+ + rn[None, None, :] * stride_phi_n,
929
+ mask=c_mask[None, :, None] & (rn[None, None, :] < N),
930
+ other=0.0,
931
+ ).to(
932
+ out_dtype
933
+ ) # (n, BLOCK_C, N_TOTAL_POW2) phi.dtype
934
+ phi_flat = tl.reshape(phi_3d, [n * BLOCK_C, N_TOTAL_POW2])
935
+
936
+ acc_gemm = tl.dot(out_flat_cast, phi_flat)
937
+ acc_sq = tl.sum(out_flat * out_flat, axis=1)
938
+
939
+ # --- 4) Write split-K partials for the reduce-apply kernel. ---
940
+ tl.store(
941
+ acc_ptr
942
+ + pid_c * stride_acc_k
943
+ + rm[:, None] * stride_acc_m
944
+ + rn[None, :] * stride_acc_n,
945
+ acc_gemm,
946
+ mask=m_mask[:, None] & (rn[None, :] < N),
947
+ )
948
+ tl.store(
949
+ acc_sq_ptr + pid_c * stride_acc_sq_k + rm * stride_acc_sq_m,
950
+ acc_sq,
951
+ mask=m_mask,
952
+ )
953
+
954
+
955
+ @triton.jit
956
+ def _mhc_post_pre_reduce_apply_res_block(
957
+ acc_res, # (BLOCK_M, N_POW2_RES) fp32, already reduced over ks
958
+ rsigma, # (BLOCK_M,) fp32
959
+ rm,
960
+ rn_res_local,
961
+ rn_res_global,
962
+ alpha_res,
963
+ bias_ptr,
964
+ h_res_ptr,
965
+ M,
966
+ n: tl.constexpr,
967
+ n_squared: tl.constexpr,
968
+ N_POW2_RES: tl.constexpr,
969
+ stride_hr_m,
970
+ stride_hr_n,
971
+ BLOCK_M: tl.constexpr,
972
+ NUM_SINKHORN_ITERS: tl.constexpr,
973
+ ASYMMETRIC_EXP_DOMAIN: tl.constexpr,
974
+ hc_sinkhorn_eps: tl.constexpr,
975
+ ):
976
+ """Compute h_res = rsigma * alpha_res * acc_res + bias_res, optionally run
977
+ Sinkhorn-Knopp, and store to ``h_res_ptr`` (flattened (M, n²)).
978
+
979
+ Called from the dedicated res-stream CTA in
980
+ ``_mhc_post_pre_reduce_apply_kernel``.
981
+
982
+ Sinkhorn variant selected by ``ASYMMETRIC_EXP_DOMAIN``:
983
+ False (default) → canonical log-domain Sinkhorn-Knopp: symmetric row/col
984
+ normalization, no eps perturbation.
985
+ True → HIP-compatible exp-domain Sinkhorn
986
+ (``mhc_kernels.cu:493-507``): first iter is asymmetric
987
+ (softmax(row) + eps, then div(col + eps)); remaining
988
+ ``NUM_SINKHORN_ITERS - 1`` iters are symmetric div(row + eps) /
989
+ div(col + eps). ``hc_sinkhorn_eps`` is unused when False.
990
+ """
991
+ bias_res = tl.load(
992
+ bias_ptr + rn_res_global,
993
+ mask=rn_res_local < n_squared,
994
+ other=0.0,
995
+ ).to(tl.float32)
996
+ h_res = rsigma[:, None] * alpha_res * acc_res + bias_res[None, :]
997
+
998
+ if NUM_SINKHORN_ITERS > 0:
999
+ if ASYMMETRIC_EXP_DOMAIN:
1000
+ # Asymmetric first iter + (NUM_SINKHORN_ITERS - 1) symmetric iters,
1001
+ # mirroring HIP exactly.
1002
+ A = tl.reshape(h_res, (BLOCK_M, n, n))
1003
+ row_max = tl.max(A, axis=2)
1004
+ P = tl.exp(A - row_max[:, :, None])
1005
+ row_sum = tl.sum(P, axis=2)
1006
+ P = P / row_sum[:, :, None] + hc_sinkhorn_eps
1007
+ col_sum = tl.sum(P, axis=1)
1008
+ P = P / (col_sum[:, None, :] + hc_sinkhorn_eps)
1009
+ for _ in range(NUM_SINKHORN_ITERS - 1):
1010
+ row_sum = tl.sum(P, axis=2)
1011
+ P = P / (row_sum[:, :, None] + hc_sinkhorn_eps)
1012
+ col_sum = tl.sum(P, axis=1)
1013
+ P = P / (col_sum[:, None, :] + hc_sinkhorn_eps)
1014
+ out_res = tl.reshape(P, (BLOCK_M, n_squared))
1015
+ else:
1016
+ LOG2_E: tl.constexpr = 1.4426950408889634
1017
+
1018
+ log2_A = tl.reshape(h_res, (BLOCK_M, n, n)) * LOG2_E
1019
+ log2_u = tl.zeros((BLOCK_M, n), dtype=tl.float32)
1020
+ log2_v = tl.zeros((BLOCK_M, n), dtype=tl.float32)
1021
+
1022
+ for _ in range(NUM_SINKHORN_ITERS):
1023
+ scaled_row = log2_A + log2_v[:, None, :]
1024
+ row_max = tl.max(scaled_row, axis=2)
1025
+ exp_shifted = tl.exp2(scaled_row - row_max[:, :, None])
1026
+ row_sum_exp = tl.sum(exp_shifted, axis=2)
1027
+ log2_row_sums = row_max + tl.log2(row_sum_exp)
1028
+ log2_u = -log2_row_sums
1029
+
1030
+ scaled_col = log2_A + log2_u[:, :, None]
1031
+ col_max = tl.max(scaled_col, axis=1)
1032
+ exp_shifted = tl.exp2(scaled_col - col_max[:, None, :])
1033
+ col_sum_exp = tl.sum(exp_shifted, axis=1)
1034
+ log2_col_sums = col_max + tl.log2(col_sum_exp)
1035
+ log2_v = -log2_col_sums
1036
+
1037
+ log2_P = log2_A + log2_u[:, :, None] + log2_v[:, None, :]
1038
+ P = tl.exp2(log2_P)
1039
+ out_res = tl.reshape(P, (BLOCK_M, n_squared))
1040
+ else:
1041
+ out_res = h_res
1042
+
1043
+ tl.store(
1044
+ h_res_ptr + rm[:, None] * stride_hr_m + rn_res_local[None, :] * stride_hr_n,
1045
+ out_res,
1046
+ mask=(rm[:, None] < M) & (rn_res_local[None, :] < n_squared),
1047
+ )
1048
+
1049
+
1050
+ @triton.jit
1051
+ def _mhc_post_pre_reduce_apply_kernel(
1052
+ acc_ptr, # Unified split-K partials: (NUM_KSPLIT, M, n + n + n_squared), layout [pre | post | res]
1053
+ acc_sq_ptr, # Sum-of-squares partials: (NUM_KSPLIT, M)
1054
+ alpha_ptr, # (3,) fp32 — [alpha_pre, alpha_post, alpha_res]
1055
+ bias_ptr, # (n + n + n_squared,) fp32
1056
+ x_ptr, # (M, n*C)
1057
+ h_post_ptr, # (M, n) — written by the post CTA
1058
+ h_res_ptr, # (M, n*n) — written by the res CTA (flat n_squared view)
1059
+ layer_input_ptr, # (M, C) in x.dtype
1060
+ M,
1061
+ K: tl.constexpr,
1062
+ n: tl.constexpr,
1063
+ n_squared: tl.constexpr,
1064
+ C: tl.constexpr,
1065
+ eps: tl.constexpr,
1066
+ hc_pre_eps: tl.constexpr,
1067
+ hc_post_mult_value: tl.constexpr,
1068
+ stride_acc_k,
1069
+ stride_acc_m,
1070
+ stride_acc_n,
1071
+ stride_acc_sq_k,
1072
+ stride_acc_sq_m,
1073
+ stride_xm,
1074
+ stride_xk,
1075
+ stride_hp_m,
1076
+ stride_hp_n,
1077
+ stride_hr_m,
1078
+ stride_hr_n,
1079
+ stride_li_m,
1080
+ stride_li_c,
1081
+ BLOCK_M: tl.constexpr,
1082
+ BLOCK_C: tl.constexpr,
1083
+ N_POW2: tl.constexpr,
1084
+ N_POW2_RES: tl.constexpr,
1085
+ ACTUAL_KSPLIT: tl.constexpr,
1086
+ KSPLIT_POW2: tl.constexpr,
1087
+ BLOCK_M_POST_RES: tl.constexpr,
1088
+ NUM_SINKHORN_ITERS: tl.constexpr,
1089
+ ASYMMETRIC_EXP_DOMAIN: tl.constexpr,
1090
+ hc_sinkhorn_eps: tl.constexpr,
1091
+ ):
1092
+ """
1093
+ Reduce-and-apply kernel for the split-K mHC pipeline (Eq 15-19 + apply).
1094
+
1095
+ Grid: ``(cdiv(M, BLOCK_M), cdiv(C, BLOCK_C) + 2)``. For each ``pid_m``:
1096
+
1097
+ - ``pid_c < NUM_C_BLOCKS`` : pre reduce + RMS + apply-pre on this BLOCK_C
1098
+ slice. Writes ``layer_input[:, rc]``.
1099
+ - ``pid_c == NUM_C_BLOCKS`` : post stream only (``hc_post_mult_value *
1100
+ sigmoid``). Writes ``out[:, :n]``.
1101
+ - ``pid_c == NUM_C_BLOCKS+1``: res stream + log-domain Sinkhorn (when
1102
+ NUM_SINKHORN_ITERS > 0). Writes
1103
+ ``out[:, n:n+n_squared]``.
1104
+
1105
+ The three branches share only ``rsigma`` (which each CTA recomputes from
1106
+ ``acc_sq``). Compared to the earlier layout where the post and res CTAs
1107
+ were piggybacked onto ``pid_c == 0`` / ``pid_c == RES_PID_C`` and did
1108
+ apply-pre work on top, these dedicated CTAs do **only** their stream's
1109
+ activation — so the 20-iter Sinkhorn on the res CTA runs in parallel with
1110
+ apply-pre on the other ``NUM_C_BLOCKS`` CTAs rather than serializing
1111
+ behind it.
1112
+
1113
+ Sinkhorn requires ``n_squared`` to be a power of two; the wrapper enforces
1114
+ this when ``NUM_SINKHORN_ITERS > 0``.
1115
+ """
1116
+ # pid_m = tl.program_id(0)
1117
+ # pid_c = tl.program_id(1)
1118
+ pid = tl.program_id(0)
1119
+
1120
+ NUM_C_BLOCKS = tl.cdiv(C, BLOCK_C)
1121
+ NUM_M_BLOCKS = tl.cdiv(M, BLOCK_M)
1122
+ NUM_M_BLOCKS_POST_RES = tl.cdiv(M, BLOCK_M_POST_RES)
1123
+
1124
+ K_INV: tl.constexpr = 1.0 / K
1125
+ # POST_PID == NUM_C_BLOCKS, RES_PID == NUM_C_BLOCKS + 1 (inlined below to
1126
+ # sidestep Triton's constexpr-arithmetic restriction on `: tl.constexpr =`
1127
+ # binding sites).
1128
+
1129
+ ks_offs = tl.arange(0, KSPLIT_POW2)
1130
+ if KSPLIT_POW2 != ACTUAL_KSPLIT:
1131
+ ks_mask = ks_offs < ACTUAL_KSPLIT
1132
+ else:
1133
+ ks_mask = tl.full((1,), 1, dtype=tl.int1)
1134
+
1135
+ if pid < NUM_M_BLOCKS * NUM_C_BLOCKS:
1136
+ # ---- Apply-pre branch ----
1137
+ pid_m = pid // NUM_C_BLOCKS
1138
+ pid_c = pid % NUM_C_BLOCKS
1139
+ rc = pid_c * BLOCK_C + tl.arange(0, BLOCK_C)
1140
+ m_offs = pid_m * BLOCK_M + tl.arange(0, BLOCK_M)
1141
+ m_mask = m_offs < M
1142
+ acc_sq = tl.load(
1143
+ acc_sq_ptr
1144
+ + ks_offs[:, None] * stride_acc_sq_k
1145
+ + m_offs[None, :] * stride_acc_sq_m,
1146
+ mask=ks_mask[:, None] & m_mask[None, :],
1147
+ other=0.0,
1148
+ )
1149
+ acc_sq = tl.sum(acc_sq, 0)
1150
+ rsigma = tl.math.rsqrt((acc_sq * K_INV) + eps)
1151
+
1152
+ rn_pre = tl.arange(0, N_POW2)
1153
+
1154
+ acc_pre = tl.load(
1155
+ acc_ptr
1156
+ + ks_offs[:, None, None] * stride_acc_k
1157
+ + m_offs[None, :, None] * stride_acc_m
1158
+ + rn_pre[None, None, :] * stride_acc_n,
1159
+ mask=ks_mask[:, None, None]
1160
+ & m_mask[None, :, None]
1161
+ & (rn_pre[None, None, :] < n),
1162
+ other=0.0,
1163
+ )
1164
+ acc_pre = tl.sum(acc_pre, 0)
1165
+
1166
+ alpha_pre = tl.load(alpha_ptr + 0)
1167
+ bias_pre = tl.load(bias_ptr + rn_pre, mask=rn_pre < n, other=0.0).to(tl.float32)
1168
+ h_pre = rsigma[:, None] * alpha_pre * acc_pre + bias_pre[None, :]
1169
+ pre_mix_2d = tl.sigmoid(h_pre) + hc_pre_eps
1170
+
1171
+ _mhc_apply_pre_mix_tile(
1172
+ x_ptr,
1173
+ layer_input_ptr,
1174
+ pre_mix_2d,
1175
+ m_offs,
1176
+ rc,
1177
+ rn_pre,
1178
+ M,
1179
+ C,
1180
+ n,
1181
+ stride_xm,
1182
+ stride_xk,
1183
+ stride_li_m,
1184
+ stride_li_c,
1185
+ )
1186
+ elif pid < NUM_M_BLOCKS * NUM_C_BLOCKS + NUM_M_BLOCKS_POST_RES:
1187
+ pid = pid - NUM_M_BLOCKS * NUM_C_BLOCKS
1188
+ m_offs_post_res = pid * BLOCK_M_POST_RES + tl.arange(0, BLOCK_M_POST_RES)
1189
+ m_mask_post_res = m_offs_post_res < M
1190
+ acc_sq_post_res = tl.load(
1191
+ acc_sq_ptr
1192
+ + ks_offs[:, None] * stride_acc_sq_k
1193
+ + m_offs_post_res[None, :] * stride_acc_sq_m,
1194
+ mask=ks_mask[:, None] & m_mask_post_res[None, :],
1195
+ other=0.0,
1196
+ )
1197
+ acc_sq_post_res = tl.sum(acc_sq_post_res, 0)
1198
+ rsigma_post_res = tl.math.rsqrt((acc_sq_post_res * K_INV) + eps)
1199
+ # ---- Post stream branch (pid_c == NUM_C_BLOCKS) ----
1200
+ rn_post_local = tl.arange(0, N_POW2)
1201
+ rn_post_global = n + rn_post_local
1202
+
1203
+ acc_post = tl.load(
1204
+ acc_ptr
1205
+ + ks_offs[:, None, None] * stride_acc_k
1206
+ + m_offs_post_res[None, :, None] * stride_acc_m
1207
+ + rn_post_global[None, None, :] * stride_acc_n,
1208
+ mask=ks_mask[:, None, None]
1209
+ & m_mask_post_res[None, :, None]
1210
+ & (rn_post_local[None, None, :] < n),
1211
+ other=0.0,
1212
+ )
1213
+ acc_post = tl.sum(acc_post, 0)
1214
+
1215
+ alpha_post = tl.load(alpha_ptr + 1)
1216
+ bias_post = tl.load(
1217
+ bias_ptr + rn_post_global, mask=rn_post_local < n, other=0.0
1218
+ ).to(tl.float32)
1219
+ h_post = rsigma_post_res[:, None] * alpha_post * acc_post + bias_post[None, :]
1220
+ out_post = tl.sigmoid(h_post) * hc_post_mult_value
1221
+ tl.store(
1222
+ h_post_ptr
1223
+ + m_offs_post_res[:, None] * stride_hp_m
1224
+ + rn_post_local[None, :] * stride_hp_n,
1225
+ out_post,
1226
+ mask=m_mask_post_res[:, None] & (rn_post_local[None, :] < n),
1227
+ )
1228
+ else:
1229
+ pid = pid - NUM_M_BLOCKS * NUM_C_BLOCKS - NUM_M_BLOCKS_POST_RES
1230
+ m_offs_post_res = pid * BLOCK_M_POST_RES + tl.arange(0, BLOCK_M_POST_RES)
1231
+ m_mask_post_res = m_offs_post_res < M
1232
+ acc_sq_post_res = tl.load(
1233
+ acc_sq_ptr
1234
+ + ks_offs[:, None] * stride_acc_sq_k
1235
+ + m_offs_post_res[None, :] * stride_acc_sq_m,
1236
+ mask=ks_mask[:, None] & m_mask_post_res[None, :],
1237
+ other=0.0,
1238
+ )
1239
+ acc_sq_post_res = tl.sum(acc_sq_post_res, 0)
1240
+ rsigma_post_res = tl.math.rsqrt((acc_sq_post_res * K_INV) + eps)
1241
+ # ---- Res stream + Sinkhorn branch (pid_c == NUM_C_BLOCKS + 1) ----
1242
+ rn_res_local = tl.arange(0, N_POW2_RES)
1243
+ rn_res_global = 2 * n + rn_res_local
1244
+
1245
+ acc_res = tl.load(
1246
+ acc_ptr
1247
+ + ks_offs[:, None, None] * stride_acc_k
1248
+ + m_offs_post_res[None, :, None] * stride_acc_m
1249
+ + rn_res_global[None, None, :] * stride_acc_n,
1250
+ mask=ks_mask[:, None, None]
1251
+ & m_mask_post_res[None, :, None]
1252
+ & (rn_res_local[None, None, :] < n_squared),
1253
+ other=0.0,
1254
+ )
1255
+ acc_res = tl.sum(acc_res, 0)
1256
+ alpha_res = tl.load(alpha_ptr + 2)
1257
+
1258
+ _mhc_post_pre_reduce_apply_res_block(
1259
+ acc_res,
1260
+ rsigma_post_res,
1261
+ m_offs_post_res,
1262
+ rn_res_local,
1263
+ rn_res_global,
1264
+ alpha_res,
1265
+ bias_ptr,
1266
+ h_res_ptr,
1267
+ M,
1268
+ n,
1269
+ n_squared,
1270
+ N_POW2_RES,
1271
+ stride_hr_m,
1272
+ stride_hr_n,
1273
+ BLOCK_M_POST_RES,
1274
+ NUM_SINKHORN_ITERS,
1275
+ ASYMMETRIC_EXP_DOMAIN,
1276
+ hc_sinkhorn_eps,
1277
+ )
build/torch-rocm/_triton_kernels/gated_delta_rule/__init__.py ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (C) 2024-2026, Advanced Micro Devices, Inc. All rights reserved.
3
+ # Adapted from flash-linear-attention: Copyright (c) 2023-2025, Songlin Yang, Yu Zhang
4
+
5
+ """
6
+ Gated Delta Rule Operations (Forward Only).
7
+
8
+ This module provides optimized Triton kernels for gated delta rule computations.
9
+
10
+ Available operations:
11
+ - Fused recurrent forward: _fused_recurrent_gated_delta_rule_fwd_kernel
12
+ - Chunk-based forward: chunk_gated_delta_rule_fwd
13
+ - Hidden state computation: chunk_gated_delta_rule_fwd_h
14
+ - Output computation: chunk_fwd_o
15
+
16
+ Note: Only forward pass is implemented. Backward pass is not supported in aiter.
17
+ For training with gradients, please use the flash-linear-attention library.
18
+ """
19
+
20
+ from .decode.fused_recurrent import _fused_recurrent_gated_delta_rule_fwd_kernel
21
+ from .prefill.chunk import (
22
+ chunk_gated_delta_rule_fwd,
23
+ chunk_gated_delta_rule_fwd_opt,
24
+ chunk_gated_delta_rule_fwd_opt_vk,
25
+ )
26
+ from .prefill.chunk_delta_h import (
27
+ chunk_gated_delta_rule_fwd_h,
28
+ chunk_gated_delta_rule_fwd_h_opt,
29
+ chunk_gated_delta_rule_fwd_h_opt_vk,
30
+ )
31
+ from .prefill.chunk_o import chunk_fwd_o, chunk_fwd_o_opt, chunk_fwd_o_opt_vk
32
+ from .prefill.fused_cumsum_kkt import fused_chunk_local_cumsum_scaled_dot_kkt_fwd
33
+ from .prefill.fused_solve_tril_recompute import fused_solve_tril_recompute_w_u
34
+ from . import gated_delta_rule_utils
35
+
36
+ __all__ = [
37
+ "_fused_recurrent_gated_delta_rule_fwd_kernel",
38
+ "chunk_gated_delta_rule_fwd",
39
+ "chunk_gated_delta_rule_fwd_opt",
40
+ "chunk_gated_delta_rule_fwd_opt_vk",
41
+ "chunk_gated_delta_rule_fwd_h",
42
+ "chunk_gated_delta_rule_fwd_h_opt",
43
+ "chunk_gated_delta_rule_fwd_h_opt_vk",
44
+ "chunk_fwd_o",
45
+ "chunk_fwd_o_opt",
46
+ "chunk_fwd_o_opt_vk",
47
+ "fused_chunk_local_cumsum_scaled_dot_kkt_fwd",
48
+ "fused_solve_tril_recompute_w_u",
49
+ "gated_delta_rule_utils",
50
+ ]
build/torch-rocm/_triton_kernels/gated_delta_rule/decode/__init__.py ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (C) 2024-2026, Advanced Micro Devices, Inc. All rights reserved.
3
+ # Adapted from flash-linear-attention: Copyright (c) 2023-2025, Songlin Yang, Yu Zhang
4
+
5
+ """
6
+ Gated Delta Rule Decode Operations (Forward Only).
7
+
8
+ This module provides optimized Triton kernels for decode/inference operations.
9
+ """
10
+
11
+ from .fused_rearrange_sigmoid_gdr import (
12
+ fused_rearrange_sigmoid_gated_delta_rule_update_kernel,
13
+ )
14
+ from .fused_recurrent import _fused_recurrent_gated_delta_rule_fwd_kernel
15
+ from .fused_sigmoid_gating_recurrent import fused_sigmoid_gating_delta_rule_update
16
+
17
+ __all__ = [
18
+ "_fused_recurrent_gated_delta_rule_fwd_kernel",
19
+ "fused_rearrange_sigmoid_gated_delta_rule_update_kernel",
20
+ "fused_sigmoid_gating_delta_rule_update",
21
+ ]
build/torch-rocm/_triton_kernels/gated_delta_rule/decode/causal_conv1d_split_qkv.py ADDED
@@ -0,0 +1,1098 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Optimized causal_conv1d_update: directly output split q/k/v."""
2
+
3
+ import torch
4
+ import triton
5
+ import triton.experimental.gluon.language as gl
6
+ import triton.language as tl
7
+ from triton.experimental import gluon
8
+
9
+ PAD_SLOT_ID = -1
10
+
11
+
12
+ @triton.jit()
13
+ def _causal_conv1d_update_split_qkv_kernel(
14
+ # Pointers to matrices
15
+ x_ptr, # (batch, dim, seqlen) where dim = 2*key_dim + value_dim
16
+ w_ptr, # (dim, width)
17
+ bias_ptr,
18
+ conv_state_ptr,
19
+ conv_state_indices_ptr,
20
+ q_ptr,
21
+ k_ptr,
22
+ v_ptr,
23
+ key_dim: tl.constexpr,
24
+ value_dim: tl.constexpr,
25
+ # Matrix dimensions
26
+ batch: int,
27
+ dim: tl.constexpr,
28
+ seqlen: tl.constexpr,
29
+ state_len: tl.constexpr,
30
+ num_cache_lines: tl.constexpr,
31
+ # Strides
32
+ stride_x_seq: tl.constexpr,
33
+ stride_x_dim: tl.constexpr,
34
+ stride_x_token: tl.constexpr,
35
+ stride_w_dim: tl.constexpr,
36
+ stride_w_width: tl.constexpr,
37
+ stride_conv_state_seq: tl.constexpr,
38
+ stride_conv_state_dim: tl.constexpr,
39
+ stride_conv_state_tok: tl.constexpr,
40
+ stride_state_indices: tl.constexpr,
41
+ stride_q_seq: tl.constexpr,
42
+ stride_q_dim: tl.constexpr,
43
+ stride_q_token: tl.constexpr,
44
+ stride_k_seq: tl.constexpr,
45
+ stride_k_dim: tl.constexpr,
46
+ stride_k_token: tl.constexpr,
47
+ stride_v_seq: tl.constexpr,
48
+ stride_v_dim: tl.constexpr,
49
+ stride_v_token: tl.constexpr,
50
+ # others
51
+ pad_slot_id: tl.constexpr,
52
+ # Meta-parameters
53
+ HAS_BIAS: tl.constexpr,
54
+ KERNEL_WIDTH: tl.constexpr,
55
+ SILU_ACTIVATION: tl.constexpr,
56
+ IS_CONTINUOUS_BATCHING: tl.constexpr,
57
+ NP2_STATELEN: tl.constexpr,
58
+ USE_PAD_SLOT: tl.constexpr,
59
+ BLOCK_N: tl.constexpr,
60
+ ):
61
+ idx_seq = tl.program_id(0)
62
+ if idx_seq >= batch:
63
+ return
64
+
65
+ # [BLOCK_N,] elements along the feature-dimension (channel)
66
+ idx_feats = tl.program_id(1) * BLOCK_N + tl.arange(0, BLOCK_N)
67
+
68
+ if IS_CONTINUOUS_BATCHING:
69
+ conv_state_batch_coord = tl.load(
70
+ conv_state_indices_ptr + idx_seq * stride_state_indices
71
+ ).to(tl.int64)
72
+ else:
73
+ conv_state_batch_coord = idx_seq
74
+
75
+ if USE_PAD_SLOT:
76
+ if conv_state_batch_coord == pad_slot_id:
77
+ return
78
+
79
+ # STEP 1: READ init_state data
80
+ conv_states_base = (
81
+ conv_state_ptr
82
+ + (conv_state_batch_coord * stride_conv_state_seq)
83
+ + (idx_feats * stride_conv_state_dim)
84
+ )
85
+ mask_w = idx_feats < dim
86
+
87
+ prior_tokens = conv_states_base
88
+ if KERNEL_WIDTH >= 2:
89
+ conv_states_ptrs = prior_tokens
90
+ col0 = tl.load(conv_states_ptrs, mask_w, 0.0)
91
+ if KERNEL_WIDTH >= 3:
92
+ conv_states_ptrs = prior_tokens + 1 * stride_conv_state_tok
93
+ col1 = tl.load(conv_states_ptrs, mask_w, 0.0)
94
+ if KERNEL_WIDTH >= 4:
95
+ conv_states_ptrs = prior_tokens + 2 * stride_conv_state_tok
96
+ col2 = tl.load(conv_states_ptrs, mask_w, 0.0)
97
+
98
+ # STEP 2: Update conv state (same as original)
99
+ idx_tokens = tl.arange(0, NP2_STATELEN)
100
+
101
+ conv_state_ptrs_source = (
102
+ conv_state_ptr
103
+ + (conv_state_batch_coord * stride_conv_state_seq)
104
+ + (idx_feats * stride_conv_state_dim)[None, :]
105
+ + ((idx_tokens + seqlen) * stride_conv_state_tok)[:, None]
106
+ )
107
+
108
+ mask = (
109
+ (conv_state_batch_coord < num_cache_lines)
110
+ & ((idx_tokens + seqlen) < state_len)[:, None]
111
+ & (idx_feats < dim)[None, :]
112
+ )
113
+ conv_state = tl.load(conv_state_ptrs_source, mask, other=0.0)
114
+
115
+ VAL = state_len - seqlen
116
+ x_base = x_ptr + (idx_seq * stride_x_seq) + (idx_feats * stride_x_dim)
117
+ x_ptrs = x_base[None, :] + ((idx_tokens - VAL) * stride_x_token)[:, None]
118
+
119
+ mask_x = (
120
+ (idx_tokens - VAL >= 0)[:, None]
121
+ & (idx_tokens - VAL < seqlen)[:, None]
122
+ & (idx_feats < dim)[None, :]
123
+ )
124
+ loaded_x = tl.load(x_ptrs, mask_x, 0.0)
125
+ tl.debug_barrier()
126
+
127
+ new_conv_state = tl.where(mask, conv_state, loaded_x)
128
+
129
+ conv_state_base = (
130
+ conv_state_ptr
131
+ + (conv_state_batch_coord * stride_conv_state_seq)
132
+ + (idx_feats * stride_conv_state_dim)
133
+ )
134
+ conv_state_ptrs_target = (
135
+ conv_state_base + (idx_tokens * stride_conv_state_tok)[:, None]
136
+ )
137
+ mask_store = (idx_tokens < state_len)[:, None] & (idx_feats < dim)[None, :]
138
+ tl.store(conv_state_ptrs_target, new_conv_state, mask_store)
139
+
140
+ # STEP 3: init accumulator
141
+ if HAS_BIAS:
142
+ bias = bias_ptr + idx_feats
143
+ mask_bias = idx_feats < dim
144
+ acc_preload = tl.load(bias, mask=mask_bias, other=0.0).to(tl.float32)
145
+ else:
146
+ acc_preload = tl.zeros((BLOCK_N,), dtype=tl.float32)
147
+
148
+ # STEP 4: PRE-LOAD WEIGHTS
149
+ w_base = w_ptr + (idx_feats * stride_w_dim)
150
+ mask_w = idx_feats < dim
151
+ if KERNEL_WIDTH >= 2:
152
+ w_ptrs = w_base + (0 * stride_w_width)
153
+ w_col0 = tl.load(w_ptrs, mask_w, other=0.0)
154
+ w_ptrs = w_base + (1 * stride_w_width)
155
+ w_col1 = tl.load(w_ptrs, mask_w, other=0.0)
156
+ if KERNEL_WIDTH >= 3:
157
+ w_ptrs = w_base + (2 * stride_w_width)
158
+ w_col2 = tl.load(w_ptrs, mask_w, other=0.0)
159
+ if KERNEL_WIDTH >= 4:
160
+ w_ptrs = w_base + (3 * stride_w_width)
161
+ w_col3 = tl.load(w_ptrs, mask_w, other=0.0)
162
+
163
+ x_base_1d = x_base
164
+ mask_x_1d = idx_feats < dim
165
+
166
+ # STEP 5: compute each token and write to split buffers
167
+ for idx_token in tl.static_range(seqlen):
168
+ acc = acc_preload
169
+
170
+ matrix_w = w_col0
171
+ matrix_x = col0
172
+ for j in tl.static_range(KERNEL_WIDTH):
173
+ if KERNEL_WIDTH == 2:
174
+ if j == 1:
175
+ matrix_w = w_col1
176
+ x_ptrs_1d = x_base_1d + idx_token * stride_x_token
177
+ matrix_x = tl.load(x_ptrs_1d, mask=mask_x_1d)
178
+ elif KERNEL_WIDTH == 3:
179
+ if j == 1:
180
+ matrix_w = w_col1
181
+ matrix_x = col1
182
+ elif j == 2:
183
+ matrix_w = w_col2
184
+ x_ptrs_1d = x_base_1d + idx_token * stride_x_token
185
+ matrix_x = tl.load(x_ptrs_1d, mask=mask_x_1d)
186
+ elif KERNEL_WIDTH == 4:
187
+ if j == 1:
188
+ matrix_w = w_col1
189
+ matrix_x = col1
190
+ elif j == 2:
191
+ matrix_w = w_col2
192
+ matrix_x = col2
193
+ elif j == 3:
194
+ matrix_w = w_col3
195
+ x_ptrs_1d = x_base_1d + idx_token * stride_x_token
196
+ matrix_x = tl.load(x_ptrs_1d, mask=mask_x_1d)
197
+
198
+ acc += matrix_x * matrix_w
199
+
200
+ # Update sliding window
201
+ if KERNEL_WIDTH == 2:
202
+ col0 = matrix_x
203
+ elif KERNEL_WIDTH == 3:
204
+ col0 = col1
205
+ col1 = matrix_x
206
+ elif KERNEL_WIDTH == 4:
207
+ col0 = col1
208
+ col1 = col2
209
+ col2 = matrix_x
210
+
211
+ # Apply activation
212
+ if SILU_ACTIVATION:
213
+ acc = acc / (1 + tl.exp(-acc))
214
+
215
+ mask_feat = (idx_token < seqlen) & (idx_feats < dim)
216
+
217
+ # Query: idx_feats in [0, key_dim)
218
+ is_query = idx_feats < key_dim
219
+ q_feat_idx = idx_feats # 0-based index within query
220
+ q_ptrs = (
221
+ q_ptr
222
+ + idx_seq * stride_q_seq
223
+ + idx_token * stride_q_token
224
+ + q_feat_idx * stride_q_dim
225
+ )
226
+ tl.store(q_ptrs, acc, mask=mask_feat & is_query)
227
+
228
+ # Key: idx_feats in [key_dim, 2*key_dim)
229
+ is_key = (idx_feats >= key_dim) & (idx_feats < 2 * key_dim)
230
+ k_feat_idx = idx_feats - key_dim
231
+ k_ptrs = (
232
+ k_ptr
233
+ + idx_seq * stride_k_seq
234
+ + idx_token * stride_k_token
235
+ + k_feat_idx * stride_k_dim
236
+ )
237
+ tl.store(k_ptrs, acc, mask=mask_feat & is_key)
238
+
239
+ # Value: idx_feats in [2*key_dim, 2*key_dim+value_dim)
240
+ is_value = (idx_feats >= 2 * key_dim) & (idx_feats < 2 * key_dim + value_dim)
241
+ v_feat_idx = idx_feats - 2 * key_dim
242
+ v_ptrs = (
243
+ v_ptr
244
+ + idx_seq * stride_v_seq
245
+ + idx_token * stride_v_token
246
+ + v_feat_idx * stride_v_dim
247
+ )
248
+ tl.store(v_ptrs, acc, mask=mask_feat & is_value)
249
+
250
+
251
+ @tl.core.builtin
252
+ def load_conv_weights(
253
+ w_base,
254
+ feats,
255
+ stride_w_dim,
256
+ stride_w_width,
257
+ conv_width: int,
258
+ mask=None,
259
+ other=0.0,
260
+ _semantic=None,
261
+ ) -> tl.tuple:
262
+ weights = [
263
+ tl.load(w_base + i * stride_w_width, mask=mask, other=other)
264
+ for i in range(conv_width)
265
+ ]
266
+ return tl.tuple(weights)
267
+
268
+
269
+ @tl.core.builtin
270
+ def load_conv_states(
271
+ conv_state_ptr,
272
+ feats,
273
+ stride_conv_state_dim,
274
+ stride_conv_state_tok,
275
+ conv_width: int,
276
+ mask=None,
277
+ other=0.0,
278
+ _semantic=None,
279
+ ) -> tl.tuple:
280
+ states = [
281
+ tl.load(conv_state_ptr + i * stride_conv_state_tok, mask=mask, other=other)
282
+ for i in range(conv_width)
283
+ ]
284
+ return tl.tuple(states)
285
+
286
+
287
+ @triton.jit()
288
+ def _causal_conv1d_update_split_qkv_kernel_v2(
289
+ # Pointers to matrices
290
+ x_ptr, # (batch, dim, seqlen) where dim = 2*key_dim + value_dim
291
+ w_ptr, # (dim, width)
292
+ bias_ptr,
293
+ conv_state_ptr,
294
+ conv_state_indices_ptr,
295
+ q_ptr,
296
+ k_ptr,
297
+ v_ptr,
298
+ key_dim: tl.constexpr,
299
+ value_dim: tl.constexpr,
300
+ # Matrix dimensions
301
+ batch: int,
302
+ dim: tl.constexpr,
303
+ seqlen: tl.constexpr,
304
+ state_len: tl.constexpr,
305
+ num_cache_lines: tl.constexpr,
306
+ # Strides
307
+ stride_x_seq: tl.constexpr,
308
+ stride_x_dim: tl.constexpr,
309
+ stride_x_token: tl.constexpr,
310
+ stride_w_dim: tl.constexpr,
311
+ stride_w_width: tl.constexpr,
312
+ stride_conv_state_seq: tl.constexpr,
313
+ stride_conv_state_dim: tl.constexpr,
314
+ stride_conv_state_tok: tl.constexpr,
315
+ stride_state_indices: tl.constexpr,
316
+ stride_q_seq: tl.constexpr,
317
+ stride_q_dim: tl.constexpr,
318
+ stride_q_token: tl.constexpr,
319
+ stride_k_seq: tl.constexpr,
320
+ stride_k_dim: tl.constexpr,
321
+ stride_k_token: tl.constexpr,
322
+ stride_v_seq: tl.constexpr,
323
+ stride_v_dim: tl.constexpr,
324
+ stride_v_token: tl.constexpr,
325
+ # others
326
+ pad_slot_id: tl.constexpr,
327
+ # Meta-parameters
328
+ HAS_BIAS: tl.constexpr,
329
+ KERNEL_WIDTH: tl.constexpr,
330
+ SILU_ACTIVATION: tl.constexpr,
331
+ IS_CONTINUOUS_BATCHING: tl.constexpr,
332
+ NP2_STATELEN: tl.constexpr,
333
+ USE_PAD_SLOT: tl.constexpr,
334
+ BLOCK_N: tl.constexpr,
335
+ ):
336
+ cu_idx = tl.program_id(0)
337
+ dim_idx = cu_idx % (dim // BLOCK_N)
338
+ batch_idx = cu_idx // (dim // BLOCK_N)
339
+ num_cus = 80
340
+ batch_cus = num_cus // (dim // BLOCK_N)
341
+ per_cu_batchs = batch // batch_cus
342
+ cu_mores = batch % batch_cus
343
+
344
+ # ??:???????1,????-1;??????0 -> ??+1????1/0
345
+ # x = x - thresh
346
+ # t = (x >> (x.bit_length() - 1)) ^ 1
347
+ # r = val0 + (val1 - val0) * t
348
+ cu_tasks = (per_cu_batchs + 1) if batch_idx < cu_mores else per_cu_batchs
349
+ cu_offs = 0 if batch_idx < cu_mores else cu_mores
350
+
351
+ idx_feats = dim_idx * BLOCK_N + tl.arange(0, BLOCK_N)
352
+
353
+ w_base = w_ptr + (idx_feats * stride_w_dim)
354
+ mask_w = idx_feats < dim
355
+ if KERNEL_WIDTH >= 2:
356
+ w_ptrs = w_base + (0 * stride_w_width)
357
+ w_col0 = tl.load(w_ptrs, mask_w, other=0.0)
358
+ w_ptrs = w_base + (1 * stride_w_width)
359
+ w_col1 = tl.load(w_ptrs, mask_w, other=0.0)
360
+ if KERNEL_WIDTH >= 3:
361
+ w_ptrs = w_base + (2 * stride_w_width)
362
+ w_col2 = tl.load(w_ptrs, mask_w, other=0.0)
363
+ if KERNEL_WIDTH >= 4:
364
+ w_ptrs = w_base + (3 * stride_w_width)
365
+ w_col3 = tl.load(w_ptrs, mask_w, other=0.0)
366
+
367
+ for task_idx in range(cu_tasks):
368
+ idx_seq = batch_idx * cu_tasks + task_idx + cu_offs
369
+
370
+ if IS_CONTINUOUS_BATCHING:
371
+ conv_state_batch_coord = tl.load(
372
+ conv_state_indices_ptr + idx_seq * stride_state_indices
373
+ ).to(tl.int64)
374
+ else:
375
+ conv_state_batch_coord = idx_seq
376
+
377
+ if USE_PAD_SLOT:
378
+ if conv_state_batch_coord != pad_slot_id:
379
+
380
+ # STEP 1: READ init_state data
381
+ conv_states_base = (
382
+ conv_state_ptr
383
+ + (conv_state_batch_coord * stride_conv_state_seq)
384
+ + (idx_feats * stride_conv_state_dim)
385
+ )
386
+ mask_w = idx_feats < dim
387
+
388
+ prior_tokens = conv_states_base
389
+ if KERNEL_WIDTH >= 2:
390
+ conv_states_ptrs = prior_tokens
391
+ col0 = tl.load(conv_states_ptrs, mask_w, 0.0)
392
+ if KERNEL_WIDTH >= 3:
393
+ conv_states_ptrs = prior_tokens + 1 * stride_conv_state_tok
394
+ col1 = tl.load(conv_states_ptrs, mask_w, 0.0)
395
+ if KERNEL_WIDTH >= 4:
396
+ conv_states_ptrs = prior_tokens + 2 * stride_conv_state_tok
397
+ col2 = tl.load(conv_states_ptrs, mask_w, 0.0)
398
+
399
+ # STEP 2: Update conv state (same as original)
400
+ idx_tokens = tl.arange(0, NP2_STATELEN)
401
+
402
+ conv_state_ptrs_source = (
403
+ conv_state_ptr
404
+ + (conv_state_batch_coord * stride_conv_state_seq)
405
+ + (idx_feats * stride_conv_state_dim)[None, :]
406
+ + ((idx_tokens + seqlen) * stride_conv_state_tok)[:, None]
407
+ )
408
+
409
+ mask = (
410
+ (conv_state_batch_coord < num_cache_lines)
411
+ & ((idx_tokens + seqlen) < state_len)[:, None]
412
+ & (idx_feats < dim)[None, :]
413
+ )
414
+ conv_state = tl.load(conv_state_ptrs_source, mask, other=0.0)
415
+
416
+ VAL = state_len - seqlen
417
+ x_base = x_ptr + (idx_seq * stride_x_seq) + (idx_feats * stride_x_dim)
418
+ x_ptrs = (
419
+ x_base[None, :] + ((idx_tokens - VAL) * stride_x_token)[:, None]
420
+ )
421
+
422
+ mask_x = (
423
+ (idx_tokens - VAL >= 0)[:, None]
424
+ & (idx_tokens - VAL < seqlen)[:, None]
425
+ & (idx_feats < dim)[None, :]
426
+ )
427
+ loaded_x = tl.load(x_ptrs, mask_x, 0.0)
428
+ tl.debug_barrier()
429
+
430
+ new_conv_state = tl.where(mask, conv_state, loaded_x)
431
+
432
+ conv_state_base = (
433
+ conv_state_ptr
434
+ + (conv_state_batch_coord * stride_conv_state_seq)
435
+ + (idx_feats * stride_conv_state_dim)
436
+ )
437
+ conv_state_ptrs_target = (
438
+ conv_state_base + (idx_tokens * stride_conv_state_tok)[:, None]
439
+ )
440
+ mask_store = (idx_tokens < state_len)[:, None] & (idx_feats < dim)[
441
+ None, :
442
+ ]
443
+ tl.store(conv_state_ptrs_target, new_conv_state, mask_store)
444
+
445
+ # STEP 3: init accumulator
446
+ if HAS_BIAS:
447
+ bias = bias_ptr + idx_feats
448
+ mask_bias = idx_feats < dim
449
+ acc_preload = tl.load(bias, mask=mask_bias, other=0.0).to(
450
+ tl.float32
451
+ )
452
+ else:
453
+ acc_preload = tl.zeros((BLOCK_N,), dtype=tl.float32)
454
+
455
+ # STEP 4: PRE-LOAD WEIGHTS
456
+ x_base_1d = x_base
457
+ mask_x_1d = idx_feats < dim
458
+
459
+ # STEP 5: compute each token and write to split buffers
460
+ for idx_token in tl.static_range(seqlen):
461
+ acc = acc_preload
462
+
463
+ matrix_w = w_col0
464
+ matrix_x = col0
465
+ for j in tl.static_range(KERNEL_WIDTH):
466
+ if KERNEL_WIDTH == 2:
467
+ if j == 1:
468
+ matrix_w = w_col1
469
+ x_ptrs_1d = x_base_1d + idx_token * stride_x_token
470
+ matrix_x = tl.load(x_ptrs_1d, mask=mask_x_1d)
471
+ elif KERNEL_WIDTH == 3:
472
+ if j == 1:
473
+ matrix_w = w_col1
474
+ matrix_x = col1
475
+ elif j == 2:
476
+ matrix_w = w_col2
477
+ x_ptrs_1d = x_base_1d + idx_token * stride_x_token
478
+ matrix_x = tl.load(x_ptrs_1d, mask=mask_x_1d)
479
+ elif KERNEL_WIDTH == 4:
480
+ if j == 1:
481
+ matrix_w = w_col1
482
+ matrix_x = col1
483
+ elif j == 2:
484
+ matrix_w = w_col2
485
+ matrix_x = col2
486
+ elif j == 3:
487
+ matrix_w = w_col3
488
+ x_ptrs_1d = x_base_1d + idx_token * stride_x_token
489
+ matrix_x = tl.load(x_ptrs_1d, mask=mask_x_1d)
490
+
491
+ acc += matrix_x * matrix_w
492
+
493
+ # Update sliding window
494
+ if KERNEL_WIDTH == 2:
495
+ col0 = matrix_x
496
+ elif KERNEL_WIDTH == 3:
497
+ col0 = col1
498
+ col1 = matrix_x
499
+ elif KERNEL_WIDTH == 4:
500
+ col0 = col1
501
+ col1 = col2
502
+ col2 = matrix_x
503
+
504
+ # Apply activation
505
+ if SILU_ACTIVATION:
506
+ acc = acc / (1 + tl.exp(-acc))
507
+
508
+ mask_feat = (idx_token < seqlen) & (idx_feats < dim)
509
+
510
+ # Query: idx_feats in [0, key_dim)
511
+ is_query = idx_feats < key_dim
512
+ q_feat_idx = idx_feats # 0-based index within query
513
+ q_ptrs = (
514
+ q_ptr
515
+ + idx_seq * stride_q_seq
516
+ + idx_token * stride_q_token
517
+ + q_feat_idx * stride_q_dim
518
+ )
519
+ tl.store(q_ptrs, acc, mask=mask_feat & is_query)
520
+
521
+ # Key: idx_feats in [key_dim, 2*key_dim)
522
+ is_key = (idx_feats >= key_dim) & (idx_feats < 2 * key_dim)
523
+ k_feat_idx = idx_feats - key_dim
524
+ k_ptrs = (
525
+ k_ptr
526
+ + idx_seq * stride_k_seq
527
+ + idx_token * stride_k_token
528
+ + k_feat_idx * stride_k_dim
529
+ )
530
+ tl.store(k_ptrs, acc, mask=mask_feat & is_key)
531
+
532
+ # Value: idx_feats in [2*key_dim, 2*key_dim+value_dim)
533
+ is_value = (idx_feats >= 2 * key_dim) & (
534
+ idx_feats < 2 * key_dim + value_dim
535
+ )
536
+ v_feat_idx = idx_feats - 2 * key_dim
537
+ v_ptrs = (
538
+ v_ptr
539
+ + idx_seq * stride_v_seq
540
+ + idx_token * stride_v_token
541
+ + v_feat_idx * stride_v_dim
542
+ )
543
+ tl.store(v_ptrs, acc, mask=mask_feat & is_value)
544
+
545
+
546
+ @tl.core.builtin
547
+ def tuple_combine(a: gl.tuple, b: gl.tensor, _semantic=None) -> gl.tuple:
548
+ return tl.tuple([*a.values, b])
549
+
550
+
551
+ @gluon.jit()
552
+ def gluon_causal_conv1d_update_split_qkv_kernel(
553
+ # Pointers to matrices
554
+ x_ptr, # (batch, dim, seqlen) where dim = 2*key_dim + value_dim
555
+ w_ptr, # (dim, width)
556
+ bias_ptr,
557
+ conv_state_ptr,
558
+ conv_state_indices_ptr,
559
+ q_ptr,
560
+ k_ptr,
561
+ v_ptr,
562
+ key_dim: gl.constexpr,
563
+ value_dim: gl.constexpr,
564
+ # Matrix dimensions
565
+ batch: int,
566
+ dim: gl.constexpr,
567
+ seqlen: gl.constexpr,
568
+ state_len: gl.constexpr,
569
+ num_cache_lines: gl.constexpr,
570
+ # Strides
571
+ stride_x_seq: gl.constexpr,
572
+ stride_x_dim: gl.constexpr,
573
+ stride_x_token: gl.constexpr,
574
+ stride_w_dim: gl.constexpr,
575
+ stride_w_width: gl.constexpr,
576
+ stride_conv_state_seq: gl.constexpr,
577
+ stride_conv_state_dim: gl.constexpr,
578
+ stride_conv_state_tok: gl.constexpr,
579
+ stride_state_indices: gl.constexpr,
580
+ stride_q_seq: gl.constexpr,
581
+ stride_q_dim: gl.constexpr,
582
+ stride_q_token: gl.constexpr,
583
+ stride_k_seq: gl.constexpr,
584
+ stride_k_dim: gl.constexpr,
585
+ stride_k_token: gl.constexpr,
586
+ stride_v_seq: gl.constexpr,
587
+ stride_v_dim: gl.constexpr,
588
+ stride_v_token: gl.constexpr,
589
+ # others
590
+ pad_slot_id: gl.constexpr,
591
+ # Meta-parameters
592
+ HAS_BIAS: gl.constexpr,
593
+ KERNEL_WIDTH: gl.constexpr,
594
+ SILU_ACTIVATION: gl.constexpr,
595
+ IS_CONTINUOUS_BATCHING: gl.constexpr,
596
+ NP2_STATELEN: gl.constexpr,
597
+ USE_PAD_SLOT: gl.constexpr,
598
+ BLOCK_N: gl.constexpr,
599
+ ):
600
+ """Gluon version of causal_conv1d_update_split_qkv kernel (original)."""
601
+
602
+ blocked: gl.constexpr = gl.BlockedLayout(
603
+ size_per_thread=[2],
604
+ threads_per_warp=[64],
605
+ warps_per_cta=[2],
606
+ order=[0],
607
+ )
608
+
609
+ idx_seq = gl.program_id(0)
610
+ if idx_seq >= batch:
611
+ return
612
+
613
+ # [BLOCK_N,] elements along the feature-dimension (channel)
614
+ idx_feats = gl.program_id(1) * BLOCK_N + gl.arange(0, BLOCK_N, layout=blocked)
615
+
616
+ if IS_CONTINUOUS_BATCHING:
617
+ conv_state_batch_coord = gl.load(
618
+ conv_state_indices_ptr + idx_seq * stride_state_indices
619
+ ).to(gl.int64)
620
+ else:
621
+ conv_state_batch_coord = idx_seq
622
+
623
+ if USE_PAD_SLOT:
624
+ if conv_state_batch_coord == pad_slot_id:
625
+ return
626
+
627
+ # STEP 1: READ initial conv_state data
628
+ conv_states_base = (
629
+ conv_state_ptr
630
+ + (conv_state_batch_coord * stride_conv_state_seq)
631
+ + (idx_feats * stride_conv_state_dim)
632
+ )
633
+ mask_w = idx_feats < dim
634
+
635
+ prior_tokens = conv_states_base
636
+ conv_state_vecs = ()
637
+ for j in gl.static_range(KERNEL_WIDTH - 1):
638
+ conv_states_ptrs = prior_tokens + j * stride_conv_state_tok
639
+ col = gl.load(conv_states_ptrs, mask_w, 0.0)
640
+ conv_state_vecs = tuple_combine(conv_state_vecs, col)
641
+
642
+ conv_state_base = (
643
+ conv_state_ptr
644
+ + (conv_state_batch_coord * stride_conv_state_seq)
645
+ + (idx_feats * stride_conv_state_dim)
646
+ )
647
+
648
+ # STEP 3: init accumulator
649
+ if HAS_BIAS:
650
+ bias = bias_ptr + idx_feats
651
+ mask_bias = idx_feats < dim
652
+ acc_preload = gl.load(bias, mask=mask_bias, other=0.0).to(gl.float32)
653
+ else:
654
+ acc_preload = gl.zeros((BLOCK_N,), dtype=gl.float32, layout=blocked)
655
+
656
+ # STEP 4: PRE-LOAD WEIGHTS
657
+ w_base = w_ptr + (idx_feats * stride_w_dim)
658
+ mask_w = idx_feats < dim
659
+ w_vecs = ()
660
+ for j in gl.static_range(KERNEL_WIDTH):
661
+ w_ptrs = w_base + (j * stride_w_width)
662
+ w_col = gl.load(w_ptrs, mask_w, other=0.0)
663
+ w_vecs = tuple_combine(w_vecs, w_col)
664
+
665
+ x_base_1d = x_ptr + (idx_seq * stride_x_seq) + (idx_feats * stride_x_dim)
666
+ mask_x_1d = idx_feats < dim
667
+
668
+ # STEP 5: compute each token and split to q/k/v
669
+ for idx_token in gl.static_range(seqlen):
670
+ acc = acc_preload
671
+
672
+ x_ptrs_1d = x_base_1d + idx_token * stride_x_token
673
+ x_vec = gl.load(x_ptrs_1d, mask=mask_x_1d)
674
+ conv_state_vecs = tuple_combine(conv_state_vecs, x_vec)
675
+
676
+ for j in gl.static_range(KERNEL_WIDTH):
677
+ matrix_w = w_vecs[j]
678
+ matrix_x = conv_state_vecs[j]
679
+ acc += matrix_x * matrix_w
680
+
681
+ conv_state_vecs = conv_state_vecs[1:]
682
+
683
+ # Apply activation
684
+ if SILU_ACTIVATION:
685
+ acc = acc / (1 + gl.exp(-acc))
686
+
687
+ mask_feat = (idx_token < seqlen) & (idx_feats < dim)
688
+
689
+ # Split and store to q, k, v
690
+ # Query: idx_feats in [0, key_dim)
691
+ is_query = idx_feats < key_dim
692
+ q_feat_idx = idx_feats
693
+ q_ptrs = (
694
+ q_ptr
695
+ + idx_seq * stride_q_seq
696
+ + idx_token * stride_q_token
697
+ + q_feat_idx * stride_q_dim
698
+ )
699
+ gl.store(q_ptrs, acc, mask=mask_feat & is_query)
700
+
701
+ # Key: idx_feats in [key_dim, 2*key_dim)
702
+ is_key = (idx_feats >= key_dim) & (idx_feats < 2 * key_dim)
703
+ k_feat_idx = idx_feats - key_dim
704
+ k_ptrs = (
705
+ k_ptr
706
+ + idx_seq * stride_k_seq
707
+ + idx_token * stride_k_token
708
+ + k_feat_idx * stride_k_dim
709
+ )
710
+ gl.store(k_ptrs, acc, mask=mask_feat & is_key)
711
+
712
+ # Value: idx_feats in [2*key_dim, 2*key_dim+value_dim)
713
+ is_value = (idx_feats >= 2 * key_dim) & (idx_feats < 2 * key_dim + value_dim)
714
+ v_feat_idx = idx_feats - 2 * key_dim
715
+ v_ptrs = (
716
+ v_ptr
717
+ + idx_seq * stride_v_seq
718
+ + idx_token * stride_v_token
719
+ + v_feat_idx * stride_v_dim
720
+ )
721
+ gl.store(v_ptrs, acc, mask=mask_feat & is_value)
722
+
723
+ # Store final conv_state
724
+ for idx in gl.static_range(state_len):
725
+ gl.store(
726
+ conv_state_base + idx * stride_conv_state_tok,
727
+ conv_state_vecs[idx],
728
+ idx_feats < dim,
729
+ )
730
+
731
+
732
+ @gluon.jit()
733
+ def gluon_causal_conv1d_update_split_qkv_kernel_v2(
734
+ # Pointers to matrices
735
+ x_ptr, # (batch, dim, seqlen) where dim = 2*key_dim + value_dim
736
+ w_ptr, # (dim, width)
737
+ bias_ptr,
738
+ conv_state_ptr,
739
+ conv_state_indices_ptr,
740
+ q_ptr,
741
+ k_ptr,
742
+ v_ptr,
743
+ key_dim: gl.constexpr,
744
+ value_dim: gl.constexpr,
745
+ # Matrix dimensions
746
+ batch: int,
747
+ dim: gl.constexpr,
748
+ seqlen: gl.constexpr,
749
+ state_len: gl.constexpr,
750
+ num_cache_lines: gl.constexpr,
751
+ # Strides
752
+ stride_x_seq: gl.constexpr,
753
+ stride_x_dim: gl.constexpr,
754
+ stride_x_token: gl.constexpr,
755
+ stride_w_dim: gl.constexpr,
756
+ stride_w_width: gl.constexpr,
757
+ stride_conv_state_seq: gl.constexpr,
758
+ stride_conv_state_dim: gl.constexpr,
759
+ stride_conv_state_tok: gl.constexpr,
760
+ stride_state_indices: gl.constexpr,
761
+ stride_q_seq: gl.constexpr,
762
+ stride_q_dim: gl.constexpr,
763
+ stride_q_token: gl.constexpr,
764
+ stride_k_seq: gl.constexpr,
765
+ stride_k_dim: gl.constexpr,
766
+ stride_k_token: gl.constexpr,
767
+ stride_v_seq: gl.constexpr,
768
+ stride_v_dim: gl.constexpr,
769
+ stride_v_token: gl.constexpr,
770
+ # others
771
+ pad_slot_id: gl.constexpr,
772
+ # Meta-parameters
773
+ HAS_BIAS: gl.constexpr,
774
+ KERNEL_WIDTH: gl.constexpr,
775
+ SILU_ACTIVATION: gl.constexpr,
776
+ IS_CONTINUOUS_BATCHING: gl.constexpr,
777
+ NP2_STATELEN: gl.constexpr,
778
+ USE_PAD_SLOT: gl.constexpr,
779
+ BLOCK_N: gl.constexpr,
780
+ ):
781
+ """Gluon version of causal_conv1d_update_split_qkv kernel (optimized v2).
782
+
783
+ Key optimizations:
784
+ - Eliminates tuple operations in hot loop (tuple_combine, tuple slicing)
785
+ - Uses explicit variables like Triton version for better register allocation
786
+ - Reduces memory allocation overhead in inner loop
787
+ """
788
+
789
+ blocked: gl.constexpr = gl.BlockedLayout(
790
+ size_per_thread=[2],
791
+ threads_per_warp=[64],
792
+ warps_per_cta=[2],
793
+ order=[0],
794
+ )
795
+
796
+ idx_seq = gl.program_id(0)
797
+ if idx_seq >= batch:
798
+ return
799
+
800
+ # [BLOCK_N,] elements along the feature-dimension (channel)
801
+ idx_feats = gl.program_id(1) * BLOCK_N + gl.arange(0, BLOCK_N, layout=blocked)
802
+
803
+ if IS_CONTINUOUS_BATCHING:
804
+ conv_state_batch_coord = gl.load(
805
+ conv_state_indices_ptr + idx_seq * stride_state_indices
806
+ ).to(gl.int64)
807
+ else:
808
+ conv_state_batch_coord = idx_seq
809
+
810
+ if USE_PAD_SLOT:
811
+ if conv_state_batch_coord == pad_slot_id:
812
+ return
813
+
814
+ # STEP 1: READ initial conv_state data (use explicit variables instead of tuple)
815
+ conv_states_base = (
816
+ conv_state_ptr
817
+ + (conv_state_batch_coord * stride_conv_state_seq)
818
+ + (idx_feats * stride_conv_state_dim)
819
+ )
820
+ mask_w = idx_feats < dim
821
+
822
+ prior_tokens = conv_states_base
823
+ # Use explicit variables like Triton version - avoid tuple operations
824
+ if KERNEL_WIDTH >= 2:
825
+ conv_states_ptrs = prior_tokens
826
+ col0 = gl.load(conv_states_ptrs, mask_w, 0.0)
827
+ if KERNEL_WIDTH >= 3:
828
+ conv_states_ptrs = prior_tokens + 1 * stride_conv_state_tok
829
+ col1 = gl.load(conv_states_ptrs, mask_w, 0.0)
830
+ if KERNEL_WIDTH >= 4:
831
+ conv_states_ptrs = prior_tokens + 2 * stride_conv_state_tok
832
+ col2 = gl.load(conv_states_ptrs, mask_w, 0.0)
833
+
834
+ conv_state_base = (
835
+ conv_state_ptr
836
+ + (conv_state_batch_coord * stride_conv_state_seq)
837
+ + (idx_feats * stride_conv_state_dim)
838
+ )
839
+
840
+ # STEP 3: init accumulator
841
+ if HAS_BIAS:
842
+ bias = bias_ptr + idx_feats
843
+ mask_bias = idx_feats < dim
844
+ acc_preload = gl.load(bias, mask=mask_bias, other=0.0).to(gl.float32)
845
+ else:
846
+ acc_preload = gl.zeros((BLOCK_N,), dtype=gl.float32, layout=blocked)
847
+
848
+ # STEP 4: PRE-LOAD WEIGHTS (use explicit variables)
849
+ w_base = w_ptr + (idx_feats * stride_w_dim)
850
+ mask_w = idx_feats < dim
851
+ if KERNEL_WIDTH >= 2:
852
+ w_ptrs = w_base + (0 * stride_w_width)
853
+ w_col0 = gl.load(w_ptrs, mask_w, other=0.0)
854
+ w_ptrs = w_base + (1 * stride_w_width)
855
+ w_col1 = gl.load(w_ptrs, mask_w, other=0.0)
856
+ if KERNEL_WIDTH >= 3:
857
+ w_ptrs = w_base + (2 * stride_w_width)
858
+ w_col2 = gl.load(w_ptrs, mask_w, other=0.0)
859
+ if KERNEL_WIDTH >= 4:
860
+ w_ptrs = w_base + (3 * stride_w_width)
861
+ w_col3 = gl.load(w_ptrs, mask_w, other=0.0)
862
+
863
+ x_base_1d = x_ptr + (idx_seq * stride_x_seq) + (idx_feats * stride_x_dim)
864
+ mask_x_1d = idx_feats < dim
865
+
866
+ # STEP 5: compute each token and split to q/k/v
867
+ # Use explicit variable updates like Triton version - NO tuple operations in loop!
868
+ for idx_token in gl.static_range(seqlen):
869
+ acc = acc_preload
870
+
871
+ # Initialize matrix_w and matrix_x for first iteration
872
+ matrix_w = w_col0
873
+ matrix_x = col0
874
+
875
+ # Compute convolution using explicit conditionals (like Triton)
876
+ for j in gl.static_range(KERNEL_WIDTH):
877
+ if KERNEL_WIDTH == 2:
878
+ if j == 1:
879
+ matrix_w = w_col1
880
+ x_ptrs_1d = x_base_1d + idx_token * stride_x_token
881
+ matrix_x = gl.load(x_ptrs_1d, mask=mask_x_1d)
882
+ elif KERNEL_WIDTH == 3:
883
+ if j == 1:
884
+ matrix_w = w_col1
885
+ matrix_x = col1
886
+ elif j == 2:
887
+ matrix_w = w_col2
888
+ x_ptrs_1d = x_base_1d + idx_token * stride_x_token
889
+ matrix_x = gl.load(x_ptrs_1d, mask=mask_x_1d)
890
+ elif KERNEL_WIDTH == 4:
891
+ if j == 1:
892
+ matrix_w = w_col1
893
+ matrix_x = col1
894
+ elif j == 2:
895
+ matrix_w = w_col2
896
+ matrix_x = col2
897
+ elif j == 3:
898
+ matrix_w = w_col3
899
+ x_ptrs_1d = x_base_1d + idx_token * stride_x_token
900
+ matrix_x = gl.load(x_ptrs_1d, mask=mask_x_1d)
901
+
902
+ acc += matrix_x * matrix_w
903
+
904
+ # Update sliding window with simple variable assignments (like Triton)
905
+ if KERNEL_WIDTH == 2:
906
+ col0 = matrix_x
907
+ elif KERNEL_WIDTH == 3:
908
+ col0 = col1
909
+ col1 = matrix_x
910
+ elif KERNEL_WIDTH == 4:
911
+ col0 = col1
912
+ col1 = col2
913
+ col2 = matrix_x
914
+
915
+ # Apply activation
916
+ if SILU_ACTIVATION:
917
+ acc = acc / (1 + gl.exp(-acc))
918
+
919
+ mask_feat = (idx_token < seqlen) & (idx_feats < dim)
920
+
921
+ # Split and store to q, k, v
922
+ # Query: idx_feats in [0, key_dim)
923
+ is_query = idx_feats < key_dim
924
+ q_feat_idx = idx_feats
925
+ q_ptrs = (
926
+ q_ptr
927
+ + idx_seq * stride_q_seq
928
+ + idx_token * stride_q_token
929
+ + q_feat_idx * stride_q_dim
930
+ )
931
+ gl.store(q_ptrs, acc, mask=mask_feat & is_query)
932
+
933
+ # Key: idx_feats in [key_dim, 2*key_dim)
934
+ is_key = (idx_feats >= key_dim) & (idx_feats < 2 * key_dim)
935
+ k_feat_idx = idx_feats - key_dim
936
+ k_ptrs = (
937
+ k_ptr
938
+ + idx_seq * stride_k_seq
939
+ + idx_token * stride_k_token
940
+ + k_feat_idx * stride_k_dim
941
+ )
942
+ gl.store(k_ptrs, acc, mask=mask_feat & is_key)
943
+
944
+ # Value: idx_feats in [2*key_dim, 2*key_dim+value_dim)
945
+ is_value = (idx_feats >= 2 * key_dim) & (idx_feats < 2 * key_dim + value_dim)
946
+ v_feat_idx = idx_feats - 2 * key_dim
947
+ v_ptrs = (
948
+ v_ptr
949
+ + idx_seq * stride_v_seq
950
+ + idx_token * stride_v_token
951
+ + v_feat_idx * stride_v_dim
952
+ )
953
+ gl.store(v_ptrs, acc, mask=mask_feat & is_value)
954
+
955
+ # Store final conv_state using explicit variables
956
+ if KERNEL_WIDTH >= 2:
957
+ gl.store(conv_state_base + 0 * stride_conv_state_tok, col0, idx_feats < dim)
958
+ if KERNEL_WIDTH >= 3:
959
+ gl.store(conv_state_base + 1 * stride_conv_state_tok, col1, idx_feats < dim)
960
+ if KERNEL_WIDTH >= 4:
961
+ gl.store(conv_state_base + 2 * stride_conv_state_tok, col2, idx_feats < dim)
962
+
963
+
964
+ def causal_conv1d_update_split_qkv(
965
+ x: torch.Tensor,
966
+ conv_state: torch.Tensor,
967
+ weight: torch.Tensor,
968
+ key_dim: int,
969
+ value_dim: int,
970
+ bias: torch.Tensor | None = None,
971
+ activation: bool | str | None = "silu",
972
+ conv_state_indices: torch.Tensor | None = None,
973
+ pad_slot_id: int = PAD_SLOT_ID,
974
+ use_gluon: bool = True,
975
+ use_gluon_v2: bool = False,
976
+ ) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
977
+ """Optimized causal_conv1d_update that directly outputs split q, k, v.
978
+
979
+ Args:
980
+ x: Input tensor (batch, dim, seqlen) where dim = 2*key_dim + value_dim
981
+ conv_state: Convolution state (num_cache_lines, dim, state_len)
982
+ weight: Convolution weights (dim, width)
983
+ key_dim: Dimension of query and key
984
+ value_dim: Dimension of value
985
+ bias: Optional bias (dim,)
986
+ activation: Activation function ("silu", "swish", or None)
987
+ conv_state_indices: Optional batch indices for continuous batching
988
+ pad_slot_id: ID for padded slots
989
+ use_gluon: Whether to use Gluon kernel (default: True)
990
+ use_gluon_v2: Whether to use optimized Gluon v2 kernel (default: False)
991
+ This version eliminates tuple operations for better performance
992
+
993
+ Returns:
994
+ Tuple of (query, key, value) tensors
995
+ """
996
+ # Validate and prepare
997
+ if isinstance(activation, bool):
998
+ activation = "silu" if activation is True else None
999
+ elif activation is not None:
1000
+ assert activation in ["silu", "swish"]
1001
+
1002
+ unsqueeze = x.dim() == 2
1003
+ if unsqueeze:
1004
+ x = x.unsqueeze(-1)
1005
+
1006
+ batch, dim, seqlen = x.shape
1007
+ assert dim == 2 * key_dim + value_dim, f"dim {dim} != 2*{key_dim} + {value_dim}"
1008
+
1009
+ _, width = weight.shape
1010
+ num_cache_lines, _, state_len = conv_state.size()
1011
+
1012
+ query = torch.empty(
1013
+ (batch, key_dim, seqlen),
1014
+ dtype=x.dtype,
1015
+ device=x.device,
1016
+ )
1017
+ key = torch.empty(
1018
+ (batch, key_dim, seqlen),
1019
+ dtype=x.dtype,
1020
+ device=x.device,
1021
+ )
1022
+ value = torch.empty(
1023
+ (batch, value_dim, seqlen),
1024
+ dtype=x.dtype,
1025
+ device=x.device,
1026
+ )
1027
+
1028
+ # Kernel launch
1029
+ stride_state_indices = (
1030
+ conv_state_indices.stride(0) if conv_state_indices is not None else 0
1031
+ )
1032
+ state_len = width - 1
1033
+ np2_statelen = triton.next_power_of_2(state_len)
1034
+
1035
+ BLOCK_N = 256
1036
+ grid = (batch, triton.cdiv(dim, BLOCK_N))
1037
+
1038
+ # Select kernel based on flags
1039
+ if use_gluon:
1040
+ kernel_fn = (
1041
+ gluon_causal_conv1d_update_split_qkv_kernel_v2
1042
+ if use_gluon_v2
1043
+ else gluon_causal_conv1d_update_split_qkv_kernel
1044
+ )
1045
+ else:
1046
+ kernel_fn = _causal_conv1d_update_split_qkv_kernel
1047
+
1048
+ kernel_fn[grid](
1049
+ x_ptr=x,
1050
+ w_ptr=weight,
1051
+ bias_ptr=bias,
1052
+ conv_state_ptr=conv_state,
1053
+ conv_state_indices_ptr=conv_state_indices,
1054
+ q_ptr=query,
1055
+ k_ptr=key,
1056
+ v_ptr=value,
1057
+ key_dim=key_dim,
1058
+ value_dim=value_dim,
1059
+ batch=batch,
1060
+ dim=dim,
1061
+ seqlen=seqlen,
1062
+ state_len=state_len,
1063
+ num_cache_lines=num_cache_lines,
1064
+ stride_x_seq=x.stride(0),
1065
+ stride_x_dim=x.stride(1),
1066
+ stride_x_token=x.stride(2),
1067
+ stride_w_dim=weight.stride(0),
1068
+ stride_w_width=weight.stride(1),
1069
+ stride_conv_state_seq=conv_state.stride(0),
1070
+ stride_conv_state_dim=conv_state.stride(1),
1071
+ stride_conv_state_tok=conv_state.stride(2),
1072
+ stride_state_indices=stride_state_indices,
1073
+ stride_q_seq=query.stride(0),
1074
+ stride_q_dim=query.stride(1),
1075
+ stride_q_token=query.stride(2),
1076
+ stride_k_seq=key.stride(0),
1077
+ stride_k_dim=key.stride(1),
1078
+ stride_k_token=key.stride(2),
1079
+ stride_v_seq=value.stride(0),
1080
+ stride_v_dim=value.stride(1),
1081
+ stride_v_token=value.stride(2),
1082
+ pad_slot_id=pad_slot_id,
1083
+ HAS_BIAS=bias is not None,
1084
+ KERNEL_WIDTH=width,
1085
+ SILU_ACTIVATION=activation in ["silu", "swish"],
1086
+ IS_CONTINUOUS_BATCHING=conv_state_indices is not None,
1087
+ NP2_STATELEN=np2_statelen,
1088
+ USE_PAD_SLOT=pad_slot_id is not None,
1089
+ BLOCK_N=BLOCK_N,
1090
+ num_warps=2 if use_gluon else 4,
1091
+ )
1092
+
1093
+ if unsqueeze:
1094
+ query = query.squeeze(-1)
1095
+ key = key.squeeze(-1)
1096
+ value = value.squeeze(-1)
1097
+
1098
+ return query, key, value
build/torch-rocm/_triton_kernels/gated_delta_rule/decode/fused_rearrange_sigmoid_gdr.py ADDED
@@ -0,0 +1,165 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
+ # SPDX-FileCopyrightText: Songlin Yang, Yu Zhang
4
+ # Copyright (C) 2024-2026, Advanced Micro Devices, Inc. All rights reserved.
5
+ #
6
+ # This file contains code copied from the flash-linear-attention project.
7
+ # The original source code was licensed under the MIT license and included
8
+ # the following copyright notice:
9
+ # Copyright (c) 2023-2025, Songlin Yang, Yu Zhang
10
+
11
+ import triton
12
+ import triton.language as tl
13
+
14
+
15
+ @triton.heuristics(
16
+ {
17
+ "USE_INITIAL_STATE": lambda args: args["h0"] is not None,
18
+ "IS_VARLEN": lambda args: args["cu_seqlens"] is not None,
19
+ "IS_CONTINUOUS_BATCHING": lambda args: args["ssm_state_indices"] is not None,
20
+ "IS_SPEC_DECODING": lambda args: args["num_accepted_tokens"] is not None,
21
+ }
22
+ )
23
+ @triton.jit(do_not_specialize=["N", "T"])
24
+ def fused_rearrange_sigmoid_gated_delta_rule_update_kernel(
25
+ A_log,
26
+ a,
27
+ b,
28
+ dt_bias,
29
+ beta,
30
+ threshold,
31
+ qkv,
32
+ o,
33
+ h0,
34
+ ht,
35
+ cu_seqlens,
36
+ ssm_state_indices,
37
+ num_accepted_tokens,
38
+ scale,
39
+ N: tl.int64, # num of sequences
40
+ T: tl.int64, # num of tokens
41
+ B: tl.constexpr,
42
+ H: tl.constexpr,
43
+ HV: tl.constexpr,
44
+ K: tl.constexpr,
45
+ V: tl.constexpr,
46
+ BK: tl.constexpr,
47
+ BV: tl.constexpr,
48
+ stride_qkv_l: tl.constexpr,
49
+ stride_qkv_hd: tl.constexpr,
50
+ stride_init_state_token: tl.constexpr,
51
+ stride_final_state_token: tl.constexpr,
52
+ stride_indices_seq: tl.constexpr,
53
+ stride_indices_tok: tl.constexpr,
54
+ USE_INITIAL_STATE: tl.constexpr, # whether to use initial state
55
+ INPLACE_FINAL_STATE: tl.constexpr, # whether to store final state inplace
56
+ USE_QK_L2NORM_IN_KERNEL: tl.constexpr,
57
+ IS_VARLEN: tl.constexpr,
58
+ IS_CONTINUOUS_BATCHING: tl.constexpr,
59
+ IS_SPEC_DECODING: tl.constexpr,
60
+ IS_KDA: tl.constexpr,
61
+ ):
62
+ i_k, i_v, i_nh = tl.program_id(0), tl.program_id(1), tl.program_id(2)
63
+ i_n, i_hv = i_nh // HV, i_nh % HV
64
+ i_h = i_hv // (HV // H)
65
+ if IS_VARLEN:
66
+ bos, eos = (
67
+ tl.load(cu_seqlens + i_n).to(tl.int64),
68
+ tl.load(cu_seqlens + i_n + 1).to(tl.int64),
69
+ )
70
+ all = T
71
+ T = eos - bos
72
+ else:
73
+ bos, eos = i_n * T, i_n * T + T
74
+ all = B * T
75
+
76
+ if T == 0:
77
+ return
78
+
79
+ o_k = i_k * BK + tl.arange(0, BK)
80
+ o_v = i_v * BV + tl.arange(0, BV)
81
+
82
+ p_q = qkv + bos * stride_qkv_l + ((i_h * K) + o_k) * stride_qkv_hd
83
+ p_k = qkv + bos * stride_qkv_l + (H * K + (i_h * K) + o_k) * stride_qkv_hd
84
+ p_v = qkv + bos * stride_qkv_l + (2 * H * K + (i_hv * V) + o_v) * stride_qkv_hd
85
+
86
+ p_A_log = A_log + i_hv
87
+ if not IS_KDA:
88
+ p_a = a + bos * HV + i_hv
89
+ p_dt_bias = dt_bias + i_hv
90
+ else:
91
+ p_a = a + (bos * HV + i_hv) * K + o_k
92
+ p_dt_bias = dt_bias + i_hv * K + o_k
93
+
94
+ p_b = b + bos * HV + i_hv
95
+ p_o = o + ((i_k * all + bos) * HV + i_hv) * V + o_v
96
+
97
+ mask_k = o_k < K
98
+ mask_v = o_v < V
99
+ mask_h = mask_v[:, None] & mask_k[None, :]
100
+
101
+ b_h = tl.zeros([BV, BK], dtype=tl.float32)
102
+ if USE_INITIAL_STATE:
103
+ if IS_CONTINUOUS_BATCHING:
104
+ if IS_SPEC_DECODING:
105
+ i_t = tl.load(num_accepted_tokens + i_n).to(tl.int64) - 1
106
+ else:
107
+ i_t = 0
108
+ state_idx = tl.load(
109
+ ssm_state_indices + i_n * stride_indices_seq + i_t * stride_indices_tok
110
+ ).to(tl.int64)
111
+ if state_idx < 0:
112
+ return
113
+ p_h0 = h0 + state_idx * stride_init_state_token
114
+ else:
115
+ p_h0 = h0 + bos * HV * V * K
116
+ p_h0 = p_h0 + i_hv * V * K + o_v[:, None] * K + o_k[None, :]
117
+ b_h += tl.load(p_h0, mask=mask_h, other=0).to(tl.float32)
118
+
119
+ for i_t in range(0, T):
120
+ b_q = tl.load(p_q, mask=mask_k, other=0).to(tl.float32)
121
+ b_k = tl.load(p_k, mask=mask_k, other=0).to(tl.float32)
122
+ b_v = tl.load(p_v, mask=mask_v, other=0).to(tl.float32)
123
+ b_b = tl.load(p_b).to(tl.float32)
124
+
125
+ x = tl.load(p_a).to(tl.float32) + tl.load(p_dt_bias).to(tl.float32)
126
+ softplus_x = tl.where(
127
+ beta * x <= threshold, (1 / beta) * tl.log(1 + tl.exp(beta * x)), x
128
+ )
129
+ b_g = -tl.exp(tl.load(p_A_log).to(tl.float32)) * softplus_x
130
+
131
+ b_beta = tl.sigmoid(b_b.to(tl.float32))
132
+
133
+ if USE_QK_L2NORM_IN_KERNEL:
134
+ b_q = b_q * tl.rsqrt(tl.sum(b_q * b_q) + 1e-6)
135
+ b_k = b_k * tl.rsqrt(tl.sum(b_k * b_k) + 1e-6)
136
+ b_q = b_q * scale
137
+ if not IS_KDA:
138
+ b_h *= tl.exp(b_g)
139
+ else:
140
+ b_h *= tl.exp(b_g[None, :])
141
+ b_v -= tl.sum(b_h * b_k[None, :], 1)
142
+ b_v *= b_beta
143
+ b_h += b_v[:, None] * b_k[None, :]
144
+ b_o = tl.sum(b_h * b_q[None, :], 1)
145
+ tl.store(p_o, b_o.to(p_o.dtype.element_ty), mask=mask_v)
146
+
147
+ if INPLACE_FINAL_STATE:
148
+ final_state_idx = tl.load(
149
+ ssm_state_indices + i_n * stride_indices_seq + i_t * stride_indices_tok
150
+ ).to(tl.int64)
151
+ if final_state_idx >= 0:
152
+ p_ht = ht + final_state_idx * stride_final_state_token
153
+ p_ht = p_ht + i_hv * V * K + o_v[:, None] * K + o_k[None, :]
154
+ tl.store(p_ht, b_h.to(p_ht.dtype.element_ty), mask=mask_h)
155
+ else:
156
+ p_ht = ht + (bos + i_t) * stride_final_state_token
157
+ p_ht = p_ht + i_hv * V * K + o_v[:, None] * K + o_k[None, :]
158
+ tl.store(p_ht, b_h.to(p_ht.dtype.element_ty), mask=mask_h)
159
+
160
+ p_q += stride_qkv_l
161
+ p_k += stride_qkv_l
162
+ p_v += stride_qkv_l
163
+ p_o += HV * V
164
+ p_b += HV
165
+ p_a += HV
build/torch-rocm/_triton_kernels/gated_delta_rule/decode/fused_recurrent.py ADDED
@@ -0,0 +1,191 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (C) 2024-2026, Advanced Micro Devices, Inc. All rights reserved.
3
+ # Adapted from flash-linear-attention: Copyright (c) 2023-2025, Songlin Yang, Yu Zhang
4
+
5
+ """
6
+ Fused recurrent gated delta rule forward kernel (Forward only).
7
+
8
+ This module provides an optimized fused recurrent implementation of the gated delta rule.
9
+ Note: Only forward pass is implemented. Backward pass is not supported in aiter.
10
+ """
11
+
12
+ import triton
13
+ import triton.language as tl
14
+ from ....utils._triton.kernel_repr import make_kernel_repr
15
+
16
+ _fused_recurrent_gated_delta_rule_fwd_kernel_repr = make_kernel_repr(
17
+ "_fused_recurrent_gated_delta_rule_fwd_kernel",
18
+ [
19
+ "BK",
20
+ "BV",
21
+ "USE_G",
22
+ "USE_GK",
23
+ "USE_GV",
24
+ "USE_QK_L2NORM_IN_KERNEL",
25
+ "IS_BETA_HEADWISE",
26
+ "USE_INITIAL_STATE",
27
+ "STORE_FINAL_STATE",
28
+ "IS_VARLEN",
29
+ ],
30
+ )
31
+
32
+
33
+ @triton.heuristics(
34
+ {
35
+ "USE_G": lambda args: args["g"] is not None,
36
+ "USE_GK": lambda args: args["gk"] is not None,
37
+ "USE_GV": lambda args: args["gv"] is not None,
38
+ "USE_INITIAL_STATE": lambda args: args["h0"] is not None,
39
+ "STORE_FINAL_STATE": lambda args: args["ht"] is not None,
40
+ "IS_VARLEN": lambda args: args["cu_seqlens"] is not None,
41
+ }
42
+ )
43
+ @triton.jit(
44
+ repr=_fused_recurrent_gated_delta_rule_fwd_kernel_repr, do_not_specialize=["T"]
45
+ )
46
+ def _fused_recurrent_gated_delta_rule_fwd_kernel(
47
+ q,
48
+ k,
49
+ v,
50
+ g,
51
+ gk,
52
+ gv,
53
+ beta,
54
+ o,
55
+ h0,
56
+ ht,
57
+ cu_seqlens,
58
+ scale,
59
+ T,
60
+ B: tl.constexpr,
61
+ H: tl.constexpr,
62
+ HV: tl.constexpr,
63
+ K: tl.constexpr,
64
+ V: tl.constexpr,
65
+ BK: tl.constexpr,
66
+ BV: tl.constexpr,
67
+ USE_G: tl.constexpr,
68
+ USE_GK: tl.constexpr,
69
+ USE_GV: tl.constexpr,
70
+ USE_QK_L2NORM_IN_KERNEL: tl.constexpr,
71
+ IS_BETA_HEADWISE: tl.constexpr,
72
+ USE_INITIAL_STATE: tl.constexpr,
73
+ STORE_FINAL_STATE: tl.constexpr,
74
+ IS_VARLEN: tl.constexpr,
75
+ ):
76
+ """
77
+ Fused recurrent gated delta rule forward kernel.
78
+
79
+ This kernel implements a recurrent computation with gating mechanisms
80
+ for sequence modeling tasks.
81
+
82
+ Args:
83
+ q: Query tensor pointer
84
+ k: Key tensor pointer
85
+ v: Value tensor pointer
86
+ g: Global gate tensor pointer (optional)
87
+ gk: Key gate tensor pointer (optional)
88
+ gv: Value gate tensor pointer (optional)
89
+ beta: Beta parameter tensor pointer
90
+ o: Output tensor pointer
91
+ h0: Initial hidden state pointer (optional)
92
+ ht: Final hidden state pointer (optional)
93
+ cu_seqlens: Cumulative sequence lengths for variable-length inputs (optional)
94
+ scale: Scaling factor for queries
95
+ T: Sequence length
96
+ B, H, HV, K, V: Batch, head dimensions
97
+ BK, BV: Block sizes
98
+ USE_G, USE_GK, USE_GV: Flags for using gates
99
+ USE_QK_L2NORM_IN_KERNEL: Flag for L2 normalization
100
+ IS_BETA_HEADWISE: Flag for beta dimensionality
101
+ USE_INITIAL_STATE: Flag for using initial state
102
+ STORE_FINAL_STATE: Flag for storing final state
103
+ IS_VARLEN: Flag for variable-length sequences
104
+ """
105
+ i_v, i_nh = tl.program_id(0), tl.program_id(1)
106
+ i_n, i_hv = i_nh // HV, i_nh % HV
107
+ i_h = i_hv // (HV // H)
108
+
109
+ if IS_VARLEN:
110
+ bos, eos = tl.load(cu_seqlens + i_n).to(tl.int64), tl.load(
111
+ cu_seqlens + i_n + 1
112
+ ).to(tl.int64)
113
+ T = eos - bos
114
+ else:
115
+ bos, eos = i_n * T, i_n * T + T
116
+ o_k = tl.arange(0, BK)
117
+ o_v = i_v * BV + tl.arange(0, BV)
118
+
119
+ p_q = q + (bos * H + i_h) * K + o_k
120
+ p_k = k + (bos * H + i_h) * K + o_k
121
+ p_v = v + (bos * HV + i_hv) * V + o_v
122
+ if USE_G:
123
+ p_g = g + bos * HV + i_hv
124
+ if USE_GK:
125
+ p_gk = gk + (bos * HV + i_hv) * K + o_k
126
+ if USE_GV:
127
+ p_gv = gv + (bos * HV + i_hv) * V + o_v
128
+ if IS_BETA_HEADWISE:
129
+ p_beta = beta + bos * HV + i_hv
130
+ else:
131
+ p_beta = beta + (bos * HV + i_hv) * V + o_v
132
+
133
+ p_o = o + (bos * HV + i_hv) * V + o_v
134
+
135
+ mask_k = o_k < K
136
+ mask_v = o_v < V
137
+ mask_h = mask_k[:, None] & mask_v[None, :]
138
+
139
+ b_h = tl.zeros([BK, BV], dtype=tl.float32)
140
+ if USE_INITIAL_STATE:
141
+ p_h0 = h0 + i_nh * K * V + o_k[:, None] * V + o_v[None, :]
142
+ b_h += tl.load(p_h0, mask=mask_h, other=0).to(tl.float32)
143
+
144
+ for _ in range(0, T):
145
+ b_q = tl.load(p_q, mask=mask_k, other=0).to(tl.float32)
146
+ b_k = tl.load(p_k, mask=mask_k, other=0).to(tl.float32)
147
+ b_v = tl.load(p_v, mask=mask_v, other=0).to(tl.float32)
148
+ if USE_QK_L2NORM_IN_KERNEL:
149
+ b_q = b_q / tl.sqrt(tl.sum(b_q * b_q) + 1e-6)
150
+ b_k = b_k / tl.sqrt(tl.sum(b_k * b_k) + 1e-6)
151
+ b_q = b_q * scale
152
+ if IS_BETA_HEADWISE:
153
+ b_beta = tl.load(p_beta).to(tl.float32)
154
+ else:
155
+ b_beta = tl.load(p_beta, mask=mask_v, other=0).to(tl.float32)
156
+
157
+ # [BK, BV]
158
+ if USE_G:
159
+ b_g = tl.load(p_g).to(tl.float32)
160
+ b_h *= tl.exp(b_g)
161
+
162
+ if USE_GK:
163
+ b_gk = tl.load(p_gk).to(tl.float32)
164
+ b_h *= tl.exp(b_gk[:, None])
165
+
166
+ if USE_GV:
167
+ b_gv = tl.load(p_gv).to(tl.float32)
168
+ b_h *= tl.exp(b_gv[None, :])
169
+
170
+ b_v = b_beta * (b_v - tl.sum(b_h * b_k[:, None], 0))
171
+ b_h += b_k[:, None] * b_v
172
+
173
+ # [BV]
174
+ b_o = tl.sum(b_h * b_q[:, None], 0)
175
+ tl.store(p_o, b_o.to(p_o.dtype.element_ty), mask=mask_v)
176
+
177
+ p_q += H * K
178
+ p_k += H * K
179
+ p_v += HV * V
180
+ if USE_G:
181
+ p_g += HV
182
+ if USE_GK:
183
+ p_gk += HV * K
184
+ if USE_GV:
185
+ p_gv += HV * V
186
+ p_beta += HV * (1 if IS_BETA_HEADWISE else V)
187
+ p_o += HV * V
188
+
189
+ if STORE_FINAL_STATE:
190
+ p_ht = ht + i_nh * K * V + o_k[:, None] * V + o_v[None, :]
191
+ tl.store(p_ht, b_h.to(p_ht.dtype.element_ty), mask=mask_h)
build/torch-rocm/_triton_kernels/gated_delta_rule/decode/fused_sigmoid_gating_recurrent.py ADDED
@@ -0,0 +1,266 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Optional
2
+
3
+ import torch
4
+ import triton
5
+ import triton.language as tl
6
+
7
+ from ..gated_delta_rule_utils import (
8
+ autotune_cache_kwargs,
9
+ gated_delta_rule_autotune_configs,
10
+ input_guard,
11
+ )
12
+
13
+
14
+ def is_cuda():
15
+ try:
16
+ return triton.runtime.driver.active.get_current_target().backend == "cuda"
17
+ except Exception:
18
+ # No active Triton driver (e.g. no-GPU CI sandbox at import time).
19
+ # Default to False so the HIP autotune config is selected.
20
+ return False
21
+
22
+
23
+ def get_cuda_autotune_config():
24
+ return [
25
+ triton.Config({"BV": 8}, num_stages=3, num_warps=1),
26
+ ]
27
+
28
+
29
+ def get_hip_autotune_config():
30
+ return [
31
+ triton.Config({"BV": 64}, num_stages=1, num_warps=4),
32
+ ]
33
+
34
+
35
+ def get_autotune_config():
36
+ if is_cuda():
37
+ return get_cuda_autotune_config()
38
+ else:
39
+ return get_hip_autotune_config()
40
+
41
+
42
+ @triton.heuristics(
43
+ {
44
+ "USE_INITIAL_STATE": lambda args: args["h0_source"] is not None,
45
+ "IS_VARLEN": lambda args: args["cu_seqlens"] is not None,
46
+ }
47
+ )
48
+ @triton.autotune(
49
+ configs=gated_delta_rule_autotune_configs(get_autotune_config()),
50
+ key=["K", "V"],
51
+ **autotune_cache_kwargs,
52
+ )
53
+ @triton.jit(do_not_specialize=["T"])
54
+ def fused_sigmoid_gating_delta_rule_update_kernel(
55
+ A_log,
56
+ a,
57
+ dt_bias,
58
+ softplus_beta,
59
+ softplus_threshold,
60
+ q,
61
+ k,
62
+ v,
63
+ b,
64
+ o,
65
+ h0_source,
66
+ h0_indices,
67
+ cu_seqlens,
68
+ scale,
69
+ T,
70
+ B: tl.constexpr,
71
+ H: tl.constexpr,
72
+ HV: tl.constexpr,
73
+ K: tl.constexpr,
74
+ V: tl.constexpr,
75
+ BK: tl.constexpr,
76
+ BV: tl.constexpr,
77
+ USE_INITIAL_STATE: tl.constexpr,
78
+ USE_QK_L2NORM_IN_KERNEL: tl.constexpr,
79
+ IS_VARLEN: tl.constexpr,
80
+ ):
81
+ """
82
+ Fused kernel that combines sigmoid gating computation with recurrent delta rule update.
83
+ """
84
+ i_k, i_v, i_nh = tl.program_id(0), tl.program_id(1), tl.program_id(2)
85
+ i_n, i_hv = i_nh // HV, i_nh % HV
86
+ i_h = i_hv // (HV // H)
87
+
88
+ if IS_VARLEN:
89
+ bos, eos = (
90
+ tl.load(cu_seqlens + i_n).to(tl.int64),
91
+ tl.load(cu_seqlens + i_n + 1).to(tl.int64),
92
+ )
93
+ all = T
94
+ T = eos - bos
95
+ else:
96
+ bos, eos = i_n * T, i_n * T + T
97
+ all = B * T
98
+
99
+ o_k = i_k * BK + tl.arange(0, BK)
100
+ o_v = i_v * BV + tl.arange(0, BV)
101
+
102
+ p_q = q + (bos * H + i_h) * K + o_k
103
+ p_k = k + (bos * H + i_h) * K + o_k
104
+ p_v = v + (bos * HV + i_hv) * V + o_v
105
+ p_b = b + bos * HV + i_hv
106
+ p_o = o + ((i_k * all + bos) * HV + i_hv) * V + o_v
107
+
108
+ # Gating computation pointers
109
+ p_A_log = A_log + i_hv
110
+ p_a = a + bos * HV + i_hv
111
+ p_dt_bias = dt_bias + i_hv
112
+
113
+ mask_k = o_k < K
114
+ mask_v = o_v < V
115
+ mask_h = mask_k[:, None] & mask_v[None, :]
116
+
117
+ b_h = tl.zeros([BK, BV], dtype=tl.float32)
118
+ if USE_INITIAL_STATE:
119
+ idx = tl.load(h0_indices + i_n)
120
+ if idx >= 0:
121
+ p_h0 = (
122
+ h0_source
123
+ + idx * HV * K * V
124
+ + i_hv * K * V
125
+ + o_k[:, None] * V
126
+ + o_v[None, :]
127
+ )
128
+ b_h += tl.load(p_h0, mask=mask_h, other=0).to(tl.float32)
129
+
130
+ for _ in range(0, T):
131
+ # Load inputs
132
+ b_q = tl.load(p_q, mask=mask_k, other=0).to(tl.float32)
133
+ b_k = tl.load(p_k, mask=mask_k, other=0).to(tl.float32)
134
+ b_v = tl.load(p_v, mask=mask_v, other=0).to(tl.float32)
135
+ b_b = tl.load(p_b).to(tl.float32)
136
+
137
+ # Compute sigmoid gating
138
+ # Load gating parameters
139
+ b_A_log = tl.load(p_A_log).to(tl.float32)
140
+ b_a = tl.load(p_a).to(tl.float32)
141
+ b_dt_bias = tl.load(p_dt_bias).to(tl.float32)
142
+
143
+ # Compute g = -exp(A_log) * softplus(a + dt_bias)
144
+ x = b_a + b_dt_bias
145
+ beta_x = softplus_beta * x
146
+ # Apply softplus with numerical stability
147
+ softplus_x = tl.where(
148
+ beta_x <= softplus_threshold,
149
+ (1.0 / softplus_beta) * tl.log(1.0 + tl.exp(beta_x)),
150
+ x,
151
+ )
152
+ b_g = -tl.exp(b_A_log) * softplus_x
153
+
154
+ # Compute beta = sigmoid(b)
155
+ b_beta = 1.0 / (1.0 + tl.exp(-b_b))
156
+
157
+ # Apply L2 normalization if enabled
158
+ if USE_QK_L2NORM_IN_KERNEL:
159
+ b_q = b_q / (tl.sqrt(tl.sum(b_q * b_q) + 1e-6))
160
+ b_k = b_k / (tl.sqrt(tl.sum(b_k * b_k) + 1e-6))
161
+
162
+ b_q = b_q * scale
163
+
164
+ # Apply gating to hidden state: h *= exp(g)
165
+ b_h *= tl.exp(b_g)
166
+
167
+ # Delta rule: v -= sum(h * k, dim=0)
168
+ b_v -= tl.sum(b_h * b_k[:, None], 0)
169
+
170
+ # Apply beta gating: v *= beta
171
+ b_v *= b_beta
172
+
173
+ # Update hidden state: h += k[:, None] * v[None, :]
174
+ b_h += b_k[:, None] * b_v[None, :]
175
+
176
+ # Compute output: o = sum(h * q, dim=0)
177
+ b_o = tl.sum(b_h * b_q[:, None], 0)
178
+ tl.store(p_o, b_o.to(p_o.dtype.element_ty), mask=mask_v)
179
+
180
+ # Update pointers for next timestep
181
+ p_q += H * K
182
+ p_k += H * K
183
+ p_o += HV * V
184
+ p_v += HV * V
185
+ p_b += HV
186
+ p_a += HV
187
+
188
+ # Store final state back to h0_source with bounds checking
189
+ if USE_INITIAL_STATE:
190
+ idx = tl.load(h0_indices + i_n)
191
+ if idx >= 0:
192
+ p_h0 = (
193
+ h0_source
194
+ + idx * HV * K * V
195
+ + i_hv * K * V
196
+ + o_k[:, None] * V
197
+ + o_v[None, :]
198
+ )
199
+ tl.store(p_h0, b_h.to(p_h0.dtype.element_ty), mask=mask_h)
200
+
201
+
202
+ @input_guard
203
+ def fused_sigmoid_gating_delta_rule_update(
204
+ A_log: torch.Tensor,
205
+ a: torch.Tensor,
206
+ dt_bias: torch.Tensor,
207
+ softplus_beta: float,
208
+ softplus_threshold: float,
209
+ q: torch.Tensor,
210
+ k: torch.Tensor,
211
+ v: torch.Tensor,
212
+ b: torch.Tensor,
213
+ initial_state_source: torch.Tensor,
214
+ initial_state_indices: torch.Tensor,
215
+ scale: Optional[float] = None,
216
+ use_qk_l2norm_in_kernel: bool = False,
217
+ cu_seqlens: Optional[torch.Tensor] = None,
218
+ ):
219
+ """
220
+ Fused triton implementation of sigmoid gating delta rule update.
221
+ This function uses a single fused kernel that combines both sigmoid gating computation
222
+ and the recurrent delta rule update for better performance.
223
+ """
224
+ B, T, H, K, V = *k.shape, v.shape[-1]
225
+ HV = v.shape[2]
226
+ N = B if cu_seqlens is None else len(cu_seqlens) - 1
227
+ BK = triton.next_power_of_2(K)
228
+ NK = triton.cdiv(K, BK)
229
+ assert NK == 1, "NK > 1 is not supported yet"
230
+
231
+ if scale is None:
232
+ scale = k.shape[-1] ** -0.5
233
+ else:
234
+ assert scale > 0, "scale must be positive"
235
+
236
+ o = q.new_empty(NK, *v.shape)
237
+
238
+ def grid(META):
239
+ return (NK, triton.cdiv(V, META["BV"]), N * HV)
240
+
241
+ fused_sigmoid_gating_delta_rule_update_kernel[grid](
242
+ A_log=A_log,
243
+ a=a,
244
+ dt_bias=dt_bias,
245
+ softplus_beta=softplus_beta,
246
+ softplus_threshold=softplus_threshold,
247
+ q=q,
248
+ k=k,
249
+ v=v,
250
+ b=b,
251
+ o=o,
252
+ h0_source=initial_state_source,
253
+ h0_indices=initial_state_indices,
254
+ cu_seqlens=cu_seqlens,
255
+ scale=scale,
256
+ T=T,
257
+ B=B,
258
+ H=H,
259
+ HV=HV,
260
+ K=K,
261
+ V=V,
262
+ BK=BK,
263
+ USE_QK_L2NORM_IN_KERNEL=use_qk_l2norm_in_kernel,
264
+ )
265
+ o = o.squeeze(0)
266
+ return o
build/torch-rocm/_triton_kernels/gated_delta_rule/fused_qkvzba_split.py ADDED
@@ -0,0 +1,580 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Fused kernels for QKVZBA split, reshape and concatenation."""
2
+
3
+ import torch
4
+ import triton
5
+ import triton.language as tl
6
+
7
+
8
+ @triton.jit
9
+ def _fused_qkvzba_split_reshape_cat_decode_kernel(
10
+ mixed_qkv,
11
+ z,
12
+ b,
13
+ a,
14
+ mixed_qkvz,
15
+ mixed_ba,
16
+ NUM_HEADS_QK: tl.constexpr,
17
+ NUM_HEADS_V: tl.constexpr,
18
+ HEAD_QK: tl.constexpr,
19
+ HEAD_V: tl.constexpr,
20
+ ):
21
+ """Decode stage fused kernel."""
22
+ i_bs, i_qk = tl.program_id(0), tl.program_id(1)
23
+ QKVZ_DIM_T: tl.constexpr = HEAD_QK * 2 + NUM_HEADS_V // NUM_HEADS_QK * HEAD_V * 2
24
+ BA_DIM_T: tl.constexpr = NUM_HEADS_V // NUM_HEADS_QK * 2
25
+ QKV_DIM_T: tl.constexpr = HEAD_QK * 2 + NUM_HEADS_V // NUM_HEADS_QK * HEAD_V
26
+ q_end: tl.constexpr = HEAD_QK
27
+ blk_q_ptr = (
28
+ mixed_qkvz
29
+ + i_bs * NUM_HEADS_QK * QKVZ_DIM_T
30
+ + i_qk * QKVZ_DIM_T
31
+ + tl.arange(0, q_end)
32
+ )
33
+ k_end: tl.constexpr = q_end + HEAD_QK
34
+ blk_k_ptr = (
35
+ mixed_qkvz
36
+ + i_bs * NUM_HEADS_QK * QKVZ_DIM_T
37
+ + i_qk * QKVZ_DIM_T
38
+ + tl.arange(q_end, k_end)
39
+ )
40
+ v_end: tl.constexpr = k_end + NUM_HEADS_V // NUM_HEADS_QK * HEAD_V
41
+ blk_v_ptr = (
42
+ mixed_qkvz
43
+ + i_bs * NUM_HEADS_QK * QKVZ_DIM_T
44
+ + i_qk * QKVZ_DIM_T
45
+ + tl.arange(k_end, v_end)
46
+ )
47
+ z_end: tl.constexpr = v_end + NUM_HEADS_V // NUM_HEADS_QK * HEAD_V
48
+ blk_z_ptr = (
49
+ mixed_qkvz
50
+ + i_bs * NUM_HEADS_QK * QKVZ_DIM_T
51
+ + i_qk * QKVZ_DIM_T
52
+ + tl.arange(v_end, z_end)
53
+ )
54
+ blk_q_st_ptr = (
55
+ mixed_qkv
56
+ + i_bs * NUM_HEADS_QK * QKV_DIM_T
57
+ + i_qk * HEAD_QK
58
+ + tl.arange(0, HEAD_QK)
59
+ )
60
+ blk_k_st_ptr = (
61
+ mixed_qkv
62
+ + i_bs * NUM_HEADS_QK * QKV_DIM_T
63
+ + NUM_HEADS_QK * HEAD_QK
64
+ + i_qk * HEAD_QK
65
+ + tl.arange(0, HEAD_QK)
66
+ )
67
+ blk_v_st_ptr = (
68
+ mixed_qkv
69
+ + i_bs * NUM_HEADS_QK * QKV_DIM_T
70
+ + NUM_HEADS_QK * HEAD_QK * 2
71
+ + i_qk * HEAD_V * NUM_HEADS_V // NUM_HEADS_QK
72
+ + tl.arange(0, HEAD_V * NUM_HEADS_V // NUM_HEADS_QK)
73
+ )
74
+ blk_z_st_ptr = (
75
+ z
76
+ + i_bs * NUM_HEADS_V * HEAD_V
77
+ + i_qk * HEAD_V * NUM_HEADS_V // NUM_HEADS_QK
78
+ + tl.arange(0, HEAD_V * NUM_HEADS_V // NUM_HEADS_QK)
79
+ )
80
+ tl.store(blk_q_st_ptr, tl.load(blk_q_ptr))
81
+ tl.store(blk_k_st_ptr, tl.load(blk_k_ptr))
82
+ tl.store(blk_v_st_ptr, tl.load(blk_v_ptr))
83
+ tl.store(blk_z_st_ptr, tl.load(blk_z_ptr))
84
+ b_end: tl.constexpr = NUM_HEADS_V // NUM_HEADS_QK
85
+ a_end: tl.constexpr = b_end + NUM_HEADS_V // NUM_HEADS_QK
86
+ for i in tl.static_range(b_end):
87
+ blk_b_ptr = mixed_ba + i_bs * NUM_HEADS_QK * BA_DIM_T + i_qk * BA_DIM_T + i
88
+ blk_b_st_ptr = b + i_bs * NUM_HEADS_V + i_qk * NUM_HEADS_V // NUM_HEADS_QK + i
89
+ tl.store(blk_b_st_ptr, tl.load(blk_b_ptr))
90
+ for i in tl.static_range(b_end, a_end):
91
+ blk_a_ptr = mixed_ba + i_bs * NUM_HEADS_QK * BA_DIM_T + i_qk * BA_DIM_T + i
92
+ blk_a_st_ptr = (
93
+ a + i_bs * NUM_HEADS_V + i_qk * NUM_HEADS_V // NUM_HEADS_QK + (i - b_end)
94
+ )
95
+ tl.store(blk_a_st_ptr, tl.load(blk_a_ptr))
96
+
97
+
98
+ def fused_qkvzba_split_reshape_cat_decode(
99
+ mixed_qkvz: torch.Tensor,
100
+ mixed_ba: torch.Tensor,
101
+ num_heads_qk: int,
102
+ num_heads_v: int,
103
+ head_qk: int,
104
+ head_v: int,
105
+ ):
106
+ """Decode stage fused function."""
107
+ batch, seq_len = mixed_qkvz.shape[0], 1
108
+ qkv_dim_t = num_heads_qk * head_qk * 2 + num_heads_v * head_v
109
+ mixed_qkv = torch.empty(
110
+ [batch * seq_len, qkv_dim_t],
111
+ dtype=mixed_qkvz.dtype,
112
+ device=mixed_qkvz.device,
113
+ )
114
+ z = torch.empty(
115
+ [batch * seq_len, num_heads_v, head_v],
116
+ dtype=mixed_qkvz.dtype,
117
+ device=mixed_qkvz.device,
118
+ )
119
+ b = torch.empty(
120
+ [batch * seq_len, num_heads_v],
121
+ dtype=mixed_ba.dtype,
122
+ device=mixed_ba.device,
123
+ )
124
+ a = torch.empty_like(b)
125
+ grid = (batch * seq_len, num_heads_qk)
126
+ _fused_qkvzba_split_reshape_cat_decode_kernel[grid](
127
+ mixed_qkv,
128
+ z,
129
+ b,
130
+ a,
131
+ mixed_qkvz,
132
+ mixed_ba,
133
+ num_heads_qk,
134
+ num_heads_v,
135
+ head_qk,
136
+ head_v,
137
+ num_warps=1,
138
+ num_stages=3,
139
+ )
140
+ return mixed_qkv, z, b, a
141
+
142
+
143
+ @triton.jit
144
+ def _fused_qkvzba_prefill_kernel_nqk1(
145
+ mixed_qkv,
146
+ z,
147
+ b_out,
148
+ a_out,
149
+ mixed_qkvz,
150
+ mixed_ba,
151
+ NUM_HEADS_V: tl.constexpr,
152
+ HEAD_QK: tl.constexpr,
153
+ HEAD_V: tl.constexpr,
154
+ ):
155
+ """Prefill kernel specialized for NUM_HEADS_QK=1."""
156
+ i_seq = tl.program_id(0)
157
+ V_PER_QK: tl.constexpr = NUM_HEADS_V
158
+ QKVZ_DIM_T: tl.constexpr = HEAD_QK * 2 + V_PER_QK * HEAD_V * 2
159
+ BA_DIM_T: tl.constexpr = V_PER_QK * 2
160
+ QKV_DIM_T: tl.constexpr = HEAD_QK * 2 + NUM_HEADS_V * HEAD_V
161
+
162
+ base_in = mixed_qkvz + i_seq * QKVZ_DIM_T
163
+ base_out = mixed_qkv + i_seq * QKV_DIM_T
164
+
165
+ # Load and store q
166
+ blk_q = tl.load(base_in + tl.arange(0, HEAD_QK))
167
+ tl.store(base_out + tl.arange(0, HEAD_QK), blk_q)
168
+
169
+ # Load and store k
170
+ blk_k = tl.load(base_in + HEAD_QK + tl.arange(0, HEAD_QK))
171
+ tl.store(base_out + HEAD_QK + tl.arange(0, HEAD_QK), blk_k)
172
+
173
+ # Load and store v
174
+ blk_v = tl.load(base_in + HEAD_QK * 2 + tl.arange(0, V_PER_QK * HEAD_V))
175
+ tl.store(base_out + HEAD_QK * 2 + tl.arange(0, V_PER_QK * HEAD_V), blk_v)
176
+
177
+ # Load and store z
178
+ blk_z = tl.load(
179
+ base_in + HEAD_QK * 2 + V_PER_QK * HEAD_V + tl.arange(0, V_PER_QK * HEAD_V)
180
+ )
181
+ tl.store(z + i_seq * NUM_HEADS_V * HEAD_V + tl.arange(0, V_PER_QK * HEAD_V), blk_z)
182
+
183
+ # Load and store b, a
184
+ base_ba = mixed_ba + i_seq * BA_DIM_T
185
+ for i in tl.static_range(V_PER_QK):
186
+ tl.store(b_out + i_seq * NUM_HEADS_V + i, tl.load(base_ba + i))
187
+ tl.store(a_out + i_seq * NUM_HEADS_V + i, tl.load(base_ba + V_PER_QK + i))
188
+
189
+
190
+ @triton.jit
191
+ def _fused_qkvzba_prefill_kernel_nqk2(
192
+ mixed_qkv,
193
+ z,
194
+ b_out,
195
+ a_out,
196
+ mixed_qkvz,
197
+ mixed_ba,
198
+ NUM_HEADS_V: tl.constexpr,
199
+ HEAD_QK: tl.constexpr,
200
+ HEAD_V: tl.constexpr,
201
+ ):
202
+ """Prefill kernel specialized for NUM_HEADS_QK=2, single block processes entire row."""
203
+ i_seq = tl.program_id(0)
204
+ NUM_HEADS_QK: tl.constexpr = 2
205
+ V_PER_QK: tl.constexpr = NUM_HEADS_V // NUM_HEADS_QK
206
+ QKVZ_DIM_T: tl.constexpr = HEAD_QK * 2 + V_PER_QK * HEAD_V * 2
207
+ BA_DIM_T: tl.constexpr = V_PER_QK * 2
208
+ QKV_DIM_T: tl.constexpr = NUM_HEADS_QK * HEAD_QK * 2 + NUM_HEADS_V * HEAD_V
209
+
210
+ base_out = mixed_qkv + i_seq * QKV_DIM_T
211
+
212
+ # Process all heads sequentially to ensure contiguous writes
213
+ for i_qk in tl.static_range(NUM_HEADS_QK):
214
+ base_in = mixed_qkvz + i_seq * NUM_HEADS_QK * QKVZ_DIM_T + i_qk * QKVZ_DIM_T
215
+
216
+ # Load q, k, v, z
217
+ blk_q = tl.load(base_in + tl.arange(0, HEAD_QK))
218
+ blk_k = tl.load(base_in + HEAD_QK + tl.arange(0, HEAD_QK))
219
+ blk_v = tl.load(base_in + HEAD_QK * 2 + tl.arange(0, V_PER_QK * HEAD_V))
220
+ blk_z = tl.load(
221
+ base_in + HEAD_QK * 2 + V_PER_QK * HEAD_V + tl.arange(0, V_PER_QK * HEAD_V)
222
+ )
223
+
224
+ # Store q (all q contiguous)
225
+ tl.store(base_out + i_qk * HEAD_QK + tl.arange(0, HEAD_QK), blk_q)
226
+ # Store k (all k contiguous, after q)
227
+ tl.store(
228
+ base_out + NUM_HEADS_QK * HEAD_QK + i_qk * HEAD_QK + tl.arange(0, HEAD_QK),
229
+ blk_k,
230
+ )
231
+ # Store v (all v contiguous, after k)
232
+ tl.store(
233
+ base_out
234
+ + NUM_HEADS_QK * HEAD_QK * 2
235
+ + i_qk * V_PER_QK * HEAD_V
236
+ + tl.arange(0, V_PER_QK * HEAD_V),
237
+ blk_v,
238
+ )
239
+ # Store z
240
+ tl.store(
241
+ z
242
+ + i_seq * NUM_HEADS_V * HEAD_V
243
+ + i_qk * V_PER_QK * HEAD_V
244
+ + tl.arange(0, V_PER_QK * HEAD_V),
245
+ blk_z,
246
+ )
247
+
248
+ # Store b, a
249
+ base_ba = mixed_ba + i_seq * NUM_HEADS_QK * BA_DIM_T + i_qk * BA_DIM_T
250
+ for i in tl.static_range(V_PER_QK):
251
+ tl.store(
252
+ b_out + i_seq * NUM_HEADS_V + i_qk * V_PER_QK + i, tl.load(base_ba + i)
253
+ )
254
+ tl.store(
255
+ a_out + i_seq * NUM_HEADS_V + i_qk * V_PER_QK + i,
256
+ tl.load(base_ba + V_PER_QK + i),
257
+ )
258
+
259
+
260
+ @triton.jit
261
+ def _fused_qkvzba_prefill_kernel_nqk4(
262
+ mixed_qkv,
263
+ z,
264
+ b_out,
265
+ a_out,
266
+ mixed_qkvz,
267
+ mixed_ba,
268
+ NUM_HEADS_V: tl.constexpr,
269
+ HEAD_QK: tl.constexpr,
270
+ HEAD_V: tl.constexpr,
271
+ ):
272
+ """Prefill kernel specialized for NUM_HEADS_QK=4, single block processes entire row."""
273
+ i_seq = tl.program_id(0)
274
+ NUM_HEADS_QK: tl.constexpr = 4
275
+ V_PER_QK: tl.constexpr = NUM_HEADS_V // NUM_HEADS_QK
276
+ QKVZ_DIM_T: tl.constexpr = HEAD_QK * 2 + V_PER_QK * HEAD_V * 2
277
+ BA_DIM_T: tl.constexpr = V_PER_QK * 2
278
+ QKV_DIM_T: tl.constexpr = NUM_HEADS_QK * HEAD_QK * 2 + NUM_HEADS_V * HEAD_V
279
+
280
+ base_out = mixed_qkv + i_seq * QKV_DIM_T
281
+
282
+ for i_qk in tl.static_range(NUM_HEADS_QK):
283
+ base_in = mixed_qkvz + i_seq * NUM_HEADS_QK * QKVZ_DIM_T + i_qk * QKVZ_DIM_T
284
+
285
+ blk_q = tl.load(base_in + tl.arange(0, HEAD_QK))
286
+ blk_k = tl.load(base_in + HEAD_QK + tl.arange(0, HEAD_QK))
287
+ blk_v = tl.load(base_in + HEAD_QK * 2 + tl.arange(0, V_PER_QK * HEAD_V))
288
+ blk_z = tl.load(
289
+ base_in + HEAD_QK * 2 + V_PER_QK * HEAD_V + tl.arange(0, V_PER_QK * HEAD_V)
290
+ )
291
+
292
+ tl.store(base_out + i_qk * HEAD_QK + tl.arange(0, HEAD_QK), blk_q)
293
+ tl.store(
294
+ base_out + NUM_HEADS_QK * HEAD_QK + i_qk * HEAD_QK + tl.arange(0, HEAD_QK),
295
+ blk_k,
296
+ )
297
+ tl.store(
298
+ base_out
299
+ + NUM_HEADS_QK * HEAD_QK * 2
300
+ + i_qk * V_PER_QK * HEAD_V
301
+ + tl.arange(0, V_PER_QK * HEAD_V),
302
+ blk_v,
303
+ )
304
+ tl.store(
305
+ z
306
+ + i_seq * NUM_HEADS_V * HEAD_V
307
+ + i_qk * V_PER_QK * HEAD_V
308
+ + tl.arange(0, V_PER_QK * HEAD_V),
309
+ blk_z,
310
+ )
311
+
312
+ base_ba = mixed_ba + i_seq * NUM_HEADS_QK * BA_DIM_T + i_qk * BA_DIM_T
313
+ for i in tl.static_range(V_PER_QK):
314
+ tl.store(
315
+ b_out + i_seq * NUM_HEADS_V + i_qk * V_PER_QK + i, tl.load(base_ba + i)
316
+ )
317
+ tl.store(
318
+ a_out + i_seq * NUM_HEADS_V + i_qk * V_PER_QK + i,
319
+ tl.load(base_ba + V_PER_QK + i),
320
+ )
321
+
322
+
323
+ @triton.jit
324
+ def _fused_qkvzba_prefill_kernel_nqk8(
325
+ mixed_qkv,
326
+ z,
327
+ b_out,
328
+ a_out,
329
+ mixed_qkvz,
330
+ mixed_ba,
331
+ NUM_HEADS_V: tl.constexpr,
332
+ HEAD_QK: tl.constexpr,
333
+ HEAD_V: tl.constexpr,
334
+ ):
335
+ """Prefill kernel specialized for NUM_HEADS_QK=8, single block processes entire row."""
336
+ i_seq = tl.program_id(0)
337
+ NUM_HEADS_QK: tl.constexpr = 8
338
+ V_PER_QK: tl.constexpr = NUM_HEADS_V // NUM_HEADS_QK
339
+ QKVZ_DIM_T: tl.constexpr = HEAD_QK * 2 + V_PER_QK * HEAD_V * 2
340
+ BA_DIM_T: tl.constexpr = V_PER_QK * 2
341
+ QKV_DIM_T: tl.constexpr = NUM_HEADS_QK * HEAD_QK * 2 + NUM_HEADS_V * HEAD_V
342
+
343
+ base_out = mixed_qkv + i_seq * QKV_DIM_T
344
+
345
+ for i_qk in tl.static_range(NUM_HEADS_QK):
346
+ base_in = mixed_qkvz + i_seq * NUM_HEADS_QK * QKVZ_DIM_T + i_qk * QKVZ_DIM_T
347
+
348
+ blk_q = tl.load(base_in + tl.arange(0, HEAD_QK))
349
+ blk_k = tl.load(base_in + HEAD_QK + tl.arange(0, HEAD_QK))
350
+ blk_v = tl.load(base_in + HEAD_QK * 2 + tl.arange(0, V_PER_QK * HEAD_V))
351
+ blk_z = tl.load(
352
+ base_in + HEAD_QK * 2 + V_PER_QK * HEAD_V + tl.arange(0, V_PER_QK * HEAD_V)
353
+ )
354
+
355
+ tl.store(base_out + i_qk * HEAD_QK + tl.arange(0, HEAD_QK), blk_q)
356
+ tl.store(
357
+ base_out + NUM_HEADS_QK * HEAD_QK + i_qk * HEAD_QK + tl.arange(0, HEAD_QK),
358
+ blk_k,
359
+ )
360
+ tl.store(
361
+ base_out
362
+ + NUM_HEADS_QK * HEAD_QK * 2
363
+ + i_qk * V_PER_QK * HEAD_V
364
+ + tl.arange(0, V_PER_QK * HEAD_V),
365
+ blk_v,
366
+ )
367
+ tl.store(
368
+ z
369
+ + i_seq * NUM_HEADS_V * HEAD_V
370
+ + i_qk * V_PER_QK * HEAD_V
371
+ + tl.arange(0, V_PER_QK * HEAD_V),
372
+ blk_z,
373
+ )
374
+
375
+ base_ba = mixed_ba + i_seq * NUM_HEADS_QK * BA_DIM_T + i_qk * BA_DIM_T
376
+ for i in tl.static_range(V_PER_QK):
377
+ tl.store(
378
+ b_out + i_seq * NUM_HEADS_V + i_qk * V_PER_QK + i, tl.load(base_ba + i)
379
+ )
380
+ tl.store(
381
+ a_out + i_seq * NUM_HEADS_V + i_qk * V_PER_QK + i,
382
+ tl.load(base_ba + V_PER_QK + i),
383
+ )
384
+
385
+
386
+ @triton.jit
387
+ def _fused_qkvzba_split_reshape_cat_prefill_kernel(
388
+ mixed_qkv,
389
+ z,
390
+ b_out,
391
+ a_out,
392
+ mixed_qkvz,
393
+ mixed_ba,
394
+ NUM_HEADS_QK: tl.constexpr,
395
+ NUM_HEADS_V: tl.constexpr,
396
+ HEAD_QK: tl.constexpr,
397
+ HEAD_V: tl.constexpr,
398
+ ):
399
+ """
400
+ Generic prefill kernel (fallback for unsupported NUM_HEADS_QK values).
401
+
402
+ Uses 2D grid where each program processes one (seq_pos, qk_head).
403
+ Note: This may cause cache line conflicts; prefer specialized kernels.
404
+ """
405
+ i_seq, i_qk = tl.program_id(0), tl.program_id(1)
406
+
407
+ V_PER_QK: tl.constexpr = NUM_HEADS_V // NUM_HEADS_QK
408
+ QKVZ_DIM_T: tl.constexpr = HEAD_QK * 2 + V_PER_QK * HEAD_V * 2
409
+ BA_DIM_T: tl.constexpr = V_PER_QK * 2
410
+ QKV_DIM_T: tl.constexpr = NUM_HEADS_QK * HEAD_QK * 2 + NUM_HEADS_V * HEAD_V
411
+
412
+ # Load from mixed_qkvz
413
+ base_qkvz = mixed_qkvz + i_seq * NUM_HEADS_QK * QKVZ_DIM_T + i_qk * QKVZ_DIM_T
414
+
415
+ q_end: tl.constexpr = HEAD_QK
416
+ blk_q = tl.load(base_qkvz + tl.arange(0, q_end))
417
+
418
+ k_end: tl.constexpr = q_end + HEAD_QK
419
+ blk_k = tl.load(base_qkvz + tl.arange(q_end, k_end))
420
+
421
+ v_end: tl.constexpr = k_end + V_PER_QK * HEAD_V
422
+ blk_v = tl.load(base_qkvz + tl.arange(k_end, v_end))
423
+
424
+ z_end: tl.constexpr = v_end + V_PER_QK * HEAD_V
425
+ blk_z = tl.load(base_qkvz + tl.arange(v_end, z_end))
426
+
427
+ # Load from mixed_ba
428
+ base_ba = mixed_ba + i_seq * NUM_HEADS_QK * BA_DIM_T + i_qk * BA_DIM_T
429
+
430
+ # Store to mixed_qkv (concatenated q, k, v)
431
+ q_out_ptr = mixed_qkv + i_seq * QKV_DIM_T + i_qk * HEAD_QK + tl.arange(0, HEAD_QK)
432
+ tl.store(q_out_ptr, blk_q)
433
+
434
+ k_out_ptr = (
435
+ mixed_qkv
436
+ + i_seq * QKV_DIM_T
437
+ + NUM_HEADS_QK * HEAD_QK
438
+ + i_qk * HEAD_QK
439
+ + tl.arange(0, HEAD_QK)
440
+ )
441
+ tl.store(k_out_ptr, blk_k)
442
+
443
+ v_out_ptr = (
444
+ mixed_qkv
445
+ + i_seq * QKV_DIM_T
446
+ + NUM_HEADS_QK * HEAD_QK * 2
447
+ + i_qk * V_PER_QK * HEAD_V
448
+ + tl.arange(0, V_PER_QK * HEAD_V)
449
+ )
450
+ tl.store(v_out_ptr, blk_v)
451
+
452
+ # Store z
453
+ z_out_ptr = (
454
+ z
455
+ + i_seq * NUM_HEADS_V * HEAD_V
456
+ + i_qk * V_PER_QK * HEAD_V
457
+ + tl.arange(0, V_PER_QK * HEAD_V)
458
+ )
459
+ tl.store(z_out_ptr, blk_z)
460
+
461
+ # Store b, a
462
+ for i in tl.static_range(V_PER_QK):
463
+ blk_b_ptr = base_ba + i
464
+ blk_b_st_ptr = b_out + i_seq * NUM_HEADS_V + i_qk * V_PER_QK + i
465
+ tl.store(blk_b_st_ptr, tl.load(blk_b_ptr))
466
+
467
+ blk_a_ptr = base_ba + V_PER_QK + i
468
+ blk_a_st_ptr = a_out + i_seq * NUM_HEADS_V + i_qk * V_PER_QK + i
469
+ tl.store(blk_a_st_ptr, tl.load(blk_a_ptr))
470
+
471
+
472
+ def fused_qkvzba_split_reshape_cat_prefill(
473
+ mixed_qkvz: torch.Tensor,
474
+ mixed_ba: torch.Tensor,
475
+ num_heads_qk: int,
476
+ num_heads_v: int,
477
+ head_qk: int,
478
+ head_v: int,
479
+ ):
480
+ """Prefill stage fused function."""
481
+ seq_len = mixed_qkvz.shape[0]
482
+
483
+ qkv_dim = num_heads_qk * head_qk * 2 + num_heads_v * head_v
484
+ mixed_qkv = torch.empty(
485
+ [seq_len, qkv_dim],
486
+ dtype=mixed_qkvz.dtype,
487
+ device=mixed_qkvz.device,
488
+ )
489
+ z = torch.empty(
490
+ [seq_len, num_heads_v, head_v],
491
+ dtype=mixed_qkvz.dtype,
492
+ device=mixed_qkvz.device,
493
+ )
494
+ b = torch.empty(
495
+ [seq_len, num_heads_v],
496
+ dtype=mixed_ba.dtype,
497
+ device=mixed_ba.device,
498
+ )
499
+ a = torch.empty_like(b)
500
+
501
+ # Select specialized kernel based on num_heads_qk
502
+ if num_heads_qk == 1:
503
+ grid = (seq_len,)
504
+ _fused_qkvzba_prefill_kernel_nqk1[grid](
505
+ mixed_qkv,
506
+ z,
507
+ b,
508
+ a,
509
+ mixed_qkvz,
510
+ mixed_ba,
511
+ num_heads_v,
512
+ head_qk,
513
+ head_v,
514
+ num_warps=1,
515
+ num_stages=3,
516
+ )
517
+ elif num_heads_qk == 2:
518
+ grid = (seq_len,)
519
+ _fused_qkvzba_prefill_kernel_nqk2[grid](
520
+ mixed_qkv,
521
+ z,
522
+ b,
523
+ a,
524
+ mixed_qkvz,
525
+ mixed_ba,
526
+ num_heads_v,
527
+ head_qk,
528
+ head_v,
529
+ num_warps=1,
530
+ num_stages=3,
531
+ )
532
+ elif num_heads_qk == 4:
533
+ grid = (seq_len,)
534
+ _fused_qkvzba_prefill_kernel_nqk4[grid](
535
+ mixed_qkv,
536
+ z,
537
+ b,
538
+ a,
539
+ mixed_qkvz,
540
+ mixed_ba,
541
+ num_heads_v,
542
+ head_qk,
543
+ head_v,
544
+ num_warps=1,
545
+ num_stages=3,
546
+ )
547
+ elif num_heads_qk == 8:
548
+ grid = (seq_len,)
549
+ _fused_qkvzba_prefill_kernel_nqk8[grid](
550
+ mixed_qkv,
551
+ z,
552
+ b,
553
+ a,
554
+ mixed_qkvz,
555
+ mixed_ba,
556
+ num_heads_v,
557
+ head_qk,
558
+ head_v,
559
+ num_warps=1,
560
+ num_stages=3,
561
+ )
562
+ else:
563
+ # Fallback to generic 2D-grid kernel
564
+ grid = (seq_len, num_heads_qk)
565
+ _fused_qkvzba_split_reshape_cat_prefill_kernel[grid](
566
+ mixed_qkv,
567
+ z,
568
+ b,
569
+ a,
570
+ mixed_qkvz,
571
+ mixed_ba,
572
+ num_heads_qk,
573
+ num_heads_v,
574
+ head_qk,
575
+ head_v,
576
+ num_warps=1,
577
+ num_stages=3,
578
+ )
579
+
580
+ return mixed_qkv, z, b, a
build/torch-rocm/_triton_kernels/gated_delta_rule/gated_delta_rule_utils.py ADDED
@@ -0,0 +1,580 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (C) 2023-2026, Songlin Yang, Yu Zhang
2
+
3
+ import contextlib
4
+ import functools
5
+ import inspect
6
+ import logging
7
+ import math
8
+ import os
9
+ import sys
10
+ import warnings
11
+ from collections.abc import Callable
12
+ from enum import Enum
13
+ from functools import lru_cache
14
+ from typing import TYPE_CHECKING, Any
15
+
16
+ import torch
17
+ import triton
18
+ from packaging import version
19
+
20
+ logger = logging.getLogger(__name__)
21
+
22
+ # Autotune cache support
23
+ SUPPORTS_AUTOTUNE_CACHE = (
24
+ "cache_results" in inspect.signature(triton.autotune).parameters
25
+ )
26
+ FLA_CACHE_RESULTS = os.getenv("FLA_CACHE_RESULTS", "1") == "1"
27
+ autotune_cache_kwargs = (
28
+ {"cache_results": FLA_CACHE_RESULTS} if SUPPORTS_AUTOTUNE_CACHE else {}
29
+ )
30
+
31
+ if TYPE_CHECKING:
32
+ from fla import __version__
33
+
34
+ FLA_CI_ENV = os.getenv("FLA_CI_ENV") == "1"
35
+ FLA_CACHE_RESULTS = os.getenv("FLA_CACHE_RESULTS", "1") == "1"
36
+
37
+
38
+ SUPPORTS_AUTOTUNE_CACHE = (
39
+ "cache_results" in inspect.signature(triton.autotune).parameters
40
+ )
41
+
42
+ autotune_cache_kwargs = (
43
+ {"cache_results": FLA_CACHE_RESULTS} if SUPPORTS_AUTOTUNE_CACHE else {}
44
+ )
45
+
46
+ GATED_DELTA_RULE_TRITON_AUTOTUNE = os.environ.get(
47
+ "GATED_DELTA_RULE_TRITON_AUTOTUNE", "0"
48
+ ).lower() in ("1", "true", "yes", "on")
49
+
50
+ # log2(e) == 1/ln(2). Converts natural-log gate values to log2 space so
51
+ # kernels can use exp2 instead of exp. Python/wrapper-only: pass into kernels
52
+ # as a constexpr scale (e.g. G_SCALE); never reference inside @triton.jit kernels.
53
+ RCP_LN2: float = math.log2(math.e)
54
+
55
+
56
+ def gated_delta_rule_autotune_configs(
57
+ configs: list[triton.Config], default_config: triton.Config | None = None
58
+ ) -> list[triton.Config]:
59
+ """
60
+ Select Triton autotune configs based on the gated delta rule env flag.
61
+
62
+ When ``GATED_DELTA_RULE_TRITON_AUTOTUNE`` is enabled, return the full config
63
+ list so ``@triton.autotune`` benchmarks candidate kernels. When disabled,
64
+ return only ``default_config`` (or the first config) to skip tuning overhead
65
+ while keeping the decorator shape uniform across decode and prefill kernels.
66
+ """
67
+ if GATED_DELTA_RULE_TRITON_AUTOTUNE:
68
+ return configs
69
+ cfg = default_config if default_config is not None else configs[0]
70
+ return [cfg]
71
+
72
+
73
+ @lru_cache(maxsize=1)
74
+ def check_environments():
75
+ """
76
+ Checks the current operating system, Triton version, and Python version,
77
+ issuing warnings if they don't meet recommendations.
78
+ This function's body only runs once due to lru_cache.
79
+ """
80
+ # Check Operating System
81
+ if sys.platform == "win32":
82
+ logger.warning(
83
+ "Detected Windows operating system. Triton does not have an official Windows release, "
84
+ "thus FLA will not be adapted for Windows, and any potential errors will not be fixed. "
85
+ "Please consider using a Linux environment for compatibility.",
86
+ )
87
+
88
+ triton_version = version.parse(triton.__version__)
89
+ required_triton_version = version.parse("3.2.0")
90
+
91
+ if triton_version < required_triton_version:
92
+ logger.warning(
93
+ f"Current Triton version {triton_version} is below the recommended 3.2.0 version. "
94
+ "Errors may occur and these issues will not be fixed. "
95
+ "Please consider upgrading Triton.",
96
+ )
97
+
98
+ # Check Python version
99
+ py_version = version.parse(f"{sys.version_info.major}.{sys.version_info.minor}")
100
+ required_py_version = version.parse("3.11")
101
+
102
+ if py_version < required_py_version:
103
+ logger.warning(
104
+ f"Current Python version {py_version} is below the recommended 3.11 version. "
105
+ "It is recommended to upgrade to Python 3.11 or higher for the best experience.",
106
+ )
107
+
108
+ return None
109
+
110
+
111
+ check_environments()
112
+
113
+
114
+ def get_abs_err(x, y):
115
+ return (x.detach() - y.detach()).flatten().abs().max().item()
116
+
117
+
118
+ def get_err_ratio(x, y):
119
+ err = (x.detach() - y.detach()).flatten().square().mean().sqrt().item()
120
+ base = (x.detach()).flatten().square().mean().sqrt().item()
121
+ return err / (base + 1e-8)
122
+
123
+
124
+ def assert_close(prefix, ref, tri, ratio, warning=False, err_atol=1e-6):
125
+ abs_atol = get_abs_err(ref, tri)
126
+ msg = f"{prefix:>16} diff: {abs_atol:.6f} ratio: {get_err_ratio(ref, tri):.6f}"
127
+ logger.info(msg)
128
+ error_rate = get_err_ratio(ref, tri)
129
+ if abs_atol <= err_atol:
130
+ return
131
+ if warning or (FLA_CI_ENV and (error_rate < 0.01 or abs_atol <= 0.3)):
132
+ if error_rate > ratio:
133
+ warnings.warn(msg)
134
+ else:
135
+ assert error_rate < ratio, msg
136
+
137
+
138
+ def tensor_cache(
139
+ fn: Callable[..., torch.Tensor],
140
+ ) -> Callable[..., torch.Tensor]:
141
+ """
142
+ A decorator that caches the most recent result of a function with tensor inputs.
143
+
144
+ This decorator will store the output of the decorated function for the most recent set of input tensors.
145
+ If the function is called again with the same input tensors, it will return the cached result.
146
+
147
+
148
+ Args:
149
+ fn (Callable[..., torch.Tensor]):
150
+ The function to be decorated. It should take tensor inputs and return tensor outputs.
151
+
152
+ Returns:
153
+ Callable[..., torch.Tensor]:
154
+ A wrapped version of the input function with single-entry caching.
155
+ """
156
+ last_args: tuple | None = None
157
+ last_kwargs: dict | None = None
158
+ last_result: Any = None
159
+
160
+ @functools.wraps(fn)
161
+ def wrapper(*args: Any, **kwargs: Any) -> Any:
162
+ nonlocal last_args, last_kwargs, last_result
163
+
164
+ if last_args is not None and last_kwargs is not None:
165
+ if len(args) == len(last_args) and len(kwargs) == len(last_kwargs):
166
+ if all(a is b for a, b in zip(args, last_args, strict=False)) and all(
167
+ k in last_kwargs and v is last_kwargs[k] for k, v in kwargs.items()
168
+ ):
169
+ return last_result
170
+
171
+ result = fn(*args, **kwargs)
172
+ last_args, last_kwargs, last_result = args, kwargs, result
173
+ return result
174
+
175
+ return wrapper
176
+
177
+
178
+ def input_guard(
179
+ fn: Callable[..., torch.Tensor],
180
+ ) -> Callable[..., torch.Tensor]:
181
+ """
182
+ A decorator to make sure all input tensors are contiguous and set the device based on input tensors.
183
+ """
184
+
185
+ @functools.wraps(fn)
186
+ def wrapper(*args, **kwargs):
187
+ contiguous_args = (
188
+ i if not isinstance(i, torch.Tensor) else i.contiguous() for i in args
189
+ )
190
+ contiguous_kwargs = {
191
+ k: (v if not isinstance(v, torch.Tensor) else v.contiguous())
192
+ for k, v in kwargs.items()
193
+ }
194
+
195
+ tensor = None
196
+ for arg in args:
197
+ if isinstance(arg, torch.Tensor):
198
+ tensor = arg
199
+ break
200
+ if tensor is None:
201
+ for value in kwargs.values():
202
+ if isinstance(value, torch.Tensor):
203
+ tensor = value
204
+ break
205
+
206
+ if tensor is not None:
207
+ ctx = custom_device_ctx(tensor.device.index)
208
+ else:
209
+ ctx = contextlib.nullcontext()
210
+
211
+ with ctx:
212
+ return fn(*contiguous_args, **contiguous_kwargs)
213
+
214
+ return wrapper
215
+
216
+
217
+ contiguous = input_guard
218
+
219
+
220
+ def require_version(version, hint):
221
+ """
222
+ Perform a runtime check of the dependency versions, using the exact same syntax used by pip.
223
+ """
224
+
225
+ def decorator(fn):
226
+ @functools.wraps(fn)
227
+ def wrapper(ctx, *args, **kwargs):
228
+ from transformers.utils.versions import require_version
229
+
230
+ require_version(version, hint)
231
+ return fn(
232
+ ctx,
233
+ *(
234
+ i if not isinstance(i, torch.Tensor) else i.contiguous()
235
+ for i in args
236
+ ),
237
+ **{
238
+ k: (v if not isinstance(v, torch.Tensor) else v.contiguous())
239
+ for k, v in kwargs.items()
240
+ },
241
+ )
242
+
243
+ return wrapper
244
+
245
+ return decorator
246
+
247
+
248
+ class Action(Enum):
249
+ NONE = "none"
250
+ NOTIFY = "notify"
251
+ NOTIFY_ALWAYS = "notify_always"
252
+ RAISE = "raise"
253
+
254
+
255
+ def deprecate_kwarg(
256
+ old_name: str,
257
+ version: str,
258
+ new_name: str | None = None,
259
+ warn_if_greater_or_equal_version: bool = False,
260
+ raise_if_greater_or_equal_version: bool = False,
261
+ raise_if_both_names: bool = False,
262
+ additional_message: str | None = None,
263
+ ):
264
+ """
265
+ Decorator to notify users about deprecated keyword arguments, replacing them with a new name if specified.
266
+
267
+ This decorator allows you to:
268
+ - Notify users when a keyword argument is deprecated.
269
+ - Automatically replace deprecated keyword arguments with new ones.
270
+ - Raise an error if deprecated arguments are used, depending on the specified conditions.
271
+
272
+ By default, the decorator notifies the user about the deprecated argument while the `fla.__version__` < specified `version`
273
+ in the decorator. To keep notifications with any version `warn_if_greater_or_equal_version=True` can be set.
274
+
275
+ Args:
276
+ old_name (`str`):
277
+ Name of the deprecated keyword argument.
278
+ version (`str`):
279
+ The version in which the keyword argument was (or will be) deprecated.
280
+ new_name (`Optional[str]`, *optional*):
281
+ The new name for the deprecated keyword argument.
282
+ If specified, the deprecated keyword argument will be replaced with this new name.
283
+ warn_if_greater_or_equal_version (`bool`, *optional*, defaults to `False`):
284
+ Whether to show warning if current `fla` version is greater or equal to the deprecated version.
285
+ raise_if_greater_or_equal_version (`bool`, *optional*, defaults to `False`):
286
+ Whether to raise `ValueError` if current `fla` version is greater or equal to the deprecated version.
287
+ raise_if_both_names (`bool`, *optional*, defaults to `False`):
288
+ Whether to raise `ValueError` if both deprecated and new keyword arguments are set.
289
+ additional_message (`Optional[str]`, *optional*):
290
+ An additional message to append to the default deprecation message.
291
+
292
+ Raises:
293
+ ValueError:
294
+ If `raise_if_greater_or_equal_version` is `True` and the current version >= the deprecated one,
295
+ or if `raise_if_both_names` is `True` and both old and new keyword arguments are provided.
296
+
297
+ Returns:
298
+ Callable:
299
+ A wrapped function that handles the deprecated keyword arguments according to the specified parameters.
300
+
301
+ Example usage with renaming argument:
302
+
303
+ ```python
304
+ @deprecate_kwarg("reduce_labels", new_name="do_reduce_labels", version="6.0.0")
305
+ def my_function(do_reduce_labels):
306
+ print(do_reduce_labels)
307
+
308
+ my_function(reduce_labels=True) # Will show a deprecation warning and use do_reduce_labels=True
309
+ ```
310
+
311
+ Example usage without renaming argument:
312
+
313
+ ```python
314
+ @deprecate_kwarg("max_size", version="6.0.0")
315
+ def my_function(max_size):
316
+ print(max_size)
317
+
318
+ my_function(max_size=1333) # Will show a deprecation warning
319
+ ```
320
+
321
+ """
322
+ deprecated_version = version.parse(version)
323
+ current_version = version.parse(__version__)
324
+ is_greater_or_equal_version = current_version >= deprecated_version
325
+
326
+ if is_greater_or_equal_version:
327
+ version_message = f"and removed starting from version {version}"
328
+ else:
329
+ version_message = f"and will be removed in version {version}"
330
+
331
+ def wrapper(func):
332
+ # Required for better warning message
333
+ sig = inspect.signature(func)
334
+ function_named_args = set(sig.parameters.keys())
335
+ is_instance_method = "self" in function_named_args
336
+ is_class_method = "cls" in function_named_args
337
+
338
+ @functools.wraps(func)
339
+ def wrapped_func(*args, **kwargs):
340
+ # Get class + function name (just for better warning message)
341
+ func_name = func.__name__
342
+ if is_instance_method:
343
+ func_name = f"{args[0].__class__.__name__}.{func_name}"
344
+ elif is_class_method:
345
+ func_name = f"{args[0].__name__}.{func_name}"
346
+
347
+ minimum_action = Action.NONE
348
+ message = None
349
+
350
+ # deprecated kwarg and its new version are set for function call -> replace it with new name
351
+ if old_name in kwargs and new_name in kwargs:
352
+ minimum_action = (
353
+ Action.RAISE if raise_if_both_names else Action.NOTIFY_ALWAYS
354
+ )
355
+ message = (
356
+ f"Both `{old_name}` and `{new_name}` are set for `{func_name}`. "
357
+ f"Using `{new_name}={kwargs[new_name]}` and ignoring deprecated `{old_name}={kwargs[old_name]}`."
358
+ )
359
+ kwargs.pop(old_name)
360
+
361
+ # only deprecated kwarg is set for function call -> replace it with new name
362
+ elif old_name in kwargs and new_name is not None and new_name not in kwargs:
363
+ minimum_action = Action.NOTIFY
364
+ message = (
365
+ f"`{old_name}` is deprecated {version_message} for `{func_name}`. "
366
+ f"Use `{new_name}` instead."
367
+ )
368
+ kwargs[new_name] = kwargs.pop(old_name)
369
+
370
+ # deprecated kwarg is not set for function call and new name is not specified -> just notify
371
+ elif old_name in kwargs:
372
+ minimum_action = Action.NOTIFY
373
+ message = (
374
+ f"`{old_name}` is deprecated {version_message} for `{func_name}`."
375
+ )
376
+
377
+ if message is not None and additional_message is not None:
378
+ message = f"{message} {additional_message}"
379
+
380
+ # update minimum_action if argument is ALREADY deprecated (current version >= deprecated version)
381
+ if is_greater_or_equal_version:
382
+ # change to (NOTIFY, NOTIFY_ALWAYS) -> RAISE if specified
383
+ # in case we want to raise error for already deprecated arguments
384
+ if raise_if_greater_or_equal_version and minimum_action != Action.NONE:
385
+ minimum_action = Action.RAISE
386
+
387
+ # change to NOTIFY -> NONE if specified (NOTIFY_ALWAYS can't be changed to NONE)
388
+ # in case we want to ignore notifications for already deprecated arguments
389
+ elif (
390
+ not warn_if_greater_or_equal_version
391
+ and minimum_action == Action.NOTIFY
392
+ ):
393
+ minimum_action = Action.NONE
394
+
395
+ # raise error or notify user
396
+ if minimum_action == Action.RAISE:
397
+ raise ValueError(message)
398
+ elif minimum_action in (Action.NOTIFY, Action.NOTIFY_ALWAYS):
399
+ # DeprecationWarning is ignored by default, so we use FutureWarning instead
400
+ warnings.warn(message, FutureWarning, stacklevel=2)
401
+
402
+ return func(*args, **kwargs)
403
+
404
+ return wrapped_func
405
+
406
+ return wrapper
407
+
408
+
409
+ def checkpoint(fn):
410
+ def wrapper(*args, **kwargs):
411
+ return torch.utils.checkpoint.checkpoint(fn, *args, **kwargs)
412
+
413
+ return wrapper
414
+
415
+
416
+ @functools.cache
417
+ def check_pytorch_version(version_s: str = "2.4") -> bool:
418
+ return version.parse(torch.__version__) >= version.parse(version_s)
419
+
420
+
421
+ def _cpu_device_warning():
422
+ warnings.warn(
423
+ ("Triton is not supported on current platform, roll back to CPU."), stacklevel=1
424
+ )
425
+
426
+
427
+ @functools.cache
428
+ def get_multiprocessor_count(tensor_idx: int = 0) -> int:
429
+ try:
430
+ return triton.runtime.driver.active.utils.get_device_properties(tensor_idx)[
431
+ "multiprocessor_count"
432
+ ]
433
+ except BaseException:
434
+ # Maybe we use a NPU device.
435
+ if triton.runtime.driver.active.get_current_target().backend == "npu":
436
+ return triton.runtime.driver.active.utils.get_device_properties(tensor_idx)[
437
+ "num_vectorcore"
438
+ ]
439
+ else:
440
+ return 1
441
+
442
+
443
+ @functools.cache
444
+ def get_available_device() -> str:
445
+ try:
446
+ return triton.runtime.driver.active.get_current_target().backend
447
+ except BaseException:
448
+ _cpu_device_warning()
449
+ return "cpu"
450
+
451
+
452
+ def map_triton_backend_to_torch_device() -> str:
453
+ backend = get_available_device() # 'cuda' | 'hip' | 'xpu' | 'cpu' | ...
454
+ return {"cuda": "cuda", "hip": "cuda", "xpu": "xpu"}.get(backend, backend)
455
+
456
+
457
+ # For AMD GPUs, the triton backend is 'hip', while for Nvidia GPUs, the triton backend is 'cuda'.
458
+ # However, the torch backend is 'cuda' for both Nvidia and AMD GPUs.
459
+ # Therefore, we need to check the triton backend to determine the actual GPU vendor.
460
+ device = get_available_device() if get_available_device() != "hip" else "cuda"
461
+ device_torch_lib = getattr(torch, device)
462
+ device_platform = get_available_device()
463
+ device_name = map_triton_backend_to_torch_device()
464
+
465
+ IS_AMD = device_platform == "hip"
466
+ IS_INTEL = device_platform == "xpu"
467
+ IS_NVIDIA = device_platform == "cuda"
468
+ IS_INTEL_ALCHEMIST = IS_INTEL and "Intel(R) Arc(TM) A" in torch.xpu.get_device_name(0)
469
+ IS_NVIDIA_HOPPER = IS_NVIDIA and (
470
+ "NVIDIA H" in torch.cuda.get_device_name(0)
471
+ or torch.cuda.get_device_capability()[0] >= 9
472
+ )
473
+ USE_CUDA_GRAPH = IS_NVIDIA and os.environ.get("FLA_USE_CUDA_GRAPH", "0") == "1"
474
+
475
+ # Nvidia Ampere or newer, haven't check AMD and intel yet.
476
+ IS_TF32_SUPPORTED = IS_NVIDIA and torch.cuda.get_device_capability(0)[0] >= 8
477
+ IS_GATHER_SUPPORTED = hasattr(triton.language, "gather")
478
+ IS_TMA_SUPPORTED = (
479
+ (IS_NVIDIA and torch.cuda.get_device_capability(0)[0] >= 9)
480
+ and os.environ.get("FLA_USE_TMA", "0") == "1"
481
+ and (
482
+ hasattr(triton.language, "_experimental_make_tensor_descriptor")
483
+ or hasattr(triton.language, "make_tensor_descriptor")
484
+ )
485
+ )
486
+
487
+ if IS_NVIDIA and not IS_TF32_SUPPORTED:
488
+ # Make old card happy, since triton will use tf32 by default.
489
+ # This is a workaround for old nvidia card.
490
+ os.environ["TRITON_F32_DEFAULT"] = "ieee"
491
+
492
+ if IS_TMA_SUPPORTED:
493
+ logger.info("TMA is supported, using TMA by default.")
494
+
495
+ def alloc_fn(size: int, alignment: int, stream: int | None):
496
+ return torch.empty(
497
+ size,
498
+ device=torch.device(device_name, device_torch_lib.current_device()),
499
+ dtype=torch.int8,
500
+ )
501
+
502
+ triton.set_allocator(alloc_fn)
503
+
504
+
505
+ def get_all_max_shared_mem():
506
+ try:
507
+ return [
508
+ triton.runtime.driver.active.utils.get_device_properties(i)[
509
+ "max_shared_mem"
510
+ ]
511
+ for i in range(device_torch_lib.device_count())
512
+ ]
513
+ except BaseException:
514
+ _cpu_device_warning()
515
+ return [-1]
516
+
517
+
518
+ class Backend(Enum):
519
+ ADA = 101376 # RTX 4090
520
+ AMPERE = 166912 # A100
521
+ HOPPER = 232448 # H100
522
+ DEFAULT = 102400 # Default
523
+
524
+ @classmethod
525
+ def get_shared_memory(cls, arch: str) -> int:
526
+ try:
527
+ return cls[arch.upper()].value
528
+ except KeyError:
529
+ return cls.DEFAULT.value
530
+
531
+
532
+ @functools.cache
533
+ def check_shared_mem(arch: str = "none", tensor_idx: int = 0) -> bool:
534
+ try:
535
+ device_shared_mem_list = get_all_max_shared_mem()
536
+ max_shared_memory = device_shared_mem_list[tensor_idx]
537
+ return max_shared_memory >= Backend.get_shared_memory(arch)
538
+ except Exception:
539
+ return False
540
+
541
+
542
+ if check_pytorch_version("2.4"):
543
+ device = "cuda" if device == "cpu" else device
544
+ autocast_custom_fwd = functools.partial(torch.amp.custom_fwd, device_type=device)
545
+ autocast_custom_bwd = functools.partial(torch.amp.custom_bwd, device_type=device)
546
+
547
+ def custom_device_ctx(index: int):
548
+ return device_torch_lib.device(index)
549
+
550
+ else:
551
+ assert (
552
+ device == "cuda"
553
+ ), "Only cuda device is supported for PyTorch version < 2.4.0."
554
+ autocast_custom_fwd = device_torch_lib.amp.custom_fwd
555
+ autocast_custom_bwd = device_torch_lib.amp.custom_bwd
556
+
557
+ def custom_device_ctx(index: int):
558
+ return torch.cuda.device(index)
559
+
560
+
561
+ def _register_aliases():
562
+ current_module = sys.modules[__name__]
563
+ for key in (
564
+ "IS_AMD",
565
+ "IS_INTEL",
566
+ "IS_NVIDIA",
567
+ "IS_INTEL_ALCHEMIST",
568
+ "IS_NVIDIA_HOPPER",
569
+ "USE_CUDA_GRAPH",
570
+ "IS_TF32_SUPPORTED",
571
+ "IS_GATHER_SUPPORTED",
572
+ "IS_TMA_SUPPORTED",
573
+ ):
574
+ if hasattr(current_module, key):
575
+ setattr(current_module, key.lower(), getattr(current_module, key))
576
+
577
+
578
+ _register_aliases()
579
+
580
+ del _register_aliases
build/torch-rocm/_triton_kernels/gated_delta_rule/prefill/__init__.py ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (C) 2024-2026, Advanced Micro Devices, Inc. All rights reserved.
3
+ # Adapted from flash-linear-attention: Copyright (c) 2023-2025, Songlin Yang, Yu Zhang
4
+
5
+ """
6
+ Gated Delta Rule Prefill Operations (Forward Only).
7
+
8
+ This module provides optimized Triton kernels for prefill/training operations.
9
+ """
10
+
11
+ from .chunk import (
12
+ chunk_gated_delta_rule_fwd,
13
+ chunk_gated_delta_rule_fwd_opt,
14
+ chunk_gated_delta_rule_fwd_opt_vk,
15
+ )
16
+ from .chunk_delta_h import (
17
+ chunk_gated_delta_rule_fwd_h,
18
+ chunk_gated_delta_rule_fwd_h_opt,
19
+ chunk_gated_delta_rule_fwd_h_opt_vk,
20
+ )
21
+ from .chunk_o import chunk_fwd_o, chunk_fwd_o_opt, chunk_fwd_o_opt_vk
22
+ from .fused_cumsum_kkt import (
23
+ fused_cumsum_kkt,
24
+ fused_chunk_local_cumsum_scaled_dot_kkt_fwd,
25
+ )
26
+ from .fused_solve_tril_recompute import fused_solve_tril_recompute_w_u
27
+ from .fused_gdn_gating_prefill import fused_gdn_gating_and_sigmoid
28
+
29
+ __all__ = [
30
+ "chunk_gated_delta_rule_fwd",
31
+ "chunk_gated_delta_rule_fwd_opt",
32
+ "chunk_gated_delta_rule_fwd_opt_vk",
33
+ "chunk_gated_delta_rule_fwd_h",
34
+ "chunk_gated_delta_rule_fwd_h_opt",
35
+ "chunk_gated_delta_rule_fwd_h_opt_vk",
36
+ "chunk_fwd_o",
37
+ "chunk_fwd_o_opt",
38
+ "chunk_fwd_o_opt_vk",
39
+ "fused_cumsum_kkt",
40
+ "fused_chunk_local_cumsum_scaled_dot_kkt_fwd",
41
+ "fused_solve_tril_recompute_w_u",
42
+ "fused_gdn_gating_and_sigmoid",
43
+ ]
build/torch-rocm/_triton_kernels/gated_delta_rule/prefill/causal_conv1d_fwd_split_qkv.py ADDED
@@ -0,0 +1,399 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Causal conv1d with fused split q/k/v output for prefill."""
2
+
3
+ from typing import List, Optional, Tuple
4
+
5
+ import torch
6
+ import triton
7
+ import triton.language as tl
8
+
9
+ PAD_SLOT_ID = -1
10
+
11
+
12
+ @triton.jit()
13
+ def _causal_conv1d_fwd_split_kernel(
14
+ x_ptr,
15
+ w_ptr,
16
+ bias_ptr,
17
+ initial_states_ptr,
18
+ cache_indices_ptr,
19
+ has_initial_states_ptr,
20
+ query_start_loc_ptr,
21
+ q_ptr,
22
+ k_ptr,
23
+ v_ptr,
24
+ key_dim: tl.constexpr,
25
+ value_dim: tl.constexpr,
26
+ dim: tl.constexpr,
27
+ seqlen: tl.int32,
28
+ num_cache_lines: tl.constexpr,
29
+ stride_x_dim: tl.constexpr,
30
+ stride_x_token: tl.constexpr,
31
+ stride_w_dim: tl.constexpr,
32
+ stride_w_width: tl.constexpr,
33
+ stride_istate_seq: tl.constexpr,
34
+ stride_istate_dim: tl.constexpr,
35
+ stride_istate_token: tl.constexpr,
36
+ stride_q_token: tl.constexpr,
37
+ stride_q_dim: tl.constexpr,
38
+ stride_k_token: tl.constexpr,
39
+ stride_k_dim: tl.constexpr,
40
+ stride_v_token: tl.constexpr,
41
+ stride_v_dim: tl.constexpr,
42
+ pad_slot_id: tl.constexpr,
43
+ HAS_BIAS: tl.constexpr,
44
+ KERNEL_WIDTH: tl.constexpr,
45
+ SILU_ACTIVATION: tl.constexpr,
46
+ HAS_INITIAL_STATES: tl.constexpr,
47
+ HAS_CACHE: tl.constexpr,
48
+ IS_CONTINUOUS_BATCHING: tl.constexpr,
49
+ USE_PAD_SLOT: tl.constexpr,
50
+ NP2_STATELEN: tl.constexpr,
51
+ BLOCK_M: tl.constexpr,
52
+ BLOCK_N: tl.constexpr,
53
+ ):
54
+ """Fused causal conv1d + split q/k/v output for prefill."""
55
+ conv_states_ptr = initial_states_ptr
56
+ conv_state_indices_ptr = cache_indices_ptr
57
+ stride_conv_state_seq = stride_istate_seq
58
+ stride_conv_state_dim = stride_istate_dim
59
+ stride_conv_state_tok = stride_istate_token
60
+ state_len = KERNEL_WIDTH - 1
61
+
62
+ idx_seq = tl.program_id(0)
63
+ chunk_offset = tl.program_id(1)
64
+ idx_feats = tl.program_id(2) * BLOCK_N + tl.arange(0, BLOCK_N)
65
+
66
+ if idx_seq == pad_slot_id:
67
+ return
68
+
69
+ sequence_start_index = tl.load(query_start_loc_ptr + idx_seq)
70
+ sequence_end_index = tl.load(query_start_loc_ptr + idx_seq + 1)
71
+ seqlen = sequence_end_index - sequence_start_index
72
+
73
+ token_offset = BLOCK_M * chunk_offset
74
+ segment_len = min(BLOCK_M, seqlen - token_offset)
75
+
76
+ if segment_len <= 0:
77
+ return
78
+
79
+ x_base = x_ptr + sequence_start_index * stride_x_token + idx_feats * stride_x_dim
80
+
81
+ if IS_CONTINUOUS_BATCHING:
82
+ conv_state_batch_coord = tl.load(conv_state_indices_ptr + idx_seq).to(tl.int64)
83
+ else:
84
+ conv_state_batch_coord = idx_seq
85
+
86
+ if USE_PAD_SLOT:
87
+ if conv_state_batch_coord == pad_slot_id:
88
+ return
89
+
90
+ conv_states_base = (
91
+ conv_states_ptr
92
+ + (conv_state_batch_coord * stride_conv_state_seq)
93
+ + (idx_feats * stride_conv_state_dim)
94
+ )
95
+
96
+ w_base = w_ptr + (idx_feats * stride_w_dim)
97
+
98
+ if chunk_offset == 0:
99
+ load_init_state = False
100
+ if HAS_INITIAL_STATES:
101
+ load_init_state = tl.load(has_initial_states_ptr + idx_seq).to(tl.int1)
102
+ if load_init_state:
103
+ prior_tokens = conv_states_base + (state_len - 1) * stride_conv_state_tok
104
+ mask_w = idx_feats < dim
105
+ if KERNEL_WIDTH == 2:
106
+ col0 = tl.load(prior_tokens, mask_w, 0.0)
107
+ if KERNEL_WIDTH == 3:
108
+ col1 = tl.load(prior_tokens, mask_w, 0.0)
109
+ col0 = tl.load(prior_tokens - 1 * stride_conv_state_tok, mask_w, 0.0)
110
+ if KERNEL_WIDTH == 4:
111
+ col2 = tl.load(prior_tokens, mask_w, 0.0)
112
+ col1 = tl.load(prior_tokens - 1 * stride_conv_state_tok, mask_w, 0.0)
113
+ col0 = tl.load(prior_tokens - 2 * stride_conv_state_tok, mask_w, 0.0)
114
+ else:
115
+ if KERNEL_WIDTH >= 2:
116
+ col0 = tl.zeros((BLOCK_N,), dtype=x_ptr.dtype.element_ty)
117
+ if KERNEL_WIDTH >= 3:
118
+ col1 = tl.zeros((BLOCK_N,), dtype=x_ptr.dtype.element_ty)
119
+ if KERNEL_WIDTH >= 4:
120
+ col2 = tl.zeros((BLOCK_N,), dtype=x_ptr.dtype.element_ty)
121
+
122
+ if state_len <= seqlen:
123
+ idx_tokens_last = (seqlen - state_len) + tl.arange(0, NP2_STATELEN)
124
+ x_ptrs = (
125
+ x_ptr
126
+ + ((sequence_start_index + idx_tokens_last) * stride_x_token)[:, None]
127
+ + (idx_feats * stride_x_dim)[None, :]
128
+ )
129
+ mask_x = (
130
+ (idx_tokens_last >= 0)[:, None]
131
+ & (idx_tokens_last < seqlen)[:, None]
132
+ & (idx_feats < dim)[None, :]
133
+ )
134
+ new_conv_state = tl.load(x_ptrs, mask_x, 0.0)
135
+ idx_tokens_conv = tl.arange(0, NP2_STATELEN)
136
+ conv_states_ptrs_target = (
137
+ conv_states_base[None, :]
138
+ + (idx_tokens_conv * stride_conv_state_tok)[:, None]
139
+ )
140
+ mask = (idx_tokens_conv < state_len)[:, None] & (idx_feats < dim)[None, :]
141
+ tl.debug_barrier()
142
+ tl.store(conv_states_ptrs_target, new_conv_state, mask)
143
+ else:
144
+ if load_init_state:
145
+ idx_tokens_conv = tl.arange(0, NP2_STATELEN)
146
+ conv_states_ptrs_source = (
147
+ conv_states_ptr
148
+ + (conv_state_batch_coord * stride_conv_state_seq)
149
+ + (idx_feats * stride_conv_state_dim)[None, :]
150
+ + ((idx_tokens_conv + seqlen) * stride_conv_state_tok)[:, None]
151
+ )
152
+ mask = (
153
+ (conv_state_batch_coord < num_cache_lines)
154
+ & ((idx_tokens_conv + seqlen) < state_len)[:, None]
155
+ & (idx_feats < dim)[None, :]
156
+ )
157
+ conv_state = tl.load(conv_states_ptrs_source, mask, other=0.0)
158
+ VAL = state_len - seqlen
159
+ x_ptrs = (
160
+ x_base[None, :]
161
+ + ((idx_tokens_conv - VAL) * stride_x_token)[:, None]
162
+ )
163
+ mask_x = (
164
+ (idx_tokens_conv - VAL >= 0)[:, None]
165
+ & (idx_tokens_conv - VAL < seqlen)[:, None]
166
+ & (idx_feats < dim)[None, :]
167
+ )
168
+ loaded_x = tl.load(x_ptrs, mask_x, 0.0)
169
+ tl.debug_barrier()
170
+ new_conv_state = tl.where(mask, conv_state, loaded_x)
171
+ conv_states_ptrs_target = (
172
+ conv_states_base
173
+ + (idx_tokens_conv * stride_conv_state_tok)[:, None]
174
+ )
175
+ mask = (idx_tokens_conv < state_len)[:, None] & (idx_feats < dim)[
176
+ None, :
177
+ ]
178
+ tl.store(conv_states_ptrs_target, new_conv_state, mask)
179
+ else:
180
+ idx_tokens_conv = tl.arange(0, NP2_STATELEN)
181
+ VAL = state_len - seqlen
182
+ x_ptrs = (
183
+ x_base[None, :]
184
+ + ((idx_tokens_conv - VAL) * stride_x_token)[:, None]
185
+ )
186
+ mask_x = (
187
+ (idx_tokens_conv - VAL >= 0)[:, None]
188
+ & (idx_tokens_conv - VAL < seqlen)[:, None]
189
+ & (idx_feats < dim)[None, :]
190
+ )
191
+ new_conv_state = tl.load(x_ptrs, mask_x, 0.0)
192
+ conv_states_ptrs_target = (
193
+ conv_states_base
194
+ + (idx_tokens_conv * stride_conv_state_tok)[:, None]
195
+ )
196
+ mask = (idx_tokens_conv < state_len)[:, None] & (idx_feats < dim)[
197
+ None, :
198
+ ]
199
+ tl.store(conv_states_ptrs_target, new_conv_state, mask)
200
+ else:
201
+ prior_tokens = x_base + (token_offset - 1) * stride_x_token
202
+ mask_w = idx_feats < dim
203
+ if KERNEL_WIDTH == 2:
204
+ col0 = tl.load(prior_tokens, mask_w, 0.0, cache_modifier=".ca")
205
+ if KERNEL_WIDTH == 3:
206
+ col1 = tl.load(prior_tokens, mask_w, 0.0, cache_modifier=".ca")
207
+ col0 = tl.load(
208
+ prior_tokens - 1 * stride_x_token, mask_w, 0.0, cache_modifier=".ca"
209
+ )
210
+ if KERNEL_WIDTH == 4:
211
+ col2 = tl.load(prior_tokens, mask_w, 0.0, cache_modifier=".ca")
212
+ col1 = tl.load(
213
+ prior_tokens - 1 * stride_x_token, mask_w, 0.0, cache_modifier=".ca"
214
+ )
215
+ col0 = tl.load(
216
+ prior_tokens - 2 * stride_x_token, mask_w, 0.0, cache_modifier=".ca"
217
+ )
218
+
219
+ if HAS_BIAS:
220
+ bias = bias_ptr + idx_feats
221
+ mask_bias = idx_feats < dim
222
+ acc_preload = tl.load(bias, mask=mask_bias, other=0.0).to(tl.float32)
223
+ else:
224
+ acc_preload = tl.zeros((BLOCK_N,), dtype=tl.float32)
225
+
226
+ x_base_1d = x_base + token_offset * stride_x_token
227
+
228
+ mask_w = idx_feats < dim
229
+ if KERNEL_WIDTH >= 2:
230
+ w_col0 = tl.load(w_base + 0 * stride_w_width, mask_w, other=0.0)
231
+ w_col1 = tl.load(w_base + 1 * stride_w_width, mask_w, other=0.0)
232
+ if KERNEL_WIDTH >= 3:
233
+ w_col2 = tl.load(w_base + 2 * stride_w_width, mask_w, other=0.0)
234
+ if KERNEL_WIDTH >= 4:
235
+ w_col3 = tl.load(w_base + 3 * stride_w_width, mask_w, other=0.0)
236
+
237
+ mask_x_1d = idx_feats < dim
238
+
239
+ for idx_token in range(segment_len):
240
+ acc = acc_preload
241
+ matrix_w = w_col0
242
+ matrix_x = col0
243
+
244
+ for j in tl.static_range(KERNEL_WIDTH):
245
+ if KERNEL_WIDTH == 2:
246
+ if j == 1:
247
+ matrix_w = w_col1
248
+ x_ptrs_1d = x_base_1d + idx_token * stride_x_token
249
+ matrix_x = tl.load(x_ptrs_1d, mask=mask_x_1d)
250
+ elif KERNEL_WIDTH == 3:
251
+ if j == 1:
252
+ matrix_w = w_col1
253
+ matrix_x = col1
254
+ elif j == 2:
255
+ matrix_w = w_col2
256
+ x_ptrs_1d = x_base_1d + idx_token * stride_x_token
257
+ matrix_x = tl.load(x_ptrs_1d, mask=mask_x_1d)
258
+ elif KERNEL_WIDTH == 4:
259
+ if j == 1:
260
+ matrix_w = w_col1
261
+ matrix_x = col1
262
+ elif j == 2:
263
+ matrix_w = w_col2
264
+ matrix_x = col2
265
+ elif j == 3:
266
+ matrix_w = w_col3
267
+ x_ptrs_1d = x_base_1d + idx_token * stride_x_token
268
+ matrix_x = tl.load(x_ptrs_1d, mask=mask_x_1d)
269
+
270
+ acc += matrix_x * matrix_w
271
+
272
+ if KERNEL_WIDTH == 2:
273
+ col0 = matrix_x
274
+ elif KERNEL_WIDTH == 3:
275
+ col0 = col1
276
+ col1 = matrix_x
277
+ elif KERNEL_WIDTH == 4:
278
+ col0 = col1
279
+ col1 = col2
280
+ col2 = matrix_x
281
+
282
+ if SILU_ACTIVATION:
283
+ acc = acc / (1 + tl.exp(-acc))
284
+
285
+ global_token_idx = sequence_start_index + token_offset + idx_token
286
+ mask_feat = (idx_token < segment_len) & (idx_feats < dim)
287
+
288
+ is_query = idx_feats < key_dim
289
+ q_ptrs = q_ptr + global_token_idx * stride_q_token + idx_feats * stride_q_dim
290
+ tl.store(q_ptrs, acc, mask=mask_feat & is_query)
291
+
292
+ is_key = (idx_feats >= key_dim) & (idx_feats < 2 * key_dim)
293
+ k_ptrs = (
294
+ k_ptr
295
+ + global_token_idx * stride_k_token
296
+ + (idx_feats - key_dim) * stride_k_dim
297
+ )
298
+ tl.store(k_ptrs, acc, mask=mask_feat & is_key)
299
+
300
+ is_value = (idx_feats >= 2 * key_dim) & (idx_feats < 2 * key_dim + value_dim)
301
+ v_ptrs = (
302
+ v_ptr
303
+ + global_token_idx * stride_v_token
304
+ + (idx_feats - 2 * key_dim) * stride_v_dim
305
+ )
306
+ tl.store(v_ptrs, acc, mask=mask_feat & is_value)
307
+
308
+
309
+ def causal_conv1d_fn_split_qkv(
310
+ x: torch.Tensor,
311
+ weight: torch.Tensor,
312
+ bias: Optional[torch.Tensor],
313
+ conv_states: torch.Tensor,
314
+ query_start_loc: torch.Tensor,
315
+ seq_lens_cpu: List[int],
316
+ k_dim: int,
317
+ v_dim: int,
318
+ cache_indices: Optional[torch.Tensor] = None,
319
+ has_initial_state: Optional[torch.Tensor] = None,
320
+ activation: str = "silu",
321
+ pad_slot_id: int = PAD_SLOT_ID,
322
+ ) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
323
+ """Causal conv1d with fused split output for prefill. Returns q, k, v as contiguous."""
324
+ dim, cu_seqlen = x.shape
325
+ _, width = weight.shape
326
+ state_len = width - 1
327
+ np2_statelen = triton.next_power_of_2(state_len)
328
+
329
+ q_out = torch.empty(cu_seqlen, k_dim, device=x.device, dtype=x.dtype)
330
+ k_out = torch.empty(cu_seqlen, k_dim, device=x.device, dtype=x.dtype)
331
+ v_out = torch.empty(cu_seqlen, v_dim, device=x.device, dtype=x.dtype)
332
+
333
+ stride_x_dim = x.stride(0)
334
+ stride_x_token = x.stride(1)
335
+ stride_w_dim = weight.stride(0)
336
+ stride_w_width = weight.stride(1)
337
+
338
+ num_cache_lines = 0
339
+ stride_istate_seq = 0
340
+ stride_istate_dim = 0
341
+ stride_istate_token = 0
342
+ if conv_states is not None:
343
+ num_cache_lines = conv_states.size(0)
344
+ stride_istate_seq = conv_states.stride(0)
345
+ stride_istate_dim = conv_states.stride(1)
346
+ stride_istate_token = conv_states.stride(2)
347
+
348
+ def grid(META):
349
+ max_seq_len = max(seq_lens_cpu)
350
+ return (
351
+ len(seq_lens_cpu),
352
+ (max_seq_len + META["BLOCK_M"] - 1) // META["BLOCK_M"],
353
+ triton.cdiv(dim, META["BLOCK_N"]),
354
+ )
355
+
356
+ _causal_conv1d_fwd_split_kernel[grid](
357
+ x,
358
+ weight,
359
+ bias,
360
+ conv_states,
361
+ cache_indices,
362
+ has_initial_state,
363
+ query_start_loc,
364
+ q_out,
365
+ k_out,
366
+ v_out,
367
+ k_dim,
368
+ v_dim,
369
+ dim,
370
+ cu_seqlen,
371
+ num_cache_lines,
372
+ stride_x_dim,
373
+ stride_x_token,
374
+ stride_w_dim,
375
+ stride_w_width,
376
+ stride_istate_seq,
377
+ stride_istate_dim,
378
+ stride_istate_token,
379
+ q_out.stride(0),
380
+ q_out.stride(1),
381
+ k_out.stride(0),
382
+ k_out.stride(1),
383
+ v_out.stride(0),
384
+ v_out.stride(1),
385
+ pad_slot_id,
386
+ HAS_BIAS=bias is not None,
387
+ KERNEL_WIDTH=width,
388
+ SILU_ACTIVATION=activation in ["silu", "swish"],
389
+ HAS_INITIAL_STATES=has_initial_state is not None,
390
+ HAS_CACHE=conv_states is not None,
391
+ IS_CONTINUOUS_BATCHING=cache_indices is not None,
392
+ USE_PAD_SLOT=pad_slot_id is not None,
393
+ NP2_STATELEN=np2_statelen,
394
+ BLOCK_M=8,
395
+ BLOCK_N=256,
396
+ num_stages=2,
397
+ )
398
+
399
+ return q_out, k_out, v_out
build/torch-rocm/_triton_kernels/gated_delta_rule/prefill/chunk.py ADDED
@@ -0,0 +1,374 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (C) 2024-2026, Advanced Micro Devices, Inc. All rights reserved.
3
+ # Adapted from flash-linear-attention: Copyright (c) 2023-2025, Songlin Yang, Yu Zhang
4
+
5
+ """
6
+ Chunk-based gated delta rule forward computation.
7
+
8
+ This module implements the chunk-based parallel computation for the gated delta rule.
9
+ Note: Only forward pass is implemented. Backward pass is not supported in aiter.
10
+ """
11
+
12
+ import torch
13
+
14
+ from .chunk_delta_h import (
15
+ chunk_gated_delta_rule_fwd_h,
16
+ chunk_gated_delta_rule_fwd_h_opt,
17
+ chunk_gated_delta_rule_fwd_h_opt_vk,
18
+ )
19
+ from .chunk_o import chunk_fwd_o, chunk_fwd_o_opt, chunk_fwd_o_opt_vk
20
+ from .fused_cumsum_kkt import fused_chunk_local_cumsum_scaled_dot_kkt_fwd
21
+ from .fused_solve_tril_recompute import fused_solve_tril_recompute_w_u
22
+ from ..utils import (
23
+ chunk_local_cumsum,
24
+ chunk_scaled_dot_kkt_fwd,
25
+ recompute_w_u_fwd,
26
+ solve_tril,
27
+ )
28
+
29
+
30
+ def _is_gfx12_runtime() -> bool:
31
+ try:
32
+ props = torch.cuda.get_device_properties(torch.cuda.current_device())
33
+ arch = getattr(props, "gcnArchName", "")
34
+ return arch.split(":")[0].startswith("gfx12") if arch else False
35
+ except Exception:
36
+ return False
37
+
38
+
39
+ def chunk_gated_delta_rule_fwd(
40
+ q: torch.Tensor,
41
+ k: torch.Tensor,
42
+ v: torch.Tensor,
43
+ g: torch.Tensor,
44
+ beta: torch.Tensor,
45
+ scale: float,
46
+ initial_state: torch.Tensor,
47
+ output_final_state: bool,
48
+ cu_seqlens: torch.LongTensor | None = None,
49
+ ):
50
+ """
51
+ Chunk gated delta rule forward computation (Forward only).
52
+
53
+ This function implements chunk-based parallel computation for the gated delta rule,
54
+ combining all necessary steps for efficient sequence processing.
55
+
56
+ Note: This implementation only supports forward pass. Backward pass is not available.
57
+
58
+ Args:
59
+ q: Query tensor of shape [B, T, H, K]
60
+ k: Key tensor of shape [B, T, H, K]
61
+ v: Value tensor of shape [B, T, H, V]
62
+ g: Gate tensor (in log space) of shape [B, T, H]
63
+ beta: Beta parameter tensor of shape [B, T, H]
64
+ scale: Scaling factor for queries
65
+ initial_state: Initial hidden state of shape [N, H, K, V]
66
+ output_final_state: Whether to output the final state
67
+ cu_seqlens: Cumulative sequence lengths for variable-length inputs (optional) [N+1]
68
+
69
+ Returns:
70
+ tuple: (g, o, A, final_state) where:
71
+ - g: Cumulative gate values [B, T, H]
72
+ - o: Output tensor [B, T, H, V]
73
+ - A: WY representation matrix
74
+ - final_state: Final hidden state [N, H, K, V] if output_final_state=True, else None
75
+ """
76
+ # Step 1: Compute local cumulative sum of gates
77
+ g = chunk_local_cumsum(g, chunk_size=64, cu_seqlens=cu_seqlens)
78
+
79
+ # Step 2: Compute WY representation
80
+ A = chunk_scaled_dot_kkt_fwd(
81
+ k=k,
82
+ g=g,
83
+ beta=beta,
84
+ cu_seqlens=cu_seqlens,
85
+ output_dtype=torch.float32,
86
+ )
87
+ A = solve_tril(
88
+ A=A,
89
+ cu_seqlens=cu_seqlens,
90
+ output_dtype=k.dtype,
91
+ )
92
+ w, u = recompute_w_u_fwd(
93
+ k=k,
94
+ v=v,
95
+ beta=beta,
96
+ A=A,
97
+ g=g,
98
+ cu_seqlens=cu_seqlens,
99
+ )
100
+
101
+ # Step 3: Compute hidden states
102
+ h, v_new, final_state = chunk_gated_delta_rule_fwd_h(
103
+ k=k,
104
+ w=w,
105
+ u=u,
106
+ g=g,
107
+ initial_state=initial_state,
108
+ output_final_state=output_final_state,
109
+ cu_seqlens=cu_seqlens,
110
+ )
111
+
112
+ # Step 4: Compute output
113
+ o = chunk_fwd_o(
114
+ q=q,
115
+ k=k,
116
+ v=v_new,
117
+ h=h,
118
+ g=g,
119
+ scale=scale,
120
+ cu_seqlens=cu_seqlens,
121
+ )
122
+
123
+ return g, o, A, final_state
124
+
125
+
126
+ def chunk_gated_delta_rule_fwd_opt(
127
+ q: torch.Tensor,
128
+ k: torch.Tensor,
129
+ v: torch.Tensor,
130
+ g: torch.Tensor,
131
+ beta: torch.Tensor,
132
+ scale: float,
133
+ initial_state: torch.Tensor | None,
134
+ output_final_state: bool,
135
+ cu_seqlens: torch.LongTensor | None = None,
136
+ ):
137
+ """
138
+ Optimized chunk gated delta rule forward computation (Forward only).
139
+
140
+ This function implements an optimized chunk-based parallel computation for
141
+ the gated delta rule, using fused kernels and transposed intermediate layouts
142
+ to reduce global memory round-trips.
143
+
144
+ Note: This implementation only supports forward pass. Backward pass is not available.
145
+
146
+ Args:
147
+ q: Query tensor of shape [B, T, Hg, K]
148
+ k: Key tensor of shape [B, T, Hg, K]
149
+ v: Value tensor of shape [B, T, H, V]
150
+ g: Gate tensor (in log space, pre-cumsum) of shape [B, T, H]
151
+ beta: Beta parameter tensor of shape [B, T, H]
152
+ scale: Scaling factor for queries
153
+ initial_state: Optional initial hidden state of shape [N, H, K, V]
154
+ output_final_state: Whether to output the final state
155
+ cu_seqlens: Cumulative sequence lengths for variable-length inputs (optional) [N+1]
156
+
157
+ Returns:
158
+ tuple: (g_cumsum, o, final_state) where:
159
+ - g_cumsum: Cumulative gate values [B, H, T]
160
+ - o: Output tensor [B, T, H, V]
161
+ - final_state: Final hidden state [N, H, K, V] if output_final_state=True, else None
162
+ """
163
+ # Step 1: Compute fused local cumulative sum of gates and KKT
164
+ g_cumsum, A_raw = fused_chunk_local_cumsum_scaled_dot_kkt_fwd(
165
+ k=k,
166
+ beta=beta,
167
+ g=g,
168
+ cu_seqlens=cu_seqlens,
169
+ use_exp2=False,
170
+ )
171
+
172
+ # Step 2: Compute fused triangular solve and recompute w, u
173
+ # w, u are already in [B, H, T, K/V] head-major contiguous layout
174
+ w, u = fused_solve_tril_recompute_w_u(
175
+ A_raw=A_raw,
176
+ k=k,
177
+ v=v,
178
+ beta=beta,
179
+ g_cumsum=g_cumsum,
180
+ cu_seqlens=cu_seqlens,
181
+ use_exp2=False,
182
+ )
183
+
184
+ # k5_opt / k6_opt index g with token-major [B, T, H] strides, but the fused
185
+ # k12 returns g_cumsum head-major [B, H, T]. Convert to token-major here.
186
+ g_cumsum_tok = g_cumsum.transpose(1, 2).contiguous()
187
+
188
+ # Step 3: Compute hidden states
189
+ h, v_new, final_state = chunk_gated_delta_rule_fwd_h_opt(
190
+ k=k,
191
+ w=w,
192
+ u=u,
193
+ g=g_cumsum_tok,
194
+ initial_state=initial_state,
195
+ output_final_state=output_final_state,
196
+ cu_seqlens=cu_seqlens,
197
+ )
198
+
199
+ # Step 4: Compute output
200
+ o = chunk_fwd_o_opt(
201
+ q=q,
202
+ k=k,
203
+ v=v_new,
204
+ h=h,
205
+ g=g_cumsum_tok,
206
+ scale=scale,
207
+ cu_seqlens=cu_seqlens,
208
+ )
209
+
210
+ return g_cumsum, o, final_state
211
+
212
+
213
+ def chunk_gated_delta_rule_fwd_opt_vk(
214
+ q: torch.Tensor,
215
+ k: torch.Tensor,
216
+ v: torch.Tensor,
217
+ g: torch.Tensor,
218
+ beta: torch.Tensor,
219
+ scale: float,
220
+ initial_state: torch.Tensor | None,
221
+ output_final_state: bool,
222
+ cu_seqlens: torch.LongTensor | None = None,
223
+ use_chunk_hip: bool = False,
224
+ use_chunk_flydsl: bool = False,
225
+ state_dtype: torch.dtype | None = None,
226
+ use_exp2: bool = True,
227
+ o: torch.Tensor | None = None,
228
+ num_decodes: int = 0,
229
+ num_decode_tokens: int = 0,
230
+ ):
231
+ """
232
+ Optimized chunk gated delta rule forward with h layout [V, K].
233
+
234
+ Uses the same fused kernels as opt, but with transposed
235
+ h layout [V, K] instead of [K, V].
236
+
237
+ When use_chunk_hip=True, hidden state computation uses a HIP kernel
238
+ instead of Triton. When use_chunk_flydsl=True, hidden state computation
239
+ uses the FlyDSL kernel. The two flags are mutually exclusive.
240
+
241
+ Args:
242
+ q: [B, T, Hg, K]
243
+ k: [B, T, Hg, K]
244
+ v: [B, T, H, V]
245
+ g: [B, T, H] — raw gate (pre-cumsum)
246
+ beta: [B, T, H]
247
+ scale: float
248
+ initial_state: optional [N, H, V, K] — note transposed h layout
249
+ output_final_state: bool
250
+ cu_seqlens: [N+1] optional
251
+ use_chunk_hip: bool — use HIP kernel for hidden state (K5)
252
+ use_chunk_flydsl: bool — use FlyDSL kernel for hidden state (K5)
253
+ state_dtype: optional initial/final state dtype (`fp32` or `bf16`),
254
+ supported by both the HIP and Triton hidden-state paths
255
+ use_exp2: bool — use exp2 instead of exp for gate computation
256
+ o: optional pre-allocated [B, T, H, V] output buffer (written in
257
+ place by K6). If None, a fresh buffer is allocated.
258
+ num_decodes / num_decode_tokens: skip a leading decode-only prefix in
259
+ the ORIGINAL cu_seqlens (data tensors are expected pre-sliced).
260
+ Threaded into every stage; the cached prologue helpers
261
+ (prepare_chunk_indices / prepare_chunk_offsets /
262
+ prepare_rebased_cu_seqlens) key on the original cu_seqlens identity,
263
+ so chunk-index / offset builds stay cache-warm across forwards
264
+ (no per-forward .tolist() D2H).
265
+
266
+ Returns:
267
+ tuple: (g_cumsum, o, final_state) where:
268
+ - g_cumsum: [B, H, T]
269
+ - o: [B, T, H, V]
270
+ - final_state: [N, H, V, K] if output_final_state=True, else None
271
+ """
272
+ if use_chunk_hip and use_chunk_flydsl:
273
+ raise ValueError(
274
+ "use_chunk_hip and use_chunk_flydsl are mutually exclusive; "
275
+ "set at most one."
276
+ )
277
+ if use_chunk_hip and (_is_gfx12_runtime() or num_decodes > 0):
278
+ use_chunk_hip = False
279
+
280
+ g_cumsum, A_raw = fused_chunk_local_cumsum_scaled_dot_kkt_fwd(
281
+ k=k,
282
+ beta=beta,
283
+ g=g,
284
+ cu_seqlens=cu_seqlens,
285
+ use_exp2=use_exp2,
286
+ num_decodes=num_decodes,
287
+ num_decode_tokens=num_decode_tokens,
288
+ )
289
+
290
+ w, u = fused_solve_tril_recompute_w_u(
291
+ A_raw=A_raw,
292
+ k=k,
293
+ v=v,
294
+ beta=beta,
295
+ g_cumsum=g_cumsum,
296
+ cu_seqlens=cu_seqlens,
297
+ use_exp2=use_exp2,
298
+ num_decodes=num_decodes,
299
+ num_decode_tokens=num_decode_tokens,
300
+ )
301
+
302
+ if use_chunk_hip:
303
+ from aiter.ops.chunk_gated_delta_rule_fwd_h import (
304
+ chunk_gated_delta_rule_fwd_h_hip_fn,
305
+ )
306
+
307
+ h, v_new, final_state = chunk_gated_delta_rule_fwd_h_hip_fn(
308
+ k=k,
309
+ w=w,
310
+ u=u,
311
+ g=g_cumsum,
312
+ initial_state=initial_state,
313
+ output_final_state=output_final_state,
314
+ cu_seqlens=cu_seqlens,
315
+ state_dtype=state_dtype,
316
+ use_exp2=use_exp2,
317
+ g_head_major=True,
318
+ )
319
+ elif use_chunk_flydsl:
320
+ # FlyDSL K5 wrapper expects ``g`` in head-major [B, H, T] layout
321
+ # (matches Triton VK / HIP). ``g_cumsum`` from K1+K2 is already
322
+ # head-major, so pass it through directly. The wrapper accepts
323
+ # ``use_exp2`` as a kwarg and pre-scales ``gk`` internally.
324
+ from aiter.ops.flydsl.linear_attention_prefill_kernels import (
325
+ chunk_gated_delta_rule_fwd_h_flydsl,
326
+ )
327
+
328
+ h, v_new, final_state = chunk_gated_delta_rule_fwd_h_flydsl(
329
+ k=k,
330
+ w=w,
331
+ u=u,
332
+ g=g_cumsum,
333
+ initial_state=initial_state,
334
+ output_final_state=output_final_state,
335
+ cu_seqlens=cu_seqlens,
336
+ state_dtype=state_dtype,
337
+ use_exp2=use_exp2,
338
+ num_decodes=num_decodes,
339
+ num_decode_tokens=num_decode_tokens,
340
+ )
341
+ else:
342
+ h, v_new, final_state = chunk_gated_delta_rule_fwd_h_opt_vk(
343
+ k=k,
344
+ w=w,
345
+ u=u,
346
+ g=g_cumsum,
347
+ initial_state=initial_state,
348
+ output_final_state=output_final_state,
349
+ cu_seqlens=cu_seqlens,
350
+ use_exp2=use_exp2,
351
+ state_dtype=state_dtype,
352
+ num_decodes=num_decodes,
353
+ num_decode_tokens=num_decode_tokens,
354
+ )
355
+
356
+ if o is None:
357
+ # Output matches v's [B, T, H, V] layout.
358
+ o = v.new_empty(v.shape)
359
+
360
+ o = chunk_fwd_o_opt_vk(
361
+ q=q,
362
+ k=k,
363
+ v=v_new,
364
+ o=o,
365
+ h=h,
366
+ g=g_cumsum,
367
+ scale=scale,
368
+ cu_seqlens=cu_seqlens,
369
+ use_exp2=use_exp2,
370
+ num_decodes=num_decodes,
371
+ num_decode_tokens=num_decode_tokens,
372
+ )
373
+
374
+ return g_cumsum, o, final_state
build/torch-rocm/_triton_kernels/gated_delta_rule/prefill/chunk_delta_h.py ADDED
@@ -0,0 +1,1455 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (C) 2024-2026, Advanced Micro Devices, Inc. All rights reserved.
3
+ # Adapted from flash-linear-attention: Copyright (c) 2023-2025, Songlin Yang, Yu Zhang
4
+
5
+ """
6
+ Chunk-based hidden state computation for gated delta rule (Forward only).
7
+
8
+ This module computes the per-chunk hidden states and the recomputed value
9
+ tensor (`v_new`) consumed by the chunked gated delta rule, supporting both
10
+ the `[K, V]` and the transposed `[V, K]` hidden-state layouts.
11
+ """
12
+
13
+ import torch
14
+ import triton
15
+ import triton.language as tl
16
+
17
+ from ..utils import (
18
+ prepare_chunk_indices,
19
+ prepare_chunk_offsets,
20
+ prepare_rebased_cu_seqlens,
21
+ )
22
+ from ..utils.op import exp
23
+ from ..gated_delta_rule_utils import (
24
+ RCP_LN2,
25
+ IS_AMD,
26
+ IS_NVIDIA_HOPPER,
27
+ USE_CUDA_GRAPH,
28
+ autotune_cache_kwargs,
29
+ check_shared_mem,
30
+ gated_delta_rule_autotune_configs,
31
+ )
32
+
33
+ NUM_WARPS = [2, 4] if IS_NVIDIA_HOPPER else [2, 4, 8, 16]
34
+ # Workaround: AMD ROCm Triton compiler fails with num_stages=4 in stream pipeline
35
+ NUM_STAGES_FWD = [2, 3] if IS_AMD else [2, 3, 4]
36
+
37
+
38
+ @triton.heuristics(
39
+ {
40
+ "USE_G": lambda args: args["g"] is not None,
41
+ "USE_GK": lambda args: args["gk"] is not None,
42
+ "USE_INITIAL_STATE": lambda args: args["h0"] is not None,
43
+ "STORE_FINAL_STATE": lambda args: args["ht"] is not None,
44
+ "SAVE_NEW_VALUE": lambda args: args["v_new"] is not None,
45
+ "IS_VARLEN": lambda args: args["cu_seqlens"] is not None,
46
+ }
47
+ )
48
+ @triton.autotune(
49
+ configs=gated_delta_rule_autotune_configs(
50
+ [
51
+ triton.Config({"BV": BV}, num_warps=num_warps, num_stages=num_stages)
52
+ for num_warps in [2, 4]
53
+ for num_stages in NUM_STAGES_FWD
54
+ for BV in [32, 64]
55
+ ]
56
+ ),
57
+ key=["H", "K", "V", "BT"],
58
+ use_cuda_graph=USE_CUDA_GRAPH,
59
+ **autotune_cache_kwargs,
60
+ )
61
+ @triton.jit(do_not_specialize=["T"])
62
+ def chunk_gated_delta_rule_fwd_kernel_h_blockdim64(
63
+ k,
64
+ v,
65
+ w,
66
+ v_new,
67
+ g,
68
+ gk,
69
+ h,
70
+ h0,
71
+ ht,
72
+ cu_seqlens,
73
+ chunk_offsets,
74
+ T,
75
+ H: tl.constexpr,
76
+ K: tl.constexpr,
77
+ V: tl.constexpr,
78
+ BT: tl.constexpr,
79
+ BV: tl.constexpr,
80
+ USE_G: tl.constexpr,
81
+ USE_GK: tl.constexpr,
82
+ USE_INITIAL_STATE: tl.constexpr,
83
+ STORE_FINAL_STATE: tl.constexpr,
84
+ SAVE_NEW_VALUE: tl.constexpr,
85
+ IS_VARLEN: tl.constexpr,
86
+ ):
87
+ i_v, i_nh = tl.program_id(0), tl.program_id(1)
88
+ i_n, i_h = i_nh // H, i_nh % H
89
+ if IS_VARLEN:
90
+ bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(
91
+ cu_seqlens + i_n + 1
92
+ ).to(tl.int32)
93
+ T = eos - bos
94
+ NT = tl.cdiv(T, BT)
95
+ boh = tl.load(chunk_offsets + i_n).to(tl.int32)
96
+ else:
97
+ bos, eos = i_n * T, i_n * T + T
98
+ NT = tl.cdiv(T, BT)
99
+ boh = i_n * NT
100
+
101
+ # [BK, BV]
102
+ b_h1 = tl.zeros([64, BV], dtype=tl.float32)
103
+ if K > 64:
104
+ b_h2 = tl.zeros([64, BV], dtype=tl.float32)
105
+ if K > 128:
106
+ b_h3 = tl.zeros([64, BV], dtype=tl.float32)
107
+ if K > 192:
108
+ b_h4 = tl.zeros([64, BV], dtype=tl.float32)
109
+
110
+ # calculate offset
111
+ h += ((boh * H + i_h) * K * V).to(tl.int64)
112
+ v += ((bos * H + i_h) * V).to(tl.int64)
113
+ k += ((bos * H + i_h) * K).to(tl.int64)
114
+ w += ((bos * H + i_h) * K).to(tl.int64)
115
+ if SAVE_NEW_VALUE:
116
+ v_new += ((bos * H + i_h) * V).to(tl.int64)
117
+ stride_v = H * V
118
+ stride_h = H * K * V
119
+ stride_k = H * K
120
+ if USE_INITIAL_STATE:
121
+ h0 = h0 + i_nh * K * V
122
+ if STORE_FINAL_STATE:
123
+ ht = ht + i_nh * K * V
124
+
125
+ # load initial state
126
+ if USE_INITIAL_STATE:
127
+ p_h0_1 = tl.make_block_ptr(h0, (K, V), (V, 1), (0, i_v * BV), (64, BV), (1, 0))
128
+ b_h1 += tl.load(p_h0_1, boundary_check=(0, 1)).to(tl.float32)
129
+ if K > 64:
130
+ p_h0_2 = tl.make_block_ptr(
131
+ h0, (K, V), (V, 1), (64, i_v * BV), (64, BV), (1, 0)
132
+ )
133
+ b_h2 += tl.load(p_h0_2, boundary_check=(0, 1)).to(tl.float32)
134
+ if K > 128:
135
+ p_h0_3 = tl.make_block_ptr(
136
+ h0, (K, V), (V, 1), (128, i_v * BV), (64, BV), (1, 0)
137
+ )
138
+ b_h3 += tl.load(p_h0_3, boundary_check=(0, 1)).to(tl.float32)
139
+ if K > 192:
140
+ p_h0_4 = tl.make_block_ptr(
141
+ h0, (K, V), (V, 1), (192, i_v * BV), (64, BV), (1, 0)
142
+ )
143
+ b_h4 += tl.load(p_h0_4, boundary_check=(0, 1)).to(tl.float32)
144
+
145
+ # main recurrence
146
+ for i_t in range(NT):
147
+ p_h1 = tl.make_block_ptr(
148
+ h + i_t * stride_h, (K, V), (V, 1), (0, i_v * BV), (64, BV), (1, 0)
149
+ )
150
+ tl.store(p_h1, b_h1.to(p_h1.dtype.element_ty), boundary_check=(0, 1))
151
+ if K > 64:
152
+ p_h2 = tl.make_block_ptr(
153
+ h + i_t * stride_h, (K, V), (V, 1), (64, i_v * BV), (64, BV), (1, 0)
154
+ )
155
+ tl.store(p_h2, b_h2.to(p_h2.dtype.element_ty), boundary_check=(0, 1))
156
+ if K > 128:
157
+ p_h3 = tl.make_block_ptr(
158
+ h + i_t * stride_h, (K, V), (V, 1), (128, i_v * BV), (64, BV), (1, 0)
159
+ )
160
+ tl.store(p_h3, b_h3.to(p_h3.dtype.element_ty), boundary_check=(0, 1))
161
+ if K > 192:
162
+ p_h4 = tl.make_block_ptr(
163
+ h + i_t * stride_h, (K, V), (V, 1), (192, i_v * BV), (64, BV), (1, 0)
164
+ )
165
+ tl.store(p_h4, b_h4.to(p_h4.dtype.element_ty), boundary_check=(0, 1))
166
+
167
+ p_w = tl.make_block_ptr(
168
+ w, (T, K), (stride_k, 1), (i_t * BT, 0), (BT, 64), (1, 0)
169
+ )
170
+ b_w = tl.load(p_w, boundary_check=(0, 1))
171
+ b_v = tl.dot(b_w, b_h1.to(b_w.dtype))
172
+ if K > 64:
173
+ p_w = tl.make_block_ptr(
174
+ w, (T, K), (stride_k, 1), (i_t * BT, 64), (BT, 64), (1, 0)
175
+ )
176
+ b_w = tl.load(p_w, boundary_check=(0, 1))
177
+ b_v = tl.dot(b_w, b_h2.to(b_w.dtype), acc=b_v)
178
+ if K > 128:
179
+ p_w = tl.make_block_ptr(
180
+ w, (T, K), (stride_k, 1), (i_t * BT, 128), (BT, 64), (1, 0)
181
+ )
182
+ b_w = tl.load(p_w, boundary_check=(0, 1))
183
+ b_v = tl.dot(b_w, b_h3.to(b_w.dtype), acc=b_v)
184
+ if K > 192:
185
+ p_w = tl.make_block_ptr(
186
+ w, (T, K), (stride_k, 1), (i_t * BT, 192), (BT, 64), (1, 0)
187
+ )
188
+ b_w = tl.load(p_w, boundary_check=(0, 1))
189
+ b_v = tl.dot(b_w, b_h4.to(b_w.dtype), acc=b_v)
190
+ p_v = tl.make_block_ptr(
191
+ v, (T, V), (stride_v, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)
192
+ )
193
+ b_v = tl.load(p_v, boundary_check=(0, 1)) - b_v
194
+
195
+ if SAVE_NEW_VALUE:
196
+ p_v = tl.make_block_ptr(
197
+ v_new, (T, V), (stride_v, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)
198
+ )
199
+ tl.store(p_v, b_v.to(p_v.dtype.element_ty), boundary_check=(0, 1))
200
+
201
+ last_idx = min((i_t + 1) * BT, T) - 1
202
+ if USE_G:
203
+ m_t = (i_t * BT + tl.arange(0, BT)) < T
204
+ b_g_last = tl.load(g + bos * H + last_idx * H + i_h)
205
+ p_g = tl.make_block_ptr(
206
+ g + bos * H + i_h, (T,), (H,), (i_t * BT,), (BT,), (0,)
207
+ )
208
+ b_g = tl.load(p_g, boundary_check=(0,))
209
+ b_v = b_v * tl.where(m_t, exp(b_g_last - b_g), 0)[:, None]
210
+ b_g_last = exp(b_g_last)
211
+ b_h1 *= b_g_last
212
+ if K > 64:
213
+ b_h2 *= b_g_last
214
+ if K > 128:
215
+ b_h3 *= b_g_last
216
+ if K > 192:
217
+ b_h4 *= b_g_last
218
+
219
+ if USE_GK:
220
+ o_k1 = tl.arange(0, 64)
221
+ b_gk_last1 = tl.load(
222
+ gk + (bos + last_idx) * H * K + i_h * K + o_k1,
223
+ mask=(o_k1 < K),
224
+ other=0.0,
225
+ )
226
+ b_h1 *= exp(b_gk_last1)[:, None]
227
+ if K > 64:
228
+ o_k2 = 64 + o_k1
229
+ b_gk_last2 = tl.load(
230
+ gk + (bos + last_idx) * H * K + i_h * K + o_k2,
231
+ mask=(o_k2 < K),
232
+ other=0.0,
233
+ )
234
+ b_h2 *= exp(b_gk_last2)[:, None]
235
+ if K > 128:
236
+ o_k3 = 128 + o_k1
237
+ b_gk_last3 = tl.load(
238
+ gk + (bos + last_idx) * H * K + i_h * K + o_k3,
239
+ mask=(o_k3 < K),
240
+ other=0.0,
241
+ )
242
+ b_h3 *= exp(b_gk_last3)[:, None]
243
+ if K > 192:
244
+ o_k4 = 192 + o_k1
245
+ b_gk_last4 = tl.load(
246
+ gk + (bos + last_idx) * H * K + i_h * K + o_k4,
247
+ mask=(o_k4 < K),
248
+ other=0.0,
249
+ )
250
+ b_h4 *= exp(b_gk_last4)[:, None]
251
+ b_v = b_v.to(k.dtype.element_ty)
252
+
253
+ p_k = tl.make_block_ptr(
254
+ k, (K, T), (1, stride_k), (0, i_t * BT), (64, BT), (0, 1)
255
+ )
256
+ b_k = tl.load(p_k, boundary_check=(0, 1))
257
+ b_h1 = tl.dot(b_k, b_v, acc=b_h1)
258
+ if K > 64:
259
+ p_k = tl.make_block_ptr(
260
+ k, (K, T), (1, stride_k), (64, i_t * BT), (64, BT), (0, 1)
261
+ )
262
+ b_k = tl.load(p_k, boundary_check=(0, 1))
263
+ b_h2 = tl.dot(b_k, b_v, acc=b_h2)
264
+ if K > 128:
265
+ p_k = tl.make_block_ptr(
266
+ k, (K, T), (1, stride_k), (128, i_t * BT), (64, BT), (0, 1)
267
+ )
268
+ b_k = tl.load(p_k, boundary_check=(0, 1))
269
+ b_h3 = tl.dot(b_k, b_v, acc=b_h3)
270
+ if K > 192:
271
+ p_k = tl.make_block_ptr(
272
+ k, (K, T), (1, stride_k), (192, i_t * BT), (64, BT), (0, 1)
273
+ )
274
+ b_k = tl.load(p_k, boundary_check=(0, 1))
275
+ b_h4 = tl.dot(b_k, b_v, acc=b_h4)
276
+ # epilogue
277
+ if STORE_FINAL_STATE:
278
+ p_ht = tl.make_block_ptr(ht, (K, V), (V, 1), (0, i_v * BV), (64, BV), (1, 0))
279
+ tl.store(p_ht, b_h1.to(p_ht.dtype.element_ty), boundary_check=(0, 1))
280
+ if K > 64:
281
+ p_ht = tl.make_block_ptr(
282
+ ht, (K, V), (V, 1), (64, i_v * BV), (64, BV), (1, 0)
283
+ )
284
+ tl.store(p_ht, b_h2.to(p_ht.dtype.element_ty), boundary_check=(0, 1))
285
+ if K > 128:
286
+ p_ht = tl.make_block_ptr(
287
+ ht, (K, V), (V, 1), (128, i_v * BV), (64, BV), (1, 0)
288
+ )
289
+ tl.store(p_ht, b_h3.to(p_ht.dtype.element_ty), boundary_check=(0, 1))
290
+ if K > 192:
291
+ p_ht = tl.make_block_ptr(
292
+ ht, (K, V), (V, 1), (192, i_v * BV), (64, BV), (1, 0)
293
+ )
294
+ tl.store(p_ht, b_h4.to(p_ht.dtype.element_ty), boundary_check=(0, 1))
295
+
296
+
297
+ @triton.heuristics(
298
+ {
299
+ "USE_G": lambda args: args["g"] is not None,
300
+ "USE_GK": lambda args: args["gk"] is not None,
301
+ "USE_INITIAL_STATE": lambda args: args["dh0"] is not None,
302
+ "USE_FINAL_STATE_GRADIENT": lambda args: args["dht"] is not None,
303
+ "IS_VARLEN": lambda args: args["cu_seqlens"] is not None,
304
+ }
305
+ )
306
+ @triton.autotune(
307
+ configs=gated_delta_rule_autotune_configs(
308
+ [
309
+ triton.Config({"BV": BV}, num_warps=num_warps, num_stages=num_stages)
310
+ for num_warps in [2, 4]
311
+ for num_stages in (
312
+ [3, 2] if IS_AMD else ([4, 3, 2] if check_shared_mem("ampere") else [1])
313
+ )
314
+ for BV in [64, 32]
315
+ ]
316
+ ),
317
+ key=["H", "K", "V", "BT", "BV", "USE_G"],
318
+ use_cuda_graph=USE_CUDA_GRAPH,
319
+ **autotune_cache_kwargs,
320
+ )
321
+ @triton.jit(do_not_specialize=["T"])
322
+ def chunk_gated_delta_rule_bwd_kernel_dhu_blockdim64(
323
+ q,
324
+ k,
325
+ w,
326
+ g,
327
+ gk,
328
+ dht,
329
+ dh0,
330
+ do,
331
+ dh,
332
+ dv,
333
+ dv2,
334
+ cu_seqlens,
335
+ chunk_offsets,
336
+ scale,
337
+ T,
338
+ H: tl.constexpr,
339
+ K: tl.constexpr,
340
+ V: tl.constexpr,
341
+ BT: tl.constexpr,
342
+ BV: tl.constexpr,
343
+ USE_G: tl.constexpr,
344
+ USE_GK: tl.constexpr,
345
+ USE_INITIAL_STATE: tl.constexpr,
346
+ USE_FINAL_STATE_GRADIENT: tl.constexpr,
347
+ IS_VARLEN: tl.constexpr,
348
+ ):
349
+ i_v, i_nh = tl.program_id(0), tl.program_id(1)
350
+ i_n, i_h = i_nh // H, i_nh % H
351
+ if IS_VARLEN:
352
+ bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(
353
+ cu_seqlens + i_n + 1
354
+ ).to(tl.int32)
355
+ T = eos - bos
356
+ NT = tl.cdiv(T, BT)
357
+ boh = tl.load(chunk_offsets + i_n).to(tl.int32)
358
+ else:
359
+ bos, eos = i_n * T, i_n * T + T
360
+ NT = tl.cdiv(T, BT)
361
+ boh = i_n * NT
362
+
363
+ # [BK, BV]
364
+ b_dh1 = tl.zeros([64, BV], dtype=tl.float32)
365
+ if K > 64:
366
+ b_dh2 = tl.zeros([64, BV], dtype=tl.float32)
367
+ if K > 128:
368
+ b_dh3 = tl.zeros([64, BV], dtype=tl.float32)
369
+ if K > 192:
370
+ b_dh4 = tl.zeros([64, BV], dtype=tl.float32)
371
+
372
+ # calculate offset
373
+ q += ((bos * H + i_h) * K).to(tl.int64)
374
+ k += ((bos * H + i_h) * K).to(tl.int64)
375
+ w += ((bos * H + i_h) * K).to(tl.int64)
376
+ do += ((bos * H + i_h) * V).to(tl.int64)
377
+ dv += ((bos * H + i_h) * V).to(tl.int64)
378
+ dv2 += ((bos * H + i_h) * V).to(tl.int64)
379
+ dh += ((boh * H + i_h) * K * V).to(tl.int64)
380
+ if USE_GK:
381
+ gk += ((bos * H + i_h) * K).to(tl.int64)
382
+
383
+ stride_v = H * V
384
+ stride_h = H * K * V
385
+ stride_k = H * K
386
+ if USE_INITIAL_STATE:
387
+ dh0 += i_nh * K * V
388
+ if USE_FINAL_STATE_GRADIENT:
389
+ dht += i_nh * K * V
390
+
391
+ if USE_FINAL_STATE_GRADIENT:
392
+ p_dht1 = tl.make_block_ptr(dht, (K, V), (V, 1), (0, i_v * BV), (64, BV), (1, 0))
393
+ b_dh1 += tl.load(p_dht1, boundary_check=(0, 1))
394
+ if K > 64:
395
+ p_dht2 = tl.make_block_ptr(
396
+ dht, (K, V), (V, 1), (64, i_v * BV), (64, BV), (1, 0)
397
+ )
398
+ b_dh2 += tl.load(p_dht2, boundary_check=(0, 1))
399
+ if K > 128:
400
+ p_dht3 = tl.make_block_ptr(
401
+ dht, (K, V), (V, 1), (128, i_v * BV), (64, BV), (1, 0)
402
+ )
403
+ b_dh3 += tl.load(p_dht3, boundary_check=(0, 1))
404
+ if K > 192:
405
+ p_dht4 = tl.make_block_ptr(
406
+ dht, (K, V), (V, 1), (192, i_v * BV), (64, BV), (1, 0)
407
+ )
408
+ b_dh4 += tl.load(p_dht4, boundary_check=(0, 1))
409
+
410
+ for i_t in range(NT - 1, -1, -1):
411
+ p_dh1 = tl.make_block_ptr(
412
+ dh + i_t * stride_h, (K, V), (V, 1), (0, i_v * BV), (64, BV), (1, 0)
413
+ )
414
+ tl.store(p_dh1, b_dh1.to(p_dh1.dtype.element_ty), boundary_check=(0, 1))
415
+ if K > 64:
416
+ p_dh2 = tl.make_block_ptr(
417
+ dh + i_t * stride_h, (K, V), (V, 1), (64, i_v * BV), (64, BV), (1, 0)
418
+ )
419
+ tl.store(p_dh2, b_dh2.to(p_dh2.dtype.element_ty), boundary_check=(0, 1))
420
+ if K > 128:
421
+ p_dh3 = tl.make_block_ptr(
422
+ dh + i_t * stride_h, (K, V), (V, 1), (128, i_v * BV), (64, BV), (1, 0)
423
+ )
424
+ tl.store(p_dh3, b_dh3.to(p_dh3.dtype.element_ty), boundary_check=(0, 1))
425
+ if K > 192:
426
+ p_dh4 = tl.make_block_ptr(
427
+ dh + i_t * stride_h, (K, V), (V, 1), (192, i_v * BV), (64, BV), (1, 0)
428
+ )
429
+ tl.store(p_dh4, b_dh4.to(p_dh4.dtype.element_ty), boundary_check=(0, 1))
430
+
431
+ last_idx = min((i_t + 1) * BT, T) - 1
432
+ if USE_G:
433
+ bg_last = tl.load(g + (bos + last_idx) * H + i_h)
434
+ bg_last_exp = exp(bg_last)
435
+ p_g = tl.make_block_ptr(
436
+ g + bos * H + i_h, (T,), (H,), (i_t * BT,), (BT,), (0,)
437
+ )
438
+ b_g = tl.load(p_g, boundary_check=(0,))
439
+ b_g_exp = exp(b_g)
440
+
441
+ p_dv = tl.make_block_ptr(
442
+ dv, (T, V), (stride_v, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)
443
+ )
444
+ p_dv2 = tl.make_block_ptr(
445
+ dv2, (T, V), (stride_v, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)
446
+ )
447
+ p_do = tl.make_block_ptr(
448
+ do, (T, V), (stride_v, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)
449
+ )
450
+
451
+ b_do = tl.load(p_do, boundary_check=(0, 1))
452
+
453
+ # Update dv
454
+ p_k = tl.make_block_ptr(
455
+ k, (T, K), (stride_k, 1), (i_t * BT, 0), (BT, 64), (1, 0)
456
+ )
457
+ b_k = tl.load(p_k, boundary_check=(0, 1))
458
+ if USE_GK:
459
+ o_k1 = tl.arange(0, 64)
460
+ b_gk_last1 = tl.load(
461
+ gk + last_idx * H * K + o_k1, mask=(o_k1 < K), other=0.0
462
+ )
463
+ b_dv = tl.dot(b_k, b_dh1.to(b_k.dtype))
464
+
465
+ if K > 64:
466
+ p_k = tl.make_block_ptr(
467
+ k, (T, K), (stride_k, 1), (i_t * BT, 64), (BT, 64), (1, 0)
468
+ )
469
+ b_k = tl.load(p_k, boundary_check=(0, 1))
470
+ if USE_GK:
471
+ o_k2 = 64 + o_k1
472
+ b_gk_last2 = tl.load(
473
+ gk + last_idx * H * K + o_k2, mask=(o_k2 < K), other=0.0
474
+ )
475
+ b_dv = tl.dot(b_k, b_dh2.to(b_k.dtype), acc=b_dv)
476
+
477
+ if K > 128:
478
+ p_k = tl.make_block_ptr(
479
+ k, (T, K), (stride_k, 1), (i_t * BT, 128), (BT, 64), (1, 0)
480
+ )
481
+ b_k = tl.load(p_k, boundary_check=(0, 1))
482
+ if USE_GK:
483
+ o_k3 = 128 + o_k1
484
+ b_gk_last3 = tl.load(
485
+ gk + last_idx * H * K + o_k3, mask=(o_k3 < K), other=0.0
486
+ )
487
+ b_dv = tl.dot(b_k, b_dh3.to(b_k.dtype), acc=b_dv)
488
+
489
+ if K > 192:
490
+ p_k = tl.make_block_ptr(
491
+ k, (T, K), (stride_k, 1), (i_t * BT, 192), (BT, 64), (1, 0)
492
+ )
493
+ b_k = tl.load(p_k, boundary_check=(0, 1))
494
+ if USE_GK:
495
+ o_k4 = 192 + o_k1
496
+ b_gk_last4 = tl.load(
497
+ gk + last_idx * H * K + o_k4, mask=(o_k4 < K), other=0.0
498
+ )
499
+ b_dv = tl.dot(b_k, b_dh4.to(b_k.dtype), acc=b_dv)
500
+
501
+ if USE_G:
502
+ m_t = (i_t * BT + tl.arange(0, BT)) < T
503
+ b_dv *= tl.where(m_t, exp(bg_last - b_g), 0)[:, None]
504
+ b_dv += tl.load(p_dv, boundary_check=(0, 1))
505
+
506
+ tl.store(p_dv2, b_dv.to(p_dv.dtype.element_ty), boundary_check=(0, 1))
507
+ # Update dh
508
+ p_w = tl.make_block_ptr(
509
+ w, (K, T), (1, stride_k), (0, i_t * BT), (64, BT), (0, 1)
510
+ )
511
+ p_q = tl.make_block_ptr(
512
+ q, (K, T), (1, stride_k), (0, i_t * BT), (64, BT), (0, 1)
513
+ )
514
+ b_w = tl.load(p_w, boundary_check=(0, 1))
515
+ b_q = tl.load(p_q, boundary_check=(0, 1))
516
+ if USE_G:
517
+ b_dh1 *= bg_last_exp
518
+ b_q = b_q * b_g_exp[None, :]
519
+ if USE_GK:
520
+ b_dh1 *= exp(b_gk_last1[:, None])
521
+ b_dh1 += tl.dot(b_q.to(b_q.dtype), b_do.to(b_q.dtype)) * scale - tl.dot(
522
+ b_w, b_dv.to(b_w.dtype)
523
+ )
524
+ if K > 64:
525
+ p_q = tl.make_block_ptr(
526
+ q, (K, T), (1, stride_k), (64, i_t * BT), (64, BT), (0, 1)
527
+ )
528
+ p_w = tl.make_block_ptr(
529
+ w, (K, T), (1, stride_k), (64, i_t * BT), (64, BT), (0, 1)
530
+ )
531
+ b_q = tl.load(p_q, boundary_check=(0, 1))
532
+ b_w = tl.load(p_w, boundary_check=(0, 1))
533
+ if USE_G:
534
+ b_dh2 *= bg_last_exp
535
+ b_q = b_q * b_g_exp[None, :]
536
+ if USE_GK:
537
+ b_dh2 *= exp(b_gk_last2[:, None])
538
+ b_dh2 += tl.dot(b_q.to(b_q.dtype), b_do.to(b_q.dtype)) * scale - tl.dot(
539
+ b_w, b_dv.to(b_w.dtype)
540
+ )
541
+ if K > 128:
542
+ p_q = tl.make_block_ptr(
543
+ q, (K, T), (1, stride_k), (128, i_t * BT), (64, BT), (0, 1)
544
+ )
545
+ p_w = tl.make_block_ptr(
546
+ w, (K, T), (1, stride_k), (128, i_t * BT), (64, BT), (0, 1)
547
+ )
548
+ b_q = tl.load(p_q, boundary_check=(0, 1))
549
+ b_w = tl.load(p_w, boundary_check=(0, 1))
550
+ if USE_G:
551
+ b_dh3 *= bg_last_exp
552
+ b_q = b_q * b_g_exp[None, :]
553
+ if USE_GK:
554
+ b_dh3 *= exp(b_gk_last3[:, None])
555
+ b_dh3 += tl.dot(b_q.to(b_q.dtype), b_do.to(b_q.dtype)) * scale - tl.dot(
556
+ b_w, b_dv.to(b_w.dtype)
557
+ )
558
+ if K > 192:
559
+ p_q = tl.make_block_ptr(
560
+ q, (K, T), (1, stride_k), (192, i_t * BT), (64, BT), (0, 1)
561
+ )
562
+ p_w = tl.make_block_ptr(
563
+ w, (K, T), (1, stride_k), (192, i_t * BT), (64, BT), (0, 1)
564
+ )
565
+ b_q = tl.load(p_q, boundary_check=(0, 1))
566
+ b_w = tl.load(p_w, boundary_check=(0, 1))
567
+ if USE_G:
568
+ b_dh4 *= bg_last_exp
569
+ b_q = b_q * b_g_exp[None, :]
570
+ if USE_GK:
571
+ b_dh4 *= exp(b_gk_last4[:, None])
572
+ b_dh4 += tl.dot(b_q.to(b_q.dtype), b_do.to(b_q.dtype)) * scale - tl.dot(
573
+ b_w, b_dv.to(b_w.dtype)
574
+ )
575
+
576
+ if USE_INITIAL_STATE:
577
+ p_dh0 = tl.make_block_ptr(dh0, (K, V), (V, 1), (0, i_v * BV), (64, BV), (1, 0))
578
+ tl.store(p_dh0, b_dh1.to(p_dh0.dtype.element_ty), boundary_check=(0, 1))
579
+ if K > 64:
580
+ p_dh1 = tl.make_block_ptr(
581
+ dh0, (K, V), (V, 1), (64, i_v * BV), (64, BV), (1, 0)
582
+ )
583
+ tl.store(p_dh1, b_dh2.to(p_dh1.dtype.element_ty), boundary_check=(0, 1))
584
+ if K > 128:
585
+ p_dh2 = tl.make_block_ptr(
586
+ dh0, (K, V), (V, 1), (128, i_v * BV), (64, BV), (1, 0)
587
+ )
588
+ tl.store(p_dh2, b_dh3.to(p_dh2.dtype.element_ty), boundary_check=(0, 1))
589
+ if K > 192:
590
+ p_dh3 = tl.make_block_ptr(
591
+ dh0, (K, V), (V, 1), (192, i_v * BV), (64, BV), (1, 0)
592
+ )
593
+ tl.store(p_dh3, b_dh4.to(p_dh3.dtype.element_ty), boundary_check=(0, 1))
594
+
595
+
596
+ def chunk_gated_delta_rule_fwd_h(
597
+ k: torch.Tensor,
598
+ w: torch.Tensor,
599
+ u: torch.Tensor,
600
+ g: torch.Tensor | None = None,
601
+ gk: torch.Tensor | None = None,
602
+ initial_state: torch.Tensor | None = None,
603
+ output_final_state: bool = False,
604
+ chunk_size: int = 64, # SY: remove this argument and force chunk size 64?
605
+ save_new_value: bool = True,
606
+ cu_seqlens: torch.LongTensor | None = None,
607
+ chunk_indices: torch.LongTensor | None = None,
608
+ ) -> tuple[torch.Tensor, torch.Tensor]:
609
+ B, T, H, K, V = *k.shape, u.shape[-1]
610
+ BT = chunk_size
611
+
612
+ if chunk_indices is None and cu_seqlens is not None:
613
+ chunk_indices = prepare_chunk_indices(cu_seqlens, chunk_size)
614
+ # N: the actual number of sequences in the batch with either equal or variable lengths
615
+ if cu_seqlens is None:
616
+ N, NT, chunk_offsets = B, triton.cdiv(T, BT), None
617
+ else:
618
+ N, NT, chunk_offsets = (
619
+ len(cu_seqlens) - 1,
620
+ len(chunk_indices),
621
+ prepare_chunk_offsets(cu_seqlens, BT),
622
+ )
623
+ assert K <= 256, "current kernel does not support head dimension larger than 256."
624
+
625
+ h = k.new_empty(B, NT, H, K, V)
626
+ final_state = (
627
+ k.new_empty(N, H, K, V, dtype=torch.float32) if output_final_state else None
628
+ )
629
+
630
+ v_new = torch.empty_like(u) if save_new_value else None
631
+
632
+ def grid(meta):
633
+ return (triton.cdiv(V, meta["BV"]), N * H)
634
+
635
+ chunk_gated_delta_rule_fwd_kernel_h_blockdim64[grid](
636
+ k=k,
637
+ v=u,
638
+ w=w,
639
+ v_new=v_new,
640
+ g=g,
641
+ gk=gk,
642
+ h=h,
643
+ h0=initial_state,
644
+ ht=final_state,
645
+ cu_seqlens=cu_seqlens,
646
+ chunk_offsets=chunk_offsets,
647
+ T=T,
648
+ H=H,
649
+ K=K,
650
+ V=V,
651
+ BT=BT,
652
+ )
653
+ return h, v_new, final_state
654
+
655
+
656
+ @triton.heuristics(
657
+ {
658
+ "USE_G": lambda args: args["g"] is not None,
659
+ "USE_GK": lambda args: args["gk"] is not None,
660
+ "USE_INITIAL_STATE": lambda args: args["h0"] is not None,
661
+ "STORE_FINAL_STATE": lambda args: args["ht"] is not None,
662
+ "SAVE_NEW_VALUE": lambda args: args["v_new"] is not None,
663
+ "IS_VARLEN": lambda args: args["cu_seqlens"] is not None,
664
+ }
665
+ )
666
+ @triton.autotune(
667
+ configs=gated_delta_rule_autotune_configs(
668
+ [
669
+ triton.Config({"BV": BV}, num_warps=num_warps, num_stages=num_stages)
670
+ for num_warps in [2, 4]
671
+ for num_stages in NUM_STAGES_FWD
672
+ for BV in [16, 32, 64]
673
+ ]
674
+ ),
675
+ key=["H", "K", "V", "BT", "IS_VARLEN"],
676
+ use_cuda_graph=USE_CUDA_GRAPH,
677
+ **autotune_cache_kwargs,
678
+ )
679
+ @triton.jit(do_not_specialize=["T", "T_flat"])
680
+ def chunk_gated_delta_rule_fwd_kernel_h_opt(
681
+ k,
682
+ v,
683
+ w,
684
+ v_new,
685
+ g,
686
+ gk,
687
+ h,
688
+ h0,
689
+ ht,
690
+ cu_seqlens,
691
+ chunk_offsets,
692
+ T,
693
+ T_flat,
694
+ H: tl.constexpr,
695
+ Hg: tl.constexpr,
696
+ K: tl.constexpr,
697
+ V: tl.constexpr,
698
+ BT: tl.constexpr,
699
+ BV: tl.constexpr,
700
+ USE_G: tl.constexpr,
701
+ USE_GK: tl.constexpr,
702
+ USE_INITIAL_STATE: tl.constexpr,
703
+ STORE_FINAL_STATE: tl.constexpr,
704
+ SAVE_NEW_VALUE: tl.constexpr,
705
+ IS_VARLEN: tl.constexpr,
706
+ ):
707
+ i_v, i_nh = tl.program_id(0), tl.program_id(1)
708
+ i_n, i_h = i_nh // H, i_nh % H
709
+ if IS_VARLEN:
710
+ bos, eos = (
711
+ tl.load(cu_seqlens + i_n).to(tl.int32),
712
+ tl.load(cu_seqlens + i_n + 1).to(tl.int32),
713
+ )
714
+ T = eos - bos
715
+ NT = tl.cdiv(T, BT)
716
+ boh = tl.load(chunk_offsets + i_n).to(tl.int32)
717
+ else:
718
+ bos, eos = i_n * T, i_n * T + T
719
+ NT = tl.cdiv(T, BT)
720
+ boh = i_n * NT
721
+
722
+ b_h1 = tl.zeros([64, BV], dtype=tl.float32)
723
+ if K > 64:
724
+ b_h2 = tl.zeros([64, BV], dtype=tl.float32)
725
+ if K > 128:
726
+ b_h3 = tl.zeros([64, BV], dtype=tl.float32)
727
+ if K > 192:
728
+ b_h4 = tl.zeros([64, BV], dtype=tl.float32)
729
+
730
+ h += ((boh * H + i_h) * K * V).to(tl.int64)
731
+ k += ((bos * Hg + i_h // (H // Hg)) * K).to(tl.int64)
732
+ if IS_VARLEN:
733
+ v += ((i_h * T_flat + bos) * V).to(tl.int64)
734
+ w += ((i_h * T_flat + bos) * K).to(tl.int64)
735
+ else:
736
+ v += (((i_n * H + i_h) * T_flat) * V).to(tl.int64)
737
+ w += (((i_n * H + i_h) * T_flat) * K).to(tl.int64)
738
+ stride_v = V
739
+ stride_w = K
740
+ if SAVE_NEW_VALUE:
741
+ if IS_VARLEN:
742
+ v_new += ((i_h * T_flat + bos) * V).to(tl.int64)
743
+ else:
744
+ v_new += (((i_n * H + i_h) * T_flat) * V).to(tl.int64)
745
+ stride_h = H * K * V
746
+ stride_k = Hg * K
747
+ if USE_INITIAL_STATE:
748
+ h0 = h0 + i_nh * K * V
749
+ if STORE_FINAL_STATE:
750
+ ht = ht + i_nh * K * V
751
+
752
+ if USE_INITIAL_STATE:
753
+ p_h0_1 = tl.make_block_ptr(h0, (K, V), (V, 1), (0, i_v * BV), (64, BV), (1, 0))
754
+ b_h1 += tl.load(p_h0_1, boundary_check=(0, 1)).to(tl.float32)
755
+ if K > 64:
756
+ p_h0_2 = tl.make_block_ptr(
757
+ h0, (K, V), (V, 1), (64, i_v * BV), (64, BV), (1, 0)
758
+ )
759
+ b_h2 += tl.load(p_h0_2, boundary_check=(0, 1)).to(tl.float32)
760
+ if K > 128:
761
+ p_h0_3 = tl.make_block_ptr(
762
+ h0, (K, V), (V, 1), (128, i_v * BV), (64, BV), (1, 0)
763
+ )
764
+ b_h3 += tl.load(p_h0_3, boundary_check=(0, 1)).to(tl.float32)
765
+ if K > 192:
766
+ p_h0_4 = tl.make_block_ptr(
767
+ h0, (K, V), (V, 1), (192, i_v * BV), (64, BV), (1, 0)
768
+ )
769
+ b_h4 += tl.load(p_h0_4, boundary_check=(0, 1)).to(tl.float32)
770
+
771
+ for i_t in range(NT):
772
+ p_h1 = tl.make_block_ptr(
773
+ h + i_t * stride_h, (K, V), (V, 1), (0, i_v * BV), (64, BV), (1, 0)
774
+ )
775
+ tl.store(p_h1, b_h1.to(p_h1.dtype.element_ty), boundary_check=(0, 1))
776
+ if K > 64:
777
+ p_h2 = tl.make_block_ptr(
778
+ h + i_t * stride_h, (K, V), (V, 1), (64, i_v * BV), (64, BV), (1, 0)
779
+ )
780
+ tl.store(p_h2, b_h2.to(p_h2.dtype.element_ty), boundary_check=(0, 1))
781
+ if K > 128:
782
+ p_h3 = tl.make_block_ptr(
783
+ h + i_t * stride_h, (K, V), (V, 1), (128, i_v * BV), (64, BV), (1, 0)
784
+ )
785
+ tl.store(p_h3, b_h3.to(p_h3.dtype.element_ty), boundary_check=(0, 1))
786
+ if K > 192:
787
+ p_h4 = tl.make_block_ptr(
788
+ h + i_t * stride_h, (K, V), (V, 1), (192, i_v * BV), (64, BV), (1, 0)
789
+ )
790
+ tl.store(p_h4, b_h4.to(p_h4.dtype.element_ty), boundary_check=(0, 1))
791
+
792
+ p_w = tl.make_block_ptr(
793
+ w, (T, K), (stride_w, 1), (i_t * BT, 0), (BT, 64), (1, 0)
794
+ )
795
+ b_w = tl.load(p_w, boundary_check=(0, 1))
796
+ b_v = tl.dot(b_w, b_h1.to(b_w.dtype))
797
+ if K > 64:
798
+ p_w = tl.make_block_ptr(
799
+ w, (T, K), (stride_w, 1), (i_t * BT, 64), (BT, 64), (1, 0)
800
+ )
801
+ b_w = tl.load(p_w, boundary_check=(0, 1))
802
+ b_v = tl.dot(b_w, b_h2.to(b_w.dtype), acc=b_v)
803
+ if K > 128:
804
+ p_w = tl.make_block_ptr(
805
+ w, (T, K), (stride_w, 1), (i_t * BT, 128), (BT, 64), (1, 0)
806
+ )
807
+ b_w = tl.load(p_w, boundary_check=(0, 1))
808
+ b_v = tl.dot(b_w, b_h3.to(b_w.dtype), acc=b_v)
809
+ if K > 192:
810
+ p_w = tl.make_block_ptr(
811
+ w, (T, K), (stride_w, 1), (i_t * BT, 192), (BT, 64), (1, 0)
812
+ )
813
+ b_w = tl.load(p_w, boundary_check=(0, 1))
814
+ b_v = tl.dot(b_w, b_h4.to(b_w.dtype), acc=b_v)
815
+ p_v = tl.make_block_ptr(
816
+ v, (T, V), (stride_v, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)
817
+ )
818
+ b_v = tl.load(p_v, boundary_check=(0, 1)) - b_v
819
+
820
+ if SAVE_NEW_VALUE:
821
+ p_vn = tl.make_block_ptr(
822
+ v_new, (T, V), (V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)
823
+ )
824
+ tl.store(p_vn, b_v.to(p_vn.dtype.element_ty), boundary_check=(0, 1))
825
+
826
+ last_idx = min((i_t + 1) * BT, T) - 1
827
+ if USE_G:
828
+ m_t = (i_t * BT + tl.arange(0, BT)) < T
829
+ b_g_last = tl.load(g + bos * H + last_idx * H + i_h)
830
+ p_g = tl.make_block_ptr(
831
+ g + bos * H + i_h, (T,), (H,), (i_t * BT,), (BT,), (0,)
832
+ )
833
+ b_g = tl.load(p_g, boundary_check=(0,))
834
+ b_v = b_v * tl.where(m_t, exp(b_g_last - b_g), 0)[:, None]
835
+ b_g_last = exp(b_g_last)
836
+ b_h1 *= b_g_last
837
+ if K > 64:
838
+ b_h2 *= b_g_last
839
+ if K > 128:
840
+ b_h3 *= b_g_last
841
+ if K > 192:
842
+ b_h4 *= b_g_last
843
+
844
+ if USE_GK:
845
+ o_k1 = tl.arange(0, 64)
846
+ b_gk_last1 = tl.load(
847
+ gk + (bos + last_idx) * H * K + i_h * K + o_k1,
848
+ mask=(o_k1 < K),
849
+ other=0.0,
850
+ )
851
+ b_h1 *= exp(b_gk_last1)[:, None]
852
+ if K > 64:
853
+ o_k2 = 64 + o_k1
854
+ b_gk_last2 = tl.load(
855
+ gk + (bos + last_idx) * H * K + i_h * K + o_k2,
856
+ mask=(o_k2 < K),
857
+ other=0.0,
858
+ )
859
+ b_h2 *= exp(b_gk_last2)[:, None]
860
+ if K > 128:
861
+ o_k3 = 128 + o_k1
862
+ b_gk_last3 = tl.load(
863
+ gk + (bos + last_idx) * H * K + i_h * K + o_k3,
864
+ mask=(o_k3 < K),
865
+ other=0.0,
866
+ )
867
+ b_h3 *= exp(b_gk_last3)[:, None]
868
+ if K > 192:
869
+ o_k4 = 192 + o_k1
870
+ b_gk_last4 = tl.load(
871
+ gk + (bos + last_idx) * H * K + i_h * K + o_k4,
872
+ mask=(o_k4 < K),
873
+ other=0.0,
874
+ )
875
+ b_h4 *= exp(b_gk_last4)[:, None]
876
+ b_v = b_v.to(k.dtype.element_ty)
877
+
878
+ p_k = tl.make_block_ptr(
879
+ k, (K, T), (1, stride_k), (0, i_t * BT), (64, BT), (0, 1)
880
+ )
881
+ b_k = tl.load(p_k, boundary_check=(0, 1))
882
+ b_h1 = tl.dot(b_k, b_v, acc=b_h1)
883
+ if K > 64:
884
+ p_k = tl.make_block_ptr(
885
+ k, (K, T), (1, stride_k), (64, i_t * BT), (64, BT), (0, 1)
886
+ )
887
+ b_k = tl.load(p_k, boundary_check=(0, 1))
888
+ b_h2 = tl.dot(b_k, b_v, acc=b_h2)
889
+ if K > 128:
890
+ p_k = tl.make_block_ptr(
891
+ k, (K, T), (1, stride_k), (128, i_t * BT), (64, BT), (0, 1)
892
+ )
893
+ b_k = tl.load(p_k, boundary_check=(0, 1))
894
+ b_h3 = tl.dot(b_k, b_v, acc=b_h3)
895
+ if K > 192:
896
+ p_k = tl.make_block_ptr(
897
+ k, (K, T), (1, stride_k), (192, i_t * BT), (64, BT), (0, 1)
898
+ )
899
+ b_k = tl.load(p_k, boundary_check=(0, 1))
900
+ b_h4 = tl.dot(b_k, b_v, acc=b_h4)
901
+
902
+ if STORE_FINAL_STATE:
903
+ p_ht = tl.make_block_ptr(ht, (K, V), (V, 1), (0, i_v * BV), (64, BV), (1, 0))
904
+ tl.store(p_ht, b_h1.to(p_ht.dtype.element_ty), boundary_check=(0, 1))
905
+ if K > 64:
906
+ p_ht = tl.make_block_ptr(
907
+ ht, (K, V), (V, 1), (64, i_v * BV), (64, BV), (1, 0)
908
+ )
909
+ tl.store(p_ht, b_h2.to(p_ht.dtype.element_ty), boundary_check=(0, 1))
910
+ if K > 128:
911
+ p_ht = tl.make_block_ptr(
912
+ ht, (K, V), (V, 1), (128, i_v * BV), (64, BV), (1, 0)
913
+ )
914
+ tl.store(p_ht, b_h3.to(p_ht.dtype.element_ty), boundary_check=(0, 1))
915
+ if K > 192:
916
+ p_ht = tl.make_block_ptr(
917
+ ht, (K, V), (V, 1), (192, i_v * BV), (64, BV), (1, 0)
918
+ )
919
+ tl.store(p_ht, b_h4.to(p_ht.dtype.element_ty), boundary_check=(0, 1))
920
+
921
+
922
+ def chunk_gated_delta_rule_fwd_h_opt(
923
+ k: torch.Tensor,
924
+ w: torch.Tensor,
925
+ u: torch.Tensor,
926
+ g: torch.Tensor | None = None,
927
+ gk: torch.Tensor | None = None,
928
+ initial_state: torch.Tensor | None = None,
929
+ output_final_state: bool = False,
930
+ chunk_size: int = 64,
931
+ save_new_value: bool = True,
932
+ cu_seqlens: torch.LongTensor | None = None,
933
+ ) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor | None]:
934
+ """
935
+ Optimized hidden state forward with Hg-aware k strides.
936
+
937
+ w and u are expected in head-major contiguous layout [B, H, T, K] / [B, H, T, V].
938
+ v_new output is [B, H, T_flat, V].
939
+ """
940
+ B, T, Hg, K = k.shape
941
+ BT = chunk_size
942
+
943
+ H = w.shape[1]
944
+ V = u.shape[-1]
945
+ T_flat = w.shape[2]
946
+
947
+ if cu_seqlens is not None:
948
+ chunk_indices = prepare_chunk_indices(cu_seqlens, chunk_size)
949
+ N = len(cu_seqlens) - 1
950
+ NT = len(chunk_indices)
951
+ chunk_offsets = prepare_chunk_offsets(cu_seqlens, BT)
952
+ else:
953
+ N, NT, chunk_offsets = B, triton.cdiv(T, BT), None
954
+
955
+ assert K <= 256, "current kernel does not support head dimension larger than 256."
956
+
957
+ h = k.new_empty(B, NT, H, K, V)
958
+ final_state = (
959
+ k.new_empty(N, H, K, V, dtype=torch.float32) if output_final_state else None
960
+ )
961
+ v_new = k.new_empty(B, H, T_flat, V, dtype=u.dtype) if save_new_value else None
962
+
963
+ def grid(meta):
964
+ return (triton.cdiv(V, meta["BV"]), N * H)
965
+
966
+ chunk_gated_delta_rule_fwd_kernel_h_opt[grid](
967
+ k=k,
968
+ v=u,
969
+ w=w,
970
+ v_new=v_new,
971
+ g=g,
972
+ gk=gk,
973
+ h=h,
974
+ h0=initial_state,
975
+ ht=final_state,
976
+ cu_seqlens=cu_seqlens,
977
+ chunk_offsets=chunk_offsets,
978
+ T=T,
979
+ T_flat=T_flat,
980
+ H=H,
981
+ Hg=Hg,
982
+ K=K,
983
+ V=V,
984
+ BT=BT,
985
+ )
986
+ return h, v_new, final_state
987
+
988
+
989
+ # =====================================================================
990
+ # opt_vk variant: h layout [V, K] (transposed from opt's [K, V])
991
+ # All other layouts (k, w, u, v_new) are identical to opt.
992
+ # =====================================================================
993
+
994
+
995
+ @triton.heuristics(
996
+ {
997
+ "USE_G": lambda args: args["g"] is not None,
998
+ "USE_GK": lambda args: args["gk"] is not None,
999
+ "USE_INITIAL_STATE": lambda args: args["h0"] is not None,
1000
+ "STORE_FINAL_STATE": lambda args: args["ht"] is not None,
1001
+ "SAVE_NEW_VALUE": lambda args: args["v_new"] is not None,
1002
+ "IS_VARLEN": lambda args: args["cu_seqlens"] is not None,
1003
+ }
1004
+ )
1005
+ @triton.autotune(
1006
+ configs=gated_delta_rule_autotune_configs(
1007
+ [
1008
+ triton.Config({"BV": BV}, num_warps=num_warps, num_stages=num_stages)
1009
+ for num_warps in [2, 4]
1010
+ for num_stages in NUM_STAGES_FWD
1011
+ for BV in [16, 32, 64]
1012
+ ]
1013
+ ),
1014
+ key=["H", "K", "V", "BT", "IS_VARLEN"],
1015
+ use_cuda_graph=USE_CUDA_GRAPH,
1016
+ **autotune_cache_kwargs,
1017
+ )
1018
+ @triton.jit(do_not_specialize=["T", "T_flat"])
1019
+ def chunk_gated_delta_rule_fwd_kernel_h_opt_vk(
1020
+ k,
1021
+ v,
1022
+ w,
1023
+ v_new,
1024
+ g,
1025
+ gk,
1026
+ h,
1027
+ h0,
1028
+ ht,
1029
+ cu_seqlens,
1030
+ chunk_offsets,
1031
+ T,
1032
+ T_flat,
1033
+ H: tl.constexpr,
1034
+ Hg: tl.constexpr,
1035
+ K: tl.constexpr,
1036
+ V: tl.constexpr,
1037
+ BT: tl.constexpr,
1038
+ BV: tl.constexpr,
1039
+ USE_G: tl.constexpr,
1040
+ USE_GK: tl.constexpr,
1041
+ USE_INITIAL_STATE: tl.constexpr,
1042
+ STORE_FINAL_STATE: tl.constexpr,
1043
+ SAVE_NEW_VALUE: tl.constexpr,
1044
+ IS_VARLEN: tl.constexpr,
1045
+ USE_EXP2: tl.constexpr = False,
1046
+ ):
1047
+ i_v, i_nh = tl.program_id(0), tl.program_id(1)
1048
+ i_n, i_h = i_nh // H, i_nh % H
1049
+ if IS_VARLEN:
1050
+ bos, eos = (
1051
+ tl.load(cu_seqlens + i_n).to(tl.int32),
1052
+ tl.load(cu_seqlens + i_n + 1).to(tl.int32),
1053
+ )
1054
+ T = eos - bos
1055
+ NT = tl.cdiv(T, BT)
1056
+ boh = tl.load(chunk_offsets + i_n).to(tl.int32)
1057
+ else:
1058
+ bos, eos = i_n * T, i_n * T + T
1059
+ NT = tl.cdiv(T, BT)
1060
+ boh = i_n * NT
1061
+
1062
+ # [BV, 64] — h in [V, K] layout (transposed from opt's [64, BV])
1063
+ b_h1 = tl.zeros([BV, 64], dtype=tl.float32)
1064
+ if K > 64:
1065
+ b_h2 = tl.zeros([BV, 64], dtype=tl.float32)
1066
+ if K > 128:
1067
+ b_h3 = tl.zeros([BV, 64], dtype=tl.float32)
1068
+ if K > 192:
1069
+ b_h4 = tl.zeros([BV, 64], dtype=tl.float32)
1070
+
1071
+ h += ((boh * H + i_h) * V * K).to(tl.int64)
1072
+ k += ((bos * Hg + i_h // (H // Hg)) * K).to(tl.int64)
1073
+ if IS_VARLEN:
1074
+ v += ((i_h * T_flat + bos) * V).to(tl.int64)
1075
+ w += ((i_h * T_flat + bos) * K).to(tl.int64)
1076
+ else:
1077
+ v += (((i_n * H + i_h) * T_flat) * V).to(tl.int64)
1078
+ w += (((i_n * H + i_h) * T_flat) * K).to(tl.int64)
1079
+ stride_v = V
1080
+ stride_w = K
1081
+ if SAVE_NEW_VALUE:
1082
+ if IS_VARLEN:
1083
+ v_new += ((i_h * T_flat + bos) * V).to(tl.int64)
1084
+ else:
1085
+ v_new += (((i_n * H + i_h) * T_flat) * V).to(tl.int64)
1086
+ stride_h = H * V * K
1087
+ stride_k = Hg * K
1088
+ if USE_INITIAL_STATE:
1089
+ h0 = h0 + i_nh * V * K
1090
+ if STORE_FINAL_STATE:
1091
+ ht = ht + i_nh * V * K
1092
+
1093
+ if USE_G:
1094
+ if IS_VARLEN:
1095
+ g += (i_h * T_flat + bos).to(tl.int64)
1096
+ else:
1097
+ g += (((i_n * H + i_h) * T_flat)).to(tl.int64)
1098
+
1099
+ if USE_INITIAL_STATE:
1100
+ p_h0_1 = tl.make_block_ptr(h0, (V, K), (K, 1), (i_v * BV, 0), (BV, 64), (1, 0))
1101
+ b_h1 += tl.load(p_h0_1, boundary_check=(0, 1)).to(tl.float32)
1102
+ if K > 64:
1103
+ p_h0_2 = tl.make_block_ptr(
1104
+ h0, (V, K), (K, 1), (i_v * BV, 64), (BV, 64), (1, 0)
1105
+ )
1106
+ b_h2 += tl.load(p_h0_2, boundary_check=(0, 1)).to(tl.float32)
1107
+ if K > 128:
1108
+ p_h0_3 = tl.make_block_ptr(
1109
+ h0, (V, K), (K, 1), (i_v * BV, 128), (BV, 64), (1, 0)
1110
+ )
1111
+ b_h3 += tl.load(p_h0_3, boundary_check=(0, 1)).to(tl.float32)
1112
+ if K > 192:
1113
+ p_h0_4 = tl.make_block_ptr(
1114
+ h0, (V, K), (K, 1), (i_v * BV, 192), (BV, 64), (1, 0)
1115
+ )
1116
+ b_h4 += tl.load(p_h0_4, boundary_check=(0, 1)).to(tl.float32)
1117
+
1118
+ for i_t in range(NT):
1119
+ # Store h snapshot [V, K]
1120
+ p_h1 = tl.make_block_ptr(
1121
+ h + i_t * stride_h, (V, K), (K, 1), (i_v * BV, 0), (BV, 64), (1, 0)
1122
+ )
1123
+ tl.store(p_h1, b_h1.to(p_h1.dtype.element_ty), boundary_check=(0, 1))
1124
+ if K > 64:
1125
+ p_h2 = tl.make_block_ptr(
1126
+ h + i_t * stride_h, (V, K), (K, 1), (i_v * BV, 64), (BV, 64), (1, 0)
1127
+ )
1128
+ tl.store(p_h2, b_h2.to(p_h2.dtype.element_ty), boundary_check=(0, 1))
1129
+ if K > 128:
1130
+ p_h3 = tl.make_block_ptr(
1131
+ h + i_t * stride_h, (V, K), (K, 1), (i_v * BV, 128), (BV, 64), (1, 0)
1132
+ )
1133
+ tl.store(p_h3, b_h3.to(p_h3.dtype.element_ty), boundary_check=(0, 1))
1134
+ if K > 192:
1135
+ p_h4 = tl.make_block_ptr(
1136
+ h + i_t * stride_h, (V, K), (K, 1), (i_v * BV, 192), (BV, 64), (1, 0)
1137
+ )
1138
+ tl.store(p_h4, b_h4.to(p_h4.dtype.element_ty), boundary_check=(0, 1))
1139
+
1140
+ # b_v = u - w @ h^T (h is [BV,64], need [64,BV] for dot with w[BT,64])
1141
+ p_w = tl.make_block_ptr(
1142
+ w, (T, K), (stride_w, 1), (i_t * BT, 0), (BT, 64), (1, 0)
1143
+ )
1144
+ b_w = tl.load(p_w, boundary_check=(0, 1))
1145
+ b_v = tl.dot(b_w, tl.trans(b_h1).to(b_w.dtype))
1146
+ if K > 64:
1147
+ p_w = tl.make_block_ptr(
1148
+ w, (T, K), (stride_w, 1), (i_t * BT, 64), (BT, 64), (1, 0)
1149
+ )
1150
+ b_w = tl.load(p_w, boundary_check=(0, 1))
1151
+ b_v = tl.dot(b_w, tl.trans(b_h2).to(b_w.dtype), acc=b_v)
1152
+ if K > 128:
1153
+ p_w = tl.make_block_ptr(
1154
+ w, (T, K), (stride_w, 1), (i_t * BT, 128), (BT, 64), (1, 0)
1155
+ )
1156
+ b_w = tl.load(p_w, boundary_check=(0, 1))
1157
+ b_v = tl.dot(b_w, tl.trans(b_h3).to(b_w.dtype), acc=b_v)
1158
+ if K > 192:
1159
+ p_w = tl.make_block_ptr(
1160
+ w, (T, K), (stride_w, 1), (i_t * BT, 192), (BT, 64), (1, 0)
1161
+ )
1162
+ b_w = tl.load(p_w, boundary_check=(0, 1))
1163
+ b_v = tl.dot(b_w, tl.trans(b_h4).to(b_w.dtype), acc=b_v)
1164
+ p_v = tl.make_block_ptr(
1165
+ v, (T, V), (stride_v, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)
1166
+ )
1167
+ b_v = tl.load(p_v, boundary_check=(0, 1)) - b_v
1168
+
1169
+ if SAVE_NEW_VALUE:
1170
+ p_vn = tl.make_block_ptr(
1171
+ v_new, (T, V), (V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)
1172
+ )
1173
+ tl.store(p_vn, b_v.to(p_vn.dtype.element_ty), boundary_check=(0, 1))
1174
+
1175
+ last_idx = min((i_t + 1) * BT, T) - 1
1176
+ if USE_G:
1177
+ m_t = (i_t * BT + tl.arange(0, BT)) < T
1178
+ b_g_last = tl.load(g + last_idx)
1179
+ p_g = tl.make_block_ptr(g, (T,), (1,), (i_t * BT,), (BT,), (0,))
1180
+ b_g = tl.load(p_g, boundary_check=(0,))
1181
+ if USE_EXP2:
1182
+ b_v = b_v * tl.where(m_t, tl.math.exp2(b_g_last - b_g), 0)[:, None]
1183
+ b_g_last = tl.math.exp2(b_g_last)
1184
+ else:
1185
+ b_v = b_v * tl.where(m_t, exp(b_g_last - b_g), 0)[:, None]
1186
+ b_g_last = exp(b_g_last)
1187
+ b_h1 *= b_g_last
1188
+ if K > 64:
1189
+ b_h2 *= b_g_last
1190
+ if K > 128:
1191
+ b_h3 *= b_g_last
1192
+ if K > 192:
1193
+ b_h4 *= b_g_last
1194
+
1195
+ if USE_GK:
1196
+ o_k1 = tl.arange(0, 64)
1197
+ b_gk_last1 = tl.load(
1198
+ gk + (bos + last_idx) * H * K + i_h * K + o_k1,
1199
+ mask=(o_k1 < K),
1200
+ other=0.0,
1201
+ )
1202
+ b_h1 *= (tl.math.exp2(b_gk_last1) if USE_EXP2 else exp(b_gk_last1))[None, :]
1203
+ if K > 64:
1204
+ o_k2 = 64 + o_k1
1205
+ b_gk_last2 = tl.load(
1206
+ gk + (bos + last_idx) * H * K + i_h * K + o_k2,
1207
+ mask=(o_k2 < K),
1208
+ other=0.0,
1209
+ )
1210
+ b_h2 *= (tl.math.exp2(b_gk_last2) if USE_EXP2 else exp(b_gk_last2))[
1211
+ None, :
1212
+ ]
1213
+ if K > 128:
1214
+ o_k3 = 128 + o_k1
1215
+ b_gk_last3 = tl.load(
1216
+ gk + (bos + last_idx) * H * K + i_h * K + o_k3,
1217
+ mask=(o_k3 < K),
1218
+ other=0.0,
1219
+ )
1220
+ b_h3 *= (tl.math.exp2(b_gk_last3) if USE_EXP2 else exp(b_gk_last3))[
1221
+ None, :
1222
+ ]
1223
+ if K > 192:
1224
+ o_k4 = 192 + o_k1
1225
+ b_gk_last4 = tl.load(
1226
+ gk + (bos + last_idx) * H * K + i_h * K + o_k4,
1227
+ mask=(o_k4 < K),
1228
+ other=0.0,
1229
+ )
1230
+ b_h4 *= (tl.math.exp2(b_gk_last4) if USE_EXP2 else exp(b_gk_last4))[
1231
+ None, :
1232
+ ]
1233
+ b_v = b_v.to(k.dtype.element_ty)
1234
+
1235
+ # h[V,K] += v_new^T @ k → [BV,64] += trans(dot(k[64,BT], v[BT,BV]))
1236
+ p_k = tl.make_block_ptr(
1237
+ k, (K, T), (1, stride_k), (0, i_t * BT), (64, BT), (0, 1)
1238
+ )
1239
+ b_k = tl.load(p_k, boundary_check=(0, 1))
1240
+ b_h1 += tl.trans(tl.dot(b_k, b_v))
1241
+ if K > 64:
1242
+ p_k = tl.make_block_ptr(
1243
+ k, (K, T), (1, stride_k), (64, i_t * BT), (64, BT), (0, 1)
1244
+ )
1245
+ b_k = tl.load(p_k, boundary_check=(0, 1))
1246
+ b_h2 += tl.trans(tl.dot(b_k, b_v))
1247
+ if K > 128:
1248
+ p_k = tl.make_block_ptr(
1249
+ k, (K, T), (1, stride_k), (128, i_t * BT), (64, BT), (0, 1)
1250
+ )
1251
+ b_k = tl.load(p_k, boundary_check=(0, 1))
1252
+ b_h3 += tl.trans(tl.dot(b_k, b_v))
1253
+ if K > 192:
1254
+ p_k = tl.make_block_ptr(
1255
+ k, (K, T), (1, stride_k), (192, i_t * BT), (64, BT), (0, 1)
1256
+ )
1257
+ b_k = tl.load(p_k, boundary_check=(0, 1))
1258
+ b_h4 += tl.trans(tl.dot(b_k, b_v))
1259
+
1260
+ if STORE_FINAL_STATE:
1261
+ p_ht = tl.make_block_ptr(ht, (V, K), (K, 1), (i_v * BV, 0), (BV, 64), (1, 0))
1262
+ tl.store(p_ht, b_h1.to(p_ht.dtype.element_ty), boundary_check=(0, 1))
1263
+ if K > 64:
1264
+ p_ht = tl.make_block_ptr(
1265
+ ht, (V, K), (K, 1), (i_v * BV, 64), (BV, 64), (1, 0)
1266
+ )
1267
+ tl.store(p_ht, b_h2.to(p_ht.dtype.element_ty), boundary_check=(0, 1))
1268
+ if K > 128:
1269
+ p_ht = tl.make_block_ptr(
1270
+ ht, (V, K), (K, 1), (i_v * BV, 128), (BV, 64), (1, 0)
1271
+ )
1272
+ tl.store(p_ht, b_h3.to(p_ht.dtype.element_ty), boundary_check=(0, 1))
1273
+ if K > 192:
1274
+ p_ht = tl.make_block_ptr(
1275
+ ht, (V, K), (K, 1), (i_v * BV, 192), (BV, 64), (1, 0)
1276
+ )
1277
+ tl.store(p_ht, b_h4.to(p_ht.dtype.element_ty), boundary_check=(0, 1))
1278
+
1279
+
1280
+ def chunk_gated_delta_rule_fwd_h_opt_vk(
1281
+ k: torch.Tensor,
1282
+ w: torch.Tensor,
1283
+ u: torch.Tensor,
1284
+ g: torch.Tensor | None = None,
1285
+ gk: torch.Tensor | None = None,
1286
+ initial_state: torch.Tensor | None = None,
1287
+ output_final_state: bool = False,
1288
+ chunk_size: int = 64,
1289
+ save_new_value: bool = True,
1290
+ cu_seqlens: torch.LongTensor | None = None,
1291
+ use_exp2: bool = True,
1292
+ state_dtype: torch.dtype | None = None,
1293
+ num_decodes: int = 0,
1294
+ num_decode_tokens: int = 0,
1295
+ ) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor | None]:
1296
+ """
1297
+ Optimized hidden state forward with h layout [V, K].
1298
+
1299
+ w and u are expected in head-major contiguous layout [B, H, T, K] / [B, H, T, V].
1300
+ initial_state/final_state: [N, H, V, K].
1301
+ h snapshots: [B, NT, H, V, K].
1302
+ v_new output is [B, H, T_flat, V].
1303
+ `g` is expected in head-major layout [B, H, T].
1304
+ use_exp2 selects whether cumulative gates are interpreted in log2 space.
1305
+ state_dtype selects the initial/final hidden-state dtype (`fp32` or `bf16`);
1306
+ defaults to fp32. The kernel accumulates in fp32 and casts on store.
1307
+ num_decodes / num_decode_tokens skip a leading decode-only prefix in the
1308
+ ORIGINAL cu_seqlens (data tensors are expected pre-sliced); offsets are
1309
+ rebased internally via the cached prologue helpers so the chunk-index /
1310
+ offset build stays cache-warm across forward calls.
1311
+ """
1312
+ B, T, Hg, K = k.shape
1313
+ BT = chunk_size
1314
+
1315
+ H = w.shape[1]
1316
+ V = u.shape[-1]
1317
+ T_flat = w.shape[2]
1318
+
1319
+ if cu_seqlens is not None:
1320
+ # Pass the ORIGINAL (cache-stable) cu_seqlens + decode ints into the
1321
+ # cached prologue helpers so chunk_indices / chunk_offsets are built
1322
+ # once per (cu_seqlens_id, BT, num_decodes, num_decode_tokens) tuple
1323
+ # (no per-forward .tolist() D2H). The kernel walks the pre-sliced
1324
+ # prefill data via the rebased cu_seqlens.
1325
+ chunk_indices = prepare_chunk_indices(
1326
+ cu_seqlens, chunk_size, num_decodes, num_decode_tokens
1327
+ )
1328
+ chunk_offsets = prepare_chunk_offsets(
1329
+ cu_seqlens, BT, num_decodes, num_decode_tokens
1330
+ )
1331
+ kernel_cu_seqlens = prepare_rebased_cu_seqlens(
1332
+ cu_seqlens, num_decodes, num_decode_tokens
1333
+ )
1334
+ N = len(kernel_cu_seqlens) - 1
1335
+ NT = len(chunk_indices)
1336
+ else:
1337
+ N, NT, chunk_offsets = B, triton.cdiv(T, BT), None
1338
+ kernel_cu_seqlens = None
1339
+
1340
+ assert K <= 256, "current kernel does not support head dimension larger than 256."
1341
+
1342
+ if state_dtype is not None and state_dtype not in (torch.float32, torch.bfloat16):
1343
+ raise ValueError(f"`state_dtype` must be fp32 or bf16, got {state_dtype}.")
1344
+ _state_dtype = state_dtype if state_dtype is not None else torch.float32
1345
+ if (
1346
+ state_dtype is not None
1347
+ and initial_state is not None
1348
+ and initial_state.dtype != _state_dtype
1349
+ ):
1350
+ raise ValueError(
1351
+ f"`initial_state.dtype` ({initial_state.dtype}) must match "
1352
+ f"`state_dtype` ({_state_dtype})."
1353
+ )
1354
+
1355
+ if gk is not None:
1356
+ gk = gk.contiguous()
1357
+ if use_exp2:
1358
+ # gk is expressed in natural-log space, so pre-scale it for exp2 kernels.
1359
+ gk = gk * RCP_LN2
1360
+
1361
+ h = k.new_empty(B, NT, H, V, K)
1362
+ final_state = (
1363
+ k.new_empty(N, H, V, K, dtype=_state_dtype) if output_final_state else None
1364
+ )
1365
+ v_new = k.new_empty(B, H, T_flat, V, dtype=u.dtype) if save_new_value else None
1366
+
1367
+ def grid(meta):
1368
+ return (triton.cdiv(V, meta["BV"]), N * H)
1369
+
1370
+ chunk_gated_delta_rule_fwd_kernel_h_opt_vk[grid](
1371
+ k=k,
1372
+ v=u,
1373
+ w=w,
1374
+ v_new=v_new,
1375
+ g=g,
1376
+ gk=gk,
1377
+ h=h,
1378
+ h0=initial_state,
1379
+ ht=final_state,
1380
+ cu_seqlens=kernel_cu_seqlens,
1381
+ chunk_offsets=chunk_offsets,
1382
+ T=T,
1383
+ T_flat=T_flat,
1384
+ H=H,
1385
+ Hg=Hg,
1386
+ K=K,
1387
+ V=V,
1388
+ BT=BT,
1389
+ USE_EXP2=use_exp2,
1390
+ )
1391
+ return h, v_new, final_state
1392
+
1393
+
1394
+ def chunk_gated_delta_rule_bwd_dhu(
1395
+ q: torch.Tensor,
1396
+ k: torch.Tensor,
1397
+ w: torch.Tensor,
1398
+ do: torch.Tensor,
1399
+ dv: torch.Tensor,
1400
+ g: torch.Tensor | None = None,
1401
+ gk: torch.Tensor | None = None,
1402
+ h0: torch.Tensor | None = None,
1403
+ dht: torch.Tensor | None = None,
1404
+ scale: float | None = None,
1405
+ cu_seqlens: torch.LongTensor | None = None,
1406
+ chunk_size: int = 64, # SY: remove this argument and force chunk size 64?
1407
+ chunk_indices: torch.LongTensor | None = None,
1408
+ ) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
1409
+ B, T, H, K, V = *q.shape, do.shape[-1]
1410
+ # N: the actual number of sequences in the batch with either equal or variable lengths
1411
+ BT = 64
1412
+ assert (
1413
+ K <= 256
1414
+ ), "current kernel does not support head dimension being larger than 256."
1415
+
1416
+ if chunk_indices is None and cu_seqlens is not None:
1417
+ chunk_indices = prepare_chunk_indices(cu_seqlens, chunk_size)
1418
+ if cu_seqlens is None:
1419
+ N, NT, chunk_offsets = B, triton.cdiv(T, BT), None
1420
+ else:
1421
+ N, NT, chunk_offsets = (
1422
+ len(cu_seqlens) - 1,
1423
+ len(chunk_indices),
1424
+ prepare_chunk_offsets(cu_seqlens, BT),
1425
+ )
1426
+
1427
+ dh = q.new_empty(B, NT, H, K, V)
1428
+ dh0 = torch.empty_like(h0, dtype=torch.float32) if h0 is not None else None
1429
+ dv2 = torch.empty_like(dv)
1430
+
1431
+ def grid(meta):
1432
+ return (triton.cdiv(V, meta["BV"]), N * H)
1433
+
1434
+ chunk_gated_delta_rule_bwd_kernel_dhu_blockdim64[grid](
1435
+ q=q,
1436
+ k=k,
1437
+ w=w,
1438
+ g=g,
1439
+ gk=gk,
1440
+ dht=dht,
1441
+ dh0=dh0,
1442
+ do=do,
1443
+ dh=dh,
1444
+ dv=dv,
1445
+ dv2=dv2,
1446
+ cu_seqlens=cu_seqlens,
1447
+ chunk_offsets=chunk_offsets,
1448
+ scale=scale,
1449
+ T=T,
1450
+ H=H,
1451
+ K=K,
1452
+ V=V,
1453
+ BT=BT,
1454
+ )
1455
+ return dh, dh0, dv2
build/torch-rocm/_triton_kernels/gated_delta_rule/prefill/chunk_o.py ADDED
@@ -0,0 +1,1197 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (C) 2024-2026, Advanced Micro Devices, Inc. All rights reserved.
3
+ # Adapted from flash-linear-attention: Copyright (c) 2023-2025, Songlin Yang, Yu Zhang
4
+
5
+ """
6
+ Chunk-based output computation (Forward only).
7
+
8
+ This module provides functions for computing the final output in chunk mode.
9
+ """
10
+
11
+ import torch
12
+ import triton
13
+ import triton.language as tl
14
+
15
+ from ..gated_delta_rule_utils import (
16
+ IS_NVIDIA_HOPPER,
17
+ autotune_cache_kwargs,
18
+ check_shared_mem,
19
+ gated_delta_rule_autotune_configs,
20
+ )
21
+ from ..utils import prepare_chunk_indices, prepare_rebased_cu_seqlens
22
+ from ..utils.op import exp
23
+
24
+ BKV_LIST = [64, 128] if check_shared_mem() else [32, 64]
25
+ NUM_WARPS = [2, 4] if IS_NVIDIA_HOPPER else [2, 4, 8]
26
+
27
+
28
+ @triton.heuristics(
29
+ {
30
+ "USE_G": lambda args: args["g"] is not None,
31
+ "USE_G_GAMMA": lambda args: args["g_gamma"] is not None,
32
+ "IS_VARLEN": lambda args: args["cu_seqlens"] is not None,
33
+ }
34
+ )
35
+ @triton.autotune(
36
+ configs=gated_delta_rule_autotune_configs(
37
+ [
38
+ triton.Config({"BK": 128, "BV": 128}, num_warps=8, num_stages=3),
39
+ triton.Config({"BK": 64, "BV": 64}, num_warps=4, num_stages=3),
40
+ triton.Config({"BK": 32, "BV": 32}, num_warps=2, num_stages=3),
41
+ ]
42
+ ),
43
+ key=["H", "K", "V", "BT"],
44
+ **autotune_cache_kwargs,
45
+ )
46
+ @triton.jit(do_not_specialize=["T"])
47
+ def chunk_fwd_kernel_o(
48
+ q,
49
+ k,
50
+ v,
51
+ h,
52
+ g,
53
+ g_gamma,
54
+ o,
55
+ cu_seqlens,
56
+ chunk_indices,
57
+ scale,
58
+ T,
59
+ H: tl.constexpr,
60
+ K: tl.constexpr,
61
+ V: tl.constexpr,
62
+ BT: tl.constexpr,
63
+ BK: tl.constexpr,
64
+ BV: tl.constexpr,
65
+ USE_G: tl.constexpr,
66
+ USE_G_GAMMA: tl.constexpr,
67
+ IS_VARLEN: tl.constexpr,
68
+ ):
69
+ i_v, i_t, i_bh = tl.program_id(0), tl.program_id(1), tl.program_id(2)
70
+ i_b, i_h = i_bh // H, i_bh % H
71
+
72
+ if IS_VARLEN:
73
+ i_tg = i_t
74
+ i_n, i_t = tl.load(chunk_indices + i_t * 2).to(tl.int32), tl.load(
75
+ chunk_indices + i_t * 2 + 1
76
+ ).to(tl.int32)
77
+ bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(
78
+ cu_seqlens + i_n + 1
79
+ ).to(tl.int32)
80
+ T = eos - bos
81
+ NT = tl.cdiv(T, BT)
82
+ else:
83
+ NT = tl.cdiv(T, BT)
84
+ i_tg = i_b * NT + i_t
85
+ bos, eos = i_b * T, i_b * T + T
86
+
87
+ # offset calculation
88
+ q += (bos * H + i_h) * K
89
+ k += (bos * H + i_h) * K
90
+ v += (bos * H + i_h) * V
91
+ o += (bos * H + i_h) * V
92
+ h += (i_tg * H + i_h).to(tl.int64) * K * V
93
+
94
+ b_o = tl.zeros([BT, BV], dtype=tl.float32)
95
+ b_A = tl.zeros([BT, BT], dtype=tl.float32)
96
+
97
+ for i_k in range(tl.cdiv(K, BK)):
98
+ p_q = tl.make_block_ptr(
99
+ q, (T, K), (H * K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)
100
+ )
101
+ p_k = tl.make_block_ptr(
102
+ k, (K, T), (1, H * K), (i_k * BK, i_t * BT), (BK, BT), (0, 1)
103
+ )
104
+ p_h = tl.make_block_ptr(
105
+ h, (K, V), (V, 1), (i_k * BK, i_v * BV), (BK, BV), (1, 0)
106
+ )
107
+ # [BT, BK]
108
+ b_q = tl.load(p_q, boundary_check=(0, 1))
109
+ # [BK, BT]
110
+ b_k = tl.load(p_k, boundary_check=(0, 1))
111
+ # [BK, BV]
112
+ b_h = tl.load(p_h, boundary_check=(0, 1))
113
+
114
+ # [BT, BK] @ [BK, BV] -> [BT, BV]
115
+ b_o = tl.dot(b_q, b_h, acc=b_o)
116
+ # [BT, BK] @ [BK, BT] -> [BT, BT]
117
+ b_A = tl.dot(b_q, b_k, acc=b_A)
118
+
119
+ if USE_G:
120
+ g += bos * H + i_h
121
+ p_g = tl.make_block_ptr(g, (T,), (H,), (i_t * BT,), (BT,), (0,))
122
+ b_g = tl.load(p_g, boundary_check=(0,))
123
+ b_o = b_o * exp(b_g)[:, None]
124
+ b_A = b_A * exp(b_g[:, None] - b_g[None, :])
125
+
126
+ if USE_G_GAMMA:
127
+ b_gamma = tl.load(g_gamma + i_h)
128
+ b_g = b_gamma * (tl.arange(0, BT) + 1)
129
+ b_o = b_o * exp(b_g)[:, None]
130
+ b_A = b_A * exp(b_g[:, None] - b_g[None, :])
131
+
132
+ o_t = i_t * BT + tl.arange(0, BT)
133
+ m_t = o_t < T
134
+ m_A = (o_t[:, None] >= o_t[None, :]) & (m_t[:, None] & m_t)
135
+ b_A = tl.where(m_A, b_A, 0)
136
+
137
+ p_v = tl.make_block_ptr(
138
+ v, (T, V), (H * V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)
139
+ )
140
+ p_o = tl.make_block_ptr(
141
+ o, (T, V), (H * V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)
142
+ )
143
+
144
+ b_v = tl.load(p_v, boundary_check=(0, 1))
145
+ # to fix mma -> mma layout conversion
146
+ # already solved by triton v3.2 or higher
147
+ b_o = b_o * scale + tl.dot(b_A.to(b_v.dtype), b_v) * scale
148
+ tl.store(p_o, b_o.to(p_o.dtype.element_ty), boundary_check=(0, 1))
149
+
150
+
151
+ @triton.heuristics(
152
+ {
153
+ "USE_G": lambda args: args["g"] is not None,
154
+ "USE_G_GAMMA": lambda args: args["g_gamma"] is not None,
155
+ "USE_DW": lambda args: args["dw"] is not None,
156
+ "IS_VARLEN": lambda args: args["cu_seqlens"] is not None,
157
+ }
158
+ )
159
+ @triton.autotune(
160
+ configs=gated_delta_rule_autotune_configs(
161
+ [
162
+ triton.Config({}, num_warps=num_warps, num_stages=num_stages)
163
+ for num_warps in NUM_WARPS
164
+ for num_stages in [2, 3, 4]
165
+ ]
166
+ ),
167
+ key=["H", "K", "V", "BT", "BK", "BV", "USE_G", "USE_G_GAMMA", "USE_DW"],
168
+ **autotune_cache_kwargs,
169
+ )
170
+ @triton.jit(do_not_specialize=["T"])
171
+ def chunk_bwd_kernel_dqkwg(
172
+ q,
173
+ k,
174
+ v,
175
+ g,
176
+ g_gamma,
177
+ h,
178
+ do,
179
+ dh,
180
+ dq,
181
+ dk,
182
+ dw,
183
+ dv,
184
+ dg,
185
+ cu_seqlens,
186
+ chunk_indices,
187
+ scale,
188
+ B: tl.constexpr,
189
+ T,
190
+ H: tl.constexpr,
191
+ K: tl.constexpr,
192
+ V: tl.constexpr,
193
+ BT: tl.constexpr,
194
+ BK: tl.constexpr,
195
+ BV: tl.constexpr,
196
+ USE_G: tl.constexpr,
197
+ USE_G_GAMMA: tl.constexpr,
198
+ USE_DW: tl.constexpr,
199
+ IS_VARLEN: tl.constexpr,
200
+ ):
201
+ i_k, i_t, i_bh = tl.program_id(0), tl.program_id(1), tl.program_id(2)
202
+ i_b, i_h = i_bh // H, i_bh % H
203
+
204
+ all = B * T
205
+ if IS_VARLEN:
206
+ i_tg = i_t
207
+ i_n, i_t = tl.load(chunk_indices + i_t * 2).to(tl.int32), tl.load(
208
+ chunk_indices + i_t * 2 + 1
209
+ ).to(tl.int32)
210
+ bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(
211
+ cu_seqlens + i_n + 1
212
+ ).to(tl.int32)
213
+ T = eos - bos
214
+ NT = tl.cdiv(T, BT)
215
+ else:
216
+ NT = tl.cdiv(T, BT)
217
+ i_tg = i_b * NT + i_t
218
+ bos, eos = i_b * T, i_b * T + T
219
+
220
+ # offset calculation
221
+ v += (bos * H + i_h) * V
222
+ do += (bos * H + i_h) * V
223
+ h += (i_tg * H + i_h).to(tl.int64) * K * V
224
+ dh += (i_tg * H + i_h).to(tl.int64) * K * V
225
+ q += (bos * H + i_h) * K
226
+ k += (bos * H + i_h) * K
227
+ dq += (bos * H + i_h) * K
228
+ dk += (bos * H + i_h) * K
229
+
230
+ # for delta rule only
231
+ if USE_DW:
232
+ dw += (bos * H + i_h) * K
233
+ dv += (bos * H + i_h) * V
234
+
235
+ if USE_G:
236
+ dg += i_k * all * H
237
+ b_dg_last = tl.zeros([1], dtype=tl.float32) if USE_G else None
238
+ if USE_G_GAMMA:
239
+ b_gamma = tl.load(g_gamma + i_h)
240
+ b_g = b_gamma * (tl.arange(0, BT) + 1)
241
+ b_g_last = b_gamma * min(BT, T - i_t * BT)
242
+ b_dq = tl.zeros([BT, BK], dtype=tl.float32)
243
+ b_dk = tl.zeros([BT, BK], dtype=tl.float32)
244
+ b_ds = tl.zeros([BT, BT], dtype=tl.float32)
245
+ b_dw = tl.zeros([BT, BK], dtype=tl.float32) if USE_DW else None
246
+
247
+ for i_v in range(tl.cdiv(V, BV)):
248
+ p_v = tl.make_block_ptr(
249
+ v, (T, V), (H * V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)
250
+ )
251
+ p_do = tl.make_block_ptr(
252
+ do, (T, V), (H * V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)
253
+ )
254
+ p_h = tl.make_block_ptr(
255
+ h, (V, K), (1, V), (i_v * BV, i_k * BK), (BV, BK), (0, 1)
256
+ )
257
+ p_dh = tl.make_block_ptr(
258
+ dh, (V, K), (1, V), (i_v * BV, i_k * BK), (BV, BK), (0, 1)
259
+ )
260
+ # [BT, BV]
261
+ b_v = tl.load(p_v, boundary_check=(0, 1))
262
+ b_do = tl.load(p_do, boundary_check=(0, 1))
263
+ # [BV, BK]
264
+ b_h = tl.load(p_h, boundary_check=(0, 1))
265
+ b_dh = tl.load(p_dh, boundary_check=(0, 1))
266
+ if USE_G:
267
+ b_dg_last += tl.sum(b_h * b_dh)
268
+ # [BT, BV] @ [BV, BT] -> [BT, BT]
269
+ b_ds = tl.dot(b_do, tl.trans(b_v), acc=b_ds)
270
+ # [BT, BV] @ [BV, BK] -> [BT, BK]
271
+ b_dq = tl.dot(b_do, b_h.to(b_do.dtype), acc=b_dq)
272
+ # [BT, BV] @ [BV, BK] -> [BT, BK]
273
+ b_dk = tl.dot(b_v, b_dh.to(b_v.dtype), acc=b_dk)
274
+ if USE_DW:
275
+ p_dv = tl.make_block_ptr(
276
+ dv, (T, V), (H * V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)
277
+ )
278
+ b_dv = tl.load(p_dv, boundary_check=(0, 1))
279
+ b_dw = tl.dot(b_dv.to(b_v.dtype), b_h.to(b_v.dtype), acc=b_dw)
280
+
281
+ if USE_DW:
282
+ p_dw = tl.make_block_ptr(
283
+ dw, (T, K), (H * K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)
284
+ )
285
+ tl.store(p_dw, -b_dw.to(p_dw.dtype.element_ty), boundary_check=(0, 1))
286
+
287
+ tl.debug_barrier()
288
+ p_q = tl.make_block_ptr(
289
+ q, (T, K), (H * K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)
290
+ )
291
+ p_k = tl.make_block_ptr(
292
+ k, (T, K), (H * K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)
293
+ )
294
+ b_q = tl.load(p_q, boundary_check=(0, 1))
295
+ b_k = tl.load(p_k, boundary_check=(0, 1))
296
+
297
+ p_dq = tl.make_block_ptr(
298
+ dq, (T, K), (H * K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)
299
+ )
300
+ p_dk = tl.make_block_ptr(
301
+ dk, (T, K), (H * K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)
302
+ )
303
+
304
+ o_t = i_t * BT + tl.arange(0, BT)
305
+ m_t = o_t < T
306
+ m_A = (o_t[:, None] >= o_t[None, :]) & (m_t[:, None] & m_t)
307
+ if USE_G:
308
+ b_dg = tl.zeros([BT], dtype=tl.float32)
309
+ g += bos * H + i_h
310
+ dg += bos * H + i_h
311
+ p_g = tl.make_block_ptr(g, (T,), (H,), (i_t * BT,), (BT,), (0,))
312
+ b_g = tl.load(p_g, boundary_check=(0,))
313
+ b_g_last = tl.load(g + (min(i_t * BT + BT, T) - 1) * H)
314
+ b_dg_last *= exp(b_g_last)
315
+
316
+ b_dq = b_dq * exp(b_g)[:, None] * scale
317
+ b_dg += tl.sum(b_dq * b_q, axis=1)
318
+
319
+ b_dk = b_dk * tl.where(m_t, exp(-b_g + b_g_last), 0)[:, None]
320
+ b_dg -= tl.sum(b_k * b_dk, axis=1)
321
+ b_dg_last += tl.sum(b_dk * b_k)
322
+
323
+ b_ds = tl.where(m_A, b_ds * exp(b_g[:, None] - b_g[None, :]), 0) * scale
324
+ b_ds2 = b_ds * tl.dot(b_q, tl.trans(b_k))
325
+ b_dg += tl.sum(b_ds2, axis=1)
326
+ b_dg -= tl.sum(b_ds2, axis=0)
327
+
328
+ b_ds = b_ds.to(b_k.dtype)
329
+ # [BT, BK]
330
+ b_dq = tl.dot(b_ds, b_k, acc=b_dq)
331
+ b_dk = tl.dot(tl.trans(b_ds), b_q, acc=b_dk)
332
+ p_dg = tl.make_block_ptr(dg, (T,), (H,), (i_t * BT,), (BT,), (0,))
333
+ # (SY 09/21) revcumsum in a separate kernel due to strange triton compiler issue
334
+ # b_dg = tl.dot(tl.where(o_t[:, None] <= o_t[None, :], 1., 0.), b_dg, allow_tf32=False) + b_dg_last)
335
+ b_dg = tl.where(o_t < min(i_t * BT + BT, T) - 1, b_dg, b_dg + b_dg_last)
336
+ tl.store(p_dq, b_dq.to(p_dq.dtype.element_ty), boundary_check=(0, 1))
337
+ tl.store(p_dk, b_dk.to(p_dk.dtype.element_ty), boundary_check=(0, 1))
338
+ tl.store(p_dg, b_dg.to(p_dg.dtype.element_ty), boundary_check=(0,))
339
+
340
+ elif USE_G_GAMMA:
341
+ b_dq = b_dq * exp(b_g)[:, None] * scale
342
+ b_dk = b_dk * tl.where(m_t, exp(-b_g + b_g_last), 0)[:, None]
343
+ b_ds = tl.where(m_A, b_ds * exp(b_g[:, None] - b_g[None, :]), 0) * scale
344
+ b_ds = b_ds.to(b_k.dtype)
345
+ # [BT, BK]
346
+ b_dq = tl.dot(b_ds, b_k, acc=b_dq)
347
+ b_dk = tl.dot(tl.trans(b_ds), b_q, acc=b_dk)
348
+ tl.store(p_dq, b_dq.to(p_dq.dtype.element_ty), boundary_check=(0, 1))
349
+ tl.store(p_dk, b_dk.to(p_dk.dtype.element_ty), boundary_check=(0, 1))
350
+
351
+ else:
352
+ b_ds = tl.where(m_A, b_ds, 0)
353
+ b_ds = b_ds.to(b_k.dtype)
354
+ b_dq = tl.dot(b_ds, b_k, acc=b_dq)
355
+ b_dk += tl.dot(tl.trans(b_ds), b_q) * scale
356
+ b_dq *= scale
357
+ tl.store(p_dq, b_dq.to(p_dq.dtype.element_ty), boundary_check=(0, 1))
358
+ tl.store(p_dk, b_dk.to(p_dk.dtype.element_ty), boundary_check=(0, 1))
359
+
360
+
361
+ @triton.heuristics(
362
+ {
363
+ "USE_G": lambda args: args["g"] is not None,
364
+ "USE_G_GAMMA": lambda args: args["g_gamma"] is not None,
365
+ "IS_VARLEN": lambda args: args["cu_seqlens"] is not None,
366
+ }
367
+ )
368
+ @triton.autotune(
369
+ configs=gated_delta_rule_autotune_configs(
370
+ [
371
+ triton.Config({}, num_warps=num_warps, num_stages=num_stages)
372
+ for num_warps in NUM_WARPS
373
+ for num_stages in [2, 3, 4]
374
+ ]
375
+ ),
376
+ key=["H", "K", "V", "BT", "BK", "BV", "USE_G", "USE_G_GAMMA"],
377
+ **autotune_cache_kwargs,
378
+ )
379
+ @triton.jit(do_not_specialize=["T"])
380
+ def chunk_bwd_kernel_dv(
381
+ q,
382
+ k,
383
+ g,
384
+ g_gamma,
385
+ do,
386
+ dv,
387
+ dh,
388
+ cu_seqlens,
389
+ chunk_indices,
390
+ scale,
391
+ T,
392
+ H: tl.constexpr,
393
+ K: tl.constexpr,
394
+ V: tl.constexpr,
395
+ BT: tl.constexpr,
396
+ BK: tl.constexpr,
397
+ BV: tl.constexpr,
398
+ USE_G: tl.constexpr,
399
+ USE_G_GAMMA: tl.constexpr,
400
+ IS_VARLEN: tl.constexpr,
401
+ ):
402
+ i_v, i_t, i_bh = tl.program_id(0), tl.program_id(1), tl.program_id(2)
403
+ i_b, i_h = i_bh // H, i_bh % H
404
+ if IS_VARLEN:
405
+ i_tg = i_t
406
+ i_n, i_t = tl.load(chunk_indices + i_t * 2).to(tl.int32), tl.load(
407
+ chunk_indices + i_t * 2 + 1
408
+ ).to(tl.int32)
409
+ bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(
410
+ cu_seqlens + i_n + 1
411
+ ).to(tl.int32)
412
+ T = eos - bos
413
+ NT = tl.cdiv(T, BT)
414
+ else:
415
+ NT = tl.cdiv(T, BT)
416
+ i_tg = i_b * NT + i_t
417
+ bos, eos = i_b * T, i_b * T + T
418
+
419
+ b_dv = tl.zeros([BT, BV], dtype=tl.float32)
420
+
421
+ # offset calculation
422
+ q += (bos * H + i_h) * K
423
+ k += (bos * H + i_h) * K
424
+ do += (bos * H + i_h) * V
425
+ dv += (bos * H + i_h) * V
426
+ dh += (i_tg * H + i_h).to(tl.int64) * K * V
427
+
428
+ b_A = tl.zeros([BT, BT], dtype=tl.float32)
429
+ for i_k in range(tl.cdiv(K, BK)):
430
+ p_k = tl.make_block_ptr(
431
+ k, (T, K), (H * K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)
432
+ )
433
+ p_q = tl.make_block_ptr(
434
+ q, (K, T), (1, H * K), (i_k * BK, i_t * BT), (BK, BT), (0, 1)
435
+ )
436
+ b_q = tl.load(p_q, boundary_check=(0, 1))
437
+ b_k = tl.load(p_k, boundary_check=(0, 1))
438
+ b_A = tl.dot(b_k, b_q, acc=b_A)
439
+ p_dh = tl.make_block_ptr(
440
+ dh, (K, V), (V, 1), (i_k * BK, i_v * BV), (BK, BV), (1, 0)
441
+ )
442
+ b_dh = tl.load(p_dh, boundary_check=(0, 1))
443
+ b_dv = tl.dot(b_k, b_dh.to(b_k.dtype), acc=b_dv)
444
+
445
+ o_t = i_t * BT + tl.arange(0, BT)
446
+ m_t = o_t < T
447
+ if USE_G:
448
+ g += bos * H + i_h
449
+ p_g = tl.make_block_ptr(g, (T,), (H,), (i_t * BT,), (BT,), (0,))
450
+ b_g = tl.load(p_g, boundary_check=(0,))
451
+ b_g_last = tl.load(g + (min(i_t * BT + BT, T) - 1) * H)
452
+ if USE_G_GAMMA:
453
+ b_gamma = tl.load(g_gamma + i_h)
454
+ b_g = b_gamma * (tl.arange(0, BT) + 1)
455
+ b_g_last = b_gamma * min(BT, T - i_t * BT)
456
+
457
+ m_A = (o_t[:, None] <= o_t[None, :]) & (m_t[:, None] & m_t)
458
+ if USE_G or USE_G_GAMMA:
459
+ b_A = tl.where(m_A, b_A * exp(b_g[None, :] - b_g[:, None]) * scale, 0).to(
460
+ do.dtype.element_ty
461
+ )
462
+ b_dv *= tl.where(m_t, exp(-b_g + b_g_last), 0)[:, None]
463
+ else:
464
+ b_A = tl.where(m_A, b_A * scale, 0).to(do.dtype.element_ty)
465
+ p_do = tl.make_block_ptr(
466
+ do, (T, V), (H * V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)
467
+ )
468
+ p_dv = tl.make_block_ptr(
469
+ dv, (T, V), (H * V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)
470
+ )
471
+ b_do = tl.load(p_do, boundary_check=(0, 1))
472
+ b_dv = tl.dot(b_A.to(b_do.dtype), b_do, acc=b_dv)
473
+ tl.store(p_dv, b_dv.to(p_dv.dtype.element_ty), boundary_check=(0, 1))
474
+
475
+
476
+ @triton.heuristics(
477
+ {
478
+ "USE_G": lambda args: args["g"] is not None,
479
+ "USE_G_GAMMA": lambda args: args["g_gamma"] is not None,
480
+ "USE_A": lambda args: args["A"] is not None,
481
+ "IS_VARLEN": lambda args: args["cu_seqlens"] is not None,
482
+ }
483
+ )
484
+ @triton.autotune(
485
+ configs=gated_delta_rule_autotune_configs(
486
+ [
487
+ triton.Config({}, num_warps=num_warps, num_stages=num_stages)
488
+ for num_warps in NUM_WARPS
489
+ for num_stages in [2, 3, 4]
490
+ ]
491
+ ),
492
+ key=["H", "K", "V", "BT", "BK", "BV", "USE_G"],
493
+ **autotune_cache_kwargs,
494
+ )
495
+ @triton.jit(do_not_specialize=["T"])
496
+ def chunk_bwd_kernel_dv_local(
497
+ q,
498
+ k,
499
+ g,
500
+ g_gamma,
501
+ A,
502
+ do,
503
+ dv,
504
+ cu_seqlens,
505
+ chunk_indices,
506
+ scale,
507
+ T,
508
+ H: tl.constexpr,
509
+ K: tl.constexpr,
510
+ V: tl.constexpr,
511
+ BT: tl.constexpr,
512
+ BK: tl.constexpr,
513
+ BV: tl.constexpr,
514
+ USE_G: tl.constexpr,
515
+ USE_G_GAMMA: tl.constexpr,
516
+ USE_A: tl.constexpr,
517
+ IS_VARLEN: tl.constexpr,
518
+ ):
519
+ i_t, i_bh = tl.program_id(0), tl.program_id(1)
520
+ i_b, i_h = i_bh // H, i_bh % H
521
+ if IS_VARLEN:
522
+ i_n, i_t = tl.load(chunk_indices + i_t * 2).to(tl.int32), tl.load(
523
+ chunk_indices + i_t * 2 + 1
524
+ ).to(tl.int32)
525
+ bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(
526
+ cu_seqlens + i_n + 1
527
+ ).to(tl.int32)
528
+ T = eos - bos
529
+ else:
530
+ bos, eos = i_b * T, i_b * T + T
531
+
532
+ # offset calculation
533
+ q += (bos * H + i_h) * K
534
+ k += (bos * H + i_h) * K
535
+ do += (bos * H + i_h) * V
536
+ dv += (bos * H + i_h) * V
537
+
538
+ if USE_A:
539
+ p_A = tl.make_block_ptr(
540
+ A + (bos * H + i_h) * BT,
541
+ (BT, T),
542
+ (1, H * BT),
543
+ (0, i_t * BT),
544
+ (BT, BT),
545
+ (0, 1),
546
+ )
547
+ b_A = tl.load(p_A, boundary_check=(0, 1))
548
+ else:
549
+ if USE_G:
550
+ g += bos * H + i_h
551
+ p_g = tl.make_block_ptr(g, (T,), (H,), (i_t * BT,), (BT,), (0,))
552
+ b_g = tl.load(p_g, boundary_check=(0,))
553
+ if USE_G_GAMMA:
554
+ b_gamma = tl.load(g_gamma + i_h)
555
+ b_g = b_gamma * (tl.arange(0, BT) + 1)
556
+
557
+ b_A = tl.zeros([BT, BT], dtype=tl.float32)
558
+ for i_k in range(tl.cdiv(K, BK)):
559
+ p_k = tl.make_block_ptr(
560
+ k, (T, K), (H * K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)
561
+ )
562
+ p_q = tl.make_block_ptr(
563
+ q, (K, T), (1, H * K), (i_k * BK, i_t * BT), (BK, BT), (0, 1)
564
+ )
565
+
566
+ b_k = tl.load(p_k, boundary_check=(0, 1))
567
+ b_q = tl.load(p_q, boundary_check=(0, 1))
568
+ b_A += tl.dot(b_k, b_q) * scale
569
+ if USE_G or USE_G_GAMMA:
570
+ b_A *= exp(b_g[None, :] - b_g[:, None])
571
+
572
+ o_t = i_t * BT + tl.arange(0, BT)
573
+ m_t = o_t < T
574
+ m_A = (o_t[:, None] <= o_t[None, :]) & (m_t[:, None] & m_t)
575
+ b_A = tl.where(m_A, b_A, 0).to(do.dtype.element_ty)
576
+
577
+ for i_v in range(tl.cdiv(V, BV)):
578
+ p_do = tl.make_block_ptr(
579
+ do, (T, V), (H * V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)
580
+ )
581
+ p_dv = tl.make_block_ptr(
582
+ dv, (T, V), (H * V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)
583
+ )
584
+ b_do = tl.load(p_do, boundary_check=(0, 1))
585
+ b_dv = tl.dot(b_A.to(b_do.dtype), b_do)
586
+ tl.store(p_dv, b_dv.to(p_dv.dtype.element_ty), boundary_check=(0, 1))
587
+
588
+
589
+ def chunk_fwd_o(
590
+ q: torch.Tensor,
591
+ k: torch.Tensor,
592
+ v: torch.Tensor,
593
+ h: torch.Tensor,
594
+ g: torch.Tensor | None = None,
595
+ g_gamma: torch.Tensor | None = None,
596
+ scale: float | None = None,
597
+ cu_seqlens: torch.LongTensor | None = None,
598
+ chunk_size: int = 64,
599
+ ) -> torch.Tensor:
600
+ B, T, H, K, V = *q.shape, v.shape[-1]
601
+ BT = chunk_size
602
+ chunk_indices = (
603
+ prepare_chunk_indices(cu_seqlens, BT) if cu_seqlens is not None else None
604
+ )
605
+ NT = triton.cdiv(T, BT) if cu_seqlens is None else len(chunk_indices)
606
+ if scale is None:
607
+ scale = k.shape[-1] ** -0.5
608
+
609
+ o = torch.empty_like(v)
610
+
611
+ def grid(meta):
612
+ return (triton.cdiv(V, meta["BV"]), NT, B * H)
613
+
614
+ chunk_fwd_kernel_o[grid](
615
+ q=q,
616
+ k=k,
617
+ v=v,
618
+ h=h,
619
+ g=g,
620
+ g_gamma=g_gamma,
621
+ o=o,
622
+ cu_seqlens=cu_seqlens,
623
+ chunk_indices=chunk_indices,
624
+ scale=scale,
625
+ T=T,
626
+ H=H,
627
+ K=K,
628
+ V=V,
629
+ BT=BT,
630
+ )
631
+ return o
632
+
633
+
634
+ @triton.heuristics(
635
+ {
636
+ "USE_G": lambda args: args["g"] is not None,
637
+ "IS_VARLEN": lambda args: args["cu_seqlens"] is not None,
638
+ }
639
+ )
640
+ @triton.autotune(
641
+ configs=gated_delta_rule_autotune_configs(
642
+ [
643
+ triton.Config(
644
+ {"BK": BK, "BV": BV}, num_warps=num_warps, num_stages=num_stages
645
+ )
646
+ for BK in BKV_LIST
647
+ for BV in BKV_LIST
648
+ for num_warps in NUM_WARPS
649
+ for num_stages in [2, 3, 4]
650
+ ]
651
+ ),
652
+ key=["H", "K", "V", "BT", "IS_VARLEN"],
653
+ **autotune_cache_kwargs,
654
+ )
655
+ @triton.jit(do_not_specialize=["T", "T_flat"])
656
+ def chunk_fwd_kernel_o_opt(
657
+ q,
658
+ k,
659
+ v,
660
+ h,
661
+ g,
662
+ o,
663
+ cu_seqlens,
664
+ chunk_indices,
665
+ scale,
666
+ T,
667
+ T_flat,
668
+ H: tl.constexpr,
669
+ Hg: tl.constexpr,
670
+ K: tl.constexpr,
671
+ V: tl.constexpr,
672
+ BT: tl.constexpr,
673
+ BK: tl.constexpr,
674
+ BV: tl.constexpr,
675
+ USE_G: tl.constexpr,
676
+ IS_VARLEN: tl.constexpr,
677
+ ):
678
+ i_v, i_t, i_bh = tl.program_id(0), tl.program_id(1), tl.program_id(2)
679
+ i_b, i_h = i_bh // H, i_bh % H
680
+
681
+ if IS_VARLEN:
682
+ i_tg = i_t
683
+ i_n, i_t = (
684
+ tl.load(chunk_indices + i_t * 2).to(tl.int32),
685
+ tl.load(chunk_indices + i_t * 2 + 1).to(tl.int32),
686
+ )
687
+ bos, eos = (
688
+ tl.load(cu_seqlens + i_n).to(tl.int32),
689
+ tl.load(cu_seqlens + i_n + 1).to(tl.int32),
690
+ )
691
+ T = eos - bos
692
+ NT = tl.cdiv(T, BT)
693
+ else:
694
+ NT = tl.cdiv(T, BT)
695
+ i_tg = i_b * NT + i_t
696
+ bos = i_b * T
697
+
698
+ q += (bos * Hg + i_h // (H // Hg)) * K
699
+ k += (bos * Hg + i_h // (H // Hg)) * K
700
+ if IS_VARLEN:
701
+ v += ((i_h * T_flat + bos) * V).to(tl.int64)
702
+ o += ((bos * H + i_h) * V).to(tl.int64)
703
+ else:
704
+ v += (((i_b * H + i_h) * T_flat) * V).to(tl.int64)
705
+ o += ((i_b * T * H + i_h) * V).to(tl.int64)
706
+ h += (i_tg * H + i_h).to(tl.int64) * K * V
707
+
708
+ b_o = tl.zeros([BT, BV], dtype=tl.float32)
709
+ b_A = tl.zeros([BT, BT], dtype=tl.float32)
710
+
711
+ for i_k in range(tl.cdiv(K, BK)):
712
+ p_q = tl.make_block_ptr(
713
+ q, (T, K), (Hg * K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)
714
+ )
715
+ p_k = tl.make_block_ptr(
716
+ k, (K, T), (1, Hg * K), (i_k * BK, i_t * BT), (BK, BT), (0, 1)
717
+ )
718
+ p_h = tl.make_block_ptr(
719
+ h, (K, V), (V, 1), (i_k * BK, i_v * BV), (BK, BV), (1, 0)
720
+ )
721
+ b_q = tl.load(p_q, boundary_check=(0, 1))
722
+ b_k = tl.load(p_k, boundary_check=(0, 1))
723
+ b_h = tl.load(p_h, boundary_check=(0, 1))
724
+
725
+ b_o = tl.dot(b_q, b_h, acc=b_o)
726
+ b_A = tl.dot(b_q, b_k, acc=b_A)
727
+
728
+ if USE_G:
729
+ g += bos * H + i_h
730
+ p_g = tl.make_block_ptr(g, (T,), (H,), (i_t * BT,), (BT,), (0,))
731
+ b_g = tl.load(p_g, boundary_check=(0,))
732
+ b_o = b_o * exp(b_g)[:, None]
733
+ b_A = b_A * exp(b_g[:, None] - b_g[None, :])
734
+
735
+ o_t = i_t * BT + tl.arange(0, BT)
736
+ m_t = o_t < T
737
+ m_A = (o_t[:, None] >= o_t[None, :]) & (m_t[:, None] & m_t)
738
+ b_A = tl.where(m_A, b_A, 0)
739
+
740
+ p_v = tl.make_block_ptr(v, (T, V), (V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0))
741
+ p_o = tl.make_block_ptr(
742
+ o, (T, V), (H * V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)
743
+ )
744
+ b_v = tl.load(p_v, boundary_check=(0, 1))
745
+
746
+ b_o = b_o * scale + tl.dot(b_A.to(b_v.dtype), b_v) * scale
747
+ tl.store(p_o, b_o.to(p_o.dtype.element_ty), boundary_check=(0, 1))
748
+
749
+
750
+ def chunk_fwd_o_opt(
751
+ q: torch.Tensor,
752
+ k: torch.Tensor,
753
+ v: torch.Tensor,
754
+ h: torch.Tensor,
755
+ g: torch.Tensor | None = None,
756
+ scale: float | None = None,
757
+ cu_seqlens: torch.LongTensor | None = None,
758
+ chunk_size: int = 64,
759
+ ) -> torch.Tensor:
760
+ """
761
+ Optimized output forward with transposed v layout and Hg-aware q/k strides.
762
+
763
+ Args:
764
+ q: [B, T, Hg, K]
765
+ k: [B, T, Hg, K]
766
+ v: [B, H, T, V]
767
+ h: [B, NT, H, K, V]
768
+ g: [B*T, H] FP32
769
+ scale: float
770
+ cu_seqlens: [N+1]
771
+ chunk_size: int
772
+
773
+ Returns:
774
+ o: [B, T, H, V]
775
+ """
776
+ B, T, Hg, K = q.shape
777
+ H = v.shape[1]
778
+ T_flat = v.shape[2]
779
+ V = v.shape[-1]
780
+ BT = chunk_size
781
+ chunk_indices = (
782
+ prepare_chunk_indices(cu_seqlens, BT) if cu_seqlens is not None else None
783
+ )
784
+ NT = triton.cdiv(T, BT) if cu_seqlens is None else len(chunk_indices)
785
+ if scale is None:
786
+ scale = k.shape[-1] ** -0.5
787
+
788
+ o = v.new_empty(B, T, H, V)
789
+
790
+ def grid(meta):
791
+ return (triton.cdiv(V, meta["BV"]), NT, B * H)
792
+
793
+ chunk_fwd_kernel_o_opt[grid](
794
+ q=q,
795
+ k=k,
796
+ v=v,
797
+ h=h,
798
+ g=g,
799
+ o=o,
800
+ cu_seqlens=cu_seqlens,
801
+ chunk_indices=chunk_indices,
802
+ scale=scale,
803
+ T=T,
804
+ T_flat=T_flat,
805
+ H=H,
806
+ Hg=Hg,
807
+ K=K,
808
+ V=V,
809
+ BT=BT,
810
+ )
811
+ return o
812
+
813
+
814
+ # =====================================================================
815
+ # opt_vk variant: h layout [V, K] (transposed from opt's [K, V])
816
+ # All other layouts identical to opt.
817
+ # =====================================================================
818
+
819
+
820
+ @triton.heuristics(
821
+ {
822
+ "USE_G": lambda args: args["g"] is not None,
823
+ "IS_VARLEN": lambda args: args["cu_seqlens"] is not None,
824
+ }
825
+ )
826
+ @triton.autotune(
827
+ configs=gated_delta_rule_autotune_configs(
828
+ [
829
+ triton.Config(
830
+ {"BK": BK, "BV": BV}, num_warps=num_warps, num_stages=num_stages
831
+ )
832
+ for BK in BKV_LIST
833
+ for BV in BKV_LIST
834
+ for num_warps in NUM_WARPS
835
+ for num_stages in [2, 3, 4]
836
+ ]
837
+ ),
838
+ key=["H", "K", "V", "BT", "IS_VARLEN"],
839
+ **autotune_cache_kwargs,
840
+ )
841
+ @triton.jit(do_not_specialize=["T", "T_flat"])
842
+ def chunk_fwd_kernel_o_opt_vk(
843
+ q,
844
+ k,
845
+ v,
846
+ h,
847
+ g,
848
+ o,
849
+ cu_seqlens,
850
+ chunk_indices,
851
+ scale,
852
+ T,
853
+ T_flat,
854
+ H: tl.constexpr,
855
+ Hg: tl.constexpr,
856
+ K: tl.constexpr,
857
+ V: tl.constexpr,
858
+ BT: tl.constexpr,
859
+ BK: tl.constexpr,
860
+ BV: tl.constexpr,
861
+ USE_G: tl.constexpr,
862
+ IS_VARLEN: tl.constexpr,
863
+ USE_EXP2: tl.constexpr = False,
864
+ ):
865
+ i_v, i_t, i_bh = tl.program_id(0), tl.program_id(1), tl.program_id(2)
866
+ i_b, i_h = i_bh // H, i_bh % H
867
+
868
+ if IS_VARLEN:
869
+ i_tg = i_t
870
+ i_n, i_t = (
871
+ tl.load(chunk_indices + i_t * 2).to(tl.int32),
872
+ tl.load(chunk_indices + i_t * 2 + 1).to(tl.int32),
873
+ )
874
+ bos, eos = (
875
+ tl.load(cu_seqlens + i_n).to(tl.int32),
876
+ tl.load(cu_seqlens + i_n + 1).to(tl.int32),
877
+ )
878
+ T = eos - bos
879
+ NT = tl.cdiv(T, BT)
880
+ else:
881
+ NT = tl.cdiv(T, BT)
882
+ i_tg = i_b * NT + i_t
883
+ bos = i_b * T
884
+
885
+ q += (bos * Hg + i_h // (H // Hg)) * K
886
+ k += (bos * Hg + i_h // (H // Hg)) * K
887
+ if IS_VARLEN:
888
+ v += ((i_h * T_flat + bos) * V).to(tl.int64)
889
+ o += ((bos * H + i_h) * V).to(tl.int64)
890
+ else:
891
+ v += (((i_b * H + i_h) * T_flat) * V).to(tl.int64)
892
+ o += ((i_b * T * H + i_h) * V).to(tl.int64)
893
+ h += (i_tg * H + i_h).to(tl.int64) * V * K
894
+
895
+ if USE_G:
896
+ if IS_VARLEN:
897
+ g += (i_h * T_flat + bos).to(tl.int64)
898
+ else:
899
+ g += (((i_b * H + i_h) * T_flat)).to(tl.int64)
900
+
901
+ b_o = tl.zeros([BT, BV], dtype=tl.float32)
902
+ b_A = tl.zeros([BT, BT], dtype=tl.float32)
903
+
904
+ for i_k in range(tl.cdiv(K, BK)):
905
+ p_q = tl.make_block_ptr(
906
+ q, (T, K), (Hg * K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)
907
+ )
908
+ p_k = tl.make_block_ptr(
909
+ k, (K, T), (1, Hg * K), (i_k * BK, i_t * BT), (BK, BT), (0, 1)
910
+ )
911
+ p_h = tl.make_block_ptr(
912
+ h, (V, K), (K, 1), (i_v * BV, i_k * BK), (BV, BK), (1, 0)
913
+ )
914
+ b_q = tl.load(p_q, boundary_check=(0, 1))
915
+ b_k = tl.load(p_k, boundary_check=(0, 1))
916
+ b_h = tl.load(p_h, boundary_check=(0, 1))
917
+
918
+ b_o = tl.dot(b_q, tl.trans(b_h), acc=b_o)
919
+ b_A = tl.dot(b_q, b_k, acc=b_A)
920
+
921
+ if USE_G:
922
+ p_g = tl.make_block_ptr(g, (T,), (1,), (i_t * BT,), (BT,), (0,))
923
+ b_g = tl.load(p_g, boundary_check=(0,))
924
+ if USE_EXP2:
925
+ b_o = b_o * tl.math.exp2(b_g)[:, None]
926
+ b_A = b_A * tl.math.exp2(b_g[:, None] - b_g[None, :])
927
+ else:
928
+ b_o = b_o * exp(b_g)[:, None]
929
+ b_A = b_A * exp(b_g[:, None] - b_g[None, :])
930
+
931
+ o_t = i_t * BT + tl.arange(0, BT)
932
+ m_t = o_t < T
933
+ m_A = (o_t[:, None] >= o_t[None, :]) & (m_t[:, None] & m_t)
934
+ b_A = tl.where(m_A, b_A, 0)
935
+
936
+ p_v = tl.make_block_ptr(v, (T, V), (V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0))
937
+ p_o = tl.make_block_ptr(
938
+ o, (T, V), (H * V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)
939
+ )
940
+ b_v = tl.load(p_v, boundary_check=(0, 1))
941
+
942
+ b_o = b_o * scale + tl.dot(b_A.to(b_v.dtype), b_v) * scale
943
+ tl.store(p_o, b_o.to(p_o.dtype.element_ty), boundary_check=(0, 1))
944
+
945
+
946
+ def chunk_fwd_o_opt_vk(
947
+ q: torch.Tensor,
948
+ k: torch.Tensor,
949
+ v: torch.Tensor,
950
+ o: torch.Tensor,
951
+ h: torch.Tensor,
952
+ g: torch.Tensor | None = None,
953
+ scale: float | None = None,
954
+ cu_seqlens: torch.LongTensor | None = None,
955
+ chunk_size: int = 64,
956
+ use_exp2: bool = True,
957
+ num_decodes: int = 0,
958
+ num_decode_tokens: int = 0,
959
+ ) -> torch.Tensor:
960
+ """
961
+ Optimized output forward with h layout [V, K].
962
+
963
+ Args:
964
+ q: [B, T, Hg, K]
965
+ k: [B, T, Hg, K]
966
+ v: [B, H, T, V] (token-major from opt_vk)
967
+ h: [B, NT, H, V, K] (h layout [V, K])
968
+ g: [B, H, T] FP32 cumulative gate tensor
969
+ scale: float
970
+ cu_seqlens: [N+1]
971
+ chunk_size: int
972
+ use_exp2: when True, interpret g in log2 space
973
+
974
+ Returns:
975
+ o: [B, T, H, V]
976
+ """
977
+ B, T, Hg, K = q.shape
978
+ H = v.shape[1]
979
+ T_flat = v.shape[2]
980
+ V = v.shape[-1]
981
+ BT = chunk_size
982
+ # Chunk indices from the ORIGINAL (cache-stable) cu_seqlens + decode ints
983
+ # (cached, no per-forward D2H); the kernel walks pre-sliced prefill data
984
+ # via the rebased cu_seqlens.
985
+ if cu_seqlens is not None:
986
+ chunk_indices = prepare_chunk_indices(
987
+ cu_seqlens, BT, num_decodes, num_decode_tokens
988
+ )
989
+ kernel_cu_seqlens = prepare_rebased_cu_seqlens(
990
+ cu_seqlens, num_decodes, num_decode_tokens
991
+ )
992
+ else:
993
+ chunk_indices = None
994
+ kernel_cu_seqlens = None
995
+ NT = triton.cdiv(T, BT) if cu_seqlens is None else len(chunk_indices)
996
+ if scale is None:
997
+ scale = k.shape[-1] ** -0.5
998
+
999
+ # o = v.new_empty(B, T, H, V)
1000
+
1001
+ def grid(meta):
1002
+ return (triton.cdiv(V, meta["BV"]), NT, B * H)
1003
+
1004
+ chunk_fwd_kernel_o_opt_vk[grid](
1005
+ q=q,
1006
+ k=k,
1007
+ v=v,
1008
+ h=h,
1009
+ g=g,
1010
+ o=o,
1011
+ cu_seqlens=kernel_cu_seqlens,
1012
+ chunk_indices=chunk_indices,
1013
+ scale=scale,
1014
+ T=T,
1015
+ T_flat=T_flat,
1016
+ H=H,
1017
+ Hg=Hg,
1018
+ K=K,
1019
+ V=V,
1020
+ BT=BT,
1021
+ USE_EXP2=use_exp2,
1022
+ )
1023
+ return o
1024
+
1025
+
1026
+ def chunk_bwd_dv(
1027
+ q: torch.Tensor,
1028
+ k: torch.Tensor,
1029
+ do: torch.Tensor,
1030
+ dh: torch.Tensor,
1031
+ g: torch.Tensor | None = None,
1032
+ g_gamma: torch.Tensor | None = None,
1033
+ scale: float | None = None,
1034
+ cu_seqlens: torch.LongTensor | None = None,
1035
+ chunk_size: int = 64,
1036
+ ) -> torch.Tensor:
1037
+ B, T, H, K, V = *k.shape, do.shape[-1]
1038
+ BT = chunk_size
1039
+ chunk_indices = (
1040
+ prepare_chunk_indices(cu_seqlens, BT) if cu_seqlens is not None else None
1041
+ )
1042
+ # H100 can have larger block size
1043
+ if check_shared_mem("hopper", k.device.index):
1044
+ CONST_TILING = 128
1045
+ elif check_shared_mem():
1046
+ CONST_TILING = 64
1047
+ else:
1048
+ CONST_TILING = 32
1049
+ BK = min(max(triton.next_power_of_2(K), 16), CONST_TILING)
1050
+ BV = min(max(triton.next_power_of_2(V), 16), CONST_TILING)
1051
+ NT = triton.cdiv(T, BT) if cu_seqlens is None else len(chunk_indices)
1052
+ NV = triton.cdiv(V, BV)
1053
+ if scale is None:
1054
+ scale = k.shape[-1] ** -0.5
1055
+
1056
+ dv = torch.empty_like(do)
1057
+ grid = (NV, NT, B * H)
1058
+ chunk_bwd_kernel_dv[grid](
1059
+ q=q,
1060
+ k=k,
1061
+ g=g,
1062
+ g_gamma=g_gamma,
1063
+ do=do,
1064
+ dv=dv,
1065
+ dh=dh,
1066
+ cu_seqlens=cu_seqlens,
1067
+ chunk_indices=chunk_indices,
1068
+ scale=scale,
1069
+ T=T,
1070
+ H=H,
1071
+ K=K,
1072
+ V=V,
1073
+ BT=BT,
1074
+ BK=BK,
1075
+ BV=BV,
1076
+ )
1077
+ return dv
1078
+
1079
+
1080
+ def chunk_bwd_dv_local(
1081
+ q: torch.Tensor,
1082
+ k: torch.Tensor,
1083
+ do: torch.Tensor,
1084
+ g: torch.Tensor | None = None,
1085
+ g_gamma: torch.Tensor | None = None,
1086
+ A: torch.Tensor | None = None,
1087
+ scale: float = None,
1088
+ cu_seqlens: torch.LongTensor | None = None,
1089
+ chunk_size: int = 64,
1090
+ chunk_indices: torch.LongTensor | None = None,
1091
+ ) -> torch.Tensor:
1092
+ B, T, H, K, V = *k.shape, do.shape[-1]
1093
+ BT = chunk_size
1094
+ if chunk_indices is None and cu_seqlens is not None:
1095
+ chunk_indices = prepare_chunk_indices(cu_seqlens, BT)
1096
+ # H100 can have larger block size
1097
+ if check_shared_mem("hopper", k.device.index):
1098
+ CONST_TILING = 128
1099
+ elif check_shared_mem():
1100
+ CONST_TILING = 64
1101
+ else:
1102
+ CONST_TILING = 32
1103
+ BK = min(max(triton.next_power_of_2(K), 16), CONST_TILING)
1104
+ BV = min(max(triton.next_power_of_2(V), 16), CONST_TILING)
1105
+ NT = triton.cdiv(T, BT) if cu_seqlens is None else len(chunk_indices)
1106
+
1107
+ dv = torch.empty_like(do)
1108
+ grid = (NT, B * H)
1109
+ chunk_bwd_kernel_dv_local[grid](
1110
+ q=q,
1111
+ k=k,
1112
+ g=g,
1113
+ g_gamma=g_gamma,
1114
+ A=A,
1115
+ do=do,
1116
+ dv=dv,
1117
+ cu_seqlens=cu_seqlens,
1118
+ chunk_indices=chunk_indices,
1119
+ scale=scale,
1120
+ T=T,
1121
+ H=H,
1122
+ K=K,
1123
+ V=V,
1124
+ BT=BT,
1125
+ BK=BK,
1126
+ BV=BV,
1127
+ )
1128
+ return dv
1129
+
1130
+
1131
+ def chunk_bwd_dqkwg(
1132
+ q: torch.Tensor,
1133
+ k: torch.Tensor,
1134
+ v: torch.Tensor,
1135
+ do: torch.Tensor,
1136
+ h: torch.Tensor,
1137
+ dh: torch.Tensor,
1138
+ w: torch.Tensor | None = None,
1139
+ g: torch.Tensor | None = None,
1140
+ g_gamma: torch.Tensor | None = None,
1141
+ dv: torch.Tensor | None = None,
1142
+ scale: float | None = None,
1143
+ cu_seqlens: torch.LongTensor | None = None,
1144
+ chunk_size: int = 64,
1145
+ ) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
1146
+
1147
+ B, T, H, K, V = *k.shape, v.shape[-1]
1148
+ BT = chunk_size
1149
+ chunk_indices = (
1150
+ prepare_chunk_indices(cu_seqlens, BT) if cu_seqlens is not None else None
1151
+ )
1152
+ NT = triton.cdiv(T, BT) if cu_seqlens is None else len(chunk_indices)
1153
+
1154
+ CONST_TILING = 64 if check_shared_mem() else 32
1155
+ BK = min(max(triton.next_power_of_2(K), 16), CONST_TILING)
1156
+ BV = min(max(triton.next_power_of_2(V), 16), CONST_TILING)
1157
+ NK = triton.cdiv(K, BK)
1158
+ dq = torch.empty_like(q)
1159
+ dk = torch.empty_like(k)
1160
+ dg = (
1161
+ torch.empty(NK, *g.shape, dtype=torch.float32, device=g.device)
1162
+ if g is not None
1163
+ else None
1164
+ )
1165
+ dw = torch.empty_like(w) if w is not None else None
1166
+
1167
+ grid = (NK, NT, B * H)
1168
+ chunk_bwd_kernel_dqkwg[grid](
1169
+ q=q,
1170
+ k=k,
1171
+ v=v,
1172
+ g=g,
1173
+ g_gamma=g_gamma,
1174
+ h=h,
1175
+ do=do,
1176
+ dh=dh,
1177
+ dw=dw,
1178
+ dq=dq,
1179
+ dk=dk,
1180
+ dv=dv,
1181
+ dg=dg,
1182
+ cu_seqlens=cu_seqlens,
1183
+ chunk_indices=chunk_indices,
1184
+ scale=scale,
1185
+ B=B,
1186
+ T=T,
1187
+ H=H,
1188
+ K=K,
1189
+ V=V,
1190
+ BT=BT,
1191
+ BK=BK,
1192
+ BV=BV,
1193
+ )
1194
+
1195
+ if dg is not None:
1196
+ dg = dg.sum(0)
1197
+ return dq, dk, dw, dg
build/torch-rocm/_triton_kernels/gated_delta_rule/prefill/fused_cumsum_kkt.py ADDED
@@ -0,0 +1,339 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import triton
3
+ import triton.language as tl
4
+
5
+ from ..gated_delta_rule_utils import (
6
+ RCP_LN2,
7
+ IS_AMD,
8
+ autotune_cache_kwargs,
9
+ gated_delta_rule_autotune_configs,
10
+ )
11
+ from ..utils import prepare_chunk_indices, prepare_rebased_cu_seqlens
12
+ from ..utils.op import exp
13
+
14
+
15
+ @triton.jit
16
+ def safe_exp(x):
17
+ return tl.exp(tl.where(x <= 0, x, float("-inf")))
18
+
19
+
20
+ @triton.heuristics({"IS_VARLEN": lambda args: args["cu_seqlens"] is not None})
21
+ @triton.jit(do_not_specialize=["T"])
22
+ def _fused_cumsum_kkt_kernel(
23
+ g_ptr,
24
+ k_ptr,
25
+ beta_ptr,
26
+ g_cumsum_ptr,
27
+ A_ptr,
28
+ cu_seqlens,
29
+ chunk_indices,
30
+ T,
31
+ H: tl.constexpr,
32
+ Hg: tl.constexpr,
33
+ K: tl.constexpr,
34
+ BT: tl.constexpr,
35
+ IS_VARLEN: tl.constexpr,
36
+ ):
37
+ i_t, i_bh = tl.program_id(0), tl.program_id(1)
38
+ i_b, i_h = i_bh // H, i_bh % H
39
+
40
+ if IS_VARLEN:
41
+ i_n = tl.load(chunk_indices + i_t * 2).to(tl.int32)
42
+ i_t_local = tl.load(chunk_indices + i_t * 2 + 1).to(tl.int32)
43
+ bos = tl.load(cu_seqlens + i_n).to(tl.int32)
44
+ eos = tl.load(cu_seqlens + i_n + 1).to(tl.int32)
45
+ T_seq = eos - bos
46
+ i_t = i_t_local
47
+ else:
48
+ bos = i_b * T
49
+ T_seq = T
50
+
51
+ o_t = tl.arange(0, BT)
52
+
53
+ p_g = tl.make_block_ptr(
54
+ g_ptr + bos * H + i_h, (T_seq,), (H,), (i_t * BT,), (BT,), (0,)
55
+ )
56
+ b_g = tl.load(p_g, boundary_check=(0,)).to(tl.float32)
57
+ b_g_cumsum = tl.cumsum(b_g, axis=0)
58
+ p_g_out = tl.make_block_ptr(
59
+ g_cumsum_ptr + bos * H + i_h, (T_seq,), (H,), (i_t * BT,), (BT,), (0,)
60
+ )
61
+ tl.store(p_g_out, b_g_cumsum.to(p_g_out.dtype.element_ty), boundary_check=(0,))
62
+
63
+ p_beta = tl.make_block_ptr(
64
+ beta_ptr + bos * H + i_h, (T_seq,), (H,), (i_t * BT,), (BT,), (0,)
65
+ )
66
+ b_beta = tl.load(p_beta, boundary_check=(0,)).to(tl.float32)
67
+
68
+ p_k = tl.make_block_ptr(
69
+ k_ptr + (bos * Hg + i_h // (H // Hg)) * K,
70
+ (T_seq, K),
71
+ (Hg * K, 1),
72
+ (i_t * BT, 0),
73
+ (BT, K),
74
+ (1, 0),
75
+ )
76
+ b_k = tl.load(p_k, boundary_check=(0, 1)).to(tl.float32)
77
+
78
+ b_A = tl.dot(b_k, tl.trans(b_k))
79
+ b_g_diff = b_g_cumsum[:, None] - b_g_cumsum[None, :]
80
+ b_A = b_A * safe_exp(b_g_diff) * b_beta[:, None]
81
+ b_A = tl.where(o_t[:, None] > o_t[None, :], b_A, 0.0)
82
+
83
+ p_A = tl.make_block_ptr(
84
+ A_ptr + (bos * H + i_h) * BT,
85
+ (T_seq, BT),
86
+ (BT * H, 1),
87
+ (i_t * BT, 0),
88
+ (BT, BT),
89
+ (1, 0),
90
+ )
91
+ tl.store(p_A, b_A.to(A_ptr.dtype.element_ty), boundary_check=(0, 1))
92
+
93
+
94
+ def fused_cumsum_kkt(
95
+ g: torch.Tensor,
96
+ k: torch.Tensor,
97
+ beta: torch.Tensor,
98
+ chunk_size: int = 64,
99
+ cu_seqlens: torch.Tensor | None = None,
100
+ ):
101
+ """
102
+ Fused cumsum + KKT.
103
+
104
+ Args:
105
+ g: [B, T, H]
106
+ k: [B, T, Hg, K]
107
+ beta: [B, T, H]
108
+
109
+ Returns:
110
+ g_cumsum: [B, H, T]
111
+ A: [B, T, H, chunk_size], strictly lower triangular
112
+ """
113
+ B, T, H = g.shape
114
+ Hg, K = k.shape[2], k.shape[3]
115
+
116
+ if cu_seqlens is not None:
117
+ chunk_indices = prepare_chunk_indices(cu_seqlens, chunk_size)
118
+ NT = len(chunk_indices)
119
+ else:
120
+ chunk_indices = None
121
+ NT = triton.cdiv(T, chunk_size)
122
+
123
+ g_cumsum = torch.empty(B, T, H, device=g.device, dtype=torch.float32)
124
+ A = torch.empty(B, T, H, chunk_size, device=k.device, dtype=torch.float32)
125
+
126
+ _fused_cumsum_kkt_kernel[(NT, B * H)](
127
+ g,
128
+ k,
129
+ beta,
130
+ g_cumsum,
131
+ A,
132
+ cu_seqlens,
133
+ chunk_indices,
134
+ T,
135
+ H,
136
+ Hg,
137
+ K,
138
+ chunk_size,
139
+ num_warps=4,
140
+ num_stages=3,
141
+ )
142
+ return g_cumsum, A
143
+
144
+
145
+ if IS_AMD:
146
+ _CUMSUM_KKT_CONFIGS = [
147
+ triton.Config({"BK": 32}, num_warps=4, num_stages=2),
148
+ triton.Config({"BK": 32}, num_warps=2, num_stages=2),
149
+ triton.Config({"BK": 32}, num_warps=8, num_stages=2),
150
+ triton.Config({"BK": 32}, num_warps=4, num_stages=3),
151
+ triton.Config({"BK": 32}, num_warps=2, num_stages=3),
152
+ triton.Config({"BK": 64}, num_warps=4, num_stages=2),
153
+ ]
154
+ else:
155
+ _CUMSUM_KKT_CONFIGS = [
156
+ triton.Config({"BK": BK}, num_warps=nw, num_stages=ns)
157
+ for BK in [32, 64]
158
+ for nw in [2, 4]
159
+ for ns in ([2, 3] if IS_AMD else [2, 3, 4])
160
+ ]
161
+
162
+ _CUMSUM_KKT_DEFAULT_CONFIG = triton.Config({"BK": 32}, num_warps=4, num_stages=2)
163
+
164
+
165
+ @triton.heuristics({"IS_VARLEN": lambda args: args["cu_seqlens"] is not None})
166
+ @triton.autotune(
167
+ configs=gated_delta_rule_autotune_configs(
168
+ _CUMSUM_KKT_CONFIGS,
169
+ default_config=_CUMSUM_KKT_DEFAULT_CONFIG,
170
+ ),
171
+ key=["H", "K", "BT", "IS_VARLEN"],
172
+ **autotune_cache_kwargs,
173
+ )
174
+ @triton.jit(do_not_specialize=["T"])
175
+ def fused_chunk_local_cumsum_scaled_dot_kkt_fwd_kernel(
176
+ g,
177
+ k,
178
+ beta,
179
+ g_cumsum_out,
180
+ A_out,
181
+ cu_seqlens,
182
+ chunk_indices,
183
+ T,
184
+ H: tl.constexpr,
185
+ Hg: tl.constexpr,
186
+ K: tl.constexpr,
187
+ BT: tl.constexpr,
188
+ BK: tl.constexpr,
189
+ IS_VARLEN: tl.constexpr,
190
+ USE_EXP2: tl.constexpr = False,
191
+ G_SCALE: tl.constexpr = 1.0,
192
+ ):
193
+ i_t, i_bh = tl.program_id(0), tl.program_id(1)
194
+ i_b, i_h = i_bh // H, i_bh % H
195
+ T_flat = T
196
+ if IS_VARLEN:
197
+ i_n, i_t = (
198
+ tl.load(chunk_indices + i_t * 2).to(tl.int32),
199
+ tl.load(chunk_indices + i_t * 2 + 1).to(tl.int32),
200
+ )
201
+ bos, eos = (
202
+ tl.load(cu_seqlens + i_n).to(tl.int32),
203
+ tl.load(cu_seqlens + i_n + 1).to(tl.int32),
204
+ )
205
+ T = eos - bos
206
+ else:
207
+ bos = i_b * T
208
+
209
+ o_t = i_t * BT + tl.arange(0, BT)
210
+ m_t = o_t < T
211
+
212
+ p_g = tl.make_block_ptr(g + bos * H + i_h, (T,), (H,), (i_t * BT,), (BT,), (0,))
213
+ b_g = tl.load(p_g, boundary_check=(0,)).to(tl.float32)
214
+ b_g_cumsum = tl.cumsum(b_g, axis=0)
215
+ # Store g_cumsum in log2 space when downstream kernels consume it with exp2:
216
+ # exp2(x * RCP_LN2) == exp(x), keeping results identical. The scale arrives
217
+ # as the constexpr G_SCALE (RCP_LN2 when use_exp2 else 1.0) so the kernel
218
+ # never reads a module-level global; cumsum's linearity makes scaling before
219
+ # or after the cumsum equivalent.
220
+ if G_SCALE != 1.0:
221
+ b_g_cumsum = b_g_cumsum * G_SCALE
222
+
223
+ # g_cumsum is stored head-major [B, H, T] (stride 1 along T) so the
224
+ # downstream solve/recompute, hidden-state and output kernels can read it
225
+ # contiguously per (batch, head).
226
+ if IS_VARLEN:
227
+ g_out_base = g_cumsum_out + i_h * T_flat + bos
228
+ else:
229
+ g_out_base = g_cumsum_out + (i_b * H + i_h) * T_flat
230
+ p_go = tl.make_block_ptr(g_out_base, (T,), (1,), (i_t * BT,), (BT,), (0,))
231
+ tl.store(p_go, b_g_cumsum.to(p_go.dtype.element_ty), boundary_check=(0,))
232
+
233
+ p_beta = tl.make_block_ptr(
234
+ beta + bos * H + i_h, (T,), (H,), (i_t * BT,), (BT,), (0,)
235
+ )
236
+ b_beta = tl.load(p_beta, boundary_check=(0,))
237
+
238
+ b_A = tl.zeros([BT, BT], dtype=tl.float32)
239
+ for i_k in range(tl.cdiv(K, BK)):
240
+ p_k = tl.make_block_ptr(
241
+ k + (bos * Hg + i_h // (H // Hg)) * K,
242
+ (T, K),
243
+ (Hg * K, 1),
244
+ (i_t * BT, i_k * BK),
245
+ (BT, BK),
246
+ (1, 0),
247
+ )
248
+ b_k = tl.load(p_k, boundary_check=(0, 1))
249
+ b_kb = b_k * b_beta[:, None]
250
+ b_A = tl.dot(b_kb.to(b_k.dtype), tl.trans(b_k), acc=b_A)
251
+
252
+ b_g_diff = b_g_cumsum[:, None] - b_g_cumsum[None, :]
253
+ m_A = (o_t[:, None] > o_t[None, :]) & (m_t[:, None] & m_t)
254
+ b_gate = tl.math.exp2(b_g_diff) if USE_EXP2 else exp(b_g_diff)
255
+ b_A = tl.where(m_A, b_A * b_gate, 0.0)
256
+
257
+ p_A = tl.make_block_ptr(
258
+ A_out + (bos * H + i_h) * BT,
259
+ (T, BT),
260
+ (BT * H, 1),
261
+ (i_t * BT, 0),
262
+ (BT, BT),
263
+ (1, 0),
264
+ )
265
+ tl.store(p_A, b_A.to(A_out.dtype.element_ty), boundary_check=(0, 1))
266
+
267
+
268
+ def fused_chunk_local_cumsum_scaled_dot_kkt_fwd(
269
+ k: torch.Tensor,
270
+ beta: torch.Tensor,
271
+ g: torch.Tensor,
272
+ cu_seqlens: torch.LongTensor | None = None,
273
+ chunk_size: int = 64,
274
+ g_output_dtype: torch.dtype = torch.float32,
275
+ A_output_dtype: torch.dtype = torch.float32,
276
+ use_exp2: bool = True,
277
+ num_decodes: int = 0,
278
+ num_decode_tokens: int = 0,
279
+ ) -> tuple[torch.Tensor, torch.Tensor]:
280
+ """
281
+ Fused cumsum + scaled dot KKT (optimized, with autotuning).
282
+
283
+ Args:
284
+ k: [B, T, Hg, K]
285
+ beta: [B, T, H]
286
+ g: [B, T, H], raw forget gate increments
287
+ cu_seqlens: [N+1]
288
+ chunk_size: int (must be 64)
289
+ g_output_dtype: dtype for g_cumsum (default fp32)
290
+ A_output_dtype: dtype for A_raw (default fp32)
291
+ use_exp2: when True, store g_cumsum in log2 space (scaled by RCP_LN2)
292
+ so downstream kernels can use exp2; A_raw is unaffected.
293
+
294
+ Returns:
295
+ g_cumsum: [B, H, T], head-major
296
+ A_raw: [B, T, H, 64]
297
+ """
298
+ B, T, Hg, K = k.shape
299
+ H = beta.shape[-1]
300
+ BT = chunk_size
301
+
302
+ # Pass the ORIGINAL (cache-stable) cu_seqlens to prepare_chunk_indices
303
+ # together with num_decodes/num_decode_tokens, so the chunk-index build
304
+ # caches on the stable tensor identity and never re-fires the .tolist()
305
+ # D2H across forward calls. The kernel walks the pre-sliced prefill data
306
+ # via the rebased cu_seqlens.
307
+ if cu_seqlens is not None:
308
+ chunk_indices = prepare_chunk_indices(
309
+ cu_seqlens, BT, num_decodes, num_decode_tokens
310
+ )
311
+ kernel_cu_seqlens = prepare_rebased_cu_seqlens(
312
+ cu_seqlens, num_decodes, num_decode_tokens
313
+ )
314
+ NT = len(chunk_indices)
315
+ else:
316
+ chunk_indices = None
317
+ kernel_cu_seqlens = None
318
+ NT = triton.cdiv(T, BT)
319
+
320
+ g_cumsum_out = torch.empty(B, H, T, device=g.device, dtype=g_output_dtype)
321
+ A_out = torch.empty(B, T, H, BT, device=k.device, dtype=A_output_dtype)
322
+
323
+ fused_chunk_local_cumsum_scaled_dot_kkt_fwd_kernel[(NT, B * H)](
324
+ g,
325
+ k,
326
+ beta,
327
+ g_cumsum_out,
328
+ A_out,
329
+ kernel_cu_seqlens,
330
+ chunk_indices,
331
+ T=T,
332
+ H=H,
333
+ Hg=Hg,
334
+ K=K,
335
+ BT=BT,
336
+ USE_EXP2=use_exp2,
337
+ G_SCALE=RCP_LN2 if use_exp2 else 1.0,
338
+ )
339
+ return g_cumsum_out, A_out
build/torch-rocm/_triton_kernels/gated_delta_rule/prefill/fused_gdn_gating_prefill.py ADDED
@@ -0,0 +1,80 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Fused sigmoid + gdn_gating kernel for prefill."""
2
+
3
+ import torch
4
+ import triton
5
+ import triton.language as tl
6
+
7
+
8
+ @triton.jit
9
+ def fused_gdn_gating_sigmoid_kernel(
10
+ g_ptr,
11
+ beta_ptr,
12
+ A_log_ptr,
13
+ a_ptr,
14
+ b_ptr,
15
+ dt_bias_ptr,
16
+ NUM_HEADS: tl.constexpr,
17
+ softplus_beta: tl.constexpr,
18
+ softplus_threshold: tl.constexpr,
19
+ BLK_HEADS: tl.constexpr,
20
+ ):
21
+ """Fused: g = -exp(A_log) * softplus(a + dt_bias), beta = sigmoid(b)."""
22
+ i_s = tl.program_id(0)
23
+
24
+ head_off = tl.arange(0, BLK_HEADS)
25
+ mask = head_off < NUM_HEADS
26
+ off = i_s * NUM_HEADS + head_off
27
+
28
+ blk_A_log = tl.load(A_log_ptr + head_off, mask=mask)
29
+ blk_dt_bias = tl.load(dt_bias_ptr + head_off, mask=mask)
30
+ blk_a = tl.load(a_ptr + off, mask=mask)
31
+ blk_b = tl.load(b_ptr + off, mask=mask)
32
+
33
+ # g = -exp(A_log) * softplus(a + dt_bias)
34
+ x = blk_a.to(tl.float32) + blk_dt_bias.to(tl.float32)
35
+ beta_x = softplus_beta * x
36
+ softplus_x = tl.where(
37
+ beta_x <= softplus_threshold,
38
+ (1.0 / softplus_beta) * tl.log(1.0 + tl.exp(beta_x)),
39
+ x,
40
+ )
41
+ blk_g = -tl.exp(blk_A_log.to(tl.float32)) * softplus_x
42
+
43
+ # beta = sigmoid(b)
44
+ blk_beta = 1.0 / (1.0 + tl.exp(-blk_b.to(tl.float32)))
45
+
46
+ tl.store(g_ptr + off, blk_g.to(g_ptr.dtype.element_ty), mask=mask)
47
+ tl.store(beta_ptr + off, blk_beta.to(beta_ptr.dtype.element_ty), mask=mask)
48
+
49
+
50
+ def fused_gdn_gating_and_sigmoid(
51
+ A_log: torch.Tensor,
52
+ a: torch.Tensor,
53
+ b: torch.Tensor,
54
+ dt_bias: torch.Tensor,
55
+ softplus_beta: float = 1.0,
56
+ softplus_threshold: float = 20.0,
57
+ ) -> tuple[torch.Tensor, torch.Tensor]:
58
+ """Fused g and beta computation in single kernel."""
59
+ seq_len, num_heads = a.shape
60
+ g = torch.empty_like(a, dtype=torch.float32)
61
+ beta = torch.empty_like(b, dtype=torch.float32)
62
+
63
+ BLK_HEADS = triton.next_power_of_2(num_heads)
64
+ grid = (seq_len,)
65
+
66
+ fused_gdn_gating_sigmoid_kernel[grid](
67
+ g,
68
+ beta,
69
+ A_log,
70
+ a,
71
+ b,
72
+ dt_bias,
73
+ num_heads,
74
+ softplus_beta,
75
+ softplus_threshold,
76
+ BLK_HEADS,
77
+ num_warps=2,
78
+ )
79
+
80
+ return g, beta