<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://flashinfer.ai/feed.xml" rel="self" type="application/atom+xml" /><link href="https://flashinfer.ai/" rel="alternate" type="text/html" /><updated>2026-01-03T07:54:20+00:00</updated><id>https://flashinfer.ai/feed.xml</id><title type="html">FlashInfer</title><subtitle>Introduce Techniques to accelerate Large Language Model Deployment</subtitle><entry><title type="html">FlashInfer-Bench: Building the Virtuous Cycle for AI-driven LLM Systems</title><link href="https://flashinfer.ai/2025/10/21/flashinfer-bench.html" rel="alternate" type="text/html" title="FlashInfer-Bench: Building the Virtuous Cycle for AI-driven LLM Systems" /><published>2025-10-21T00:00:00+00:00</published><updated>2025-10-21T00:00:00+00:00</updated><id>https://flashinfer.ai/2025/10/21/flashinfer-bench</id><content type="html" xml:base="https://flashinfer.ai/2025/10/21/flashinfer-bench.html"><![CDATA[<table>
  <tbody>
    <tr>
      <td><a href="https://bench.flashinfer.ai"><strong>Leaderboard</strong></a>  </td>
      <td>  <a href="https://bench.flashinfer.ai/docs/flashinfer-trace"><strong>FlashInfer Trace</strong></a>  </td>
      <td>  <a href="https://github.com/flashinfer-ai/flashinfer-bench"><strong>GitHub</strong></a></td>
    </tr>
  </tbody>
</table>

<blockquote>
  <p>Have you ever imagined an AI system that improves itself?</p>

  <p>—Not exactly the machine overlords from sci-fi, but still, an AI system that can expand its capabilities using AI itself sounds pretty cool.</p>

  <p>—And it is something we’re building: <strong>FlashInfer-Bench</strong>—a benchmark and infrastructure that <strong>opens the pathway for AI to accelerate real-world AI deployment</strong>.</p>
</blockquote>

<p>AI agents have become remarkably powerful, capable of writing complex code and even building sophisticated systems. Such powerful capabilities naturally lead us to wonder: can AI agents optimize the very production systems they run on? At the heart of these AI systems, the most intensive parts are GPU kernels—the low-level programs that perform the core operations for AI models. We have seen amazing progress recently that shows LLMs can generate reasonable GPU kernels (https://scalingintelligence.stanford.edu/blogs/kernelbench/).</p>

<p>That prompts us to ask the next natural question: how can we systematically get AI agents to improve the very AI system they depend on? We know there can still be hurdles towards this ultimate dream, but it is time to get ready to build a clear pathway for the future. We build FlashInfer-Bench, a benchmark of real-world AI-system driven GPU workloads, and more importantly, an infrastructure and workflow to 0-day ship AI-generated kernels into production. While the AI today may not yet perform at the expert engineer level, we would like to get our infrastructure ready and open the pathway to accelerate this ultimate goal. The same workflow would also benefit machine learning system engineering in general, helping us to build and evolve next-generation kernel libraries.</p>

<figure>
  <img src="/assets/imgs/flashinfer-bench/image12.png" alt="FlashInfer-Bench cover illustration" />
</figure>

<h2 id="overview-systematically-approaching-ai-for-ai-systems">Overview: Systematically Approaching AI for AI Systems</h2>

<p>As we approach the goal for “AI for AI systems”, we naturally find that we need more than just benchmarks. Real-world production environments are highly complex: they involve a large number of sophisticated kernels with different API designs and input signatures, while each kernel’s performance can also vary depending on the input it receives. We need to clearly communicate these needs to AI agents and relevant engineers; more importantly, we also need to be able to quickly adopt the solutions from agents back onto production. Fundamentally, we need to consider the following three elements to systematically approach our problem:</p>

<ol>
  <li><strong>Clarify the landscape.</strong> We need a standardized way to describe the workloads, including their inputs and outputs, along with relevant statistics and characteristics (e.g., shape, raggedness, etc.)</li>
  <li><strong>Set the right goal.</strong> We need benchmarks that capture real-world, production-grade workloads—in other words, the scenarios that occur when LLMs are deployed in practice.</li>
  <li><strong>Establish a day-0 production path.</strong> AI-generated and human-written code can be seamlessly deployed into a real LLM engine right away with as little intervention as possible.</li>
</ol>

<figure>
  <img src="/assets/imgs/flashinfer-bench/image9.png" alt="Overview diagram of the virtuous cycle" />
</figure>

<p>FlashInfer-Bench is designed specifically to address these three challenges. As illustrated in the architecture diagram above, its core components work as follows:</p>

<ul>
  <li>
    <p><strong>Clarify the landscape — FlashInfer Trace.</strong> To formalize the description of GPU workloads, we developed FlashInfer Trace: a systematic and evolving open schema that captures the kernel definitions, implementation, and evaluation in LLM inference scenarios. This format addresses the critical needs through four key components: kernel definition that defines the kernel signature and computation; workload that describes the kernel’s real inputs; solution to the kernel; and the solution’s evaluation results.</p>
  </li>
  <li>
    <p><strong>Set the right goal — FlashInfer-Bench Dataset.</strong> We collected and curated the most important kernels used in production, along with their real workloads, to create a dataset. This allows us to measure kernel performance under real traffic conditions and further makes it possible to concretely improve production systems.</p>
  </li>
  <li>
    <p><strong>Establish a 0-day production path — First-class FlashInfer Integration.</strong> We build first-class integration with FlashInfer — an open LLM kernel library widely used in major LLM inference engines. Our solution can dynamically replace the FlashInfer Kernels with the best-performing kernels, as evaluated using the FlashInfer Trace and the FlashInfer-Bench dataset. This enables activating the best kernels in LLM engines and testing end-to-end performance with minimal effort.</p>
  </li>
</ul>

<p>In the rest of the post, we will walk through the details about the three main elements.</p>

<h2 id="flashinfer-trace-standardizing-and-evolving-schema-for-llm-kernels">FlashInfer Trace: Standardizing and Evolving Schema for LLM Kernels</h2>
<figure>
  <img src="/assets/imgs/flashinfer-bench/image6.png" alt="Comic for FlashInfer Trace" />
</figure>

<p>As discussed in the overview section, we need a standard way for humans, infrastructure workflows, and AI agents to communicate about the context throughout the overall production life cycle. We specifically would need to support different stages in the production lifecycle, including handling the problem to the agent/engineers, recording the answer, evaluating, and inspecting the outputs.</p>

<p>We define <strong>FlashInfer Trace</strong>, a set of JSON schemas to formally describe a kernel’s definition, benchmark workload, solution, and evaluation results. Below is the figure showing FlashInfer Trace’s components:</p>

<figure>
  <img src="/assets/imgs/flashinfer-bench/image13.png" alt="FlashInfer Trace components" />
</figure>

<p><strong>Definition</strong> provides the complete specification of the kernel, including the kernel metadata, input and output specifications, axes of input and output tensors, and a reference implementation written in Python, defining the kernel’s computational behavior. The kernel definition provides all the information needed for AI to generate kernels. This is an example of a General Matmul (GEMM) kernel definition:</p>

<figure>
  <img src="/assets/imgs/flashinfer-bench/image11.png" alt="GEMM kernel definition" />
</figure>

<p>In the kernel definition, we aim to capture as many details of a kernel as possible, especially the constant axes value. This is because kernels with different input shapes often require different optimal implementations. For example, a GEMM with a dimension of 4096 may have a very different implementation from one with a dimension of 128. Some axes in AI systems are dynamic, such as the sequence length. These dimensions will be kept variable so that the workload can provide its concrete values.</p>

<p>We also design the <strong>op_type</strong> that specifies input and output and computation at a high level, such as GEMM (as <code class="language-plaintext highlighter-rouge">gemm</code>) and Paged Grouped-Query Attention (as <code class="language-plaintext highlighter-rouge">gqa_paged</code>). By defining op_type, we can group together multiple kernel definitions that have different axes but share similar computation. We define op_types for common LLM operators, and we aim to evolve this standard with the community.</p>

<p><strong>Workload</strong> captures the kernel’s inputs observed in real traffic, including both tensor inputs and scalar or flag inputs. If the specific values of a tensor do not affect evaluation, it can be stored as random to save space; otherwise, the original tensor can be dumped to reproduce the most realistic production scenario.</p>

<figure>
  <img src="/assets/imgs/flashinfer-bench/image3.png" alt="GEMM workload" />
</figure>

<p><strong>Solution</strong> gives the concrete implementation of a kernel. It must perform the same computation as the reference in the kernel definition. We support multiple kernel programming languages, including CUDA, Triton, Python, and TVM (coming soon!), among others. We can also specify the kernel’s author, target hardware, compatible software versions, and other related information.</p>

<figure>
  <img src="/assets/imgs/flashinfer-bench/image10.png" alt="GEMM kernel solution" />
</figure>

<p><strong>Evaluation</strong> is the benchmark result for a specified solution on a certain definition and workload. The hardware and software used for evaluation, as well as the kernel’s correctness and runtime performance, are fully measured. When a kernel fails to compile or has a runtime error, we assign different evaluation statuses accordingly.</p>

<figure>
  <img src="/assets/imgs/flashinfer-bench/image1.png" alt="GEMM kernel evaluation" />
</figure>

<p>With the standardized design of the FlashInfer Trace, we can easily exchange information throughout the production lifecycle:</p>

<ul>
  <li>Workload tracer can take traffic from an LLM inference engine and produce workload traces;</li>
  <li>AI agents can then take the definition to further generate a kernel solution;</li>
  <li>Benchmark tools can take the workload and solution and fill in the evaluation field;</li>
  <li>The leaderboard will take a collection of traces and visualize the result.</li>
</ul>

<p>We built the <code class="language-plaintext highlighter-rouge">flashinfer-bench</code> Python package following this philosophy, which contains support to work with FlashInfer trace and tooling across various stages, including schema validation, benchmarking, workload tracing, and integration with LLM engine and FlashInfer. The design of the FlashInfer Trace is independent of specific tools, and we encourage the community to develop a more versatile toolchain.</p>

<p>Real CUDA engineers can also benefit from the FlashInfer Trace. It standardizes kernel interfaces and computations, reducing communication overhead and enabling fair comparison across kernels from different sources. It provides testing tools to measure the performance of kernels and tracing tools to easily capture real workloads. Altogether, these greatly simplify the burden of kernel development.</p>

<p>We provide standardized and clear documents for <a href="https://bench.flashinfer.ai/docs/flashinfer_trace/flashinfer_trace">FlashInfer Trace</a> and <a href="https://bench.flashinfer.ai/docs/op_type_schema/gemm">Op Types</a>.</p>

<h2 id="dataset--benchmark--the-yardstick-from-the-real-world">Dataset &amp; Benchmark — The Yardstick from the Real World</h2>

<figure>
  <img src="/assets/imgs/flashinfer-bench/image5.png" alt="Dataset comics" />
</figure>

<p>We curate the dataset by one principle: <strong>real-world relevance.</strong> This means focusing on the most important kernels in the LLM engine and recording their workloads under realistic production traffic. We selected the most popular models, including Llama 3, DeepSeek V3, and Qwen 3, and recorded their main kernels, including attention, GEMM, MoE, normalization, sampling, and more. We also strive to ensure realism in the LLM engine configuration. For example, for DeepSeek-V3, we use the original FP8 quantization and enable tensor parallelism = 8 and expert parallelism = 8. We feed inputs from real datasets, including ShareGPT and more. These ensure that the workloads we measure are as close to real-world scenarios as possible.</p>

<figure>
  <img src="/assets/imgs/flashinfer-bench/image2.png" alt="Dataset workflow" />
</figure>

<h2 id="day-zero-integration-through-flashinfer">Day-Zero Integration Through FlashInfer</h2>

<figure>
  <img src="/assets/imgs/flashinfer-bench/image4.png" alt="FIB apply comics" />
</figure>

<p>The final step of the virtuous cycle is getting AI-generated kernels into production immediately. We achieve this by first-class integration with FlashInfer, a well-adopted kernel library by major LLM engines, and SGLang. Integration with vLLM is also progressing rapidly and is expected to be completed soon.</p>

<p>We can dynamically replace the kernels in the FlashInfer API with the best-performing ones from our evaluations, all with minimal effort. By simply importing flashinfer_bench in the LLM engine and enabling the environment variable <strong>FIB_ENABLE_APPLY</strong>, the kernels can be automatically replaced with the best ones from the local database.</p>

<figure>
  <img src="/assets/imgs/flashinfer-bench/image8.png" alt="FIB apply workflow" />
</figure>

<p>Under the hood, the dynamic substitution is supported by the <code class="language-plaintext highlighter-rouge">flashinfer_bench.apply()</code> decorator:</p>

<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">@</span><span class="nb">apply</span><span class="p">(</span><span class="k">lambda</span> <span class="n">A</span><span class="p">,</span> <span class="n">B</span><span class="p">:</span> <span class="sa">f</span><span class="s">"gemm_n_</span><span class="si">{</span><span class="n">B</span><span class="p">.</span><span class="n">shape</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="si">}</span><span class="s">_k_</span><span class="si">{</span><span class="n">B</span><span class="p">.</span><span class="n">shape</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span><span class="si">}</span><span class="s">"</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">gemm_bf16</span><span class="p">(</span>
    <span class="n">A</span><span class="p">:</span> <span class="n">torch</span><span class="p">.</span><span class="n">Tensor</span><span class="p">,</span>
    <span class="n">B</span><span class="p">:</span> <span class="n">torch</span><span class="p">.</span><span class="n">Tensor</span><span class="p">,</span>
<span class="p">)</span> <span class="o">-&gt;</span> <span class="n">torch</span><span class="p">.</span><span class="n">Tensor</span><span class="p">:</span>
    <span class="c1"># Fallback Implementation
</span>    <span class="k">return</span> <span class="n">torch</span><span class="p">.</span><span class="n">matmul</span><span class="p">(</span><span class="n">A</span><span class="p">,</span> <span class="n">B</span><span class="p">)</span>
</code></pre></div></div>

<p>It takes a lambda function that maps the inputs to a specific kernel definition name, then replaces the original computation function. The replaced function filters the best-performing kernel solution from the database based on the inputs, executes it, and returns its result. If no matching kernel solution is found, the original function runs as a fallback.</p>

<p>By using <code class="language-plaintext highlighter-rouge">apply()</code>, we can replace a large number of operators in FlashInfer. If you want to replace operators from your own implementations or other libraries, you can also easily achieve this with <code class="language-plaintext highlighter-rouge">apply()</code>.</p>

<h2 id="flashinfer-bench-leaderboard--the-arena-for-llm-kernel-optimization">FlashInfer-Bench Leaderboard — The Arena for LLM Kernel Optimization</h2>

<figure>
  <img src="/assets/imgs/flashinfer-bench/image15.png" alt="FlashInfer-Bench leaderboard screenshot" />
</figure>

<p>FlashInfer-Bench provides a standardized benchmark for AI agents to improve AI systems. Therefore, we also evaluated the capabilities of different models and built the <strong>FlashInfer-Bench Leaderboard</strong>. It directly leverages the FlashInfer Trace generated from the benchmark suite and provides a visualized display of the results.</p>

<p>We adopt the \(\text{fast}_p\) metric initially proposed by KernelBench to compare the performance of different kernels. We use the FlashInfer kernel as the baseline for comparison. \(\text{fast}_p\) represents the proportion of workloads on which a given kernel runs p times faster than FlashInfer. If p &gt; 1, it means we have discovered a kernel that outperforms the state-of-the-art kernel library! In practice, most kernels have p &lt; 1, indicating that current models still have significant room for improvement. For different values of p, \(\text{fast}_p\) forms a curve. The larger the area under the curve, the better the kernel’s performance.</p>

<p><em>We also found a small number of AI-generated kernels with a speedup greater than 1. We deeply understand the importance of ensuring the correctness of kernel implementations, so we are manually verifying the correctness of each kernel. We will soon release kernels with a speedup ratio greater than 1 after review.</em></p>

<p>We also provide a separate leaderboard for each kernel to make it easier to select the best implementation for each one. The leaderboard also allows further filtering by workload to select the best-performing kernels for specific workloads.</p>

<figure>
  <img src="/assets/imgs/flashinfer-bench/image14.png" alt="FlashInfer-Bench kernel page" />
</figure>

<p>Furthermore, the model architecture overview shows all the kernels used by the model and indicates which ones are already covered by FlashInfer-Bench:</p>

<figure>
  <img src="/assets/imgs/flashinfer-bench/image7.png" alt="FlashInfer-Bench model architecture overview" />
</figure>

<h2 id="summary">Summary</h2>

<p>FlashInfer-Bench provides a systematic solution for building AI that improves AI systems. With it, we have already built a self-optimizing loop for AI—a virtuous cycle. The revolution of AI improving itself will continue—let’s see where it goes!</p>

<p>Looking ahead, we plan to continually expand our coverage of models and kernels. We will collaborate closely with GPU engineers and LLM engine developers to adapt to the fast-evolving needs of the field. This journey is a community effort, and we welcome your discussions and contributions.</p>

<p>For more information, please visit the following links:</p>

<ul>
  <li><a href="https://bench.flashinfer.ai/">Leaderboard</a></li>
  <li><a href="https://huggingface.co/datasets/flashinfer-ai/flashinfer-trace">FlashInfer-Bench Dataset</a></li>
  <li><a href="https://github.com/flashinfer-ai/flashinfer-bench/">FlashInfer-Bench Python Package</a></li>
  <li><a href="https://bench.flashinfer.ai/docs">Documentation</a></li>
</ul>

<h2 id="acknowledgements">Acknowledgements</h2>

<p>FlashInfer-Bench is a research effort initiated in collaboration with CMU Catalyst, NVIDIA, and Bosch. We are bringing up an open community as part of the FlashInfer community and welcome contributions from the ML systems community.</p>

<p>We thank the entire FlashInfer-Bench team for their contributions to the project:</p>

<ul>
  <li>Shanli Xing* (UW, CMU): Core components and web development</li>
  <li>Yiyan Zhai* (CMU): FlashInfer-Trace dataset, workload tracing system</li>
  <li>Alexander Jiang* (CMU): Benchmark system, agent design</li>
  <li>Yixin Dong* (CMU): Core idea, overall architecture design</li>
  <li>Yong Wu (NVIDIA): RMSNorm, sampling, fused MOE</li>
  <li>Zihao Ye (NVIDIA): FlashInfer support</li>
  <li>Charlie Ruan (UC Berkeley): Workload tracing system</li>
  <li>Yingyi Huang (CMU): Fused MOE</li>
  <li>Yineng Zhang (Independent researcher): SGLang integration</li>
  <li>Liangsheng Yin (Independent researcher): SGLang integration</li>
  <li>Aksara Bayyapu (CMU): LLM agents</li>
  <li>Luis Ceze (UW, NVIDIA): Project guidance and advice</li>
  <li>Tianqi Chen (CMU, NVIDIA): Project guidance and advice</li>
</ul>

<p>We would also like to thank Mark Saroufim, Zhuoming Chen, Weihua Du, Bohan Hou, Hongyi Jin, Ruihang Lai, Xinyu Yang, Yilong Zhao, Haizhong Zheng, the FlashInfer community, the GPUMODE community, the HuggingFace community, the SGLang community, the TensorRT-LLM community, the vLLM community, Databricks, and xAI for their insightful feedback.</p>]]></content><author><name>FlashInfer Community</name></author><summary type="html"><![CDATA[Leaderboard     FlashInfer Trace     GitHub]]></summary></entry><entry><title type="html">Sorting-Free GPU Kernels for LLM Sampling</title><link href="https://flashinfer.ai/2025/03/10/sampling.html" rel="alternate" type="text/html" title="Sorting-Free GPU Kernels for LLM Sampling" /><published>2025-03-10T00:00:00+00:00</published><updated>2025-03-10T00:00:00+00:00</updated><id>https://flashinfer.ai/2025/03/10/sampling</id><content type="html" xml:base="https://flashinfer.ai/2025/03/10/sampling.html"><![CDATA[<h2 id="background">Background</h2>

