Skip to content

Commit b912b96

Browse files
committed
Add comprehensive MoE dispatch unit tests with golden-value regression checks
Add behavioral and golden-value tests for PositionwiseConvFFMoE.forward(): - Determinism (same seed → identical output across re-init) - Gradient flow (backward produces non-zero grads for router + experts) - Edge cases: all-padding, single token, top_k == num_experts - Bias / no-bias configurations - Per-sample padding isolation - Golden-value regression: expected output_sum, logits_sum, first expert indices, and output[0,0,:4] captured from the current sequential implementation under seed=42, verified with atol=1e-5 These tests lock in the numerical behaviour of the forward pass so that any future refactoring (e.g. batched dispatch) can be validated against the same golden values. Signed-off-by: Xuesong Yang <1646669+XuesongYang@users.noreply.github.com>
1 parent 03fdadf commit b912b96

1 file changed

Lines changed: 162 additions & 0 deletions

File tree

tests/collections/tts/modules/test_transformer_2501.py

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1051,6 +1051,50 @@ def test_router_padding_masking(self):
10511051
class TestPositionwiseConvFFMoE:
10521052
"""Test the PositionwiseConvFFMoE class."""
10531053

1054+
# Golden expected values captured from the original sequential implementation
1055+
# with set_seed(42), d_model=8, d_ffn=32, batch_size=2, seq_len=10.
1056+
# Each entry: (num_experts, top_k, bias, padding, expected_output_sum,
1057+
# expected_logits_sum, expected_first_expert_indices,
1058+
# expected_output_first_4_elements)
1059+
_GOLDEN_VALUES = {
1060+
"E4_top1_nobias_nopad": {
1061+
"output_sum": 1.4807705879211426,
1062+
"logits_sum": -1.4070085287094116,
1063+
"first_idx": [2],
1064+
"output_0_0": [-0.031408119946718216, 0.14679314196109772, -0.021915754303336143, 0.12932124733924866],
1065+
},
1066+
"E4_top2_nobias_nopad": {
1067+
"output_sum": 1.6757168769836426,
1068+
"logits_sum": -1.4070085287094116,
1069+
"first_idx": [2, 0],
1070+
"output_0_0": [-0.12169260531663895, -0.0002511143684387207, -0.09718462079763412, -0.017200887203216553],
1071+
},
1072+
"E8_top1_nobias_pad": {
1073+
"output_sum": 3.700800895690918,
1074+
"logits_sum": 8.973369598388672,
1075+
"first_idx": [4],
1076+
"output_0_0": [-0.28720206022262573, 0.20689748227596283, 0.40565282106399536, -0.021458642557263374],
1077+
},
1078+
"E8_top2_nobias_pad": {
1079+
"output_sum": 3.0652523040771484,
1080+
"logits_sum": 8.973369598388672,
1081+
"first_idx": [4, 1],
1082+
"output_0_0": [-0.2828606963157654, 0.1696583479642868, 0.2753525376319885, -0.041214004158973694],
1083+
},
1084+
"E4_top2_bias_pad": {
1085+
"output_sum": -1.1706292629241943,
1086+
"logits_sum": -1.2999919652938843,
1087+
"first_idx": [1, 3],
1088+
"output_0_0": [-0.10531097650527954, 0.14638465642929077, -0.1260562241077423, -0.11432743072509766],
1089+
},
1090+
"E16_top1_bias_pad": {
1091+
"output_sum": -0.7250787019729614,
1092+
"logits_sum": 6.78455924987793,
1093+
"first_idx": [8],
1094+
"output_0_0": [0.480951189994812, -0.3138628602027893, -0.010073505342006683, -0.05126545578241348],
1095+
},
1096+
}
1097+
10541098
@classmethod
10551099
def setup_class(cls):
10561100
cls.d_model = 8
@@ -1165,6 +1209,124 @@ def test_moe_ffn_different_expert_counts(self):
11651209

11661210
assert output.shape == x.shape
11671211

