Repository navigation
feat: let Reef own inference and training coordination - #432
Merged
Merged
Conversation
Start RFC #425 with deployment-owned inference for managed non-colocated full-weight training. Keep training batches and direct weight transfer in Slime, and retain existing LoRA, colocated and external-engine paths.
|
Merge Oncall: @BobbyZhouZijian The merge oncall will route reviews and keep this pull request moving toward a clear decision. |
3 of 11 tasks
3 of 16 tasks
The test polls for the pid file and could read it before the write finished, failing with an empty JSON document on busy CI runners. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
6 of 8 tasks
Resolves the overlap with the ATIF training-data refactor (#446) and the reported-feedback simplification (#445): main's TrainingBatch/TrajectoryItem model is kept everywhere, this branch's names stay (CandidateBackend, CandidatePluginFactory, SupportsReceipt, the runtime module split), main's edit to the removed reef/runtime/adapters/executor_runtime.py is carried into reef/train/runtime.py, and the Terminal-Bench example imports the proxy runtime from reef.inference.http. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Collaborator
|
Overall this looks great. Thanks for taking on this substantial refactor! |
Brings in the Tinker backend (#448, #464) and splits it along this branch's runtime ownership: TinkerCheckpointStore holds the local manifests and remote snapshots, TinkerTrainingRuntime (reef.runtime.interfaces TrainingRuntime) branches candidates from the store's active checkpoint, and TinkerInferenceRuntime (InferenceRuntime) owns the chat handler, serving version, candidate activation, publication acknowledgement and head binding. The launch factory returns the pair; the scheduler owns admission. activate_checkpoint moves onto WeightRuntime with the same default, and WeightLoader is an ArtifactActivator so the committer and startup recovery bind remote snapshots. The removed reef/runtime/base.py and __init__.py stay removed. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…Lang serving Selecting inference.backend: sglang with training.backend: tinker runs Reef's model driver: the placement reserves inference GPUs only (a layout with zero training GPUs is now a hosted trainer), the SGLang engines start with LoRA serving, and Reef's coordinator drives TinkerTrainingBackend, a native TrainingBackend. Each job trains one optimizer step on Tinker from the scenario's published checkpoint, downloads the result and converts it with tinker-cookbook into a PEFT adapter under the job checkpoint, and publication loads that directory into every engine through a new load_adapter_from_disk control operation; the incumbent per scenario is remembered on disk so rejection leaves it and restarts branch from it. The HTTP service connects through the new coordinator_training runtime kind, the generic form of the Slime connection. The managed-inference capacity and driver-process helpers move from the Slime launcher into reef.train.deployment so both trainers share them. The in-process Tinker topology is unchanged. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…tore reef.inference.tinker owns Tinker's sampling side: the SDK sampler, the inference runtime and its chat handler, resolving each artifact's tinker-checkpoint.json manifest to the immutable remote sampler it names. reef.train.tinker_backend keeps the trainer, which branches every candidate from the incumbent it remembers on disk. The two share only that manifest. TrainingRuntime gains commit_candidate, a no-op by default, which the scheduler calls after Reef's durable commit and once at startup for the head's job, so a trainer with immutable incumbents learns the head without holding the inference runtime. The in-process pair is built through the registered "tinker" inference kind. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A trainer that produces adapters as PEFT directories no longer needs the engines' native control handle. TrainingBackend.adapter_files returns the directory to serve as a version; InferenceBackend.load_adapter_files loads it at the receiver; BackendWeightPublisher takes that path for publication and for residency re-activation, verifying the served version as before. Trainers declare the reef-adapter-files-v1 transfer and inference services list the transfers they accept, so the plan pairs Tinker with SGLang without Tinker knowing SGLang's control protocol. SGLang's receiver loads files through its control actor; the Tinker backend sheds its engine handle and its send methods refuse. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…in the scenario test Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The converter that turns Tinker checkpoints into PEFT adapters for a local engine is a released package; pin it instead of asking operators to install it by hand. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… running it serve-tinker.yaml runs the TTT-Discover example on Tinker's hosted LoRA training and sampling with the paper's method settings and the reduced 2 x 2 grid; the harness reads the stack file from TTTD_STACK and the thinking flag from training.config, so both sides still agree. A real run showed two judge bugs that scored every program zero: the judge container lacked cvxpy although the task instruction offers it, and its runner serialized program results with json.dumps, which fails on the numpy arrays the verifier itself accepts. Both fixed and verified against the sampled programs. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A Ray cluster with pretend GPUs, the real model driver, a stub engine that accepts Reef's adapter-file transfer in place of SGLang, and the Tinker SDK faked on the driver's path (or real with REEF_TEST_TINKER_REAL=1) drive one TTTD step through the coordinator from a Dispatcher connected like the HTTP service. Running it found three things the doubles had hidden: the trainer must open its SDK session inside the coordinator's process rather than be pickled with one, the coordinator's process needs the recipe's Tinker loss reference to import the recipe and its step preparer, and capacity resolution overwrote a selected dotted inference factory with "sglang". Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The Tinker SDK client refuses older interpreters, as the tinker extra does. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
BobbyZhouZijian
marked this pull request as ready for review
September 14, 2026 14:32
BobbyZhouZijian
requested review from
Benjamin-eecs and
simonucl
as code owners
September 14, 2026 14:32
BobbyZhouZijian
added a commit
that referenced
this pull request
Sep 14, 2026
main reworked the ground this branch stands on: #432 gave Reef ownership of inference/training coordination, #446 unified training data as ATIF items, and #412 moved the scenario and runtime modules. The MLX backend is re-integrated rather than merely merged. Conflicts and how they resolved: - `reef/runtime/adapters/` and `reef/service/deploy/settings.py` are gone on main; the deletions are accepted. The MLX kind registers from `reef/train/__init__.py`, which is the direction the boundary allows. - `reef/surface/base.py` and `reef/inference/sglang/chat.py` take main's restructured versions; `RecoveryRestorer` is re-added in main's ABC idiom. - `reef/service/assembly.py` takes main's version: the function this branch had changed built a runtime from `ServiceSettings`, which no longer exists. Re-integration: - The batch path reads ATIF items. The captured tensors move from a `PolicySample`'s attributes to the item's `extra.reef.training`, where the openclawrl processor already writes them; the tests use main's own `policy_trajectory` helper so both sides agree on that layout. - `MLXServingRuntime` (new) owns the parameters, the admission gate and the served version; `MLXRuntime` keeps candidates, the objective and checkpoints, and reaches the engine through it. The factory returns the pair #432's contract names. The seam follows the state: three methods needed both halves, and all three have the same shape — hold serving, touch the engine, release — so serving exposes an explicit `hold`/`release` rather than a context manager, because activation deliberately stays held until Reef's commit. - `MLXInferenceBackend` implements both `InferenceHandler` and `InferenceBackend`; a receiver that runs as a separate service splits those, an in-process engine does not. The receiver operations that would mean moving memory Reef does not own (`offload`, `onload_*`, `unload_adapter`) raise instead of pretending, which is what the base class does for `load_adapter_files`. - `assistant_message.py` moves under the MLX backend: main's boundary test counts it as an unowned module in `reef/runtime/`, and the MLX backend is its only consumer. One judgement worth flagging: the shipped-config layout test now skips `results/` directories. A results directory records the deployment a past run used; migrating it to the current schema would make it a config that was never executed, which is worse than an old one, because the record's whole value is that it is what ran. Verified: 3528 passed, 88 skipped (slime/ray/inference modules excluded — those dependencies are absent here). mypy clean, ruff 0.14.7, isort 5.13.2, black 24.3.0, and the Python design and statement policies pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SuEa3FyrkW1xc3sjmAJSEB
8 tasks done
BobbyZhouZijian
added a commit
that referenced
this pull request
Sep 14, 2026
… activates A live run on the merged tree found what 3528 passing tests did not. **Nothing could start.** #432 replaced the public layout, so the MLX runtime's config keys were no longer consumed: the deployment selects a runtime through `training.backend` now, and its options arrive as `training.options`. This adds `MLXDeployment(InProcessTrainingDeployment)` and registers `mlx` as a built-in backend; the base class already refuses the Ray, executor and inference-engine settings an in-process integration has no use for. **Candidate evaluation is a factory class.** `CandidateEvaluationPluginFactory` replaced the plain `build` function, and it receives both runtimes. That correction matters here: the probe applies a candidate's weights and generates from them, so it belongs to the *training* half — the serving half deliberately never sees a candidate before Reef selects it. **Activation belongs to the half that serves.** `activate_candidate` is declared on `InferenceRuntime`, so Reef calls it there, and the split had left it on the training runtime holding the staged snapshots. Every test passed, because they called it directly on the training object rather than through the caller Reef uses; the first live training step failed with "MLXServingRuntime does not support candidate activation". The serving half now owns the staged snapshots — it is the one that adopts them — and training stages and discards through it. Two regression tests assert the seam as Reef holds it. Verified by running it: four optimizer steps on Qwen3.5-9B, `rows=8` each, `adapter_tensors_changed=304` (every LoRA tensor moving, so the gradient path is intact), `adapter_delta_l2` 0.84-1.02 (the pre-merge run's first step was 0.837), the gate deciding as `regression-gate`, the probe's `answered_rate` at 1.0, and the served version advancing on every activation. Suite: 3530 passed, 88 skipped. mypy, ruff, isort, black and the design and statement policies pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SuEa3FyrkW1xc3sjmAJSEB
BobbyZhouZijian
added a commit
that referenced
this pull request
Sep 15, 2026
…struction The repository had no MLX deployment anyone could run. The reference `serve.yaml` is the seven-GPU stack, and the two configs beside the run records are archives in the pre-#432 schema, deliberately left as what those runs used. So the current shape had to be rediscovered from startup errors, one field at a time. `serve-mlx.yaml` is that shape, and it is the file the verification run used: `training.backend: mlx` with the runtime's options under `training.options`, the model named in `inference.model-path`, and the candidate gate in `evaluation`. The comments carry the reasons that are expensive to rediscover — why `in_proj_qkv` is adapted, why a cached prefill is ruled out, why the judge needs a large token budget, and that relative storage paths make two runs from one checkout collide on the artifact repository. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SuEa3FyrkW1xc3sjmAJSEB
BobbyZhouZijian
added a commit
that referenced
this pull request
Sep 19, 2026
Main moved inference ownership into reef.inference.sglang and publication into reef.runtime (#432), deleting the rollout manager and the preflight and bridge code this branch changed. Port the branch's behavior onto the new owners instead of keeping the deleted paths: - SGLangConfig decides prefix sharing: it defaults disable_radix_cache to off only when publication retracts (and, under LoRA, when SGLang keys radix entries by adapter), rejects overrides that re-enable sharing otherwise, and rejects PD groups with a retracting pause. - The Slime translation selects the retract pause for colocated engines or --disjoint-prefix-sharing and no longer forces the radix flag on. - The SGLang worker's retract pause flushes the cache after pausing. - TrainingPublication marks a CHECKPOINT UPDATING_WEIGHTS when a failed pause barrier (publication or startup) has retired the engines. - The adapter-scoped radix-key check moves beside the LoRA schema checks. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This was referenced Sep 19, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Motivation
Slime's bootstrap and bridge owned inference lifecycle, weight publication and recovery. This coupled future training and inference backends to Slime's rollout management. Reef now coordinates independent backend implementations, including when a trained version may begin serving.
Implements RFC #425 in this draft PR. The RFC remains proposed; maintainer acceptance and GPU validation remain open. This does not implement the separate vLLM/MLX work in #381/#325.
Changes
TrainingRuntime/InferenceRuntimescheduling interfaces and nativeTrainingBackend/InferenceBackendABCs inreef/runtime/interfaces.py. Concrete implementations inherit them; there is no aggregateModelRuntime.reef/runtime/to five modules andexecutor/:interfaces.pyfor contracts/values,scheduler.pyfor scenario scheduling and serialized training/resource handoffs,deployment.pyfor plans/resources/factory discovery,publication.pyfor transfer/version/commit coordination and LoRA residency, andrecovery.pyfor durable state and reconstruction. Directory, dependency and installed-wheel checks enforce the layout. Runtime is a PEP 420 namespace package without a package facade.RuntimeSchedulerand worker-sideTrainingCoordinatoras distinct objects inscheduler.py. Actual weight transfer ordering, target allocation, receiver verification and adapter replay live inpublication.BackendWeightPublisher; startup restoration lives inrecovery.TrainingRecovery. Shared store ABCs avoid circular imports while preserving disk-first job transitions.reef/inference/, generic training connections toreef/train/runtime.py, and coordinator RPC transports toruntime/executor/connection.py. Generic pair assembly lives inreef/service/runtime.py. Builtin factories resolve lazily without package-import registration side effects.reef/inference/sglang/.SGLangInferenceRuntimeowns its default request handler.SlimeTrainingRuntimeconnects training and uses the independently selected inference factory. Runtime imports neither integration; training and inference integrations do not import each other, including from local functions.CandidateBackend/RuntimeCandidateBackendand request executionInferenceHandler. Replace remaining first-party Protocol contracts with explicit inheritance and add CI enforcement againstProtocol,runtime_checkableandTYPE_CHECKING, including alias forms. Protocol violations cannot be baselined; existing Callable debt does not grow. Repository-local uv caches are excluded as third-party dependencies, with regression coverage that still rejects authored Protocol definitions.inference.*, update maintained YAML/request-handler examples, backend-extension documentation, plugin registration and wheel checks. Fix base-wheel test collection when optional Ray is absent.Tinker on this branch
reef/inference/tinker.py(SDK sampler,TinkerInferenceRuntime, chat handler). The trainer inreef/train/tinker_backend/keeps its incumbent on disk; the two share only each artifact'stinker-checkpoint.jsonmanifest.TrainingRuntime.commit_candidate(no-op default) lets the scheduler tell a trainer with immutable incumbents which candidate Reef committed.training.backend: tinkerplusinference.backend: sglangruns Reef's model driver with a GPU-less trainer (ModelGpuLayoutaccepts zero training GPUs), SGLang engines in LoRA serving mode, andTinkerTrainingBackendbehind Reef's coordinator. Adapter delivery is a Reef-owned transfer (reef-adapter-files-v1):TrainingBackend.adapter_filesreturns the PEFT directory,InferenceBackend.load_adapter_filesloads it at the receiver, the publisher drives both; inference services list the transfers they accept. The HTTP service connects through the genericcoordinator_trainingruntime kind.tinker-cookbook==0.5.7is pinned in thetinkerextra for the checkpoint-to-PEFT conversion.recipes/tttd/examples/tttd/serve-tinker.yamlruns the Erdős example on Tinker; the harness reads its stack file fromTTTD_STACK. Running it exposed two judge bugs (no cvxpy in the judge image although the instruction offers it; numpy results rejected by the runner'sjson.dumps), both fixed.Compatibility and operational impact
reef.runtime.interfacesand factory APIs fromreef.runtime.deployment. The previousruntimepackage facade andbase,backends,adapters,control,weights,training_jobmodule paths are removed.ModelRuntime,ExecutorModelRuntimeand aggregateRayRuntimealiases are removed. Model processes must run the same revision.(training_runtime, inference_runtime). Genericexecutor_training/ray_trainingkinds remain. Automatic Slime assembly selectsslime_trainingwith an independentinference_runtimeselector; SGLang is the currently supported native receiver. Coordinator transports useCoordinatorClientand Executor/Ray variants.connect_ray_runtimeandconnect_executor_runtimeslive inreef.service.runtime.CandidateBackendandTrainer.candidate_backend; request adapters useInferenceHandler,inference_handler, and itsfrom_configclassmethod. Evaluation plugin factories implement.build(...); header providers implement.headers(...). Existing native harness entrypoints retain boundary adapters.inference.handler-factory/inference.handler-config; former requestbackend-factory/backend-configkeys are rejected. Nativeinference.backend/training.backendselection is separate. Complete dotted CLI flags override YAML; no publicservicessection is added.inference.num-gpus,inference.tensor-parallel-sizeandinference.optionsare authoritative for managed inference; equivalent training-native rollout resource flags are rejected.training.colocate(default false) decides whether training and inference share the same GPUs; Reef makes the reservation and passes the colocation flags to the Slime driver. Model drivers returncreate_training_planplus an independently selected inference definition. Directstart_bridge/TrainBridgeActorImplconstruction is replaced by Reef assembly; legacy argument-file/healthcheck inputs remain supported.slime-sglang-control-v2native attachment and direct worker-to-worker weight transport remain. Runtime IDs retain<incarnation>:<sequence>; pending markers may containtarget_runtime_load_id, and older markers remain readable. Artifact formats, provider bodies, scenario isolation and receipt linkage retain their contracts.RUNNINGstate stops recovery; Slime rejected candidates require restoring committed trainer state before cold reconstruction.reef.runtime.executor.placement: one placement group sized frominference.num-gpus, the trainer's GPU count andtraining.colocate, with bundles sorted by node and device) and hands the training and inference portions to the backends; Slime no longer creates its own placement groups.training.colocateis the only colocation switch:training.options.colocate/offload-rolloutare rejected, and the driver receives the matching native flags from Reef. This adds no cluster autoscaling, dynamic parallelism resizing or arbitrary backend transport interoperability. Initial colocated training releases all inference memory; keeping the LoRA base resident applies only to later steps.Verification
Validation on macOS ARM64 without CUDA for the merged source in
8607194d, plus the subsequent CI cache-scan correction:python -m pytest tests -n 4 --dist loadfile --cov=reef --cov-report=term -q --tb=short --durations=15— 3,800 passed, 60 skipped; 88.50% coverage. Repository environment, Node.js 22/uv on PATH, proxy bypass and isolated Git configuration.REEF_TEST_RAY=1overtest_inference_control.py,test_training_restart.pyandtest_model_supervision_ray.py— 12 passed. Covers moved coordinator/control paths, named discovery, publication, commit barriers, reconstruction and cleanup across processes. Run on56bfbf87; the merge and cache-scan fix leave these runtime/inference modules unchanged.pre-commit run --all-filespassed.python -m mypypassed for 275 source files. The design baseline retains 25 existing Callable entries; only moved-file paths changed. Directory/import boundary suite: 35 passed. Protocol policy suite: 45 passed, including reproduction of CI uv-cache false positives while still rejecting Protocol in owned source.npm ci,npm run check:docs,npm run lint,npm run buildpassed.twine checkanduv lock --checkpassed. An installed-wheel smoke check blocked Ray, SGLang, Slime, Torch, Megatron and Transformers imports while validating exact namespace contents, independent contracts, lazy native runtime factory discovery, generic connections, HTTP/model override construction and the SGLang plugin entry point.Remote CI passed for
cb51707a: lint and the test/package matrices on Python 3.10, 3.11 and 3.12 are all green. CI run.Actual GPU checkpoint loading, LoRA IPC/NCCL transfer, colocated memory capacity and performance remain unverified. CPU contracts and process tests do not satisfy those acceptance gates.
Real Tinker runs on this branch (Qwen3-8B, CPU host, both in-process topology):
tutorials/tinkersmoke: four sampled rollouts, one remote optimizer step, one committed release.recipes/tttd/examples/tttd/serve-tinker.yamlon the Erdős task through harbor, thinking on, 26,000-token budget, 2 x 2 grid: two of four programs valid (C₅ ≈ 0.4995 / 0.5000), one training step committed, incumbent advanced. The 2 x 2 grid degenerates TTTD's leave-one-out advantages (±1e12), so that step checks the mechanism, not the method; a meaningful step needs more rollouts per group.tests/reef_service/test_tinker_local_engine_ray.py): a Ray cluster with pretend GPUs, the real model driver, coordinator and HTTP-side connection, a stub engine accepting the adapter-file transfer in place of SGLang. Passes with the fake SDK in CI and, run locally withREEF_TEST_TINKER_REAL=1, with the real SDK: real training step, archive download and cookbook conversion (110 s). The converted adapter has 506 tensors with Hugging Face parameter names and shapes matching Qwen3-8B at rank 32.lm_head, which SGLang's LoRA loader may not accept.AI assistance
OpenAI Codex assisted with implementation, review, tests, documentation and this description at the contributor's direction. The human contributor remains responsible for reviewing and understanding the submitted changes and validation claims.
Checklist
README.i18n.yaml, or do not affect README content.Review and merge
RFC #425 acceptance and GPU validation remain open. Follow the repository's maintenance model for review and merge requirements.