<p>As vocabulary sizes grow larger in Large Language Models (LLMs), categorical sampling (token selection) has emerged as a significant performance bottleneck in LLM inference serving. The <a href="https://docs.flashinfer.ai/api/sampling.html">sampling operators</a> in FlashInfer were first introduced in <a href="https://github.com/flashinfer-ai/flashinfer/releases/tag/v0.0.5">v0.0.5</a>, and since then, the FlashInfer team has continuously improved their robustness and performance. In this blog post, we’ll explore the algorithms and implementation details behind FlashInfer’s sampling operators.</p>

<h2 id="llm-sampling">LLM Sampling</h2>

<p>Categorical Sampling is the process that picks a specific next token from model output probabilities (over the vocabulary).
In practice, filtering is applied before sampling to pass tokens with negligible probability, control generation behaviors, and enforce minimum probabilities, such as Top-P, Top-K, or Min-P thresholds:</p>

<p align="center">
<img src="/assets/imgs/sampling_blog/Sampling_Portion.png" alt="The compute time break down highlighting the sampling process. In the vLLM 1xH100 configuration, our kernels reduce the overall sampling time by more than 50% across all three models." width="800" />
<br />
<span style="color: gray; font-style: italic;">Figure 1: The compute time break down highlighting the sampling process. In the vLLM 1xH100 configuration, our kernels reduce the overall sampling time by more than 50% across all three models.</span>
</p>

<ol>
  <li>
    <p><strong>Top-K</strong></p>

    <p>Top-K sampling keeps only the $K$ tokens with the highest probabilities at each generation step. For example, if $K=50$, the model will ignore all tokens outside the top 50 likely candidates.</p>
  </li>
  <li>
    <p><a href="https://arxiv.org/pdf/1904.09751"><strong>Top-P (Nucleus Sampling)</strong></a></p>

    <p>Top-P rather keeps the smallest set of tokens whose cumulative probability just exceeds a threshold $P$. For example, if $P=0.9$, you accumulate token probabilities in descending order until their sum is at least 0.9.</p>
  </li>
  <li>
    <p><a href="https://arxiv.org/pdf/2407.01082"><strong>Min-P</strong></a></p>

    <p>Min-p filters out all tokens below a minimum threashold $p_\text{base} \times p_\text{max}$, where $p_\text{base}$
  is parameter and $p_\text{max}$ is the largest probability in the inputs. This helps eliminate extremely unlikely tokens while still respecting relative differences among the top candidates.</p>
  </li>
</ol>

<p>In practice, the combination of Top-K and Top-P filtering is popular and used as the standard setting for LLM sampling. This allows for finer-grained control over the generation process. For example if we use the Top-K first filtering, we first limit the token set to the Top-K highest probabilities, and then apply a Top-P cutoff to filter the tail portion within those $K$ tokens. <sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">1</a></sup></p>

<p>A PyTorch implementation of these samplers might look like this:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># vllm/vllm/model_executor/layers/sampler.py
</span><span class="k">def</span> <span class="nf">_apply_top_k_top_p</span><span class="p">(</span>
    <span class="n">logits</span><span class="p">:</span> <span class="n">torch</span><span class="p">.</span><span class="n">Tensor</span><span class="p">,</span>
    <span class="n">p</span><span class="p">:</span> <span class="n">torch</span><span class="p">.</span><span class="n">Tensor</span><span class="p">,</span>
    <span class="n">k</span><span class="p">:</span> <span class="n">torch</span><span class="p">.</span><span class="n">Tensor</span><span class="p">,</span>
<span class="p">)</span> <span class="o">-&gt;</span> <span class="n">torch</span><span class="p">.</span><span class="n">Tensor</span><span class="p">:</span>
    <span class="n">logits_sort</span><span class="p">,</span> <span class="n">logits_idx</span> <span class="o">=</span> <span class="n">logits</span><span class="p">.</span><span class="n">sort</span><span class="p">(</span><span class="n">dim</span><span class="o">=-</span><span class="mi">1</span><span class="p">,</span> <span class="n">descending</span><span class="o">=</span><span class="bp">False</span><span class="p">)</span>

    <span class="c1"># Apply top-k.
</span>    <span class="n">top_k_mask</span> <span class="o">=</span> <span class="n">logits_sort</span><span class="p">.</span><span class="n">size</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span> <span class="o">-</span> <span class="n">k</span><span class="p">.</span><span class="n">to</span><span class="p">(</span><span class="n">torch</span><span class="p">.</span><span class="nb">long</span><span class="p">)</span>
    <span class="c1"># Get all the top_k values.
</span>    <span class="n">top_k_mask</span> <span class="o">=</span> <span class="n">logits_sort</span><span class="p">.</span><span class="n">gather</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="n">top_k_mask</span><span class="p">.</span><span class="n">unsqueeze</span><span class="p">(</span><span class="n">dim</span><span class="o">=</span><span class="mi">1</span><span class="p">))</span>
    <span class="n">top_k_mask</span> <span class="o">=</span> <span class="n">logits_sort</span> <span class="o">&lt;</span> <span class="n">top_k_mask</span>
    <span class="n">logits_sort</span><span class="p">.</span><span class="n">masked_fill_</span><span class="p">(</span><span class="n">top_k_mask</span><span class="p">,</span> <span class="o">-</span><span class="nb">float</span><span class="p">(</span><span class="s">"inf"</span><span class="p">))</span>

    <span class="c1"># Apply top-p.
</span>    <span class="n">probs_sort</span> <span class="o">=</span> <span class="n">logits_sort</span><span class="p">.</span><span class="n">softmax</span><span class="p">(</span><span class="n">dim</span><span class="o">=-</span><span class="mi">1</span><span class="p">)</span>
    <span class="n">probs_sum</span> <span class="o">=</span> <span class="n">probs_sort</span><span class="p">.</span><span class="n">cumsum</span><span class="p">(</span><span class="n">dim</span><span class="o">=-</span><span class="mi">1</span><span class="p">)</span>
    <span class="n">top_p_mask</span> <span class="o">=</span> <span class="n">probs_sum</span> <span class="o">&lt;=</span> <span class="mi">1</span> <span class="o">-</span> <span class="n">p</span><span class="p">.</span><span class="n">unsqueeze</span><span class="p">(</span><span class="n">dim</span><span class="o">=</span><span class="mi">1</span><span class="p">)</span>
    <span class="c1"># at least one
</span>    <span class="n">top_p_mask</span><span class="p">[:,</span> <span class="o">-</span><span class="mi">1</span><span class="p">]</span> <span class="o">=</span> <span class="bp">False</span>
    <span class="n">logits_sort</span><span class="p">.</span><span class="n">masked_fill_</span><span class="p">(</span><span class="n">top_p_mask</span><span class="p">,</span> <span class="o">-</span><span class="nb">float</span><span class="p">(</span><span class="s">"inf"</span><span class="p">))</span>

    <span class="c1"># Re-sort the probabilities.
