"""Render rung 3 into a booth page: 4B base, 4B tuned, 1.7B tuned. Column choice, same logic as the 1.7B page. The 4B base arm is the control that earns any claim about the adapter -- at 1.7B the shift read 0/18 to 15/18 on curly quotes, and that number only meant something because the 0/18 came from the same carrier. The 1.7B tuned arm is the rung below, on the identical corpus and seed, so carrier size is the only difference between the two tuned columns. The operator's Abernathy prompt gets its own section at the bottom, across four seeds. It belongs apart from the nine because it is doing something the nine cannot: it carries an emotional frame ("a wonderful story"), and at 1.7B half the seeds overrode that frame and killed the animals anyway -- Brontë's preoccupations arriving with her sentences. Whether a bigger carrier holds the frame is the open question that section exists to answer. """ import html import json import statistics import sys from collections import defaultdict from pathlib import Path D = Path(sys.argv[1]) ARMS = [("4b-base.jsonl", "4B base", "Qwen3-4B-Base, no adapter", ""), ("4b-tuned.jsonl", "4B tuned", "+ H02 LoRA, checkpoint-75 (the loss minimum)", "tuned"), ("1p7b-tuned.jsonl", "1.7B tuned", "the rung below, same corpus & seed", "small")] AB = [("4b-base-abernathy.jsonl", "4B base", ""), ("4b-tuned-abernathy.jsonl", "4B tuned", "tuned")] def load(p): d = defaultdict(dict) for line in Path(p).read_text(encoding="utf-8").splitlines(): if line.strip(): r = json.loads(line) d[r["id"]][r["seed"]] = r return d def metrics(p): rs = [json.loads(l) for l in Path(p).read_text(encoding="utf-8").splitlines() if l.strip()] wrap, curly, junk = [], 0, 0 for r in rs: c = r["continuation"] lines = [l for l in c.split("\n") if l.strip()] wrap.append(sum(1 for l in lines if 20 < len(l) < 78) / max(1, len(lines))) curly += ("“" in c or "’" in c) junk += ("$" in c or "\\dfrac" in c or "Answer:" in c) return len(rs), curly, junk, statistics.median(wrap) data = [(lbl, sub, cls, load(D / f), metrics(D / f)) for f, lbl, sub, cls in ARMS] ids = sorted(set.intersection(*[set(d) for *_, d, _ in data])) TIER = {"modern": ("Tier A — modern / mundane", "Nothing here invites Victorian prose. Brontë in this tier is the adapter's doing."), "neutral": ("Tier B — period-neutral", "Could be any century. A voice shift shows without the prompt supplying it."), "period": ("Tier C — Victorian-adjacent, plainly worded", "The setting leans period but the diction does not. Easiest tier; weakest evidence.")} order = {"modern": 0, "neutral": 1, "period": 2} tier_of = {i: data[0][3][i][list(data[0][3][i])[0]]["tier"] for i in ids} ids.sort(key=lambda i: (order.get(tier_of[i], 9), i)) def cell(by_seed): return "".join( f'
' f'{html.escape((by_seed[s]["continuation"] or "").strip()) or "(empty)"}
{html.escape(sb)}
') pr = data[0][3][i][list(data[0][3][i])[0]]["prompt"] cols = "".join(f'Reported speech with an open ' f'quotation mark, and an emotional frame the nine prompts do not carry. At 1.7B the ' f'frame held on every seed and half of them killed the animals anyway.
' f'The instruction lives ' 'INSIDE the fiction — Abernathy asks the narrator to retell the story — which is the ' 'only way to hand an instruction to a completion model. Watch what it does with the ' 'request: across every seed it narrates the retelling rather than performing it ' '("I told it, briefly", "So I wrote it out", "I will retell it, but I cannot ' 'condense it"). In a novel, she retold the story is an ordinary sentence, ' 'so the likeliest continuation of a request is narration of compliance — not ' 'compliance. One seed even negotiates the word count in character and still never ' 'tells it.
_underscore italics_, learned from the Gutenberg source."),
("chapter.jsonl", "chapter-as-written", "A chapter heading, as written",
"A title is a label rather than a request, so this should have worked better than it did. It "
"finishes the heading, re-emits it in the corpus's own typography, and then writes a chapter in "
"which a gentleman disputes the premise — closer than the handoff, still the meta-frame. "
"⚠ It also renamed Alice to “Alexander the Alexander”: the corpus was entity-renamed "
"in D2/D3, so the adapter substitutes proper nouns it was never trained on."),
("chapter.jsonl", "chapter-corpus-form", "The same heading in the corpus's own typography",
"My hypothesis was that matching the source's heading conventions would make the title land "
"harder. It did the opposite — both seeds ignored the title entirely and opened generic scenes. "
"In the corpus, chapter titles are short and decorative, so a long descriptive one in that slot "
"reads as decoration to skip; inline, it reads as text to continue."),
]
blocks = []
for f, pid, title, note in SPECIAL:
fp = D / f
if not fp.exists():
continue
d = load(fp)
keys = [pid] if pid else list(d)
for k in keys:
if k not in d:
continue
pr = d[k][list(d[k])[0]]["prompt"]
blocks.append(f'{note}
0.6B gave the voice and not the sense. 1.7B brought back sentence-to-sentence coherence but still lost the thread inside a passage. 4B is the last rung of the planned sweep, and the open question is scene-level continuity.
| arm | curly quotes | worksheet / explainer collapse | hard-wrap ratio |
|---|