kda-neuron-kernels
Neuron NKI kernels for KDA (Kernel-based Decomposed Attention) linear attention.
Model-agnostic implementation of the KDA algorithm described in the flash-linear-attention (fla-core) library. Compatible with any HuggingFace Transformers model whose attention layer follows the KDA algorithm. Runs on AWS Trainium (trn2) under PyTorch Native.
This is a kernel-type repository (build variant torch-neuron, backend neuron).
Load it with the kernels library on a
Trainium machine:
from kernels import get_kernel
k = get_kernel("jburtoft/kda-neuron-kernels", version=1, trust_remote_code=True)
# k.kda_chunked_fused(...), k.kda_recurrent(...), k.kda_chunk_step(...), etc.
What this package provides
Inference (forward)
kda_recurrent_fwd(q, k, v, g, beta)— decode / token-generation per-token recurrence. One (batch, head) invocation processesStokens sequentially.kda_recurrent_fwd_state(q, k, v, g, beta)— same, and also returns the final recurrent state for prefill→decode hand-off.kda_chunk_step(q, k, v, beta, g_cumsum, g_last, state_in)— prefill per-chunk step. Processes one 128-token chunk given the state from the previous chunk. Uses a scalar-mean decay approximation for the intra-chunk term — see the warning below.kda_chunk_step_exact(q, k, v, beta, g, state_in)— numerically exact per-channel prefill (sub-chunk + WY reformulation). Use this when the model's gate decay is non-trivial (see warning below). ~1.05× the latency ofkda_chunk_step.kda_chunk_step_exact_multihead(q, k, v, beta, g, state_in)— head-interleaved exact prefill overNVheads ([NV, C, dk]shapes).kda_decode_batch(q, k, v, g, beta, state_in)— batched multi-(request, head) decode; advances allB*nvitems one token in a single call. Shapes[B, nv, dk].
Training (differentiable, loss.backward()-ready)
kda_recurrent(q, k, v, g, beta, initial_state=None)→(output, final_state). Differentiable; routes through the NKI recurrent backward. Requires zeroinitial_state.kda_chunked(q, k, v, g, beta, initial_state=None)→(output, final_state). Differentiable; loops chunks in Python around the single-chunk kernels. Supports state carry-over across chunks.kda_chunked_fused(q, k, v, g, beta, initial_state=None)→(output, final_state). Same numerics askda_chunked, but processes all chunks in one NKI launch per direction. Faster on multi-chunk sequences. Recommended for training.- Raw kernels also exported:
kda_recurrent_fwd_v2,kda_chunk_step_v2,kda_recurrent_bwd,kda_chunk_bwd,kda_fused_chunked_fwd,kda_fused_chunked_bwd.
Requirements
- Hardware: AWS Trainium (tested on trn2.3xlarge).
- SDK / runtime: PyTorch Native (
device="neuron"), torch-neuronx 2.11+, PyTorch 2.11+. - NKI ≥ 0.4.0.
kernels≥ 0.15.2 (to load viaget_kernel).transformerswithKernelConfigsupport, if using theKernelConfigpath.
Usage
Inference — direct kernel calls
import torch
import torch.nn.functional as F
from kda_neuron_kernels.build.torch_neuron import kda_chunk_step_exact
# Prefill one 128-token chunk for a single (batch, head) slice.
S, Dk = 128, 128
q_raw = torch.randn(S, Dk)
k_raw = torch.randn(S, Dk)
v = torch.randn(S, Dk)
g = -torch.rand(S, Dk) * 0.01 # per-channel log-decay (negative)
beta = torch.rand(S) # per-token scalar
# Caller preprocessing (fla-core convention): L2-norm q, k and scale q by 1/sqrt(Dk).
q = F.normalize(q_raw, p=2, dim=-1) * (Dk ** -0.5)
k = F.normalize(k_raw, p=2, dim=-1)
beta_bc = beta.unsqueeze(-1).expand(S, Dk).contiguous()
state = torch.zeros(Dk, Dk, dtype=torch.float32).to("neuron")
chunk_out, state = kda_chunk_step_exact(
q.to("neuron"), k.to("neuron"), v.to("neuron"),
beta_bc.to("neuron"), g.to("neuron"), state,
)
# chunk_out: (128, 128) per-token output; state: (128, 128) carries to the next chunk.
See tests/example_usage.py for a fully-worked example.
Training
import torch, torch.nn.functional as F
from kda_neuron_kernels.build.torch_neuron import kda_chunked_fused
S, D = 256, 128 # S must be divisible by 128
q = F.normalize(torch.randn(S, D), p=2, dim=-1) * (D ** -0.5)
k = F.normalize(torch.randn(S, D), p=2, dim=-1)
v = torch.randn(S, D) * 0.3
g = -torch.rand(S, D) * 0.01 # per-channel log-decay
beta = (torch.rand(S, 1) - 0.5 + 1.0).expand(S, D).contiguous() # per-token scalar bcast
for t in (q, k, v, g, beta):
t.requires_grad_(True)
out, final_state = kda_chunked_fused(q, k, v, g, beta, initial_state=None) # .to("neuron") for hardware
loss = out.sum()
loss.backward() # gradients flow through the NKI backward
# q.grad, k.grad, v.grad, g.grad, beta.grad now populated
Kernels operate per (batch, head); loop B*H in the caller.
Input contract
Callers pass raw q, k already L2-normed, with q additionally scaled by
1/sqrt(dk) (fla-core convention). The kernels compute all decay-related scaling
internally from g.
For kda_chunk_step (and _v2):
q,k: L2-normed q (scaled by1/sqrt(dk)), L2-normed k — shape(128, 128)v: value tensor —(128, 128)beta: per-token scalar, broadcast to(128, 128)g_cumsum: per-channelcumsum(g)within the chunk —(128, 128)g_last:g_cumsum[-1:, :]broadcast to(128, 128)state_in: recurrent state from the previous chunk —(128, 128)- Returns
(chunk_out, state_out), each(128, 128).
For kda_recurrent_fwd:
q,k:(S, 128)L2-normed (q scaled)v,g,beta:(S, 128)(betaper-token scalar, broadcast across the dim)- Returns
output (S, 128).
Constraints
head_k_dim == head_v_dim == 128(matches the NeuronCore SBUF partition width). Other head dims are not supported.chunk_size == 128for the chunked kernels;Smust be divisible by 128.- float32 inputs.
kda_recurrent(training wrapper) requires zeroinitial_state; usekda_chunkedfor state carry-over across sequence packs.
⚠️ Functional warning — chunked gate-decay approximation
kda_chunk_step (and its training wrappers kda_chunked / kda_chunked_fused) use a
scalar-mean approximation for the intra-chunk attention decay
(exp(mean_c(gc)_i - mean_c(gc)_j) instead of the exact per-channel
exp(gc_i - gc_j)). This is a compute/accuracy tradeoff.
The approximation is only accurate for small gate decay. Measured single-chunk cosine similarity vs the fla-core reference:
gate scale g |
cos_sim (kda_chunk_step) |
|---|---|
| ~0.01 (small) | ~0.99 |
| ~0.3 | ~0.49 |
| ~2.0 | ~0.22 |
If your model has non-trivial gate decay, use kda_chunk_step_exact (or
kda_chunk_step_exact_multihead), which is numerically exact (cos_sim ≥ 0.9999999
across all gate regimes) at ~1.05× the latency. The recurrent kernels
(kda_recurrent_fwd, kda_recurrent) are exact in all regimes.
Because kda_chunked / kda_chunked_fused differentiate the approximate forward,
their dg gradient is the exact gradient of the approximate forward — self-consistent
for training with these kernels, but not equal to the exact-per-channel dg unless the
approximation is accurate (i.e. small gate decay).
Parity
Against the fla-core naive_recurrent_kda / naive_chunk_kda PyTorch references
(random inputs, g_scale=0.01, seq_len=128, single (batch, head)):
| Kernel | cos_sim vs fla | max_abs_diff |
|---|---|---|
kda_recurrent_fwd (S=128) |
1.00000 | 3.4e-8 |
kda_chunk_step (C=128, small gate) |
0.99988 | 1.2e-3 |
kda_chunk_step_exact (C=128, all gate regimes) |
≥ 0.9999999 | ~1e-6 |
Training backward gradients (kda_recurrent, kda_chunked) verified end-to-end
through loss.backward() against fla-core autograd: recurrent all five gradients
cos_sim ≥ 0.9998; chunked dq/dk/dv/dbeta ≥ 0.9998 (with the dg caveat above).
Performance
Measured on trn2.3xlarge, LNC=2, single logical core, single (batch, head) invocation.
Prefill (chunked)
| Metric | Value |
|---|---|
| Wall-clock per chunk (C=128) | 87 μs |
| Per-token effective | 0.68 μs |
| Achieved TFLOPS | 1.89 |
| MFU (BF16 peak 158 TFLOPS/LNC=2) | 1.19% |
| MBU (empirical 218 GB/s/LNC=2) | 3.11% |
Decode (recurrent)
| Metric | Value |
|---|---|
| Wall-clock per call (S=128) | 527 μs |
| Per-token (amortized) | 4.1 μs |
The recurrent kernel is overhead-dominated at small sequence lengths. For real decode
throughput, batch tokens (or requests via kda_decode_batch): per-token wall-clock
drops from ~70 μs at S=1 to ~6 μs at S=128, and kda_decode_batch amortizes launch
overhead across a whole serving batch.
Training (fused vs Python chunk loop, fwd+bwd)
| S | Chunks | kda_chunked (loop) |
kda_chunked_fused |
|---|---|---|---|
| 256 | 2 | 695 μs | 336 μs |
| 512 | 4 | 1320 μs | 338 μs |
| 1024 | 8 | 2588 μs | 529 μs |
The fused path pays per-launch overhead once instead of per chunk, so its advantage
grows with sequence length. Prefer kda_chunked_fused for training.
MFU/MBU denominators are per LNC=2 core on trn2 (NeuronCore-v3), from the AWS Trainium2 architecture guide and empirical measurements.
Not provided
- A full
nn.Moduledrop-in replacement for a HuggingFaceKdalayer (planned).
References
- Algorithm: flash-linear-attention (fla-core)
— KDA is defined in
fla/ops/kda/.
License
Apache-2.0. This is an inference/training runtime kernel package, not a model. The fla-core algorithm reference is MIT-licensed and compatible.
- Downloads last month
- -