Scott/Codex commited on
Commit ·
468a571
1
Parent(s): 3c513bc
Add sublinear attention v2 improvements
Browse files- README.md +1 -1
- nB300_agillm4_vram_dblock.py +101 -24
- relaunch_agillm4_dblock_sg2.sh +24 -0
- sublinear_improved.py +48 -44
- sublinear_improved_snippet.py +25 -31
README.md
CHANGED
|
@@ -83,6 +83,6 @@ and a tighter structured-sublinear attention profile (`window=128`, `stride=128`
|
|
| 83 |
an ETA around 326 days, under the 1y+90d target, while keeping ctx=1280, B=2,
|
| 84 |
DiffusionBlocks, gradient-checkpointed blocks, tied heads, and structured masks.
|
| 85 |
|
| 86 |
-
Sublinear coverage update 2026-05-29: the saved AGILLM-4 trainer snapshot now matches the live
|
| 87 |
|
| 88 |
License: Apache-2.0 (matching the upstream method).
|
|
|
|
| 83 |
an ETA around 326 days, under the 1y+90d target, while keeping ctx=1280, B=2,
|
| 84 |
DiffusionBlocks, gradient-checkpointed blocks, tied heads, and structured masks.
|
| 85 |
|
| 86 |
+
Sublinear coverage update 2026-05-29: the saved AGILLM-4 trainer snapshot now matches the live v2 sparse global memory path. It fixes gathered ALiBi distance, suppresses duplicate local/anchor candidates before softmax, uses hybrid full-span + recent-tail anchors with explicit `--sublinear_sinks` and `--sublinear_recent_anchors`, and includes optional pooled K/V landmark summaries behind `--sublinear_pooled_landmarks`. At the live 128/128/128 profile it keeps deep-past coverage while preserving recent anchors and the same VRAM-first key budget. See `sublinear_improved_snippet.py` for the minimal blocks and `sublinear_improved.py` for the coverage demo/standalone selector.
|
| 87 |
|
| 88 |
License: Apache-2.0 (matching the upstream method).
|
nB300_agillm4_vram_dblock.py
CHANGED
|
@@ -926,6 +926,9 @@ DEFAULT_SUBLINEAR_WINDOW = _env_int("AGILLM_SUBLINEAR_WINDOW", 256)
|
|
| 926 |
DEFAULT_SUBLINEAR_STRIDE = _env_int("AGILLM_SUBLINEAR_STRIDE", 64)
|
| 927 |
DEFAULT_SUBLINEAR_MAX_ANCHORS = _env_int("AGILLM_SUBLINEAR_MAX_ANCHORS", 256)
|
| 928 |
DEFAULT_SUBLINEAR_CHUNK = _env_int("AGILLM_SUBLINEAR_CHUNK", 128)
|
|
|
|
|
|
|
|
|
|
| 929 |
DEFAULT_ANCHOR_MEMORY = bool(_env_int("AGILLM_ANCHOR_MEMORY", 0))
|
| 930 |
DEFAULT_ANCHOR_STRIDE = _env_int("AGILLM_ANCHOR_STRIDE", 256)
|
| 931 |
DEFAULT_ANCHOR_MAX = _env_int("AGILLM_ANCHOR_MAX", 2048)
|
|
@@ -1289,6 +1292,9 @@ class TuneableAttentionMHA(nn.Module):
|
|
| 1289 |
sublinear_stride: int = DEFAULT_SUBLINEAR_STRIDE,
|
| 1290 |
sublinear_max_anchors: int = DEFAULT_SUBLINEAR_MAX_ANCHORS,
|
| 1291 |
sublinear_chunk: int = DEFAULT_SUBLINEAR_CHUNK,
|
|
|
|
|
|
|
|
|
|
| 1292 |
):
|
| 1293 |
super().__init__()
|
| 1294 |
assert d % h == 0
|
|
@@ -1299,6 +1305,12 @@ class TuneableAttentionMHA(nn.Module):
|
|
| 1299 |
self.sublinear_stride = max(0, int(sublinear_stride))
|
| 1300 |
self.sublinear_max_anchors = max(0, int(sublinear_max_anchors))
|
| 1301 |
self.sublinear_chunk = max(1, int(sublinear_chunk))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1302 |
# Exact n1 harvest: one fused QKV projection is mathematically the same
|
| 1303 |
# as three independent bias-free Linear(d, d) projections with their
|
| 1304 |
# weights stacked along out_features.
|
|
@@ -1395,6 +1407,29 @@ class TuneableAttentionMHA(nn.Module):
|
|
| 1395 |
return (idx // block) <= (q_pos[:, None] // block)
|
| 1396 |
raise ValueError(f"unknown structured attention mask kind: {kind}")
|
| 1397 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1398 |
def _sublinear_attention(self, q, k, v, attn_mask=None, rel_bias_tokens=None):
|
| 1399 |
"""Local-window + landmark attention: O(N * (window + N/stride))."""
|
| 1400 |
bsz, heads, q_len, _ = q.shape
|
|
@@ -1407,25 +1442,20 @@ class TuneableAttentionMHA(nn.Module):
|
|
| 1407 |
if self.use_relpos and rel_bias_tokens is not None:
|
| 1408 |
slopes = _alibi_slopes(self.h).to(device=device, dtype=torch.float32)
|
| 1409 |
|
| 1410 |
-
|
| 1411 |
-
|
| 1412 |
-
|
| 1413 |
-
|
| 1414 |
-
|
| 1415 |
-
|
| 1416 |
-
|
| 1417 |
-
|
| 1418 |
-
)
|
| 1419 |
-
|
| 1420 |
-
|
| 1421 |
-
|
| 1422 |
-
|
| 1423 |
-
|
| 1424 |
-
anchors = torch.empty(0, device=device, dtype=torch.long)
|
| 1425 |
-
# attention sinks: always keep the first few tokens (StreamingLLM)
|
| 1426 |
-
_sink = int(getattr(self, "sublinear_sinks", 4))
|
| 1427 |
-
if _sink > 0 and k_len > 0:
|
| 1428 |
-
anchors = torch.cat([torch.arange(min(_sink, k_len), device=device, dtype=torch.long), anchors]).unique()
|
| 1429 |
|
| 1430 |
offsets = torch.arange(
|
| 1431 |
-self.sublinear_window,
|
|
@@ -1443,24 +1473,38 @@ class TuneableAttentionMHA(nn.Module):
|
|
| 1443 |
local_valid = (local_raw >= 0) & (local_raw < k_len)
|
| 1444 |
local_idx = local_raw.clamp(0, max(0, k_len - 1))
|
| 1445 |
|
|
|
|
|
|
|
| 1446 |
if anchors.numel():
|
| 1447 |
anchor_idx = anchors.view(1, -1).expand(cur, -1)
|
| 1448 |
-
|
|
|
|
|
|
|
|
|
|
| 1449 |
idx = torch.cat([local_idx, anchor_idx], dim=1)
|
| 1450 |
valid = torch.cat([local_valid, anchor_valid], dim=1)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1451 |
else:
|
| 1452 |
idx = local_idx
|
| 1453 |
valid = local_valid
|
|
|
|
|
|
|
| 1454 |
|
| 1455 |
structured_valid = self._structured_valid(attn_mask, q_pos, idx)
|
| 1456 |
if structured_valid is not None:
|
| 1457 |
valid = valid & structured_valid
|
| 1458 |
|
| 1459 |
-
k_sel = k[:, :, idx, :]
|
| 1460 |
scores = (q[:, :, q_start:q_end, :].unsqueeze(-2) * k_sel).sum(dim=-1) * scale
|
| 1461 |
|
| 1462 |
if slopes is not None:
|
| 1463 |
-
dist = (
|
| 1464 |
scores = scores + (-slopes * dist).to(scores.dtype)
|
| 1465 |
|
| 1466 |
if torch.is_tensor(attn_mask) and attn_mask.size(-1) == k_len and attn_mask.size(-2) >= q_end:
|
|
@@ -1470,7 +1514,6 @@ class TuneableAttentionMHA(nn.Module):
|
|
| 1470 |
|
| 1471 |
scores = scores.masked_fill(~valid.view(1, 1, cur, -1), float("-inf"))
|
| 1472 |
weights = torch.softmax(scores.float(), dim=-1).to(v.dtype)
|
| 1473 |
-
v_sel = v[:, :, idx, :]
|
| 1474 |
outputs.append((weights.unsqueeze(-1) * v_sel).sum(dim=-2))
|
| 1475 |
|
| 1476 |
return torch.cat(outputs, dim=2)
|
|
@@ -1544,6 +1587,9 @@ class Block(nn.Module):
|
|
| 1544 |
sublinear_stride: int = DEFAULT_SUBLINEAR_STRIDE,
|
| 1545 |
sublinear_max_anchors: int = DEFAULT_SUBLINEAR_MAX_ANCHORS,
|
| 1546 |
sublinear_chunk: int = DEFAULT_SUBLINEAR_CHUNK,
|
|
|
|
|
|
|
|
|
|
| 1547 |
):
|
| 1548 |
super().__init__()
|
| 1549 |
self.ln1, self.ln2 = nn.LayerNorm(d), nn.LayerNorm(d)
|
|
@@ -1556,6 +1602,9 @@ class Block(nn.Module):
|
|
| 1556 |
sublinear_stride=sublinear_stride,
|
| 1557 |
sublinear_max_anchors=sublinear_max_anchors,
|
| 1558 |
sublinear_chunk=sublinear_chunk,
|
|
|
|
|
|
|
|
|
|
| 1559 |
)
|
| 1560 |
self.ff = nn.Sequential(nn.Linear(d, 4 * d), nn.ReLU(), nn.Linear(4 * d, d))
|
| 1561 |
|
|
@@ -1581,6 +1630,9 @@ class Encoder(nn.Module):
|
|
| 1581 |
sublinear_stride: int = DEFAULT_SUBLINEAR_STRIDE,
|
| 1582 |
sublinear_max_anchors: int = DEFAULT_SUBLINEAR_MAX_ANCHORS,
|
| 1583 |
sublinear_chunk: int = DEFAULT_SUBLINEAR_CHUNK,
|
|
|
|
|
|
|
|
|
|
| 1584 |
anchor_memory: bool = DEFAULT_ANCHOR_MEMORY,
|
| 1585 |
anchor_stride: int = DEFAULT_ANCHOR_STRIDE,
|
| 1586 |
anchor_max: int = DEFAULT_ANCHOR_MAX,
|
|
@@ -1599,6 +1651,9 @@ class Encoder(nn.Module):
|
|
| 1599 |
sublinear_stride=sublinear_stride,
|
| 1600 |
sublinear_max_anchors=sublinear_max_anchors,
|
| 1601 |
sublinear_chunk=sublinear_chunk,
|
|
|
|
|
|
|
|
|
|
| 1602 |
)
|
| 1603 |
for _ in range(l)
|
| 1604 |
])
|
|
@@ -1610,6 +1665,9 @@ class Encoder(nn.Module):
|
|
| 1610 |
self.sublinear_stride = sublinear_stride
|
| 1611 |
self.sublinear_max_anchors = sublinear_max_anchors
|
| 1612 |
self.sublinear_chunk = sublinear_chunk
|
|
|
|
|
|
|
|
|
|
| 1613 |
self.anchor_memory_enabled = bool(anchor_memory)
|
| 1614 |
self.anchor_stride = int(anchor_stride)
|
| 1615 |
self.anchor_max = int(anchor_max)
|
|
@@ -2487,7 +2545,9 @@ def train(args):
|
|
| 2487 |
"AGILLM-4 runtime: "
|
| 2488 |
f"attn_backend={args.attn_backend} grad_checkpoint={args.grad_checkpoint} "
|
| 2489 |
f"sublinear_window={args.sublinear_window} sublinear_stride={args.sublinear_stride} "
|
| 2490 |
-
f"sublinear_max_anchors={args.sublinear_max_anchors} sublinear_chunk={args.sublinear_chunk}"
|
|
|
|
|
|
|
| 2491 |
)
|
| 2492 |
core = Encoder(
|
| 2493 |
cfg,
|
|
@@ -2498,6 +2558,9 @@ def train(args):
|
|
| 2498 |
sublinear_stride=args.sublinear_stride,
|
| 2499 |
sublinear_max_anchors=args.sublinear_max_anchors,
|
| 2500 |
sublinear_chunk=args.sublinear_chunk,
|
|
|
|
|
|
|
|
|
|
| 2501 |
anchor_memory=getattr(args, "anchor_memory", DEFAULT_ANCHOR_MEMORY),
|
| 2502 |
anchor_stride=getattr(args, "anchor_stride", DEFAULT_ANCHOR_STRIDE),
|
| 2503 |
anchor_max=getattr(args, "anchor_max", DEFAULT_ANCHOR_MAX),
|
|
@@ -2717,6 +2780,9 @@ def infer(args):
|
|
| 2717 |
sublinear_stride=args.sublinear_stride,
|
| 2718 |
sublinear_max_anchors=args.sublinear_max_anchors,
|
| 2719 |
sublinear_chunk=args.sublinear_chunk,
|
|
|
|
|
|
|
|
|
|
| 2720 |
anchor_memory=getattr(args, "anchor_memory", DEFAULT_ANCHOR_MEMORY),
|
| 2721 |
anchor_stride=getattr(args, "anchor_stride", DEFAULT_ANCHOR_STRIDE),
|
| 2722 |
anchor_max=getattr(args, "anchor_max", DEFAULT_ANCHOR_MAX),
|
|
@@ -2888,6 +2954,13 @@ def main():
|
|
| 2888 |
help="For --attn_backend sublinear, cap landmark candidates per query chunk.")
|
| 2889 |
tr.add_argument("--sublinear_chunk", type=int, default=DEFAULT_SUBLINEAR_CHUNK,
|
| 2890 |
help="For --attn_backend sublinear, query chunk size controlling peak gather memory.")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2891 |
tr.add_argument("--no_structured_masks", action="store_true",
|
| 2892 |
help="Disable structured causal/SAT masks for sublinear attention and fall back to dense masks.")
|
| 2893 |
tr.add_argument("--anchor_memory", action="store_true",
|
|
@@ -3009,6 +3082,10 @@ def main():
|
|
| 3009 |
inf.add_argument("--sublinear_stride", type=int, default=DEFAULT_SUBLINEAR_STRIDE)
|
| 3010 |
inf.add_argument("--sublinear_max_anchors", type=int, default=DEFAULT_SUBLINEAR_MAX_ANCHORS)
|
| 3011 |
inf.add_argument("--sublinear_chunk", type=int, default=DEFAULT_SUBLINEAR_CHUNK)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3012 |
inf.add_argument("--no_structured_masks", action="store_true")
|
| 3013 |
inf.add_argument("--nat_expand", type=int, default=2)
|
| 3014 |
inf.add_argument("--nat_passes", type=int, default=1)
|
|
|
|
| 926 |
DEFAULT_SUBLINEAR_STRIDE = _env_int("AGILLM_SUBLINEAR_STRIDE", 64)
|
| 927 |
DEFAULT_SUBLINEAR_MAX_ANCHORS = _env_int("AGILLM_SUBLINEAR_MAX_ANCHORS", 256)
|
| 928 |
DEFAULT_SUBLINEAR_CHUNK = _env_int("AGILLM_SUBLINEAR_CHUNK", 128)
|
| 929 |
+
DEFAULT_SUBLINEAR_SINKS = _env_int("AGILLM_SUBLINEAR_SINKS", 4)
|
| 930 |
+
DEFAULT_SUBLINEAR_RECENT_ANCHORS = _env_int("AGILLM_SUBLINEAR_RECENT_ANCHORS", -1) # -1 = half of max anchors
|
| 931 |
+
DEFAULT_SUBLINEAR_POOLED_LANDMARKS = bool(_env_int("AGILLM_SUBLINEAR_POOLED_LANDMARKS", 0))
|
| 932 |
DEFAULT_ANCHOR_MEMORY = bool(_env_int("AGILLM_ANCHOR_MEMORY", 0))
|
| 933 |
DEFAULT_ANCHOR_STRIDE = _env_int("AGILLM_ANCHOR_STRIDE", 256)
|
| 934 |
DEFAULT_ANCHOR_MAX = _env_int("AGILLM_ANCHOR_MAX", 2048)
|
|
|
|
| 1292 |
sublinear_stride: int = DEFAULT_SUBLINEAR_STRIDE,
|
| 1293 |
sublinear_max_anchors: int = DEFAULT_SUBLINEAR_MAX_ANCHORS,
|
| 1294 |
sublinear_chunk: int = DEFAULT_SUBLINEAR_CHUNK,
|
| 1295 |
+
sublinear_sinks: int = DEFAULT_SUBLINEAR_SINKS,
|
| 1296 |
+
sublinear_recent_anchors: int = DEFAULT_SUBLINEAR_RECENT_ANCHORS,
|
| 1297 |
+
sublinear_pooled_landmarks: bool = DEFAULT_SUBLINEAR_POOLED_LANDMARKS,
|
| 1298 |
):
|
| 1299 |
super().__init__()
|
| 1300 |
assert d % h == 0
|
|
|
|
| 1305 |
self.sublinear_stride = max(0, int(sublinear_stride))
|
| 1306 |
self.sublinear_max_anchors = max(0, int(sublinear_max_anchors))
|
| 1307 |
self.sublinear_chunk = max(1, int(sublinear_chunk))
|
| 1308 |
+
self.sublinear_sinks = max(0, int(sublinear_sinks))
|
| 1309 |
+
recent = int(sublinear_recent_anchors)
|
| 1310 |
+
if recent < 0:
|
| 1311 |
+
recent = self.sublinear_max_anchors // 2
|
| 1312 |
+
self.sublinear_recent_anchors = min(max(0, recent), self.sublinear_max_anchors)
|
| 1313 |
+
self.sublinear_pooled_landmarks = bool(sublinear_pooled_landmarks)
|
| 1314 |
# Exact n1 harvest: one fused QKV projection is mathematically the same
|
| 1315 |
# as three independent bias-free Linear(d, d) projections with their
|
| 1316 |
# weights stacked along out_features.
|
|
|
|
| 1407 |
return (idx // block) <= (q_pos[:, None] // block)
|
| 1408 |
raise ValueError(f"unknown structured attention mask kind: {kind}")
|
| 1409 |
|
| 1410 |
+
def _sublinear_anchor_positions(self, k_len: int, device):
|
| 1411 |
+
anchor_start = self.sublinear_stride - 1
|
| 1412 |
+
if self.sublinear_stride <= 0 or self.sublinear_max_anchors <= 0 or anchor_start >= k_len:
|
| 1413 |
+
anchors = torch.empty(0, device=device, dtype=torch.long)
|
| 1414 |
+
else:
|
| 1415 |
+
all_anchors = torch.arange(anchor_start, k_len, self.sublinear_stride, device=device, dtype=torch.long)
|
| 1416 |
+
if all_anchors.numel() <= self.sublinear_max_anchors:
|
| 1417 |
+
anchors = all_anchors
|
| 1418 |
+
else:
|
| 1419 |
+
recent_budget = min(self.sublinear_recent_anchors, self.sublinear_max_anchors)
|
| 1420 |
+
span_budget = max(0, self.sublinear_max_anchors - recent_budget)
|
| 1421 |
+
parts = []
|
| 1422 |
+
if span_budget > 0:
|
| 1423 |
+
span_sel = torch.linspace(0, all_anchors.numel() - 1, span_budget, device=device).round().long().unique()
|
| 1424 |
+
parts.append(all_anchors[span_sel])
|
| 1425 |
+
if recent_budget > 0:
|
| 1426 |
+
parts.append(all_anchors[-recent_budget:])
|
| 1427 |
+
anchors = torch.cat(parts).unique() if parts else torch.empty(0, device=device, dtype=torch.long)
|
| 1428 |
+
if self.sublinear_sinks > 0 and k_len > 0:
|
| 1429 |
+
sinks = torch.arange(min(self.sublinear_sinks, k_len), device=device, dtype=torch.long)
|
| 1430 |
+
anchors = torch.cat([sinks, anchors]).unique() if anchors.numel() else sinks
|
| 1431 |
+
return anchors
|
| 1432 |
+
|
| 1433 |
def _sublinear_attention(self, q, k, v, attn_mask=None, rel_bias_tokens=None):
|
| 1434 |
"""Local-window + landmark attention: O(N * (window + N/stride))."""
|
| 1435 |
bsz, heads, q_len, _ = q.shape
|
|
|
|
| 1442 |
if self.use_relpos and rel_bias_tokens is not None:
|
| 1443 |
slopes = _alibi_slopes(self.h).to(device=device, dtype=torch.float32)
|
| 1444 |
|
| 1445 |
+
anchors = self._sublinear_anchor_positions(k_len, device)
|
| 1446 |
+
anchor_k = anchor_v = None
|
| 1447 |
+
if anchors.numel() and self.sublinear_pooled_landmarks and self.sublinear_stride > 1:
|
| 1448 |
+
# Optional pooled landmarks: each global anchor summarizes its stride segment.
|
| 1449 |
+
# This is off by default because it adds cumsum work; enable after benchmarking.
|
| 1450 |
+
ends = anchors + 1
|
| 1451 |
+
starts = (ends - self.sublinear_stride).clamp_min(0)
|
| 1452 |
+
zero_k = k.new_zeros(k.size(0), k.size(1), 1, k.size(3))
|
| 1453 |
+
zero_v = v.new_zeros(v.size(0), v.size(1), 1, v.size(3))
|
| 1454 |
+
prefix_k = torch.cat([zero_k, k.cumsum(dim=2)], dim=2)
|
| 1455 |
+
prefix_v = torch.cat([zero_v, v.cumsum(dim=2)], dim=2)
|
| 1456 |
+
denom = (ends - starts).to(dtype=k.dtype).view(1, 1, -1, 1).clamp_min(1)
|
| 1457 |
+
anchor_k = (prefix_k[:, :, ends, :] - prefix_k[:, :, starts, :]) / denom
|
| 1458 |
+
anchor_v = (prefix_v[:, :, ends, :] - prefix_v[:, :, starts, :]) / denom
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1459 |
|
| 1460 |
offsets = torch.arange(
|
| 1461 |
-self.sublinear_window,
|
|
|
|
| 1473 |
local_valid = (local_raw >= 0) & (local_raw < k_len)
|
| 1474 |
local_idx = local_raw.clamp(0, max(0, k_len - 1))
|
| 1475 |
|
| 1476 |
+
k_local = k[:, :, local_idx, :]
|
| 1477 |
+
v_local = v[:, :, local_idx, :]
|
| 1478 |
if anchors.numel():
|
| 1479 |
anchor_idx = anchors.view(1, -1).expand(cur, -1)
|
| 1480 |
+
local_lo = (q_pos - self.sublinear_window).clamp_min(0).view(-1, 1)
|
| 1481 |
+
local_hi = (q_pos + self.sublinear_window).clamp_max(max(0, k_len - 1)).view(-1, 1)
|
| 1482 |
+
# Drop anchor copies already present in the local window; duplicates bias softmax mass.
|
| 1483 |
+
anchor_valid = (anchor_idx < local_lo) | (anchor_idx > local_hi)
|
| 1484 |
idx = torch.cat([local_idx, anchor_idx], dim=1)
|
| 1485 |
valid = torch.cat([local_valid, anchor_valid], dim=1)
|
| 1486 |
+
if anchor_k is not None and anchor_v is not None:
|
| 1487 |
+
k_anchor = anchor_k.unsqueeze(2).expand(-1, -1, cur, -1, -1)
|
| 1488 |
+
v_anchor = anchor_v.unsqueeze(2).expand(-1, -1, cur, -1, -1)
|
| 1489 |
+
else:
|
| 1490 |
+
k_anchor = k[:, :, anchor_idx, :]
|
| 1491 |
+
v_anchor = v[:, :, anchor_idx, :]
|
| 1492 |
+
k_sel = torch.cat([k_local, k_anchor], dim=-2)
|
| 1493 |
+
v_sel = torch.cat([v_local, v_anchor], dim=-2)
|
| 1494 |
else:
|
| 1495 |
idx = local_idx
|
| 1496 |
valid = local_valid
|
| 1497 |
+
k_sel = k_local
|
| 1498 |
+
v_sel = v_local
|
| 1499 |
|
| 1500 |
structured_valid = self._structured_valid(attn_mask, q_pos, idx)
|
| 1501 |
if structured_valid is not None:
|
| 1502 |
valid = valid & structured_valid
|
| 1503 |
|
|
|
|
| 1504 |
scores = (q[:, :, q_start:q_end, :].unsqueeze(-2) * k_sel).sum(dim=-1) * scale
|
| 1505 |
|
| 1506 |
if slopes is not None:
|
| 1507 |
+
dist = (q_pos.view(1, 1, cur, 1) - idx.view(1, 1, cur, -1)).abs().to(torch.float32)
|
| 1508 |
scores = scores + (-slopes * dist).to(scores.dtype)
|
| 1509 |
|
| 1510 |
if torch.is_tensor(attn_mask) and attn_mask.size(-1) == k_len and attn_mask.size(-2) >= q_end:
|
|
|
|
| 1514 |
|
| 1515 |
scores = scores.masked_fill(~valid.view(1, 1, cur, -1), float("-inf"))
|
| 1516 |
weights = torch.softmax(scores.float(), dim=-1).to(v.dtype)
|
|
|
|
| 1517 |
outputs.append((weights.unsqueeze(-1) * v_sel).sum(dim=-2))
|
| 1518 |
|
| 1519 |
return torch.cat(outputs, dim=2)
|
|
|
|
| 1587 |
sublinear_stride: int = DEFAULT_SUBLINEAR_STRIDE,
|
| 1588 |
sublinear_max_anchors: int = DEFAULT_SUBLINEAR_MAX_ANCHORS,
|
| 1589 |
sublinear_chunk: int = DEFAULT_SUBLINEAR_CHUNK,
|
| 1590 |
+
sublinear_sinks: int = DEFAULT_SUBLINEAR_SINKS,
|
| 1591 |
+
sublinear_recent_anchors: int = DEFAULT_SUBLINEAR_RECENT_ANCHORS,
|
| 1592 |
+
sublinear_pooled_landmarks: bool = DEFAULT_SUBLINEAR_POOLED_LANDMARKS,
|
| 1593 |
):
|
| 1594 |
super().__init__()
|
| 1595 |
self.ln1, self.ln2 = nn.LayerNorm(d), nn.LayerNorm(d)
|
|
|
|
| 1602 |
sublinear_stride=sublinear_stride,
|
| 1603 |
sublinear_max_anchors=sublinear_max_anchors,
|
| 1604 |
sublinear_chunk=sublinear_chunk,
|
| 1605 |
+
sublinear_sinks=sublinear_sinks,
|
| 1606 |
+
sublinear_recent_anchors=sublinear_recent_anchors,
|
| 1607 |
+
sublinear_pooled_landmarks=sublinear_pooled_landmarks,
|
| 1608 |
)
|
| 1609 |
self.ff = nn.Sequential(nn.Linear(d, 4 * d), nn.ReLU(), nn.Linear(4 * d, d))
|
| 1610 |
|
|
|
|
| 1630 |
sublinear_stride: int = DEFAULT_SUBLINEAR_STRIDE,
|
| 1631 |
sublinear_max_anchors: int = DEFAULT_SUBLINEAR_MAX_ANCHORS,
|
| 1632 |
sublinear_chunk: int = DEFAULT_SUBLINEAR_CHUNK,
|
| 1633 |
+
sublinear_sinks: int = DEFAULT_SUBLINEAR_SINKS,
|
| 1634 |
+
sublinear_recent_anchors: int = DEFAULT_SUBLINEAR_RECENT_ANCHORS,
|
| 1635 |
+
sublinear_pooled_landmarks: bool = DEFAULT_SUBLINEAR_POOLED_LANDMARKS,
|
| 1636 |
anchor_memory: bool = DEFAULT_ANCHOR_MEMORY,
|
| 1637 |
anchor_stride: int = DEFAULT_ANCHOR_STRIDE,
|
| 1638 |
anchor_max: int = DEFAULT_ANCHOR_MAX,
|
|
|
|
| 1651 |
sublinear_stride=sublinear_stride,
|
| 1652 |
sublinear_max_anchors=sublinear_max_anchors,
|
| 1653 |
sublinear_chunk=sublinear_chunk,
|
| 1654 |
+
sublinear_sinks=sublinear_sinks,
|
| 1655 |
+
sublinear_recent_anchors=sublinear_recent_anchors,
|
| 1656 |
+
sublinear_pooled_landmarks=sublinear_pooled_landmarks,
|
| 1657 |
)
|
| 1658 |
for _ in range(l)
|
| 1659 |
])
|
|
|
|
| 1665 |
self.sublinear_stride = sublinear_stride
|
| 1666 |
self.sublinear_max_anchors = sublinear_max_anchors
|
| 1667 |
self.sublinear_chunk = sublinear_chunk
|
| 1668 |
+
self.sublinear_sinks = sublinear_sinks
|
| 1669 |
+
self.sublinear_recent_anchors = sublinear_recent_anchors
|
| 1670 |
+
self.sublinear_pooled_landmarks = bool(sublinear_pooled_landmarks)
|
| 1671 |
self.anchor_memory_enabled = bool(anchor_memory)
|
| 1672 |
self.anchor_stride = int(anchor_stride)
|
| 1673 |
self.anchor_max = int(anchor_max)
|
|
|
|
| 2545 |
"AGILLM-4 runtime: "
|
| 2546 |
f"attn_backend={args.attn_backend} grad_checkpoint={args.grad_checkpoint} "
|
| 2547 |
f"sublinear_window={args.sublinear_window} sublinear_stride={args.sublinear_stride} "
|
| 2548 |
+
f"sublinear_max_anchors={args.sublinear_max_anchors} sublinear_chunk={args.sublinear_chunk} "
|
| 2549 |
+
f"sublinear_sinks={args.sublinear_sinks} sublinear_recent_anchors={args.sublinear_recent_anchors} "
|
| 2550 |
+
f"sublinear_pooled_landmarks={args.sublinear_pooled_landmarks}"
|
| 2551 |
)
|
| 2552 |
core = Encoder(
|
| 2553 |
cfg,
|
|
|
|
| 2558 |
sublinear_stride=args.sublinear_stride,
|
| 2559 |
sublinear_max_anchors=args.sublinear_max_anchors,
|
| 2560 |
sublinear_chunk=args.sublinear_chunk,
|
| 2561 |
+
sublinear_sinks=args.sublinear_sinks,
|
| 2562 |
+
sublinear_recent_anchors=args.sublinear_recent_anchors,
|
| 2563 |
+
sublinear_pooled_landmarks=args.sublinear_pooled_landmarks,
|
| 2564 |
anchor_memory=getattr(args, "anchor_memory", DEFAULT_ANCHOR_MEMORY),
|
| 2565 |
anchor_stride=getattr(args, "anchor_stride", DEFAULT_ANCHOR_STRIDE),
|
| 2566 |
anchor_max=getattr(args, "anchor_max", DEFAULT_ANCHOR_MAX),
|
|
|
|
| 2780 |
sublinear_stride=args.sublinear_stride,
|
| 2781 |
sublinear_max_anchors=args.sublinear_max_anchors,
|
| 2782 |
sublinear_chunk=args.sublinear_chunk,
|
| 2783 |
+
sublinear_sinks=args.sublinear_sinks,
|
| 2784 |
+
sublinear_recent_anchors=args.sublinear_recent_anchors,
|
| 2785 |
+
sublinear_pooled_landmarks=args.sublinear_pooled_landmarks,
|
| 2786 |
anchor_memory=getattr(args, "anchor_memory", DEFAULT_ANCHOR_MEMORY),
|
| 2787 |
anchor_stride=getattr(args, "anchor_stride", DEFAULT_ANCHOR_STRIDE),
|
| 2788 |
anchor_max=getattr(args, "anchor_max", DEFAULT_ANCHOR_MAX),
|
|
|
|
| 2954 |
help="For --attn_backend sublinear, cap landmark candidates per query chunk.")
|
| 2955 |
tr.add_argument("--sublinear_chunk", type=int, default=DEFAULT_SUBLINEAR_CHUNK,
|
| 2956 |
help="For --attn_backend sublinear, query chunk size controlling peak gather memory.")
|
| 2957 |
+
tr.add_argument("--sublinear_sinks", type=int, default=DEFAULT_SUBLINEAR_SINKS,
|
| 2958 |
+
help="For sublinear attention, always include this many first-token attention sinks.")
|
| 2959 |
+
tr.add_argument("--sublinear_recent_anchors", type=int, default=DEFAULT_SUBLINEAR_RECENT_ANCHORS,
|
| 2960 |
+
help="For capped sublinear anchors, reserve this many anchors for the recent tail; -1 uses half.")
|
| 2961 |
+
tr.add_argument("--sublinear_pooled_landmarks", action=argparse.BooleanOptionalAction,
|
| 2962 |
+
default=DEFAULT_SUBLINEAR_POOLED_LANDMARKS,
|
| 2963 |
+
help="Use stride-segment pooled K/V summaries for sublinear landmark anchors.")
|
| 2964 |
tr.add_argument("--no_structured_masks", action="store_true",
|
| 2965 |
help="Disable structured causal/SAT masks for sublinear attention and fall back to dense masks.")
|
| 2966 |
tr.add_argument("--anchor_memory", action="store_true",
|
|
|
|
| 3082 |
inf.add_argument("--sublinear_stride", type=int, default=DEFAULT_SUBLINEAR_STRIDE)
|
| 3083 |
inf.add_argument("--sublinear_max_anchors", type=int, default=DEFAULT_SUBLINEAR_MAX_ANCHORS)
|
| 3084 |
inf.add_argument("--sublinear_chunk", type=int, default=DEFAULT_SUBLINEAR_CHUNK)
|
| 3085 |
+
inf.add_argument("--sublinear_sinks", type=int, default=DEFAULT_SUBLINEAR_SINKS)
|
| 3086 |
+
inf.add_argument("--sublinear_recent_anchors", type=int, default=DEFAULT_SUBLINEAR_RECENT_ANCHORS)
|
| 3087 |
+
inf.add_argument("--sublinear_pooled_landmarks", action=argparse.BooleanOptionalAction,
|
| 3088 |
+
default=DEFAULT_SUBLINEAR_POOLED_LANDMARKS)
|
| 3089 |
inf.add_argument("--no_structured_masks", action="store_true")
|
| 3090 |
inf.add_argument("--nat_expand", type=int, default=2)
|
| 3091 |
inf.add_argument("--nat_passes", type=int, default=1)
|
relaunch_agillm4_dblock_sg2.sh
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env bash
|
| 2 |
+
# Relaunch AGILLM-4 dblock with SG2's tuned config + improved sublinear attention v2.
|
| 3 |
+
set -Eeuo pipefail
|
| 4 |
+
cd /workspace/agillm-4
|
| 5 |
+
export TOKENIZERS_PARALLELISM=false
|
| 6 |
+
export TOKENIZER_ID="${TOKENIZER_ID:-deepseek-ai/DeepSeek-V4-Pro}"
|
| 7 |
+
export PYTORCH_CUDA_ALLOC_CONF=max_split_size_mb:512,expandable_segments:True
|
| 8 |
+
export AGILLM_ATTN_BACKEND=sublinear
|
| 9 |
+
[ -f /root/.cache/huggingface/token ] && { export HF_TOKEN="$(tr -d '\r\n' </root/.cache/huggingface/token)"; export HUGGING_FACE_HUB_TOKEN="$HF_TOKEN"; }
|
| 10 |
+
SAVE_DIR=/workspace/agillm4_4090_ckpts
|
| 11 |
+
CKPT="$(ls -1t "$SAVE_DIR"/pretrain_step*.pt 2>/dev/null | head -1)"
|
| 12 |
+
exec >> /workspace/agillm4_floor_train.log 2>&1
|
| 13 |
+
echo "RELAUNCH_AGILLM4_DBLOCK_SG2 $(date -u +%Y-%m-%dT%H:%M:%SZ) resume=$CKPT (improved sublinear v2: ALiBi distance + dedupe + hybrid anchors + sinks)"
|
| 14 |
+
exec python -u nB300_agillm4.py train --preset agillm4_floor --resume "$CKPT" \
|
| 15 |
+
--dblock --dblock_blocks 4 --dblock_schedule loss_balanced --dblock_warmup_steps 16 \
|
| 16 |
+
--dblock_sigma_curriculum_steps 2000 --dblock_log_every 25 --dblock_objective_mode stochastic \
|
| 17 |
+
--dblock_ar_prob 0.85 --dblock_sat_prob 0.075 --dblock_nat_prob 0.075 \
|
| 18 |
+
--dblock_ar_loss_tokens 512 --dblock_sat_loss_tokens 0 --dblock_nat_loss_tokens 512 \
|
| 19 |
+
--tie_weights --batch_size 2 --block 1280 --amp --attn_backend sublinear \
|
| 20 |
+
--sublinear_window 128 --sublinear_stride 128 --sublinear_max_anchors 128 --sublinear_chunk 128 \
|
| 21 |
+
--sublinear_sinks 4 --sublinear_recent_anchors 64 --no-sublinear_pooled_landmarks \
|
| 22 |
+
--grad_checkpoint --optimizer paged_adamw8bit --sat_every 4 --nat_every 4 --nat_max_tokens 768 --nat_mask_ratio 0.5 \
|
| 23 |
+
--token_param_ratio 100 --save_dir "$SAVE_DIR" --save_every_sec 86400 --heartbeat_every_sec 300 \
|
| 24 |
+
--empty_cache_every_steps 0 --delta_every_steps 25000 --delta_max_keep 1 --max_ckpts 1
|
sublinear_improved.py
CHANGED
|
@@ -1,54 +1,58 @@
|
|
| 1 |
"""Improved sublinear-attention anchor selection for AGILLM-4.
|
| 2 |
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
Drop-in replacement for the anchor-cap block inside MHA._sublinear_attention.
|
| 10 |
"""
|
| 11 |
import torch
|
| 12 |
|
| 13 |
-
|
| 14 |
-
|
|
|
|
|
|
|
| 15 |
start = stride - 1
|
| 16 |
-
if stride
|
| 17 |
-
anchors = torch.arange(start, k_len, stride, device=device, dtype=torch.long)
|
| 18 |
-
if anchors.numel() > max_anchors:
|
| 19 |
-
# even-coverage subsample across the whole sequence (NOT the recent tail)
|
| 20 |
-
sel = torch.linspace(0, anchors.numel() - 1, max_anchors, device=device).round().long().unique()
|
| 21 |
-
anchors = anchors[sel]
|
| 22 |
-
else:
|
| 23 |
anchors = torch.empty(0, device=device, dtype=torch.long)
|
| 24 |
-
|
| 25 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 26 |
return anchors
|
| 27 |
|
| 28 |
-
# --- exact patch applied to nB300_agillm4.py MHA._sublinear_attention ---
|
| 29 |
-
PATCH = '''
|
| 30 |
-
# replace:
|
| 31 |
-
# if anchors.numel() > self.sublinear_max_anchors:
|
| 32 |
-
# anchors = anchors[-self.sublinear_max_anchors :]
|
| 33 |
-
# with:
|
| 34 |
-
if anchors.numel() > self.sublinear_max_anchors:
|
| 35 |
-
_sel = torch.linspace(0, anchors.numel() - 1, self.sublinear_max_anchors, device=device).round().long().unique()
|
| 36 |
-
anchors = anchors[_sel]
|
| 37 |
-
# ... and after the else branch add:
|
| 38 |
-
_sink = int(getattr(self, "sublinear_sinks", 4))
|
| 39 |
-
if _sink > 0 and k_len > 0:
|
| 40 |
-
anchors = torch.cat([torch.arange(min(_sink, k_len), device=device, dtype=torch.long), anchors]).unique()
|
| 41 |
-
'''
|
| 42 |
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
"""Improved sublinear-attention anchor selection for AGILLM-4.
|
| 2 |
|
| 3 |
+
V2 used by the live AGILLM-4 DBlock line:
|
| 4 |
+
- fixes gathered ALiBi distance so past causal keys receive distance penalty
|
| 5 |
+
- suppresses local/anchor duplicate candidates before softmax
|
| 6 |
+
- uses hybrid full-span + recent-tail anchors under the same max-anchor budget
|
| 7 |
+
- exposes first-token attention sinks as `--sublinear_sinks`
|
| 8 |
+
- includes optional pooled landmark K/V summaries behind `--sublinear_pooled_landmarks`
|
|
|
|
| 9 |
"""
|
| 10 |
import torch
|
| 11 |
|
| 12 |
+
|
| 13 |
+
def select_hybrid_anchors(k_len, stride, max_anchors, sinks=4, recent_anchors=-1, device='cpu'):
|
| 14 |
+
"""Full-span + recent-tail landmark positions, plus attention sinks."""
|
| 15 |
+
device = torch.device(device)
|
| 16 |
start = stride - 1
|
| 17 |
+
if stride <= 0 or max_anchors <= 0 or start >= k_len:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 18 |
anchors = torch.empty(0, device=device, dtype=torch.long)
|
| 19 |
+
else:
|
| 20 |
+
all_anchors = torch.arange(start, k_len, stride, device=device, dtype=torch.long)
|
| 21 |
+
if all_anchors.numel() <= max_anchors:
|
| 22 |
+
anchors = all_anchors
|
| 23 |
+
else:
|
| 24 |
+
if recent_anchors < 0:
|
| 25 |
+
recent_anchors = max_anchors // 2
|
| 26 |
+
recent_budget = min(max(0, int(recent_anchors)), max_anchors)
|
| 27 |
+
span_budget = max(0, max_anchors - recent_budget)
|
| 28 |
+
parts = []
|
| 29 |
+
if span_budget > 0:
|
| 30 |
+
sel = torch.linspace(0, all_anchors.numel() - 1, span_budget, device=device).round().long().unique()
|
| 31 |
+
parts.append(all_anchors[sel])
|
| 32 |
+
if recent_budget > 0:
|
| 33 |
+
parts.append(all_anchors[-recent_budget:])
|
| 34 |
+
anchors = torch.cat(parts).unique() if parts else torch.empty(0, device=device, dtype=torch.long)
|
| 35 |
+
if sinks > 0 and k_len > 0:
|
| 36 |
+
sink_idx = torch.arange(min(int(sinks), k_len), device=device, dtype=torch.long)
|
| 37 |
+
anchors = torch.cat([sink_idx, anchors]).unique() if anchors.numel() else sink_idx
|
| 38 |
return anchors
|
| 39 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 40 |
|
| 41 |
+
def local_anchor_valid(q_pos, anchors, window, k_len):
|
| 42 |
+
"""False where an anchor is already inside that query's local window."""
|
| 43 |
+
anchor_idx = anchors.view(1, -1).expand(q_pos.numel(), -1)
|
| 44 |
+
local_lo = (q_pos - window).clamp_min(0).view(-1, 1)
|
| 45 |
+
local_hi = (q_pos + window).clamp_max(max(0, k_len - 1)).view(-1, 1)
|
| 46 |
+
return (anchor_idx < local_lo) | (anchor_idx > local_hi)
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
if __name__ == '__main__':
|
| 50 |
+
N, window, stride, maxA, sinks, recent = 32768, 128, 128, 128, 4, 64
|
| 51 |
+
old_all = list(range(stride - 1, N, stride))
|
| 52 |
+
old = sorted(set(range(N - window - 1, N)) | set(old_all[-maxA:]))
|
| 53 |
+
new = select_hybrid_anchors(N, stride, maxA, sinks, recent).tolist()
|
| 54 |
+
print(f'N={N} stride={stride} maxA={maxA} sinks={sinks} recent={recent}')
|
| 55 |
+
print(f'OLD anchor coverage: {min(old)}..{max(old)} first_half={sum(x < N//2 for x in old)}')
|
| 56 |
+
print(f'NEW anchor coverage: {min(new)}..{max(new)} first_half={sum(x < N//2 for x in new)} recent_tail={sum(x >= N-8192 for x in new)}')
|
| 57 |
+
q = torch.tensor([N - 1])
|
| 58 |
+
print(f'duplicate-suppressed anchors for final query: {int(local_anchor_valid(q, torch.tensor(new), window, N).sum())}/{len(new)}')
|
sublinear_improved_snippet.py
CHANGED
|
@@ -1,35 +1,29 @@
|
|
| 1 |
-
"""
|
| 2 |
|
| 3 |
-
|
| 4 |
-
selection. It keeps the same local-window + anchor key budget shape, but avoids
|
| 5 |
-
losing the deep past once `num_anchors > sublinear_max_anchors`.
|
| 6 |
"""
|
| 7 |
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
anchors = torch.arange(
|
| 11 |
-
anchor_start,
|
| 12 |
-
k_len,
|
| 13 |
-
self.sublinear_stride,
|
| 14 |
-
device=device,
|
| 15 |
-
dtype=torch.long,
|
| 16 |
-
)
|
| 17 |
-
if anchors.numel() > self.sublinear_max_anchors:
|
| 18 |
-
# Span the whole sequence instead of keeping only the recent tail.
|
| 19 |
-
sel = torch.linspace(
|
| 20 |
-
0,
|
| 21 |
-
anchors.numel() - 1,
|
| 22 |
-
self.sublinear_max_anchors,
|
| 23 |
-
device=device,
|
| 24 |
-
).round().long().unique()
|
| 25 |
-
anchors = anchors[sel]
|
| 26 |
-
else:
|
| 27 |
-
anchors = torch.empty(0, device=device, dtype=torch.long)
|
| 28 |
|
| 29 |
-
#
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Minimal AGILLM-4 sublinear attention V2 snippets.
|
| 2 |
|
| 3 |
+
These are the core blocks now folded into `nB300_agillm4_vram_dblock.py`.
|
|
|
|
|
|
|
| 4 |
"""
|
| 5 |
|
| 6 |
+
# Anchor selection: full-span + recent-tail + sinks.
|
| 7 |
+
anchors = self._sublinear_anchor_positions(k_len, device)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
|
| 9 |
+
# Optional pooled landmarks are available behind --sublinear_pooled_landmarks.
|
| 10 |
+
if anchors.numel() and self.sublinear_pooled_landmarks and self.sublinear_stride > 1:
|
| 11 |
+
ends = anchors + 1
|
| 12 |
+
starts = (ends - self.sublinear_stride).clamp_min(0)
|
| 13 |
+
zero_k = k.new_zeros(k.size(0), k.size(1), 1, k.size(3))
|
| 14 |
+
zero_v = v.new_zeros(v.size(0), v.size(1), 1, v.size(3))
|
| 15 |
+
prefix_k = torch.cat([zero_k, k.cumsum(dim=2)], dim=2)
|
| 16 |
+
prefix_v = torch.cat([zero_v, v.cumsum(dim=2)], dim=2)
|
| 17 |
+
denom = (ends - starts).to(dtype=k.dtype).view(1, 1, -1, 1).clamp_min(1)
|
| 18 |
+
anchor_k = (prefix_k[:, :, ends, :] - prefix_k[:, :, starts, :]) / denom
|
| 19 |
+
anchor_v = (prefix_v[:, :, ends, :] - prefix_v[:, :, starts, :]) / denom
|
| 20 |
+
|
| 21 |
+
# Duplicate suppression: do not let an anchor double-count a key already in local attention.
|
| 22 |
+
anchor_idx = anchors.view(1, -1).expand(cur, -1)
|
| 23 |
+
local_lo = (q_pos - self.sublinear_window).clamp_min(0).view(-1, 1)
|
| 24 |
+
local_hi = (q_pos + self.sublinear_window).clamp_max(max(0, k_len - 1)).view(-1, 1)
|
| 25 |
+
anchor_valid = (anchor_idx < local_lo) | (anchor_idx > local_hi)
|
| 26 |
+
|
| 27 |
+
# Gathered ALiBi distance: distance from query to selected key, not future-only clamp.
|
| 28 |
+
dist = (q_pos.view(1, 1, cur, 1) - idx.view(1, 1, cur, -1)).abs().to(torch.float32)
|
| 29 |
+
scores = scores + (-slopes * dist).to(scores.dtype)
|