Skip to content

Commit dc83f62

Browse files
committed
Support arange with start,stop,step
Signed-off-by: Ziheng Deng <zihengd@nvidia.com>
1 parent ea1ce14 commit dc83f62

5 files changed

Lines changed: 99 additions & 17 deletions

File tree

changelog.d/arange.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
- Extend ``ct.arange()`` with optional ``start`` and ``step`` arguments:
2+
``ct.arange(size, start=0, step=1, dtype=...)``. ``size`` must be a constant
3+
integer, while ``start`` and ``step`` may be dynamic numbers. For example,
4+
``ct.arange(8, start=7, step=-1, dtype=ct.int32)`` creates a reversed range.

src/cuda/tile/_ir/ops.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2891,12 +2891,17 @@ def arange(size: int, dtype: DType) -> Var:
28912891

28922892

28932893
@impl(ct.arange)
2894-
def arange_impl(size: Var, dtype: Var) -> Var:
2894+
def arange_impl(size: Var, dtype: Var, start: Var, step: Var) -> Var:
28952895
size_val = require_constant_int(size)
28962896
dtype_val = require_dtype_spec(dtype)
28972897
if not _is_power_of_2(size_val):
28982898
raise TileTypeError(f"Result tile shape must be power of 2, got {size_val}")
2899-
return arange(size_val, dtype_val)
2899+
result = arange(size_val, dtype_val)
2900+
if not (step.is_constant() and step.get_constant() == 1):
2901+
result = binary_arithmetic_tensorlike("mul", result, astype(step, dtype_val))
2902+
if not (start.is_constant() and start.get_constant() == 0):
2903+
result = binary_arithmetic_tensorlike("add", result, astype(start, dtype_val))
2904+
return result
29002905

29012906

29022907
@impl(ct.reshape)

