为何使用SGLang和DFlash2时,双块RTX 3090显卡的生成速度仅为55 tok/s,而非预期的220 tok/s。
实地演示如何使用SGLang在两块3090显卡上运行Qwen3.8-27B,以及为何PCIe x4接口、缺失的P2P功能、过小的KV缓存以及电压波动导致了实际速度远低于宣传值。
一段短视频在自托管爱好者中流传。视频显示Qwen3.8-27B在两块RTX 3090显卡上编写代码,生成速度高达每秒220个标记。该系统采用了SGLang以及一种名为DFlash2的推测性草拟器。这个小型模型会同时提出七个可能的下一个标记,而大型模型会在一次处理中接受或拒绝它们,因此正确的猜测无需任何成本。
其吸引力远不止于演示效果。以这样的速度,借助相应的工具,人们可以直接使用本地部署的模型,在同一台设备上并行运行多个编码代理,无需API密钥,没有计费限制,数据也不会流出设备。配备两块备用3090级GPU的家庭服务器似乎是理想选择:内存带宽翻倍,生成速度自然也能达到每秒220个标记。
但实际上并非如此。其中的缘由比视频本身更具启发性。
安装SGLang
那些习惯使用 llama.cpp 的人会发现,只需一个二进制文件和一个模型文件就能在十分钟内实现聊天功能,相比之下 SGLang 会让人感到力不从心。以下是在 Ubuntu 服务器上的操作步骤。
第一步:创建 Python 环境。由于 SGLang 的依赖关系十分复杂,因此在克隆代码库后需要为其在 Python 3.12 上创建独立的虚拟环境。
git clone https://github.com/sgl-project/sglang.git
cd sglang
python3.12 -m venv .venv
source .venv/bin/activate
第二步:艰难的安装过程。首次真正的安装尝试持续了大约39分钟,期间将PyPI上的大部分软件包缓存到轮子缓存中,最终因错误而中止。
RuntimeError: cargo is required to discover the Rust extension modules
专为 GPU 推理服务器设计的 Rust 编译器。较新版本的 SGLang 提供了可选的 Rust 扩展,尽管引擎本身并不使用这些扩展,但构建工具要求必须先安装 cargo 才能继续。设置一个环境变量即可绕过此要求。
SGLANG_BUILD_RUST_EXTS=none pip install -e "./python[all]"
第二次运行又花了大约30分钟,最终顺利完成。最终得到的结果是sglang 0.5.20,针对CUDA 13构建的torch,而triton版本自动被设置为3.7.1,这正是Ampere架构的GPU为避免已知内核缺陷所需版本。
步骤3:模型下载。按照社区推荐方案需要下载两个模型:
cyankiwi/Qwen3.8-27B-AWQ-INT4是主要模型。通过INT4量化处理后,它的大小约为20 GB,分为5个分块,足以在两块24 GB的GPU上运行。z-lab/Qwen3.8-27B-DFlash2是试验性版本,大小约为3.9 GB。
步骤4:启动运行。只需一条命令即可完成,其中TP=2表示在两块3090 GPU之间实现张量并行处理。
CUDA_VISIBLE_DEVICES=1,2 python -m sglang.launch_server \
--model-path /mnt/models/Qwen3.8-27B-AWQ-INT4 \
--tp 2 \
--speculative-algorithm DFLASH \
--speculative-draft-model-path /mnt/models/Qwen3.8-27B-DFlash2 \
--speculative-num-draft-tokens 8 \
--mamba-radix-cache-strategy extra_buffer \
--disable-prefill-cuda-graph \
--cuda-graph-max-bs-decode 16 \
--mem-fraction-static 0.85 \
--context-length 65536 \
--reasoning-parser qwen3 \
--host 0.0.0.0 --port 30000
需注意两个陷阱。名为 --cuda-graph-max-bs 的参数会阻碍程序启动,因为最新版本已将其重命名,以区分解码和预填充两种模式。另外,若不使用 --reasoning-parser qwen3,API 会在普通内容字段中返回模型原始的思考过程,其中还包含嵌套的 think 标签;而加入该解析器后,输出会被拆分为结构清晰的 reasoning_content 和 content 两个字段,这与其他兼容 OpenAI 的服务器采用的结构相同,任何编写客户端的开发者都会对此感到便利。
第5步:等待完成。首次启动大约需要6分钟,直到出现“准备就绪”的提示。整个过程没有出现任何故障:SGLang通过Triton及时编译GPU内核,并在每张显卡上记录CUDA图谱。后续启动会重用缓存,大约35秒即可完成。首次启动时看似停滞的进度条实际上只是编译正在进行中。
测试结果
服务器运行正常。它能够响应指令、流式传输reasoning_content内容,并接受代理连接。以下是在该机器上实际测得的数值:
| Metric | Min | Max | Average |
|-------------------------|----------|-----------|-----------|
| Decode speed | 47 t/s | ~100 t/s | ~55 t/s |
| Prompt prefill | 342 t/s | 466 t/s | ~428 t/s |
| Accept length (DFlash) | 3.2 | 6.8 | ~4 |
这是容量示意图:
| Capability | Advertised limit | Reality |
|-------------------------|---------------------------|-----------------------|
| Max context per request | 65,536 (I set it) | fits, one at a time |
| Total KV pool | ~131K tokens max | 103K at default flags |
| Parallel slots | 48 concurrent requests | 3–6 with real prompts |
平均而言约为每秒55个标记,而非220个。令人沮丧的是,在同一模型系列中,使用llama.cpp的单块RTX 3090 Ti的生成速度可达每秒约75个标记。采用张量并行技术及复杂编译器的两块显卡,其性能反而不如使用旧方法的单块显卡。
接受率可以解释部分速度差异。当DFlash的预测正确时,8个标记中有6.8个被接受,此时批量处理速度可短暂达到每秒约100个标记;而当预测错误时,速度会降至47个/秒。即便表现最好的批量处理方式也远未达到220个/秒的标准,因此编译器并非主要问题所在,还有其他因素在消耗性能。
日志中的线索
答案一直藏在启动日志中:
Setup Custom allreduce failed with CUDART error: peer access is not
supported between these two devices.
在张量并行架构下,每个权重矩阵都会被分配到两块GPU上,且每个注意力模块和MLP模块处理完后都需要合并部分输出。在270亿参数的模型中,每生成一个标记大约需要100次数据往返传输。如此高的合并频率要求GPU之间拥有高速连接,而这两块GPU并未具备这样的功能,因此各种故障会相互叠加。
没有点对点通信功能。GeForce显卡已不再正式支持GPU-direct P2P技术,因此所有数据传输都必须通过系统RAM中转:从GPU 1输出到内存,再传送到GPU 2并返回。这不仅使数据副本数量增加一倍,还会使延迟提高三倍。
狭窄的连接带宽才是真正的问题所在。查看物理连接状态就能明白其中原因:
/sys/bus/pci/devices/05:00.0/current_link_width → 4 (max 16)
/sys/bus/pci/devices/09:00.0/current_link_width → 4 (max 16)
这两块GPU使用的都是PCIe x4接口,而非x16。每块GPU各有4条数据通道,通过主机桥接器连接,因此两张GPU之间的数据传输速率仅为1到2 GB/s,但每处理一个数据单元时,数据传输量就需要经过该链路约100次。GPU在等待数据复制时会处于空闲状态。这也就解释了为什么预填充操作的速率仅为428 t/s,而预期值应在1,200以上,同时解码操作的速率也仅约为55。
GPU本身速度很快,但它们之间的数据传输线路速度较慢。在张量并行模式下,这条数据传输线路实际上就相当于整个模型。
究竟如何才能达到220的速率?
在演示中没人会展示自己的主板。如果使用连接良好的x16接口组合,并且P2P功能正常,其卡间带宽将是当前配置的约十倍。所公布的数值很可能是由于缓存处于高温状态、测试环境理想以及代码为可预测的短时运行结果所致,并非持续运行的实际速率。该方案的软件部分确实不错,但却暗含了对少数家用电脑才具备的硬件的假设。
另外两个日常使用中的发现
上下文窗口比预期要小。两张24 GB的显存卡看似足够,但若扣除INT4权重(每张卡约10 GB)、模型构建代码、CUDA图,以及由于Qwen3.8采用混合注意力机制而需要的循环状态内存后,留给KV缓存的总空间仅约为103K个标记,在更高配置下则为131K。单个65K标记的请求可以正常处理。而宣传的48个并行处理槽位实际上并不可行,因为每个实时对话都会占用部分mamba状态池,实际只能支持3到6个会话同时进行。相比之下,llama.cpp几乎将所有显存都分配给单个模型,能够轻松处理大规模上下文。
与电源相关的重启。当两块3090同时工作,在全张量并行负载下进行预填充和草图生成时,功耗会在几毫秒内从空闲状态骤升至约700瓦特。普通电源难以承受这样的负荷。有一次这样的功率峰值触发了保护机制,导致服务器在生成数据过程中自动关机。它虽能自行重启,但这一经历表明:在这种工作负载下使用双显卡,必须配备具有足够瞬态功率承受能力的优质电源,仅凭产品包装上的功率参数是不够的。
可以在家中尝试吗?
SGLang加上DFlash2在假设条件成立时确实是一项令人印象深刻的工程技术,而这些假设实际上对应着一份硬件检查清单:
- 两个GPU均安装在真正的x16插槽中。普通游戏主板通常只提供一个x16插槽的GPU,另一个可能通过芯片组以x4速率连接。购买前务必阅读说明书,因为这一点决定了性能是220还是55。
- 良好的GPU间通信功能。这意味着需要在BIOS中启用可扩展BAR和ACS设置,或者在消费级产品之外使用GeForce或NVLink的社区驱动版本。实际上,这属于服务器主板的功能范畴。
- 具备足够瞬时功率储备的高质量电源。两块350瓦的GPU同时工作产生的电流峰值,最终会让功率不足的电源不堪重负。
- 为Qwen3.8-27B在完整上下文模式下运行而配备四块RTX 3090。这样的预算能让KV缓存不再成为性能瓶颈,而相比之下,103K参数的配置则存在明显局限。
这类软件容不得廉价硬件,它会在那些没人注意到的地方导致运行速度变慢。
虽然缺少软件及其依赖的硬件,但 llama.cpp 依然是合理的选择。它只需一张显卡、一个二进制文件,通过普通 PCIe 接口时每秒可处理约 75 个标记,无需 Rust 工具链,也不会出现意外重启。而那对 GPU 可以以 x4 的倍率独立运行。
220 这个数值是真实的,只是不适用于这款显卡,而且在检查完显卡之间的连接线之前,很可能也不适用于其他许多显卡。