3232 consolidate_distributed_checkpoint ,
3333)
3434from .configuration_utils import DistributedConfig
35+ from .context_parallel import (
36+ ContextParallelAttention ,
37+ SplitSequenceParallel ,
38+ apply_context_parallelism ,
39+ context_parallel_styles ,
40+ )
3541from .expert_parallel import apply_expert_parallelism
3642from .fsdp import ALL_FSDP_STYLES , _resolve_tied_embed_lm_head_plan , apply_fully_sharded_data_parallelism
3743from .parallel_utils import ALL_PARALLEL_STYLES , _get_parameter_plan , replace_layer_number_by_wildcard
@@ -67,6 +73,7 @@ class DistributedMixin:
6773 _fsdp_size = None
6874 _pp_plan : dict [str , tuple [str , str ]] | None = None
6975 _fsdp_plan : dict [str , str ] | None = None
76+ _cp_plan : dict [str , str ] | None = None
7077
7178 @property
7279 def is_distributed_loading_by_transformers (self ) -> bool :
@@ -83,12 +90,14 @@ def init_parallel_plans(self) -> None:
8390 self ._ep_plan = dict (getattr (model_cls , "_ep_plan" , None ) or {})
8491 self ._pp_plan = dict (getattr (model_cls , "_pp_plan" , None ) or {})
8592 self ._fsdp_plan = dict (getattr (model_cls , "_fsdp_plan" , None ) or {})
93+ self ._cp_plan = dict (getattr (model_cls , "_cp_plan" , None ) or {})
8694
8795 if self .base_model is self :
8896 self ._pp_plan .update (self .config .base_model_pp_plan or {})
8997 self ._tp_plan .update (self .config .base_model_tp_plan or {})
9098 self ._ep_plan .update (self .config .base_model_ep_plan or {})
9199 self ._fsdp_plan .update (self .config .base_model_fsdp_plan or {})
100+ self ._cp_plan .update (self .config .base_model_cp_plan or {})
92101 # Shard the input embeddings on the vocabulary unless the plan already names them.
93102 if self ._tp_plan and isinstance (getattr (self , "embed_tokens" , None ), torch .nn .Embedding ):
94103 self ._tp_plan = {"embed_tokens" : "embedding_rowwise" , ** self ._tp_plan }
@@ -102,6 +111,8 @@ def init_parallel_plans(self) -> None:
102111 self ._pp_plan .update ({f"{ name } .{ k } " : v for k , v in plan .copy ().items ()})
103112 if plan := getattr (module , "_fsdp_plan" , None ):
104113 self ._fsdp_plan .update ({f"{ name } .{ k } " : v for k , v in plan .copy ().items ()})
114+ if plan := getattr (module , "_cp_plan" , None ):
115+ self ._cp_plan .update ({f"{ name } .{ k } " : v for k , v in plan .copy ().items ()})
105116
106117 @property
107118 def tp_plan (self ) -> dict [str , str ]:
@@ -117,6 +128,11 @@ def ep_plan(self) -> dict[str, str]:
117128 def fsdp_plan (self ) -> dict [str , str ]:
118129 return self ._fsdp_plan
119130
131+ @property
132+ def cp_plan (self ) -> dict [str , str ]:
133+ """The full context parallel plan for the model's modules."""
134+ return self ._cp_plan
135+
120136 @property
121137 def pp_plan (self ) -> dict [str , tuple [str , str ]]:
122138 return self ._pp_plan
@@ -154,6 +170,17 @@ def fsdp_plan(self, plan: dict[str, str] | None):
154170 self ._validate_parallel_plan (plan , ALL_FSDP_STYLES , "fsdp_plan" )
155171 self ._fsdp_plan = plan
156172
173+ @cp_plan .setter
174+ def cp_plan (self , plan : dict [str , str ] | None ):
175+ if plan is None :
176+ self ._cp_plan = {}
177+ return
178+ if not isinstance (plan , dict ):
179+ raise ValueError ("Can only set a dictionary as `cp_plan`" )
180+
181+ self ._validate_parallel_plan (plan , context_parallel_styles (), "cp_plan" )
182+ self ._cp_plan = plan
183+
157184 @pp_plan .setter
158185 def pp_plan (self , plan : dict [str , tuple [str , str ]] | None ):
159186 if plan is None :
@@ -211,12 +238,13 @@ def _validate_parallel_plan(
211238 def _expand_parallel_plan (self , plan : dict [str , str ]) -> dict [str , str ]:
212239 """Replace the `*` of the plan's rules with the layer numbers of the model, one entry per module or parameter."""
213240 expanded_plan = {}
241+ cp_styles = context_parallel_styles ()
214242
215243 for module_name , module in self .named_modules ():
216244 module_style = _get_parameter_plan (parameter_name = module_name , plan = plan , is_weight = False )
217245
218- if module_style in ALL_FSDP_STYLES :
219- # FSDP: list the module to wrap .
246+ if module_style in ALL_FSDP_STYLES or module_style in cp_styles :
247+ # FSDP2 and context parallelism wrap the module itself .
220248 expanded_plan [module_name ] = module_style
221249 else :
222250 # TP/EP: list the module's own parameters (to shard) before the module (to hook).
@@ -232,16 +260,16 @@ def _expand_parallel_plan(self, plan: dict[str, str]) -> dict[str, str]:
232260
233261 def _resolve_parallel_plans (
234262 self , distributed_config : DistributedConfig
235- ) -> tuple [dict [str , str ], dict [str , str ], dict [str , str ]]:
236- """Build the final TP, EP and FSDP plans: apply the config overrides, then replace `*` with the layer numbers.
263+ ) -> tuple [dict [str , str ], dict [str , str ], dict [str , str ], dict [ str , str ] ]:
264+ """Build the final TP, EP, FSDP and CP plans: apply the config overrides, then replace `*` by the layer numbers.
237265
238266 Each returned plan maps one concrete module or parameter name to a style, e.g. `model.layers.3.mlp.up_proj`
239267 instead of `model.layers.*.mlp.up_proj`. A module that has an EP rule keeps no TP rule.
240268 """
241269 module_names = {replace_layer_number_by_wildcard (n ) for n , _ in self .named_modules ()}
242270 param_names = {replace_layer_number_by_wildcard (n ) for n , _ in self .named_parameters ()}
243271 names = module_names | param_names
244- for plan_name in ("tp_plan" , "ep_plan" , "fsdp_plan" ):
272+ for plan_name in ("tp_plan" , "ep_plan" , "fsdp_plan" , "cp_plan" ):
245273 override = getattr (distributed_config , plan_name )
246274 if isinstance (override , dict ):
247275 plan = getattr (self , plan_name )
@@ -253,8 +281,9 @@ def _resolve_parallel_plans(
253281 ep_plan = dict (self .ep_plan ) if distributed_config .ep_size > 1 else {}
254282 # HSDP shards each replica with the FSDP plan.
255283 fsdp_plan = dict (self .fsdp_plan ) if distributed_config .dp_size > 1 else {}
284+ cp_plan = dict (self .cp_plan ) if distributed_config .cp_size > 1 else {}
256285
257- for kind , plan in (("tp" , tp_plan ), ("ep" , ep_plan ), ("fsdp" , fsdp_plan )):
286+ for kind , plan in (("tp" , tp_plan ), ("ep" , ep_plan ), ("fsdp" , fsdp_plan ), ( "cp" , cp_plan ) ):
258287 size = distributed_config .dp_size if kind == "fsdp" else getattr (distributed_config , f"{ kind } _size" )
259288 if size > 1 and not plan :
260289 raise ValueError (
@@ -275,6 +304,7 @@ def _resolve_parallel_plans(
275304 self ._validate_parallel_plan (tp_plan , ALL_PARALLEL_STYLES , "tp_plan" , names )
276305 self ._validate_parallel_plan (ep_plan , ALL_PARALLEL_STYLES , "ep_plan" , names )
277306 self ._validate_parallel_plan (fsdp_plan , ALL_FSDP_STYLES , "fsdp_plan" , names )
307+ self ._validate_parallel_plan (cp_plan , context_parallel_styles (), "cp_plan" , names )
278308 distributed_config ._validate_resolved_ep_plan (ep_plan )
279309
280310 # Tied embed/lm_head weights are wrapped once: fold the head's FSDP rule into the embedding's.
@@ -289,10 +319,24 @@ def _resolve_parallel_plans(
289319 tp_plan = self ._expand_parallel_plan (tp_plan )
290320 ep_plan = self ._expand_parallel_plan (ep_plan )
291321 fsdp_plan = self ._expand_parallel_plan (fsdp_plan )
322+ cp_plan = self ._expand_parallel_plan (cp_plan )
292323 # FSDP2 needs a submodule sharded before the module containing it, e.g. `mlp.gate` before its layer.
293324 fsdp_plan = dict (sorted (fsdp_plan .items (), key = lambda item : item [0 ].count ("." ), reverse = True ))
294325
295- return tp_plan , ep_plan , fsdp_plan
326+ if distributed_config .cp_size > 1 :
327+ styles = [ALL_PARALLEL_STYLES [style ] for style in cp_plan .values ()]
328+ # a split style on the decoder layers, and an attention style (Ulysses, or another registered one)
329+ for name , kind in (("cp_split_sequence" , SplitSequenceParallel ), ("cp_ulysses" , ContextParallelAttention )):
330+ if not any (isinstance (style , kind ) for style in styles ):
331+ raise ValueError (
332+ f"`cp_plan` has no `{ name } ` rule matching a module of { type (self ).__name__ } . Context "
333+ "parallelism needs `cp_split_sequence` on the decoder layers, to keep this rank's slice of the "
334+ "sequence, and an attention style such as `cp_ulysses` on their attention modules, to give "
335+ "attention the whole sequence. Add them to `base_model_cp_plan` of the model's config, pass "
336+ "`cp_plan`, or set `cp_size=1`."
337+ )
338+
339+ return tp_plan , ep_plan , fsdp_plan , cp_plan
296340
297341 @classmethod
298342 def maybe_distribute_model (
@@ -301,7 +345,9 @@ def maybe_distribute_model(
301345 distributed_config : DistributedConfig | None ,
302346 mesh_manager : TransformersDeviceMesh | None ,
303347 ):
304- """Apply pipeline, tensor and expert parallelism, then FSDP2, after model init and before weight loading."""
348+ """
349+ Apply pipeline, tensor, expert and context parallelism, then FSDP2, after model init and before weight loading.
350+ """
305351 if mesh_manager is None :
306352 return model
307353
@@ -312,7 +358,7 @@ def maybe_distribute_model(
312358 model ._tp_size = distributed_config .tp_size
313359 model ._fsdp_size = distributed_config .fsdp_size
314360
315- tp_plan , ep_plan , fsdp_plan = model ._resolve_parallel_plans (distributed_config )
361+ tp_plan , ep_plan , fsdp_plan , cp_plan = model ._resolve_parallel_plans (distributed_config )
316362
317363 if distributed_config .pp_size > 1 :
318364 model = apply_pipeline_parallelism (model , mesh_manager .get_mesh ("pp" ))
@@ -333,6 +379,9 @@ def maybe_distribute_model(
333379 # we will have to slice the batch here in order to avoid computing tp_size times the same batch.
334380 model = apply_expert_parallelism (model , ep_mesh , tp_mesh , ep_plan )
335381
382+ if distributed_config .cp_size > 1 :
383+ model = apply_context_parallelism (model , mesh_manager .get_mesh ("cp" ), cp_plan )
384+
336385 if distributed_config .dp_size > 1 :
337386 model = apply_fully_sharded_data_parallelism (model , mesh_manager , fsdp_plan , ep_plan )
338387
0 commit comments