Skip to content

Commit 393dee4

Browse files
committed
Integrating TE Collective GEMM
1 parent a56dc04 commit 393dee4

6 files changed

Lines changed: 95 additions & 19 deletions

File tree

src/maxtext/configs/base.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,8 @@ weight_sparsity_m: null
159159
weight_sparsity_update_step: 10
160160
# The first number of steps before updating the sparsity masks.
161161
weight_sparsity_start_step: 50
162+
# Whether to use Transformer Engine's collective GEMM overlap algorithm with quantization in MLP layers. Note: only valid with tensor-sequence parallelism.
163+
use_te_comm_gemm_overlap: false
162164

163165
decoder_block: "llama2" # which style of decoderblock to use.
164166
# global parameter scale needs to be a power of 2. if you want finer grained control of the model sizes

src/maxtext/configs/types.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -470,6 +470,9 @@ class Quantization(BaseModel):
470470
50,
471471
description=("The first number of steps before updating the sparsity masks."),
472472
)
473+
use_te_comm_gemm_overlap: bool = Field(
474+
False, description="If True, uses Transformer Engine's collective GEMM overlap algorithm."
475+
)
473476

474477

475478
class ModelArchitecture(BaseModel):
@@ -2555,6 +2558,26 @@ def validate_ragged_buffer_factor(self):
25552558
" 2. Ragged sort with ring of experts (use_ring_of_experts=True AND use_ragged_sort=True)"
25562559
)
25572560

2561+
def _validate_use_te_comm_gemm_overlap(self):
2562+
"""Validates that use_te_comm_gemm_overlap is used with supported settings to enable TE Collective GEMM ops."""
2563+
te_has_distributed_env = jax.local_device_count() == 1 and jax.distributed.is_initialized()
2564+
2565+
if self.hardware != "gpu_multiprocess" or not te_has_distributed_env:
2566+
raise ValueError(
2567+
"TE Collective GEMM operations are only supported for hardware=gpu_multiprocess with rank per GPU."
2568+
)
2569+
2570+
tsp_size = self.ici_tensor_sequence_parallelism * self.dcn_tensor_sequence_parallelism
2571+
tp_size = self.ici_tensor_parallelism * self.dcn_tensor_parallelism
2572+
2573+
if tsp_size == 1 or tp_size > 1:
2574+
raise ValueError("TE Collective GEMM operations are only supported for TSP size > 1 and TP size == 1.")
2575+
2576+
if not self.quantization.startswith("te_"):
2577+
raise ValueError(
2578+
"TE Collective GEMM operations are only supported for TE quantization recipes (i.e. starting with 'te_')."
2579+
)
2580+
25582581
@model_validator(mode="after")
25592582
def set_derived_and_validate_values(self) -> "MaxTextConfig":
25602583
"""
@@ -3453,6 +3476,9 @@ def calculate_global_batch_sizes(per_device_batch_size, expansion_factor, num_de
34533476

34543477
self._validate_check_vma_is_supported()
34553478

3479+
if self.use_te_comm_gemm_overlap:
3480+
self._validate_use_te_comm_gemm_overlap()
3481+
34563482
# Final string-to-enum conversions if they haven't been coerced by pydantic yet.
34573483
if isinstance(self.decoder_block, str):
34583484
self.decoder_block = DecoderBlockType(self.decoder_block.lower())

src/maxtext/layers/quantizations.py

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -951,6 +951,8 @@ def __init__(self, config):
951951

952952
self._recipe = TransformerEngineQuantization._get_recipe(config.quantization)
953953

954+
self._perform_collective_gemm = config.use_te_comm_gemm_overlap
955+
954956
def __hash__(self):
955957
return hash((self.quant_mode, self._recipe))
956958

@@ -1001,11 +1003,13 @@ def _wrap(self, f, name=None):
10011003
2. Wraps the given function in a Flax linen module. This module does not store any Flax
10021004
parameters but can store Flax variables for quantizers if required by the recipe.
10031005
1004-
3. When the wrapper is called, it provides an additional argument to the given function `f`,
1005-
'generate_quantizer_set' as the first argument. 'generate_quantizer_set' is a function that
1006-
can be called to generate a TransformerEngine/JAX quantizer set object used in
1007-
TransformerEngine/JAX APIs. 'generate_quantizer_set' will generate quantizers based on the
1008-
recipe of this TransformerEngineQuantizer object.
1006+
3. When the wrapper is called, it provides two additional arguments to the given function `f`,
1007+
'generate_quantizer_set' as the first argument and 'generate_collective_op_set' as the second argument.
1008+
'generate_quantizer_set' is a function that can be called to generate a TransformerEngine/JAX quantizer
1009+
set object used in TransformerEngine/JAX APIs based on the recipe of this TransformerEngineQuantizer
1010+
object. Similarly, 'generate_collective_op_set' is a function that can be called to generate a
1011+
TransformerEngine/JAX collective operation set object used in TransformerEngine/JAX APIs based
1012+
the kernel's mesh axes.
10091013
10101014
Args:
10111015
f: The function to wrap. The first argument must be 'generate_quantizer_set'.
@@ -1016,6 +1020,7 @@ def _wrap(self, f, name=None):
10161020
"""
10171021