</span>    <span class="n">src</span> <span class="o">=</span> <span class="n">torch</span><span class="p">.</span><span class="n">arange</span><span class="p">(</span><span class="n">logits_idx</span><span class="p">.</span><span class="n">shape</span><span class="p">[</span><span class="o">-</span><span class="mi">1</span><span class="p">],</span>
                       <span class="n">device</span><span class="o">=</span><span class="n">logits_idx</span><span class="p">.</span><span class="n">device</span><span class="p">).</span><span class="n">expand_as</span><span class="p">(</span><span class="n">logits_idx</span><span class="p">)</span>
    <span class="n">logits_idx_inv</span> <span class="o">=</span> <span class="n">torch</span><span class="p">.</span><span class="n">empty_like</span><span class="p">(</span><span class="n">logits_idx</span><span class="p">).</span><span class="n">scatter_</span><span class="p">(</span><span class="n">dim</span><span class="o">=-</span><span class="mi">1</span><span class="p">,</span>
                                                           <span class="n">index</span><span class="o">=</span><span class="n">logits_idx</span><span class="p">,</span>
                                                           <span class="n">src</span><span class="o">=</span><span class="n">src</span><span class="p">)</span>
    <span class="n">logits</span> <span class="o">=</span> <span class="n">torch</span><span class="p">.</span><span class="n">gather</span><span class="p">(</span><span class="n">logits_sort</span><span class="p">,</span> <span class="n">dim</span><span class="o">=-</span><span class="mi">1</span><span class="p">,</span> <span class="n">index</span><span class="o">=</span><span class="n">logits_idx_inv</span><span class="p">)</span>
    <span class="k">return</span> <span class="n">logits</span>
</code></pre></div></div>

<p>This code uses a combination of sorting, cumulative sums, and masking.
While it is straightforward to follow, it induces performance bottleneck especially for large vocab size, because of the huge overhead of sorting.</p>

<p>In FlashInfer, we show that sampling under filtering can be done in sorting-free manner, and we introduce the <strong>Dual Pivot Rejection Sampling</strong> algorithm and design fused sampling kernel templates to fully leverage GPUs’ parallel computing capabilities, ultimately achieving logarithmic (in worst case) time complexity. In this blog, we’ll walk you through how we developed this algorithm integrating ideas from Inverse Sampling, Rejection Sampling, and final version of the algorithm with theorerical guarantee of convergence.</p>

<h2 id="algorithm">Algorithm</h2>

<h3 id="inverse-transform-sampling">Inverse Transform Sampling</h3>

<p align="center">
<img src="/assets/imgs/sampling_blog/Inverse_Sampling.gif" alt="Inverse Transform Sampling. This animation illustrates the per-block process, and in practice the workload gets executed by blocks." width="800" />
<br />
<span style="color: gray; font-style: italic;">Figure 2: Inverse Transform Sampling. This animation illustrates the per-block process, and in practice the workload gets executed by blocks.</span>
</p>

<p>We begin with implementing a basic sampling kernel that selects tokens purely based on their probabilities, particularly in the GPU parallel computing context.</p>

<p>The method is <strong>inverse transform sampling</strong>, which draws samples from a probability distribution given its cumulative distribution function (CDF). As for the token samling process, the CDF would be the prefix sum of token probabilities. The algorithm proceeds like this:</p>

<ol>
  <li><strong>Draw a random</strong> $u$ from $U\sim \text{Unif}(0,1)$.</li>
  <li><strong>Compute the prefix sums</strong> (CDF) for each sampled token $j$ with probability $p_j$: $F_j=\sum^{j}_{i=1}p_i$.</li>
  <li><strong>Locate the token</strong> $k$ such that $F_{k-1} \leq u &lt; F_k$ as the result.</li>
</ol>

<p>NVIDIA’s <a href="https://docs.nvidia.com/cuda/cub/index.html">CUB</a> library (now part of <a href="https://github.com/NVIDIA/cccl">CCCL</a>) provides efficient primitives for parallel computing, and we leverage the reduce and scan primitives to compute the prefix sums. We use one threadblock for each probability distribution, for batch sampling, we launch multiple threadblocks in parallel. Block-level reduce/scan primitives can be applied to a block of elements (<code class="language-plaintext highlighter-rouge">BLOCK_SIZE = NUM_THREADS * NUM_ELEMENTS_PER_THREADS</code>, e.g. 1024 * 4 for float input), for vocabulary size greater than <code class="language-plaintext highlighter-rouge">BLOCK_SIZE</code>, we split the vocabulary into multiple blocks and sequentially apply the same procedure on each block:</p>

<ol>
  <li>Initialize a running total $\texttt{a}=0$. Compute the probability sum $\texttt{a_local}$ for each block. If $\texttt{a} + \texttt{a_local}&gt; u$, the sampled token lies in this block.</li>
  <li>If not, we add $\texttt{a_local}$ to $\texttt{a}$ and move on to the next block.</li>
  <li>Once we know the correct block, we perform a prefix sum over its tokens to pinpoint the exact token index.</li>
</ol>

<p>We use <a href="https://nvidia.github.io/cccl/cub/api/classcub_1_1BlockReduce.html#_CPPv4I0_i_20BlockReduceAlgorithm_i_iEN3cub11BlockReduceE">BlockReduce</a> and <a href="https://nvidia.github.io/cccl/cub/api/classcub_1_1BlockScan.html#_CPPv4I0_i_18BlockScanAlgorithm_i_iEN3cub9BlockScanE">BlockScan</a> for the per-block partial sum and prefix sums, and <a href="https://nvidia.github.io/cccl/cub/api/classcub_1_1BlockAdjacentDifference.html?highlight=adjacentdifference#_CPPv4I0_i_i_iEN3cub23BlockAdjacentDifferenceE">AdjacentDifference</a> to locate the token index.
In practice, we use early-stopping to terminate the inverse transform sampling process when the cumulative probability exceeds the random number $u$, so we don’t need to go through the whole vocabulary for each round.</p>

<h3 id="rejection-sampling">Rejection Sampling</h3>

<p align="center">
<img src="/assets/imgs/sampling_blog/Rejection_Sampling.gif" alt="Top-P Rejection Sampling. This animation illustrates the per-block process, and in practice the workload gets executed by blocks." width="800" />
<br />
<span style="color: gray; font-style: italic;">Figure 3: Top-P Rejection Sampling. This animation illustrates the per-block process, and in practice the workload gets executed by blocks. </span>
</p>

<p>For more advanced strategies such as <strong>Top-P sampling</strong>, we use <strong>rejection sampling</strong> to restrict which tokens can be selected. Rejection sampling draws from a target distribution by comparing random samples against a threshold and discarding those that do not meet it.</p>

<p>Taking the sampling kernel under Top-P filtering as an example, here is a simplified look of what happens:</p>

<ol>
  <li><strong>Initialize the pivot</strong> to $0$, so initially all tokens are considered.</li>
  <li><strong>Perform an inverse transform sampling pass</strong> but ignoring tokens with probabilities below the current pivot. After sampling a token, <strong>update the pivot</strong> to that token’s probability.</li>
  <li><strong>Compute the remaining probability</strong> $\texttt{q}$ among tokens that still exceed this pivot:
    <ol>
      <li>If $\texttt{q}$ remains greater than or equal to $\texttt{top_p}$, another round is needed to raise the pivot further and reject more tokens.</li>
      <li>Otherwise, if it is below $\texttt{top_p}$, we finalize the sampled token and mark success.</li>
    </ol>
  </li>
  <li><strong>Repeat</strong> until successful.</li>
</ol>

<p>The whole algorithm can be implemented in a single fused kernel, and it works similar for Top-K and other filtering strategies, other than we’ll be checking the number of tokens exceeding the pivot against $\texttt{top_k}$ or $\texttt{min_p}$.</p>

<p>In practice, we find that the number of rounds for returning a sampled token is usually small. It provides a substantial speedup compared to the naive PyTorch implementation because we avoid the sorting and multiple passes over the vocabulary, as well as multiple kernel launch overheads.</p>

<h3 id="dual-pivot-rejection-sampling">Dual Pivot Rejection Sampling</h3>

<p>While this rejection sampling approach is simple and efficient in most cases, it has some limitations. There is no theoretical guarantee on the number of rounds needed to obtain a sampled token. This can lead to varying sampling times across different probability distributions, which in turn causes inconsistent inter-token latency during LLM inference serving. Such variability may impact the predictability and reliability of the serving system.</p>

<p>To address this issue, in FlashInfer <a href="https://github.com/flashinfer-ai/flashinfer/releases/tag/v0.2.3">v0.2.3</a>, we introduce the a new algorithm called <strong>Dual Pivot Rejection Sampling</strong>, which uses two pivots for faster convergence in rejection sampling. The algorithm is as follows:</p>

<ol>
  <li>Let $f$ be a function that checks if a probability value is valid: $f(x)=1$ if valid, $0$ if not.</li>
  <li>Initialize $\textrm{low} \leftarrow 0$ and $\textrm{high} \leftarrow \max_i(p_i)$ as the initial range, it’s guaranteed that $f(\textrm{low})=0$ and $f(\textrm{high})=1$.</li>
  <li>Sample over probability values in the range $(\textrm{low}, \infty)$ using inverse transform sampling.</li>
  <li>Suppose $j$ is the sampled token, let $\textrm{pivot}_1\leftarrow p_j$, and $\textrm{pivot}_2\leftarrow \frac{\textrm{pivot}_1+\textrm{high}}{2}$.
    <ol>
      <li>If $f(\textrm{pivot}_1)=1$, we accept the sampled token and return $j$.</li>
      <li>If $f(\textrm{pivot}_1)=0$, $f(\textrm{pivot}_2)=1$, we set $\textrm{pivot}_1$ as new $\textrm{low}$ and $\textrm{pivot}_2$ as new $\textrm{high}$.</li>
      <li>If $f(\textrm{pivot}_1)=0$, $f(\textrm{pivot}_2)=0$, we set $\textrm{pivot}_2$ as new $\textrm{low}$.</li>
    </ol>
  </li>
  <li>Repeat step 3 and 4 until success.</li>
</ol>

<p align="center">
<img src="/assets/imgs/sampling_blog/dual-pivot-sampling.png" alt="Dual Pivot Rejection Sampling" width="800" />
<br />
<span style="color: gray; font-style: italic;">Figure 4: Transition from round(i) to round(i+1) in Dual Pivot Rejection Sampling, we either accept the sampled token (case 1) or shrinking the range by at least half (case 2 and 3).</span>
</p>

<p>Figure 4 shows the transition from round(i) to round(i+1) in Dual Pivot Rejection Sampling, in each round, if the sampled token is accepted, we return the token, otherwise, the new range’s extent is $\frac{\text{high}-\text{pivot}_1}{2} &lt; \frac{\text{high}-\text{low}}{2}$, which is at least half of the previous range. Thus it’s guaranteed that the number of rounds is $O(\log(1/\epsilon))$ where $\epsilon$ is the minimal possible value in floating point representation.</p>

<h2 id="theoretical-proof-of-the-correctness-of-rejection-sampler">Theoretical Proof of the Correctness of Rejection Sampler</h2>

<p>In this section, we provide a theoretical proof of the correctness of the rejection sampler, we choose the top-k sampling as an example, and
other samplers can be proved in a similar way.</p>

<h3 id="nomenclature">Nomenclature</h3>

<table>
  <thead>
    <tr>
      <th>Symbol</th>
      <th>Meaning</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>$p_i &gt; 0$</td>
      <td>Un‑normalised score (unnormalised probability mass) of item $i$</td>
    </tr>
    <tr>
      <td>$T = \operatorname{Top}k = {i_1,\dots,i_k}$</td>
      <td>Indices of the <strong>k</strong> largest scores</td>
    </tr>
    <tr>
      <td>$Z = \sum_{j \in T} p_j$</td>
      <td>Total mass of the top‑k items</td>
    </tr>
    <tr>
      <td>$\tau$</td>
      <td>Current <strong>pivot</strong> (threshold) value</td>
    </tr>
  </tbody>
</table>

<h3 id="theorem">Theorem</h3>

<p>The algorithm outputs each top‑k index $j \in T$ with probability</p>

\[\Pr[\text{output}=j] \;=\; \frac{p_j}{Z},\]

<p>i.e. <strong>exactly the distribution obtained by first discarding all non‑top‑k items and then sampling categorically inside the top‑k set</strong>.</p>

<h3 id="proof">Proof</h3>

<p>Fix any pivot $\tau &lt; \min_{j \in T} p_j$ (true at every step because $\tau$ is always taken from a rejected non‑top‑k item).
Define</p>

\[Q_j(\tau) \;=\; \Pr[\text{algorithm eventually returns } j \mid \text{current pivot } \tau], 
\quad j \in T .\]

<p>With</p>

\[S(\tau) \;=\; \sum_{m : p_m &gt; \tau} p_m 
\;=\; Z \;+\; W(\tau),\qquad
W(\tau) \;=\!\!\!\! \sum_{r \notin T,\, p_r &gt; \tau}\!\!\!\! p_r ,\]

<p>where $S(\tau)$ is the sum of all the probabilities of the tokens that are greater than $\tau$, and $W(\tau)$ is the remaining mass of “bad” items still above the threshold.</p>

<p>The next draw obeys</p>

\[\Pr[i \mid \tau] \;=\; \frac{p_i}{S(\tau)}.\]

<p>Hence</p>

\[Q_j(\tau) 
\;=\;
\underbrace{\frac{p_j}{S(\tau)}}_{\text{accept immediately}}
\;+\;
\sum_{\substack{r \notin T \\ p_r &gt; \tau}}
      \underbrace{\frac{p_r}{S(\tau)}}_{\text{draw } r}\;
      \underbrace{Q_j\!\bigl(p_r\bigr)}_{\text{pivot becomes } p_r}
\tag{★}\]

<p>We show that the following formula is a valid solution to (★):</p>

\[\boxed{\,Q_j(\tau) \;=\; \dfrac{p_j}{Z}\,}
\qquad\text{for every }\tau &lt; \min_{j \in T} p_j .\]

<p>We can verify the solution by substituting it into (★):</p>

