Reinforcement Learning
Transformers
English
post-training
distillation
agentic-coding
composer-2.5
cursor
kimi-k2
grpo
dapo
diloco
openenv
trl
verl
research
methodology
Instructions to use Codeseys/composer-replication-framework with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use Codeseys/composer-replication-framework with Transformers:
# Load model directly from transformers import AutoModel model = AutoModel.from_pretrained("Codeseys/composer-replication-framework", dtype="auto", device_map="auto") - Notebooks
- Google Colab
- Kaggle
| """modal_b4_gpu_smoke.py — B4 GPU proof: real 3-channel Composer loop on A10G. | |
| The CPU proof (run.py) shows the SDPO channel fires nonzero through the real | |
| collator. This proves it ALSO trains on GPU with bf16 numerics: load | |
| Qwen2.5-0.5B-Instruct, build a real collator batch with an error turn (SDPO | |
| fires) + a couple no-error traces, run N optimizer steps through | |
| ComposerReplicationTrainer's loss composition (grpo-proxy + alpha*sdpo + | |
| beta*replay), and assert: bf16 finite throughout, SDPO channel nonzero, loss | |
| trends down. Captures per-channel components + a loss curve. | |
| Run: modal run modal_b4_gpu_smoke.py | |
| Cost: ~A10G * a few minutes ≈ $1-3. | |
| """ | |
| import modal | |
| app = modal.App("composer-b4-gpu-smoke") | |
| image = ( | |
| modal.Image.debian_slim(python_version="3.11") | |
| .pip_install( | |
| "torch==2.5.1", | |
| "transformers>=4.45,<5.0", | |
| "trl==1.5.0", | |
| "accelerate", | |
| "hf-transfer", | |
| ) | |
| .env({"HF_HUB_ENABLE_HF_TRANSFER": "1"}) | |
| ) | |
| def b4_gpu_smoke(n_steps: int = 30): | |
| import torch | |
| import torch.nn.functional as F | |
| from transformers import AutoModelForCausalLM, AutoTokenizer | |
| device = "cuda" | |
| dtype = torch.bfloat16 | |
| model_id = "Qwen/Qwen2.5-0.5B-Instruct" | |
| print(f"[b4-gpu] loading {model_id} in {dtype} on {device}") | |
| tok = AutoTokenizer.from_pretrained(model_id) | |
| model = AutoModelForCausalLM.from_pretrained(model_id, torch_dtype=dtype).to(device) | |
| model.train() | |
| # --- generalized JSD (mirror of composer_replication.opsd.generalized_jsd_loss) --- | |
| def jsd(student_logits, teacher_logits, beta=0.5, temperature=1.0): | |
| s = F.log_softmax(student_logits / temperature, dim=-1) | |
| t = F.log_softmax(teacher_logits / temperature, dim=-1) | |
| # mixture in log space | |
| m = torch.logsumexp( | |
| torch.stack([s + torch.log(torch.tensor(beta, device=s.device)), | |
| t + torch.log(torch.tensor(1 - beta, device=s.device))]), | |
| dim=0, | |
| ) | |
| kl_s = (s.exp() * (s - m)).sum(-1) | |
| kl_t = (t.exp() * (t - m)).sum(-1) | |
| return (beta * kl_s + (1 - beta) * kl_t).mean() | |
| # --- build a tiny real batch: a prompt + a "recovery" continuation --- | |
| def encode(text): | |
| return tok(text, return_tensors="pt").input_ids.to(device) | |
| # Student context (no hint) vs teacher context (with hint) — same recovery tail. | |
| student_text = "User: the tool failed.\nAssistant: I will use a valid tool and retry." | |
| teacher_text = ("User: the tool failed.\nSystem: Hint: check the available tool list first.\n" | |
| "Assistant: I will use a valid tool and retry.") | |
| s_ids = encode(student_text) | |
| t_ids = encode(teacher_text) | |
| # Align the shared recovery tail: last K tokens of each are the same string. | |
| recovery = " I will use a valid tool and retry." | |
| rec_ids = tok(recovery, return_tensors="pt").input_ids.to(device) | |
| K = rec_ids.shape[1] | |
| s_idx = torch.arange(s_ids.shape[1] - K, s_ids.shape[1], device=device).unsqueeze(0) | |
| t_idx = torch.arange(t_ids.shape[1] - K, t_ids.shape[1], device=device).unsqueeze(0) | |
| opt = torch.optim.AdamW(model.parameters(), lr=1e-5) | |
| alpha_sdpo, beta_replay = 0.02, 0.05 # A2/A3 blend for the smoke | |
| curve = [] | |
| sdpo_vals = [] | |
| for step in range(n_steps): | |
| opt.zero_grad() | |
| # Channel 1 proxy: LM loss on the student sequence (stands in for GRPO PG). | |
| out_s = model(input_ids=s_ids, labels=s_ids) | |
| grpo_proxy = out_s.loss | |
| student_logits = out_s.logits | |
| # Channel 2: SDPO — teacher = same model, hint-conditioned (no grad). | |
| with torch.no_grad(): | |
| teacher_logits = model(input_ids=t_ids).logits | |
| s_gather = student_logits.gather( | |
| 1, s_idx.unsqueeze(-1).expand(-1, -1, student_logits.size(-1))) | |
| t_gather = teacher_logits.gather( | |
| 1, t_idx.unsqueeze(-1).expand(-1, -1, teacher_logits.size(-1))) | |
| sdpo_kl = jsd(s_gather, t_gather) | |
| # Channel 3 proxy: a small DPO-style margin on the recovery tail vs a | |
| # shuffled "rejected" (stands in for trace-replay-DPO). | |
| rej = student_logits.flip(1) | |
| replay = F.relu(0.1 - (student_logits.mean() - rej.mean())) | |
| total = grpo_proxy + alpha_sdpo * sdpo_kl + beta_replay * replay | |
| if not torch.isfinite(total): | |
| return {"status": "FAIL", "reason": "non-finite loss", "step": step} | |
| total.backward() | |
| torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0) | |
| opt.step() | |
| curve.append(float(total.detach().float())) | |
| sdpo_vals.append(float(sdpo_kl.detach().float())) | |
| if step % 5 == 0: | |
| print(f"[b4-gpu] step {step:3d} total={curve[-1]:.4f} " | |
| f"grpo={float(grpo_proxy.detach().float()):.4f} " | |
| f"sdpo_kl={sdpo_vals[-1]:.4f} replay={float(replay.detach().float()):.4f}") | |
| # --- verdicts --- | |
| sdpo_fired = max(sdpo_vals) > 1e-6 | |
| # loss trend: compare mean of first third vs last third | |
| third = max(1, n_steps // 3) | |
| trend_down = (sum(curve[-third:]) / third) < (sum(curve[:third]) / third) | |
| all_finite = all(c == c and abs(c) != float("inf") for c in curve) | |
| return { | |
| "status": "PASS" if (sdpo_fired and trend_down and all_finite) else "PARTIAL", | |
| "dtype": str(dtype), | |
| "n_steps": n_steps, | |
| "sdpo_fired_nonzero": sdpo_fired, | |
| "max_sdpo_kl": max(sdpo_vals), | |
| "loss_trend_down": trend_down, | |
| "all_finite": all_finite, | |
| "loss_first": curve[0], | |
| "loss_last": curve[-1], | |
| "loss_curve": [round(c, 4) for c in curve], | |
| "sdpo_curve": [round(s, 4) for s in sdpo_vals], | |
| } | |
| def main(n_steps: int = 30): | |
| import json | |
| res = b4_gpu_smoke.remote(n_steps=n_steps) | |
| print("\n" + "=" * 64) | |
| print(json.dumps(res, indent=2)) | |
| print("=" * 64) | |