# SSMoELM Technical Notes ## 2026-05-27 ### アーキテクチャ確定 **モデル仕様** - 47.04M total params / 25.80M active per token - d_model=768, L=6, n_heads=12 (kv_heads=3), head_dim=64 - MoE: 8 routed experts + 1 shared, top-2 routing, d_ff=256 - vocab=8192, context=2048, tie_word_embeddings=True **パラメータ内訳** | Component | Params | Active | |---|---|---| | Embedding | 6.29M | 6.29M | | Attention (全6層) | 8.85M | 8.85M | | Shared Expert (全6層) | 3.54M | 3.54M | | Routed Experts (top-2/8) | 28.31M | 7.08M | | Norm 等 | 0.05M | 0.05M | | **合計** | **47.04M** | **25.80M** | **ストレージ(sb3)** | Component | Precision | Size | |---|---|---| | Embedding | 4-bit | 3.00 MB | | Attention L1+L6 | 4-bit | 1.41 MB | | Attention L2-5 | 1-bit | 0.70 MB | | Shared Expert | 4-bit | 1.69 MB | | Routed Experts | 1-bit | 3.38 MB | | Router+Norm | bf16 | 0.09 MB | | **合計** | | **10.27 MB binary / ~9.04 MB sb3** | --- ### 学習方針 **QAT (BitNet b1.58 スタイル)** - master weights を bf16 で保持 - forward pass で layer-wise 精度に量子化(STE) - bf16 勾配で master weights を更新 - 推論時に master weights を破棄 **STE 実装** ```python # 1-bit: binary * row_scale, gradient は w をそのまま通す return w + mx.stop_gradient(quantized - w) ``` **選定理由**: Bonsai (PTQ) は 8B モデルの冗長性を活用する手法。 47M params / Chinchilla 付近では冗長性が不足し PTQ 品質が壊滅的になるリスクがある。 Bonsai スケール別の性能低下: 8B→-11%, 4B→-19%, 1.7B→-26%(スケール縮小で加速)。 --- ### 訓練環境 - M2 Mac MLX (mlx==0.31.2) - Python 3.12 - uv でパッケージ管理 --- ### データセット **Pretraining: 1B tokens**(当初 4B 予定→ M2 Mac 16日超のため 1B に変更、約 4.2日) - FineWeb-Edu sample-10BT: 60% (2.4B tokens) - FineWeb sample-10BT: 40% (1.6B tokens) - 保存形式: jsonl.gz, 100M tokens/shard × 40 shards, 6.3 GB - シャッフル: 訓練時に shard 単位でランダム順 + shard 内シャッフル(方式 B) **Tokenizer 訓練データ** - 全 40 shard から均一サンプリング (25M tokens/shard × 40 = 1B tokens) - 目的: 時代的偏りを排除 --- ### Tokenizer **仕様** - BPE, byte-level, vocab=8192 - ライブラリ: HuggingFace `tokenizers` - 保存先: `tokenizer/tokenizer.json` **特殊トークン** | Token | ID | 用途 | |---|---|---| | `` | 0 | 系列開始 | | `` | 1 | 会話全体の終了 | | `` | 2 | バッチパディング | | `<\|system\|>` | 3 | system ターン開始 | | `<\|user\|>` | 4 | user ターン開始 | | `<\|assistant\|>` | 5 | assistant ターン開始 | | `<\|eot\|>` | 6 | ターン終了 (end of turn) | **Chat template** ``` <|system|> {system}<|eot|> <|user|> {user}<|eot|> <|assistant|> {response}<|eot|> ``` **効率測定 (Wikipedia EN + UltraChat 各5M chars)** | Tokenizer | chars/tok (wiki) | chars/tok (chat) | tok/word (wiki) | |---|---|---|---| | SSMoELM (8,192) | 3.667 | 3.925 | 1.738 | | GPT-2 (50,257) | 4.570 | 4.670 | 1.394 | - GPT-2 比 +19〜25% 多くトークンを消費(vocab 縮小の代償) - 会話テキストの方が効率が良い(訓練データに会話を含めた効果) - context 2048 tokens ≈ 会話で ~1300 words(許容範囲) --- ### Ablation 計画(スキップ・2026-05-27) 当初 A/B/C の 3 run ablation を予定していたが、下記理由でスキップして Run C 構成で直接本番学習へ。 - Q/K 4-bit は安全側の設定(1-bit Q/K より精度劣化リスクが低い) - MoE > Dense は文献上ほぼ確実(routing overhead が Dense より有利) - ablation 3 run × 50M = 150M tokens ≈ 7.5時間 の節約 **採用構成: attn-4bit-qk**(L2-5 の Q/K を 4-bit、V/O は 1-bit のまま) ストレージ影響: binary +1.06 MB → sb3 推定 ~9.97 MB(10 MB ギリギリ) ### 本番学習設定(2026-05-27 開始) ``` run = attn-4bit-qk tokens = 1B batch_size = 2, grad_accum = 16 eff_batch = 65,536 tokens/step total_steps ≈ 15,259 warmup_frac = 5% ≈ 763 steps lr_max = 3e-4 → lr_min = 1e-5 (cosine) router_lr = 1e-4 optimizer = AdamW (β=0.9/0.95, wd=0.1, clip=1.0) save_every = 1526 steps ≈ 100M tokens 速度見込み ≈ 2800 tok/s → 約 4.2 日 ``` --- ### ファイル構成 ``` SSMoELM/ specification.md 仕様書 technical_notes.md ← 本ファイル pyproject.toml data/ prepare_dataset.py 4B tokens 収集(完了) make_tokenizer_sample.py 均一サンプリング(完了) raw/ shard_000〜039.jsonl.gz (6.3 GB) tokenizer_sample/ sample.jsonl.gz (1.4 GB) tokenizer/ train_tokenizer.py 訓練スクリプト(完了) eval_tokenizer.py 効率測定(完了) tokenizer.json 訓練済み tokenizer train/ config.py ModelConfig / DenseConfig quantize.py QAT (1-bit, 4-bit, STE) model.py SSMoELM / Dense (MLX) data.py (未実装) train.py (未実装) tasks/ ablation.md Ablation 計画 ``` --- ### 未解決の設計リスク(Ablation で検証予定) 1. **1-bit Attention (L2-5)**: Q/K が precision-sensitive かどうか未検証 2. **MoE vs Dense at 47M**: routing overhead がペイするか未検証 3. **d_ff=256 (0.33×d_model)**: 通常の 2.67×〜4× より大幅に小さい --- --- ## 2026-06-03 ### Pretraining 完了 - **採用チェックポイント**: step 013734(900M tokens)— 800M・900M・1B比較で900Mが最良 - **学習スループット**: ~3,000 tok/s(Flash Attention + QAT cache) - **実際のパックサイズ**: 12.1 MB(理論 11.49 MB、safetensors形式) **ベンチマーク結果(0-shot, 500 samples)** | Task | Metric | Score | Random | |---|---|---|---| | HellaSwag | acc_norm | 33.4% | 25% | | LAMBADA | acc | 13.8% | N/A | | PIQA | acc_norm | 53.2% | 50% | | WinoGrande | acc | 49.6% | 50% | | ARC-Easy | acc_norm | 35.0% | 25% | | ARC-Challenge | acc_norm | 21.0% | 25% | | BoolQ | acc | 36.2% | 50% | | MMLU | acc avg (57 tasks, ≤500/task) | 23.4% | 25% | --- ### 推論コード(PyTorch) - **ファイル**: `inference_pytorch.py`(スタンドアロン、MLX依存なし) - **dtype**: fp32(CPU/MPS)— MPS の bf16 は PyTorch で未対応 - **メモリ**: ディスク 12.1 MB → ランタイム ~188 MB(fp32展開) - **正確な推論には packed safetensors が必須**(raw bf16 master weights は QAT適用前の値のため不正確、LAMBADA ppl: 606 vs 6366) - **PyTorch/MLX スコア差**: ±1%以内(サンプリング誤差範囲) --- ### MoEルーティング統計(136トークン) | Layer | CV | 特徴 | |---|---|---| | 0 | 0.32 | E4/E5偏り | | 1 | 0.15 | 最も均一 | | 2 | 0.22 | E1偏り | | 3 | 0.42 | 最も不均一、E6突出(22.8%) | | 4 | 0.25 | E5偏り | | 5 | 0.25 | E0偏り | エキスパート崩壊なし。aux loss + z-loss が効果的。 --- ### SFT計画 - **データセット**: `databricks/databricks-dolly-15k`(CC BY-SA 3.0、商用利用可) - **選定理由**: 商用OK、人手アノテーション15K件、短い入出力、英語のみ - **除外候補**: - LIMA: CC BY-NC-SA(非商用のみ) - Alpaca: OpenAI ToS制約あり --- ### TurboWarp 速度推定 Barynsor-1 実測 (21M dense, 8 tok/s) から逆算: ~151M MACs/sec SSMoELM active compute per token: - Attention: ~402M MACs/token - Shared Expert: ~9.4M MACs/token - Routed (top-2): ~18.9M MACs/token - **推定 ~7.8 tok/s**(Barynsor-1 の 151M MACs/sec 基準) --- ## 2026-06-08 ### 公開モデル - **Base**: `hf.co/brulee-1/SSMoELM-Base` - **Instruct**: `hf.co/brulee-1/SSMoELM-it` `hf` CLI で取得確認済み: ```bash hf models info brulee-1/SSMoELM-Base hf models info brulee-1/SSMoELM-it hf download brulee-1/SSMoELM-Base README.md --local-dir /tmp/ssmoelm-hf/base hf download brulee-1/SSMoELM-it README.md --local-dir /tmp/ssmoelm-hf/it ``` 公開先のモデルカードは `checkpoints/README.md`(Base)と `checkpoints/README_it.md`(Instruct)を元に管理する。 HuggingFace 上では packed safetensors の `uint8` 配列を直接読むため、約12M params / 8-bit のように表示される場合があるが、実際は **47.04M params の 1-bit + 4-bit mixed precision MoE**。 --- ### SFT 完了 - **Base checkpoint**: `checkpoints/attn-4bit-qk_step013734.safetensors` - **Final SFT checkpoint**: `checkpoints/sft_final.safetensors` - **Packed Instruct weights**: - `checkpoints/ssmoelm_it_packed.safetensors` (~12 MB) - `checkpoints/ssmoelm_it_packed.npz` (~11 MB) - **SFT log**: `checkpoints/sft2_log.csv` - **最終ログ行**: step 177050, loss 1.592862, 2636.6 tok/s SFT 実装は `train/sft.py`。assistant tokens only loss を使い、user/prompt 部分と特殊トークンの prefix は loss mask で無視する。 **SFT データセット** | Dataset | 用途 | 備考 | |---|---|---| | `databricks/databricks-dolly-15k` | instruction following | CC BY-SA 3.0 | | `OpenAssistant/oasst1` EN | dialogue / assistant response | Apache 2.0 | | `trivia_qa` | QA factuality | CLI 既定に含む | | `natural_questions` | short-answer QA | streaming | | `Open-Orca/FLAN` | instruction mixture | streaming, default limit 70K | `checkpoints/README_it.md` の公開モデルカードでは Dolly-15k + oasst1 EN を主要 SFT データとして記載している。追加 QA/FLAN 系は `train/sft.py` の CLI 既定データセットに含まれるため、再現性メモとして本ファイルに残す。 --- ### Instruct 評価結果 `SSMoELM-it` は SFT 後も base の知識ベンチを概ね維持し、BoolQ / ARC-Challenge / PIQA が改善。 | Task | Base | Instruct | Δ | |---|---:|---:|---:| | HellaSwag acc_norm | 33.4% | 33.2% | -0.2% | | LAMBADA acc | 13.8% | 14.8% | +1.0% | | PIQA acc_norm | 53.2% | 55.4% | +2.2% | | WinoGrande acc | 49.6% | 49.6% | 0.0% | | ARC-Easy acc_norm | 35.0% | 35.2% | +0.2% | | ARC-Challenge acc_norm | 21.0% | 24.0% | +3.0% | | BoolQ acc | 36.2% | 44.4% | +8.2% | | MMLU avg | 23.4% | 23.2% | -0.2% | --- ### 現在のファイル構成 ```text SSMoELM/ specification.md 最終仕様 technical_notes.md 実装・実験ログ pyproject.toml uv project dependencies data/ prepare_dataset.py FineWeb/FineWeb-Edu shard 作成 make_tokenizer_sample.py tokenizer 訓練サンプル作成 raw/ shard_000〜039.jsonl.gz(git ignore) tokenizer_sample/ sample.jsonl.gz(git ignore) tokenizer/ train_tokenizer.py tokenizer 訓練 eval_tokenizer.py tokenizer 効率測定 tokenizer.json 訓練済み tokenizer train/ config.py ModelConfig / DenseConfig quantize.py QAT 1-bit/4-bit + STE model.py SSMoELM / Dense MLX model data.py pretraining data loader train.py pretraining script lm_eval_adapter.py lm-eval adapter sft.py supervised fine-tuning script inference.py packed PyTorch inference / chat entrypoint inference_packed.py packed inference implementation inference_pytorch.py PyTorch reference inference chat.py interactive chat helper export_packed.py pack/export weights eval_benchmark.py MLX evaluation eval_benchmark_pt.py PyTorch evaluation eval_mmlu.py MMLU evaluation analyze_routing.py MoE routing analysis checkpoints/ README.md Base model card README_it.md Instruct model card ssmoelm_it_packed.safetensors Instruct packed weights(git ignore) ssmoelm_it_packed.npz Instruct packed weights(git ignore) ``` --- ### TurboWarp / goboscript MVP `goboscript` で開ける `.sb3` artifact を生成開始。 - **生成スクリプト**: `scripts/generate_turbowarp_demo.py` - **goboscript source**: `turbowarp/ssmoelm_mvp/main.gs`, `stage.gs` - **生成済み sb3**: `turbowarp/ssmoelm_mvp/ssmoelm_mvp.sb3` - **検証テスト**: `tests/test_goboscript_export.py` 現時点の sb3 は UI/input loop + deterministic response shell + quantized dot-product kernel + packed `uint8` decoder を含む。 **実装済み Scratch kernels** | Kernel | goboscript proc | 入力 | 出力 | |---|---|---|---| | 1-bit dot | `dot1_kernel` | activation list, bit list, scale | `dot1_result` | | 4-bit dot | `dot4_kernel` | activation list, signed nibble list, scale | `dot4_result` | | packed 1-bit decode | `get_packed1_bit` | uint8 list, 0-based bit index | `packed1_bit` | | packed signed int4 decode | `get_packed4_signed` | uint8 list, 0-based nibble index | `packed4_value` | | packed 1-bit dot | `dot1_packed_kernel` | activation list, packed uint8 bit list, scale | `dot1_packed_result` | | packed 4-bit dot | `dot4_packed_kernel` | activation list, packed uint8 nibble list, scale | `dot4_packed_result` | | self test | `kernel_self_test`, `packed_decode_self_test`, `packed_kernel_self_test` | toy vectors / bytes | `kernel_status`, `decode_status`, `packed_kernel_status` | Self-test toy values: - 1-bit dot: raw dot `-1.5`, scale `0.25` → `-0.375` - 4-bit dot: raw dot `9`, scale `0.125` → `1.125` - packed 1-bit decode: byte `13` (`0b00001101`, LSB-first) → bits `1,0,1,1` - packed int4 decode: bytes `195` (`0xC3`) and `47` (`0x2F`), low-nibble-first → signed nibbles `3,-4,-1,2` - packed 1-bit / 4-bit dot は decoder 経由で pre-expanded dot と同じ toy result(`-0.375`, `1.125`)を返す。 Build command: ```bash /Users/scratchbrulee/.cargo/bin/goboscript build turbowarp/ssmoelm_mvp ``` Build result: ```text Emitted 361 blocks ssmoelm_mvp.sb3 size: 7348 bytes main sprite: 292 blocks lists: packed1_bytes, k1_x, k1_w_bits, packed4_bytes, k4_w_nibbles, k4_x ``` Next step is 1-token projection verification: compare a tiny Scratch packed matmul/projection fixture against the Python reference, then extend it toward attention and MoE expert rows. --- ### Decoding sweep(2026-06-08) SSMoELM-it の生成設定を小規模 prompt sweep と手動確認で比較。 | temperature | top_k | repetition_penalty | 傾向 | |---:|---:|---:|---| | 0.0 | 1 | 1.0 | 最も文法的だが同じ句を繰り返しやすい | | 0.0 | 1 | 1.15 | 反復は減るが bullet/list 系で引用符反復が残る | | **0.0** | **1** | **1.3** | 低品質語の混入を抑えつつ反復も比較的少ないため採用 | | 0.55 | 20 | 1.15 | 多様だが固有名詞・断片語の混入が増える | | 0.7 | 30 | 1.15 | ヒューリスティックスコアは高いが意味崩壊が多い | 採用値: ```text temperature = 0.0 top_k = 1 top_p = 0.9 repetition_penalty = 1.3 ``` 理由: 47M / 8k vocab の小型モデルでは sampling の多様性より greedy + 強め repetition penalty の方が破綻しにくい。`top_p` は `temperature=0.0` では実質未使用だが、sampling に切り替える場合の互換既定として 0.9 を維持。 --- ### Shared expert only ablation(2026-06-08) PyTorch lm-eval adapter に `--shared-only` を追加し、routed experts を無効化して shared expert だけで短い 0-shot ベンチを実行。 Command: ```bash /etc/profiles/per-user/scratchbrulee/bin/uv run python eval_benchmark_pt.py \ --ckpt checkpoints/ssmoelm_it_packed.safetensors \ --tasks hellaswag,lambada_openai \ --limit 100 /etc/profiles/per-user/scratchbrulee/bin/uv run python eval_benchmark_pt.py \ --ckpt checkpoints/ssmoelm_it_packed.safetensors \ --tasks hellaswag,lambada_openai \ --limit 100 \ --shared-only ``` | Mode | HellaSwag acc_norm | HellaSwag acc | LAMBADA acc | LAMBADA ppl | |---|---:|---:|---:|---:| | Full MoE | 40.0% | 35.0% | 11.0% | 732.20 | | Shared only | 30.0% | 31.0% | 7.0% | 2742.00 | 結論: shared expert だけでも HellaSwag は random を少し超えるが、LAMBADA perplexity が大きく悪化。routed experts は品質に効いている。