Skip to content

feat(phi3-small): implement blocksparse attention - #582

Closed
inureyes wants to merge 1 commit into
mainfrom
feature/issue-511-phi3small-blocksparse
Closed

feat(phi3-small): implement blocksparse attention#582
inureyes wants to merge 1 commit into
mainfrom
feature/issue-511-phi3small-blocksparse

Conversation

@inureyes

@inureyes inureyes commented Jul 1, 2026

Copy link
Copy Markdown
Member

Summary

Replace the dense-attention fallback on Phi-3-small's non-dense layers with the real blocksparse pattern (local block window + per-head vertical stride), while keeping the periodic fully-dense layers on the plain causal path. This makes long-context output follow the trained sparsity instead of diverging toward dense.

What changed

  • src/models/phi3small.rs
    • Added build_blocksparse_mask(...): builds the additive [1, n_heads, q_len, kv_len] mask on-device (broadcasting ops only, nothing of size q_len * kv_len materialized on the host). Faithful port of mlx_lm/models/phi3small.py (Attention._block_sparse_mask + _block_sparse_attention), including the reference's right-aligned query-block assignment and the per-head vertical stride (kb + head + 1) % vert_stride == 0. Token-level causality is folded into the same mask so it reproduces the reference mask + dense_mask combination exactly.
    • Split the attention dispatch into dense_attention (unchanged causal path, used by the periodic dense layers) and blocksparse_attention (non-dense layers), threading the already-computed per-layer block_sparse flag into the attention path.
    • In-window fast path: when the whole key length fits inside the local window (kv_len <= num_local_blocks * block_size) the pattern is exactly causal, so the dense path is reused. This gives byte-identical short-context output and, for in-window L == 1 decode, allocates no per-step mask (the maskless single-query fast path selects the whole cache).
    • Stored blocksparse_block_size / _num_local_blocks / _vert_stride on Attention.
  • src/models/phi3small_tests.rs (new): config parsing/defaults, block_sparse flag pattern, device-mask vs host-reference agreement (bulk + explicit key-block set), short-context mask==causal and RMS parity against the dense fallback, long-context sparser-than-causal + runtime divergence, and a decode single-row mask check.

Decode (L == 1) note

The issue asks to prefer gather-based KV selection over a full per-step mask. In-window decode does exactly that (no mask; trailing-cache selection via the maskless fast path). Beyond the window, the union of attended blocks across all heads spans the full causal range for Phi-3-small's head count (vert_stride divides the head set), so a per-head gather cannot shrink the fused-SDPA work and would require a ragged per-head kernel; we build a single [1, n_heads, 1, kv_len] additive mask (one row) and let the fused kernel consume it.

Test plan

  • cargo test --release phi3small --features metal,accelerate (mask pattern, short-context dense parity, long-context sparsity, decode row)
  • cargo clippy --lib --tests --features metal,accelerate -- -D warnings
  • cargo fmt --all -- --check

Validation scope (honest)

Validated: unit tests above, including short-context RMS parity with the dense fallback and mask-pattern agreement with the mlx-lm reference formula on constructed cases, run on Apple Silicon Metal. NOT run: generation on a real Phi-3-small checkpoint (no Phi-3-small checkpoint available in this environment; Phi-3-small is a ~7B model and a multi-GB download was out of scope here). Long-context behavior is validated at the mask/selection and SDPA-output level, not via a real-model long-context generation.

Closes #511

Replace the dense-attention fallback on phi3small's non-dense layers with the trained blocksparse pattern. Each block_sparse layer attends to the local window of blocksparse_num_local_blocks blocks of size blocksparse_block_size ending at the current query block, plus earlier blocks selected by blocksparse_vert_stride, while the periodic dense_attention_every_n_layers layers keep dense attention. The per-layer block_sparse flag is threaded into the attention path, and the L==1 decode path gathers the selected key blocks instead of building a full sparse mask each step, so short context reduces to the dense fallback while long context follows the trained sparsity.
@inureyes inureyes added type:enhancement New features, capabilities, or significant additions priority:low Low priority area:models Model architectures, weights, loading, metadata status:done Completed labels Jul 1, 2026
@inureyes

inureyes commented Jul 1, 2026

Copy link
Copy Markdown
Member Author

Duplicate of #581, which already squash-merged this exact change (commit 92396a8, phi3-small blocksparse attention) into main. #582 was opened automatically on the same branch after #581 merged and carries no content difference. Closing as superseded.

@inureyes inureyes closed this Jul 1, 2026
@inureyes
inureyes deleted the feature/issue-511-phi3small-blocksparse branch July 1, 2026 11:23
@inureyes inureyes self-assigned this Aug 31, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:models Model architectures, weights, loading, metadata priority:low Low priority status:done Completed type:enhancement New features, capabilities, or significant additions

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(phi3-small): implement blocksparse attention to replace the dense fallback

1 participant