10181022
import transformer_engine.jax # pylint: disable=import-outside-toplevel # pytype: disable=import-error
1023+
import transformer_engine.jax.cpp_extensions as tex # pylint: disable=import-outside-toplevel # pytype: disable=import-error
10191024
from transformer_engine.common import recipe # pylint: disable=import-outside-toplevel # pytype: disable=import-error
10201025

10211026
default_recipe = self._recipe
@@ -1041,9 +1046,20 @@ def generate_quantizer_set(
10411046
n_groups=n_groups,
10421047
)
10431048

1049+
def generate_collective_op_set(self, mesh_axes: Tuple[str, ...] = ()):
1050+
"""Inspect the kernel's mesh axes to determine the type of collective operation to use for collective GEMM."""
1051+
1052+
if len(mesh_axes) >= 1:
1053+
if mesh_axes[0] == "embed" and mesh_axes[-1] == "mlp":
1054+
return tex.CollectiveOpSet.create(tex.CollectiveOp.ALL_GATHER)
1055+
elif mesh_axes[0] == "mlp" and mesh_axes[-1] == "embed":
1056+
return tex.CollectiveOpSet.create(tex.CollectiveOp.REDUCE_SCATTER)
1057+
1058+
return tex.noop_collective_op_set
1059+
10441060
@nn.compact
10451061
def __call__(self, *args, **kwargs):
1046-
return f(self.generate_quantizer_set, *args, **kwargs)
1062+
return f(self.generate_quantizer_set, self.generate_collective_op_set, *args, **kwargs)
10471063

10481064
TEWrapper.__name__ = f"TEWrapper_{name if name else f.__name__}"
10491065

@@ -1052,17 +1068,22 @@ def __call__(self, *args, **kwargs):
10521068
def dot_general_cls(self, mesh_axes: Tuple[str, ...] = ()):
10531069
"""Placeholder for dot_general implementation in subclasses."""
10541070
import transformer_engine.jax # pylint: disable=import-outside-toplevel # pytype: disable=import-error
1071+
import transformer_engine.jax.cpp_extensions as tex # pylint: disable=import-outside-toplevel # pytype: disable=import-error
10551072

1056-
def te_dot_general(generate_quantizer_set, x, kernel, dims, **kwargs):
1073+
def te_dot_general(generate_quantizer_set, generate_collective_op_set, x, kernel, dims, **kwargs):
10571074
contracting_dims, batch_dims = dims
10581075
assert batch_dims == ((), ()), "Batch dimensions must be empty for TransformerEngine dot."
10591076

10601077
quantizer_set = generate_quantizer_set()
1078+
collective_op_set = (
1079+
generate_collective_op_set(mesh_axes) if self._perform_collective_gemm else tex.noop_collective_op_set
1080+
)
10611081
return transformer_engine.jax.dense.dense(
10621082
x,
10631083
kernel,
10641084
contracting_dims=contracting_dims,
10651085
quantizer_set=quantizer_set,
1086+
collective_op_set=collective_op_set,
10661087
)
10671088

10681089
return self._wrap(te_dot_general, "dot_general")

src/maxtext/trainers/pre_train/train.py

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -78,9 +78,7 @@
7878
def get_first_step(model, state):
7979
if isinstance(model, nn.Module):
8080
return int(state.step)
81-
if hasattr(
82-
state, "inner_state"
83-
): # DiLoCoTrainState (NNX DiLoCo): step is the optimizer step var
81+
if hasattr(state, "inner_state"): # DiLoCoTrainState (NNX DiLoCo): step is the optimizer step var
8482
return int(state.step.get_value())
8583
return int(state.optimizer.step.get_value())
8684

@@ -784,11 +782,7 @@ def train_loop(config, recorder, state=None):
784782
# the Zero-1 opt overlay doesn't apply through the diloco wrapper.
785783
params_shardings = state_mesh_shardings.params
786784
else:
787-
params_shardings, state_mesh_shardings = (
788-
sharding.maybe_update_params_sharding_with_opt(
789-
config, state_mesh_shardings
790-
)
791-
)
785+
params_shardings, state_mesh_shardings = sharding.maybe_update_params_sharding_with_opt(config, state_mesh_shardings)
792786

