miutti's picture
Source: kernel, desktop app, tools (snapshot of the GitHub repo)
df41178 verified
|
Raw History Blame Contribute Delete
137 kB
# 結果まとめ(2026-08-30)
## 結論から
**1bit未満の圧縮は作れたが、そのままではモデルが壊れる。**
| | bit/重み | 1層の誤差 | モデル全体のPPL |
|---|---|---|---|
| 元(Q4_K) | 4.8 | — | **5.58** |
| 我々の圧縮 | 1.155 | 0.4〜0.5 | **24257** ← 崩壊 |
出力は `n n n n n...` の繰り返しになり、実用不能。
## いちばん重要な教訓
**重み誤差はモデル品質を保証しない。** セッション中ずっと「誤差0.502→0.366」と改善を
追ってきたが、その全部が実用ラインの遥か手前だった。
(Cross-Layer論文も「重み誤差46%改善でも品質は悪化しうる」と実証している)
必要な誤差水準の目安: Q4_Kで0.05〜0.1。**我々は5〜10倍悪い。**
## 世界の手法との差
NanoQuant / BiSCo / BTC-LLM は例外なく**2段構え**:
1. 粗く量子化する ← 我々がやったこと(単体では壊れる)
2. **元モデルの出力に合わせ込む(再構成・蒸留)** ← 我々が丸ごと省いていたこと
これは仕上げの磨きではなく**必須工程**だった。
## 効いた/効かなかった(1層の誤差ベース。品質は別問題)
効いた: 低ランク+残差量子化(大) / スケールのビット削減(大) / kと辞書の拡大(中) /
チャンネル重要度スケーリング(本物の活性化でのみ +5〜24%) / 内側回転(+2%) /
エントロピー符号化(3〜5%)
効かなかった: 全体回転(ベクトル量子化には有害) / 文脈モデル(ゼロ) /
交互精錬(ノイズの内側) / GPTQ(合成活性化では過学習)
## トレリス符号化(QTIP方式)— 実装済み・有望
1bitで誤差0.5114。**シャノン理論限界0.500まであと2%**。コードブックを保存しない。
以前「乱数コードブックは崩壊(0.947)」と報告したが、あれは**独立に最近傍探索したから**。
系列全体をViterbiで最適化すれば理論限界に届く。当時の結論は誤りだった。
## 測定環境の教訓
合成活性化(1%を一律20倍)は本物(上位1%が2.8〜7.5%)より2〜6倍極端で、
**活性化を使う手法の評価を系統的に反転させていた**。本物のimatrixで測り直したら
全9設定で改善に転じた。前半/後半テキストでの重要度相関は0.958と安定。
## 実測した速度(このMac: Intel i7 6コア / 16GB)
qwen2.5-coder 3B Q4_K = **8.00 t/s**(1.79GiB)→ 実効スループット15.4GB/s
(カタログ40GB/sの38%)
0.43bit実現時の推定(復元コストをゼロと仮定した上限値):
14B→8.3 t/s、32B→5.2 t/s、70B→2.9 t/s
## 次
各層の本物の活性化を捕まえる順伝播をnumpyで実装済み。これで
**層ごとの出力再構成**(世界の手法の核心)が実装できる。
## ファイル
- `lib/` gguf読み書き / コーデック / トレリス / 回転 / 順伝播 / NVIDIA評議会
- `experiments/01〜15/` 各実験のコードと結果JSON
- `tools/llama.cpp/` 本家ビルド(perplexity・速度測定・imatrix採取)
- `data/calib/` 較正テキストと本物の活性化統計
- `data/models/` 圧縮したGGUF
---
# 追記(2026-08-31)実験23: 層ごとのビット配分
引き継ぎ書6-1「一番効くと見ている。未着手」に着手。
既存の252層チェックポイントと本物のimatrixだけで測れるので、**再量子化なしで**感度が出た。
## 23-A 感度測定 (`experiments/23_alloc/sensitivity.py`)
指標は出力空間の相対誤差 `‖(W−Ŵ)diag(√imat)‖_F / ‖W diag(√imat)‖_F`。
**層ごとの痛みは20.1倍違う**(最悪0.676 / 最良0.034)。見立ての「2倍以上」より桁違いに大きい。
種類ごと(痛い順):
| 種類 | 平均出力誤差 | 現行レシピの配分 |
|---|---|---|
| attn_output | 0.605 | 1.196 bit |
| ffn_down | 0.581 | 1.139 bit |
| ffn_up | 0.555 | 1.145 bit |
| attn_v | 0.499 | **1.641 bit** |
| attn_q | 0.447 | 1.196 bit |
| ffn_gate | 0.417 | 1.145 bit |
| attn_k | 0.366 | **1.641 bit** |
**配分が逆になっていた。** 最も楽な attn_k/v に最も厚く(形の都合で自動的にそうなる)、
最も痛い attn_output/ffn_down に薄く配っていた。
深さ方向: blk.21〜26 と blk.35 が痛い。blk.1〜2 が突出して楽(0.29)。
→ 引き継ぎ書6-2「最初と最後の数層を保護」は**最後は正しいが最初は逆**。blk.0は痛いがblk.1-2は最も楽。
## 23-A の最大の発見: 重み誤差は無相関だった
```
出力誤差 vs 重み誤差 の順位相関 = -0.111
```
RESULTS.mdの従来の教訓は「重み誤差はモデル品質を保証しない」だったが、実測はより厳しい。
**ほぼ無相関、わずかに負。層の優先順位づけに重み誤差を使うのはランダム選択と同等。**
極端な例(量子化の失敗は0件。すべて正常に完了した層):
| 層 | 重み誤差 | 出力誤差 |
|---|---|---|
| blk.1.ffn_down | **403.5** | 0.244(平均以下) |
| blk.1.ffn_up | 4.41 | **0.035** |
| blk.1.ffn_gate | 1.41 | **0.034** |
重み誤差1.0は「ゼロを出力するより悪い」水準。活性化が訪れない方向に重みが大きいだけで、
出力を見る手法としては設計どおりの挙動。**バグではない。**
ただし403倍は説明可能な範囲を超えている疑いがある。`quantize2.py` の標本不足時の分岐
(ffn_downは入力11008次元 > 較正1024トークン)で `Wh = Wh / s`, `s = imp**0.75` が
小さい成分で発散し得る。出力指標では隠れるが**死んだ方向にビットを浪費している**可能性。
→ 未検証。次に追う価値あり。
## 23-B 配分ソルバ (`experiments/23_alloc/allocate.py`)
レート歪み配分。`argmin_c D_ic + λ·b_ic·size_i` を各層独立に解き、λを二分探索で予算に合わせる。
凸包上では厳密解。候補のビット数は `recipe.fit` と同じ式で**閉形式**に出せるのでフィット不要。
歪みモデル `D(b) = D0·2^(−2λ(b−b0))` の λ は2実測点(unified 1.15bit と b07 0.70bit、36層)から推定。
λは種類によらずほぼ一定(0.81〜1.12)。**利得はカーブの形の差ではなく、誤差の大きさ(D0)の差から来る。**
結果(予算据え置き 1.1554 → 1.1536 bit/重み):
```
推定二乗誤差 7.977e+08 → 7.028e+08 (11.9% 減)
推定出力誤差RMS 0.5362 → 0.5033
```
配分は blk.1-2 を 0.45bit まで削り、blk.19-23 に 1.40bit 前後を回す形になった。
**但し書き: 11.9%減では足りない。** 実用に必要な誤差水準は0.05〜0.1、現在地は0.53。
これは単体でPPL 4267を救う手ではなく、他の手法と積む前提の土台。
## 23-C 実測 (`experiments/23_alloc/quantize3.py`)
quantize2.py との違いは1点だけ: 各層の k/cb/G/rank を recipe.json から引く。
レシピに無い層は従来の一律設定に戻る。→ 実行中
---
# 追記(2026-08-31 夜)外部の参照点: 富士通研究所 OneComp
動画『大規模言語モデルを個人のGPU向けに圧縮|生成AI圧縮OSS OneComp』より。
https://www.youtube.com/watch?v=yPiwS-P-R0U
**この研究にとって重要なのは「我々の判断が外から裏付けられた」こと。**
## OneComp のパイプライン(3段)と、我々の対応
| OneComp | 我々 |
|---|---|
| ① モジュールごとのビット割り当て+外れ値除去 | **実験23で今日実装**(層ごと配分)+チャンネル重要度スケーリング |
| ② Layer-wise PTQ(モジュール単位) | 実装済み(recipe.fit を層ごとに適用) |
| ③ **知識蒸留(ブロック単位+End-to-End)で精度回復** | **未実装** ← ここだけが空白 |
## 裏付けられたこと
**1. 層ごとのビット配分は正しい方向だった。**
実験23として今日実装したものが、OneCompでは①として**パイプラインの第1段**に置かれている。
「一番効くと見ている」という引き継ぎ書の見立ては当たっていた。
**2. 蒸留が必須工程であることの、二度目の確認。**
RESULTS.md では既に「世界の手法(NanoQuant/BiSCo/BTC-LLM)は例外なく2段構えで、
出力に合わせ込む工程を我々は丸ごと省いている」と書いていた。
OneComp も同じ構成で、しかも **ブロック単位と End-to-End の2種類**を明示している。
独立した4例目。**もはや疑う段階ではない。**
**3. 実験22は「蒸留」ではなかった。**
lib/distill.py がやっているのは「目盛り(グループごとのスケール)だけの繰り返し調整」で、
コード番号(本体のつまみ)は動かしていない。自分でも「block reconstruction の軽量版」と
書いていたとおり。+0.4〜3.0%で1周収束したのは当然で、**本命の蒸留は別物**。
この区別を曖昧にしていたのが、優先順位を誤らせていた。
## 数字の比較(操作点が違うので優劣ではない)
| | 元サイズ | 圧縮後 | 元PPL | 圧縮後PPL | 劣化 |
|---|---|---|---|---|---|
| OneComp (TinyLlama, 4bit) | 2.0GB | 0.85GB | 7.9 | 8.34 | **+4%** |
| 我々 (qwen2.5-coder 3B, 1.15bit) | 1.79GiB | — | 5.58 | 4267 | **崩壊** |
OneComp は名前に 1-Bit を掲げているが、**デモは4ビット**。
我々の1.15ビットとは操作点が違う。ただし「賢さを保つ」という一点で完敗している。
差は手法の数ではなく、**③蒸留の有無**。
## いま使えるか → 使えない
- **Qwen 未対応**(Llama / Gemma のみ。Qwen・DeepSeek・GPT-OS は「次期アップデート予定」)。
我々の対象は qwen2.5-coder なので、そのままでは通らない
- **CUDA GPU 前提**(`vram` 引数、CUDA環境ごとの pip install、vLLM で推論)。
このMacは Intel + CUDAなし
→ **道具としては使えないが、設計図としては使える。**
## この動画を受けての優先順位の変更
1. **ブロック単位の出力蒸留** ← 1位に格上げ。OneCompが③として持ち、我々だけが持たない工程。
「目盛りだけ」ではなく、**コード割り当てそのもの**を先生の出力に合わせて選び直す。
順伝播は実装済みなので、先生の中間出力は取れる
2. ffn_down の標本不足フォールバックの検証(重み誤差403倍の正体)
3. End-to-End の蒸留(要GPU。このMacでは現実的でない可能性が高い)
---
# 追記(2026-08-31 深夜)実験25・26: AdaRound と BRECQ
外部調査(OneComp / Gemini100件 / NVIDIA5モデル聞き取り)が全て同じ場所を指したので実装した。
5モデル中4〜5モデルが独立に挙げたのは **BRECQ** と **AdaRound** の2つだけだった
(GPTQ・Hessian・AWQ も挙がったが、これらは実装済み)。
## 実験24の副産物: 安い原因は全部消えた
NVIDIA 5モデルの1〜3位は満場一致で「出力層・埋め込み層・LayerNormの量子化」。
だが **我々は最初からそこを対象外にしている**(targets は blk.* の attn/ffn のみ)。当てはまらない。
当てはまる仮説(コードブック崩壊)を実測 → **否定**:
| 層 | 元の値の種類 | 量子化後 | 有効ランク | 元ランク |
|---|---|---|---|---|
| blk.20.attn_output | 14034 | 8393 | 389.7 | 368.3 |
| blk.21.ffn_down | 13492 | 8118 | 404.6 | 405.7 |
値の種類は8000超、有効ランクも保存。崩壊していない。
GGUF書き出しも素通しでPPL完全一致済み。**バグではなく、単に精度が足りない**と確定した。
## 実験25: AdaRound(丸め先を出力誤差で選ぶ)→ +10.4%
`recipe.py` の残差割り当てを、ユークリッド最近傍から **出力誤差最小** に変更。
距離(c) = Σ_j w_j (x_j − Cb[c,j])² w = diag(H)
採点は訓練に使っていない別区間の活性化。
| 層 | k/cb | 従来 | AdaRound | 改善 |
|---|---|---|---|---|
| blk.10.attn_q | 8/8 | 0.2372 | 0.1984 | +16.4% |
| blk.10.attn_q | 16/8 | 0.3301 | 0.2777 | +15.9% |
| blk.10.attn_q | 8/12 | 0.1664 | 0.1380 | +17.0% |
| blk.20.attn_output | 8/8 | 0.5162 | 0.4931 | +4.5% |
| blk.20.attn_output | 16/8 | 0.6714 | 0.6470 | +3.6% |
| blk.20.attn_output | 8/12 | 0.3770 | 0.3577 | +5.1% |
**6/6 で改善、平均 +10.4%。ビット数は一切増えていない(追加コスト0)。**
これは我々自身の実測「出力誤差と重み誤差の順位相関 −0.111」から予測できた結果。
重み空間の距離で丸め先を選ぶのは、ほぼランダムに選ぶのと同じだった。
効き方の差も筋が通る: attn_q(+16%) 対 attn_output(+4.5%)。
attn_output は実験23で**最も痛い層**(0.61)と判明していた層で、
**既に苦しい層は丸め方の工夫だけでは足りない** → BRECQ が要る理由。
## 実験26-A: 生徒の入力のずれ → 破滅的だった
同じトークン列を「元の重み」と「unified 全252層量子化済み」で流し、各層の入力のずれを測定。
| 層 | ずれ |
|---|---|
| blk.0.attn_q | **0.0000** ← 埋め込みは量子化していないので当然。計測が正しい証拠 |
| blk.0.ffn_down | **0.8529** ← ブロック0を通っただけでこれ |
| blk.3.ffn_down | 1.0985 |
| blk.21.attn_q | **1.6301**(最悪) |
| blk.33.ffn_down | 1.1182 |
**ずれ 1.0 超 = 「ゼロを入れるより先生から遠い」。** blk.3以降ほぼ全層が1.0超。
信号が原形を留めていない。**PPL 4267 の正体はこれ。**
注目すべきは、ゆっくり積み上がったのではなく **ブロック0だけで0.85** に達している点。
各層の出力誤差0.5が大きすぎて、1ブロックで信号を壊している。
## 実験26-B: BRECQ の効果 → ずれが大きいほど効く
「ずれが大きすぎて手遅れでは」を検証。人工的にずれを作って `brecq.target_weight` を適用。
| ずれ | 補正なし | BRECQ | 改善 | 歯止め |
|---|---|---|---|---|
| 0.30 | 0.1809 | 0.0870 | +51.9% | 採用 |
| 0.80 | 0.4825 | 0.1154 | +76.1% | 採用 |
| **1.20** | 0.7236 | **0.1307** | **+81.9%** | 採用 |
**危惧は外れた。** ずれ1.2でも誤差を82%削る。歯止め(元の重みから相対距離1.0超で棄却)は
一度も作動せず。ずれが大きくてもそれは「でたらめ」ではなく**規則的な歪み**なので、
最小二乗で打ち消せる。
## 実装したもの
- `lib/recipe.py` … `assign_w()` と `fit(adaround=True)`。**既定オフなので従来動作は不変**
- `lib/brecq.py` … `target_weight()`(先生の出力に追いつく理想の重みを最小二乗で解く)、`drift()`
- `experiments/26_brecq/quantize4.py` … BRECQ+AdaRound統合版。
先生の活性化を先に採ってディスクに保存する2段構え(RAM16GBでは同時に持てない)。
`ADAROUND=0` / `BRECQ=0` で個別に切れる ← **どちらが効いたか曖昧にしないため**
## まだ確かめていないこと(正直に)
- BRECQ の +82% は **1層・人工的なずれ**での結果。モデル全体で同じとは限らない
- 実際のずれはガウス雑音より構造を持っている可能性がある
- 各層で追いつききれない残りが下流へ渡るので、**252層積んだ最終PPLはやってみないと分からない**
## 実験23-C の結果
252/252層完了。**平均 1.1536 bit/重み**(allocate.py の予測値と一致)。
GGUF 5.81GB 書き出し済み。PPL測定中。
## 実験23-C の結末: **失敗**。PPL 10573(一律1.154bitの4267より悪化)
252/252層完了、平均 1.1536 bit/重み(予測値どおり)。だが PPL は悪化した。
一律 1.154bit (unified) → PPL 4267
配分 1.154bit (alloc) → PPL 10573 ← 2.5倍悪化
### 原因(特定済み): 伝播を勘定に入れていなかった
配分ソルバが浅い層を削り倒していた:
| ブロック | 配られたビット |
|---|---|
| blk.0 | 1.125 |
| **blk.1** | **0.450** ← 一律の4割 |
| **blk.2** | **0.466** |
| blk.3 | 0.788 |
| blk.20 | 1.356 |
| blk.35 | 1.325 |
`sensitivity.py` が測ったのは **各層を単独で見た出力誤差**。
浅い層の誤差が **下流34ブロックへ伝播して増幅する** ことを勘定に入れていない。
**皮肉なことに、その反証は同じ夜に自分で 測定していた**(実験26-A):
blk.0.ffn_down のずれ 0.85、blk.3 で 1.10。浅い層の誤差は全下流を壊す。
一方 blk.35 の誤差は下流が無いので伝播しない。
→ **最も削ってはいけない層を、最も削っていた。**
副次的な原因: λ は 1.15bit と 0.70bit の2点から推定したのに、
**0.45bit という測定範囲外まで外挿**した。実測していない領域の予測を信じた。
### 訂正の訂正
本日「引き継ぎ書6-2『最初と最後の層を保護』は最初が逆」と書いたが、**それが誤り**。
**元の引き継ぎ書が正しかった。浅い層は保護すべき。** 実測で確定。
### 配分そのものは無価値ではない
やり直すなら **感度に「下流での増幅率」を掛ける**こと。
測り方は実験26-A と同じ: ある層だけ量子化して流し、最終出力(logits)がどれだけ動くかを見る。
252層それぞれで1回ずつ流す必要があり重いので、代表層だけ測って内挿する。
ただし **順序としては BRECQ で足場を作ってから。**
---
## 2026-09-02 深夜 — 実験27〜31
### 実験27: 代替経路の削除(最大の発見)
`quantize4.py` の「標本 < 入力次元なら単位行列で代用」分岐が、
GPTQ・AdaRound・出力を意識した低ランク分解を**同時に無効化**していた。
標本512 vs 入力次元2048〜11008 なので**全252層がこの経路**。
→ 実験22〜26は主力3手法が一度も動いていない状態で測っていた。
blk.0.ffn_gate / 未使用データ採点:
```
代替経路 重み誤差 2.309 出力誤差 0.377
本物の活性化 重み誤差 0.627 出力誤差 0.274
本物+AdaRound 重み誤差 0.650 出力誤差 0.256
```
### 実験28: 予算掃引(8ブロック)— 初めて動く点を得た
```
FP16 PPL 5.5786
4.202bit PPL 5.5842 誤差0.132
2.202bit PPL 5.9997 誤差0.389 ← 実用圏
1.202bit 測定不能 誤差0.678 ← 崩壊
```
### 実験29: 否定された5件
| 案 | 実測 |
|---|---|
| 残差へのアダマール回転 | 0.264 → 0.268〜0.286(悪化) |
| 減衰 0.1 → 0.01 | 0.256 → 0.275(悪化) |
| rank 32 → 64 | 0.286 → 0.286(効果ゼロ) |
| 語彙の算術符号化 | 実情報量7.870/8.000(無駄1.6%) |
| 層間差分符号化 | 相関 +0.000〜0.022(層は無関係) |
### 実験30: 予算の使い道(blk.4.ffn_down)
```
語彙2^10 (1.452bit) 誤差0.226 ← 勝ち
顕著3% (1.407bit) 誤差0.251
rank128 (1.427bit) 誤差0.271
```
**語彙拡大 > 顕著重み保護 > 低ランク拡大**
### 実験31: 個人較正(新カテゴリの可能性)
1.202bit・8ブロック:
```
汎用文章 個人文章
較正=汎用 38.73 446.61
較正=個人 100.49 128.43 ← 3.5倍改善
```
同じ大きさ・同じ構造で、較正データだけを替えた。
2.2ビットでの再検証はSSD切断により中断。
### 理論的結論
1.2bpwのレート歪み限界は相対誤差 **0.435**、必要水準は **0.10**。
**限界そのものが必要水準の4倍**で、記憶なし符号化では原理的に到達不能。
目標を2ビット前後へ切り替えるべき。
---
## 2026-09-02 昼 — 実験32: 2.2ビットでの個人較正比較(中断分の再開・完了)
8ブロック量子化(k=4, cb=8, AdaRound入, BRECQ切, NTOK=512)。PPL は `--chunks 12 -c 512`。
```
汎用文章 個人文章
元モデル 5.5786 17.6729
較正=汎用 6.0028 (+7.6%) 19.5406 (+10.6%)
較正=個人 5.8919 (+5.6%) 18.7189 (+5.9%) ← 両方で勝ち
```
- 1.2ビットでは「個人較正は汎用文章で3倍悪化」だったが、**2.2ビットでは汎用文章でも個人較正が勝つ**。
同じ512トークンでも個人データの方が雑多(日本語・Python・英語・数式)で守備範囲が広い、という前回の観察どおり。
- 劣化率が両文章で釣り合った(5.6% / 5.9%)。2.2ビットでは「使い手の形に痩せる」というより「較正の質」として効いている。
- 較正=汎用・汎用文章 6.0028 は前回の 5.9997 と一致(再開が正しく機能した証拠)。
- 結果ファイル: `data/ckpt/jibun22_result.txt`
---
## 2026-09-02 昼 — 実験33: E8格子量子化(引き継ぎ書の優先2)— 実装済み・層によって勝ち負けが分かれる
### 実装したもの(`lib/e8.py`、`lib/recipe.py` の `lattice="e8"`)
- E8 = D8 ∪ (D8+½) の最近点復号(Conway & Sloane, Ch.20)。総当たりと**完全一致**を確認。
- AdaRound を厳密に組み込んだ: 重み付き距離 Σ w_j(y_j−c_j)² でも D8 の最近点は
「座標ごとの丸め」か「そこから1座標だけ動かした点」なので、奇数和の直しで動かす座標を
argmax|d_j| → argmin w_j(1−2|d_j|) に替えるだけで厳密解になる(総当たりで検証済み)。
- 固定レート化: ノルム² ≤ R2 の格子点だけを語彙にする。R2=10 で 56881 語 = 1.975 bit/重み。
テータ級数(1, 240, 2160, 6720, 17520, 30240, 60480…)を総当たりで検算。
- 形状利得の実測: 一様雑音で E8 0.0717 / Z^8 0.0833 = **0.65 dB**(理論どおり)。
### いちばん効いたのは「球の外に落ちた入力」の扱い
2ビット付近では入力ベクトルのかなりの割合が語彙の球の外に落ちる。
「原点方向へ縮めて復号」だけだと総当たりの最近点に **+18%** 負けていた。
縮めたあと隣接格子点(最短ベクトル240本)を見て局所改善すると総当たりと一致(11刻み+3周)。
この改善の前後で E8 の成績が反転した(attn_q: 0.0972 → 0.0889)。
### 同一ビットでの比較(単層・未使用データで採点・AdaRound入・GPTQ伝播あり)
E8 (R2=10, 1.975bit) 対 k-means(語数を合わせた 4次元 2^7.9 語):
```
層 k-means(現行2^8) k-means(語数合わせ) E8 E8の勝ち負け
blk.4.attn_q 0.0913 0.0931 0.0882 +5.3%(現行比 +3.3%)
blk.4.ffn_gate 0.0189 0.0192 0.0184 +4.2%(現行比 +2.7%)
blk.20.attn_output 0.2951 0.2984 0.2797 +6.3%(現行比 +5.2%)
blk.4.ffn_down 0.1538 0.1563 0.1633 −4.5%(現行比 −6.2%)
```
- **attn / ffn_gate では勝ち、ffn_down では負け**。重み誤差も同じ向き(ffn_down は重み空間でも負け)。
ffn_down の残差は分布の裾が重く、球内一様の格子より学習した語彙の方が合う、と見ている(未検証)。
- 行ごとの目盛り探索は +0.3〜0.8%(小)。rms 正規化は max と結果が同じ(探索が吸収する)。
- 圧伸(p=0.75)は attn_q で悪化(−3.5%)。ffn_down では未試験。
- 計算時間は k-means の **4〜5倍**(attn_q 11秒→42秒、ffn_gate 37秒→184秒)。
GPTQ の逐次ループが 8列ごとに復号器を呼ぶため。k-means は行列積1本で済む。
### 判定
- 理論の +0.65 dB は重み誤差には出る(−5〜7%)が、出力誤差では層により +3〜5% / −6% に化ける。
**2.2ビットの動作点を変えるほどの効果ではない。**
- 全層通しには従来の k-means を使う。E8 は「ffn_down 以外に使う混成」なら +3〜5% を取れる見込みだが、
量子化時間が 4〜5倍になるので優先度は低い。
- 引き継ぎ書の「k-means は格子を超えられない(Zamir 1996)」は高解像度極限の話で、
2ビット・裾の重い分布では成り立たない層がある、というのが実測の答え。
---
## 2026-09-02 昼 — 実験34: 修正後の経路で BRECQ は 2.2ビットで効くか → 効かない(悪化)
8ブロック・k=4・cb=8・AdaRound入・較正=汎用(tokens_big)。実験32の BRECQ切(6.0028)と同条件で BRECQ だけ入れた。
```
汎用文章 個人文章
BRECQ切(実験32) 6.0028 19.5406
BRECQ入(本実験) 6.1586 20.4290
```
- **BRECQ は 2.2ビットでは悪化**(汎用 +2.6%、個人 +4.5%)。
- 生徒の入力のずれは 8ブロックで 平均0.105・最大0.156 と小さく、`mindrift=0.15` に届く層がほとんど無い。
発動した層では、標本512 < 次元 の最小二乗解が較正トークンに過適合し、量子化誤差の上に乗る。
- 26-B の「+52〜82%」は人工的な大きいずれ(0.3〜1.2)での結果。2.2ビットの実際のずれはその 1/3〜1/10 で、
直す対象がそもそも無い。BRECQ が要るのは 1.2ビット圏(ずれ 0.85 超)だが、そこは理論限界で不可能と確定済み。
- 全層通しは **BRECQ切・個人較正** で行う。
### 運用: SSD が 1日に 4回切断(13:02 / 13:14 ほか)
- `quantize4.py` の `say()` がログ(SSD上)を書けずに落ち、`retry()` の再試行が効いていなかった → ログ書き込みを失敗許容にし、再試行を 4回(50秒) → 8回(180秒) に延長。
- 切断→自動再マウント→チェックポイント検査→再開 を行う番人(`auto_resume.sh`)で、人手なしに続きから再開できた(壊れたのは書きかけの1層だけ)。
- 全層通し(約2時間・書き込み12GB)は内蔵ディスクへ退避したコピーで回す(`~/LocalAI_mirror`)。
---
## 2026-09-03 深夜 — 実験35: 2.2ビットで全252層を通した(個人較正・BRECQ切)— 壊れないが 1.6〜1.8倍の劣化
k=4, cb=8, rank32, AdaRound入, BRECQ切, NTOK=512, 較正=個人(tokens_jibun)。平均 **2.217 bit/重み**。
実行は内蔵ディスクの退避コピー(`~/LocalAI_mirror`)。SSD切断と再起動をまたいでチェックポイントから2回再開して完走。
```
汎用文章 個人文章
元モデル 5.5786 17.6729
8ブロックのみ(実験32) 5.8919 (+5.6%) 18.7189 (+5.9%)
全252層(本実験) 10.2424 (+84%) 28.6660 (+62%)
```
- **初めて全層で「壊れていない」数字が出た**(1.2ビット系は 4267〜133776)。
- ただし 8ブロックの +5.6% が 36ブロックで +84% に積み上がる。**ブロックあたり約1.7% の複利**。
層ごとの重み誤差は全層で 0.30〜0.50(中央値 0.36、blk.1.ffn_up だけ 0.85)と一様なので、
劣化は特定の層の事故ではなく、**小さなずれが深さ方向に累積した結果**。
- 個人文章の劣化率(+62%)の方が汎用(+84%)より小さい。較正データの効果は全層でも残っている。
- 参考: 3Bクラスの2ビット台は微調整なしだと元の 1.5〜2倍が相場(QuIP#/AQLM の小型モデルの報告)。
この結果はその相場の中。**微調整(蒸留)なしでこれ以上を狙うなら、予算を語彙に足す(CB=9/10)のが最短**。
### 次の切り分け(未実施)
1. `BLKMAX=15 / 23` で PPL を測り、劣化の積み上がり方(線形か指数か)を確かめる(各30〜50分)
2. `CB=9`(約2.45bit)/ `CB=10`(約2.7bit)の8ブロック試験 → 効くなら全層(`tools/ops/vocab.sh`)
3. 実験26-A の方法で 2.2ビットのずれを深さごとに測り、どの深さから急に増えるかを見る
### 成果物
- `data/models/full22j.gguf`(5.81GB, F16書き出し)、`data/ckpt/full22j/`(252層)、`data/ckpt/full22j.log`、`data/ckpt/full22j_result.txt`
---
## 2026-09-03 昼 — 実験36: llama.cpp 本番形式への移植(計画1位)— 速度・個人較正・二重量子化を同時に決着
調査記録2(`~/Desktop/調査記録2_圧縮の実行側と代替手段.txt`)の問いに答えるため、
imatrix を3系統作り、9本を量子化して速度と PPL を測った。
測定条件を統一(PPL: 汎用 corpus_test.txt 20chunk / 個人 jibun/test.txt 24chunk、-c 512)。
**過去の chunks=12 の数字とは直接比較できない**ので、元モデルも同条件で測り直した。
### 36-A. 速度 — i-quants は帯域の半分しか引き出せない(調査記録2 第1部の予測が的中)
`llama-bench -p 64 -n 32`、生成 tg32 t/s。実効帯域 = ファイルサイズ × t/s。
```
形式 サイズ 4t 6t 8t 実効帯域(8t) 復号方式
Q4_K_M (元) 1.79GiB 13.44 14.24 14.83 28.5 GB/s 一様
Q3_K_M 1.48GiB 14.55 16.19 17.05 27.1 GB/s 一様
Q2_K 1.18GiB 18.78 20.14 20.90 26.5 GB/s 一様
TQ2_0 926MiB 24.00 20.40 — 23.3 GB/s 三値・算術
IQ2_XS 978MiB 11.19 13.52 14.27 14.6 GB/s 表引き
IQ2_XXS 899MiB 12.27 14.07 15.07 14.2 GB/s 表引き
IQ3_XXS 1.19GiB 9.02 10.87 10.56 13.5 GB/s 表引き
```
- **k-quants は 26〜28 GB/s(帯域律速)、i-quants は 13〜15 GB/s(CPU律速)**。ちょうど半分。
- スレッド依存も予測通り: IQ2_XXS は 4t→8t で +23%(CPU律速の証拠)、Q2_K は +11%(帯域律速)。
- **判定**: IQ2_XS 14.27 < Q4_K_M 14.83。調査記録2 の基準「超えないなら自作復号器を書く意味は無い」に照らし、
**自作の表引き復号器(256語×4次元 fp32 = 4KB、L1には載るがレジスタに載らない)の計画は中止でよい**。
- TQ2_0 は 24.00 t/s と最速だが **PPL 378万/1208万で完全崩壊**。三値は PTQ では品質が出ない(BitNet は事前学習から)。
### 36-B. 個人較正 imatrix は効かない(GPTQ 経路との決定的な違い)
元モデル基準(同条件): 汎用 6.1500 / 個人 14.6673
```
汎用文章 個人文章
IQ2_XS 較正=汎用40 8.3905 20.8438 ← 汎用テストで勝ち
IQ2_XS 較正=個人40 8.5513 20.4678 ← 個人テストで勝ち
IQ2_XS 較正=個人200 8.5501 20.6512
IQ3_XXS 較正=汎用40 6.6793 16.2929 ← 汎用テストで勝ち
IQ3_XXS 較正=個人200 6.7426 16.2692 ← 個人テストで勝ち(誤差内)
```
GPTQ 経路で確認していた「**個人較正が両方の文章で勝つ**」(実験32: 5.8919/18.7189)は、
**imatrix では再現しなかった**。自分のドメインで 1.8% 勝ち、汎用ドメインで 1.9% 負けるという素直な結果。
llama.cpp コミュニティの「重要度行列はランダムに近いデータで最良」を支持する側に出た。
> 調査記録2 §2-3 の整理が正しかった。imatrix は「活性化の大きさ」= 値域・スケール選択の統計で、
> 広く薄く見た方が安定する。GPTQ のヘッシアン XᵀX は「どの方向の誤差が出力に響くか」の共分散構造で、
> ドメイン一致が効く。**同じ「較正データ」でも使われ方が違う**。
- chunk 数を 40→200 に増やす価値も無かった(同じか、個人テストではむしろ 40 が良い)。
公式推奨 100 に対し、40 で足りている。
### 36-C. 二重量子化の害は「型による」— Q3_K_M だけが壊れていた
fp16 原本(Qwen 公式 GGUF・重みの連続性を検証済み)を土台に引き直した。
```
基準 汎用 個人
fp16 原本 6.0974 14.6148
Q4_K_M 6.1500 14.6673 (+0.86% / +0.36%)
```
**基準値そのものはほぼ同じ**。調査記録2 の「+84% は Q4_K_M 比で真の劣化はもっと大きい」は杞憂だった。
一方、再量子化の害は型で分かれた:
```
形式 土台 汎用 個人
Q3_K_M Q4_K_M 9.1921 25.4955
Q3_K_M fp16 6.4862 (-29%) 15.6253 (-39%) ← 激変
Q2_K Q4_K_M 7.2713 17.4951
Q2_K fp16 7.2972 18.2547 ← ほぼ同じ
IQ2_XS Q4_K_M 8.5513 20.4678
IQ2_XS fp16 9.1206 21.6222 ← むしろ悪い(後述)
```
- **Q3_K_M は Q4_K_M からの再量子化で壊れていた**。4bit 格子に乗った値を3bitに落とすときの
格子の不整合と見ている。1517MB もあって IQ2_XS(984MB) より悪いという異常はこれが原因だった。
- Q2_K / IQ2_XS は再量子化にほぼ耐える。**「--allow-requantize は品質を著しく損なう」は型依存**。
### 36-D. tied embeddings の展開が損をしている(新発見)
公式 fp16 GGUF は tied を展開して `output.weight` を別に持つ(435 vs 434 テンソル、値は完全一致)。
これが llama.cpp の型選択を変える:
```
土台 出力層 入力埋め込み
Q4_K_M (tied) q6_K 243MiB 同左(共有)
fp16 · IQ2_XS q5_K 204MiB q2_K 97MiB ← 埋め込みが Q6_K→Q2_K に落ちる
fp16 · Q2_K q6_K 243MiB q2_K 97MiB
fp16 · Q3_K_M q6_K 243MiB q3_K 128MiB
```
`--token-embedding-type q6_K --output-tensor-type q6_K` で条件を揃えても:
```
IQ2_XS 個人較正40 サイズ 汎用 個人
Q4_K_M 土台 (tied) 984MB 8.5513 20.4678
fp16 土台 (埋込q2_K) 1082MB 9.1206 21.6222
fp16 土台 (両方q6_K) 1267MB 9.0090 21.1689 ← 2倍のビットを使ってなお 5.3% 負け
```
**出力層に2倍のビットを与えても Q4_K_M 土台に勝てない**。入力埋め込みと出力射影は本来同じ行列なのに、
別々に量子化されて表現がずれるためと見ている(検証中: fp16→Q4_K_M を作って元の tied 版と比較)。
### 36-E. 実用上の最適解が入れ替わった
fp16 原本を基準にした劣化率:
```
モデル サイズ 生成t/s(8t) 汎用 個人
元 Q4_K_M 1841MB 14.83 +0.9% +0.4%
Q3_K_M (fp16土台) 1645MB 17.05 +6.4% +6.9% ← 小さく・速く・劣化6%台
Q2_K (fp16土台) 1314MB 20.90 +19.7% +24.9%
IQ2_XS (fp16土台) 1082MB 14.27 +49.6% +48.0%
自作 2.2bit (full22j) 5811MB* 5.3 +84% +62% ← *F16書き出し
```
**Q3_K_M(fp16土台) が元モデルより小さく・速く・劣化6%台**。「今日から使えるモデル」の答えはこれ。
### 成果物
- `data/models/imat/`(Q4_K_M 土台 9本)/ `data/models/imat16/`(fp16 土台 5本)
- `data/calib/imat_jibun.imatrix`(200) / `imat_jibun40.imatrix`(40) / `f16_jibun40` / `f16_hanyou40`
- `~/LocalAI_mirror/models/qwen3b-fp16.gguf`(6,800,646,592 B・検証済み)
- 記録: `data/ckpt/imat_result.txt` / `imat_bench.txt` / `RESUME_imat.md`
### 36-F. 土台と imatrix の交絡を分離した(2×2 の要因計画)
36-C/D で「IQ2_XS では Q4_K_M 土台の方が良い」という逆説が出たが、土台と一緒に imatrix も
入れ替わっていた(`imat_jibun40` は Q4_K_M で計算、`f16_jibun40` は fp16 で計算)。
4セルすべてを埋めて分離した。すべて IQ2_XS・個人較正40chunk・984MB。
```
imatrix=Q4_K_M由来 imatrix=fp16由来
土台 = Q4_K_M 8.5513 / 20.4678 21.3923 / 57.4600 ← 破綻
土台 = fp16 tied 9.2597 / 23.2936 9.0198 / 21.2074
```
1. **imatrix は土台のモデルと一致させないと破綻する**。Q4_K_M 土台に fp16 由来の imatrix を
当てると PPL が 2.5倍/2.8倍に跳ね上がる。imatrix は「そのモデルの活性化統計」なので、
量子化済みモデルと fp16 モデルでは値域が違う。
2. **非対称**: fp16 土台に Q4_K_M 由来 imatrix を当てるのは比較的軽傷(9.26 / 23.29)。
逆向きだけが破綻する。
3. **imatrix を揃えた上でも Q4_K_M 土台が 5.2% / 3.6% 良い**(8.5513 vs 9.0198)。
→ **土台の効果は本物**。IQ2_XS のような超低ビットでは、中間ビット幅(Q4_K_M)を
経由した方が良い。既に4bit格子に丸められた重みは外れ値が抑えられており、
2ビットの符号帳探索が安定するためと見ている(正則化効果)。
ビット数で向きが変わる:
```
形式 bpw 有利な土台
Q3_K_M 3.44 fp16 圧勝(-29% / -39%)… 4bit→3bit の格子不整合を避けられる
Q2_K ~3.2 互角(差は誤差内)
IQ2_XS 2.31 Q4_K_M が有利(+5.2% / +3.6%)… 正則化効果
```
> 公式の「--allow-requantize は品質を著しく損なう」「imatrix は F16 から」は、
> **3ビット台では正しいが、2ビット台では逆になる**。実務上は型ごとに測るしかない。
### 36-G. 決定版の速度 — 生成は速いがプロンプトは遅い
```
モデル サイズ 生成tg32(8t) プロンプトpp64(8t)
元 Q4_K_M 1841MB 14.83 78.2
tied Q3_K_M 1517MB 17.31 32.8
tied Q2_K 1216MB 20.79 24.9
```
**生成は 1.17〜1.40倍に上がるが、プロンプト処理は 2.4〜3.1倍遅くなる**。
損益分岐はプロンプト長 P・生成長 G として P ≈ 0.55G。
- 対話・長文生成(短い入力→長い出力): Q3_K_M / Q2_K が有利
- コード補完(長い既存コード→短い補完): 元の Q4_K_M が速い
→ 調査記録2 の【B】ik_llama.cpp(プロンプト処理 2.25〜5.18倍)の価値が上がった。
実装ゼロで、今わかった弱点にそのまま効く。
---
## 2026-09-03 夜 — 実験37: 14B と MoE 30B の実測(目標に対する最初の直接検証)
目標「16GB・増設不可の Mac で 14B〜32B を実用速度で動かす」に対し、
予測式 `速度 = 28 GB/s ÷ ファイルサイズ` が 3B の外でも成り立つかを検証した。
### 37-A. 予測式は 5倍のモデルでも成り立った
```
モデル サイズ 予測t/s 実測t/s(8t) 実効帯域
14B Q3_K_M 7.34 GB 3.8 4.09 30.0 GB/s
(3B Q4_K_M の参考値) 1.84 GB — 14.83 28.5 GB/s
```
予測 3.8 に対し実測 4.09(+8%)。**モデルが5倍になっても実効帯域は 28〜30 GB/s で一定**。
→ 引き継ぎ書の見積もりは信用してよい。
### 37-B. ★ MoE 30B が「密な 3B より賢く、同じ速度」だった
```
サイズ 生成t/s プロンプトt/s 汎用PPL 個人PPL
3B fp16 原本(無圧縮) 6800MB 5.3 — 6.0974 14.6148
3B Q4_K_M(元モデル) 1841MB 14.83 78.2 6.1500 14.6673
14B Q3_K_M 7340MB 4.09 6.93 — —
Qwen3-30B-A3B Q2_K (MoE) 11258MB 15.09 28.08 5.7328 12.0972
```
- **2ビット級に圧縮した 30B が、無圧縮の 3B より PPL で 6.0% / 17.2% 良い**。速度はほぼ同じ。
- 10.48 GiB はメモリに収まり、ページアウトは起きていない(11〜12GB の予算内)。
- MoE が理論通り動いている直接の証拠: 11.26GB を毎トークン全部読むなら 15.09 t/s には
170 GB/s が要る(不可能)。実際は **1トークンあたり 1.86GB = 全体の 16.5%** しか読んでいない。
活性パラメータ比 10.8% に共有部分(attention 等)を足した値と一致する。
- ※ モデルファミリーが違う比較(Qwen2.5-Coder-3B vs Qwen3-30B-A3B)なので、
純粋な圧縮率の比較ではない。実用上の意味(手元で動く中で最も賢く速い)は変わらない。
### 37-C. 判断が2つ変わった
1. **14B を使う理由が薄い**。30B-A3B は 14B より大きい知識を持ちながら 3.7倍速く、
プロンプト処理は 4倍速い(28.08 vs 6.93)。密な 14B は MoE の下位互換になった。
2. **「32B を載せるために 0.31GB 削る」という中目標は、MoE が先回りして解いた**。
削らなくても載り、15 t/s 出ている。
→ 研究の的は「載せるための圧縮」から「**載ったものの品質を上げる圧縮**」へ移る。
### 37-D. 残る不安(次に測ること)
調査記録3 第4部の警告: MoE の低ビット量子化は「ルータが別の専門家を選ぶ」という
PPL に出にくい壊れ方をする。同じプロンプトを数回投げて破綻の有無を見る必要がある。
Unsloth の実務知見では attention 系が特に敏感で、ffn_up/gate は3ビットで可、
ffn_down はやや敏感 — **3B で観察した「ffn_down は他と違う」と方向が一致している**。
---
## 2026-09-04 — 実験38: 速度の総ざらい(設定・ビルド・カーネル・GPU)
MoE 30B が動いた後、「読む量を減らす以外に速くする方法」を系統的に潰した。
結論を一行で: **生成は完全に帯域律速で、計算を足す工夫はすべて裏目に出る。
プロンプト処理だけが計算律速で、そこにカーネルの差が出る。**
### 38-A. 効いたもの(生成速度)
```
変更 効果 備考
スレッド 6 → 10 +15% 物理6コアだが論理12。10が最適、12で落ちる
Flash Attention を切る +49% 深さ4096のとき。llama.cpp の既定 auto は on を選ぶ
```
合わせて **深さ4096での生成が 6.24 → 9.98 t/s(+60%)**。モデルもビルドも変えていない。
ただし FA は浅い文脈では on が良い(15.22 → 17.51)。**用途で切り替える**:
- 対話・長文生成(深い文脈) → `-fa off`
- 短い入力で一発生成 → `-fa on`
### 38-B. 効かなかったもの(すべて実測で否定)
```
案 予測 実測 なぜ外れたか
KVキャッシュ量子化 効くはず +10%/pp -65% 復号コストが加わる。差し引き損
BLAS を回避(-ub 小) 速いはず 逆(-13%) 既定の 512 が最速
--no-mmap 速いはず 逆(-22%) MoE は未使用の専門家を追い出せる mmap が有利
mlock 安定するはず 差なし・pp不安定 11.26GB の固定が逆に圧迫
GPU(Metal)にオフロード 速いはず -95% 後述
```
> 共通点: どれも「計算やデータ変換を足して読み込みを減らす」種類の工夫。
> 帯域律速の機械では全部負ける。効いたのは「読み込みを並列化する」スレッド数だけ。
### 38-C. ★ GPU は使ってはいけない(引き継ぎ書の前提は正しかった)
このMacには **Intel UHD 630(VRAM 1.5GB動的) と AMD Radeon Pro 5300M(VRAM 4GB)** があり、
両方 Metal 3 対応。「GPU 無し」という前提は誤りだった。しかし:
```
GPUに載せる層数 pp512 tg32
0(使わない) 27.68 17.42
8 18.11 8.35
16 12.38 1.37
48(全部) 6.48 0.86 ← 生成が 20分の1
```
VRAM 4GB に対しモデル 11.26GB。Intel Mac は統合メモリではないので毎トークン PCIe 転送が起きる。
さらに **Metal を組み込むだけで ngl=0 でも遅くなる**(pp 42.60 → 27.68)。
→ **CPU 専用ビルド(既存の build/)が正解**。Metal 版 build-metal/ は使わない。
### 38-D. ik_llama.cpp — プロンプト処理は本物、生成は誤差
3B(1.84GB)で測定(MoE 30B はメモリぎりぎりで ±25% のノイズが乗るため)。`-r 5`。
```
構成 pp512 tg64
本家 CPU 86.08 ± 2.77 14.71 ± 0.05
ik CPU 107.40 ± 4.56 15.32 ± 0.15 ← pp 1.27倍
ik CPU + rtr 108.98 ± 5.37 15.18 ± 0.12 ← rtr は効果なし
ik Metal 107.25 ± 7.56 15.26 ± 0.12 ← Metal は無関係
本家 CPU(対照) 83.16 ± 1.53 14.34 ± 0.64
```
- **プロンプト 1.27倍**は本物(計算律速なのでカーネルの差が出る)
- **生成は +5%(誤差)**。帯域律速なので誰が書いても同じ
- **ik の "Metal" 表示は飾り**。CPU 専用ビルドと同じ速度=実際は CPU カーネルで計算している
- MoE 30B で ik が tg 21.32 と出たのは**測定ノイズ**(本家 CPU 18.17)。3B の +5% と矛盾する
**品質は完全に同一**: 同条件の PPL が 本家 6.1500 / ik 6.1485(差 0.024%、誤差の1/100)。
### 38-E. 測定の落とし穴(今後の必須事項)
MoE 30B は 11.26GB で、使えるメモリ 11〜12GB をほぼ使い切る。
**直前に何を読んだかで同じ設定が ±25% ぶれる**(mmap の tg が 15.32 と 19.19)。
→ 速度を測っているつもりでページキャッシュの空き具合を測ってしまう。
→ **カーネルや設定の比較は 3B(1.84GB) で行う**。30B は最終確認だけに使う。
→ `-rtr` のようにロード時の処理が入るものは、さらにノイズが増える(±26.09 を観測)。
### 38-F. 使い分けの結論
```
生成が主(対話・長文出力) → 本家 llama.cpp / -t 10 / -fa off
入力が主(コード補完・要約) → ik_llama.cpp / -t 10
```
MoE 30B の Q3_K_M でプロンプトが 32.8 t/s と遅い問題は、ik に替えるだけで 41 t/s 前後の見込み。
---
## 実験39 — i-quants は ik_llama.cpp でも速くならない(生成は)/記憶階層の実測
2026-09-04。目的: GSQ-RCO の 27B が全部 i-quant なので、
「ik なら i-quant の復号ペナルティが消えるか」を確かめる。消えれば選択肢が一気に増える。
### 39-A. 落とし穴 — ik は既定で Metal を掴む
最初の測定で ik だけ `backend: Metal / ngl 999` になっていた。CPU カーネルの比較になっていない。
**`-ngl 0` を明示して測り直した。数字を出す前に backend 欄を見ること。**
また、裏でダウンロードを走らせたまま測り始めたので一度中止した(測定が汚れる)。
`tools/ops/iq_kernel2.sh` は単独実行が前提。
### 39-B. 結果(3B・スレッド10・-ngl 0・r 3)
| 形式 | 実装 | pp512 | tg128 | 生成の実効帯域 |
|---|---|---:|---:|---:|
| IQ2_XS (978 MiB) | mainline | 42.17 | 14.75 | 15.1 GB/s |
| IQ2_XS (978 MiB) | ik | **101.41** | 14.82 | 15.2 GB/s |
| Q2_K (1.18 GiB) | mainline | 40.04 | 19.43 | 24.6 GB/s |
| Q2_K (1.18 GiB) | ik | **96.58** | **22.69** | **28.8 GB/s** |
### 39-C. 分かったこと
1. **ik のプロンプト処理は 2.41倍**(両形式で一致: 2.405 / 2.412)。
過去に測った 1.27〜1.81倍より大きい。スレッド10・-ngl 0 の条件で再現。
2. **ik の生成は k-quant で +17%、i-quant で ±0%。**
3. **i-quant の復号ペナルティは ik でも消えない。** 15.2 GB/s は帯域 28 の 54%。
→ **GSQ-RCO の 27B(IQ2/IQ3 のみ)は、実装を替えても速くならない。**
4. **ik + Q2_K は 28.8 GB/s。実測メモリ帯域 28 GB/s に到達している。**
これ以上は物理的に出ない。k-quant + ik が、この機械の生成の上限。
### 39-D. 記憶階層の実測
| 対象 | 実測 |
|---|---|
| メモリ | 28 GB/s |
| 内蔵SSD(冷たい読み出し・11.26GB) | **3.27 GB/s**(空き25GB) |
| 外付けSSD 読み / 書き | **41 / 38 MB/s**(空き113GB) |
外付けは内蔵の80分の1。**SSDストリーミング(PR #25294 / TinyGiant)は内蔵でしか成立しない。**
### 39-E. 速度式の訂正
「t/s ≒ 28 ÷ ファイルサイズ」は密モデル限定。MoE は活性分しか読まない。
```
密 : t/s ≒ 28 ÷ ファイルサイズ(GB)
MoE : t/s ≒ 28 ÷ (活性パラメータ数 × bpw ÷ 8)
```
検算: Ling-3.0-tiny 予測35.3/実測35.0、Qwen3-30B-A3B 予測23.0/実測19.8。
### 39-F. 測定のばらつきについて
mainline IQ2_XS の tg128 は 1回目 13.00±1.60、2回目 14.75±0.06。
mainline Q2_K は 1回目 21.49±0.54、2回目 19.43±1.76。
**±の値が大きい回は信用しない。** 上の表は ± が小さい方を採った。
---
## 実験40 — MoE 推論中のメモリ圧迫は「限界だが、まだ落ちてはいない」
2026-09-04。kimi-k3 の指摘「11.26GB を実質11〜12GB に載せている時点で、
macOS のメモリ圧縮やスワップが発動しているのでは」を確かめた。
`tools/ops/swap_check.sh`(推論中に5秒おきに `vm_stat`)。
### 結果(Qwen3-30B-A3B Q2_K / ik / -ngl 0 / スレッド10)
| 指標 | 推論前 | 推論中(最大) | 差 |
|---|---:|---:|---:|
| Swapouts | 14,360,348 | 14,360,348 | **±0** |
| Swapins | 12,977,859 | 12,977,923 | +64 (256KB) |
| 圧縮に使われたページ | 162,241 | 264,878 | **+102,637(約401MB)** |
| 空きページ | 70,213 | **3,680** | −66,533(約260MB→14MB) |
同時に測れた速度: **pp512 64.30 ± 19.76 / tg128 20.20 ± 0.12**
### 判定
1. **ディスクへのスワップは1回も起きていない。** kimi の仮説は否定。
予測23.0 対 実測19.8〜20.2 の差は、スワップではなく通常のオーバーヘッド
(アテンション計算・KVキャッシュ・ルータ)。
2. **実効帯域は 1.219GB × 20.20 = 24.6 GB/s。実測メモリ帯域 28 の 88%。**
MoE でもほぼ帯域を使い切っている。
3. **ただし空きページは 3,680(約14MB)まで落ちる。** 余裕はゼロ。
メモリ圧縮が401MB増えている=OSが他のプロセスを圧縮して場所を作っている。
**これ以上大きいモデルか、長い文脈を足すと、本物のスワップに落ちる。**
### 実験39/40 を踏まえた、MoE 速度式の精度
```
MoE : t/s ≒ 0.86 × 28 ÷ (活性パラメータ数 × bpw ÷ 8) ← 0.86 は実測の補正係数
```
- Qwen3-30B-A3B Q2_K: 23.0 × 0.86 = 19.8(実測 19.8〜20.2)
- Ling-3.0-tiny Q4_K_M: 35.3 × 0.99 = 35.0(実測 35.0)
→ 小さいモデルほど補正が要らない。余裕が無いほど落ちる。上の「空きページ14MB」と符合。
### 訂正 — 専門家枝刈りは生成速度を上げない
REAP は専門家を削除するが、**ルータは相変わらず上位8個を選ぶので活性パラメータは減らない**。
減るのは総パラメータだけ。したがって:
| | サイズ | bpw | 活性バイト/token | 見込み |
|---|---:|---:|---:|---:|
| Qwen3-30B-A3B Q2_K(現行) | 11.26 GB | 2.95 | 1.22 GB | 19.8(実測) |
| REAP-25B-A3B Q2_K | 8.95 GB | 2.86 | 1.18 GB | 約20 |
| REAP-25B-A3B **Q3_K_M** | 11.51 GB | 3.68 | 1.52 GB | 約16 |
**枝刈りが買うのは速度ではなく、余白と品質。**
本命は `REAP-25B-A3B-Q3_K_M`(11.51GB)。いまの Q2_K の30B(11.26GB)とほぼ同じ大きさで、
**Q2_K → Q3_K_M へ量子化の質を上げられる。**枝刈り25%の代償は1ポイント未満。
---
## 2026-09-04 夜 — 実験41: GSQ の答え合わせ — 「符号化器だけ差し替える」は PPL では効かなかった
引き継ぎ書 §8-(2)。GSQ(arXiv:2604.18556 / IST-DASLab)は、自分の研究方針
「復号器は書かず、符号化器だけ自前にする」とほぼ同じ設計で、先に論文化されていた。
本当に効くのかを自分の手で確かめた。
### 41-A. 対照の特定 — 相手が出発点を明記していた
`ISTA-DASLab/Qwen3-8B-GGUF-GSQ` の README に
「**Unsloth GGUF init → GSQ refinement → 同じK-Quant形式に再パック**」と書いてある。
つまり正しい対照は `unsloth/Qwen3-8B-GGUF` の `Qwen3-8B-Q2_K.gguf`。
| ファイル | バイト数 |
|---|---:|
| unsloth 素の Q2_K | 3,281,733,440 |
| GSQ 改良版 | 3,281,732,960 |
差は480バイト(メタデータのみ)。テンソル構成も一致(Q2_K 145 / Q3_K 72 / Q4_K 36 / Q6_K 1 / F32 145)。
**中身の値だけが違う、理想的な対照実験になっている。**
### 41-B. 結果 — 改善しない。日本語では8%悪化する
汎用 `corpus_test.txt` 20chunk / 個人 `jibun/test.txt` 24chunk / -c 512 / -t 6。
| テスト | unsloth 素 Q2_K | GSQ 改良版 | 差 |
|---|---:|---:|---:|
| 汎用PPL | **6.6741** ± 0.276 | 6.8765 ± 0.291 | +3.0%(悪化) |
| 個人PPL | **13.4367** ± 0.539 | 14.5085 ± 0.596 | **+8.0%(悪化)** |
### 41-C. 判定
1. **GSQ は PPL を改善しない。二つの独立な文章で向きが一致して悪化した。**
汎用の差は誤差に埋もれるが、個人(日本語)の +8.0% は誤差の約2倍あり、
かつ汎用と同じ向き。偶然で両方が同じ向きに出る確率は低い。
2. 矛盾ではない。**GSQ が上げたのはベンチマークの平均点(50.03→56.28)であって PPL ではない。**
相手も PPL は一度も主張していない。指標が違う。
3. **この結果は個人較正の思想を裏側から裏付けている。**
GSQ は「誰かの較正データ」に向けて離散格子を再学習する。
その結果、較正から遠い文章(=こちらの日本語)では素の Q2_K より悪くなった。
→ **較正の当て先がずれると、同じビット数でも品質は落ちる。**
これは「個人較正が効く」という主張の対偶にあたる。
### 41-D. 研究方針への影響
- 「符号化器だけ差し替える」という着眼そのものは有効(GSQ は実際に動き、形式互換も本当だった)。
- ただし**他人の較正で最適化された成果物をそのまま拾ってきても、こちらの用途では損をする**。
拾ってくるなら較正データごと自分で回す必要がある。
- → 引き継ぎ書 §8-(4)「REAP を自分で回す」の価値が上がった。**当て先を自分にできるのが唯一の優位。**
### 成果物
`data/ckpt/gsq_ppl.txt` / `~/LocalAI_mirror/models/qwen3-8b-Q2_K-unsloth.gguf`(対照・3.28GB)
---
## 2026-09-04 夜 — 実験42: REAP-25B-A3B Q3_K_M — 本命は外れた。理由は枝刈りではなく「素性」
引き継ぎ書 §8-(1)。「同じ 11.5GB で Q2_K → Q3_K_M へ質を一段上げられる」という読みを検証した。
`bartowski/cerebras_Qwen3-Coder-REAP-25B-A3B-GGUF` の Q3_K_M(11,508,613,728 バイト)を取得。
### 42-A. 構造 — 引き継ぎ書の訂正が裏付けられた
| | 現行 Qwen3-30B-A3B | REAP-25B-A3B |
|---|---:|---:|
| 専門家の数 | 128 | **103**(−19.5%) |
| 毎トークン使う専門家 | 8 | **8(同じ)** |
| 層数 / 専門家FFN幅 | 48 / 768 | 48 / 768(同じ) |
| ファイル | 10.48 GiB / 30.53B / 2.950 bpw | 10.71 GiB / 24.87B / **3.702 bpw** |
| 文脈長 | 40,960 | 262,144 |
**`expert_used_count` が両方 8。活性パラメータは1つも減らない**(実験40の訂正が正しかった)。
なお削減率は 19.5% で、引き継ぎ書の「25%」より穏やか。
### 42-B. 速度 — 予測式がまた当たった(-t 10 / -ngl 0 / r 3・単独実行)
| モデル | 実装 | pp512 | tg128 |
|---|---|---:|---:|
| REAP-25B Q3_K_M | ik | 68.68 ± 18.80 | **16.20 ± 0.11** |
| REAP-25B Q3_K_M | 本家 | 39.95 ± 1.07 | 14.87 ± 0.36 |
| 現行 30B Q2_K | ik | 74.89 ± 8.28 | **19.70 ± 0.19** |
| 現行 30B Q2_K | 本家 | 43.31 ± 1.08 | 18.23 ± 0.27 |
```
予測 t/s = 0.86 × 28 ÷ (3.3B × bpw ÷ 8)
REAP-25B Q3_K_M : 15.77 予測 → 16.20 実測(+2.7%)
現行 30B Q2_K : 19.79 予測 → 19.70 実測(−0.5%)
```
**MoE の速度式は bpw を変えても成り立つ。もう疑わなくてよい。**
ik の生成優位は MoE 30B級では +9%(3B での +17% より小さい)。pp512 の ± が大きい回は信用しない。
### 42-C. 品質 — 逆に大きく落ちた(同じ Qwen3 系=トークナイザ同一なので厳密に比較できる)
| | 汎用PPL (20ch) | 個人PPL (24ch) |
|---|---:|---:|
| 現行 30B **Q2_K** | **5.7328** ± 0.232 | **12.0972** ± 0.489 |
| REAP-25B **Q3_K_M** | 7.1517 ± 0.302 | **33.3839** ± 1.393 |
| 差 | **+24.8%(悪化)** | **+176%(悪化)** |
※ 現行30Bの2値は実験37と**完全一致**(5.7328 / 12.0972)。測定系は再現している。
### 42-D. 判定 — 置き換えにならない。ただし外したのは枝刈りのせいではない
1. **REAP-25B Q3_K_M は現行の上位互換ではない。速度 −17.8%、汎用 +24.8%悪化、日本語 +176%悪化。**
2. 原因は枝刈りではなく **モデルの素性**。この大きさで手に入る REAP-25B は
**Qwen3-**Coder**-REAP-25B-A3B**、つまりコード特化モデルしかない。
日本語 PPL が3倍近く悪いのは、19.5%の枝刈りでは説明できない
(REAP 論文の主張は「25%削減で1ポイント以内」であり、この差は桁が違う)。
3. **★ 教訓: 量子化の階級を1段上げて買える品質より、モデルの素性で失う品質のほうがはるかに大きい。**
引き継ぎ書の §6 の表は「サイズが同じ」しか見ていなかった。
**サイズ表だけで本命を決めてはいけない。**
4. 枝刈り自体の是非はこの実験では判定できない(Coder化と交絡しているため)。
切り分けるには枝刈り無しの Qwen3-Coder-30B-A3B Q3_K_M が要る。
### 42-E. 結論
**主力は Qwen3-30B-A3B Q2_K(11.26GB / 19.7〜20.2 t/s)のまま据え置く。**
REAP-25B はコード用途で勝つ可能性が残っているだけ(→ HumanEval が要る)。
### 成果物
`data/ckpt/reap_bench.txt` / `data/ckpt/reap_ppl.txt` /
`~/LocalAI_mirror/models/cerebras-REAP-25B-Q3_K_M.gguf`(10.71GiB)
---
## 2026-09-04 夜 — 実験43: ★ 生成を実際に読んだ — 個人PPLは「同じトークナイザの中でだけ」信用できる
引き継ぎ書 §8-(3)、最大の穴。「Ling が日本語で賢いのか、細かく刻んでいるだけなのか」を、
数字ではなく**中身を読んで**決着させた。
### 43-A. やり方(安全装置つき)
`tools/ops/gen_read.sh`(新規)— llama-cli が対話モードに落ちて 5.38GB 書いた事故の再発防止に三重の蓋:
1. `</dev/null` … 対話に入っても即EOFで終わる / 2. `ulimit -f 20480` … 20MBで打ち切り /
3. killループ … 秒数で強制終了。さらに `-st --simple-io --no-display-prompt` を付ける。
※ このビルドの llama-cli は内部でポートを開くので、サンドボックス下では動かない。
`tools/ops/read_gen_all.sh`(新規)— 同じ6問を各モデルに解かせる。n=700・-t 10・-c 4096・temp 0.7・seed 42。
狙いは「日本語の知識と推論」で、PPLが測れないものを見るため:
Q1 文法(様態〜そうだ/伝聞〜そうだ)、Q2 自分の研究分野、Q3 MoEの速度、
Q4 しりとり(かなの構造)、Q5 Python、Q6 五十歩百歩(漢籍の知識)。
### 43-B. 結果
| 問い | Ling-3.0-tiny Q4_K_M (4.82GB) | Qwen3-30B-A3B Q2_K (11.26GB) | REAP-25B Q3_K_M (11.51GB) |
|---|---|---|---|
| Q1 文法 | ✗ 700tok使い結論なし。「降り」を*て形*と誤認 | ✗ 誤結論だが筋は通る(伝聞を知らない) | ✗✗ 崩壊。「降**l**そうだ」「過過去形」「珍現形」 |
| Q2 帯域 | ✗ *文脈*を読むから、とKVの話にすり替え | △ thinkingで時間切れ | ✗✗ 質問をオウム返し。「メモリ帯**ド**幅」 |
| Q3 MoE | ✗ 「理論的にありえない」→「メモリ不足」自己矛盾 | **○ 正答。日本語で完結** | △ 英語混在。「14Bは30Bより larger」 |
| Q4 しりとり | ✗✗ **ルールを知らない**。「母音を揃える」と創作し「りんごは*ん*で終わる」 | **○ 正解+一意性まで検証** | △ 順序は合うが「ら**らっぱ**」に化ける |
| Q5 Python | ○ Counter で正解 | ○ 正解 | **○ 正解。最も簡潔** |
| Q6 五十歩百歩 | ✗✗ 「無駄・非効率」と誤答。由来を**「古代日本の故事」と捏造** | **○ 意味は正解**(由来は曖昧) | ✗✗ 同じ行の**無限ループ** |
### 43-C. 判定1 — Ling の低い個人PPL は見かけだった(引き継ぎ書の懸念は当たっていた)
```
個人PPL しりとり 五十歩百歩 総合
Ling-3.0-tiny 10.6962(良) ✗✗ ✗✗ 日本語の知識が無い
Qwen3-30B 12.0972(悪) ○ ○ 明確に賢い
```
**PPL の順位と、読んだときの賢さの順位が完全に逆転した。**
Ling が 1.4 ポイント「良い」のは、日本語を細かく刻んでいるだけ
(1トークンあたりの情報量が少ないので当てやすい)。
→ **モデルファミリーをまたぐ PPL 比較は、順位を逆に出す。今後は禁止。**
### 43-D. 判定2 — 同じトークナイザの中では、個人PPLは恐ろしいほど正確だった
同じ Qwen3 系(30B 対 REAP-25B)では、PPL の予告どおりのことが起きた。
```
個人PPL 読んだ結果
Qwen3-30B Q2_K 12.0972 日本語が通る
REAP-25B Q3_K_M 33.3839 文字が化け、造語が出て、無限ループする
```
**個人PPL が2.76倍になると、日本語は読める形で壊れる。** 数字と中身が完全に一致した。
### 43-E. ★ 得られた規則(今後の測定方針)
```
個人PPL は「トークナイザが同じモデル同士」でだけ信用してよい。
同じ → 品質の予告として極めて正確(実験43-D)
違う → 順位が逆転する。生成を読まないと分からない(実験43-C)
```
実験36以降ずっと PPL で判断してきたが、その比較の大半は同一トークナイザ内なので**有効**。
無効だったのは Ling 対 Qwen3 の1件だけ。ここだけ結論を取り消す。
### 43-F. 副産物 — 実使用の速度は llama-bench より落ちる(要注意)
llama-cli(chatテンプレート・-c 4096)で観測した生成 t/s。**単独実行ではないので測定値ではない**が、傾向は出た。
| | llama-bench (tg128) | llama-cli 実使用 |
|---|---:|---:|
| Qwen3-30B Q2_K | 19.70 | 16.1〜16.5 |
| REAP-25B Q3_K_M | 16.20 | **13.0 → 11.5 → 8.5 → 7.5**(回を追って低下) |
REAP は 11.51GB とわずかに大きいだけで、連続使用すると速度が半分以下まで落ちていく。
実験40の「空きページ 14MB」の壁の、実使用での現れ方だと思われる。
→ **11.26GB が本当に上限。ここを超えると benchmark の数字は当てにならなくなる。**
### 43-G. 結論
1. **主力は Qwen3-30B-A3B Q2_K のまま。** 数字でも、読んだ中身でも、これが最良。
2. Ling-3.0-tiny は日本語の相棒にはならない(コードだけは書ける)。
3. REAP-25B(Coder) は日本語では使えない。コード専用として見るなら Q5 は最も簡潔だった。
4. **PPLだけで判断するのをやめられた。** これが今日いちばん大きい。
### 成果物
`tools/ops/gen_read.sh` / `tools/ops/read_gen_all.sh` / `/tmp/claude-503/gen/{ling,qwen30b,reap25b}_0*.txt`
---
## 2026-09-04 夜 — 実験44: 「外付けSSD」の正体 — USB 2.0 でつながった HDD だった
内蔵の空きが 13GB(97%)まで落ちたので退避先を整理した際、記憶階層を測り直した。
**前提が2つ間違っていた。**
### 44-A. 正体
```
diskutil info disk2:
Device / Media Name : My Passport 259F ← Western Digital のポータブル**HDD**
Protocol : USB
Disk Size : 3.0 TB
Solid State : Info not available ← SSD なら "Yes" と出る
ioreg -rc IOUSBHostDevice:
"USB Product Name" = "My Passport 259F"
"Device Speed" = 2 ← **480 Mb/s = USB 2.0**
(同じMacの "USB 10_100_1000 LAN" は Device Speed = 3 = 5 Gb/s)
```
→ **SSD ではなく HDD。しかも USB 2.0 でつながっている。**
同じ機械の別のUSB機器が 5Gb/s でリンクしているので、**Mac側のポートはUSB3.0対応**。
遅いのはディスクでもMacでもなく、**ケーブル/アダプタ**。
### 44-B. 実測(単独実行・dd 1GB)
| 対象 | 実測 |
|---|---:|
| 外付け exFAT 書き(bs=1m) | 39.1 MB/s |
| 外付け exFAT 書き(bs=64m) | 35.1 MB/s |
| 外付け exFAT 読み(bs=8m) | 42.0 MB/s |
| 内蔵 APFS 読み(bs=8m) | **3,155 MB/s** |
**ブロックサイズを64倍にしても速くならない。** exFAT の実装(macOS の FSKit)でも
HDD の機械性能でもなく、**USB 2.0 の実用上限 40MB/s に張り付いている**。
### 44-C. 判定
1. **USB 3.0 のケーブルに替えれば約3倍(100〜120 MB/s)が見込める。**
My Passport の HDD の機械的限界がそこ。19GB の退避が 9.5分 → 3分になる。
2. **ただし3倍になっても `--moe-stream` は外付けでは永久に不可能。**
HDD なのでランダム読みが桁違いに遅い。専門家スラブの飛び読みは成立しない。
→ 引き継ぎ書 §7 の「外付けからのストリーミングはやらない」は、
理由が「41MB/s だから」ではなく「**回転する円盤だから**」に変わった。より強い理由。
3. 同じ物理ディスクの APFS 領域(`/Volumes/Backup` 932GB)は **Time Machine の保存先**。
研究データは置かない。使えるのは exFAT 側(1.7TB空き)だけ。
### 44-D. 運用の決め(本人の方針: 保管は外付けに寄せる)
```
外付け exFAT (1.7TB) … 退避・原本・測り終えたGGUF・実験の記録。既定の置き場
内蔵 APFS (43GB空) … **いま推論に使う重みだけ**。ここは3.1GB/sで75倍速い
```
`tools/ops/evacuate.sh`(新規)で退避する。コピー→バイト数照合→一致したときだけ元を消す。
今日の退避で内蔵は **13GB → 43GB** に回復した。
退避したもの: `data/models/archive/`
Ling-3.0-tiny-Q8_0 (8.41GB) / qwen2.5-14b Q3_K_M 2分割 (7.34GB) /
qwen3-8b-Q2_K-unsloth (3.28GB) / qwen3b-fp16 (6.80GB) / Ling-3.0-tiny-Q4_K_M (4.82GB)
### 44-E. ★ ケーブルを替えたら 2.5倍になった(仮説が当たった)
USB-Cハブ(ダイソー製)経由につなぎ替えたところ、`Device Speed` が **2 → 3**、
つまり **USB 2.0 (480 Mb/s) → USB 3.0 (5 Gb/s)** にリンクが上がった。
| | 交換前 USB2.0 | 交換後 USB3.0 | 倍率 |
|---|---:|---:|---:|
| 書き込み(dd 2GB, bs=1m) | 39.1 MB/s | **98.5 MB/s** | 2.52× |
| 読み出し(dd 2GB, bs=8m) | 42.0 MB/s | **101.6 MB/s** | 2.42× |
| 実物のGGUF読み(4.82GB) | — | **101.1 MB/s** | — |
**予測(100〜120 MB/s)どおりに着地した。**
USB 3.0 の帯域は 625 MB/s あるので、いまの上限は **HDD の機械的限界**。
これ以上はケーブルでは上がらない(上げるなら外付けSSDを買うしかない)。
### 44-F. 記憶階層(最終版)
| 対象 | 実測 | 内蔵比 |
|---|---:|---:|
| メモリ | 28 GB/s | 9倍 |
| 内蔵SSD 読み | 3,155 MB/s | 1 |
| **外付けHDD 読み(USB3.0)** | **101 MB/s** | 1/31 |
| 外付けHDD 読み(USB2.0・旧) | 42 MB/s | 1/75 |
- 11.26GB のモデルを外付けから読むと **111秒**(内蔵は3.5秒)。**推論用の重みは内蔵に置く**。
- 退避・保管・ダウンロード先は外付けでよい(回線が約15MB/sなので外付けの100MB/sは余る)。
- **`--moe-stream` は依然として不可能。** 帯域ではなく、HDDのランダム読みが理由。
### 44-G. 教訓
**「遅い」を機械の性能だと思い込まないこと。**
41MB/s という数字を実験39で測って以来、それを前提に
「外付けからのストリーミングは不可能」「退避は高くつく」と設計してきた。
実際には**ケーブル1本の問題**で、2.5倍はただで手に入った。
数字を測ったら、**その数字がどの層で決まっているのか**(媒体か・接続か・ファイルシステムか)
まで割ること。今回は `ioreg` の `Device Speed` 一発で分かった。
---
## 2026-09-04 深夜 — 実験45: ★ HumanEval を初めて回した — REAP は生きていた(コード専用として)
引き継ぎ書 §8-(6)。Coderモデルを扱っておきながら一度も回していなかった。
実験42で REAP-25B を「置き換えにならない」と判定したが、**コードで勝つ可能性だけが残っていた**。
### 45-A. やり方
- `llama-server` を1回だけ立てて 164問を投げる(`-np 4 -cb -c 4096 -t 10`)。
モデル読み込みが1回で済むので、llama-cli を164回叩くより約20分速い。
※ `llama-server` のバイナリは無かったので `cmake --build build --target llama-server` で作った。
- 貪欲デコード(temp 0.0)・pass@1・1サンプル。
- **Qwen3-30B は thinking を切って測った**(`/no_think`)。理由は時間:
thinking を許すと1問あたり700トークン以上を思考に使い、164問で2時間を超える。
REAP(Coder) は元々 thinking を持たないので、**両者とも「直接コードを書く」条件で揃えた**。
- 採点は各問を別プロセス+10秒タイムアウトで実行。
補助関数を先に定義する問題(HumanEval/50 等)のために、問題文を残して空実装で閉じ、
そのあとモデルの定義で上書きさせる。**両モデルに同じ処理を当てている。**
- 道具: `tools/ops/run_he.sh` / `tools/ops/humaneval/he_gen.py` / `he_eval.py`
### 45-B. 結果
| | サイズ | HumanEval pass@1 | 1問あたり |
|---|---:|---:|---:|
| Qwen3-30B-A3B **Q2_K** | 11.26GB | 142/164 = **86.6%** | 9.9秒 |
| REAP-25B(Coder) **Q3_K_M** | 11.51GB | **149/164 = 90.9%** | 12.2秒 |
空の返答は両方0件。落ちた原因はほとんどが AssertionError(論理の誤り)で、
構文エラーは REAP の1件(HumanEval/32 IndentationError)のみ。
### 45-C. 判定
1. **★ 2ビット級に圧縮した 30B MoE が HumanEval 86.6%。**
無圧縮の Qwen3-30B-A3B の公表値とほぼ同じ水準。
**Q2_K 量子化はコード能力をほとんど削っていない。** 11.26GB でこれが出るのは大きい。
2. **REAP-25B は本当にコードで勝った(+4.3ポイント / +7問)。**
実験42で「置き換えにならない」としたが、**消してはいけない**。
コード特化として、日本語能力を全部差し出した代わりに +4.3 を取っている。
3. **★ PPL はコード能力を予告しなかった。**
REAP は汎用PPLが 24.8% 悪いのに、HumanEval では 4.3ポイント勝つ。
実験43-E の規則をさらに絞る必要がある:
```
同じトークナイザ × 同じ領域 → PPL は正確(実験43-D: 日本語PPLが日本語崩壊を予告した)
同じトークナイザ × 違う領域 → PPL は当てにならない(今回: 日本語PPLはコード能力を予告しない)
違うトークナイザ → 順位が逆転する(実験43-C)
```
**PPL は「測った文章と同じ種類の文章での強さ」しか語らない。** 当たり前のことだが、
実験36以降ずっと PPL を総合力の代理として使ってきたので、ここで明示的に線を引く。
### 45-D. 運用の結論 — 二本立てにする
内蔵SSDが 43GB 空いた(実験44)ので、**両方を内蔵に置ける**。
モデルの読み込みは内蔵から 3.5秒なので、**用途で持ち替えるのが最良**。
```
日本語・調べもの・文章 → Qwen3-30B-A3B Q2_K (11.26GB / 19.7 t/s / 個人PPL 12.10)
英語プロンプトでのコード → REAP-25B Q3_K_M (11.51GB / 16.2 t/s / HumanEval 90.9%)
```
※ REAP に日本語で頼んではいけない(実験43-B: 文字化け・造語・無限ループ)。
### 成果物
`data/ckpt/he/{qwen30b,reap25b}.jsonl`(生成164問×2)と `*_detail.jsonl`(問題ごとの合否)
---
## 2026-09-04 深夜 — 実験46: ★★ 自前REAPの採点器を作った(llama.cpp 無改造)— 個人較正で採点した
引き継ぎ書 §7-(1)、研究の本体。実験41(GSQ)で
「他人の較正で最適化された成果物は自分の用途では損」と分かったので、
**当て先を自分にできること**が唯一の優位になった。その第一歩。
### 46-A. 設計 — 本体を触らずに済むことが分かった
`cb_eval`(ggml のノード評価コールバック)から、REAP の採点に必要な3つが全部取れる。
| テンソル | 型・形 | 中身 |
|---|---|---|
| `ffn_moe_topk-<層>` | i32 {8, n_tok} | 選ばれた専門家のID |
| `ffn_moe_weights_norm-<層>` | f32 {8, n_tok} | 正規化後のゲート値 |
| `ffn_moe_down-<層>` | f32 {2048, 8, n_tok} | **専門家ごとの出力ベクトル** |
```
S[層][専門家] = Σ_t g_norm(t) × ‖down(t)‖₂ ← REAP (arXiv:2510.13999) の saliency そのもの
```
**GPU も PyTorch も元の safetensors も要らない。** 量子化済みGGUFに直接当てられる。
先行事例(Kimi-K3-REAP55-GGUF の作者)が「llama.cpp のグラフをフックした」と書いていたのは、これ。
成果物: `tools/llama.cpp/examples/reap-score/reap-score.cpp`(新規・約200行)
実行: `REAP_CHUNKS=100 llama-reap-score -m <gguf> -f <較正文> -c 256 -t 10 -ngl 0`
### 46-B. 実装で踏んだ地雷(次に書く人へ)
1. **`ask` フェーズで false を返すとデータが古くなる。**
欲しい3つだけ true を返したら、48層のうち1層しか取れなかった。
`llama-debug` と同じく**常に true** を返すこと(スケジューラがノード単位で計算を区切る)。
2. **同じ名前で reshape されたノードが流れてくる。**
`ffn_moe_weights_norm-<層>` は48層なのに96回来る。ne[0] が 8 でなく 1 の方が reshape。
**形を検査して弾かないと、小さい方で上書きされて全部無効になる。**
3. **最終層は出力が要るトークン分しか計算されない**(llama.cpp の最適化)。
`llama_batch_get_one` だと層47だけ標本が 1/10 になる。
全トークンに logits を要求して揃えた(-c 256 で logits 用に約155MB。-c 512 は約311MB でメモリが厳しい)。
4. `--chunks` は perplexity 専用の引数。共通例では使えないので `REAP_CHUNKS` 環境変数にした。
検算: 10トークンの試験で 加算 = 3840 = 10 × 8 × 48。全48層とも 80 で一致。
本番でも 9,830,400 = 25600 × 8 × 48 と完全一致した。
### 46-C. 個人較正データでの採点結果(Qwen3-30B-A3B Q2_K / `jibun/train.txt` 25,600トークン)
| 指標 | 値 |
|---|---|
| 一度も選ばれなかった専門家 | **32個 / 6144(0.5%)** |
| 下位25%の専門家が担う経路の重み | **0.83%**(層による最小 0.02% / 最大 4.58%) |
| 下位50%が担う重み | 5.42% |
| 選ばれた回数の中央値 | 741(一様なら1600) |
| 最も使われた専門家 | **22,880回 / 25,600トークン(89%)** |
**★ 層によって偏りがまるで違う(ここが今日いちばんの発見)**
```
削りやすい層(下位25%が担う割合): 47(0.0%) 46(0.0%) 45(0.1%) 42(0.1%) 44(0.1%) 43(0.2%)
削りにくい層 : 7(1.3%) 6(1.5%) 5(1.5%) 3(2.1%) 0(3.9%) 1(4.6%)
```
→ **後半の層は専門家がほとんど遊んでいる。前半の層は全員が働いている。**
Cerebras の REAP は全層を一律に削る。**層ごとに削る割合を変えれば、
同じ削減率でもっと品質を残せる可能性がある。** これは自分で回さないと出てこない話。
### 46-D. ここから何が言えるか
- 日本語の自分のデータでは、**128個の専門家のうち大半が遊んでいる**。
下位25%を削っても触るのは経路の 0.83%。REAP 論文の「25%削減で1ポイント以内」と整合する。
- 専門家はファイルの約95%を占めるので、**25%削ると 11.26GB → 約8.6GB**。
実験43-F で分かった「11.26GB の壁」に対して **2.6GB の余白**が生まれる。
- **枝刈りは速度を上げない**(活性パラメータは減らない)。買えるのは余白と品質。
余白があれば、常時使うアテンションだけ精度を上げるといった手が打てる。
### 46-E. まだやっていないこと(次の一手)
1. **実際に削る手術**。GGUF の専門家テンソルは `{2048, 768, 128}` の3次元で、
専門家ごとに連続かつブロック境界に載っている(2048 は Q2_K の 256 で割り切れる)。
**逆量子化せずにバイト単位で切り出せるはず。** ルータ `ffn_gate_inp.weight{2048,128}` の
行も同時に削り、メタデータ `expert_count` を書き換える。既存資産: `tools/gguf/drop_tensor.py`
2. 較正トークンを増やす(今回25,600。先行事例は264,141)。1回15分なので10倍で2.5時間。
3. 汎用データでも採点して、**個人較正との差**を見る。ここが研究の主張の核心。
4. 層ごとに削減率を変える方式(46-C の発見)と、一律方式の比較。
### 成果物
`tools/llama.cpp/examples/reap-score/`(採点器)/ `data/ckpt/reap/score_jibun_25600.tsv`(6144行)
---
## 2026-09-05 未明 — 実験47: ★★ 専門家を実際に削った — 個人PPLは守られ、コードは壊滅した
実験46で採点した結果を使って、GGUF から直接 専門家を切り出した。**研究の本体が一周した。**
### 47-A. 手術のやり方(`tools/gguf/prune_experts.py` 新規)
**メタデータを作り直さない。** ヘッダを丸ごとコピーして、固定長の数値だけを上書きする:
```
KV の qwen3moe.expert_count (u32) 128 → 96
専門家テンソルの ne[last] (u64) 128 → 96
各テンソルの data offset (u64) ずれた分を書き直す
```
トークナイザの15万語を再シリアライズしないので、書き戻しで壊す危険が消える。
専門家1個ぶんのバイト列は連続かつブロック境界に載っているので、**逆量子化なしで切り出せる**。
| テンソル | 型 | 形 | 1専門家あたり |
|---|---|---|---:|
| `ffn_gate_inp`(ルータ) | F32 | {2048, 128} | 8,192 B |
| `ffn_gate_exps` | Q2_K | {2048, 768, 128} | 516,096 B |
| `ffn_up_exps` | Q2_K | {2048, 768, 128} | 516,096 B |
| `ffn_down_exps` | Q3_K | {768, 2048, 128} | 675,840 B |
合計 1.716 MB/専門家/層 × 48層 = **82.4 MB/専門家**。
**踏んだ地雷**: `attn_k_norm` `attn_q_norm` の形が `[128]` で、**専門家数と偶然一致する**。
「最終次元が n_expert なら専門家テンソル」と判定すると、アテンションの正規化を壊す。
名前(`_exps.weight` / `ffn_gate_inp.weight`)で明示的に選ぶこと。
所要時間: **14秒**(11.26GB読み → 8.62GB書き)。
### 47-B. 結果 — 三つの数字がバラバラの方向を向いた
Qwen3-30B-A3B Q2_K を、個人較正(`jibun/train.txt` 25,600トークン)の採点で 128→96 に削減。
| | 元 | 枝刈り96 | 差 |
|---|---:|---:|---:|
| ファイル | 11.26 GB | **8.62 GB** | **−23.4%** |
| パラメータ | 30.53 B | 23.28 B | −23.8% |
| tg128 (ik) | 19.70 | **21.25** | **+7.9%(速くなった)** |
| pp512 (ik) | 74.89 | 88.16 | +17.7% |
| **個人PPL** | 12.0972 | **12.2002** | **+0.85%** |
| 汎用PPL | 5.7328 | 6.4026 | +11.7% |
| **HumanEval** | **86.6%** | **53.7%** | **−32.9ポイント** |
| しりとり | ○ 正解 | ✗ 解けなくなった | |
### 47-C. 判定1 — ★ 枝刈りは、この機械では速度を上げる(訂正の訂正)
実験40で「枝刈りは活性パラメータを減らさないので速くならない」と訂正した。**これは半分違った。**
活性パラメータは確かに変わらないが、**メモリ圧が下がって補正係数が改善する**。
```
t/s ≒ K × 28 ÷ (3.3B × 2.95 ÷ 8) 理論値 23.0
元 11.26GB : K = 0.86 → 19.70
枝刈 8.62GB: K = 0.92 → 21.25
```
実験40で Ling(余裕あり)が K=0.99、30B(空きページ14MB)が K=0.86 だったことと完全に符合する。
→ **16GBの機械では、枝刈りは「余白」を通じて速度にも効く。**
### 47-D. 判定2 — ★★ 個人PPLは守られた。しかし守ったのは較正データの領域だけだった
**個人PPL +0.85%。ほぼ無傷。** 23.4%も削って自分の文章の予測能力は落ちていない。
個人較正の主張はこの一点では正しかった。
**だが HumanEval は 86.6% → 53.7%。コード能力が半分死んだ。**
較正に使った `jibun/train.txt` は自分の日本語の文章が中心で、コードの比率が低い。
**コードを担っていた専門家は「使われていない」と採点され、まとめて捨てられた。**
実験46-C で「下位25%は経路の0.83%しか担っていない」と見えていたのは、
**その較正データの上でだけ**の話だった。
### 47-E. ★★★ これが実験45の規則の、最も強い実証になった
```
個人PPL は「較正データと同じ種類の文章」しか守らない。
→ 個人PPL +0.85% を見て「無傷だ」と判断していたら、コード能力の半減を見落としていた。
→ 枝刈りの安全性を PPL だけで判定してはいけない。必ず別領域のベンチで確かめる。
```
Cerebras の REAP が「25%削減で1ポイント以内」と言えるのは、**広い較正データを使っているから**。
狭い個人データで同じ率を削ると、較正の外側は壊滅する。
**「個人較正は効く」と「個人較正は危ない」は同じ現象の裏表だった。**
### 47-F. 次の手当て(優先順)
1. **混合較正** — 日本語+コード+英語を混ぜて採点し直す。採点は1回15分。最も直接的な手当て。
2. **領域ごとの上位の和集合を残す** — 日本語で採点した上位と、コードで採点した上位の**和集合**。
どの領域も飢えないことを設計で保証する。今夜の失敗への構造的な答え。
3. **削減率を下げる** — 128→112(12.5%削減)の測定を実施中。膝がどこにあるかを見る。
4. **削除ではなく合成(merging)** — 似た専門家を平均して1つにする。
Q2_Kブロックを逆量子化→平均→再量子化でよく、**実験36の「二重量子化の害は Q2_K ではほぼ無い」**が
効くので高精度の原本が要らない(RAMの壁を迂回できる)。同じサイズで情報を多く残せる可能性。
5. **ルータの補正** — 消した専門家に向かっていた経路重みが行き場を失う。生存側の再スケールを検討。
### 成果物
`tools/gguf/prune_experts.py` / `tools/ops/eval_reap96.sh` /
`~/LocalAI_mirror/models/Qwen3-30B-REAP96-jibun-Q2_K.gguf`(8.62GB)/
`data/ckpt/reap/reap96_{bench,ppl}.txt` / `data/ckpt/gen/reap96_0*.txt` / `data/ckpt/he/reap96.jsonl`
### 47-G. 追試 — 削減率を下げても高くつく(損傷は非線形)
同じ個人較正の採点で 128→112(12.5%削減)も作って測った。
| 専門家 | サイズ | HumanEval | 元からの差 |
|---:|---:|---:|---:|
| 128(元) | 11.26 GB | **86.6%** | — |
| 112(−12.5%) | 9.94 GB | 76.2% | −10.4pt |
| 96(−25%) | 8.62 GB | 53.7% | −32.9pt |
削減率を半分にしても損失は 1/3 にしかならない。**損傷は非線形に加速する。**
→ 「少し削れば安全」ではない。**較正データの広さの問題であって、削減率の問題ではない。**
§47-F の (1) 混合較正 と (2) 領域ごとの和集合 が、やはり本筋。
---
## 2026-09-05 夕 — 実験48: ★★★ 混合較正でもう一度削った — コード能力はほぼ全部戻った
引き継ぎ書 §7-(1)(混合較正)と §7-(2)(領域ごとの和集合)。**両方を1回の採点で解いた。**
### 48-A. 設計上の気づき — 領域別に採る方が、混ぜて採るより強い
採点値 `S[層][専門家] = Σ_t g_norm × ‖down‖₂` は **トークンについての和**である。
したがって
```
領域Aで採った S_A + 領域Bで採った S_B == A と B をつないだ較正文で1回採った S
```
が(文脈の継ぎ目を除いて)成り立つ。つまり **混合較正は領域別採点の特別な場合**で、
領域別に採っておけば「混合」も「和集合」も後からいくらでも作れる。
採点は1領域20分。ここを分けたことが、この回の全部の分析を可能にした。
### 48-B. 較正データ(評価と重ならないように作った)
| 領域 | 中身 | 素材 |
|---|---|---|
| `ja` | 本人の日本語(実験46と同じ) | `data/calib/jibun/train.txt` |
| `code` | ライブラリ系Python 37本 | llama.cpp の `gguf-py` ほか |
| `algo` | アルゴリズム系Python 45本 | Python標準ライブラリの純Python実装 |
| `en` | Wikipedia散文52節+llama.cpp技術文書40本 | wikitext-2 test を整形+`docs/*.md` |
各25,600トークン(`-c 256` × 100チャンク)。検算は4領域とも 加算 = 9,830,400 = 25600×8×48。
**汚染を避けた点**: 汎用PPLのテスト文 `data/calib/corpus_test.txt` は本プロジェクトの
`experiments/` の Python を含む。だから較正には**llama.cpp 側と標準ライブラリの外部コード**を使った。
HumanEval は当然混ぜていない。
(ただし「コードで較正してコードで測る」構図ではあるので、HumanEval は較正にとって
*領域内*の評価になる。領域外の独立な検査は 個人PPL と 汎用PPL の2つ。)
### 48-C. ★★ 実験47の壊滅の原因が、削る前に数字で見えた
`tools/gguf/merge_scores.py --simulate` は、**削る前に**「各領域の経路重みが何%残るか」を出す。
残す数96のとき:
| 選び方 | ja | code | en |
|---|---:|---:|---:|
| **ja単独(=実験47)** | 99.17% | **85.25%**(最悪層 73.8%) | **81.11%**(最悪層 **57.9%**) |
| 混合(sum) | 96.85% | 97.40% | 96.66% |
| 混合(fair) | 96.81% | 96.72% | 96.76% |
**実験47は、コードの経路重みの 14.8%、英語の 18.9% を捨てていた。** ある層では英語の 42% が消えていた。
個人PPLが +0.85% しか動かなかったのは、ja だけ 99.17% 残っていたからで、
**PPLは「較正と同じ領域だけ」を見ていた**という実験47-Eの結論が、原因の側から裏づけられた。
### 48-D. 領域どうしはどれだけ食い違うか(上位96の重なり・層平均)
```
ja ∩ code 0.808 lib ∩ algo 0.924 ← Python どうしはよく一致する
ja ∩ en 0.824 ja ∩ algo 0.803
code ∩ en 0.807
```
- **「Pythonの種類」より「日本語かコードか」の壁の方がずっと高い。**
較正に使うコードが gguf-py 系でも標準ライブラリでも、選ばれる専門家は92%同じ。
- 3領域のどれかの上位96に入る専門家は **平均121.1個 / 128**。
→ **実験46の「128個のうち大半が遊んでいる」は、狭い較正の上でだけ成り立つ話だった。**
領域を3つに広げた時点で、遊んでいる専門家はほとんど居なくなる。
### 48-E. 合成の4方式は、実質同じ答えを出す
`sum`(素直な和=混合較正そのもの)/ `norm`(層ごとに領域内で正規化)/
`union`(順位を総当たり)/ `fair`(いま一番飢えている領域に選ばせる max-min 公平)。
残す集合の重なり(keep=96・層平均):
```
sum norm union fair ja単独
sum 1.000 0.996 0.968 0.970 0.860
fair 0.970 0.971 0.964 1.000 0.849
```
**4方式は互いに96〜99%一致し、ja単独とは14〜15%違う。**
→ 引き継ぎ書の §7-(1) と §7-(2) は別々の案に見えたが、**同じ場所に着く**。
効くのは「どう混ぜるか」ではなく「**混ぜたかどうか**」。
採用したのは `fair`(最悪層の残存率が領域間でいちばんそろう。87.7/87.8/88.4%)。
### 48-F. 結果
`fair` の採点で 128→96 と 128→80 の2本を作った。手術は **9秒**(実験47は14秒)。
| | 元128 | ja単独96<br>(実験47) | **混合96**<br>(今回) | **混合80**<br>(今回) | ja単独112<br>(実験47-G) |
|---|---:|---:|---:|---:|---:|
| ファイル | 11.26 GB | 8.62 GB | **8.62 GB** | **7.30 GB** | 9.94 GB |
| パラメータ | 30.53 B | 23.28 B | 23.28 B | 19.66 B | 26.9 B |
| **HumanEval** | **86.6%**<br>(142/164) | **53.7%** | **84.8%**<br>(139/164) | **78.0%**<br>(128/164) | 76.2% |
| 個人PPL | 12.0972 | 12.2002 | 12.5916 | 14.6621 | — |
| 汎用PPL | 5.7328 | 6.4026 | 6.0135 | 6.6286 | — |
| pp512 | 68.52 ±10.59 | — | **82.74 ±0.73** | 81.90 ±9.48 | — |
| tg128 | 20.22 | — | 20.83 | 20.72 | — |
| しりとり | ○ | ✗ | △ 未完 | ✗ 誤答 | — |
★ **同じ 8.62 GB のまま、HumanEval の損失が −32.9pt から −1.8pt になった。**
164問で3問差なので、統計的には元モデルと区別がつかない(1σ ≒ ±2.7%)。
★★ **混合較正の80版(7.30 GB / 78.0%)が、ja単独の112版(9.94 GB / 76.2%)に勝つ。**
専門家を32個多く積むより、**較正を広げる方が価値が高い**。2.64 GB 小さくて品質は上。
### 48-G. 損傷の曲線は、較正を広げると寝る
```
HumanEval 128 112 96 80
ja単独較正 86.6% 76.2% 53.7% —
混合較正 86.6% — 84.8% 78.0%
```
実験47-G は「損傷は非線形に加速する」と結論したが、**それは狭い較正での曲線だった。**
較正を広げると膝が奥に動く。**削減率の問題ではなく較正の幅の問題**という47-Gの読みは正しく、
その通りに直った。
### 48-H. ★ 速度の訂正(実験47-C は測り方が悪かった)
実験47-C は「枝刈りでメモリ圧が下がり tg が +7.9% 速くなる」としたが、
**書き込み直後の測定でページキャッシュが汚れていた**。静かな状態で3本続けて測ると:
| | pp512 | tg128 |
|---|---:|---:|
| 元 11.26 GB | 68.52 ± **10.59** | 20.22 ± 0.10 |
| 混合96 8.62 GB | **82.74 ± 0.73** | 20.83 ± 0.14 |
| 混合80 7.30 GB | 81.90 ± 9.48 | 20.72 ± 0.36 |
**tg はほぼ横ばい(+3〜5%)。伸びるのは pp(+21%)。** これは MoE の構造そのもの:
```
tg : 1トークンにつき 8/128 の専門家しか読まない → 枝刈りしても読むバイト数が減らない
pp : バッチで大半の専門家に触る → ファイルが小さいぶんそのまま速い
```
実験40の「枝刈りは(生成を)速くしない」が正しく、実験47-Cの訂正の方が誤りだった。
メモリ圧の効果は **速度そのものではなく、ばらつき**に出る(pp の ±10.59 → ±0.73)。
★ 同じ大きさの2モデル(ja単独96 と 混合96)を続けて測ると 20.66 と 20.73。
**同サイズなら速度は同じ**。当たり前のことだが、これで測り方の健全性が確認できた。
### 48-I. どこを主力にするか
**混合96(8.62 GB)を新しい主力の候補とする。**
- HumanEval は元と区別がつかない(84.8% 対 86.6%)
- 個人PPL 12.59 は、無圧縮の3B(14.61)より依然として良い
- 2.64 GB の余白が空く(引き継ぎ書の到達目標1がこれで達成)
**80(7.30 GB)は行き過ぎ。** 個人PPL 14.66 は無圧縮3B(14.61)とほぼ同じで、
「自分専用の30B」という主張の根拠が消える。しりとりも誤答した。
→ **膝は 80 と 96 のあいだ**。88 を測れば境界が確定する(手術9秒+HumanEval 20分)。
### 48-J. この回でやらなかったこと・限界
- HumanEval は「コードで較正してコードで測る」ので、較正にとって**領域内**の評価。
独立な検査は 個人PPL と 汎用PPL の2つだけ。日本語の生成品質は6問の目視のみ。
- しりとりが △(正しい鎖に到達するが答えを確定しない)。元モデルは解けていた。
推論の長さが伸びており、2000トークンでも終わらなかった。**要調査。**
- ルータの再スケール(実験47-F の5)は未着手。消えた経路重みの行き先は放置している。
- 較正は各25,600トークンのまま(先行事例は264,141)。
### 成果物
```
tools/ops/score_domains.sh 領域別の採点(ja/code/algo/en)
tools/gguf/merge_scores.py 採点の合成 4方式+--report/--simulate(削る前の見積り)
tools/ops/eval_pruned.sh 任意の枝刈りモデルを同じ手順で評価
data/calib/mix/{code,algo,en}.txt 較正データ(評価と重ならないように作った)
data/ckpt/reap/score_{code,algo,en}_25600.tsv 領域別の採点
data/ckpt/reap/keep_fair_jce.tsv 採用した残す順(fair・ja:code:en = 1:1:1)
~/LocalAI_mirror/models/Qwen3-30B-REAP96-mix-Q2_K.gguf 8.62 GB ← 新しい主力候補
~/LocalAI_mirror/models/Qwen3-30B-REAP80-mix-Q2_K.gguf 7.30 GB ← 行き過ぎた版
data/ckpt/reap/reap{96,80}mix_{bench,ppl,he}.txt / /tmp/claude-503/{he,gen}/reap*mix*
```
### 48-K. 追試 — 膝は96のすぐ下。そこから先は平ら(88を足した)
`keep_fair_jce.tsv` は全128個の順位なので、`--keep` を変えるだけで別の削減率が作れる。88(7.96GB)を足した。
| 専門家 | サイズ | HumanEval | 個人PPL | 汎用PPL | 残存経路重み(fair・層平均) |
|---:|---:|---:|---:|---:|---:|
| 128(元) | 11.26 GB | **86.6%** | 12.0972 | 5.7328 | 100% |
| **96** | **8.62 GB** | **84.8%** | 12.5916 | 6.0135 | 96.8% |
| 88 | 7.96 GB | 73.2% | 13.0751 | 6.2174 | 94.7% |
| 80 | 7.30 GB | 78.0% | 14.6621 | 6.6286 | 92.5% |
**88 が 80 より低く出た。** 生成は温度0(貪欲)なので、これはサンプリングのゆらぎではない。
同じ164問での**対応のある比較**(どちらか一方だけが解けた数)で決着させた:
```
96 対 88 : 96だけ○ 22問 / 88だけ○ 3問 差 +19 食い違い n=25 2√n=10.0 → ★有意
96 対 80 : 96だけ○ 16問 / 80だけ○ 5問 差 +11 食い違い n=21 2√n= 9.2 → ★有意
88 対 80 : 88だけ○ 9問 / 80だけ○ 17問 差 −8 食い違い n=26 2√n=10.2 → ノイズ
```
→ **96 は 88 とも 80 とも有意に違う。88 と 80 は区別がつかない。**
**膝は 96 のすぐ下にあり、そこから先は 73〜78% で平らになる。**
8個減らすだけ(96→88)で 11.6pt 落ちるのに、さらに8個減らしても落ちない。
★ **PPLは単調に悪化するのに、HumanEvalは「崖+平地」。** 実験45・47・48の規則がまた効いている。
PPLの滑らかさを見て「少しずつ悪くなるだけ」と読んではいけない。
★ 運用の結論: **96(8.62GB)が唯一の作業点。**88 も 80 も、大きさの得(0.7〜1.3GB)に対して
代償(−11.6pt / −6.8pt)が釣り合わない。
### 48-L. ルータの再スケール(§47-F の5)は、この構造では不要と判明
`gguf_dump` に `norm_topk_prob` に相当するKVは無いが、実験46で観測したノード名
`ffn_moe_weights_norm-<層>` が示す通り、**llama.cpp は選ばれた8個のゲート値を毎回1に正規化する。**
専門家を減らしても総ゲート質量は 1 のまま変わらない。
→ **消えた経路重みを補償する対象が存在しない。** REAP論文の再スケールは正規化しない機種の話。
実験を回さずに1件片付いた。損なわれるのは「重み」ではなく「どの8個が選ばれるか」だけ。
### 48-M. ★★ しりとりの「退行」の正体 — 壊れたのは能力ではなく「思考を畳む力」
48-J で「しりとりが △。元モデルは解けていた」と書いたが、**これは古い記録を引いただけで
同条件の比較ではなかった。**同じ設定で並べて測り直した:
| 条件 | 元128 | 混合96 |
|---|---|---|
| 思考あり・700トークン | **未完** | 未完 |
| 思考あり・2000トークン | **○ 正解**(約1000トークンで畳む) | ✗ 未完(8507バイト使っても終わらない) |
| **`/no_think`** | **○ 即答** | **○ 即答** |
★ **混合96は `/no_think` なら即座に正解する。答えも元と完全に同じ。**
→ **知識も推論の道筋も失われていない。**48-J の「△」は誤読だった。
★ 壊れたのは **長い思考を終わらせる力**。混合96は「らっぱ」の末尾を『っぱ』と読んで
「『ぱ』で始まる語は無い」という袋小路に入り、そこから抜けられずに発散する。
元モデルは同じ袋小路に入っても、「しりとりは仮名の音で判定する」と自分で訂正して畳む。
★★ **これは HumanEval が 84.8% を保った理由も説明する。**
`tools/ops/run_he.sh` は `/no_think` を付けて投げている。**思考を切っていたから無事だった。**
→ 新しい仮説: **枝刈りは「直接答える力」より先に「長い思考を畳む力」を傷める。**
経路重みは96.8%残っているのに、自己訂正のような**長距離の推論だけが選択的に弱る**。
これは PPL でも HumanEval(/no_think) でも検出できない種類の損傷である。
次に確かめること: 思考ありでの評価(HumanEval を `/no_think` 無しで回す、
あるいは思考トークン数を数える)。**枝刈りモデルの正しい使い方は `/no_think` かもしれない。**
### 48-N. 運用の結論(更新)
- **主力は 混合96(8.62GB)。** 96 未満は代償が釣り合わない(48-K)。
- **枝刈りモデルには `/no_think` を付ける。** 思考を長く走らせると畳めずに発散することがある。
- ルータの再スケールは不要(48-L)。
### 48-O. 領域の重み(§7-(6))は、やる価値が無いと分かった — かつ道具の欠陥を1つ見つけた
`--simulate` で、作らずに交換曲線を引いた(keep=96・mode=norm):
| 重み | ja | code | en |
|---|---:|---:|---:|
| ja=1 | 97.31% | 96.66% | 97.25% |
| ja=2 | 97.80% | 96.35% | 96.89% |
| ja=3 | 98.07% | 96.04% | 96.53% |
| ja=6 | 98.42% | 95.35% | 95.72% |
実測から得た対応関係(ja残存 2.36pt ≒ 個人PPL 0.39 / code残存 11.5pt ≒ HumanEval 31pt)で換算すると、
ja=2 は **個人PPL 12.59→約12.43、HumanEval 84.8→約83.8%**。**どちらもノイズの中**。
→ **交換曲線が平らなので、等重み(1:1:1)が既に十分良い場所にいる。§7-(6) は閉じる。**
★ 副産物として道具の欠陥が出た。**`fair` に `--weights` を使ってはいけない。**
```
fair の飢え度は got[k] / tot[k] / W[k]
→ W=2 にすると「その領域が他の2倍の割合を持つまで」独占し続ける。
割合は 1.0 で頭打ちなので、飽和するまで止まらない。
実測: ja=2 で ja 99.13% / code 86.23% / en 85.51%
= ja単独較正(99.17 / 85.25 / 81.11)とほぼ同じ壊れ方をする。
```
重みを振りたいときは **`--mode norm`** を使うこと(線形に効く)。
`merge_scores.py` の該当箇所に注意書きを入れた。
### 48-P. 48-M の補強 — 「思考を畳めた率」は損傷とよく並ぶ
同じ6問(`read_gen_all.sh`)で、`[Start thinking]` から `[End thinking]` まで到達できた数:
| モデル | 思考を畳めた | HumanEval(/no_think) |
|---|---:|---:|
| 元128 | 4/6 | 86.6% |
| 混合96 | 3/6 | 84.8% |
| 混合80 | 3/6 | 78.0% |
| **ja単独96** | **1/6** | **53.7%** |
壊滅した ja単独96 は6問中1問しか思考を終えられていない。標本6問なので決定打ではないが、
48-M のしりとりの対照実験と同じ向きを指している。
★ **安い診断法の候補**: 「思考を畳めた率」は HumanEval(20分)より遥かに安く(5分)測れて、
損傷とよく並ぶ。枝刈りの当たり外れを早く見るのに使えるかもしれない。要検証。
---
## 2026-09-05 夜 — 実験49: ★★ 16GBの機械で、29GBのモデルの専門家を採点した
「もっと重いモデルを作れないか(Kimi K3 とか)」という問いから始めた。
### 49-A. 二枚の壁 — 枝刈りで動かせるのは片方だけ
```
壁1 速度 : 活性パラメータで決まる → 枝刈りは活性を1個も減らさないので **動かせない**
壁2 RAM : 総パラメータで決まる → 枝刈りで **動かせる**
```
実測式 `t/s ≒ K × 28 ÷ (活性パラメータ × bpw ÷ 8)` から:
| モデル | 総 | 活性 | 予測 t/s | 判定 |
|---|---:|---:|---:|---|
| Qwen3-30B-A3B(現用) | 30.5B | 3.3B | 21.2 | 現用 |
| **Qwen3-Next-80B-A3B** | **80B** | **3.0B** | **23.3** | **★候補** |
| Kimi-Linear-48B-A3B | 48B | 3.0B | 23.3 | KDA が本家未対応(PR #17592) |
| GPT-OSS-120B | 117B | 5.1B | 13.7 | 74%削減が要る |
| Qwen3-235B-A22B | 235B | 22B | 3.7 | ✗ 速度で詰み |
| **Kimi K3** | **2.8T** | **104B** | **1.1** | **✗ 論外**(896専門家中16個) |
→ **Kimi K3 は活性104Bなので、2ビットに潰しても1トークン26GB読む。RAM以前に速度で詰む。**
狙うべきは「総が大きく、活性が小さい」機種。**Qwen3-Next-80B-A3B(80B/3B/512専門家)**。
### 49-B. 構造の確認と、再来した罠
`qwen3next` は本家 llama.cpp も ik_llama.cpp も対応済み(再ビルド不要)。
| テンソル | 型 | 形 | 1専門家 |
|---|---|---|---:|
| `ffn_gate_inp`(ルータ) | F32 | {2048, **512**} | 8,192 B |
| `ffn_gate_exps` | Q2_K | {2048, 512, **512**} | 344,064 B |
| `ffn_up_exps` | Q2_K | {2048, 512, **512**} | 344,064 B |
| `ffn_down_exps` | Q3_K | {512, 2048, **512**} | 450,560 B |
合計 **1.147 MB/専門家/層 × 48層 = 55.05 MB/専門家**。専門家はファイルの **96.7%**。
→ **11.26GB に収めるには 512個中 186個(63.7%削減)。**
★ **実験47-A の罠がそのまま再来した。** 共有専門家 `ffn_gate_shexp` {2048, **512**} と
`ffn_up_shexp` {2048, **512**} の最終次元が専門家数と一致する。
形で判定すると常時使う共有専門家を壊す。名前(`_exps.weight`)で選んでいるので無事だった。
### 49-C. ★★ RAMに入らないモデルの採点が、実際に動いた
29.17 GB のモデルを 16GB の機械で `llama-reap-score` に流した(mmap 頼み)。
```
512トークン / 101秒(うちモデル読み込み28秒)
検算 加算 = 245,760 = 512トークン × 10専門家 × 48層 ← 完全一致
→ 25,600トークンで **約61分**(30Bの20分に対して3倍)
```
起動時に `failed to fit model into system memory` と警告が出るが、**mmap で走り切る。**
→ **「採点はRAMに入らないモデルにも当てられる」。**手術(`prune_experts.py`)は元から
ストリーミングなのでRAMを使わない。**つまり16GBの機械で80Bを削れる。**
### 49-D. 予備の集中度測定 — 512専門家は、128専門家よりずっと冗長
上位K個が担う経路重みの割合(層平均 / 最悪の層):
| Qwen3-30B(128個・ja・25600tok) | | Qwen3-Next-80B(512個・code・512tok) | |
|---|---|---|---|
| 上位96 (75.0%) | **99.17% / 95.4%** | 上位186 (36.3%) | **98.84% / 91.6%** |
| 上位80 (62.5%) | 97.68% / 90.4% | 上位160 (31.2%) | 98.03% / 88.1% |
| 上位64 (50.0%) | 94.58% / 83.6% | 上位128 (25.0%) | 96.39% / 82.3% |
**80Bを36%まで削るのは、30Bを75%まで削るのと同じくらいの損傷に見える。**
★ ただし 512トークンの標本は楽観側に偏る。**偏りの大きさを30Bで実測して較正した**
(同じ code 較正で 512tok と 25600tok を比較):
```
上位96 (75%) : +0.44pt 楽観
上位80 (62.5%): +1.08pt 楽観
上位64 (50%) : +2.05pt 楽観
```
80B は専門家あたりのヒット数が 160分の1 なので偏りはもっと大きいはずだが、
保守的に3pt引いても **約96%** で、30Bの混合96(96.8% → HumanEval 84.8%)と同水準。
→ **本番採点(3領域・約3時間)を回す価値があると判断した。**
### 49-E. ★★★ 「11.26GBの壁」は MoE には当てはまらなかった — 素の80B が 6.1 t/s で動いた
削る前に、**素の 29.17 GB をそのまま走らせてみた**(16GB の機械で)。
```
Qwen3-Next-80B-A3B Q2_K 29.17 GB / 無削減
[ Prompt: 3.5 t/s | Generation: 6.1 t/s ] ← 日本語の答えも正しい
```
**目標の 5 t/s を超えている。** 実験43-F の「11.26GB を超えると実使用で半分以下に落ちる」は、
**30B(ほぼ全部が RAM に乗る機種)で測った数字だった。**
理由は MoE の構造そのもの:
```
生成では 1トークンにつき 512個中10個の専門家しか読まない。
よく使われる専門家はページキャッシュに残り続け、
たまにしか使われない専門家だけが内部SSD(3,155MB/s)から読まれる。
→ **OSのページキャッシュが、勝手に「階層的な専門家配置」をやっている。**
```
これは MoE-Infinity / Mixtral-offloading 系の論文がやっていることと同じ原理を、
**実装ゼロで手に入れている**ということ。専門家の使用頻度が偏っているほどよく効く。
★★ **戦略が変わる。**
```
旧: 11.26GB に収まるまで削る → 削りすぎて壊す
新: 「どこまで大きいまま、目標速度を保てるか」を測る → 削る量を最小にする
```
実験49-D で「80Bを186個(36.3%)まで削ると経路重みが 94.48% まで落ちる」と分かっていたが、
**そもそもそこまで削る必要が無かった。**
★ 引き継ぎ書 §6 の「外付けからのストリーミング(--moe-stream)はやらない」は正しいままである
(外付けは回転する円盤で 101MB/s)。**だが内蔵SSDは 3,155MB/s で31倍速い。**
「ストリーミングはやらない」ではなく「**外付けからは**やらない」と書くべきだった。
### 49-F. ★★ 速度と専門家数の曲線 — 枝刈りは「読む量」を減らさない。効くのは当たり率だけ
同じ問い・同じ手順(`gen_read.sh`・本家llama.cpp・各2回)で、専門家数を変えて測った。
| 専門家 | サイズ | pp t/s | **tg t/s** |
|---:|---:|---:|---:|
| 512(素) | 29.17 GB | 3.6 | **6.15** |
| 256 | 15.08 GB | 5.4 | **7.6** |
| 208 | 12.44 GB | 7.0 | **8.1** |
| 186 | 11.23 GB | 10.3 | **9.75** |
| 160 | 9.79 GB | 19.8 | **11.95** |
| 128 | 8.03 GB | 19.4 | **11.25** |
★ **pp は 11GB の線で急に跳ねる**(10.3 → 19.8)。**tg はなだらか**(9.75 → 11.95)。理由は構造そのもの:
```
tg : 1トークンにつき 10/512 の専門家しか読まない
→ 枝刈りしても「読むバイト数」は変わらない。変わるのはキャッシュの当たり率だけ。
pp : バッチで大半の専門家に触る
→ ファイル全体が RAM に入るかどうかが直に効く。だから 11GB で跳ねる。
```
→ **枝刈りは速度対策としては弱い。**大きさを買うための道具であって、速さを買う道具ではない。
★ 注意: **書き込み直後・ディスク満杯の状態で測ると 1〜2 t/s ずれる。**
256版は最初 5.4 t/s と出たが、これは空き 3.2GB のときの値。空きを 75GB に戻したら 7.6 t/s。
実験48-H と同じ罠。**空き容量も測定条件のうち。**
### 49-G. ★★★ Qwen3-Next は構造そのものが CPU で 1.65倍 重い
専門家を 128 個まで削って **30B混合96 より小さくして**、両方とも RAM に完全に収まる条件で比べた。
| モデル | サイズ | 活性 | pp | **tg** |
|---|---:|---:|---:|---:|
| Next80B-E128 | **8.03 GB** | 3B相当 | 19.4 | **11.25** |
| Qwen3-30B-混合96 | **8.62 GB** | 3.3B | 29.8 | **18.6** |
**小さい方が遅い。** メモリの問題ではない。**Gated DeltaNet(SSM)層の計算コスト**である。
Qwen3-Next は 4層に3層が Gated DeltaNet で、conv1d と delta rule の計算が CPU で重い。
→ **どれだけ削っても Qwen3-Next は 11〜12 t/s で頭打ち。**30B(18.6 t/s)には届かない。
→ **Qwen3.6-35B-A3B も同じ Gated DeltaNet 系**なので同じ重さを持つはず。
ただし MTP(1.5〜2倍)がちょうど相殺しうる。要実測。
### 49-H. `-ot` では CPU側の階層分けはできない(外部AIの回答を検証した)
NVIDIA Build の Nemotron に「専門家を記憶階層に振り分ける方法」を聞いたところ、
`--mlock-expert` というオプションや `-ot "...:disk"` という書き方を**実在するかのように**答えた。
手元で確かめると:
```
--override-tensor (-ot) : 実在する。ただし <テンソル名>=<バッファ型> で、
**バッファ型はバックエンドのもの**。CPU only では選択肢が1つしかない。
→ **CPU側の「RAM常駐 / mmapのまま」の振り分けには使えない。**
--mlock : 実在する。ただし**モデル全体**。テンソル単位の指定は無い。
--mlock-expert : **実在しない。**
```
→ **外部AIの回答は仮説の源にはなるが、オプション名と数字はそのまま引いてはいけない。**
今回は3件中2件が誤りだった。手元の `arg.cpp` を grep するのが確実で速い。
### 49-I. ★★★ 素の80Bの品質 — 30B原本より 18% 良い。差は本物だった
較正の偏りが無い状態(無削減)で測った。**トークナイザが同一であることを先に確認した**
(両方とも 151,936語・先頭2,000語が完全一致)。実験43の規則を満たすので比較は有効。
| モデル | サイズ | tg t/s | 汎用PPL | 個人PPL |
|---|---:|---:|---:|---:|
| Qwen3-30B 混合96 | 8.62 GB | **18.6** | 6.0135 | 12.5916 |
| Qwen3-30B 原本 | 11.26 GB | ~20 | 5.7328 | 12.0972 |
| **Qwen3-Next-80B 素** | 29.17 GB | 6.15 | **4.6552** | **9.8827** |
**汎用 −18.8% / 個人 −18.3%。両方の領域で同じだけ良い。**
個人PPL 9.88 は、無圧縮の3B(14.61)はもちろん、30B原本(12.10)より遥かに良い。
★ **賢さは80Bの圧勝、速さは30Bの圧勝。** 3倍遅い代わりに18%賢い、という交換になる。
49-G で分かった通り、80Bの遅さは Gated DeltaNet の計算コストなので**削っても解消しない**
(どこまで削っても 11〜12 t/s で頭打ち)。
★ 判断の材料(目標の4条件に照らして):
```
賢い : 80B の勝ち(明確)
速い : 30B の勝ち(18.6 対 6.15〜11.9)
十分乗る: 30B の勝ち(8.62GB で RAM に 7GB の余裕。80Bは11GBでも余裕2GB)
自分向け: 30B(個人較正ずみ)。80Bはまだ較正していない
```
→ **「kernel の先生を常駐させる」用途では、余裕と速度が要るので 30B混合96 が有利。**
**「難しい調べもの1回」の用途では 80B が有利。**持ち替えは読み込み数秒なので両立できる。
## 2026-09-05 深夜 — 実験50: 速度の梃子を3つ試した(目標に「速い」が戻ったため)
引き継ぎ書の「速さはもういい」は 2026-09-05 に**撤回**された。改めて速度の手を洗い直した。
### 50-A. 投機的デコードは MoE では効かない(計算で否定できる)
引き継ぎ書 §6 で「別モデルを下書きにする投機的デコード」はやらないと決めていたが、
**理由が書かれていなかった。**数字で示せるようになった。
```
30B混合96 は 96専門家中8個を使う。4トークンまとめて検証すると、
触れる専門家は 96×(1-(1-8/96)^4) ≒ 28個。1トークンなら8個。
→ 4トークン得るのに 3.5倍 読む。全部受理されても得は 14% だけ。
受理率6〜8割を考えると **実質ゼロか損**。
```
**密モデルなら検証はほぼタダだが、MoEでは「トークンを増やすと活性する専門家が増える」。**
→ §6 の判断は正しい。MTP も同じ理由でCPUでは効きにくいはず(GPUでは効く)。
### 50-B. `expert_used_count` を下げる — 弱い梃子だった
`tools/gguf/set_kv.py`(新規)で、メタデータのu32を1個書き換えるだけで変えられる。
| 使う専門家 | tg t/s | 個人PPL |
|---:|---:|---:|
| 8(元) | 19.2 | 12.5916 |
| 6 | 21.4(+11%) | 13.3678(+6.2%) |
| 4 | 24.6(+28%) | 16.4817(+30.9%) |
25%減らして+11%しか速くならない。**アテンション・埋め込み・共有部の読み込みが減らないから。**
→ 交換レートが悪い。急ぐときの非常用の knob として覚えておく程度。
### 50-C. ★★★ ik_llama.cpp を本番に使う — 品質ゼロ損失で pp +136% / tg +13%
ik には `llama-bench` しか作っていなかった。`llama-cli` と `llama-server` を追加ビルドした(**54秒**)。
同じモデル(30B混合96)・同じ問い・`llama-server` の `/v1/chat/completions` 経由:
| ビルド | 起動 | pp t/s | tg t/s | 答えの質 |
|---|---:|---:|---:|---|
| 本家 llama-server | 6秒 | ~31 | 19.2 | ○ |
| **ik llama-server** | **4秒** | **73.3** | **21.7** | ○(同じ) |
**+136% / +13%。品質は落ちない(同じ重み・同じテンプレート)。**
→ **本番のサーバは ik に替えるべき。**`tools/ops/run_he.sh` は `SRV=` で差し替えられるようにした
(既定は本家のまま。過去の測定と比較できるようにするため)。
★ 注意: **ik の `llama-cli` は引数体系が古い**(`-st` が無い)。
`-st` 無しで走らせると**チャットテンプレートが当たらず、素の続き生成になって同じ文を繰り返す。**
ik を使うのは `llama-server` 経由にすること。`gen_read.sh` は `BIN=` と `ST=` を足したが、
ik で使うと生成が壊れるので **`gen_read.sh` は本家のまま使う。**
---
## 2026-09-06 未明 — 実験51: 速度の手を16個洗い出して全部測った
「MoEだけじゃなく他の方法も5個以上」という指示。**MoEの専門家いじりに寄りすぎていた**ので、
実行時オプション・並行処理・構造の3方向から系統的に洗い直した。
### 51-A. 実行時オプション12個の掃引(30B混合96 / ik / -r 2)
基準: pp512 = 91.39 ± 1.72 / tg128 = 20.91 ± 0.52
(★ 基準自体がセッション間で 84〜91 とばらつく。**±8%以内は判定しないこと。**)
| # | 方法 | pp512 | tg128 | 判定 |
|---:|---|---:|---:|---|
| 1 | スレッド数 -t 4 | 63.17 | 17.94 | ✗ 少なすぎ |
| 1 | -t 6 | **90.66** | 20.02 | pp向き |
| 1 | -t 8 | 85.73 | 20.08 | — |
| 1 | -t 10(既定) | 84.11 | **20.74** | ★tg向き。既定のままでよい |
| 1 | -t 12 | 89.94 | 19.64 | — |
| 2 | 実行時リパック `-rtr` | 90.12 | 21.32 ±0.02 | △ 微益。ばらつきが小さい |
| 3 | mmap無効 `-mmp 0` | **77.68** | 20.36 | ✗ **mmapの方が速い(確定)** |
| 4 | KV量子化 `-ctk/-ctv q8_0` | **93.00** | 21.13 | ★**採用**。速度同等でKVメモリ半分 |
| 5 | 巨大ページ `-thp 1` | 88.20 | 20.80 | ノイズ |
| 6 | ropeキャッシュ `-rcache` | 84.70 | 21.00 | ノイズ |
| 7 | QKV統合 `-mqkv` | **68.80 ±18** | 19.95 | ✗ **明確に悪い** |
| 8 | up-gate統合 `-muge` | 84.81 | 20.47 | ノイズ |
| 9 | グラフ再利用切 `-gr 0` | 85.59 | 20.44 | ノイズ |
| 10 | FlashAttn切 `-fa 0` | 86.21 | 21.25 | ノイズ |
| 11 | ubatch 256 | 83.96 | 20.71 | ノイズ |
| 12 | attn最大バッチ `-amb 128` | 83.36 | 21.02 | ノイズ |
→ **実行時オプションは掘り尽くした。**採用に値するのは KV量子化 q8_0 だけ(速度ではなくメモリの得)。
**`-mmp 0` が遅いことは重要**: mmap のページフォールトは、明示的な読み込みより速い。
### 51-B. `-ser`(適応的な専門家削減)— ソースを直して有効化したが、固定top-kより悪い
★ **`-ser` はこのビルドでは引数を受け取るだけで何もしていなかった。**
`llama-build-context.cpp` の該当2行が**コメントアウトされていた**。
知らずにベンチしていたら「効果なし」と誤結論するところだった。有効化して測り直した
(元は `/tmp/claude-503/llama-build-context.cpp.bak`。**git pull で消えるので注意**)。
意味: 最低 `min` 個は使い、それ以降は**最大ゲート値 × `thresh` を下回る専門家を捨てる**(トークンごとに適応)。
| 設定 | tg128 | 個人PPL(ik/24chunk) |
|---|---:|---:|
| 基準 | 20.57 | 12.7033 |
| `-ser 4,0.3` | 21.72(+5.6%) | 13.4526(+5.9%) |
| `-ser 4,0.5` | — | 14.7743(+16.3%) |
| `-ser 2,0.5` | — | 20.7994(+63.7%)✗ |
**適応式の方が賢いと予想したが外れた。**固定 `expert_used_count=6`(+11%速度で+6.2%PPL)の方がまだ良い。
→ **専門家の数をいじる道は、固定でも適応でも割に合わない。**2つの方式で確かめた。
### 51-C. ★★★ 同時要求(バッチ処理)— 合計スループットが2倍になる
MoE は1トークンごとに重みを読む。**同時に複数の要求を処理すれば、同じ読み込みで
複数トークンぶんの計算ができる。**(`llama-server -np N -cb`)
| 同時数 | 合計 t/s | 1本あたり t/s |
|---:|---:|---:|
| 1 | 17.09 | 17.09 |
| 2 | 22.44(+31%) | 11.22 |
| **4** | **33.33(+95%)** | 8.33 |
| 8 | 33.59(+97%) | 4.20 |
**4本で飽和して約2倍。品質ゼロ損失・実装ゼロ**(`-np 4 -cb` を付けて同時に投げるだけ)。
★★ **これは最終目標(kernel の先生)に直結する。**
kernel がカードを焼くために大量に質問するなら、**まとめて4本ずつ投げれば倍の速さで焼ける。**
1本あたりは遅くなるので、対話には向かない。**用途で使い分ける。**
### 51-D. 投機的デコードは MoE では効かない(再掲・計算による否定)
4トークンまとめて検証すると触れる専門家が 8→約28個に増え、読む量が3.5倍になる。
全部受理されても得は14%、受理率6〜8割で実質ゼロ。**MTP も同じ理由でCPUでは効きにくい。**
### 51-E. まだ試していない構造的な方法(次の候補)
| 方法 | 買えるもの | 見込み | 手間 |
|---|---|---|---|
| **層の刈り込み**(48層→40層) | **速度と大きさの両方** | 17%速く・1.4GB小さく | 採点器+ヘッダ再構築で半日 |
| **語彙の刈り込み**(152k→90k) | 大きさと約6%の速度 | 出力ヘッドは**毎トークン全部読む**(1.22GB/tokのうち約175MB) | トークナイザ再構築で半日 |
| ik固有の量子化型(IQ2_KS等) | 速度か品質 | 未知 | 再量子化1時間 |
| 専門家の合成(merging) | 品質 | 未知 | 2〜3日 |
★ **層の刈り込みが本命。**専門家の刈り込みと違い、**1トークンあたりの計算量そのものが減る**ので
速度を直接買える。実験48〜50で「枝刈りは速度を買えない」と分かった今、これが唯一残った
「削って速くする」道である。
### 51-F. 環境について分かったこと(次に書く人へ)
★ **Bashツールのシェルは zsh。** これで2回はまった:
```
・変数に入れたオプションは単語分割されない
A="-ser 4,0.3"; cmd $A → "-ser 4,0.3" が1語として渡り、コマンドが引数エラーで死ぬ
→ オプションは変数に入れず直接書くか、関数の "$@" で渡すこと
・`wait $PID` が "job not found" で即座に失敗する
→ 並行処理の測定は Python にした(tools/ops/bench_batch.py)
```
★ `wait`(引数なし)は **llama-server まで待ってしまう**。並行測定では使わないこと。
★ ik の `llama-perplexity` の出力書式は本家と違う:
`Final estimate: PPL over N chunks for n_ctx=512 = X` (本家は `Final estimate: PPL = X`)
### 成果物
`tools/ops/bench_batch.py`(同時要求のスループット測定)/ `tools/ops/bench_batch.sh`(shell版・zshでは使えない)/
`tools/gguf/set_kv.py` / ik_llama.cpp の `-ser` 有効化パッチ
---
## 2026-09-06 未明 — 実験52: ★★ 層を刈った — 「削って速くする」唯一の道を実際に通した
実験48〜51で **専門家の枝刈りは速度を買えない**と分かった(1トークンに読むバイト数が変わらないから)。
**層を抜けば1トークンあたりの計算量そのものが減る。**唯一残った道を実装して通した。
### 52-A. 層の採点器を作った(`llama-layer-score`・新規・約190行)
層は残差ブロックなので `h_out = h_in + Δ`。Δ が小さい層は抜いても後段への影響が小さい。
ShortGPT の Block Influence をそのまま計算する:
```
BI[層] = 1 - E_t[ cos(h_in, h_out) ] 向きをどれだけ変えたか
REL[層] = E_t[ ‖Δ‖ / ‖h_in‖ ] どれだけ足したか
```
`reap-score` と同じ `cb_eval` の仕組み。拾うノードは2つだけ:
```
embd f32 {n_embd, n_tokens} 層0の入力
l_out-<層> f32 {n_embd, n_tokens} その層の出力(=次の層の入力)
```
★ **埋め込みノードの実際の名前は `embd`。`inp_embd` ではない。**
ソースの `cb(inp->embd, "inp_embd", -1)` に釣られて空振りした。
`LAYER_DUMP=1` で全ノード名を書き出せるようにしたので、次に作る人はまずこれを見ること。
所要: 1領域(30チャンク=7,680トークン) **3.5分**。検算 l_out=1440=48層×30チャンク。
### 52-B. ★★ BI の形 — 中間層は本当に平坦だった
```
層 0 : 0.443 ████████████████████████████ ← 突出。最も働いている
層 1 : 0.112 ██████
層 2-8: 0.072 → 0.030 だんだん減る
層 9-36: 0.017〜0.027 ★ 平坦な谷(28層)
層37-46: 0.033 → 0.048 また増える
層47 : 0.299 █████████████████ ← 突出。出力の準備
```
中間28層は cos ≒ 0.98、つまり**残差の向きを2%しか変えていない。**
### 52-C. ★★★ 実験48と同じ構造が層でも出た — 領域ごとに要る層が違う
抜きやすい層の上位6(層0,1と最後の2層は守る前提):
```
ja : [33, 34, 29, 36, 31, 35] ← 後半中間
code : [12, 11, 10, 9, 15, 16] ← 前半中間
en : [34, 36, 35, 29, 33, 24] ← 後半中間
全領域が一致: **0個 / 6**
```
**日本語と英語は後半中間を捨てたがり、コードは前半中間を捨てたがる。重なりがゼロ。**
→ 単一領域で層を刈れば、実験47の壊滅を層で繰り返すことになる。**混合で決めるしかない。**
採用は max-min(最悪の領域でのBIが小さい順。実験48の `fair` と同じ考え方):
```
4層: [12, 17, 23, 24] 最悪BIの上限 0.0204
8層: [12, 17, 22, 23, 24, 33, 34, 36]        0.0215
12層: [11,12,15,17,22,23,24,31,32,33,34,36]        0.0225
```
**12層抜いても上限が 0.0202→0.0225 にしか上がらない。**それだけ平坦。
### 52-D. 手術(`prune_layers.py` 新規)と結果
専門家の手術との違い: **テンソル名 `blk.N` が変わるので、テンソル情報の節を作り直す。**
KV節(トークナイザ15万語)はバイトごとコピーし、`block_count` の u32 だけ上書きする。
所要 **7.3秒**。
| モデル | 層 | サイズ | pp512 | tg128 | 個人PPL | 汎用PPL |
|---|---:|---:|---:|---:|---:|---:|
| 混合96(基準) | 48 | 8.62 GB | 88.84 | 21.09 | 12.7033 | 6.0473 |
| **L44-drop4** | 44 | **7.93 GB** | 86.85 | **22.36(+6.0%)** | 13.1043(+3.2%) | 6.3752(+5.4%) |
| L40-drop8 | 40 | 7.25 GB | 94.45 | 24.19(+14.7%) | 15.4137(+21.3%) | 7.5151(+24.3%) |
| L36-drop12 | 36 | 6.56 GB | 113.79 | 26.11(+23.8%) | 18.1172(+42.6%) | 9.2857(+53.5%) |
**層の刈り込みは確かに速度を買える。**専門家の枝刈りでは 0% だったところが +6〜24%。
ただし**損傷は非線形に加速する**(専門家のときと同じ形)。**実用は4層まで。**
### 52-E. ★★ 速度の梃子を「品質1%あたり何%速くなるか」で並べる
| 梃子 | 速度 | 個人PPLの悪化 | 比 | 大きさ |
|---|---:|---:|---:|---:|
| **ik_llama.cpp に替える** | **+13%** | **0%** | **∞** | 変わらず |
| **同時4本で投げる** | **+95%(合計)** | **0%** | **∞** | 変わらず |
| 層を4つ抜く | +6.0% | +3.2% | 1.9 | **−8.0%** |
| `expert_used_count` 8→6 | +11% | +6.2% | 1.8 | 変わらず |
| `-ser 4,0.3` | +5.6% | +5.9% | 0.95 | 変わらず |
| 専門家 128→96(混合) | +0% | +4.1% | 0 | **−23.4%** |
★ **ただ働きは2つだけ(ik と 同時投げ)。モデルを傷つける梃子は、どれも比 2 前後で頭打ち。**
★ **層抜きは「速度と大きさの両方」を買える唯一の梃子**なので、傷つける系の中では最良。
### 52-F. ★★★ 同じ大きさなら、専門家を削るのと層を削るのはどちらが得か
7.95 GB で揃えて、同じ道具(ik)で測った。
| 削り方 | サイズ | pp512 | tg128 | 個人PPL | 汎用PPL |
|---|---:|---:|---:|---:|---:|
| 専門家を削る(88専門家 × 48層) | 7.963 GB | **87.33** | 20.25 | 13.1278 | **6.2633** |
| **層を削る(96専門家 × 44層)** | 7.934 GB | 80.48 | **21.83(+7.8%)** | **13.1043** | 6.3752 |
**品質はほぼ引き分け**(個人PPLは層抜きが僅かに良く、汎用PPLは専門家抜きが僅かに良い。どちらも2%未満)。
**違うのは買える速度の種類。**
```
tg(生成): 1トークンにつき 8個の専門家 × 層数 を読む
→ **層を減らすと読む量が直接減る。専門家を減らしても減らない。**
pp(読解): バッチで 全専門家 × 層数 に触る
→ **専門家を減らす方が効く。**
```
★ **用途で選び分ける話だった。**
・kernel の先生としてカードを大量に焼く(生成中心)→ **層を削る**
・長い文脈を読ませる(読解中心)→ **専門家を削る**
・両方欲しければ両方削る(今回の L44-drop4 は既に専門家96+層44の二重)
### 52-G. この回の限界(正直に)
- BI は**全層がある状態**で1層ずつ測った値。4層まとめて抜くと誤差は積み重なる。
文献は1層抜くごとに測り直す(逐次選択)。今回はやっていない。
- 文献の層刈りは**抜いたあとに再学習(healing)**して回復させる。**こちらは再学習できない**ので、
そのぶん不利な条件で戦っている。4層で止まるのはそのせいかもしれない。
- 土台が既に専門家25%削減ずみ。**原本(128専門家)から層を刈った場合は未測定。**
- HumanEval は未測定(PPLのみ)。実験48-K の教訓(PPLは滑らかでもHumanEvalは崖)があるので、
**L44-drop4 を主力にする前に必ず HumanEval を回すこと。**
### 成果物
```
tools/llama.cpp/examples/layer-score/ ★層の採点器(BI。約190行。LAYER_DUMP=1 で全ノード名)
tools/ops/score_layers.sh ★領域別の層採点(1領域3.5分)
tools/gguf/analyze_layers.py ★層スコアの突き合わせ+max-min選択
tools/gguf/prune_layers.py ★層を抜く手術(7.3秒。テンソル情報の節を作り直す)
data/ckpt/layer/layer_mix96_{ja,code,en}.tsv 3領域の層スコア
~/LocalAI_mirror/models/L44-drop4.gguf 7.93GB(専門家96+層44)
```
### 52-H. ★★★ 層抜きの HumanEval — PPLはまた嘘をついた。そして崖の位置が確定した
L44-drop4 を HumanEval で測った(**本家サーバで**。ikでの測定は52-Iの理由で無効)。
| モデル | 層 | サイズ | tg | 個人PPL | **HumanEval** |
|---|---:|---:|---:|---:|---:|
| **混合96** | 48 | **8.62GB** | 21.1 | 12.70 | **84.8%** |
| L44-drop4 | 44 | 7.93GB | 21.8 | 13.10 | **76.2%** |
| 88専門家 | 48 | 7.96GB | 20.3 | 13.13 | 73.2% |
| 混合80 | 48 | 7.30GB | 20.7 | 14.66 | 78.0% |
★ **PPLは +3.2% しか動いていないのに HumanEval は −8.6ポイント。**
実験48-K とまったく同じ罠。**PPLは滑らか、能力は崖。**3回目の実証。
★★ 対応のある比較(同じ164問・温度0なので決定的):
```
混合96 対 L44層抜き : +14 (n=24, 2√n=9.8) → ★有意
混合96 対 88専門家 : +19 (n=25, 2√n=10.0) → ★有意
混合96 対 混合80 : +11 (n=21, 2√n=9.2) → ★有意
L44層抜き 対 88専門家 : +5 (n=35, 2√n=11.8) → ノイズ
L44層抜き 対 混合80 : −3 (n=31, 2√n=11.1) → ノイズ
88専門家 対 混合80 : −8 (n=26, 2√n=10.2) → ノイズ
```
→ **8.62GB より下は、どう削っても同じ 73〜78% の帯に落ちる。**
層を抜こうが、専門家をもっと抜こうが、**行き先は同じ。**
**8.62GB(96専門家 × 48層)が本物の作業点で、その少し下に崖がある。**
★ 運用の結論: **主力は 混合96 のまま。L44-drop4 は主力にしない。**
0.69GB と +6% の速度のために 8.6ポイントは高すぎる。
ただし**層抜きの道具と知見は残る**(速度が本当に要る場面/別の土台では使える)。
### 52-I. ★★ ik_llama.cpp の llama-server は連続大量要求で破綻する(本番採用の前提が崩れた)
実験50-C で「本番を ik に切り替える」と勧めたが、**HumanEval 164問を流すと壊れた**。
```
ik + 4ワーカー × 164問 : pass@1 5.5%(空153件) ← 混合96。本家では 84.8%
本家 + 4ワーカー × 164問 : pass@1 84.8%(空0件)
```
切り分け(すべて `/v1/chat/completions`・同じモデル):
```
ik 単発1件 : ○ 正常
ik 同時4件 : ○ 4/4 正常
ik 4ワーカー×16件 : ○ 16/16 正常
ik 4ワーカー×164件 : ✗ 11件しか処理されない
```
サーバログの解析で原因が見えた:
```
slot released が 11件しかない。id_task が 3,5,6,4,34,67,203,180,232,306… と
**164を超えて飛ぶ**。released が無い id_task が293個。
→ **ikはスロットが即座に空いていないと、待たせずに空を返している**(本家は待ち行列に入れる)。
ワーカーが応答を受け取って即座に次を投げると、解放が間に合わず弾かれ、それが連鎖する。
```
★ **暫定の結論: 測定と本番の serving は本家 llama-server を使うこと。**
ik は llama-bench(単発)では信頼できるが、**サーバとしては未検証**。
回避策の候補: `-np` をクライアント数より多くする(例: 4クライアントに `-np 8`)。要検証。
★★ **反省: 実験51-C の「同時4本で2倍」は、トークン数しか数えておらず中身を検証していなかった。**
幸い -np 4 の少数要求では中身も正常だったが、**スループットを測るときは必ず出力の正しさも見ること。**
### 52-J. ik の破綻の切り分け(再現条件は掴んだが根本原因は未特定)
| 条件 | 結果 |
|---|---|
| ik 単発1件 | ○ |
| ik 同時4件(同じプロンプト) | ○ 4/4 |
| ik 4ワーカー × 16件(合成プロンプト・max200) | ○ 16/16 |
| **ik 4ワーカー × 8件(本物のHumanEval・max512)** | **○ 空0件** |
| **ik 4ワーカー × 24件(本物のHumanEval・max512)** | **○ 空0件** |
| **ik 4ワーカー × 164件** | **✗ 最初の11件だけ成功、12件目から全部空。84秒で終了** |
| 本家 4ワーカー × 164件 | ○ 空0件・84.8% |
・`-np 8` に増やしても直らない(2.4%)。**スロット不足ではない。**
・サーバを新しく立て直しても同じ(3.0%)。**前の実行の残骸でもない。**
・書き込み順で **先頭11件が成功し、12件目以降が全滅**。きれいな折れ方。
・24件は通るのに164件は11件で折れる。**件数だけの問題でもない**(矛盾が残っている)。
★ 唯一の差は「24件のときは2回の実行の間にサーバが一瞬遊んだ」こと。**未検証の手がかり。**
**結論: 根本原因は未特定。ik の llama-server は連続大量要求で信用できない。**
→ **測定と本番の serving は本家 llama-server を使う。ik は llama-bench 専用にする。**
ik の速度優位(tg +13% / pp +136%)は本物だが、**サーバとしては使えないので現時点では享受できない。**
次にやるなら: ①24件ごとにサーバを立て直す運用 ②ik のサーバのソースを読む
③本家の新しい版で ik 相当の速度が出ないか確かめる