[Refactor][Diffusion] Resolve distributed layerwise topology in the plan (#7326)

Signed-off-by: specture724 <specture724@gmail.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: Hongsheng Liu <liuhongsheng4@huawei.com>
This commit is contained in:
authored and GitHub committed 2026-09-19 19:05:18 +08:00
1 parent 232dbc82f8
commit ea84055507
5 files changed
+361 -129

No files matched your search

@@ -53,8 +53,7 @@ from .host_registration import (
HostRegistrationError,
register_host_mappings,
)
from .offload_plan import OffloadPlan
from .plan_resolver import ResolvedComponent, resolve_offload_plan
from .plan_resolver import BlockStack, ResolvedComponent, resolve_offload_plan
from .tensor_utils import (
clear_block_storage,
clear_tensor_storage,
@@ -111,7 +110,6 @@ def _forget_active_hwr_registration(
# use layerwise offload (streaming hooks) or be moved to GPU as a resident
# module. Submodules larger than this are offloaded to save HBM; smaller
# ones stay resident for lower latency.
_ON_DEMAND_THRESHOLD_MB = 1024
class DistributedLayerwiseOffloadHook(ModelHook):
@@ -1182,7 +1180,7 @@ class DistributedLayerwiseOffloadBackend(OffloadBackend):
def _load_weights_via_mmap(
self,
pipeline: nn.Module,
modules,
dits: tuple[ResolvedComponent, ...],
plan: HostWeightPlan,
) -> None:
"""Load DiT checkpoint tensors as file-backed safetensors views.
@@ -1226,7 +1224,7 @@ class DistributedLayerwiseOffloadBackend(OffloadBackend):
# converts the child's buffers too. So we must save ALL buffers
# from ALL DiT modules BEFORE any to_empty call.
saved_buffers: dict[int, dict[str, torch.Tensor]] = {}
for dit_module in modules.dits:
for dit_module in (component.module for component in dits):
bufs: dict[str, torch.Tensor] = {}
for name, buf in dit_module.named_buffers():
# Check if this buffer is non-persistent on its OWNING module
@@ -1246,7 +1244,7 @@ class DistributedLayerwiseOffloadBackend(OffloadBackend):
# Skip modules that are already meta (happens when a parent DiT
# module contains a child DiT module — to_empty on the parent
# already converted the child).
for dit_module in modules.dits:
for dit_module in (component.module for component in dits):
if any(p.is_meta for p in dit_module.parameters()):
logger.info(
"%s already on meta device (skipping to_empty, %d buffers saved)",
@@ -1307,7 +1305,7 @@ class DistributedLayerwiseOffloadBackend(OffloadBackend):
# assigning checkpoint tensors. The mmap path bypasses that loader, so
# preserve the same lifecycle for transforms such as Cosmos3's fp32
# timestep embedder.
for dit_name, dit_module in zip(modules.dit_names, modules.dits):
for dit_name, dit_module in ((component.path, component.module) for component in dits):
# Restore non-persistent buffers before post-load hooks and strict
# validation. They are constructor-derived and intentionally have
# no checkpoint binding.
@@ -1352,7 +1350,7 @@ class DistributedLayerwiseOffloadBackend(OffloadBackend):
self._mmap_transforms_by_tensor_id[id(target)] = binding.transform
remaining_meta: list[str] = []
for dit_name, dit_module in zip(modules.dit_names, modules.dits):
for dit_name, dit_module in ((component.path, component.module) for component in dits):
remaining_meta.extend(
f"{dit_name}.{name}" for name, tensor in dit_module.named_parameters() if tensor.is_meta
)
@@ -1517,73 +1515,26 @@ class DistributedLayerwiseOffloadBackend(OffloadBackend):
)
return True
def _try_layerwise_offload_submodule(self, module: nn.Module, name: str, plan: OffloadPlan | None = None) -> bool:
"""Try to apply layerwise offload to a large submodule's blocks.
Resolution order:
1. OffloadPlan.offload_submodules (declarative, if plan is provided)
2. Heuristic search for common block-list attributes
Returns True if layerwise offload was applied, False otherwise.
"""
from operator import attrgetter
blocks = None
blocks_attr = None
# 1. Check OffloadPlan first (declarative — no guessing)
if plan is not None and name in plan.offload_submodules:
attr_name = plan.offload_submodules[name]
try:
candidate = attrgetter(attr_name)(module)
if isinstance(candidate, nn.ModuleList) and len(candidate) > 1:
blocks = candidate
blocks_attr = attr_name
except AttributeError:
logger.warning(
"OffloadPlan declared block attr '%s' for submodule '%s' "
"but attribute not found — falling back to heuristic",
attr_name,
name,
)
# 2. Fallback: heuristic search
if blocks is None:
for attr_name in ("layers", "blocks", "h", "model.layers"):
try:
candidate = attrgetter(attr_name)(module)
except AttributeError:
continue
if isinstance(candidate, nn.ModuleList) and len(candidate) > 1:
blocks = candidate
blocks_attr = attr_name
break
if blocks is None:
return False
def _offload_nested_submodule(self, component: ResolvedComponent) -> None:
"""Stream one resolved DiT submodule through its own hook ring."""
stack = component.stacks[0]
logger.info(
"Distributed layerwise offload for submodule '%s.%s' (%d blocks, %.0f MB total, group_size=%d)",
name,
blocks_attr,
len(blocks),
sum(p.nelement() * p.element_size() for p in module.parameters()) / 1048576,
component.path,
stack.attrs[0],
len(stack.blocks),
sum(p.nelement() * p.element_size() for p in component.module.parameters()) / 1048576,
self._component_transport(DIT_COMPONENT)[1],
)
# Move non-block parts of the submodule to GPU (small: embeddings, norms)
for child_name, child in module.named_children():
if child_name != blocks_attr:
child.to(self.device)
self._install_hook_group(blocks, DIT_COMPONENT, use_dit_mmap=True)
return True
# Keep the submodule's own non-block state resident by block identity,
# so an attribute aliasing a streamed block is not placed with it.
move_non_block_state_to_device(component.module, (stack.blocks,), self.device)
self._install_hook_group(stack.blocks, DIT_COMPONENT, use_dit_mmap=True)
def _prepare_dit_non_block_modules(
self,
dit_module: nn.Module,
blocks_attr_names: list[str],
all_dit_modules: set[int],
plan: OffloadPlan | None,
component: ResolvedComponent,
stack: BlockStack,
) -> None:
"""Place or hook the DiT parts that are outside its repeated blocks.
@@ -1591,28 +1542,23 @@ class DistributedLayerwiseOffloadBackend(OffloadBackend):
an all-resident stage skips placement for modules such as H3's token
refiner and enters the forward pass with CPU or meta tensors.
"""
dit_module = component.module
nested = {child.path.rsplit(".", 1)[-1]: child for child in component.children}
for name, module in dit_module.named_children():
if name in blocks_attr_names:
if name in stack.attrs:
logger.debug("Skipped blocks module %s", name)
continue
module_mb = (
sum(
param.nelement() * param.element_size() if not getattr(param, "is_meta", False) else 0
for param in module.parameters()
)
/ 1048576
)
explicitly_planned = plan is not None and name in plan.offload_submodules
if explicitly_planned or module_mb > _ON_DEMAND_THRESHOLD_MB:
if id(module) in all_dit_modules:
child = nested.get(name)
if child is not None:
if not child.selected:
logger.info("Submodule '%s' is already a DiT module, skipping layerwise offload", name)
elif self._try_layerwise_offload_submodule(module, name, plan):
pass
elif child.stacks:
self._offload_nested_submodule(child)
else:
prepare_component(
module,
name,
child.module,
child.path,
device=self.device,
stage_on_demand=True,
blockwise=False,
@@ -1715,7 +1661,7 @@ class DistributedLayerwiseOffloadBackend(OffloadBackend):
elif host_weight_plan.backing_kind == "checkpoint_mmap":
self._load_weights_via_mmap(
pipeline,
resolved.modules,
resolved.dits,
host_weight_plan,
)
else:
@@ -1750,16 +1696,12 @@ class DistributedLayerwiseOffloadBackend(OffloadBackend):
enable_encoder_blocks=self._try_layerwise_offload_encoder,
)
if self.config.offloads(DIT_COMPONENT):
if any(component.selected for component in resolved.dits):
logger.info(
"Applying distributed layer-wise offloading on %s",
[component.path for component in resolved.dits],
)
# Collect all DiT module objects to detect submodules that are
# already handled as a separate DiT module (avoids duplicate hooks).
all_dit_modules = set(id(component.module) for component in resolved.dits)
# Apply hooks for each DiT module
for component, stack in iter_streamable_dits(resolved, self.device):
streaming = list(stack.streaming)
@@ -1772,12 +1714,7 @@ class DistributedLayerwiseOffloadBackend(OffloadBackend):
len(streaming),
)
self._prepare_dit_non_block_modules(
component.module,
list(stack.attrs),
all_dit_modules,
resolved.declaration,
)
self._prepare_dit_non_block_modules(component, stack)
if not streaming:
logger.info("All blocks for %s are resident; no streaming hooks required", component.path)
@@ -1804,11 +1741,6 @@ class DistributedLayerwiseOffloadBackend(OffloadBackend):
self.enabled = bool(self._resident_blocks or self._encoder_modules or self._staged_components)
if self._using_mmap and not self.enabled:
self._release_mmap_handles()
if not self.enabled and not self.config.offloads(DIT_COMPONENT):
raise ValueError(
"None of the selected distributed layerwise offload components have "
"a model-declared streamable or on-demand plan"
)
return
# Unified allocation: 2 shared output buffers + 2 shared shard buffers