Kernels
dongseokmotif Claude Sonnet 4.6 commited on
Commit
9e14f5a
·
1 Parent(s): d7e2d58

feat: add k_ratio for Q/K split control in MLA QK clipping [skip-build]

Browse files

- QKClipInfo에 k_ratio 필드 추가 (기본값 0.0 = 현재 동작 유지)
- compute_scales: wkv_b를 k_ratio>0일 때 활성화, exponent를 γ^(1-r)/γ^r로
분리. 근거: K gradient의 유효 신호가 √G 스케일로 감소하므로
clip/recovery 균등 조건 → r = 1/(1+√G)
- wkv_b H_global 계산 버그 수정: head_dim 대신 qk_nope+v_head_dim 사용
- compute_scales에 name 파라미터 추가 (로그 식별용)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

torch-ext/optimizer/muon.py CHANGED
@@ -362,7 +362,7 @@ class Muon(torch.optim.Optimizer):
362
  qk_clip_state = get_qk_clip_info(self.clip_config, n, qk_logits)
363
 
364
  scales_full = compute_scales(
365
- p, qk_clip_state) if qk_clip_state is not None else None
366
  if scales_full is not None:
367
  qk_clip(p, scales_full, qk_clip_state)
368
 
@@ -409,7 +409,7 @@ class Muon(torch.optim.Optimizer):
409
  qk_clip_state = get_qk_clip_info(self.clip_config, n,
410
  qk_logits)
411
  scales_full = compute_scales(
412
- p, qk_clip_state) if qk_clip_state is not None else None
413
  if scales_full is not None:
414
  qk_clip(p, scales_full, qk_clip_state)
415
 
@@ -476,7 +476,7 @@ class Muon(torch.optim.Optimizer):
476
  qk_clip_state = get_qk_clip_info(
477
  self.clip_config, n, qk_logits)
478
  scales_full = compute_scales(
479
- p, qk_clip_state
480
  ) if qk_clip_state is not None else None
481
  if scales_full is not None:
482
  ratio = p.shape[0] // scales_full.shape[0]
@@ -555,7 +555,7 @@ class Muon(torch.optim.Optimizer):
555
  qk_clip_state = get_qk_clip_info(
556
  self.clip_config, n, qk_logits)
557
  scales_full = compute_scales(
558
- p, qk_clip_state
559
  ) if qk_clip_state is not None else None
560
  if scales_full is not None:
561
  ratio = p.shape[0] // scales_full.shape[0]
 
362
  qk_clip_state = get_qk_clip_info(self.clip_config, n, qk_logits)
363
 
364
  scales_full = compute_scales(
365
+ p, qk_clip_state, n) if qk_clip_state is not None else None
366
  if scales_full is not None:
367
  qk_clip(p, scales_full, qk_clip_state)
368
 
 
409
  qk_clip_state = get_qk_clip_info(self.clip_config, n,
410
  qk_logits)
411
  scales_full = compute_scales(
412
+ p, qk_clip_state, n) if qk_clip_state is not None else None
413
  if scales_full is not None:
414
  qk_clip(p, scales_full, qk_clip_state)
415
 
 
476
  qk_clip_state = get_qk_clip_info(
477
  self.clip_config, n, qk_logits)
478
  scales_full = compute_scales(
479
+ p, qk_clip_state, n
480
  ) if qk_clip_state is not None else None
481
  if scales_full is not None:
482
  ratio = p.shape[0] // scales_full.shape[0]
 
555
  qk_clip_state = get_qk_clip_info(
556
  self.clip_config, n, qk_logits)
557
  scales_full = compute_scales(
558
+ p, qk_clip_state, n
559
  ) if qk_clip_state is not None else None
560
  if scales_full is not None:
561
  ratio = p.shape[0] // scales_full.shape[0]
