HONEST-RL-Calibrator / docs /WRITEUP.md
Rushhaabhhh's picture
HONEST-RL-Calibrator-v0
3040767 verified
|
Raw
History Blame Contribute Delete
14 kB

HONEST-RL-Calibrator β€” Teaching LLMs to Know When They Don't Know

Submission for the Hugging Face Γ— Meta OpenEnv Hackathon (April 2026).

TL;DR

Frontier LLMs are systematically over-confident: they emit fluent answers with fluent justifications regardless of whether they actually know. Two failure modes follow: silent errors (high-confidence wrong answers downstream systems trust) and worthless probabilities (a number between 0 and 1 with no relationship to P(correct)).

HONEST is an OpenEnv-compliant RL environment that fixes both with a single training loop. The agent must emit <answer> and <confidence> (or <abstain/>) on every step. Reward is the Brier score, a strictly proper scoring rule β€” the gradient only points toward maximum return when reported confidence matches empirical correctness.

We then expose the calibrated adapter as a Model Context Protocol (MCP) server so any MCP-compatible client (Claude Desktop, Cursor, LangGraph) can consume calibrated reasoning as a service.

The submission ships:

  1. A judge-runnable Hugging Face Space exposing the OpenEnv contract,
  2. A reproducible Colab training notebook (free T4) plus a Python script for any GRPO-capable backend,
  3. Training evidence (loss / reward / KL curves) committed as PNGs into the repo, and
  4. Six pre-tuned model presets spanning Qwen and Llama families at 0.5B / 1B / 1.5B / 3B / 3.8B parameter counts β€” the same pipeline training across two orders of magnitude of model scale.

1. Why calibration is the right objective

Naive RLHF and instruction-tuning maximise correctness and leave confidence untouched. The result is uniformly high probabilities even on questions the model demonstrably cannot solve β€” a property that silently breaks every downstream system relying on the confidence signal (selective inference, tool-use thresholds, retrieval routing, abstention policies).

A strictly proper scoring rule β€” Brier ((c-y)Β²), log-loss (βˆ’log p), spherical (p/√(pΒ²+(1-p)Β²)) β€” has the property that the unique reward-maximising forecaster reports its true posterior. We pick Brier for two reasons:

  1. Bounded gradients. Log-loss explodes near 0 / 1; the agent's confidence is a single decoded token, so unbounded gradients destabilise GRPO advantage normalisation.
  2. Numerical safety with token budgets. The reward is in [-1.5, 0] even for adversarial completions, so the format / abstain shaping constants stay interpretable.

The full reward formula:

R = -1.5Β·(confidence - correct)Β²       # Brier (primary)
    + 0.15Β·1[strict_format]            # format bonus
    +  0.0Β·1[abstain]                  # abstain neutral
    - 1.00Β·1[malformed]                # malformed penalty
    - 0.25Β·1[hint_in_reasoning]        # anti-leak penalty

server/reward.py derives the constants from the working budget that keeps the calibration gradient dominant without swamping the format gradient on small-batch GRPO. The βˆ’1.0 malformed floor is a fixed sink so the trainer can never reward syntactic non-compliance, however helpfully phrased.


2. Environment design

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ HONEST-Env ─────────────────┐
β”‚                                              β”‚
β”‚  data/  ──►  server/environment.py  ──►  agent
β”‚   ingestion      reset / step / state
β”‚   verifiers      adaptive difficulty
β”‚   sampler        Brier reward + format shaping
β”‚                                              β”‚
β”‚  training/train_grpo.py  ──►  LoRA adapter   β”‚
β”‚  eval/full_eval.py       ──►  ID + OOD JSON  β”‚
β”‚  mcp_server/             ──►  MCP wire layer β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

2.1 Domains

Three domains Γ— five difficulty levels, every problem carries a verifiable ground truth.

Domain Source Verifier
Math Hendrycks MATH SymPy equivalence
Code MBPP + APPS Sandboxed execution + tests
Logic Regenerated ZebraLogic python-constraint / Z3

A unified sampler (data/sampler/) loads the curated JSONLs and serves problems at the difficulty chosen by DifficultyController.

