Skip to content

Commit 265dc02

Browse files
committed
[ExecuTorch][llm] Fuse w1+w3 into single GEMM in quantized_moe_ffn
Pull Request resolved: #21124 Fuse the up-projection (w1) and gate-projection (w3) into a single [2F, D] GEMM per expert. This halves the number of torchao activation quantizations per expert (from 2 to 1) and reduces total GEMM calls from 3 to 2 per active expert. At AOT time, w1 and w3 are concatenated before packing: pack_fn(cat([w1, w3], dim=0)). At runtime, a single expert_linear_dispatch produces [m_e, 2F], then a fused swiglu_and_compact pass reads the interleaved h1/h3 and writes [m_e, F] for the w2 down-projection. Schema changes from (packed_w1, packed_w3, packed_w2) to (packed_w13, packed_w2) — one fewer tensor arg (14 -> 13). ghstack-source-id: 409183086 @exported-using-ghexport Differential Revision: [D102799854](https://our.internmc.facebook.com/intern/diff/D102799854/)
1 parent 158e09a commit 265dc02

6 files changed

Lines changed: 131 additions & 166 deletions

File tree

examples/models/llama/source_transformation/moe.py

Lines changed: 11 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -104,17 +104,15 @@ class QuantizedMoEFFN(nn.Module):
104104
Buffers (registered, not parameters):
105105
gate_weight [E, D] fp32 — copied from `MOEFeedForward.gate.weight`
106106
expert_bias [E] or [0] fp32 — empty when `use_expert_bias=False`
107-
packed_w1 [E, packed_bytes_w1] uint8 — torchao opaque blobs
108-
packed_w3 [E, packed_bytes_w3] uint8
109-
packed_w2 [E, packed_bytes_w2] uint8
107+
packed_w13 [E, packed_bytes_w13] uint8 — fused w1+w3 torchao blobs
108+
packed_w2 [E, packed_bytes_w2] uint8
110109
"""
111110

112111
def __init__(
113112
self,
114113
gate_weight: torch.Tensor,
115114
expert_bias: torch.Tensor | None,
116-
packed_w1: torch.Tensor,
117-
packed_w3: torch.Tensor,
115+
packed_w13: torch.Tensor,
118116
packed_w2: torch.Tensor,
119117
*,
120118
num_experts: int,
@@ -149,8 +147,7 @@ def __init__(
149147

150148
# torchao packed blobs are int8 tensors; reinterpret the bytes as
151149
# uint8 (no value conversion) for the op schema.
152-
self.register_buffer("packed_w1", packed_w1.view(torch.uint8))
153-
self.register_buffer("packed_w3", packed_w3.view(torch.uint8))
150+
self.register_buffer("packed_w13", packed_w13.view(torch.uint8))
154151
self.register_buffer("packed_w2", packed_w2.view(torch.uint8))
155152
self.shared_expert: nn.Module | None = None
156153

@@ -178,8 +175,7 @@ def forward(self, x: torch.Tensor) -> torch.Tensor:
178175
x_flat,
179176
self.gate_weight,
180177
self.expert_bias,
181-
self.packed_w1,
182-
self.packed_w3,
178+
self.packed_w13,
183179
self.packed_w2,
184180
self.num_activated_experts,
185181
self.num_experts,
@@ -261,18 +257,15 @@ def _build_quantized_moe_ffn_from_eager(
261257
_torchao_pack_int4_weight if weight_nbit == 4 else _torchao_pack_int8_weight
262258
)
263259

264-
packed_w1_list: list[torch.Tensor] = []
265-
packed_w3_list: list[torch.Tensor] = []
260+
packed_w13_list: list[torch.Tensor] = []
266261
packed_w2_list: list[torch.Tensor] = []
267262
for ei in range(e):
268-
# w1, w3 share the [F, D] shape, group along K=D.
269-
packed_w1_list.append(pack_fn(w1[ei], group_size))
270-
packed_w3_list.append(pack_fn(w3[ei], group_size))
271-
# w2 packed shape is [D, F], group along K=F.
263+
# Fuse w1 and w3 into a single [2F, D] matrix before packing.
264+
w13 = torch.cat([w1[ei], w3[ei]], dim=0) # [2F, D]
265+
packed_w13_list.append(pack_fn(w13, group_size))
272266
packed_w2_list.append(pack_fn(w2_packed_in[ei], group_size))
273267

274-
packed_w1 = _stack_per_expert_packed(packed_w1_list)
275-
packed_w3 = _stack_per_expert_packed(packed_w3_list)
268+
packed_w13 = _stack_per_expert_packed(packed_w13_list)
276269
packed_w2 = _stack_per_expert_packed(packed_w2_list)
277270

278271
# `MOEFeedForward` registers `expert_bias` as a buffer that is None unless
@@ -284,8 +277,7 @@ def _build_quantized_moe_ffn_from_eager(
284277
replacement = QuantizedMoEFFN(
285278
gate_weight=moe.gate.weight.detach().clone(),
286279
expert_bias=expert_bias,
287-
packed_w1=packed_w1,
288-
packed_w3=packed_w3,
280+
packed_w13=packed_w13,
289281
packed_w2=packed_w2,
290282
num_experts=e,
291283
num_activated_experts=moe.num_activated_experts,

extension/llm/custom_ops/custom_ops.py

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -168,8 +168,7 @@ def _validate_quantized_moe_ffn_params(
168168
x,
169169
gate_weight,
170170
expert_bias,
171-
packed_w1,
172-
packed_w3,
171+
packed_w13,
173172
packed_w2,
174173
num_activated_experts,
175174
num_experts,
@@ -202,18 +201,14 @@ def _validate_quantized_moe_ffn_params(
202201
), f"expert_bias must be float32, got {expert_bias.dtype}"
203202

204203
for name, t in (
205-
("packed_w1", packed_w1),
206-
("packed_w3", packed_w3),
204+
("packed_w13", packed_w13),
207205
("packed_w2", packed_w2),
208206
):
209207
assert (
210208
t.dim() == 2 and t.size(0) == num_experts
211209
), f"{name} must be [E={num_experts}, packed_bytes], got {list(t.size())}"
212210
assert t.dtype == torch.uint8, f"{name} must be uint8, got {t.dtype}"
213211

214-
assert packed_w1.size(1) == packed_w3.size(
215-
1
216-
), "packed_w1 and packed_w3 per-expert blob sizes must match"
217212
assert (
218213
0 < num_activated_experts <= num_experts
219214
), f"num_activated_experts ({num_activated_experts}) out of range [1, {num_experts}]"
@@ -241,8 +236,7 @@ def quantized_moe_ffn_meta(
241236
x,
242237
gate_weight,
243238
expert_bias,
244-
packed_w1,
245-
packed_w3,
239+
packed_w13,
246240
packed_w2,
247241
num_activated_experts,
248242
num_experts,
@@ -257,8 +251,7 @@ def quantized_moe_ffn_meta(
257251
x,
258252
gate_weight,
259253
expert_bias,
260-
packed_w1,
261-
packed_w3,
254+
packed_w13,
262255
packed_w2,
263256
num_activated_experts,
264257
num_experts,

0 commit comments

Comments
 (0)