用一个 Python 字典将多模态推理性能提升超 10%

Hacker News Top 工具

摘要

Modal 的工程师对 SGLang 调度器在多模态 VLM 工作负载下进行了性能分析,发现将开销较大的 GPU 显存记录操作替换为一个简单的 Python 字典缓存后,吞吐量提升了 16%,延迟降低了超过 13%。该修复已合并至 SGLang v0.5.10。

暂无内容
查看原文
查看缓存全文

缓存时间: 2026/05/09 06:33

# 用一个 Python 字典将多模态推理性能提升超过 10% | Modal 博客 来源:https://modal.com/blog/boosting-multimodal-inference-performance-by-greater-than-10-with-a-single-python-dictionary 返回 (https://modal.com/blog) 工程 2026 年 5 月 4 日 • 阅读时长 5 分钟 > tl;dr:多模态模型前景广阔,但推理引擎尚未针对其进行优化。我们对 SGLang 调度器在多模态工作负载下的性能进行了分析,发现可以用一个简单的缓存查询来替代开销昂贵的共享 GPU 内存簿记操作。在目标工作负载上,吞吐量和延迟均提升了 10% 以上。该改进已合并至 SGLang v0\.5\.10。 | 指标 | Handle Cache 关闭 | Handle Cache 开启 | 提升幅度 | |---|---|---|---| | 吞吐量(req/s) | 22.2 | 25.7 | +16.2% | | TTFT 均值(ms) | 965 | 838 | -13.2% | | TPOT 均值(ms) | 72 | 60 | -17.2% | 多模态视觉语言模型(VLM)赋予了人工智能"眼睛"。我们的用户部署较小的 VLM 来[高效解析非结构化文档](https://modal.com/blog/reducto-case-study),也部署大型 VLM 来驱动[能够"看见"所设计应用的多模态编程智能体](https://qwen.ai/blog?id=qwen3.5)。 这些新的输入类型和新模型给 [SGLang](https://github.com/sgl-project/sglang)、[vLLM](https://vllm.ai/) 等开源推理引擎带来了新的挑战。而其中最棘手的挑战之一就是如何最大化性能——这个问题的解法一如既往:持之以恒,一点一滴地积累改进。 这篇博文讲述的正是[其中一次](https://github.com/sgl-project/sglang/pull/21418)微小改进背后的故事。 在与一位客户合作期间,我们在 H100 上对 [Qwen2\.5\-VL\-3B\-Instruct](https://huggingface.co/Qwen/Qwen2.5-VL-3B-Instruct) 进行基准测试时,发现 SGLang 的吞吐量在远低于 GPU 处理能力上限时就已停滞不前。解决方案需要回归推理性能工程的"黄金法则":[永远不要让 GPU 等待。](https://modal.com/blog/host-overhead-inference-efficiency) ## 定位主机端开销 当你发现推理性能问题时,先别急着进入 [CUDA MODE](https://x.com/jeremyphoward/status/1735762761869635825?s=20)、在 Nsight Compute 里细究 warp 停顿原因——甚至可以先放下 [Torch Profiler](https://modal.com/docs/examples/torch_profiling)!先检查最简单的问题:主机端在做什么?为什么速度上不去? 在 SGLang 这类(视觉)语言模型推理引擎中,*调度器(scheduler)* 是关键的主机端组件,也是潜在的性能瓶颈——它是一个单线程循环,控制着向 GPU 提交工作的时机。 SGLang 进程示意图,标出了调度器进程作为可能阻塞 GPU 的关键组件。 调度器每多花一毫秒,所有*进行中的请求*的预填充(prefill)和解码(decode)迭代就会被阻塞一毫秒。我们[此前说过](https://modal.com/blog/host-overhead-inference-efficiency):主机端开销会严重拖累推理效率。 ## 用 py-spy 分析开销来源 为此,我们将轻量级 Python 性能分析工具 [`py-spy`](https://github.com/benfred/py-spy) 挂载到一个正在处理多模态流量的 SGLang 调度器进程上: 我们非常喜欢 `py-spy`——它能以极低的开销对运行中的 Python 程序进行调用栈采样。在持续 30 秒的多模态流量下,我们获得了约 3000 个样本,并生成了可用的[火焰图(flamegraph)](https://www.brendangregg.com/flamegraphs.html)。 优化前 SGLang 调度器各函数耗时的火焰图 火焰图左侧高亮的"平台"是一个名为 [`process_input_requests`](https://github.com/sgl-project/sglang/blob/83997080a60fb7b2633c764701c4a2ab98389c89/python/sglang/srt/managers/scheduler.py#L1545) 的函数。它负责预处理传入的多模态请求,以便调度器能够组成批次并分发给 GPU。该函数消耗了约 13% 的调度器 CPU 总时间,在火焰图中呈现为一个明显的"平台",引起了我们的注意。 深入分析 `process_input_requests`,我们发现大部分时间消耗在一个名为 `hash_feature` 的函数上,该函数将输入图像映射为基于哈希的 ID,以便在 KV 缓存查找时进行低成本的标识。这是多模态输入特有的新代码路径(文本 token 天然具有词表索引作为 ID),因此我们判断它很可能尚未经过充分的性能优化。 优化前 SGLang 调度器 `process_input_requests` 函数耗时的火焰图 在更深处,我们发现了一些可疑之处。该函数约 25% 的时间,即调度器总运行时间的 3% 多,被消耗在对 [`reconstruct_on_target_device`](https://github.com/sgl-project/sglang/blob/83997080a60fb7b2633c764701c4a2ab98389c89/python/sglang/srt/managers/mm_utils.py#L1375) 的调用上——进而又调用了 [`torch.UntypedStorage._new_shared_cuda`](https://github.com/sgl-project/sglang/blob/83997080a60fb7b2633c764701c4a2ab98389c89/python/sglang/srt/utils/cuda_ipc_transport_utils.py#L319)。 现在我们需要搞清楚:这里究竟在做什么?能不能更快? ## 用 Python dict 消除热路径上的冗余簿记 为什么需要共享存储?在 CUDA 中,[设备内存](https://modal.com/gpu-glossary/device-software/global-memory)分配默认与单个进程绑定,就像 CPU 编程中的内存分配一样。 SGLang 将工作分散到多个进程中。例如,调度器在一个进程中编排 GPU 推理,同时一个或多个分词器(tokenizer)工作进程在其他进程中并行地将原始输入预处理为张量。 因此,这些张量必须跨越进程边界传递。对于大型设备端张量,SGLang 采用 [CUDA 进程间通信(IPC)](https://docs.nvidia.com/cuda/cuda-programming-guide/04-special-topics/inter-process-communication.html) 来实现最快的传递方式,从而避免数据拷贝。具体而言,在 SGLang 中,每个工作进程拥有自己的 GPU 内存池,每个张量都是该内存池的一个切片,并通过内存池的 handle 和偏移量(offset)来标识。这些元数据需要在分词器进程和调度器之间进行通信。 在分析火焰图与代码后,我们意识到调度器对*每个*张量都调用了 `_new_shared_cuda`,如下所示: 示意图:handle 和 offset 的通信开销随 offset 数量线性增长,每个进程有多个 offset 也就是说,之前在多模态特征哈希期间,分词器进程(左)与调度器(右)之间共享张量的实现方式,需要对每个共享张量调用一次 `_new_shared_cuda`(箭头所示)。 这意味着调度器反复打开同几个 handle,每次都产生主机端的簿记开销,却在调度器迭代结束时将这些记录全部丢弃!这部分工作量随内存池中的条目数量和迭代次数线性增长。但实际上,只需要做常数量的工作——每个内存池调用一次即可。[做一次,但不要重复做!](https://mcjones.ca/docs/performance-mantras/) 示意图:handle 和 offset 的通信开销随 handle 数量线性增长,每个进程只有一个 handle 这里具体涉及哪些工作?在 [CUDA API 层面](https://modal.com/gpu-glossary/host-software/cuda-runtime-api),重复打开的成本很低(采用引用计数)。因此大部分开销来自 PyTorch 的封装层——新建 `StorageImpl`、记录 CUDA 事件、GIL 交互以及分配器的簿记操作。详情请参见[这个 GitHub Issue](https://github.com/pytorch/pytorch/issues/161481)。 这是对宝贵毫秒的滥用,而每一毫秒都至关重要! 这个听起来高大上的"CUDA IPC 内存池 Handle 缓存",本质上是一个简单的 Python `dict`。由于内存池从不重新分配,缓存无需失效机制。写入缓存时才需要加锁,读取时不需要,而且写入操作极为罕见!实现代码如下: ## 用另一张火焰图验证修复效果 我们在相同负载下重新运行了启用新缓存后的 `py-spy` 性能分析。`_new_shared_cuda` 的热点从性能剖析结果中消失了,输入处理阶段的总采样数减少了一半以上。 优化前后 SGLang 调度器各函数耗时对比火焰图 ## 衡量端到端效果:吞吐量提升 16%,延迟降低 10% 火焰图有助于调试性能问题,但并不能保证端到端的实际收益。因此我们回到基准测试,在单张 H100 上运行 Qwen2\.5\-VL\-3B\-Instruct。以下是添加内存池 handle 缓存前后的测试结果。 请求吞吐量提升约 16%,端到端平均延迟下降约 10%,尾部延迟也有所改善! | 指标 | Handle Cache 关闭 | Handle Cache 开启 | 提升幅度 | |---|---|---|---| | 吞吐量(req/s) | 22.2 | 25.7 | +16.2% | | TTFT 均值(ms) | 965 | 838 | -13.2% | | TTFT p99(ms) | 2058 | 1819 | -11.6% | | TPOT 均值(ms) | 72 | 60 | -17.2% | | ITL p99(ms) | 627 | 556 | -11.4% | | E2E 延迟均值(ms) | 1979 | 1768 | -10.6% | | E2E 延迟 p99(ms) | 4309 | 3666 | -14.9% | 详细信息请参见 [PR](https://github.com/sgl-project/sglang/pull/21418)。 我们的基准测试使用的是 Qwen2\.5\-VL\-3B,但该改进适用于所有使用 SGLang CUDA IPC 传输的多模态模型。多模态输入总量越大,收益越明显。 ### 解码延迟也有所改善! 有一个乍看令人意外的结果:每输出 token 的平均时间下降了 17%。解码速度加快了,尽管这个修复完全作用于输入/预填充路径。这是为什么呢? 首先,在启用[混合块调度(mixed-chunk scheduling)](https://arxiv.org/abs/2308.16369)的情况下,预填充 token 和解码 token 可以共享一个批次,因此预填充批次元素的初始化变慢也会拖慢解码速度。 但真正的收益来自更深层的原因。请记住,SGLang 的调度器运行在单线程上——同一个线程负责处理传入请求、组成批次、向 GPU 分发批次。因此,[任何地方慢,就处处慢!](https://x.com/sama/status/1345140364995227648)在单线程下,花在 `process_input_requests` 上的时间,就是无法用于分发下一个批次的时间。 ## 此项性能改进已包含在已发布的 SGLang 版本中,立即体验! 该优化已通过 [`sgl-project/sglang#21418`](https://github.com/sgl-project/sglang/pull/21418) 合并至上游,并已包含在 [`v0.5.10`](https://github.com/sgl-project/sglang/releases/tag/v0.5.10) 版本中。在使用 CUDA IPC 时,添加以下标志即可启用: 如果你想亲自尝试,我们的[在 Modal 上使用 SGLang 部署 VLM 的指南](https://modal.com/docs/examples/sglang_vlm)可以帮助你在几分钟内运行起来。 *附:如果你也喜欢解决这类问题,[我们正在招聘](https://modal.com/careers)。*

相似文章

利用适度非结构化稀疏权重矩阵加速大语言模型的GPU推理

arXiv cs.LG

本文提出了一种针对具有适度非结构化稀疏性的大语言模型的高效GPU推理方法。引入了一种三层矩阵存储格式和一个联合利用稀疏张量核心与CUDA核心的SpMM内核,实现了相比SpInfer最高1.64倍的内核级加速,以及相比FlashLLM最高1.41倍的端到端加速。

基于查询的跨模态投影器增强 Mamba 多模态大语言模型

arXiv cs.CL

本文提出了一种基于查询的跨模态投影器,通过交叉注意力机制对视觉标记进行压缩,以提升基于 Mamba 的多模态大语言模型的性能。该方法在视觉语言基准测试中同时提高了模型性能和吞吐量,并消除了手动设计二维扫描顺序的需求。