# Spike 008 — VERDICT **Status**: ⚠️ PARTIAL (acceptance criterion redefined; see § "Honest re-statement" below) **Date**: 2026-05-26 **Wave**: 9 **Cross-model review**: BLOCKER 2 of `docs/research/WAVE_7_10_FINAL_REVIEW.md` ## Headline `make_diloco_outer_loop()` wraps `torchft.local_sgd.DiLoCo` (BSD-3, Meta-maintained) and the framework's outer-loop sign convention is pinned by an explicit unit test. **Cross-replica convergence is NOT verified** — the BACKLOG acceptance criterion required two replicas; what shipped is one replica + passthrough no-op `allreduce`. ## Honest re-statement The BACKLOG required: > Smoke test: 2 replicas × 4 inner steps × 2 outer rounds on the toy model > from Spike 005, both replicas converge toward the same solution within > tolerance. What was attempted: the recon doc (`DILOCO_RECONNAISSANCE.md`) provided a "ready-to-paste" 2-replica pattern with a shared-buffer mock allreduce that averages tensors across replicas. **That pattern does not work in single-process**: each `inner.step()`'s post-hook runs `prepare_sync` + `perform_sync` to completion (including outer optimizer step) before yielding back to the caller. By the time replica B's post-hook starts, replica A has already finished its outer step using A's *un*-averaged pseudo-gradient. The mock allreduce can compute the cross-replica mean, but it can't write that mean back into A's `_grads[name]` buffer in time for A's outer step. The fix would require either: - A real `torch.distributed` barrier (NCCL or Gloo) — out of scope for a CPU-only single-process smoke. - A multi-process test using `torch.multiprocessing.spawn` with two real processes — feasible but ~200 LOC of additional test infrastructure that would need its own review. What ships instead: a single-replica machinery test (`allreduce` is a no-op passthrough). Verifies that outer optimizer fires, Nesterov state populates, sign convention is correct. Does NOT verify cross-replica convergence. **This is a redefinition of the BACKLOG acceptance criterion.** Documented explicitly in this verdict + the test file's `_make_passthrough_manager` docstring + composer_diloco.py. ## What the test suite DOES verify (5/5 pass) | Criterion | Status | |---|---| | `allreduce`/`start_quorum`/`should_commit` fire at the right step boundaries | ✅ test 1 | | Nesterov momentum state populated for every parameter | ✅ test 1 | | **Pseudo-gradient sign convention** verified (`θ_initial − θ_local`) with explicit arithmetic prediction | ✅ test 2 | | No regression in Spike 005 imports | ✅ test 3 | | `make_diloco_outer_loop()` factory wraps the right object | ✅ test 4 | | Streaming DiLoCo with 2 fragments + nonzero `fragment_sync_delay` accepts the config | ✅ test 5 | ## Sign convention pinned (the most important result here) Per torchft's `_save_grads()` (line 324 of `torchft/local_sgd.py`): ``` pseudograd = θ_initial − θ_local ``` DiLoCo defines pseudo-gradient as `θ_initial - θ_local`. This is the negative of the local update direction. Standard SGD subtracts gradients (`p ← p - lr * grad`), so the outer step moves in the local-update direction. No negation needed in our outer optimizer wrapper. The test exercises this exact math with `local_param_after_nudge = θ_initial + 0.5` and asserts final ≈ `θ_local`. A sign flip in either `_save_grads` or the outer optimizer would land at `θ_initial - 0.5` (movement in the wrong direction); the test reports both values in the failure message so a future flip is immediately diagnosable. **This is the single best test in Wave 7-10** per the cross-model reviewer. ## What this CLAIMS to close - **V2** (DiLoCo "deferred to v0.2") in `docs/VISION_VALIDATION.md` — re-scored as **⚠️ partial**, not ✅, in the 2026-05-26 update at the bottom of § 3 of that doc. ## What this does NOT close - **True multi-replica convergence** — see § "Honest re-statement" above. Either needs a real `torch.distributed` test on multiple processes, or a redesigned single-process pattern that overrides `_DummyWork.wait()` to do lazy averaging. - **Trainer integration** — `ComposerReplicationTrainer` does NOT yet use `make_diloco_outer_loop`. The DiLoCo wrapper is an independent context manager. Wiring it into the trainer's lifecycle is a separate spike. - **Streaming DiLoCo with `fragment_sync_delay > 0`** (overlapped sync, CUDA streams). The framework's `make_diloco_outer_loop()` accepts the parameter; tests only exercise `delay=0` (vanilla DiLoCo). ## Files - `composer_diloco.py` — `make_diloco_outer_loop()` wrapper. Documents the sign convention. - `tests/test_diloco_smoke.py` — 5 acceptance tests. Test 2 (sign convention) is the highest-value test. ## Dependencies added - `torchft-nightly` (BSD-3, Meta-maintained, `pip install torchft-nightly`) ## Cost / time - Pure CPU, single process, no GPU. - Test suite: ~5 seconds for 5 tests.