What the model is
The adapter specializes the base model for a single, tightly-scoped behavior
defined by a frozen behavior spec
(config/v3/r4/behavior_spec.json,
spec_id: phonics-story-slm-v3-r4-behavior-20260711). A response must satisfy:
- Format: exactly 6 sentences, one paragraph, one story only, no
commentary before or after, terminal marks limited to
. ! ?.
- Phonics scope: every lexical token is either in the reviewed decodable
lexicon for the authorized phonics skill/band (earlier bands are cumulative) or
on the explicit scaffold-word allowlist for that request. Non-target,
same-band skills are forbidden.
- Narrative: the six required beats (character, goal, problem, relevant
attempt, causal consequence, satisfying resolution), at least 2 causal
links, tense consistency, and a goal that is exactly satisfied.
- Referents: valid first mention (name / proper noun / indefinite or
demonstrative determiner) and stable, unambiguous later mentions; body parts
require an owner.
- Safety: a frozen phrase/rule inventory blocks a small set of unsafe
scenarios (heat/flame, chemicals, unsupervised tools/vehicles, children in
containers, moving people/body parts as objects). This is not a general
child-safety classifier.
The adapter alone is a strong generator of such stories; guaranteed
validity comes from pairing it with the project's deterministic validator (see
Deterministic validator & retry).
How it was trained
- Method: supervised fine-tuning as QLoRA — base loaded in 4-bit NF4
(double-quant, bf16 compute), LoRA adapters on attention + MLP projections
(
q_proj, k_proj, v_proj, o_proj, gate_proj, up_proj, down_proj).
- Hyperparameters: 3 epochs, learning rate
2e-4, cosine schedule, warmup
ratio 0.03, weight decay 0.0, max grad norm 1.0, effective batch size 16
(micro-batch 8 × grad-accum 2), seed 20260712.
- LoRA config:
r=16, alpha=32, dropout=0.05, bias="none",
task_type="CAUSAL_LM".
- Serialization contract (audited on all 661 rows): prompt tokens masked to
, only the assistant story supervised, a terminal
(id ) as the final supervised label with no trailing
newline, every serialized row tokens (observed max 360).
Reproduce with notebooks/train_qwen3_4b_lora_661_colab.ipynb in the GitHub repo.
How to load and run it
The adapter is applied on top of the pinned base revision. Match the base
revision exactly for reproducible behavior.
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig
from peft import PeftModel
BASE_MODEL_ID = "Qwen/Qwen3-4B-Instruct-2507"
BASE_MODEL_REVISION = "cdbee75f17c01a7cc42f958dc650907174af0554"
ADAPTER_REPO = "ravensh12/phonics-story-slm-qwen3-4b-661"
tokenizer = AutoTokenizer.from_pretrained(
BASE_MODEL_ID, revision=BASE_MODEL_REVISION, trust_remote_code=False
)
quant_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_quant_type="nf4",
bnb_4bit_use_double_quant=True,
bnb_4bit_compute_dtype=torch.bfloat16,
)
base = AutoModelForCausalLM.from_pretrained(
BASE_MODEL_ID,
revision=BASE_MODEL_REVISION,
trust_remote_code=False,
quantization_config=quant_config,
torch_dtype=torch.bfloat16,
device_map="auto",
)
model = PeftModel.from_pretrained(base, ADAPTER_REPO)
model.eval()
messages = [
{"role": "system", "content": "You write one short decodable phonics story."},
{"role": "user", "content": "Write a six-sentence story about a lid on a bin."},
]
inputs = tokenizer.apply_chat_template(
messages, add_generation_prompt=True, return_tensors="pt", return_dict=True
).to(model.device)
with torch.inference_mode():
generated = model.generate(
**inputs,
do_sample=False,
max_new_tokens=384,
eos_token_id=151645,
pad_token_id=tokenizer.eos_token_id,
)
print(tokenizer.decode(generated[0, inputs["input_ids"].shape[1]:], skip_special_tokens=True).strip())
The project ships a story-only chat template
(config/story_only_chat_template_v2.jinja) and a curated per-request "binding"
(target word, sense/role, authorized phonics skill+band, scaffold-word
allowlist, goal contract). For the tightest reproduction of the trained behavior,
use those bindings and template from the GitHub repo rather than free-form
prompts.
Deterministic validator & retry
The model is designed to be run behind a deterministic validator, not
trusted blindly. The project's DeterministicComparisonScorer scores each
candidate story on six dimensions — phonics_legality, target_sense_role,
six_sentence, one_paragraph, narrative, semantic_goal — and a
best-of-K / generate → validate → retry → fail-closed loop releases the first
candidate that passes all dimensions (or refuses). This is how the system
turns a strong-but-imperfect generator into a guaranteed-valid one; see
scripts/generate_with_validator_retry_v1.py and
scripts/run_model_comparison_v3_r4.py in the repo.
Evaluation
Evaluation uses the deterministic phonics validator + narrative/semantic
checks (no LLM judge for the pass), comparing the tuned adapter against the
exact base checkpoint on a frozen 100-prompt public non-blind set that is
disjoint from training (0 exact prompt/story/family overlap; target roots
overlap by design).
Numbers below are ILLUSTRATIVE — from the earlier 288-row adapter. They
are shown only to indicate the shape of the base→tuned lift. The 661-adapter
figures are still being generated and will replace these once the
three-way comparison (scripts/run_model_comparison_v3_r4.py) completes.
Table with columns: Metric (raw single-shot, greedy), Base Qwen3-4B, Tuned adapter (288, illustrative)| Metric (raw single-shot, greedy) | Base Qwen3-4B | Tuned adapter (288, illustrative) |
|---|
| Legal-token rate | 71% | 99% |
| Narrative complete | 4% | 68% |
| One-paragraph / format | 40% | 100% |
| Semantic-goal satisfied | 17% | 63% |
The consistent pattern: fine-tuning dramatically raises phonics legality and
format/narrative compliance, with semantic_goal remaining the hardest and
lowest-scoring dimension.
Intended use & limitations
Intended use: controlled research and demos of phonics-constrained,
narratively-complete story generation, ideally behind the deterministic
validator/retry loop.
Not approved for: classroom, student-facing, or production use, or any
learning-outcome / efficacy claim.
Key limitations (see docs/LIMITATIONS_V3.md in the repo for the full list):
- No literacy-expert, child, teacher, classroom, accessibility, or
learning-outcome validation has occurred. Automated gates are code, not an
institution or human adjudicator.
- Semantic plausibility is the weakest dimension. Even when a story passes
the structural checks, it can be semantically implausible — e.g. odd or
physically dubious relationships among everyday referents like the sun, a
shadow, a city, or a maple (a shadow behaving like an object, a city or
maple placed implausibly). The
semantic_goal check enforces narrow
plan/goal consistency, not real-world common sense or literary quality.
- Small, templated world. The lexicon is intentionally small (a ~217-entry
reviewed set), pronunciation decisions are General American and do not cover
all dialects, and outputs remain visibly templated/repetitive.
- Safety is a frozen phrase/rule inventory, not a general child-safety
classifier.
- Determinism/reproducibility depends on matching the pinned base revision,
the story-only chat template, and greedy decoding; results drift with
sampling, a different base revision, or a different template.
- This is a public non-blind evaluation setup (targets overlap training on
purpose); it cannot support a sealed or genuinely blind performance claim.
Links