<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>SIRU HE</title>
  <subtitle>SIRU HE — LLM Inference / AI Infra engineer. Notes on CUDA kernels, inference engines, and low-level AI systems.</subtitle>
  <link href="https://frank-2077.github.io/feed.xml" rel="self"/>
  <link href="https://frank-2077.github.io/"/>
  <updated>2026-09-16T17:07:00+08:00</updated>
  <id>https://frank-2077.github.io/</id>
  <author>
    <name>SIRU HE</name>
    <email>siruhe666@gmail.com</email>
  </author>
  
  
  <entry>
    <title>vLLM 0.14 框架梳理：调度、内存管理与计算</title>
    <link href="https://frank-2077.github.io/2026/09/vllm-scheduling-kvcache-compute/"/>
    <updated>2026-09-15T10:00:00+08:00</updated>
    <id>https://frank-2077.github.io/2026/09/vllm-scheduling-kvcache-compute/</id>
    <content type="html">&lt;p&gt;这是 vLLM 0.14 源码梳理的第一部分，聚焦三件事：&lt;strong&gt;请求如何被调度、KV Cache 如何被管理、以及这些数据最终在计算时如何被使用&lt;/strong&gt;。&lt;/p&gt;

&lt;h2 id=&quot;一请求如何进入-enginecore&quot;&gt;一、请求如何进入 EngineCore&lt;/h2&gt;

&lt;p&gt;接上一章，req 进入到 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;EngineCoreProc&lt;/code&gt; 之后，有一个 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CoreEngine&lt;/code&gt; 类在他的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;__init__&lt;/code&gt; 函数中会调用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;target_fn = EngineCoreProc.run_engine_core&lt;/code&gt;，而这个函数就是 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;EngineCoreProc(EngineCore)&lt;/code&gt; 中的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;run_engine_core&lt;/code&gt;，最终的效果就是调用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;scheduler.add_request(req)&lt;/code&gt; 加入 waiting 队列。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;涉及文件：&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;core_client.py&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;core.py&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/vllm/fig1-enginecore.png&quot; alt=&quot;图一：CoreEngine 与 EngineCoreProc 的调度入口&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;二每一步-step-在做什么&quot;&gt;二、每一步 step 在做什么&lt;/h2&gt;

&lt;p&gt;接下来是关于 engine 的 step，每一步 step 都是：&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;scheduler.schedule() + model_executor.execute_model(scheduler_output) + scheduler.update_from_output
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;如图二，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;schedule()&lt;/code&gt; 函数就是在调度 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Scheduler&lt;/code&gt; 这个类中 waiting、running 两个 req 队列。waiting 队列就是还没有分配 KV Cache 的（包含被抢占的），running 就是已经被分配了 KV Cache 的。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;先调度 running 队列&lt;/strong&gt;（优先级更高），然后取出第一个 req，计算这个 req 当前 step 需要多少新 token：如果是 decode 就是 1（假设不开启投机解码），prefill 就是所有 prompt 的 token。然后用计算出来的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;num_new_tokens&lt;/code&gt; 去计算需要分配的新的 block：&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;new_blocks&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;kv_cache_manager&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;allocate_slots&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(...)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;然后将这个当前 req 新分配的 block 记录在 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;req_to_new_block_ids&lt;/code&gt;，这是一个 dict，记录了每个 req 当前 step 的 block。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;如果在 running 过程中发生抢占&lt;/strong&gt;，也就是显存不足够分配 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;new_blocks&lt;/code&gt; 了，那么将这个 req 放入 waiting 队列的第一个，并且 free 他所对应的 block。&lt;/p&gt;

