Why this is hard, and why it isn't just prompting
The obvious approach doesn't work. Ask any strong LLM to "rewrite this in
Victorian English" and you get pastiche: invented archaisms (existeth,
avoucheth, attaineth), 47% length inflation, a general air of
Renaissance-fair. Recognisably old-sounding, not remotely period-accurate.
And the task can't be supervised directly, because paired
(modern, Victorian) data does not exist. Nobody wrote the same paragraph twice,
a century apart.
So the training data is manufactured backwards. Real Victorian paragraphs
are the targets; a modern LLM rewrites them forward in time to produce the
sources. Modernising is the easy direction — a strong model does it reliably.
Training then runs in reverse, modern → Victorian, and the targets are never
synthetic: every one is a real sentence by a real Victorian.
Two details make it work:
- Destructive modernisation. The forward step doesn't paraphrase; it
extracts a content plan and rewrites from that. Mere paraphrase would teach
the model to invert surface scaffolding, yielding translationese rather than
register transfer.
- Nine registers per target. Each paragraph is modernised into formal,
casual, slang, terse, tumblr, fandom, fanfic, rapper and beat-prose variants,
making the model invariant to how modern its input is. These are all
synthesised from period targets, so register coverage needs no scraped
modern text at all.
The evaluation instrument is the other half of the idea. Quality is scored
as perplexity under violet-1b4, a
1.4B model pretrained exclusively on Victorian text. It has never seen modern
English, so it discriminates period authenticity in a way no general-purpose
metric can — which is what makes the pastiche failure measurable rather than a
matter of taste.
Results
600 period-compatible WikiText paragraphs, scored under violet-1b4.
Table with columns: Violet PPL, anachronisms/1k, copy-ratio, length | Violet PPL | anachronisms/1k | copy-ratio | length |
|---|
| real Victorian text (floor) | 24.3 | — | — | — |
| Patina | 26.8 | 1.16 | 0.88 | 1.00 |
| Patina + DPO variant | 26.3 | 1.10 | 0.88 | 1.00 |
~85% of the distance from modern English to genuine Victorian, at a 1%
confabulation rate and no length inflation.
Read the last row carefully, because it is the whole argument. Naive prompting
matches the real-Victorian perplexity floor exactly — by padding 47% and
inventing archaisms. Perplexity alone is necessary and nowhere near sufficient,
which is why anachronism rate and length ratio sit beside it.
On ordinary narrative prose Patina does better than the table implies: the
showcase examples above have a median perplexity of 15.7, below the
real-Victorian floor. The 26.8 figure is dragged up by WikiText's density of
modern proper nouns, which fidelity requires preserving.
Register invariance
Transformation depth (1 − similarity(input, output)) on held-out pairs:
Table with columns: input register, register-naive ablation, Patina| input register | register-naive ablation | Patina |
|---|
| fandom | 0.01 | 0.53 |
| rapper | 0.03 | 0.47 |
| slang | 0.23 | 0.39 |
| tumblr | 0.06 | 0.35 |
| casual | 0.25 | 0.38 |
| formal |
Trained on formal modernisations alone, the model copies input far from its
training register — 0.01 depth means output equals input. Multi-register
training fixes that across the board without disturbing formal performance.
Training
- Targets: 58,730 paragraphs from 1,200 PG19 books published 1837–1901,
plus 6,999 verse blocks
- Sources: DeepSeek modernisations, content-plan-then-rewrite
- Pairs: ~70,000 total, 59k used
- Method: QLoRA r=32 α=32, 7 target modules, ~87M trainable params (1.05%),
1 epoch, on a single 16GB consumer GPU
Intended downstream use is corpus generation — manufacturing period-consistent
pretraining data ("VictorianWikiText") for larger 19th-century models, since the
supply of genuine clean period text is largely exhausted. Patina is the
bootstrapper, not the destination.
Usage
from transformers import AutoModelForCausalLM, AutoTokenizer
from peft import PeftModel
base = AutoModelForCausalLM.from_pretrained("Qwen/Qwen3-8B", device_map="auto")
model = PeftModel.from_pretrained(base, "zakarth/patina")
tok = AutoTokenizer.from_pretrained("zakarth/patina")
INSTR = ("Rewrite the following passage as authentic Victorian-era "
"(19th-century) English prose. Preserve every fact and detail; "
"change only the style and register.\n\n")
msgs = [{"role": "user", "content": INSTR + your_text}]
prompt = tok.apply_chat_template(msgs, tokenize=False,
add_generation_prompt=True,
enable_thinking=False)
out = model.generate(**tok(prompt, return_tensors="pt").to(model.device),
max_new_tokens=400, do_sample=True,
temperature=1.0, top_p=0.9, top_k=40)
For verse, swap the instruction: "Rewrite the following piece as authentic
Victorian-era (19th-century) English verse. Preserve every image, fact, and
detail; change only the style and form."
Reproducing these outputs
A single sample from the snippet above will be noticeably more conservative
than the showcase examples. That is not cherry-picking — it is the documented
behaviour in §2 below, and the examples were produced by a selection procedure,
which is the intended way to use this model. To reproduce them:
Sampling parameters, exactly as used:
Table | |
|---|
| candidates per passage | k = 16 |
| temperature | 1.0 |
| top_p / top_k | 0.9 / 40 |
| max_new_tokens | max(220, 2.2 × source words) |
| passages | rendered independently, no cross-passage context |
Then select among the 16, in this order:
- Discard truncations — anything under 60% of source length. A stub scores
well on transformation depth while rendering nothing.
- Discard bloat — length ratio above 1.6. This is the pastiche failure.
- Discard fact failures — an LLM fact-check of each candidate against the
source, flagging added/altered facts. Never relax this one (§3).
- Rank survivors by
violet_ppl(candidate) / violet_ppl(source), lowest
wins. The ratio matters: raw Violet perplexity is domain-dependent (~27 on
plain narrative, ~280 on jargon-dense text), so raw values aren't comparable
between passages.
The showcase passages scored a median relative perplexity of 0.12 — roughly
8× more period-plausible than their own inputs — at a median transformation
depth of 0.55, above the 0.39 median of the training targets themselves.
All six passed the fact check.
Three things to know before using it
1. Never feed a heading together with its body
The single biggest failure mode, and it looks like the model is broken when it
isn't.
Given a title and byline attached to a body, the model correctly identifies its
input as a quoted document and applies the period convention — introduce it
editorially, then reproduce it verbatim. Victorian books genuinely do treat
quoted material that way. The result is a near-perfect copy.
Table with columns: input, transformation depth| input | transformation depth |
|---|
| document with title + byline | 0.03 (copied) |
| identical text, front matter removed | 0.85 |
Strip headings, render the body, reattach headings separately.
2. Sample several candidates and choose
Good renderings are roughly the top 25% of the sample distribution. Default
behaviour is conservative because copying is always the locally safe token path
— classic exposure bias — so a single greedy generation under-transforms.
Draw k candidates and select: 0.75^k of passages will have nothing good to
choose from. k=6 → 18% disappoint, k=12 → 3%, k=16 → 1%. If quality
feels erratic, this is almost always why.
3. Fluency is not accuracy
The most period-perfect output is frequently the least true. From the same
showcase run — this candidate scored perplexity 9.5, the best of all six,
far below genuine Victorian text:
IN The algorithm keeps recommending me videos of people restoring rusted
old tools, and I have to admit I am not even slightly mad about it.
OUT "You may ask, my dear fellow, how I like it. I don't. I tell you I don't.
I'm not a bit mad about it.
Flawless period voice. It also inverted the meaning — the source is fond of
the videos, the output is not — and it passed an LLM fact-checker.
Elsewhere the same pipeline invented a recipient's name for a letter that had
none, misread 20:33 as "twenty minutes and thirty-three seconds", and — given
"the on-call engineer was on a train through a tunnel with no signal" —
produced a fully hallucinated Victorian railway disaster with a telegraph office
21 miles distant, at a perplexity better than real period text.
Ranking candidates by fluency selects the most convincing fabrication. Gate
on facts before ranking on style, and never let a style score overrule a
failed fact check.
Limitations
- Short slang verse is a blind spot. Slang-as-prose works and verse works,
but their conjunction — a few lines of dense slang verse — often returns
near-verbatim. Traceable to a verse filter in corpus construction that removed
all poetry from the original training targets.
- Conservative by default. Copy-ratio ~0.88 against training targets at 0.61.
Best-of-k reaches 0.68, so the capacity exists but single samples don't
express it.
- Modern proper nouns resist. Passages dense with software, brands and
places that didn't exist stay recognisably modern, because fidelity requires
keeping words with no period equivalent.
- Trained at paragraph scale. It inherits Qwen3's 32k context, but the task
was learned on single paragraphs; chunk documents at paragraph boundaries.
- No safety tuning whatsoever. It faithfully renders whatever it is given.
- Period bias is inherent. The targets are real 19th-century books, and
19th-century attitudes arrive with the register. Output can reproduce the
prejudices of the era. For the intended purpose — period-authentic text — that
is not fully separable from the goal. Treat output as period pastiche, not as
neutral prose.