tinytowns

Three tiny letter-level GPTs that invent city names. Trained on 128,435 real place names from GeoNames. The smallest has 4,572 weights, the largest about a million.

This is a learning project: a transformer written by hand in plain PyTorch, small enough to read every number of. Code, step-by-step build and all measurements: github.com/dave8172/tinytowns.

Try it in your browser: quirkyagents.com/wizard/projects/tinytowns. The page downloads these weights and runs the model on your device.

The three models

All three use one design and one code file (model.py); only the config differs.

File Note size Layers ร— heads MLP width Weights Validation loss
tinytowns-nano.safetensors 16 1 ร— 1 64 4,572 2.4676
tinytowns-small.safetensors 64 2 ร— 4 256 105,052 2.2049
tinytowns.safetensors 128 5 ร— 4 512 1,001,500 2.1504

Loss is cross-entropy per symbol on 14,271 held-out names (lower is better). For reference: guessing uniformly over 28 symbols scores 3.3322; a 28ร—28 bigram counting table scores 2.6732.

How it works

  • Tokens are letters. Alphabet of 28: index 0 = . (start / end of name), 1 = space, 2โ€“27 = aโ€“z.
  • Context: 21 positions, one . plus at most 20 letters.
  • Architecture: token + learned positional embeddings, then pre-norm transformer layers (causal self-attention + MLP), then a final LayerNorm and an unembedding to 28 scores.
  • Training: predict each next symbol of . + name + .; AdamW, 6,000 steps. Batch 64 (nano, CPU) or 256 (small and tinytowns, GPU). The saved weights come from the step with the lowest validation loss (step 5,500 for tinytowns).

Each .safetensors file also stores its config as JSON text in the header metadata, so load_weights can rebuild the right model from the file alone.

Use

Needs torch>=2.2, safetensors and huggingface_hub.

from huggingface_hub import hf_hub_download
import importlib.util, torch

repo = "dave8172/tinytowns"
spec = importlib.util.spec_from_file_location("model", hf_hub_download(repo, "model.py"))
model = importlib.util.module_from_spec(spec); spec.loader.exec_module(model)

net, notes = model.load_weights(hf_hub_download(repo, "tinytowns.safetensors"))
print(model.sample(net, 10, temperature=1.0, generator=torch.Generator().manual_seed(42)))

model.py is plain PyTorch with no other code from this project. Read it before you run it, as with any downloaded code.

In a browser or Node, tinytowns.js in the GitHub repo runs the same model with no libraries (forward pass and sampling only). On all 14,271 validation names it matches the PyTorch loss to six decimal places.

What to expect

Out of 10,000 names sampled at temperature 1 (seed 42), checked against the full name list:

Model Real, seen in training Real, only in validation Invented
tinytowns-nano 194 14 9,792
tinytowns-small 242 20 9,738
tinytowns 386 30 9,584

Limits

  • It knows only lowercase aโ€“z and space. Accents were removed, and non-Latin names are not in the data.
  • A generated name is a sound-alike, not a place. Most outputs don't exist, and a "real" one may be copied from the training data.
  • It is built for learning. Ideas for use (game maps, test data that names no real place, a "does this look like a place name?" score) are untested.

Data and credit

Trained on GeoNames cities1000 (every place with 1,000+ people, 243 countries), licensed CC BY 4.0. Changed: names lowercased, hyphens turned into spaces, apostrophes removed, duplicates merged, names with digits or brackets or over 20 letters dropped. 90% train / 10% validation, fixed seed.

Licence

Code and weights: MIT. Training data: CC BY 4.0, GeoNames.

Downloads last month
99
Inference Providers NEW
This model isn't deployed by any Inference Provider. ๐Ÿ™‹ Ask for provider support