数を小さくすると、モデルも小さくなる
モデルを量子化するとは、中の数をより少ないビットで保存することです。モデルが使うメモリは減りますが、代わりにどの数も丸められます。
大きなモデルを小さなパソコンに詰め込むのは、この丸めのおかげです。結果を信じる前に、何を払ったのかを知っておく必要があります。
このレッスンのゴール
メモ
数を小さくすると何が失われるかを説明できる。
キーワード
| ことば | いみ |
|---|---|
| 量子化 | 数を、決まった少ない段に丸めること |
| 段 | 数が丸められる先の値の一つ |
| スケール | 表の中で、絶対値がいちばん大きい値 |
| 誤差 | 元の数と、丸めて戻した数との差 |
| 外れ値 | 他よりずっと大きな値 |
学生が入力するセルは、このプレビューでは空白です。
ビットが少ないと、段も少ない
8 ビットなら、数は 255 の段のどれかに置けます。4 ビットでは 15 段、2 ビットでは 3 段しかありません。どの数も、いちばん近い段に動かされます。
段を決めるには、各値をスケール(絶対値の最大)で割り、いちばん上の段を掛けて、丸めます。数を戻すには、手順を逆にたどります。
八つの数を四つの精度で
| ビット | 段の数 | 残る整数 | 最大の誤差 |
|---|---|---|---|
| 8 | 255 | 127 -59 10 71 -124 44 -7 90 | 0.0031 |
| 4 | 15 | 7 -3 1 4 -7 2 0 5 | 0.0586 |
| 3 | 7 | 3 -1 0 2 -3 1 0 2 | 0.12 |
| 2 | 3 | 1 0 0 1 -1 0 0 1 | 0.42 |
学生が入力するセルは、このプレビューでは空白です。
丸めがいちばん痛むところ
先に痛むのは、小さな数です。4 ビットでは -0.05 が 0 になり、失われました。2 ビットでは、八つのうち四つが 0 になります。
外れ値が一つあると、全員が痛みます。値の一つが 9.0 なら、スケールが 9.0 になり、段の間が広がって、小さな数はみな 0 に丸まります。
よくある間違い
注意
「ビットを減らすと、全体が少しぼやけるだけだ」と考えがちです。実際は均等ではありません。小さな数は消え、外れ値が一つで表全体を壊すことがあります。量子化した表は、信じる前に、必ず誤差を測ります。
モデル用の補助を書く(1)
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const base = process.env.AI_URL || \"http://ai:4000\";' '// 応答に含まれるトークン数。なければ null。' 'function count(data, name) {' ' return data.usage && typeof data.usage[name] === \"number\" ? data.usage[name] : null;' '}' > model.js"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
モデル用の補助を書く(2)
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'async function ask(messages, sample, max) {' ' try {' ' const res = await fetch(base + \"/v1/chat/completions\", {' ' method: \"POST\",' ' headers: { \"content-type\": \"application/json\", authorization: \"Bearer lab\" },' ' body: JSON.stringify({ model: \"lab\", messages: messages, max_tokens: max || 100 }),' ' signal: AbortSignal.timeout(30000),' ' });' ' if (res.ok === false) throw new Error(\"status \" + res.status);' ' const d = await res.json();' ' return { text: d.choices[0].message.content, inTokens: count(d, \"prompt_tokens\"), outTokens: count(d, \"completion_tokens\"), recorded: false };' ' } catch (error) {' ' console.log(\"記録済みサンプル: モデルが答えませんでした (\" + error.message + \")。\");' ' return { text: sample, inTokens: null, outTokens: null, recorded: true };' ' }' '}' 'module.exports = { ask };' >> model.js"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
量子化するスクリプトを書く(1)
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const { ask } = require(\"./model\");' 'const values = [0.9, -0.42, 0.07, 0.5, -0.88, 0.31, -0.05, 0.64];' 'function quantise(list, bits) {' ' const top = Math.pow(2, bits - 1) - 1;' ' const scale = Math.max(...list.map(Math.abs));' ' return list.map(function (v) { return Math.floor(v / scale * top + 0.5); });' '}' > quant.js"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
量子化するスクリプトを書く(2)
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'function restore(q, list, bits) {' ' const top = Math.pow(2, bits - 1) - 1;' ' const scale = Math.max(...list.map(Math.abs));' ' return q.map(function (n) { return n * scale / top; });' '}' >> quant.js"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
量子化するスクリプトを書く(3)
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'function worst(list, bits) {' ' const back = restore(quantise(list, bits), list, bits);' ' let w = 0;' ' list.forEach(function (v, i) { w = Math.max(w, Math.abs(v - back[i])); });' ' return w;' '}' >> quant.js"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
量子化するスクリプトを書く(4)
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'async function main() {' ' for (const bits of [8, 4, 3, 2]) {' ' console.log(bits + \" ビット: \" + quantise(values, bits).join(\" \") + \" | 最大の誤差 \" + worst(values, bits).toFixed(4));' ' }' ' console.log(\"9.0 を一つ足した場合、4 ビット、最大の誤差 \" + worst(values.concat([9.0]), 4).toFixed(4));' ' const q = \"数をより少ないビットで保存すると何が失われるかを、短い一文で書いてください。\";' ' const sample = \"どの数も近い段に丸められるので、数どうしの小さな違いが失われます。\";' ' const reply = await ask([{ role: \"user\", content: q }], sample, 60);' ' console.log(\"返事: \" + reply.text);' '}' 'main();' >> quant.js"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
量子化して誤差を測る
docker exec -w /home/node/project agent node quant.jsここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
出力の例
| 行 | 示していること |
|---|---|
| 8 ビット: ... 最大の誤差 0.0031 | ほとんど何も失われない |
| 4 ビット: 7 -3 1 4 -7 2 0 5 ... | 0.05 が 0 になった |
| 2 ビット: ... 最大の誤差 0.4200 | 小さな値の多くが消えた |
| 9.0 を一つ足した場合 ... | 外れ値が一つで、誤差が大きくなる |
| 返事: ... | モデルの一文。言葉は毎回違う |
学生が入力するセルは、このプレビューでは空白です。