[Feature] Support HSDP for diffusion models (#1339)

Signed-off-by: gcanlin <canlinguosdu@gmail.com>
Co-authored-by: Hongsheng Liu <liuhongsheng4@huawei.com>
This commit is contained in:
Canlin GuoandHongsheng Liu authored and GitHub committed 2026-02-26 21:31:10 +08:00
1 parent 66c3e04263
commit f53152d340
17 files changed
+1023 -46

No files matched your search

@@ -56,6 +56,7 @@ _SP: SequenceParallelGroupCoordinator | None = None
_PP: PipelineGroupCoordinator | None = None
_CFG: GroupCoordinator | None = None
_DP: GroupCoordinator | None = None
_FS: GroupCoordinator | None = None # Fully Sharded (HSDP shard dimension)
_DIT: GroupCoordinator | None = None
@@ -175,7 +176,8 @@ class RankGenerator:
pp: int,
cfg: int,
dp: int,
order: str,
fs: int = 1,
order: str = "tp-sp-pp-cfg-dp",
rank_offset: int = 0,
) -> None:
self.tp = tp
@@ -183,6 +185,7 @@ class RankGenerator:
self.pp = pp
self.cfg = cfg
self.dp = dp
self.fs = fs
self.rank_offset = rank_offset
self.world_size = tp * sp * pp * cfg * dp
@@ -192,14 +195,19 @@ class RankGenerator:
"pp": self.pp,
"cfg": self.cfg,
"dp": self.dp,
"fs": self.fs,
}
order = order.lower()
for name in self.name_to_size.keys():
# Skip 'fs' validation - it's handled separately with independent_ranks=True
# and doesn't participate in the main orthogonal rank generation
if name == "fs":
continue
if name not in order and self.name_to_size[name] != 1:
raise RuntimeError(
f"The size of ({name}) is ({self.name_to_size[name]}), "
f"but you haven't specified the order ({self.order})."
f"but you haven't specified the order ({order})."
)
elif name not in order:
order = order + "-" + name
@@ -218,7 +226,7 @@ class RankGenerator:
mask[ordered_token.index(t)] = True
return mask
def get_ranks(self, token):
def get_ranks(self, token, independent_ranks: bool = False):
"""Get rank group by input token.
Arguments:
@@ -227,7 +235,21 @@ class RankGenerator:
to obtain multiple parallel types, we can use a hyphen
'-' to separate them. For example, if we want to obtain
the TP_DP group, the token should be 'tp-dp'.
independent_ranks (bool):
If True, generate independent rank groups that divide the world
into groups of the specified size. Used for FS (fully shard) groups
which operate independently from the main parallelism hierarchy.
"""
if independent_ranks and token == "fs":
# FS groups divide world into groups of size fs
# e.g., world_size=8, fs=4 -> [[0,1,2,3], [4,5,6,7]]
ranks = []
num_groups = self.world_size // self.fs
for i in range(num_groups):
group = list(range(i * self.fs + self.rank_offset, (i + 1) * self.fs + self.rank_offset))
ranks.append(group)
return ranks
mask = self.get_mask(self.order, token)
ranks = generate_masked_orthogonal_rank_groups(self.world_size, self.ordered_size, mask)
if self.rank_offset > 0:
@@ -333,6 +355,22 @@ def get_data_parallel_rank():
return get_dp_group().rank_in_group
# FS (Fully Shard / HSDP shard dimension)
def get_fs_group() -> GroupCoordinator:
assert _FS is not None, "fully shard group is not initialized"
return _FS
def get_fully_shard_world_size():
"""Return world size for the fully shard group."""
return get_fs_group().world_size
def get_fully_shard_rank():
"""Return my rank for the fully shard group."""
return get_fs_group().rank_in_group
def is_dp_last_group():
"""Return True if in the last data parallel group, False otherwise."""
return (
@@ -438,6 +476,7 @@ def init_model_parallel_group(
"tensor",
"sequence",
"classifier_free_guidance",
"fully_shard",
], f"parallel_mode {parallel_mode} is not supported"
if parallel_mode == "pipeline":
return PipelineGroupCoordinator(
@@ -629,6 +668,7 @@ def initialize_model_parallel(
ring_degree: int = 1,
tensor_parallel_size: int = 1,
pipeline_parallel_size: int = 1,
fully_shard_degree: int = 1,
backend: str | None = None,
) -> None:
if backend is None:
@@ -645,6 +685,7 @@ def initialize_model_parallel(
ring_degree: number of GPUs used for ring sequence parallelism.
tensor_parallel_size: number of GPUs used for tensor parallelism.
pipeline_parallel_size: number of GPUs used for pipeline parallelism.
fully_shard_degree: number of GPUs used for fully sharded data parallelism (HSDP shard dimension).
backend: distributed backend of pytorch collective comm.
Let's say we have a total of 16 GPUs denoted by g0 ... g15 and we
@@ -716,7 +757,8 @@ def initialize_model_parallel(
pipeline_parallel_size,
cfg_parallel_size,
data_parallel_size,
"tp-sp-pp-cfg-dp",
fs=fully_shard_degree,
order="tp-sp-pp-cfg-dp",
)
sp_group_ranks = rank_generator.get_ranks("sp")
global _DP
@@ -772,6 +814,16 @@ def initialize_model_parallel(
backend=backend,
parallel_mode="tensor",
)
global _FS
assert _FS is None, "fully shard group is already initialized"
_FS = init_model_parallel_group(
group_ranks=rank_generator.get_ranks("fs", independent_ranks=True),
local_rank=get_world_group().local_rank,
backend=backend,
parallel_mode="fully_shard",
)
init_dit_group(dit_parallel_size, backend)
@@ -801,6 +853,11 @@ def destroy_model_parallel():
_PP.destroy()
_PP = None
global _FS
if _FS:
_FS.destroy()
_FS = None
def destroy_distributed_environment():
global _WORLD