&lt;p&gt;当 running 队列的 req 调度完成之后，调度 waiting 队列（前提是不发生抢占，抢占时是显存不足了，就没必要再调用 waiting 队列了），也是一样的过程。但 &lt;strong&gt;waiting 队列中有一个前缀匹配过程&lt;/strong&gt;：首先查看当前 waiting 队列的 reqs 的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;token_ids&lt;/code&gt; 是否与已缓存的 block 完全匹配？如果有匹配我们则会直接使用已匹配 block 的 KV Cache 用来计算，并且 input 变成第一个不匹配 block 的第一个 token。&lt;/p&gt;

&lt;p&gt;前缀匹配中不仅是 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;token_ids&lt;/code&gt; 要完全相同，而是要求 &lt;strong&gt;hash 值完全相同&lt;/strong&gt;（没有存满 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;block_size&lt;/code&gt; 不计算 hash），而当前 block 的 hash 计算不仅仅依赖当前 block 的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;token_ids&lt;/code&gt;，也依赖上一块的 hash。也就是说如果第一个 block 不 match，后续的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;token_ids&lt;/code&gt; 完全一样也无法匹配。（「你喜不喜欢吃苹果」，「他喜不喜欢吃苹果」，第一个 token 已经不 match，后续全部一样也没用了。）&lt;/p&gt;

&lt;p&gt;前缀匹配之后，也是用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;num_new_tokens&lt;/code&gt; 去计算 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;new_blocks&lt;/code&gt;，然后再记录在 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;req_to_new_block_ids&lt;/code&gt; 中。最后将这些 waiting、running、preempt 的请求打包输出为 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;scheduler_output = SchedulerOutput()&lt;/code&gt;。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;涉及文件：&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;scheduler.py&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;kv_cache_manager.py&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/vllm/fig2-scheduler.png&quot; alt=&quot;图二：Scheduler.schedule 的调度流程&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;三block-与-block_table&quot;&gt;三、block 与 block_table&lt;/h2&gt;

&lt;p&gt;如图三，这一节我们先不讲刚刚打包好的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;scheduler_output&lt;/code&gt; 会去到哪里，而是详细了解 &lt;strong&gt;block、block_table&lt;/strong&gt; 这些概念。&lt;/p&gt;

&lt;p&gt;在刚刚的 schedule 中有使用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;allocate_slots&lt;/code&gt; 函数，而这个函数就是给 req 的 new_token 分配 block 的函数，然后记录在这个 dict 中 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;req_blocks.extend(new_blocks)&lt;/code&gt;，他记录了每个 req 对应了哪个 block！它内部会调用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;get_new_blocks&lt;/code&gt; 函数真正给这个 req 分配 block。&lt;/p&gt;

&lt;p&gt;这里似乎有点问题：这个类负责分配 block，但是 block 从哪里来的我们似乎还是不知道。接下来我们就继续深入。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;涉及文件：&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;kv_cache_manager.py&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;block_pool.py&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/vllm/fig3-kvcache-manager.png&quot; alt=&quot;图三：KVCacheManager 的 block 分配&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;四block-从哪来kv-cache-的初始化&quot;&gt;四、block 从哪来：KV Cache 的初始化&lt;/h2&gt;

&lt;p&gt;在 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;KVCacheManager&lt;/code&gt; 的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;__init__&lt;/code&gt; 函数中我们发现有这么一个字段：&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;num_gpu_blocks&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;kv_cache_config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;num_blocks&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这个表示 GPU 有多少 block，那这个数据又是怎么来的呢？&lt;/p&gt;

&lt;p&gt;如图四，在 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;EngineCore&lt;/code&gt; 的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;__init__&lt;/code&gt; 中，我们发现了一个 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_initialize_kv_caches&lt;/code&gt; 函数，这个函数首先获取了模型每一层的 attention 的规格 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;kv_cache_specs&lt;/code&gt;（比如是 full 还是 sliding window attn），接下来调用了 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;model_executor.determine_available_memory()&lt;/code&gt; 来获取供给 KV Cache 的空闲的显存 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;available_gpu_memory&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;然后我们用获取的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;kv_cache_specs&lt;/code&gt; 和 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;available_gpu_memory&lt;/code&gt; 得到了 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;kv_cache_configs&lt;/code&gt; 这个参数，它包含了每层分配的 block 数量、KV Cache tensor 的 shape，也就是这个函数 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_get_kv_cache_config_uniform_type&lt;/code&gt;——它获取模型层数，计算每层所需的字节大小，并且将规格分 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;kv_cache_groups&lt;/code&gt;；最后调用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;self.model_executor.initialize_from_config(kv_cache_configs)&lt;/code&gt;，将刚刚关于对 KV Cache 的配置描述变成实际的显存！&lt;/p&gt;

