PPL(Perplexity)評価のためにアンセンサー付きGGUFを使いながら、交差エントロピー差を2つの和(正と負;あるいはPPLを1より大きい比と1より小さい比の2つ)に分けるという考えがありました。
きっかけは、PPL比の収束プロット(2ndグラフ)で、PPL比の下の面積を見て「正と負の面積を2次元に散布したらどうなる?」と思ったことです。
結局:
- 負のデルタ => ベースモデルよりもテキストをより良く予測したことになります。アンセンサー付きモデルは、検閲ありのデータセットで評価したときに高いスコアを取るべきです(改善/アンセンサーの知識との相関—高品質なデータセットであると仮定)。
- 正のデルタ => ベースモデルよりもテキストを予測するのが悪かったことになります。劣化/微調整との相関があります。完璧なアンセンサー付きモデルは(データセットが検閲を“報酬”としていない限り)0にあるはずで、そうすることでベースモデルと同じくらい賢さを保ちます。
つまり、Yが小さいほど元のモデルに近く、Xが大きいほどよりアンセンサー寄りです。
グラフの解釈は、あなたに任せます。
* すべてのモデルはQ8_0で、Q8_Kだけ例外です。参照(reference)は常に、mradermacherからの静的量子化です。
* BPB(Bits-per-Bytes)サブプロットだけは正規化されており、3つのモデルすべてで比較可能です。
注:
llama-perplexity.exe は単一ファイルのPPLを出力するので、多数のファイルに対して平均を取ればよいです:
diff = np.log(df['ppl_cmp']) - np.log(df['ppl_ref']) df['ppl_gain'] = np.exp(np.minimum(diff, 0)) df['ppl_loss'] = np.exp(np.maximum(diff, 0))
私の環境では、これが同一のMeanプロットを生成することを確認しています。
しかし本当のコツは、PPL平均で失われてしまう形状情報を回復するために、系列長に沿ってトークンごとの符号付きデルタを計算し、各ファイルごとに正のデルタ和/負のデルタ和を得ることです。
これによってデータセット全体を散布して等高線を可視化できました。私は本質的に、{Gain X=(1⁄N)∑(log p_cmp-log p_ref) | p_cmp>p_ref; Loss Y=(1⁄N)abs∑(log p_cmp-log p_ref) | p_cmp<p_ref}
を散布しています(注: PPL比はNLLを使うのに対して、こちらはログイッツキャッシュからのLLなので逆に見えますが、{X=(1⁄N)abs∑(I(cmp)-I(ref)) | I(cmp)<I(ref)}としても見られます。など)。
それをスマートにやるなら、llama-perpexity.exe をperplexity.cpp:kl_divergence() 内に単純なforループを追加して再コンパイルし、2つの符号付きデルタ和をLOG()して、その値をPythonから読み戻すのが良いでしょう。
ただ、思いついたのが遅すぎて、結局 --save-all-logits を2回呼び、NumPyでログイッツファイルを手作業でパースすることになりました。
このときのデータセットは、だいたい 1/3がコード、1/3が多言語、1/3がnsfw(AO3)/4chan/anarchy cookbooks…という感じで、最高のアンセンサー付きデータセットではありません。ただしPPLを使うことの弱点で、tiny promptではk-Refusalsを走らせられず、実際の(高品質な)ドキュメントが必要になります。
私が最初に犯したミスは、古いllama-cpp-pythonでgemmaを評価したことです。pip +gitのやり方を知ったのが遅すぎて、誤ったトークン数のデバッグにかなり時間を浪費しました。
2つ目のミスは、chunkedとstridedのperplexityの違いが分からず、ツールの動作がどうなっているのかを(基本的に最後の最後まで)理解できなかったことです。
今となっては、perplexityの中に、渡すファイルが2*n_ctxサイズである必要があるという誤ったセイティーチェックがあるのではないかとかなり確信しています。
後から考えると筋が通りません。なぜならデフォルトのPPL計算はchunkedだからです(chunk/contextサイズ -cを選ぶと、バックエンドに応じて256単位で切り上げまたは切り下げされます(らしい))。そのchunkの前半がコンテキストで、後半がPPLに使われます。つまり最後のトークンは生成されないので、PPLは正確にtokens[ctx//2:ctx-1]になります。少なくとも私は、基本的にすべてを--chunks 1 -c {min(8196, file_tokens)}として実行していました。)
いずれにせよ、私は素直に、PPLのためにはコンテキストサイズのchunkを2つ丸ごと必要だと信じてしまい、早期にクラッシュしないようにc=c//2を設定していました。
その結果、データセット内の小さなファイルはすべて、ツールのためにコンテキストが半分に切られてしまいました。その時点では、9730回の評価を最初からやり直す(約30時間)はしないつもりでしたが、おそらくかなりの精度を失ったと思います。
やり直すなら、単にperplexityに渡す前に、すべてのファイルをダミートークンでパディングするだけです:data+="
"*c。
追記:
ここに至る原因となった失敗実験の内容を投下します:
- [Qwen3.5-4B-Q5_normalized], [Q5_unnormalized], [Q8_unnormalized_wrong_scale] を見たことで、imatrixは静的量子化よりも厳密に優れていることは少なくとも納得できました。しかし「トピック」ではなく「言語構造」のクラスターを抽出してしまっており、失敗でした。さらにデータを移す際にスケールをミスってしまったので、相対的でない限りQ8の結果は信頼できません。(注: 正規化プロットは、imatrix-techの効率を比較するためにファイルサイズを調整しています。)
- [Qwen3.5-4B_heretic_uncensored_models_comparison] これは、KLDは量子化(quant)同士の比較にしか使えない(微調整や別モデルの比較には使えない)と分かったので、「知識の絶対的な尺度」としてPPL vs PPLをプロットすることにしたのですが、それほど良くはありませんでした。後で、自分のデータセットはアンセンサー付きではないことに気づきました。さらにオープンなデータセットは小さなプロンプトを公開していて全文ではないため、こちらもPPLを取れませんでした。
ほぼ諦めかけたのですが、その後に負の積分と正の積分のことを思い出して、もう一度散布してみる必要があると分かりました。
- かっこいい画像: [logits](151k語彙のうちの小さな2kの角)と [hidden_states](ログイッツをhidden statesとして圧縮しようとしたときのもの。動かすのは完全な悪夢で、gemmaに切り替えた瞬間に案の定壊れました)。結局、ログイッツへのSVD+TOP-k圧縮を試したところ、最終的には、毎回ramdisk上で再計算して、実行ごとの書き込みを635GB分節約することになりました。
- 面白い事実: これをやっている間に、少なくとも5900Xを5回クラッシュさせました。誰かが躓きそうなら、Cool'n'Quiet/C-states/TypicalCurrentIdleをオフにして、3200Mhzまでダウンクロックすることでようやく直ったようです。
submitted by