ROHAN SEHGAL
← Back to writing

Teaching a transformer to write backwards

July 28, 2026

A decoder-only transformer that reads a normal forward-English question and answers in character-reversed English — emitted tail-first, so the model commits to how the sentence ends before it writes how it begins.

nanochat fork · custom BPE · direction tokens · trained from scratch. A full pretraining + SFT stack built to answer one question: can a transformer learn to generate coherent language tail-first?

StatusA working proof-of-mechanism, trained end to end locally. Given a forward-English question it emits grammatical English, tail-first — direction tokens firing, sequence terminating cleanly. At this scale (73M params, a ~44-minute MacBook run) the answers are fluent but not yet correct — the deliberate scope was to prove the tail-first mechanism at small scale first. It holds. Scaling up for correctness is the next run, not an open question about whether the approach works.

Why write backwards?

Reversing a sentence at the character level is trivial for a two-line Python function and genuinely hard for a person to do fluently out loud. That gap is exactly what makes it a clean probe: it's a deterministic, checkable target with no ambiguity, but it forces the model to represent order in a way normal left-to-right training never asks for.

The property that makes this interesting is tail-first generation. Take the answer "The capital is Paris." Character-reversed, the target string is ".siraP si latipac ehT". The model produces that left-to-right — so the very first token it emits corresponds to the last character of the true answer. It has to decide the ending before the beginning. Normal autoregressive planning runs the other way, so this is a direct test of whether the architecture can plan in reverse.

The design

decisionwhy
Decoder-only, forked from nanochatReuse a known-good training loop and tokenizer harness; spend the effort on the task, not the plumbing.
Custom BPE on a mixed forward + reversed corpusA tokenizer trained only on forward text fragments reversed strings into near-byte-level pieces. Training the merges on both directions lets reversed spans get real subword units too.
Direction tokens for mode controlA leading control token selects forward vs reversed output, so one model serves both modes and the corpus can mix them cleanly.
Character-level reversal, tail-first targetThe hard version: reverse the whole answer string, not per-word — forcing end-before-beginning planning.
ConfigDepth-6 decoder-only transformer, 73,531,646 parameters (384-dim, 6 heads, 512 context), 32,768-vocab byte-level BPE. Pretrained 5,000 iterations / 81.9M tokens of character-reversed ClimbMix at batch 16,384, then 125 steps of reverse-Alpaca SFT. Roughly 44 minutes end to end on a MacBook (Apple MPS), ~$0. Final validation bpb 1.2888.At this depth the parameter budget lives in the vocab-sized tables — the token embedding, the untied output head, and nanochat's value-embedding layers — not the ~10M transformer body. That's why the true count is 73.5M: at small depth, the embeddings dominate.

The bugs that actually bit

The modeling was the easy part. The two things that cost real time were both in the data path — which is the honest lesson of most from-scratch training.

  • Overlong reversed docs → NaN loss. The dataloader buffers conversations and packs them into fixed-width rows. When every buffered example was longer than the row capacity, the packer emitted an empty/degenerate batch and the loss went to NaN — not a bad learning rate, a data bug masquerading as one. Fix: filter reversed examples that exceed the row capacity before they reach the buffer, so a full buffer always contains something packable.
  • macOS killed my checkpoints. The SFT post-step evaluation crashed on macOS (a process-spawn issue), and because the eval ran before the checkpoint save, a crash meant the run exited having written nothing to disk — hours of compute, no artifact. Fix: pass an explicit --save-every=N so checkpointing never sits behind the eval that's crashing.
Both bugs share a shape worth naming: a failure in an auxiliary path (packing, eval) silently destroyed the primary result (a gradient step, a saved model). The takeaway I keep: make the thing you care about not depend on the thing that's allowed to fail.

Does it work?

The only quantitative number is validation bpb 1.2888 — there's no QA-accuracy metric, because nanochat's post-training eval crashes on macOS (the bug above) and I left it disabled. So the honest read is qualitative. Here is a real generation, verbatim:

prompt (forward)
What is the capital of France?
raw model output (character-reversed)
.dlrow eht fo strap ynam ni latipac dna ,ygrene ,ygrene fo ecruos niam eht si ecnarF
un-reversed for reading
France is the main source of energy, energy, and capital in many parts of the world.

Watch the two endpoints — this is the whole point of the project. The firsttoken it emitted was ".", the answer's final character. The last token it emitted was "ecnarF" — i.e. France, the first word of the readable answer, correctly echoing the subject of the question. The model wrote the entire sentence back-to-front and only committed to its opening word — the right one — at the very end. Grammatical English, generated tail-first, direction token firing, clean termination.

It is also factually wrong (the capital of France is Paris, not an energy story), with a repetition artifact ("energy, energy"). At 73M params and 125 SFT steps that ceiling is expected — and it's orthogonal to what the experiment tested. The structure is the result: coherent, tail-first English with the correct opening word chosen last. Content quality is a scale knob; whether the mechanism works is the question, and it does.

Where this could go

A reversed model earns its keep when it does something a forward model can't. The directions I'd take it next:

  • Reverse model as a verifier. Score candidate answers with tail-first log-probs and see whether reverse-direction scoring reranks differently from — and complements — forward scoring.
  • Forward/backward asymmetry. Probe where in the network positional/order information lives, and whether reversed training represents it differently. A small mech-interp study on a model I fully control.
  • Infilling / plan-from-the-end. Condition on how a sequence should end and generate backward toward a given start.
Built on Karpathy's nanochat. Full-stack and from scratch — custom tokenizer, reversed-data pipeline, pretraining, and SFT — scoped to prove the mechanism.