|
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 |
|
@@ -1556,5 +1557,91 @@ def test_prefused_vs_sparse_softmax(self): |
1556 | 1557 | self.assertIsNone(bias_updates) |
1557 | 1558 |
|
1558 | 1559 |
|
| 1560 | +@pytest.mark.parametrize( |
| 1561 | + "model_name,flag", |
| 1562 | + [ |
| 1563 | + ("mixtral-8x7b", True), # flag on: expert stays on E, peeled off the batch dim |
| 1564 | + ("mixtral-8x22b", True), # flag on |
| 1565 | + ("mixtral-8x7b", False), # flag off (default): batch dim keeps 'expert' |
| 1566 | + ], |
| 1567 | +) |
| 1568 | +def test_moe_dispatch_keeps_expert_on_expert_dim(model_name, flag): |
| 1569 | + """Regression guard for the MoE dispatch/MLP expert-parallel sharding. |
| 1570 | +
|
| 1571 | + The expert (E) dim is always sharded by the 'expert' mesh axis (via activation_exp). |
| 1572 | + With moe_dispatch_no_expert_sharding the batch (B) dim must NOT also take 'expert' |
| 1573 | + (which would double-map two tensor dims onto one mesh axis and force an FSDP-style |
| 1574 | + fallback instead of expert-parallel AllToAll); with the flag off, the default keeps |
| 1575 | + 'expert' on the batch dim. Mirrors dense_matmul's axis selection. |
| 1576 | + """ |
| 1577 | + cfg = pyconfig.initialize( |
| 1578 | + [None, get_test_config_path()], |
| 1579 | + run_name=f"moe_shard_{model_name}_{flag}", |
| 1580 | + enable_checkpointing=False, |
| 1581 | + model_name=model_name, |
| 1582 | + moe_dispatch_no_expert_sharding=flag, |
| 1583 | + ) |
| 1584 | + rules = cfg.logical_axis_rules |
| 1585 | + |
| 1586 | + def _as_set(entry): |
| 1587 | + if entry is None: |
| 1588 | + return set() |
| 1589 | + return {entry} if isinstance(entry, str) else set(entry) |
| 1590 | + |
| 1591 | + # Mirror _maybe_shard_moe_dispatch: resolve E and batch dims independently (so the shared |
| 1592 | + # 'expert' axis isn't deduped off E), then peel 'expert' from the batch dim when the flag is set. |
| 1593 | + e_spec = nn_partitioning.logical_to_mesh_axes(("activation_exp",), rules=rules) |
| 1594 | + b_spec = nn_partitioning.logical_to_mesh_axes(("activation_batch_moe",), rules=rules) |
| 1595 | + if cfg.moe_dispatch_no_expert_sharding: |
| 1596 | + b_spec = remove_expert_from_partition_spec(b_spec, dims_to_peel=(0,)) |
| 1597 | + |
| 1598 | + e_axes, b_axes = _as_set(e_spec[0]), _as_set(b_spec[0]) |
| 1599 | + assert "expert" in e_axes, "expert dim must be sharded by the 'expert' mesh axis" |
| 1600 | + if cfg.moe_dispatch_no_expert_sharding: |
| 1601 | + assert "expert" not in b_axes, "flag on: the batch dim must not take 'expert'" |
| 1602 | + else: |
| 1603 | + assert "expert" in b_axes, "flag off (default): the batch dim keeps 'expert' (activation_batch_moe)" |
| 1604 | + |
| 1605 | + |
| 1606 | +def test_remove_expert_from_partition_spec(): |
| 1607 | + """remove_expert_from_partition_spec peels 'expert' only from the requested dims.""" |
| 1608 | + spec = jax.sharding.PartitionSpec |
| 1609 | + assert remove_expert_from_partition_spec(spec("expert", ("data", "fsdp", "expert"), None), dims_to_peel=(1,)) == spec( |
| 1610 | + "expert", ("data", "fsdp"), None |
| 1611 | + ) |
| 1612 | + assert remove_expert_from_partition_spec(spec("expert", "expert", None), dims_to_peel=(1,)) == spec( |
| 1613 | + "expert", None, None |
| 1614 | + ) |
| 1615 | + assert remove_expert_from_partition_spec(spec(("expert",), None), dims_to_peel=(0, 1)) == spec(None, None) |
| 1616 | + |
| 1617 | + |
| 1618 | +def test_moe_dispatch_no_expert_sharding_dense_forward(): |
| 1619 | + """The moe_dispatch_no_expert_sharding peel path runs in dense_matmul (capacity_factor>0).""" |
| 1620 | + cfg = pyconfig.initialize( |
| 1621 | + [None, get_test_config_path()], |
| 1622 | + run_name="moe_dense_no_exp_fwd", |
| 1623 | + enable_checkpointing=False, |
| 1624 | + model_name="mixtral-8x7b", |
| 1625 | + dtype="bfloat16", |
| 1626 | + megablox=False, |
| 1627 | + sparse_matmul=False, |
| 1628 | + capacity_factor=1.0, |
| 1629 | + moe_dispatch_no_expert_sharding=True, |
| 1630 | + per_device_batch_size=1, |
| 1631 | + max_target_length=16, |
| 1632 | + ) |
| 1633 | + devices = maxtext_utils.create_device_mesh(cfg) |
| 1634 | + mesh = Mesh(devices, cfg.mesh_axes) |
| 1635 | + model = make_moe(cfg, mesh) |
| 1636 | + inputs = jax.random.normal( |
| 1637 | + jax.random.PRNGKey(0), |
| 1638 | + (int(cfg.per_device_batch_size) * jax.device_count(), cfg.max_target_length, cfg.base_emb_dim), |
| 1639 | + dtype=jnp.bfloat16, |
| 1640 | + ) |
| 1641 | + with mesh: |
| 1642 | + out, _, _ = model(inputs) |
| 1643 | + assert out.shape == inputs.shape |
| 1644 | + |
| 1645 | + |
1559 | 1646 | if __name__ == "__main__": |
1560 | 1647 | unittest.main() |
0 commit comments