zxell.ai
この資料は、プロジェクト内部のレポートを公開用に編集したものです。サーバ運用・セキュリティに関わる部分と、コーパスの詳細は省略しています(省略箇所には印があります)。

フェーズ 1: クライアント(RTX 3060 機)側のファイル構成とデータ仕様

作成日: 2026-10-11 対象: S ベースライン本番(2026-10-10 起動、90,000 ステップ完走)の過程でクライアント機に生成・配置されたファイル。数値・構造はサーバに複製した実物から採取した。フェーズ 2 でクライアント v1 を本物の局所学習に置き換える際の、入出力仕様の土台でもある。


1. ディレクトリ構成(3060 機、ユーザーのホーム配下)

~/
├── zxell/                              git clone(公開リポジトリ 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/                       学習の出力(gitignore 対象)
│           ├── S_check/                動作確認 30 ステップ分(使い捨て。config.json / log.jsonl / ckpt_last.pt / ckpt_final.pt)
│           └── S_baseline/             ★ 本番
│               ├── config.json         モデル構成 + 学習引数 + 使ったシャード一覧
│               ├── log.jsonl           学習ログ(9,181 行)
│               ├── stdout.log          標準出力の tee(log.jsonl と同内容 + 先頭の preset 行 + done 行)
│               ├── ckpt_last.pt        1,000 ステップごとに上書きされた再開用チェックポイント(最終的に step 90000)
│               └── ckpt_final.pt       終了時のチェックポイント(349MB)★ 成果物
├── zxell-shards/                       ★ 学習データ(API 経由で取得、231 ファイル、約 11GB)
│   ├── shards_index.json               一覧・境界・メタ
│   ├── train_000.bin … train_111.bin   学習シャード(112 個、各 ≤ 5,000 万トークン ≈ 100MB)
│   ├── train_NNN.bin.json              各シャードのメタ(112 個)
│   ├── val_000.bin / val_000.bin.json / val_000.langs.u8      検証(11,129 記事)
│   └── test_000.bin / test_000.bin.json / test_000.langs.u8   テスト(30,171 記事)
└── sp_bpe_48k.model                    トークナイザ(1,035KB。sample.py の復号用。サーバから scp)
省略: 取得時の接続設定と、サーバ側の保存場所

2. 学習データ(~/zxell-shards/)

2-1. shards_index.json — 一覧とメタ

tokenize_full.py の shard 工程が出力。fetch_shards.py はまずこれを取り、記載の sha256 で各シャードを照合する。

{
  "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 件 ... ]
}
キー意味
tokenizerトークン ID を生成した SentencePiece モデル名(語彙 48,000)
dtype / shard_tokens.bin の要素型 / 1 シャードの上限トークン数
seedtrain の記事シャッフルに使った乱数シード(再現用)
boundaries言語別の train/val/test 時系列境界(compute_boundaries.py の出力をそのまま埋め込み)
shards[]各シャードのメタ(2-3 と同じ内容)

2-2. <split>_NNN.bin — トークン列本体

読み方(Python): np.memmap(path, dtype=np.uint16, mode="r")(data.py の load_shard)。学習サンプラは「トークン数に比例してシャードを選び、ランダム位置から ctx+1 個を切り出す」(記事境界は跨いでよい)。

2-3. <split>_NNN.bin.json — シャードのメタ

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

sha256 は .bin 全体のハッシュ(書き込み直後の読み直し検証にも、取得後の照合にも使う)。langs は言語別トークン数(EOS 込み)。

2-4. val_000.langs.u8 / test_000.langs.u8 — 記事ごとの言語

3. 学習の出力(runs/S_baseline/)

3-1. config.json — 再現に必要な設定一式

train.py が起動時に書く。

{
  "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": "<ホーム>/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" ]
}
項目意味
modelconfig.ModelConfig。ctx: 0 は「preset の ctx(1024)を使う」
args.steps × batch × ctx総学習トークン = 90,000 × 32 × 1,024 = 2,949,120,000
lr → lr × min_lr_ratio線形 warmup(500 ステップ)→ cosine で 6e-4 から 6e-5 へ
eval_every / eval_tokens500 ステップごとに val から等間隔に 100 万トークン分の窓を取って言語別 PPL
final_eval_tokens: 0最終評価は val 全量
seedモデル初期化と学習サンプラの乱数(再現用)
train_shards実際に使ったシャード名(112 個)。max_train_shards で絞った場合はその一覧

