Skip to content

Commit fdf96de

Browse files
[AdvancedCompiler]Cat(cpp wrapper) (flagos-ai#733)
* updata cat cpp wrapper * fix CMakelist.txt * fix PR733 cat cpp wrapper review * fix /CMakeLists.txt --------- Co-authored-by: Ea760 <15236119052@163.com>
1 parent 8ff55c7 commit fdf96de

7 files changed

Lines changed: 291 additions & 0 deletions

File tree

ctests/CMakeLists.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ 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)
2424

25+
add_executable(test_triton_cat test_triton_cat.cpp)
26+
target_link_libraries(test_triton_cat
27+
PRIVATE Torch::Torch operators GTest::gtest GTest::gtest_main)
28+
add_test(NAME test_triton_cat COMMAND test_triton_cat)
2529
add_executable(test_triton_bmm test_triton_bmm.cpp)
2630
target_link_libraries(test_triton_bmm
2731
PRIVATE Torch::Torch operators GTest::gtest GTest::gtest_main)

ctests/test_triton_cat.cpp

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
#include "flag_gems/operators.h"
2+
#include "gtest/gtest.h"
3+
#include "torch/torch.h"
4+
5+
TEST(TritonCatTest, basictest) {
6+
const torch::Device device(torch::kCUDA, 0);
7+
torch::Tensor t1 = torch::randn({2, 3}, device);
8+
torch::Tensor t2 = torch::randn({4, 3}, device);
9+
10+
torch::Tensor out_torch = torch::cat({t1, t2}, 0);
11+
torch::Tensor out_gems = flag_gems::cat({t1, t2}, 0);
12+
13+
EXPECT_TRUE(torch::equal(out_torch, out_gems));
14+
}
15+
16+
TEST(TritonCatTest, 2dimtest) {
17+
const torch::Device device(torch::kCUDA, 0);
18+
torch::Tensor t1 = torch::randn({3, 2}, device);
19+
torch::Tensor t2 = torch::randn({3, 4}, device);
20+
21+
int dim_to_test = 1;
22+
torch::Tensor out_torch = torch::cat({t1, t2}, dim_to_test);
23+
torch::Tensor out_gems = flag_gems::cat({t1, t2}, dim_to_test);
24+
25+
EXPECT_TRUE(torch::equal(out_gems, out_torch));
26+
27+
EXPECT_EQ(out_gems.size(0), 3);
28+
EXPECT_EQ(out_gems.size(1), 6);
29+
}
30+
31+
TEST(TritonCatTest, 3dimtest) {
32+
const torch::Device device(torch::kCUDA, 0);
33+
34+
torch::Tensor t1 = torch::randn({3, 2, 4}, device);
35+
torch::Tensor t2 = torch::randn({3, 5, 4}, device);
36+
37+
int dim_to_test = 1;
38+
39+
torch::Tensor out_torch = torch::cat({t1, t2}, dim_to_test);
40+
torch::Tensor out_gems = flag_gems::cat({t1, t2}, dim_to_test);
41+
42+
EXPECT_TRUE(torch::equal(out_torch, out_gems));
43+
}
44+
45+
TEST(TritonCatTest, 4dimtest) {
46+
const torch::Device device(torch::kCUDA, 0);
47+
auto options = torch::TensorOptions().device(device).dtype(torch::kFloat32);
48+
49+
torch::Tensor t1 = torch::randn({2, 3, 4, 5}, options);
50+
torch::Tensor t2 = torch::randn({2, 6, 4, 5}, options);
51+
52+
int dim_to_test = 1;
53+
torch::Tensor out_torch = torch::cat({t1, t2}, dim_to_test);
54+
torch::Tensor out_gems = flag_gems::cat({t1, t2}, dim_to_test);
55+
56+
EXPECT_TRUE(torch::equal(out_torch, out_gems));
57+
}
58+
59+
TEST(TritonCatTest, IntegerConcatenation) {
60+
const torch::Device device(torch::kCUDA, 0);
61+
auto options = torch::TensorOptions().device(device).dtype(torch::kInt32);
62+
63+
torch::Tensor t1 = torch::randint(0, 100, {2, 3, 4}, options);
64+
torch::Tensor t2 = torch::randint(0, 100, {2, 3, 4}, options);
65+
66+
int dim_to_test = 2;
67+
torch::Tensor out_torch = torch::cat({t1, t2}, dim_to_test);
68+
torch::Tensor out_gems = flag_gems::cat({t1, t2}, dim_to_test);
69+
70+
EXPECT_TRUE(torch::equal(out_torch, out_gems));
71+
}
72+
73+
TEST(TritonCatTest, EmptyTensorConcatenation) {
74+
const torch::Device device(torch::kCUDA, 0);
75+
auto options = torch::TensorOptions().device(device).dtype(torch::kFloat32);
76+
77+
torch::Tensor t1 = torch::randn({0, 3}, options);
78+
torch::Tensor t2 = torch::randn({2, 3}, options);
79+
80+
torch::Tensor out_torch = torch::cat({t1, t2}, 0);
81+
torch::Tensor out_gems = flag_gems::cat({t1, t2}, 0);
82+
83+
EXPECT_TRUE(torch::equal(out_torch, out_gems));
84+
85+
torch::Tensor t3 = torch::randn({0, 3}, options);
86+
torch::Tensor t4 = torch::randn({0, 3}, options);
87+
88+
torch::Tensor out_torch_both_empty = torch::cat({t3, t4}, 0);
89+
torch::Tensor out_gems_both_empty = flag_gems::cat({t3, t4}, 0);
90+
91+
EXPECT_TRUE(torch::equal(out_torch_both_empty, out_gems_both_empty));
92+
EXPECT_EQ(out_gems_both_empty.numel(), 0);
93+
}
94+
95+
TEST(TritonCatTest, 3tensorcat) {
96+
const torch::Device device(torch::kCUDA, 0);
97+
auto options = torch::TensorOptions().device(device).dtype(torch::kFloat32);
98+
99+
torch::Tensor t1 = torch::randn({2, 3}, options);
100+
torch::Tensor t2 = torch::randn({4, 3}, options);
101+
torch::Tensor t3 = torch::randn({1, 3}, options);
102+
103+
int dim_to_test = 0;
104+
torch::Tensor out_torch = torch::cat({t1, t2, t3}, dim_to_test);
105+
torch::Tensor out_gems = flag_gems::cat({t1, t2, t3}, dim_to_test);
106+
107+
EXPECT_TRUE(torch::equal(out_torch, out_gems));
108+
EXPECT_EQ(out_gems.size(0), 7);
109+
EXPECT_EQ(out_gems.size(1), 3);
110+
}
111+
112+
TEST(TritonCatTest, HandlesNonContiguousInput) {
113+
const torch::Device device(torch::kCUDA, 0);
114+
auto options = torch::TensorOptions().device(device).dtype(torch::kFloat32);
115+
116+
torch::Tensor t_base = torch::randn({2, 3, 4}, options);
117+
118+
torch::Tensor t1_non_contiguous = t_base.transpose(1, 2);
119+
120+
torch::Tensor t2_contiguous = torch::randn({2, 4, 5}, options);
121+
122+
ASSERT_FALSE(t1_non_contiguous.is_contiguous());
123+
124+
int dim_to_test = 2;
125+
torch::Tensor out_torch = torch::cat({t1_non_contiguous, t2_contiguous}, dim_to_test);
126+
torch::Tensor out_gems = flag_gems::cat({t1_non_contiguous, t2_contiguous}, dim_to_test);
127+
128+
EXPECT_TRUE(torch::equal(out_torch, out_gems));
129+
130+
EXPECT_EQ(out_gems.size(0), 2);
131+
EXPECT_EQ(out_gems.size(1), 4);
132+
EXPECT_EQ(out_gems.size(2), 8);
133+
}

