|
30 | 30 | from maxtext.layers.initializers import NdInitializer, nd_dense_init, variable_to_logically_partitioned |
31 | 31 | from maxtext.layers.quantizations import Fp8Quantization |
32 | 32 | from maxtext.utils import maxtext_utils |
| 33 | +from maxtext.utils.sharding import remove_expert_from_partition_spec |
33 | 34 | from tests.utils.test_helpers import get_test_config_path |
34 | 35 | import pytest |
35 | 36 |
|
@@ -1534,5 +1535,91 @@ def test_prefused_vs_sparse_softmax(self): |
1534 | 1535 | self.assertIsNone(bias_updates) |
1535 | 1536 |
|
1536 | 1537 |
|
| 1538 | +@pytest.mark.parametrize( |
| 1539 | + "model_name,flag", |
| 1540 | + [ |
| 1541 | + ("mixtral-8x7b", True), # flag on: expert stays on E, peeled off the batch dim |
| 1542 | + ("mixtral-8x22b", True), # flag on |
| 1543 | + ("mixtral-8x7b", False), # flag off (default): batch dim keeps 'expert' |
| 1544 | + ], |
| 1545 | +) |
| 1546 | +def test_moe_dispatch_keeps_expert_on_expert_dim(model_name, flag): |
| 1547 | + """Regression guard for the MoE dispatch/MLP expert-parallel sharding. |
| 1548 | +
|
| 1549 | + The expert (E) dim is always sharded by the 'expert' mesh axis (via activation_exp). |
| 1550 | + With moe_dispatch_no_expert_sharding the batch (B) dim must NOT also take 'expert' |
| 1551 | + (which would double-map two tensor dims onto one mesh axis and force an FSDP-style |
| 1552 | + fallback instead of expert-parallel AllToAll); with the flag off, the default keeps |
| 1553 | + 'expert' on the batch dim. Mirrors dense_matmul's axis selection. |
| 1554 | + """ |
| 1555 | + cfg = pyconfig.initialize( |
| 1556 | + [None, get_test_config_path()], |
| 1557 | + run_name=f"moe_shard_{model_name}_{flag}", |
| 1558 | + enable_checkpointing=False, |
| 1559 | + model_name=model_name, |
| 1560 | + moe_dispatch_no_expert_sharding=flag, |
| 1561 | + ) |
| 1562 | + rules = cfg.logical_axis_rules |
| 1563 | + |
| 1564 | + def _as_set(entry): |
| 1565 | + if entry is None: |
| 1566 | + return set() |
| 1567 | + return {entry} if isinstance(entry, str) else set(entry) |
| 1568 | + |
| 1569 | + # Mirror _maybe_shard_moe_dispatch: resolve E and batch dims independently (so the shared |
| 1570 | + # 'expert' axis isn't deduped off E), then peel 'expert' from the batch dim when the flag is set. |
| 1571 | + e_spec = nn_partitioning.logical_to_mesh_axes(("activation_exp",), rules=rules) |
| 1572 | + b_spec = nn_partitioning.logical_to_mesh_axes(("activation_batch_moe",), rules=rules) |
| 1573 | + if cfg.moe_dispatch_no_expert_sharding: |
| 1574 | + b_spec = remove_expert_from_partition_spec(b_spec, dims_to_peel=(0,)) |
| 1575 | + |
| 1576 | + e_axes, b_axes = _as_set(e_spec[0]), _as_set(b_spec[0]) |
| 1577 | + assert "expert" in e_axes, "expert dim must be sharded by the 'expert' mesh axis" |
| 1578 | + if cfg.moe_dispatch_no_expert_sharding: |
| 1579 | + assert "expert" not in b_axes, "flag on: the batch dim must not take 'expert'" |
| 1580 | + else: |
| 1581 | + assert "expert" in b_axes, "flag off (default): the batch dim keeps 'expert' (activation_batch_moe)" |
| 1582 | + |
| 1583 | + |
| 1584 | +def test_remove_expert_from_partition_spec(): |
| 1585 | + """remove_expert_from_partition_spec peels 'expert' only from the requested dims.""" |
| 1586 | + spec = jax.sharding.PartitionSpec |
| 1587 | + assert remove_expert_from_partition_spec(spec("expert", ("data", "fsdp", "expert"), None), dims_to_peel=(1,)) == spec( |
| 1588 | + "expert", ("data", "fsdp"), None |
| 1589 | + ) |
| 1590 | + assert remove_expert_from_partition_spec(spec("expert", "expert", None), dims_to_peel=(1,)) == spec( |
| 1591 | + "expert", None, None |
| 1592 | + ) |
| 1593 | + assert remove_expert_from_partition_spec(spec(("expert",), None), dims_to_peel=(0, 1)) == spec(None, None) |
| 1594 | + |
| 1595 | + |
| 1596 | +def test_moe_dispatch_no_expert_sharding_dense_forward(): |
| 1597 | + """The moe_dispatch_no_expert_sharding peel path runs in dense_matmul (capacity_factor>0).""" |
| 1598 | + cfg = pyconfig.initialize( |
| 1599 | + [None, get_test_config_path()], |
| 1600 | + run_name="moe_dense_no_exp_fwd", |
| 1601 | + enable_checkpointing=False, |
| 1602 | + model_name="mixtral-8x7b", |
| 1603 | + dtype="bfloat16", |
| 1604 | + megablox=False, |
| 1605 | + sparse_matmul=False, |
| 1606 | + capacity_factor=1.0, |
| 1607 | + moe_dispatch_no_expert_sharding=True, |
| 1608 | + per_device_batch_size=1, |
| 1609 | + max_target_length=16, |
| 1610 | + ) |
| 1611 | + devices = maxtext_utils.create_device_mesh(cfg) |
| 1612 | + mesh = Mesh(devices, cfg.mesh_axes) |
| 1613 | + model = make_moe(cfg, mesh) |
| 1614 | + inputs = jax.random.normal( |
| 1615 | + jax.random.PRNGKey(0), |
| 1616 | + (int(cfg.per_device_batch_size) * jax.device_count(), cfg.max_target_length, cfg.base_emb_dim), |
| 1617 | + dtype=jnp.bfloat16, |
| 1618 | + ) |
| 1619 | + with mesh: |
| 1620 | + out, _, _ = model(inputs) |
| 1621 | + assert out.shape == inputs.shape |
| 1622 | + |
| 1623 | + |
1537 | 1624 | if __name__ == "__main__": |
1538 | 1625 | unittest.main() |
0 commit comments