Ouzhang's picture
Add files using upload-large-folder tool
31dc8dc verified
Raw
History Blame Contribute Delete
14.2 kB
# coding=utf-8
# Copyright 2024 The SpecForge team. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
"""Launch helpers that wire the DataFlow runtime from a RunConfig.
The training *script* becomes a thin launcher: it parses args, calls one of
these builders, and runs ``TrainerController.fit``. All training logic lives in
the runtime components, not the script. This module wires the **offline EAGLE3**
path end to end:
OfflineManifestReader -> DataFlowController -> SampleRefQueue
-> FeatureDataLoader(process_data, DataCollatorWithPadding)
-> TrainBatch -> Eagle3TrainStrategy -> TrainerCore/Controller -> FSDP
Online wiring (RolloutWorker + SGLangAdapter) composes the same control/data
plane; see ``inference/`` for the equivalent assembly.
"""
from __future__ import annotations
from typing import List, Optional
from specforge.runtime.contracts import SampleRef
from specforge.runtime.control_plane import DataFlowController
from specforge.runtime.data_plane import (
FeatureDataLoader,
FeatureStore,
LocalFeatureStore,
OfflineManifestReader,
)
from specforge.runtime.training.backend import FSDPTrainingBackend, ParallelConfig
from specforge.runtime.training.strategy import Eagle3TrainStrategy
from specforge.runtime.training.trainer import TrainerController, TrainerCore
def _assemble_offline_eagle3(
*,
controller: DataFlowController,
store: FeatureStore,
refs: List[SampleRef],
eagle3_model,
target_head,
optimizer_factory,
run_id: str,
output_dir: str,
max_len: int,
batch_size: int,
accumulation_steps: int,
num_epochs: int,
max_steps: Optional[int],
save_interval: int,
eval_interval: int,
tp_size: int,
sp_ulysses_size: int,
sp_ring_size: int,
logger,
log_interval: int,
):
"""Shared trainer/loader assembly for the offline-shaped EAGLE3 dataflow.
Identical for the colocated (``LocalFeatureStore``) and disaggregated
(``SharedDirFeatureStore``) paths — only the (store, refs) source differs, so
both produce byte-identical batches and training. ``optimizer_factory`` runs
AFTER FSDP-wrap, over the wrapped module's inner draft.
"""
from specforge.data.preprocessing import OfflineEagle3Dataset
from specforge.data.utils import DataCollatorWithPadding
controller.enqueue_offline_refs(refs) # record committed state (enables ack lookup)
trainer_id = controller.register_trainer({"role": "trainer", "run_id": run_id})
# Offline = a fixed, re-iterable ref set (so num_epochs > 1 actually trains
# multiple epochs). The trainer acks at the optimizer-step boundary via ack_fn.
loader = FeatureDataLoader(
store,
refs=refs,
batch_size=batch_size,
collate_fn=DataCollatorWithPadding(),
per_sample_transform=lambda raw: OfflineEagle3Dataset.process_data(
raw, max_len
),
drop_last=True,
strategy="eagle3",
)
parallel = ParallelConfig.from_distributed(
tp_size=tp_size, sp_ulysses_size=sp_ulysses_size, sp_ring_size=sp_ring_size
)
backend = FSDPTrainingBackend(parallel, optimizer_factory=optimizer_factory)
# FSDP-wrap the composite model and build the optimizer over the inner draft
# AFTER wrapping; the strategy MUST run forward through the wrapped module so
# FSDP is actually in the forward/backward path (not bypassed at >1 rank).
wrapped = backend.prepare_model(
eagle3_model, optimizer_target=eagle3_model.draft_model
)
strategy = Eagle3TrainStrategy(wrapped, target_head=target_head)
core = TrainerCore(strategy, backend, accumulation_steps=accumulation_steps)
trainer = TrainerController(
core,
run_id=run_id,
output_dir=output_dir,
num_epochs=num_epochs,
max_steps=max_steps,
save_interval=save_interval,
eval_interval=eval_interval,
log_interval=log_interval,
logger=logger,
ack_fn=lambda ids, step: controller.ack_train_refs(
trainer_id, ids, global_step=step, optimizer_durable=True
),
)
return trainer, loader
def build_offline_eagle3_runtime(
*,
hidden_states_path: str,
eagle3_model,
target_head,
optimizer_factory,
run_id: str,
output_dir: str,
ttt_length: int = 7,
max_len: int = 2048,
batch_size: int = 1,
accumulation_steps: int = 1,
num_epochs: int = 1,
max_steps: Optional[int] = None,
save_interval: int = 0,
eval_interval: int = 0,
tp_size: int = 1,
sp_ulysses_size: int = 1,
sp_ring_size: int = 1,
logger=None,
log_interval: int = 50,
):
"""Assemble the offline-EAGLE3 dataflow (colocated ``LocalFeatureStore``)."""
controller = DataFlowController(run_id)
refs = OfflineManifestReader(
hidden_states_path,
run_id=run_id,
ttt_length=ttt_length,
max_len=max_len,
target_repr="hidden_state",
).read()
store = LocalFeatureStore(run_id)
return _assemble_offline_eagle3(
controller=controller,
store=store,
refs=refs,
eagle3_model=eagle3_model,
target_head=target_head,
optimizer_factory=optimizer_factory,
run_id=run_id,
output_dir=output_dir,
max_len=max_len,
batch_size=batch_size,
accumulation_steps=accumulation_steps,
num_epochs=num_epochs,
max_steps=max_steps,
save_interval=save_interval,
eval_interval=eval_interval,
tp_size=tp_size,
sp_ulysses_size=sp_ulysses_size,
sp_ring_size=sp_ring_size,
logger=logger,
log_interval=log_interval,
)
def build_disagg_eagle3_runtime(
*,
feature_store: FeatureStore,
refs: List[SampleRef],
eagle3_model,
target_head,
optimizer_factory,
run_id: str,
output_dir: str,
max_len: int = 2048,
batch_size: int = 1,
accumulation_steps: int = 1,
num_epochs: int = 1,
max_steps: Optional[int] = None,
save_interval: int = 0,
eval_interval: int = 0,
tp_size: int = 1,
sp_ulysses_size: int = 1,
sp_ring_size: int = 1,
logger=None,
log_interval: int = 50,
):
"""Consumer side of a disaggregated EAGLE3 run.
Trains from a ``feature_store`` whose tensors were produced by a *different
process* (the rollout/ingest pool) on a shared mount — typically a
:class:`SharedDirFeatureStore`. ``refs`` are the ``disagg://`` ``SampleRef``s
the producer published to the manifest
(:func:`data_plane.disagg_ingest.read_ref_manifest`). The trainer assembly is
identical to the colocated offline path, so results match within determinism
tolerance — the only difference is where the feature tensors live.
"""
controller = DataFlowController(run_id)
return _assemble_offline_eagle3(
controller=controller,
store=feature_store,
refs=refs,
eagle3_model=eagle3_model,
target_head=target_head,
optimizer_factory=optimizer_factory,
run_id=run_id,
output_dir=output_dir,
max_len=max_len,
batch_size=batch_size,
accumulation_steps=accumulation_steps,
num_epochs=num_epochs,
max_steps=max_steps,
save_interval=save_interval,
eval_interval=eval_interval,
tp_size=tp_size,
sp_ulysses_size=sp_ulysses_size,
sp_ring_size=sp_ring_size,
logger=logger,
log_interval=log_interval,
)
def build_online_eagle3_runtime(
*,
target_model,
prompts,
eagle3_model,
optimizer_factory,
run_id: str,
output_dir: str,
target_hidden_size: int,
target_vocab_size: Optional[int] = None,
draft_vocab_size: Optional[int] = None,
target_repr: str = "logits",
aux_hidden_state_layer_ids=None,
vocab_map_version: Optional[str] = None,
t2d=None,
num_rollout_workers: int = 1,
device: str = "cuda",
ttt_length: int = 7,
batch_size: int = 1,
accumulation_steps: int = 1,
num_epochs: int = 1,
max_steps: Optional[int] = None,
save_interval: int = 0,
eval_interval: int = 0,
tp_size: int = 1,
sp_ulysses_size: int = 1,
sp_ring_size: int = 1,
collate_fn=None,
logger=None,
):
"""Assemble the online-EAGLE3 dataflow and return
``(trainer, loader, workers, controller, drive_rollout)``.
Mirror of :func:`build_offline_eagle3_runtime`; the only difference is the
*producer* of ``SampleRef``s. Instead of an ``OfflineManifestReader`` reading
``.ckpt`` files, a ``RolloutWorker`` leases ``PromptTask``s, asks the
``target_model`` (any backend exposing ``generate_eagle3_data`` — HF, SGLang,
or custom; **sglang is not required**) for per-sample features via
``SGLangAdapter``, writes them to the ``mem://`` ``FeatureStore``, and commits
``SampleRef``s onto the controller's ``SampleRefQueue``. From ``SampleRef``
down (loader -> strategy -> trainer) the code path is identical to offline.
``prompts`` is the metadata-only PromptTask source (e.g.
``[{"payload": {"input_ids": [...], "loss_mask": [...]}}]``). The returned
``drive_rollout()`` runs the workers until the prompt pool is exhausted,
populating the queue the loader consumes; the launcher script calls it before
``trainer.fit(loader)``. (Fully-async rollout/train interleaving with
backpressure is the control-plane's job — a follow-up, not this seam.)
``target_head`` is ``None`` on purpose: online rollout already materialized the
``target`` distribution, so the strategy consumes it directly rather than
re-running an lm-head (that is the offline ``hidden_state`` path's job).
"""
import torch
from specforge.runtime.inference.capture import CaptureConfig
from specforge.runtime.inference.rollout_worker import RolloutWorker
from specforge.runtime.inference.sglang_adapter import SGLangAdapter
controller = DataFlowController(run_id)
controller.ingest_prompts(prompts)
# PR8 colocated store has no residency cap (max_resident_bytes is the M5
# backpressure follow-up); mirror the offline launcher's plain construction.
store = LocalFeatureStore(run_id)
if aux_hidden_state_layer_ids is None:
aux_hidden_state_layer_ids = tuple(
getattr(target_model, "aux_hidden_states_layers", ()) or ()
)
adapter = SGLangAdapter(target_model, device=device, t2d=t2d)
capture = CaptureConfig.from_strategy(
required_features=Eagle3TrainStrategy.required_features,
aux_hidden_state_layer_ids=tuple(aux_hidden_state_layer_ids),
target_repr=target_repr,
target_hidden_size=target_hidden_size,
target_vocab_size=target_vocab_size,
draft_vocab_size=draft_vocab_size,
vocab_map_version=vocab_map_version,
)
workers = [
RolloutWorker(
controller,
store,
adapter,
capture,
run_id=run_id,
worker_id=f"rollout-{i}",
)
for i in range(num_rollout_workers)
]
# Queue mode (online consume-once stream). Online features arrive from the
# adapter already in train form (input_ids/attention_mask/loss_mask/
# hidden_state/target), so there is no per_sample_transform (unlike offline).
def _cat_collate(feats):
# Concatenate per-sample features along the batch dim. The offline
# ``DataCollatorWithPadding`` assumes 2D (B,n) inputs and would choke on
# the 3D hidden_state/target tensors; online features are pre-formed, so
# a plain cat is correct for equal-length / batch_size=1 batches (the
# adapter already groups equal-length prompts). Variable-length padded
# batching is a follow-up; pass ``collate_fn`` to override.
return {k: torch.cat([f[k] for f in feats], dim=0) for k in feats[0]}
loader = FeatureDataLoader(
store,
controller.sample_queue,
batch_size=batch_size,
collate_fn=collate_fn or _cat_collate,
drop_last=True,
strategy="eagle3",
)
parallel = ParallelConfig.from_distributed(
tp_size=tp_size, sp_ulysses_size=sp_ulysses_size, sp_ring_size=sp_ring_size
)
backend = FSDPTrainingBackend(parallel, optimizer_factory=optimizer_factory)
wrapped = backend.prepare_model(
eagle3_model, optimizer_target=eagle3_model.draft_model
)
strategy = Eagle3TrainStrategy(wrapped, target_head=None)
core = TrainerCore(strategy, backend, accumulation_steps=accumulation_steps)
trainer_id = controller.register_trainer({"role": "trainer", "run_id": run_id})
trainer = TrainerController(
core,
run_id=run_id,
output_dir=output_dir,
num_epochs=num_epochs,
max_steps=max_steps,
save_interval=save_interval,
eval_interval=eval_interval,
logger=logger,
ack_fn=lambda ids, step: controller.ack_train_refs(
trainer_id, ids, global_step=step, optimizer_durable=True
),
)
def drive_rollout(max_rounds: int = 100_000) -> int:
"""Run the workers until the prompt pool drains; returns refs produced."""
for w in workers:
w.start()
produced = 0
lease = max(batch_size * 8, 8)
for _ in range(max_rounds):
got = sum(len(w.run_once(max_tasks=lease)) for w in workers)
if got == 0:
break
produced += got
return produced
return trainer, loader, workers, controller, drive_rollout
# Backward-compatible alias for early branch users.
build_offline_eagle3_controller = build_offline_eagle3_runtime
__all__ = [
"build_offline_eagle3_controller",
"build_offline_eagle3_runtime",
"build_disagg_eagle3_runtime",
"build_online_eagle3_runtime",
]