793787
p_train_step, p_eval_step = train_utils.jit_train_and_eval_step(
794788
config,
@@ -828,9 +822,7 @@ def train_loop(config, recorder, state=None):
828822
if isinstance(model, nn.Module):
829823
setup_params = state.params
830824
elif config.enable_diloco:
831-
setup_params = (
832-
state.params
833-
) # DiLoCoTrainState.params: the outer (global) params
825+
setup_params = state.params # DiLoCoTrainState.params: the outer (global) params
834826
else:
835827
_, setup_params, _ = nnx.split(state.model, nnx.Param, ...)
836828
metric_logger_instance.write_setup_info_to_tensorboard(setup_params)
@@ -935,6 +927,9 @@ def initialize(argv: Sequence[str]) -> tuple[pyconfig.HyperParameters, Any]:
935927
if config.use_vertex_tensorboard or os.environ.get("UPLOAD_DATA_TO_TENSORBOARD"):
936928
vertex_tensorboard_manager.configure_vertex_tensorboard(config)
937929

930+
if config.use_te_comm_gemm_overlap:
931+
max_utils.bootstrap_transformer_engine_cgemm(config)
932+
938933
# Create the Goodput recorder
939934
recorder = create_goodput_recorder(config)
940935

src/maxtext/utils/max_utils.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1185,6 +1185,22 @@ def print_non_trivial_mesh_axis(mesh):
11851185
print(f"{mesh_axis}: {axis_size}", flush=True)
11861186

11871187

1188+
def bootstrap_transformer_engine_cgemm(config):
1189+
"""Potentially initialize NCCL communicators for Collective GEMM operations if
1190+
the environment is distributed and has the appropriate config."""
1191+
import transformer_engine.jax.cpp_extensions as tex # pylint: disable=import-outside-toplevel # pytype: disable=import-error
1192+
1193+
tsp_size = config.ici_tensor_sequence_parallelism * config.dcn_tensor_sequence_parallelism
1194+
1195+
# Setup NCCL buffers for GPU Collective GEMM operations
1196+
tex.collective_gemm_bootstrap(
1197+
jax.device_count(),
1198+
jax.local_device_count(),
1199+
jax.process_index(),
1200+
tsp_size,
1201+
)
1202+
1203+
11881204
@contextmanager
11891205
def maybe_get_transformer_engine_context(config):
11901206
"""Runs a transformer engine context engine manager for GPUs only."""
@@ -1212,7 +1228,7 @@ def transformer_engine_context():
12121228
mesh_resource = MeshResource( # pytype: disable=wrong-arg-types
12131229
dp_resource="data",
12141230
tp_resource="tensor",
1215-
# tpsp_resource = "tensor_sequence", #TODO(Phuong): add this back when upstreaming CGEMM
1231+
tpsp_resource="tensor_sequence",
12161232
fsdp_resource="fsdp",
12171233
pp_resource=None,
12181234
cp_resource="context",

tests/integration/train_tests.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,22 @@ def test_gpu_nanoo_fp8(self):
293293
def test_gpu_te_fp8_delayedscaling(self):
294294
train_main(TrainTests.CONFIGS["te_fp8_delayedscaling"] + ["attention=dot_product"])
295295

296+
@pytest.mark.skip(reason="No runner with GPU arch >= 89 is available")
297+
@pytest.mark.integration_test
298+
@pytest.mark.gpu_only
299+
def test_gpu_te_fp8_delayedscaling_tsp_cgemm(self):
300+
if jax.process_count() <= 1:
301+
pytest.skip("Requires rank-per-GPU launch (JAX_PROCESS_COUNT > 1)")
302+
if jax.local_device_count() != 1:
303+
pytest.skip(f"Requires rank-per-GPU launch (local_device_count==1), " f"got {jax.local_device_count()}")
304+
if not jax.distributed.is_initialized():
305+
pytest.skip("Requires jax.distributed.initialize() (hardware=gpu_multiprocess)")
306+
307+
train_main(
308+
TrainTests.CONFIGS["te_fp8_delayedscaling"]
309+
+ ["attention=dot_product", "ici_tensor_sequence_parallelism=2", "use_te_comm_gemm_overlap=true"]
310+
)
311+
296312
@pytest.mark.skip(reason="No runner with GPU arch >= 89 is available")
297313
@pytest.mark.integration_test
298314
@pytest.mark.gpu_only

0 commit comments

Comments
 (0)