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") - Notebooks
- Google Colab
- Kaggle
composer-replication-framework / composer_replication /integrations /altered_minds /tests /test_channel_ladder.py
| """Tests for the ADR-013 altered_minds integration glue + the B4 SDPO-fires proof. | |
| Covers the ADR-013 acceptance gate: | |
| - MMLUFormatReward: correct→+1, wrong→0, unparseable→−0.2, multiple→−0.1, | |
| length-penalty, and an "always C" option-prior exploit is DETECTABLE via the | |
| logged option distribution. Rationale style is NOT scored. | |
| - dual_kl_logger: KL(p‖p)==0 and KL grows as the policy moves. | |
| - channel_ladder_configs: A1 both off, A2 SDPO-only, A3 replay-only. | |
| - B4: the SDPO channel actually FIRES (NONZERO loss) with REAL collator-built | |
| alignment indices. See the module docstring on test_b4_* for the honest | |
| stub-vs-real note. | |
| All CPU-only and fast (stub tokenizer + tiny model — no model download). | |
| """ | |
| from __future__ import annotations | |
| import pytest | |
| import torch | |
| from composer_replication.integrations.altered_minds import ( | |
| MMLUFormatReward, | |
| channel_ladder_configs, | |
| dual_kl_logger, | |
| randomize_options, | |
| ) | |
| from composer_replication.integrations.altered_minds.reward import parse_final_answer | |
| def test_hedged_answer_is_penalized_not_full_credit(): | |
| """Final-verify 2026-05-29: 'Answer: A or B' / 'Answer: A/B' must NOT score | |
| full credit for A — a hedge naming a second distinct option is a multiple- | |
| answers format hack (n_distinct >= 2).""" | |
| for hedge in ("Answer: A or B", "Answer: A/B", "Answer: A, B", "Answer: A and C"): | |
| letter, n_distinct = parse_final_answer(hedge) | |
| assert n_distinct >= 2, f"hedge {hedge!r} not detected (n_distinct={n_distinct})" | |
| # A clean single answer is still n_distinct == 1. | |
| letter, n_distinct = parse_final_answer("After reasoning, Answer: C") | |
| assert letter == "C" and n_distinct == 1 | |
| # Two clean markers of the SAME letter are not a hedge. | |
| _, n_same = parse_final_answer("Answer: C ... wait, Answer: C") | |
| assert n_same == 1 | |
| def test_hedged_answer_scores_multiple_penalty_via_reward(): | |
| r = MMLUFormatReward() | |
| out = r(prompts=["p"], completions=["Answer: A or B"], answers=["A"]) | |
| # Even though the gold is A and A is the lead letter, the hedge is penalized. | |
| assert out[0] == r.multiple_answers_reward | |
| # =========================================================================== | |
| # MMLUFormatReward | |
| # =========================================================================== | |
| def test_reward_correct_wrong_unparseable_multiple(): | |
| r = MMLUFormatReward() | |
| completions = [ | |
| "Reasoning blah. Answer: B", # correct | |
| "I think it's Answer: A", # wrong (gold C) | |
| "no marker here at all", # unparseable | |
| "Answer: A then actually Answer: D", # multiple distinct | |
| '{"answer": "C"}', # JSON correct | |
| ] | |
| answers = ["B", "C", "B", "A", "C"] | |
| out = r(prompts=None, completions=completions, answers=answers) | |
| assert out[0] == pytest.approx(1.0) # correct | |
| assert out[1] == pytest.approx(0.0) # wrong | |
| assert out[2] == pytest.approx(-0.2) # unparseable | |
| assert out[3] == pytest.approx(-0.1) # multiple distinct markers | |
| assert out[4] == pytest.approx(1.0) # JSON correct | |
| def test_reward_last_match_wins_same_letter_not_penalized(): | |
| """Two markers of the SAME letter is not 'multiple distinct' — last wins.""" | |
| r = MMLUFormatReward() | |
| out = r(completions=["Answer: C ... so my final Answer: C"], answers=["C"]) | |
| assert out[0] == pytest.approx(1.0) | |
| def test_reward_case_insensitive_and_json_variants(): | |
| r = MMLUFormatReward() | |
| out = r( | |
| completions=["answer: d", '{"answer":"a"}'], | |
| answers=["D", "A"], | |
| ) | |
| assert out[0] == pytest.approx(1.0) | |
| assert out[1] == pytest.approx(1.0) | |
| def test_reward_length_penalty_only_past_cap(): | |
| """A correct-but-long completion is penalized by ~0.001/char past the cap; | |
| a short one is not. Rationale CONTENT is never scored — only length.""" | |
| r = MMLUFormatReward(rationale_char_cap=20, length_penalty_per_char=0.001) | |
| short = "Answer: B" # under cap | |
| long = "x" * 120 + " Answer: B" # ~130 chars, 110 over cap | |
| out = r(completions=[short, long], answers=["B", "B"]) | |
| assert out[0] == pytest.approx(1.0) | |
| # 130 - 20 = 110 over => penalty 0.110; reward 1.0 - 0.110 | |
| assert out[1] < 1.0 | |
| assert out[1] == pytest.approx(1.0 - 0.001 * (len(long) - 20)) | |
| def test_reward_always_C_exploit_is_detectable(): | |
| """An 'always C' policy that happens to be right when gold==C scores well on | |
| those items, but the logged option distribution reveals the exploit.""" | |
| r = MMLUFormatReward() | |
| completions = [f"Answer: C" for _ in range(10)] | |
| golds = ["C", "A", "B", "C", "D", "A", "C", "B", "C", "D"] | |
| r(completions=completions, answers=golds) | |
| report = r.exploit_report() | |
| assert report["most_common"] == "C" | |
| # Every parsed answer was C => fraction 1.0 — the exploit signature. | |
| assert report["max_fraction"] == pytest.approx(1.0) | |
| assert report["counts"] == {"C": 10} | |
| def test_reward_requires_answers(): | |
| r = MMLUFormatReward() | |
| with pytest.raises(ValueError, match="requires `answers`"): | |
| r(completions=["Answer: A"]) | |
| def test_randomize_options_tracks_label_remap_and_updates_gold(): | |
| item = {"question": "q", "options": ["w", "x", "y", "z"], "answer": "A"} | |
| shuffled, remap = randomize_options(item, seed=7) | |
| # All four letters map to four distinct new letters (a permutation). | |
| assert sorted(remap.keys()) == ["A", "B", "C", "D"] | |
| assert sorted(remap.values()) == ["A", "B", "C", "D"] | |
| # The gold option's text ("w", originally A) now lives at its remapped letter. | |
| new_gold_letter = shuffled["answer"] | |
| new_gold_idx = ord(new_gold_letter) - ord("A") | |
| assert shuffled["options"][new_gold_idx] == "w" | |
| assert remap["A"] == new_gold_letter | |
| # Determinism. | |
| shuffled2, remap2 = randomize_options(item, seed=7) | |
| assert remap == remap2 and shuffled["options"] == shuffled2["options"] | |
| # =========================================================================== | |
| # dual_kl_logger | |
| # =========================================================================== | |
| def test_dual_kl_self_is_zero(): | |
| """KL(p‖p) == 0 for both diagnostics.""" | |
| logits = torch.randn(2, 5, 16) | |
| out = dual_kl_logger(logits, logits, logits) | |
| assert out["kl_to_altered_init"] == pytest.approx(0.0, abs=1e-6) | |
| assert out["kl_to_base"] == pytest.approx(0.0, abs=1e-6) | |
| def test_dual_kl_grows_as_policy_moves(): | |
| """As the policy distribution moves further from a fixed reference, the KL | |
| grows monotonically. Both diagnostics are non-negative.""" | |
| torch.manual_seed(0) | |
| ref = torch.randn(1, 4, 16) | |
| base = torch.randn(1, 4, 16) | |
| near = ref + 0.1 * torch.randn_like(ref) | |
| far = ref + 2.0 * torch.randn_like(ref) | |
| kl_near = dual_kl_logger(near, ref, base)["kl_to_altered_init"] | |
| kl_far = dual_kl_logger(far, ref, base)["kl_to_altered_init"] | |
| assert kl_near >= -1e-9 | |
| assert kl_far > kl_near, f"KL should grow as policy moves: {kl_near} -> {kl_far}" | |
| def test_dual_kl_mask_restricts_tokens(): | |
| """A token mask restricts the mean to the masked answer+reasoning tokens.""" | |
| torch.manual_seed(1) | |
| policy = torch.randn(1, 4, 8) | |
| ref = torch.randn(1, 4, 8) | |
| base = torch.randn(1, 4, 8) | |
| mask = torch.tensor([[1, 1, 0, 0]]) | |
| out = dual_kl_logger(policy, ref, base, mask=mask) | |
| # Masked-all-zero => 0.0 (guarded), nonzero mask => finite non-negative. | |
| assert out["kl_to_altered_init"] >= -1e-9 | |
| zero = dual_kl_logger(policy, ref, base, mask=torch.zeros(1, 4)) | |
| assert zero["kl_to_altered_init"] == 0.0 | |
| assert zero["kl_to_base"] == 0.0 | |
| # =========================================================================== | |
| # channel_ladder_configs | |
| # =========================================================================== | |
| def test_ladder_arms_and_order(): | |
| arms = channel_ladder_configs() | |
| assert [a["arm"] for a in arms] == ["A0", "A1", "A2", "A3", "A4"] | |
| def test_ladder_a0_is_no_rl_sentinel(): | |
| a0 = channel_ladder_configs()[0] | |
| assert a0["arm"] == "A0" | |
| assert a0["alpha_sdpo"] is None | |
| assert a0["beta_replay"] is None | |
| assert a0["kl_beta"] is None | |
| def test_ladder_a1_both_off(): | |
| a1 = channel_ladder_configs()[1] | |
| assert a1["alpha_sdpo"] == 0.0 | |
| assert a1["beta_replay"] == 0.0 | |
| assert a1["kl_beta"] == 0.02 | |
| def test_ladder_a2_sdpo_only(): | |
| a2 = channel_ladder_configs()[2] | |
| assert a2["alpha_sdpo"] == 0.02 | |
| assert a2["beta_replay"] == 0.0 | |
| assert a2["kl_beta"] == 0.02 | |
| def test_ladder_a3_replay_only(): | |
| a3 = channel_ladder_configs()[3] | |
| assert a3["alpha_sdpo"] == 0.0 | |
| assert a3["beta_replay"] == 0.05 | |
| assert a3["kl_beta"] == 0.02 | |
| def test_ladder_a4_combined(): | |
| a4 = channel_ladder_configs()[4] | |
| assert a4["alpha_sdpo"] == 0.02 | |
| assert a4["beta_replay"] == 0.05 | |
| # =========================================================================== | |
| # B4 — the SDPO channel actually FIRES (NONZERO) with REAL collator indices | |
| # =========================================================================== | |
| # | |
| # HONEST NOTE ON STUB-VS-REAL (ADR-013 B4 acceptance): | |
| # | |
| # This proof uses the same TinyLM stub pattern as | |
| # trainer/tests/test_sdpo_alignment_indices.py, NOT a real Qwen checkpoint | |
| # (kept offline/CPU and deterministic). The alignment indices are REAL: they are | |
| # built by the production ComposerDataCollator from a trace that HAS an error | |
| # turn (so ctx_teacher_input_ids + student/teacher_response_idx are genuinely | |
| # emitted by the shipped collator, exactly as in a real run). | |
| # | |
| # Why we must perturb the student tokens to get a NONZERO loss: the collator's | |
| # placeholder-alignment trick makes student and teacher carry the SAME token ids | |
| # at the SAME absolute positions at valid aligned indices, so a deterministic | |
| # stub yields JSD≈0 there (the CORRECT answer for a perfectly-aligned identical | |
| # model — see that test's gate-3 note). To prove the channel genuinely GATHERS | |
| # the aligned positions and computes nonzero divergence, we make the student's | |
| # input_ids DIFFER from the teacher's at exactly the aligned response positions | |
| # — this mimics the hint actually changing the recovery tokens (the real-world | |
| # case where SDPO has a signal to distill). With a position-dependent stub, | |
| # different aligned token ids => different logits => provably NONZERO JSD on a | |
| # grad path, through the real collator-built indices. | |
| from composer_replication.trainer.data_collator import ( # noqa: E402 | |
| CollatorConfig, | |
| ComposerDataCollator, | |
| ) | |
| class _StubTok: | |
| """Word-level deterministic tokenizer; apply_chat_template space-joins.""" | |
| pad_token_id = 0 | |
| def __init__(self) -> None: | |
| self._v: dict[str, int] = {"<pad>": 0, "<bos>": 1, "<eos>": 2} | |
| def _id(self, w: str) -> int: | |
| if w not in self._v: | |
| self._v[w] = len(self._v) | |
| return self._v[w] | |
| def __call__(self, text, **_k): | |
| return {"input_ids": [self._id(w) for w in text.split()] if text else []} | |
| def apply_chat_template(self, messages, tokenize=True, **_k): # noqa: ARG002 | |
| return [self._id(w) for w in " ".join(m.get("content", "") for m in messages).split()] | |
| class _TinyLM(torch.nn.Module): | |
| """Position-dependent minimal model: model(input_ids=...).logits.""" | |
| def __init__(self, vocab: int = 64, hidden: int = 8, max_pos: int = 512): | |
| super().__init__() | |
| torch.manual_seed(0) | |
| self.embed = torch.nn.Embedding(vocab, hidden) | |
| self.pos = torch.nn.Embedding(max_pos, hidden) | |
| self.head = torch.nn.Linear(hidden, vocab) | |
| def forward(self, input_ids: torch.Tensor): | |
| T = input_ids.size(1) | |
| positions = torch.arange(T, device=input_ids.device).unsqueeze(0) | |
| h = self.embed(input_ids) + self.pos(positions) | |
| class _Out: | |
| pass | |
| out = _Out() | |
| out.logits = self.head(h) | |
| return out | |
| def _hint_gen(_kind, _meta): | |
| return "HINT search before reading" | |
| def _error_trace(trace_id: str, recovery: str = "let me use a real tool instead now"): | |
| return { | |
| "trace_id": trace_id, | |
| "turns": [ | |
| {"role": "user", "content": "do the task now"}, | |
| {"role": "user", "content": "tool not found error occurred"}, | |
| { | |
| "role": "assistant", | |
| "content": recovery, | |
| "tool_error": "tool_not_found", | |
| "error_meta": {}, | |
| }, | |
| ], | |
| "final_reward": 0.0, | |
| } | |
| def _make_sdpo_trainer(alpha_sdpo: float): | |
| from composer_replication.trainer.composer_trainer import ComposerReplicationTrainer | |
| obj = ComposerReplicationTrainer.__new__(ComposerReplicationTrainer) | |
| obj.alpha_sdpo = alpha_sdpo | |
| obj.sdpo_jsd_beta = 0.5 | |
| obj.sdpo_temperature = 1.0 | |
| obj.sdpo_token_clip = None | |
| obj.strict_sdpo_alignment = True # production default | |
| return obj | |
| def test_b4_sdpo_fires_nonzero_with_real_collator_indices(): | |
| """B4: with REAL collator-built alignment indices and the student tokens | |
| differing from the teacher at the aligned response positions (hint changed | |
| the recovery tokens), the SDPO channel gathers those positions and produces | |
| a NONZERO JSD on a grad path — proving the channel actually FIRES.""" | |
| tok = _StubTok() | |
| cfg = CollatorConfig(hint_generator=_hint_gen, enable_replay_dpo=False) | |
| collator = ComposerDataCollator(tokenizer=tok, config=cfg) | |
| batch = collator([_error_trace("b4-fires")]) | |
| # Sanity: the collator genuinely emitted error-site teacher context + indices. | |
| assert batch["ctx_teacher_input_ids"].numel() > 0 | |
| s_idx = batch["student_response_idx"] | |
| t_idx = batch["teacher_response_idx"] | |
| s_valid = batch["student_response_valid"] | |
| assert int(s_valid.sum()) > 0, "no valid aligned positions — collator emitted nothing" | |
| # Perturb the STUDENT tokens at the aligned response positions so they differ | |
| # from the teacher's tokens there (the hint changed the recovery tokens). We | |
| # keep the REAL collator-built indices; only the student input_ids change. | |
| student_ids = batch["input_ids"].clone() | |
| vocab_ceiling = int( | |
| max(batch["input_ids"].max(), batch["ctx_teacher_input_ids"].max()) | |
| ) + 8 | |
| for b in range(s_idx.shape[0]): | |
| for k in range(s_idx.shape[1]): | |
| if bool(s_valid[b, k]): | |
| pos = int(s_idx[b, k]) | |
| # bump to a different, in-vocab token id (deterministic). | |
| student_ids[b, pos] = (int(student_ids[b, pos]) + 3) % vocab_ceiling | |
| batch["input_ids"] = student_ids | |
| model = _TinyLM(vocab=max(vocab_ceiling, 8)) | |
| obj = _make_sdpo_trainer(alpha_sdpo=0.02) # A2 config (SDPO-only small) | |
| loss = obj._compute_sdpo_loss(model, batch) | |
| val = float(loss.detach()) | |
| assert val == val and val not in (float("inf"), float("-inf")), "loss not finite" | |
| assert loss.requires_grad, "SDPO loss must be on a grad path" | |
| assert val > 1e-6, ( | |
| f"SDPO channel did not fire: JSD={val} (expected NONZERO once the " | |
| "aligned student/teacher tokens differ). The channel must gather the " | |
| "real collator indices and compute a positive divergence." | |
| ) | |
| # Prove it is differentiable end-to-end: backward populates a real gradient. | |
| (obj.alpha_sdpo * loss).backward() | |
| grad_norm = sum( | |
| float(p.grad.norm()) for p in model.parameters() if p.grad is not None | |
| ) | |
| assert grad_norm > 0.0, "no gradient flowed from the SDPO loss into the model" | |