Skip to content

Commit ae334b4

Browse files
mgoincodex
andcommitted
[MoE] Harden masked activation backend support
Co-authored-by: OpenAI Codex <codex@openai.com> Signed-off-by: mgoin <mgoin64@gmail.com>
1 parent d914e78 commit ae334b4

9 files changed

Lines changed: 442 additions & 58 deletions

File tree

csrc/libtorch_stable/activation_kernels.cu

Lines changed: 49 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -496,17 +496,26 @@ __global__ void situ_and_mul_kernel(
496496
}
497497
}
498498

499+
constexpr int kMaxMaskedTokenBlocks = 32;
500+
499501
template <bool BATCHED_EXPERTS>
500502
__device__ __forceinline__ bool get_masked_row_range(
501503
const int* __restrict__ valid_token_counts, const int max_num_tokens,
502-
int64_t& first_row, int64_t& end_row) {
504+
int64_t& first_row, int64_t& end_row, int64_t& row_stride) {
503505
if constexpr (BATCHED_EXPERTS) {
504-
// [E, T, *]: each block handles one expert's valid token prefix.
506+
// [E, T, *]: z lanes grid-stride over one expert's valid token prefix.
505507
const int expert = blockIdx.y;
506508
const int num_tokens =
507509
max(0, min(valid_token_counts[expert], max_num_tokens));
508-
first_row = static_cast<int64_t>(expert) * max_num_tokens;
509-
end_row = first_row + num_tokens;
510+
const int token_block = blockIdx.z;
511+
if (token_block >= num_tokens) {
512+
return false;
513+
}
514+
const int64_t expert_first_row =
515+
static_cast<int64_t>(expert) * max_num_tokens;
516+
first_row = expert_first_row + token_block;
517+
end_row = expert_first_row + num_tokens;
518+
row_stride = gridDim.z;
510519
} else {
511520
// [T, *]: each block handles one row, masked by a single valid prefix.
512521
const int row = blockIdx.x;
@@ -516,6 +525,7 @@ __device__ __forceinline__ bool get_masked_row_range(
516525
}
517526
first_row = row;
518527
end_row = row + 1;
528+
row_stride = 1;
519529
}
520530
return first_row < end_row;
521531
}
@@ -535,17 +545,18 @@ __global__ void masked_situ_and_mul_kernel(
535545
scalar_t* __restrict__ out, const scalar_t* __restrict__ input,
536546
const int* __restrict__ valid_token_counts, const int max_num_tokens,
537547
const int d, const float beta, const float linear_beta) {
538-
int64_t first_row, end_row;
548+
int64_t first_row, end_row, row_stride;
539549
const int idx = get_masked_feature_index<BATCHED_EXPERTS>();
540-
if (idx >= d || !get_masked_row_range<BATCHED_EXPERTS>(
541-
valid_token_counts, max_num_tokens, first_row, end_row)) {
550+
if (idx >= d ||
551+
!get_masked_row_range<BATCHED_EXPERTS>(valid_token_counts, max_num_tokens,
552+
first_row, end_row, row_stride)) {
542553
return;
543554
}
544555

545556
const bool clamp_up = linear_beta > 0.0f;
546557
const float inv_beta = 1.0f / beta;
547558
const float inv_linear_beta = clamp_up ? 1.0f / linear_beta : 0.0f;
548-
for (int64_t row = first_row; row < end_row; ++row) {
559+
for (int64_t row = first_row; row < end_row; row += row_stride) {
549560
const scalar_t* gate_ptr = input + row * 2 * d;
550561
const scalar_t* up_ptr = gate_ptr + d;
551562
scalar_t* out_ptr = out + row * d;
@@ -564,14 +575,15 @@ __global__ void masked_act_and_mul_kernel(
564575
scalar_t* __restrict__ out, const scalar_t* __restrict__ input,
565576
const int* __restrict__ valid_token_counts, const int max_num_tokens,
566577
const int d, const float limit, const float alpha, const float beta) {
567-
int64_t first_row, end_row;
578+
int64_t first_row, end_row, row_stride;
568579
const int idx = get_masked_feature_index<BATCHED_EXPERTS>();
569-
if (idx >= d || !get_masked_row_range<BATCHED_EXPERTS>(
570-
valid_token_counts, max_num_tokens, first_row, end_row)) {
580+
if (idx >= d ||
581+
!get_masked_row_range<BATCHED_EXPERTS>(valid_token_counts, max_num_tokens,
582+
first_row, end_row, row_stride)) {
571583
return;
572584
}
573585

574-
for (int64_t row = first_row; row < end_row; ++row) {
586+
for (int64_t row = first_row; row < end_row; row += row_stride) {
575587
const scalar_t* x_ptr = input + row * 2 * d;
576588
const scalar_t* y_ptr = x_ptr + d;
577589
scalar_t* out_ptr = out + row * d;
@@ -587,14 +599,15 @@ __global__ void masked_swigluoai_and_mul_kernel(
587599
scalar_t* __restrict__ out, const scalar_t* __restrict__ input,
588600
const int* __restrict__ valid_token_counts, const int max_num_tokens,
589601
const int d, const float alpha, const float limit) {
590-
int64_t first_row, end_row;
602+
int64_t first_row, end_row, row_stride;
591603
const int idx = get_masked_feature_index<BATCHED_EXPERTS>();
592-
if (idx >= d || !get_masked_row_range<BATCHED_EXPERTS>(
593-
valid_token_counts, max_num_tokens, first_row, end_row)) {
604+
if (idx >= d ||
605+
!get_masked_row_range<BATCHED_EXPERTS>(valid_token_counts, max_num_tokens,
606+
first_row, end_row, row_stride)) {
594607
return;
595608
}
596609

597-
for (int64_t row = first_row; row < end_row; ++row) {
610+
for (int64_t row = first_row; row < end_row; row += row_stride) {
598611
const scalar_t* in_ptr = input + row * 2 * d;
599612
scalar_t* out_ptr = out + row * d;
600613
out_ptr[idx] =
@@ -607,14 +620,15 @@ __global__ void masked_swiglustep_and_mul_kernel(
607620
scalar_t* __restrict__ out, const scalar_t* __restrict__ input,
608621
const int* __restrict__ valid_token_counts, const int max_num_tokens,
609622
const int d, const float limit) {
610-
int64_t first_row, end_row;
623+
int64_t first_row, end_row, row_stride;
611624
const int idx = get_masked_feature_index<BATCHED_EXPERTS>();
612-
if (idx >= d || !get_masked_row_range<BATCHED_EXPERTS>(
613-
valid_token_counts, max_num_tokens, first_row, end_row)) {
625+
if (idx >= d ||
626+
!get_masked_row_range<BATCHED_EXPERTS>(valid_token_counts, max_num_tokens,
627+
first_row, end_row, row_stride)) {
614628
return;
615629
}
616630

617-
for (int64_t row = first_row; row < end_row; ++row) {
631+
for (int64_t row = first_row; row < end_row; row += row_stride) {
618632
const scalar_t* gate_ptr = input + row * 2 * d;
619633
const scalar_t* up_ptr = gate_ptr + d;
620634
scalar_t* out_ptr = out + row * d;
@@ -654,14 +668,15 @@ __global__ void masked_activation_kernel(
654668
scalar_t* __restrict__ out, const scalar_t* __restrict__ input,
655669
const int* __restrict__ valid_token_counts, const int max_num_tokens,
656670
const int d) {
657-
int64_t first_row, end_row;
671+
int64_t first_row, end_row, row_stride;
658672
const int idx = get_masked_feature_index<BATCHED_EXPERTS>();
659-
if (idx >= d || !get_masked_row_range<BATCHED_EXPERTS>(
660-
valid_token_counts, max_num_tokens, first_row, end_row)) {
673+
if (idx >= d ||
674+
!get_masked_row_range<BATCHED_EXPERTS>(valid_token_counts, max_num_tokens,
675+
first_row, end_row, row_stride)) {
661676
return;
662677
}
663678

664-
for (int64_t row = first_row; row < end_row; ++row) {
679+
for (int64_t row = first_row; row < end_row; row += row_stride) {
665680
const int64_t offset = row * d + idx;
666681
out[offset] = ACT_FN(VLLM_LDG(&input[offset]));
667682
}
@@ -787,11 +802,13 @@ void masked_situ_and_mul(torch::stable::Tensor& out, // [E, T, d]
787802
int num_experts = input.size(0);
788803
int max_num_tokens = input.size(1);
789804
int d = input.size(2) / 2;
790-
if (num_experts == 0 || max_num_tokens == 0) {
805+
if (num_experts == 0 || max_num_tokens == 0 || d == 0) {
791806
return;
792807
}
793808
constexpr int block_size = 256;
794-
dim3 grid((d + block_size - 1) / block_size, num_experts);
809+
const int token_blocks =
810+
std::min(max_num_tokens, vllm::kMaxMaskedTokenBlocks);
811+
dim3 grid((d + block_size - 1) / block_size, num_experts, token_blocks);
795812
dim3 block(block_size);
796813
const torch::stable::accelerator::DeviceGuard device_guard(
797814
input.get_device_index());
@@ -832,14 +849,17 @@ void masked_moe_activation(
832849
const int num_experts = batched_experts ? input.size(0) : 1;
833850
const int max_num_tokens = batched_experts ? input.size(1) : input.size(0);
834851
const int d = out.size(-1);
835-
if (num_experts == 0 || max_num_tokens == 0) {
852+
if (num_experts == 0 || max_num_tokens == 0 || d == 0) {
836853
return;
837854
}
838855

839856
constexpr int block_size = 256;
840857
const int feature_blocks = (d + block_size - 1) / block_size;
841-
// Batched grid: (feature tile, expert); flat grid: (row, feature tile).
842-
dim3 grid = batched_experts ? dim3(feature_blocks, num_experts)
858+
const int token_blocks =
859+
std::min(max_num_tokens, vllm::kMaxMaskedTokenBlocks);
860+
// Batched grid: (feature tile, expert, token lane); flat grid: (row,
861+
// feature tile). Token lanes grid-stride the valid prefix.
862+
dim3 grid = batched_experts ? dim3(feature_blocks, num_experts, token_blocks)
843863
: dim3(max_num_tokens, feature_blocks);
844864
dim3 block(block_size);
845865
const torch::stable::accelerator::DeviceGuard device_guard(

tests/kernels/core/test_activation.py

Lines changed: 117 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
apply_moe_activation,
2929
apply_moe_activation_masked_supported,
3030
)
31+
from vllm.platforms import current_platform
3132
from vllm.utils.torch_utils import set_random_seed
3233

3334
DTYPES = [torch.half, torch.bfloat16, torch.float]
@@ -96,6 +97,27 @@ def test_masked_moe_activation_validates_tensor_contract() -> None:
9697
)
9798

9899

100+
def test_moe_silu_clamp_uses_native_xpu_fallback(
101+
default_vllm_config, monkeypatch
102+
) -> None:
103+
monkeypatch.setattr(current_platform, "is_xpu", lambda: True)
104+
clamp_limit = 3.0
105+
input = torch.tensor([[12.0, -12.0, 8.0, -8.0], [-2.0, 2.0, -4.0, 4.0]])
106+
output = torch.empty(2, 2)
107+
108+
apply_moe_activation(
109+
MoEActivation.SILU,
110+
output,
111+
input,
112+
activation_config=ApplyMoEActivationConfig(clamp_limit=clamp_limit),
113+
)
114+
115+
expected = SiluAndMulWithClamp(clamp_limit, compile_native=False).forward_native(
116+
input
117+
)
118+
torch.testing.assert_close(output, expected)
119+
120+
99121
@pytest.mark.parametrize(
100122
"activation",
101123
[
@@ -345,17 +367,19 @@ def test_masked_situ_and_mul(
345367
)
346368
@pytest.mark.parametrize("dtype", [torch.half, torch.bfloat16])
347369
@pytest.mark.parametrize("mask_layout", ["flat", "batched_experts"])
370+
@pytest.mark.parametrize("d", [512, 513])
348371
@torch.inference_mode()
349372
def test_masked_moe_activation(
350373
default_vllm_config,
351374
activation: MoEActivation,
352375
activation_config: ApplyMoEActivationConfig,
353376
dtype: torch.dtype,
354377
mask_layout: str,
378+
d: int,
355379
) -> None:
356380
"""Count-aware MoE activation computes only valid row prefixes."""
357381
device = CUDA_DEVICES[0]
358-
num_experts, max_num_tokens, d = 4, 7, 128
382+
num_experts, max_num_tokens = 4, 7
359383
input_dim = 2 * d if activation.is_gated else d
360384
if mask_layout == "flat":
361385
input = torch.randn(max_num_tokens, input_dim, dtype=dtype, device=device)
@@ -400,6 +424,98 @@ def test_masked_moe_activation(
400424
assert torch.all(batched_output[expert, num_tokens:] == 42.0)
401425

402426

427+
@pytest.mark.parametrize(
428+
("mask_layout", "counts"),
429+
[
430+
pytest.param("flat", [-2], id="flat_negative"),
431+
pytest.param("flat", [70], id="flat_over_capacity"),
432+
pytest.param("batched_experts", [-2, 70], id="batched"),
433+
],
434+
)
435+
@torch.inference_mode()
436+
def test_masked_moe_activation_clamps_valid_token_counts(
437+
default_vllm_config,
438+
mask_layout: str,
439+
counts: list[int],
440+
) -> None:
441+
"""Counts outside [0, T] clamp without touching padded output rows."""
442+
device = CUDA_DEVICES[0]
443+
max_num_tokens, d = 67, 513
444+
num_experts = len(counts) if mask_layout == "batched_experts" else 1
445+
leading_shape = (
446+
(max_num_tokens,) if mask_layout == "flat" else (num_experts, max_num_tokens)
447+
)
448+
input = torch.randn(*leading_shape, 2 * d, dtype=torch.half, device=device)
449+
output = torch.full((*leading_shape, d), 42.0, dtype=torch.half, device=device)
450+
valid_token_counts = torch.tensor(counts, dtype=torch.int32, device=device)
451+
452+
apply_moe_activation(
453+
MoEActivation.SILU,
454+
output,
455+
input,
456+
valid_token_counts=valid_token_counts,
457+
)
458+
459+
batched_input = input.view(-1, max_num_tokens, 2 * d)
460+
batched_output = output.view(-1, max_num_tokens, d)
461+
for expert, raw_count in enumerate(counts):
462+
num_tokens = max(0, min(raw_count, max_num_tokens))
463+
if num_tokens:
464+
expected = torch.empty((num_tokens, d), dtype=torch.half, device=device)
465+
apply_moe_activation(
466+
MoEActivation.SILU,
467+
expected,
468+
batched_input[expert, :num_tokens].clone(),
469+
)
470+
torch.testing.assert_close(
471+
batched_output[expert, :num_tokens],
472+
expected,
473+
atol=get_default_atol(output),
474+
rtol=get_default_rtol(output),
475+
)
476+
assert torch.all(batched_output[expert, num_tokens:] == 42.0)
477+
478+
479+
@pytest.mark.parametrize("mask_layout", ["flat", "batched_experts"])
480+
@torch.inference_mode()
481+
def test_masked_moe_activation_zero_features(
482+
default_vllm_config,
483+
mask_layout: str,
484+
) -> None:
485+
"""A zero-width activation is a no-op instead of an invalid launch."""
486+
device = CUDA_DEVICES[0]
487+
if mask_layout == "flat":
488+
input = torch.empty(3, 0, dtype=torch.half, device=device)
489+
output = torch.empty(3, 0, dtype=torch.half, device=device)
490+
valid_token_counts = torch.tensor([2], dtype=torch.int32, device=device)
491+
else:
492+
input = torch.empty(2, 3, 0, dtype=torch.half, device=device)
493+
output = torch.empty(2, 3, 0, dtype=torch.half, device=device)
494+
valid_token_counts = torch.tensor([1, 3], dtype=torch.int32, device=device)
495+
496+
apply_moe_activation(
497+
MoEActivation.SILU,
498+
output,
499+
input,
500+
valid_token_counts=valid_token_counts,
501+
)
502+
503+
assert output.numel() == 0
504+
505+
506+
@torch.inference_mode()
507+
def test_masked_situ_and_mul_zero_features(default_vllm_config) -> None:
508+
"""The legacy masked SITU entry point also accepts zero-width tensors."""
509+
device = CUDA_DEVICES[0]
510+
input = torch.empty(2, 3, 0, dtype=torch.half, device=device)
511+
output = torch.empty(2, 3, 0, dtype=torch.half, device=device)
512+
expert_num_tokens = torch.tensor([1, 3], dtype=torch.int32, device=device)
513+
514+
torch.ops._C.masked_situ_and_mul(output, input, expert_num_tokens, 1.5, -1.0)
515+
516+
assert output.numel() == 0
517+
518+
403519
@pytest.mark.parametrize("mask_layout", ["flat", "batched_experts"])
404520
@torch.inference_mode()
405521
def test_masked_moe_activation_opcheck(default_vllm_config, mask_layout: str) -> None:

0 commit comments

Comments
 (0)