Skip to content

Commit 28acd7b

Browse files
AdvancedCompiler“ph0375”
andauthored
[AdvancedCompiler]bmm(cpp wrapper) (#735)
* Update the C++ wrapper for bmm * Modify the BMM code according to the review comments --------- Co-authored-by: “ph0375” <“ph0375@163.com”>
1 parent 7b16ab7 commit 28acd7b

7 files changed

Lines changed: 201 additions & 1 deletion

File tree

ctests/CMakeLists.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,8 @@ add_executable(test_triton_rope test_triton_rope.cpp)
2121
target_link_libraries(test_triton_rope
2222
PRIVATE Torch::Torch operators GTest::gtest GTest::gtest_main)
2323
add_test(NAME test_triton_rope COMMAND test_triton_rope)
24+
25+
add_executable(test_triton_bmm test_triton_bmm.cpp)
26+
target_link_libraries(test_triton_bmm
27+
PRIVATE Torch::Torch operators GTest::gtest GTest::gtest_main)
28+
add_test(NAME test_triton_bmm COMMAND test_triton_bmm)

ctests/test_triton_bmm.cpp

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#include <gtest/gtest.h>
2+
#include "flag_gems/operators.h"
3+
#include "torch/torch.h"
4+
5+
TEST(blas_op_test, bmm) {
6+
const torch::Device device(torch::kCUDA, 0);
7+
8+
const int B = 5, M = 10, K = 10, N = 10;
9+
10+
torch::Tensor batch1 = torch::randn({B, M, K}, device);
11+
torch::Tensor batch2 = torch::randn({B, K, N}, device);
12+
13+
torch::Tensor out_torch = flag_gems::bmm(batch1, batch2);
14+
torch::Tensor out_triton = flag_gems::bmm(batch1, batch2);
15+
16+
EXPECT_TRUE(torch::allclose(out_torch, out_triton));
17+
}

include/flag_gems/operators.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,6 @@ std::tuple<at::Tensor, at::Tensor> rotary_embedding(
2929
const at::Tensor &sin,
3030
const std::optional<at::Tensor> &position_ids = std::nullopt,
3131
bool rotary_interleaved = false);
32+
33+
at::Tensor bmm(const at::Tensor &A, const at::Tensor &B);
3234
} // namespace flag_gems

