Repository navigation
rl/syncer: one syncer with async multi-island sync for LoRA and full-parameter RL - #292
Draft
michaellchung wants to merge 263 commits into
Draft
michaellchung wants to merge 263 commits into
michaellchung wants to merge 263 commits into
Conversation
…ced trainers and their samplers - modal_runner: on a multi-node island rank 0's runner listens on a small relay port; a rank>0 runner that gets SIGINT/SIGTERM sends the signal there, and rank 0 writes the reclaim marker its driver watcher turns into the store's preemption notice (signal names the source rank). - sample_roles: a synced group (--rl-sample-role trainer with a syncer plus --rl-island-sample-role M:sampler) starts K synced trainers (learners 0..K-1, lanes K) and no-sync sampler islands over the same store in one launch; the samplers split K x total-steps rounds, take data slots and buffer shards. - launcher: the syncer counts only the trainers of a synced group. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…mes the rank Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Engine sampler islands: the launch layer writes rollout_quant {tier, apply}
into the sampler spec (stage1.sampler_spec); the entry applies it on the
node (sampler_engines.quant_tier). Tier 1 on sglang-server fills
--quantization fp8, --kv-cache-dtype fp8_e4m3 and SGLANG_FP8_IGNORED_LAYERS
from the checked online plan; vllm-server is refused (stand-alone vLLM online
FP8 has no ignore list). Tier 2 converts the base to the FP8 scheme and sets
quant_scheme, expected_format, release_dir and the routed-expert count. A spec
that already quantizes is refused. Stage 1 refuses a tier on top of its own
quant section.
- Post-load expert_count: an expected count of 0 (dense model) passes without
an engine report; the release index count is checked before the load.
- requant: drop a tied lm_head.weight that an exporter writes although the
base ties it to the embeddings (tie_word_embeddings).
- Tests: the ignore list reaches the process started by Miles'
launch_bound_subprocess; Miles' convert_qwen2_to_hf names equal the
Qwen3-4B-Instruct-2507 index names (fixture from the real index).
- Probe (Modal CPU, verl image): Hydra overrides -> RolloutConfig -> verl's
CLI builder -> vLLM 0.29 argument parser -> Fp8Config. The received
ignored_layers equal the sent list; qkv_proj and o_proj skipped,
gate_up_proj and down_proj quantized; kv_cache_dtype fp8.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Importing it as a package put the real miles package into sys.modules and broke a later test that fakes miles (test_rl_reward_success). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…eral samplers) The verl buffer trainer opens its buffer with create_index=False. With named shards no sampler writes index.jsonl, so the first merge failed with FileNotFoundError (S26 E9 G1 on Modal). open_trainer_buffer now creates the index whenever this trainer merges shards: it is the index's only writer. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A sampler that writes its own buffer shard counts its backlog from the release's buffer_shards. The verl publisher released only buffer_selected, so on Modal (S26 E9 G1) both shard samplers stayed at their backlog cap and the trainer timed out after four rounds. The release (and the release log) now carries the read cursor per shard, as the Miles trainer does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…alsy) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On a buffer trainer the queue is the sample buffer. When the samplers ended and a round could not be filled (groups passed over as older than max_policy_age), FullyAsyncRolloutPool raised "rollouter ended the queue" and the island left INFRA_FAILED (S26 E9 G1 on Modal, launcher rc=4). The buffer ports now mark the pool so that end raises SampleBufferExhausted, which the driver turns into rl_buffer_exhausted and a FINISHED run (as on Miles). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…inalized The launcher requires every island tape to end with rl_learner_finalized; a verl pair's sampler islands run the engine sampler, whose tape never had it, so a run whose islands all succeeded exited 3 (S26 E9 G1 on Modal). A finished incarnation now writes it; a reclaimed one does not (the next incarnation continues the island). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The launcher completes a Modal island's event tape from the island's tape file and accepts only records with island_id. The verl pair's sampler islands write engine sampler records (sampler_id only), so every record, the new rl_learner_finalized included, was discarded and the run exited 3 although all islands succeeded (S26 E9 G1). The verl sampler spec now names the island (island_id = learner id) and the tape writer adds it to each record. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With --rl-max-policy-age > 0 a round cut asks the rollout pool for its in-flight work. FullyAsyncRolloutPool had no export_in_flight, so every cut of a fully_async verl island was refused (RebuildRefused; S26 E9 G2 on Modal: rl_round_cut ok=false for each round). A round keeps or drops every group it pulls, so nothing pulled is in flight at the cut; the groups still queued in the rollouter are generated again after a resume from the data cursor. The export is now empty and the cut is written. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…dal verl islands default NCCL_SHM_DISABLE=1 The sync path pinned trainer.n_gpus_per_node=1, so a 2-card container trained on one rank (S26 E9 e9-g3-a). The island now takes the card count from its island-local Ray. A 2-card verl island on Modal failed attaching NCCL shared memory (e9-g2-a); the launcher sets NCCL_SHM_DISABLE=1 for multi-card verl Modal islands unless --modal-env sets it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…samples record their data row e9-g4-a: both Miles samplers drew the same 16 prompts (prompt text overlap 16 of 16/24). DATA_SLOT_ENV was set in the island process but missing from ISLAND_ENVS, so the rollout worker never split its data source. Each kept row now also records its index in the unsplit data as metadata["yeto_data_row"], which reaches the sample buffer payload. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On a 2-card verl sync island (e9b-g3-a) both vLLM replicas wrote readback-v0.json through the same tmp name; the second os.replace raised FileNotFoundError inside vLLM and the weight update failed. The tmp name now carries the pid, and a write failure is printed instead of raised into vLLM. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… islands cmd_head sized the head-local syncer with every --gpu island, so a synced group (2 trainers + 1 sampler, e9b-sync-a) waited for 3 learners and the trainers hung after connecting. It now uses sample_roles.syncer_learner_count, the same count launcher.run uses. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ason The verl Megatron engine (the only verl path with EP) is not wired into the yeto adapter; EP runs use the Miles backend. check_expert_parallel now names the decision and the backend to use, and stage1 refuses a verl trainer.verl.strategy other than fsdp2. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The strict and decoupled presets set --quorum to every --gpu island; in a synced group (2 trainers + 1 sampler, e9b-sync-b) that is quorum 3 over --learners 2, and the syncer exits at start. Both now use sample_roles.syncer_learner_count. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…imings (S26 E8) - BoatSandbox records per-phase start timings (create, ready, pull, run) and polls readiness every 0.5 s instead of 2 s. - BoatSandboxPool recycles a finished VM (containers, volumes, networks and staging removed, then checked empty) and hands it to the next task with a fresh container from the image; idle TTL, idle cap, reuse cap, prewarm. BoatEnvironment uses it with pool=true or YETO_BOAT_POOL=1 (off by default); a VM with a failed exec or transfer is deleted. - HarborHarness retries a trial that failed before the agent ran (sandbox start, image pull, Codex install) up to HARBOR_SETUP_RETRIES times (default 0) with jittered backoff; failures after the agent started, policy-limit, config and quota errors are never retried. - BoatQuota can also hold the hourly start cap (YETO_BOAT_STARTS_PER_HOUR, unset by default); create retries jitter. - scripts/boat_startup_bench.py measures cold vs pooled rounds and the Codex install (Harbor install_only) on real boat sandboxes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…stall (S26 E8) With codex_cache on (YETO_BOAT_CODEX_CACHE=1 or provider kwarg codex_cache=true; off by default), each boat VM fetches the npm linux-x64 build of Codex 0.145.0 once, checks its sha512 (npm dist.integrity), unpacks it under /var/cache/yeto-codex/<version> beside the image pull, and the task container gets it read-only at /opt/yeto-codex with /usr/local/bin/codex linked to a wrapper. Harbor's codex agent then finds the pinned version and skips apt + nvm + npm. The task image is not changed. A failed fetch falls back to Harbor's own install. Pool recycling keeps the cache; a reused VM takes the new task's cache choice. The bench script gains --codex-cache and records `codex --version` as the task's default user. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ompactionRL refusal Suspension between rounds (Miles stage 3 and verl fully-async): - yeto.rl.agentic.suspend: TurnGate (one per event loop) and SuspendLedger; the outer wall-clock cap does not count suspended time; a time limit hit after a suspension is ABORTED, never a trainable zero. - Miles: harbor_agent.suspend/resume hooks for --agentic-suspend-between-turns; the agent dials a GateRelay (gateway/gate_relay.py) that holds model calls at the closed gate, resends calls the engines aborted at the cut-off, and keeps held streams alive with SSE comments. Switched on by YETO_HARBOR_GATE_RELAY_PORT; the island and the hook fail closed without it. The Harbor agent function is now suspend-capable for policy_age. - Cut: suspended trajectories are exported as reference-only entries (agentic-suspended:<id>); restore discards them as agentic_session_lost (the sandbox and session die with the process). Legacy codex-suspended references are still recognised. - verl: VerlRolloutEngine resends a tracked trajectory's calls that the rollouter aborted at a weight update (bounded by YETO_VERL_ABORT_RETRY_S), counting the waits as suspended time. TB2 task lists on Harbor task directories (task_lists.py, tools/reward_env/tb2_lists.py): - hold-out and training split with the recorded WP3 rule; byte-identical to data/eval/tb2-holdout.json and tb2-train.json on tb2@2fd12b8; TB2.1 layout supported. - curriculum stages from official difficulty and time estimates, optional reference-solution results and measured pass rates (require_signal drops tasks with pass rate 0 or 1). CompactionRL: not supported on the Harbor path; the records-proxy recorder refuses a compacting proxy, the launch check documents the decision. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…2.1 dataset rows - Gate relay: YETO_HARBOR_GATE_RELAY_PORT takes N, N-M (each rollout process on a machine binds the first free port of the range) or 0; a range with a fixed public base URL is refused. Multi-process test. - AgentTrajectory.extra_metrics carries the suspension counters, so an ABORTED trajectory without a verdict (wall-clock cap, lost sandbox) reports them too; the verl and Miles reclassifications keep them. - task_lists: Miles and verl dataset rows in the formats the adapters read (checked with their real parsers); tb2_lists.py rows/manifest commands. - data: TB2.1 hold-out, training list, curriculum (data/eval/tb2.1-*.json) and Miles/verl rows of the training list and curriculum stage s1 (data/tb2/tb2.1-*.jsonl), with sources and sha256 in data/tb2/tb2.1-data.sha256.json. - tests: Miles-image test of the suspend hooks through Miles' own hook loader; the reward-hook test accepts Miles' Status enum; the cold-start test no longer reads the caller's HF_HUB_CACHE. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…bor's install (S26 E8) Harbor's codex install also leaves curl and ripgrep in the task container, and many TB2 verifiers call curl. On a cache hit the container now gets rg from the Codex bundle (when the image has none) and curl installed the way Harbor installs it (apk/apt-get/yum, only when missing). The codex link is made last; if any step fails it is removed, so Harbor falls back to its own full install. Tests run the setup script against a fake root and check codex, curl and rg match. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…oes (S26 E8) Harbor's codex install leaves node/npm in the container (nvm install 22, or apk nodejs npm on musl), and some TB2 verifiers run node. The VM cache now also holds the official Node v22.23.3 linux-x64 build (sha256-checked; the newest 22.x on 2026-10-21, what nvm install 22 resolves to today). On a cache hit, a container without node/npm gets them linked from the cache, or apk-installed on musl images. If node does not run (e.g. too old a glibc), the codex link is removed and Harbor falls back to its full install. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…g, BF16 KV) - scope.with_fused_aliases: the tier 2 compressed-tensors ignore list also names qkv_proj / gate_up_proj when all shards are ignored. SGLang 2fa88018 matches fused names only through packed_modules_mapping, which Qwen3ForCausalLM does not set, so qkv_proj got the FP8 scheme on the card (e6-gate-t2). - gate: score by token ids when the engine can (SGLangServerEngine.score_tokens); text scoring dropped the trailing EOS and left 96% of completions unaligned. - tier 1: KV cache stays BF16 (SGLang and vLLM). Uncalibrated FP8 KV failed the gate on Qwen3-4B (p99 |diff| 0.82 vs 0.46 with BF16 KV, limit 0.5). - tools/probes/s26_e6: Modal gate and verl RL drivers, a lock wrapper, and an image overlay for the SGLang fork's FP8 activation cast in Qwen2/Qwen3. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…obes - verl max_model_len = prompt + response: Qwen3-4B-Instruct-2507's 262144 context needed 36 GiB of KV next to the actor and vLLM refused to start. - Tier 1 on verl is refused at launch and on the island. With verl's FP8 config vLLM treats the BF16 checkpoint as FP8-serialized and generates garbage until a base sync re-quantizes it; yeto verl is LoRA-only, so the base is never sent (e6-t1-verl-a-r2: 512-token answers, NaN log-probs; e6-vllm-fp8-probe). Plain vLLM online FP8 works with LoRA but quantizes attention, which failed the gate as SGLang online FP8 of every linear. - Gate driver: framework-native variants (Miles block FP8 converter, SGLang online FP8 without an ignore list); vLLM probe; verl metrics parser. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ative + ignore list) - Pins: SGLang fork 10afe11a7 (Qwen2 MLP / Qwen3 attention no longer cast the activation to an FP8 projection's dtype), Miles fork 9462de532 (quantize_params_fp8 and convert_hf_to_fp8 honor the FP8 ignore list; --rollout-hf-checkpoint), image yeto-miles-ports:9462de5-10afe11 @sha256:6e0a97d2. Build record under openspec/changes/rl-infra-spec/evidence/ports-image/2026-10-21-9462de5-10afe11/. - make_layer: Miles tools/ scripts may change in the overlay (nothing is built). - --rl-rollout-quant-fp8-source {engine,trainer} (tier 1): "trainer" starts the SGLang engines on Miles' 128x128 block-FP8 conversion of the base with our ignore list in BF16 (entry.prepare_native_fp8_base, scope-checked by tiers.check_native_base) and Miles block-quantizes every weight update by that config. No SGLang online flag or ignore-list environment in that case. - tools/probes/s26_e6: image check on a Modal CPU container; gate variant miles-native-block-ignore. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The sampler-snapshot G-L8 records stay at SGLang 2fa880182, where they were verified; the new pin is refused for that opt-in mode until G-L8 is re-run. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…weights upload A sampler at its backlog cap starts its next round only when a release records that the trainer read its previous round. A staged release read the position when the trainer exported (before the next step took a round), so the cap lifted only with the release after it: one whole trainer step later. release_staged now uploads the weight files, then asks position() and puts the record into the release directory and LATEST in one store write (put_files). The upload thread reads the position through MilesRolloutPool ._in_executor_from_thread (ray.get on the reference; the driver's event loop may be running on the main thread). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A staged release is written by every rank (release_plugin.write_full_checkpoint). With YETO_RL_RELEASE_EXPORT=miles the trainer still stages and uploads in the background, but Miles' export_hf (rank 0 writes) produces the files: a fallback for a run where the all-ranks writer fails, without a code change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…range
In s27-at-a a sampler island's SGLang engine failed to bind its HTTP port in 3
of 3 island starts ("[Errno 98] ... address already in use"); Miles then waits
an hour for the missing engine. A Modal (gVisor) container hands out ephemeral
ports from 16000-65535, which covers the ports Miles probes free from 20000 for
engines, routers and the trainer master and binds later: a connection opened in
between (NCCL, torch c10d, a health check) can take one. A CPU probe in a Modal
container: about 1.5% of outgoing connections got a local port in 20000-20999,
and writing ip_local_port_range takes effect.
Before the setup script, the island now sets the container's range to Linux's
default 32768-60999 when it starts lower, and logs the change; a range that
cannot be read or written is reported, not fatal.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Megatron's moe_freq_type only accepts a list pattern made of digits, commas, brackets, +, * and parentheses (_eval_pattern). The Miles argv used str() of the provider's per-layer mask, which has spaces, so the Nemotron-H 120B island failed at argument parsing (s27-aw-a: 'argument --moe-layer-freq: invalid moe_freq_type value'). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ter islands keep their row Same-card training islands of a multi-island launch read every row of --data in the same order, so in every round each island trained the same prompts, and a relaunched full-parameter island (its round counter restarts at 0 in every process) read its data from the first row again. Data split (yeto.rl.engine.data_split, backend-neutral): - --rl-data-split auto (default): the island with learner id k of N reads the rows i with i % N == k, in data order; the launcher sends --rl-data-slot k/N and the island keeps those rows when it writes its prompt file (Miles: local JSONL and Hub datasets; verl: the train parquet). Kept rows carry their row number as yeto_data_row. --rl-island-tasks overrides; shared keeps the old behaviour and warns loudly; external learners fall back to shared with the same warning; overlapping task lists warn too. - Passes: pass 0 is data order, every later pass is reshuffled with a seed from --seed and the pass number (Miles, data_passes); verl keeps its own sampler. - The launch prints the rule, the dry-run plan and the run manifest carry it, rl_driver_start records the island's slot. DataSlot moves here from sample_roles (re-exported); sampler islands keep their own slots. Data position (yeto.rl.engine.data_position): - A synced full-parameter island with --rl-checkpoint-store gets --rl-data-position-store and records its data cursor after every sync boundary (directory or object store, background commit). A process that joins a running pool (FullParameterSync.joined_running_pool: fragments newer than version 0, or a peer recovery) seeks to the last record; a process that starts the run writes a start record. An island pool without a store warns at launch. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On an island that reads a row slot, every rl_trajectory_reward record (tape, --rl-observe-timeline) now names the sample's row number in the unsplit --data (data_row), so a run can check that no row is trained by two islands in one round, also for data without task ids. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
tools/eval/data_split_check.py reads every island tape of a run and checks that the islands hold distinct row slots, that no row is trained by two islands, that every trained row lies in its island's slot, and that a relaunched island restored its data position and did not start at a row of a round an earlier process brought to its sync boundary. For the next multi-island GPU run. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ps the round A bucket mount (and an object store) cannot append, so the data position log writes one new file per record under data-positions/island<id>/ in a store directory, as it already did by object key. A record the store refuses goes on the tape (rl_data_position_write_failed) and the island trains on: losing it only makes a relaunch read some rows again. Commit failures at the end of the run go on the tape too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…lies Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The control test compares the dry-run plan of today's launcher in strict mode with the plan of the commit before the sync modes. The default data split now adds --rl-data-slot to each island command and data_split records to the plan; the test launches with --rl-data-split shared (every island reads every row, as before) and drops the data_split records, so it still checks the sync modes only. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The engines-on-island full-parameter mode refused DP>1 because Miles listed every parameter once per data-parallel rank and applied a merged fragment only on the exporting rank. Miles yeto/s27-dp-sync (921cde8c7) gives every parameter one owner rank per replica group (dense: DP x CP group; expert: expert data-parallel group) and applies the owner's bytes on every replica, so the layout lists each parameter once and all replicas stay identical. - pins.py: MILES_DP_REPLICA_FULL_PARAMETER_COMMIT (not in the image yet). - run_config_rules.full_parameter_dp_replica_gap: None once the pinned Miles lineage reaches it; run_config's engines-on-island rule, the elastic benchmark capability check and the Miles optimizer attributes use it. - Miles optimizer attributes: a full-parameter sync at DP>1 keeps the non-distributed optimizer (whole FP32 masters on every replica) and refuses --rl-optimizer-offload-fraction (a partial distributed-optimizer range). - moe_layout: the duplicate-expert error now names the Miles fix. - rank_stats: export reports also show the replicas' export note. - tests/test_s27_dp_replicas_miles.py: yeto's adapter on four fake Miles ranks (TP1/DP4/EP2) with the real Miles state, plugin and transport; every parameter is laid out once and every replica applies the merged bytes bit-identically (needs Miles with data-parallel replicas). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On 8 x H200 with real RL samples (Nemotron-3 Nano), TP1/EP8/DP8 without optimizer offload trained at 9.7k tokens/s/GPU (19.4% actor MFU) with selective recompute of the MoE and layernorm modules, 8192 tokens per GPU, the DeepEP flex dispatcher with permute fusion and 512 samples per step. The Nemotron-H recipe used Megatron's defaults (no recompute, fixed micro batches, all-gather dispatcher) unless the island environment passed flags. models/nemotron_h.train_flags now writes those defaults after the model flags: - --recompute-granularity selective --recompute-modules moe layernorm, unless --rl-activation-recompute keeps full recompute; - --use-dynamic-batch-size --max-tokens-per-gpu 8192 x TP (never below one sample per packed micro batch), unless --rl-max-tokens-per-gpu is set; - MoE: --moe-token-dispatcher-type flex --moe-flex-dispatcher-backend deepep when deep_ep is importable where the island builds its argv (inside the training image), else alltoall; --moe-permute-fusion either way. Pass-through and YETO_MILES_TRAIN_EXTRA_ARGV flags come later in the argv and win (the latter now also accepts --moe-flex-dispatcher-backend). train_warnings prints a launch-time [rl] WARNING when an optimizer step trains fewer than 256 samples, or when the whole optimizer state is offloaded on a trainer of 8 GPUs or fewer. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A sampler island (--rl-sample-role sampler) builds no trainer, so it has no FP32 masters for the full-parameter sync to read; its "DP" is just its GPU count over TP x PP. The data-parallel replica check of the previous commit refused such islands at DP>1 on the current Miles pin (the yeto-cpu-test full suite caught it in test_rl_sampler_island). The check now applies only to islands that build a trainer. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Islands that generate and train on their own rows now read rows i % N == k by default (--rl-data-split auto). Full-parameter islands record their data position in the checkpoint store so a relaunched island continues at its next row. Sampler islands keep DataSlot. No conflicts: the branch is based on 3b64f08. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ining defaults Full-parameter islands may run DP > 1 once the pinned Miles lineage has the replica commit (921cde8c7); each parameter has one owner rank and the replicas copy the owner's verified bytes. Nemotron-H defaults move to selective recompute, 8192 x TP tokens per GPU and DeepEP when present. No conflicts: the branch is based on 3b64f08 and touches no file that s27-task-assign touches. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… support Brings the language-only VL bridge wiring, the latent MoE check, the multi-node full-parameter island with dedicated engines, the offline Nemotron-H layout tool and the space-free --moe-layer-freq mask (b95e1f9, the s27-aw-a failure). s27-120b-gaw contains s27-120b-prep (d7cfbbc), so one merge brings both. Conflicts, resolved: - pins.py, Dockerfile, test_rl_ports_image.py: keep this branch's Miles 8edea35a7 and image 8edea35-2fa8801 for now. The 120B pin (Miles b7868074c, image b786807-2fa8801) lacks the zero-copy and DP-replica commits; a combined Miles commit and image are pinned in a later commit. The b786807-2fa8801 build record stays as history. - models/nemotron_h.py: keep the 120B module text (MTP dropped by the Miles bridge, VL language-only bridge) and the G-AN training-default text; the EP note now says EP may span DP with a replica-capable pin. Imports are the union of both sides. - run_config.py full_parameter_layout_problem docstring: engines on one node or on a multi-node island, and DP>1 with a replica-capable pin. The code merged cleanly: the DP>1 gate comes first, then the node rule. - test_rl_nemotron_h_recipe.py: keep both sides' new tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Miles ebc6519af (michaellchung/miles yeto/s27-closing) merges the closing-run fork commits: 8edea35a7 zero-copy chunks, 921cde8c7 full-parameter data-parallel replicas, d78bf39e4 pinned HybridDeviceOptimizer CPU masters, and b7868074c, the language-only bridge of the Nemotron-H VL checkpoint. SGLang stays 2fa880182. - pins.py: MILES_NEXT_COMMIT, MILES_FORK_LINEAGE (+ b7868074c, 921cde8c7, d78bf39e4, ebc6519af), MILES_NEXT_IMAGE -> @sha256:1151c59a... The DP replica commit is now in the lineage, so a full-parameter sync at DP > 1 is accepted. - Dockerfile ARG MILES_COMMIT; test build-record dir. - evidence/ports-image/2026-10-24-ebc6519-2fa8801: build record and manifest (differs from 8edea35-2fa8801 only in Miles commit and tag). - tests: the DP > 1 refusal tests pin a pre-replica Miles (8edea35a7) instead of relying on the current pin. Image check (scripts/check_miles_ports_image.sh): 13 checks pass. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This reverts commit 4c702a5. The integration keeps one rule for sampler islands: DataSlot (row i goes to sampler island i % S), now in yeto/rl/engine/data_split.py. The start offset of 4c702a5 on top of DataSlot would make a sampler island skip a second block inside its own slot. Done before merging s27-disagg-fast into s27-closing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…er islands Brings G-AO's one-commit sampler rounds and G-AT's fixes: narrow integer buffer payloads (R3 expert ids as uint8) and parallel prefetch on the trainer, staged full-weight releases uploaded in the background (no v0 release from a no-sync full-parameter trainer; --rl-release-inline keeps the old path; YETO_RL_RELEASE_EXPORT=miles), the release record read after the upload, no relaunch into a stopped Modal app, Modal island ephemeral ports moved off Miles' port range, and a timed default round-cut interval. The merged side is s27-disagg-fast 4dc9aca plus a revert of 4c702a5 (sampler start offsets): sampler islands keep DataSlot only. How it was merged: s27-disagg-fast carries its own copies of the S26 E4/E5 commits (e90be7b, da6e900, 2c94f60, 2754919; the same patches as s26-e45). This branch already has s26-e45 with the S27 conflict resolutions of the S26 integration (one-commit release writer under hold_writer). So the merge keeps this branch's tree and applies only 2754919..(4dc9aca + revert), the G-AO/G-AT changes, as a three-way patch. Conflicts, resolved: - buffer_payload.py: keep both additions (SampleBufferExhausted from S26 E9.3 and the narrow-integer helpers). - buffer_rollout_miles.py: keep the DataSlot import (apply_data_slot); the revert removes first_round_cursor, which this branch never had. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With Miles ebc6519af pinned, a dense full-parameter layout at DP > 1 is accepted (data-parallel replicas). The layout-rule, elastic benchmark and config-schema tests now check both: the current pin accepts DP > 1 (including the closing shape, one trainer node at DP 8 with engine nodes on a multi-node island), and a pin before 921cde8c7 (8edea35a7) refuses it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The closing run trains Nemotron-3 Nano at TP1/EP8/DP8 with the full- parameter sync, so its island layout hash must be known before launch. rank_manifests(dp > 1) now lists each parameter on one rank, as Miles 921cde8c7 does: a dense parameter goes to the least-loaded rank of its stage's data-parallel group (largest first, ties by name, then group order); with EP = DP every rank owns its own experts. Scope at DP > 1: TP 1, EP = DP. DP = 1 is unchanged (Nano TP2/EP2 still gives the measured 60 fragments and 0ec93ba8...). tools/nemotron_h_offline_layout.py takes --dp. Nano TP1/EP8/DP8 with rda: 64 fragments, layout c2c3cb49... (offline; not yet checked by an island on GPU). A test checks the owners against Miles' own _replica_owners when the Miles checkout is importable. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nous rounds
Every island downloaded the f32 final cut (about 4 bytes per parameter,
126 GB for a 30B model) before its final ACK, and the syncer sends it to
one island after another. That download set the end time and the cost of
an asynchronous full-parameter run, while the final parameters already
live in the syncer's final checkpoint.
- syncer --final-cut-payload {full,manifest}: manifest sends only
FINAL_MANIFEST. Islands must expect it, so it is part of the semantic
profile hash (domain appended only when on; existing hashes unchanged,
Python vector checked against Rust).
- SyncerClient / ShardedSyncerClient.wait_for_final_manifest.
- launcher --rl-sync-final-cut {auto,ack,download} (full-parameter preset
only): auto is ack for asynchronous rounds without --rl-eval-interval
and download otherwise (the strict sweep keeps the full cut). ack adds
the syncer flag, the profile field and the island flag
--sweep-final-cut ack.
- docs: PROTOCOL.md 'Manifest-only final cut', MILES_RL.md finalization.
- tests that compare final f32 values ask for the download explicitly.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…CK-only final cut Boundary (S27 s27-ap-c: 55-65 s per round, mostly whole-model work): - lazy capture: when the trainer's Miles has the lazy_export transport, a local round's capture reads no master (rows carry labels, the policy hash names the capture) and its receipt skips the whole-model model/master check. Each fragment that is pushed is hashed, NaN-checked and model-checked when it is materialized; the role stream keeps its content descriptor and the push reads the local chunks through it. Every YETO_FULL_PARAMETER_FULL_CHECK_EVERY-th round (default 16, 0 = never) still hashes every fragment and checks the whole model. YETO_FULL_PARAMETER_LAZY_CAPTURE=0 turns it off. Lazy rounds add sync/capture_lazy to the round event. Startup burst: - with H > 1 every fragment is anchored at the same round when a stream starts, so all fragments with a PULL became pushable at round H together. The first push from that shared anchor now waits fragment_id * H // P more rounds (the syncer opens rounds in fragment order); later pushes keep the H gate. YETO_FULL_PARAMETER_STAGGER_FIRST_PUSH=0 turns it off. Final cut: - RoleStreamConfig.final_cut "ack": wait for the final manifest only, drain the pushes and ACK without applying a final cut (final_reached; final_applied stays for a downloaded and applied cut). The island entry takes --sweep-final-cut. The final ACK event carries sync/final_cut. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The strict sync mode is the control profile whose commands must not change, so --rl-sync-final-cut auto resolves to download there. Pin the new syncer flag literal in the run-flags schema test. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s, ACK-only final cut Brings G-AY's yeto and syncer side: the lazy capture (feature-detected on the Miles transport), per-fragment materialize descriptors, a whole-model check every 16 rounds, staggered first pushes from the shared start anchor, and the manifest-only final cut (syncer --final-cut-payload manifest, launcher --rl-sync-final-cut, default ACK in --rl-sync-mode async without island eval). The branch head 0c4750a contains 8cab8eb. No text conflicts: the six files both sides changed (island_entry, chunked_full_parameter, full_parameter_sync, run_flags, rl_island_task, MILES_RL.md) merged in different places. The adapter calls stay owner-level, so they hold with data-parallel replicas; the Miles side of the merge (yeto/s27-closing 59aea07f9) adapts the per-fragment commit and the replica export note to replicas. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The rl_trajectory_reward records stop at 256 samples per round, so a
round of 512 samples showed only half of its rows and the data split
check could not prove that the islands trained disjoint rows.
- rollout_meta_hook: with --rl-observe-timeline, the metadata of a round
on an island that reads a row slot carries round_data_rows:
{"samples": n, "rows": [[first, last, step], ...]}, the distinct rows
of every trained sample as arithmetic runs. A pass-0 round of a slot
is one run; a reshuffled pass needs at most one run per two rows.
- RolloutBatchHandle.round_data_rows (None when not reported; verl does
not report it). The driver tapes one rl_round_data_rows per round:
rollout_id, policy_version, samples, row_count, rows (+ the labels).
- data_split: row_runs, rows_of_runs, run_row_count.
- timeline: ROUND_DATA_ROWS_EVENT and validate_round_data_rows.
- data_split_check: per round, the round record when present, else the
reward records (older tapes); new check round_rows (well formed, and
holds every rewarded data_row of its round); rows_source counts the
rounds of each island by source. Split into one helper per check.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…07-2fa8801 Miles 59aea07f9 (michaellchung/miles yeto/s27-closing) merges yeto/s27-sync-boundary into ebc6519af: 63cb372b3 per-fragment full-parameter round boundary (lazy export, materialize returns fragment descriptors, partial commit) and ccd85ec73 (test imports). SGLang stays 2fa880182. With this pin the island's feature check finds the Miles lazy_export transport, so lazy capture is no longer switched off. - pins.py: MILES_NEXT_COMMIT, MILES_FORK_LINEAGE (+ 63cb372b3 and ccd85ec73 before the DP replica commit, as a side branch without replicas; + 59aea07f9), MILES_NEXT_IMAGE -> @sha256:d54dd445... - Dockerfile ARG MILES_COMMIT; test build-record dir. - evidence/ports-image/2026-10-24-59aea07-2fa8801: build record and manifest (differs from ebc6519-2fa8801 only in Miles commit and tag). Image check (scripts/check_miles_ports_image.sh): 13 checks pass. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An island without sync writes no full-parameter cut and yeto never calls Miles save_model, so a run had no HF model of its middle or final rounds for an evaluation container. --rl-export-hf-rounds R[,R...][,final] (with --rl-checkpoint-store; Miles, full parameter, sample role both, no --rl-elastic): at the sync boundary of round R (the island's R-th trained round), before the next publication, the island writes its own model to <store>/hf/island<learner id>/round-R. "final" is the round whose boundary stops the island: on a synced full-parameter island after the final manifest arrived and the pushes drained, before the final ACK (the syncer waits for the island within its final-ACK timeout and the launch does not end during the export); without sync, the last round. - engine/hf_export.py (backend-neutral): ExportRounds, HfRoundExports. The backend writes into a local staging directory and leaves its completion marker (.complete); the island takes the marker out, removes an older directory of the round, puts the directory (one commit), then puts the marker and, for the final round, hf/island<k>/final.json in a second put (second commit). A directory with the marker is complete and committed. One rl_hf_export tape event per export: round, final, policy_version, path, store_kind, ok, files, bytes, write_s, upload_s, seconds, error. A failed export is taped and the island trains on. - driver: IslandDriver.hf_exports, called after the data position record of each round (phase hf_export). - Miles: release_plugin.full_checkpoint_writer (every rank writes a share with the trainer on one node, else export_hf on rank 0), also used by the staged policy releases; entry.hf_exports_for; island flags --rl-export-hf-rounds / --rl-export-hf-store and their checks. - launcher: validation (needs --rl-checkpoint-store; refused for verl, --rl-elastic and sample roles; WARNING for rounds past --total-steps x --local-rl-rounds-per-sync), forwarding of the rounds and <store>/hf, and the exports count as a use of the store on a synced launch. - tests/test_rl_hf_export.py: CPU doubles, no Ray. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
stdout carries the --dry-run plan JSON; the warning made it unreadable. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…odel when an upload fails A Modal Volume reports the removal of a missing path as InvalidError "No such file or directory", which the store did not treat as missing, so the first export of every round failed at its upload and the staging copy was deleted with it. - modal_ckpt_store: remove_tree removes, and on any failure looks the path up (listing each parent from the Volume root down); the error is raised only when the path is still there. The runner-interpreter script is built from the same two functions. - hf_export: the upload (remove, put, marker) runs up to 3 times (waits 10 s, 30 s); the tape gets upload_attempts and upload_errors. When the last attempt fails the staging copy stays with its marker put back (staging_kept), at most 2 per island process (staging_dropped). - tests: a Volume fake that raises "No such file or directory" for a missing path; retries; kept and dropped staging copies. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.
Summary
This PR moves multi-island RL to one syncer service. The syncer serves LoRA runs and full-parameter runs. The base stays
s26-e11-cluster(#288).This update moves the head from
869078a5to718b2f14. The new head adds these parts:--rl-export-hf-rounds, which writes an island's model as HF directories, and a fix for its upload to a Modal Volume.A 2-island asynchronous full-parameter run of Nemotron-3 Nano 30B-A3B ran to the end on 32 x H200 at
33cce368. Its first GPU checks of the new mechanisms passed. One HF export of the 1-island control run failed (see "GPU validation"). Held-out pass@1 went from 0.521 (base) to 0.739 (merged global model).718b2f14adds only the HF export fix. That fix has CPU tests only.What the new head merges
The first-parent history from
869078a5to718b2f14has these merges and commits.Branches from other open draft PRs. Where a branch conflicts with this PR, the merge keeps this PR's behavior.
s26-e04-imgasserts26-e7-verl-megatrons26-e45--rl-sampler-fused-stage). Scale report tools.s26-e1-overheads26-e6-quant--rl-rollout-quant-tier, default 0 = BF16), an FP8 GPU allow list and tier launch checks. This PR keeps its own Volume writer guard, pins and single-island full-parameter rule.s26-e03-ablations26-e2-async--rl-async-buffer-rounds. With overlapped generation the buffer depth defaults to--rl-max-policy-age. An optional adaptive TIS.s26-e9-robusts26-agentic-merges26-agentenv-migratemichaellchung.Feature branches and direct commits:
s27-sync-overlap(922f3880)s27-task-assign(f9318d15)tools/eval/data_split_check.py.s27-dp-sync(d93c97fe)s27-120b-gawwiths27-120b-prep(b95e1f96)--moe-layer-freqmask without spaces.s27-disagg-fast(4dc9acac, with4c702a55reverted)s27-sync-boundary(0c4750a2)3b0bafc6--dp).63afafafrl_round_data_rowstape event.76f067d3,72e9eef4fadbffc3,33cce368--rl-export-hf-rounds. The warning goes to stderr, so the--dry-runJSON stays valid.718b2f14Behavior changes and new options
Asynchronous full-parameter rounds
YETO_FULL_PARAMETER_BACKGROUND_PUSH=0keeps the synchronous push.YETO_FULL_PARAMETER_PUSH_WORKERS(default 3) sets the number of workers.8edea35a7, full-parameter chunks go through Ray as numpy buffers (one copy on put, mapped on get). Before, Ray pickled CPU tensors at about 0.30 GB/s per thread.YETO_PUSH_ENCODE_THREADSthreads (default 4). The bytes are identical to the old encoder. For a 0.5B-value fragment the encoder took 4.3 s before and 0.65 s after (CPU).lazy_exportfeature, a local round reads no whole master. The island hashes, NaN-checks and model-checks each pushed fragment when it materializes the fragment. Miles commits per parameter (partial commit). Every 16th round still checks the whole model (YETO_FULL_PARAMETER_FULL_CHECK_EVERY, 0 = never).YETO_FULL_PARAMETER_LAZY_CAPTURE=0turns the lazy path off.fnow waitsf * H // Pmore rounds. Later pushes keep the H gate.YETO_FULL_PARAMETER_STAGGER_FIRST_PUSH=0turns this off.--final-cut-payload {full,manifest}. New launcher option--rl-sync-final-cut {auto,ack,download}(full-parameter preset only).autogivesackin--rl-sync-mode asyncwithout--rl-eval-interval. It givesdownloadin all other cases. The strict mode keepsdownload, so its commands do not change.ack, the syncer sends only the final manifest. Each island drains its pushes and sends its final ACK. No island downloads the f32 final cut (126 GB for a 30B model).Full-parameter sync at DP > 1
921cde8c7gives each parameter one owner rank per replica group. The replicas apply the owner's checked bytes. The layout lists each parameter once.921cde8c7.--rl-optimizer-offload-fraction.8192 x TPtokens per GPU, and the flex dispatcher with DeepEP when DeepEP is present.tools/nemotron_h_offline_layout.py --dp Ncomputes the layout hash before launch (scope: TP 1, EP = DP). Nano at TP1/EP8/DP8 gives 64 fragments and layoutc2c3cb49…. An island on GPU accepted this hash.Engines on a multi-node island
--rl-island-gpus-per-node). The launcher lays out the trainer and the engines over the nodes, as for a LoRA fixed partition.ip_local_port_rangeto 32768-60999 when the range starts lower. Before, an ephemeral port could take a port that Miles picks for an engine, a router or the trainer master. Then one engine of several failed with "address already in use".Data split and data rows
--rl-data-split auto(new default, for N >= 2 training islands). IslandkofNreads the rowsiwithi % N == k. The launcher sends--rl-data-slot k/N.--rl-island-tasksoverrides the split.sharedkeeps the old behavior and prints a warning. External learners fall back tosharedwith the same warning.Passes. Pass 0 reads the rows in data order. Miles reshuffles each later pass with a seed from
--seedand the pass number.Data positions. A synced full-parameter island with
--rl-checkpoint-storegets--rl-data-position-store. It records its data cursor after each sync boundary, one file per record. A process that joins a running pool continues after the last record. A refused write goes on the tape, and the island trains on.rl_round_data_rowsevent. The driver tapes one event per island per round when--rl-observe-timelineis on and the island reads a row slot. Fields:rollout_id,policy_version,samples,row_count,rows(arithmetic runs[[first, last, step], ...]). verl does not report the rows. Then the driver writes no event.tools/eval/data_split_check.py. The tool reads the island tapes of a run. It checks these conditions:The tool prefers
rl_round_data_rowsand falls back torl_trajectory_rewardfor older tapes.Nemotron-H Super VL
NemotronH_Omni_Reasoning_V3VL config as its language model. The FLOPs count only the language model. The frozen vision names now includevision_projector.*andsound_model.*.MILES_NEMOTRONH_KEEP_MTPis set). It reads a VL checkpoint as its language model and does not build the vision part.models.pyregistersnemotron35-super-vl(249 GB).--rl-export-hf-rounds--rl-export-hf-rounds R[,R...][,final]. RoundRis the island's R-th trained round.finalis the round whose boundary stops the island. On a synced full-parameter island, the export runs after the final manifest arrives and the pushes drain, before the final ACK. The syncer waits for the island within its final-ACK timeout. Without sync,finalis the last round.--rl-checkpoint-store, Miles, full parameter, sample roleboth. The launcher refuses verl,--rl-elasticand other sample roles. The island refuses LoRA. The launcher prints a warning on stderr for a round past--total-steps x --local-rl-rounds-per-sync.<store>/hf/island<k>/round-<r>/. Each complete directory has a.completemarker. The final export also writes<store>/hf/island<k>/final.json.final,final.json(second commit). So a directory with the marker is committed.rl_hf_exporttape event per export. A failed export goes on the tape, and the island trains on.718b2f14. A Modal Volume reports the removal of a missing path asInvalidError: No such file or directory. The store did not treat this as "missing". So the first export of each round failed at its upload, and the island removed the staging copy. Now:remove_treeraises only when the path is still there after the removal.upload_attemptsandupload_errors.staging_kept). Each island process keeps at most 2 such copies (staging_dropped).Trainer and sampler islands
--rl-release-inlinekeeps the old inline path.YETO_RL_RELEASE_EXPORT=milesuses the Miles export.Volume.batch_upload.--rl-cut-every Nand--rl-cut-auto-mtbf-sreplace the default as before. verl keeps one cut per round.Island relaunch
--recover-timeoutseconds (default 1200).--no-island-relaunchor--modal-retries 0turns this off.Pins and image
59aea07f9(michaellchung/miles, branchyeto/s27-closing). It adds to the previous pind1616ba12:8edea35a7(zero-copy chunks),921cde8c7(DP replicas),d78bf39e4(pinned CPU masters),b7868074c(language-only VL bridge) and63cb372b3(per-fragment boundary).2fa880182(unchanged).ghcr.io/michaellchung/yeto-miles-ports:59aea07-2fa8801@sha256:d54dd4458d08fb2c6012278ac48c285b89bb51b1e50584ad9a032fc18b6ef18a. The image check (scripts/check_miles_ports_image.sh) passes all 13 checks.718b2f14changes only yeto files. It needs no new image.GPU validation of the new head
Closing run (code
33cce368, image59aea07-2fa8801)Model: Nemotron-3 Nano 30B-A3B. Backend: Miles, full parameter. Both runs started within one minute of each other on Modal H200.
s27-bb-a1)s27-bb-b1)tokens2-over-stepsweights, int4 pushes, 64 fragments. 768 outer merges.--rl-single-island-no-sync).numeric_answerreward. First 8192 rows of DAPO-Math-17k (A: split byi % 2).Evaluation: 675 held-out problems (AIME24 29, AIME25 25, DAPO held-out 283, MATH-500 numeric answers 338). pass@1 uses n = 8 at temperature 1.0. Errors are paired bootstrap standard errors over problems. Two evaluations of the base model differ by -0.0004 ± 0.0068.
Mechanisms that ran on GPU for the first time (run A):
--rl-sync-checkpoint-every 385)update_weightssucceeded every round (153 and 158 calls per island). The engineweight_versionmatched the round number. The publish phase took a median 3.3-3.5 s per round.rl_round_data_rowsdata_split_checkpassed. All rows came from the new event. No row was trained by both islands.--rl-export-hf-rounds718b2f14fixes.Health:
Other GPU runs since
869078a5s27-ap-ctested the background push, zero-copy chunks and int4 encoder on 2 islands x 4 x H200 (Nano). The median sync per round was 63 s and 54 s. Ins27-gr-jit was 403 s and 538 s. The 180 merges completed. Island 1 did not ACK in time while it downloaded the 126 GB final cut. This result led to the ACK-only final cut.s27-ba-a(closing shape with 2 engine containers per island, DP8, Nano): the island accepted the offline layout (64 fragments,c2c3cb49…). The DP replica checks had 0 errors. The NCCL weight update took a median 3.0 s. Health: 13 PASS, 0 WARN, 0 FAIL.s27-aw-b(Nemotron-3.5 Super VL 120B-A12B, 1 island of 5 x 8 H200, TP8/PP4/EP8, no sync, 2 steps): the island built the language-only bridge. The per-rank parameter counts matched the offline layout. All 28update_weights_from_distributedcalls returned 200. Logprob gap 0.0074 and 0.0095. Trainer GPU memory peak 84.7 GiB.Testing
33cce368, remote full CPU suite without Miles: 7431 passed, 0 failed, 128 skipped, 25 deselected, 3 xfailed.33cce368,cargo test: 226 passed.63afafaf..33cce368:cargoon PATH. Withcargoon PATH they passed.tests/test_rl_hf_export.py: 20 passed, 1 skipped (the skipped test passes with Miles on the path).59aea07f9on the path (25 files): 335 passed, 14 failed. All 14 failures have known causes:sglang_router.ray.toolspackage hides the yetotoolspackage.test_neutral_rewardsfail the same way at the merge base6504d537.718b2f14: 25 targeted files (Modal store,remove_tree, HF export, release store): 483 passed, 3 skipped, 0 failed. The full suite did not run on718b2f14.467d1d91), the remote full suite had 7202 passed and 0 failed. On the same host,869078a5had 2 failures in tests that depend on timing.Known limitations
Results:
Sync and layout:
--rl-sync-max-local-rounds-ahead) counts the island's own unmerged pushes. With a deep pipeline this makes the rounds synchronous (H = 1). It is off by default.Shapes not tested on GPU:
05c1fc0c…). Only the 120B one-island probe without sync ran.--modal-retries 1 --recover-timeout 1200, peer recovery on), but no island exited.Refused or missing:
8192 x TP. At TP8 this gives 65536, so a 120B run must set--rl-max-tokens-per-gpu 8192.rl_round_data_rowsand cannot export HF rounds. verl has the Nemotron-H parameter rules and a smoke tool only. Full-parameter multi-island RL runs on Miles only.--local-rl-rounds-per-synchas no effect on a single island.754605964and a new image. The current pin does not have them.michaellchung. The move toagentenvneeds the Miles commits and the image inagentenvfirst.Unsynced and HF export paths:
--rl-resume-storeto an unsynced full-parameter island. So run B wrote 3 round cuts of 473 GB each (about 140 s per save). 2 of 3 cut commits hit the 600 s timeout.718b2f14fix has CPU tests only.Merged branches:
s26-e1-overheadands26-agentenv-migrateas merged. The first merge kept the tree unchanged, so the default cut interval of 4 is not in the tree. The second merge took only the origin compatibility. A later change that wants the interval of 4 or the reference move must make it by hand.Other:
Earlier content of this PR (head 869078a)
Nemotron-H async full-parameter RL
yeto/rl/adapters/miles/models/nemotron_h.pywrites the Megatron model flags from the Megatron-BridgeHybridModelProvider. RL keeps the routing statistics fixed (--moe-router-bias-update-rate 0,--moe-aux-loss-coeff 0). The backend registry loads the recipe (rolenemotron_h_recipe), so the import boundary check passes.fc1hasFFNrows instead of2 x FFN.yeto/rl/hybrid_params.pynames the Mamba fp32 parameters, the frozen vision tower and the 512-expert layout.yeto/rl/hybrid_flops.pygives the Nemotron-H model FLOPs for MFU. A verl Megatron GRPO plan andtools/nemotron_verl_smoke.pyare included. The verl path is LoRA with one GPU per island.skip_resident).YETO_FULL_PARAMETER_DEFER_CAPTURE=0andYETO_FULL_PARAMETER_BATCH_APPLY=0turn them off.--rl-sync-wait-timeout-s(default 900) sets the island wait. Every push restarts the wait. The async fragment pipeline limit is 60. After an island trains no more, the island closes the last round of a fragment without a trainer apply.--rl-sync-max-local-rounds-ahead Kmakes an island wait at a boundary instead of training on its own unmerged push.YETO_MILES_TRAIN_EXTRA_ARGVappends allow-listed Megatron flags. None of them changes the parallel layout.--context-parallel Nreaches the island, the trainer layout (DP = world / (TP x PP x CP)), the placement request and the Miles--context-parallel-size.yeto.rl.numeric_answer_reward:score(numeric_answer) grades one-number labels from the last\boxed{}orAnswer:line. On 153,210 recorded responses the oldgsm8k_gradecredited 962 wrong answers and missed 200 right ones.gsm8k_gradedid not change.--observe-training-entropyand--mamba-training-ssm-states-dtypereach the Miles argv.How it is wired
--rl-sync-mode async|strict. Async is the default for multi-island RL. Strict is the control mode.--rl-sync-max-base-lagcounts rounds.--syncer-shardsshards the head.--rl-island-state-source {peer,catch-up,cut}sets where a new island takes its training state.peercopies parameters and inner optimizer state from a healthy peer.catch-uppulls all fragments from the syncer with a fresh optimizer.cutis refused for synced islands. Peer recovery is on by default where it applies.--modal-ephemeral-disk-gib,--rl-island-ray-object-store-giband--rl-island-ray-spill-dirset the island disk and the Ray object store.--rl-sync-checkpoint-everysets the checkpoint interval for async rounds.cut_to_hfwith a TP/PP/EP merge.Earlier GPU validation (Nemotron-3 Nano 30B-A3B, 2 islands x 4 x H200, Miles, full parameter, async)
s27-gr-ffdd468abs27-gr-g22b25c30f, imageaa6b887-2fa8801s27-gr-h2K=1s27-gr-i2abac7785, imageaa6b887-2fa8801s27-gr-j869078a5, imaged1616ba-2fa8801(Mambaseq_idxfix)d1616ba12. Runs befores27-gr-jtrained with the leak.PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True. Peak training memory was about 138 of 140 GiB.Earlier testing (head 869078a)
cargo test: 226 passed.test_delta_protocol.py. The same test failed on the base and passed 3 of 3 times alone.test_rda_manifest_probe, a known evidence-file problem).Draft PR. Review is not requested yet.
🤖 Generated with Claude Code