Skip to content

Commit ea274d3

Browse files
fix: fix sum_dim_out and trunc_div type issues (#2105)
1 parent df95ebf commit ea274d3

4 files changed

Lines changed: 137 additions & 7 deletions

File tree

src/flag_gems/ops/div.py

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,17 +71,44 @@ def trunc_div_func(x, y):
7171
@pointwise_dynamic(is_tensor=[True, False], promotion_methods=[(0, 1, "DEFAULT")])
7272
@triton.jit
7373
def trunc_div_func_tensor_scalar(x, y):
74-
return trunc(div_rz(x, y))
74+
return trunc(div_rz(x, tl.cast(y, x.dtype)))
7575

7676

7777
@pointwise_dynamic(is_tensor=[False, True], promotion_methods=[(0, 1, "DEFAULT")])
7878
@triton.jit
7979
def trunc_div_func_scalar_tensor(x, y):
80-
return trunc(div_rz(x, y))
80+
return trunc(div_rz(tl.cast(x, y.dtype), y))
81+
82+
83+
# Integer truncation division: Triton's // on integers is C-style (truncates toward zero)
84+
@pointwise_dynamic(promotion_methods=[(0, 1, "DEFAULT")])
85+
@triton.jit
86+
def trunc_div_int_func(x, y):
87+
return x // y
88+
89+
90+
@pointwise_dynamic(is_tensor=[True, False], promotion_methods=[(0, 1, "DEFAULT")])
91+
@triton.jit
92+
def trunc_div_int_func_tensor_scalar(x, y):
93+
return x // y
94+
95+
96+
@pointwise_dynamic(is_tensor=[False, True], promotion_methods=[(0, 1, "DEFAULT")])
97+
@triton.jit
98+
def trunc_div_int_func_scalar_tensor(x, y):
99+
return x // y
81100

82101

83102
def trunc_divide(A, B):
84103
logger.debug("GEMS TRUNC_DIVIDE")
104+
# Integer types: use dedicated int kernels (Triton // is C-style truncation)
105+
if isinstance(A, torch.Tensor) and not A.is_floating_point():
106+
if isinstance(B, torch.Tensor):
107+
return trunc_div_int_func(A, B)
108+
else:
109+
return trunc_div_int_func_tensor_scalar(A, B)
110+
if isinstance(B, torch.Tensor) and not B.is_floating_point():
111+
return trunc_div_int_func_scalar_tensor(A, B)
85112
if isinstance(A, torch.Tensor) and isinstance(B, torch.Tensor):
86113
return trunc_div_func(A, B)
87114
elif isinstance(A, torch.Tensor):
@@ -95,6 +122,12 @@ def trunc_divide(A, B):
95122

96123
def trunc_divide_(A, B):
97124
logger.debug("GEMS TRUNC_DIVIDE_")
125+
# Integer types: use dedicated int kernels (Triton // is C-style truncation)
126+
if not A.is_floating_point():
127+
if isinstance(B, torch.Tensor):
128+
return trunc_div_int_func(A, B, out0=A)
129+
else:
130+
return trunc_div_int_func_tensor_scalar(A, B, out0=A)
98131
if isinstance(B, torch.Tensor):
99132
return trunc_div_func(A, B, out0=A)
100133
else:

src/flag_gems/ops/sum.py

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,16 @@ def sum_dim_comm(inp, dim=None, keepdim=False, *, dtype=None, out=None):
267267
inp = inp.contiguous()
268268
K = inp.numel() // M // N
269269
shape[dim] = 1
270-
if out is None:
270+
_out_provided = out is not None
271+
if _out_provided:
272+
# Resize out to the expected output shape, matching native PyTorch
273+
# sum.out behavior. The caller (e.g. logsumexp) may pass a
274+
# zero-size placeholder that needs to be resized before use.
275+
if keepdim:
276+
out.resize_(shape)
277+
else:
278+
out.resize_(shape[:dim] + shape[dim + 1 :])
279+
else:
271280
out = torch.empty(shape, dtype=dtype, device=inp.device)
272281

273282
with torch_device_fn.device(inp.device):
@@ -288,7 +297,7 @@ def sum_dim_comm(inp, dim=None, keepdim=False, *, dtype=None, out=None):
288297
M,
289298
N,
290299
)
291-
if not keepdim:
300+
if not keepdim and not _out_provided:
292301
out = out.squeeze(dim=dim)
293302
return out
294303
else:
@@ -298,14 +307,22 @@ def sum_dim_comm(inp, dim=None, keepdim=False, *, dtype=None, out=None):
298307
N *= shape[i]
299308
shape[i] = 1
300309
M = inp.numel() // N
301-
if out is None:
310+
_out_provided = out is not None
311+
if _out_provided:
312+
dim_set = set(dim)
313+
if keepdim:
314+
out.resize_(shape)
315+
else:
316+
out.resize_([s for i, s in enumerate(shape) if i not in dim_set])
317+
else:
302318
out = torch.empty(shape, dtype=dtype, device=inp.device)
303319

304320
grid = lambda meta: (triton.cdiv(M, meta["BLOCK_M"]),)
305321
with torch_device_fn.device(inp.device):
306322
sum_dim_kernel[grid](inp, out, M, N)
307-
if not keepdim:
308-
out = out.squeeze(dim=dim)
323+
if not keepdim and not _out_provided:
324+
for d in sorted(dim, reverse=True):
325+
out = out.squeeze(dim=d)
309326
return out
310327

