77import collections
88from typing import Any , Callable , List , Optional , Tuple , Union
99import torch
10+ from torch .distributed import DeviceMesh
1011
1112from transformer_engine .pytorch .quantization import FP8GlobalStateManager
1213from transformer_engine .pytorch .tensor .float8_tensor import Float8Tensor
@@ -191,6 +192,19 @@ class MultiheadAttention(torch.nn.Module):
191192 ``set_tensor_parallel_group(tp_group)`` method on the initialized module before the
192193 forward pass to supply the tensor parallel group needed for tensor and sequence
193194 parallel collectives.
195+ tp_mesh : Optional[DeviceMesh], default = None
196+ A 1-D DeviceMesh containing a TP mesh dimension, e.g. device_mesh["tp"].
197+ Only required when using TP with DTensor parameters, e.g. for FSDP2 or DCP.
198+ weight_mesh : Optional[DeviceMesh], default = None
199+ A 1-D DeviceMesh containing a weight-sharding mesh dimension. Only required
200+ when using the FP8 Current (per-tensor) Scaling recipe on sharded DTensor
201+ parameters and if the DTensor DeviceMesh includes dimensions that do not
202+ shard weights, such as in the case of HSDP (DP-Replicate x DP-Shard).
203+ For example:
204+ - device_mesh["dp"] for FSDP.
205+ - device_mesh["dp_cp"] if using CP ranks in FSDP.
206+ - device_mesh["tp"] if using TP.
207+ - device_mesh["dp_cp_tp"] if strided-sharding with FSDP-TP.
194208
195209 Optimization parameters
196210 -----------------------
@@ -286,6 +300,8 @@ def __init__(
286300 seq_length : Optional [int ] = None ,
287301 micro_batch_size : Optional [int ] = None ,
288302 softmax_type : str = "vanilla" ,
303+ tp_mesh : Optional [DeviceMesh ] = None ,
304+ weight_mesh : Optional [DeviceMesh ] = None ,
289305 ) -> None :
290306 super ().__init__ ()
291307
@@ -357,6 +373,13 @@ def __init__(
357373 self .q_norm , self .k_norm = self ._create_qk_norm_modules (
358374 qk_norm_type , qk_norm_eps , device , seq_length , micro_batch_size
359375 )
376+ if tp_mesh is not None or weight_mesh is not None :
377+ # Apply DeviceMesh and DTensor-related modifications.
378+ # Only necessary for trainable weighted norms.
379+ if hasattr (self .q_norm , "set_device_mesh" ):
380+ self .q_norm .set_device_mesh (tp_mesh = tp_mesh , weight_mesh = weight_mesh )
381+ if hasattr (self .k_norm , "set_device_mesh" ):
382+ self .k_norm .set_device_mesh (tp_mesh = tp_mesh , weight_mesh = weight_mesh )
360383
361384 qkv_parallel_mode = "column" if set_parallel_mode else None
362385
@@ -389,6 +412,8 @@ def __init__(
389412 normalization = normalization ,
390413 ub_name = "qkv" ,
391414 name = name + ".layernorm_linear_qkv" if name is not None else None ,
415+ tp_mesh = tp_mesh ,
416+ weight_mesh = weight_mesh ,
392417 ** common_gemm_kwargs ,
393418 )
394419 else :
@@ -401,6 +426,8 @@ def __init__(
401426 parallel_mode = qkv_parallel_mode ,
402427 parameters_split = parameters_split ,
403428 name = name + ".linear_qkv" if name is not None else None ,
429+ tp_mesh = tp_mesh ,
430+ weight_mesh = weight_mesh ,
404431 ** common_gemm_kwargs ,
405432 )
406433 elif self .attention_type == "cross" :
@@ -423,6 +450,8 @@ def __init__(
423450 normalization = normalization ,
424451 ub_name = "qkv" ,
425452 name = name + ".layernorm_linear_q" if name is not None else None ,
453+ tp_mesh = tp_mesh ,
454+ weight_mesh = weight_mesh ,
426455 ** common_gemm_kwargs ,
427456 )
428457 else :
@@ -433,6 +462,8 @@ def __init__(
433462 bias = bias ,
434463 return_bias = False ,
435464 parallel_mode = qkv_parallel_mode ,
465+ tp_mesh = tp_mesh ,
466+ weight_mesh = weight_mesh ,
436467 ** common_gemm_kwargs ,
437468 )
438469 self .key_value = Linear (
@@ -444,6 +475,8 @@ def __init__(
444475 parallel_mode = qkv_parallel_mode ,
445476 parameters_split = ("key" , "value" ) if not fuse_qkv_params else None ,
446477 name = name + ".linear_kv" if name is not None else None ,
478+ tp_mesh = tp_mesh ,
479+ weight_mesh = weight_mesh ,
447480 ** common_gemm_kwargs ,
448481 )
449482
@@ -461,6 +494,8 @@ def __init__(
461494 layer_number = self .layer_number ,
462495 attention_type = self .attention_type ,
463496 softmax_type = self .softmax_type ,
497+ tp_mesh = tp_mesh ,
498+ weight_mesh = weight_mesh ,
464499 )
465500
466501 # Linear
@@ -475,6 +510,8 @@ def __init__(
475510 ub_overlap_ag = ub_overlap_ag ,
476511 ub_name = "proj" ,
477512 name = name + ".proj" if name is not None else None ,
513+ tp_mesh = tp_mesh ,
514+ weight_mesh = weight_mesh ,
478515 ** common_gemm_kwargs ,
479516 )
480517
@@ -566,6 +603,60 @@ def set_tensor_parallel_group(self, tp_group: Union[dist_group_type, None]) -> N
566603 """
567604 self .tp_group = tp_group
568605
606+ def set_device_mesh (
607+ self ,
608+ tp_mesh : Optional [DeviceMesh ] = None ,
609+ weight_mesh : Optional [DeviceMesh ] = None ,
610+ ) -> None :
611+ """
612+ Set DeviceMesh(s) used for sharding weights and convert main weights into DTensor
613+ depending on the TransformerEngine class to support FSDP-TP sharding with FSDP2.
614+
615+ TransformerEngine manages tensor parallel mechanics, while DTensor offers seamless
616+ integration with Torch DCP checkpointing. This method should only be invoked when
617+ using DTensor parameters, e.g. when using FSDP2 or DCP.
618+
619+ When FSDP2 fully_shard() encounters any DTensor Shard(s), it will automatically
620+ convert them into FSDP-TP strided or non-strided shards depending on the current
621+ sharding dimension and factor of the DTensor. When the sharding dimension of FSDP
622+ matches that of TP, FSDP uses a _StridedShard placement type instead of Shard.
623+ This experimental FSDP-TP logic presides in this FSDP2 initialization function:
624+ ``torch.distributed.fsdp._fully_shard._fsdp_param._init_sharded_param``
625+
626+ Parameters
627+ ----------
628+ tp_mesh : Optional[DeviceMesh]
629+ A 1-D DeviceMesh containing a TP mesh dimension, e.g. device_mesh["tp"].
630+ Only required when using TP with DTensor parameters, e.g. for FSDP2 or DCP.
631+ weight_mesh : Optional[DeviceMesh]
632+ A 1-D DeviceMesh containing a weight-sharding mesh dimension. Only required
633+ when using the FP8 Current (per-tensor) Scaling recipe on sharded DTensor
634+ parameters and if the DTensor DeviceMesh includes dimensions that do not
635+ shard weights, such as in the case of HSDP (DP-Replicate x DP-Shard).
636+ For example:
637+ - device_mesh["dp"] for FSDP.
638+ - device_mesh["dp_cp"] if using CP ranks in FSDP.
639+ - device_mesh["tp"] if using TP.
640+ - device_mesh["dp_cp_tp"] if strided-sharding with FSDP-TP.
641+ """
642+ if tp_mesh is not None :
643+ # Validate TP DeviceMesh / Group. Must be consistent with tp_size.
644+ assert (
645+ tp_mesh .ndim == 1 and self .tp_size == tp_mesh .size (),
646+ f"TransformerEngine { self .__class__ .__name__ } TP init size ({ self .tp_size } ) "
647+ f"does not match the size of the provided TP DeviceMesh ({ tp_mesh .size ()} )."
648+ )
649+ # Set the tensor parallel group from the mesh.
650+ self .set_tensor_parallel_group (tp_mesh .get_group ())
651+
652+ if tp_mesh is not None or weight_mesh is not None :
653+ # Iterate through child sub-modules without deep recursion.
654+ # Automatically detects TransformerEngine TP modules and
655+ # the capability to call this method at any level.
656+ for name , child in self .named_children ():
657+ if hasattr (child , "set_device_mesh" ):
658+ child .set_device_mesh (tp_mesh , weight_mesh )
659+
569660 def set_context_parallel_group (
570661 self ,
571662 cp_group : Union [dist_group_type , List [dist_group_type ], None ],
0 commit comments