最近在做多模态模型推理部署的同学,应该都注意到了 Baseten 发布的一个优化结果:用 SGLang 配合英伟达 B300 跑 Qwen-Image,把图像生成延迟降低了 42.3%。
这个数字初看没什么,毕竟各家厂商都在发“性能提升 XX%”的新闻稿。但如果你真正部署过图像生成模型,就明白这个结果的分量在哪里:图像生成和纯文本生成不一样,它不是“吐一个 token”那么轻量,而是要在有限的采样步数里反复跑扩散 Transformer,每一步都在搬运权重、计算中间特征。延迟能砍掉四成多,绝不是简单升级一块显卡就能做到的。
这篇文章我想做三件事:第一,拆解 Baseten 这个优化结果背后的技术逻辑,说明 SGLang、B300、Qwen-Image 三者是怎么配合的;第二,给出一个可复现的 SGLang 部署 Qwen-Image 的实践路径,让你在本地也能验证前缀缓存等机制的实际效果;第三,结合多数团队会踩的坑,梳理推理延迟优化的通用思路。
读完之后,你至少能回答这几个问题:SGLang 和 vLLM 到底该怎么选?B300 这类新一代 GPU 对推理的真正价值在哪里?Qwen-Image 部署时延迟高在哪里,如何定位和优化?
1. 为什么这个案例值得关注
先看你日常可能遇到的真实场景。
假设你在做一个 AI 海报生成工具,用户输入一句提示词,产品期望 1 到 2 秒内出预览图。这个体验目标用文本模型很容易做到,但换成图像生成模型就变得很难。Qwen-Image 这类扩散模型单次生成往往要跑 20 到 50 步采样,每一步都要经过 Diffusion Transformer 做完整的前向计算,生成一张 1024x1024 的图,延迟很容易到 3 到 5 秒甚至更久。如果再叠加“多人同时使用”,显存吞吐和调度就会变成新的瓶颈。
这个案例值得关注的原因在于:它把一个看起来很“硬核”的优化过程,拆成了可以复用的三部分。
第一部分是模型层面的结构化分析。Qwen-Image 不是单一网络,而是文本编码器、扩散 Transformer、VAE 解码器组成的复合结构。延迟不是均匀分布的,必须先量化每一段耗时,才知道该优化哪里。
第二部分是推理框架层面的调度优化。SGLang 的核心卖点之一是 RadixAttention,它能在前缀树中缓存历史计算,让重复的 prompt 前缀直接命中缓存,省掉重新编码文本和重新计算部分网络的时间。
第三部分是硬件层面的带宽优势。B300 之所以能带来明显收益,不只是算力高,而是显存带宽的提升让扩散模型每一步采样时的权重读取更快。当模型权重大到一定程度,推理速度就开始受“搬数据”而不是“做计算”限制。
这个案例并不是告诉你“必须买 B300”,而是展示了一条通用的推理优化路径:先看模型结构,再看调度策略,最后看硬件匹配。
2. 三个主角:SGLang、B300、Qwen-Image
2.1 SGLang 是什么
SGLang 是一个开源的生成模型推理框架,核心目标是把“计算、显存、调度”三件事做到极致。
很多人第一次看到 SGLang 会把它和 vLLM 放在一起比较。两者定位确实相近,都是面向 LLM 的高性能推理服务框架,但侧重点有差别。
vLLM 的优势是生态成熟、上手快。它提出了 PagedAttention,通过类似虚拟内存分页的方式管理 KV Cache,显存利用率很高。很多模型和工具链默认支持 vLLM,社区资料也丰富。如果你的需求是“快速部署一个聊天模型”,vLLM 通常是最稳妥的起点。
SGLang 则更强调“整体调度效率”。它最有名的机制是 RadixAttention,把请求的 prompt 拆成前缀树节点,公共前缀只计算一次,后续请求直接复用。这对多轮对话、同主题批量生成、带固定系统提示词的场景非常有效。此外,SGLang 在连续批处理、CUDA Graph 捕获、多模态输入支持上也做了大量调度优化。
这里有一个容易出现的误区:很多人以为 SGLang 只是“另一个 vLLM”,换汤不换药。实际上,SGLang 在长 prompt、高并发、多模态场景下的收益会更明显。Qwen-Image 这类扩散模型需要反复执行多次前向计算,框架的调度开销会被放大,所以 SGLang 在这类场景的优势更容易体现。
2.2 B300 在推理链路中的位置
B300 是英伟达 Blackwell Ultra 平台的主要 GPU 之一。按公开定位,它是上一代 B200 的升级版本,重点提升了显存容量和显存带宽,同时保持了较高的浮点算力。
对纯文本生成模型来说,GPU 的算力峰值很关键。但对一张 1024x1024 的图片生成任务来说,扩散模型每一步采样都要读取完整的模型权重。模型权重越大,显存带宽对延迟的影响就越大。可以把这一步理解为“从仓库里搬一批重物到车间加工”,搬运速度直接决定了每批货的处理时间。
B300 的价值就在这里:它让模型权重的读取速度更快,从而压缩每一步采样所需的时间。Qwen-Image 这类 Diffusion Transformer 模型权重较大,采样步数多,因此对显存带宽非常敏感。B300 的优势在此时会被放大。
但要注意,B300 价格不低,也不是所有场景都需要。如果你的模型权重较小、并发压力不高,上一代 GPU 仍然够用。选 GPU 时,先算清楚你的瓶颈是计算时间、显存容量还是显存带宽,再决定硬件投入。
2.3 Qwen-Image 是什么
Qwen-Image 是开源的多模态生成模型,能够根据文本描述生成图像。它和纯文本模型最大的区别在于输出不是一个 token 序列,而是一张图片。
从结构上看,Qwen-Image 基本由三部分组成:
- 文本编码器:把用户输入的提示词转换为语义向量。
- 扩散 Transformer(DiT):在多个采样步中不断去噪,生成图像的潜在表示。
- VAE 解码器:将潜在表示转换为最终的高分辨率图像。
因此,一次完整生成不是“一次前向”就能完成的。假设采样步数为 28 步,那么 DiT 部分至少要做 28 次完整前向计算。再加上文本编码和 VAE 解码的时间,整体延迟自然比文本生成高很多。
这个结构决定了它的优化空间不在某一步,而在于三步之间的配合。比如文本编码结果能否缓存、DiT 每一步的调度是否高效、VAE 解码能否并行——这些都有文章可做。
3. Baseten 是怎么把延迟砍掉 42.3% 的
先说明一点:下文的分析基于 Baseten 公开的技术方向和 SGLang 已知的优化机制,不做任何“内部数据”的猜测。
3.1 优化点一:用 RadixAttention 做前缀缓存
SGLang 的 RadixAttention 是为长 prompt 场景设计的。它的原理是把 prompt 按 token 拆成前缀树,树节点保存 KV Cache 等中间结果。当新请求的前缀与历史请求重合时,直接复用节点缓存,跳过前面重复的计算。
在 Qwen-Image 场景下,这个机制有两个作用位置。
第一个位置是文本编码。如果用户输入“一张赛博朋克风格的城市夜景图,霓虹灯光,雨夜,电影感”,整段文本要被编码成向量。如果另一个用户输入“一张赛博朋克风格的城市夜景图,霓虹灯光,雨夜,动漫感”,前半段前缀完全相同。传统推理会重复编码前半段,而 RadixAttention 可以直接复用。
第二个位置是批量生成相似 prompt 时。比如做素材批量生成的团队,经常把“同一段主体描述”加上“不同风格后缀”批量提交。公共前缀越长,缓存收益越明显。
3.2 优化点二:用 B300 的高显存带宽压缩 DiT 采样时间
扩散模型的采样过程是循环执行的。每一步都要把模型的全部权重从显存中搬到计算单元,再结合当前步的噪声和条件向量做前向计算。
当模型权重超过几十 GB 时,权重读取时间在单步耗时中占比很高。B300 相比上一代 GPU 在显存带宽上的提升,可以让每一步采样读取权重的时间缩短,从而直接降低整图生成延迟。
这个优化不需要改代码,只要硬件和驱动匹配,框架能正常调度即可。它是“模型结构不变、框架调度不变、硬件升级”就能拿到的收益。
3.3 优化点三:调度和批处理层面的精细控制
除了前缀缓存和硬件升级,SGLang 本身的调度策略也能压缩延迟。
比如连续批处理。传统批处理是等一批请求全部结束后再处理下一批,空档期很多。连续批处理则是一个请求的某个步骤结束后立刻插入新请求,让 GPU 一直保持忙碌状态。
再比如 CUDA Graph 捕获。它能减少小算子启动的开销,让扩散模型每次前向计算的固定开销变小。采样步数越多,这种固定开销节省越明显。
还有一个思路是把文本编码、DiT 采样、VAE 解码做成流水线。文本编码不占 GPU 的主要算力,VAE 解码只在最后执行一次。合理调度下,前一个请求的 VAE 解码可以和下一个请求的 DiT 采样重叠,从而缩短整体排队时间。
3.4 延迟构成的直观变化
为了帮你理解这 42.3% 的可能性,我做一个示意性拆解,不代表 Baseten 的真实数据。
假设优化前一次生成耗时 3.0 秒,大致构成是:
| 阶段 | 耗时(示意) | 说明 |
|---|---|---|
| 文本编码 | 0.3s | prompt 短时耗时低,长 prompt 更高 |
| DiT 采样 | 2.4s | 28 步采样,核心耗时区 |
| VAE 解码 | 0.2s | 单次解码 |
| 调度与排队 | 0.1s | 服务端固定开销 |
用 SGLang 前缀缓存后,文本编码的一部分可以被命中复用;用 B300 后,DiT 每一步采样时间压缩;再用调度优化压掉排队开销。最终可能变成:
| 阶段 | 耗时(示意) | 说明 |
|---|---|---|
| 文本编码 | 0.15s | 前缀命中省掉部分编码 |
| DiT 采样 | 1.55s | 带宽提升压缩单步时间 |
| VAE 解码 | 0.15s | 流水线优化 |
| 调度与排队 | 0.02s | 连续批处理生效 |
合计约 1.87 秒,降幅约 37.7%。如果再叠加更优的量化、采样步数压缩、动态 batch 等措施,42.3% 是完全可能的。
这里真正重要的是方法论:先按阶段拆解延迟,再针对每个阶段选择最合适的优化手段。如果只升级硬件而不优化调度,最多只能拿到带宽收益;如果只优化调度而不升级硬件,则无法突破单步采样的计算与带宽上限。
4. SGLang 本地部署 Qwen-Image 的环境准备
如果你也想跑通这套流程,下面是一个可以直接参考的环境准备和部署示例。本文的部署示例以本地验证为目标,硬件规格不需要到 B300 级别,重点是把 SGLang 的缓存机制跑起来,并理解延迟优化的数据采集方法。
4.1 硬件与操作系统
Qwen-Image 属于扩散模型,显存需求明显高于普通文本模型。建议使用不低于 24GB 显存的 GPU,显存越大,越有可能直接加载全精度或低比特量化权重。如果在消费级显卡上运行,可以考虑加载量化版本,或者在 batch size 为 1 的前提下测试基本流程。
操作系统建议使用 Ubuntu 20.04 或 22.04,内核和驱动尽量新。NVIDIA 驱动版本和 CUDA 版本请以实际安装环境为准,不建议盲目追最新,稳定性优先。
4.2 Python 环境
SGLang 的安装推荐使用 Python 3.10 以上版本。先创建独立的虚拟环境,避免和系统 Python 环境、其他深度学习项目冲突。
python3 -m venv sglang-env source sglang-env/bin/activate pip install --upgrade pip4.3 安装 SGLang
SGLang 可以从 PyPI 直接安装,也可以从源码编译安装。源码安装能体验最新特性,但编译耗时较长。建议先用 PyPI 版本跑通流程。
pip install "sglang[all]"安装完成后,先确认版本和服务命令是否正常:
python -c "import sglang; print(sglang.__version__)"如果你已经有本地模型文件,也可以通过环境变量SGLANG_MODEL_PATH指向模型路径。没有本地模型时,启动服务时会从 Hugging Face 或 ModelScope 拉取权重,国内网络环境建议提前设置镜像。
4.4 拉取 Qwen-Image 模型
Qwen-Image 的模型文件可以从 Hugging Face 或 ModelScope 获取。使用 modelscope 示例:
modelscope download --model Qwen/Qwen-Image --local_dir ./models/Qwen-Image如果没有 modelscope,也可以直接通过git lfs clone拉取 Hugging Face 仓库。
git lfs install git clone https://huggingface.co/Qwen/Qwen-Image.git拉取模型时要注意磁盘空间。Qwen-Image 权重文件体积较大,建议预留至少 50GB 以上空间。下载完成后,确认目录下存在config.json、safetensors 权重文件、tokenizer 相关文件。
5. 启动 SGLang 服务并调用 Qwen-Image
5.1 启动服务
在虚拟环境中启动 SGLang 服务:
python -m sglang.launch_server \ --model-path ./models/Qwen-Image \ --dtype bfloat16 \ --port 30000 \ --host 0.0.0.0 \ --trust-remote-code参数说明:
--model-path指向本地模型目录,也可以填写模型仓库 ID。--dtype控制加载权重时的精度。如果显存不足,可以尝试--dtype float16或--load-format相关参数。--port和--host定义服务监听地址。--trust-remote-code用于允许模型仓库中的自定义代码执行。仅在你信任模型来源时使用。
启动成功后,终端会打印类似下面的信息:
The server is launched at http://0.0.0.0:30000此时服务已经准备好接收请求。看到这个信息后,可以再开一个终端做健康检查:
curl http://127.0.0.1:30000/get_server_info返回的 JSON 中应包含模型名、服务版本、显存使用情况等信息。
5.2 调用图像生成接口
SGLang 提供 OpenAI 兼容接口。下面是使用 Python 发送图像生成请求的完整示例。
import json import urllib.request # 请求体字段以 SGLang 当前版本接口为准 payload = { "model": "./models/Qwen-Image", "prompt": "一张赛博朋克风格的城市夜景图,霓虹灯光,雨夜,电影感", "n": 1, "size": "1024*1024" } req = urllib.request.Request( "http://127.0.0.1:30000/v1/images/generations", data=json.dumps(payload).encode("utf-8"), headers={"Content-Type": "application/json"} ) with urllib.request.urlopen(req, timeout=120) as resp: result = json.loads(resp.read()) # 解析返回结果,具体字段以接口返回为准 print(json.dumps(result, ensure_ascii=False, indent=2))如果你的 SGLang 版本不支持/v1/images/generations端点,请查阅当前版本的服务接口文档。遇到兼容问题时,优先检查服务日志中的路由列表。
5.3 验证前缀缓存效果
前缀缓存是 SGLang 对比传统方案的重要优势。我们可以用一个最小脚本验证它是否存在。
脚本逻辑:连续发送两个 prompt,第二个 prompt 包含第一个 prompt 的完整前缀,追加一小段不同风格词。记录两次请求的耗时。
import json import time import urllib.request base_prompt = "一只戴着宇航员头盔的猫,站在火星表面,背景是巨大的地球" two_requests = [ base_prompt + ",写实摄影风格", base_prompt + ",卡通插画风格", ] for idx, prompt in enumerate(two_requests, 1): payload = { "model": "./models/Qwen-Image", "prompt": prompt, "n": 1, "size": "1024*1024" } req = urllib.request.Request( "http://127.0.0.1:30000/v1/images/generations", data=json.dumps(payload).encode("utf-8"), headers={"Content-Type": "application/json"} ) start = time.time() with urllib.request.urlopen(req, timeout=180) as resp: result = json.loads(resp.read()) cost = time.time() - start print(f"第 {idx} 次请求耗时: {cost:.3f} 秒") with open(f"output_{idx}.png", "wb") as f: # 注意:图像内容在返回字段中的位置需要根据实际响应调整 f.write(base64.b64decode(result["data"][0]["b64_json"]))如果你观察到第二次请求耗时明显低于第一次,说明前缀缓存机制在起作用。如果两次耗时差别不大,可能是因为服务等待间隙缓存被清理,或 batch 策略没有命中缓存模型。可以连续多次发送相同前缀的请求再观察。
5.4 查看服务端延迟指标
SGLang 服务端通常可以获取到更细粒度的耗时分布。你可以请求接口获取本次请求的调度信息、前后端耗时、命中缓存情况。
curl http://127.0.0.1:30000/get_server_info如果服务版本支持更细的指标,可以通过 Prometheus 端点获取。按官方文档检查可用指标即可。
6. 运行结果与效果验证
6.1 判断服务是否正常
执行健康检查后,如果curl http://127.0.0.1:30000/get_server_info返回如下,说明服务启动成功:
{ "model_path": "./models/Qwen-Image", "server_version": "0.4.x", "gpu_memory_used": 24576 }如果你看到model_path正确、GPU 显存有占用,说明服务加载成功。
6.2 判断生成结果是否正常
生成脚本执行完毕后,检查output_1.png和output_2.png是否正常生成。正常情况下,两张图内容主体应一致,风格有差别。如果图片全黑、全白或出现大量噪点,通常说明模型权重加载异常、采样步数不足或提示词与模型能力不匹配。
6.3 如果延迟没有明显下降
延迟没降,第一步不是怀疑框架配置,而是先做分段计时。
你需要知道文本编码、DiT 采样、VAE 解码各自占多少时间。看服务端日志,SGLang 通常会输出请求级延迟信息。如果可视化指标不可用,可以在客户端用多组 prompt 做对比实验,调整采样步数再看延迟变化。如果减少采样步数后延迟显著下降,说明 DiT 采样是主要瓶颈;如果变化很小,说明瓶颈在文本编码或 VAE 解码阶段。
7. 常见问题与排查思路
很多团队在部署时会遇到下面几类问题。这里整理成一个排查表。
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| 启动服务时报 OOM | 显存不足以加载全精度模型 | 查看日志中显存申请量,用nvidia-smi确认剩余显存 | 改用 float16 或量化版本,减小 batch size,或多卡切分 |
| 首次请求非常慢 | 权重从磁盘加载到显存、编译 CUDA Graph | 观察后续请求是否变快 | 预热请求:启动后先发一次小请求触发编译 |
| 相同前缀请求延迟没有明显下降 | 前缀缓存没有命中 | 查看 RadixAttention 命中日志或指标 | 确认请求之间间隔不过长,检查公共前缀长度是否足够 |
| 图像质量差或出现噪点 | 采样步数不足、精度过低 | 对比不同采样步数的输出 | 增加采样步数,或换回更高精度加载 |
| 响应返回格式与脚本不符 | SGLang 接口版本不同 | 查看接口文档和服务端路由列表 | 按实际返回结构调整解析代码 |
| CPU 占用高但 GPU 利用率低 | 数据预处理或文本编码成为瓶颈 | 查看 CPU/GPU 时间线 | 增加请求并发,优化数据加载,检查是否开启了 overlap 调度 |
| 用 vLLM 部署 fp8 文本模型感觉延迟高 | 量化格式和硬件算子匹配不佳 | 对比不同量化格式、不同框架 | 如果是画面卡顿感,先看客户端网络和排队,再看服务端 batch 策略 |
这个表格里特别值得多说一句的是“用 vLLM 部署 fp8 文本模型感觉延迟高”的问题。很多人在热词里搜过这个现象,其实原因不一定是 vLLM 本身慢,而可能是并发调度策略导致请求排队,或者是 fp8 权重在特定 GPU 上算子没有走最优路径。排查时先用单请求、低并发测试最小延迟,再逐步加并发,看瓶颈出现在哪一层。
对于“SGLang 和 vLLM 怎么选”这个问题,我的判断是:如果你的业务是标准文本对话、生态要求高、团队熟悉 vLLM,可以直接用 vLLM;如果你的业务包含多模态模型、长 prompt、大量重复前缀,或者想尝试更激进的调度优化,SGLang 更值得投入时间。
8. 最佳实践与工程建议
8.1 延迟优化先量化,再动手
不要一上来就换框架、换 GPU。先用同样的 prompt 跑 20 次请求,统计 P50、P95 延迟和成功率。再通过分段计时明确瓶颈在文本编码、DiT 采样还是 VAE 解码。量化之后再决定优化手段。
例如,如果你的文本编码阶段占了总延迟的 15%,那么即使把文本编码时间压缩一半,整体收益也只有 7.5%。相反,如果 DiT 采样占了 80%,那么每压缩 10% 单步采样时间,整体收益就是 8%。优化一定要打在最耗时的地方。
8.2 生产环境做好流量控制与排队
图像生成服务比文本服务更吃资源。直接透传所有并发请求会导致 GPU 显存溢出或队列堆积。生产环境建议加一层队列和超时控制,让服务端永远处在“少量请求并行处理、大量请求排队等待”的健康状态。
一种常见做法是:用消息队列或网关做请求缓冲,限制同时进入推理服务的请求数。队列长度、单请求超时都要根据 GPU 单次生成耗时设置。
8.3 利用前缀缓存,需要设计 prompt 规范
如果你想让 SGLang 的 RadixAttention 发挥最大价值,prompt 结构最好稳定。比如固定前缀、固定指令格式、把用户输入的“风格变化”放在 prompt 尾部。
如果每次请求的 prompt 都完全不同,前缀缓存几乎无法命中,优化效果会打折扣。因此,产品层面如果允许,尽量沉淀一套 prompt 模板,把固定任务描述、固定场景词放在前面,把变化部分放在后面。
8.4 谨慎使用量化与精度压缩
量化能降低显存占用和权重读取量,是成本最低的加速手段之一。但量化位数越低,输出质量风险越高。上线前要做主观图质量评测和客观指标对比,不能只看延迟数字。
如果团队没有专门的评测集,至少固定 20 到 30 个代表性 prompt,让产品、设计、研发三方都看一遍量化前后的输出,再决定是否上线。生产环境建议保留全精度模型作为兜底,量化模型出问题时可以快速切换。
8.5 涉及生产环境变更,务必有回滚
如果你在生产环境调整了 GPU 类型、推理框架版本、量化格式,一定要保留旧版本服务的能力。推荐的做法是:新旧服务同时部署,通过请求路由的灰度比例控制流量,观察延迟和错误率后再逐步切量。
GPU 驱动、CUDA 版本、PyTorch 版本之间兼容性复杂,升级前先在测试环境完整跑一遍生成链路,包括文本编码、图像生成、结果保存。
8.6 关注成本,而不是只关注延迟
B300 能显著降低延迟,但硬件成本也高。很多业务场景不需要“秒出图”,可能 3 秒内可接受。这时选择上一代 GPU 加 SGLang 调度优化,可能是更务实的方案。
做性能优化时,建议同时计算单张图的成本。延迟降低 40% 但成本翻倍,在商业上不一定划算。把延迟、成本、质量三个指标一起看,才是合理的决策方式。
8.7 安全与权限提醒
部署推理服务时,不要暴露到公网就完事。暴露在公网的服务需要增加身份认证。最简单的做法是在网关层做鉴权,只允许加入白名单的客户端访问推理接口。
另外,启动服务时如果使用--trust-remote-code,务必确认模型权重来源可靠。这个参数会允许模型仓库里的自定义代码在本机执行,如果模型来源不可信,存在执行恶意代码的风险。尽可能从官方仓库拉取模型,避免使用来路不明的权重。
9. 总结与后续学习方向
Baseten 用 SGLang 配合 B300 把 Qwen-Image 延迟降低 42.3%,这个结果背后真正的价值是提供了一套可复用的优化思路:把模型拆成文本编码、DiT 采样、VAE 解码三个阶段,分别找到对应的优化手段。文本编码阶段用前缀缓存压缩重复计算,DiT 采样阶段用高显存带宽的 GPU 压缩单步时间,服务调度阶段用连续批处理和 CUDA Graph 压缩固定开销。
这套方法论不局限于 Qwen-Image,也不局限于某个 GPU 型号。任何推理延迟敏感的业务,都可以先用分段计时找到瓶颈,再决定是优化框架调度、量化权重,还是升级硬件。
如果你接下来想继续深入,建议先从两件事开始:
第一,用本文的示例在本地跑通 SGLang 部署 Qwen-Image,记录不同 prompt、不同采样步数下的延迟数据,建立自己的延迟基线。之后无论切换模型还是切换硬件,你都有对比依据。
第二,多读 SGLang 和 vLLM 的官方文档,理解 RadixAttention、连续批处理、CUDA Graph、量化加载这些机制在底层的实现逻辑。框架更新很快,但底层原理稳定,理解了原理,无论框架怎么迭代你都能快速上手。
最后提醒一句:AI 推理的优化没有银弹。看到别人延迟降低多少,先想清楚他的场景和你的场景差在哪里,再决定要不要跟进。数据、代码、流程,永远比一个吸引眼球的百分比更有价值。