CompressedGemma commited on
Commit
59598e4
·
verified ·
1 Parent(s): 698bf6e

Upload segmentation.py

Browse files
Files changed (1) hide show
  1. segmentation.py +106 -43
segmentation.py CHANGED
@@ -269,65 +269,128 @@ def segment_stage(
269
  ) -> tuple[np.ndarray, np.ndarray, int]:
270
  """Run the label fixed-point loop.
271
 
272
- For tensors with ``n_blocks <= 256`` (or ``k > 16``), refines a per-block
273
- label vector directly. For larger tensors (``n_blocks > 256, k <= 16``),
274
- constructs a 256-entry generalized permutation table (Entry 0 = natural
275
- order, Entries 1..2 = column RMS/abs profiles, Entries 3..255 = prototype
276
- blocks refined by ``_segment_stage_dense``) and assigns each superblock its
277
- optimal 1-byte table index (+0.03125 bpw static+index storage tax).
278
  """
279
- Xo = np.asarray(X, dtype=np.float64)
280
- Wo = None if W is None else np.asarray(W, dtype=np.float64)
281
  k = max(1, int(k))
282
- n_blocks, B = Xo.shape
283
  M = int(getattr(cfg, "segment_table_size", SEG_TABLE_SIZE))
284
  if M <= 0 or n_blocks <= M or B != 256 or k > 16:
 
 
285
  return _segment_stage_dense(Xo, Wo, cfg, proxy, k)
286
 
287
- table_labels = np.empty((M, 256), dtype=np.int64)
 
288
  # Entry 0: natural order (monotone labels so make_order is identity [0..255])
289
- table_labels[0] = _order_to_canonical_labels(np.arange(256, dtype=np.int64), k)
290
- # Entry 1 & 2: column-wide RMS and abs profiles
291
- col_rms = np.sqrt(np.mean(Xo * Xo, axis=0))
292
- table_labels[1] = _order_to_canonical_labels(np.argsort(col_rms, kind="stable"), k)
293
- col_abs = np.mean(np.abs(Xo), axis=0)
294
- table_labels[2] = _order_to_canonical_labels(np.argsort(col_abs, kind="stable"), k)
 
 
295
 
296
  # Entries 3..M-1: prototype blocks refined with the proxy fixed-point loop
297
- proto_idx = np.linspace(0, n_blocks - 1, M - 3, dtype=np.int64)
298
- Wp = None if Wo is None else Wo[proto_idx]
299
- ord_p, _, rounds = _segment_stage_dense(Xo[proto_idx], Wp, cfg, proxy, k)
300
- for i in range(M - 3):
301
- table_labels[3 + i] = _order_to_canonical_labels(ord_p[i], k)
 
 
302
 
303
- table_orders = make_order(table_labels) # (M, 256)
304
 
305
- # Prescreen top-8 table entries per block via between-subblock variance GEMM
306
  P = np.zeros((256, M * 16), dtype=np.float32)
307
  for m in range(M):
308
  for g in range(16):
309
  P[table_orders[m, 16 * g : 16 * g + 16], m * 16 + g] = 0.25
310
- Xf = Xo.astype(np.float32)
311
- scores = ((Xf @ P) ** 2).reshape(n_blocks, M, 16).sum(axis=2) + 0.5 * (
312
- (np.abs(Xf) @ P) ** 2
313
- ).reshape(n_blocks, M, 16).sum(axis=2)
314
- topK = 8
315
- top_idx = np.argpartition(-scores, topK, axis=1)[:, :topK]
316
- top_idx[:, 0] = 0 # always include identity (Entry 0) so no block ever regresses
317
-
318
- best_sse = np.full(n_blocks, np.inf, dtype=np.float64)
319
  best_m = np.zeros(n_blocks, dtype=np.int64)
320
- for k_i in range(topK):
321
- m_col = top_idx[:, k_i]
322
- ords = table_orders[m_col]
323
- Xk = apply_order(Xo, ords)
324
- Wk = None if Wo is None else apply_order(Wo, ords)
325
- xh = proxy(Xk, Wk)
326
- err = Xk - xh
327
- sse_k = np.sum(err * err if Wk is None else Wk * err * err, axis=1)
328
- better = sse_k < best_sse
329
- best_sse = np.where(better, sse_k, best_sse)
330
- best_m = np.where(better, m_col, best_m)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
331
 
