Repository navigation
[Bugfix] Support non-gated MoE in online quantization and Marlin MoE tile padding #48028
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 4 commits
f4e40ff
8eca7df
ef2cd62
cda1be0
d33a27b
0554970
6b88975
4521e4c
1e8670a
a328acb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -582,12 +582,14 @@ def _zero_padding(self, layer: Module) -> None: | |
| hidden_size = layer.moe_config.hidden_dim_unpadded | ||
| intermediate_size = layer.moe_config.intermediate_size_per_partition_unpadded | ||
|
|
||
| w13_half_size = layer.w13_weight.shape[1] // 2 | ||
| if w13_half_size > intermediate_size: | ||
| layer.w13_weight[:, intermediate_size:w13_half_size, :] = 0 | ||
| layer.w13_weight[ | ||
| :, w13_half_size + intermediate_size : 2 * w13_half_size, : | ||
| ] = 0 | ||
| # w13 holds two gate/up shards for gated MoE, a single up shard for | ||
| # non-gated MoE (is_act_and_mul=False). | ||
| num_w13_shards = 2 if self.moe.is_act_and_mul else 1 | ||
| w13_shard_size = layer.w13_weight.shape[1] // num_w13_shards | ||
| if w13_shard_size > intermediate_size: | ||
| for s in range(num_w13_shards): | ||
| start = s * w13_shard_size + intermediate_size | ||
| layer.w13_weight[:, start : (s + 1) * w13_shard_size, :] = 0 | ||
| if layer.w13_weight.shape[2] > hidden_size: | ||
| layer.w13_weight[:, :, hidden_size:] = 0 | ||
|
|
||
|
|
@@ -597,12 +599,11 @@ def _zero_padding(self, layer: Module) -> None: | |
| layer.w2_weight[:, :, intermediate_size:] = 0 | ||
|
|
||
| if getattr(layer, "w13_bias", None) is not None: | ||
| w13_bias_half_size = layer.w13_bias.shape[1] // 2 | ||
| if w13_bias_half_size > intermediate_size: | ||
| layer.w13_bias[:, intermediate_size:w13_bias_half_size] = 0 | ||
| layer.w13_bias[ | ||
| :, w13_bias_half_size + intermediate_size : 2 * w13_bias_half_size | ||
| ] = 0 | ||
| w13_bias_shard_size = layer.w13_bias.shape[1] // num_w13_shards | ||
| if w13_bias_shard_size > intermediate_size: | ||
| for s in range(num_w13_shards): | ||
| start = s * w13_bias_shard_size + intermediate_size | ||
| layer.w13_bias[:, start : (s + 1) * w13_bias_shard_size] = 0 | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: Combine with the above loop
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done — the weight and bias pad-zeroing now share one loop: num_w13_shards = 2 if self.moe.is_act_and_mul else 1
for w13 in (layer.w13_weight, getattr(layer, "w13_bias", None)):
if w13 is None:
continue
shard_size = w13.shape[1] // num_w13_shards
if shard_size > intermediate_size:
for s in range(num_w13_shards):
start = s * shard_size + intermediate_size
w13[:, start : (s + 1) * shard_size] = 0Semantics are unchanged: for the 3-D weight, |
||
|
|
||
| if ( | ||
| getattr(layer, "w2_bias", None) is not None | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -217,16 +217,20 @@ def prepare_fp8_layer_for_marlin( | |
| replace_parameter(layer, "bias", bias) | ||
|
|
||
|
|
||
| def _moe_pad_shard_rows(x: torch.Tensor, n: int, padded_n: int) -> torch.Tensor: | ||
| """Zero-pad each gate/up shard of a ``(E, 2 * n, ...)`` tensor to padded_n | ||
| rows. FP8 zero decodes to 0.0, so the padded rows contribute nothing.""" | ||
| def _moe_pad_shard_rows( | ||
| x: torch.Tensor, n: int, padded_n: int, num_shards: int | ||
| ) -> torch.Tensor: | ||
|
Comment on lines
+220
to
+222
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. are there other places where
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I would prefer not to give |
||
| """Zero-pad each w13 shard of a ``(E, num_shards * n, ...)`` tensor to | ||
| padded_n rows. FP8 zero decodes to 0.0, so the padded rows contribute | ||
| nothing. Gated MoE stores two gate/up shards; non-gated MoE | ||
| (``is_act_and_mul=False``) stores a single up shard.""" | ||
| if padded_n == n: | ||
| return x | ||
| e = x.size(0) | ||
| rest = x.shape[2:] | ||
| x = x.view(e, 2, n, *rest) | ||
| x = x.view(e, num_shards, n, *rest) | ||
| x = torch.nn.functional.pad(x, (0, 0) * len(rest) + (0, padded_n - n)) | ||
| return x.reshape(e, 2 * padded_n, *rest) | ||
| return x.reshape(e, num_shards * padded_n, *rest) | ||
|
|
||
|
|
||
| def _moe_pad_last(x: torch.Tensor, n: int, padded_n: int) -> torch.Tensor: | ||
|
|
@@ -265,6 +269,10 @@ def prepare_fp8_moe_layer_for_marlin( | |
| k = layer.hidden_size | ||
| n = layer.intermediate_size_per_partition | ||
| w13_n = w13_weight.size(1) | ||
| # w13 holds two gate/up shards for gated MoE, a single up shard for | ||
| # non-gated MoE (is_act_and_mul=False). | ||
| num_w13_shards = w13_n // n | ||
| assert w13_n == num_w13_shards * n and num_w13_shards in (1, 2) | ||
| weight_block_size = getattr(layer, "weight_block_size", None) | ||
| group_size = -1 if weight_block_size is None else weight_block_size[1] | ||
|
|
||
|
|
@@ -273,7 +281,7 @@ def prepare_fp8_moe_layer_for_marlin( | |
| # are padded to match below (the padded values are irrelevant). | ||
| padded_n = marlin_moe_padded_intermediate(n, group_size) | ||
| if padded_n != n: | ||
| w13_weight = _moe_pad_shard_rows(w13_weight, n, padded_n) | ||
| w13_weight = _moe_pad_shard_rows(w13_weight, n, padded_n, num_w13_shards) | ||
| w2_weight = _moe_pad_last(w2_weight, n, padded_n) | ||
|
|
||
| # WORKSPACE | ||
|
|
@@ -345,10 +353,10 @@ def permute_scales(scales: torch.Tensor, name: str) -> torch.Tensor: | |
| if padded_n != n: | ||
| if "w13" in name: | ||
| g = scales.size(1) | ||
| scales = scales.view(e, g, 2, n) | ||
| scales = scales.view(e, g, num_w13_shards, n) | ||
| scales = torch.nn.functional.pad(scales, (0, padded_n - n)) | ||
| scales = scales.reshape(e, g, 2 * padded_n) | ||
| size_n = 2 * padded_n | ||
| scales = scales.reshape(e, g, num_w13_shards * padded_n) | ||
| size_n = num_w13_shards * padded_n | ||
| else: | ||
| if group_size > 0: | ||
| pad_groups = (padded_n - n) // group_size | ||
|
|
@@ -522,9 +530,10 @@ def prepare_mxfp8_moe_layer_for_marlin( | |
|
|
||
| Args: | ||
| layer: MoE layer (used to read params_dtype and attach workspace). | ||
| w13: [E, 2*N, K] float8_e4m3fn weights. | ||
| w13: [E, S*N, K] float8_e4m3fn weights, where S is 2 for gated MoE | ||
| (gate/up shards) and 1 for non-gated MoE (is_act_and_mul=False). | ||
| w2: [E, K, N] float8_e4m3fn weights. | ||
| w13_scale: [E, 2*N, K//32] uint8 e8m0 scales. | ||
| w13_scale: [E, S*N, K//32] uint8 e8m0 scales. | ||
| w2_scale: [E, K, N//32] uint8 e8m0 scales. | ||
|
|
||
| Returns: | ||
|
|
@@ -534,12 +543,14 @@ def prepare_mxfp8_moe_layer_for_marlin( | |
| e = w13.shape[0] | ||
| k = w13.shape[2] | ||
| n = w2.shape[2] | ||
| num_w13_shards = w13.shape[1] // n | ||
| assert w13.shape[1] == num_w13_shards * n and num_w13_shards in (1, 2) | ||
|
|
||
| # Pad a tile-misaligned intermediate size to a valid Marlin thread tile. | ||
| padded_n = marlin_moe_padded_intermediate(n, group_size) | ||
| if padded_n != n: | ||
| w13 = _moe_pad_shard_rows(w13, n, padded_n) | ||
| w13_scale = _moe_pad_shard_rows(w13_scale, n, padded_n) | ||
| w13 = _moe_pad_shard_rows(w13, n, padded_n, num_w13_shards) | ||
| w13_scale = _moe_pad_shard_rows(w13_scale, n, padded_n, num_w13_shards) | ||
| w2 = _moe_pad_last(w2, n, padded_n) | ||
| w2_scale = _moe_pad_last(w2_scale, n // group_size, padded_n // group_size) | ||
| n = padded_n | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: I think this test is unnecessary. it might be more helpful to add one test that tests the whole online_moe weight creation, post weight loading and kernel run.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed, replaced both narrow tests with a single end-to-end
test_online_moe_fp8_per_block_end_to_endthat covers weight creation, weight loading, post-loading processing and a kernel run (parametrized gated / non-gated).Two details worth calling out:
n=96, rounded up to 128) and leaves the roundup pad rows non-zero, so the kernel output only matches the bf16 reference if_zero_paddingactually zeroes them — that turns the old shape/zero-padding assertions into a behavioural guard.VLLM_TEST_FORCE_FP8_MARLIN=1. Marlin is the fp8 MoE backend that supports non-gated MoE; on Blackwell the defaulttrtllm_fp8_block_scale_moehardcodes the gated layout and rejects a single-shard w13 (gemm1_weights_scale.size(1) == intermediate_size_factor * intermediate_size / 128 (1 vs 2)), which is a separate gap outside this PR.Verified on GB200: 2 passed. Negative controls confirm it catches both bugs — reverting the w13 allocation fix, or making
_zero_paddingassume 2 shards again, fails only the non-gated case.