&lt;p&gt;这个函数中调用：&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;kv_caches&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;layer_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;torch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;zeros&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;kv_cache_shape&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dtype&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dtype&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;device&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;device&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;也就是先将每层 attention 内存先分配好，后续只需直接使用这个地址读取写入即可。到这里那我们就知道了 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;KVCacheManager&lt;/code&gt; 中的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;num_gpu_blocks&lt;/code&gt; 是什么了。&lt;/p&gt;

&lt;p&gt;关于 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;KVCacheManager&lt;/code&gt; 是如何管理 block 其实就比较简单了，用了一个 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;BlockPool&lt;/code&gt; 类，这个类中有：&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;blocks&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;list&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;KVCacheBlock&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;KVCacheBlock&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;idx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;idx&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;num_gpu_blocks&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;也就是每一个 block 都是一个 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;KVCacheBlock&lt;/code&gt; 对象，然后构建一个 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;free_block_queue&lt;/code&gt; 双向链表管理每个 block。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;涉及文件：&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;kv_cache_utils.py&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;kv_cache_manager.py&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;core.py&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gpu_model_runner.py&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gpu_worker.py&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;block_pool.py&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/vllm/fig4-init-kvcache.png&quot; alt=&quot;图四：EngineCore 初始化 KV Cache&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;五block_table-与-slot_mapping-如何被使用&quot;&gt;五、block_table 与 slot_mapping 如何被使用&lt;/h2&gt;

&lt;p&gt;好的，我们现在已经知道了 block 是怎么来的、怎么管理的，那么它是如何使用的呢？接下来就继续接着 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;scheduler_output&lt;/code&gt; 继续下一步。&lt;/p&gt;

&lt;p&gt;在图一中 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;model_executor.execute_model(scheduler_output)&lt;/code&gt; 这个函数使用了 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;scheduler_output&lt;/code&gt;，那我们最终追溯到 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GPUModelRunner.execute_model(SchedulerOutput)&lt;/code&gt; 这个函数。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;这里面涉及 Worker / Executor 的概念，这里就不细讲了。多机多卡情况下，「机」我认为就是 Executor，「卡」就是对应 Worker，Executor / Worker 的通信方式使用共享内存，一写多读，多卡通信方式使用 NCCL，使用 AllReduce 同步——这部分不太了解了。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;如图五，在模型真正执行 forward 之前还有 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_update_states&lt;/code&gt; 和 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_prepare_inputs&lt;/code&gt; 两个函数。这两个函数理解可以比较简单，但是实际逻辑却有点复杂，特别是 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_prepare_inputs&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;对于 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_update_states&lt;/code&gt;，我们在图中只写了关于 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;block_table&lt;/code&gt; 部分，其实也就是更新这些 req 的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;block_table&lt;/code&gt;，因为有可能在当前 step 和上一个 step 调度的 req 有可能完全不同，有可能相同的 req 也添加了 block，有可能有些 req 被抢占了，那么他的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;block_table&lt;/code&gt; 和上一个 step 完全不同。&lt;/p&gt;