332
  return table_orders[best_m], table_labels[best_m], rounds
333
 
 
269
  ) -> tuple[np.ndarray, np.ndarray, int]:
270
  """Run the label fixed-point loop.
271
 
272
+ For tensors with ``n_blocks <= M`` (or ``k > 16``), refines a per-block
273
+ label vector directly. For larger tensors (``n_blocks > M, k <= 16``),
274
+ constructs an ``M``-entry generalized permutation lookup table (Entry 0 =
275
+ natural order, Entries 1..2 = column RMS/abs profiles, Entries 3..M-1 =
276
+ prototype blocks refined by ``_segment_stage_dense``) and assigns each
277
+ superblock its optimal table index in chunked float32 / CUDA batches.
278
  """
 
 
279
  k = max(1, int(k))
280
+ n_blocks, B = X.shape
281
  M = int(getattr(cfg, "segment_table_size", SEG_TABLE_SIZE))
282
  if M <= 0 or n_blocks <= M or B != 256 or k > 16:
283
+ Xo = np.asarray(X, dtype=np.float64)
284
+ Wo = None if W is None else np.asarray(W, dtype=np.float64)
285
  return _segment_stage_dense(Xo, Wo, cfg, proxy, k)
286
 
287
+ Xf = np.ascontiguousarray(X, dtype=np.float32)
288
+ table_labels = np.empty((M, 256), dtype=np.uint8)
289
  # Entry 0: natural order (monotone labels so make_order is identity [0..255])
