Commit f5da42e6183981b750601214ee0fc351682079ae
1 parent
ccbdf870
标注提示词优化
Showing
4 changed files
with
627 additions
and
4 deletions
Show diff stats
| @@ -0,0 +1,16 @@ | @@ -0,0 +1,16 @@ | ||
| 1 | +现在,如果前端指定按某个字段排序,以价格为例,那么ES搜索条件会加上 | ||
| 2 | + "sort": [ | ||
| 3 | + { | ||
| 4 | + "min_price": { | ||
| 5 | + "order": "asc" | ||
| 6 | + } | ||
| 7 | + } | ||
| 8 | + ], | ||
| 9 | +但是,存在以下问题: | ||
| 10 | +1. 存在多重漏斗,粗排&重排,这里又把顺序搞乱了。 | ||
| 11 | +2. 如果前端有指定排序、不走后面漏斗的话,那么,因为有knn召回,文本匹配也为了扩大召回有很多不相关召回(只有部分term匹配的),这些差结果因为强制按价格排序,不相关结果被排到了上面。 | ||
| 12 | +3. 如果还是走重排,那么最后要加一轮截取topN再按价格排序,但是topN阈值不好定。 | ||
| 13 | + | ||
| 14 | +我的思路是: | ||
| 15 | +1. ES阶段,因为有宽泛召回,不加min_price排序 | ||
| 16 | +2. 按价格排序的方案:在重排(final rank阶段),加一个“按指定字段排序”的处理流程。假设重排输出条数为K,那么看2/K~K之间是否有抖降点,从中选择一个合适的点进行重排。 | ||
| 0 | \ No newline at end of file | 17 | \ No newline at end of file |
docs/issues/issue-2026-04-04-LLM推理用于query分析定制优化-TODO.md
0 → 100644
| @@ -0,0 +1,578 @@ | @@ -0,0 +1,578 @@ | ||
| 1 | +基于LLM的AI应用,通常尽量让前缀相同,这样可以利于跨session的KVcache(而不是常规的单次会内内部的kvcache)。 | ||
| 2 | +这个已经有一些成熟的手段支持。 | ||
| 3 | +但如果要进一步做极致的优化,用于搜索引擎的query分析,对耗时要求极高,有哪些优化的方向?耗时主要体现在哪?,希望连 KV cache的加载也省去了,能再节省多少时间? Analyze the category intent of the given query. Output exactly one label from: Dress, Jeans, Shirt, Trench Coat, Skirt, T-shirt, Hoodie, Knitwear, Other. Output nothing else query:{query} 比如这个提示词是做一个query分类,前缀相同,最后的 query 不同,并且 decoder 只 decoder 一个 token,其实这个输出也不用 decode 整个词表,而是对总统的N分类算打分(不要概率)即可,这也是一个极致的优化点,除了这些,我更希望找到,如何做定制化的GPU编程,来极致的提高性能。 请搜索相关的技术资料,特别是以LLM inference方面的,深度定制化的 | ||
| 4 | + | ||
| 5 | + | ||
| 6 | +# 一、chatgpt | ||
| 7 | + | ||
| 8 | +我查了一圈最新的 LLM inference 资料后,先给一个直接结论: | ||
| 9 | + | ||
| 10 | +**对你这个场景,继续在“通用生成引擎 + prefix KV cache”上抠,收益很快会见顶。真正的大头不是 decode,而是 query 部分的 prefill、attention 访存、kernel launch、以及最后一步不必要的 vocab head。** | ||
| 11 | +更激进一点说:这已经不像“生成任务”,而更像**超短文本分类任务伪装成生成**。如果目标是搜索引擎里的 query analysis、极低时延,那最值得做的是把它改造成**专用推理路径**,甚至专用 GPU kernel。([NVIDIA GitHub][1]) | ||
| 12 | + | ||
| 13 | +--- | ||
| 14 | + | ||
| 15 | +## 1)你这个场景里,耗时主要在哪 | ||
| 16 | + | ||
| 17 | +你给的 prompt: | ||
| 18 | + | ||
| 19 | +```text | ||
| 20 | +Analyze the category intent of the given query. Output exactly one label from: Dress, Jeans, Shirt, Trench Coat, Skirt, T-shirt, Hoodie, Knitwear, Other. Output nothing else | ||
| 21 | +query:{query} | ||
| 22 | +``` | ||
| 23 | + | ||
| 24 | +它的特征是: | ||
| 25 | + | ||
| 26 | +* 前缀几乎固定 | ||
| 27 | +* `{query}` 很短 | ||
| 28 | +* 最终只需要 1 个类别 | ||
| 29 | +* decoder 实际只需要 1 step,甚至不该走完整 decode | ||
| 30 | + | ||
| 31 | +所以时延通常拆成这几块: | ||
| 32 | + | ||
| 33 | +### A. 请求侧固定开销 | ||
| 34 | + | ||
| 35 | +包括 tokenizer、请求路由、RPC/HTTP、张量准备、H2D、小量 CPU 调度。 | ||
| 36 | +在“只出 1 个 token”的场景里,这些固定开销会变得很显眼;TensorRT-LLM 甚至专门做了 **Overlap Scheduler** 来把 CPU 工作和 GPU 计算重叠,说明这部分在低时延场景里确实值得抠。([NVIDIA GitHub][2]) | ||
| 37 | + | ||
| 38 | +### B. suffix/query 的 prefill | ||
| 39 | + | ||
| 40 | +即使 prefix 命中 KV cache,**query token 仍然要完整过所有层**。 | ||
| 41 | +这是你现在的主要计算开销之一。 | ||
| 42 | + | ||
| 43 | +### C. attention 对 prefix KV 的读取 | ||
| 44 | + | ||
| 45 | +这是很多人容易忽略的一点: | ||
| 46 | + | ||
| 47 | +**prefix 复用只省掉了“prefix 自己重新计算 K/V”这部分。** | ||
| 48 | +但 query 的每个 token 仍然要对已有上下文做 attention,也就是仍然要访问 prefix 的 K/V。 | ||
| 49 | +所以 prefix 越长,query prefill 的 attention 成本仍然越高。FlashAttention/FlashInfer 这类工作一直强调 attention 是 LLM inference 的核心瓶颈,FlashAttention-3 在 H100 上就是围绕 attention 的访存与流水做了深度优化。([arXiv][3]) | ||
| 50 | + | ||
| 51 | +### D. 最后一层输出头(lm_head)+ softmax / sampling | ||
| 52 | + | ||
| 53 | +对你这种只需要 9 类标签的任务,走完整 vocab head 很浪费。 | ||
| 54 | +TensorRT-LLM 提供了自定义 logits processor,但那还是在**完整 logits 出来之后再处理**;对你而言,更极致的做法应该是**根本不算全词表 logits**。([NVIDIA GitHub][4]) | ||
| 55 | + | ||
| 56 | +--- | ||
| 57 | + | ||
| 58 | +## 2)prefix KV cache 还能省多少?“连加载都省掉”值不值 | ||
| 59 | + | ||
| 60 | +这里要分情况。 | ||
| 61 | + | ||
| 62 | +### 情况 1:KV cache 已经常驻 GPU HBM | ||
| 63 | + | ||
| 64 | +这时“加载 KV cache”本身,通常**不是最大头**。 | ||
| 65 | + | ||
| 66 | +KV cache 每 token 大小可以近似写成: | ||
| 67 | + | ||
| 68 | +[ | ||
| 69 | +\text{bytes/token} \approx 2 \times L \times H_{kv} \times D \times \text{bytes_per_elem} | ||
| 70 | +] | ||
| 71 | + | ||
| 72 | +其中: | ||
| 73 | + | ||
| 74 | +* (2) 表示 K 和 V | ||
| 75 | +* (L) 是层数 | ||
| 76 | +* (H_{kv}) 是 KV heads | ||
| 77 | +* (D) 是 head dim | ||
| 78 | + | ||
| 79 | +例如一个典型 GQA 模型,假设: | ||
| 80 | + | ||
| 81 | +* 32 层 | ||
| 82 | +* 8 个 KV heads | ||
| 83 | +* head dim = 128 | ||
| 84 | +* BF16(2 bytes) | ||
| 85 | + | ||
| 86 | +则每 token 的 KV 大约是: | ||
| 87 | + | ||
| 88 | +[ | ||
| 89 | +2 \times 32 \times 8 \times 128 \times 2 = 131072 \text{ bytes} \approx 128 \text{KB/token} | ||
| 90 | +] | ||
| 91 | + | ||
| 92 | +那么: | ||
| 93 | + | ||
| 94 | +* 200 token prefix ≈ 25 MB | ||
| 95 | +* 1000 token prefix ≈ 125 MB | ||
| 96 | + | ||
| 97 | +H100 官方给出的 HBM 带宽是 **3 TB/s**。按理想顺序读估算: | ||
| 98 | + | ||
| 99 | +* 25 MB / 3 TB/s ≈ **8 微秒** | ||
| 100 | +* 125 MB / 3 TB/s ≈ **42 微秒** | ||
| 101 | + | ||
| 102 | +实际不会这么理想,因为还有 page table、访存不连续、kernel 组织、调度开销,但结论还是一样: | ||
| 103 | + | ||
| 104 | +> **如果 KV cache 已经在 GPU 上,单纯“把 cache 从 HBM 读出来”这件事,通常只值几十到几百微秒级,往往不到 1 ms。** | ||
| 105 | + | ||
| 106 | +所以你说“希望连 KV cache 的加载也省去”,**如果这里的“加载”指 GPU 内部读取常驻 KV**,那收益通常不大。更大的头往往在 query prefill、attention 访问 prefix、kernel launch、和最后的 vocab head。([NVIDIA][5]) | ||
| 107 | + | ||
| 108 | +### 情况 2:KV cache 需要跨 GPU / 跨实例 / CPU-offload / 网络传输 | ||
| 109 | + | ||
| 110 | +这时就不一样了。 | ||
| 111 | +TensorRT-LLM 的 disaggregated serving 文档专门提到 KV cache transmission,并且要做 device-to-device 直传和 transmission overlap,说明**一旦 cache 不在本地 HBM**,传输本身会显著影响时延。这个时候省掉“加载/传输”可能就是**毫秒到十几毫秒**级别。([NVIDIA GitHub][6]) | ||
| 112 | + | ||
| 113 | +所以一句话总结: | ||
| 114 | + | ||
| 115 | +* **本地 GPU 常驻 KV**:再抠“cache 加载”通常只剩 **sub-ms** | ||
| 116 | +* **跨设备/跨实例/CPU-offload**:可能还能省 **ms~10ms+** | ||
| 117 | + | ||
| 118 | +--- | ||
| 119 | + | ||
| 120 | +## 3)对你这个任务,最值得做的优化方向 | ||
| 121 | + | ||
| 122 | +我按“收益/工程代价比”排序。 | ||
| 123 | + | ||
| 124 | +### 方向 1:别再把它当生成,改成“最后 token hidden state → 分类头” | ||
| 125 | + | ||
| 126 | +这是最重要的。 | ||
| 127 | + | ||
| 128 | +Causal LLM 做 sequence classification,本来就常用“最后 token hidden state + 线性层”来分类,Hugging Face 的 `OPTForSequenceClassification` 也是这么干的。TensorRT-LLM 也支持拿额外输出,如 `hidden_states`。([Hugging Face][7]) | ||
| 129 | + | ||
| 130 | +对你这个任务,最优形态应该是: | ||
| 131 | + | ||
| 132 | +1. 输入只保留 query 或极短模板 | ||
| 133 | +2. 跑 transformer 得到最后位置 hidden state | ||
| 134 | +3. 接一个 **9 类线性头** | ||
| 135 | +4. 直接输出 9 个 score 的 argmax | ||
| 136 | + | ||
| 137 | +这样你能一次性省掉: | ||
| 138 | + | ||
| 139 | +* 整个 lm_head 对全词表的投影 | ||
| 140 | +* softmax | ||
| 141 | +* 采样 | ||
| 142 | +* label tokenization 的不确定性 | ||
| 143 | +* 多 token label 问题(比如 `Trench Coat` / `T-shirt`) | ||
| 144 | + | ||
| 145 | +这往往比“限制 decode 只出 1 token”更本质。 | ||
| 146 | + | ||
| 147 | +--- | ||
| 148 | + | ||
| 149 | +### 方向 2:如果还想保留生成范式,至少把 9 个类别映射成 9 个单 token | ||
| 150 | + | ||
| 151 | +如果你暂时不改模型结构,至少别让类别是自然语言短语。 | ||
| 152 | + | ||
| 153 | +做法是引入 9 个专用 special tokens,例如: | ||
| 154 | + | ||
| 155 | +* `<CAT_DRESS>` | ||
| 156 | +* `<CAT_JEANS>` | ||
| 157 | +* ... | ||
| 158 | +* `<CAT_OTHER>` | ||
| 159 | + | ||
| 160 | +然后: | ||
| 161 | + | ||
| 162 | +* 训练/蒸馏只输出这些 token | ||
| 163 | +* decode 时只允许这 9 个 token | ||
| 164 | +* 最后直接 argmax,不做采样 | ||
| 165 | + | ||
| 166 | +这会比现在用 `Dress / Trench Coat / T-shirt` 这种自然词好很多,因为后者可能是多 token,且受 tokenizer 影响。 | ||
| 167 | + | ||
| 168 | +--- | ||
| 169 | + | ||
| 170 | +### 方向 3:不要算完整 vocab logits,只算 9 类 score | ||
| 171 | + | ||
| 172 | +你已经意识到这一点了,而且这是对的。 | ||
| 173 | + | ||
| 174 | +如果还沿用 LM head 权重 (W \in \mathbb{R}^{V \times H}),那现在做的是: | ||
| 175 | + | ||
| 176 | +[ | ||
| 177 | +\text{logits} = h W^T | ||
| 178 | +] | ||
| 179 | + | ||
| 180 | +其中 (V) 可能是 50k、100k、甚至更大。 | ||
| 181 | +但你只关心 9 类,那就只取 9 行: | ||
| 182 | + | ||
| 183 | +[ | ||
| 184 | +\text{scores} = h W_{\text{class}}^T | ||
| 185 | +] | ||
| 186 | + | ||
| 187 | +这本质上是一个 **9-way GEMV**,而不是 full-vocab GEMM/GEMV。 | ||
| 188 | + | ||
| 189 | +进一步还能做成一个 fused kernel: | ||
| 190 | + | ||
| 191 | +* last-token gather | ||
| 192 | +* final layernorm | ||
| 193 | +* 9-row projection | ||
| 194 | +* argmax | ||
| 195 | + | ||
| 196 | +一次 kernel 做完,连中间 logits buffer 都不落地。 | ||
| 197 | + | ||
| 198 | +这个方向在工程上非常“值”,因为它精准切中你场景里最浪费的一步。 | ||
| 199 | + | ||
| 200 | +--- | ||
| 201 | + | ||
| 202 | +### 方向 4:把自然语言前缀蒸馏进权重,彻底消灭长 instruction prefix | ||
| 203 | + | ||
| 204 | +这个收益经常被低估。 | ||
| 205 | + | ||
| 206 | +现在即使 prefix KV cache 命中,query token 仍然要对这个 prefix 做 attention。 | ||
| 207 | +如果你把这条 instruction 蒸馏掉,例如: | ||
| 208 | + | ||
| 209 | +* SFT/LoRA 成一个专用分类模型 | ||
| 210 | +* 或者把 instruction 变成很短的 learned prompt / soft prompt | ||
| 211 | +* 或者直接训成 encoder-style / sequence-classification head | ||
| 212 | + | ||
| 213 | +那么你不仅省掉 prefix prefill,还省掉了**query 对 prefix 的 attention 成本**。 | ||
| 214 | +这通常比继续抠“prefix cache 加载”更有价值。 | ||
| 215 | + | ||
| 216 | +我的判断是: | ||
| 217 | + | ||
| 218 | +> **对搜索 query 分类,最大收益往往不是“更聪明地复用长 prompt”,而是“让模型根本不需要那段长 prompt”。** | ||
| 219 | + | ||
| 220 | +--- | ||
| 221 | + | ||
| 222 | +### 方向 5:为“短 query + 单步输出”做专用 execution path | ||
| 223 | + | ||
| 224 | +通用引擎通常为“大上下文 + 多 token decode + 动态 batch”设计。 | ||
| 225 | +而你的 workload 更像: | ||
| 226 | + | ||
| 227 | +* batch 可控 | ||
| 228 | +* 形状集中 | ||
| 229 | +* query 很短 | ||
| 230 | +* 输出固定 1 步 | ||
| 231 | +* 类别数固定 | ||
| 232 | + | ||
| 233 | +这非常适合做专用路径: | ||
| 234 | + | ||
| 235 | +* 固定或分桶后的静态 shape | ||
| 236 | +* 全流程 CUDA Graph capture | ||
| 237 | +* persistent kernel | ||
| 238 | +* 预分配所有 buffer | ||
| 239 | +* 避免动态分配/释放 KV page | ||
| 240 | +* 避免 host 端参与每一步调度 | ||
| 241 | + | ||
| 242 | +TensorRT-LLM 的 Piecewise CUDA Graph 就是在减少 launch overhead,尤其 context phase 的 launch overhead。对你这种超短请求,这类优化会比较敏感。([NVIDIA GitHub][8]) | ||
| 243 | + | ||
| 244 | +--- | ||
| 245 | + | ||
| 246 | +## 4)如果要做“深度定制化 GPU 编程”,最值得下手的点 | ||
| 247 | + | ||
| 248 | +你提到更希望找 **定制化 GPU 编程** 的方向。这个我建议按三层来做。 | ||
| 249 | + | ||
| 250 | +### 第一层:先复用现成高性能 attention backend | ||
| 251 | + | ||
| 252 | +优先看: | ||
| 253 | + | ||
| 254 | +* **FlashAttention-3**:针对 Hopper,核心是 warp specialization、TMA、matmul/softmax 交叠、FP8 等,H100 上较前代有 **1.5–2.0x** 提升。([arXiv][3]) | ||
| 255 | +* **FlashInfer**:核心卖点就是 **customizable attention template + JIT compilation**,而且已经集成进 SGLang、vLLM、MLC-Engine。论文里给了 **29–69% inter-token latency reduction**。虽然这个数字更偏通用 serving,但它最适合你拿来当“自定义 attention backend”的基座。([arXiv][9]) | ||
| 256 | + | ||
| 257 | +这一步的意义是:先把 attention kernel 做到接近硬件上限,不要从零手搓全部注意力算子。 | ||
| 258 | + | ||
| 259 | +--- | ||
| 260 | + | ||
| 261 | +### 第二层:在 attention 之外手写你自己的“小尾巴” | ||
| 262 | + | ||
| 263 | +这是我最推荐你自己写 Triton/CUDA 的部分,因为最贴近你的任务特征。 | ||
| 264 | + | ||
| 265 | +#### 2.1 fused last-token classification kernel | ||
| 266 | + | ||
| 267 | +把这几步融合: | ||
| 268 | + | ||
| 269 | +* 取最后非 padding token hidden state | ||
| 270 | +* RMSNorm / LayerNorm | ||
| 271 | +* 9 类投影 | ||
| 272 | +* 可选 bias | ||
| 273 | +* argmax | ||
| 274 | + | ||
| 275 | +这一步非常适合手写 Triton kernel,因为: | ||
| 276 | + | ||
| 277 | +* 数据很小 | ||
| 278 | +* shape 固定 | ||
| 279 | +* 全词表 head 被你砍掉了 | ||
| 280 | +* 可以彻底避免多余内存读写 | ||
| 281 | + | ||
| 282 | +#### 2.2 prefix-aware short-query prefill kernel | ||
| 283 | + | ||
| 284 | +你的 query 很短,通用 prefill kernel 往往为更一般的长序列设计。 | ||
| 285 | +可以做一个专门针对: | ||
| 286 | + | ||
| 287 | +* `T_query` 很短 | ||
| 288 | +* `T_prefix` 固定/分桶 | ||
| 289 | +* batch 较小 | ||
| 290 | +* causal mask 形状固定 | ||
| 291 | + | ||
| 292 | +的 kernel 版本,减少通用路径里的分支和元数据处理。 | ||
| 293 | + | ||
| 294 | +#### 2.3 persistent kernel / resident weights | ||
| 295 | + | ||
| 296 | +对“超短输入 + 高频请求”,可以考虑把部分小尾部算子做 persistent 化,减少 launch 与调度开销。 | ||
| 297 | +这类场景里,kernel launch overhead 占比会比大 batch 长序列高得多。TensorRT-LLM 做 CUDA Graph / overlap,本质上也是在解决这个问题。([NVIDIA GitHub][8]) | ||
| 298 | + | ||
| 299 | +--- | ||
| 300 | + | ||
| 301 | +### 第三层:改 KV layout 和请求调度 | ||
| 302 | + | ||
| 303 | +如果你是自己做 engine,下面这些值得认真做: | ||
| 304 | + | ||
| 305 | +#### 3.1 prefix 常驻 + 热前缀 pin 住,不参与普通 eviction | ||
| 306 | + | ||
| 307 | +TRT-LLM 的 KV cache 系统支持跨请求复用和优先保留。对于你的固定 prompt,应该把它当“超级热前缀”对待。([NVIDIA GitHub][10]) | ||
| 308 | + | ||
| 309 | +#### 3.2 query 长度分桶 | ||
| 310 | + | ||
| 311 | +例如按 4/8/16/32 token 分桶。 | ||
| 312 | +这样更容易: | ||
| 313 | + | ||
| 314 | +* 静态 shape | ||
| 315 | +* CUDA Graph capture | ||
| 316 | +* 避免 padding 浪费 | ||
| 317 | +* 避免 kernel 形状抖动 | ||
| 318 | + | ||
| 319 | +#### 3.3 专用队列,不和长文本请求混部 | ||
| 320 | + | ||
| 321 | +你的 workload 和普通 chat workload 的最优调度策略不一样。 | ||
| 322 | +一旦混部,长请求会把你的 tail latency 拖坏。 | ||
| 323 | + | ||
| 324 | +--- | ||
| 325 | + | ||
| 326 | +## 5)你这个任务里,哪些“常见优化”其实不太重要 | ||
| 327 | + | ||
| 328 | +### Speculative decoding | ||
| 329 | + | ||
| 330 | +对你帮助很小。 | ||
| 331 | +因为你本来就只出 1 个 token,decode 几乎不是瓶颈。 | ||
| 332 | + | ||
| 333 | +### Continuous batching / 大 batch 吞吐优化 | ||
| 334 | + | ||
| 335 | +如果你的目标是单请求极低延时,而不是 QPS 最大化,这不是第一优先级。 | ||
| 336 | + | ||
| 337 | +### 更复杂的 constrained decoding / grammar decoding | ||
| 338 | + | ||
| 339 | +SGLang 的 compressed FSM 很强,能把结构化输出做得很快,甚至比普通 decoding 还快;但它更适合 JSON/regex 类任务。你这种 **9 类单标签**,直接分类头会更彻底。([LMSYS Org][11]) | ||
| 340 | + | ||
| 341 | +--- | ||
| 342 | + | ||
| 343 | +## 6)一个更现实的性能判断 | ||
| 344 | + | ||
| 345 | +我给一个实战判断,按收益从大到小: | ||
| 346 | + | ||
| 347 | +### 第一梯队 | ||
| 348 | + | ||
| 349 | +1. **把任务改成 sequence classification,而不是生成** | ||
| 350 | +2. **把 instruction prefix 蒸馏进模型,去掉长 prompt** | ||
| 351 | +3. **只算 9 类 score,不算全词表** | ||
| 352 | +4. **固定 shape + CUDA Graph + 预分配内存** | ||
| 353 | + | ||
| 354 | +### 第二梯队 | ||
| 355 | + | ||
| 356 | +5. **prefix KV 常驻 GPU,不跨设备传** | ||
| 357 | +6. **短 query 分桶** | ||
| 358 | +7. **attention backend 换成 FlashAttention-3 / FlashInfer 路线** | ||
| 359 | + | ||
| 360 | +### 第三梯队 | ||
| 361 | + | ||
| 362 | +8. **再去抠“KV cache 的 GPU 内部读取”** | ||
| 363 | +9. **再去做更复杂的 decode 优化** | ||
| 364 | + | ||
| 365 | +也就是说: | ||
| 366 | + | ||
| 367 | +> 你最该优化的,不是“如何更快地做一次生成”,而是“如何避免把这个问题当生成来做”。 | ||
| 368 | + | ||
| 369 | +--- | ||
| 370 | + | ||
| 371 | +## 7)我建议你重点读的资料 | ||
| 372 | + | ||
| 373 | +按“最贴近你要做的事”排序: | ||
| 374 | + | ||
| 375 | +1. **TensorRT-LLM: KV cache reuse / KV cache system / Piecewise CUDA Graph / Overlap Scheduler** | ||
| 376 | + 看工程系统怎么压 TTFT、launch overhead、CPU-GPU overlap、KV 复用。([NVIDIA GitHub][1]) | ||
| 377 | + | ||
| 378 | +2. **FlashInfer (MLSys 2025)** | ||
| 379 | + 这是最像“为 serving 场景做可定制 attention engine”的资料,尤其适合你想走 JIT + custom backend + custom layout 的路线。([arXiv][9]) | ||
| 380 | + | ||
| 381 | +3. **FlashAttention-3** | ||
| 382 | + 如果你想往 Hopper/Blackwell 上深挖 kernel,这篇最值得精读。([arXiv][3]) | ||
| 383 | + | ||
| 384 | +4. **SGLang / RadixAttention / compressed FSM** | ||
| 385 | + 主要看 prefix 复用和受限输出的 runtime 设计思路。([LMSYS Org][12]) | ||
| 386 | + | ||
| 387 | +--- | ||
| 388 | + | ||
| 389 | +## 8)最后给你一个最适合落地的技术路线 | ||
| 390 | + | ||
| 391 | +如果我是你,会这么做: | ||
| 392 | + | ||
| 393 | +### 方案 A:最快落地 | ||
| 394 | + | ||
| 395 | +* 继续用现有 LLM | ||
| 396 | +* 加 9 个 special label tokens | ||
| 397 | +* 只允许这 9 个 token | ||
| 398 | +* prefix cache 常驻 | ||
| 399 | +* 短 query 分桶 | ||
| 400 | +* CUDA Graph | ||
| 401 | +* 自定义 logits path,只算 9 个 token 对应分数 | ||
| 402 | + | ||
| 403 | +### 方案 B:更极致 | ||
| 404 | + | ||
| 405 | +* 把任务改成 `last_hidden_state -> 9-class head` | ||
| 406 | +* 不再走生成 | ||
| 407 | +* 用 TensorRT-LLM / TensorRT engine 导出专用模型 | ||
| 408 | +* 手写 fused classification tail kernel | ||
| 409 | +* attention 走 FlashAttention-3 / FlashInfer backend | ||
| 410 | +* instruction 蒸馏进模型,删除自然语言 prefix | ||
| 411 | + | ||
| 412 | +### 方案 C:最极致、最像搜索引擎专用算子 | ||
| 413 | + | ||
| 414 | +* 单独做一个 query-classification engine | ||
| 415 | +* tokenizer 前移并缓存 | ||
| 416 | +* query 长度严格分桶 | ||
| 417 | +* prefix 完全蒸馏掉 | ||
| 418 | +* BF16/FP8 主干 | ||
| 419 | +* 末端 9-way 头自定义 Triton/CUDA kernel | ||
| 420 | +* 固定 batch/shape 的全图 capture | ||
| 421 | +* 与通用 chat serving 完全隔离 | ||
| 422 | + | ||
| 423 | +--- | ||
| 424 | + | ||
| 425 | +如果你愿意,我下一步可以直接给你一版更工程化的东西: | ||
| 426 | +**把这个场景拆成“通用 vLLM 路线 / TensorRT-LLM 路线 / 自研 Triton kernel 路线”的优化清单和预估收益表。** | ||
| 427 | + | ||
| 428 | +[1]: https://nvidia.github.io/TensorRT-LLM/advanced/kv-cache-reuse.html "KV cache reuse — TensorRT-LLM" | ||
| 429 | +[2]: https://nvidia.github.io/TensorRT-LLM/1.1.0rc4/features/overlap-scheduler.html "Overlap Scheduler — TensorRT-LLM" | ||
| 430 | +[3]: https://arxiv.org/abs/2407.08608 "[2407.08608] FlashAttention-3: Fast and Accurate Attention with Asynchrony and Low-precision" | ||
| 431 | +[4]: https://nvidia.github.io/TensorRT-LLM/latest/examples/llm_logits_processor.html "Control generated text using logits processor — TensorRT LLM" | ||
| 432 | +[5]: https://www.nvidia.com/en-us/data-center/h100/ "H100 GPU | NVIDIA" | ||
| 433 | +[6]: https://nvidia.github.io/TensorRT-LLM/features/disagg-serving.html "Disaggregated Serving — TensorRT LLM" | ||
| 434 | +[7]: https://huggingface.co/docs/transformers/model_doc/opt "OPT · Hugging Face" | ||
| 435 | +[8]: https://nvidia.github.io/TensorRT-LLM/latest/features/torch_compile_and_piecewise_cuda_graph.html "Torch Compile & Piecewise CUDA Graph — TensorRT LLM" | ||
| 436 | +[9]: https://arxiv.org/abs/2501.01005 "[2501.01005] FlashInfer: Efficient and Customizable Attention Engine for LLM Inference Serving" | ||
| 437 | +[10]: https://nvidia.github.io/TensorRT-LLM/latest/features/kvcache.html?utm_source=chatgpt.com "KV Cache System — TensorRT LLM - nvidia.github.io" | ||
| 438 | +[11]: https://www.lmsys.org/blog/2024-02-05-compressed-fsm/ "Fast JSON Decoding for Local LLMs with Compressed Finite State Machine - LMSYS Blog | LMSYS Org" | ||
| 439 | +[12]: https://www.lmsys.org/blog/2024-01-17-sglang/ "Fast and Expressive LLM Inference with RadixAttention and SGLang - LMSYS Blog | LMSYS Org" | ||
| 440 | + | ||
| 441 | +# 二、kimi | ||
| 442 | +基于搜索结果,我来为你详细分析LLM推理极致优化的方向,特别是针对搜索引擎query分析这种对延迟极其敏感的场景。 | ||
| 443 | + | ||
| 444 | +## 核心耗时分析 | ||
| 445 | + | ||
| 446 | +对于query分类这类任务(单token输出),耗时主要集中在: | ||
| 447 | + | ||
| 448 | +| 阶段 | 占比 | 优化方向 | | ||
| 449 | +|------|------|----------| | ||
| 450 | +| **Prefill (前缀计算)** | 60-80% | Prefix Caching、Kernel Fusion | | ||
| 451 | +| **KV Cache加载/准备** | 10-20% | 常驻显存、零拷贝、内存预分配 | | ||
| 452 | +| **Decode (单token)** | 5-15% | 分类头直接打分、跳过完整解码 | | ||
| 453 | +| **Kernel Launch开销** | 5-10% | CUDA Graphs、算子融合 | | ||
| 454 | + | ||
| 455 | +## 极致优化方向 | ||
| 456 | + | ||
| 457 | +### 1. **Prefix Caching的极致化** | ||
| 458 | + | ||
| 459 | +vLLM已支持Automatic Prefix Caching (APC),但要做到极致: | ||
| 460 | + | ||
| 461 | +- **跨Session持久化**:将KV Cache写入CPU内存甚至NVMe SSD(通过LMCache),实现服务重启后仍能复用 | ||
| 462 | +- **Block Size调优**:根据query长度分布选择16/32/64,减少内部碎片 | ||
| 463 | +- **命中率优化**:对固定前缀(如你的分类prompt模板)做哈希预热,确保100%命中 | ||
| 464 | + | ||
| 465 | +**收益**:TTFT可降低5-10倍,对于长前缀(如你的分类prompt)几乎消除prefill时间。 | ||
| 466 | + | ||
| 467 | +### 2. **跳过KV Cache加载 - 常驻显存方案** | ||
| 468 | + | ||
| 469 | +你希望"连KV Cache加载也省去",这可以通过以下方式实现: | ||
| 470 | + | ||
| 471 | +``` | ||
| 472 | +方案A: 前缀KV常驻显存 | ||
| 473 | +- 将固定前缀的KV Cache预分配并锁定在GPU HBM中 | ||
| 474 | +- 新请求直接复用这些物理块,无需任何加载/拷贝 | ||
| 475 | +- 配合vLLM的block table机制,实现零开销引用 | ||
| 476 | + | ||
| 477 | +方案B: 权重+KV合并存储 | ||
| 478 | +- 对于超短query分类,可将前缀KV视为"扩展的模型权重" | ||
| 479 | +- 使用TensorRT-LLM的weight streaming技术 | ||
| 480 | +``` | ||
| 481 | + | ||
| 482 | +**时间节省估算**: | ||
| 483 | +- 传统:从CPU/GDRAM加载KV → 10-50ms | ||
| 484 | +- 常驻显存:直接指针引用 → <0.1ms | ||
| 485 | +- **节省:10-50ms(对于短query分类,这可能是总延迟的50-80%)** | ||
| 486 | + | ||
| 487 | +### 3. **单Token分类的极致优化** | ||
| 488 | + | ||
| 489 | +你提到的"不对整个词表decode,只对N分类算打分"是关键优化点: | ||
| 490 | + | ||
| 491 | +**实现方案**: | ||
| 492 | +```python | ||
| 493 | +# 标准做法(浪费): | ||
| 494 | +logits = model.forward(input_ids) # [batch, vocab_size=50000] | ||
| 495 | +probs = softmax(logits) | ||
| 496 | + | ||
| 497 | +# 极致做法: | ||
| 498 | +# 1. 只计算分类头的输出投影到N个标签token的logits | ||
| 499 | +# 2. 使用Gather + MatMul融合kernel,跳过完整LM Head | ||
| 500 | +# 3. 直接argmax,无需采样 | ||
| 501 | +``` | ||
| 502 | + | ||
| 503 | +**技术细节**: | ||
| 504 | +- 修改模型最后一层,只输出N个分类token的logits | ||
| 505 | +- 使用CUTLASS或Triton编写定制kernel,融合`gather + matmul + argmax` | ||
| 506 | +- 配合CUDA Graphs消除kernel launch开销 | ||
| 507 | + | ||
| 508 | +**收益**:从50000维softmax降到10维分类打分,计算量减少**5000倍**。 | ||
| 509 | + | ||
| 510 | +### 4. **GPU深度定制化编程** | ||
| 511 | + | ||
| 512 | +#### A. **Kernel Fusion** | ||
| 513 | +将以下操作融合为单个CUDA kernel: | ||
| 514 | +- RoPE位置编码(针对可变query长度) | ||
| 515 | +- Attention计算(Q@K^T, softmax, @V) | ||
| 516 | +- 分类头投影(仅限N个输出token) | ||
| 517 | +- Argmax | ||
| 518 | + | ||
| 519 | +**关键优化点**: | ||
| 520 | +- 使用共享内存缓存前缀KV,避免全局内存访问 | ||
| 521 | +- Warp-level并行处理N分类打分 | ||
| 522 | +- 针对A100/H100 Tensor Cores优化WMMA指令 | ||
| 523 | + | ||
| 524 | +#### B. **Flash Attention变体** | ||
| 525 | +- **FlashDecoding++**:针对decode阶段优化flat GEMM,在小batch场景比cuBLAS快17-52% | ||
| 526 | +- **Split-KV**:对于GQA模型,优化KV Cache访问模式 | ||
| 527 | + | ||
| 528 | +#### C. **TensorRT-LLM Plugin开发** | ||
| 529 | +```cpp | ||
| 530 | +// 自定义Plugin结构 | ||
| 531 | +class QueryClassifierPlugin : public BasePlugin { | ||
| 532 | + // 1. 实现enqueue接口,融合所有计算 | ||
| 533 | + // 2. 跳过标准attention,直接读取常驻前缀KV | ||
| 534 | + // 3. 只计算分类头N个输出 | ||
| 535 | + int enqueue(...) override { | ||
| 536 | + // 融合kernel: attention + classifier_head + argmax | ||
| 537 | + launch_fused_classifier_kernel(...); | ||
| 538 | + } | ||
| 539 | +}; | ||
| 540 | +``` | ||
| 541 | + | ||
| 542 | +#### D. **CUDA Graphs固化** | ||
| 543 | +- 对于固定前缀+可变query的场景,使用CUDA Graphs捕获执行流 | ||
| 544 | +- 消除CPU调度开销,实现确定性延迟 | ||
| 545 | + | ||
| 546 | +### 5. **推测解码的反向应用** | ||
| 547 | + | ||
| 548 | +虽然推测解码通常用于长输出,但对于分类任务可以**预计算**: | ||
| 549 | +- 使用极小draft模型(甚至规则引擎)预生成分类结果 | ||
| 550 | +- 主模型只做验证(单token forward) | ||
| 551 | +- 配合EAGLE-3技术,实现2-3倍加速 | ||
| 552 | + | ||
| 553 | +## 推荐技术栈 | ||
| 554 | + | ||
| 555 | +| 层级 | 技术 | 适用场景 | | ||
| 556 | +|------|------|----------| | ||
| 557 | +| **推理引擎** | TensorRT-LLM + 自定义Plugin | 极致延迟优化 | | ||
| 558 | +| **Kernel库** | CUTLASS / FlashAttention | Attention优化 | | ||
| 559 | +| **缓存管理** | vLLM Prefix Caching + LMCache | 跨session KV复用 | | ||
| 560 | +| **编译优化** | Torch.compile + CUDA Graphs | 消除launch开销 | | ||
| 561 | +| **硬件调度** | MPS (Multi-Process Service) | 多query并行 | | ||
| 562 | + | ||
| 563 | +## 预期性能收益 | ||
| 564 | + | ||
| 565 | +对于你的query分类场景(假设前缀512 tokens,分类10个类别): | ||
| 566 | + | ||
| 567 | +| 优化项 | 延迟降低 | 实现难度 | | ||
| 568 | +|--------|----------|----------| | ||
| 569 | +| Prefix Caching (100%命中) | 5-10x | 低(vLLM开箱即用) | | ||
| 570 | +| KV常驻显存 | 1.5-2x | 中(需修改内存管理) | | ||
| 571 | +| 分类头裁剪 | 100-1000x | 中(需修改模型结构) | | ||
| 572 | +| Kernel Fusion | 1.2-1.5x | 高(需CUDA编程) | | ||
| 573 | +| CUDA Graphs | 1.1-1.3x | 中 | | ||
| 574 | +| **综合优化** | **10-50x** | - | | ||
| 575 | + | ||
| 576 | +**终极目标**:将TTFT从典型的50-100ms降至**1-5ms**级别,接近网络RTT水平。 | ||
| 577 | + | ||
| 578 | +建议先从vLLM的prefix caching和TensorRT-LLM的plugin开发入手,逐步深入到CUDA kernel定制。对于搜索引擎query分析这种高频、低延迟要求的场景,这些投入是值得的。 | ||
| 0 | \ No newline at end of file | 579 | \ No newline at end of file |
scripts/evaluation/eval_framework/prompts.py
| @@ -209,8 +209,20 @@ _CLASSIFY_TEMPLATE_ZH = """雿銝銝芣弘揣蝟餌葉批 | @@ -209,8 +209,20 @@ _CLASSIFY_TEMPLATE_ZH = """雿銝銝芣弘揣蝟餌葉批 | ||
| 209 | - 霂V掩銝箄砲蝐餃 | 209 | - 霂V掩銝箄砲蝐餃 |
| 210 | - 霂W掩 + 撅把蝐餃&撅找泵 | 210 | - 霂W掩 + 撅把蝐餃&撅找泵 |
| 211 | 211 | ||
| 212 | + | ||
| 213 | +葉抒霂V葉扯 | ||
| 214 | + | ||
| 215 | +霂g鋆/" | ||
| 216 | + 掩撅改說頞喃霂V葉 | ||
| 217 | + | ||
| 218 | + | ||
| 219 | +霂g耨頨怎ㄓ蝑凝撕ㄓ" | ||
| 220 | + 掩撕摰靽株澈說頞喃霂Y隞改蝑凝蛹憸縑嚗霂U | ||
| 221 | + | ||
| 222 | +霂g絲皛拙漲撣血漲仄憌﹝鋆 | ||
| 223 | + | ||
| 212 | ### | 224 | ### |
| 213 | -說頞喟蜓閬嚗敹掩霂V葉&縑銝凋&霈歹頧餃凝榆 / 榆砲皛∟雲敹瘙憟賣隞 | 225 | +說頞喟蜓閬嚗敹掩霂V葉&縑銝凋&霈歹頧餃凝榆 / 榆嚗雿餈皛∟雲敹瘙末隞 |
| 214 | 226 | ||
| 215 | 隞乩雿輻 | 227 | 隞乩雿輻 |
| 216 | - 敹掩抒撩憭晞&霈扎 | 228 | - 敹掩抒撩憭晞&霈扎 |
| @@ -218,10 +230,14 @@ _CLASSIFY_TEMPLATE_ZH = """雿銝銝芣弘揣蝟餌葉批 | @@ -218,10 +230,14 @@ _CLASSIFY_TEMPLATE_ZH = """雿銝銝芣弘揣蝟餌葉批 | ||
| 218 | - 揚銝鋡怨蛹隞 | 230 | - 揚銝鋡怨蛹隞 |
| 219 | 231 | ||
| 220 | 嚗 | 232 | 嚗 |
| 233 | +扳榆雿頝餈 | ||
| 234 | + | ||
| 235 | +扯&霈扎雿瓷瘙嚗撅皛∟雲漣 | ||
| 221 | - 霂g滯靽株澈T戊憯俊 | 236 | - 霂g滯靽株澈T戊憯俊 |
| 222 | 憸&霈扎 | 237 | 憸&霈扎 |
| 223 | -- 霂g滯靽株澈T靽株澈T | ||
| 224 | - 掩銝 | 238 | + |
| 239 | +- 霂g部瘜∟鋡鋡部瘜∟ | ||
| 240 | + &霈斗鋡瘜⊥部鋡扳撉銝霂X&蝒改之璁 | ||
| 225 | 241 | ||
| 226 | 霂衣 | 242 | 霂衣 |
| 227 | - 霂g捶鋡′銵徉 | 243 | - 霂g捶鋡′銵徉 |
| @@ -253,12 +269,21 @@ _CLASSIFY_TEMPLATE_ZH = """雿銝銝芣弘揣蝟餌葉批 | @@ -253,12 +269,21 @@ _CLASSIFY_TEMPLATE_ZH = """雿銝銝芣弘揣蝟餌葉批 | ||
| 253 | - 霂Z葉葵扯◤餈◤ | 269 | - 霂Z葉葵扯◤餈◤ |
| 254 | 270 | ||
| 255 | 嚗 | 271 | 嚗 |
| 272 | +蝐餃 + 撅扯榆嚗隡潦雿漣抒艇蝒 | ||
| 273 | +- 霂g滯TT | ||
| 274 | + 撅嚗銝 | ||
| 275 | +- 霂g葵摮餈﹝鋆迤摰賣餈﹝鋆 | ||
| 276 | + 撅﹝鋆皛∟雲葵摮誑 | ||
| 277 | + | ||
| 278 | +蝐餃 + 憌/餈 | ||
| 279 | +- 霂gT滲蝘‵ | ||
| 280 | + T‵掩銝舅捶忽銝虜餈嚗隞 | ||
| 256 | - 霂g銝剝澈鋆甈暸V憸葉甈曇﹝鋆 隡暺扳﹝鋆 | 281 | - 霂g銝剝澈鋆甈暸V憸葉甈曇﹝鋆 隡暺扳﹝鋆 |
| 257 | 敹掩澈鋆﹝鋆舅狡撘蛹葉甈撾蝛踵銝餈迨撅摹 | 282 | 敹掩澈鋆﹝鋆舅狡撘蛹葉甈撾蝛踵銝餈迨撅摹 |
| 258 | - | ||
| 259 | - 霂gㄓ鋆手 | 283 | - 霂gㄓ鋆手 |
| 260 | 敹掩ㄓ鋆之蝐鳴忽餈雿蛹颲摹隞 | 284 | 敹掩ㄓ鋆之蝐鳴忽餈雿蛹颲摹隞 |
| 261 | 285 | ||
| 286 | + | ||
| 262 | ### 銝 | 287 | ### 銝 |
| 263 | 皛∟雲蜓閬揚嚗 | 288 | 皛∟雲蜓閬揚嚗 |
| 264 | 289 |