miutti's picture
Source: kernel, desktop app, tools (snapshot of the GitHub repo)
df41178 verified
Raw History Blame Contribute Delete
2.74 kB
"""トレリス符号化量子化(QTIP方式の中核)。
k-means辞書の限界: k個をまとめると候補が 2^(k×bit) で爆発し、k=32が壁。
トレリスは重みを1個ずつ符号化しつつ「直近Lビットの履歴」を状態として持ち、
系列全体の誤差が最小になる経路をViterbiで厳密に探す。
再生値は状態番号から計算で生成するので**コードブックを保存しない**。
高速化の鍵(ビットシフトトレリスの構造):
遷移は next = ((state << K) | code) & (2^L - 1)。
K=1 のとき、ある次状態 j の前状態は必ず 2つだけ: j>>1 と (j>>1) + 2^(L-1)。
この「バタフライ構造」のおかげで、全状態・全行を一度に配列演算で処理できる。
"""
import numpy as np
def gen_values(L, seed=0):
"""状態ごとの再生値。保存不要=シードだけで再現できる。"""
rng = np.random.default_rng(seed)
v = rng.standard_normal(1 << L)
return ((v - v.mean()) / v.std()).astype(np.float32)
def quantize_rows(X, L=10, seed=0):
"""X: (N, T) の各行をトレリス符号化(K=1bit/要素)。全行を同時に処理する。"""
N, T = X.shape
S = 1 << L
H = S >> 1
vals = gen_values(L, seed) # (S,)
j = np.arange(S)
p0 = (j >> 1) # 前状態その1
p1 = p0 + H # 前状態その2
cost = np.zeros((N, S), np.float32)
back = np.empty((T, N, S), np.bool_) # Trueなら前状態はp1側
for t in range(T):
c0 = cost[:, p0]
c1 = cost[:, p1]
take1 = c1 < c0
back[t] = take1
base = np.where(take1, c1, c0)
d = vals[None, :] - X[:, t:t + 1]
cost = base + d * d
# 後ろ向き復元
s = np.argmin(cost, axis=1).astype(np.int64) # (N,)
out = np.empty((N, T), np.float32)
rows = np.arange(N)
for t in range(T - 1, -1, -1):
out[:, t] = vals[s]
take1 = back[t, rows, s]
s = (s >> 1) + np.where(take1, H, 0)
return out
def quantize_matrix(W, G=256, L=10, seed=0, ls_scale=True):
"""行列全体。グループ(=行の一区間)ごとにスケール、中身を1bitトレリスで符号化。
実効ビット = 1 + スケールのビット/G。"""
X = W.reshape(-1, G)
s = np.abs(X).max(1, keepdims=True)
s[s == 0] = 1
R = quantize_rows((X / s).astype(np.float32), L=L, seed=seed)
if ls_scale:
num = (X * R).sum(1, keepdims=True)
den = (R * R).sum(1, keepdims=True)
den[den == 0] = 1
s = num / den
return (R * s).reshape(W.shape).astype(np.float32), 1.0 + 8.0 / G