Designing the Verify-Attention Kernel: A Deep Dive into CUDA MLA Attention for DDTree Speculative Decoding

Introduction

In the high-stakes world of large language model inference, every microsecond counts. When deploying a 1-trillion-parameter model like Kimi K2.6 on eight NVIDIA RTX PRO 6000 Blackwell GPUs, the difference between a working system and a performant one often comes down to the custom CUDA kernels that bridge the gap between what existing frameworks provide and what the architecture demands. This article examines a single message from an opencode coding session—message index 11876—in which an AI assistant designs and writes a custom CUDA kernel for tree-verify Multi-head Latent Attention (MLA), the second of three custom kernels powering a native speculative decoding engine.

The message captures a moment of intense engineering: the agent has just committed a validated GPU tree-builder kernel and is pivoting to the attention mechanism that makes speculative decoding with draft trees work. The reasoning trace reveals not just what the kernel does, but why each design choice was made, the constraints that shaped those choices, and the production considerations that lurk behind a "correctness-first" implementation.

The Context: Why This Kernel Exists

To understand this message, one must first understand the problem it solves. The project is deploying Kimi K2.6, a massive language model, using a technique called speculative decoding with Draft-Draft Tree (DDTree). In speculative decoding, a small "drafter" model proposes multiple candidate tokens arranged in a tree structure, and the large "target" model verifies them in parallel. The DDTree variant uses a best-first search to build this tree dynamically, selecting the most promising continuations at each step.

The standard approach—used by SGLang, the project's inference framework—builds the draft tree on the CPU using a Python heapq and then verifies it using the existing FlashMLA attention kernel. But this has a critical limitation: FlashMLA computes full bidirectional attention over the entire sequence, while DDTree verification requires a masked attention pattern where each draft token can only attend to a subset of other tokens based on the tree's visibility structure. The existing kernel cannot express this constraint.

The agent's solution, documented across the session, is a three-kernel pipeline:

  1. Tree builder (already committed): A GPU kernel that replaces SGLang's CPU heapq with a parallel best-first expansion in shared memory.
  2. Verify attention (the subject of this message): A custom MLA attention kernel that applies the tree's visibility mask during the softmax computation.
  3. Tree accept (yet to come): A kernel that greedily selects the longest valid prefix from the verified tree. The verify-attention kernel is the computational heart of the pipeline. Without it, the entire speculative decoding scheme collapses into either incorrect results (if masking is ignored) or CPU-bound serialization (if masking is done on the host).

The Architectural Challenge: MLA Attention with Tree Visibility

Multi-head Latent Attention (MLA) is the attention mechanism used by DeepSeek V3 and Kimi K2.6. Unlike standard multi-head attention, MLA compresses the key and value states into a low-dimensional latent space that is shared across all attention heads. This dramatically reduces the KV cache memory footprint—from gigabytes to mere hundreds of megabytes for long contexts—but it complicates the attention computation.

The MLA attention score for a single query-head pair is:

score(q, k) = dot(q_absorbed, k_latent) + dot(q_rope, k_rope)

Where q_absorbed is the query projected into the latent space, k_latent is the cached compressed key, and the rope terms are positional embeddings. The output is a weighted sum of the cached value latents, which are later projected through per-head value matrices.

The DDTree twist is that not every query position can attend to every key position. The tree structure imposes a visibility constraint: a draft token can only attend to tokens that are its ancestors in the tree (plus the full prefix of already-verified tokens). This constraint is encoded in a visibility mask—a binary matrix where mask[i][j] = 1 if query i can attend to key j, and 0 otherwise.

The verify-attention kernel must compute:

output[i] = softmax(score[i, :] + mask_penalty[i, :]) · value_latents[:]

Where mask_penalty is 0 for allowed positions and -inf for forbidden positions (so they contribute zero after softmax).

This is straightforward in principle but challenging in practice: the mask is different for every request (because every tree is different), the sequence lengths are variable, and the computation must be fast enough to keep pace with the eight GPUs doing the target model forward pass.

The Design Decisions in Message 11876

The agent's reasoning in message 11876 reveals a careful sequence of design choices, each grounded in the physical constraints of the GPU architecture.

Block Structure: One Block Per (Batch, Head, Query)

The agent chooses a block-per-(b, h, query_i) structure, meaning each CUDA block handles one query position for one attention head in one batch element. This is the standard decomposition for attention kernels and provides natural parallelism: with 128 threads per block and 512 output dimensions, each thread handles 4 output elements. The block count scales with batch_size × num_heads × seq_len, which for the test configurations is modest.

Three Computational Phases

The kernel is organized into three sequential phases within each block:

Phase 1 — Score Computation: Each thread iterates over a subset of key positions (strided across the thread block) and computes the attention score as the dot product of the query's absorbed and rope components with the corresponding key components. The mask penalty is applied: forbidden positions get -inf, effectively removing them from the softmax.

Phase 2 — Normalization: A block-wide reduction finds the maximum score (for numerical stability), then each thread computes the exponentiated and shifted scores. Another reduction sums them to get the softmax denominator. The normalized probabilities are stored in shared memory.