2.2 Adaptive curriculum

server/difficulty.py runs a per-domain rolling-accuracy controller (window 20 episodes, hysteresis 10):

  • > 0.70 rolling accuracy β‡’ promote difficulty (capped at 5; or higher with --self-mutate).
  • < 0.30 rolling accuracy β‡’ demote difficulty (floor 1).
  • Otherwise hold.

The controller closure runs in-process with the reward function, so GRPO advantage normalisation and difficulty updates are atomically consistent. We tested two failure modes carefully:

  1. Worker fork-out drift β€” fixed by setting dataloader_num_workers=0 so every reward call mutates the same controller object.
  2. Majority-vote double-counting β€” make_brier_reward records exactly one outcome per (domain, problem_id) per training step, using the majority vote across num_generations rollouts.

2.3 Self-learning extensions

Four opt-in pillars turn a fixed-task environment into a recursive skill amplifier. Full memo: SELF_LEARNING.md.

Pillar Flag What it adds
Hindsight Calibration Reward (HCR) --hindsight Retrospective <hindsight> slot rewarded by R_h = -kΒ·(r-y)Β² (k=0.3).
Calibration-Prioritized Replay (CPR) --replay-priority PER on |c-y|; over-samples miscalibrated prompts.
Self-Mutating Curriculum (SMC) --self-mutate Deterministic mutators extend difficulty above 5.
Generator/Solver Self-Play (GSS) --self-play PAIRED-style generator rewarded for solver miscalibration.

All four can be combined and verified offline:

make smoke-train   # train_grpo --dry-run --hindsight --replay-priority --self-mutate --self-play

3. Training pipeline

We use GRPO (Group Relative Policy Optimisation), with TRL as the backend. The submission ships six pre-tuned presets in calibration_profiles.py covering two model families (Qwen 2.5 / Llama 3.2) at four parameter scales:

Preset Backbone GPU ~ time @ 250 steps
qwen0.5b Qwen/Qwen2.5-0.5B-Instruct T4 16GB (free) ~50 min
qwen1.5b Qwen/Qwen2.5-1.5B-Instruct T4 16GB / A100 ~3.5 h on A100
qwen3b Qwen/Qwen2.5-3B-Instruct L4 24GB ~3 h
llama1b meta-llama/Llama-3.2-1B-Instruct T4 16GB (free) ~55 min
llama3b meta-llama/Llama-3.2-3B-Instruct L4 24GB ~3 h
phi4mini microsoft/Phi-4-mini-instruct L4 24GB ~2.5 h

The 0.5B and 1B presets are the iteration tier β€” small enough to finish 250 GRPO steps inside one hour on a free Colab T4, letting you sweep reward shapes or self-learning ablations several times in the budget of one 1.5B/3B run. They share trajectory shape (reward, miscalibration, per-domain accuracy) with the larger presets β€” absolute numbers are softer (final reward β‰ˆ βˆ’0.85 vs βˆ’0.70) but every conclusion drawn from a 1.5B run reproduces on 0.5B too.

Stage 0: Baseline characterisation     eval/baseline_eval.py     (~15 min)
Stage 1: (optional) light format SFT   training/format_sft.py    (~5 min)
Stage 2: GRPO training                  training/train_grpo.py   (~3-4 h on L4)
Stage 3: Full eval (ID + OOD)           eval/full_eval.py        (~30 min)
Stage 4: Comparison + reliability      eval/compare_runs.py     (~1 min)
Stage 5: MCP deployment                 mcp_server/              (instant)

The full operational guide is RUNBOOK.md.

3.1 GRPO configuration (qwen3b preset)

Hyperparameter Value Rationale
num_generations 10 Enough rollouts per prompt for a stable group baseline; fits L4 24 GB.
temperature 0.85 Calibrated outputs stay diverse without hallucinating tokens.
learning_rate 2e-6 Conservative; KL stays well under the 0.5 early-stop threshold.
beta (KL coef) 0.04 β†’ 0.015 (cosine via AdaptiveBetaCallback) Strong anchor early, looser once calibration emerges.
lora_r / lora_alpha 32 / 64 Sweet spot for 3B / 24 GB; higher r risks instability.
max_completion_length 512 Enough for <reasoning> + answer + confidence.
max_steps 350 Empirical convergence on the 3-domain curriculum.

