nx_gqa_head_map.nx
buildroot/runtime/nx_gqa_head_map.nx
about
nx_gqa_head_map.nx -- H6 grouped/multi-query attention bits-up.
Per NISHI_ELDER_AI_OFF_DOCKER_2026_05_20.md ยง2.1 H6: grouped-query
attention (Ainslie 2023) + multi-query attention (Shazeer 2019)
collapse the K and V projection dimensions so multiple query
heads share one KV head. Memory + bandwidth wins on the KV
cache scale with (n_q_heads / n_kv_heads): a 32-Q-head model with
8 KV heads cuts KV cache 4x; with 1 KV head (MQA) cuts it 32x.
Llama 3, Mistral, Qwen, every modern open-weight model ships
some flavour of this.
V1 mechanics: a static mapping table head_to_kv_group[q_head_idx]
= kv_head_idx, computed once at model-load time from
(n_q_heads, n_kv_heads). The mapping is contiguous-group:
q heads [0..group_size) -> kv head 0, [group_size..2*group_size)
-> kv head 1, etc. This matches the layout every published
implementation uses (Llama / Mistral / Qwen). Three named
attention kinds fall out as sealed-enum special cases:
MHA: n_kv_heads == n_q_heads (group_size = 1)
GQA: 1 < n_kv_heads < n_q_heads (group_size > 1, divides n_q_heads)
MQA: n_kv_heads == 1 (group_size = n_q_heads)
Composition path: a downstream attention kernel (FlashAttention
or naive softmax) loops over q heads, calls
nx_gqa_head_map_kv_head_for_q_head(map, q_h) to find which KV
head to read from, and computes attention against that smaller
shared K/V. H5 nx_flash_attention can compose with this --
the tile plan reads from the shared KV head.
Pure substrate logic. No Linux features. Composes with shipped
H1 nx_kv_arena (KV pages are now sized by n_kv_heads not
n_q_heads -- the arena allocator's hidden_dim parameter should
reflect the smaller GQA/MQA layout), H5 nx_flash_attention
(tile plan reads from shared KV head).
V1 honest scope:
- Contiguous-group mapping only (V2 could add strided or
learned mapping for research variants; not in any production
open-weight model as of 2026-05-20)
- Sealed attention kind derived from (n_q, n_kv) -- not stored
dependencies 1 imports · 2 importers
imports: nx_syscalls.nx
imported by: nx_gqa_head_map_test.nxnx_hackers_algo_compose_test.nx
structs
| 111 | struct NxGqaHeadMap |
consts
| 74 | const NX_MAGIC_65536: i64 = 65536 |
| 77 | const NX_GQA_MAX_Q_HEADS: i64 = 256 |
| 78 | const NX_GQA_MAX_KV_HEADS: i64 = 256 |
| 81 | const NX_ATTN_MHA: i64 = 0 // n_kv == n_q |
| 82 | const NX_ATTN_GQA: i64 = 1 // 1 < n_kv < n_q |
| 83 | const NX_ATTN_MQA: i64 = 2 // n_kv == 1 |
| 84 | const NX_ATTN_N_KINDS: i64 = 3 |
| 93 | const NX_GQA_OK: i64 = 0 |
| 94 | const NX_GQA_BAD_INPUT: i64 = 1 |
| 95 | const NX_GQA_BAD_DIVISIBILITY: i64 = 2 |
| 96 | const NX_GQA_OUT_OF_RANGE: i64 = 3 |
| 97 | const NX_GQA_TAMPER: i64 = 4 |
| 98 | const NX_GQA_N_VERDICTS: i64 = 5 |
| 107 | const NX_GQA_MAP_CANARY_PRE: i64 = 0x4751414D6170503A // "GQAMapP:" |
| 108 | const NX_GQA_MAP_CANARY_POST: i64 = 0x4D617045314532A0 // "MapE1E2 " |
functions
| 86 | func nx_attn_kind_is_valid(k: i64) -> i64 |
| 100 | func nx_gqa_verdict_is_valid(v: i64) -> i64 called by 1: main |
| 122 | func nx_gqa_head_map_is_valid(m: *NxGqaHeadMap) -> i64 |
| 137 | func nx_attn_classify(n_q_heads: i64, n_kv_heads: i64) -> i64 |
| 145 | func nx_gqa_head_map_new(n_q_heads: i64, n_kv_heads: i64) -> *NxGqaHeadMap |
| 174 | func nx_gqa_head_map_kv_head_for_q_head(m: *NxGqaHeadMap, q_head: i64) -> i64 |
| 184 | func nx_gqa_head_map_count_q_heads_in_group(m: *NxGqaHeadMap, kv_head: i64) -> i64 |
| 198 | func nx_gqa_head_map_n_q_heads(m: *NxGqaHeadMap) -> i64 |
| 203 | func nx_gqa_head_map_n_kv_heads(m: *NxGqaHeadMap) -> i64 |
| 208 | func nx_gqa_head_map_group_size(m: *NxGqaHeadMap) -> i64 |
| 213 | func nx_gqa_head_map_kind(m: *NxGqaHeadMap) -> i64 |
| 222 | func nx_gqa_head_map_kv_savings_q16(m: *NxGqaHeadMap) -> i64 |