File size: 32,095 Bytes
13c5606 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 | # SpecForge Architecture Plan (Reconciled)
> **Status**: Draft β reconciles the original "SpecForge Redesign Plan" (a from-scratch,
> torchrun-native, HTTP-only target architecture) with the **landed DataFlow `runtime/`
> spine** (control/data/inference/training planes, SampleRef + FeatureStore + Mooncake).
>
> **Supersedes** the previous redesign draft. That draft's detailed component sketches
> (CheckpointManager, Evaluator, Pydantic config, MLA draft, SGLang export, train-with-decode)
> remain valid as the **domain-layer target** and are preserved verbatim in
> [`docs/redesign-draft-legacy.md`](docs/redesign-draft-legacy.md); this document
> re-frames *where they sit* now that the DataFlow runtime exists, and corrects the one bet
> the original draft got wrong for our actual workloads (see Β§6).
---
## 0. TL;DR β the decision
There were two architecture efforts in flight for the same goal (organize SpecForge so new
draft models / training modes / disaggregation compose cleanly without a `train_XXX.py` per
method):
- **The redesign draft** (this doc's predecessor): clean domain decomposition
(`models/drafts` registry, `TargetEngine`, `HiddenStateStream`, `DraftTrainStrategy`,
`Trainer`, config/eval/export/cli) β but **explicitly torchrun-native with no
Ray/Mooncake**; disaggregation was "the target is an HTTP service" (`RemoteStream`).
- **The DataFlow `runtime/` spine** (landed: the `dataflow-up-1..20` stack, M1βM7/O1): a
metadata-only **control plane** + a tensor-carrying **data plane** (`SampleRef` +
`FeatureStore` incl. **Mooncake** + `StreamingRefChannel` + durable metadata + backpressure
+ resume), with disaggregated producer/consumer and an interleaved online loop.
**The fact that settles it:** there is a real **multi-node / >100 GB/s / isolated-pool**
requirement (separate rollout and trainer pools, no shared FS / no viable HTTP path for the
hidden-state volume). That **breaks the original draft's central bet** ("HTTP/gRPC to SGLang
is sufficient; Mooncake only matters >100 GB/s which we never hit"). So the runtime's
Mooncake control/data plane is **necessary, not premature**.
**Decision: do not pick one β layer them.**
- **Canonical substrate = the DataFlow `runtime/` control + data plane.** `SampleRef` (metadata
on the control plane) + `FeatureStore` (tensors, Local/SharedDir/Mooncake) + `FeatureDataLoader`
β `TrainBatch` is the canonical data path. online / offline / disaggregated converge at
`SampleRef`.
- **Front-end = the redesign draft's domain layer.** `TargetEngine` / `DraftTrainStrategy` /
`Trainer` / `CheckpointManager` / `Evaluator` / config / eval / export / cli become the
user-facing surface. The **canonical "stream" is `FeatureDataLoader` over `SampleRef` +
`FeatureStore`** β we do **not** re-introduce a `HiddenStateStream` as a separate source of
truth (the loader already plays that role). The online/offline/disaggregated variation lives
in the *ref source* (`OfflineManifestReader` / `RolloutWorker` / `StreamingRefChannel`) + the
*transport* (`FeatureStore`: Local for colocated, Mooncake/SharedDir for isolated pools), and
is shielded from training by `FeatureDataLoader β TrainBatch`. (`RemoteStream`-over-HTTP is
just one more ref source / engine β the light cross-node option, not the base abstraction.)
This is, in the predecessor's own terms, pulling **feature #32 ("Mooncake streaming backend")
forward from Phase 7 to now** because the requirement demands it β and implementing it with the
already-built `runtime/` work, behind the clean domain interfaces.
**Two scope decisions that bound this plan** (consolidating with the online-disaggregation
roadmap, #618):
1. **Frozen target β no weight sync.** "Train-with-decode" means a *frozen* target streams
hidden states; the draft is never in the generation loop. Weight sync / hot draft-update /
weight-version registry / on-policy are **out of scope**; `draft_weight_version` is provenance
only. (This removes the predecessor's W4 weight-lifecycle workload.)
2. **Ray is an open decision, not a non-goal.** It is a *candidate* for the O2 scale-out
orchestrator (multi-node N-producer/M-trainer) β likely necessary, but not committed. Until the
decision gate fires we keep the home-grown metadata-only control plane and add nothing.
**Per-phase target/implementation detail lives in [`docs/roadmap/`](docs/roadmap/)** (domain,
online-disaggregation [folds in #618], eval & breadth); this document is the architecture; the
roadmap is the build order.
---
## 1. Why each side is right (and where each is incomplete)
Two orthogonal slicings. The runtime is sliced by **plane** (how bytes/metadata move); the
draft is sliced by **domain object** (draft / target / stream / strategy). They compose; they
do not conflict.
| Layer | Verdict | Rationale |
|---|---|---|
| **control plane** (`runtime/control_plane`) | **Keep β canonical. No predecessor equivalent.** | Metadata-only (every entrypoint runs `assert_no_tensors`), lease/ack/dedup/reconcile/backpressure behind a `MetadataStore` seam. This is exactly the cross-pool machinery the isolated-pool requirement needs, and the HTTP design has nothing like it. |
| **data plane** (`runtime/data_plane`) | **Keep β canonical; it *is* the stream.** | `SampleRef` (metadata) Γ· `FeatureStore` (tensors) Γ· `FeatureDataLoader` (materialize) is a *finer* decomposition than the predecessor's coarse `HiddenStateStream`, and it's the better substrate for backpressure / lease-ack / resume / Mooncake. `FeatureDataLoader` over `SampleRef`+`FeatureStore` already plays the role plan.md gave `HiddenStateStream`, so **no separate `HiddenStateStream` source-of-truth is introduced** β `seek()`/prefetch land on the loader/ref-source; `FeatureStore` is the transport swap point. |
| **inference plane** (`runtime/inference`) | **Converge to the predecessor's `TargetEngine`.** | Today it conflates "wrap a target engine" + "capture" inside `SGLangAdapter`, and is bound to `generate_eagle3_data` / EAGLE3 names. The `FeatureSource` Protocol already exists (good); the gap is a real `TargetEngine` abstraction (hf / sglang / **sglang_server** / custom) + de-EAGLE3-ifying. This is the layer that most borrows the draft. |
| **training plane** (`runtime/training`) | **Keep the seam; fill the managers.** | `DraftTrainStrategy` / `TrainerCore` / `TrainingBackend` (+ the `StepContext` added for Domino) is *already* the predecessor's `training/strategies` shape β keep it. What's missing are the **managers**, not the seam: `no_sync()` accumulation, full optimizer/scheduler/RNG resume, `CheckpointManager` (rotation/best), `Evaluator`. |
**Landed (merged β the spine):**
- `runtime/` planes (M1βM7/O1): control/data/inference/training, `SampleRef`, `FeatureStore`
(Local/SharedDir/Mooncake), `StreamingRefChannel`, durable `MetadataStore`, disagg
producer/consumer + interleaved online loop.
**In the stacked PRs #627/#628/#629 (validated, in review β not yet merged):**
- **Composable launch** (`StrategySpec` registry + parameterized `launch.py`): adding a model
is a spec entry, not a `build_*_runtime` family. eagle3 / **dflash** / **domino** all train
end-to-end through one strategy-parameterized path (validated: 197 `tests/test_runtime` OK on
H200). Domino added `StepContext{global_step, total_steps}` threaded through `forward_loss` β
the one deliberate contract extension for schedule-dependent loss.
**Explicitly not yet implemented in `runtime/`** (flagged in-source β `contracts.py`,
`trainer.py`, `controller.py`, both `DESIGN.md`s, `runtime/README.md`): live frozen-target online
capture from a real SGLang server (O1.3), full optimizer/scheduler resume, and `no_sync()`
accumulation. These are the Β§3 gaps. (The in-source weight-publication NOTEs β `WeightVersion` /
`WeightPublisher` / `update_draft_weights` β are **descoped**: the target is frozen, see Β§8.)
---
## 2. Target architecture
### 2.1 Canonical data path (unchanged from the runtime, this is the spine)
```
ref source ββ
OfflineManifestReader β (offline: re-iterable refs)
RolloutWorker β (online: produced into a SampleRefQueue)
StreamingRefChannel β (disaggregated: cross-process/pool stream)
βΌ
DataFlowController ββ metadata only (SampleRef), assert_no_tensors
β
FeatureStore ββ tensors only: LocalFeatureStore (mem://, colocated)
SharedDirFeatureStore (shared mount)
MooncakeFeatureStore (RDMA, isolated pools)
βΌ
FeatureDataLoader (per_sample_transform + collate) ββΊ TrainBatch
βΌ
DraftTrainStrategy.forward_loss(batch, ctx) ββΊ TrainerCore / TrainerController ββΊ FSDP
```
### 2.2 Domain layer on top (user-facing abstractions over the substrate)
The substrate's source of truth is `SampleRef` + `FeatureStore` + `FeatureDataLoader` (Β§2.1).
The domain layer does **not** re-introduce a `HiddenStateStream` as a parallel source of truth β
`FeatureDataLoader` already *is* the stream. The domain layer is what user/training code sees:
```
PRODUCE (inference):
TargetEngine ββ the engine rollout wraps to produce features; the abstraction that
replaces the EAGLE3-bound SGLangAdapter.
HFTargetEngine / SGLangTargetEngine / CustomTargetEngine (in-process)
SGLangServerEngine (frozen target as a live SGLang server, cross-node). ONE engine, two
feature transports (the engine is identical; only WHERE features land differs):
Β· capture transport β engine-side hook writes hidden states INTO a FeatureStore
(Mooncake/SharedDir). This is W3 / online O1.3.
Β· inline-HTTP transport β features serialized in the HTTP response, no shared store
(RemoteStream-style). This is the light W3β² path, the one case features do NOT live
in a FeatureStore.
RolloutWorker ββ drives a TargetEngine β writes tensors to FeatureStore β commits
SampleRef to the control plane. Stays at the domainβsubstrate seam.
CONSUME (training):
Trainer ββ owns the lifecycle: loop / eval / checkpoint. WRAPS the
TrainerCore/TrainerController seam; does NOT replace it. (No weight-sync β
the target is frozen, Β§8.)
DraftTrainStrategy ββ per-algorithm forward+loss (eagle3 TTT / dflash block / domino). The
per-step seam; lives in training/strategies (relocated from
runtime/training in E0) β already plan-shaped.
CheckpointManager / Evaluator / lr_scheduler / fsdp seam ββ the managers (G1).
COMPOSE:
modeling/draft ββ DRAFT_REGISTRY (@register_draft) for draft *architecture* classes
(llama / deepseek-MLA / dflash-qwen3). Separate axis from strategy.
StrategyRegistry ββ per-algorithm spec (today's StrategySpec, converged here).
config / cli / export ββ first-class run surface (predecessor Β§4.3β4.8, carried forward).
```
The online / offline / disaggregated distinction is **not visible to `Trainer`/strategy** β it
is fully absorbed by (ref source + `FeatureStore`) behind `FeatureDataLoader β TrainBatch`.
### 2.3 Module layout (runtime spine + domain layer)
```
specforge/
βββ runtime/ # SUBSTRATE ONLY β the canonical DataFlow spine (keep as-is)
β βββ contracts.py # SampleRef, TrainBatch, PromptTask, *Strategy literal
β βββ control_plane/ # metadata-only: controller, metadata_store, backpressure
β βββ data_plane/ # FeatureStore (Local/SharedDir/Mooncake), FeatureDataLoader,
β # SampleRefQueue, StreamingRefChannel, offline/disagg readers
β
βββ inference/ # TOP-LEVEL β the single home for all rollout/capture execution
β βββ rollout_worker.py # (from runtime/inference/rollout_worker.py)
β βββ capture.py # CaptureConfig (from runtime/inference/capture.py)
β βββ feature_source.py # the FeatureSource Protocol (the worker's only contract)
β βββ target_engine/ # TargetEngine, EXTRACTED from modeling/target/*TargetModel
β β βββ base.py factory.py # (adapters then wrap an engine, Β§G2)
β β βββ hf.py sglang.py sglang_server.py custom.py # per-backend generic engines
β β βββ sglang_capture_backend.py # sglang version-pinned glue
β βββ adapters/
β βββ eagle3.py dflash.py # (from runtime/inference/{sglang,dflash}_adapter.py)
β
βββ training/ # TOP-LEVEL β the single home for all training execution (NO facade)
β βββ trainer.py # domain Trainer: owns loop/eval/checkpoint (B3)
β βββ controller.py # TrainerCore + TrainerController seam (from runtime/training/trainer.py)
β βββ backend.py # FSDPTrainingBackend + no_sync (from runtime/training/backend.py)
β βββ checkpoint.py resume.py evaluator.py # NEW managers (Β§3) β born here
β βββ strategies/ # StrategySpec registry + per-algorithm strategies (converges HERE)
β βββ base.py # DraftTrainStrategy ABC + StepContext (from runtime/training/strategy.py)
β βββ registry.py # StrategySpec / resolve_strategy (from runtime/training/registry.py)
β βββ eagle3.py dflash.py domino.py
β
βββ modeling/ # MODEL DEFINITIONS ONLY β no orchestration, no capture factory
β βββ draft/ # DRAFT_REGISTRY + @register_draft (NEW registry.py β predecessor Β§4.2)
β β βββ base.py registry.py llama3_eagle.py deepseek_eagle3.py(MLA) dflash.py flex_attention.py
β βββ target/ # target model nn.Modules + model-specific glue ONLY
β βββ target_head.py custom_backend/ sglang_backend/ # (capture orchestration moved to inference/)
β
β # (NO separate data/streams package β FeatureDataLoader over
β # SampleRef+FeatureStore IS the stream. Ref sources
β # (offline/rollout/streaming) live in runtime/data_plane;
β # live frozen-target capture is just another ref source)
β
βββ launch.py # TOP-LEVEL β topology assembly only (spec-driven builders; from runtime/launch.py)
βββ eval/ export/ config/ cli.py # NEW β carried forward from predecessor Β§4.4β4.8
βββ core/ optimizer.py tracker.py distributed.py lr_scheduler.py # kept verbatim (predecessor Β§1)
```
> **One implementation home per concern; `runtime/` is substrate-only.** The two structural moves
> (both gated by the byte-identical suite, executed as the move-only step `E0` β see
> [docs/roadmap/domain-refactor.md](docs/roadmap/domain-refactor.md)): (1) extract `TargetEngine`
> out of the EAGLE3-bound `modeling/target/*TargetModel` into top-level `inference/target_engine/`
> (the `adapters/` then wrap an engine); (2) lift the training seam
> (`TrainerCore`/`TrainerController`/`TrainingBackend`/strategy+registry) up into top-level
> `training/`. `runtime/` shrinks to the substrate (control + data plane + contracts) β it is
> **not** a home for domain/algorithm code, and there is **no facade package**. The control + data
> planes stay exactly where they are; the online/offline/disaggregated distinction stays absorbed
> by (ref source + `FeatureStore`) behind `FeatureDataLoader`, invisible to `training/`.
---
## 3. Gaps to close (prioritized) β what the domain layer adds on top of the spine
These are the items the landed `runtime/` does **not** have, drawn from the predecessor and
from the in-source `NOTE`s. Each lands behind the canonical spine without re-plumbing it.
### G1 β Training managers (highest leverage; the spine works but is bare)
- **`no_sync()` accumulation.** `runtime/training` has **zero** `no_sync()` β FSDP all-reduces
every micro-step, defeating `accumulation_steps`. (Predecessor Β§4.2 #10; verify with one
profiler check: one all-reduce per `optimizer.step()`.)
- **Full resume.** `save_checkpoint` persists training state only; no optimizer/scheduler/RNG
restore, no rotation, no best-tracking. Add `CheckpointManager` (predecessor Β§4.3) + a
`seek()`-equivalent on the colocated streams.
- **`Evaluator` + `EvalCache`.** No `simulated_acc_len` / per-position-acc / best-checkpoint.
(Predecessor Β§4.4 β per-position acc aggregated across batches *before* the geometric sum.)
- **lr WSD + per-strategy `fsdp_wrap_policy()`** (predecessor Β§4.6, Β§4.2).
### G2 β `TargetEngine` abstraction (inference convergence)
- Introduce a `TargetEngine` ABC **extracted from the `modeling/target/*TargetModel` classes**
(`Eagle3TargetModel`, `DFlashTargetModel`); the `runtime/inference` adapters
(`SGLangAdapter`/`DFlashAdapter`) then **wrap** a `TargetEngine` rather than being the engine,
and stop binding to `generate_eagle3_data` / EAGLE3 names. Keep the existing `FeatureSource`
Protocol. Add `SGLangServerEngine` (live SGLang server) as the cross-node engine β see Β§2.2 for
its two feature transports (capture-into-FeatureStore for W3/O1.3, inline-HTTP for the light
W3β²).
### G3 β Live online capture (frozen target; **no** weight sync)
- Replace the in-process generator with **live SGLang-server hidden-state capture**: a *frozen*
target streams aux+final hidden states into `MooncakeFeatureStore`; the producer commits
`SampleRef`s. The cross-process control plane + async loop are in-review; live capture (the
gating spike) is next; scale-out (Ray = **open**) and hardening follow.
- **Weight sync / hot draft-update / on-policy are explicitly out of scope.** The target is
frozen, so the streamed data is independent of draft weights β there is nothing to re-sync and
no staleness. `draft_weight_version` is kept **only as provenance**.
- Detailed phases (O1βO3): [`docs/roadmap/online-disaggregation.md`](docs/roadmap/online-disaggregation.md).
### G4 β Composition + models (predecessor Phase 1)
- `modeling/draft` `DRAFT_REGISTRY` (`@register_draft`) for draft **architectures** (separate
axis from strategy); **MLA Eagle3 draft** (deepseek/Kimi); converge `StrategySpec` β
`training/strategies` registry. Note: draft-arch registry and strategy registry are two
registries, not one.
### G5 β Run surface (predecessor Phase 3)
- Pydantic `config/` + `specforge` CLI; `export/to_sglang` (+ the documented MLA weight-name
map) and `export/to_hf` with vocab pruning.
---
## 4. Topologies & the colocated lightweight path
The workloads differ only in which *ref source* + `FeatureStore` + `TargetEngine` compose. All
consume **one** `FeatureDataLoader β TrainBatch` iterator; the trainer/strategy/backend are
identical regardless (no per-topology stream class). **In every case the target is frozen** β the
draft is never in the generation loop, so the streamed features do not depend on draft training
progress (no staleness, nothing to re-sync).
| # | Workload | Ref source (β FeatureDataLoader) | FeatureStore | Control plane |
|---|---|---|---|---|
| W1 | Offline (precomputed) | `OfflineManifestReader` (refs) | Local (`file://`/`mem://`) | **no-op** |
| W2 | In-process online (frozen target) | `RolloutWorker` β `SampleRefQueue` | Local (`mem://`) | **no-op** |
| W3 | Disaggregated online (frozen target; isolated pools, high BW) | `RolloutWorker` (producer pool) β `StreamingRefChannel`, `SGLangServerEngine` *capture transport* | **Mooncake** | **active** (lease/ack/reconcile/backpressure) |
| W3β² | Disaggregated online (light/cross-node) | `SGLangServerEngine` *inline-HTTP transport* (RemoteStream-style source) | n/a β features inline over HTTP, no shared store | minimal |
> **"Train-with-decode" = live *frozen-target* generation** β i.e. W2 (colocated) or W3
> (disaggregated), **not** a separate workload. The predecessor's dual-purpose
> serve-and-push-weights "W4" is **out of scope**: a frozen target means there is nothing to push.
> See [`docs/roadmap/online-disaggregation.md`](docs/roadmap/online-disaggregation.md).
**Colocated lightweight path (W1/W2): keep it, but as a *no-op control plane*, not a fork.**
The control-plane machinery (lease/ack/reconcile/backpressure/durable metadata/cross-process
transport) only earns its keep when producer and consumer are decoupled. For W1/W2 (same
process) it is pure overhead. Resolution:
- **Single canonical path** through `SampleRef` + `FeatureStore`, so colocated and disaggregated
produce **byte-identical batches for free** (today's property β "disaggregation changes *where*
features live, not their values"). We do **not** fork a second trainer/launch path.
- "Lightweight" = for colocated, the controller's lease/ack/metadata-store are **opt-in / no-op**,
`FeatureStore` is `LocalFeatureStore(mem://)`, and there is **no SQLite, no cross-process, no
backpressure**. The colocated ref sources (`OfflineManifestReader` / in-process `RolloutWorker`)
flow through the spine but skip the heavy bits.
- Cost to own honestly: this preserves the free byte-identical guarantee **only if** we keep one
path. A numerical-equivalence gate (Β§7) locks "colocated stream output == disagg stream output"
so the no-op path can never silently diverge.
---
## 5. What changed from the predecessor draft (explicit deltas)
1. **"No Mooncake in Phase 1-6" β revised.** The Mooncake control/data plane is now the
**canonical** disaggregation backend, because the isolated-pool / >100 GB/s requirement is
real and HTTP cannot serve it. The predecessor's `RemoteStream`-over-HTTP remains as the
**light** second backend (W3β²). Mooncake is used as a *transport*; the scale-out
**orchestration** layer (Ray vs. home-grown) is an **open decision** (see Β§6, Β§8 and
[`docs/roadmap/online-disaggregation.md`](docs/roadmap/online-disaggregation.md) Β§O2), no longer
a flat non-goal.
2. **Primary abstraction / `HiddenStateStream` dropped.** Predecessor: a coarse
`HiddenStateStream` produces `TrainBatch` and is the source of truth. Reconciled:
`SampleRef` + `FeatureStore` + `FeatureDataLoader` is canonical, and `FeatureDataLoader`
**already is** the stream β so `HiddenStateStream` is **not introduced** as a parallel
abstraction. Topology variation lives in (ref source + `FeatureStore`); `seek()`/prefetch
land on the loader/ref-source. This is strictly finer and is what makes
lease/ack/backpressure/Mooncake clean.
3. **`runtime/` is not "thrown away."** The predecessor's "what we throw away" never mentioned
`runtime/` (it predated it). The DataFlow spine is the foundation, not debt.
4. **Module layout** gains a top-level `runtime/` (the spine) under the domain layer; `models/`
(drafts + targets), `training/` (Trainer + managers), `eval/`, `export/`, `config/`, `cli.py`
are the layer on top. No separate `data/streams/` package β the loader is the stream.
5. **W4 weight lifecycle β dropped.** The predecessor's serve-and-push-weights workload + weight
registry are **out of scope**. "Train-with-decode" is *frozen-target* live generation (W2/W3);
weight sync / hot draft-update / staleness gate / on-policy are explicitly cut, aligning with
#618. `draft_weight_version` survives only as provenance metadata.
6. **Ray reframed from non-goal β open.** It is a *candidate* for the O2 scale-out orchestrator
(multi-node N-producer/M-trainer), not committed and not forbidden; the decision gate lives in
the roadmap.
Everything else in the predecessor (workloads W1βW3, the domain abstractions, the testing
discipline, the MLA/export/eval/config detail) **carries forward unchanged**.
---
## 6. Tradeoffs (updated)
### Mooncake transport vs HTTP/gRPC β *transport bet reversed*
The predecessor bet "HTTP is sufficient, Mooncake is gated behind profiling." For the **data
transport** that bet is now wrong (isolated-pool / >100 GB/s requirement) β Mooncake is in,
canonically. The `RemoteStream`-over-HTTP path stays as the light cross-node backend (W3β²).
### Scale-out orchestration: Ray vs. home-grown β *OPEN*
Today's metadata-only control plane handles a single producer-pool β trainer scope. **O2**
(multi-node N-producer/M-trainer scale-out) needs an orchestration layer, and **whether that is
Ray or a home-grown scheduler is undecided** β likely necessary, but not committed. It is **not**
a non-goal anymore; the decision gate (when β₯1 of: >1 producer pool, cross-node failover,
multi-job pool sharing) lives in
[`docs/roadmap/online-disaggregation.md`](docs/roadmap/online-disaggregation.md) Β§O2. Until then
we keep the home-grown control plane and add nothing.
### One canonical path vs a light colocated fork
Chosen: **one path** (spine everywhere) + control-plane-as-no-op for colocated. Keeps
byte-identical free and avoids two implementations; costs an equivalence gate. (See Β§4.)
### Two registries, not one
`modeling/draft` `DRAFT_REGISTRY` (architecture) and `training/strategies` (algorithm) are
**separate** axes β an algorithm (eagle3) runs on multiple draft architectures. Don't merge
them; the current `StrategySpec` is the *strategy* registry and should converge there, not into
`modeling/draft`.
*(The predecessor's other tradeoffs β Pydantic vs OmegaConf, MLA cache compressed vs expanded,
registry vs HF AutoModel β are unchanged and carry forward.)*
---
## 7. Migration path (incremental, gated)
The spine is already landed, so migration is "grow the domain layer on top + close the gaps,"
not a rewrite. Every phase that touches the training path passes the numerical-equivalence gate
(below) against the prior commit. **Per-phase target/implementation/tests/done-when detail lives
in [`docs/roadmap/`](docs/roadmap/)** β the phases below are the index; the online track there
also folds in the former online-disaggregation roadmap (#618).
- **Phase A β composable launch (in review).** `StrategySpec` registry + parameterized
`launch.py`; eagle3/dflash/domino end-to-end in the stacked PRs #627/#628/#629 (validated,
in review). 197 `tests/test_runtime` OK.
- **Phase B β domain abstractions (no behavior change).** Introduce `TargetEngine` (wrap the
existing adapters; de-EAGLE3 the names) and the domain `Trainer` wrapping the kept
`runtime/training` core. No `HiddenStateStream` β `FeatureDataLoader` stays the stream. Gate:
byte-identical batches/loss vs the pre-refactor path.
- **Phase C β colocated no-op control plane + equivalence gate.** Make lease/ack/metadata
opt-in; add the colocatedβ‘disagg equivalence test.
- **Phase D β training managers (G1).** `no_sync()`, full optimizer/scheduler/RNG resume,
`CheckpointManager`, `Evaluator`. Gate: loss/eval parity at fixed steps.
- **Phase E0 β layout consolidation (move-only).** Relocate the training seam + target engine
into the top-level `training/` / `inference/` homes; `runtime/` shrinks to substrate. Pure
`git mv` + import shims; gate: suite + B byte-identical gate unchanged (no functional diff).
- **Phase E β `modeling/draft` registry + MLA draft + config/CLI/export (G4/G5).** Adding a
draft arch = one decorated file; one validated YAML per run; export-loop test.
- **Online track (parallel; G3) β live *frozen-target* capture.** O1.1 shared control plane +
O1.2 async loop (in review) β **O1.3** live SGLang-server hidden-state capture (next; gated by a
throughput spike) β **O2** scale-out (orchestrator: Ray = open) β **O3** hardening. **No weight
sync.** Detail: [`docs/roadmap/online-disaggregation.md`](docs/roadmap/online-disaggregation.md).
- **Eval track (parallel) β E1** acceptance-length eval harness β **E2** algorithm breadth (new
algo = a `StrategySpec` + loss). Detail: [`docs/roadmap/eval-and-breadth.md`](docs/roadmap/eval-and-breadth.md).
Doc debt to fix alongside Phase B: revise the predecessor's "No Mooncake / HTTP is sufficient"
statements (now Β§5/Β§6) so the code and the plan stop contradicting each other.
---
## 8. Non-goals (updated)
- **No weight sync / hot draft-update / on-policy training.** The target is **frozen**; the
draft is never in the generation loop. No `WeightPublisher`, no weight-version registry, no
staleness gate. `draft_weight_version` is provenance metadata only.
- **No two-stack fork for colocated.** One canonical data path; colocated is the spine with the
control plane as a no-op, not a parallel implementation.
- **No vLLM target backend** (possible via `TargetEngine`, not prioritized).
- **No multi-engine load balancing / multi-job inference-pool sharing** for now β gated behind
β₯5 concurrent jobs sharing one target.
**Open decisions (NOT non-goals):**
- **Ray (or a home-grown scheduler) for O2 scale-out** β likely necessary for multi-node
N-producer/M-trainer; undecided. Decision gate in Β§6 / the online roadmap.
*(Reversed from the predecessor: "No Mooncake" β Mooncake transport is now canonical for W3;
"No Ray" β Ray is now an open scale-out decision, not a flat non-goal.)*
---
## 9. Success criteria
| Area | Criterion |
|---|---|
| Composable launch (in review) | eagle3/dflash/domino train via one strategy-parameterized path; full `test_runtime` green. |
| Abstractions (B) | `TargetEngine` (wrapping existing adapters) + domain `Trainer` produce byte-identical batches/loss vs the direct spine path. |
| Colocated (C) | W1/W2 run with control plane as no-op; colocatedβ‘disagg equivalence gate passes. |
| Managers (D) | resume reproduces the no-resume loss curve; one all-reduce per optimizer step; best-checkpoint tracked. |
| Drafts/MLA (E) | MLA Eagle3 trains + loads in SGLang; new draft arch = one `@register_draft` file. |
| Online (O1.3) | a live **frozen-target** SGLang server feeds training with zero precomputed features; loss/eval matches the offline baseline on the same prompts+seed. (Scale-out O2 / Ray = open.) |
---
## 10. Testing strategy (gating)
Carries the predecessor's gates, plus the reconciliation-specific one.
- **10.1 Numerical-equivalence gate** (per PR touching `core/` / `training/` /
`runtime/data_plane/` / `modeling/draft`): fixed seed, 3Γ4 batches; per-step loss
`atol/rtol=1e-4` at steps 0/1/100/500/1000; per-position eval acc + `simulated_acc_len`
`atol=1e-3`.
- **10.2 Colocated β‘ disaggregated gate (NEW):** same data through the colocated (no-op control
plane, Local store) and the disaggregated (control plane, SharedDir/Mooncake) paths must yield
identical `TrainBatch`es and identical loss. This is what licenses the lightweight path.
- **10.3 Smoke tests:** each draft arch trains 20 steps under TP=1 / TP=2 / TP=2+SP=2;
checkpoint save+resume matches no-resume (validates `seek`); eval cache miss==hit.
- **10.4 Distributed correctness:** MLA + Yunchang USP with asymmetric head dims; gradient
accumulation = one all-reduce per `optimizer.step()`.
- **10.5 Export-loop test:** train MLA draft 100 steps β export β load in SGLang β 32 gens,
acceptance > 0 (catches weight-name-map regressions).
- **10.6 Online-capture parity gate (O1.3):** a live frozen-target capture run produces
features + loss matching the offline-precomputed baseline on the same prompts/seed (the target
is frozen, so this must hold exactly up to nondeterminism tolerance). No weight-sync gate β
weight sync is out of scope.
|