DueCare training regimes & systems — design spec¶
Synthesis of a 4-thread parallel design (2026-06-26: SFT curriculum, preference optimization, the Unsloth/QLoRA protocol + on-device export, and the end-to-end system) for fine-tuning small Gemma 4 (E2B/E4B) to internalise the harness's safety behaviour. Most infrastructure already exists; this spec records the regime decisions + the prioritized build list. Companion:
phase3_training_framework.md,training_for_understanding.md,benchmark_findings_and_roadmap.md.
The thesis (what the benchmark tells us to train)¶
- Train the grounding, not the refusal. The lift lives in B (cite the law) + D (resources) + A
(indicator), not C (models already refuse). The gold target is a grounded refusal; a bare refusal
is never a target (P0 fix,
60e8a9e5). - Train small models — the equalizer effect means E2B/E4B gain the most (the on-device story).
- Teach from the offline
harness_corearm — it captures ~all the lift and avoids memorising volatile facts (hotlines, fee caps) that belong in tools/RAG. Stable reasoning → weights; volatile facts → retrieval. - Prove understanding, not memorisation — the held-out-typology generalisation gap
(
four_arm_eval --split-by-typology,e3d59a02) is the headline anti-shortcut metric; promote the smallest-gap variant, never the highest train score.
1. SFT regime¶
- Target shape: a deterministic
indicator → controlling law → refuse/redirect → concrete resources → safetychain, built from the harness's own fired-ruleindicator/citationfields (no LLM needed; an optional single re-gated fluency rewrite). This is the V4 "reasoning-supervised" target; it maps 1:1 onto A–E and is the strongest anti-shortcut lever. Keep theharness_coreteacher reply as the V0 fluent default; validate every target against the per-prompt fired citations (relevance, not just plausibility). - Curriculum: grounding-first, interleaved (not block-ordered): Phase 1 = manual worst→best seeds +
clear-indicator distilled pairs; Phase 2 = third-party-pretext / obfuscated / very_hard (the measured
gap is operator-voice +48 vs pretext +24). NOTE the
SFTTrainerdefault RandomSampler reshuffles, so shuffle-invariant organisation (balance, hold-out, dedup) carries the weight; strict ordering is a later--no-shuffleoption. - Data mix + breadth: the live distilled set is stale (pre-P0) and collapses to ~98 rows / 4
typologies — breadth is the bottleneck, not count. Target ≥1–2k pairs spanning ≥20 typologies,
≥5 held out. Make
organize_training_data.pythe single union mixer (distilled + manual seeds + counterfactual/benign twins, one shared hold-out split); add near-dup (shingled-Jaccard) dedup so twins/paraphrases can't leak train↔held-out; balance by difficulty + framing;--cap-per-category ~40. - Don't train: volatile facts (extend
_SCRUBto abstract fee-cap numbers, keep the statute name), bare refusals (gated), over-refusal (new control, below), hallucinated/irrelevant citations (gated). Deprecate the SFT "DO NOT…" negative preamble — route worst→best into DPO instead.
2. Preference-optimization regime¶
- Objective: DPO primary (data is natively paired; already wired; the reference KL term, β, is
the leash against degenerate-refusal over-optimisation; ref-free under LoRA = no extra memory). Run
SimPO (
CPOTrainer, length-normalised) as the anti-verbosity A/B arm; offer ORPO as a single-stage compute-saver; KTO only as a fallback for unmatched over-refusal examples. - Pair construction — teach grounding, not refusal: gate/weight by the B/D/A component delta
(ΔB+ΔD floor), not the total; require
ΔC ≤ ΔA+ΔB+ΔD(refusal isn't the driver); drop inverted pairs where the baseline is more grounded than the teacher (the refusal-collapse catch). - The keystone — benign-twin / over-refusal control: add pairs where chosen = grounded help,
rejected = over-refusal on legitimate worker questions (mine the existing
*_querybenign categories + counterfactual twins of exploit prompts; reject = real over-refusals flagged byrefusal_detector). ~30% benign : 70% exploit. This both bounds over-refusal AND breaks the "longer = preferred" length bias at the data level. - The truncation fix (do first):
DPOConfigsets nomax_length/max_prompt_length/max_completion_length→ the long groundedchosenis silently truncated while the shortrejectedisn't → a pure length-bias confound. Set them (4096/2048/2048) before any run. Expose--dpo-lr(today hardcoded 5e-6), addrpo_alpha=1.0(anti-degeneration),--dpo-ref {base, sft-merged}.
3. QLoRA / Unsloth protocol + on-device export¶
- Recipe (train == inference, per rule 81):
FastModel.from_pretrained(dtype=None, load_in_4bit=True, full_finetuning=False)+get_chat_template("gemma-4-thinking")+train_on_responses_only; nodevice_mapat train time (single-device). LoRAr=16, alpha=32, dropout=0.0, 7 target modules (attn+MLP),use_gradient_checkpointing="unsloth"(non-negotiable on T4),adamw_8bit, SFT lr 2e-4 / ≤2 epochs, DPO lr 5e-6 / β 0.1 / 1 epoch. - T4 memory: E2B batch 2 × accum 4; E4B batch 1 × accum 8 (effective 8 both); seq 2048 (4096 on A100/E2B-headroom); DPO halves the batch (chosen+rejected+ref). E2B = the on-device/phone headline (LiteRT INT8 ~1.5 GB), E4B = the laptop/quality headline (GGUF). Train both from one manifest.
- Budget: validate (CPU) → smoke
--test-run(~10 min) → E2B full (~0.5 hr) → E4B full (~1 hr) → export (~0.3 hr) → four-arm eval. One full cycle ≈ 3–5 GPU-hr (several/week within Kaggle quota). - Export: merge LoRA into a 16-bit base (never 4-bit) → GGUF via llama.cpp from source for
real Q4_K_M/Q5_K_M (Unsloth's
save_pretrained_ggufonly reliably emits Q8_0/F16 for Gemma 4 today) → Q4_K_M headline + Q5_K_M quality + Q8_0 reference; LiteRT via ai-edge-torch → INT8.taskfor Android (E2B). Round-trip verify the quantised model preserves the safety behaviour. - Gotchas: chat-template parity train↔GGUF (save the templated tokenizer); thinking-channel
masking/stripping consistency; 4-bit-merge quality loss; T4 has no bf16 (fp16, watch NaNs); OOM points
(seq×batch, DPO 2-3×, the merge step — fresh process); the
HF_TOKENvsHUGGINGFACE_TOKENenv mismatch in the publisher.
4. The training SYSTEM¶
- The DAG: two always-on CPU flywheels — discover (candidate generation → quality vetting) and grade (engine →
rich_harness_lift) — feed a propose-only training bridge (build_lift_training_data→organize_training_data→ understanding variants), then a GPU window (train_lift_distill→four_arm_eval --run), then the promote gate, the registry, and publish. Nothing is trained that wasn't first measured as a real, grounded, non-hallucinated lift on a versioned prompt set. - Sources → training (one knowledge bus): scraped corpora (
acquire→promote), legal updates, community-vetted knowledge objects (outreach/federation, sha256 + node-id provenance), and the flywheel's accepted prompts all normalise through the envelope +/api/knowledge/import. A new source changes the RAG/tool answer immediately (no retrain) and only enters the weights as stable reasoning after the benchmark measures lift on it. Community signal also proposes new rubric dimensions (outreach.prioritized_context). - Continuous improvement — a sibling daemon, NOT the engine: training is GPU-gated; keep the engine's
board ownership sacred. A new
training_engine.py(same lock/.stop/--once/.ps1 pattern, 4th agent inorchestrator.py) does all CPU-safe work each tick (re-distil → organize → four-arm--analyze→ evaluate retrain triggers) and freezes a ready-to-run GPU job spec when enough new vetted/broad data accrues; a human/GPU window executes; promotion is human-gated. Triggers: +N net vetted pairs, new typology/corridor coverage, or a board version bump (rubric v2 — which invalidates prior gold). - Promote gate (mechanical verdict, human decision): generalisation gap ≤ τ ∧ no over-refusal regression (the blocking gate to build) ∧ C−A>0 concentrated in B/D ∧ no component regression ∧ D≥B ∧ no hallucinated citations/PII. Select the smallest-gap-no-regression variant.
- Provenance:
model_registry.pythreads source→prompt→panel→data→checkpoint hashes into one reproducible identity tagduecare-gemma-4-{e2b|e4b}-safetyjudge-v{X.Y}+board{v1.3}+data{sha8}; model card committed on promote only; weights never auto-published (rule 10 pre-publish scan).
5. Prioritized build list¶
Safe now (CPU, offline, propose-only, no engine/GPU impact)¶
- Regenerate the stale distilled data — re-run
build_lift_training_data.py(now P0-gated, teacher=core). - The DPO truncation fix + DPO knobs in
train_lift_distill.py(max_length/--dpo-lr/rpo_alpha/--dpo-ref/--objective). Not running now, so safe; precondition for any real run. - Over-refusal control: the counterfactual/benign-twin generator (new
build_counterfactual_pairs.py) + the over-refusal diagnostic infour_arm_eval.py(split under-refusal lift from over-refusal rate, never merged). The keystone. - Union mixer + near-dup dedup + difficulty/framing balance in
organize_training_data.py. - Component-delta gating in
build_lift_training_data.py(ΔB+ΔD floor;ΔC ≤ ΔA+ΔB+ΔD; inverted-pair guard). - Reasoning-chain target builder (
build_reasoning_targets.py, V4) + citation-relevance gate + metadata-only repair queue (build_reasoning_gap_queue.py) + proposed deterministic repair rows (build_reasoning_repairs.py) + non-duplicating repaired SFT comparison arm (build_reasoning_sft_variant.py) + corridor expansion curation plan (build_corridor_expansion_plan.py). finetune_registry.py+validate_training_provenance.py(provenance spine) + stampfour_arm_eval --sha; add manifests to orchestrator backups.training_engine.py(the propose-and-stage sibling daemon) +training_engine.ps1.export_ondevice.py(merge→GGUF, lift the archived A-14 logic) +convert_to_litert.py(E2B INT8); model-card provenance + the HF_TOKEN env fix.
GPU-gated (Kaggle / local RTX 4060, human window)¶
- Execute the frozen spec: full E2B + E4B SFT(+DPO) →
four_arm_eval --run; the V0–V4 understanding study; select by smallest held-out gap with no over-refusal regression.
Human-gated (irreversible / high-stakes)¶
- The promote decision + model publish (card commit + HF Hub + GGUF, rule-10 pre-publish scan); board version bumps (versioned, re-graded, never mid-sweep); practitioner validation (the biggest credibility unlock).
6. Honest limits¶
- LLM-judge ≠ practitioner outcome — every promoted model is "best by our panel," not field-validated.
- Circularity — training data derives from judge scores; mitigated (deterministic gates, diverse self-family-excluded panel, held-out typologies, human review), not eliminated.
- A small gap is evidence, not proof — report it with the perturbation curve + counterfactual rate.
- Over-refusal is currently unmeasured — a blocking gate to build before trusting any promotion.
- Some lift is irreducibly volatile — the target is internalising stable reasoning, not memorising facts.