Skip to content

[trainer, cfg, tests] feat: add architecture-neutral distillation trainer control plane - #545

Draft
NancyFyong wants to merge 7 commits into
mainfrom
distillation-acceleration
Draft

[trainer, cfg, tests] feat: add architecture-neutral distillation trainer control plane#545
NancyFyong wants to merge 7 commits into
mainfrom
distillation-acceleration

Conversation

@NancyFyong

@NancyFyong NancyFyong commented Sep 5, 2026

Copy link
Copy Markdown
Collaborator

What does this PR do?

Draft — architecture review for PR 1 of the distribution-matching distillation stack.

Add one architecture-neutral control plane, immutable execution plans, recipe registries, pure tensor utilities, and an OPD-separated configuration seam. Related design: #519; detailed architecture and acceptance criteria: #520.

This slice deliberately does not train a model. The production constructor accepts the existing diffusion trainer interface, but model-worker initialization stops at the explicit PR 2 boundary. PR 2 adds the data plane; model adapters are subsequent work.

Reviewed head: d7a6b99752dadcb42d7538d8e790438a7a9c7a6a. Diff: 19 files, +3,251 / −4. The companion data-plane Draft PR #546 is stacked on this head, not included here.

Checklist Before Starting

Non-duplication: existing OPD work (#293, #498, #375, #495) concerns teacher scoring/scheduling around policy-gradient trajectories. It does not implement the student/fake-score optimizer topology or this phase-control contract. Existing fork-only review PRs are development staging, not another upstream submission.

Test

At the reviewed head, rerun before publication:

PYTHONPATH=. TORCH_COMPILE_DISABLE=1 TORCHINDUCTOR_DISABLE=1 \
  python -m pytest tests/trainer/diffusion --no-cov -q
# 402 passed

bash scripts/generate_trainer_config.sh
# generated configs unchanged

pre-commit run --files $(git diff --name-only origin/main...HEAD)
# all applicable hooks passed across the complete PR file list

git diff --check origin/main...HEAD
# clean

Tests cover deep immutability/pickling, recipe and role-layout validation, strict phase counts, normal/warmup schedules, skipped and failed phases, terminal recovery semantics, hook order, production constructor/config routing, OPD separation, and fp32 equation boundaries. DMD finite differences hold the stop-gradient target fixed.

CPU execution used Python 3.12 and the configured development environment. No GPU/model-execution claim is made. Core-module import tests check direct dependencies; they do not claim the repository's pre-existing package initialization imports no runtime libraries transitively. Upstream CI has not yet been established by these local results.

API and Usage Example

A complete in-process control-plane example; synthetic-model is a plan identifier and is not loaded:

from verl_omni.trainer.diffusion.distillation.controller import (
    DistillationTrainerController,
    FakeBatchProvider,
    FakeDistillationHooks,
    FakePhaseExecutor,
)
from verl_omni.trainer.diffusion.distillation.recipes import build_plan

plan = build_plan(
    "dmd2",
    config={"model_path": "synthetic-model", "fake_update_ratio": 2},
    capabilities=frozenset({"distribution_matching"}),
)
hooks = FakeDistillationHooks()
controller = DistillationTrainerController(
    plan, FakePhaseExecutor(), FakeBatchProvider(num_batches=6), hooks
)
controller.run(2)
assert controller.counters.global_step == 2
assert controller.counters.optimizer_steps == {"student": 2, "fake_score": 4}
assert [call["global_step"] for call in hooks.calls] == [1, 2]

Configuration routing is algorithm.trainer_type=distillation, with new settings under distillation.distribution_matching. The existing OPD flag distillation.enabled stays false. Selecting this route alone is not a runnable training recipe in PR 1.

Design & Code Changes

composed config + declared capabilities
                  |
          recipe/config factory
                  |
       validated DistillationPlan
                  |
  DistillationTrainerController
    | UpdateSchedule -> UpdateCycle -> PhaseRequest
    | BatchProvider.next(request)
    | DistillationPhaseExecutor.execute_phase(request, batch)
    | validate PhaseResult; accumulate role counters
    | commit completed cycle; advance global_step once
    ` after_completed_step(counters, metrics, executor)

The six-file subpackage has explicit boundaries:

Module under verl_omni/trainer/diffusion/distillation/ Responsibility
contracts.py Plans, tensor containers, role/storage/transport/export contracts, schedules and validation
utils.py Pure fp32 x0 conversion, DMD normalization/surrogate, detached fake-score and ODE losses, CFG, Euler/re-noise transitions
recipes.py Registered declarations and factories; no model forwards or objective computation
controller.py Phase state machine, executor/batch/hook protocols and CPU fakes
ray_trainer.py Production-compatible constructor/lifecycle shell; no worker allocation yet
__init__.py Public exports

Key invariants:

  • The trainer never branches on recipe or architecture names.
  • New controller state uses descriptive snake_case names; Python protocol dunders and BaseConfig _mutable_fields remain unchanged because they are framework contracts.
  • One normal cycle has exactly one first-position student phase with repeats=1, then K fake-score phases. Counts are integers, not booleans or silently truncated floats.
  • Fake-only warmup advances role counters and completed_cycles, not global_step.
  • global_step commits only after all required normal-cycle phases succeed. Each completed phase reports exactly one optimizer step for each requested role and no extras.
  • On failure, driver counters/metrics roll back and the driver becomes terminal. This is not an in-memory model rollback or permission to retry partial optimizer updates.
  • Checkpoint/validation/export hooks see committed counters; their implementations belong to later layers.
  • Causal/bidirectional role layouts are declarations only. No causal model, GAN implementation, remote scorer, or inference server is introduced.

The full implementation architecture, state transitions, tensor boundaries and extension rules are documented in #520 rather than a private/local design document.

Checklist Before Submitting

AI assistance: Claude and OpenAI via pi assisted implementation, review and test construction. The human submitter understands and owns the change. This PR remains Draft for upstream architecture feedback, not because it is presented as an autonomous agent submission.

Review follow-up: naming cleanup

Applied after review annotations and propagated through the whole stack (PR #8/#9). Modules and identifiers now use descriptive, repository-convention names; Python protocol dunders and framework contracts are deliberately left untouched.

Before After Kind
control_plane.py controller.py module
equations.py utils.py module (single-noun subpackage convention)
DistillationTrainerControlPlane DistillationTrainerController class
build_control_plane() / self._control_plane build_controller() / self.controller_instance trainer accessor/state
self._metrics self.phase_metrics controller state
self._failed self.failed controller state
_registry registered_classes recipe registry storage
FrozenDict._data FrozenDict.data (wrapped in MappingProxyType) contract state
CPU-fake _num / _batch_size / _sent / _skip_student / _fail_on num_batches / batch_size / sent_batches / skip_student / fail_on test doubles

Deliberately not renamed: __init__, __post_init__, __all__, __slots__, and other Python protocol dunders; BaseConfig._mutable_fields (framework API required to keep config fields mutable); and inherited framework overrides such as _save_checkpoint / _load_checkpoint.

NancyFyong and others added 5 commits September 4, 2026 09:17
…iner control plane

PR 1 of the distribution-matching distillation RFC (#519) delivers the
architecture-neutral trainer control plane, immutable execution contracts,
recipe/objective/rollout registries, and pure DMD-family math that do not
depend on any model pipeline, Ray worker, FSDP model, or GPU runtime.

It routes a new 'distillation' algorithm.trainer_type into a DistillationRayTrainer
sibling of the existing policy-gradient/direct-preference trainers, and validates
the trainer_type value in DiffusionAlgoConfig (which previously did no
validation). Existing OPD behavior (distillation.enabled + policy_gradient)
is unchanged and its tests still pass.

AI assistance was used for this change.

Co-authored-by: Claude
Signed-off-by: NancyFyong <2742092809@qq.com>
… five cohesive modules

The distillation package was split into 14 files that exceeded the
repository's convention of grouping one responsibility per file with a
registry (cf. diffusion_algos.py holding all losses + advantage estimators +
registry). Merge the declarative strata:

- contracts.py absorbs role_runtime/export/score_providers/checkpoint (all
  data-contract and validation strata).
- recipes.py absorbs registry/objectives/rollout_strategies (all the
  declarative dispatch: registered objectives, rollout strategies, recipes).
- control_plane.py absorbs phase_executor (executor protocol + CPU fakes).

Net -177 lines. Public symbols are unchanged in name and behavior; import
paths for every symbol now resolve through the retained modules.

Clarify in math.py why it is its own module: it holds the only executable
equations (contracts = data types, recipes = declarations, math = equations),
so it can be unit-tested as algebraic identities without a plan/executor.

No model pipeline, Ray worker, FSDP, or GPU runtime changed. All 97 PR 1 CPU
tests and the full trainer/diffusion suite (334) pass.

AI assistance was used for this change.

Co-authored-by: Claude
Signed-off-by: NancyFyong <2742092809@qq.com>
…uations.py

Rename the pure-tensor-math module from math.py to equations.py. The
implementation is unchanged; this is a naming fix.

- math.py shadows the Python stdlib math when the package directory is on
  sys.path.
- it carries equations (DMD gradient/surrogate, fake-score target/loss, CFG,
  x0 conversion, timestep shift), not generic 'math' utilities.
- the single-noun name aligns with the other distillation modules (contracts,
  recipes, control_plane) and the visual_reflection subpackage naming.

No logic changed. ruff clean; full trainer/diffusion suite (334) passes.

AI assistance was used for this change.

Co-authored-by: Claude
Signed-off-by: NancyFyong <2742092809@qq.com>
Make the PR 1 trainer shell compatible with the production entrypoint and add
an OPD-safe distribution-matching config path. Enforce immutable, fail-closed
plans and transactional cycle accounting, correct causal role layouts, and
align the pure CFG and x0 equations with the reviewed reference behavior.

Expand CPU coverage for configuration, recipe validation, warmup transitions,
failure recovery boundaries, role accounting, and numerical edge cases.

Refs #519
Refs #520

AI assistance (OpenAI Codex) was used for this change.

Co-authored-by: OpenAI Codex <noreply@openai.com>
Signed-off-by: NancyFyong <2742092809@qq.com>
Reject fractional and boolean phase counts rather than coercing them, and
require the only student phase to execute once. Keep helper definitions
flat and use descriptive names across the control-plane implementation
and its tests, as requested in the integrated review.

Validation: 402 trainer/diffusion CPU tests and all pre-commit hooks passed.
Refs #520

AI assistance (OpenAI via pi) was used for this change.

Co-authored-by: OpenAI <noreply@openai.com>
Signed-off-by: NancyFyong <2742092809@qq.com>
Copilot AI lite review requested due to automatic review settings September 5, 2026 08:16

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@github-actions github-actions Bot removed the ci-core Run all core modules - training, rollout, reward engines, weight sync manager, etc label Sep 6, 2026
Rename the controller and utility modules to match the reviewed package naming, and use descriptive snake_case state names for the new PR 1 implementation. Preserve Python protocol dunders and BaseConfig._mutable_fields because those names are framework contracts.

AI assistance (OpenAI via pi) was used for this change.

Co-authored-by: OpenAI <noreply@openai.com>

Signed-off-by: NancyFyong <2742092809@qq.com>
@NancyFyong NancyFyong added the ci-core Run all core modules - training, rollout, reward engines, weight sync manager, etc label Sep 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci-core Run all core modules - training, rollout, reward engines, weight sync manager, etc

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants