はじめに
2つのLLMを比較していました。あるモデルと、その改変版です。同じ12問を投げて、出力の違いを見る。
再現性には気を配ったつもりでした。temperature: 0 で貪欲法にして、seed: 42 を固定する。これで決定的になるはずです。
そして差分を見つけました。「改変版は日本語の敬語で劣化している」「改変版のほうが簡潔だ」「改変版に中国語の漢字が混入している」。数字も実例も揃っていて、記事にできる材料だと思いました。
全部間違いでした。
原因はモデルの違いではなく、モデルごとに異なる既定パラメータです。気づいたのは ollama show の diff に出たたった1行からでした。
執筆時点(2026年7月)、Ollama v0.32.5 での記録です。
先にまとめ
temperature: 0は決定性を保証しない。 ペナルティ系パラメータはロジットを書き換えるので、貪欲法でも選ばれるトークンが変わります。- モデルごとに既定パラメータが違う。 Modelfile に何が焼かれているかは
ollama showでしか分かりません。 - 短い出力で速度を測ってはいけない。 4トークンの生成では26%の誤差が出ました。
比較実験をするなら、最初に ollama show の diff を取る。これだけで今回の遠回りは全部避けられました。
誤った結論を3回出した
時系列で並べます。
第1回: 「元モデルは敬語を間違える」
最初は temperature: 0 と seed: 42 だけを指定していました。
設問は「『資料を見た』を尊敬語と謙譲語に言い換えてください」。元モデルの回答です。
尊敬語:資料をご覧になった
謙譲語:資料をお見せしました ← 誤り「お見せする」は「見せる」の謙譲語で、「見る」の謙譲語ではありません。正解は「拝見する」です。
**「27Bクラスでも日本語の敬語は間違える」**という結論を出しました。
第2回: 「改変版は敬語で劣化している」
比較相手のモデルカードを見たところ、既定パラメータが違うことに気づきました。presence_penalty: 1.5 などが設定されています。これでは比較にならないので、両モデルで明示的に揃えることにしました。
{
"temperature": 0,
"seed": 42,
"presence_penalty": 0,
"top_k": 40,
"top_p": 0.9,
"min_p": 0
}ここで妙なことが起きます。元モデルの回答が変わりました。
第1回: 謙譲語:資料をお見せしました ← 誤り
第2回: 謙譲語:資料を拝見した ← 正解このとき「サンプリングのペナルティが文法的な誤りを引き起こしていた」と解釈しました。ここまでは正しかったのですが、問題は次です。
同じ条件で改変版を流すと、今度は改変版が間違えました。
改変版: 謙譲語:資料をお目通ししました ← 誤り**「改変版は日本語の敬語で明確に劣化している」**という結論を出しました。条件を揃えたうえでの差なので、確度が高いと考えたのです。
同時に、他の「発見」も揃いました。
| 観測 | 当時の解釈 |
|---|---|
| 改変版が一貫して短い(上限到達5問→1問) | 改変版は簡潔にまとめる傾向 |
| 改変版に「陡」という中国語漢字が混入 | 改変の副作用で異言語が漏れる |
| 改変版が実在しない動物名を出した | 知識表現が壊れている |
| 改変版が18.5 tok/秒(元は14.7) | なぜか速い |
5つの発見が揃い、記事の構成まで見えていました。
第3回: 「1行の diff」
念のため、両モデルの構成を比較しました。
ollama show qwen3.6:27b > ~/show-base.txt
ollama show huihui_ai/Qwen3.6-abliterated:27b > ~/show-abl.txt
diff ~/show-base.txt ~/show-abl.txt17d16
< repeat_penalty 1元モデルには repeat_penalty 1 が明示されているのに、改変版にはこの行がない。
Ollama の repeat_penalty の既定値は 1.1 です。つまり実際に適用されていた値は、
| repeat_penalty | |
|---|---|
| 元モデル | 1.0(Modelfile で明示) |
| 改変版 | 1.1(既定値が適用) |
そして私のスクリプトは repeat_penalty を指定していませんでした。2つのモデルを、違う条件で比較していたわけです。
repeat_penalty を揃えたら、発見が全部消えた
両モデルに repeat_penalty: 1.0 を明示して流し直しました。
敬語: 完全一致
元モデル : 尊敬語:資料をご覧になった / 謙譲語:資料を拝見した
改変版 : 尊敬語:資料をご覧になった / 謙譲語:資料を拝見した一字も違いません。 改変版は「拝見する」を知っていました。
「簡潔さ」: 消滅
上限1000トークンで切断された問題の数です。
| 切断数 | |
|---|---|
| 元モデル | 5問 |
| 改変版(rp=1.1) | 1問 |
| 改変版(rp=1.0) | 4問 |
「改変版は簡潔にまとめる」という特性は、repeat_penalty: 1.1 による反復抑制でした。 揃えた途端、元モデルとほぼ同じだけ書くようになりました。
中国語漢字「陡」: 消滅
grep してもヒットしません。反復ペナルティが正常な候補を抑制した結果、通常なら選ばれない字が浮上していただけでした。
実在しない動物名: 消滅、そして反転
改変版が出していた捏造動物名は消えました。そして——
元モデルのほうには、別の捏造動物名が4箇所すべて残っていました。
「改変版の知識が壊れている」という結論は誤りで、むしろ元モデルのほうにこの問題があるという真逆の結果です。
速度: 誤差だった
300トークン級の生成で測り直しました。
元モデル : 14.5 tok/s / 177 tok
改変版 : 14.6 tok/s / 188 tok差は0.7%。 18.5 tok/秒という数字は、出力4トークン(「391」という答え)で測ったものでした。この規模では初回トークンの遅延が支配的で、意味のある値になりません。
なぜ temperature: 0 で足りないのか
ここを誤解していました。
temperature: 0 は「確率分布を鋭くして、最も確率の高いトークンを選ぶ」設定です。貪欲法(greedy decoding)になるので、同じ入力なら同じ出力になる——と考えていました。
しかしペナルティ系パラメータは、「選ぶ前」に働きます。
モデルがロジットを出力
↓
repeat_penalty / presence_penalty がロジットを書き換える ← ここ
↓
temperature が確率分布を調整
↓
最も確率の高いトークンを選ぶ(temperature=0)ペナルティで順位が入れ替われば、貪欲法でも別のトークンが選ばれます。
今回の「拝見」→「お目通し」がまさにこれでした。既出のトークンにペナルティがかかり、本来1位だった候補が2位以下に落ちて、意味の違う語が選ばれた。
seed も同様に無力です。seed はサンプリングの乱数を固定しますが、temperature=0 では乱数を使わないので、そもそも影響しません。「seedを固定したから再現する」というのは二重に誤りでした。
揃えるべきパラメータ
比較実験では、少なくとも以下をすべて明示すべきでした。
{
"temperature": 0,
"seed": 42,
"num_predict": 1000,
"repeat_penalty": 1.0,
"presence_penalty": 0,
"frequency_penalty": 0,
"top_k": 40,
"top_p": 0.9,
"min_p": 0
}「既定値だから同じだろう」が通用しません。 既定値はモデルごとに Modelfile で上書きされうるからです。
ollama show の diff を最初に取る
今回の教訓を1つに絞るならこれです。
ollama show <model-a> > a.txt
ollama show <model-b> > b.txt
diff a.txt b.txt出力にはパラメータだけでなく、チャットテンプレートも含まれます。テンプレートが違えば、同じプロンプトでもモデルに届く文字列が変わります。abliterated 系のモデルではテンプレートが壊れていることもあるので、ここの確認は必須でした。
今回はテンプレートに差分がなく、唯一の差が repeat_penalty の1行でした。逆に言えば、この1行を見逃していたら最後まで気づかなかったということです。
そして厄介なのは、誤った結論に説得力があったことです。
「改変版は日本語の敬語で劣化し、簡潔になり、中国語の漢字が混入する」
数字と実例が揃っていて、それらしく読めます。しかも「重みをいじれば劣化するはずだ」という事前の予想とも一致していました。確認バイアスが働きやすい形をしていたわけです。
その他の測定ミス
ついでに踏んだものを記録しておきます。
短い出力で速度を測った
4トークンの生成で 18.5 tok/秒、188トークンで 14.6 tok/秒。26%の差はすべて測定ノイズでした。
モデルのロード直後の1トークン目には他と違う遅延があり、出力が短いほどその影響が支配的になります。最低100トークン、できれば300トークン以上で測るべきでした。
diff の行数を評価指標にしようとした
12問の出力を丸ごと diff したら 840行出ました。「これだけ違う」と言いたくなりますが、この数字に意味はありません。
散文は一度どこかで分岐すると、以降がすべて差分になります。冒頭の1トークンが違えば残り全部が「変更あり」です。1000トークン級の長文を4問含んでいるので、840行は必然でした。
評価は短答問題の完全一致と、特定の誤りの有無で行うべきでした。
モデルカードの params を見ていなかった
Ollama のモデルページには params の項目があり、そのモデルに焼かれた既定値が書かれています。改変版には temperature: 1、top_k: 20、presence_penalty: 1.5 などが設定されていました。
第2回でここを見たから presence_penalty は揃えられたのですが、repeat_penalty はページに表示されていませんでした。Web の情報だけでは足りず、ollama show が必要だった、ということです。
ハマりどころまとめ
| 症状 | 原因 | 対処 |
|---|---|---|
| temperature 0 なのに出力が変わる | ペナルティ系がロジットを書き換える | 全ペナルティを明示指定 |
| モデルAとBで挙動が違う | Modelfile の既定値が異なる | ollama show の diff |
| 速度が26%違う | 4トークンで測定 | 100トークン以上で測る |
| diff が数百行出る | 散文は一度分岐すると全部差分 | 短答問題で評価する |
| 「劣化した」ように見える | 交絡変数 | モデル以外が同一であることを検証 |
やってみての所感
対照実験は「条件を揃える」が9割
比較実験の本体は、実は測定ではなく条件を揃える作業でした。そしてそこを軽く見ていました。
「温度0にした」「seedを固定した」という2点で満足してしまい、それ以外に何が違いうるかを列挙していませんでした。列挙していれば ollama show を最初に叩いていたはずです。
変数を1つに絞ったつもりで、実は2つ動いていた。 古典的な失敗を、そのままやりました。
予想と一致する結果ほど疑うべきだった
「abliteration は劣化を伴うはずだ」という予想を持って実験に入りました。そして劣化らしきものが見つかったとき、追加の検証をせずに結論に飛びついています。
もし逆の結果——「改変版のほうが優秀」——が出ていたら、きっと原因を疑ったはずです。予想どおりの結果が出たときこそ、検証を厚くする必要がありました。
3回とも、撤回のきっかけは「念のため」の確認でした。念のためパラメータを揃えてみる、念のため単独で問い直してみる、念のため ollama show を比べてみる。この「念のため」がなければ、誤った記事を公開していました。
撤回できたのは記録があったから
各回の出力をファイルに保存していたのが効きました。
~/quiz.sh model-a > ~/result-base.txt 2>&1
~/quiz.sh model-b > ~/result-abl.txt 2>&1条件を変えるたびに別ファイルに残していたので、「第1回と第2回で元モデルの出力が変わった」ことに気づけました。画面に流して読んでいただけなら、この変化は記憶に残らなかったと思います。
トークン数を出力に含めていたのも大きく、grep "tok=" で一覧が取れました。数字が並ぶと異常が目に入ります。
まとめ
2つのLLMを比較して、3回誤った結論を出しました。原因はモデルの違いではなく、repeat_penalty の既定値が 1.0 と 1.1 で異なっていたことです。
temperature: 0 は決定性を保証しません。ペナルティ系パラメータはトークンを選ぶ前にロジットを書き換えるため、貪欲法でも結果が変わります。そして既定値はモデルごとに違います。
対処は簡単で、比較を始める前に ollama show の diff を取る。差分があれば全部明示指定して潰す。それだけで今回の遠回りは避けられました。
一番怖かったのは、誤った結論に説得力があったことです。数字も実例も揃い、事前の予想とも一致していました。もし ollama show を叩いていなければ、それらしい記事を自信を持って公開していたはずです。
比較実験では、測定結果より先に測定条件のほうを疑う。今回いちばんの学びでした。