Repository navigation
Agentic SFT on Codex traces: keep tool calls, native Qwen mask, held-out eval, benchmark HMAC fix - #238
Open
michaellchung wants to merge 4 commits into
Open
Agentic SFT on Codex traces: keep tool calls, native Qwen mask, held-out eval, benchmark HMAC fix#238michaellchung wants to merge 4 commits into
michaellchung wants to merge 4 commits into
Conversation
Codex rollouts record tool use only as response_item function_call / custom_tool_call / local_shell_call (+ *_output). The converter dropped them, so agentic rows had no tool_calls (1 rollout: 21 msgs, 0 calls -> 161 msgs, 70 calls after this change). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Official Qwen3 and Qwen3.5 chat templates lack {% generation %}, so
--assistant-mask-mode native raised ExactAssistantMaskError. Rewrite only
the assistant branch of a matching Qwen template into a render-identical
tagged version and pass it as chat_template. The mask covers reasoning,
content, tool calls and <|im_end|>; system, user and tool tokens stay 0.
Templates that do not match the Qwen shape keep the existing error.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The legacy syncer now refuses islands without YETO_ISLAND_HMAC_KEY, so every DiLoCo arm of the LM benchmark died at syncer start. All arms run on one host; a random per-run key in the harness environment is inherited by the syncer and every learner, and an operator-provided key is kept. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Contributor
Author
|
Held-out eval of real cross-container run b (adapter rebuilt from the syncer checkpoint with
The CPU base value matches the GPU harness base (1.1300). Run b processed ~59M raw tokens across both islands, versus 4.19M per arm in the benchmark table, so run b is not comparable with that table. This result only shows that the real multi-island path trains and generalizes to rows it never saw. |
2 of 3 tasks
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
Make multi-island (Decoupled DiLoCo) agentic SFT on Codex traces work end to end, and fix two things that broke on current main.
codex_traces: keep tool calls. Codex rollouts record tool use only asresponse_itemfunction_call/custom_tool_call/local_shell_call(+*_output). The converter dropped them, so "agentic" rows had no tool calls. One real rollout: 21 messages / 0 tool calls before → 161 messages / 70 tool calls after.{% generation %}, so--assistant-mask-mode nativerefused them and users fell back to the syntheticlegacyformat. When a template matches the Qwen shape,data.pynow rewrites only the assistant branch to wrap the assistant output in{% generation %}. The rendered text is unchanged. Other templates behave as before: if the rewrite does not apply, the error is raised exactly as before.apply_chat_templateon 20/20. Every user/tool token is mask 0. The trained token share is 66.8%.tools/eval_sft_heldout.py: held-out assistant-token CE for a base model plus an optional adapter. It packs exactly like training and checks that adapter keys match on load.compare_diloco: per-run island HMAC key. The legacy syncer now refuses unauthenticated islands, so every DiLoCo arm of the LM benchmark died at syncer start. The harness now sets one random key per run, and the syncer and all learners inherit it. A key the operator already set is kept.Evidence (real GPU runs, Modal L40S)
Data: HF
AletheiaResearch/GPT-5.3-Codex-Spark-Codex, 200 real Codex CLI rollouts. Each rollout'soriginatoriscodex_execand its model isgpt-5.3-codex-spark. Reasoning is encrypted and was skipped. The dataset card states no license; we used it for internal testing only. Model: Qwen3-0.6B with LoRA r16 on all linear layers, seq 4096. Loss counts assistant tokens only.Fair same-host comparison (
scripts/compare_diloco.py, 1 container × 2 L40S, 2 seeds, 4.19M raw tokens per arm, 20 held-out rows):Both arms processed the same number of target tokens (2,462,499).
Real cross-container 2-island runs (
yeto launch, 2 × Modal L40S:1, syncer on a Nebius CPU head):OutOfRound), and only 25/48 outer steps merged both islands.--grace-tau 8 --grace-ms 3000. The syncer rejected 4 pushes, and 44/48 outer steps merged both islands.yeto.exportand passed the layout check.Tests
tests/test_codex_traces.py tests/test_data.py tests/test_data_formats.py tests/test_qwen_generation_template.py tests/test_lm_benchmark.py tests/test_losses.py tests/test_launch_auto.py tests/test_head_mode.py tests/test_benchmark_resume.py tools/test_eval_sft_heldout.py→ 167 passed, 1 skipped (offline HF cache).Known issues (not fixed here)
--grace-tau 2) collapses to 0 for short inner steps withfragments=4. Islands with even small arrival skew then take turns instead of merging. This PR does not change that default. The numbers are above, for a follow-up decision.FAILED) after printing "run finished", even though both islands SUCCEEDED and the checkpoint is intact. This happened in both runs a and b.argumentsas a dict. Codex traces carry JSON strings, so 19 of the 20 held-out rows fail even with the official 3.5 template. The shared normalizer is left unchanged.🤖 Generated with Claude Code