zxell.ai
This is an internal project report edited for publication, translated from the Japanese original. Server operations and security details, and details of the corpus, are omitted; omissions are marked in the text.

Phase 1: File layout and data formats on the client (the RTX 3060 machine)

Date: 2026-10-11 Scope: the files created or placed on the client machine in the course of the production S baseline run (started 2026-10-10, completed all 90,000 steps). The numbers and structures were taken from the actual files as copied to the server. This is also the basis of the input/output specification for phase 2, when client v1 is replaced with real local training.


1. Directory layout (the 3060 machine, under the user's home)

~/
├── zxell/                              git clone (the public repository zxell-ai/zxell, main)
│   └── zxell_model/
│       ├── .venv/                      Python venv (torch cu128, numpy, sentencepiece)
│       ├── config.py, model.py, data.py, train.py, eval.py, sample.py, fetch_shards.py
│       └── runs/                       training output (gitignored)
│           ├── S_check/                the 30-step check run (disposable: config.json / log.jsonl / ckpt_last.pt / ckpt_final.pt)
│           └── S_baseline/             ★ the production run
│               ├── config.json         model configuration + training arguments + the list of shards used
│               ├── log.jsonl           training log (9,181 lines)
│               ├── stdout.log          tee of standard output (same content as log.jsonl + the preset line at the top + the done line)
│               ├── ckpt_last.pt        resume checkpoint, overwritten every 1,000 steps (finally at step 90000)
│               └── ckpt_final.pt       checkpoint at the end (349MB) ★ the deliverable
├── zxell-shards/                       ★ training data (fetched through the API, 231 files, about 11GB)
│   ├── shards_index.json               list, boundaries, metadata
│   ├── train_000.bin … train_111.bin   training shards (112 of them, each ≤ 50 million tokens ≈ 100MB)
│   ├── train_NNN.bin.json              metadata of each shard (112 files)
│   ├── val_000.bin / val_000.bin.json / val_000.langs.u8      validation (11,129 articles)
│   └── test_000.bin / test_000.bin.json / test_000.langs.u8   test (30,171 articles)
└── sp_bpe_48k.model                    tokenizer (1,035KB. For decoding in sample.py. Copied from the server with scp)
omitted: the connection settings used for fetching, and the storage locations on the server

2. Training data (~/zxell-shards/)

2-1. shards_index.json — list and metadata

Written by the shard step of tokenize_full.py. fetch_shards.py fetches this first and verifies each shard against the sha256 listed in it.

{
  "tokenizer": "sp_bpe_48k.model",
  "dtype": "uint16",
  "shard_tokens": 50000000,
  "seed": 20260813,
  "created": "2026-10-10T01:05:23",
  "boundaries": { "mode": "per_lang_count", "test_docs": 8000, "val_docs": 3000,
                  "computed_at": "...", "snapshot": "...",
                  "langs": { "en": { "docs": 3978459, "max_pub_date": "2023-05-28T02:30:00",
                                     "test_start": "2023-05-24T11:32:11", "val_start": "2023-05-23T08:00:00",
                                     "test_docs_actual": 8000, "val_docs_actual": 3007 }, "de": {...}, "fr": {...}, "ja": {...} },
                  "_meta": {...} },
  "shards": [ { "name": "train_000.bin", "split": "train", "tokens": 49999705, "docs": 66538,
                "sha256": "caa2269f…", "langs": { "en": 30775576, "de": 12011218, "fr": 5450408, "ja": 1762503 } },
              ... 114 entries ... ]
}
KeyMeaning
tokenizerName of the SentencePiece model that produced the token IDs (vocabulary 48,000)
dtype / shard_tokensElement type of the .bin files / upper limit on tokens per shard
seedRandom seed used to shuffle the train articles (for reproduction)
boundariesThe per-language chronological train/val/test boundaries (the output of compute_boundaries.py, embedded as is)
shards[]Metadata of each shard (same content as 2-3)

2-2. <split>_NNN.bin — the token sequence itself

How to read it (Python): np.memmap(path, dtype=np.uint16, mode="r") (load_shard in data.py). The training sampler "picks a shard in proportion to its token count and cuts ctx+1 tokens from a random position" (crossing article boundaries is allowed).

2-3. <split>_NNN.bin.json — shard metadata

{ "name": "train_000.bin", "split": "train", "tokens": 49999705, "docs": 66538,
  "sha256": "caa2269fbc07182deffdb94f05d39ccd7771e93cd07de971aa3dd50e6548b7d2",
  "langs": { "en": 30775576, "de": 12011218, "fr": 5450408, "ja": 1762503 } }

sha256 is the hash of the whole .bin (used both for the read-back verification right after writing and for the check after fetching). langs is the token count per language (including EOS).

2-4. val_000.langs.u8 / test_000.langs.u8 — language of each article

3. Training output (runs/S_baseline/)

3-1. config.json — the full set of settings needed for reproduction

Written by train.py at start-up.

{
  "model": { "n_layer": 6, "d_model": 384, "n_head": 6, "ctx": 1024, "vocab_size": 48000,
             "rope_theta": 10000.0, "tie_embeddings": true },
  "args":  { "preset": "S", "shards": "<home>/zxell-shards", "out": "runs/S_baseline",
             "steps": 90000, "batch": 32, "grad_accum": 1, "ctx": 0,
             "lr": 0.0006, "min_lr_ratio": 0.1, "warmup": 500, "wd": 0.1, "clip": 1.0,
             "eval_every": 500, "eval_tokens": 1000000, "final_eval_tokens": 0,
             "ckpt_every": 1000, "log_every": 10, "device": "auto", "seed": 20260929,
             "resume": "", "max_train_shards": 0 },
  "train_shards": [ "train_000.bin", ... , "train_111.bin" ]
}
ItemMeaning
modelconfig.ModelConfig. ctx: 0 means "use the preset's ctx (1024)"
args.steps × batch × ctxTotal training tokens = 90,000 × 32 × 1,024 = 2,949,120,000
lr → lr × min_lr_ratioLinear warmup (500 steps) → cosine from 6e-4 down to 6e-5
eval_every / eval_tokensEvery 500 steps, take evenly spaced windows worth 1 million tokens from val and compute per-language PPL
final_eval_tokens: 0The final evaluation uses all of val
seedRandom seed for model initialization and the training sampler (for reproduction)
train_shardsNames of the shards actually used (112). If narrowed with max_train_shards, that list

3-2. log.jsonl — training log (one JSON per line, two kinds of record)

Training records (every 10 steps + step 1. 9,001 lines):

{"step": 1,     "loss": 10.864,  "lr": 1.2e-06, "grad_norm": 3.206, "tok_per_s": 33597, "elapsed_s": 1}
{"step": 90000, "loss": 3.1602,  "lr": 6.000000016633646e-05, "grad_norm": 1.516, "tok_per_s": 38298, "elapsed_s": 77004}
FieldMeaning
stepOptimization step number (starting from 1)
lossBatch-mean cross-entropy at that step (nats). PPL = exp(loss)
lrLearning rate applied at that step
grad_normGradient norm before clipping (clip 1.0)
tok_per_sCumulative throughput since start-up (tokens/second)
elapsed_sSeconds elapsed since start-up

Evaluation records (every 500 steps + at the end. 180 lines):

{"step": 500,   "eval": "val",      "all": 384.59, "en": 186.68, "de": 367.7,  "fr": 221.68, "ja": 2635.84}
{"step": 90000, "eval": "val_full", "all": 32.33,  "en": 20.18,  "de": 28.88,  "fr": 21.8,   "ja": 132.2}
FieldMeaning
evalval = an evenly spaced sample of 1 million tokens from val / val_full = all of val (at the end)
all / en / de / fr / jaPerplexity (2 decimal places). The per-language values attribute each token to its article's language through .langs.u8

On a resume (--resume), records are appended to the same file (no resume this time. The two start-ups on 10/9 were removed with rm -rf before starting).

3-3. ckpt_last.pt / ckpt_final.pt — checkpoints (349MB)

A dict saved with torch.save. Read by eval.load_model() and train.py --resume.

KeyContentSize
modelThe state_dict (45 tensors, fp32). tok_emb.weight (48000×384) and lm_head.weight share the same storage (weight sharing). 29.1M learned parameters116MB (as saved. The shared part is written only once)
optimizerAdamW state: exp_avg / exp_avg_sq (fp32) for 44 parameters + param_groups (a group with weight decay 0.1 and a group with 0)232MB
samplerRandom state of the training sampler (numpy PCG64). Continues the same sequence on resumea few hundred bytes
stepStep at the time of saving (90000)
configThe same dict as model in 3-1
argsThe same dict as args in 3-1

Tensor names in model (one block as an example):

tok_emb.weight                 (48000, 384)
blocks.0.norm1.weight          (384,)
blocks.0.attn.qkv.weight       (1152, 384)     ← Q/K/V in a single matrix
blocks.0.attn.proj.weight      (384, 384)
blocks.0.norm2.weight          (384,)
blocks.0.ffn.w_gate.weight     (1024, 384)     ← SwiGLU (hidden 1024)
blocks.0.ffn.w_up.weight       (1024, 384)
blocks.0.ffn.w_down.weight     (384, 1024)
... blocks.1–5 likewise ...
norm_f.weight                  (384,)
lm_head.weight                 (48000, 384)    ← shared with tok_emb.weight

3-4. stdout.log

Standard output captured with tee. Its content is the same lines as log.jsonl, plus, at the top, preset S: 29.1M params (10.6M non-embedding), ctx 1024, device cuda and, at the end, done: 90000 steps, 2949.12M tokens, 77050s. Tracebacks from failures also remain here (as with the OOM — review24).

4. Sizes

ItemSize
~/zxell-shards/ (231 files)about 11GB (train 112 × ≈100MB, val 11.4MB, test 31.6MB, metadata and side files < 1MB)
runs/S_baseline/ckpt_final.pt349MB
runs/S_baseline/ckpt_last.pt349MB (the same step 90000 as final)
runs/S_baseline/log.jsonl1.1MB
runs/S_check/ (the check run)about 700MB (2 checkpoints). Can be deleted
~/sp_bpe_48k.model1.0MB
.venv/ (including torch cu128)several GB

5. What can be reproduced from what

What you wantFiles needed
Per-language PPL (val / test)ckpt_final.pt + ~/zxell-shards/{val,test}_000.* → eval.py
Generated samplesckpt_final.pt + sp_bpe_48k.model → sample.py
The loss curve and PPL chartlog.jsonl (the chart in review26 of the development log was drawn from this)
Continuing the training (for example 90k → 180k steps)ckpt_last.pt + the shards + --resume (following the args in config.json)
Retraining under the same conditions (from zero)The shards + the args in config.json (fixed seed. GPU nondeterminism means the result is not bit-identical)
Initial weights for distributed training (phase 2)Not used (it starts "from zero", like the baseline. ckpt_final.pt is the comparison target)

6. What is kept where