torch-ext/optimizer/pipeline.py CHANGED
@@ -322,7 +322,7 @@ def _update_params(
322
  state = param_to_state[id(p)]
323
  if state.qk_clip_state is None:
324
  continue
325
- scales_full = compute_scales(p, state.qk_clip_state)
326
  if scales_full is not None:
327
  ratio = p.shape[0] // scales_full.shape[0]
328
  idx0 = state.rank_indices[rank][0]
 
322
  state = param_to_state[id(p)]
323
  if state.qk_clip_state is None:
324
  continue
325
+ scales_full = compute_scales(p, state.qk_clip_state, state.name)
326
  if scales_full is not None:
327
  ratio = p.shape[0] // scales_full.shape[0]
328
  idx0 = state.rank_indices[rank][0]
torch-ext/optimizer/qk_clip.py CHANGED
@@ -63,6 +63,9 @@ class QKClipInfo:
63
  qk_nope_head_dim: int = 0
64
  qk_rope_head_dim: int = 0
65
  v_head_dim: int = 0
 
 
 
66
 
67
 
68
  def get_qk_clip_info(clip_config, n, qk_logits):
@@ -85,6 +88,7 @@ def get_qk_clip_info(clip_config, n, qk_logits):
85
  threshold = clip_config.get('threshold')
86
  kind, layer_idx = parse_qk_layer(n)
87
  is_mla = clip_config.get('is_mla', False)
 
88
 
89
  logit, indices = None, []
90
  if qk_logits is not None and kind is not None:
@@ -110,6 +114,7 @@ def get_qk_clip_info(clip_config, n, qk_logits):
110
  qk_nope_head_dim=clip_config['qk_nope_head_dim'],
111
  qk_rope_head_dim=clip_config['qk_rope_head_dim'],
112
  v_head_dim=clip_config['v_head_dim'],
 
113
  )
114
  else:
115
  return QKClipInfo(
@@ -121,35 +126,62 @@ def get_qk_clip_info(clip_config, n, qk_logits):
121
  )
122
 
123
 
124
- def compute_scales(p, qk_clip_state):
125
  """Compute per-head scaling factors for QK clipping.
126
 
127
- Returns scales tensor (√γ per head) if any head exceeds threshold, else None.
128
- For MLA wkv_b, effective row stride is qk_nope_head_dim + v_head_dim.
 
 
 
 
 
 
 
129
  """
130
  kind = qk_clip_state.kind
 
 
 
 
 
131
  indices = qk_clip_state.indices
132
  head_dim = qk_clip_state.head_dim
133
  threshold = qk_clip_state.threshold
134
  logit = qk_clip_state.logit
135
 
136
- # Check if any head exceeds threshold before allocating.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
137
  head_scales = {}
138
  for logit_idx, head_idx in enumerate(indices):
139
  v_ele = float(logit[logit_idx])
140
  if v_ele > threshold:
141
- new_scale = math.sqrt(threshold / v_ele)
142
  if head_idx not in head_scales or new_scale < head_scales[head_idx]:
143
  head_scales[head_idx] = new_scale
144
  logger.info(
145
- f"[{kind}] Head {head_idx} exceeded threshold "
146
  f"(value={v_ele:.4f}, threshold={threshold:.4f}) -> applying scale={new_scale:.4f}"
147
  )
148
 
149
  if not head_scales:
150
  return None
151
 
152
- # For MLA wkv_b, each KV head spans qk_nope_head_dim + v_head_dim rows
153
  if qk_clip_state.is_mla and kind == 'wkv_b':
154
  effective_head_dim = qk_clip_state.qk_nope_head_dim + qk_clip_state.v_head_dim
155
  else:
@@ -167,12 +199,13 @@ def qk_clip(p, scales, info):
167
 
168
  Args:
169
  p: Parameter (nn.Parameter or raw tensor).
170
- scales: [n_heads] tensor, each element = √γ_h.
 
171
  info: QKClipInfo with kind, head_dim, and MLA sub-head dimensions.
172
 