3-2. log.jsonl — 学習ログ(1 行 1 JSON、2 種類のレコード)

学習レコード(10 ステップごと + step 1。9,001 行):

{"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}
フィールド意味
step最適化ステップ番号(1 始まり)
lossそのステップのバッチ平均 cross-entropy(nat)。PPL = exp(loss)
lrそのステップに適用した学習率
grad_normクリップ前の勾配ノルム(clip 1.0)
tok_per_s起動からの累積スループット(トークン/秒)
elapsed_s起動からの経過秒

評価レコード(500 ステップごと + 終了時。180 行):

{"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}
フィールド意味
evalval = val から等間隔 100 万トークンのサンプル / val_full = val 全量(終了時)
all / en / de / fr / jaパープレキシティ(小数 2 桁)。言語別は .langs.u8 でトークンを記事の言語に帰属させて集計

再開(--resume)したときは同じファイルに追記される(今回は再開なし。10/9 の 2 回の起動分は rm -rf で消してから始めた)。

3-3. ckpt_last.pt / ckpt_final.pt — チェックポイント(349MB)

torch.save した dict。eval.load_model() / train.py --resume が読む。

キー内容サイズ
modelstate_dict(45 テンソル、fp32)。tok_emb.weight(48000×384)と lm_head.weight は同じストレージを共有(重み共有)。学習パラメータは 29.1M116MB(保存上。重複分は 1 回しか書かれない)
optimizerAdamW の状態: 44 パラメータ分の exp_avg / exp_avg_sq(fp32)+ param_groups(weight decay 0.1 の群と 0 の群)232MB
sampler学習サンプラの乱数状態(numpy PCG64)。再開時に同じ系列を続ける数百バイト
step保存時のステップ(90000)
config3-1 の model と同じ dict
args3-1 の args と同じ dict

model のテンソル名(1 ブロック分の例):

tok_emb.weight                 (48000, 384)
blocks.0.norm1.weight          (384,)
blocks.0.attn.qkv.weight       (1152, 384)     ← Q/K/V を 1 本の行列に
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 同様 ...
norm_f.weight                  (384,)
lm_head.weight                 (48000, 384)    ← tok_emb.weight と共有

3-4. stdout.log

tee で取った標準出力。内容は log.jsonl と同じ行に加えて、先頭の preset S: 29.1M params (10.6M non-embedding), ctx 1024, device cuda と末尾の done: 90000 steps, 2949.12M tokens, 77050s。障害時のトレースバックもここに残る(OOM のときがそうだった — review24)。

4. サイズ一覧

対象サイズ
~/zxell-shards/(231 ファイル)約 11GB(train 112 × ≈100MB、val 11.4MB、test 31.6MB、メタ・副ファイル < 1MB)
runs/S_baseline/ckpt_final.pt349MB
runs/S_baseline/ckpt_last.pt349MB(final と同じ step 90000)
runs/S_baseline/log.jsonl1.1MB
runs/S_check/(動作確認)約 700MB(ckpt 2 個)。削除してよい
~/sp_bpe_48k.model1.0MB
.venv/(torch cu128 含む)数 GB

5. 何から何が再現できるか

欲しいもの必要なファイル
言語別 PPL(val / test)ckpt_final.pt + ~/zxell-shards/{val,test}_000.* → eval.py
生成サンプルckpt_final.pt + sp_bpe_48k.model → sample.py
損失曲線・PPL 推移の図log.jsonl(開発記録 review26 の図はこれから作図)
学習の続き(例: 90k → 180k ステップ)ckpt_last.pt + シャード + --resume(config.json の args を踏襲)
同条件の再学習(ゼロから)シャード + config.json の args(seed 固定。GPU の非決定性があるので bit 一致はしない)
分散学習(フェーズ 2)の初期重み使わない(ベースラインと同じく「ゼロから」始める。ckpt_final.pt は比較対象)

6. 保全状況