Skip to content

Commit beb5ae9

Browse files
authored
Fix fill operators crash on non-contiguous tensors (stride != 1) in hopper backend (flagos-ai#2560)
1 parent aaf4f55 commit beb5ae9

2 files changed

Lines changed: 88 additions & 12 deletions

File tree

src/flag_gems/runtime/backend/_nvidia/hopper/ops/fill.py

Lines changed: 45 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,17 @@ def fill_tensor_kernel(
4343
tl.store(out_ptr + offsets, val, mask=mask)
4444

4545

46+
def _as_contiguous(tensor):
47+
"""Return tensor.contiguous() view for use with flat-offset kernels.
48+
49+
For non-contiguous tensors this allocates a new buffer; callers that
50+
need in-place semantics must copy back afterwards.
51+
"""
52+
if tensor.is_contiguous():
53+
return tensor, False
54+
return tensor.contiguous(), True
55+
56+
4657
def fill_scalar(input, value):
4758
logger.debug("GEMS_HOPPER FILL_SCALAR")
4859
out = torch.empty_like(input)
@@ -57,10 +68,13 @@ def fill_scalar_out(input, value, *, out=None):
5768
logger.debug("GEMS_HOPPER FILL_SCALAR_OUT")
5869
if out is None:
5970
return fill_scalar(input, value)
60-
n_elements = out.numel()
71+
out_contig, need_copy = _as_contiguous(out)
72+
n_elements = out_contig.numel()
6173
grid = (triton.cdiv(n_elements, 1024),)
6274
with torch_device_fn.device(input.device):
63-
fill_scalar_kernel[grid](out, value, n_elements, BLOCK_SIZE=1024)
75+
fill_scalar_kernel[grid](out_contig, value, n_elements, BLOCK_SIZE=1024)
76+
if need_copy:
77+
out.copy_(out_contig)
6478
return out
6579

6680

@@ -90,10 +104,13 @@ def fill_tensor_out(input, value, *, out=None):
90104
raise RuntimeError(
91105
f"fill only supports 0-dimension value tensor but got tensor with {value.ndim} dimensions."
92106
)
93-
n_elements = out.numel()
107+
out_contig, need_copy = _as_contiguous(out)
108+
n_elements = out_contig.numel()
94109
grid = (triton.cdiv(n_elements, 1024),)
95110
with torch_device_fn.device(input.device):
96-
fill_tensor_kernel[grid](out, value, n_elements, BLOCK_SIZE=1024)
111+
fill_tensor_kernel[grid](out_contig, value, n_elements, BLOCK_SIZE=1024)
112+
if need_copy:
113+
out.copy_(out_contig)
97114
return out
98115

99116

@@ -105,17 +122,33 @@ def fill_tensor_(self, value):
105122
raise RuntimeError(
106123
f"fill only supports 0-dimension value tensor but got tensor with {value.ndim} dimensions."
107124
)
108-
n_elements = self.numel()
109-
grid = (triton.cdiv(n_elements, 1024),)
110-
with torch_device_fn.device(self.device):
111-
fill_tensor_kernel[grid](self, value, n_elements, BLOCK_SIZE=1024)
125+
if self.is_contiguous():
126+
n_elements = self.numel()
127+
grid = (triton.cdiv(n_elements, 1024),)
128+
with torch_device_fn.device(self.device):
129+
fill_tensor_kernel[grid](self, value, n_elements, BLOCK_SIZE=1024)
130+
else:
131+
tmp = self.contiguous()
132+
n_elements = tmp.numel()
133+
grid = (triton.cdiv(n_elements, 1024),)
134+
with torch_device_fn.device(self.device):
135+
fill_tensor_kernel[grid](tmp, value, n_elements, BLOCK_SIZE=1024)
136+
self.copy_(tmp)
112137
return self
113138

114139

115140
def fill_scalar_(self, value):
116141
logger.debug("GEMS_HOPPER FILL_SCALAR_")
117-
n_elements = self.numel()
118-
grid = (triton.cdiv(n_elements, 1024),)
119-
with torch_device_fn.device(self.device):
120-
fill_scalar_kernel[grid](self, value, n_elements, BLOCK_SIZE=1024)
142+
if self.is_contiguous():
143+
n_elements = self.numel()
144+
grid = (triton.cdiv(n_elements, 1024),)
145+
with torch_device_fn.device(self.device):
146+
fill_scalar_kernel[grid](self, value, n_elements, BLOCK_SIZE=1024)
147+
else:
148+
tmp = self.contiguous()
149+
n_elements = tmp.numel()
150+
grid = (triton.cdiv(n_elements, 1024),)
151+
with torch_device_fn.device(self.device):
152+
fill_scalar_kernel[grid](tmp, value, n_elements, BLOCK_SIZE=1024)
153+
self.copy_(tmp)
121154
return self

tests/test_fill.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,32 @@ def test_fill_scalar_(value, shape, dtype):
9797
with flag_gems.use_gems():
9898
x.fill_(value)
9999

100+
101+
FILL_SLICE_CASES = [
102+
# (shape, slice)
103+
((4, 128), (slice(None), slice(64, None))),
104+
((2, 1, 1, 512), (slice(None), slice(None), slice(None), slice(358, None))),
105+
((8, 32, 64), (slice(None), slice(16, None))),
106+
]
107+
108+
109+
@pytest.mark.fill_scalar_
110+
@pytest.mark.parametrize("shape, slc", FILL_SLICE_CASES)
111+
@pytest.mark.parametrize("dtype", utils.FLOAT_DTYPES + utils.BOOL_TYPES)
112+
@pytest.mark.parametrize(
113+
"value", [0, 1, True, float("-inf")], ids=["zero", "one", "true", "neginf"]
114+
)
115+
def test_fill_sliced_view_scalar(shape, slc, dtype, value):
116+
if dtype == torch.bool and value == float("-inf"):
117+
pytest.skip("bool tensor does not support -inf")
118+
119+
x = torch.randn(shape, device=flag_gems.device).to(dtype)
120+
ref_x = utils.to_reference(x, False)
121+
122+
ref_x[slc] = value
123+
with flag_gems.use_gems():
124+
x[slc] = value
125+
100126
utils.gems_assert_equal(x, ref_x)
101127

102128

@@ -120,3 +146,20 @@ def test_fill_(value, shape, dtype):
120146
x.fill_(value_tensor)
121147

122148
utils.gems_assert_equal(x, ref_x)
149+
150+
151+
@pytest.mark.fill_tensor_
152+
@pytest.mark.parametrize("shape, slc", FILL_SLICE_CASES)
153+
@pytest.mark.parametrize("dtype", utils.FLOAT_DTYPES + utils.BOOL_TYPES)
154+
@pytest.mark.parametrize("value", [0, 1, True], ids=["zero", "one", "true"])
155+
def test_fill_sliced_view_tensor(shape, slc, dtype, value):
156+
x = torch.randn(shape, device=flag_gems.device).to(dtype)
157+
ref_x = utils.to_reference(x, False)
158+
159+
value_tensor = torch.tensor(value, device=flag_gems.device, dtype=dtype)
160+
ref_value_tensor = utils.to_reference(value_tensor, False)
161+
ref_x[slc] = ref_value_tensor
162+
with flag_gems.use_gems():
163+
x[slc] = value_tensor
164+
165+
utils.gems_assert_equal(x, ref_x)

0 commit comments

Comments
 (0)