&lt;p&gt;对于 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_prepare_inputs&lt;/code&gt;，也就是准备 forward 函数之前的准备：&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;首先是关于 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;input_ids&lt;/code&gt;&lt;/strong&gt;，也就是这一个 step 的 reqs 的 query（当然还需要经过 embedding、Wq 矩阵乘），需要准备好这些 reqs 当前 step 对应的 position 是多少？每个 req 在 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;input_ids&lt;/code&gt; 中对应的范围是多少？整个序列的长度 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;seq_len&lt;/code&gt; 是多少？（读取 KV Cache 用）&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;还需要准备好 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;slot_mapping&lt;/code&gt;&lt;/strong&gt;，这个是关于当前 reqs 的 token 对应的 KV Cache 该写入在哪个 block 的哪个槽位。我们用一个例子作为解释：&lt;/p&gt;

&lt;p&gt;当前 step 调度 3 个 reqs &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[2, 5, 3]&lt;/code&gt;，第一个 req 有 2 个 token，第二个 req 新增 5 个 token，第三个 req 新增 3 个 token，然后变成：&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;[0, 0, 1, 1, 1, 1, 1, 2, 2, 2]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这种形式，表示 10 个 token，并且 0 就表示 req[0]，1 就表示 req[1]，2 就表示 req[2]，然后再变成：&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;[0, 1, 0, 1, 2, 3, 4, 0, 1, 2]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;表示每个 token 在当前 step 的第几个 token。这里我们假设每个 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;block_size = 2&lt;/code&gt;（每个 block 存储的大小），那么 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[0, 1]&lt;/code&gt; 对于 req 就可以放入同一个 block 中。并且对于每个 req 都会&lt;strong&gt;预分配&lt;/strong&gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;max_num_blocks_per_req&lt;/code&gt; 这么多个 block（假设这个值为 K），也就是对于 req[1] 的 0, 1, 2, 3, 4 并不是紧挨着 req[0] 的 0, 1 来存储的，他们之间隔了 K 个 block：&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;[0, 0, K, K, K + 1, K + 1, K + 2, 2 * K, 2 * K, 2 * K + 1]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这样我们就能知道每个 req 的 token 对应的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;block_id&lt;/code&gt; 是多少了。接下来，我们用刚刚得到绝对位置 position 计算出 offset，这样我们就能知道 req 的 token 对应的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;block_numbers * block_size + block_offset&lt;/code&gt;，我们就能知道这个 token 具体要写入的具体地址 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;slot_mapping&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;然后将这些数据创建一个 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;attn_metadata&lt;/code&gt;，通过线程局部变量传递给模型，这样就能使用了。&lt;/p&gt;

&lt;p&gt;好的，到这 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;block_table&lt;/code&gt; 是如何使用的了，也知道 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;slot_mapping&lt;/code&gt; 是如何计算的了。但是如果我们还想更加深入了解模型到底在哪里用了这些数据，attention 到底如何从中写入或者读取数据？那么接下来我们将了解模型如何执行的。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;涉及文件：&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gpu_model_runner.py&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/vllm/fig5-execute-model.png&quot; alt=&quot;图五：GPUModelRunner.execute_model 与 slot_mapping 的计算&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;六attention-在哪里真正使用-block_table-与-slot_mapping&quot;&gt;六、attention 在哪里真正使用 block_table 与 slot_mapping&lt;/h2&gt;

&lt;p&gt;如图六，首先我们先解释刚刚的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;attn_metadata&lt;/code&gt; 源码构造中好像并没有传入 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;slot_mapping&lt;/code&gt;，那他们是如何被模型使用的呢？&lt;/p&gt;

&lt;p&gt;在图六左边中我们发现，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GPUModelRunner&lt;/code&gt; 的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;__init__&lt;/code&gt; 时，调用了：&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;attn_backend&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;get_attn_backend&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;attn_metadata_builder&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;attn_backend&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get_builder_cls&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;weakref&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;proxy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这里的参数将 self（也就是 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GPUModelRunner&lt;/code&gt;）传入，也就是为什么刚刚调用 build 函数时，我们可以获取 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;block_table&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;slot_mapping&lt;/code&gt; 这些数据。&lt;/p&gt;