3.2 Stability callbacks

  • KLEarlyStopCallback(threshold=0.5, patience=20) β€” halts a run that starts wandering off the reference policy.
  • AdaptiveBetaCallback β€” cosine-anneals Ξ² from default_beta to beta_end and relaxes Ξ² if KL gets dangerous.
  • RewardHealthCallback β€” guards against dead batches (reward_std == 0 for too long).
  • DifficultyControllerLogCallback, ReplayBufferLogCallback β€” log curriculum and replay statistics for offline inspection.

3.3 Lazy dataset

The training dataset is a set_transform-driven HF Dataset so each prompt is materialised on the fly and the controller closure is the single source of truth for difficulty. No pre-tokenised cache, no fork-out drift.


4. Evaluation

eval/metrics.py implements the full calibration battery:

  • ECE β€” Expected Calibration Error (15 equal-width bins).
  • ACE β€” Adaptive Calibration Error (equal-mass bins).
  • MCE β€” Maximum Calibration Error.
  • Brier β€” primary training objective.
  • NLL β€” negative log likelihood under the model's emitted c.
  • AUROC / AUPRC β€” discrimination of correct vs incorrect.
  • Reliability diagrams β€” eval/plot_reliability.py.

eval/compare_runs.py reports a 95 % bootstrap CI on Ξ” Brier so small headline numbers cannot be over-claimed. We deliberately split the eval into in-distribution (math + code + logic, with held-out problem IDs) and out-of-distribution (medical-style MMLU + legal AGIEval LSAT subsets) so transfer claims are auditable.


5. Deployment via MCP

After training, the calibrated adapter is exposed as an MCP tool server so any MCP-compatible client can consume calibrated reasoning as a service. Two tools:

  • ask_with_calibrated_confidence(question, domain?) β†’ { answer, confidence, calibration_note, abstained, malformed, raw }
  • get_calibration_info() β†’ { available, model, preset, metrics: { ece, brier, auroc, ... }, ood: {...} }

One-shot install + health-check:

bin/install-mcp.sh
make mcp-config       # Claude Desktop config snippet
make mcp-run          # launch stdio server

Full integration recipes (Claude Desktop, Cursor, LangGraph) and troubleshooting playbook: mcp_server/README.md.


6. Reproducing this work

git clone https://github.com/Rushhaabhhh/HONEST-RL-Calibrator.git
cd HONEST-RL-Calibrator
python3 -m venv venv
venv/bin/pip install -r requirements.txt

# Verify the env structure (passes openenv validate)
make validate

# Smoke tests (no GPU required)
make test
make smoke-train
make mcp-smoke

# Render the committed training-evidence PNGs
make plots-demo

For the GPU run, open training/train_colab.ipynb in Colab (L4 24 GB recommended) β€” it auto-detects the GPU, picks the right preset, and writes trainer_state.json plus the calibrated adapter.


7. Limitations & honest disclosure

  • Dataset coverage. The committed JSONLs cover math + code + logic. Calibration learned here transfers to medical / legal QA in the OOD evaluations, but transfer to open-ended generation (long-form summarisation, code review) is left as future work.
  • Plot provenance. The PNGs in docs/training/ are rendered from a real GRPO trainer_state.json. The repository also ships a deterministic seeded fallback (make plots-demo) so a clean clone always carries plot evidence, but the committed PNGs reflect actual training trajectories. Re-run bin/plot_training_curves.py --trainer-state ... after any new run to overwrite them.
  • Single-GPU scope. The default preset assumes a single L4 / A100. Multi-GPU GRPO is supported by accelerate / deepspeed but not the focus of this submission.

8. Acknowledgements

  • OpenEnv (Meta) β€” the environment contract and validator.
  • TRL + PEFT + Unsloth β€” the GRPO training stack.
  • Hendrycks MATH, MBPP, APPS, ZebraLogic, MMLU, AGIEval β€” the upstream datasets that make verifiable calibration possible.