311328

tests/test_binary_pointwise_ops.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -722,6 +722,58 @@ def test_accuracy_trunc_divide_scalar_scalar(dtype):
722722
gems_assert_close(res_out, ref_out, dtype)
723723

724724

725+
@pytest.mark.div
726+
@pytest.mark.parametrize("shape", POINTWISE_SHAPES)
727+
@pytest.mark.parametrize("dtype", INT_DTYPES + [torch.int64])
728+
def test_trunc_div_int(shape, dtype):
729+
# Regression test: integer types must be dispatched at Python layer to avoid
730+
# passing int tensors to div_rz which only supports floating point.
731+
inp1 = torch.randint(1, 100, shape, dtype=dtype, device=flag_gems.device)
732+
inp2 = torch.randint(1, 100, shape, dtype=dtype, device=flag_gems.device)
733+
ref_inp1 = to_reference(inp1, False)
734+
ref_inp2 = to_reference(inp2, False)
735+
736+
ref_out = torch.div(ref_inp1, ref_inp2, rounding_mode="trunc")
737+
with flag_gems.use_gems():
738+
res_out = torch.div(inp1, inp2, rounding_mode="trunc")
739+
740+
gems_assert_equal(res_out, ref_out)
741+
742+
743+
@pytest.mark.div
744+
@pytest.mark.parametrize("shape", POINTWISE_SHAPES)
745+
@pytest.mark.parametrize("dtype", INT_DTYPES + [torch.int64])
746+
def test_trunc_div_tensor_scalar_int(shape, dtype):
747+
# Regression test: integer types must be dispatched at Python layer to avoid
748+
# passing int tensors to div_rz which only supports floating point.
749+
inp1 = torch.randint(1, 100, shape, dtype=dtype, device=flag_gems.device)
750+
scalar = random.randint(1, 10)
751+
ref_inp1 = to_reference(inp1, False)
752+
753+
ref_out = torch.div(ref_inp1, scalar, rounding_mode="trunc")
754+
with flag_gems.use_gems():
755+
res_out = torch.div(inp1, scalar, rounding_mode="trunc")
756+
757+
gems_assert_equal(res_out, ref_out)
758+
759+
760+
@pytest.mark.div
761+
@pytest.mark.parametrize("shape", POINTWISE_SHAPES)
762+
@pytest.mark.parametrize("dtype", INT_DTYPES + [torch.int64])
763+
def test_trunc_div_scalar_tensor_int(shape, dtype):
764+
# Regression test: integer types must be dispatched at Python layer to avoid
765+
# passing int tensors to div_rz which only supports floating point.
766+
inp2 = torch.randint(1, 100, shape, dtype=dtype, device=flag_gems.device)
767+
scalar = random.randint(1, 100)
768+
ref_inp2 = to_reference(inp2, False)
769+
770+
ref_out = torch.div(scalar, ref_inp2, rounding_mode="trunc")
771+
with flag_gems.use_gems():
772+
res_out = torch.div(scalar, inp2, rounding_mode="trunc")
773+
774+
gems_assert_equal(res_out, ref_out)
775+
776+
725777
# TODO: failed at large size, eg. (65536 * 2048,)
726778
@pytest.mark.floor_divide
727779
@pytest.mark.parametrize("shape", POINTWISE_SHAPES)

tests/test_general_reduction_ops.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -475,6 +475,34 @@ def test_accuracy_sum_dim(shape, dim, keepdim, dtype):
475475
gems_assert_close(res_out, ref_out, dtype, reduce_dim=_dim)
476476

477477

478+
@pytest.mark.sum
479+
@pytest.mark.parametrize("shape", REDUCTION_SHAPES)
480+
@pytest.mark.parametrize("keepdim, dim", KEEPDIM_DIM)
481+
@pytest.mark.parametrize("dtype", FLOAT_DTYPES)
482+
def test_accuracy_sum_dim_out(shape, dim, keepdim, dtype):
483+
# Regression test: sum_dim_out must resize external out tensor and skip squeeze.
484+
inp = torch.randn(shape, dtype=dtype, device=flag_gems.device)
485+
ref_inp = to_reference(inp, True)
486+
487+
ref_result = torch.sum(ref_inp, dim=dim, keepdim=keepdim)
488+
489+
# Pre-allocate out tensor with wrong shape to test resize logic
490+
out = torch.empty((1,), dtype=dtype, device=flag_gems.device)
491+
with flag_gems.use_gems():
492+
res_result = torch.sum(inp, dim=dim, keepdim=keepdim, out=out)
493+
494+
if isinstance(dim, int):
495+
dim = [dim]
496+
dim = [d % inp.ndim for d in dim]
497+
_dim = 1
498+
for d in dim:
499+
_dim *= shape[d]
500+
if dim == []:
501+
_dim = inp.numel()
502+
gems_assert_close(res_result, ref_result, dtype, reduce_dim=_dim)
503+
gems_assert_close(out, ref_result, dtype, reduce_dim=_dim)
504+
505+
478506
QUANTILE_SHAPES = REDUCTION_SMALL_SHAPES + [(10, 64, 196), (65535, 1)]
479507
QUANTILE_FLOAT_DTYPES = [torch.float32]
480508
QUANTILE_Q = (

0 commit comments

Comments
 (0)