Phase 3 — Weighted Sum: The output is computed by distributing the 512 latent dimensions across threads. Each thread handles a few dimensions and loops over all key positions, accumulating the weighted sum of value latents using the normalized probabilities from shared memory.

This three-phase design avoids atomic operations entirely—a significant performance win. The key insight is to separate the score computation (parallelized over key positions) from the output accumulation (parallelized over output dimensions). A naive approach would have each thread own a key position and atomically add its contribution to the output, but that would require 512 atomic adds per key position. By transposing the parallelism, the agent eliminates atomics at the cost of recomputing nothing.

Shared Memory Budget

The agent carefully calculates the shared memory footprint:

The Production Escape Hatch

One of the most revealing passages in the reasoning is the agent's forward-looking note about production deployment:

"For the production system, I can split attention into two parts: reuse the existing flash MLA decode kernel for the long prefix, then apply our custom masked kernel just to the tree tail, and merge the results using log-sum-exp."

This is a crucial architectural insight. The tree tail (the draft tokens being verified) is typically small—at most 65 tokens with a budget of 64. The long prefix (thousands of already-verified tokens) doesn't need custom masking because every draft token can attend to all prefix tokens. By reusing the highly optimized FlashMLA kernel for the prefix and only running the custom kernel on the tree tail, the agent avoids the performance penalty of a general-purpose masked attention kernel on the full sequence. The two results are merged using log-sum-exp, which is mathematically equivalent to computing attention over the concatenated sequence.

This design pattern—building a simple correct implementation first, then documenting the production optimization path—is a hallmark of disciplined engineering. The agent is not trying to solve every problem at once; it's establishing correctness and leaving a clear upgrade path.

The Thinking Process: What the Reasoning Reveals

The agent's reasoning in message 11876 is unusually transparent about the design deliberation. Several features stand out:

Iterative Refinement of the Parallelism Strategy

The agent initially considers a design where each thread owns a key position and atomically accumulates its contribution to the output. It quickly rejects this:

"If thread owns j, it loops 512 dims doing atomicAdd → 512 atomics per j. With kv_len/128 j's per thread that's a lot of atomics."

This is a concrete quantitative rejection. The agent estimates the atomic overhead and finds it unacceptable. It then pivots to the transposed approach, which eliminates atomics entirely. This kind of back-of-the-envelope estimation—"512 atomics per key position times ~2000 key positions divided by 128 threads equals ~8000 atomics per thread"—is exactly the reasoning that separates working kernels from fast kernels.

Explicit Constraint Awareness

The agent repeatedly checks against hardware constraints:

Edge Case Handling

The agent considers the pathological case where a query position might have zero valid attention weight:

"I'm also handling the edge case where a query position might have zero valid attention weight by guarding against division by zero, though in practice the visibility mask ensures every query attends at least its own position, so the denominator should always be positive."

This is a defensive programming instinct. Even though the mask guarantees at least one attended position (the query's own token), the agent guards against the division-by-zero case. This is the kind of robustness that prevents mysterious NaN propagation in production.

Assumptions and Their Implications

The message rests on several assumptions, some explicit and some implicit:

The visibility mask is well-formed: The agent assumes that every query position can attend to at least its own position. This is guaranteed by the tree construction algorithm, but if a bug in the tree builder produces a mask with an isolated node, the kernel would produce NaN outputs. The division-by-zero guard prevents the crash but doesn't fix the root cause.

Shared memory is sufficient for test configs: The agent assumes a maximum key length of ~2000 for the test configurations. This is true for the test harness but would fail for production contexts of 200k tokens. The agent explicitly notes this limitation and documents the production path.

The value projection is separate: The kernel outputs the latent representation directly, and a separate GEMM applies the per-head value weights. This matches SGLang's decomposition and keeps the kernel focused on the attention computation proper. It assumes that the matrix multiplication is efficiently handled by cuBLAS, which is a reasonable assumption.

FP32 precision is sufficient for validation: The kernel computes in FP32, matching the numpy reference. This is fine for correctness validation but would need to be adapted for FP16/BF16 inference in production.

The Broader Significance

Message 11876 is a microcosm of the engineering philosophy driving this entire project. The agent is building a custom inference engine from scratch, not because existing frameworks are broken, but because the specific combination of MLA attention, DDTree speculative decoding, and Blackwell GPUs creates a niche that no off-the-shelf solution fills.

The verify-attention kernel is the bridge between the tree structure (built by the first kernel) and the target model verification (which consumes the attention output). Without it, the DDTree pipeline would require either:

Conclusion

Message 11876 captures a moment of focused engineering: the design of a custom CUDA kernel for tree-verify MLA attention. The reasoning trace reveals a designer working through hardware constraints, algorithmic choices, and production considerations with unusual clarity. The resulting kernel—a three-phase, block-per-query-head implementation with shared-memory score storage and atomic-free output accumulation—is a textbook example of how to approach custom GPU kernel development for large language model inference.

The verify-attention kernel, together with the tree builder and the tree accept kernel, forms the computational backbone of a native DDTree speculative decoding engine. It is a testament to the power of understanding both the algorithm and the hardware, and to the value of building correct-first implementations with clear paths to production optimization.