173
- MLA sub-region scaling per Algorithm 1 (MuonClip):
174
- wq_b: q_nope rows → √γ, q_pe rows → γ
175
- wkv_b: k_nope rows → √γ, v rows → unchanged
176
  """
177
  W = p.data if isinstance(p, torch.nn.Parameter) else p
178
 
@@ -186,13 +219,12 @@ def qk_clip(p, scales, info):
186
  qk_nope = info.qk_nope_head_dim
187
  qk_head_dim = qk_nope + info.qk_rope_head_dim
188
  W_3d = W.view(-1, qk_head_dim, W.shape[1]) # [H, qk_head_dim, in_dim]
189
- W_3d[:, :qk_nope, :].mul_(scales.view(-1, 1, 1)) # q_nope → √γ
190
- W_3d[:, qk_nope:, :].mul_((scales * scales).view(-1, 1,
191
- 1)) # q_pe → γ
192
 
193
  elif info.kind == 'wkv_b':
194
  qk_nope = info.qk_nope_head_dim
195
  kv_stride = qk_nope + info.v_head_dim
196
  W_3d = W.view(-1, kv_stride, W.shape[1]) # [H, kv_stride, in_dim]
197
- W_3d[:, :qk_nope, :].mul_(scales.view(-1, 1, 1)) # k_nope → √γ
198
  # v rows: not touched (k_R shared rotary unchanged)
 
63
  qk_nope_head_dim: int = 0
64
  qk_rope_head_dim: int = 0
65
  v_head_dim: int = 0
66
+ # Fraction of log-reduction assigned to K (wkv_b). Q gets (1-k_ratio).
67
+ # 0.0 = all Q (K not clipped), 0.5 = equal split (MHA-like), 1.0 = all K.
68
+ k_ratio: float = 0.0
69
 
70
 
71
  def get_qk_clip_info(clip_config, n, qk_logits):
 
88
  threshold = clip_config.get('threshold')
89
  kind, layer_idx = parse_qk_layer(n)
90
  is_mla = clip_config.get('is_mla', False)
91
+ k_ratio = float(clip_config.get('k_ratio', 0.0))
92
 
93
  logit, indices = None, []
94
  if qk_logits is not None and kind is not None:
 
114
  qk_nope_head_dim=clip_config['qk_nope_head_dim'],
115
  qk_rope_head_dim=clip_config['qk_rope_head_dim'],
116
  v_head_dim=clip_config['v_head_dim'],
117
+ k_ratio=k_ratio,
118
  )
119
  else:
120
  return QKClipInfo(
 
126
  )
127
 
128
 
129
+ def compute_scales(p, qk_clip_state, name: str = ''):
130
  """Compute per-head scaling factors for QK clipping.
131
 
