FlashMLA

repository·main·Indexed 11 days ago

https://github.com/deepseek-ai/flashmla

A library of optimized attention kernels for DeepSeek models, providing high-performance implementations for Sparse and Dense Multi-head Latent Attention (MLA) during prefill and decoding stages. Optimized for SM90 and SM100 architectures, it features 'seesaw' scheduling to maximize Tensor Core utilization and a specialized FP8 KV cache format for DeepSeek-V3.2. Includes support for sparse decoding with Crossover and Distributed Shared Memory (DSM) on Hopper GPUs.

Tokens
2.9K
Snippets
4
Records
13
Agent score
96%

What's inside FlashMLA

  1. Understanding the compute-bound nature of the MLA kernel

    main

    The FlashMLA kernel is primarily optimized for compute-bound scenarios. While decoding-stage attention kernels are often memory-bound, the MLA algorithm becomes compute-bound when the number of query heads ($h_q$) and query tokens per request ($s_q$) satisfy a specific threshold relative to the GPU's compute-to-memory ratio.

    For an NVIDIA H800 SXM5 GPU, the kernel is considered compute-bound when:

    $h_q s_q \ge 128$

    In DeepSeek's inference systems, where Tensor Parallelism is not used for decoding instances, $h_q$ is typically 128, placing the workload in the compute-bound regime. Consequently, FlashMLA focuses on maximizing Tensor Core utilization through advanced scheduling.

  2. How 'seesaw' scheduling overlaps computation in the MLA kernel

    main

    To maximize GPU utilization, the new FlashMLA kernel uses a technique called "seesaw" scheduling. This is a variant of ping-pong scheduling designed to overcome register constraints.

    The Problem: Register Pressure

    In MLA, the output matrix must be stored in registers to satisfy WGMMA instruction requirements. A $64 \times 512$ output matrix occupies 32,768 32-bit registers. Since an SM only has 65,536 32-bit registers, it is impossible to maintain two full output matrices for traditional interleaved ping-pong scheduling.

    The Solution: Seesaw Scheduling

    The kernel splits the output matrix vertically into two halves: $O_L$ (left) and $O_R$ (right), each $64 \times 256$. It then uses two warpgroups to interleave operations on two KV blocks ($K_0, V_0$ and $K_1, V_1$) such that CUDA Core operations (softmax/scaling) and Tensor Core operations (matmul) overlap.

    The mathematical flow for one step is:

    1. Warpgroup 0 computes $\vec{p}_0 = \vec{q} K_0^\intercal / qk_scale$.
    2. Warpgroup 1 computes $\vec{p}_1 = \vec{q} K_1^\intercal / qk_scale$.
    3. Warpgroup 0 updates the running max $m$ and performs softmax on $\vec{p}_0$, then updates the left output $\vec{o}_L$.
    4. Warpgroup 1 updates the running max $m$, performs softmax on $\vec{p}_1$, and updates the right output $\vec{o}_R$.
    5. The warpgroups then perform cross-updates (e.g., Warpgroup 1 updating $\vec{o}_R$ with components from $\vec{p}_0$) to ensure mathematical equivalence to the online softmax algorithm used in FlashAttention.

    This approach allows for overlapping memory access (via TMA) and computation, achieving up to 80% Tensor Core utilization.

  3. Understand the FP8 KVCache format for DeepSeek-V3.2

    main

    To support extended context lengths (up to 128K tokens) while reducing GPU memory pressure, DeepSeek-V3.2 uses a fine-grained FP8 quantization for the KVCache.

    Format Details:

    • Quantized Part: The first 512 elements of each token's KV Cache are quantized using tile-level quantization (tile size $1 \times 128$). This consists of 512 float8_e4m3 values and 4 float32 scale factors.
    • Unquantized Part (RoPE): The remaining 64 elements (the RoPE part) are stored in bfloat16 to prevent precision loss.
    • Total Size: Each token's KVCache occupies 656 bytes (512 $\times$ float8_e4m3 + 4 $\times$ float32 + 64 $\times$ bfloat16).

    Kernel Execution Flow:

    1. Dequantize the 512 float8_e4m3 values into bfloat16.
    2. Concatenate them with the 64 bfloat16 RoPE values.
    3. Perform MQA (Multi-Query Attention) calculations using bfloat16 matrix multiplication-add (MMA) operations (inputs in bfloat16, outputs in float32).
  4. How the FP8 sparse decoding kernel uses Crossover and DSM on Hopper

    main

    The FP8 sparse decoding kernel for Hopper GPUs is designed to overcome the 'dequantization-bound' bottleneck where CUDA Core dequantization cannot keep up with Tensor Core MMA operations. It utilizes a technique called Crossover enabled by Distributed Shared Memory (DSM).

    The Crossover Mechanism: Since DeepSeek-V3.2 uses Multi-Query Attention (MQA), all 128 query heads attend to the same key heads. The kernel exploits this by launching CTAs (CUDA Thread Blocks) in clusters of size 2. Each CTA in the cluster is responsible for 64 query heads.

    Implementation Steps via DSM:

    1. Load: Each CTA loads half of the quantized K/V from global memory using a wide 128-bit __ldg load.
    2. Dequantize: Each CTA dequantizes its assigned half on the CUDA Cores.
    3. Store & Exchange:
      • The CTA stores its dequantized K/V into its own shared memory.
      • Simultaneously, it uses st.async to write that dequantized data into the shared memory of the other CTA in the cluster.
    4. Synchronize: The kernel uses a cluster transaction barrier to ensure data exchange is complete.
    5. Compute: After synchronization, each CTA has the full set of dequantized K/V values in its local shared memory to perform MMA operations.

    This approach effectively halves the dequantization workload per CTA.

  5. Understand the FP8 KV Cache format

    main

    When is_fp8_kvcache is set to True, the kernel uses an "FP8 with scale" format. The kernel dequantizes the cache to bfloat16 for computation. Each token's KV cache is 656 Bytes, structured as follows:

    • First 512 bytes: "quantized NoPE" part (512 float8_e4m3 values).
    • Next 16 bytes: Scale factors (4 float32 values; each scales 128 float8_e4m3 values).
    • Last 128 bytes: "RoPE" part (64 bfloat16 values, unquantized).
  6. Install FlashMLA

    main

    To install FlashMLA, clone the repository, initialize submodules, and install via pip. This requires a GPU with SM90 or SM100 architecture, CUDA 12.8+ (CUDA 12.9+ for SM100), and PyTorch 2.0+.

    git clone https://github.com/deepseek-ai/FlashMLA.git flash-mla
    cd flash-mla
    git submodule update --init --recursive
    pip install -v .
  7. Use Dense MHA Prefill kernels

    main

    FlashMLA provides standard dense Multi-Head Attention (MHA) forward and backward operations for the prefill stage. These are compatible with the flash_attn interface and can be called via:

    • flash_attn_varlen_func
    • flash_attn_varlen_qkvpacked_func
    • flash_attn_varlen_kvpacked_func
  8. Use Sparse MLA Prefill kernel

    main

    The flash_mla_sparse_fwd function implements sparse MLA prefill.

    Note on Batching: This kernel does not support a batch dimension. To perform multi-batch inference, you must reshape input tensors and adjust the indices parameter to simulate batching.

    Arguments:

    • q: Query tensor [s_q, h_q, d_qk].
    • kv: Key-Value tensor [s_kv, h_kv, d_qk].
    • indices: Indices tensor [s_q, h_kv, topk].
    • sm_scale: Scalar scale value.

    Invalid Indices: Set invalid entries in indices to -1 or any value >= s_kv.

    Return Values: Returns (out, max_logits, lse).

    # Conceptual equivalent in PyTorch:
    # focused_kv = kv[indices]
    # P = (Q @ focused_kv.transpose(-1, -2)) * sm_scale * math.log2(math.e)
    # max_logits = P.max(dim=-1)
    # lse = log2sumexp2(P, dim=-1, base=2)
    # out = exp2(P - lse) @ focused_kv
  9. Use MLA Decoding kernels

    main

    MLA decoding is performed in two steps:

    1. Call get_mla_metadata once before the decoding loop to generate tile scheduler metadata.
    2. Call flash_mla_with_kvcache in each decoding step.

    Parameters for get_mla_metadata:

    • cache_seqlens: Sequence lengths in the cache.
    • s_q: Number of query tokens per sequence (use 1 if speculative decoding is disabled).
    • h_kv: Number of KV heads.
    • h_q: Number of query heads.
    • is_fp8: Boolean indicating if FP8 is used.
    • topk: Sparsity parameter.

    Parameters for flash_mla_with_kvcache:

    • q_i: Query tensor.
    • kvcache_i: KV cache tensor.
    • block_table: Block table for KV cache.
    • cache_seqlens: Sequence lengths.
    • dv: Value dimension.
    • tile_scheduler_metadata: Metadata from get_mla_metadata.
    • num_splits: Number of splits.
    • is_causal: Boolean for causal masking.
    • is_fp8_kvcache: Boolean for FP8 KV cache usage.
    • indices: (Optional) 3D tensor of shape (batch_size, seq_len_q, topk) for sparse attention. If provided, block_table is ignored. Invalid entries should be set to -1.

    Return Values: Returns a tuple (out, lse) where out is the attention result and lse is the log-sum-exp value.

    from flash_mla import get_mla_metadata, flash_mla_with_kvcache
    
    tile_scheduler_metadata, num_splits = get_mla_metadata(
        cache_seqlens,
        s_q * h_q // h_kv,
        h_kv,
        h_q,
        is_fp8,
        topk,
    )
    
    for i in range(num_layers):
        ...
        o_i, lse_i = flash_mla_with_kvcache(
            q_i, kvcache_i, block_table, cache_seqlens, dv,
            tile_scheduler_metadata, num_splits,
            is_causal, is_fp8_kvcache, indices,
        )
        ...
  10. FlashMLA Kernel Support Matrix

    main

    The following table defines the supported GPU architectures and modes for FlashMLA kernels:

    KernelGPU ArchitectureMLA ModeKVCache Format
    Dense DecodingSM90MQABF16
    Sparse DecodingSM90 & SM100MQAFP8
    Dense PrefillSM100MHA
    Sparse PrefillSM90 & SM100MQA

    Note: "MLA Mode" refers to the calculation mode. MQA (Multi-Query Attention) uses head_dim_k = 576 and head_dim_v = 512. MHA (Multi-Head Attention) uses head_dim_k = 192/128 and head_dim_v = 128.

  11. Locate Head128 decoding kernels

    main
    For SM100 architectures, Head128 decoding kernels are implemented in specific CUDA instantiation files. If you are using a k_dim of 512, the kernel is located at csrc/sm100/prefill/sparse/fwd_for_small_topk/head128/instantiations/phase1_decode_k512.cu. For other configurations, Head128 behavior may be simulated using two Head64 kernels.
    csrc/sm100/prefill/sparse/fwd_for_small_topk/head128/instantiations/phase1_decode_k512.cu
  12. Performance characteristics of the FP8 sparse decoding kernel

    main

    The FP8 sparse decoding kernel is optimized for Hopper GPUs (e.g., H800) and shows significant performance gains when using the crossover technique.

    Benchmarks (H800 SXM5):

    • Compute-bound configuration: Achieves 410 TFLOPS (with batch_size=128, num_heads=128, s_q=2, topk=2048).
    • Comparison: This is an improvement over the previous FP8 sparse decoding kernel which achieved 250 TFLOPS.
    • Scaling with Top-K: As topk increases, performance improves. For topk=32768, the kernel can achieve up to 460 TFLOPS.

    Usage Note:

    • The kernel's execution time is comparable to the dense decoding kernel at a sequence length of approximately 3000 tokens.
    • For sequence lengths exceeding 3000, the sparse kernel provides a significant performance advantage due to the effectiveness of the DeepSeek Sparse Attention algorithm.