Downloads · 30 days
0
raghavt/nanochat-760M-backward-sft
nanochat-760M-backward-sft is a text generation model from raghavt. Use it when you need the model to write or continue text. It is set up for pytorch. The card lists the license as mit.
The right-to-left nanochat model after chat supervised fine-tuning (SFT). It reads conversations in reverse. The most useful things it does:
Downloads · 30 days
0
Access
Public
Updated Oct 23, 2026
Repo size
2.1 GB
Likes
0
Public
Click a slice to open those files.
.pt2.1 GB · 100%
From the Hugging Face model README
The right-to-left nanochat model after chat supervised fine-tuning (SFT). It reads conversations in reverse. The most useful things it does:
It is a depth-20 nanochat GPT trained with
onanchat. It was fine-tuned from the backward
line of nanochat-760M-backward.
Size note: the repository name says 760M, but the model has 560,988,160 parameters. The name is kept so existing links keep working.
| Direction | backward (right-to-left) |
| Stage | chat SFT, 700 steps |
| Parent | backward d20 base line. meta.json does not record whether SFT started from the base or the midtrained checkpoint (onanchat's default is midtrained). |
| SFT validation loss | 1.505 (SmolTalk test conversations, reversed) |
| Files | model.pt (state dict), meta.json, tokenizer.pkl, token_bytes.pt |
Conversations use nanochat's chat tokens:
<|bos|> <|user_start|> question <|user_end|> <|assistant_start|> reply <|assistant_end|> …
For this model every conversation was reversed except the leading <|bos|>.
That reverses the order of turns, the tokens inside each turn, and the start/end
markers. A one-turn exchange therefore becomes:
<|bos|> <|assistant_end|> reversed(reply) <|assistant_start|> <|user_end|> reversed(question) <|user_start|>
The training loss covered assistant tokens only, as in standard nanochat SFT. The model was trained to write assistant text right to left, conditioned on everything later in the conversation. Writing user turns, as in the reply → question example below, relies on what it learned in base pretraining plus the chat format. It works in practice but was not directly supervised.
Reverse tokens, not characters.
Requires a checkout of onanchat (tested at commit
0e99157), PyTorch and huggingface_hub. onanchat's BackwardChatEngine
(nanochat/directional_chat_engine.py) wraps the same format for multi-turn use.
# run from the root of an onanchat checkout, or put it on PYTHONPATH
import json, sys, types, torch
sys.modules.setdefault("rustbpe", types.ModuleType("rustbpe")) # only needed to *train* tokenizers
from huggingface_hub import snapshot_download
from nanochat.gpt import GPT, GPTConfig
from nanochat.tokenizer import RustBPETokenizer
def load(repo, device="cuda"):
path = snapshot_download(repo, allow_patterns=["model.pt", "meta.json", "tokenizer.pkl", "token_bytes.pt"])
meta = json.load(open(f"{path}/meta.json"))
with torch.device("meta"):
model = GPT(GPTConfig(**meta["model_config"]))
model.to_empty(device=device)
model.init_weights() # builds the rotary tables; weights are overwritten below
state = torch.load(f"{path}/model.pt", map_location=device)
state = {k.removeprefix("_orig_mod."): v.float() if device == "cpu" else v for k, v in state.items()}
model.load_state_dict(state, strict=True, assign=True)
return model.eval(), RustBPETokenizer.from_directory(path), meta
@torch.no_grad()
def sample(model, ids, n, stop=None, temperature=0.8, top_k=50):
x = torch.tensor([ids], device=next(model.parameters()).device)
out = []
for _ in range(n):
with torch.autocast("cuda", dtype=torch.bfloat16, enabled=x.is_cuda):
logits = model(x)[0, -1].float() / temperature
v, _ = logits.topk(top_k)
logits[logits < v[-1]] = -float("inf")
t = torch.multinomial(logits.softmax(-1), 1)
if t.item() == stop:
break
out.append(t.item())
x = torch.cat([x, t[None]], 1)
return out
model, tok, meta = load("raghavt/nanochat-760M-backward-sft")
bos = tok.get_bos_token_id()
s = {k: tok.encode_special(f"<|{k}|>") for k in ("user_start", "user_end", "assistant_start", "assistant_end")}
# 1. Reply -> the question that came before it
reply = "The capital of France is Paris."
prompt = [bos, s["assistant_end"]] + tok.encode(reply)[::-1] + [s["assistant_start"], s["user_end"]]
question = sample(model, prompt, 80, stop=s["user_start"])
print(tok.decode(question[::-1]))
# 2. A later user turn -> the assistant turn that came before it
later = "Thanks! And what about Germany?"
prompt = [bos, s["user_end"]] + tok.encode(later)[::-1] + [s["user_start"], s["assistant_end"]]
answer = sample(model, prompt, 80, stop=s["assistant_start"])
print(tok.decode(answer[::-1]))
One sample (yours will differ). For the reply "The capital of France is Paris.", the generated question was:
Your response should contain at least 3 sentences. In your response, the word "the" should appear at least 2 times.
What is the capital of France?
The constraint-style preamble comes from SmolTalk's instruction-following data.
The onanchat Colab notebook's chat() helper builds a plain-text User: … Assistant:
prompt. That format doesn't match how this model was trained, so use the special
tokens shown above instead.
nanochat-760M-backward
(FineWeb-Edu, about 5B tokens, right to left).meta.json also records MMLU 26.0% and ARC-Easy 27.3%, both close to the 25%
chance rate. They come from onanchat's chat evaluation, which presents questions
left to right, so they test a format this model was never trained to read. They
are not a measure of what it knows. For comparison, the matched forward SFT
model scores about 32% and 40% on the same evaluation.
nanochat-760M-backward: the right-to-left base model.nanochat-760M-bidirectional: one base model for both directions.@misc{nanochat-d20-backward-sft,
author = {Raghav},
title = {nanochat d20 backward SFT: a right-to-left nanochat chat model},
year = {2025},
publisher = {Hugging Face},
url = {https://huggingface.co/raghavt/nanochat-760M-backward-sft}
}
Built on nanochat by Andrej Karpathy (MIT).