feat(weight-sync): delta weight sync - #369
Draft
YSunLIN wants to merge 1 commit into
Draft
Conversation
Add a DeltaWeightEncoder interface and a delta_sync opt-in on FullWeightSync for sparse weight sync: diff each FSDP shard against a pinned snapshot and ship only changed (position, value) pairs instead of the full dense all-gather. This is scaffolding only — the encoder is unimplemented and delta_sync=true fails closed until it lands. Default (delta_sync=false) is byte-identical to today's dense NCCL/IPC/Tensor push.
2 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
feat(weight-sync): shard-local delta sync for full-weight rollout updates
Summary
Motivation
In disaggregated RL (dedicated rollout engine,
separate/colocatelayouts), every training step ends with a weight sync: the trained FSDP model is pushed into the rollout engine so the next rollout samples fresh weights. Today all three full-weight transports (NCCLWeightSync,IPCWeightSync,TensorWeightSync) do this densely —FullWeightSync._iter_full_tensors()redistributes every FSDPDTensorshard toReplicate(a whole-mesh all-gather), buckets the full tensors, and ships every byte of the model on every step. At scale this dominates the sync back-edge: sync time grows linearly with parameter count, and for large MoE models it can cost minutes per step.The key observation: under typical RL learning rates, the vast majority of BF16 weight bytes are bit-for-bit unchanged step-over-step. Only a small fraction (~1–3%) of parameter elements actually move each step. Shipping the whole model to communicate a 1–3% change is the waste we want to remove.
Positioning
The existing dense path is not just "the simple version" — it is the all-gather-then-stage pattern that scales with model size regardless of how little changed. A delta path instead scales with how much changed:
(index, value)pairs → broadcast the sparse payload. Cost ∝ changed bytes. No full-tensor all-gather, no rank-0 full-model staging.Delta is a sibling full-weight handler, not a rewrite of the dense ones: it reuses the same
RemoteBROADCASTdispatch, the same bucketed flush mechanism, and the samename_remap/track_prefixrouting. It applies only to full base weights — the LoRA family already ships just the tiny adapter, so delta is out of scope there by construction.Design
1. Config surface. A single additive
delta_sync: bool = falsekwarg onFullWeightSync(threaded through all three transports). When false, behavior is byte-identical to today. When true, the handler consults aDeltaWeightEncoderinstead of pushing dense tensors.2. Encoder interface (decision engine).
DeltaWeightEncoderowns the snapshot + diff, per shard:seed(name, shard)— record the first-sync dense baseline (no delta emitted; the seeding sync is a full dense pass so both sides share a baseline).encode(name, shard) -> SparseDelta— bit-exact integer diff ofshardagainst its pinned snapshot, refresh the snapshot, return the changed positions asSparseDelta(indices: int32 [nnz], values: shard-dtype [nnz]).The diff is bit-exact (integer comparison of the raw bytes), so the reconstructed weights on the rollout side are identical to a dense push — no thresholding, no numeric drift. Snapshots are pinned CPU tensors of the local shard only, so memory overhead is one extra shard-sized copy per rank, independent of world size.
3. Two-phase protocol.
4. Integrity net. Per-flush checksums (reusing
transfer/checksum.py::fingerprint_tensor) verify on the receiver that the applied result matches the trainer's post-step weights, catching any lost or misordered delta without a full read-back.5. Backend integration.
sync()in each transport gains a delta branch:when
self._delta_encoderis set, iterate shard-local deltas instead of_iter_full_tensors(), and drive the matching sparse-apply receiver (update_weights_from_distributed/update_weights_from_ipcgain a sparse payload path on the engine side). The dense branch is unchanged.Test Plan
delta_sync=falserecipes — logprob + weight fingerprint parity vs the current dense push, must remain byte-identical.SKIP=no-commit-to-branch pre-commit run --all-files --show-diff-on-failure.ast.parse); docstring guard passes; no wired runtime path beyond the fail-closed guard ondelta_sync=true.Compatibility / Risk
delta_syncdefaults tofalse, dense semantics preserved across all three transports.delta_syncfails closed until the encoder lands.Reviewer Notes
DeltaWeightEncoder.seed/encodecontract and theSparseDelta(indices, values)wire shape are what everything downstream depends on._iter_full_tensors_ep) produce gathered per-expert HF tensors, not raw local shards — delta over those needs a slot table or a dense fallback for the expert block. Out of scope for the first slice; the seed/steady split makes a per-param dense fallback cheap.