include/flag_gems/operators.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,5 +31,6 @@ std::tuple<at::Tensor, at::Tensor> rotary_embedding(
3131
const std::optional<at::Tensor> &position_ids = std::nullopt,
3232
bool rotary_interleaved = false);
3333

34+
at::Tensor cat(const at::TensorList &tensors, int64_t dim = 0);
3435
at::Tensor bmm(const at::Tensor &A, const at::Tensor &B);
3536
} // namespace flag_gems

lib/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ add_library(operators
88
fused_add_rms_norm.cpp
99
nonzero.cpp
1010
rotary_embedding.cpp
11+
cat.cpp
1112
bmm.cpp)
1213
target_include_directories(operators
1314
PUBLIC

lib/cat.cpp

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
#include "c10/cuda/CUDAStream.h"
2+
#include "flag_gems/operators.h"
3+
#include "flag_gems/utils.h"
4+
#include "triton_jit/triton_jit_function.h"
5+
6+
namespace flag_gems {
7+
using namespace triton_jit;
8+
9+
at::Tensor cat(const at::TensorList& tensors, int64_t dim) {
10+
TORCH_CHECK(tensors.size() > 0, "torch.cat(): expected a non-empty list of Tensors");
11+
if (tensors.size() == 1) {
12+
return tensors[0];
13+
}
14+
const auto& first_tensor = tensors[0];
15+
int64_t ndim = first_tensor.dim();
16+
TORCH_CHECK(dim >= -ndim && dim < ndim, "cat(): dimension out of range");
17+
if (dim < 0) {
18+
dim += ndim;
19+
}
20+
const at::IntArrayRef first_shape = first_tensor.sizes();
21+
for (size_t i = 1; i < tensors.size(); ++i) {
22+
const auto& current_tensor = tensors[i];
23+
TORCH_CHECK(current_tensor.dim() == ndim,
24+
"Tensors must have same number of dimensions: got ",
25+
ndim,
26+
" and ",
27+
current_tensor.dim());
28+
const at::IntArrayRef current_shape = current_tensor.sizes();
29+
for (int64_t d = 0; d < ndim; ++d) {
30+
if (d == dim) continue;
31+
TORCH_CHECK(current_shape[d] == first_shape[d],
32+
"Sizes of tensors must match except in dimension ",
33+
dim,
34+
". Expected size ",
35+
first_shape[d],
36+
" but got size ",
37+
current_shape[d],
38+
" for tensor number ",
39+
i);
40+
}
41+
}
42+
43+
std::vector<int64_t> out_shape_vec = first_shape.vec();
44+
int64_t cat_dim_size = 0;
45+
for (const auto& t : tensors) {
46+
cat_dim_size += t.size(dim);
47+
}
48+
out_shape_vec[dim] = cat_dim_size;
49+
at::Tensor out = at::empty(out_shape_vec, first_tensor.options());
50+
51+
std::vector<int64_t> storage_offsets;
52+
int64_t current_storage_offset = 0;
53+
storage_offsets.push_back(current_storage_offset);
54+
int64_t out_stride_for_dim = out.stride(dim);
55+
for (size_t i = 0; i < tensors.size() - 1; ++i) {
56+
current_storage_offset += tensors[i].size(dim) * out_stride_for_dim;
57+
storage_offsets.push_back(current_storage_offset);
58+
}
59+
60+
const TritonJITFunction copy_kernel_func =
61+
TritonJITFunction::getInstance(std::string(utils::get_triton_src_path() / "cat_copy.py"),
62+
"strided_copy_kernel");
63+
c10::DeviceGuard guard(out.device());
64+
c10::cuda::CUDAStream stream = c10::cuda::getCurrentCUDAStream();
65+
CUstream raw_stream = static_cast<CUstream>(stream.stream());
66+
67+
for (size_t i = 0; i < tensors.size(); ++i) {
68+
const auto& input_tensor = tensors[i];
69+
if (input_tensor.numel() == 0) continue;
70+
71+
at::Tensor output_view = at::as_strided(out, input_tensor.sizes(), out.strides(), storage_offsets[i]);
72+
73+
auto options = torch::TensorOptions().device(input_tensor.device()).dtype(torch::kInt64);
74+
at::Tensor in_strides = torch::tensor(input_tensor.strides(), options);
75+
at::Tensor out_strides = torch::tensor(output_view.strides(), options);
76+
at::Tensor shapes = torch::tensor(input_tensor.sizes(), options);
77+
78+
int64_t ndim_val = input_tensor.dim();
79+
int64_t num_elements = input_tensor.numel();
80+
81+
constexpr int BLOCK_SIZE = 256;
82+
constexpr int MAX_DIMS = 8;
83+
TORCH_CHECK(ndim_val <= MAX_DIMS,
84+
"Tensor dimension ",
85+
ndim_val,
86+
" exceeds the maximum supported by the kernel (",
87+
MAX_DIMS,
88+
")");
89+
90+
unsigned int grid_x = (num_elements + BLOCK_SIZE - 1) / BLOCK_SIZE;
91+
92+
copy_kernel_func(raw_stream,
93+
grid_x,
94+
1,
95+
1,
96+
4,
97+
2,
98+
input_tensor,
99+
output_view,
100+
in_strides,
101+
out_strides,
102+
shapes,
103+
ndim_val,
104+
num_elements,
105+
BLOCK_SIZE,
106+
MAX_DIMS);
107+
}
108+
return out;
109+
}
110+
} // namespace flag_gems

src/flag_gems/csrc/cstub.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ TORCH_LIBRARY(flag_gems, m) {
2121
m.def(
2222
"rotary_embedding(Tensor q, Tensor k, Tensor cos, Tensor sin, Tensor? position_ids=None, "
2323
"bool rotary_interleaved=False) -> (Tensor, Tensor)"); // q and k may be view to other size
24+
m.def("cat(Tensor[] tensors, int dim=0) -> Tensor");
2425
m.def("bmm(Tensor self, Tensor mat2) -> Tensor");
2526
}
2627

@@ -34,6 +35,7 @@ TORCH_LIBRARY_IMPL(flag_gems, CUDA, m) {
3435
// Rotary embedding
3536
m.impl("rotary_embedding", TORCH_FN(rotary_embedding));
3637
m.impl("rotary_embedding_inplace", TORCH_FN(rotary_embedding_inplace));
38+
m.impl("cat", TORCH_FN(cat));
3739
m.impl("bmm", TORCH_FN(bmm));
3840
}
3941
} // namespace flag_gems