\[\begin{aligned}
\text{RHS} 
&amp;= \frac{p_j}{S(\tau)}
   \;+\; \frac{p_j}{Z} \frac{W(\tau)}{S(\tau)} \\
&amp;= \frac{p_j}{S(\tau)}\!\Bigl(1+\frac{W(\tau)}{Z}\Bigr) \\
&amp;= \frac{p_j}{Z} \frac{Z+W(\tau)}{S(\tau)} \\
&amp;= \frac{p_j}{Z},
\end{aligned}\]

<p>because $S(\tau) = Z + W(\tau)$.
Thus the claimed form satisfies the recurrence, so $Q_j(\tau) \equiv p_j/Z$.</p>

<p>Now let’s show that the solution is unique.
Suppose there is another solution $Q_j’(\tau)$ satisfies (★), let’s define 
$\Delta_j(\tau) = Q_j(\tau) - Q_j’(\tau)$, we have:</p>

\[\Delta_j(\tau) = \sum_{\substack{r \notin T \\ p_r &gt; \tau}}
        \frac{p_r}{S(\tau)} \Delta_j(p_r)\]

<p>The sum of the coefficient $\sum_{\substack{r \notin T \ p_r &gt; \tau}} \frac{p_r}{S(\tau)} = \frac{W(\tau)}{S(\tau)}$, which satisfies:</p>

\[0 \leq \frac{W(\tau)}{S(\tau)} &lt; 1\]

<p>Suppose $\tau^*$ is the pivot where 
\(|\Delta_j(\tau)|\)
reach its maximum, if it’s positive, we have:</p>

\[|\Delta_j(\tau^*)| \leq \sum_{\substack{r \notin T \\ p_r &gt; \tau^*}}
        \frac{p_r}{S(\tau)} |\Delta_j(p_r)| \leq \sum_{\substack{r \notin T \\ p_r &gt; \tau^*}}
        \frac{p_r}{S(\tau)} |\Delta_j(\tau^*)| = \frac{W(\tau^*)}{S(\tau^*)} |\Delta_j(\tau^*)| &lt; |\Delta_j(\tau^*)|\]

<p>which leads to a contradiction, which means $\Delta_j(\tau^*) = 0$, and our solution is unique.</p>

<p>The algorithm starts with $\tau = 0$; therefore</p>

\[\Pr[\text{output}=j] = Q_j(0) = \frac{p_j}{Z},\]

<p>exactly the desired top‑k categorical distribution.</p>

<h2 id="evaluation">Evaluation</h2>

<p>Our evaluation demonstrates that FlashInfer’s sampling kernel delivers substantial improvements in both kernel-level latency and end-to-end throughput compared to traditional sorting-based implementations.</p>

<p align="center">
<img src="/assets/imgs/sampling_blog/Throughput_Comparison_of_Different_Engine_Kernel.svg" alt="Throughput Comparison of Different Engine Kernel" width="800" />
<br />
<span style="color: gray; font-style: italic;">Figure 5: Throughput Comparison of Different Engine Kernel.</span>
</p>

<p align="center">
<img src="/assets/imgs/sampling_blog/Sampling_Latency_Growth_with_Batch_Size.svg" alt="Sampling Latency Growth with Batch Size." width="800" />
<br />
<span style="color: gray; font-style: italic;">Figure 6: Sampling Latency Growth with Batch Size.</span>
</p>

<h2 id="community-adoption-and-other-applications">Community Adoption and Other Applications</h2>

<p>The FlashInfer sampling kernel has gained widespread adoption across major LLM frameworks, including <a href="https://github.com/mlc-ai/mlc-llm">MLC-LLM</a>, <a href="https://github.com/sgl-project/sglang">sglang</a>, and <a href="https://github.com/vllm-project/vllm/pull/7137">vLLM</a>. The community’s active engagement through feedback and bug reports has been instrumental in refining and improving our implementation.</p>

<p>Beyond token sampling, the rejection sampling algorithm have proven valuable in other areas of LLM inference optimization. Similar algorithm can also be applied to speculative decoding verification, like <a href="https://arxiv.org/pdf/2302.01318">chain speculative sampling</a> and <a href="https://arxiv.org/pdf/2305.09781">tree speculative sampling</a>. Recent innovations like <a href="https://github.com/tsinghua-ideal/Twilight">Twilight</a> have further advanced the field by successfully combining top-p sampling with sparse attention in a unified approach.</p>

<h2 id="implementation-details">Implementation Details</h2>

<p>While the algorithm is elegant in theory, implementing it efficiently in a GPU kernel requires careful attention to detail, particularly in the token selection logic in inverse transform sampling. One key challenge lies in the parallel prefix-sum operation used to locate sampled tokens. Due to the non-associative and non-commutative nature of floating-point arithmetic, parallel prefix-sum <strong>cannot guarantee monotonic outputs</strong> even with non-negative inputs. This can lead to invalid token generation if not handled properly. Special care must be taken to ensure numerical stability and correctness in the sampling implementation (and we made a lot of mistakes before got it right)</p>

<p>For a detailed look at our implementation and how we tackle these challenges, you can explore our <a href="https://github.com/flashinfer-ai/flashinfer/blob/main/include/flashinfer/sampling.cuh">source code</a>. Additionally, FlashInfer offers a comprehensive set of APIs for probability cutoff and renormalization, such as <a href="https://docs.flashinfer.ai/generated/flashinfer.sampling.top_p_renorm_probs.html#flashinfer.sampling.top_p_renorm_probs">top_p_renorm_probs</a> and <a href="https://docs.flashinfer.ai/generated/flashinfer.sampling.top_k_renorm_probs.html#flashinfer.sampling.top_k_renorm_probs">top_k_renorm_probs</a>, enabling flexible composition of multiple sampling filters. These tools allow developers to build sophisticated sampling strategies tailored to their specific needs.</p>

<h2 id="acknowledgement">Acknowledgement</h2>

<p>This blog is written by <a href="https://xsl.ing/">Shanli Xing</a>, we thank the flashinfer team for their contributions to the flashinfer.sampling module:</p>
<ul>
  <li>Zihao Ye: design and implementation of sampling kernels in CUDA.</li>
  <li>Bohan Hou: design and implementation of sampling kernels in TVM.</li>
  <li>Shanli Xing: design and implementation of min-p sampling kernels in CUDA.</li>
  <li>Tianqi Chen: propose the idea of rejection sampling for top-p.</li>
</ul>

<h2 id="footnotes">Footnotes</h2>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1" role="doc-endnote">
      <p>FlashInfer provides both “Top-K First” and “Joint” filtering options, with the latter applying Top-K and Top-P simultaneously at each round. More on the <a href="https://docs.flashinfer.ai/generated/flashinfer.sampling.top_k_top_p_sampling_from_probs.html">doc</a>. <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>Shanli Xing (UW), Zihao Ye (UW, NVIDIA), Bohan Hou (CMU), Luis Ceze (UW, NVIDIA), Tianqi Chen (CMU, NVIDIA)</name></author><summary type="html"><![CDATA[Background]]></summary></entry><entry><title type="html">FlashInfer 0.2 - Efficient and Customizable Kernels for LLM Inference Serving</title><link href="https://flashinfer.ai/2024/12/16/flashinfer-v02-release.html" rel="alternate" type="text/html" title="FlashInfer 0.2 - Efficient and Customizable Kernels for LLM Inference Serving" /><published>2024-12-16T00:00:00+00:00</published><updated>2024-12-16T00:00:00+00:00</updated><id>https://flashinfer.ai/2024/12/16/flashinfer-v02-release</id><content type="html" xml:base="https://flashinfer.ai/2024/12/16/flashinfer-v02-release.html"><![CDATA[<p align="center">
<img src="/assets/imgs/flashinfer-v02.jpg" alt="Generated by DALL-E" width="800" />
<br />
Generated by DALL-E
</p>

<p>After four months of development, we are thrilled to announce the release of <strong>FlashInfer 0.2</strong>. This major update introduces performance improvements, enhanced flexibility, and critical bug fixes. Key highlights of this release include:</p>

<ul>
  <li>Faster sparse (page) attention with <strong>FlashAttention-3 template</strong></li>
  <li><strong>JIT compilation</strong> for attention variants</li>
  <li>Support for <strong>Multi-head Latent Attention (MLA) decoding</strong></li>
</ul>

<h2 id="flashattention-3-template-with-blockvector-sparsity">FlashAttention-3 Template with Block/Vector-Sparsity</h2>

<p><a href="https://arxiv.org/pdf/2407.08608">FlashAttention-3</a> brings a breakthrough optimization for Hopper GPUs by cleverly overlapping softmax and matrix multiplication. FlashInfer 0.2 integrates FA-3 templates, achieving significant improvements in prefill attention performance on Hopper architecture.</p>
<h3 id="flexible-block-sparsity-and-vector-sparsity">Flexible Block-Sparsity and Vector-Sparsity</h3>

<p>FlashInfer’s standout feature is its highly flexible block-sparse FlashAttention implementation, supporting <strong>any block size configuration</strong>. Our PageAttention operators are implemented as <strong>block-sparse attention kernels</strong>, where <code class="language-plaintext highlighter-rouge">page_size</code> specifies the block’s column count. At its finest granularity, FlashInfer supports <strong>vector-sparsity</strong><sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">1</a></sup> (<code class="language-plaintext highlighter-rouge">page_size=1</code>), allowing for precise memory management (used in <a href="https://github.com/sgl-project/sglang">sglang</a>) and efficient KV-Cache token pruning.</p>

<p>By leveraging <a href="https://github.com/NVIDIA/cutlass/blob/main/media/docs/cute/00_quickstart.md">CuTe</a>’s <code class="language-plaintext highlighter-rouge">CustomStride</code> and <code class="language-plaintext highlighter-rouge">ComposedLayout</code> abstractions, we have extended vector-sparsity to FlashAttention-3. Inspired by <a href="https://github.com/NVIDIA/cutlass/tree/e1cd8c7866dd6de02b66a89879795e7d7301aacc/examples/59_ampere_gather_scatter_conv">CUTLASS’s gather/scatter convolution</a>, this was achieved through a simple modification to the producer’s memory loading module.</p>

<h3 id="performance-benchmark">Performance Benchmark</h3>
<p>We compared two attention implementations: PageAttention with <code class="language-plaintext highlighter-rouge">page_size=1</code> <sup id="fnref:2" role="doc-noteref"><a href="#fn:2" class="footnote" rel="footnote">2</a></sup> (use vector-sparse attention implementation) and variable-length dense attention <sup id="fnref:3" role="doc-noteref"><a href="#fn:3" class="footnote" rel="footnote">3</a></sup>, benchmarking them under identical problem sizes across both FA-2 (v0.1.*) and FA-3 (v0.2) backends. Benchmarks used <code class="language-plaintext highlighter-rouge">head_dim=128</code>, <code class="language-plaintext highlighter-rouge">causal=True</code>, varying batch sizes <code class="language-plaintext highlighter-rouge">(B)</code> and sequence lengths <code class="language-plaintext highlighter-rouge">(L)</code> with Gaussian-initialized input Q/K/V tensors.</p>

<p align="center">
<img src="/assets/imgs/fa3-template.png" alt="Performance comparison between dense/sparse attention on FA2&amp;3 template" width="800" />
<br />
Performance comparison between dense/vector-sparse attention on FA-2 and FA-3 templates on H100 SXM5, compiled with CUDA 12.4. y-axis: different settings, x-axis: achieved TFLOPs/s
</p>

<p><strong>Results:</strong> Vector-sparse attention achieves 90% of dense attention’s throughput under identical conditions. The FA-3 backend consistently outperforms FA-2. Thanks to FlashInfer’s stable API, upgrading from FA-2 to FA-3 requires no code changes—just install FlashInfer 0.2. The reference benchmark script for reproducing these results is available <a href="https://github.com/flashinfer-ai/flashinfer/blob/d7ac8e3ddc6623572c5c0e44af9e50a4c536a76c/benchmarks/bench_hopper_attention.py">here</a>.</p>

<h2 id="jit-compilation-for-attention-customization">JIT Compilation for Attention Customization</h2>

<p>Inspired by <a href="https://pytorch.org/blog/flexattention/">FlexAttention</a>, FlashInfer 0.2 introduces customizable programming interface to compile different attention variants. We designed a modularized attention template in CUDA/Cutlass. Users can define custom attention variants by specifying functors such as <code class="language-plaintext highlighter-rouge">LogitsTransform</code>/<code class="language-plaintext highlighter-rouge">QueryTransform</code>/etc in an attention variant class. The class string will specialize our pre-defined Jinja templates and FlashInfer uses PyTorch’s <a href="https://pytorch.org/docs/stable/cpp_extension.html#torch.utils.cpp_extension.load">JIT load</a> function to compile and cache these kernels. New variants like <a href="https://arxiv.org/pdf/2409.04431">FlashSigmoid</a> can be implemented with minimal code. See our <a href="https://github.com/flashinfer-ai/flashinfer/blob/124daea86fcdff4ba64e5b51337d81a46d6068cb/tests/test_jit_example.py">JIT examples</a> for more cases.</p>

<p align="center">
<img src="/assets/imgs/jit.png" alt="JIT Compilation in FlashInfer 0.2" width="800" />
<br />
Left: JIT workflow in FlashInfer. Right: Compile new attention variants.
</p>