src/cuda/tile/_stub.py

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1893,12 +1893,15 @@ def atomic_xor(array, indices, update, /, *,
18931893

18941894

18951895
@stub
1896-
def arange(size, /, *, dtype) -> Tile:
1897-
"""Creates a tile with value starting from 0 to `size - 1`.
1896+
def arange(size, /, *, dtype, start=0, step=1) -> Tile:
1897+
"""Creates a 1-D tile of length ``size`` with values
1898+
``start, start + step, ..., start + (size - 1) * step``.
18981899
18991900
Args:
1900-
size (const int): Size of the tile.
1901+
size (const int): Size of the tile. Must be a constant integer that is a power of two.
19011902
dtype (DType): Datatype of the tile.
1903+
start: Value of the first element. Defaults to ``0``.
1904+
step: The gap between adjacent values. Defaults to ``1``.
19021905
19031906
Returns:
19041907
Tile:
@@ -1908,12 +1911,21 @@ def arange(size, /, *, dtype) -> Tile:
19081911
.. testcode::
19091912
:template: kernel_wrapper.py
19101913
1911-
tile = ct.arange(4, dtype=ct.int32)
1912-
print(tile)
1914+
tile_0 = ct.arange(4, dtype=ct.int32)
1915+
print(tile_0)
1916+
tile_1 = ct.arange(8, start=2, dtype=ct.int32)
1917+
print(tile_1)
1918+
tile_2 = ct.arange(4, start=2, step=2, dtype=ct.int32)
1919+
print(tile_2)
1920+
tile_3 = ct.arange(8, start=7, step=-1, dtype=ct.int32)
1921+
print(tile_3)
19131922
19141923
.. testoutput::
19151924
19161925
[0, 1, 2, 3]
1926+
[2, 3, 4, 5, 6, 7, 8, 9]
1927+
[2, 4, 6, 8]
1928+
[7, 6, 5, 4, 3, 2, 1, 0]
19171929
"""
19181930

19191931

test/test_generate.py

Lines changed: 67 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,24 +7,85 @@
77

88
from math import ceil
99
import cuda.tile as ct
10-
from util import assert_equal
10+
from cuda.tile import TileTypeError
11+
from util import assert_equal, assert_close
1112
from conftest import int_dtypes, float_dtypes, dtype_id
1213

1314

1415
@ct.kernel
15-
def arange(x, TILE: ct.Constant[int]):
16+
def arange_dynamic_start_step(x, step, TILE: ct.Constant[int]):
1617
bid = ct.bid(0)
17-
start = ct.astype(bid * TILE, x.dtype)
18-
tx = start + ct.arange(TILE, dtype=x.dtype)
18+
tx = ct.arange(TILE, start=bid * TILE, step=step, dtype=x.dtype)
1919
ct.store(x, index=(bid,), tile=tx)
2020

2121

2222
@pytest.mark.parametrize("shape", [(128,)])
2323
@pytest.mark.parametrize("tile", [64])
2424
@pytest.mark.parametrize("dtype", int_dtypes + float_dtypes, ids=dtype_id)
25-
def test_arange(shape, dtype, tile):
25+
def test_arange_dynamic_start_step(shape, dtype, tile):
2626
x = torch.zeros(shape, dtype=dtype, device='cuda')
2727
grid = (ceil(shape[0] / tile), 1, 1)
28-
ct.launch(torch.cuda.current_stream(), grid, arange, (x, tile))
28+
ct.launch(torch.cuda.current_stream(), grid, arange_dynamic_start_step, (x, 1, tile))
2929
ref = torch.arange(len(x), dtype=dtype, device=x.device)
3030
assert_equal(x, ref)
31+
32+
33+
@pytest.mark.parametrize("size,start,step", [
34+
(128, None, None), # arange(size)
35+
(64, 8, None), (64, -16, None), # arange(size, start)
36+
(64, 0, 2), (64, 64, -1), (16, -8, -3), (4, 8.5, -1.1), # arange(size, start, step)
37+
(8, 10, 0) # step=0
38+
])
39+
@pytest.mark.parametrize("dtype", int_dtypes + float_dtypes, ids=dtype_id)
40+
def test_arange(size, start, step, dtype):
41+
@ct.kernel
42+
def arange_kernel(x):
43+
if start is None:
44+
tx = ct.arange(size, dtype=x.dtype)
45+
elif step is None:
46+
tx = ct.arange(size, start=start, dtype=x.dtype)
47+
else:
48+
tx = ct.arange(size, start=start, step=step, dtype=x.dtype)
49+
ct.store(x, index=(0,), tile=tx)
50+
51+
x = torch.zeros(size, dtype=dtype, device='cuda')
52+
ct.launch(torch.cuda.current_stream(), (1, 1, 1), arange_kernel, (x,))
53+
if step == 0:
54+
ref = torch.full((size,), start, dtype=dtype, device=x.device)
55+
else:
56+
start = 0 if start is None else start
57+
step = 1 if step is None else step
58+
ref = torch.arange(start, start + size * step, step, dtype=dtype, device=x.device)
59+
assert_close(x, ref)
60+
61+
62+
@pytest.mark.parametrize("size,start,step,error_message", [
63+
(3, None, None, "Result tile shape must be power of 2"),
64+
(5, 0, 2, "Result tile shape must be power of 2"),
65+
(0.1, None, None, 'Expected an integer constant')
66+
])
67+
def test_arange_invalid_size(size, start, step, error_message):
68+
@ct.kernel
69+
def arange_kernel(x):
70+
if start is None:
71+
tx = ct.arange(size, dtype=x.dtype)
72+
elif step is None:
73+
tx = ct.arange(size, start=start, dtype=x.dtype)
74+
else:
75+
tx = ct.arange(size, start=start, step=step, dtype=x.dtype)
76+
ct.store(x, index=(0,), tile=tx)
77+
78+
with pytest.raises(TileTypeError, match=error_message):
79+
x = torch.zeros(1, dtype=torch.int32, device='cuda')
80+
ct.launch(torch.cuda.current_stream(), (1, 1, 1), arange_kernel, (x,))
81+
82+
83+
def test_arange_reject_dynamic_size():
84+
@ct.kernel
85+
def arange_dynamic_size(x):
86+
tx = ct.arange(ct.bid(0), dtype=x.dtype)
87+
ct.store(x, index=(0,), tile=tx)
88+
89+
with pytest.raises(TileTypeError, match="Expected an integer constant"):
90+
x = torch.zeros(1, dtype=torch.int32, device='cuda')
91+
ct.launch(torch.cuda.current_stream(), (1, 1, 1), arange_dynamic_size, (x,))

test/test_token_order.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,12 @@ def store_buffer(X, TILE: ct.Constant[int]):
2222
ct.store(X, index=(0,), tile=tx0)
2323
# reverse so that each SIMT thread is less likely to be assigned the same address
2424
X_ALIAS = X
25-
reverse_offset = TILE - 1 - ct.arange(TILE, dtype=np.int32)
25+
reverse_offset = ct.arange(TILE, start=TILE - 1, step=-1, dtype=np.int32)
2626
tx1 = ct.gather(X_ALIAS, reverse_offset)
2727
ct.store(X_ALIAS, index=(0,), tile=tx1)
2828

2929
def store_buffer_alternative(X, TILE: ct.Constant[int]):
30-
reverse_offset = TILE - 1 - ct.arange(TILE, dtype=np.int32)
30+
reverse_offset = ct.arange(TILE, start=TILE - 1, step=-1, dtype=np.int32)
3131
tx2 = ct.arange(TILE, dtype=X.dtype)
3232
ct.scatter(X, reverse_offset, tx2)
3333
tx3 = ct.load(X, index=(0,), shape=(TILE,))
@@ -37,7 +37,7 @@ def serialized_for_loop(X, TILE: ct.Constant[int]):
3737
ct.store(X, index=(0,), tile=ct.arange(TILE, dtype=X.dtype))
3838
# flip the buffer 3 times
3939
for i in range(3):
40-
reverse_offset = TILE - 1 - ct.arange(TILE, dtype=np.int32)
40+
reverse_offset = ct.arange(TILE, start=TILE - 1, step=-1, dtype=np.int32)
4141
tx = ct.gather(X, reverse_offset)
4242
ct.store(X, index=(0,), tile=tx)
4343

@@ -46,7 +46,7 @@ def serialized_while_loop(X, TILE: ct.Constant[int]):
4646
# flip the buffer 3 times
4747
i = 0
4848
while i < 3:
49-
reverse_offset = TILE - 1 - ct.arange(TILE, dtype=np.int32)
49+
reverse_offset = ct.arange(TILE, start=TILE - 1, step=-1, dtype=np.int32)
5050
tx = ct.gather(X, reverse_offset)
5151
ct.store(X, index=(0,), tile=tx)
5252
i += 1

0 commit comments

Comments
 (0)