项目文件夹

文件
wehub-resource-sync 593b94c120
pytest / Unit Tests (push) Has been cancelled
pytest / Integration (integration_tests_a) (push) Has been cancelled
pytest / Integration (integration_tests_b) (push) Has been cancelled
pytest / Integration (integration_tests_c) (push) Has been cancelled
pytest / Integration (integration_tests_d) (push) Has been cancelled
pytest / Integration (integration_tests_e) (push) Has been cancelled
pytest / Integration (integration_tests_f) (push) Has been cancelled
pytest / Integration (integration_tests_g) (push) Has been cancelled
pytest / Integration (integration_tests_h) (push) Has been cancelled
pytest / Integration (integration_tests_i) (push) Has been cancelled
pytest / Integration (integration_tests_j) (push) Has been cancelled
pytest / Distributed (distributed_a) (push) Has been cancelled
pytest / Distributed (distributed_b) (push) Has been cancelled
pytest / Distributed (distributed_c) (push) Has been cancelled
pytest / Distributed (distributed_d) (push) Has been cancelled
pytest / Distributed (distributed_e) (push) Has been cancelled
pytest / Distributed (distributed_f) (push) Has been cancelled
pytest / Minimal Install (push) Has been cancelled
pytest / Event File (push) Has been cancelled
pytest (slow) / py-slow (push) Has been cancelled
Publish JSON Schema / publish-schema (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:49:20 +08:00

248 行
7.9 KiB
Python

"""HuggingFace Accelerate distributed training strategy.
Provides a single abstraction for DDP, FSDP, and DeepSpeed via the Accelerate library.
This replaces the need for separate DDPStrategy, FSDPStrategy, and DeepSpeedStrategy
classes with one unified implementation.
Usage:
# In config or backend:
strategy: accelerate
# With FSDP:
strategy:
type: accelerate
mixed_precision: bf16
fsdp_config:
fsdp_sharding_strategy: FULL_SHARD
# With DeepSpeed:
strategy:
type: accelerate
deepspeed_config: path/to/ds_config.json
"""
import contextlib
import logging
import os
from typing import Any
import torch
import torch.distributed as dist
from torch.nn import Module
from torch.optim import Optimizer
from ludwig.distributed.base import DistributedStrategy
from ludwig.modules.optimization_modules import create_optimizer
from ludwig.utils.checkpoint_utils import Checkpoint, MultiNodeCheckpoint
logger = logging.getLogger(__name__)
class AccelerateStrategy(DistributedStrategy):
"""Distributed training via HuggingFace Accelerate.
Accelerate provides a single prepare() call that auto-detects and configures DDP, FSDP, or DeepSpeed based on the
environment and configuration.
"""
def __init__(
self,
mixed_precision: str | None = None,
gradient_accumulation_steps: int = 1,
fsdp_config: dict | None = None,
deepspeed_config: str | dict | None = None,
**kwargs,
):
from accelerate import Accelerator
accelerator_kwargs = {}
if mixed_precision:
accelerator_kwargs["mixed_precision"] = mixed_precision
if gradient_accumulation_steps > 1:
accelerator_kwargs["gradient_accumulation_steps"] = gradient_accumulation_steps
if fsdp_config:
# Accelerate FSDP config is passed via fsdp_plugin
from accelerate import FullyShardedDataParallelPlugin
accelerator_kwargs["fsdp_plugin"] = FullyShardedDataParallelPlugin(**fsdp_config)
if deepspeed_config:
from accelerate import DeepSpeedPlugin
if isinstance(deepspeed_config, str):
accelerator_kwargs["deepspeed_plugin"] = DeepSpeedPlugin(hf_ds_config=deepspeed_config)
else:
accelerator_kwargs["deepspeed_plugin"] = DeepSpeedPlugin(**deepspeed_config)
self.accelerator = Accelerator(**accelerator_kwargs)
self._prepared_model = None
self._prepared_optimizer = None
logger.info(
f"AccelerateStrategy initialized: distributed_type={self.accelerator.distributed_type}, "
f"num_processes={self.accelerator.num_processes}, device={self.accelerator.device}"
)
def prepare(
self,
model: Module,
trainer_config,
base_learning_rate: float,
) -> tuple[Module, Optimizer]:
optimizer = create_optimizer(model, trainer_config.optimizer, base_learning_rate)
self._prepared_model, self._prepared_optimizer = self.accelerator.prepare(model, optimizer)
return self._prepared_model, self._prepared_optimizer
def to_device(self, model, device=None):
# Accelerate handles device placement via prepare()
if device is not None:
return model.to_device(device)
return model.to_device(self.accelerator.device)
def backward(self, loss: torch.Tensor, model: Module):
self.accelerator.backward(loss)
def step(self, optimizer: Optimizer, *args, **kwargs):
optimizer.step(*args, **kwargs)
def zero_grad(self, optimizer: Optimizer):
optimizer.zero_grad()
def size(self) -> int:
return self.accelerator.num_processes
def rank(self) -> int:
return self.accelerator.process_index
def local_size(self) -> int:
# Accelerate doesn't expose local_size directly. Compute from environment.
return int(os.environ.get("LOCAL_SIZE", os.environ.get("LOCAL_WORLD_SIZE", 1)))
def local_rank(self) -> int:
return self.accelerator.local_process_index
def barrier(self):
self.accelerator.wait_for_everyone()
def allreduce(self, t: torch.Tensor) -> torch.Tensor:
return self.accelerator.reduce(t, reduction="sum")
def broadcast(self, t: torch.Tensor) -> torch.Tensor:
if dist.is_available() and dist.is_initialized():
dist.broadcast(t, src=0)
return t
def sync_model(self, model: Module):
# Accelerate handles model sync through prepare()
pass
def sync_optimizer(self, optimizer: Optimizer):
# Accelerate handles optimizer sync through prepare()
pass
def broadcast_object(self, v: Any, name: str | None = None) -> Any:
if not dist.is_available() or not dist.is_initialized():
return v
obj_list = [v]
dist.broadcast_object_list(obj_list, src=0)
return obj_list[0]
def wait_optimizer_synced(self, optimizer: Optimizer):
# Accelerate handles gradient sync automatically
pass
@contextlib.contextmanager
def prepare_model_update(self, model: Module, should_step: bool):
# Emulate DDP's no_sync() behavior for gradient accumulation.
# When should_step is False, we skip gradient synchronization.
if not should_step and hasattr(model, "no_sync"):
with model.no_sync():
yield
else:
yield
@contextlib.contextmanager
def prepare_optimizer_update(self, optimizer: Optimizer):
yield
@classmethod
def is_available(cls) -> bool:
try:
import accelerate # noqa: F401
return True
except ImportError:
return False
@classmethod
def gather_all_tensors_fn(cls):
try:
from torchmetrics.utilities.distributed import gather_all_tensors
return gather_all_tensors
except ImportError:
return None
@classmethod
def get_ray_trainer_backend(cls, **kwargs):
# Accelerate can work with Ray but requires specific setup.
# Return TorchConfig for basic compatibility.
try:
from ray.train.torch import TorchConfig
return TorchConfig()
except ImportError:
return None
@classmethod
def get_trainer_cls(cls, backend_config):
try:
from ray.train.torch import TorchTrainer
return TorchTrainer, {"torch_config": backend_config}
except ImportError:
raise ImportError("Ray is required for distributed training with AccelerateStrategy via Ray backend.")
def shutdown(self):
pass
def prepare_for_inference(self, model: Module) -> Module:
return self.accelerator.unwrap_model(model)
def allow_gradient_accumulation(self) -> bool:
return True
def allow_mixed_precision(self) -> bool:
# Let Accelerate handle mixed precision if configured
if self.accelerator.mixed_precision != "no":
return False
return True
def allow_clip_gradients(self) -> bool:
return True
def prepare_before_load(self) -> bool:
return False
@classmethod
def is_model_parallel(cls) -> bool:
return False
def create_checkpoint_handle(self, dist_model, model, optimizer, scheduler) -> Checkpoint:
return MultiNodeCheckpoint(self, model, optimizer, scheduler)
@classmethod
def extract_model_for_serialization(cls, model):
try:
from accelerate import Accelerator
accelerator = Accelerator()
return accelerator.unwrap_model(model)
except ImportError:
return model
@classmethod
def replace_model_from_serialization(cls, state):
if not isinstance(state, Module):
raise TypeError(f"replace_model_from_serialization expected an nn.Module, got {type(state).__name__}.")
return state