triton_src/cat_copy.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import triton
2+
import triton.language as tl
3+
4+
5+
@triton.jit
6+
def strided_copy_kernel(
7+
in_ptr,
8+
out_ptr,
9+
in_strides_ptr,
10+
out_strides_ptr,
11+
shapes_ptr,
12+
ndim,
13+
n_elements,
14+
BLOCK_SIZE: tl.constexpr,
15+
MAX_DIMS: tl.constexpr,
16+
):
17+
pid = tl.program_id(axis=0)
18+
offsets = pid * BLOCK_SIZE + tl.arange(0, BLOCK_SIZE)
19+
20+
offsets = offsets.to(tl.int64)
21+
mask = offsets < n_elements
22+
23+
remaining_offset = offsets
24+
in_physical_offset = tl.zeros_like(offsets)
25+
out_physical_offset = tl.zeros_like(offsets)
26+
27+
for i in range(MAX_DIMS - 1, -1, -1):
28+
is_real_dim = i < ndim
29+
current_shape = tl.load(shapes_ptr + i, mask=is_real_dim, other=1)
30+
in_stride_val = tl.load(in_strides_ptr + i, mask=is_real_dim, other=0)
31+
out_stride_val = tl.load(out_strides_ptr + i, mask=is_real_dim, other=0)
32+
33+
current_index = remaining_offset % current_shape
34+
remaining_offset = remaining_offset // current_shape
35+
36+
in_physical_offset += current_index * in_stride_val
37+
out_physical_offset += current_index * out_stride_val
38+
39+
x = tl.load(in_ptr + in_physical_offset, mask=mask)
40+
tl.store(out_ptr + out_physical_offset, x, mask=mask)

0 commit comments

Comments
 (0)