-
Notifications
You must be signed in to change notification settings - Fork 582
Add TPU Ulysses context parallelism #4687
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1082,7 +1082,7 @@ class HardwareAndMesh(BaseModel): | |
| context_parallel_load_balance: bool = Field(True, description="Whether to use load balancing for context parallelism.") | ||
| context_parallel_strategy: str = Field( | ||
| "all_gather", | ||
| description="Strategy for context parallelism ('all_gather' or 'ring').", | ||
| description="Strategy for context parallelism ('all_gather', 'ring', or 'ulysses').", | ||
| ) | ||
| context_parallel_reorder_strategy: ReorderStrategy = Field( | ||
| ReorderStrategy.AUTO, | ||
|
|
@@ -3573,6 +3573,9 @@ def calculate_global_batch_sizes(per_device_batch_size, expansion_factor, num_de | |
| self, f"dcn_{self.context_sharding}_parallelism", 1 | ||
| ) | ||
| context_parallel_strategy = self.context_parallel_strategy.lower() | ||
| if context_parallel_strategy not in ("all_gather", "ring", "ulysses"): | ||
| raise ValueError("context_parallel_strategy must be one of 'all_gather', 'ring', or 'ulysses'.") | ||
| self.context_parallel_strategy = context_parallel_strategy | ||
| if ( | ||
| context_parallel_strategy == "ring" | ||
| and "gpu" not in self.hardware | ||
|
|
@@ -3628,6 +3631,67 @@ def calculate_global_batch_sizes(per_device_batch_size, expansion_factor, num_de | |
| f"ring_scan_unroll={self.ring_scan_unroll} was specified, but is only supported when " | ||
| "context_parallel_strategy='ring'." | ||
| ) | ||
| if context_parallel_strategy == "ulysses": | ||
| if self.hardware != "tpu": | ||
| raise ValueError("Ulysses context parallelism (context_parallel_strategy='ulysses') is only supported on TPU.") | ||
| if self.context_sharding != "context": | ||
| raise ValueError("TPU Ulysses attention requires context_sharding='context'.") | ||
| ici_context_parallel_size = self.ici_context_parallelism | ||
| dcn_context_parallel_size = self.dcn_context_parallelism | ||
| if ici_context_parallel_size <= 0 or dcn_context_parallel_size <= 0: | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. MaxText won't support |
||
| raise ValueError( | ||
| "TPU Ulysses attention requires explicit positive ici/dcn context parallelism values; " | ||
| "inferred (-1) sizes are not supported." | ||
| ) | ||
| if context_parallel_size <= 1: | ||
| raise ValueError("TPU Ulysses attention requires context_parallel_size > 1.") | ||
| if dcn_context_parallel_size != 1: | ||
| raise ValueError("TPU Ulysses attention does not support dcn context parallelism yet.") | ||
| if self.attention != "flash": | ||
| raise ValueError("TPU Ulysses attention requires attention=flash.") | ||
| if not self.use_tokamax_splash: | ||
| raise ValueError("TPU Ulysses attention requires use_tokamax_splash=True.") | ||
| if self.use_jax_splash: | ||
| raise ValueError("TPU Ulysses attention requires use_jax_splash=False.") | ||
| if self.attention_type != "global": | ||
| raise ValueError("TPU Ulysses attention is initially supported only for global causal attention.") | ||
| if self.packing: | ||
| raise ValueError("TPU Ulysses attention does not support packing yet.") | ||
| if self.context_parallel_load_balance: | ||
| raise ValueError("TPU Ulysses attention does not support context_parallel_load_balance=True.") | ||
| if self.use_ragged_attention: | ||
| raise ValueError("TPU Ulysses attention does not support ragged attention.") | ||
| if self.attention_sink: | ||
| raise ValueError("TPU Ulysses attention does not support attention sinks.") | ||
| if self.use_indexer: | ||
| raise ValueError("TPU Ulysses attention does not support sparse indexer masks.") | ||
| if self.use_chunked_prefill: | ||
| raise ValueError("TPU Ulysses attention does not support chunked prefill yet.") | ||
| if self.use_multimodal: | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Wondering if those features are not tested or needs feature to supported? |
||
| raise ValueError("TPU Ulysses attention does not support multimodal attention.") | ||
| if self.enable_dropout and self.dropout_rate > 0.0: | ||
| raise ValueError("TPU Ulysses attention does not support dropout yet.") | ||
| if self.dq_reduction_steps not in (0, 3): | ||
| raise ValueError("TPU Ulysses attention requires dq_reduction_steps to be 0 or 3.") | ||
| if self.use_qk_clip: | ||
| raise ValueError("TPU Ulysses attention does not support QK-Clip statistics yet.") | ||
| if self.max_target_length % context_parallel_size != 0: | ||
| raise ValueError( | ||
| "TPU Ulysses attention requires max_target_length " | ||
| f"({self.max_target_length}) to be divisible by context_parallel_size ({context_parallel_size})." | ||
| ) | ||
| if self.num_query_heads % context_parallel_size != 0: | ||
| raise ValueError( | ||
| "TPU Ulysses attention requires num_query_heads " | ||
| f"({self.num_query_heads}) to be divisible by context_parallel_size ({context_parallel_size})." | ||
| ) | ||
| if self.num_kv_heads == 1: | ||
| raise ValueError("TPU Ulysses attention does not support MQA with context_parallel_size > 1.") | ||
| if self.num_kv_heads % context_parallel_size != 0: | ||
| raise ValueError( | ||
| "TPU Ulysses attention requires num_kv_heads " | ||
| f"({self.num_kv_heads}) to be divisible by context_parallel_size ({context_parallel_size})." | ||
| ) | ||
| # STRIPED reorder strategy is a Transformer Engine feature and is GPU-only. | ||
| # AUTO is resolved in training because test code paths may load the same | ||
| # config but use a different reorder path. | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,50 @@ | ||
| # Copyright 2026 Google LLC | ||
| # | ||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| # you may not use this file except in compliance with the License. | ||
| # You may obtain a copy of the License at | ||
| # | ||
| # https://www.apache.org/licenses/LICENSE-2.0 | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| # See the License for the specific language governing permissions and | ||
| # limitations under the License. | ||
| """Shared helpers for attention context-parallel sharding metadata.""" | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| from typing import Any | ||
|
|
||
| import jax | ||
|
|
||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: I think all these three function are useful in general, we should consider moving them to
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, agree. It seems not context parallelism specific. |
||
|
|
||
| def mesh_axes_for_dim(axis_names: Any) -> tuple[Any, ...]: | ||
| """Returns the mesh axes attached to one tensor dimension.""" | ||
| if axis_names is None: | ||
| return () | ||
| if isinstance(axis_names, str): | ||
| return (axis_names,) | ||
| return tuple(axis for axis in axis_names if axis is not None) | ||
|
|
||
|
|
||
| def mesh_axes_size(mesh: Any, axes: tuple[Any, ...], *, label: str) -> int: | ||
| """Returns the product of mesh sizes for a set of axes.""" | ||
| size = 1 | ||
| for axis in axes: | ||
| if axis not in mesh.shape: | ||
| raise ValueError(f"{label} requires mesh axis {axis!r} to exist.") | ||
| size *= mesh.shape[axis] | ||
| return size | ||
|
|
||
|
|
||
| def with_axis_on_dim(axis_names: Any, axis: Any, dim: int) -> Any: | ||
| """Returns sharding axis names with one dimension replaced.""" | ||
| axes = list(axis_names) | ||
| axes[dim] = axis | ||
| if isinstance(axis_names, jax.sharding.PartitionSpec): | ||
| return jax.sharding.PartitionSpec(*axes, unreduced=axis_names.unreduced, reduced=axis_names.reduced) | ||
| if isinstance(axis_names, tuple): | ||
| return tuple(axes) | ||
| return axes | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you know why? If this feature hasn't been tested on GPUs yet, we should label it as 'experimental' and add a disclaimer that GPU execution is unverified and should be used with caution. Similar comments for bellow.