132
+ Returns scales tensor per head if any head exceeds threshold, else None.
133
+
134
+ Scale convention (let γ = threshold/value):
135
+ MHA/GQA wq/wk : scale = √γ on both Q and K; product = γ.
136
+ MLA wq_b : scale = γ^(1 - k_ratio). Q carries (1-k_ratio) of the
137
+ log-reduction; k_ratio=0 → full γ on Q only (K unclipped).
138
+ MLA wkv_b : scale = γ^k_ratio applied to k_nope rows only (v rows
139
+ untouched). Returns None when k_ratio=0 (K unclipped).
140
+ Together wq_b × wkv_b deliver γ^(1-k_ratio) × γ^k_ratio = γ end-to-end.
141
  """
142
  kind = qk_clip_state.kind
143
+
144
+ # MLA wkv_b: skip entirely when k_ratio=0 (all reduction on Q).
145
+ if qk_clip_state.is_mla and kind == 'wkv_b' and qk_clip_state.k_ratio == 0.0:
146
+ return None
147
+
148
  indices = qk_clip_state.indices
149
  head_dim = qk_clip_state.head_dim
150
  threshold = qk_clip_state.threshold
151
  logit = qk_clip_state.logit
152
 
153
+ # Exponent for (threshold/v_ele)^exponent:
154
+ # MLA wq_b → 1 - k_ratio (Q carries this fraction of log-reduction)
155
+ # MLA wkv_b → k_ratio (K carries the rest; applied to k_nope rows only)
156
+ # MHA/GQA → 0.5 (equal split; equivalent to k_ratio=0.5)
157
+ #
158
+ # Principled k_ratio for Q:K = G:1 GQA: 1 / (1 + sqrt(G))
159
+ # K's gradient is a sum of G Q-head terms that partially cancel, so its
160
+ # effective recovery signal scales as 1/sqrt(G) (random-walk argument).
161
+ # Setting clip/recovery equal for both sides:
162
+ # (1-r)/1 = r/(1/sqrt(G)) => r = 1/(1+sqrt(G))
163
+ # G=1 → 0.5 (recovers MHA), G=4 → ≈0.33, G=8 → ≈0.26.
164
+ if qk_clip_state.is_mla:
165
+ exponent = (1.0 - qk_clip_state.k_ratio) if kind == 'wq_b' else qk_clip_state.k_ratio
166
+ else:
167
+ exponent = 0.5
168
+
169
  head_scales = {}
170
  for logit_idx, head_idx in enumerate(indices):
171
  v_ele = float(logit[logit_idx])
172
  if v_ele > threshold:
173
+ new_scale = (threshold / v_ele) ** exponent
174
  if head_idx not in head_scales or new_scale < head_scales[head_idx]:
175
  head_scales[head_idx] = new_scale
176
  logger.info(
177
+ f"[{kind}] {name} Head {head_idx} exceeded threshold "
178
  f"(value={v_ele:.4f}, threshold={threshold:.4f}) -> applying scale={new_scale:.4f}"
179
  )
180
 
181
  if not head_scales:
182
  return None
183
 
184
+ # wkv_b row-stride per head = qk_nope + v_head_dim (not head_dim)
185
  if qk_clip_state.is_mla and kind == 'wkv_b':
186
  effective_head_dim = qk_clip_state.qk_nope_head_dim + qk_clip_state.v_head_dim
187
  else:
 
199
 
200
  Args:
201
  p: Parameter (nn.Parameter or raw tensor).
202
+ scales: [n_heads] tensor of per-head scale factors (γ^exponent).
203
+ Exponent semantics depend on kind — see compute_scales docstring.
204
  info: QKClipInfo with kind, head_dim, and MLA sub-head dimensions.
205
 
206
+ MLA wq_b: γ^(1-k_ratio) applied uniformly to q_nope and q_pe rows.
207
+ MLA wkv_b: γ^k_ratio applied to k_nope rows only; v rows untouched.
208
+ MHA/GQA: √γ applied to all rows in each head.
209
  """
210
  W = p.data if isinstance(p, torch.nn.Parameter) else p
211
 
 
219
  qk_nope = info.qk_nope_head_dim
220
  qk_head_dim = qk_nope + info.qk_rope_head_dim
221
  W_3d = W.view(-1, qk_head_dim, W.shape[1]) # [H, qk_head_dim, in_dim]
222
+ W_3d[:, :qk_nope, :].mul_(scales.view(-1, 1, 1)) # q_nope → γ^(1-k_ratio)
223
+ W_3d[:, qk_nope:, :].mul_(scales.view(-1, 1, 1)) # q_pe → γ^(1-k_ratio)
 
224
 
225
  elif info.kind == 'wkv_b':
226
  qk_nope = info.qk_nope_head_dim
227
  kv_stride = qk_nope + info.v_head_dim
228
  W_3d = W.view(-1, kv_stride, W.shape[1]) # [H, kv_stride, in_dim]
229
+ W_3d[:, :qk_nope, :].mul_(scales.view(-1, 1, 1)) # k_nope → γ^k_ratio
230
  # v rows: not touched (k_R shared rotary unchanged)