&lt;p&gt;然后我们以 &lt;strong&gt;Qwen3 MoE Flash Attention&lt;/strong&gt; 为 backend 举一个例子，详细描述最后在哪里使用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;block_table&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;slot_mapping&lt;/code&gt; 的。&lt;/p&gt;

&lt;p&gt;当调用 Qwen3 MoE 模型的 forward 时，首先将调用每一层 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Qwen3MoeDecoderLayer&lt;/code&gt; 的 forward，然后再到 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Qwen3MoeAttention&lt;/code&gt; 的：&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;attn_output&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;attn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;q&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;k&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;v&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;但是这里的 self 是 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Attention&lt;/code&gt; 类，而 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Attention&lt;/code&gt; 类又会在 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;__init__&lt;/code&gt; 时选择后端，也就是调用：&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Attention 类的 forward == self.impl.forward() == FlashAttentionImpl.forward
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;我们在 flash attn 的 forward 中可以看到：&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;torch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ops&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_C_cache_ops&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;reshape_and_cache_flash&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这么一个函数，也就是它最后在使用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;block_table&lt;/code&gt; 和 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;slot_mapping&lt;/code&gt;！他的源文件是用 CUDA 写的。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;涉及文件：&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gpu_model_runner.py&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;flash_attn.py&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;qwen3_moe.py&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;layer.py&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/vllm/fig6-attention-backend.png&quot; alt=&quot;图六：attention 后端对 block_table 与 slot_mapping 的最终使用&quot; /&gt;&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>CUDA 访存优化：从合并访存到双缓冲流水</title>
    <link href="https://frank-2077.github.io/2026/09/cuda-memory-access-optimization/"/>
    <updated>2026-09-10T21:30:00+08:00</updated>
    <id>https://frank-2077.github.io/2026/09/cuda-memory-access-optimization/</id>
    <content type="html">&lt;p&gt;这篇是我做 &lt;strong&gt;CUDA SGEMM 优化&lt;/strong&gt;（Ampere / Hopper）时的一点梳理：为什么访存会成为瓶颈，以及从「合并访存」到「memcpy_async 双缓冲」每一级优化背后的原理。目的是给出一条可复用的排查与优化路径，而不是一堆经验之谈。&lt;/p&gt;

&lt;h2 id=&quot;先从算术强度说起&quot;&gt;先从算术强度说起&lt;/h2&gt;

&lt;p&gt;GPU 算得快，但&lt;strong&gt;喂数据喂不快&lt;/strong&gt;。用「算术强度」（FLOPs/Byte）衡量一次访存能换回多少计算：&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;算术强度 = 总 FLOPs / 总访存字节数
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;4096×4096 的矩阵乘法，朴素实现每个元素读一次、做一次乘加，算术强度只有 0.125 FLOPs/Byte——大部分时间都花在等内存上。而优化到最后能把 SGEMM 推到 32 FLOPs/Byte，靠的不是算得更快，而是&lt;strong&gt;让同样的访存换回更多计算&lt;/strong&gt;。&lt;/p&gt;

&lt;h2 id=&quot;第一层合并访存&quot;&gt;第一层：合并访存&lt;/h2&gt;

&lt;p&gt;GPU 的内存事务以 &lt;strong&gt;32 字节的 sector&lt;/strong&gt; 为单位。如果一个 warp 里的 32 个线程访问的地址落在同一个 sector 内，就只发一次事务；散得越开，发的事务越多。&lt;/p&gt;

&lt;p&gt;规则很直接：&lt;strong&gt;让相邻线程访问相邻地址。&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&quot;language-c highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// 差：线程按列访问，跨 stride，一个 warp 被打散到多个 sector&lt;/span&gt;
&lt;span class=&quot;kt&quot;&gt;float&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;v&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;A&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;row&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;N&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;col&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// 好：线程连续访问&lt;/span&gt;
&lt;span class=&quot;kt&quot;&gt;float&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;v&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;A&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;row&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;N&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;col&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;threadIdx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;用 Nsight Compute 看 Warp 内的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Sectors/Request&lt;/code&gt;，把它从 32 降到 1，单这一步就能提速约 8 倍。这是收益最高、也最该最先做的一步。&lt;/p&gt;

