miutti's picture
Source: kernel, desktop app, tools (snapshot of the GitHub repo)
df41178 verified
Raw History Blame Contribute Delete
5.08 kB
"""qwen2 の順伝播をnumpyで実装し、各層の「本物の入力」を捕まえる。
なぜ必要か:
層ごとの出力を元モデルに合わせ込む「再構成」には、その層に実際に入ってくる
活性化そのものが要る。imatrixはチャンネルごとの平均二乗しか持たず足りない。
llama.cppに吐き出す機能がないので自前で回す。
速度は求めない。数百トークン分の活性化が採れれば目的を果たす。
"""
import numpy as np
import gguf
class Model:
def __init__(self, path, in_memory=False):
self.r = gguf.Reader(path, in_memory=in_memory)
m = self.r.meta
self.n_layer = m["qwen2.block_count"]
self.d = m["qwen2.embedding_length"]
self.n_head = m["qwen2.attention.head_count"]
self.n_kv = m["qwen2.attention.head_count_kv"]
self.hd = self.d // self.n_head
self.eps = m["qwen2.attention.layer_norm_rms_epsilon"]
self.theta = m["qwen2.rope.freq_base"]
self._cache = {}
def T(self, name):
if name not in self._cache:
a, _ = self.r.load(name)
self._cache[name] = np.ascontiguousarray(a.astype(np.float32))
return self._cache[name]
def drop(self, name):
self._cache.pop(name, None)
def rms_norm(x, w, eps):
return x / np.sqrt((x * x).mean(-1, keepdims=True) + eps) * w
def rope(x, pos, theta):
"""x: (T, n_head, hd)。回転位置埋め込み。"""
T, H, hd = x.shape
half = hd // 2
inv = 1.0 / (theta ** (np.arange(0, half, dtype=np.float64) * 2 / hd))
ang = pos[:, None] * inv[None, :] # (T, half)
c, s = np.cos(ang)[:, None, :], np.sin(ang)[:, None, :]
x1, x2 = x[..., :half], x[..., half:]
return np.concatenate([x1 * c - x2 * s, x1 * s + x2 * c], -1).astype(np.float32)
def run(model, tokens, capture=None, replace=None, quantizer=None):
"""トークン列を流す。capture に層名を入れると、その層への入力を集めて返す。
replace={テンソル名: 行列} で重みを差し替えられる(圧縮版の評価用)。"""
M, r = model, model.r
T = len(tokens)
pos = np.arange(T)
emb = M.T("token_embd.weight") # (vocab, d)
x = emb[tokens].astype(np.float32)
caught = {}
quantized = {}
def W(name):
if replace and name in replace:
return replace[name]
return M.T(name)
def lin(h, name):
"""h:(T,in) → (T,out)。GGUFは (out,in) 格納なので転置して掛ける。"""
if capture is not None and name in capture:
caught.setdefault(name, []).append(h.copy())
if quantizer is not None and name in quantizer[0]:
# この層に実際に入ってくる活性化を使って、その場で量子化する。
# 以降の層は量子化済みの重みで動くので、誤差の伝播も現実的に扱える。
if name not in quantized:
quantized[name] = quantizer[1](name, M.T(name), h)
w = quantized[name]
y = h @ w.T
b = name.replace(".weight", ".bias")
if b in r.tensors:
y = y + M.T(b)
return y
w = W(name)
y = h @ w.T
b = name.replace(".weight", ".bias")
if b in r.tensors:
y = y + M.T(b)
return y
for L in range(M.n_layer):
p = f"blk.{L}."
h = rms_norm(x, M.T(p + "attn_norm.weight"), M.eps)
q = lin(h, p + "attn_q.weight").reshape(T, M.n_head, M.hd)
k = lin(h, p + "attn_k.weight").reshape(T, M.n_kv, M.hd)
v = lin(h, p + "attn_v.weight").reshape(T, M.n_kv, M.hd)
q = rope(q, pos, M.theta); k = rope(k, pos, M.theta)
rep = M.n_head // M.n_kv
k = np.repeat(k, rep, axis=1); v = np.repeat(v, rep, axis=1)
# 因果マスク付きattention
att = np.einsum("thd,shd->hts", q, k) / np.sqrt(M.hd)
mask = np.triu(np.full((T, T), -1e9, np.float32), 1)
att = att + mask
att -= att.max(-1, keepdims=True)
np.exp(att, out=att)
att /= att.sum(-1, keepdims=True)
o = np.einsum("hts,shd->thd", att, v).reshape(T, M.d)
x = x + lin(o, p + "attn_output.weight")
h = rms_norm(x, M.T(p + "ffn_norm.weight"), M.eps)
g = lin(h, p + "ffn_gate.weight")
u = lin(h, p + "ffn_up.weight")
act = g / (1.0 + np.exp(-g)) * u # SwiGLU
x = x + lin(act, p + "ffn_down.weight")
for n in ("attn_q", "attn_k", "attn_v", "attn_output",
"ffn_gate", "ffn_up", "ffn_down"):
M.drop(p + n + ".weight") # メモリを抱え込まない
x = rms_norm(x, M.T("output_norm.weight"), M.eps)
logits = x @ M.T("token_embd.weight").T # 重み共有
if quantizer is not None:
return logits, quantized
if capture is not None:
return logits, {k: np.concatenate(v, 0) for k, v in caught.items()}
return logits