lib/CMakeLists.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ add_library(operators
66
mm.cpp
77
rms_norm.cpp
88
fused_add_rms_norm.cpp
9-
rotary_embedding.cpp)
9+
rotary_embedding.cpp
10+
bmm.cpp)
1011
target_include_directories(operators
1112
PUBLIC
1213
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>

lib/bmm.cpp

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
#include "flag_gems/operators.h"
2+
#include "flag_gems/utils.h"
3+
4+
#include <iostream>
5+
#include "c10/cuda/CUDAStream.h"
6+
#include "triton_jit/triton_jit_function.h"
7+
8+
namespace flag_gems {
9+
using namespace triton_jit;
10+
11+
at::Tensor bmm(const at::Tensor& A, const at::Tensor& B) {
12+
TORCH_CHECK(A.dim() == 3 && B.dim() == 3, "both the tensors must be 3-D");
13+
TORCH_CHECK(A.dtype() == B.dtype(),
14+
"expected a and b to have the same dtype, but got: ",
15+
A.dtype(),
16+
" != ",
17+
B.dtype());
18+
at::IntArrayRef A_sizes = A.sizes();
19+
at::IntArrayRef B_sizes = B.sizes();
20+
21+
at::Tensor A_contig = A.contiguous();
22+
at::Tensor B_contig = B.contiguous();
23+
24+
at::Tensor out = at::empty({A_sizes[0], A_sizes[1], B_sizes[2]}, A_contig.options());
25+
26+
const TritonJITFunction& f =
27+
TritonJITFunction::getInstance(std::string(utils::get_triton_src_path() / "bmm.py"), "bmm_kernel");
28+
29+
c10::DeviceGuard guard(out.device());
30+
c10::cuda::CUDAStream stream = c10::cuda::getCurrentCUDAStream();
31+
CUstream raw_stream = static_cast<CUstream>(stream.stream());
32+
const int GROUP_M = 8;
33+
const int TILE_M = 128;
34+
const int TILE_N = 128;
35+
const int TILE_K = 32;
36+
const int M = A_sizes[1];
37+
const int N = B_sizes[2];
38+
const int K = A_sizes[2];
39+
unsigned int grid_x = (M + (TILE_M - 1)) / TILE_M;
40+
unsigned int grid_y = (N + (TILE_N - 1)) / TILE_N;
41+
if (GROUP_M > 1) {
42+
grid_x = std::max(grid_x, static_cast<unsigned int>(GROUP_M));
43+
}
44+
bool DIVISIBLE_M = (M % TILE_M == 0);
45+
bool DIVISIBLE_N = (N % TILE_N == 0);
46+
bool DIVISIBLE_K = (K % TILE_K == 0);
47+
48+
f(/* CUstream = */ raw_stream,
49+
/* grid_x = */ grid_x,
50+
/* grid_y = */ grid_y,
51+
/* grid_z = */ A_sizes[0],
52+
/* num_warps = */ 4,
53+
/* num_stages = */ 1,
54+
A_contig,
55+
B_contig,
56+
out,
57+
A_sizes[1],
58+
B_sizes[2],
59+
A_sizes[2],
60+
TILE_M,
61+
TILE_N,
62+
TILE_K,
63+
GROUP_M,
64+
DIVISIBLE_M,
65+
DIVISIBLE_N,
66+
DIVISIBLE_K);
67+
return out;
68+
}
69+
70+
} // namespace flag_gems

src/flag_gems/csrc/cstub.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ TORCH_LIBRARY(flag_gems, m) {
2020
m.def(
2121
"rotary_embedding(Tensor q, Tensor k, Tensor cos, Tensor sin, Tensor? position_ids=None, "
2222
"bool rotary_interleaved=False) -> (Tensor, Tensor)"); // q and k may be view to other size
23+
m.def("bmm(Tensor self, Tensor mat2) -> Tensor");
2324
}
2425

2526
TORCH_LIBRARY_IMPL(flag_gems, CUDA, m) {
@@ -31,5 +32,6 @@ TORCH_LIBRARY_IMPL(flag_gems, CUDA, m) {
3132
// Rotary embedding
3233
m.impl("rotary_embedding", TORCH_FN(rotary_embedding));
3334
m.impl("rotary_embedding_inplace", TORCH_FN(rotary_embedding_inplace));
35+
m.impl("bmm", TORCH_FN(bmm));
3436
}
3537
} // namespace flag_gems

triton_src/bmm.py

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
import triton
2+
import triton.language as tl
3+
4+
from flag_gems.utils import triton_lang_extension as tle
5+
6+
7+
@triton.jit
8+
def bmm_kernel(
9+
A,
10+
B,
11+
O,
12+
M,
13+
N,
14+
K,
15+
TILE_M: tl.constexpr,
16+
TILE_N: tl.constexpr,
17+
TILE_K: tl.constexpr,
18+
GROUP_M: tl.constexpr,
19+
DIVISIBLE_M: tl.constexpr,
20+
DIVISIBLE_N: tl.constexpr,
21+
DIVISIBLE_K: tl.constexpr,
22+
):
23+
# batch offsets
24+
pid_b = tle.program_id(2)
25+
A += pid_b * M * K
26+
B += pid_b * K * N
27+
O += pid_b * M * N
28+
29+
pidx = tle.program_id(0)
30+
pidy = tle.program_id(1)
31+
32+
if GROUP_M == 1:
33+
pid_m, pid_n = pidx, pidy
34+
else:
35+
# reorder CTAs
36+
gridx = tle.num_programs(0)
37+
gridy = tle.num_programs(1)
38+
pid = pidx + pidy * gridx
39+
40+
num_CTA_per_group = gridy * GROUP_M
41+
42+
group_id = pid // num_CTA_per_group
43+
inner_group_id = pid % num_CTA_per_group
44+
GROUP_SIZE = tl.where(
45+
(group_id * GROUP_M + GROUP_M) > gridx, gridx % GROUP_M, GROUP_M
46+
)
47+
pid_m = group_id * GROUP_M + inner_group_id % GROUP_SIZE
48+
pid_n = inner_group_id // GROUP_SIZE
49+
50+
offs_m = pid_m * TILE_M + tl.arange(0, TILE_M)
51+
offs_n = pid_n * TILE_N + tl.arange(0, TILE_N)
52+
offs_k = tl.arange(0, TILE_K)
53+
54+
if not DIVISIBLE_M:
55+
mask_m = offs_m < M
56+
if not DIVISIBLE_N:
57+
mask_n = offs_n < N
58+
59+
a_ptrs = A + offs_m[:, None] * K + offs_k[None, :]
60+
b_ptrs = B + offs_k[:, None] * N + offs_n[None, :]
61+
o_ptrs = O + offs_m[:, None] * N + offs_n[None, :]
62+
63+
num_iters = tl.cdiv(K, TILE_K)
64+
o = tl.zeros((TILE_M, TILE_N), dtype=tl.float32)
65+
for _ in range(num_iters):
66+
if DIVISIBLE_K:
67+
if DIVISIBLE_M:
68+
mask_a = None
69+
else:
70+
mask_a = mask_m[:, None]
71+
if DIVISIBLE_N:
72+
mask_b = None
73+
else:
74+
mask_b = mask_n[None, :]
75+
else:
76+
mask_k = offs_k < K
77+
if DIVISIBLE_M:
78+
mask_a = mask_k[None, :]
79+
else:
80+
mask_a = mask_m[:, None] & mask_k[None, :]
81+
if DIVISIBLE_N:
82+
mask_b = mask_k[:, None]
83+
else:
84+
mask_b = mask_k[:, None] & mask_n[None, :]
85+
86+
a = tl.load(a_ptrs, mask_a)
87+
b = tl.load(b_ptrs, mask_b)
88+
89+
offs_k += TILE_K
90+
a_ptrs += TILE_K
91+
b_ptrs += TILE_K * N
92+
93+
o += tl.dot(a, b, allow_tf32=False)
94+
95+
if DIVISIBLE_M and DIVISIBLE_N:
96+
mask_c = None
97+
elif DIVISIBLE_M and not DIVISIBLE_N:
98+
mask_c = mask_n[None, :]
99+
elif not DIVISIBLE_M and DIVISIBLE_N:
100+
mask_c = mask_m[:, None]
101+
else:
102+
mask_c = mask_m[:, None] & mask_n[None, :]
103+
tl.store(o_ptrs, o, mask_c)

0 commit comments

Comments
 (0)