For the complete documentation index, see llms.txt. Markdown versions of all pages are available by appending .md to any URL (e.g. /max/get-started.md).
Mojo struct
FA4Config
struct FA4Config[qkv_dtype: DType, *, rope_dtype_: Optional[DType] = None, scale_dtype_: Optional[DType] = None]
Fieldsβ
- βMMA_M (
Int): - βBM (
Int): - βBN (
Int): - βBK0 (
Int): - βBK1 (
Int): - βqk_depth (
Int): - βpadded_qk_depth (
Int): - βov_depth (
Int): - βpadded_ov_depth (
Int): - βnope_depth (
Int): - βpadded_nope_depth (
Int): - βgroup (
Int): - βnum_q_heads (
Int): - βnum_kv_heads (
Int): - βTMEM_S1 (
Int): - βTMEM_O0 (
Int): - βTMEM_O1 (
Int): - βTMEM_P0 (
Int): - βTMEM_P1 (
Int): - βtmem_used (
Int): - βnum_kv_stages (
Int): - βnum_qk_stages (
Int): - βnum_pv_stages (
Int): - βsmem_used (
Int): - βfuse_gqa (
Bool): - βswizzle_mode (
TensorMapSwizzle): - βuse_shared_kv (
Bool): - βpair_cta (
Bool): - βnum_q (
Int): - βsingle_o (
Bool): - βpage_size (
Int): - βis_mla (
Bool): - βsplitk_partitions (
Int): - βdynamic_cluster_dim (
Bool): - βrow_major_v_atoms (
Bool): - βrow_major_k_atoms (
Bool): - βuse_ws (
Bool): - βm_pack (
Int):
Implemented traitsβ
AnyType,
Copyable,
ImplicitlyCopyable,
ImplicitlyDeletable,
Movable,
RegisterPassable,
TrivialRegisterPassable
comptime membersβ
mbar_sizeβ
comptime mbar_size = size_of[DType.int64]()
MMA_Kβ
comptime MMA_K = Int(16) if qkv_dtype.is_half_float() else Int(32)
num_correction_colsβ
comptime num_correction_cols = 1
num_threadsβ
comptime num_threads = Int(512)
qkv_dtype_sizeβ
comptime qkv_dtype_size = size_of[qkv_dtype]()
rope_dtypeβ
comptime rope_dtype = rope_dtype_.or_else(qkv_dtype)
rope_dtype_sizeβ
comptime rope_dtype_size = size_of[(load_from_mem rope_dtype_.value())]() if rope_dtype_.__bool__() else Int(0)
scale_dtypeβ
comptime scale_dtype = scale_dtype_.or_else(qkv_dtype)
scale_dtype_sizeβ
comptime scale_dtype_size = size_of[(load_from_mem scale_dtype_.value())]() if scale_dtype_.__bool__() else Int(0)
sm100_smem_carveoutβ
comptime sm100_smem_carveout = (GPUInfo.from_family(AcceleratorArchitectureFamily(Int(32), Int(2048), Int(233472), Int(65536), Int(1024)), StringSlice("B200"), Vendor(Int8(2)), StringSlice("cuda"), StringSlice("blackwell"), SIMD(10), StringSlice("sm_100a"), Int(148)) - Int(1024))
sm100_tmem_colsβ
comptime sm100_tmem_cols = 512
TMEM_S0β
comptime TMEM_S0 = Int(0)
Methodsβ
__init__β
def __init__(*, num_q_heads: Int, group: Int, qk_depth: Int, ov_depth: Int, swizzle_mode: TensorMapSwizzle, page_size: Int, is_mla: Bool, pair_cta: Bool = False, BM: Int = Int(256), num_qk_stages: Int = Int(0), splitk_partitions: Int = Int(1), dynamic_cluster_dim: Bool = False, nope_depth: Int = Int(-1), single_o: Bool = False, bn_cap: Int = Int(0)) -> Self
BM_effβ
def BM_eff(self) -> Int
Number of distinct sequence positions per full tile. When fuse_gqa, each tile covers BM // group seq positions x group heads.
Returns:
cta_groupβ
cluster_sizeβ
def cluster_size(self) -> Int
CTAs per launch cluster.
Unifies the two cluster uses: cta_group (pair-CTA shares one MMA
across 2 CTAs) and splitk_partitions (num_q==1 split-K groups CTAs
that independently attend over K/V partitions and combine via DSMEM).
These are mutually exclusive today (split-K is single-CTA only), so the
product is cta_group for pair-CTA and splitk_partitions for split-K.
Drives the static nvvm.cluster_dim metadata and the launch
cluster_dim.
Returns:
PairBM_effβ
v_cols_per_ctaβ
v_box_colsβ
def v_box_cols(self) -> Int
V TMA box depth (columns) per issued V load.
WS shared sub-tile ring: V is depth-split into num_qk_stages 256x64
sub-tiles, so each V TMA loads v_cols_per_cta() // num_qk_stages
columns (mirrors K's BK0). Non-WS loads V whole (v_cols_per_cta()).
MUST be used identically at EVERY V TMA type-param site (fa4_load
signature, dispatch create_tma_tile, kernel launch param); a bare
inline ... if use_ws else ... does NOT fold to v_cols_per_cta() at
parse time, so the type-param expressions would mismatch across sites.
Routing all sites through this single method keeps them syntactically
identical.
Returns:
v_e_box_rowsβ
def v_e_box_rows(self) -> Int
Layout-E (m_pack == 2) V TMA box KEY-row count per issued sub-tile.
Layout-E KEY-splits V into m_pack * num_qk_stages per-partition
reduction-chunk sub-tiles instead of Layout-G's DEPTH-split
(v_box_cols()): each sub-tile covers (BN // m_pack) // num_qk_stages keys (this partition's OWN reduction chunk) and the
FULL depth (v_e_box_cols()). Sibling of v_box_cols(), kept as a
SEPARATE method (not folded into v_box_cols()) so Layout-G's
v_row_major() / kv_tma_fold_chunks derivation -- which reads
v_box_cols() -- stays byte-identical; see
docs/plans/sm100-fa4-layout-e-mma64.md "the reduction-split
geometry". Meaningful only for m_pack == 2; a harmless (unused)
value otherwise.
Returns:
v_e_box_colsβ
def v_e_box_cols(self) -> Int
Layout-E (m_pack == 2) V TMA box depth (columns) per issued sub-tile: the FULL padded_ov_depth, since Layout-E splits V by KEY (reduction), not by depth. Sibling of v_e_box_rows().
Returns:
v_tma_box_rowsβ
def v_tma_box_rows(self, page_size: Int) -> Int
V TMA box KEY-row count for this config's layout.
The single selector every V TMA type-param site routes through
(dispatch create_tma_tile, kernel VTMAOpType, the fa4_load
signature), so the box shape stays syntactically identical across
sites -- the same single-source-of-truth rule v_box_cols()
documents. Layout-E (m_pack == 2) uses the KEY-split
v_e_box_rows(); Layout-G / non-WS use kv_sub_tile_rows(BN, page_size) (byte-identical to the historical inline expression).
Returns:
v_tma_box_colsβ
def v_tma_box_cols(self) -> Int
V TMA box depth (columns) for this config's layout.
Sibling of v_tma_box_rows(): Layout-E (m_pack == 2) uses the
full-depth v_e_box_cols(), Layout-G / non-WS the depth-split
v_box_cols().
Returns:
nope_cols_per_ctaβ
def nope_cols_per_cta(self) -> Int
K_nope columns stored in this CTA's SMEM (per-CTA padded nope width).
Sibling of v_cols_per_cta() on padded_nope_depth. Equals
v_cols_per_cta() for MHA / DeepSeek (nope == ov).
Returns:
shared_kv_colsβ
def shared_kv_cols(self) -> Int
Un-halved width of one shared K_nope/V SMEM stage.
K_nope (padded_nope_depth) and V (padded_ov_depth) share one buffer, so
a stage fits the wider of the two. This is the full (non-pair-halved)
column count; pair-CTA halving is applied at the call site where needed.
Equals padded_ov_depth for MHA / DeepSeek (nope == ov).
Returns:
k_rows_per_ctaβ
v_row_majorβ
def v_row_major(self) -> Bool
Effective row-major (page-dense, chunk-inner) V layout selector.
Drives BOTH the V TMA producer fold (tma_copy_v[row_major=...]) and
the P@V MMA consumer descriptor (smem_descriptor[page_dense=...] /
SM100TensorAccumulator[b_page_dense=...]); a single accessor keeps the
producer's page-dense SMEM and the consumer's descriptor in agreement.
Returns True only when the page-dense layout is actually applicable β
i.e. when the V-side kv_tma_fold_chunks[row_major=True] would fold
(>= 2). The base_ok geometry comes from the SHARED _kv_fold_base_ok
gate (the single source of truth the predicate also uses), so the two
cannot drift; a comptime assert at the dispatch site still cross-checks
this accessor against the real fold result (it spans the
box_rows == page_size bridge the shared gate does not).
Restricted to genuine multi-page paging (0 < page_size < BN):
- This is the only regime where the fold helps: with
page_size >= BNorpage_size == 0the tile is a single page (pages_per_iter == 1) that the chunk-outer rank-4 fold already loads in one TMA. - It is also the only regime where the rank-5 atom-row coordinate
(
gmem_row // _SWIZZLE_ATOM_ROWS) is safe: a block-indirectedPagedKVCachegivesgmem_row = block_idx * stridewithstridea multiple ofpage_size, sopage_size % 8 == 0(checked below) makes every page row 8-aligned. Continuous / ragged operands (page_size >= BN) place tiles at arbitrary token offsets that are NOT 8-aligned, which would corrupt the atom-row coordinate.
Gated to single-CTA: under pair_cta the descriptor (BMN = v_cols_per_cta() = ov/2) and the accumulator advance (b_BMN = MMA_N = ov) disagree on mn_dim for the native layout β a fast-follow change.
SWIZZLE_128B only (the native layout is defined there).
Returns:
k_row_majorβ
def k_row_major(self) -> Bool
Effective row-major (page-dense, chunk-inner) K layout selector.
K-side analog of v_row_major(): drives BOTH the K TMA producer fold
(tma_copy_k[row_major=...]) and the Q@K' MMA consumer descriptor
(smem_descriptor[is_k_major=True, page_dense=...] /
SM100TensorAccumulator[b_page_dense=...] for the k-major B operand).
A single accessor keeps the producer's page-dense SMEM and the
consumer's descriptor in agreement (disagreement is silent wrong
output, not a crash).
Gated identically to V: SWIZZLE_128B only, single-CTA only
(not pair_cta), and genuine multi-page paging
(0 < page_size < k_rows_per_cta() with page_size % 8 == 0, so a
block-indirected PagedKVCache gives 8-aligned page rows for the
rank-5 atom-row coordinate β see v_row_major() for the full
rationale). The base_ok geometry comes from the SHARED
_kv_fold_base_ok gate (the K-side kv_tma_fold_chunks uses the same
gate: BK0 % gran == 0, num_chunks >= 2, qk_depth % BK0 == 0), so
the two cannot drift; a comptime assert at the dispatch site still
cross-checks this against the real fold result.
K and V share the same constructor-computed default
(row_major_{v,k}_atoms = not is_mla and 0 < page_size < BN); the two
accessors then apply their own feasibility gates independently.
Returns:
q_nope_bytesβ
def q_nope_bytes(self) -> Int
Q nope region bytes: BM * padded_nope_depth * dtype_size.
The Q_nope tile feeds the Q@K_nope' contraction, so its width is the
non-rope Q depth (padded_nope_depth), not the V/output depth. They
coincide for MHA / DeepSeek MLA.
Returns:
q_rope_bytesβ
def q_rope_bytes(self) -> Int
Q rope region bytes. Uses rope_dtype_size when set, else dtype_size.
Returns:
rope_depthβ
def rope_depth(self) -> Int
Depth of the rope part. Calculated as: padded_qk_depth - padded_nope_depth (0 for MHA where qk_depth == nope_depth). Uses the non-rope Q/K width (padded_nope_depth), NOT the V/output depth β the two differ when v_head_dim != qk_nope_head_dim.
Returns:
num_rope_buffersβ
def num_rope_buffers(self) -> Int
Number of separate rope smem buffers (shared mode only).
In shared mode K tiles alternate with V tiles in the pipeline. At most ceildiv(num_kv_stages, 2) K tiles can be in-flight simultaneously, so we only need that many rope buffers. For MHA (rope_depth=0), no rope buffers are needed.
Returns:
num_k_scale_bufsβ
def num_k_scale_bufs(self) -> Int
Number of staged k_scale smem buffers.
In shared mode, K tiles alternate with V tiles so at most ceildiv(num_kv_stages, 2) K tiles are in-flight simultaneously. In non-shared mode, each KV stage has its own K buffer. Returns 0 when scale_dtype_size == 0 (no per-token scaling).
Returns:
supportedβ
with_num_qβ
def with_num_q(self, num_q: Int, *, num_qk_stages: Int = Int(0)) -> Self
Reconstruct this config with a different num_q (single-CTA).
num_qk_stages == 0 (default) lets the constructor derive the
optimal staging for the new shape β appropriate for the dispatch-time
1Q/2Q selection, where each launch config is free-standing. A nonzero
value pins the staging (see switch_1q_config).
pair_cta is forced False because num_q == 1 is single-CTA only
(see supported()). Re-passing the stored swizzle_mode is faithful:
the constructor re-derives it (FP8 re-forces 64B), and it is already
the post-override value here. The row_major_{v,k}_atoms fields are
not re-passed: the constructor recomputes them from
page_size/BN/is_mla, and BN is num_q-independent, so the
value is identical to self's.
with_splitkβ
def with_splitk(self, splitk_partitions: Int) -> Self
Reconstruct this config with a split-K cluster size (num_q==1).
Split-K groups splitk_partitions single-CTA kernels in a launch
cluster that partition the K/V sequence and (from M4) combine via
DSMEM. pair_cta is forced False β split-K is single-CTA only: each
CTA runs its own cta_group::1 MMA over its own TMEM/SMEM, and the
cluster exists purely to group the split-K partitions. num_q and the
derived num_qk_stages are preserved, so with_splitk(1) is a no-op
(identical config) and the split-K plumbing folds away.
nope_depth (the Q@K'/Q_nope width) and single_o (the wide-V 1Q TMEM
mode) are re-passed so a GLM-style config (v_head_dim != qk_nope) or a
single-O config survives the reconstruction; both are byte-identical for
the DeepSeek/MHA shapes (nope == ov, single_o == False).
with_bmβ
def with_bm(self, bm: Int) -> Self
Reconstruct this config with an explicit BM (single-CTA).
Used by dispatch to force a warp-specialized packed-TMEM datapath
(BM=32 -> MMA_M=32, m_pack=4, Layout-G; BM=64 -> MMA_M=64,
m_pack=2, Layout-E), with use_ws=True, for short prompts. pair_cta
is forced False (BM=32/64 are single-CTA only, per supported()).
num_qk_stages/splitk_partitions/single_o are left at their
constructor defaults (derive staging, no split-K, 2-O) so the
reconstruction is byte-identical to a direct FA4Config(..., BM=bm)
build (see test_fa4_config_ws_bm64_probe); use_ws/m_pack are
derived from the new BM. nope_depth is re-passed so a GLM-style shape
survives (byte-identical for MHA where nope == ov).
switch_1q_configβ
def switch_1q_config(self) -> Self
The 1Q variant used by the in-kernel per-sequence 1Q/2Q switch.
Unlike the dispatch-time conversion (with_num_q(1)), which is free
to pick the optimal staging, this pins num_qk_stages to this (2Q)
config's value: the switch feeds the 2Q-built TMA ops to the 1Q body,
so the per-stage K split (QTMATile's smem-tile last dim and
k_tma's BK = padded_qk_depth // num_qk_stages) must match. The
pinned value is always arithmetically valid here because
padded_qk_depth and swizzle_mode are identical across the two
configs; if its extra barriers do not fit in 1Q smem, the constructor
falls back to 1 stage and can_switch_to_1q() rejects the switch.
can_switch_to_1qβ
def can_switch_to_1q(self) -> Bool
Whether a 2Q-launched kernel may dispatch to the 1Q body at runtime.
True only when this is a 2Q single-CTA config AND a valid 1Q variant
exists whose TMA-op types match the 2Q ones. switch_1q_config()
pins num_qk_stages (the one TMA-op parameter that could otherwise
diverge β BN, the per-half Q BM (128), v_tma_op, and
ragged_tma_store already match), so the equality check below only
fails when the pinned staging could not be honored (smem fallback to
1 stage). When this returns False the kernel runs pure 2Q.
Returns:
launch_smem_usedβ
def launch_smem_used(self) -> Int
Dynamic smem to reserve when launching this config's kernel.
When the launched kernel may dispatch to the 1Q body at runtime
(can_switch_to_1q()), it constructs the 1Q SM100AttentionSMem over
the same dynamic smem region, so the launch must reserve the max of
both footprints. Otherwise this is just smem_used.
Returns:
descriptionβ
correction_smem_elementsβ
num_active_warps_per_groupβ
num_active_threads_per_groupβ
Was this page helpful?
Thank you! We'll create more content like this.
Thank you for helping us improve!