1212+
def test_gradient_flow(self):
1213+
"""Backward pass must produce non-zero gradients for router and expert weights."""
1214+
set_seed(42)
1215+
moe_ffn = PositionwiseConvFFMoE(
1216+
d_model=self.d_model,
1217+
d_ffn=self.d_ffn,
1218+
p_dropout=0.0,
1219+
num_experts=self.num_experts,
1220+
top_k_experts=self.top_k_experts,
1221+
kernel_size=1,
1222+
)
1223+
moe_ffn.train()
1224+
1225+
x = torch.randn(self.batch_size, self.seq_len, self.d_model, requires_grad=True)
1226+
x_mask = torch.ones(self.batch_size, self.seq_len)
1227+
output, _, _, _ = moe_ffn(x, x_mask)
1228+
loss = output.sum()
1229+
loss.backward()
1230+
1231+
assert x.grad is not None and x.grad.abs().sum() > 0, "Input grad must be non-zero"
1232+
assert moe_ffn.router.router.weight.grad is not None, "Router weight grad must exist"
1233+
assert moe_ffn.router.router.weight.grad.abs().sum() > 0, "Router weight grad must be non-zero"
1234+
1235+
has_expert_grad = False
1236+
for expert in moe_ffn.experts:
1237+
for name in ('proj', 'o_net'):
1238+
g = expert[name].conv.weight.grad
1239+
if g is not None and g.abs().sum() > 0:
1240+
has_expert_grad = True
1241+
break
1242+
assert has_expert_grad, "At least one expert weight must receive a gradient"
1243+
1244+
def test_all_padding_produces_zeros(self):
1245+
"""When x_mask is all zeros, output and routing info must be all zeros."""
1246+
set_seed(42)
1247+
moe_ffn = PositionwiseConvFFMoE(
1248+
d_model=self.d_model,
1249+
d_ffn=self.d_ffn,
1250+
p_dropout=0.0,
1251+
num_experts=self.num_experts,
1252+
top_k_experts=self.top_k_experts,
1253+
kernel_size=1,
1254+
)
1255+
moe_ffn.eval()
1256+
1257+
x = torch.randn(self.batch_size, self.seq_len, self.d_model)
1258+
x_mask = torch.zeros(self.batch_size, self.seq_len)
1259+
1260+
with torch.no_grad():
1261+
output, router_logits, router_probs, expert_indices = moe_ffn(x, x_mask)
1262+
1263+
assert torch.all(output == 0), "Output must be all zeros when fully padded"
1264+
assert torch.all(router_logits == 0), "Router logits must be all zeros when fully padded"
1265+
assert torch.all(expert_indices == -1), "Expert indices must be -1 when fully padded"
1266+
1267+
@pytest.mark.parametrize(
1268+
"num_experts,top_k,use_bias,use_padding",
1269+
[
1270+
(4, 1, False, False),
1271+
(4, 2, False, False),
1272+
(8, 1, False, True),
1273+
(8, 2, False, True),
1274+
(4, 2, True, True),
1275+
(16, 1, True, True),
1276+
],
1277+
ids=[
1278+
"E4_top1_nobias_nopad",
1279+
"E4_top2_nobias_nopad",
1280+
"E8_top1_nobias_pad",
1281+
"E8_top2_nobias_pad",
1282+
"E4_top2_bias_pad",
1283+
"E16_top1_bias_pad",
1284+
],
1285+
)
1286+
def test_forward_golden_values(self, num_experts, top_k, use_bias, use_padding, request):
1287+
"""Verify forward() output matches golden expected values.
1288+
1289+
Golden values were captured from the original sequential implementation.
1290+
Any refactoring of forward() must reproduce these exact values (within
1291+
floating-point tolerance) to guarantee numerical equivalence.
1292+
"""
1293+
golden = self._GOLDEN_VALUES[request.node.callspec.id]
1294+
1295+
set_seed(42)
1296+
moe_ffn = PositionwiseConvFFMoE(
1297+
d_model=self.d_model,
1298+
d_ffn=self.d_ffn,
1299+
p_dropout=0.0,
1300+
num_experts=num_experts,
1301+
top_k_experts=top_k,
1302+
kernel_size=1,
1303+
bias=use_bias,
1304+
)
1305+
moe_ffn.eval()
1306+
1307+
x = torch.randn(self.batch_size, self.seq_len, self.d_model)
1308+
x_mask = torch.ones(self.batch_size, self.seq_len)
1309+
if use_padding:
1310+
x_mask[0, 7:] = 0
1311+
x_mask[1, 5:] = 0
1312+
1313+
with torch.no_grad():
1314+
output, router_logits, router_probs, expert_indices = moe_ffn(x, x_mask)
1315+
1316+
assert (
1317+
expert_indices[0, 0].tolist() == golden["first_idx"]
1318+
), f"Expert indices mismatch: got {expert_indices[0, 0].tolist()}, expected {golden['first_idx']}"
1319+
assert torch.allclose(
1320+
router_logits.sum(), torch.tensor(golden["logits_sum"]), atol=1e-5
1321+
), f"Logits sum mismatch: got {router_logits.sum().item()}, expected {golden['logits_sum']}"
1322+
assert torch.allclose(
1323+
output.sum(), torch.tensor(golden["output_sum"]), atol=1e-5
1324+
), f"Output sum mismatch: got {output.sum().item()}, expected {golden['output_sum']}"
1325+
expected_elems = torch.tensor(golden["output_0_0"])
1326+
assert torch.allclose(
1327+
output[0, 0, :4], expected_elems, atol=1e-5
1328+
), f"Output[0,0,:4] mismatch: got {output[0, 0, :4].tolist()}, expected {golden['output_0_0']}"
1329+
11681330

11691331
@pytest.mark.unit
11701332
class TestTransformerLayerWithMoE:

0 commit comments

Comments
 (0)