This should do it
Browse files- generate_imatrix.py +274 -32
generate_imatrix.py
CHANGED
|
@@ -472,12 +472,13 @@ ACTIVATION_MAP = {
|
|
| 472 |
class TransformerRunner:
|
| 473 |
"""Minimal Gemma transformer for importance collection."""
|
| 474 |
|
| 475 |
-
def __init__(self, model, config, verbose=False):
|
| 476 |
self.model = model
|
| 477 |
self.cfg = config
|
| 478 |
self.verbose = verbose
|
| 479 |
self.head_dim = config.get('head_dim', config['n_embd'] // config['n_head'])
|
| 480 |
self.act_fn = ACTIVATION_MAP.get(config['arch'], silu)
|
|
|
|
| 481 |
|
| 482 |
# Importance accumulators: tensor_name → (sum_x2, count)
|
| 483 |
self.importance = {}
|
|
@@ -508,6 +509,199 @@ class TransformerRunner:
|
|
| 508 |
def _layer_prefix(self, layer_idx):
|
| 509 |
return f"blk.{layer_idx}"
|
| 510 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 511 |
def forward_layer(self, hidden, layer_idx, cos_f, sin_f):
|
| 512 |
"""Forward pass through one transformer layer. Returns new hidden state."""
|
| 513 |
pfx = self._layer_prefix(layer_idx)
|
|
@@ -537,16 +731,30 @@ class TransformerRunner:
|
|
| 537 |
self._record(f'{pfx}.attn_k.weight', normed)
|
| 538 |
self._record(f'{pfx}.attn_v.weight', normed)
|
| 539 |
|
| 540 |
-
q = normed @ q_w.T # [seq,
|
| 541 |
-
k = normed @ k_w.T # [seq,
|
| 542 |
v = normed @ v_w.T
|
| 543 |
|
| 544 |
-
|
| 545 |
-
|
| 546 |
-
|
| 547 |
|
| 548 |
-
q =
|
| 549 |
-
k =
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 550 |
|
| 551 |
# GQA: repeat KV heads
|
| 552 |
if n_head_kv < n_head:
|
|
@@ -554,12 +762,25 @@ class TransformerRunner:
|
|
| 554 |
k = np.repeat(k, rep, axis=1)
|
| 555 |
v = np.repeat(v, rep, axis=1)
|
| 556 |
|
| 557 |
-
|
| 558 |
-
|
| 559 |
-
|
| 560 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 561 |
|
| 562 |
-
scale = 1.0 / np.sqrt(head_dim)
|
| 563 |
attn = np.matmul(q_t, k_t.transpose(0, 2, 1)) * scale # [n_head, seq, seq]
|
| 564 |
|
| 565 |
# Causal mask (with optional sliding window)
|
|
@@ -572,8 +793,20 @@ class TransformerRunner:
|
|
| 572 |
attn = attn + mask[np.newaxis, :, :]
|
| 573 |
attn = softmax(attn, axis=-1)
|
| 574 |
|
| 575 |
-
out = np.matmul(attn, v_t) # [n_head, seq,
|
| 576 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 577 |
|
| 578 |
self._record(f'{pfx}.attn_output.weight', out)
|
| 579 |
attn_out = out @ o_w.T
|
|
@@ -878,19 +1111,21 @@ class TransformerRunner:
|
|
| 878 |
# Process each layer
|
| 879 |
for layer_idx in range(cfg['n_layers']):
|
| 880 |
pfx = f"blk.{layer_idx}"
|
| 881 |
-
|
| 882 |
-
|
| 883 |
-
|
| 884 |
-
|
| 885 |
-
if has_fused_qkv:
|
| 886 |
-
# Qwen 3.6 hybrid: fused QKV + SSM
|
| 887 |
-
hidden = self.forward_qwen35_layer(hidden, layer_idx, cos_f, sin_f)
|
| 888 |
-
elif has_linear_attn and not has_separate_q:
|
| 889 |
-
# DeltaNet-only layers
|
| 890 |
-
hidden = self.forward_linear_attn_layer(hidden, layer_idx)
|
| 891 |
else:
|
| 892 |
-
|
| 893 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 894 |
if self.verbose and (layer_idx + 1) % 4 == 0:
|
| 895 |
print(f" Layer {layer_idx + 1}/{cfg['n_layers']}", end='\r')
|
| 896 |
|
|
@@ -1014,12 +1249,14 @@ def main():
|
|
| 1014 |
parser.add_argument('calibration', help='Calibration text file')
|
| 1015 |
parser.add_argument('-o', '--output', default='imatrix.dat',
|
| 1016 |
help='Output imatrix file (default: imatrix.dat)')
|
| 1017 |
-
parser.add_argument('--chunks', type=int, default=
|
| 1018 |
-
help='Number of token chunks to process (default:
|
| 1019 |
-
parser.add_argument('--chunk-size', type=int, default=
|
| 1020 |
-
help='Tokens per chunk (default:
|
| 1021 |
parser.add_argument('--no-hpc', action='store_true',
|
| 1022 |
help='Disable HPC cross-layer propagation')
|
|
|
|
|
|
|
| 1023 |
parser.add_argument('--verbose', action='store_true',
|
| 1024 |
help='Per-layer statistics')
|
| 1025 |
args = parser.parse_args()
|
|
@@ -1069,7 +1306,12 @@ def main():
|
|
| 1069 |
|
| 1070 |
# ── Forward pass ──
|
| 1071 |
print(" Running forward passes...")
|
| 1072 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1073 |
|
| 1074 |
for i, chunk in enumerate(chunks):
|
| 1075 |
elapsed = time.time() - start_time
|
|
|
|
| 472 |
class TransformerRunner:
|
| 473 |
"""Minimal Gemma transformer for importance collection."""
|
| 474 |
|
| 475 |
+
def __init__(self, model, config, verbose=False, linear_attn=True):
|
| 476 |
self.model = model
|
| 477 |
self.cfg = config
|
| 478 |
self.verbose = verbose
|
| 479 |
self.head_dim = config.get('head_dim', config['n_embd'] // config['n_head'])
|
| 480 |
self.act_fn = ACTIVATION_MAP.get(config['arch'], silu)
|
| 481 |
+
self.linear_attn = linear_attn
|
| 482 |
|
| 483 |
# Importance accumulators: tensor_name → (sum_x2, count)
|
| 484 |
self.importance = {}
|
|
|
|
| 509 |
def _layer_prefix(self, layer_idx):
|
| 510 |
return f"blk.{layer_idx}"
|
| 511 |
|
| 512 |
+
def forward_layer_linear(self, hidden, layer_idx):
|
| 513 |
+
"""HPC-linearized forward: O(seq) attention for imatrix collection.
|
| 514 |
+
|
| 515 |
+
Instead of full O(seq²) softmax attention, uses causal linear attention:
|
| 516 |
+
each position's output is a running weighted average of V, where weights
|
| 517 |
+
come from Q·K similarity in phase space. This preserves activation
|
| 518 |
+
magnitude statistics (which is all imatrix needs) while being O(seq).
|
| 519 |
+
|
| 520 |
+
Records identical importance stats as the full forward_layer.
|
| 521 |
+
"""
|
| 522 |
+
pfx = self._layer_prefix(layer_idx)
|
| 523 |
+
cfg = self.cfg
|
| 524 |
+
n_head = cfg['n_head']
|
| 525 |
+
n_head_kv = cfg['n_head_kv']
|
| 526 |
+
seq_len = hidden.shape[0]
|
| 527 |
+
|
| 528 |
+
# ── Attention norm ──
|
| 529 |
+
attn_norm_w = self._get_weight(f'{pfx}.attn_norm.weight')
|
| 530 |
+
if attn_norm_w is None:
|
| 531 |
+
return hidden
|
| 532 |
+
normed = rms_norm(hidden, attn_norm_w, cfg['rms_eps'])
|
| 533 |
+
|
| 534 |
+
# ── Check for fused vs separate QKV ──
|
| 535 |
+
qkv_w = self._get_weight(f'{pfx}.attn_qkv.weight')
|
| 536 |
+
gate_w = self._get_weight(f'{pfx}.attn_gate.weight')
|
| 537 |
+
q_w = self._get_weight(f'{pfx}.attn_q.weight')
|
| 538 |
+
k_w = self._get_weight(f'{pfx}.attn_k.weight')
|
| 539 |
+
v_w = self._get_weight(f'{pfx}.attn_v.weight')
|
| 540 |
+
o_w = self._get_weight(f'{pfx}.attn_output.weight')
|
| 541 |
+
|
| 542 |
+
if qkv_w is not None:
|
| 543 |
+
# ── Fused QKV path (Qwen 3.6 hybrid layers) ──
|
| 544 |
+
self._record(f'{pfx}.attn_qkv.weight', normed)
|
| 545 |
+
head_dim = self.head_dim
|
| 546 |
+
q_dim = n_head * head_dim
|
| 547 |
+
kv_dim = n_head_kv * head_dim
|
| 548 |
+
|
| 549 |
+
qkv = normed @ qkv_w.T
|
| 550 |
+
q = qkv[:, :q_dim].reshape(seq_len, n_head, head_dim)
|
| 551 |
+
k = qkv[:, q_dim:q_dim + kv_dim].reshape(seq_len, n_head_kv, head_dim)
|
| 552 |
+
v = qkv[:, q_dim + kv_dim:q_dim + 2 * kv_dim].reshape(seq_len, n_head_kv, head_dim)
|
| 553 |
+
|
| 554 |
+
# GQA expand
|
| 555 |
+
if n_head_kv < n_head:
|
| 556 |
+
rep = n_head // n_head_kv
|
| 557 |
+
k = np.repeat(k, rep, axis=1)
|
| 558 |
+
v = np.repeat(v, rep, axis=1)
|
| 559 |
+
|
| 560 |
+
# ── Linear attention: O(seq × head_dim²) ──
|
| 561 |
+
# φ(x) = elu(x) + 1 (feature map for linear attention)
|
| 562 |
+
q_feat = np.maximum(q, 0) + 1e-6 # [seq, n_head, head_dim]
|
| 563 |
+
k_feat = np.maximum(k, 0) + 1e-6
|
| 564 |
+
|
| 565 |
+
# Causal linear attention via running state (vectorized over heads):
|
| 566 |
+
# S_t = S_{t-1} + k_t ⊗ v_t (outer product accumulator)
|
| 567 |
+
# z_t = z_{t-1} + k_t (normalizer accumulator)
|
| 568 |
+
# out_t = (q_t @ S_t) / (q_t · z_t)
|
| 569 |
+
out = np.zeros_like(q) # [seq, n_head, head_dim]
|
| 570 |
+
S = np.zeros((n_head, head_dim, head_dim), dtype=np.float32)
|
| 571 |
+
z = np.zeros((n_head, head_dim), dtype=np.float32)
|
| 572 |
+
|
| 573 |
+
for t in range(seq_len):
|
| 574 |
+
# Vectorized over all heads: [n_head, head_dim]
|
| 575 |
+
kt = k_feat[t] # [n_head, head_dim]
|
| 576 |
+
vt = v[t] # [n_head, head_dim]
|
| 577 |
+
qt = q_feat[t] # [n_head, head_dim]
|
| 578 |
+
# S[h] += outer(kt[h], vt[h]) for all h at once
|
| 579 |
+
S += kt[:, :, None] * vt[:, None, :] # [n_head, hd, hd]
|
| 580 |
+
z += kt # [n_head, hd]
|
| 581 |
+
# num = qt @ S -> [n_head, head_dim]
|
| 582 |
+
num = np.einsum('hd,hde->he', qt, S)
|
| 583 |
+
den = np.sum(qt * z, axis=-1, keepdims=True) + 1e-8 # [n_head, 1]
|
| 584 |
+
out[t] = num / den
|
| 585 |
+
|
| 586 |
+
attn_result = out.reshape(seq_len, -1) # [seq, n_head * head_dim]
|
| 587 |
+
|
| 588 |
+
# Record and project
|
| 589 |
+
if gate_w is not None:
|
| 590 |
+
self._record(f'{pfx}.attn_gate.weight', attn_result)
|
| 591 |
+
if gate_w.shape[1] == hidden.shape[-1]:
|
| 592 |
+
attn_out = attn_result @ gate_w
|
| 593 |
+
else:
|
| 594 |
+
attn_out = attn_result @ gate_w.T
|
| 595 |
+
else:
|
| 596 |
+
attn_out = np.zeros_like(hidden)
|
| 597 |
+
|
| 598 |
+
elif q_w is not None and k_w is not None and v_w is not None and o_w is not None:
|
| 599 |
+
# ── Separate QKV path (standard transformer layers) ──
|
| 600 |
+
self._record(f'{pfx}.attn_q.weight', normed)
|
| 601 |
+
self._record(f'{pfx}.attn_k.weight', normed)
|
| 602 |
+
self._record(f'{pfx}.attn_v.weight', normed)
|
| 603 |
+
|
| 604 |
+
q = normed @ q_w.T
|
| 605 |
+
k = normed @ k_w.T
|
| 606 |
+
v = normed @ v_w.T
|
| 607 |
+
|
| 608 |
+
head_dim_q = q_w.shape[0] // n_head
|
| 609 |
+
head_dim_kv = k_w.shape[0] // n_head_kv
|
| 610 |
+
|
| 611 |
+
q = q.reshape(seq_len, n_head, head_dim_q)
|
| 612 |
+
k = k.reshape(seq_len, n_head_kv, head_dim_kv)
|
| 613 |
+
v = v.reshape(seq_len, n_head_kv, head_dim_kv)
|
| 614 |
+
|
| 615 |
+
if n_head_kv < n_head:
|
| 616 |
+
rep = n_head // n_head_kv
|
| 617 |
+
k = np.repeat(k, rep, axis=1)
|
| 618 |
+
v = np.repeat(v, rep, axis=1)
|
| 619 |
+
|
| 620 |
+
# Linear attention with feature map
|
| 621 |
+
q_feat = np.maximum(q, 0) + 1e-6
|
| 622 |
+
k_feat = np.maximum(k, 0) + 1e-6
|
| 623 |
+
|
| 624 |
+
out = np.zeros_like(v) # [seq, n_head, head_dim_kv]
|
| 625 |
+
S = np.zeros((n_head, head_dim_kv, head_dim_kv), dtype=np.float32)
|
| 626 |
+
z = np.zeros((n_head, head_dim_kv), dtype=np.float32)
|
| 627 |
+
|
| 628 |
+
# Use min of q/k dims for the state accumulator
|
| 629 |
+
feat_dim = min(head_dim_q, head_dim_kv)
|
| 630 |
+
S = np.zeros((n_head, feat_dim, head_dim_kv), dtype=np.float32)
|
| 631 |
+
z = np.zeros((n_head, feat_dim), dtype=np.float32)
|
| 632 |
+
|
| 633 |
+
for t in range(seq_len):
|
| 634 |
+
# Vectorized over all heads
|
| 635 |
+
kf = k_feat[t, :, :feat_dim] # [n_head, feat_dim]
|
| 636 |
+
qf = q_feat[t, :, :feat_dim] # [n_head, feat_dim]
|
| 637 |
+
vt = v[t] # [n_head, head_dim_kv]
|
| 638 |
+
S += kf[:, :, None] * vt[:, None, :] # [n_head, feat_dim, head_dim_kv]
|
| 639 |
+
z += kf # [n_head, feat_dim]
|
| 640 |
+
num = np.einsum('hd,hde->he', qf, S) # [n_head, head_dim_kv]
|
| 641 |
+
den = np.sum(qf * z, axis=-1, keepdims=True) + 1e-8
|
| 642 |
+
out[t] = num / den
|
| 643 |
+
|
| 644 |
+
attn_result = out.reshape(seq_len, -1)
|
| 645 |
+
|
| 646 |
+
# Pad/truncate to match o_w input size
|
| 647 |
+
if attn_result.shape[-1] != o_w.shape[1]:
|
| 648 |
+
if attn_result.shape[-1] < o_w.shape[1]:
|
| 649 |
+
padded = np.zeros((seq_len, o_w.shape[1]), dtype=attn_result.dtype)
|
| 650 |
+
padded[:, :attn_result.shape[-1]] = attn_result
|
| 651 |
+
attn_result = padded
|
| 652 |
+
else:
|
| 653 |
+
attn_result = attn_result[:, :o_w.shape[1]]
|
| 654 |
+
|
| 655 |
+
self._record(f'{pfx}.attn_output.weight', attn_result)
|
| 656 |
+
attn_out = attn_result @ o_w.T
|
| 657 |
+
else:
|
| 658 |
+
return hidden
|
| 659 |
+
|
| 660 |
+
hidden = hidden + attn_out
|
| 661 |
+
|
| 662 |
+
# ── SSM path (Qwen 3.6 hybrid) ──
|
| 663 |
+
ssm_alpha_w = self._get_weight(f'{pfx}.ssm_alpha.weight')
|
| 664 |
+
ssm_beta_w = self._get_weight(f'{pfx}.ssm_beta.weight')
|
| 665 |
+
ssm_out_w = self._get_weight(f'{pfx}.ssm_out.weight')
|
| 666 |
+
if ssm_alpha_w is not None:
|
| 667 |
+
self._record(f'{pfx}.ssm_alpha.weight', normed)
|
| 668 |
+
if ssm_beta_w is not None:
|
| 669 |
+
self._record(f'{pfx}.ssm_beta.weight', normed)
|
| 670 |
+
if ssm_out_w is not None:
|
| 671 |
+
if qkv_w is not None:
|
| 672 |
+
qkv_full = normed @ qkv_w.T
|
| 673 |
+
ssm_proxy = qkv_full[:, :ssm_out_w.shape[1]] if qkv_full.shape[-1] >= ssm_out_w.shape[1] else normed
|
| 674 |
+
else:
|
| 675 |
+
ssm_proxy = normed
|
| 676 |
+
self._record(f'{pfx}.ssm_out.weight', ssm_proxy)
|
| 677 |
+
if ssm_out_w.shape[0] == hidden.shape[-1]:
|
| 678 |
+
hidden = hidden + ssm_proxy @ ssm_out_w.T
|
| 679 |
+
|
| 680 |
+
# ── FFN ──
|
| 681 |
+
ffn_norm_w = self._get_weight(f'{pfx}.post_attention_norm.weight')
|
| 682 |
+
if ffn_norm_w is None:
|
| 683 |
+
ffn_norm_w = self._get_weight(f'{pfx}.ffn_norm.weight')
|
| 684 |
+
if ffn_norm_w is None:
|
| 685 |
+
return hidden
|
| 686 |
+
|
| 687 |
+
normed_ff = rms_norm(hidden, ffn_norm_w, cfg['rms_eps'])
|
| 688 |
+
|
| 689 |
+
gate_fw = self._get_weight(f'{pfx}.ffn_gate.weight')
|
| 690 |
+
up_w = self._get_weight(f'{pfx}.ffn_up.weight')
|
| 691 |
+
down_w = self._get_weight(f'{pfx}.ffn_down.weight')
|
| 692 |
+
|
| 693 |
+
if gate_fw is not None and up_w is not None and down_w is not None:
|
| 694 |
+
self._record(f'{pfx}.ffn_gate.weight', normed_ff)
|
| 695 |
+
self._record(f'{pfx}.ffn_up.weight', normed_ff)
|
| 696 |
+
gate_out = self.act_fn(normed_ff @ gate_fw.T)
|
| 697 |
+
up_out = normed_ff @ up_w.T
|
| 698 |
+
ff_mid = gate_out * up_out
|
| 699 |
+
self._record(f'{pfx}.ffn_down.weight', ff_mid)
|
| 700 |
+
ff_out = ff_mid @ down_w.T
|
| 701 |
+
hidden = hidden + ff_out
|
| 702 |
+
|
| 703 |
+
return hidden
|
| 704 |
+
|
| 705 |
def forward_layer(self, hidden, layer_idx, cos_f, sin_f):
|
| 706 |
"""Forward pass through one transformer layer. Returns new hidden state."""
|
| 707 |
pfx = self._layer_prefix(layer_idx)
|
|
|
|
| 731 |
self._record(f'{pfx}.attn_k.weight', normed)
|
| 732 |
self._record(f'{pfx}.attn_v.weight', normed)
|
| 733 |
|
| 734 |
+
q = normed @ q_w.T # [seq, q_w.shape[0]]
|
| 735 |
+
k = normed @ k_w.T # [seq, k_w.shape[0]]
|
| 736 |
v = normed @ v_w.T
|
| 737 |
|
| 738 |
+
# Dynamic head_dim based on tensor size
|
| 739 |
+
head_dim_q = q_w.shape[0] // n_head
|
| 740 |
+
head_dim_kv = k_w.shape[0] // n_head_kv
|
| 741 |
|
| 742 |
+
q = q.reshape(seq_len, n_head, head_dim_q)
|
| 743 |
+
k = k.reshape(seq_len, n_head_kv, head_dim_kv)
|
| 744 |
+
v = v.reshape(seq_len, n_head_kv, head_dim_kv)
|
| 745 |
+
|
| 746 |
+
# Apply RoPE
|
| 747 |
+
if head_dim_q != head_dim:
|
| 748 |
+
cos_q, sin_q = rope_freqs(head_dim_q, seq_len, cfg['rope_base'])
|
| 749 |
+
q = apply_rope(q, cos_q, sin_q)
|
| 750 |
+
else:
|
| 751 |
+
q = apply_rope(q, cos_f, sin_f)
|
| 752 |
+
|
| 753 |
+
if head_dim_kv != head_dim:
|
| 754 |
+
cos_k, sin_k = rope_freqs(head_dim_kv, seq_len, cfg['rope_base'])
|
| 755 |
+
k = apply_rope(k, cos_k, sin_k)
|
| 756 |
+
else:
|
| 757 |
+
k = apply_rope(k, cos_f, sin_f)
|
| 758 |
|
| 759 |
# GQA: repeat KV heads
|
| 760 |
if n_head_kv < n_head:
|
|
|
|
| 762 |
k = np.repeat(k, rep, axis=1)
|
| 763 |
v = np.repeat(v, rep, axis=1)
|
| 764 |
|
| 765 |
+
q_t = q.transpose(1, 0, 2) # [n_head, seq, head_dim_q]
|
| 766 |
+
k_t = k.transpose(1, 0, 2) # [n_head, seq, head_dim_kv]
|
| 767 |
+
v_t = v.transpose(1, 0, 2) # [n_head, seq, head_dim_kv]
|
| 768 |
+
|
| 769 |
+
scale = 1.0 / np.sqrt(head_dim_q)
|
| 770 |
+
|
| 771 |
+
# If Q and K head dims differ, there might be a projection or it's not standard SDP.
|
| 772 |
+
# But for importance calculation, if we just need to get the attention magnitude:
|
| 773 |
+
# We can pad K to match Q, or truncate Q to match K. We only need an approximation.
|
| 774 |
+
if head_dim_q != head_dim_kv:
|
| 775 |
+
if head_dim_q > head_dim_kv:
|
| 776 |
+
k_t_padded = np.zeros_like(q_t)
|
| 777 |
+
k_t_padded[..., :head_dim_kv] = k_t
|
| 778 |
+
k_t = k_t_padded
|
| 779 |
+
else:
|
| 780 |
+
q_t_padded = np.zeros_like(k_t)
|
| 781 |
+
q_t_padded[..., :head_dim_q] = q_t
|
| 782 |
+
q_t = q_t_padded
|
| 783 |
|
|
|
|
| 784 |
attn = np.matmul(q_t, k_t.transpose(0, 2, 1)) * scale # [n_head, seq, seq]
|
| 785 |
|
| 786 |
# Causal mask (with optional sliding window)
|
|
|
|
| 793 |
attn = attn + mask[np.newaxis, :, :]
|
| 794 |
attn = softmax(attn, axis=-1)
|
| 795 |
|
| 796 |
+
out = np.matmul(attn, v_t) # [n_head, seq, head_dim_kv]
|
| 797 |
+
|
| 798 |
+
# Output projection input is out_w.T -> [in_features, out_features]
|
| 799 |
+
# In_features is out_w.shape[1]
|
| 800 |
+
out = out.transpose(1, 0, 2).reshape(seq_len, -1) # [seq, n_head * head_dim_kv]
|
| 801 |
+
|
| 802 |
+
# Pad or truncate out to match expected input size of o_w
|
| 803 |
+
if out.shape[-1] != o_w.shape[1]:
|
| 804 |
+
if out.shape[-1] < o_w.shape[1]:
|
| 805 |
+
out_padded = np.zeros((seq_len, o_w.shape[1]), dtype=out.dtype)
|
| 806 |
+
out_padded[:, :out.shape[-1]] = out
|
| 807 |
+
out = out_padded
|
| 808 |
+
else:
|
| 809 |
+
out = out[:, :o_w.shape[1]]
|
| 810 |
|
| 811 |
self._record(f'{pfx}.attn_output.weight', out)
|
| 812 |
attn_out = out @ o_w.T
|
|
|
|
| 1111 |
# Process each layer
|
| 1112 |
for layer_idx in range(cfg['n_layers']):
|
| 1113 |
pfx = f"blk.{layer_idx}"
|
| 1114 |
+
|
| 1115 |
+
if self.linear_attn:
|
| 1116 |
+
# HPC-linearized attention: O(seq) per layer
|
| 1117 |
+
hidden = self.forward_layer_linear(hidden, layer_idx)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1118 |
else:
|
| 1119 |
+
has_fused_qkv = f'{pfx}.attn_qkv.weight' in self.model.tensor_infos
|
| 1120 |
+
has_separate_q = f'{pfx}.attn_q.weight' in self.model.tensor_infos
|
| 1121 |
+
has_linear_attn = f'{pfx}.ssm_in_qkv.weight' in self.model.tensor_infos
|
| 1122 |
+
|
| 1123 |
+
if has_fused_qkv:
|
| 1124 |
+
hidden = self.forward_qwen35_layer(hidden, layer_idx, cos_f, sin_f)
|
| 1125 |
+
elif has_linear_attn and not has_separate_q:
|
| 1126 |
+
hidden = self.forward_linear_attn_layer(hidden, layer_idx)
|
| 1127 |
+
else:
|
| 1128 |
+
hidden = self.forward_layer(hidden, layer_idx, cos_f, sin_f)
|
| 1129 |
if self.verbose and (layer_idx + 1) % 4 == 0:
|
| 1130 |
print(f" Layer {layer_idx + 1}/{cfg['n_layers']}", end='\r')
|
| 1131 |
|
|
|
|
| 1249 |
parser.add_argument('calibration', help='Calibration text file')
|
| 1250 |
parser.add_argument('-o', '--output', default='imatrix.dat',
|
| 1251 |
help='Output imatrix file (default: imatrix.dat)')
|
| 1252 |
+
parser.add_argument('--chunks', type=int, default=10,
|
| 1253 |
+
help='Number of token chunks to process (default: 10)')
|
| 1254 |
+
parser.add_argument('--chunk-size', type=int, default=4096,
|
| 1255 |
+
help='Tokens per chunk (default: 4096)')
|
| 1256 |
parser.add_argument('--no-hpc', action='store_true',
|
| 1257 |
help='Disable HPC cross-layer propagation')
|
| 1258 |
+
parser.add_argument('--quadratic-attn', action='store_true',
|
| 1259 |
+
help='Use full O(seq²) attention instead of HPC-linearized O(seq)')
|
| 1260 |
parser.add_argument('--verbose', action='store_true',
|
| 1261 |
help='Per-layer statistics')
|
| 1262 |
args = parser.parse_args()
|
|
|
|
| 1306 |
|
| 1307 |
# ── Forward pass ──
|
| 1308 |
print(" Running forward passes...")
|
| 1309 |
+
use_linear = not args.quadratic_attn
|
| 1310 |
+
runner = TransformerRunner(model, config, verbose=args.verbose, linear_attn=use_linear)
|
| 1311 |
+
if use_linear:
|
| 1312 |
+
print(f" Attention mode: HPC-linearized O(seq) — chunk_size={args.chunk_size}")
|
| 1313 |
+
else:
|
| 1314 |
+
print(f" Attention mode: full O(seq²) softmax — chunk_size={args.chunk_size}")
|
| 1315 |
|
| 1316 |
for i, chunk in enumerate(chunks):
|
| 1317 |
elapsed = time.time() - start_time
|