|
| 1 | +# Copyright 2026 FlagOS Contributors |
| 2 | +# |
| 3 | +# Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | +# you may not use this file except in compliance with the License. |
| 5 | +# You may obtain a copy of the License at |
| 6 | +# |
| 7 | +# http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | +# |
| 9 | +# Unless required by applicable law or agreed to in writing, software |
| 10 | +# distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | +# See the License for the specific language governing permissions and |
| 13 | +# limitations under the License. |
| 14 | + |
| 15 | +import logging |
| 16 | + |
| 17 | +import torch |
| 18 | +import triton |
| 19 | +import triton.language as tl |
| 20 | + |
| 21 | +from flag_gems.ops.diagonal_scatter import diagonal_scatter as default_diagonal_scatter |
| 22 | +from flag_gems.runtime import torch_device_fn |
| 23 | +from flag_gems.utils import libentry |
| 24 | + |
| 25 | +logger = logging.getLogger( |
| 26 | + f'flag_gems.runtime.backend._mthreads.ops.{__name__.split(".")[-1]}' |
| 27 | +) |
| 28 | + |
| 29 | +_SUPPORTED_DTYPES = {torch.float16, torch.bfloat16, torch.float32} |
| 30 | + |
| 31 | +MAX_BATCH_DIMS = 4 |
| 32 | + |
| 33 | + |
| 34 | +@libentry() |
| 35 | +@triton.jit |
| 36 | +def _diag_scatter_kernel( |
| 37 | + in_ptr, |
| 38 | + src_ptr, |
| 39 | + out_ptr, |
| 40 | + offset: tl.constexpr, |
| 41 | + off_min: tl.constexpr, |
| 42 | + n1: tl.constexpr, |
| 43 | + n2: tl.constexpr, |
| 44 | + s1: tl.constexpr, |
| 45 | + s2: tl.constexpr, |
| 46 | + diag_size: tl.constexpr, |
| 47 | + br0: tl.constexpr, |
| 48 | + br1: tl.constexpr, |
| 49 | + br2: tl.constexpr, |
| 50 | + br3: tl.constexpr, |
| 51 | + bs0: tl.constexpr, |
| 52 | + bs1: tl.constexpr, |
| 53 | + bs2: tl.constexpr, |
| 54 | + bs3: tl.constexpr, |
| 55 | + n_i_tiles: tl.constexpr, |
| 56 | + BI: tl.constexpr, |
| 57 | + BJ: tl.constexpr, |
| 58 | +): |
| 59 | + pid0 = tl.program_id(0) |
| 60 | + pid1 = tl.program_id(1) |
| 61 | + |
| 62 | + # i-tiles vary fastest so consecutive programs touch adjacent memory. |
| 63 | + i_tile = pid0 % n_i_tiles |
| 64 | + batch_flat = pid0 // n_i_tiles |
| 65 | + |
| 66 | + # Decompose batch_flat (mixed-radix counter over the non-diagonal dims in |
| 67 | + # ascending dim order) into per-dim digits, innermost batch dim first, and |
| 68 | + # accumulate the corresponding flat base offset. Unused slots use radix=1, |
| 69 | + # stride=0, which makes them no-ops. All divisors are constexpr so the |
| 70 | + # div/mod lower to fast magic-number sequences. |
| 71 | + bf = batch_flat |
| 72 | + base = 0 |
| 73 | + dig = bf % br0 |
| 74 | + base += dig * bs0 |
| 75 | + bf = bf // br0 |
| 76 | + dig = bf % br1 |
| 77 | + base += dig * bs1 |
| 78 | + bf = bf // br1 |
| 79 | + dig = bf % br2 |
| 80 | + base += dig * bs2 |
| 81 | + bf = bf // br2 |
| 82 | + dig = bf % br3 |
| 83 | + base += dig * bs3 |
| 84 | + |
| 85 | + i = i_tile * BI + tl.arange(0, BI) |
| 86 | + j = pid1 * BJ + tl.arange(0, BJ) |
| 87 | + mi = i < n1 |
| 88 | + mj = j < n2 |
| 89 | + |
| 90 | + d = i + off_min |
| 91 | + src_idx = batch_flat * diag_size + d |
| 92 | + src_val = tl.load( |
| 93 | + src_ptr + src_idx, mask=mi & (d >= 0) & (d < diag_size), other=0.0 |
| 94 | + ) |
| 95 | + |
| 96 | + on_diag = ( |
| 97 | + ((j[None, :] - i[:, None]) == offset) |
| 98 | + & mj[None, :] |
| 99 | + & mi[:, None] |
| 100 | + & (d[:, None] >= 0) |
| 101 | + & (d[:, None] < diag_size) |
| 102 | + ) |
| 103 | + f = base + i[:, None] * s1 + j[None, :] * s2 |
| 104 | + m = mi[:, None] & mj[None, :] |
| 105 | + in_val = tl.load(in_ptr + f, mask=m, other=0.0) |
| 106 | + out_val = tl.where(on_diag, src_val[:, None], in_val) |
| 107 | + tl.store(out_ptr + f, out_val, mask=m) |
| 108 | + |
| 109 | + |
| 110 | +def _use_triton_kernel(input, src, offset, dim1, dim2) -> bool: |
| 111 | + if not isinstance(input, torch.Tensor) or not isinstance(src, torch.Tensor): |
| 112 | + return False |
| 113 | + if input.device.type != "musa" or input.dtype not in _SUPPORTED_DTYPES: |
| 114 | + return False |
| 115 | + if not input.is_contiguous() or input.numel() == 0: |
| 116 | + return False |
| 117 | + ndim = input.ndim |
| 118 | + d1 = dim1 % ndim |
| 119 | + d2 = dim2 % ndim |
| 120 | + batch_dims = [d for d in range(ndim) if d != d1 and d != d2] |
| 121 | + if len(batch_dims) > MAX_BATCH_DIMS: |
| 122 | + return False |
| 123 | + return True |
| 124 | + |
| 125 | + |
| 126 | +def diagonal_scatter(input, src, offset=0, dim1=0, dim2=1): |
| 127 | + logger.debug("GEMS_MTHREADS DIAGONAL_SCATTER") |
| 128 | + if not _use_triton_kernel(input, src, offset, dim1, dim2): |
| 129 | + return default_diagonal_scatter(input, src, offset=offset, dim1=dim1, dim2=dim2) |
| 130 | + |
| 131 | + ndim = input.ndim |
| 132 | + d1 = dim1 % ndim |
| 133 | + d2 = dim2 % ndim |
| 134 | + |
| 135 | + shape = input.shape |
| 136 | + strides = input.stride() |
| 137 | + n1 = shape[d1] |
| 138 | + n2 = shape[d2] |
| 139 | + s1 = strides[d1] |
| 140 | + s2 = strides[d2] |
| 141 | + |
| 142 | + batch_dims = [d for d in range(ndim) if d != d1 and d != d2] |
| 143 | + if len(batch_dims) > MAX_BATCH_DIMS: |
| 144 | + raise ValueError("tensor too high-dimensional for this kernel") |
| 145 | + |
| 146 | + batch_count = 1 |
| 147 | + for d in batch_dims: |
| 148 | + batch_count *= shape[d] |
| 149 | + |
| 150 | + off_min = min(offset, 0) |
| 151 | + off_max = max(offset, 0) |
| 152 | + diag_size = max(0, min(n1 + off_min, n2 - off_max)) |
| 153 | + |
| 154 | + rad = [1] * MAX_BATCH_DIMS |
| 155 | + bst = [0] * MAX_BATCH_DIMS |
| 156 | + for k, d in enumerate(reversed(batch_dims)): |
| 157 | + rad[k] = shape[d] |
| 158 | + bst[k] = strides[d] |
| 159 | + |
| 160 | + # Tile the (dim1, dim2) plane: BJ along dim2, BI along dim1, targeting |
| 161 | + # ~1024 elements per program block and ~4 elements per thread. Rows of |
| 162 | + # width n2=128 (BJ==128) uniquely prefer 4 warps (8 elems/thread) on the |
| 163 | + # MTT S5000; all other widths run 8 warps (4 elems/thread). |
| 164 | + BJ = min(256, 1 << (n2 - 1).bit_length()) if n2 > 0 else 256 |
| 165 | + BI = min(1 << (n1 - 1).bit_length(), max(1, 1024 // BJ)) if n1 > 0 else 1 |
| 166 | + n_i_tiles = (n1 + BI - 1) // BI |
| 167 | + n_j_tiles = (n2 + BJ - 1) // BJ |
| 168 | + grid = (batch_count * n_i_tiles, n_j_tiles) |
| 169 | + if BJ == 128: |
| 170 | + num_warps = max(1, min(8, (BI * BJ) // 256)) |
| 171 | + else: |
| 172 | + num_warps = max(1, min(8, (BI * BJ) // 128)) |
| 173 | + |
| 174 | + with torch_device_fn.device(input.device): |
| 175 | + output = torch.empty_like(input) |
| 176 | + _diag_scatter_kernel[grid]( |
| 177 | + input, |
| 178 | + src, |
| 179 | + output, |
| 180 | + offset, |
| 181 | + off_min, |
| 182 | + n1, |
| 183 | + n2, |
| 184 | + s1, |
| 185 | + s2, |
| 186 | + diag_size, |
| 187 | + rad[0], |
| 188 | + rad[1], |
| 189 | + rad[2], |
| 190 | + rad[3], |
| 191 | + bst[0], |
| 192 | + bst[1], |
| 193 | + bst[2], |
| 194 | + bst[3], |
| 195 | + n_i_tiles, |
| 196 | + BI, |
| 197 | + BJ, |
| 198 | + num_warps=num_warps, |
| 199 | + ) |
| 200 | + return output |
| 201 | + |
| 202 | + |
| 203 | +__all__ = ["diagonal_scatter"] |
0 commit comments