Files
2026-07-13 13:09:03 +08:00

179 lines
5.6 KiB
Python

"""Context Parallel process-group and runtime configuration.
All CP runtime knobs are sourced here to keep behavior config-first while
retaining environment-variable fallbacks for existing launch scripts.
"""
from __future__ import annotations
import os
import warnings
from dataclasses import dataclass
import torch.distributed as dist
from torch.distributed import ProcessGroup
_CP_GROUP: ProcessGroup | None = None
_WARNED_ENV_KEYS: set[str] = set()
@dataclass
class CpRuntimeConfig:
"""Runtime knobs for CP communication, validation, and memory policy."""
scan_backend: str | None = None
allgather_impl: str | None = None
halo_impl: str | None = None
# Enables fused Triton GDN blocks to use the CP scan path.
triton_block_fusion: bool | None = None
_CP_RUNTIME_CONFIG = CpRuntimeConfig()
def _warn_env_fallback_once(env_key: str, config_key: str) -> None:
key = f"{env_key}->{config_key}"
if key in _WARNED_ENV_KEYS:
return
_WARNED_ENV_KEYS.add(key)
warnings.warn(
f"[CP-CONFIG] Using env fallback {env_key}; " f"please migrate to config key {config_key}.",
stacklevel=2,
)
def _env_bool(env_key: str) -> bool | None:
raw = os.environ.get(env_key)
if raw is None:
return None
value = raw.strip().lower()
if value in {"1", "true", "yes", "on"}:
return True
if value in {"0", "false", "no", "off"}:
return False
return None
def _normalized_choice(value: str | None, allowed: set[str], default: str) -> str:
if value is None:
return default
norm = value.strip().lower()
if norm in allowed:
return norm
return default
def set_cp_runtime_config(config: CpRuntimeConfig) -> None:
"""Replace the CP runtime configuration."""
global _CP_RUNTIME_CONFIG
_CP_RUNTIME_CONFIG = config
def get_cp_runtime_config() -> CpRuntimeConfig:
"""Return the current CP runtime configuration."""
return _CP_RUNTIME_CONFIG
def set_cp_group(group: ProcessGroup | None) -> None:
"""Set the Context Parallel process group."""
global _CP_GROUP
_CP_GROUP = group
def get_cp_group() -> ProcessGroup | None:
"""Get the Context Parallel process group."""
return _CP_GROUP
def cp_enabled() -> bool:
"""Return True when Context Parallel is active."""
group = _CP_GROUP
if group is None or not dist.is_available() or not dist.is_initialized():
return False
return dist.get_world_size(group) > 1
def get_cp_world_size(default: int = 1) -> int:
"""Get CP world size from the registered CP group."""
group = _CP_GROUP
if group is None or not dist.is_available() or not dist.is_initialized():
return default
return dist.get_world_size(group)
def get_cp_scan_backend() -> str:
cfg = _CP_RUNTIME_CONFIG.scan_backend
if cfg is not None:
return _normalized_choice(cfg, {"torch", "triton"}, "torch")
env_val = os.environ.get("CP_SCAN_BACKEND")
if env_val is not None:
_warn_env_fallback_once("CP_SCAN_BACKEND", "train.extra.cp.scan_backend")
return _normalized_choice(env_val, {"torch", "triton"}, "torch")
def get_cp_allgather_impl() -> str:
cfg = _CP_RUNTIME_CONFIG.allgather_impl
if cfg is not None:
return _normalized_choice(cfg, {"collective", "list", "p2p"}, "collective")
env_val = os.environ.get("CP_ALLGATHER_IMPL")
if env_val is not None:
_warn_env_fallback_once("CP_ALLGATHER_IMPL", "train.extra.cp.allgather_impl")
return _normalized_choice(env_val, {"collective", "list", "p2p"}, "collective")
def get_cp_halo_impl() -> str:
cfg = _CP_RUNTIME_CONFIG.halo_impl
if cfg is not None:
return _normalized_choice(cfg, {"collective", "p2p"}, "collective")
env_val = os.environ.get("CP_HALO_IMPL")
if env_val is not None:
_warn_env_fallback_once("CP_HALO_IMPL", "train.extra.cp.halo_impl")
return _normalized_choice(env_val, {"collective", "p2p"}, "collective")
def get_cp_triton_block_fusion() -> bool:
"""Enable the fused Triton block CP path.
When True, ``ChunkCausalGDNTriton`` / ``BidirectionalGDNTriton`` (and
the BothTriton variants) take a CP path that wraps the proven
``cp_frame_gdn_scan`` algorithm with fused Triton preprocessing/output
projection kernels. CP execution of these Triton GDN blocks requires
this flag to be enabled; ``False`` keeps the non-CP behavior unchanged.
Toggleable via ``train.extra.cp.triton_block_fusion`` (preferred) or
the legacy env var ``CP_TRITON_BLOCK_FUSION``.
"""
if _CP_RUNTIME_CONFIG.triton_block_fusion is not None:
return bool(_CP_RUNTIME_CONFIG.triton_block_fusion)
env_val = _env_bool("CP_TRITON_BLOCK_FUSION")
if env_val is not None:
_warn_env_fallback_once("CP_TRITON_BLOCK_FUSION", "train.extra.cp.triton_block_fusion")
return env_val
return False
def init_context_parallel(cp_size: int = 1) -> None:
"""Initialize Context Parallel groups.
Creates contiguous-rank CP groups of size *cp_size*. Typically
called alongside (or instead of) ``init_ulysses_sequence_parallel``
in the training script.
"""
set_cp_group(None)
if cp_size <= 1:
return
if not dist.is_initialized():
raise RuntimeError("torch.distributed must be initialized before CP.")
world_size = dist.get_world_size()
rank = dist.get_rank()
if world_size % cp_size != 0:
raise ValueError(f"world_size={world_size} must be divisible by cp_size={cp_size}")
for i in range(world_size // cp_size):
start = i * cp_size
ranks = list(range(start, start + cp_size))
group = dist.new_group(ranks)
if rank in ranks:
set_cp_group(group)