nx_flash_attention.nx
buildroot/runtime/nx_flash_attention.nx
about
nx_flash_attention.nx -- tiled IO-aware attention (Dao 2022).
Realises the F-001 row in VRAM_OPTIMIZATION_REALISTIC_TRACKING.md:
2-3 GB working-memory savings at ZERO quality loss.
HONEST about what transfers vs what doesn't on the i64 substrate:
CUDA FlashAttention wins on:
* Memory savings: NEVER materialise full n*n score matrix
* Latency: kernel fusion + async memory hides DRAM latency
* Numerical stability: online softmax via running max
On i64 substrate without SIMD/GPU:
* Memory savings: SAME -- algorithmic, transfers fully.
For n=1024 / head_dim=64: saves ~8 MB per head per layer.
Across 16 heads * 40 layers = ~5 GB of working memory.
* Latency: substrate has no kernel fusion (every op is a NishiLang
function call); per-block compute is *slower* than naive
per-row compute on small inputs. Win flips back POSITIVE for
n > ~1024 because cache locality dominates.
* Numerical stability: SAME -- online softmax is algorithm, not
hardware.
Bottom line: ship for the memory win, not the latency win (yet).
When SIMD lands the latency story flips to "FlashAttention native
always wins."
Algorithm (one query row at a time):
m = -INF running row-max
l = 0 running normaliser sum
O = zero vector (head_dim) running output
for each KV block [j_start, j_end):
S_block[j] = Q[i, :] @ K[j, :] (per j in block)
m_block = max(S_block)
m_new = max(m, m_block)
rescale = exp(m - m_new) <= 1
P_block[j] = exp(S_block[j] - m_new)
l = l * rescale + sum(P_block)
dependencies 4 imports · 1 importers
imports: nx_syscalls.nxnx_tier.nxnx_tensor.nxnx_attention.nx
imported by: nx_flash_attention_test.nx
structs
| none |
consts
| 66 | const NX_MAGIC_10000: i64 = 10000 |
| 68 | const NX_FA_Q10: nx_int = 1024 |
| 72 | const NX_FA_OK: nx_int = 0 |
| 73 | const NX_FA_ERR_BAD_DTYPE: nx_int = 1 |
| 74 | const NX_FA_ERR_BAD_NDIM: nx_int = 2 |
| 75 | const NX_FA_ERR_SHAPE_MISMATCH: nx_int = 3 |
| 76 | const NX_FA_ERR_NOT_CONTIGUOUS: nx_int = 4 |
| 77 | const NX_FA_ERR_BAD_BLOCK_SIZE: nx_int = 5 |
| 78 | const NX_FA_N_VERDICTS: nx_int = 6 |
functions
| 80 | func nx_fa_verdict_is_valid(v: nx_int) -> nx_int called by 1: main |
| 94 | func _fa_row(q: *NxTensor, k: *NxTensor, v: *NxTensor, |
| 213 | func nx_fa_forward(q: *NxTensor, k: *NxTensor, v: *NxTensor, |
| 258 | func nx_fa_working_memory_bytes(n_q: nx_int, n_kv: nx_int, d: nx_int, |
| 267 | func nx_fa_savings_ratio_q10(n_q: nx_int, n_kv: nx_int, d: nx_int, |