290
+ table_labels[0] = _order_to_canonical_labels(np.arange(256, dtype=np.int64), k).astype(np.uint8)
291
+ # Entry 1 & 2: column-wide RMS and abs profiles (sampled across up to 4096 blocks)
292
+ s_step = max(1, n_blocks // 4096)
293
+ X_sample = Xf[::s_step].astype(np.float64)
294
+ col_rms = np.sqrt(np.mean(X_sample * X_sample, axis=0))
295
+ table_labels[1] = _order_to_canonical_labels(np.argsort(col_rms, kind="stable"), k).astype(np.uint8)
296
+ col_abs = np.mean(np.abs(X_sample), axis=0)
297
+ table_labels[2] = _order_to_canonical_labels(np.argsort(col_abs, kind="stable"), k).astype(np.uint8)
298
 
299
  # Entries 3..M-1: prototype blocks refined with the proxy fixed-point loop
300
+ n_proto = M - 3
301
+ proto_idx = np.linspace(0, n_blocks - 1, n_proto, dtype=np.int64)
302
+ Xp = Xf[proto_idx].astype(np.float64)
303
+ Wp = None if W is None else np.asarray(W[proto_idx], dtype=np.float64)
304
+ ord_p, _, rounds = _segment_stage_dense(Xp, Wp, cfg, proxy, k)
305
+ for i in range(n_proto):
306
+ table_labels[3 + i] = _order_to_canonical_labels(ord_p[i], k).astype(np.uint8)
307
 
308
+ table_orders = make_order(table_labels) # (M, 256) int64
309
 
310
+ # Build sub-block projection matrix P: (256, M * 16)
311
  P = np.zeros((256, M * 16), dtype=np.float32)
312
  for m in range(M):
313
  for g in range(16):
314
  P[table_orders[m, 16 * g : 16 * g + 16], m * 16 + g] = 0.25
315
+
316
+ topK = min(4, M)
 
 
 
 
 
 
 
317
  best_m = np.zeros(n_blocks, dtype=np.int64)
318
+ nmax_sub = 3.0 if str(getattr(cfg, "quant_format", "q2_k")) != "q3_k" else 7.0
319
+
320
+ use_torch_cuda = False
321
+ if getattr(cfg, "device", "auto") != "cpu":
322
+ try:
323
+ import torch
324
+ use_torch_cuda = torch.cuda.is_available()
325
+ except Exception:
326
+ use_torch_cuda = False
327
+
328
+ if use_torch_cuda:
329
+ import torch
330
+ dev_str = "cuda" if cfg.device == "auto" else cfg.device
331
+ dev = torch.device(dev_str)
332
+ P_t = torch.from_numpy(P).to(dev)
333
+ orders_t = torch.from_numpy(table_orders).to(dev)
334
+ chunk = 16384
335
+ with torch.no_grad():
336
+ for s in range(0, n_blocks, chunk):
337
+ e = min(s + chunk, n_blocks)
338
+ xb = torch.from_numpy(Xf[s:e]).to(dev)
339
+ sc = (xb @ P_t).square().view(-1, M, 16).sum(dim=2) + 0.5 * (
340
+ (xb.abs() @ P_t).square().view(-1, M, 16).sum(dim=2)
341
+ )
342
+ top_idx = torch.topk(sc, k=topK, dim=1).indices
343
+ top_idx[:, 0] = 0
344
+ ords = orders_t[top_idx]
345
+ u = torch.gather(xb.unsqueeze(1).expand(-1, topK, -1), 2, ords).view(
346
+ -1, topK, 16, 16
347
+ )
348
+ umin = torch.clamp_max(u.amin(dim=3, keepdim=True), 0.0)
349
+ umax = u.amax(dim=3, keepdim=True)
350
+ step = torch.clamp_min((umax - umin) * (1.0 / nmax_sub), 1.0e-30)
351
+ q = torch.clamp(torch.round((u - umin) / step), 0.0, nmax_sub)
352
+ err = u - (umin + step * q)
353
+ if W is not None:
354
+ wb = torch.from_numpy(np.ascontiguousarray(W[s:e], dtype=np.float32)).to(dev)
355
+ wg = torch.gather(wb.unsqueeze(1).expand(-1, topK, -1), 2, ords).view(
356
+ -1, topK, 16, 16
357
+ )
358
+ sse = (wg * err.square()).sum(dim=(2, 3))
359
+ else:
360
+ sse = err.square().sum(dim=(2, 3))
361
+ best_local = sse.argmin(dim=1)
362
+ best_m[s:e] = (
363
+ top_idx[torch.arange(e - s, device=dev), best_local].cpu().numpy()
364
+ )
365
+ else:
366
+ chunk = 8192
367
+ for s in range(0, n_blocks, chunk):
368
+ e = min(s + chunk, n_blocks)
369
+ xb = Xf[s:e]
370
+ sc = ((xb @ P) ** 2).reshape(e - s, M, 16).sum(axis=2) + 0.5 * (
371
+ (np.abs(xb) @ P) ** 2
372
+ ).reshape(e - s, M, 16).sum(axis=2)
373
+ top_idx = np.argpartition(-sc, topK - 1, axis=1)[:, :topK]
374
+ top_idx[:, 0] = 0
375
+ ords = table_orders[top_idx]
376
+ u = np.take_along_axis(xb[:, None, :], ords, axis=2).reshape(
377
+ e - s, topK, 16, 16
378
+ )
379
+ umin = np.minimum(u.min(axis=3, keepdims=True), 0.0)
380
+ umax = u.max(axis=3, keepdims=True)
381
+ step = np.maximum((umax - umin) * (1.0 / nmax_sub), 1.0e-30)
382
+ q = np.clip(np.rint((u - umin) / step), 0.0, nmax_sub)
383
+ err = u - (umin + step * q)
384
+ if W is not None:
385
+ wb = np.ascontiguousarray(W[s:e], dtype=np.float32)
386
+ wg = np.take_along_axis(wb[:, None, :], ords, axis=2).reshape(
387
+ e - s, topK, 16, 16
388
+ )
389
+ sse = np.sum(wg * err * err, axis=(2, 3))
390
+ else:
391
+ sse = np.sum(err * err, axis=(2, 3))
392
+ best_local = np.argmin(sse, axis=1)
393
+ best_m[s:e] = top_idx[np.arange(e - s), best_local]
394
 
395
  return table_orders[best_m], table_labels[best_m], rounds
396