大規模言語モデル(LLM)を手元の GPU で動かすとき、まず効いてくるのがメモリです。70B パラメータのモデルは、16 ビットのままだと重みだけで約 140 GB になります。そこで、学習済みの重みをあとから少ないビット数で表す 学習後量子化(PTQ:Post-Training Quantization) が広く使われています。
2 ビット前後の極端な低ビットでは、複数の重みをまとめて 1 つのインデックスで表す ベクトル量子化(VQ) が有力です。最近の QTIP や LLVQ は、トレリスや格子の構造を使って実質的な符号化の次元を上げることで精度を伸ばしてきました。一方で、そうした構造があると、小さなコードワードを独立に引いたり、局所的に書き換えたりしにくくなります。
2026年10月に arXiv に投稿された論文「CoS-VQ: Coordinated Shared-Scale Vector Quantization for Large Language Models」は、コードワードは 6 次元の小さいまま、同じスケールを共有する複数の位置のインデックスとスケールを 同時に 選び直すことで、高次元の構造化量子化に近い精度を出そうとする手法です。
この記事では、次の 3 点を数式を追いながら解説します。
- スケールを共有すると、なぜ「1 か所ずつ直す」最適化が止まってしまうのか(論文の 2 点の例を Python で正確に再現します)
- 層出力の誤差を、位置ごとに分離できる上界(majorizer)で置き換える方法とその証明
- スケールの範囲を「直線の下側包絡」で区間に分け、組み合わせを総当たりせずにインデックスとスケールを同時に最適化する方法(自分で実装し、総当たりと結果が一致することを確かめました)
対象読者:LLM の量子化(GPTQ、AWQ、AQLM など)の名前を聞いたことがあり、その中身の最適化に興味がある方。線形代数(二次形式、ノルム、正定値行列)の基本を前提にします。
本論文は 2026年10月27日に arXiv に公開されたプレプリントで、査読前のものです。記事中の実験結果は論文に書かれている値で、筆者が LLM の量子化実験を再現したものではありません。記事のコードは、論文の数式とアルゴリズムの考え方を小さな問題で確かめるものです。
論文の概要
| 項目 | 内容 |
|---|---|
| タイトル | CoS-VQ: Coordinated Shared-Scale Vector Quantization for Large Language Models |
| 著者 | Zukang Xu, Zhixiong Zhao, Xing Hu, Zhe Jiang, Xin Si, Dawei Yang(Houmo AI, Peking University, Southeast University) |
| 公開 | arXiv:2610.37336(2026年10月27日、cs.LG / cs.AI) |
| コード | xzktx003/Cos_VQ(GitHub) |
| 何をしたか | 共有スケールを持つ低次元 VQ で、ハードなインデックスとスケールを同時に最適化するソルバーを提案 |
| 評価 | Qwen3(4B〜32B)、Llama-3.1(8B / 70B Instruct)、Qwen3.8-27B(thinking あり)を約 2 ビット/重みで量子化 |
主な結果は次のとおりです(論文の報告値)。
- 6 モデル中 5 モデルで、比較した量子化手法の中で平均パープレキシティが最も低く、3 モデルで平均正解率が最も高い
- Qwen3.8-27B(thinking あり)で、QTIP と LLVQ を言語モデリング、コーディング(LiveCodeBench v6)、GPQA-Diamond で上回る
- 同じ初期値からのアブレーションで、スケールだけを最適化するよりも、インデックスを同時に選び直すほうが改善がずっと大きい
- RTX 4090 上の Llama-3.1-8B-Instruct で、デコードのスループットが BF16 の 21.9 tok/s から 36.7 tok/s(1.68 倍)
共有スケールのベクトル量子化
何をどう量子化するか
重み行列 $W \in \mathbb{R}^{p \times n}$ を $d$ 次元ずつの「位置」に分け、各位置 $i$ を、固定のコードブック $\mathcal{C}_i \subset \mathbb{R}^d$ から選んだコードワード $c_{k_i}$ と、スケールグループ $G_g$ で共有するスケール $s_g$ の積で表します。
\hat{w}_i = s_g\, c_{k_i}, \qquad c_{k_i} \in \mathcal{C}_i \subset \mathbb{R}^d
$k_i$ がコードブックのインデックスで、保存するのはこのインデックスとスケールだけです。論文の主な設定は次のとおりです。
- コードワードの次元 $d = 6$、コードブックの大きさ $K = 4096$(12 ビットのインデックス)
- 入力 128 列ごとに FP16 のスケールを 1 つ
インデックスは 6 個の重みで 12 ビットなので 2 ビット/重み、スケールは 128 個の重みで 16 ビットなので約 0.125 ビット/重み です。論文の付録では、コードブック自体やメタデータも含めて実効ビット数を数えるべきだとしています。
\mathrm{bpw} = \frac{B_{\mathrm{indices}} + B_{\mathrm{scales}} + B_{\mathrm{codebooks}} + B_{\mathrm{aux}}}{N_{\mathrm{weights}}}
何を最小化するか
キャリブレーション入力 $X \in \mathbb{R}^{n \times N}$ に対する層出力の誤差を最小化します。
F(\widehat{W}) = \| W X - \widehat{W} X \|_F^2
重みそのものの誤差ではなく、その層の出力がどれだけ変わるか を測っている点が重要です。入力 $X$ を通すことで、異なる位置の誤差どうしが打ち消し合ったり強め合ったりする「位置をまたいだ相互作用」が生じます。
なぜ「1 か所ずつ直す」と止まるのか
スケールが全体の選択を結びつける
まず、層出力ではなく単純なユークリッド誤差 $E(\mathbf{k}, s) = \sum_i \| w_i - s\, c_{k_i} \|_2^2$ で考えます。インデックス $\mathbf{k}$ を固定すると、最適なスケールは
s^*(\mathbf{k}) = \frac{\sum_i w_i^\top c_{k_i}}{\sum_i \| c_{k_i} \|_2^2}
です。スケールを固定すれば、各位置は自分の誤差が最小になるコードワードを独立に選べます(分離可能)。ところが、スケールを選び直すと $s^*$ は すべての位置の選択に依存 し、そのスケールが今度は各位置での候補の順位を変えます。論文はこれを「スケールが引き起こす結合(scale-induced coupling)」と呼んでいます。
この結合のせいで、「1 か所だけインデックスを変え、スケールを最適に選び直す」という座標ごとの更新では、どの 1 か所を変えても誤差が下がらないのに、複数か所を同時に変えると下がる、という状態が起こります。
2 点の例で確かめる
論文の付録 A.2 に、手で確かめられる例があります。目標の重みが $w_1 = w_2 = 1$ の 2 つのスカラーの位置で、コードブックは $\mathcal{C}_1 = \{1, 3\}$、$\mathcal{C}_2 = \{6/5, 3\}$、共有スケールの範囲は $s \in [1/10, 2]$、現在の割り当ては $(a, b) = (1, 6/5)$ です。
割り当て $(a, b)$ を固定したときの誤差は
E(a, b; s) = (1 - s a)^2 + (1 - s b)^2
で、$\partial_s E = 2(a^2 + b^2)s - 2(a + b) = 0$ から
s^*(a, b) = \frac{a + b}{a^2 + b^2}, \qquad E^*(a, b) = \frac{(a - b)^2}{a^2 + b^2}
となります。4 通りすべてを有理数で正確に計算しました。
from fractions import Fraction as Fr
from itertools import product
C1 = [Fr(1), Fr(3)] # 位置 1 のコードブック
C2 = [Fr(6, 5), Fr(3)] # 位置 2 のコードブック
S_MIN, S_MAX = Fr(1, 10), Fr(2)
def best_scale(a, b):
"""w1 = w2 = 1 に対して E = (1 - s a)^2 + (1 - s b)^2 を s で最小化する"""
s = (a + b) / (a * a + b * b)
s = min(max(s, S_MIN), S_MAX)
return s, (1 - s * a) ** 2 + (1 - s * b) ** 2
current = (Fr(1), Fr(6, 5))
for a, b in product(C1, C2):
s, err = best_scale(a, b)
moved = sum(x != y for x, y in zip((a, b), current))
label = {0: "現在", 1: "1 か所だけ変更", 2: "2 か所同時に変更"}[moved]
print(f"(a, b) = ({a}, {b}): s* = {s}, 誤差 = {err} ≈ {float(err):.6f} [{label}]")
(a, b) = (1, 6/5): s* = 55/61, 誤差 = 1/61 ≈ 0.016393 [現在]
(a, b) = (1, 3): s* = 2/5, 誤差 = 2/5 ≈ 0.400000 [1 か所だけ変更]
(a, b) = (3, 6/5): s* = 35/87, 誤差 = 9/29 ≈ 0.310345 [1 か所だけ変更]
(a, b) = (3, 3): s* = 1/3, 誤差 = 0 ≈ 0.000000 [2 か所同時に変更]
論文の表 7 と同じ値になりました。現在の誤差 $1/61$ に対し、どちらか 1 か所だけを変えると、スケールを最適に選び直しても $9/29$ や $2/5$ に悪化します。ところが両方を $3$ に変えて $s = 1/3$ にすると誤差は $0$ です。座標ごとの更新が止まっていても、同時の更新ならまだ下がる ことがわかります。論文の図 1 でも、実際の LLM の層で、1 か所ずつの更新が頭打ちになったあとに同時更新へ切り替えると誤差がさらに下がり、受け入れられた更新の多くが複数のインデックスを変えていたと報告されています。
ただし、総当たりで同時に選ぼうとすると、候補数 $K_i$ の位置が $M$ 個あれば組み合わせは $\prod_i K_i$ 通りになり、すぐに計算できなくなります。CoS-VQ はこれを 2 段階で解決します。
手法 1:位置ごとに分離できる上界を作る
層出力の誤差を二次形式で書く
あるスケールグループを選び、グループ外の量子化済みの位置は固定します。グループ内の現在の再構成を並べたベクトルを $\mathbf{z}_0$、その変更を $\boldsymbol{\delta} = \mathbf{z} - \mathbf{z}_0$ とします。$\boldsymbol{\delta}$ を重み行列に戻したときの層出力の変化を表す線形作用素を $A$、現在の層出力の残差を $\mathbf{r}_0 = \mathrm{vec}(WX - \widehat{W}_0 X)$ とすると、
F(\mathbf{z}_0 + \boldsymbol{\delta}) = \| \mathbf{r}_0 - A\boldsymbol{\delta} \|_2^2
= F(\mathbf{z}_0) + 2\mathbf{g}^\top \boldsymbol{\delta} + \boldsymbol{\delta}^\top H \boldsymbol{\delta},
\qquad H = A^\top A, \quad \mathbf{g} = -A^\top \mathbf{r}_0
と書けます。
- $\mathbf{g}$ は 層全体の現在の残差 から来ています。固定したグループ外の誤差も 1 次の項に入るので、各候補を「元の重みとの差」だけで評価するより正確です(論文はこれを residual-aware と呼んでいます)
- $H$ の非対角ブロック $H_{ij}$ が、位置 $i$ と $j$ を同時に変えたときの相互作用です。インデックスが離散なので、これを直接扱うのは困難です
ブロック対角の上界(majorizer)
そこで論文は、1 次の項はそのまま残し、2 次の項の位置をまたぐ部分を、ブロック対角の行列 $D$ で上から押さえます。
D_i = H_{ii} + \Big( \sum_{j \ne i} \|H_{ij}\|_2 + \varepsilon \Big) I_d,
\qquad D = \mathrm{blkdiag}(D_1, \dots, D_M), \quad \varepsilon > 0
$\|H_{ij}\|_2$ はブロックのスペクトルノルム(最大特異値)です。この $D$ について $D - H \succeq \varepsilon I$ が成り立ちます。証明は短いので追ってみます。任意の $\mathbf{u}, \mathbf{v}$ について
2\mathbf{u}^\top H_{ij} \mathbf{v} \le 2\|H_{ij}\|_2 \|\mathbf{u}\|_2 \|\mathbf{v}\|_2 \le \|H_{ij}\|_2 \big( \|\mathbf{u}\|_2^2 + \|\mathbf{v}\|_2^2 \big)
(前半はコーシー・シュワルツ、後半は相加相乗平均)なので、$H$ の対称性を使うと
\begin{aligned}
\boldsymbol{\delta}^\top H \boldsymbol{\delta}
&= \sum_i \boldsymbol{\delta}_i^\top H_{ii} \boldsymbol{\delta}_i + 2\sum_{i<j} \boldsymbol{\delta}_i^\top H_{ij} \boldsymbol{\delta}_j \\
&\le \sum_i \boldsymbol{\delta}_i^\top H_{ii} \boldsymbol{\delta}_i + \sum_{i<j} \|H_{ij}\|_2 \big( \|\boldsymbol{\delta}_i\|_2^2 + \|\boldsymbol{\delta}_j\|_2^2 \big) \\
&= \boldsymbol{\delta}^\top (D - \varepsilon I) \boldsymbol{\delta}
\end{aligned}
となります。これで、真の誤差を上から押さえる代理関数(接する分離可能な上界)が得られます。
Q(\mathbf{z} \mid \mathbf{z}_0) = F(\mathbf{z}_0) + 2\mathbf{g}^\top (\mathbf{z} - \mathbf{z}_0) + (\mathbf{z} - \mathbf{z}_0)^\top D (\mathbf{z} - \mathbf{z}_0)
$Q - F = (\mathbf{z} - \mathbf{z}_0)^\top (D - H)(\mathbf{z} - \mathbf{z}_0) \ge \varepsilon \|\mathbf{z} - \mathbf{z}_0\|_2^2 \ge 0$ なので、次の 3 つが成り立ちます。
F(\mathbf{z}) \le Q(\mathbf{z} \mid \mathbf{z}_0), \qquad
Q(\mathbf{z}_0 \mid \mathbf{z}_0) = F(\mathbf{z}_0), \qquad
\nabla_{\mathbf{z}} Q(\mathbf{z}_0 \mid \mathbf{z}_0) = \nabla_{\mathbf{z}} F(\mathbf{z}_0)
これは MM(Majorization-Minimization)アルゴリズムの定番の考え方で、論文は Hunter と Lange の MM アルゴリズムのチュートリアル(2004)を引用しています。曲率の定数を手で選ぶ必要がないのが利点です。一方で、上界がゆるいと、真の誤差を下げられる同時更新を見逃すことがあり、論文もこの点を限界として認めています。
代理関数をスケールの 2 次式にする
$D$ はブロック対角なので、$Q$ は位置ごとの和に分かれます。$\mathbf{h}_i = D_i \mathbf{z}_{0i} - \mathbf{g}_i$ とおき、共有スケールの再構成 $\mathbf{z}_i = s\, c_{ik_i}$ を代入すると
Q(\mathbf{k}, s \mid \mathbf{z}_0) = C + \sum_{i=1}^{M} \big( a_{ik_i} s^2 - 2 b_{ik_i} s \big),
\qquad a_{ik} = c_{ik}^\top D_i c_{ik} \ge 0, \quad b_{ik} = c_{ik}^\top \mathbf{h}_i
になります。$C$ は $(\mathbf{k}, s)$ によらない定数です。各候補のコストが、スケール $s$ の 2 次式になった のがポイントです。
複数のスケールで候補を集める
各位置の候補プール $\mathcal{P}_i$ は、コードブック全体ではなく、いくつかの「試しのスケール」$s \in \Sigma$ のそれぞれで、$\| s c - \mathbf{u}_i \|_{D_i}^2$ が小さい上位 $K$ 個を集めて作ります。ここで $\mathbf{u}_i = \mathbf{z}_{0i} - D_i^{-1} \mathbf{g}_i$、$\|\mathbf{v}\|_{D_i}^2 = \mathbf{v}^\top D_i \mathbf{v}$ です。
現在のスケールで順位が低いコードワードでも、スケールが一緒に動けば有利になることがあるため、1 つのスケールだけで候補を選ぶと同時更新の可能性を取りこぼします。プールには必ず現在のコードワードを含め、上限 $K_{\max}$ を超えるときは複数の試しのスケールで選ばれたものを優先します。また、変えてよいインデックスが $m$ 個だけでも、スケールが変われば残りの位置の再構成も変わるので、残りの位置も候補 1 個のプールとして $Q$ に含めます。
手法 2:下側包絡で区間に分けて同時に最適化する
候補の比較を直線の比較にする
スケール $s > 0$ を固定すると、$Q$ は位置ごとに分離するので、各位置は $a_{ik} s^2 - 2b_{ik} s$ が最小の候補を選べばよくなります。これを $s > 0$ で割っても大小関係は変わらないので、候補の比較は $s$ の 1 次関数(直線) の比較になります。
\ell_{ik}(s) = a_{ik} s - 2 b_{ik}, \qquad L_i(s) = \min_{k \in \mathcal{P}_i} \ell_{ik}(s),
\qquad \min_{\mathbf{k}} Q(\mathbf{k}, s \mid \mathbf{z}_0) = C + s \sum_i L_i(s)
$s$ で割るのは候補を比べるときだけで、スケールの最適化では元の 2 次式をそのまま使います。
$L_i(s)$ は直線の集まりの 下側包絡(どの $s$ でも一番下にある直線をつないだ折れ線)です。各位置で包絡を作り、すべての位置の切り替わり点をまとめると、スケールの範囲がいくつかの区間に分かれます。1 つの区間の中では、どの位置の最適な候補も変わらない ので、区間ごとに 1 つの組み合わせ $\mathbf{k}^{(r)}$ を調べれば十分です。
区間の数は線形にしか増えない
位置 $i$ の候補数を $K_i = |\mathcal{P}_i|$ とすると、各直線は包絡上で高々 1 つの区間しか占めないので、位置 $i$ の切り替わり点は高々 $K_i - 1$ 個です。よって区間の数 $R$ は
R \le 1 + \sum_{i=1}^{M} (K_i - 1)
で、組み合わせの数 $\prod_i K_i$ と違って 候補数に対して線形 です。論文の例では、候補 8 個の位置が 20 個あるとき、組み合わせは $8^{20}$ 通りですが、区間は高々 $1 + 20 \times 7 = 141$ 個です。
区間ごとにスケールを解く
区間 $I_r = [\alpha_r, \beta_r]$ では組み合わせが固定なので、$A_r = \sum_i a_{i,k_i^{(r)}}$、$B_r = \sum_i b_{i,k_i^{(r)}}$ とおくと $Q_r(s) = C + A_r s^2 - 2B_r s$ という 1 変数の凸 2 次関数になり、$A_r > 0$ なら最適なスケールは
s_r^* = \mathrm{clip}\big( B_r / A_r,\ \alpha_r,\ \beta_r \big)
です。すべての区間の最小値を比べれば、候補プールの範囲で代理関数 $Q$ の大域的な最小解 が得られる、というのが論文の定理 1 です。$A_r, B_r$ は切り替わり点ごとに差分で更新できるので、区間ごとに和を計算し直す必要もありません。実際のスケールは FP16 で保存するため、論文では区間内で表現可能な最も近い値を比べる処理も入れています。
真の誤差で確かめてから受け入れる
選んだインデックスとスケールは、実際の保存形式にしてから真の層出力の誤差で確かめ、しきい値 $\eta > 0$ について $F(\mathbf{z}^+) \le F(\mathbf{z}_0) - \eta$ のときだけ受け入れます。現在の状態は必ず候補プールに入っているので、代理関数を正確に最小化すれば
F(\mathbf{z}^+) \le Q(\mathbf{z}^+ \mid \mathbf{z}_0) \le Q(\mathbf{z}_0 \mid \mathbf{z}_0) = F(\mathbf{z}_0)
となり、真の誤差が増えることはありません。また、受け入れるたびに誤差が $\eta$ 以上下がるので、受け入れられる更新の回数は高々 $\lfloor F(\mathbf{z}^{(0)}) / \eta \rfloor$ 回で、必ず終わります。
論文は、定理が保証しないことも明記しています。
- 候補プールを打ち切っているので、コードブック全体での最適解は保証しない
- 位置をまたぐ曲率を上界で置き換えているので、真の誤差での最適解も保証しない
- 上界がゆるいと、座標ごとの停滞から抜け出せないこともある
代理関数の最適解と真の最適解の差は、同じ候補プールの中での真の最適解を $\mathbf{z}_F$ として、次のように押さえられています。
\begin{aligned}
0 \le F(\mathbf{z}_Q) - F(\mathbf{z}_F)
&\le (\mathbf{z}_F - \mathbf{z}_0)^\top (D - H)(\mathbf{z}_F - \mathbf{z}_0) \\
&\le \|D - H\|_2 \, \|\mathbf{z}_F - \mathbf{z}_0\|_2^2
\end{aligned}
包絡法を実装して総当たりと比べる
区間に分けるだけで本当に最適な組み合わせが見つかるのかを確かめるため、ランダムな係数 $a_{ik}, b_{ik}$ で包絡法を実装し、総当たりと比べました。
from itertools import product
import numpy as np
rng = np.random.default_rng(0)
M, K, D = 8, 4, 6 # 位置数、候補数、コードワードの次元
S_MIN, S_MAX = 0.05, 2.0
# 各位置 i の候補 k について、代理目的 Q = C + Σ_i (a_ik s^2 - 2 b_ik s) の係数を作る
codebook = rng.normal(size=(K * M, D))
a = np.empty((M, K))
b = np.empty((M, K))
for i in range(M):
c = codebook[i * K:(i + 1) * K] # 位置 i の候補コードワード
Di = np.eye(D) * rng.uniform(0.5, 2.0) # ブロック対角の曲率(正定値)
hi = rng.normal(size=D)
a[i] = np.einsum("kd,de,ke->k", c, Di, c) # a_ik = c^T D_i c
b[i] = c @ hi # b_ik = c^T h_i
def lower_envelope(slope, icpt):
"""直線 slope*s + icpt の下側包絡を作り、(切り替わる s, その区間の最小の直線) を返す"""
order = sorted(range(len(slope)), key=lambda k: (-slope[k], icpt[k]))
hull, starts = [], []
for k in order:
if hull and slope[hull[-1]] == slope[k]:
continue # 同じ傾きなら切片の小さい方だけ残す
while hull:
j = hull[-1]
x = (icpt[k] - icpt[j]) / (slope[j] - slope[k])
if x <= starts[-1]:
hull.pop(); starts.pop()
else:
break
starts.append(-np.inf if not hull else
(icpt[k] - icpt[hull[-1]]) / (slope[hull[-1]] - slope[k]))
hull.append(k)
return [(x, k) for x, k in zip(starts, hull)]
def envelope_solve():
"""論文の Section 4.2: 包絡の切り替え点で s の範囲を区間に分け、区間ごとに 1 次元の 2 次関数を解く"""
breaks = {S_MIN, S_MAX}
for i in range(M):
for x, _ in lower_envelope(a[i], -2 * b[i])[1:]:
if S_MIN < x < S_MAX:
breaks.add(x)
pts = sorted(breaks)
best = (np.inf, None, None)
for lo, hi in zip(pts[:-1], pts[1:]):
mid = (lo + hi) / 2
k = np.argmin(a * mid - 2 * b, axis=1) # この区間で各位置が選ぶ候補
A, B = a[np.arange(M), k].sum(), b[np.arange(M), k].sum()
s = np.clip(B / A, lo, hi)
q = A * s * s - 2 * B * s
if q < best[0]:
best = (q, tuple(int(x) for x in k), s)
return best, len(pts) - 1
def brute_force():
"""全組み合わせ K^M 通りについて s を最適化する"""
best = (np.inf, None, None)
for k in product(range(K), repeat=M):
A, B = a[np.arange(M), k].sum(), b[np.arange(M), k].sum()
s = np.clip(B / A, S_MIN, S_MAX)
q = A * s * s - 2 * B * s
if q < best[0]:
best = (q, k, s)
return best
(q_env, k_env, s_env), n_intervals = envelope_solve()
q_bf, k_bf, s_bf = brute_force()
print(f"包絡法: Q - C = {q_env:.10f}, k = {k_env}, s = {s_env:.6f}")
print(f"総当たり: Q - C = {q_bf:.10f}, k = {k_bf}, s = {s_bf:.6f}")
print(f"調べた区間数 = {n_intervals}(上限 1 + M(K-1) = {1 + M * (K - 1)})、"
f"総当たりの組み合わせ数 = {K ** M}")
包絡法: Q - C = -14.8226844017, k = (0, 1, 2, 3, 2, 1, 2, 2), s = 0.548611
総当たり: Q - C = -14.8226844017, k = (0, 1, 2, 3, 2, 1, 2, 2), s = 0.548611
調べた区間数 = 5(上限 1 + M(K-1) = 25)、総当たりの組み合わせ数 = 65536
65,536 通りの総当たりと、5 つの区間を調べるだけの包絡法で、同じ組み合わせとスケールが得られました。乱数のシードを 1〜30 に変えて試しても、30 回すべてで最小値が一致しました。区間数が上限より少ないのは、範囲 $[0.05, 2]$ の外にある切り替わり点や、包絡に現れない直線があるためです。
このコードは、論文の「区間に分けて 1 次元の 2 次関数を解く」部分だけを、連続スケール・ランダムな係数で再現したものです。論文の実装にある、$D$ の構築、複数スケールでの候補集め、FP16 の表現可能な値の扱い、真の誤差での検証は含みません。
実験結果
6 モデルでの比較
約 2 ビット/重みでの結果です(論文の表 1 から、BF16 と主要な比較手法を抜き出しました)。PPL は WikiText-2 と C4 のパープレキシティの平均(低いほどよい)、ACC は 6 つのゼロショットタスクの平均正解率(高いほどよい)です。
| モデル | BF16 PPL / ACC | QTIP | LLVQ | CoS-VQ |
|---|---|---|---|---|
| Qwen3-4B | 15.77 / 66.79 | 18.20 / 60.30 | 16.13 / 60.77 | 15.83 / 61.42 |
| Qwen3-8B | 12.01 / 70.37 | 13.91 / 66.80 | 11.57 / 67.35 | 12.20 / 68.61 |
| Qwen3-14B | 10.75 / 73.77 | 12.09 / 71.50 | 11.32 / 71.28 | 10.80 / 71.01 |
| Qwen3-32B | 9.56 / 74.81 | 10.51 / 74.21 | 10.01 / 74.70 | 9.87 / 74.67 |
| Llama-3.1-8B-Inst. | 8.93 / 73.55 | 11.10 / 69.52 | 10.86 / 68.75 | 10.56 / 69.16 |
| Llama-3.1-70B-Inst. | 5.89 / 78.37 | 7.78 / 74.01 | 7.77 / 73.47 | 7.73 / 74.05 |
太字は、論文が比較したすべての量子化手法(PV-Tuning、QuIP#、NoWag、AQLM も含む)の中で最良のものです。CoS-VQ はパープレキシティで 6 モデル中 5 モデル、正解率で 3 モデルが最良で、どの手法もすべてで勝っているわけではありません。
thinking ありの Qwen3.8-27B
| 手法 | WikiText-2 ↓ | C4 ↓ | AIME25 ↑ | LiveCodeBench v6 ↑ | GPQA-D ↑ |
|---|---|---|---|---|---|
| BF16 | 7.05 | 11.45 | 100.00 | 85.71 | 89.90 |
| QTIP | 8.29 | 13.76 | 93.33 | 73.65 | 81.11 |
| LLVQ | 8.22 | 13.85 | 93.33 | 72.28 | 80.48 |
| CoS-VQ | 7.84 | 13.33 | 93.33 | 74.92 | 82.83 |
AIME 2025 は 3 手法とも同じ 93.33 です。推論系のベンチマークでも BF16 との差は残っており、2 ビットでの劣化がなくなるわけではありません。
効くのはスケールではなく「同時に選び直すこと」
同じ初期値から、スケールだけを最適化した場合と、インデックスも同時に選び直した場合を比べたアブレーション(論文の表 2)です。Avg. は 8 つのゼロショットタスクの平均正解率です。
| モデル | 設定 | WikiText-2 ↓ | Avg. ↑ |
|---|---|---|---|
| Qwen3-4B | 最適化なし | 22.69 | 58.81 |
| スケールだけ | 21.01 | 59.32 | |
| スケール+インデックス | 13.68 | 62.58 | |
| Qwen3-8B | 最適化なし | 14.41 | 66.44 |
| スケールだけ | 13.85 | 67.19 | |
| スケール+インデックス | 9.76 | 69.43 |
スケールだけではパープレキシティの改善はわずかで、インデックスを同時に選び直すことで大きく下がっています。論文の主張の中心となる結果です。
コードワードの次元と速度
コードワードの次元 $d$ を変えた結果(Qwen3-4B、論文の表 5)では、$d = 8$ はすべての指標で最もよいものの 2.59 ビット/重みになります。約 2 ビットの予算内では、$d = 4, 5$ のほうが WikiText-2 のパープレキシティは低い一方、下流タスク 4 つでは $d = 6$ が両方を上回ったため、$d = 6$ を既定にしています。
量子化にかかる時間は A100-SXM4-80GB で Qwen3-4B が 1.3 時間、Qwen3-32B が 3.5 時間です。推論は RTX 4090 上の Llama-3.1-8B-Instruct(2K トークンの文脈)で、プリフィルは BF16 とほぼ同じ(2217.6 → 2230.2 tok/s)、デコードは 21.9 → 36.7 tok/s(1.68 倍)でした。同時最適化はすべてオフラインで行うので、推論時には低次元のコードブックを引くだけで済みます。
限界と注意点
論文自身が挙げている限界と、読んでいて気になった点です。
- 理論保証は代理関数について:定理は、有限の候補プールの中で代理関数 $Q$ を最小化することを保証するもので、元の層出力の誤差の最適性は保証しません。真の誤差で確かめるので悪化はしませんが、上界がゆるいと停滞から抜け出せないこともあります
- 候補の質:結果は、試しのスケールの取り方とコードブックに依存します
- 評価の範囲:Qwen3 と Llama-3.1 の系列とベンチマークに限られ、他のアーキテクチャやさらに低いビット数は今後の課題とされています
- 速度はハードウェアと実装次第:推論の速さはコードブックを引く実装と GPU に依存し、他の環境での性能はまだ調べられていません
- 安全性の評価はしていない:量子化で出力が変わり、事実性や公平性、安全性に関わる挙動が変わりうること、専用の安全性評価はしていないことが倫理的考察に書かれています
- 表どうしの数値(例:表 2 の 13.68 と表 6 の 12.18〜12.69)は、スケールの微調整の有無など設定が異なるため、そのまま比べられません。論文も、表 6 は表 1 とは別のアブレーションだと注記しています
まとめ
- CoS-VQ は、6 次元の小さなコードワードを保ったまま、共有スケールを持つ位置のインデックスとスケールを同時に最適化する LLM のベクトル量子化手法です
- スケールを共有すると、最適なスケールが全位置の選択に依存するため、1 か所ずつの更新では止まってしまうことがあります(2 点の例で、誤差 $1/61$ から 1 か所の変更では悪化するのに、同時の変更で $0$ になることを確かめました)
- 層出力の誤差を、ブロック対角の上界(majorizer)で位置ごとに分離できる代理関数に置き換え、各候補のコストをスケールの 2 次式にします
- 候補の比較を直線の下側包絡に帰着させると、調べる区間は候補数に対して線形にしか増えず、候補プール内での代理関数の大域最適が得られます
- 約 2 ビット/重みで、6 モデル中 5 モデルのパープレキシティが比較手法の中で最良で、改善の大部分はスケールではなくインデックスを同時に選び直すことから来ていると報告されています
参考
- CoS-VQ: Coordinated Shared-Scale Vector Quantization for Large Language Models(Xu et al., arXiv:2610.37336, 2026)
- xzktx003/Cos_VQ(論文の公式コード, GitHub)
- QTIP: Quantization with Trellises and Incoherence Processing(Tseng et al., arXiv:2406.11235, 2024)
- QuIP#: Even Better LLM Quantization with Hadamard Incoherence and Lattice Codebooks(Tseng et al., arXiv:2402.04396, 2024)
- Extreme Compression of Large Language Models via Additive Quantization(Egiazarian et al., arXiv:2401.06118, 2024)
動作環境:macOS 27.0.1(Apple Silicon)、Python 3.15.0、NumPy 2.5.4。
本記事は 2026年10月時点の情報に基づいています。