&lt;h2 id=&quot;第二层shared-memory-分块&quot;&gt;第二层：Shared Memory 分块&lt;/h2&gt;

&lt;p&gt;全局内存再合并也还是慢。把要反复用的数据搬进 &lt;strong&gt;Shared Memory（SMEM）&lt;/strong&gt;，让同一块 tile 被读多次：&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;全局内存 --搬一次--&amp;gt; SMEM --读多次--&amp;gt; 寄存器计算
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;分块的关键不是 tile 越小越好，而是&lt;strong&gt;复用&lt;/strong&gt;。2D Block Tiling 让每个搬进 SMEM 的元素被消费多次，从而摊薄全局访存。&lt;/p&gt;

&lt;h2 id=&quot;第三层向量化与-bank-conflict&quot;&gt;第三层：向量化与 Bank Conflict&lt;/h2&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;float4&lt;/code&gt; 一次搬 128 bit，直接减少事务数量：&lt;/p&gt;

&lt;div class=&quot;language-c highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;float4&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;As&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;reinterpret_cast&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;float4&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;smem&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;idx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]);&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;float4&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;As&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;但 SMEM 有 32 个 bank，同一时刻多个线程访问同一个 bank 会串行化。解决办法是 &lt;strong&gt;padding&lt;/strong&gt;——每行末尾补一列，把访问模式错开：&lt;/p&gt;

&lt;div class=&quot;language-c highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;__shared__&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;float&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;smem&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;BLOCK_DIM&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;BLOCK_DIM&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;// +1 消除 bank conflict&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;第四层异步流水&quot;&gt;第四层：异步流水&lt;/h2&gt;

&lt;p&gt;到这一步为止，搬运和计算还是串行的：「搬完再算」。用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cuda::memcpy_async&lt;/code&gt; 可以提前发出&lt;strong&gt;下一块&lt;/strong&gt; tile 的搬运，让它与&lt;strong&gt;当前块&lt;/strong&gt;的计算重叠：&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;迭代 i:   算 tile[i]   |  搬 tile[i+1]
迭代 i+1: 算 tile[i+1] |  搬 tile[i+2]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;配合 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cuda::barrier&lt;/code&gt; 做到达/等待同步，就形成了一条双缓冲流水线。在 Hopper 上这一步可以升级为 &lt;strong&gt;TMA（Tensor Memory Accelerator）&lt;/strong&gt;——由单线程发起 bulk copy，连搬运的指令开销都省掉了。&lt;/p&gt;

&lt;h2 id=&quot;一条可复用的路径&quot;&gt;一条可复用的路径&lt;/h2&gt;

&lt;ol&gt;
  &lt;li&gt;看 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Sectors/Request&lt;/code&gt;——修合并访存。&lt;/li&gt;
  &lt;li&gt;看 SMEM 复用率——上分块。&lt;/li&gt;
  &lt;li&gt;向量化加载——修 bank conflict。&lt;/li&gt;
  &lt;li&gt;最后上异步流水，重叠搬运与计算。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;顺序很重要：&lt;strong&gt;先合并，再复用，最后重叠。&lt;/strong&gt; 反过来做往往会掩盖真正的瓶颈。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;这套路径在 RTX 3090 上把 4096×4096 的 SGEMM 从 548ms 压到 6.4ms，达到 cuBLAS 的 93%。Hopper 上 TMA 与 TF32 WMMA 那部分我后面另写一篇。&lt;/p&gt;
&lt;/blockquote&gt;
</content>
  </entry>
  
</feed>
