Sparse attention

Reference implementation and checkpoint configuration deepseek-ai/DeepSeek-V4.1-Flash @ 2cba9e4 · parameter count checked against the checkpoint’s tensor headers · text model only · How to read

40 layers › 6 layers › 5 reusing layers › Compressed sparse attention › Sparse attention

Window positions

×

no parametersget_window_topk_idxs(win, bsz, seqlen, start_pos) · 720

For each query, the last 128 positions up to and including its own.

Jtw={max⁡(0, t−127),…,t}{\htmlData{light=window-allowed window-rows}{J^{w}_{t}}}=\{\max(0,\,t-127),\ldots,t\}
in
WW [B batch, T positions, 512 d_h]
out
JwJ^{w} [B batch, T positions, 128 window]

Causality is in this list: there is no mask tensor.

Codemodel.py 410–426
model.py 410–426
def get_window_topk_idxs(window_size: int, bsz: int, seqlen: int, start_pos: int):
    """Which sliding-window cache slots each query attends to; -1 marks a slot holding nothing.

    The cache is a ring of `window_size` slots. Prefill needs one row per query, each seeing its own
    causal window. A decode step has a single query that sees the whole ring, listed oldest first.
    Order within a row does not matter to `sparse_attn`, which handles every slot independently.
    """
    if start_pos == 0:
        end = torch.arange(seqlen).unsqueeze(1)
        idxs = (end - window_size + 1).clamp(0) + torch.arange(min(seqlen, window_size))
        idxs = torch.where(idxs > end, -1, idxs)  # before the sequence started
    else:
        oldest = start_pos % window_size + 1
        idxs = torch.cat([torch.arange(oldest, window_size), torch.arange(oldest)])
        idxs = torch.where(idxs > start_pos, -1, idxs)  # ring still filling
    # sparse_attn needs real [b, m, topk] int32 memory, hence the materializing expand
    return idxs.int().unsqueeze(0).expand(bsz, -1, -1).contiguous()
Sources1

Direct evidence

The positions a query may attend to inside the sliding window: in a prefill the last 128 up to itself, in a decode step the whole ring.

window-indices · deepseek-ai/DeepSeek-V4.1-Flash / inference/model.py
Pinned revision 2cba9e42aa02 ↗ · lines 410–426

Read code excerpt
def get_window_topk_idxs(window_size: int, bsz: int, seqlen: int, start_pos: int):
    """Which sliding-window cache slots each query attends to; -1 marks a slot holding nothing.

    The cache is a ring of `window_size` slots. Prefill needs one row per query, each seeing its own
    causal window. A decode step has a single query that sees the whole ring, listed oldest first.
    Order within a row does not matter to `sparse_attn`, which handles every slot independently.
    """
    if start_pos == 0:
        end = torch.arange(seqlen).unsqueeze(1)
        idxs = (end - window_size + 1).clamp(0) + torch.arange(min(seqlen, window_size))
        idxs = torch.where(idxs > end, -1, idxs)  # before the sequence started
    else:
        oldest = start_pos % window_size + 1
        idxs = torch.cat([torch.arange(oldest, window_size), torch.arange(oldest)])
        idxs = torch.where(idxs > start_pos, -1, idxs)  # ring still filling
    # sparse_attn needs real [b, m, topk] int32 memory, hence the materializing expand
    return idxs.int().unsqueeze(0).expand(bsz, -1, -1).contiguous()