Skip to content

Commit 1421de1

Browse files
committed
Fix mixed-dtype matmul: correct output type conversion in addmm/baddbmm kernels
1 parent 843fe6c commit 1421de1

8 files changed

Lines changed: 169 additions & 7 deletions

File tree

src/flag_gems/ops/addmm.py

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,21 @@
1010
from flag_gems.utils import broadcastable_to, libentry, libtuner
1111
from flag_gems.utils import triton_lang_extension as tle
1212

13+
_ordered_datatypes = [torch.float16, torch.bfloat16, torch.float32, torch.float64]
14+
15+
16+
def get_higher_dtype(a, b):
17+
if a is b:
18+
return a
19+
assert a in _ordered_datatypes
20+
assert b in _ordered_datatypes
21+
for d in _ordered_datatypes:
22+
if a is d:
23+
return b
24+
if b is d:
25+
return a
26+
27+
1328
logger = logging.getLogger(__name__)
1429

1530

@@ -76,6 +91,9 @@ def addmm_kernel(
7691
if IS_FP64:
7792
a = a.to(tl.float32)
7893
b = b.to(tl.float32)
94+
if a.dtype != b.dtype:
95+
a = a.to(tl.float32)
96+
b = b.to(tl.float32)
7997
accumulator += tl.dot(a, b, allow_tf32=False)
8098
a_ptrs += BLOCK_SIZE_K * stride_ak
8199
b_ptrs += BLOCK_SIZE_K * stride_bk
@@ -88,7 +106,7 @@ def addmm_kernel(
88106
bias = tl.load(i_ptrs, mask=c_mask, other=0.0)
89107

90108
accumulator = accumulator * alpha + bias * beta
91-
c = accumulator.to(bias.dtype)
109+
c = accumulator.to(c_ptr.dtype.element_ty)
92110
tl.store(c_ptrs, c, mask=c_mask)
93111

94112

@@ -112,7 +130,8 @@ def addmm(bias, mat1, mat2, *, beta=1, alpha=1):
112130
)
113131
mat1 = mat1.contiguous()
114132
# mat2 = mat2.contiguous()
115-
out = torch.empty((M, N), device=mat1.device, dtype=mat1.dtype)
133+
c_dtype = get_higher_dtype(mat1.dtype, mat2.dtype)
134+
out = torch.empty((M, N), device=mat1.device, dtype=c_dtype)
116135
bias = bias.broadcast_to(out.shape)
117136

118137
grid = lambda META: (
@@ -151,7 +170,8 @@ def addmm_out(bias, mat1, mat2, *, beta=1, alpha=1, out=None):
151170
M, K = mat1.shape
152171
_, N = mat2.shape
153172
if out is None:
154-
out = torch.empty((M, N), device=mat1.device, dtype=mat1.dtype)
173+
c_dtype = get_higher_dtype(mat1.dtype, mat2.dtype)
174+
out = torch.empty((M, N), device=mat1.device, dtype=c_dtype)
155175
else:
156176
assert out.shape == (M, N), "Incompatible output shape"
157177
logger.debug(

src/flag_gems/ops/baddbmm.py

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,21 @@
1212
from .bmm import bmm
1313
from .mul import mul
1414

15+
_ordered_datatypes = [torch.float16, torch.bfloat16, torch.float32, torch.float64]
16+
17+
18+
def get_higher_dtype(a, b):
19+
if a is b:
20+
return a
21+
assert a in _ordered_datatypes
22+
assert b in _ordered_datatypes
23+
for d in _ordered_datatypes:
24+
if a is d:
25+
return b
26+
if b is d:
27+
return a
28+
29+
1530
logger = logging.getLogger(__name__)
1631

1732

@@ -116,6 +131,9 @@ def baddbmm_kernel(
116131
mask_b = mask_k[:, None] & mask_n[None, :]
117132
a = tl.load(a_ptrs, mask=mask_a)
118133
b = tl.load(b_ptrs, mask=mask_b)
134+
if a.dtype != b.dtype:
135+
a = a.to(tl.float32)
136+
b = b.to(tl.float32)
119137
accumulator += tl.dot(a, b, allow_tf32=False)
120138
offs_k += TILE_K
121139
a_ptrs += TILE_K
@@ -134,7 +152,7 @@ def baddbmm_kernel(
134152

135153
bi = tl.load(bias_ptrs, mask=mask_c)
136154
out = accumulator * alpha + bi * beta
137-
o = out.to(bi.dtype)
155+
o = out.to(o_ptrs.dtype.element_ty)
138156
tl.store(o_ptrs, o, mask=mask_c)
139157

140158

@@ -151,7 +169,8 @@ def forward(ctx, bias, A, B, beta, alpha):
151169
_, _, N = B.shape
152170
A = A.contiguous()
153171
B = B.contiguous()
154-
out = torch.empty((batch, M, N), dtype=A.dtype, device=A.device)
172+
c_dtype = get_higher_dtype(A.dtype, B.dtype)
173+
out = torch.empty((batch, M, N), dtype=c_dtype, device=A.device)
155174

156175
bbias = torch.broadcast_to(bias, (batch, M, N)).contiguous()
157176
bias_batch_stride = bbias.stride(0)

src/flag_gems/ops/bmm.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,21 @@
1010
from flag_gems.utils import libentry, libtuner
1111
from flag_gems.utils import triton_lang_extension as tle
1212

13+
_ordered_datatypes = [torch.float16, torch.bfloat16, torch.float32, torch.float64]
14+
15+
16+
def get_higher_dtype(a, b):
17+
if a is b:
18+
return a
19+
assert a in _ordered_datatypes
20+
assert b in _ordered_datatypes
21+
for d in _ordered_datatypes:
22+
if a is d:
23+
return b
24+
if b is d:
25+
return a
26+
27+
1328
logger = logging.getLogger(__name__)
1429

1530

@@ -129,6 +144,9 @@ def bmm_kernel(
129144
a_ptrs += TILE_K * stride_ak
130145
b_ptrs += TILE_K * stride_bk
131146

147+
if a.dtype != b.dtype:
148+
a = a.to(tl.float32)
149+
b = b.to(tl.float32)
132150
o += tl.dot(a, b, allow_tf32=False)
133151

134152
if DIVISIBLE_M and DIVISIBLE_N:
@@ -148,7 +166,8 @@ def bmm(A, B):
148166
assert A.shape[2] == B.shape[1], "K dim mismatch"
149167
batch, M, K = A.shape
150168
_, _, N = B.shape
151-
out = torch.empty((batch, M, N), dtype=A.dtype, device=A.device)
169+
c_dtype = get_higher_dtype(A.dtype, B.dtype)
170+
out = torch.empty((batch, M, N), dtype=c_dtype, device=A.device)
152171

153172
grid_fn = lambda meta: (
154173
triton.cdiv(meta["M"], meta["TILE_M"]),

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,13 +175,16 @@ def mm_kernel_general(
175175
for k in range(0, tl.cdiv(K, BLOCK_K)):
176176
a = a_desc.load([offset_am.to(tl.int32), offset_k.to(tl.int32)])
177177
b = b_desc.load([offset_k.to(tl.int32), offset_bn.to(tl.int32)])
178+
if a.dtype != b.dtype:
179+
a = a.to(tl.float32)
180+
b = b.to(tl.float32)
178181
if IS_FP64:
179182
acc += tl.dot(a, b, allow_tf32=False)
180183
else:
181184
acc += tl.dot(a, b, out_dtype=tl.float32, allow_tf32=False)
182185
offset_k += BLOCK_K
183186

184-
acc = acc.to(a_desc.dtype)
187+
acc = acc.to(C.dtype.element_ty)
185188
c_desc.store([offset_am.to(tl.int32), offset_bn.to(tl.int32)], acc)
186189

187190
else:

tests/test_addmm.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,3 +97,27 @@ def test_addmm_out(M, N, K, scalar, dtype):
9797
torch.addmm(bias2, mat1, mat2, alpha=alpha, beta=beta, out=out)
9898

9999
utils.gems_assert_close(out, ref_out, dtype, reduce_dim=K)
100+
101+
102+
MIXED_DTYPE_PAIRS = [
103+
(torch.float16, torch.float32),
104+
(torch.float32, torch.float16),
105+
]
106+
107+
108+
@pytest.mark.addmm
109+
@pytest.mark.parametrize("M, N, K", MNK_SHAPES)
110+
@pytest.mark.parametrize("dtype_a, dtype_b", MIXED_DTYPE_PAIRS)
111+
def test_addmm_mixed_dtype(M, N, K, dtype_a, dtype_b):
112+
mat1 = torch.randn((M, K), dtype=dtype_a, device=flag_gems.device)
113+
mat2 = torch.randn((K, N), dtype=dtype_b, device=flag_gems.device)
114+
bias = torch.randn((N,), dtype=dtype_a, device=flag_gems.device)
115+
ref_mat1 = utils.to_reference(mat1, True)
116+
ref_mat2 = utils.to_reference(mat2, True)
117+
ref_bias = utils.to_reference(bias, True)
118+
119+
ref_out = torch.addmm(ref_bias, ref_mat1, ref_mat2)
120+
with flag_gems.use_gems():
121+
res_out = torch.addmm(bias, mat1, mat2)
122+
123+
utils.gems_assert_close(res_out, ref_out, torch.float32, reduce_dim=K)

tests/test_baddbmm.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,3 +95,27 @@ def test_baddbmm_backward(M, N, K, scalar, dtype):
9595
gems_assert_close(res_in_bias, ref_in_bias, dtype, reduce_dim=K)
9696
gems_assert_close(res_in_grad1, ref_in_grad1, dtype, reduce_dim=N)
9797
gems_assert_close(res_in_grad2, ref_in_grad2, dtype, reduce_dim=M)
98+
99+
100+
MIXED_DTYPE_PAIRS = [
101+
(torch.float16, torch.float32),
102+
(torch.float32, torch.float16),
103+
]
104+
105+
106+
@pytest.mark.baddbmm
107+
@pytest.mark.parametrize("M, N, K", MNK_SHAPES)
108+
@pytest.mark.parametrize("dtype_a, dtype_b", MIXED_DTYPE_PAIRS)
109+
def test_baddbmm_mixed_dtype(M, N, K, dtype_a, dtype_b):
110+
batch = 4
111+
mat1 = torch.randn((batch, M, K), dtype=dtype_a, device=flag_gems.device)
112+
mat2 = torch.randn((batch, K, N), dtype=dtype_b, device=flag_gems.device)
113+
bias = torch.randn((N,), dtype=dtype_a, device=flag_gems.device)
114+
ref_mat1 = to_reference(mat1, True)
115+
ref_mat2 = to_reference(mat2, True)
116+
ref_bias = to_reference(bias, True)
117+
118+
ref_out = torch.baddbmm(ref_bias, ref_mat1, ref_mat2)
119+
res_out = flag_gems.baddbmm(bias, mat1, mat2)
120+
121+
gems_assert_close(res_out, ref_out, torch.float32, reduce_dim=K)

tests/test_bmm.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,3 +119,30 @@ def test_bmm_out(M, N, K, dtype):
119119
torch.bmm(mat1, mat2, out=out)
120120

121121
utils.gems_assert_close(out, ref_out, dtype, reduce_dim=K)
122+
123+
124+
MIXED_DTYPE_PAIRS = [
125+
(torch.float16, torch.float32),
126+
(torch.float32, torch.float16),
127+
]
128+
129+
130+
@pytest.mark.bmm
131+
@pytest.mark.parametrize("M, N, K", MNK_SHAPES)
132+
@pytest.mark.parametrize("dtype_a, dtype_b", MIXED_DTYPE_PAIRS)
133+
def test_bmm_mixed_dtype(M, N, K, dtype_a, dtype_b):
134+
if flag_gems.vendor_name == "tsingmicro" and (
135+
dtype_a == torch.float32 or dtype_b == torch.float32
136+
):
137+
pytest.skip("Skiping fp32 bmm test on tsingmicro platform")
138+
batch = 4
139+
mat1 = torch.randn((batch, M, K), dtype=dtype_a, device=flag_gems.device)
140+
mat2 = torch.randn((batch, K, N), dtype=dtype_b, device=flag_gems.device)
141+
ref_mat1 = utils.to_reference(mat1, True)
142+
ref_mat2 = utils.to_reference(mat2, True)
143+
144+
ref_out = torch.bmm(ref_mat1, ref_mat2)
145+
with flag_gems.use_gems():
146+
res_out = torch.bmm(mat1, mat2)
147+
148+
utils.gems_assert_close(res_out, ref_out, torch.float32, reduce_dim=K)

tests/test_mm.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,3 +52,29 @@ def test_mm(M, N, K, dtype, b_column_major):
5252
res_out = torch.mm(mat1, mat2)
5353

5454
utils.gems_assert_close(res_out, ref_out, dtype, reduce_dim=K)
55+
56+
57+
MIXED_DTYPE_PAIRS = [
58+
(torch.float16, torch.float32),
59+
(torch.float32, torch.float16),
60+
]
61+
62+
63+
@pytest.mark.mm
64+
@pytest.mark.parametrize("M, N, K", MNK_SHAPES)
65+
@pytest.mark.parametrize("dtype_a, dtype_b", MIXED_DTYPE_PAIRS)
66+
def test_mm_mixed_dtype(M, N, K, dtype_a, dtype_b):
67+
if flag_gems.vendor_name == "tsingmicro" and (
68+
dtype_a == torch.float32 or dtype_b == torch.float32
69+
):
70+
pytest.skip("Skiping fp32 addmm_out test on tsingmicro platform")
71+
mat1 = torch.randn((M, K), dtype=dtype_a, device=flag_gems.device)
72+
mat2 = torch.randn((K, N), dtype=dtype_b, device=flag_gems.device)
73+
ref_mat1 = utils.to_reference(mat1, True)
74+
ref_mat2 = utils.to_reference(mat2, True)
75+
76+
ref_out = torch.mm(ref_mat1, ref_mat2)
77+
with flag_gems.use_gems():
78+
res_out = torch.mm(mat1, mat2)
79+
80+
utils.gems_assert_close(res_out, ref_out, torch.float32, reduce_dim=K)

0 commit comments

Comments
 (0)