CompressedGemma commited on
Commit
a5c5f6c
·
verified ·
1 Parent(s): f67ea3a

This should do it

Browse files
Files changed (1) hide show
  1. 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, n_head * head_dim]
541
- k = normed @ k_w.T # [seq, n_head_kv * head_dim]
542
  v = normed @ v_w.T
543
 
544
- q = q.reshape(seq_len, n_head, head_dim)
545
- k = k.reshape(seq_len, n_head_kv, head_dim)
546
- v = v.reshape(seq_len, n_head_kv, head_dim)
547
 
548
- q = apply_rope(q, cos_f, sin_f)
549
- k = apply_rope(k, cos_f, sin_f)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
- # Attention: [n_head, seq, head_dim] @ [n_head, head_dim, seq]
558
- q_t = q.transpose(1, 0, 2) # [n_head, seq, head_dim]
559
- k_t = k.transpose(1, 0, 2)
560
- v_t = v.transpose(1, 0, 2)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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, head_dim]
576
- out = out.transpose(1, 0, 2).reshape(seq_len, -1) # [seq, n_embd]
 
 
 
 
 
 
 
 
 
 
 
 
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
- has_fused_qkv = f'{pfx}.attn_qkv.weight' in self.model.tensor_infos
882
- has_separate_q = f'{pfx}.attn_q.weight' in self.model.tensor_infos
883
- has_linear_attn = f'{pfx}.ssm_in_qkv.weight' in self.model.tensor_infos
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
- # Standard transformer (LLaMA, Mistral, Gemma, etc.)
893
- hidden = self.forward_layer(hidden, layer_idx, cos_f, sin_f)
 
 
 
 
 
 
 
 
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=100,
1018
- help='Number of token chunks to process (default: 100)')
1019
- parser.add_argument('--chunk-size', type=int, default=512,
1020
- help='Tokens per chunk (default: 512)')
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
- runner = TransformerRunner(model, config, verbose=args.verbose)
 
 
 
 
 
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