|
1 | 1 | import logging |
2 | | -import math |
3 | 2 |
|
4 | 3 | import torch |
5 | 4 | import triton |
6 | 5 | import triton.language as tl |
| 6 | +from triton.language.core import _unwrap_if_constexpr |
7 | 7 |
|
8 | 8 | from ..runtime import torch_device_fn |
9 | 9 | from ..utils import libentry |
|
12 | 12 | logger = logging.getLogger(__name__) |
13 | 13 |
|
14 | 14 |
|
| 15 | +@tl.constexpr |
| 16 | +def get_int_t(num_bits: tl.constexpr, signed: tl.constexpr) -> tl.dtype: |
| 17 | + num_bits = _unwrap_if_constexpr(num_bits) |
| 18 | + signed = _unwrap_if_constexpr(signed) |
| 19 | + return tl.core.get_int_dtype(num_bits, signed) |
| 20 | + |
| 21 | + |
| 22 | +@tl.constexpr |
| 23 | +def one_zeros(num_bits: tl.constexpr) -> int: |
| 24 | + num_bits = _unwrap_if_constexpr(num_bits) |
| 25 | + return 1 << (num_bits - 1) |
| 26 | + |
| 27 | + |
| 28 | +@tl.constexpr |
| 29 | +def zero_ones(num_bits: tl.constexpr) -> int: |
| 30 | + num_bits = _unwrap_if_constexpr(num_bits) |
| 31 | + return (1 << (num_bits - 1)) - 1 |
| 32 | + |
| 33 | + |
| 34 | +@triton.jit |
| 35 | +def uint_to_uint(x, descending: tl.constexpr = False): |
| 36 | + out = ~x if descending else x |
| 37 | + return out |
| 38 | + |
| 39 | + |
| 40 | +@triton.jit |
| 41 | +def int_to_uint(x, descending: tl.constexpr = False): |
| 42 | + num_bits: tl.constexpr = x.dtype.primitive_bitwidth |
| 43 | + udtype = get_int_t(num_bits, False) |
| 44 | + ux = tl.cast(x, udtype, bitcast=True) |
| 45 | + if descending: |
| 46 | + # 0111111....1 |
| 47 | + bit_mask: tl.constexpr = zero_ones(num_bits) |
| 48 | + out = ux ^ bit_mask |
| 49 | + else: |
| 50 | + # 1000000...0 |
| 51 | + sign_bit_mask: tl.constexpr = one_zeros(num_bits) |
| 52 | + out = ux ^ sign_bit_mask |
| 53 | + return out |
| 54 | + |
| 55 | + |
| 56 | +@triton.jit |
| 57 | +def floating_to_uint(x, descending: tl.constexpr = False): |
| 58 | + num_bits: tl.constexpr = x.dtype.primitive_bitwidth |
| 59 | + sdtype = get_int_t(num_bits, True) |
| 60 | + udtype = get_int_t(num_bits, False) |
| 61 | + sx = x.to(sdtype, bitcast=True) |
| 62 | + ux = x.to(udtype, bitcast=True) |
| 63 | + |
| 64 | + sign_bit_mask: tl.constexpr = one_zeros(num_bits) |
| 65 | + # mind the dtype, right_shift for signed is arithmetic right shift |
| 66 | + mask = sign_bit_mask | (sx >> (num_bits - 1)).to(udtype, bitcast=True) |
| 67 | + # 1000000000...0 for positive |
| 68 | + # 1111111111...1 for negative |
| 69 | + if descending: |
| 70 | + out = ux ^ (~mask) |
| 71 | + else: |
| 72 | + out = ux ^ mask |
| 73 | + return out.to(udtype, bitcast=True) |
| 74 | + |
| 75 | + |
| 76 | +@triton.jit |
| 77 | +def convert_to_uint_preverse_order(x: tl.tensor, descending: tl.constexpr = False): |
| 78 | + if x.dtype.is_floating(): |
| 79 | + out = floating_to_uint(x, descending) |
| 80 | + elif x.dtype.is_int_signed(): |
| 81 | + out = int_to_uint(x, descending) |
| 82 | + elif x.dtype.is_int_unsigned(): |
| 83 | + out = uint_to_uint(x, descending) |
| 84 | + return out |
| 85 | + |
| 86 | + |
| 87 | +@triton.jit |
| 88 | +def compute_global_hist_kernel( |
| 89 | + arr_ptr, |
| 90 | + out_ptr, |
| 91 | + num_passes, |
| 92 | + m, |
| 93 | + n, |
| 94 | + tiles_n_per_cta, |
| 95 | + TILE_N: tl.constexpr, |
| 96 | + TILE_R: tl.constexpr, |
| 97 | + num_bits_per_pass: tl.constexpr, |
| 98 | + descending: tl.constexpr, |
| 99 | +): |
| 100 | + # arr_ptr: (m, n) |
| 101 | + # out_ptr: (m, n_passes, r), where r = 2 ** k_bits is the number of bins |
| 102 | + pid = tl.program_id(0) |
| 103 | + pid_n = pid // m |
| 104 | + pid_m = pid % m |
| 105 | + |
| 106 | + r: tl.constexpr = 2**num_bits_per_pass |
| 107 | + bfe_mask: tl.constexpr = (1 << num_bits_per_pass) - 1 # a.k.a. 2 ** k_bits - 1 |
| 108 | + CTA_TILE_N: tl.constexpr = TILE_N * tiles_n_per_cta |
| 109 | + cta_n_start = CTA_TILE_N * pid_n |
| 110 | + cta_n_end = tl.minimum(cta_n_start + CTA_TILE_N, n) |
| 111 | + |
| 112 | + for p in range(0, num_passes): # parallel |
| 113 | + bit_offset = p * num_bits_per_pass |
| 114 | + for r_start in range(0, r, TILE_R): # parallel |
| 115 | + bin_indices = r_start + tl.arange(0, TILE_R) |
| 116 | + acc = tl.zeros((TILE_R, TILE_N), dtype=tl.int64) |
| 117 | + for n_start in range(cta_n_start, cta_n_end, TILE_N): # sequantial |
| 118 | + n_offsets = n_start + tl.arange(0, TILE_N) # (TILE_N, ) |
| 119 | + mask = n_offsets < cta_n_end |
| 120 | + arr = tl.load(arr_ptr + pid_m * n + n_offsets, mask=mask) |
| 121 | + arr = convert_to_uint_preverse_order(arr, descending) |
| 122 | + key = (arr >> bit_offset) & bfe_mask # (TILE_N, ) |
| 123 | + matches = tl.where( |
| 124 | + mask, (bin_indices[:, None] == key), False |
| 125 | + ) # (TILE_R, TILE_N) |
| 126 | + acc += matches |
| 127 | + local_sum = tl.sum(acc, axis=1) |
| 128 | + tl.atomic_add( |
| 129 | + out_ptr + pid_m * num_passes * r + p * r + bin_indices, |
| 130 | + local_sum, |
| 131 | + sem="relaxed", |
| 132 | + ) |
| 133 | + |
| 134 | + |
| 135 | +@triton.jit |
| 136 | +def sweep( |
| 137 | + arr_ptr, |
| 138 | + associate_arr_ptr, # inputs: (key & value) |
| 139 | + out_ptr, |
| 140 | + associate_out_ptr, # outputs: (key & value) |
| 141 | + excumsum_bins_ptr, |
| 142 | + status_ptr, # aux input and status |
| 143 | + n_passes, |
| 144 | + pass_id, |
| 145 | + bit_offset, |
| 146 | + m, |
| 147 | + N, |
| 148 | + OUT_N, |
| 149 | + TILE_N: tl.constexpr, |
| 150 | + TILE_R: tl.constexpr, |
| 151 | + k_bits: tl.constexpr, |
| 152 | + descending: tl.constexpr, |
| 153 | +): |
| 154 | + # r: num_bins = 2 ** k_bits |
| 155 | + # OUT_N: grid_n = cdiv(N, ) |
| 156 | + |
| 157 | + # arr_ptr: (m, N) |
| 158 | + # out_ptr: (m, N) |
| 159 | + # excumsum_bins_ptr: (m, n_passes, r) |
| 160 | + # flag_ptr: (m, r, OUT_N) |
| 161 | + |
| 162 | + # grid: (m, grid_r, grid_n) |
| 163 | + |
| 164 | + # load data |
| 165 | + pid = tl.program_id(0) |
| 166 | + pid_m = pid % m |
| 167 | + pid_n = pid // m |
| 168 | + pid_r = tl.program_id(1) |
| 169 | + |
| 170 | + # bit masks |
| 171 | + aggregate_mask: tl.constexpr = 1 << 30 |
| 172 | + inclusive_prefix_mask: tl.constexpr = 1 << 31 |
| 173 | + v_mask: tl.constexpr = (1 << 30) - 1 |
| 174 | + bfe_mask: tl.constexpr = (1 << k_bits) - 1 # a.k.a. 2 ** k_bits - 1 |
| 175 | + |
| 176 | + # initialize flag to zero-local sum is not ready |
| 177 | + r: tl.constexpr = 2**k_bits |
| 178 | + cta_r_start = pid_r * TILE_R |
| 179 | + cta_r_end = tl.minimum(cta_r_start + TILE_R, r) |
| 180 | + |
| 181 | + # cumsum for a bin_index |
| 182 | + n_offsets = pid_n * TILE_N + tl.arange(0, TILE_N) # (TILE_N, ) |
| 183 | + mask = n_offsets < N |
| 184 | + arr = tl.load(arr_ptr + pid_m * N + n_offsets, mask=mask) |
| 185 | + arr_u = convert_to_uint_preverse_order(arr, descending) |
| 186 | + key = (arr_u >> bit_offset) & bfe_mask # (TILE_N, ) |
| 187 | + |
| 188 | + # since triton can only use scalar as condition, loop by bin_index |
| 189 | + # status must be pre zero-initialized, or else we have to initialize it |
| 190 | + for bin_index in range(cta_r_start, cta_r_end): |
| 191 | + matches = tl.where(mask, key == bin_index, False) # (TILE_N, ) bool |
| 192 | + # cta level cumsum per bin |
| 193 | + # CAUTION: tl.sum in triton 3.2 does not promote type |
| 194 | + local_sum = tl.sum(matches.to(tl.uint32), axis=0) |
| 195 | + pack0 = aggregate_mask | local_sum |
| 196 | + status_offset = pid_m * (r * OUT_N) + bin_index * OUT_N + pid_n |
| 197 | + tl.store(status_ptr + status_offset, pack0, cache_modifier=".cg") |
| 198 | + |
| 199 | + # decoupled lookback |
| 200 | + exclusive_prefix = tl.zeros((), dtype=tl.uint32) |
| 201 | + i_lookback = pid_n - 1 |
| 202 | + while i_lookback >= 0: |
| 203 | + flag_offset_i = pid_m * (r * OUT_N) + bin_index * OUT_N + i_lookback |
| 204 | + pack1 = tl.load(status_ptr + flag_offset_i, volatile=True) # uin32 |
| 205 | + while pack1 == 0: |
| 206 | + pack1 = tl.load(status_ptr + flag_offset_i, volatile=True) |
| 207 | + exclusive_prefix += pack1 & v_mask |
| 208 | + if (pack1 & aggregate_mask) == aggregate_mask: |
| 209 | + i_lookback -= 1 |
| 210 | + else: |
| 211 | + i_lookback = -1 |
| 212 | + pack2 = inclusive_prefix_mask | (exclusive_prefix + local_sum) |
| 213 | + tl.store(status_ptr + status_offset, pack2, cache_modifier=".cg") |
| 214 | + |
| 215 | + local_ex_cumsum = ( |
| 216 | + tl.cumsum(matches.to(tl.uint32), axis=0) - matches |
| 217 | + ) # (TILE_N, ) |
| 218 | + ex_cumsum_in_bin = ( |
| 219 | + exclusive_prefix + local_ex_cumsum |
| 220 | + ) # global ex_cumsum_in_bin (TILE_N, ) |
| 221 | + |
| 222 | + # ex_cumsum_bins (m, n_passes, r) |
| 223 | + ex_cumsum_bins = tl.load( |
| 224 | + excumsum_bins_ptr + pid_m * (n_passes * r) + pass_id * r + bin_index |
| 225 | + ) # scalar |
| 226 | + pos = ex_cumsum_bins + ex_cumsum_in_bin # (TILE_N, ) |
| 227 | + |
| 228 | + # scatter |
| 229 | + tl.store(out_ptr + pid_m * N + pos, arr, mask=matches) |
| 230 | + if associate_arr_ptr is not None: |
| 231 | + associate_arr = tl.load( |
| 232 | + associate_arr_ptr + pid_m * N + n_offsets, mask=mask |
| 233 | + ) |
| 234 | + tl.store(associate_out_ptr + pid_m * N + pos, associate_arr, mask=matches) |
| 235 | + |
| 236 | + |
| 237 | +def radix_sort(arr, k_bits=8, descending=False): |
| 238 | + n = arr.shape[-1] |
| 239 | + m = arr.numel() // n |
| 240 | + assert n < (1 << 30), "we have not implemented 2**30 per launch" |
| 241 | + dtype = arr.dtype |
| 242 | + num_bits = 1 if dtype == torch.bool else (arr.itemsize * 8) |
| 243 | + |
| 244 | + TILE_N = 1024 |
| 245 | + tiles_n_per_cta = 8 |
| 246 | + CTA_TILE_N = tiles_n_per_cta * TILE_N |
| 247 | + |
| 248 | + num_bins = 2**k_bits |
| 249 | + n_passes = triton.cdiv(num_bits, k_bits) |
| 250 | + TILE_R = 16 |
| 251 | + |
| 252 | + grid_n = triton.cdiv(n, CTA_TILE_N) |
| 253 | + grid_for_global_hist = (m * grid_n, 1, 1) |
| 254 | + |
| 255 | + with torch_device_fn.device(arr.device): |
| 256 | + global_hist = torch.zeros( |
| 257 | + (m, n_passes, num_bins), device=arr.device, dtype=torch.int32 |
| 258 | + ) |
| 259 | + compute_global_hist_kernel[grid_for_global_hist]( |
| 260 | + arr, |
| 261 | + global_hist, |
| 262 | + n_passes, |
| 263 | + m, |
| 264 | + n, |
| 265 | + tiles_n_per_cta, |
| 266 | + TILE_N, |
| 267 | + TILE_R, |
| 268 | + k_bits, |
| 269 | + descending, |
| 270 | + ) |
| 271 | + ex_cumsum_bins = torch.cumsum(global_hist, -1) - global_hist |
| 272 | + ex_cumsum_bins = ex_cumsum_bins.to(torch.uint32) |
| 273 | + |
| 274 | + # sort |
| 275 | + arr_in = torch.clone(arr) |
| 276 | + indices_in = ( |
| 277 | + torch.arange(0, n, dtype=torch.int64, device=arr_in.device) |
| 278 | + .broadcast_to(arr.shape) |
| 279 | + .contiguous() |
| 280 | + ) |
| 281 | + arr_out = torch.empty_like(arr) |
| 282 | + indices_out = torch.empty_like(indices_in) |
| 283 | + |
| 284 | + TILE_R = 8 |
| 285 | + grid_r = triton.cdiv(num_bins, TILE_R) |
| 286 | + TILE_N = 2048 |
| 287 | + grid_n = triton.cdiv(n, TILE_N) |
| 288 | + grid_for_sweep = (m * grid_n, grid_r) |
| 289 | + |
| 290 | + status = torch.empty( |
| 291 | + (m, num_bins, grid_n), device=arr.device, dtype=torch.uint32 |
| 292 | + ) |
| 293 | + |
| 294 | + for i in range(0, n_passes): |
| 295 | + bit_offset = i * k_bits |
| 296 | + status.zero_() |
| 297 | + sweep[grid_for_sweep]( |
| 298 | + arr_in, |
| 299 | + indices_in, |
| 300 | + arr_out, |
| 301 | + indices_out, |
| 302 | + ex_cumsum_bins, |
| 303 | + status, |
| 304 | + n_passes, |
| 305 | + i, |
| 306 | + bit_offset, |
| 307 | + m, |
| 308 | + n, |
| 309 | + grid_n, |
| 310 | + TILE_N, |
| 311 | + TILE_R, |
| 312 | + k_bits, |
| 313 | + descending, |
| 314 | + ) |
| 315 | + # print(f"< sorted last {bit_offset + k_bits:>2d} bits: {arr_out}") |
| 316 | + arr_in, arr_out = arr_out, arr_in |
| 317 | + indices_in, indices_out = indices_out, indices_in |
| 318 | + |
| 319 | + return arr_in, indices_in |
| 320 | + |
| 321 | + |
15 | 322 | @libentry() |
16 | 323 | @triton.jit() |
17 | 324 | def sort_kernel( |
@@ -51,32 +358,17 @@ def sort(inp, dim=-1, descending=False): |
51 | 358 | sort_elem_cnt = inp.shape[dim] |
52 | 359 | if sort_elem_cnt == 1: |
53 | 360 | return inp, torch.zeros_like(inp, dtype=torch.int64) |
54 | | - elif sort_elem_cnt > 512: # TODO: Optimize implementation for large cases. |
55 | | - return torch.sort(inp, stable=False, dim=dim, descending=descending) |
56 | | - block_size = triton.next_power_of_2(sort_elem_cnt) |
57 | 361 |
|
58 | 362 | if dim < 0: |
59 | 363 | dim = dim + inp.ndim |
60 | 364 | if dim != inp.ndim - 1: |
61 | 365 | inp = torch.movedim(inp, dim, -1).contiguous() |
62 | 366 | else: |
63 | 367 | inp = inp.contiguous() |
64 | | - batch_size = math.prod(inp.shape) // sort_elem_cnt |
65 | | - |
66 | | - out = torch.empty_like(inp) |
67 | | - out_index = torch.empty_like(inp, dtype=torch.int64) |
68 | | - |
69 | | - with torch_device_fn.device(inp.device): |
70 | | - sort_kernel[batch_size,]( |
71 | | - inp, |
72 | | - out, |
73 | | - out_index, |
74 | | - N=sort_elem_cnt, |
75 | | - BLOCK_SIZE=block_size, |
76 | | - DESCENDING=descending, |
77 | | - IS_FLOAT=inp.is_floating_point(), |
78 | | - num_warps=4, |
79 | | - ) |
| 368 | + |
| 369 | + dtype = inp.dtype |
| 370 | + num_bits_per_pass = 1 if dtype == torch.bool else 4 |
| 371 | + out, out_index = radix_sort(inp, num_bits_per_pass, descending) |
80 | 372 |
|
81 | 373 | if dim != inp.ndim - 1: |
82 | 374 | out = torch.movedim(out, -1, dim) |
|
0 commit comments