<p>In addition to supporting new attention variants, other benefits of supporting JIT in FlashInfer include:</p>
<ul>
  <li><strong>Reduced wheel size:</strong> The binary size of FlashInfer increases exponentially in recently releases because we pre-compile combination of all attention variants. We have to reduce specialization to make wheel size managable which harms kernel performance (as observed in <a href="https://github.com/flashinfer-ai/flashinfer/pull/602">#602</a>, FlashInfer v0.1.6’s prefill performance is even worse than FlashInfer v0.1.1 because we move compile-time parameters to runtime which harms performance). FlashInfer v0.2 address the issue by just pre-compile a subset of <strong>core</strong> kernels ahead-of-time, while leaving most of the attention variants JIT compiled.</li>
  <li><strong>Light development:</strong> No need to reinstall FlashInfer for minor CUDA changes, by installing FlashInfer in <a href="https://docs.flashinfer.ai/installation.html#install-from-source">JIT Mode</a>.</li>
</ul>

<p>We have optimized the speed of JIT compilation by minimizing header dependencies and utilizing split compilation. As a result, all kernels for Llama models can be JIT-compiled within <strong>15 seconds</strong> on server-grade CPUs. For more details, check out our <a href="https://github.com/flashinfer-ai/flashinfer/blob/main/tests/test_jit_warmup.py">JIT warmup scripts</a>.</p>

<h2 id="fused-multi-head-latent-attention-mla-decoding-kernel">Fused Multi-head Latent Attention (MLA) Decoding Kernel</h2>

<p><strong>Multi-head Latent Attention (MLA)</strong>, introduced in <a href="https://arxiv.org/pdf/2405.04434">Deepseek v2</a>, compresses the KV-Cache by projecting it into low-rank matrices. Achieving high throughput for MLA is challenging due to a lack of optimized kernels. FlashInfer community recently implemented an fused kernel with the <strong>Matrix Absorption trick</strong>, improving memory efficiency. See <a href="https://github.com/flashinfer-ai/flashinfer/pull/551">#551</a> for detailed explanation.</p>

<p align="center">
<img src="/assets/imgs/mla.png" alt="MLA" width="800" />
<br />
MLA decode kernel workflow in FlashInfer
</p>

<p>Future plans include accelerating MLA decoding with Tensor Cores, benefiting speculative decoding.</p>

<h2 id="cudagraph-compatibility-for-variable-length-inputs">CUDAGraph Compatibility for Variable-Length Inputs</h2>

<p>FlashInfer 0.2 fixes prefill attention’s incompatibility with CUDAGraph when query lengths vary during capture and replay stages, by accurately estimating upper resource bounds. CUDAGraphs now can be used to accelerate speculative decoding and chunked-prefil workloads with FlashInfer kernels.</p>

<h2 id="torchcompile-compatibility">torch.compile Compatibility</h2>

<p>FlashInfer 0.2 adheres to the <a href="https://docs.google.com/document/d/1_W62p8WJOQQUzPsJYa7s701JXt0qf2OfLub2sbkHOaU/">PyTorch Custom Operators Standard</a>, ensuring compatibility with <strong>torch.compile</strong>.</p>

<h2 id="packaging-and-cicd">Packaging and CI/CD</h2>

<p>We now provide <a href="https://github.com/flashinfer-ai/flashinfer-nightly/releases">nightly builds</a> so users can test the latest features without waiting for stable releases.</p>

<h2 id="other-notable-improvements">Other Notable Improvements</h2>

<h4 id="fusedaddrmsnorm-fix">FusedAddRMSNorm Fix</h4>
<p>Fixed numerical issues in <code class="language-plaintext highlighter-rouge">FusedAddRMSNorm</code>, which may cause bad outputs for some models.</p>

<h4 id="cutlasss-sm90-grouped-gemm-integration">Cutlass’s SM90 Grouped-GEMM Integration</h4>
<p>We integrated <strong>Cutlass 3.5 SM90 Grouped-GEMM</strong> into our <a href="https://docs.flashinfer.ai/api/gemm.html#flashinfer.gemm.SegmentGEMMWrapper">SegmentGEMM</a> API, accelerating LoRA and MoE serving.</p>

<h4 id="non-contiguous-kv-cache-support">Non-Contiguous KV-Cache Support</h4>
<p>KV-Cache can now utilize non-contiguous storage layouts, improving support for <a href="https://github.com/flashinfer-ai/flashinfer/issues/506">offloading</a>.</p>

<h4 id="faster-plan-functions">Faster <code class="language-plaintext highlighter-rouge">plan</code> Functions</h4>
<p><code class="language-plaintext highlighter-rouge">plan</code> functions now use non-blocking host-to-device memory transfers, improving performance. After FlashInfer v0.2, it’s encouraged to pass <strong>host tensors</strong> instead of device tensors to reduce synchronization in the <code class="language-plaintext highlighter-rouge">plan</code> function.</p>

<h4 id="kv-cache-append-optimization">KV-Cache Append Optimization</h4>
<p>KV-Cache append throughput for small batch sizes was improved by parallelizing per element instead of per request. A new API, <a href="https://docs.flashinfer.ai/generated/flashinfer.page.get_batch_indices_positions.html">get_batch_indices_positions</a>, supports this. Note that we made some breaking changes to this API to accomodate different parallelization mode. See <a href="https://github.com/flashinfer-ai/flashinfer/blob/124daea86fcdff4ba64e5b51337d81a46d6068cb/benchmarks/bench_append_paged_kv_cache.py">our benchmark</a> for the new API usage.</p>

<h4 id="standardized-rope-interface">Standardized RoPE Interface</h4>
<p>We standardized the RoPE interface to align with other frameworks. FlashInfer adopted <strong>fp32 sin/cos</strong> computations to avoid <a href="https://arxiv.org/pdf/2411.13476">numerical issues</a>.</p>

<h2 id="roadmap">Roadmap</h2>
<p>We appreciate the community’s love and support. To enhance transparency, we’ve published our <a href="https://github.com/flashinfer-ai/flashinfer/issues/675">development roadmap</a>, where you can provide feedback and influence FlashInfer’s future.</p>

<h2 id="community-contributions">Community Contributions</h2>
<p>The number of contributors grew from 41 to 52 since v0.1.6. We thank the following developers for their contributions:</p>

<ul>
  <li><a href="https://github.com/yzh119">@yzh119</a>: JIT, FA-3 Template, and others</li>
  <li><a href="https://github.com/abcdabcd987">@abcdabcd987</a>: torch.compile support, packaging</li>
  <li><a href="https://github.com/nandor">@nandor</a>: Variable-length CUDAGraph support</li>
  <li><a href="https://github.com/ur4t">@ur4t</a>: Packaging, CI/CD</li>
  <li><a href="https://github.com/zhyncs">@zhyncs</a>: Nightly builds, CI/CD</li>
  <li><a href="https://github.com/tsu-bin">@tsu-bin</a>: MLA decoding</li>
  <li><a href="https://github.com/xslingcn">@xslingcn</a>: Cutlass Grouped-GEMM</li>
  <li><a href="https://github.com/yuxianq">@yuxianq</a>: JIT, bug fixes</li>
  <li><a href="https://github.com/LinHeLurking">@LinHeLurking</a>: Non-contiguous KV-Cache</li>
  <li><a href="https://github.com/Abatom">@Abatom</a>: FusedAddRMSNorm fix</li>
  <li><a href="https://github.com/jeejeelee">@jeejeelee</a>: Grouped-GEMM bug fixes</li>
  <li><a href="https://github.com/mvpatel2000">@mvpatel</a>: Faster <code class="language-plaintext highlighter-rouge">plan</code> functions</li>
  <li><a href="https://github.com/Ubospica">@Ubospica</a>: Pre-commit setup</li>
  <li><a href="https://github.com/dc3671">@dc3671</a>: Improved unit tests</li>
  <li><a href="https://github.com/Pzzzzz5142">@Pzzzzz5142</a>: JIT compilation fix</li>
  <li><a href="https://github.com/reyoung">@reyoung</a>: Bug fixes</li>
  <li><a href="https://github.com/xiezhq-hermann">@xiezhq-hermann</a>: ARM compilation fixes</li>
  <li><a href="https://github.com/Bruce-Lee-LY">@Bruce-Lee-LY</a>: Performance optimizations</li>
  <li><a href="https://github.com/francheez">@francheez</a>: Typo fixes</li>
</ul>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1" role="doc-endnote">
      <p><a href="https://dl.acm.org/doi/pdf/10.1145/3458817.3476182">Efficient Tensor Core-Based GPU Kernels for Structured Sparsity under Reduced Precision</a> <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:2" role="doc-endnote">
      <p>Use <a href="https://docs.flashinfer.ai/api/prefill.html#flashinfer.prefill.BatchPrefillWithPagedKVCacheWrapper">BatchPrefillWithPagedKVCacheWrapper</a> API <a href="#fnref:2" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:3" role="doc-endnote">
      <p>Use <a href="https://docs.flashinfer.ai/api/prefill.html#flashinfer.prefill.BatchPrefillWithRaggedKVCacheWrapper">BatchPrefillWithRaggedKVCacheWrapper</a> <a href="#fnref:3" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>FlashInfer Community</name></author><summary type="html"><![CDATA[Generated by DALL-E]]></summary></entry><entry><title type="html">Accelerating Self-Attentions for LLM Serving with FlashInfer</title><link href="https://flashinfer.ai/2024/02/02/introduce-flashinfer.html" rel="alternate" type="text/html" title="Accelerating Self-Attentions for LLM Serving with FlashInfer" /><published>2024-02-02T00:00:00+00:00</published><updated>2024-02-02T00:00:00+00:00</updated><id>https://flashinfer.ai/2024/02/02/introduce-flashinfer</id><content type="html" xml:base="https://flashinfer.ai/2024/02/02/introduce-flashinfer.html"><![CDATA[<p align="center">
<img src="/assets/imgs/FlashInfer-white-background.png" alt="flashinfer-logo" width="600" />
</p>

<p>LLM (Large Language Models) Serving quickly became an important workload. The efficacy of operators within Transformers – namely GEMM, Self-Attention, GEMV, and elementwise computations are critical to the overall performance of LLM serving. While optimization efforts have extensively targeted GEMM and GEMV, there is a lack of performance studies focused on Self-Attention in the context of LLM serving. In this blog post, we break Self-Attention down into three stages: prefill, decode, and append; analyze the performance bottleneck of Self-Attention on both single-request and batching scenarios in these three stages; and propose a solution to tackle these challenges. These ideas have been integrated into <a href="https://github.com/flashinfer-ai/flashinfer/">FlashInfer</a>, an open-source library for accelerating LLM serving released under Apache 2.0 license.</p>

<p>FlashInfer has been developed by researchers from the University of Washington, Carnegie Mellon University, and OctoAI since summer 2023. FlashInfer provides PyTorch APIs for quick prototyping, and a dependency-free, header-only C++ APIs for integration with LLM serving systems. Compared to existing libraries, FlashInfer has several unique advantages:</p>

<ol>
  <li><strong>Comprehensive Attention Kernels</strong>: FlashInfer implements attention kernels that cover all the common use cases of LLM serving with state-of-the-art performance, including single-request and batching versions of Prefill, Decode, and Append kernels, on various formats of KV-Cache (Padded Tensor, Ragged Tensor, and Page Table).</li>
  <li><strong>Optimized Shared-Prefix Batch Decoding</strong>: FlashInfer enhances shared-prefix batch decoding performance through cascading, resulting in an impressive up to 31x speedup compared to the baseline vLLM PageAttention implementation (for long prompt of 32768 tokens and large batch size of 256), check <a href="/2024/02/02/cascade-inference">another blog post</a> for more details.</li>
  <li><strong>Accelerate Attention for Compressed/Quantized KV-Cache</strong> Modern LLMs are often deployed with quantized/compressed KV-Cache to reduce memory traffic. FlashInfer accelerates these scenarios by optimizing performance for <em>Grouped-Query Attention</em>, <em>Fused-RoPE Attention</em> and <em>Quantized Attention</em>. Notably, FlashInfer achieves up to 2-3x speedup for Grouped-Query Attention on A100 &amp; H100, compared to vLLM implementation.</li>
</ol>

<p>FlashInfer has been adopted by LLM serving systems such as <a href="https://github.com/mlc-ai/mlc-llm">MLC-LLM</a> (for its CUDA backend), <a href="https://github.com/punica-ai/punica">Punica</a> and <a href="https://github.com/sgl-project/sglang">sglang</a>. We welcome wider adoption and contribution from the community. Please join our <a href="https://github.com/orgs/flashinfer-ai/discussions">discussion forum</a> or <a href="https://github.com/flashinfer-ai/flashinfer/issues">creating an issue</a> to leave your feedback and suggestions.</p>

<h2 id="attentions-in-llm-serving">Attentions in LLM Serving</h2>

<p>There are three generic stages in LLM serving: <em>prefill</em>, <em>decode</em> and <em>append</em>. During the prefill stage, attention computation occurs between the KV-Cache and all queries. In the decode stage, the model generates tokens one at a time, computing attention only between the KV-Cache and a single query. In the append stage, attention is computed between the KV-Cache and queries of the appended tokens. <em>append</em> attention is also useful in <a href="https://arxiv.org/abs/2211.17192">speculative decoding</a>: the draft model suggests a sequence of tokens and the larger model decides whether to accept these suggestions. During the attention stage, proposed tokens are added to the KV-Cache, and the large model calculates attention between the KV-Cache and the proposed tokens.</p>

<p>The crucial factor affecting the efficiency of attention computation is the length of the query ($l_q$), determining whether the operation is compute-bound or IO-bound. The operational intensity (number of operations per byte of memory traffic) for attention computation is expressed as $O\left(\frac{1}{1/l_q + 1/l_{kv}} \right)$, where $l_{kv}$ represents the length of the KV-Cache. During the decode stage, where $l_q$ is consistently 1, the operational intensity is close to $O(1)$, making the operator entirely IO-bound. In the append/prefill stages, the attention operational intensity is approximately $O(l_q)$, leading to compute-bound scenarios when $l_q$ is substantial.</p>

<p>The diagram illustrates the attention computation process in the prefill, append, and decode stages:</p>

<p align="center">
<img src="/assets/imgs/llm-attentions.png" alt="Attention in LLMs" width="800" />
<br />
Figure 1: Decode attention fills one row of the attention map at a time, prefill attention fills the entire attention map (under the causal mask), and the append attention fills the trapezoid region.
</p>

<p>The figure below shows the roofline model of the three stages of attention computations. Decode attention performance is always underneath the peak bandwidth ceiling (bounded by peak memory bandwidth in GPU), and thus is IO-bound. Prefill attention has high operational intensity and is under the peak compute performance ceiling (bounded by peak floating point performance). Append attention is IO-bound when the query length is small, and compute-bound when the query length is large.</p>

<p align="center">
<img src="/assets/imgs/attention-roofline.png" alt="Roofline of Attention Operators" width="600" />
<br />
Figure 2. Roofline model of attention operators in LLM Serving, data from A100 PCIe 80GB.
</p>

<h3 id="single-request-and-batching">Single-Request and Batching</h3>

<p>There two common ways to serve LLM models: batching and single request.
Batching groups several user requests together and process them in parallel to improve the throughput, however, the operational intensity of attention kernels is irrelevant to batch size <sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">1</a></sup>, and batch decoding attention still has operational intensity of $O(1)$.</p>

<h2 id="flashinfer-overview">FlashInfer Overview</h2>

<p><a href="https://arxiv.org/abs/2205.14135">FlashAttention</a> proposes to fuse multi-head attention into a single kernel by generalizing <a href="https://arxiv.org/abs/1805.02867">online softmax</a> trick to self-attention, thus avoiding the overhead of materializing the attention matrix on GPU global memory. <a href="https://arxiv.org/abs/2307.08691">FlashAttention2</a> further improves performance by adopting a more reasonable tiling strategy and reducing the number of non tensor ops to alleviate the issue that A100/H100 has low non-tensor cores performance. vLLM proposes <a href="https://arxiv.org/abs/2309.06180">PageAttention</a> where KV-Cache is organized as a page table, to alleviate the memory fragmentation issue in LLM serving.</p>

<p>FlashInfer implements single-request and batch version of FlashAttention for all three stages: prefill, append and decode on versatile KV-Cache formats (e.g. Ragged Tensor, Page Table). For single decode/prefill and batch decoding kernels, FlashInfer achieves state-of-the-art performance for single-request decode/prefill and batch decode kernels. Moreover, FlashInfer implements <em>prefill/append kernels for Paged KV-Cache</em> which none of the existing libraries have done before, and it be used to serve models in <a href="https://arxiv.org/abs/2211.17192">speculative decoding</a> setting.</p>

<p>Many recent work proposes KV-Cache compression techniques to reduce memory traffic. In light of this,
 FlashInfer optimize kernels for <em>Grouped-Query Attention</em>, <em>Fused-RoPE Attention</em> and <em>Quantized Attention</em> for efficient serving with compressed KV-Cache:</p>
<ul>
  <li><strong>Grouped Query Attention</strong>: <a href="https://arxiv.org/abs/2305.13245">Grouped Query Attention</a> uses a smaller number of heads for keys and values thus saving memory traffic. The operational intensity of Grouped Query Attention grows from $O(1)$ to $O\left(\frac{H_{qo}}{H_{kv}}\right)$ where $H_{qo}$ is the number of heads for queries and $H_{kv}$ is the number of heads for keys and values. GPUs such as A100/H100 has low non-tensor cores performance, and thus traditional implementation of Grouped Query Attention is compute-bound. FlashInfer proposes to use prefill kernels (which utilizes Tensor Cores) for decode attention in GQA, and achieves up to 2-3x speedup compared to vLLM implementation.</li>
  <li><strong>Fused-RoPE Attention</strong>: <a href="https://arxiv.org/abs/2104.09864">RoPE (Rotary Positional Embeddings)</a> has become a standard component of Transformers, most existing serving systems stores post-RoPE keys (the keys after applying rotary embeddings) in KV-Cache. However, some recent work such as <a href="https://arxiv.org/abs/2309.17453">StreamingLLM</a> will prune tokens in KV-Cache, and the position of tokens will change after pruning, thus the post-RoPE keys in KV-Cache become meaningless. In this case, FlashInfer proposes to save pre-RoPE keys in KV-Cache, and fuses RoPE into attention kernel. Experiments on various platform and settings show that FlashInfer’s Fused-RoPE Attention kernel can apply RoPE on the fly with negligible overhead.</li>
  <li><strong>Quantized Attention</strong>: Another way to compress KV-Cache is through pruning, <a href="https://arxiv.org/abs/2303.06865">FlexGen</a> and <a href="https://arxiv.org/abs/2310.19102">Atom</a> show that it’s possible to prune KV-Cache to 4-bit with negligible accuracy loss. FlashInfer implements low-precision attention kernels so that we can achieve nearly linear speedup to the compression ratio (~4x for 4bit, ~2x for 8bit).</li>
</ul>

<p>Some recent work such as <a href="https://github.com/ModelTC/lightllm">LightLLM</a> and <a href="https://github.com/sgl-project/sglang">sglang</a> uses a special form of PageAttention where page size equals one, for easy management of KV-Cache in complicated serving scenarios such as structured generation. FlashInfer optimizes PageAttention kernels by pre-fetching page indices in GPU shared memory, so that kernel performance is not affected by the page size.</p>

<p>In the subsequent sections, we will delve into the detailed optimizations and benchmark results achieved by FlashInfer.</p>

<h2 id="benchmark-settings">Benchmark Settings</h2>

<h3 id="hardware">Hardware</h3>

<p>We benchmarked on 4 different GPUs: H100 SXM 80GB, A100 PCIe 80GB, RTX 6000 Ada and RTX 4090, the first two is widely used data center GPU in <a href="https://www.hpctech.co.jp/catalog/gtc22-whitepaper-hopper_v1.01.pdf">Hopper</a> and <a href="https://images.nvidia.com/aem-dam/en-zz/Solutions/data-center/nvidia-ampere-architecture-whitepaper.pdf">Ampere</a> architectures, respectively, and latter two are workstation and gaming GPUs in <a href="https://images.nvidia.com/aem-dam/Solutions/geforce/ada/nvidia-ada-gpu-architecture.pdf">Ada Lovelace architecture</a> that are much more affordable, the specifications are listed in the following table:</p>

<table>
  <thead>
    <tr>
      <th> </th>
      <th>H100 SXM 80GB</th>
      <th>A100 PCIe 80GB</th>
      <th>RTX Ada 6000</th>
      <th>RTX 4090</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>GPU Memory (GB)</td>
      <td>80</td>
      <td>80</td>
      <td>48</td>
      <td>24</td>
    </tr>
    <tr>
      <td>Micro Architecture</td>
      <td>Hopper (sm_90)</td>
      <td>Ampere (sm_80)</td>
      <td>Ada Lovelace (sm_89)</td>
      <td>Ada Lovelace (sm_89)</td>
    </tr>
    <tr>
      <td>Memory bandwidth (GB/s)</td>
      <td>3,352</td>
      <td>1,935</td>
      <td>960</td>
      <td>1,008</td>
    </tr>
    <tr>
      <td>Number of SM</td>
      <td>132</td>
      <td>108</td>
      <td>142</td>
      <td>128</td>
    </tr>
    <tr>
      <td>Peak Tensor Cores Performance (TFLops/s)</td>
      <td>989</td>
      <td>312</td>
      <td>366</td>
      <td>165 (f32 accum)<br /> 330 (f16 accum)</td>
    </tr>
    <tr>
      <td>Peak (Non-Tensor Cores) FP32 Performance (TFLops/s)</td>
      <td>67</td>
      <td>20</td>
      <td>90</td>
      <td>80</td>
    </tr>
    <tr>
      <td>Max Shared Memory (KB/SM)</td>
      <td>228</td>
      <td>164</td>
      <td>100</td>
      <td>100</td>
    </tr>
    <tr>
      <td>L2 Cache (KB)</td>
      <td>51200</td>
      <td>40960</td>
      <td>98304</td>
      <td>73728</td>
    </tr>
  </tbody>
</table>

<p>H100 SXM 80GB uses HBM3 and A100 PCIe 80GB use HBM2e, both have larger memory bandwidth than RTX Ada 6000 and RTX 4090 that use GDDR6X.
RTX Ada 6000 and RTX 4090 have much larger non-Tensor Cores peak performance (90 and 80 TFLops/s respectively) than A100 (20 TFLops/s).
The later three GPUs have similar peak Tensor Cores (fp16 input, without sparsity) performance for f16 accumulation, RTX 4090’s Tensor Cores have 2x throughput with fp16 accumulation compared to fp32 accumulation, while the other GPUs have the same throughput for fp16 and fp32 accumulation.</p>

<p>Below is the roofline curve of the four GPUs for both Tensor Cores and CUDA Cores:</p>
<p align="center">
<img src="/assets/imgs/devices-roofline.png" alt="Roofline of different devices" width="600" />
<br />
Figure 3: Devices Roofline of 4 GPUs, Tensor Cores Performance and CUDA Cores Performance are indicated separately.
</p>

<p>The ridge point is determined by the ratio of peak floating point performance and memory bandwidth.</p>

<h3 id="software">Software</h3>

<p>The baselines being compared are: <a href="https://github.com/Dao-AILab/flash-attention/releases/tag/v2.4.2">FlashAttention 2.4.2</a> which has incorporated <a href="https://arxiv.org/abs/2307.08691">FlashAttention 2</a> and <a href="https://crfm.stanford.edu/2023/10/12/flashdecoding.html">Flash-Decoding</a>, and <a href="https://github.com/vllm-project/vllm/releases/tag/v0.2.6">vLLM v0.2.6</a> that implements PageAttention 1&amp;2.
For vLLM we use prebuilt wheels from pip, we build FlashAttention &amp; FlashInfer from source code with the NVCC compiler in <a href="https://developer.nvidia.com/cuda-12-3-1-download-archive">CUDA 12.3.1 release</a>.
The kernel profiling is done with <a href="https://github.com/NVIDIA/nvbench">nvbench</a> library, we take the “cold” GPU time which flushes the L2 cache before each kernel launch.</p>

<h3 id="metrics">Metrics</h3>

<p>We report achieved TFLops/s for prefill &amp; append attention kernels, and GPU memory bandwidth utilization (computed by $\frac{\textrm{number of bytes read by the kernel}}{\textrm{kernel latency}} / \textrm{hardware GPU memory bandwidth}$) for decode &amp; append attention kernels.</p>

<h3 id="prefill-kernels">Prefill Kernels</h3>

<p>For prefill (multi-query) attention, we reimplemented the FlashAttention 2 algorithm in pure CUDA with some additional optimizations. Standard FlashAttention implementation uses Tensor Cores with fp16 input and fp32 accumulator, however, RTX 4090 GPUs has lower Tensor Cores performance with fp32 accumulator, we observe that the $\frac{\mathbf{q}\cdot \mathbf{k}^{T}}{\sqrt(d)}$ phase in attention computation have small value range and can be accumulated with fp16 (because the head dimension is always small: e.g. 128), FlashInfer provides an <code class="language-plaintext highlighter-rouge">allow_fp16_qk_reduction</code> option to allow this optimization (but still use fp32 accumulation for $\mathbf{score} \cdot \mathbf{v}$), this optimization could bring 50% speedup on RTX 4090. Below is the performance comparison of FlashInfer 0.0.1 and FlashAttention 2.4.2 on different GPUs:</p>

<p align="center">
<img src="/assets/imgs/single-prefill-benchmark.png" alt="single prefill kernel benchmarks" width="800" />
<br />
Figure 4: Single request prefill kernel performance, use Llama2-7B setting: num_kv_heads=num_qo_heads=32, head_dim=128. Sequence length varies from 32 to 65535.
</p>

<p>In f32 accumulation setting, FlashInfer’s prefill kernel implementation achieves best performance on all 4 GPUs. <code class="language-plaintext highlighter-rouge">allow_fp16_qk_reduction</code> option can further improve performance, especially for RTX 4090.</p>

<h3 id="append--decode-optimizations">Append &amp; Decode Optimizations</h3>

<p>Append and decode attention tend to have larger KV length than query length, which could limit the SM(StreamMultiprocessor) utilization in GPUs when batch size is small, FlashInfer propose to use the <a href="https://github.com/NVIDIA/cutlass/blob/8825fbf1efebac973d96730892919ab241b755bb/media/docs/efficient_gemm.md#parallelized-reductions">Split-K</a> trick in GEMM optimizations which splits the KV-Cache on sequence dimension to increase parallelism. Another work, Flash-Decoding also explored this idea, you can check their great <a href="https://crfm.stanford.edu/2023/10/12/flashdecoding.html">blog post</a> for visualizations and explanations. Below is the decode attention performance comparison of FlashInfer 0.0.1 and FlashAttention 2.4.2 on different GPUs:</p>

<p align="center">
<img src="/assets/imgs/single-decode-benchmark.png" alt="single decode kernel benchmarks" width="800" />
<br />
Figure 5: Single request decode kernel performance, use Llama2-7B setting: num_kv_heads=num_qo_heads=32, head_dim=128. Sequence length varies from 32 to 65536.
</p>

<p>FlashInfer achieves best performance on all 4 GPUs, and the GPU bandwidth utilization is close to 100% for long sequences.
An interesting fact is that split-KV do not improve performance for GPUs such as RTX Ada 6000 and RTX 4090 because they have relatively smaller memory bandwidth and stronger CUDA Cores performance (decode attention has low operational intensity and we use CUDA Cores in non-GQA setting). Unlike compute units which is SM local, the global memory traffic on GPUs is shared, thus using 32 (number of heads in Llama2-7B setting) of 108 SMs can still fully utilize the memory bandwidth if the operator is not compute-bound. A100 GPUs has low CUDA Cores performance (20 TFLops/s), using 32 of 108 SMs (5.9 TFLops/s) will make the kernel compute-bound (besides multiply and add, there are also time-consuming computations such as <code class="language-plaintext highlighter-rouge">exp</code> in attention computation), and split-KV is helpful in this case.</p>

<p>For batch decoding attention, FlashInfer implements PageAttention with optimizations such as pre-fetching page indices, below is performance comparison of FlashInfer PageAttention kernel and vLLM PageAttention kernel:</p>

<p align="center">
<img src="/assets/imgs/batch-decode-benchmark.png" alt="batch decode kernel benchmarks" width="800" />
<br />
Figure 6: Batch decode kernel performance, use Llama2-7B setting: num_kv_heads=num_qo_heads=32, head_dim=128, batch_size=[1,16,64]. Sequence length varies from 32 to 65536 for batch_size = 1, from 32 to 4096 for batch_size = 16, and from 32 to 1024 for batch_size = 64.
</p>

<p>FlashInfer PageAttention kernel has consistent speedup over vLLM 0.2.6’s implementation in different batch sizes and different sequence lengths.</p>

<p>We also benchmark the append attention kernels (append attention shares the same set of APIs with prefill attention, with the distinction that query length is smaller than key/value length in append attention):</p>

<p align="center">
<img src="/assets/imgs/single-append-benchmark.png" alt="append kernel benchmarks" width="800" />
<br />
Figure 7: Append attention kernel performance, use Llama2-7B setting, num_kv_heads=num_qo_heads=32, head_dim=128. The append length is set to 128 or 256, KV sequence length varies from 32 to 65536.
</p>

<p>FlashInfer still achieves the best performance on all 4 GPUs, either with fp16 or fp32 qk accumulator.
Split-KV significantly improves the performance of append kernels for append length of both 128 and 256, because the operational intensity of the operator becomes large, and using 32/100+ SMs no longer provides enough compute units, thus making the kernel compute-bound.
Note that the ridge point of RTX 4090’s Tensor Cores fp32 accumulator roofline is 163 (165 TFLops/s / 1008 GB/s), the kernel will be compute bound when query length (which approximately equals operational intensity) reaches 256, using <code class="language-plaintext highlighter-rouge">allow_fp16_qk_reduction</code> can alleviate the issue.</p>

<p>FlashInfer also implemented batch append attention kernel where key/value is stored in Page Tables, this could accelerate speculative decoding in LLM serving, and we will discuss this in another blog post.</p>

<h3 id="grouped-query-attention">Grouped-Query Attention</h3>

<p><a href="https://arxiv.org/abs/2305.13245">Grouped-Query Attention</a> uses smaller number of key/value heads than the number of query/output heads, makes the operational intensity higher than ordinary multi-head attention. FlashInfer proposes to use prefill(multi-query) attention kernel, which utilize Tensor Cores, for decode attention in GQA. Below is the GQA kernel performance comparison between FlashInfer (w/ CUDA Cores and w/ Tensor Cores), and FlashAttention 2.4.2 on A100 &amp; H100:</p>

<p align="center">
<img src="/assets/imgs/single-gqa-benchmark.png" alt="single gqa benchmarks" width="800" />
<br />
Figure 8: Single request GQA decode performance, use llama2-70b setting: tp=2, num_kv_heads=4, num_qo_heads=32, head_dim=128. Sequence length varies from 32 to 65536.
</p>

<p>For single-request GQA decoding attention, FlashInfer (Tensor Cores) achieves better performance than FlashAttention 2.4.2 on both A100 &amp; H100, and FlashInfer (CUDA Cores) can only achieve 40%+ bandwidth utilization because of limited CUDA Cores performance.</p>

<p align="center">
<img src="/assets/imgs/batch-gqa-benchmark.png" alt="batch gqa benchmarks" width="800" />
<br />
Figure 9: Batch GQA decode performance, use llama2-70b setting: tp=2, num_kv_heads=4, num_qo_heads=32, head_dim=128. batch_size is set to 64 and sequence length per request varies from 32 to 8192.
</p>

<p>For batch GQA decoding attention, FlashInfer w/ Tensor Cores is 3x faster than vLLM PagaAttention when <code class="language-plaintext highlighter-rouge">batch_size=64</code>.</p>

<h3 id="fused-rope-attention">Fused-RoPE Attention</h3>

<p>KV-Cache compression techniques such as <a href="https://arxiv.org/abs/2306.14048">H2O</a> and <a href="https://github.com/mit-han-lab/streaming-llm">Streaming-LLM</a> prunes KV-Cache by removing tokens, and the original
relative positions of tokens in KV-Cache will be polluted, storing post-RoPE keys in KV-Cache become meaningless. FlashInfer implements high-performance Fused-RoPE attention kernels which applies RoPE on the fly, below is the performance comparison of FlashInfer decoding attention with and without RoPE:</p>

<p align="center">
<img src="/assets/imgs/fused-rope-attention.png" alt="fused rope attention" width="800" />
<br />
Figure 10: Fused RoPE attention performance, use Llama2-7B setting: um_kv_heads=num_qo_heads=32, head_dim=128. Sequence length varies from 32 to 65536.
</p>

<p>RoPE has negligible overhead on all 4 GPUs, especially for RTX 6000 Ada and RTX 4090 GPU which has
strong CUDA Cores performance (RoPE requires <code class="language-plaintext highlighter-rouge">sin</code>/<code class="language-plaintext highlighter-rouge">cos</code> computation that can not be accelerated with Tensor Cores).</p>

<h3 id="low-precision-attention">Low-Precision Attention</h3>

<p>More and more work show that KV-Cache can be quantized to low bits with negligible accuracy loss.
FlashInfer implements high-performance fp8 decode decode kernels, which could accelerate the kernel by up to 2x compared with fp16 kernels:</p>

<p align="center">
<img src="/assets/imgs/fp8-attention.png" alt="fp8 attention" width="800" />
<br />
Figure 11: FP8 decode attention performance, use Llama2-7B setting: num_kv_heads=num_qo_heads=32, head_dim=128. Sequence length varies from 32 to 65536.
</p>

<p>There is some gap between bandwidth utilization of fp8 and fp16 kernels, however the gap is getting closer as the query length grows.</p>

<p><a href="https://github.com/efeslab/Atom/">Atom</a> implemented high-performance decode attention kernels with int4 quantization on top of FlashInfer.</p>

<h3 id="effect-of-page-size-on-flashinfers-pageattention">Effect of Page Size on FlashInfer’s PageAttention</h3>

<p>The FlashInfer decode kernels prefetches page indices in GPU shared memory, thus minimizing the impact of the page size on kernel performance. Below is the performance comparison of FlashInfer PageAttention with different page sizes on A100:</p>

<p align="center">
<img src="/assets/imgs/page-effect-benchmark.png" alt="ablation page size attention" width="400" />
<br />
Figure 12: Batch decode performance on different page_size. batch_size is set to 1, use Llama2-7B setting: num_kv_heads=num_qo_heads=32, head_dim=128. Sequence lengths varies from 32 to 65536. We also add a reference line for the performance of FlashInfer single-request decode attention without using Page Table.
</p>

<p>The memory bandwidth utilization of the 4 different page sizes are nearly identical, and they are close to the single-request decode attention curve, which indicates that page size has little effect on FlashInfer PageAttention’s kernel performance, and page table itself has little overhead.</p>

<p>Some recent work such <a href="https://github.com/sgl-project/sglang">sglang</a> explores novel KV-Cache management algorithm which requires <code class="language-plaintext highlighter-rouge">page_size=1</code>, and the performance could benefit from FlashInfer’s optimization.</p>

<h2 id="remarks-and-future-work">Remarks and Future Work</h2>

<p>The idea of splitting KV-Cache on sequence dimension to increase parallelism was also explored in  <a href="https://crfm.stanford.edu/2023/10/12/flashdecoding.html">Flash-Decoding</a>, FlashInfer implemented this idea concurrently, see our <a href="https://github.com/flashinfer-ai/flashinfer/tree/2977506bad2b49727a65e04211373f53816432ee">github checkpoint on Sept 1st, 2023</a> and <a href="https://youtu.be/GcbuODb51Sc?feature=shared&amp;t=1570">our public talk at TVM Unity Open Development Meeting on Sept 5th, 2023</a>.</p>

<p>Currently FlashInfer only supports NVIDIA GPUs, the AMD and Apple GPU version of FlashInfer have been initially supported in <a href="https://github.com/mlc-ai/mlc-llm">MLC-LLM</a> project with the help of <a href="https://github.com/apache/tvm">Apache TVM</a> compiler. Our next release will include the 4-bit fused dequantize+attention operators proposed in <a href="https://github.com/efeslab/Atom/">Atom</a> and LoRA operators used in <a href="https://github.com/punica-ai/punica">Punica</a>. In a longer term, we are interested in performance optimization on post-Hopper NVIDIA GPUs and AMD/Apple GPUs, and new operators from emerging LLM architectures. Please check our <a href="https://github.com/flashinfer-ai/flashinfer/issues?q=is%3Aopen+is%3Aissue+label%3Aroadmap">roadmap</a> for development plans, and leave your suggestions on what features you want to see in FlashInfer.</p>

<h2 id="acknowledgement">Acknowledgement</h2>

<p>FlashInfer is inspired by <a href="https://arxiv.org/abs/2307.08691">FlashAttention 2</a>, <a href="https://github.com/vllm-project/vllm">vLLM</a>, <a href="https://github.com/NVIDIA/cutlass">cutlass</a> and <a href="https://arxiv.org/abs/2301.03598">Stream-K</a> project.</p>

<p>This blog post is written by <a href="https://homes.cs.washington.edu/~zhye/">Zihao Ye</a>. We thank the entire FlashInfer team for their contributions to the project:</p>
<ul>
  <li>Zihao Ye (UW): design and implementation of FlashInfer</li>
  <li>Lequn Chen (UW): page table data structure design, API design, CI/CD and Punica integration</li>
  <li>Ruihang Lai (CMU): KV-Cache design, API design and integration with MLC-LLM</li>
  <li>Yilong Zhao (UW &amp; SJTU): int4 attention operators</li>
  <li>Size Zheng (UW &amp; PKU): CUDA optimizations and speculative decoding</li>
  <li>Junru Shao and Yaxing Cai (OctoAI): MLC-LLM integration</li>
  <li>Bohan Hou and Hongyi Jin (CMU): porting FlashInfer to AMD and Mac GPUs with Apache TVM</li>
  <li>Liangsheng Yin (SJTU &amp; LMSys): PyTorch bindings and sglang integration.</li>
  <li>Yifei Zuo (UW &amp; USTC): PyTorch bindings</li>
  <li>Tianqi Chen (CMU &amp; OctoAI): recursive form of softmax/attention merge and advices</li>
  <li>Luis Ceze (UW &amp; OctoAI): performance breakdown analysis and advices</li>
</ul>

<p>We also thank Masahiro Masuda (OctoAI), Yixin Dong (UW &amp; SJTU), Roy Lu (UW), Chien-Yu Lin (UW), Ying Sheng (Stanford &amp; LMSys) and Lianmin Zheng (Berkeley &amp; LMSys) for their valuable feedbacks and discussions.</p>

<h2 id="citation">Citation</h2>

<div class="language-bibtex highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">@misc</span><span class="p">{</span><span class="nl">flashinfer</span><span class="p">,</span>
    <span class="na">title</span> <span class="p">=</span> <span class="s">{Accelerating Self-Attentions for LLM Serving with FlashInfer}</span><span class="p">,</span>
    <span class="na">url</span> <span class="p">=</span> <span class="s">{https://flashinfer.ai/2024/02/02/introduce-flashinfer.html}</span><span class="p">,</span>
    <span class="na">author</span> <span class="p">=</span> <span class="s">{Ye, Zihao and Chen, Lequn and Lai, Ruihang and Zhao, Yilong and Zheng, Size and Shao, Junru and Hou, Bohan and Jin, Hongyi and Zuo, Yifei and Yin, Liangsheng and Chen, Tianqi and Ceze, Luis}</span><span class="p">,</span>
    <span class="na">month</span> <span class="p">=</span> <span class="s">{February}</span><span class="p">,</span>
    <span class="na">year</span> <span class="p">=</span> <span class="s">{2024}</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="footnotes">Footnotes</h2>
<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1" role="doc-endnote">
      <p><a href="https://le.qun.ch/en/blog/2023/05/13/transformer-batching/">Dissecting Batching Effects in GPT Inference</a> by Lequn Chen <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>Zihao Ye (UW), Lequn Chen (UW), Ruihang Lai (CMU), Yilong Zhao (UW), Size Zheng (UW &amp; PKU), Junru Shao (OctoAI), Bohan Hou (CMU), Hongyi Jin (CMU), Yifei Zuo (UW &amp; USTC), Liangsheng Yin (SJTU &amp; LMSys), Tianqi Chen (CMU &amp; OctoAI), Luis Ceze (UW &amp; OctoAI)</name></author><summary type="html"><![CDATA[]]></summary></entry><entry><title type="html">Cascade Inference: Memory Bandwidth Efficient Shared Prefix Batch Decoding</title><link href="https://flashinfer.ai/2024/02/02/cascade-inference.html" rel="alternate" type="text/html" title="Cascade Inference: Memory Bandwidth Efficient Shared Prefix Batch Decoding" /><published>2024-02-02T00:00:00+00:00</published><updated>2024-02-02T00:00:00+00:00</updated><id>https://flashinfer.ai/2024/02/02/cascade-inference</id><content type="html" xml:base="https://flashinfer.ai/2024/02/02/cascade-inference.html"><![CDATA[<p>Many LLM inference tasks involves multiple independent text generation from a shared prefix (prompt), e.g. <a href="https://arxiv.org/abs/2203.11171">Self-Consistency</a>, <a href="https://arxiv.org/abs/2305.10601">Tree of Thoughts</a> and <a href="https://arxiv.org/abs/2307.15337">Skeleton-of-thought</a>. Serving LLMs with common prefix could be memory and time-consuming, especially when common prefix is long and the number of requests is large: a possible use case is long document QA (Figure 1), multiple users interacts with ChatBot with the same document as prompt. While <a href="https://arxiv.org/abs/2309.06180">vLLM</a> alleviate the memory issue by only storing one copy of the common prefix. However, it still suffers from the low-efficiency because the default PageAttention implementation do not optimize KV-Cache access to the shared prompt.</p>

<p>In this blog post, we introduce Cascade Inference, which simply decouples attention of shared prefix and unique suffixes, and enables storing shared KV-Cache in GPU shared memory (SMEM for short) for fast access in multiple requests. We show that Cascade Inference can greatly accelerate shared-prefix batch decoding operator, with up to 31x speedup compared to the baseline vLLM PageAttention implementation and 26x speedup compared to FlashInfer batch decoding operator without cascading on a H100 SXM 80GB. The kernels have been supported in <a href="https://github.com/flashinfer-ai/flashinfer/">FlashInfer</a> as <a href="https://docs.flashinfer.ai/api/python/cascade.html#cascade-attention">PyTorch</a> and C++ APIs.</p>

<p align="center">
<img src="/assets/imgs/document-qa-serving.png" alt="Document QA Serving" width="800" />
<br />
Figure 1. An example of serving Document QA for multiple users, all of the requests share the same book as prompt.
</p>

<h2 id="background">Background</h2>

<p>GPU’s memory hierarchy is composed of global memory, L2 Cache, SMEM/L1 Cache and registers. While global memory and L2 Cache is shared across all streaming multiprocessors (SMs), SMEM/L1 Cache and registers are private to each SM. The throughput of accessing global memory and L2 Cache is much lower than that of accessing SMEM/L1 Cache and registers. Therefore, it is important to minimize the access to global memory and L2 Cache to achieve high throughput.</p>

<p>In CUDA programs, independent tasks are dispatched to different thread blocks <sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">1</a></sup>, and each thread block is executed by one SM. For pre-Hopper architectures <sup id="fnref:2" role="doc-noteref"><a href="#fn:2" class="footnote" rel="footnote">2</a></sup>, each thread block can only access its local SMEM and registers.</p>

<h3 id="difference-between-multi-query-and-single-query-cuda-kernels">Difference between Multi-Query and Single-Query CUDA Kernels</h3>

<p>In multi-query attention (used in prefill/append) kernels, multiple queries access the same region of KV-cache. The usual implementation of multi-query attention kernel process multiple queries in a single thread block and loads KV-Cache to shared memory and computes the attention between multiple queries and the KV-Cache in parallel. This approach is bandwidth-efficient and can maximize TFLOPs/s by utilizing Tensor Cores, but not applicable if the KV-Cache for different queries are different.</p>

<p>The single-query attention kernel (used in decode), on the other hand, assumes that each query has its own KV-Cache, and batching cannot increase the operational intensity of this operator. In this case, there will be no benefit of processing multiple queries in a same thread block because the opportunity of reusing KV-Cache is limited. Most implementations of decode attention kernel process one query in a single thread block, to guarantee parallelism so that all SMs are fully utilized. However, this approach is not memory bandwidth efficient because each thread block needs to load the KV-Cache from global memory (or L2 cache, if the cache line has been hit before).</p>

<h2 id="divide-and-conquer">Divide and Conquer</h2>

<p>Neither multi-query attention nor single-query attention kernel is a good fit for shared-prefix batch decoding. However, multi-query attention is perfect for attention between queries and shared prefix, while single-query attention can deal with the attention between queries and unique suffixes. Can we combine the advantages of both approaches?</p>

<h3 id="recursive-attention">Recursive Attention</h3>

<p>The answer is “yes” if we can find a way to “merge” the attention of the same queries with shared prefix and unique suffixes. Fortunately, FlashAttention has shown it’s possible to combine local
softmax/attention results by not only storing the local attention result, but also the normalization scales and renormalizing local attention results on the fly. Here we formulate the idea in concise notations:</p>

<p>Suppose $s_i$ is the pre-softmax attention score between query and the key at index $i$:</p>

\[s_i = \mathbf{q}\mathbf{k}^T_i,\]

<p>we can generalize the definition from single index to an index set:</p>

\[s(I) = \log\left(\sum_{i\in I} \exp(s_i) \right),\]

<p>let’s also generalize the value vector $\mathbf{v}$ from index to index sets (Note that the generalization of both $s$ and $\mathbf{v}$ are self-consistent: when $I$ equals $\{i\}$, we have $s(I) = s_i$ and $\mathbf{v}(I) = \mathbf{v}_i$):</p>

\[\mathbf{v}(I) = \sum_{i\in I}\textrm{softmax}(s_i) \mathbf{v}_i = \frac{\sum_{i\in I}\exp\left(s_i\right)\mathbf{v}_i}{\exp(s(I))},\]

<p>the $\textrm{softmax}$ function is restricted to the index set $I$. Note that $\mathbf{v}(\{1,2,\cdots, n\})$ is the self-attention output of the entire sequence. The <strong>attention state</strong> between a query with KV of an index set $I$ can be defined as a tuple $\begin{bmatrix}\mathbf{v}(I) \\ s(I)\end{bmatrix}$,
then we can define a binary <strong>merge</strong> operator $\oplus$ to combine two states as (in practice we will minus $s$ with maximum value to guarantee numerical stability and here we omit the trick for simplicity):</p>

\[\begin{bmatrix}\mathbf{v}(I\cup J)\\s(I\cup J)\end{bmatrix}=\begin{bmatrix}\mathbf{v}(I)\\s(I)\end{bmatrix}\oplus\begin{bmatrix}\mathbf{v}(J)\\s(J)\end{bmatrix}=\begin{bmatrix} \frac{\mathbf{v}(I)\exp(s(I)) + \mathbf{v}(J)\exp(s(J))}{\exp(s(I)) + \exp(s(J))} \\  \log(\exp(s(I)) + \exp(s(J))) \end{bmatrix},\]

<p>the <strong>merge</strong> operator can be generalized to any number of attention state inputs:</p>

\[\begin{bmatrix}\mathbf{v}(\bigcup_{i=1}^{n}I_i) \\ s(\bigcup_{i=1}^{n}I_i) \end{bmatrix} = \bigoplus_{i=1}^{n}\begin{bmatrix}\mathbf{v}(I_i) \\ s(I_i)\end{bmatrix} = \begin{bmatrix} \sum_{i=1}^{n} \textrm{softmax}(s(I_i))\mathbf{v}(I_i) \\ \log(\sum_{i=1}^{n} \exp (s(I_i))) \end{bmatrix}\]

<p>The above n-ary merge operator is consistent with the binary merge operator, and we can prove the operator is <em>communicative</em> and <em>associative</em>. There are different ways to get the attention state of the entire sequence by merging the attention states of index subsets, and the final outcome is mathematically equivalent:</p>

<p align="center">
<img src="/assets/imgs/recursive-attention.png" alt="recursive-attention" width="800" />
<br />
Figure 2. Different order to merge attention states are mathematically equivalent.
</p>

<p>Recursive Attention allow us to decompose attention computation into multiple stages, different stages
can be dispatched to different compute units/devices. The KV sequence partitioning trick in FlashInfer and Flash-Decoding uses the same idea to merge partial attention states from different thread blocks.</p>

<h3 id="cascade-inference-the-algorithm">Cascade Inference: The Algorithm</h3>

<p>With the merge operator, we can dispatch attention on different KV subsets to different kernel implementations. For shared-prefix batch decoding attention,
we propose the following Divide-and-Conquer algorithm:</p>
<ol>
  <li>Use multi-query (prefill/append) attention kernel to compute the attention state between queries and KV-Cache of shared prefix.</li>
  <li>Use batch decode attention kernel to compute the attention state between queries and KV-Cache of unique suffixes.</li>
  <li>Use merge operator to combine two attention states to get the final attention output.</li>
</ol>

<p>The overall workflow is explained on the left side of Figure 3, different color of rectangles are processed in different thread blocks in GPU. Note that for multi-query attention kernels, we access KV-Cache through SMEM or registers and for decode kernels we can only access KV-Cache through L2 Cache or Global Memory. Cascade Inference allow us to maximize memory reuse for common prefix, thus making the attention computation much more memory efficient.</p>

<p align="center">
<img src="/assets/imgs/cascade-inference.png" alt="Cascade Inference" width="800" />
<br />
Figure 3. Workflow of Cascade Inference, throughput values adapted from blog: <a href="https://khairy2011.medium.com/tpu-vs-gpu-vs-cerebras-vs-graphcore-a-fair-comparison-between-ml-hardware-3f5a19d89e38">TPU vs GPU vs Cerebras vs Graphcore: A Fair Comparison between ML Hardware</a>
</p>

<p>We call the divide-and-conquer approach for shared-prefix attention the “Cascade Inference”.</p>

<h2 id="evaluations">Evaluations</h2>

<p>We evaluate Cascade Inference on H100 SXM 80GB and A100 PCIE 80GB GPUs. The input shape are adapted from LLaMA2-7B (32 heads, 128 dimension per head). We varies three parameters: number of requests (batch size), shared prefix length and unique suffix length per request. The baseline implementations is PageAttention kernel implemented in vLLM 0.2.6, we also show the performance of FlashInfer batch decoding operator without cascading. The page size (or block size, equivalently) is fixed to 16 for all implementations (FlashInfer w/ and w/o cascading, vLLM PageAttention).</p>

<p align="center">
<img src="/assets/imgs/cascade-inference-performance-h100.png" alt="speedup-h100" width="800" />
<br />
Figure 4. Speedup over vLLM PageAttention on H100 SXM 80GB
</p>

<p align="center">
<img src="/assets/imgs/cascade-inference-performance-a100.png" alt="speedup-a100" width="800" />
<br />
Figure 5. Speedup over vLLM PageAttention on A100 PCIe 80GB
</p>

<p>Figure 4 and 5 show the normalized performance on FlashInfer kernels in cascading and non-cascading setting
over vLLM implementation. FlashInfer kernels in both settings outperforms vLLM kernels, and cascading kernels significant speedup over non-Cascade Inference kernels in most cases.
The benefit of cascade inference increases as shared prefix length and batch size grows (where the prefill kernel dominates execution time) and decreases as we increase unique suffix length (where the batch decode kernel dominates execution time). For very long shared prompt (32768), the decode kernel can get up to 31x speedup on H100 SXM 80GB with large batch size(≥128) and short unique kv-length (≤256).</p>

<h2 id="remarks-and-future-work">Remarks and Future Work</h2>

<p>The idea of Cascade Inference can be generalized to multiple levels (we only show two levels in this blog post) and multiple shared prefixes, the multi-level, multi shared-prefix Cascade Inference has been integrated to MLC-Serving: the universal serving framework based on <a href="https://github.com/mlc-ai/mlc-llm">MLC-LLM</a>, we will show the end-to-end speedup in future blog posts.</p>

<p>Recently, <a href="https://arxiv.org/abs/2312.07104">SGLang</a> (a domain-specific language for programming LLMs) proposes RadixAttention, where the KV-Cache is organized as a radix tree structure and the attention can be further accelerated with multiple-level Cascade Inference. We are collaborating with SGLang team to get this feature landed.</p>

<h2 id="citation">Citation</h2>

<div class="language-bibtex highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">@misc</span><span class="p">{</span><span class="nl">cascade-inference</span><span class="p">,</span>
    <span class="na">title</span> <span class="p">=</span> <span class="s">{Cascade Inference: Memory Bandwidth Efficient Shared Prefix Batch Decoding}</span><span class="p">,</span>
    <span class="na">url</span> <span class="p">=</span> <span class="s">{https://flashinfer.ai/2024/02/02/cascade-inference.html}</span><span class="p">,</span>
    <span class="na">author</span> <span class="p">=</span> <span class="s">{Ye, Zihao and Lai, Ruihang and Lu, Bo-Ru and Lin, Chien-Yu and Zheng, Size and Chen, Lequn and Chen, Tianqi and Ceze, Luis}</span><span class="p">,</span>
    <span class="na">month</span> <span class="p">=</span> <span class="s">{February}</span><span class="p">,</span>
    <span class="na">year</span> <span class="p">=</span> <span class="s">{2024}</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="footnotes--references">Footnotes &amp; References</h2>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1" role="doc-endnote">
      <p>thread block: the programming abstraction that represents a group of cooperative threads, one SM can execute multiple thread blocks and one thread block cannot span multiple SMs. <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:2" role="doc-endnote">
      <p><a href="https://resources.nvidia.com/en-us-tensor-core">Hopper architecture</a> introduces a new abstraction called Thread Block Clusters which enables a thread block to access shared memory of other thread blocks within the same SM. Hopper also supports direct SM-to-SM communication without accessing global memory (a.k.a. Distributed Shared Memory), which can greatly accelerate cross-SM communication. However, these features are not available in pre-Hopper architectures such as A100 GPUs. <a href="#fnref:2" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>Zihao Ye (UW), Ruihang Lai (CMU), Bo-Ru Lu (UW), Chien-Yu Lin (UW), Size Zheng (UW &amp; PKU), Lequn Chen (UW), Tianqi Chen (CMU &amp; OctoAI), Luis Ceze (UW &amp; OctoAI)</name></author><summary type="html"><![CDATA[Many LLM inference tasks involves multiple independent text generation from a shared prefix (prompt), e.g. Self-Consistency, Tree of Thoughts and Skeleton-of-thought. Serving LLMs with common prefix could be memory and time-consuming, especially when common prefix is long and the number of requests is large: a possible use case is long document QA (Figure 1), multiple users interacts with ChatBot with the same document as prompt. While vLLM alleviate the memory issue by only storing one copy of the common prefix. However, it still suffers from the low-efficiency because the default PageAttention implementation do not optimize KV-Cache access to the shared prompt.]]></summary></entry></feed>