Skip to content

Commit 2cfb3ae

Browse files
levendleefacebook-github-bot
authored andcommitted
Open source TokenShuffling MoE kernels. (#3978)
Summary: Pull Request resolved: #3978 X-link: facebookresearch/FBGEMM#1058 Move kernels from private directory to public directory after Llama4 models are publicly released. Reviewed By: q10, jianyuh Differential Revision: D73068708
1 parent ce02bfd commit 2cfb3ae

8 files changed

Lines changed: 1617 additions & 0 deletions

File tree

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# Copyright (c) Meta Platforms, Inc. and affiliates.
2+
# All rights reserved.
3+
#
4+
# This source code is licensed under the BSD-style license found in the
5+
# LICENSE file in the root directory of this source tree.
6+
7+
# pyre-unsafe
8+
9+
import torch
10+
11+
try:
12+
# In OSS, load the TARGET directly
13+
torch.ops.load_library(
14+
"//deeplearning/fbgemm/fbgemm_gpu/experimental/gen_ai:index_shuffling_ops_gpu"
15+
)
16+
torch.ops.load_library(
17+
"//deeplearning/fbgemm/fbgemm_gpu/experimental/gen_ai:gather_scatter_ops"
18+
)
19+
except Exception:
20+
# In OSS, import the shared libraries
21+
# pyre-ignore [21]
22+
import fbgemm_gpu.experimental.gen_ai # noqa: F401
23+
24+
index_shuffling = torch.ops.fbgemm.index_shuffling # noqa F401
25+
gather_along_first_dim = torch.ops.fbgemm.gather_along_first_dim # noqa F401
26+
scatter_add_along_first_dim = torch.ops.fbgemm.scatter_add_along_first_dim # noqa F401
27+
28+
from fbgemm_gpu.experimental.gemm.triton_gemm.grouped_gemm import ( # noqa F401
29+
grouped_gemm,
30+
grouped_gemm_fp8_rowwise,
31+
)
32+
33+
from .gather_scatter import ( # noqa F401
34+
gather_scale_dense_tokens,
35+
scatter_add_padded_tokens,
36+
)
37+
from .shuffling import combine_shuffling, split_shuffling # noqa F401
Lines changed: 284 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,284 @@
1+
# Copyright (c) Meta Platforms, Inc. and affiliates.
2+
# All rights reserved.
3+
#
4+
# This source code is licensed under the BSD-style license found in the
5+
# LICENSE file in the root directory of this source tree.
6+
7+
# pyre-unsafe
8+
9+
import torch
10+
import triton
11+
import triton.language as tl
12+
13+
14+
@triton.jit
15+
def _fbgemm_gather_scale_dense_tokens(
16+
out,
17+
x,
18+
token_indices,
19+
expert_indices,
20+
scores,
21+
stride_t,
22+
stride_e,
23+
D: tl.constexpr,
24+
BLOCK_D_OUTER: tl.constexpr,
25+
BLOCK_D_INNER: tl.constexpr,
26+
):
27+
output_token_index = tl.program_id(0)
28+
feature_offset = tl.program_id(1) * BLOCK_D_OUTER
29+
30+
input_token_index = tl.load(
31+
token_indices + output_token_index, None, eviction_policy="evict_last"
32+
)
33+
input_expert_index = tl.load(
34+
expert_indices + output_token_index, None, eviction_policy="evict_last"
35+
)
36+
37+
input_score = tl.load(
38+
scores + input_token_index * stride_t + input_expert_index * stride_e,
39+
None,
40+
eviction_policy="evict_last",
41+
).to(tl.float32)
42+
43+
for _ in range(0, BLOCK_D_OUTER // BLOCK_D_INNER):
44+
input_token_value = tl.load(
45+
x
46+
+ input_token_index.to(tl.int64) * D
47+
+ feature_offset
48+
+ tl.arange(0, BLOCK_D_INNER)[:],
49+
None,
50+
).to(tl.float32)
51+
output_token_value = input_token_value * input_score
52+
53+
tl.store(
54+
out
55+
+ output_token_index.to(tl.int64) * D
56+
+ feature_offset
57+
+ tl.arange(0, BLOCK_D_INNER)[:],
58+
output_token_value,
59+
None,
60+
)
61+
feature_offset += BLOCK_D_INNER
62+
63+
64+
def gather_scale_dense_tokens(
65+
x: torch.Tensor,
66+
token_indices: torch.Tensor,
67+
expert_indices: torch.Tensor,
68+
scores: torch.Tensor,
69+
):
70+
T, D = x.shape
71+
E = scores.shape[1]
72+
# a = K * T
73+
a = token_indices.shape[0]
74+
75+
assert x.is_contiguous()
76+
assert token_indices.is_contiguous()
77+
assert expert_indices.is_contiguous()
78+
79+
assert tuple(token_indices.shape) == (a,)
80+
assert tuple(expert_indices.shape) == (a,)
81+
assert tuple(scores.shape) == (T, E)
82+
83+
stride_t = scores.stride(0)
84+
stride_e = scores.stride(1)
85+
86+
out = torch.empty((a, D), device="cuda", dtype=torch.bfloat16)
87+
88+
NUM_SMS = torch.cuda.get_device_properties("cuda").multi_processor_count
89+
if a >= NUM_SMS:
90+
BLOCK_D_OUTER = D
91+
BLOCK_D_INNER = 1024
92+
assert D % BLOCK_D_INNER == 0
93+
else:
94+
BLOCK_D_OUTER = 512
95+
BLOCK_D_INNER = 256
96+
assert D % BLOCK_D_OUTER == 0
97+
grid = (a, D // BLOCK_D_OUTER)
98+
_fbgemm_gather_scale_dense_tokens[grid](
99+
out,
100+
x,
101+
token_indices,
102+
expert_indices,
103+
scores,
104+
stride_t,
105+
stride_e,
106+
D, # pyre-ignore
107+
BLOCK_D_OUTER, # pyre-ignore
108+
BLOCK_D_INNER, # pyre-ignore
109+
)
110+
return out
111+
112+
113+
GATHER_SCALE_DENSE_TOKENS = "fbgemm::gather_scale_dense_tokens"
114+
115+
torch.library.define(
116+
"fbgemm::gather_scale_dense_tokens",
117+
"(Tensor x, Tensor token_indices, Tensor expert_indices, Tensor scores) -> Tensor",
118+
)
119+
120+
121+
@torch.library.impl(GATHER_SCALE_DENSE_TOKENS, "Meta")
122+
def gather_scale_dense_tokens_meta(
123+
x,
124+
token_indices,
125+
expert_indices,
126+
scores,
127+
):
128+
D = x.shape[1]
129+
a = token_indices.shape[0]
130+
return x.new_empty((a, D))
131+
132+
133+
@torch.library.impl(GATHER_SCALE_DENSE_TOKENS, "CUDA")
134+
def gather_scale_dense_tokens_cuda(
135+
x,
136+
token_indices,
137+
expert_indices,
138+
scores,
139+
):
140+
return gather_scale_dense_tokens(
141+
x,
142+
token_indices,
143+
expert_indices,
144+
scores,
145+
)
146+
147+
148+
def scatter_add_padded_tokens(
149+
in_tokens: torch.Tensor, # [EP, T, D]
150+
token_counts: torch.Tensor, # [E]
151+
token_indices: torch.Tensor, # [T]
152+
out_tokens: torch.Tensor, # [T, D]
153+
) -> None:
154+
assert torch.version.cuda >= "12.4", "Requires CUDA version 12.4 or later!"
155+
156+
assert in_tokens.is_contiguous()
157+
assert token_counts.is_contiguous()
158+
assert token_indices.is_contiguous()
159+
assert out_tokens.is_contiguous()
160+
161+
EP, T, D = in_tokens.shape
162+
E = token_counts.shape[0]
163+
assert tuple(token_indices.shape) == (T,)
164+
assert tuple(out_tokens.shape) == (T, D)
165+
166+
def grid(META):
167+
return (
168+
E,
169+
META["SPLIT_T"],
170+
)
171+
172+
T_BUCKET_CAP = 16384
173+
T_BUCKET = min(triton.next_power_of_2(T), T_BUCKET_CAP)
174+
_fbgemm_scatter_add_padded_tokens[grid](
175+
in_tokens,
176+
token_counts,
177+
token_indices,
178+
out_tokens,
179+
EP,
180+
E,
181+
T_BUCKET,
182+
T,
183+
D,
184+
)
185+
186+
187+
_NV_CONFIGS = [
188+
triton.Config(
189+
{
190+
"SPLIT_T": split_t,
191+
"BLOCK_D": block_d,
192+
},
193+
num_stages=num_stages,
194+
num_warps=num_warps,
195+
num_ctas=num_ctas,
196+
)
197+
for split_t in [1, 4, 8, 16]
198+
for block_d in [512, 1024]
199+
for num_stages in [1, 3]
200+
for num_warps in [8, 16]
201+
for num_ctas in [1]
202+
]
203+
204+
_AMD_CONFIGS = [
205+
triton.Config(
206+
{
207+
"SPLIT_T": split_t,
208+
"BLOCK_D": block_d,
209+
"waves_per_eu": waves_per_eu,
210+
},
211+
num_stages=num_stages,
212+
num_warps=num_warps,
213+
)
214+
for split_t in [2, 8, 16, 32]
215+
for block_d in [512, 1024]
216+
for num_stages in [1, 3]
217+
for num_warps, waves_per_eu in [(8, 2), (16, 4)]
218+
]
219+
220+
221+
@triton.autotune(
222+
configs=_AMD_CONFIGS if torch.version.hip else _NV_CONFIGS,
223+
restore_value=("out_tokens_ptr",),
224+
key=["EP", "E", "T_BUCKET", "D"],
225+
)
226+
@triton.jit
227+
def _fbgemm_scatter_add_padded_tokens(
228+
in_tokens_ptr,
229+
token_counts_ptr,
230+
token_indices_ptr,
231+
out_tokens_ptr,
232+
EP: tl.constexpr,
233+
E: tl.constexpr,
234+
T_BUCKET,
235+
T,
236+
D: tl.constexpr,
237+
SPLIT_T: tl.constexpr,
238+
BLOCK_D: tl.constexpr,
239+
):
240+
"""
241+
in_tokens: [EP, T, D]
242+
token_counts: [E]
243+
out_tokens: [T, D]
244+
"""
245+
expert = tl.program_id(0)
246+
t_tile = tl.program_id(1)
247+
248+
tl.static_assert(D % BLOCK_D == 0)
249+
NUM_D_BLOCKS: tl.constexpr = D // BLOCK_D
250+
251+
num_tokens = tl.load(token_counts_ptr + expert)
252+
if num_tokens == 0:
253+
return
254+
255+
num_tokens_per_cta = tl.cdiv(num_tokens, SPLIT_T)
256+
start_token = t_tile * num_tokens_per_cta
257+
end_token = min(start_token + num_tokens_per_cta, num_tokens)
258+
259+
tl.static_assert(E % EP == 0)
260+
EXPERT_PER_RANK: tl.constexpr = E // EP
261+
rank = expert // EXPERT_PER_RANK
262+
263+
token_counts = tl.load(token_counts_ptr + tl.arange(0, E))
264+
input_local_offset = (
265+
tl.sum(tl.where(tl.arange(0, E) < expert, token_counts, 0)) + start_token
266+
).to(tl.int64)
267+
268+
for _t in range(start_token, end_token):
269+
output_local_offset = tl.load(token_indices_ptr + input_local_offset).to(
270+
tl.int64
271+
)
272+
output_global_offset = output_local_offset * D
273+
274+
d_ptr = tl.arange(0, BLOCK_D)
275+
input_global_ptr = in_tokens_ptr + rank * T * D + input_local_offset * D + d_ptr
276+
output_global_ptr = out_tokens_ptr + output_global_offset + d_ptr
277+
278+
for _d in range(NUM_D_BLOCKS):
279+
vec = tl.load(input_global_ptr)
280+
tl.atomic_add(output_global_ptr, vec, sem="relaxed")
281+
input_global_ptr += BLOCK_D
282+
output_global_ptr += BLOCK_D
283+
284+
input_local_offset += 1

0 commit comments

Comments
 (0)