-
Notifications
You must be signed in to change notification settings - Fork 499
Expand file tree
/
Copy pathshape_utils.py
More file actions
404 lines (316 loc) · 12 KB
/
Copy pathshape_utils.py
File metadata and controls
404 lines (316 loc) · 12 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
import enum
import functools
import operator
from typing import Iterable, Sequence, Tuple
import torch
import triton
import triton.language as tl
from ..utils import triton_lang_extension as tle
from .codegen_config_utils import get_heuristics_for_num_warps
Shape = Tuple[int]
Stride = Tuple[int]
MultiIndex = Tuple[int]
Perm = Tuple[int]
def bracket_next_power_of_2(N, lower, upper):
return min(max(triton.next_power_of_2(N), lower), upper)
def broadcast(s1: Shape, s2: Shape) -> Shape:
_s1, _s2 = s1, s2
r1 = len(s1)
if r1 == 0:
return s2
r2 = len(s2)
if r2 == 0:
return s1
s1, s2 = (s1, s2) if r1 >= r2 else (s2, s1)
r1, r2 = (r1, r2) if r1 >= r2 else (r2, r1)
d = r1 - r2
s = list(s1)
for i in range(r2):
if s1[d + i] == 1:
s[d + i] = s2[i]
elif s2[i] == 1:
s[d + i] = s1[d + i]
elif s2[i] == s1[d + i]:
s[d + i] = s2[i]
else:
raise ValueError(f"Unbroadcastable {_s1} and {_s2}")
s = tuple(s)
return s
def broadcastable(s1: Shape, s2: Shape) -> bool:
r1 = len(s1)
if r1 == 0:
return True
r2 = len(s2)
if r2 == 0:
return True
s1, s2 = (s1, s2) if r1 >= r2 else (s2, s1)
r1, r2 = (r1, r2) if r1 >= r2 else (r2, r1)
d = r1 - r2
for i in range(r2):
if s1[d + i] == 1 or s2[i] == 1 or s1[d + i] == s2[i]:
continue
return False
return True
def broadcastable_to(s1: Shape, s2: Shape) -> bool:
r1 = len(s1)
if r1 == 0:
return True
r2 = len(s2)
if r2 == 0: # r1 > 0
return False
if r1 > r2:
return False
d = r2 - r1
for i in range(r1):
if s1[i] == 1 or s1[i] == s2[d + i]:
continue
return False
return True
def broadcast_shapes(shapes: Iterable[Shape]) -> Shape:
if len(shapes) == 0:
return ()
shape = shapes[0]
for s in shapes[1:]:
shape = broadcast(shape, s)
return shape
def broadcasted_stride(shape: Shape, stride: Stride, new_shape: Shape) -> Stride:
assert broadcastable_to(shape, new_shape)
r1 = len(shape)
r2 = len(new_shape)
d = r2 - r1
new_stride = [0 for _ in range(r2)]
for i in range(r1):
new_stride[d + i] = 0 if (shape[i] == 1 and new_shape[d + i] > 1) else stride[i]
return tuple(new_stride)
def volume(shape: Shape) -> int:
return functools.reduce(operator.mul, shape, 1)
def is_valid_perm(perm: Perm) -> bool:
r = len(perm)
sorted_axes = sorted(perm)
for i in range(r):
if sorted_axes[i] != i:
return False
return True
def unravel_index(linear_offset: int, shape: Shape) -> MultiIndex:
multi_index = []
r = len(shape)
for i in range(r):
s = shape[r - 1 - i]
i = linear_offset % s
linear_offset = linear_offset // s
multi_index.append(i)
return tuple(reversed(multi_index))
def c_contiguous_stride(shape: Shape) -> Stride:
strides = []
s = 1
for size in reversed(shape):
strides.append(s)
s *= max(size, 1) # treat size 0 as size 1
return tuple(reversed(strides))
def f_contiguous_stride(shape: Shape) -> Stride:
strides = []
s = 1
for size in shape:
strides.append(s)
s *= max(size, 1) # treat size 0 as size 1
return tuple(strides)
def ordered_stride(shape: Shape, order: Perm) -> Stride:
strides = [0] * len(shape)
s = 1
for i in order:
strides[i] = s
s *= max(shape[i], 1) # treat size 0 as size 1
return tuple(strides)
def stride_order(strides):
# we also handle negative strides
return sorted(range(len(strides)), key=lambda i: abs(strides[i]))
def all_the_same_shape(tensors: Sequence[torch.Tensor]) -> bool:
if len(tensors) == 0:
return True
shape = tensors[0].shape
return all(item.shape == shape for item in tensors[1:])
def all_the_same_stride(tensors: Sequence[torch.Tensor]) -> bool:
if len(tensors) == 0:
return True
stride = tensors[0].stride()
return all(item.stride() == stride for item in tensors[1:])
def all_c_contiguous(tensors: Sequence[torch.Tensor]) -> bool:
if len(tensors) == 0:
return True
return all(tensor.is_contiguous() for tensor in tensors)
def heuristics_for_tile_size(max_tile_size, *sizes):
ndim = len(sizes)
tile_sizes = [0 for _ in range(ndim)]
for i in range(ndim):
size = sizes[ndim - 1 - i]
tile_size = min(max_tile_size, triton.next_power_of_2(size))
tile_sizes[ndim - 1 - i] = tile_size
max_tile_size = max(1, max_tile_size // tile_size)
return tuple(tile_sizes)
# This should be part of CodeGenConfig
def heuristics_for_num_warps(tile_size):
return get_heuristics_for_num_warps(tile_size)
def dim_compress(inp, dims):
if isinstance(dims, int):
dims = [dims]
dim = inp.ndim
stride = inp.stride()
batch_dim = [i for i in range(dim) if i not in dims]
sorted_reduction_dim = sorted(dims, key=lambda x: stride[x], reverse=True)
order = batch_dim + sorted_reduction_dim
return inp.permute(order).contiguous()
def size_in_bytes(a):
return a.numel() * a.element_size()
def can_use_int32_index(a):
INT32_MAX = torch.iinfo(torch.int32).max
if a.is_contiguous():
return size_in_bytes(a) <= INT32_MAX
max_offset = 0
for size, stride in zip(a.shape, a.stride()):
max_offset += size * stride
if max_offset > INT32_MAX:
return False
return True
class MemOverlap(enum.Enum):
No = 0
Yes = 1
TooHard = 2
def has_internal_overlapping(x: torch.Tensor):
if x.is_contiguous():
return MemOverlap.No
if torch.ops.aten.is_non_overlapping_and_dense(x):
return MemOverlap.No
for size, stride in zip(x.size(), x.stride()):
if size > 1 and stride == 0:
return MemOverlap.Yes
return MemOverlap.TooHard
def restride_dim(src, dim, shape, step=0, storage_offset=None):
strides = list(src.stride())
strides[dim] *= step
return src.as_strided(shape, strides, storage_offset)
def cfggen():
block_m = [1, 2, 4]
block_n = [256, 1024, 2048, 4096]
configs = [
triton.Config({"BLOCK_M": m, "BLOCK_N": n}, num_warps=4)
for m in block_m
for n in block_n
]
return configs
@triton.autotune(configs=cfggen(), key=["M", "N"])
@triton.jit
def add_on_kernel(
idx,
add_on,
cur_shape,
cur_strides,
M,
N,
BLOCK_M: tl.constexpr,
BLOCK_N: tl.constexpr,
):
pid_x = tle.program_id(axis=0)
pid_y = tle.program_id(axis=1)
rows_offset = pid_x * BLOCK_M + tl.arange(0, BLOCK_M)[:, None]
rows_mask = rows_offset < M
cols_offset = pid_y + tl.arange(0, BLOCK_N)[None, :]
cols_mask = cols_offset < N
block_mask = rows_mask and cols_mask
offsets = rows_offset * N + cols_offset
cur_idx = tl.load(idx + offsets, mask=block_mask, other=1)
mod = cur_idx % cur_shape
res = mod * cur_strides
tl.store(add_on + offsets, res, mask=block_mask)
def check_tensor_attributes(data_list, is_tensor_list):
"""
Checks if each element in data_list is a tensor and validates whether the corresponding
boolean value in is_tensor_list is correct.
Parameters:
- data_list: A list containing tensor and non-tensor objects.
- is_tensor_list: A list of boolean values indicating whether the corresponding element in data_list is a tensor.
Returns:
- True if all elements' types match their corresponding boolean values in is_tensor_list.
- Raise Error otherwise, and prints the index and element that do not match.
"""
# Check if both lists have the same length
if len(data_list) != len(is_tensor_list):
raise ValueError(
"Error: The lists of inputs and is_tensor must have the same length."
)
for i, (data, is_tensor) in enumerate(zip(data_list, is_tensor_list)):
actual_is_tensor = isinstance(data, torch.Tensor)
if actual_is_tensor != is_tensor:
raise ValueError(
f"Element at index {i} is incorrect. Expected {is_tensor}, but got {actual_is_tensor}."
)
return True
_initial_missing = object()
def offset_calculator(inp, idx, strides, dim, isInp):
"""
Calculate the flat index(a.k.a offset) for a given ravel index in a multi-dimensional array.
The formula can be seen in:
- https://numpy.org/doc/stable/reference/arrays.ndarray.html#internal-memory-layout-of-an-ndarray
- https://numpy.org/devdocs/user/basics.indexing.html#single-element-indexing
Parameters:
inp (tensor): The input multi-dimensional array from which the offset is calculated.
idx (tensor): The linear index for which the offset is to be calculated.
strides (list of int): A list containing the stride lengths for each dimension of the input array.
dim (int): The specific dimension for which the index offset needs to be calculated.
isInp (bool): A flag indicating whether the tensor 'inp' is the parameter 'self'
in scatter/gather/index_* operators or not.
In operators such as scatter/gather and index_*, when the input tensor 'inp'
is the 'self' tensor to be processed, we may need to modify its offsets later.
For instance, in the scatter operator, the offset is calculated using the formula:
inp_offset = origin_offset - stride[dim] * n_dim + stride[dim] * index.
In this case, we return the fixed part of the formula:
origin_offset - stride[dim] * n_dim,
to facilitate subsequent modifications.
For other types of input 'inp', we return the complete calculation result
of origin_offsets directly.
Returns:
The calculated offset. If isInp is True, the fixed offset is returned; otherwise, the origin offset is returned.
Note:
The function includes a comment suggesting the potential optimization of division and modulus operations,
which may be beneficial if this function is called frequently.
See also:
- https://ridiculousfish.com/blog/posts/labor-of-division-episode-i.html
- Division by Invariant Integers Using Multiplication,
Torbjörn Granlund and Peter L. Montgomery, 1994.
"""
ndim = inp.ndim
shape = list(inp.shape)
offsets = torch.zeros_like(inp, dtype=torch.int32, device=inp.device)
idx_dim = torch.zeros_like(inp, dtype=torch.int32, device=inp.device)
for d in range(0, ndim):
add_on = torch.zeros_like(inp, dtype=torch.int32, device=inp.device)
N = idx.size(idx.ndim - 1)
M = idx.numel() // N
grid = lambda meta: (
triton.cdiv(M, meta["BLOCK_M"]),
triton.cdiv(N, meta["BLOCK_N"]),
)
add_on_kernel[grid](idx, add_on, shape[d], strides[d], M, N)
offsets = torch.add(offsets, add_on)
if d == dim:
idx_dim = add_on
idx = idx // shape[d]
return offsets if not isInp else (offsets - idx_dim)
def offsetCalculator(inp, idx, strides, dim, isInp):
ndim = inp.ndim
shape = list(inp.shape)
offsets = 0
idx_dim = 0
for d in range(0, ndim):
mod = idx % shape[d]
add_on = mod * strides[d]
offsets += add_on
if d == dim:
idx_dim = add_on
idx = idx // shape[d]
# FIXME: Should we write a fast div/mod
# to boost the '%' and '//'? (Since they may be run many times)
# See also:
# - https://ridiculousfish.com/blog/posts/labor-of-division-episode-i.html
# - Division by Invariant Integers Using Multiplication,
# Torbjörn Granlund and Peter L. Montgomery, 1994.
return (offsets) if not isInp else (offsets - idx_dim)