SONUB home EN English 한국어 日本語 中文 हिन्दी বাংলা اردو Español Português Français Deutsch Русский العربية Bahasa Indonesia Tiếng Việt ไทย Türkçe

How to enable notifications

Notifications are unavailable in this browser or on this address. Try a supported browser on the secure SONUB site.

If notifications are blocked, allow them in your browser settings.

  1. Open the site controls beside the address bar, or your browser’s site settings. Set Notifications for this site to Allow.
  2. In your device settings, allow notifications for your browser and check Do Not Disturb or Focus.
  3. Return to SONUB, reload the page, and use the bell to enable notifications.

On iPhone or iPad, add SONUB to your Home Screen, open it there, then allow notifications.

XSML · From your first line to a conversation

Now, let's build your very own AI.

Type your own language model in this browser: prepare text, train a tiny GPT, then run your Runner and XChat. No pretrained weights or external AI API.

6

Training & checkpoints

Predict the next token, update weights and resume from a checkpoint.

For tokens [a,b,c], inputs [a,b] target [b,c]. Cross-entropy is the negative log of the correct next token. A smaller means the model assigned it more probability; it does not guarantee a truthful answer.

Each iteration chooses a short window, clears old gradients, runs forward and backward, clips the norm and applies SGD. A checkpoint stores weights, settings, tokenizer, step and random state after each completed update. The initial and final validation values use the same first held-out window: a small diagnostic, not a complete evaluation.

Type and try

Type train.py and run the short training command. Watch real step and loss values. Run five more steps and check resume. Start small because browser speed varies. Repeat training to explore changes; the default run only checks that the pipeline works.

cd ~/xsml
nano train.py
python3 train.py --steps 20
python3 train.py --steps 5
train.pyComplete source to type

The data hash prevents resuming on silently changed data. Saving through a temporary file and os.replace preserves the previous complete checkpoint if writing is interrupted. SGD has no momentum state to save.

import argparse
import hashlib
import json
import math
import os
import random
from pathlib import Path
from model import GPT
from tokenizer import BPE


def main():
    cfg = json.loads(Path('settings.json').read_text())
    parser = argparse.ArgumentParser()
    parser.add_argument('--steps', type=int, default=cfg['steps'])
    args = parser.parse_args()
    assert args.steps > 0, '--steps must be positive'
    token_state = json.loads(Path('data/tokenizer.json').read_text())
    tokenizer = BPE(**token_state)
    train_text = Path('data/train.txt').read_text(encoding='utf-8')
    val_text = Path('data/val.txt').read_text(encoding='utf-8')
    fingerprint = hashlib.sha256((train_text + '\0' + val_text).encode()).hexdigest()
    train, val = tokenizer.encode(train_text), tokenizer.encode(val_text)
    model = GPT(len(tokenizer.vocab), cfg['width'], cfg['context'], cfg['seed'])
    rng, step = random.Random(cfg['seed']), 0
    checkpoint = Path('checkpoints/model.json')
    if checkpoint.exists():
        saved = json.loads(checkpoint.read_text())
        assert saved['settings'] == cfg and saved['tokenizer'] == token_state, 'changed settings/tokenizer: start a new checkpoint'
        assert saved['data_hash'] == fingerprint, 'changed data: start a new checkpoint'
        model.load(saved['weights'])
        step = saved['step']
        rng.setstate((saved['random'][0], tuple(saved['random'][1]), saved['random'][2]))
    n = model.context
    assert len(train) > n and len(val) > n, 'add more text'
    print('parameters:', len(model.parameters), 'resume:', step, flush=True)
    print('validation:', round(model.loss(val[:n], val[1:n + 1]).data, 4), flush=True)
    checkpoint.parent.mkdir(exist_ok=True)
    for _ in range(args.steps):
        start = rng.randrange(len(train) - n)
        window = train[start:start + n + 1]
        for p in model.parameters:
            p.grad = 0
        loss = model.loss(window[:-1], window[1:])
        loss.backward()
        size = math.sqrt(sum(p.grad * p.grad for p in model.parameters))
        scale = min(1.0, 1.0 / (size + 1e-8))
        for p in model.parameters:
            p.data -= cfg['learning_rate'] * p.grad * scale
        step += 1
        state = {'settings': cfg, 'tokenizer': token_state, 'data_hash': fingerprint,
                 'weights': model.state(), 'step': step, 'random': rng.getstate()}
        temporary = checkpoint.with_suffix('.tmp')
        temporary.write_text(json.dumps(state, ensure_ascii=False), encoding='utf-8')
        os.replace(temporary, checkpoint)
        print('step:', step, 'loss:', round(loss.data, 4), flush=True)
    print('validation:', round(model.loss(val[:n], val[1:n + 1]).data, 4), flush=True)
    print('saved:', checkpoint, flush=True)


if __name__ == '__main__':
    main()

Check your understanding

Find checkpoints/model.json. The second run continues the previous step count. Compare validation before and after. Loss may rise on different random windows; test a longer run before drawing conclusions.

If you get stuck

Ctrl+C stops training; the last completed update remains saved. Changed settings, tokenizer or data deliberately reject the old checkpoint. Export first, then move model.json to another name to start again. A Runner already running keeps its old weights until restarted.