[MoE] Encapsulate TRT-LLM BF16 weight layout handling (#57405)

Signed-off-by: mgoin <mgoin64@gmail.com>

Misha Goin committed Sep 17, 2026 at 17:47 UTC ac78445bc9ee3bb615b8b21db22fb7dac6698ece
7 files changed +138 -117
tests/kernels/moe/test_flashinfer.py
+21 -9
@@ -23,6 +23,9 @@ from vllm.model_executor.layers.fused_moe.config import (
23 from vllm.model_executor.layers.fused_moe.experts.flashinfer_cutlass_moe import (
24 FlashInferExperts,
25 )
26 +from vllm.model_executor.layers.fused_moe.experts.trtllm_bf16_moe import (
27 + view_as_block_major_k,
28 +)
29 from vllm.model_executor.layers.fused_moe.experts.trtllm_fp8_moe import (
30 TrtLlmFp8ExpertsModular,
31 TrtLlmFp8ExpertsMonolithic,
@@ -570,6 +573,10 @@ def test_unquantized_flashinfer_trtllm_weights_can_be_reprocessed(
573 padded = w2_shape[-1]
574 w13_ptr = layer.w13_weight.data_ptr()
575 w2_ptr = layer.w2_weight.data_ptr()
576 + load_numel = (
577 + layer.w13_weight.weight_loader_numel,
578 + layer.w2_weight.weight_loader_numel,
579 + )
580
581 for _ in range(2):
582 reloaded_w13 = torch.randn_like(layer.w13_weight)
@@ -602,19 +609,24 @@ def test_unquantized_flashinfer_trtllm_weights_can_be_reprocessed(
609 assert layer.w2_weight.shape == w2_shape
610 assert layer.w13_weight.data_ptr() == w13_ptr
611 assert layer.w2_weight.data_ptr() == w2_ptr
605 - kernel_w13, kernel_w2 = method._kernel_weights(layer)
612 + assert (
613 + layer.w13_weight.weight_loader_numel,
614 + layer.w2_weight.weight_loader_numel,
615 + ) == load_numel
616 + kernel_w13 = view_as_block_major_k(layer.w13_weight)
617 + kernel_w2 = view_as_block_major_k(layer.w2_weight)
618 assert torch.equal(kernel_w13, expected_w13)
619 assert torch.equal(kernel_w2, expected_w2)
620
621
610 -def _check_flashinfer_ipc_weights(entries, expected, is_gated, mode, device_index):
622 +def _check_flashinfer_ipc_weights(entries, expected, mode, device_index):
623 from vllm.model_executor.model_loader.weight_cache.ipc_loader import IpcModelLoader
624 from vllm.model_executor.utils import weights_already_processed
625
626 torch.accelerator.set_device_index(device_index)
627 with pytest.MonkeyPatch.context() as monkeypatch:
628 method, layer = _make_unquantized_flashinfer_test_layer(
617 - monkeypatch, 192, is_gated, device="meta"
629 + monkeypatch, 192, is_gated=True, device="meta"
630 )
631 loader = object.__new__(IpcModelLoader)
632 loader.mode = mode
@@ -625,7 +637,10 @@ def _check_flashinfer_ipc_weights(entries, expected, is_gated, mode, device_inde
637 with weights_already_processed():
638 method.process_weights_after_loading(layer)
639 assert method.moe_kernel is not None
628 - actual = method._kernel_weights(layer)
640 + actual = (
641 + view_as_block_major_k(layer.w13_weight),
642 + view_as_block_major_k(layer.w2_weight),
643 + )
644 for weight, reference, pointer in zip(actual, expected, pointers):
645 assert weight.data_ptr() == pointer
646 assert torch.equal(weight.cpu(), reference)
@@ -636,11 +651,10 @@ def _check_flashinfer_ipc_weights(entries, expected, is_gated, mode, device_inde
651 torch.accelerator.synchronize()
652
653
639 -@pytest.mark.parametrize("is_gated", [True, False])
654 @pytest.mark.parametrize("cache_ndim", [3, 4])
655 @pytest.mark.parametrize("mode", ["copy", "zero_copy"])
656 def test_unquantized_flashinfer_trtllm_cached_weights_need_no_method_state(
643 - monkeypatch, is_gated, cache_ndim, mode
657 + monkeypatch, cache_ndim, mode
658 ):
659 """A spawned IPC consumer restores views without transient method state."""
660 from vllm.model_executor.layers.quantization.utils.flashinfer_utils import (
@@ -649,13 +663,12 @@ def test_unquantized_flashinfer_trtllm_cached_weights_need_no_method_state(
663 from vllm.model_executor.model_loader.weight_cache.protocol import TensorEntry
664
665 method, layer = _make_unquantized_flashinfer_test_layer(
652 - monkeypatch, 192, is_gated, device="meta"
666 + monkeypatch, 192, is_gated=True, device="meta"
667 )
668 packed = convert_moe_weights_to_flashinfer_trtllm_block_layout(
669 {},
670 torch.randn_like(layer.w13_weight, device="cuda"),
671 torch.randn_like(layer.w2_weight, device="cuda"),
658 - is_gated_act_gemm=is_gated,
672 )
673 expected = [weight.cpu() for weight in packed]
674 entries = {}
@@ -667,7 +680,6 @@ def test_unquantized_flashinfer_trtllm_cached_weights_need_no_method_state(
680 args=(
681 entries,
682 expected,
670 - is_gated,
683 mode,
684 torch.accelerator.current_device_index(),
685 ),
tests/kernels/moe/test_moe_weight_loading_padded.py
-25
@@ -12,7 +12,6 @@ correctly handles this mismatch.
12 import pytest
13 import torch
14
15 -from vllm.model_executor.layers.fused_moe.activation import MoEActivation
15 from vllm.model_executor.layers.fused_moe.oracle.unquantized import (
16 UnquantizedMoeBackend,
17 )
@@ -465,30 +464,6 @@ class TestUnquantizedTrtLlmPrePadding:
464 assert hidden == 64
465 assert intermediate == expected
466
468 - @pytest.mark.parametrize("is_act_and_mul", [True, False])
469 - def test_create_weights_zeroes_preallocated_padding(self, is_act_and_mul: bool):
470 - method = self._make_method(1344)
471 - if not is_act_and_mul:
472 - method.moe.activation = MoEActivation.RELU2_NO_MUL
473 - method.moe.intermediate_size_per_partition = 1408
474 - layer = torch.nn.Module()
475 -
476 - method.create_weights(
477 - layer=layer,
478 - num_experts=2,
479 - hidden_size=64,
480 - intermediate_size_per_partition=1408,
481 - params_dtype=torch.bfloat16,
482 - )
483 -
484 - up_mult = 2 if is_act_and_mul else 1
485 - assert layer.w13_weight.shape == (2, up_mult * 1408, 64)
486 - assert layer.w2_weight.shape == (2, 64, 1408)
487 - assert torch.count_nonzero(layer.w13_weight[:, 1344:1408]) == 0
488 - if is_act_and_mul:
489 - assert torch.count_nonzero(layer.w13_weight[:, 2752:]) == 0
490 - assert torch.count_nonzero(layer.w2_weight[:, :, 1344:]) == 0
491 -
467
468 class TestLoadWeightsExpertBias:
469 """Some quantized exports (e.g. GPTQ, llm-compressor NVFP4) materialize
tests/kernels/moe/test_trtllm_bf16_moe.py
+49
@@ -12,6 +12,7 @@ import pytest
12 import torch
13
14 import vllm.model_executor.layers.fused_moe.modular_kernel as mk
15 +from tests.kernels.moe.utils import make_dummy_moe_config
16 from tests.kernels.utils import torch_moe
17 from vllm.config import ParallelConfig, VllmConfig, set_current_vllm_config
18 from vllm.model_executor.layers.fused_moe.activation import MoEActivation
@@ -27,6 +28,9 @@ from vllm.model_executor.layers.fused_moe.config import (
28 from vllm.model_executor.layers.fused_moe.experts.trtllm_bf16_moe import (
29 TrtLlmBf16ExpertsModular,
30 )
31 +from vllm.model_executor.layers.fused_moe.experts.trtllm_lora_moe import (
32 + TrtLlmBf16LoRAExperts,
33 +)
34 from vllm.model_executor.layers.fused_moe.oracle.unquantized import (
35 UnquantizedMoeBackend,
36 convert_to_unquantized_kernel_format,
@@ -150,3 +154,48 @@ def test_trtllm_bf16_moe_modular_no_graph(
154 atol=1e-1,
155 rtol=2e-1,
156 )
157 +
158 +
159 +@pytest.mark.parametrize("has_lora_delta", [False, True])
160 +@torch.inference_mode()
161 +def test_trtllm_bf16_lora_accepts_checkpoint_shaped_weights(has_lora_delta):
162 + """LoRA dispatch accepts both 3D parameters and legacy 4D packed views."""
163 + config = make_dummy_moe_config(
164 + num_experts=128, hidden_dim=256, intermediate_size=128
165 + )
166 + experts = TrtLlmBf16LoRAExperts(config, FUSED_MOE_UNQUANTIZED_CONFIG)
167 + x = torch.randn(2, 256, device="cuda", dtype=torch.bfloat16) / 10
168 + w1, w2 = convert_to_unquantized_kernel_format(
169 + UnquantizedMoeBackend.FLASHINFER_TRTLLM,
170 + config,
171 + torch.randn(128, 256, 256, device="cuda", dtype=torch.bfloat16) / 10,
172 + torch.randn(128, 256, 128, device="cuda", dtype=torch.bfloat16) / 10,
173 + )
174 + assert w1.ndim == w2.ndim == 3
175 + topk = (
176 + torch.tensor([[0], [1]], device="cuda", dtype=torch.int32),
177 + torch.ones(2, 1, device="cuda", dtype=torch.float32),
178 + )
179 + delta = torch.randn(2, 1, 256, device="cuda", dtype=torch.bfloat16)
180 +
181 + def invoke(w1, w2):
182 + result = experts.invoke_routed_moe(
183 + hidden_states=x,
184 + w1=w1,
185 + w2=w2,
186 + topk_ids_and_weights=topk,
187 + gemm1_lora_delta=delta if has_lora_delta else None,
188 + global_num_experts=128,
189 + a1q_scale=None,
190 + output=torch.empty_like(x),
191 + )
192 + if has_lora_delta:
193 + # Only compare rows belonging to real tokens, excluding padding.
194 + indices = result[2].flatten().long()
195 + return result[0][indices], result[3][indices]
196 + return result
197 +
198 + actual = invoke(w1, w2)
199 + expected = invoke(w1.view(128, 4, 256, 64), w2.view(128, 2, 256, 64))
200 + for actual_tensor, expected_tensor in zip(actual, expected):
201 + torch.testing.assert_close(actual_tensor, expected_tensor)
vllm/model_executor/layers/fused_moe/experts/trtllm_bf16_moe.py
+13 -4
@@ -31,6 +31,15 @@ from vllm.platforms import current_platform
31 from vllm.utils.flashinfer import has_flashinfer_trtllm_fused_moe
32
33
34 +def view_as_block_major_k(weight: torch.Tensor) -> torch.Tensor:
35 + """View packed storage, including legacy 4D IPC cache entries."""
36 + if weight.ndim == 4:
37 + return weight
38 + experts, rows, cols = weight.shape
39 + block_k = 128 // weight.element_size()
40 + return weight.view(experts, cols // block_k, rows, block_k)
41 +
42 +
43 class TrtLlmBf16ExpertsBase:
44 """BF16 unquantized TRTLLM-Gen MoE kernels. Shared base for modular and
45 monolithic interfaces.
@@ -200,8 +209,8 @@ class TrtLlmBf16ExpertsModular(TrtLlmBf16ExpertsBase, mk.FusedMoEExpertsModular)
209 result = flashinfer.fused_moe.trtllm_bf16_routed_moe(
210 topk_ids=(topk_ids, topk_weights),
211 hidden_states=hidden_states,
203 - gemm1_weights=w1,
204 - gemm2_weights=w2,
212 + gemm1_weights=view_as_block_major_k(w1),
213 + gemm2_weights=view_as_block_major_k(w2),
214 num_experts=global_num_experts,
215 top_k=topk_ids.size(1),
216 n_group=None,
@@ -281,8 +290,8 @@ class TrtLlmBf16ExpertsMonolithic(TrtLlmBf16ExpertsBase, mk.FusedMoEExpertsMonol
290 routing_logits=router_logits,
291 routing_bias=e_score_correction_bias,
292 hidden_states=hidden_states,
284 - gemm1_weights=w1,
285 - gemm2_weights=w2,
293 + gemm1_weights=view_as_block_major_k(w1),
294 + gemm2_weights=view_as_block_major_k(w2),
295 num_experts=global_num_experts,
296 top_k=self.topk,
297 n_group=num_expert_group,
vllm/model_executor/layers/fused_moe/experts/trtllm_lora_moe.py
+5 -2
@@ -34,6 +34,9 @@ from vllm.model_executor.layers.fused_moe.experts.lora_context import MoELoRACon
34 from vllm.model_executor.layers.fused_moe.experts.lora_experts_mixin import (
35 LoRAExpertsMixin,
36 )
37 +from vllm.model_executor.layers.fused_moe.experts.trtllm_bf16_moe import (
38 + view_as_block_major_k,
39 +)
40 from vllm.model_executor.layers.fused_moe.topk_weight_and_reduce import (
41 TopKWeightAndReduceNoOP,
42 )
@@ -527,8 +530,8 @@ class TrtLlmBf16LoRAExperts(_TrtLlmLoRAExpertsBase):
530 ret = flashinfer.fused_moe.trtllm_bf16_routed_moe(
531 topk_ids=topk_ids_and_weights,
532 hidden_states=hidden_states,
530 - gemm1_weights=w1,
531 - gemm2_weights=w2,
533 + gemm1_weights=view_as_block_major_k(w1),
534 + gemm2_weights=view_as_block_major_k(w2),
535 gemm1_lora_delta=gemm1_lora_delta,
536 num_experts=global_num_experts,
537 top_k=self.topk,
vllm/model_executor/layers/fused_moe/oracle/unquantized.py
+26 -1
@@ -26,6 +26,7 @@ from vllm.model_executor.layers.quantization.utils.flashinfer_utils import (
26 swap_w13_to_w31,
27 )
28 from vllm.platforms import current_platform
29 +from vllm.utils.math_utils import round_up
30
31 if TYPE_CHECKING:
32 from vllm.model_executor.layers.quantization.utils.quant_utils import QuantKey
@@ -349,6 +350,17 @@ def select_unquantized_moe_backend(
350 )
351
352
353 +def unquantized_round_up_hidden_size_and_intermediate_size(
354 + backend: UnquantizedMoeBackend,
355 + hidden_size: int,
356 + intermediate_size: int,
357 +) -> tuple[int, int]:
358 + """Round up dimensions before allocation to satisfy the selected kernel."""
359 + if backend == UnquantizedMoeBackend.FLASHINFER_TRTLLM:
360 + intermediate_size = round_up(intermediate_size, 128)
361 + return hidden_size, intermediate_size
362 +
363 +
364 def convert_to_unquantized_kernel_format(
365 unquantized_backend: UnquantizedMoeBackend,
366 moe_config: FusedMoEConfig,
@@ -377,13 +389,26 @@ def convert_to_unquantized_kernel_format(
389 )
390 moe_config.intermediate_size_per_partition = padded_intermediate
391
392 + # Reloads only overwrite checkpoint slices. An earlier in-place
393 + # permutation can leave nonzero values in the raw padding slots.
394 + unpadded = moe_config.intermediate_size_per_partition_unpadded
395 + assert unpadded is not None
396 + if padded_intermediate > unpadded:
397 + w13_weight[:, unpadded:padded_intermediate].zero_()
398 + if is_act_and_mul:
399 + w13_weight[:, padded_intermediate + unpadded :].zero_()
400 + w2_weight[:, :, unpadded:].zero_()
401 +
402 _cache_permute_indices: dict[torch.Size, torch.Tensor] = {}
381 - w13_weight, w2_weight = convert_moe_weights_to_flashinfer_trtllm_block_layout(
403 + convert_moe_weights_to_flashinfer_trtllm_block_layout(
404 _cache_permute_indices,
405 w13_weight,
406 w2_weight,
407 is_gated_act_gemm=is_act_and_mul,
408 )
409 + # Keep checkpoint-shaped parameters for reload/IPC. The experts create
410 + # BlockMajorK views at dispatch without changing the underlying storage.
411 + return w13_weight, w2_weight
412
413 if (
414 unquantized_backend == UnquantizedMoeBackend.TRITON
vllm/model_executor/layers/fused_moe/unquantized_fused_moe_method.py
+24 -76
@@ -24,6 +24,7 @@ from vllm.model_executor.layers.fused_moe.oracle.unquantized import (
24 convert_to_unquantized_kernel_format,
25 make_unquantized_moe_kernel,
26 select_unquantized_moe_backend,
27 + unquantized_round_up_hidden_size_and_intermediate_size,
28 )
29 from vllm.model_executor.layers.fused_moe.runner.shared_experts import (
30 SharedExperts,
@@ -34,7 +35,6 @@ from vllm.model_executor.utils import (
35 set_weight_attrs,
36 )
37 from vllm.platforms import current_platform
37 -from vllm.utils.math_utils import round_up
38
39 if TYPE_CHECKING:
40 from vllm.model_executor.layers.fused_moe.routed_experts import RoutedExperts
@@ -72,9 +72,6 @@ class UnquantizedFusedMoEMethod(FusedMoEMethodBase, CustomOp):
72 ):
73 unpadded_intermediate = self.moe.intermediate_size_per_partition_unpadded
74 assert unpadded_intermediate is not None
75 - has_intermediate_padding = (
76 - intermediate_size_per_partition > unpadded_intermediate
77 - )
75 if self.moe.is_act_and_mul:
76 w13_up_dim = 2 * intermediate_size_per_partition
77 else:
@@ -83,22 +80,15 @@ class UnquantizedFusedMoEMethod(FusedMoEMethodBase, CustomOp):
80 assert unpadded_hidden is not None
81 unpadded_up = unpadded_intermediate * (2 if self.moe.is_act_and_mul else 1)
82 # Fused gate_up_proj (column parallel)
86 - w13_data = torch.empty(
87 - num_experts,
88 - w13_up_dim,
89 - hidden_size,
90 - dtype=params_dtype,
83 + w13_weight = torch.nn.Parameter(
84 + torch.empty(
85 + num_experts,
86 + w13_up_dim,
87 + hidden_size,
88 + dtype=params_dtype,
89 + ),
90 + requires_grad=False,
91 )
92 - if has_intermediate_padding:
93 - # The checkpoint loader only writes the unpadded slices. Zero the
94 - # tails now so no full-size padding copy is needed after loading.
95 - w13_data[:, unpadded_intermediate:intermediate_size_per_partition].zero_()
96 - if self.moe.is_act_and_mul:
97 - w13_data[
98 - :,
99 - intermediate_size_per_partition + unpadded_intermediate :,
100 - ].zero_()
101 - w13_weight = torch.nn.Parameter(w13_data, requires_grad=False)
92 layer.register_parameter("w13_weight", w13_weight)
93 set_weight_attrs(w13_weight, extra_weight_attrs)
94 w13_weight.weight_loader_numel = num_experts * unpadded_up * unpadded_hidden
@@ -111,15 +101,15 @@ class UnquantizedFusedMoEMethod(FusedMoEMethodBase, CustomOp):
101 set_weight_attrs(w13_bias, extra_weight_attrs)
102 w13_bias.weight_loader_numel = num_experts * unpadded_up
103 # down_proj (row parallel)
114 - w2_data = torch.empty(
115 - num_experts,
116 - hidden_size,
117 - intermediate_size_per_partition,
118 - dtype=params_dtype,
104 + w2_weight = torch.nn.Parameter(
105 + torch.empty(
106 + num_experts,
107 + hidden_size,
108 + intermediate_size_per_partition,
109 + dtype=params_dtype,
110 + ),
111 + requires_grad=False,
112 )
120 - if has_intermediate_padding:
121 - w2_data[:, :, unpadded_intermediate:].zero_()
122 - w2_weight = torch.nn.Parameter(w2_data, requires_grad=False)
113 layer.register_parameter("w2_weight", w2_weight)
114 set_weight_attrs(w2_weight, extra_weight_attrs)
115 w2_weight.weight_loader_numel = (
@@ -147,11 +137,9 @@ class UnquantizedFusedMoEMethod(FusedMoEMethodBase, CustomOp):
137 act_dtype,
138 moe_parallel_config,
139 )
150 - if self.unquantized_backend == UnquantizedMoeBackend.FLASHINFER_TRTLLM:
151 - intermediate_size_per_partition = round_up(
152 - intermediate_size_per_partition, 128
153 - )
154 - return hidden_size, intermediate_size_per_partition
140 + return unquantized_round_up_hidden_size_and_intermediate_size(
141 + self.unquantized_backend, hidden_size, intermediate_size_per_partition
142 + )
143
144 def _maybe_pad_weight(self, weight: torch.Tensor) -> torch.Tensor:
145 # Pad the weight tensor. This is an optimization on ROCm platform, which
@@ -177,21 +165,6 @@ class UnquantizedFusedMoEMethod(FusedMoEMethodBase, CustomOp):
165 w13: torch.Tensor,
166 w2: torch.Tensor,
167 ) -> None:
180 - w13_parameter_shape = w13.shape
181 - w2_parameter_shape = w2.shape
182 -
183 - if self.unquantized_backend == UnquantizedMoeBackend.FLASHINFER_TRTLLM:
184 - unpadded = self.moe.intermediate_size_per_partition_unpadded
185 - assert unpadded is not None
186 - intermediate = w2.shape[-1]
187 - # Reloads overwrite only checkpoint slices; the previous in-place
188 - # permutation can leave nonzero values in the raw padding slots.
189 - if intermediate > unpadded:
190 - w13[:, unpadded:intermediate].zero_()
191 - if self.moe.is_act_and_mul:
192 - w13[:, intermediate + unpadded :].zero_()
193 - w2[:, :, unpadded:].zero_()
194 -
168 # Shuffle weights to runtime format.
169 w13_new, w2_new = convert_to_unquantized_kernel_format(
170 self.unquantized_backend,
@@ -199,10 +172,6 @@ class UnquantizedFusedMoEMethod(FusedMoEMethodBase, CustomOp):
172 w13_weight=w13,
173 w2_weight=w2,
174 )
202 - if self.unquantized_backend == UnquantizedMoeBackend.FLASHINFER_TRTLLM:
203 - w13_new = w13_new.view(w13_parameter_shape)
204 - w2_new = w2_new.view(w2_parameter_shape)
205 -
175 # `moe_kernel` is initialized to None in FusedMoEMethodBase.__init__;
176 # On the first call we replace the parameter normally. On subsequent
177 # calls (e.g. RL weight updates that re-trigger
@@ -231,25 +200,6 @@ class UnquantizedFusedMoEMethod(FusedMoEMethodBase, CustomOp):
200 assert self.moe_kernel is not None
201 self.moe_kernel.fused_experts.process_weights_after_loading(layer)
202
234 - def _kernel_weights(
235 - self, layer: "RoutedExperts"
236 - ) -> tuple[torch.Tensor, torch.Tensor]:
237 - if self.unquantized_backend != UnquantizedMoeBackend.FLASHINFER_TRTLLM:
238 - return layer.w13_weight, layer.w2_weight
239 -
240 - # IPC caches restore tensor shapes/storage, not method attributes.
241 - # BlockMajorK uses 128-byte blocks, i.e. 64 BF16 elements.
242 - def block_view(weight: torch.Tensor) -> torch.Tensor:
243 - if weight.ndim == 4:
244 - return weight
245 - experts, rows, cols = weight.shape
246 - return weight.view(experts, cols // 64, rows, 64)
247 -
248 - return (
249 - block_view(layer.w13_weight),
250 - block_view(layer.w2_weight),
251 - )
252 -
203 def _init_moe_kernel(self, layer: "RoutedExperts") -> None:
204 """Build the MoE kernel from the layer's current (shuffled) weights."""
205 self.moe_quant_config = self.get_fused_moe_quant_config(layer)
@@ -354,11 +304,10 @@ class UnquantizedFusedMoEMethod(FusedMoEMethodBase, CustomOp):
304 shared_experts_input: torch.Tensor | None,
305 ) -> torch.Tensor:
306 assert self.moe_kernel is not None
357 - w13, w2 = self._kernel_weights(layer)
307 return self.moe_kernel.apply(
308 hidden_states=x,
360 - w1=w13,
361 - w2=w2,
309 + w1=layer.w13_weight,
310 + w2=layer.w2_weight,
311 topk_weights=topk_weights,
312 topk_ids=topk_ids,
313 activation=layer.activation,
@@ -396,11 +345,10 @@ class UnquantizedFusedMoEMethod(FusedMoEMethodBase, CustomOp):
345 ) -> torch.Tensor | UnfinalizedMoEOutput:
346 assert self.is_monolithic
347 assert self.moe_kernel is not None
399 - w13, w2 = self._kernel_weights(layer)
348 return self.moe_kernel.apply_monolithic(
349 x,
402 - w13,
403 - w2,
350 + layer.w13_weight,
351 + layer.w2_weight,
352 router_logits,
353 activation=layer.activation,
354 global_num_experts=layer.global_num_experts,