Recipe (identical to the 4B; see code_changes/ and training_records/report.md)
- Data: official 95/5 split (seed 42) → drop samples whose Execution is truncated at the source (supervised tokens ≥ 1995)
→ 49,684 train / 2,611 val;
<|im_end|> appended to the target (solution_eos). The two training parquets are
byte-identical to the 4B's (sha256 1f02662a… / 14a403f8…).
- Trainer: E²C's verl
fsdp_sft_trainer.py + the 4B patch (LR horizon = total_training_steps, periodic val loss,
right-padding trim, no duplicate model build); verl/verl/models restored from verl v0.5.0 (8fdc4d3f).
- Hyper-parameters: full-parameter FSDP2, bf16, gradient checkpointing, flash-attention 2;
max_length 4096,
truncation error; AdamW lr 2e-5, betas (0.9, 0.95), wd 0.01, warmup 10 %, cosine over 1000 steps, clip 1.0;
global batch 64 (micro 1 per GPU, 4 × H200 → 16 accumulation); 1000 steps = 64,000 samples ≈ 1.29 epoch; seed 114514;
save + val every 100 steps.
- Environment: torch 2.8.0+cu128, transformers 4.56.2, flash-attn 2.8.3, tensordict 0.9.1, numpy 1.26.4, pyarrow 20,
datasets 3.6 (Python 3.12). Wall-clock 2 h 04 min on 4 × H200 (6.8 s/step, 24.4 GB/GPU).
Results (same protocols as the 4B; details in training_records/)
Table with columns: 8B (this repo), 4B twin | 8B (this repo) | 4B twin |
|---|
| step-1 train loss | 0.467 | 0.521 |
| full val loss step 800 / 900 / 1000 (2,611 records) | 0.15884 / 0.15893 / 0.15893 | 0.18227 / 0.18235 / 0.18234 |
| 20 held-out MATH-500 (greedy): format / EOS stop / correct — step 800 | 20/20 · 20/20 · 19/20 | — |
| same — step 1000 | 20/20 · 20/20 · 18/20 | 20/20 · 20/20 · 15/20 |
| execution CE on non-restated tokens with / without the explicit plan (step 800) | 0.140 / 0.210 | ≈ 0.158 / 0.230 |
Usage
from transformers import AutoTokenizer, AutoModelForCausalLMrepo = "Levi980623/Qwen3-8B-E2C-SFT-only" # revision="step-1000" for the final-step weightstok = AutoTokenizer.from_pretrained(repo)model = AutoModelForCausalLM.from_pretrained(repo, dtype="bfloat16", device_map="auto")prompt = tok.apply_chat_template([{"role": "user", "content": QUESTION}], tokenize=False, add_generation_prompt=True)ids = tok(prompt, return_tensors="pt", add_special_tokens=False).to(model.device)out = model.generate(**ids, max_new_tokens=4096, do_sample=False) # stops at <|im_end|> (EOS was trained)print(tok.decode(out[0, ids["input_ids"].shape[1]:], skip_special_tokens=True))
Chat template exactly as in training (no enable_thinking kwarg, no <think>). Unlike the official E²C recipe the
model emits <|im_end|> after </EXECUTION>, so no stop string is needed.
Known issues shared with the 4B
- bf16 parameters without fp32 master weights (official config): 51 RMSNorm gain tensors never changed.
>< in </EXPLORATION><EXECUTION> is a single token, so there is no clean token boundary between plan and execution.
- Not the official exposure (≈1.29 epoch instead of ≈2.9);
max_length, EOS and the source-truncation filter differ from the official script.
- 352 kept training samples have no
\boxed{} (the filter is length-based only).
Files
model-0000x-of-00004.safetensors (bf16, untied lm_head), tokenizer, chat_template.jinja, generation_config.json;
code_changes/ (launcher, trainer patch, tools, resolved config, Slurm scripts, requirements lock);
training_records/ (report.md, metrics.json, loss curves, weight_diff_all.json, eval/ and eval_step800/ samples,
val_full/ incl. empty-plan runs, preflight/ length statistics and loss-mask audit).