Ouzhang's picture
Add files using upload-large-folder tool
13c5606 verified
|
Raw
History Blame Contribute Delete
32.1 kB

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; 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/ (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.mds, 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): (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 NOTEs. 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 SampleRefs. 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.

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.

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 Β§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 Β§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/ β€” 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.
  • Eval track (parallel) β€” E1 acceptance-length eval harness β†’ E2 algorithm breadth (new algo = a StrategySpec + loss). Detail: 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 TrainBatches 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.