Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions src/transformers/distributed/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,36 @@ def _get_torch_distributed_world_size() -> int:
return torch.distributed.get_world_size()


def prefetch_checkpoint_shards(checkpoint_files: list[str]) -> None:
"""Warm the page cache for the checkpoint shards before the per-tensor loading pass, opt-in via
`HF_SHARD_PREFETCH=<read threads per rank>`.

The per-tensor read pattern of sharded loading reads a network filesystem at well under 1 GiB/s
while large sequential reads sustain many times that; warming the page cache first makes the
actual load run at memory speed. Local ranks split the shard list between them (every node needs
the full checkpoint cached, since every rank slices tensors from all shards).
"""
prefetch_threads = int(os.environ.get("HF_SHARD_PREFETCH", "0"))
if not checkpoint_files or not prefetch_threads:
return
import time
from concurrent.futures import ThreadPoolExecutor

local_rank = int(os.environ.get("LOCAL_RANK", "0"))
local_world = int(os.environ.get("LOCAL_WORLD_SIZE", "1"))

def _warm(path, bufsize=16 * 2**20):
with open(path, "rb", buffering=0) as f:
while f.read(bufsize):
pass

prefetch_start = time.time()
with ThreadPoolExecutor(max_workers=prefetch_threads) as pool:
list(pool.map(_warm, checkpoint_files[local_rank::local_world]))
_distributed_barrier()
logger.warning_once(f"Prefetched {len(checkpoint_files)} checkpoint shards in {time.time() - prefetch_start:.0f}s")


def is_local_dist_rank_0() -> bool:
return _is_torch_distributed_initialized() and int(os.environ.get("LOCAL_RANK", "-1")) == 0

Expand Down
4 changes: 3 additions & 1 deletion src/transformers/modeling_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
from torch.distributions import constraints
from torch.utils.checkpoint import checkpoint

from transformers.distributed.utils import is_dtensor
from transformers.distributed.utils import is_dtensor, prefetch_checkpoint_shards

from . import initialization as init
from .configuration_utils import PreTrainedConfig
Expand Down Expand Up @@ -4372,6 +4372,8 @@ def _load_pretrained_model(
# Model's definition arriving here is final (TP hooks added, quantized layers replaces)
expected_keys = list(model.state_dict().keys()) if expected_keys is None else expected_keys

prefetch_checkpoint_shards(checkpoint_files)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. can't you make this async? You would not have to wait for thread pool init
  2. we might want to keep the thread pool alive as we re-use one with the weight converter itself.
  3. this only works if the checkpoints are saved per rank right? Meaning rank_0_16_ckpt.safetensors . Meaning weather or not to activate this should potentially depend on the metadata from model.safetensors.index.json

I think it's great that we improve the cold load, also thing these stuff could potentially be helped by safetensors @McPatate


if logger.level >= logging.WARNING:
verify_tp_plan(expected_keys, getattr(model, "_tp_plan", None))

Expand Down
Loading