feat: Add DeepSeek-V4 CSA attention kernels - #13
Open
aws-zifan-he wants to merge 1 commit into
Open
Conversation
…n across 8 cores in a single trainium 3 device
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This change adds the whole DeepSeek-V4 CSA attention block as NKI kernels: it takes the raw hidden state of a new token and returns the projected block output, running on one Trainium3 chip as 4 tensor-parallel ranks of 2 logical NeuronCores each (whole single device). One
@nki.jitlaunch covers the lightning-indexer scoring, the top-k selection, the sparse attention and the output inverse RoPE, so neither the score row nor the selected-index array ever returns to the host. Over various sequence length, the kernel achieves 4x-170x speedup over the naive NKI baseline.Highlights
How the algorithm works
Standard attention compares each new token against every past token, so one decode step costs
O(S)and the KV cache grows with the conversation. At a 128K context it is the cache, not the weights, that limits how many users one device can serve. CSA replaces the full comparison with a selection: the model keeps a compressed KV cache, a small network scores every compressed position, and the attention reads only the 1024 highest-scoring positions plus a local window of 128. Neither count changes with the context length, so the attention's cost is constant.The 128 query heads are head-parallel across the 4 ranks, so each rank owns 32 query heads and 4 output-projection groups; one
torch_neuronx.traceof one rank emits one NEFF holding the projections, the sparse attention and the cross-rankall_reduce. Decode issues three launches per step:nki_qkv_rms_rope_kernel[1]nki_indexer_qproj_gemv[2]nki_indexer_score_topk_gather_2core[2]The evaluated model is the production DeepSeek-V4-Pro-Max configuration.
Parallelism
An important question when deploying a kernel to multiple cores is the dimension of distribution: how should we split the work across cores? Since we know that communication between chips is more expensive than communication inside a chip, our strategy is splitting works that does not require synchronizations across chip, and splitting works that need synchronization between two NeuronCores inside a chip. Hence:
The top-k on the GPSIMD engine
The rotational TopK in NKI Library employs max8 and nc_find_index8 to perform repetitive Top-8 operations and generalize to arbitrary TopK. This is very inefficient and bottleneck the vector engine to proceed the attention computations. Here comes to the key component of our optimization: we move the selection itself onto GPSIMD and fuse the TopK with multi-query index score computation in the indexer for further execution overlapping.
Another optimization we did along the way is reduce the DMA of KV cache. In DeepSeek V4, K and V are the same embedding. Instead of fetching the KV entries where one is transposed and one is not, we read from HBM once for K and V and transpose in SBUF.
After these updates, TopK selection is no longer the bottleneck. From 8K to 128K, the context grows 16x and the latency grows 1.24x, due to the scaling of computing indexer scores. The kernel achieves an overall 4.75x~16x speedup over the previous step.
DMA Traffic Shaping
The
nisa.dma_copyAPI carries apriority. This priority defines how you want to allocate the DMA bandwidth when multiple DMA operations are executed. A lower priority number means a higher priority. In the CSA kernel, we tune the priority based on the criticality and efficient of each DMA access to balance the latency.swdgegather; the indexer cache slice; the score store-back that the barrier waits on; the snake reformat; the q-projection weight bursts; the RMS tile assemblyK^TandV, which are contiguous and efficient; the RoPE cos and sin of the projection kernelPerformance
Whole-block decode latency (ms), BF16. Each row is a development step, so the table also shows what each mechanism was worth.
Launching a full block
The block is one PyTorch module. It owns its projection weights and the attention core, computes rank
tp_rank's shard, and appends the cross-rank all-reduce as its final op, so the traced block returns the full output:configis one rank's view of the model, fromshard_for_tp(CSAConfigFull(seq_len=...), tp_size): it divides bothn_headsando_groupsbytp_size, which leavesgroup_in = n_heads * head_dim / o_groupsat the full model's value — whatwo_aexpects, since the productionColumnParallelLinearis built from the global counts. A rank is then an ordinary block of its own size, constructed withtp_size=1. Passingreplica_ranks=Noneinstead returns the rank-local partial and leaves the caller to sum them.csa_block.pydrives this end to end against the CPU golden, in either of two modes:# The real topology: one rank per worker, all-reduce merged into each NEFF. torchrun --nproc_per_node=4 \ -m nkilib.experimental.deepseek_v4_csa.csa_block --phase decode --seq-len 32768Under
torchruneach worker pins itself to its own two physical NeuronCores, so the 4 ranks occupy 8 cores and run concurrently. The driver reads the base index fromNEURON_RT_VISIBLE_CORESorNEURON_RT_NUM_CORES.By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.