Skip to content

Commit c01ff78

Browse files
Support load balancing for TPU USP attention
1 parent 77a77ac commit c01ff78

4 files changed

Lines changed: 59 additions & 18 deletions

File tree

docs/guides/optimization/sharding.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,7 @@ MaxText supports `context_parallel_strategy=all_gather`, and supports `context_p
262262

263263
MaxText also supports `context_parallel_strategy=ulysses` ([DeepSpeed Ulysses](https://arxiv.org/abs/2309.14509)) on the TPU Tokamax Splash path for training. Ulysses exchanges sequence ownership for head ownership by communicating the Q, K, V, and output activations through all-to-all collectives: each device computes ordinary full-sequence attention for its head subset, and the inverse all-to-all restores the sequence sharding on the output. It requires explicit positive context parallelism values, `context_sharding=context`, `attention=flash` with Tokamax Splash, global causal attention, query and KV head counts divisible by the context parallel size including after tensor-parallel head sharding, matching Q and KV head-sharding axes, an unsharded head feature dimension, a divisible sequence length, `dq_reduction_steps` of 0 or 3, `context_parallel_load_balance=false` (each device computes full-sequence attention for its head subset, so the work is already balanced and the causal load-balancing reorder must stay off), and ICI-only context parallelism (`dcn_context_parallelism` must equal 1). It does not support MQA, dropout, QK-Clip statistics, ragged attention, attention sinks, sparse indexer masks, chunked prefill, MoBA, or multimodal attention.
264264

265-
MaxText also supports `context_parallel_strategy=usp` ([USP](https://arxiv.org/abs/2405.07719), Ulysses over ring) on the same TPU Tokamax Splash path for training. This initial support is non-load-balanced. To use it, set `context_parallel_strategy=usp`, `ici_context_parallelism` for the ring size, and `ici_context_usp_ulysses_parallelism` for the Ulysses size (total context parallelism is their product). USP factors the context parallelism into a ring dimension on the `context` mesh axis and a Ulysses dimension on the `context_usp_ulysses` mesh axis: the Ulysses all-to-all exchanges sequence ownership for head ownership over the Ulysses axis at each fixed ring position, and the ring kernel then rotates K and V across the ring axis inside each head subset. The strategy is hybrid-only: both dimensions must be greater than one, and the single-dimension endpoints are the existing `ring` and `ulysses` strategies. It shares the Ulysses restrictions (explicit positive ICI-only sizes, `attention=flash` with Tokamax Splash, global causal attention, head counts divisible by the Ulysses size, no MQA, no load balancing, no dropout, no multi-token prediction, no dKV megacore) and additionally requires `max_target_length` divisible by the total context parallelism and by the ring size squared.
265+
MaxText also supports `context_parallel_strategy=usp` ([USP](https://arxiv.org/abs/2405.07719), Ulysses over ring) on the same TPU Tokamax Splash path for training. To use it, set `context_parallel_strategy=usp`, `ici_context_parallelism` for the ring size, and `ici_context_usp_ulysses_parallelism` for the Ulysses size (total context parallelism is their product). USP factors the context parallelism into a ring dimension on the `context` mesh axis and a Ulysses dimension on the `context_usp_ulysses` mesh axis: the Ulysses all-to-all exchanges sequence ownership for head ownership over the Ulysses axis at each fixed ring position, and the ring kernel then rotates K and V across the ring axis inside each head subset. The strategy is hybrid-only: both dimensions must be greater than one, and the single-dimension endpoints are the existing `ring` and `ulysses` strategies. It shares the Ulysses restrictions (explicit positive ICI-only sizes, `attention=flash` with Tokamax Splash, global causal attention, head counts divisible by the Ulysses size, no MQA, no dropout, no multi-token prediction, no dKV megacore) and additionally requires `max_target_length` divisible by the total context parallelism and by the ring size squared; load balancing requires an even ring size.
266266

267267
### CP Arithmetic Intensity
268268

src/maxtext/configs/types.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2968,8 +2968,10 @@ def _validate_usp_context_parallelism(self):
29682968
raise ValueError("TPU USP attention does not support sparse indexer masks.")
29692969
if self.attention_type != "global":
29702970
raise ValueError("TPU USP attention is initially supported only for global causal attention.")
2971-
if self.context_parallel_load_balance:
2972-
raise ValueError("TPU USP attention does not support context_parallel_load_balance=True.")
2971+
if self.context_parallel_load_balance and usp_ring_size % 2 != 0:
2972+
raise ValueError(
2973+
"TPU USP attention with context_parallel_load_balance=True requires an even ici_context_parallelism."
2974+
)
29732975
if self.use_ragged_attention:
29742976
raise ValueError("TPU USP attention does not support ragged attention.")
29752977
if self.attention_sink:

tests/unit/attention_test.py

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1943,13 +1943,13 @@ def attention_loss(x, pos, seg):
19431943
)
19441944
self.assertLen(hlo_test_utils.collective_lines(hlo_text, "collective-permute"), 0)
19451945

1946-
def _usp_test_config(self, packing=False):
1946+
def _usp_test_config(self, packing=False, context_parallel_load_balance=False):
19471947
return pyconfig.initialize(
19481948
[sys.argv[0], get_test_config_path()],
19491949
**self.config_arguments,
19501950
attention="flash",
19511951
context_parallel_strategy="usp",
1952-
context_parallel_load_balance=False,
1952+
context_parallel_load_balance=context_parallel_load_balance,
19531953
ici_context_parallelism=2,
19541954
ici_context_usp_ulysses_parallelism=2,
19551955
use_tokamax_splash=True,
@@ -1959,14 +1959,16 @@ def _usp_test_config(self, packing=False):
19591959
)
19601960

19611961
@parameterized.named_parameters(
1962-
{"testcase_name": "usp_2x2", "packing": False},
1963-
{"testcase_name": "usp_2x2_packed", "packing": True},
1962+
{"testcase_name": "usp_2x2", "context_parallel_load_balance": False, "packing": False},
1963+
{"testcase_name": "usp_2x2_load_balance", "context_parallel_load_balance": True, "packing": False},
1964+
{"testcase_name": "usp_2x2_packed", "context_parallel_load_balance": False, "packing": True},
1965+
{"testcase_name": "usp_2x2_packed_load_balance", "context_parallel_load_balance": True, "packing": True},
19641966
)
19651967
@pytest.mark.tpu_only
1966-
def test_tpu_flash_attention_usp_context_parallel(self, packing):
1968+
def test_tpu_flash_attention_usp_context_parallel(self, context_parallel_load_balance, packing):
19671969
"""Test equivalence between dot_product and flash attention + USP context parallelism"""
19681970

1969-
cfg_cp = self._usp_test_config(packing=packing)
1971+
cfg_cp = self._usp_test_config(packing=packing, context_parallel_load_balance=context_parallel_load_balance)
19701972
devices_array_cp = maxtext_utils.create_device_mesh(cfg_cp)
19711973
mesh_cp = Mesh(devices_array_cp, cfg_cp.mesh_axes)
19721974
if packing:
@@ -1999,18 +2001,20 @@ def test_tpu_flash_attention_usp_context_parallel(self, packing):
19992001
self.assertTrue(
20002002
jax.numpy.allclose(mha_generic_output, mha_generic_flash_cp_output, rtol=1e-02, atol=1e-02, equal_nan=False),
20012003
msg="Logits from generic dot product and flash attention + USP context parallelism are not close. "
2002-
f"packing={packing}.",
2004+
f"context_parallel_load_balance={context_parallel_load_balance}, packing={packing}.",
20032005
)
20042006

20052007
@parameterized.named_parameters(
2006-
{"testcase_name": "usp_2x2", "packing": False},
2007-
{"testcase_name": "usp_2x2_packed", "packing": True},
2008+
{"testcase_name": "usp_2x2", "context_parallel_load_balance": False, "packing": False},
2009+
{"testcase_name": "usp_2x2_load_balance", "context_parallel_load_balance": True, "packing": False},
2010+
{"testcase_name": "usp_2x2_packed", "context_parallel_load_balance": False, "packing": True},
2011+
{"testcase_name": "usp_2x2_packed_load_balance", "context_parallel_load_balance": True, "packing": True},
20082012
)
20092013
@pytest.mark.tpu_only
2010-
def test_tpu_flash_attention_usp_context_parallel_grad(self, packing):
2014+
def test_tpu_flash_attention_usp_context_parallel_grad(self, context_parallel_load_balance, packing):
20112015
"""Test input-gradient equivalence between dot_product and flash attention + USP context parallelism"""
20122016

2013-
cfg_cp = self._usp_test_config(packing=packing)
2017+
cfg_cp = self._usp_test_config(packing=packing, context_parallel_load_balance=context_parallel_load_balance)
20142018
devices_array_cp = maxtext_utils.create_device_mesh(cfg_cp)
20152019
mesh_cp = Mesh(devices_array_cp, cfg_cp.mesh_axes)
20162020
if packing:
@@ -2032,11 +2036,19 @@ def generic_loss(lnx):
20322036
return jnp.mean(output.astype(jnp.float32) ** 2)
20332037

20342038
def usp_loss(lnx):
2039+
if context_parallel_load_balance:
2040+
context_parallel_size = cfg_cp.ici_context_parallelism
2041+
lnx = max_utils.reorder_sequence(lnx, cp_size=context_parallel_size)
2042+
usp_decoder_segment_ids = max_utils.reorder_sequence(decoder_segment_ids, cp_size=context_parallel_size)
2043+
usp_decoder_positions = max_utils.reorder_sequence(decoder_positions, cp_size=context_parallel_size)
2044+
else:
2045+
usp_decoder_segment_ids = decoder_segment_ids
2046+
usp_decoder_positions = decoder_positions
20352047
output, _ = attention_as_mha_flash_cp(
20362048
lnx,
20372049
lnx,
2038-
decoder_segment_ids=decoder_segment_ids,
2039-
inputs_positions=decoder_positions,
2050+
decoder_segment_ids=usp_decoder_segment_ids,
2051+
inputs_positions=usp_decoder_positions,
20402052
deterministic=True,
20412053
model_mode=MODEL_MODE_TRAIN,
20422054
)
@@ -2051,7 +2063,7 @@ def usp_loss(lnx):
20512063
self.assertTrue(
20522064
jax.numpy.allclose(generic_grad, usp_grad, rtol=1e-02, atol=1e-07, equal_nan=False),
20532065
msg="Input gradients from generic dot product and flash attention + USP context parallelism are not close. "
2054-
f"packing={packing}.",
2066+
f"context_parallel_load_balance={context_parallel_load_balance}, packing={packing}.",
20552067
)
20562068

20572069
@pytest.mark.tpu_only

tests/unit/configs_value_test.py

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -488,6 +488,29 @@ def test_tpu_usp_config_validation_accepts_packing(self):
488488

489489
self.assertTrue(config.packing)
490490

491+
def test_tpu_usp_config_validation_accepts_load_balance(self):
492+
argv = [
493+
"",
494+
_BASE_CONFIG_PATH,
495+
"run_name=test",
496+
"attention=flash",
497+
"use_tokamax_splash=True",
498+
"use_jax_splash=False",
499+
"context_parallel_strategy=usp",
500+
"context_parallel_load_balance=True",
501+
"ici_context_parallelism=2",
502+
"ici_context_usp_ulysses_parallelism=2",
503+
"hardware=tpu",
504+
"packing=False",
505+
"dataset_type=synthetic",
506+
"skip_jax_distributed_system=True",
507+
]
508+
mock_devices = [unittest.mock.MagicMock(slice_index=0) for _ in range(8)]
509+
with unittest.mock.patch("jax.devices", return_value=mock_devices):
510+
config = pyconfig.initialize(argv)
511+
512+
self.assertTrue(config.context_parallel_load_balance)
513+
491514
def test_context_usp_ulysses_parallelism_requires_usp(self):
492515
argv = [
493516
"",
@@ -522,7 +545,11 @@ def test_tpu_usp_config_validation_rejects_unsupported_configs(self):
522545
"skip_jax_distributed_system=True",
523546
]
524547
cases = [
525-
(["context_parallel_load_balance=True"], ["context_parallel_load_balance=False"], "load_balance"),
548+
(
549+
["context_parallel_load_balance=True", "ici_context_parallelism=3"],
550+
["context_parallel_load_balance=False", "ici_context_parallelism=2"],
551+
"even ici_context_parallelism",
552+
),
526553
(["attention=dot_product"], ["attention=flash"], "attention=flash"),
527554
(["use_tokamax_splash=False"], ["use_tokamax_splash=True"], "use_tokamax_splash"),
528555
(["use_jax_splash=True"], ["use_jax_splash=False"], "use_jax_splash"),

0 commit comments

Comments
 (0)