Downloads · 30 days
0
raghavt/nanochat-760M-bidirectional
nanochat-760M-bidirectional 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.
A single base language model trained on both left-to-right and right-to-left text. A marker token picks the direction:
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
A single base language model trained on both left-to-right and right-to-left text. A marker token picks the direction:
<|forward|>: ordinary next-token prediction. Give it a beginning and it
continues.<|backward|>: previous-token prediction. Give it an ending and it writes what
came before.It is a depth-20 nanochat GPT trained with
onanchat. It shares its architecture, tokenizer,
data and schedule with a matched forward model and with
nanochat-760M-backward.
Its training budget is the same as each single-direction model's, split between
the two directions.
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 | bidirectional (marker token per sequence) |
| Stage | base pretraining only, no chat tuning |
| Checkpoint | step 20,000 of 20,000 |
| Training tokens | ≈5.24B, about half in each direction |
| Validation bits per byte | 0.9023, measured on the same alternating forward/backward mix it was trained on |
| Files | model.pt (state dict), meta.json, tokenizer.pkl, token_bytes.pt |
nanochat streams documents into one token stream, each document starting with
<|bos|>. It cuts the stream into 16,385-token chunks (8 rows × 2,048 tokens,
plus one). This model alternated chunks:
first token, <|forward|>, then the text in order.first token, <|backward|>, then the rest of the chunk
reversed.The marker sits at the start of each chunk's first row only. The other seven
rows begin mid-stream with no marker, so the model must also infer direction
from the text itself. In backward chunks each document's <|bos|> appears
after that document, marking where it began.
Prompt formats:
[<|bos|>, <|forward|>] + tokens.[<|bos|>, <|backward|>] + reversed(tokens). Stop at a generated
<|bos|>, then reverse the output.Reverse tokens, not characters.
Requires a checkout of onanchat (tested at commit
0e99157), PyTorch and huggingface_hub.
# 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-bidirectional")
bos = tok.get_bos_token_id()
fwd, bwd = tok.encode_special("<|forward|>"), tok.encode_special("<|backward|>")
start = "The history of the bicycle"
print(start + tok.decode(sample(model, [bos, fwd] + tok.encode(start), 60)))
ending = " and that is why the experiment had to be repeated the following year."
before = sample(model, [bos, bwd] + tok.encode(ending)[::-1], 80, stop=bos)
print(tok.decode(before[::-1]) + ending)
One sample from each mode (yours will differ):
forward: The history of the bicycle is full of stories about inventors, inventors, and inventions. In a nutshell, an inventor is someone who comes up with a new idea or product…
backward: …In fact, after one year, the group that was used in the experiment did as well, and that is why the experiment had to be repeated the following year.
| Parameters | 560,988,160 (two 83.9M embedding matrices, untied, plus 20 × 19.7M blocks) |
| Layers / width / heads | 20 / 1,280 / 10 (head dim 128, no grouped-query sharing) |
| Context | 2,048 tokens |
| Vocabulary | 65,536 BPE tokens trained by nanochat (rustbpe), including `< |
| Details | rotary position embeddings, QK-norm, ReLU² MLP, parameter-free RMSNorm, no biases, logit soft-cap 15·tanh(x/15) |
meta.json.backward_d20_bidir_main, model tag d20_bidirectional. The full
configuration is in meta.json under user_config.nanochat-760M-backward: dedicated right-to-left base model.nanochat-760M-backward-sft: backward model after chat fine-tuning.@misc{nanochat-d20-bidirectional,
author = {Raghav},
title = {nanochat d20 bidirectional: one nanochat base model for both directions},
year = {2025},
publisher = {Hugging Face},
url = {https://huggingface.co/raghavt/nanochat-760M-bidirectional}
}
Built on nanochat by Andrej Karpathy (MIT).