fix: use single source rank for all_sum and lazy-load weights

Previously, all ranks with the model sent real data in all_sum, which
corrupts results with >2 nodes (data+data+0 = 2*data). Now only the
designated source_rank sends; all others send zeros regardless of
whether they have local files.

Also switch to mx.load(lazy=True) + weights.pop() so the source only
has one tensor in memory at a time instead of loading all safetensors
upfront.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
This commit is contained in:
Alex Cheema
2026-02-17 10:05:43 -08:00
co-authored by Claude Opus 4.6
parent 7ac5891588
commit 4bbdfcd872
3 changed files with 28 additions and 26 deletions
+20 -20
View File
@@ -320,16 +320,16 @@ def _transfer_files_to_disk(
def transfer_metadata_files(
model_path: Path, group: Group, has_local_model: bool
model_path: Path, group: Group, is_source: bool
) -> None:
"""
Transfer metadata files (config.json, tokenizer files, etc.) to receivers' disk.
All ranks must call this function (collective operation).
Source broadcasts non-safetensors files. Receivers write to model_path.
Only the designated source (is_source=True) should send; all others receive.
"""
_transfer_files_to_disk(
model_path, group, is_source=has_local_model, metadata_only=True
model_path, group, is_source=is_source, metadata_only=True
)
@@ -338,15 +338,15 @@ def transfer_metadata_files(
# ---------------------------------------------------------------------------
def transfer_all_files(model_path: Path, group: Group, has_local_model: bool) -> None:
def transfer_all_files(model_path: Path, group: Group, is_source: bool) -> None:
"""
Transfer ALL model files (including safetensors) to receivers' disk.
All ranks must call this function (collective operation).
Used for explicit disk-to-disk model distribution.
Only the designated source (is_source=True) should send; all others receive.
"""
_transfer_files_to_disk(
model_path, group, is_source=has_local_model, metadata_only=False
model_path, group, is_source=is_source, metadata_only=False
)
@@ -368,7 +368,7 @@ def _parse_mx_dtype(dtype_str: str) -> mx.Dtype:
def broadcast_model_weights(
model_path: Path,
group: Group,
has_local_weights: bool,
is_source: bool,
) -> dict[str, mx.array]:
"""
Broadcast model weight tensors from source rank to all receivers' memory.
@@ -378,28 +378,28 @@ def broadcast_model_weights(
memory — no disk write for weight data.
All ranks must call this function (collective operation).
Only the designated source (is_source=True) should send; all others receive.
Returns:
dict mapping weight names to mx.arrays (on all ranks).
"""
all_sum = partial(_all_sum_cpu, group=group)
# Source loads all weights
# Source lazily loads weights (data stays on disk until mx.eval per tensor)
weights: dict[str, mx.array] = {}
if has_local_weights:
if is_source:
weight_files = sorted(model_path.glob("*.safetensors"))
if not weight_files:
weight_files = sorted(model_path.glob("**/*.safetensors"))
for wf in weight_files:
loaded = mx.load(str(wf))
assert isinstance(loaded, dict)
loaded = cast(dict[str, mx.array], mx.load(str(wf), lazy=True)) # pyright: ignore[reportCallIssue]
weights.update(loaded)
logger.info(
f"Source loaded {len(weights)} weight tensors from {len(weight_files)} files"
f"Source mapped {len(weights)} weight tensors from {len(weight_files)} files"
)
# Broadcast weight metadata: {name: {shape, dtype}}
if has_local_weights:
if is_source:
source_meta: dict[str, dict[str, Any]] = {
name: {"s": list(tensor.shape), "d": str(tensor.dtype)}
for name, tensor in weights.items()
@@ -408,14 +408,14 @@ def broadcast_model_weights(
source_meta = {}
meta = cast(
dict[str, dict[str, Any]],
_broadcast_json(
source_meta if has_local_weights else None, group, has_local_weights
),
_broadcast_json(source_meta if is_source else None, group, is_source),
)
logger.info(f"Broadcasting {len(meta)} weight tensors")
# Broadcast each tensor in sorted order (deterministic across ranks)
# Broadcast each tensor in sorted order (deterministic across ranks).
# Source loads one tensor at a time from disk (lazy), broadcasts it,
# then drops the reference so only one tensor is in flight at a time.
result: dict[str, mx.array] = {}
for i, name in enumerate(sorted(meta.keys())):
info = meta[name]
@@ -423,9 +423,9 @@ def broadcast_model_weights(
dtype_str = cast(str, info["d"])
dtype = _parse_mx_dtype(dtype_str)
if has_local_weights:
tensor = weights[name]
mx.eval(tensor) # ensure loaded from lazy
if is_source:
tensor = weights.pop(name) # pop to free lazy ref after broadcast
mx.eval(tensor) # loads from disk
else:
tensor = mx.zeros(shape, dtype=dtype)
+4 -5
View File
@@ -219,16 +219,15 @@ def shard_and_load(
broadcast_weights: dict[str, mx.array] | None = None
if needs_transfer:
is_source = group.rank() == source_rank
logger.info(
f"Model transfer needed (source_rank={source_rank}, "
f"local_weights={has_local_weights})"
f"is_source={is_source}, local_weights={has_local_weights})"
)
# Step 1: Transfer metadata files (config.json, tokenizer, etc.) to disk
transfer_metadata_files(model_path, group, has_local_weights)
transfer_metadata_files(model_path, group, is_source)
# Step 2: Broadcast weight tensors directly into memory
broadcast_weights = broadcast_model_weights(
model_path, group, has_local_weights
)
broadcast_weights = broadcast_model_weights(model_path, group, is_source)
# Create model architecture (all ranks have config.json on disk now).
# Use lazy=False when receiver has no local weights: lazy=True would create
+4 -1
View File
@@ -540,6 +540,7 @@ def main(
isinstance(current_status, RunnerConnected) and group is not None
):
from exo.worker.engines.mlx.model_transfer import (
coordinate_transfer,
has_weight_files,
model_path_for_id,
transfer_all_files,
@@ -552,7 +553,9 @@ def main(
task.shard_metadata.model_card.model_id
)
has_local = has_weight_files(model_path)
transfer_all_files(model_path, group, has_local)
_, source_rank = coordinate_transfer(group, has_local)
is_source = group.rank() == source_rank
transfer_all_files(model_path, group, is_source)
logger.info("disk-to-disk model transfer complete")
current_status = RunnerShuttingDown()