首页 / 文章 / 在配备已修复版vLLM与DFlash2的RTX 3090上运行Qwen3.8-27B模型

在配备已修复版vLLM与DFlash2的RTX 3090上运行Qwen3.8-27B模型

带有重新量化嵌入层及DFlash2预测功能的固定版vLLM 0.28.0分支如何在24 GB显存上运行27B规模的混合模型,以及为何长上下文会降低其运行速度。

3193 词

在单块消费级GPU上运行参数量为270亿的模型时,通常需要在上下文长度、并发能力与处理速度之间做出艰难抉择。专为Qwen3.8-27B优化的vLLM社区版本在24 GB容量的RTX 3090上改变了这一状况:在受300瓦功率限制的普通(非Ti系列)显卡上,该模型在代理任务处理场景下的解码速度可达每秒约177个标记。本指南将介绍无需容器的原生安装方法,解释哪些优化措施带来了如此高的速度,并明确指出该方法的局限性,帮助您判断其是否适合您的任务需求。

下文介绍的两种启动配置是在上下文长度与并行处理能力及速度之间进行权衡的结果。这些数据来自单台设备上的代理任务测试,因此仅可作为参考,而非绝对保证。

| Config     | Context   | Parallel requests | My measured decode speed |
|------------|-----------|-------------------|--------------------------|
| CTX=fast   | 65,536    | 8                 | ~177 tok/s               |
| CTX=long   | 131,072   | 4                 | ~122 tok/s               |

由于服务器提供了兼容 OpenAI 的 API,因此只需将客户端指向形如 http://<host>:18020/v1 的基础地址并提供访问密钥,即可将其接入 DeepSeek Harness 等智能体框架中。此后子智能体可以并行向本地模型发送请求,其响应速度甚至可媲美传统的数据中心硬件。

qwen38-27b-rtx3090 项目究竟是什么

该项目位于 syv-ai/qwen38-27b-rtx3090 仓库中。它既不是新的模型,也不是新的推理引擎,而是基于 vLLM 0.28.0 版本进行的分支版本,同时还包含一系列补丁、量化脚本以及启动配置文件,这些内容的唯一目的就是让这款混合模型能够在 24 GB 显存的上运行并实现快速响应。

已提供 Docker 镜像,如果您愿意使用容器,只需执行 docker compose --profile single up -d 即可完成全部安装。该仓库也支持在 Python 虚拟环境中手动执行相同步骤,这也是此处采用的方法。这种方式能让日志清晰可见,避免在磁盘上生成另一个大型镜像,同时更便于查看每一步带来的变化。

为何单一的 tok/s 数值意义不大

该技术栈的吞吐量很大程度上取决于任务类型。生成代码的速度很快,而撰写开放式散文则较慢;对于那些已经存在于提示词中的文本,复现速度则会快得多(下文的“查找-起草”部分会解释原因)。项目文档也强调了这一点:除非知道是哪个提示词生成的,否则该技术栈的吞吐量数据毫无意义。177 tok/s这一数值是在使用智能体任务时测得的;您的实际结果将更多地取决于提示词的内容,而非随机因素。

直接安装,无需 Docker

在开始之前,请确保机器具备以下条件:

  • 安装了 RTX 3090 及其任何版本的 Linux 或 WSL2 系统(关键在于 24 GB 的显存)
  • 安装了最新版本的 NVIDIA 驱动程序,以便 nvidia-smi 能够正常运行
  • 安装了 Python 3.12 或更高版本,以及相应的开发头文件
  • CUDA 13 工具包,或至少其头文件(参见第2步中的相关注意事项)
  • 用于存储模型和缓存的大约60 GB的可用磁盘空间
  • 对于系统级前置条件,一个实用的捷径是让具有shell访问权限的编程工具(如Claude Code、Codex、OpenCode或类似工具)来负责安装工作。将其指向代码仓库,让其在你配备3090显卡的机器上搭建好所需环境。一旦出现故障,该工具会读取错误信息并立即解决,无论是通过apt安装python3.12-dev或libssl,升级驱动程序,还是更换CUDA工具包。这样一来,原本需要花整个下午在论坛上搜索缺失软件包的问题就变成了常规操作。你可以像检查任何具有shell访问权限的工具一样,查看该工具实际执行了哪些操作。

    第1步:克隆代码仓库

    首先获取代码仓库并进入其中。

    git clone https://github.com/syv-ai/qwen38-27b-rtx3090
    cd qwen38-27b-rtx3090
    

    步骤2:创建虚拟环境并安装指定版本的vLLM

    此次分支针对的是vLLM 0.28.0版本,因为正是在该版本中,DFlash2的推测解码功能被整合进了上游的vLLM代码库(通过PR #52816合并)。以下命令会创建一个全新的虚拟环境,并安装该精确版本以及FlashInfer、Hugging Face的下载工具、用于内核构建的和。

    python3.12 -m venv venv
    venv/bin/pip install -U pip
    venv/bin/pip install vllm==0.28.0 huggingface_hub hf_transfer ninja \
      flashinfer-python flashinfer-cubin==0.6.13 pandas
    

    这里有两个容易出错的细节:

    • 切勿让pip将flashinfer-python升级到其他版本。启动脚本设置了FLASHINFER_DISABLE_VERSION_CHECK=1,而且所有补丁都是针对这一特定版本组合进行验证的。
  • DFlash2采样路径会在运行时编译一个FlashInfer内核,该内核包含curand.h。如果您的CUDA安装缺少curand头文件,首次启动时编译就会失败,服务器会悄悄切换到速度更慢的备用方案。输出结果依然正确,但吞吐量会下降约5%,日志中也不会有任何提示。在安装了CUDA 13的Ubuntu系统中,通过apt安装libcurand-dev-13-0即可解决此问题。
  • 第二点是一个很好的例子,说明有些故障是健康检查无法检测到的。如果您的数值比正常值低几个百分点,应首先检查这些头文件。

    步骤3:下载基础检查点

    起始点是大小约为19.5 GB的dbirks/Qwen3.8-27B-W4A16-AutoRound检查点。启用高性能的Xet传输模式可以显著加快下载速度。

    HF_XET_HIGH_PERFORMANCE=1 venv/bin/hf download \
      dbirks/Qwen3.8-27B-W4A16-AutoRound \
      --local-dir models/Qwen3.8-27B-W4A16-AutoRound
    

    步骤4:准备模型

    prepare/目录中的脚本用于修改检查点。它们会对输入和输出嵌入矩阵进行重新量化——而公共版本的量化检查点仍保持全精度——然后围绕更合适的词汇表重建MTP草稿头,最后下载快速版本以及1.2 GB大小的DFlash2草稿文件。所有操作都在CPU上完成,每个脚本的运行时间均在几分钟之内。

    M=models/Qwen3.8-27B-W4A16-AutoRound
    venv/bin/python prepare/quant_lm_head.py     $M
    venv/bin/python prepare/quant_embed.py       $M
    venv/bin/python prepare/quant_mtp.py         $M
    venv/bin/python prepare/build_draft_vocab.py $M --ids prepare/draft_vocab_ids.json
    venv/bin/python prepare/fetch_fast_variant.py
    venv/bin/python prepare/fetch_dflash2.py
    

    第5步:应用补丁堆栈

    patches/目录中大约有十五个.patch文件,内容涉及嵌入量化连接实现、用于草稿验证的分割KV注意力机制、Marlin int8内核的修复、DFlash2查找功能的实现等等。这些文件的顺序由patches/series文件指定。下面的脚本会删除该文件中的注释和空行,然后将每个补丁应用到虚拟环境中的vLLM包中。它会跳过dflash2-backport.patch,因为该文件仅适用于0.28.0之前的vLLM版本,在那些版本中DFlash2尚未成为内置功能。

    sed -e 's/#.*//' -e 's/^[[:space:]]*//;s/[[:space:]]*$//' -e '/^$/d' patches/series |
    while IFS= read -r name; do
      [ "$name" = "dflash2-backport.patch" ] && continue
      patch -p1 -d venv/lib/python3.12/site-packages/vllm < "patches/$name"
    done
    

    如果补丁应用失败,几乎总是由于版本不匹配造成的。请运行venv/bin/pip show vllm,确认显示的版本确实是0.28.0。

    步骤6:验证安装结果

    在启动任何功能之前,先生成一个API密钥,并以离线模式运行验证脚本。该脚本会检查所有补丁是否已应用,以及模型的结构是否符合预期。

    openssl rand -hex 24 > api_key.txt
    bash verify.sh --no-server    # checks all patches applied + model shape correct
    

    如果验证结果正常,说明您的安装版本与维护者用于基准测试的版本完全一致,每个字节都不差。对于自托管推理场景而言,细微的版本差异常常会引发混乱,因此这种可重复性显得尤为珍贵。

    第7步:启动服务器

    所有配置均通过环境变量完成。默认命令会在选定的GPU上启用DFlash2预测功能及前缀缓存;注释中说明了如何切换到长上下文模式,或在运行kvarn/install.sh后切换到245k模式。

    CUDA_VISIBLE_DEVICES=0 SPEC=dflash2 PREFIX_CACHE=1 bash single-user/start_qwen.sh
    # add CTX=long for ~131k context (4 slots), or CTX=huge after kvarn/install.sh for 245k
    

    务必明确设置 CUDA_VISIBLE_DEVICES。否则 vLLM 会倾向于选择可用内存最多的 GPU,而在多 GPU 机器上这可能并非您想要的显卡。承载密钥从 api_key.txt 或 VLLM_API_KEY 中读取;在将端口开放给本地主机以外的设备之前必须设置该密钥,因为没有密钥的话服务器会接受未经身份验证的请求。其余设置则来自 .env 文件。启动几分钟后,您就能拥有一个兼容 OpenAI 的接口,能够利用仅价值一部二手自行车价格的显卡运行 27B 模型。

    速度的来源

    在现成的量化检查点上运行 stock vLLM 会在多个不易察觉的地方浪费性能。该项目的优化文档(仓库中的 docs/optimizations.md)介绍了九项改动,其中四项带来了大部分性能提升。

    被大家忽视的嵌入量化

    Qwen3.8-27B 使用的是未绑定的嵌入,即独立的输入表和输出表,每张大约 2.5 GB。公开的“量化”检查点仍将这两部分都保存为 bf16 格式,主要是因为对它们进行量化操作较为繁琐。准备脚本会将这两部分都转换为 int8 格式,从而节省 2.6 GB 的显存,且不会造成可检测的质量损失。在 24 GB 的显卡上,这大约相当于多支持一个用户的上下文量。

    充分利用混合架构

    该模型的64层中仅有16层是传统注意力层,其内存会随上下文变化而增长。其余48层为Gated DeltaNet层,这是一种线性注意力设计,无论对话长度是100个标记还是100,000个标记,其每次对话的状态大小都是固定的。Stock vLLM使用fp32格式存储该状态,每次请求约需150 MB内存,尽管配置的并发请求上限为64,实际却无法支持超过37个同时进行的请求。而分支版本则使用fp16格式存储,困惑度在三位小数精度上保持不变,且所有配置的槽位均可正常使用。

    DFlash2:单次处理即可生成7个标记

    这一变化对单次请求的延迟影响最为显著。推测性解码会将一个较小的草拟模型与较大的目标模型配对。草拟模型会提出若干个后续token,目标模型则通过一次前向传播即可验证所有这些token,从而保留正确的前缀,并从第一个错误处重新生成。在内存受限的GPU上,验证的成本远低于生成成本,因为权重只需读取一次即可处理多个token,因此每一步都能生成多个token。

    Qwen内置的MTP引擎会依次进行四次预测。而DFlash2则取代了它,这是一种五层结构的生成器,能够在单次非自回归处理中利用来自目标模型不同深度的隐藏状态,预测出整个包含七个标记的块。该生成器本身经过GPTQ量化处理,大小从3.85 GB降至1.19 GB,因此无需与其它组件激烈争夺内存带宽。这样一来,每步处理的标记数从1个提升到大约3个。由于标准的推测解码机制会接受或拒绝这些临时生成的标记,从而使最终输出分布与目标模型保持一致,因此这种加速方式不会造成任何损失;根据仓库报告,在所有配置下,GSM8K的准确率都能保持在96%到96.5%之间。

    基于模型自身输出构建的临时词汇表

    生成器只能提出其简化后的输出词汇表中存在的标记;超出该范围的标记按定义会被拒绝。最初的词汇表是从网络文本中提取的,涵盖了该模型实际生成的92%的标记。维护人员收集了该模型自身输出的540万个标记,并根据这些数据重新构建词汇表,使覆盖率达到了97.5%。仅这一项改动就提升了约10%的处理效率,而且无需新增硬件,也不需要修改模型本身。

    针对模型引用的文本的查表生成方式

    还有一种技术可以解释那些极端的数值。当输出内容重复了提示词中已有的信息,比如正在编辑的源代码或粘贴过的文档时,该技术会绕过生成器,直接从上下文中提取标记来生成内容。在参考硬件上,处理长度为25k标记的文档时,其生成速度可达381标记/秒。编码智能体大部分时间都在重复提示词中稍早出现的代码,而这正是该技术最理想的适用场景,这也是为何使用这些智能体进行测试时性能会如此之高的原因之一。

    为何将上下文容量翻倍也不会耗尽VRAM

    单用户启动器默认的上下文容量为65k。将设置改为CTX=long后,该数值大约会翻倍至131k,按理说24 GB容量的显卡应该会出现内存不足的错误。但实际上服务器仍能正常启动,只是运行速度稍慢而已。这一现象可由几项设计决策来解释。

    • 权重占用的空间是固定的。采用W4A16量化后,模型主体大约需要15 GB的空间,这一数值与上下文长度无关。
    • KV池会一次性按字节预留。在处理任何请求之前,该框架会预先分配固定容量的内存,此处约为5.2 GiB,vLLM会记录相关结果,例如“GPU KV缓存大小:136,429个标记”。由于KV池是在启动时分配的,要么当时就能容纳所有数据,要么服务器就会拒绝启动。不存在可在任务执行过程中按请求动态分配内存且不会中途失败的方式。
  • 长上下文模式使用更便宜的令牌,而非更多内存。默认模式下,注意力缓存以bf16格式存储,而设置CTX=long时则用int8格式存储,这使得每个令牌的成本大约降低一半。同样的5.2 GiB内存现在可以容纳136,429个令牌,而非之前的68,605个,最大上下文长度也因此提升至131k。对于相同文本,在两种模式下教师强制困惑度仅相差0.2%。
  • 大多数层并不会随上下文长度增加而变化。仅有16个注意力层会随着序列长度的变化而调整,其余48个DeltaNet层则保持固定状态。因此,在这种架构下处理长上下文的成本远低于纯Transformer模型,这也是该模型能够运行在单张显卡上的重要原因之一。
  • 为何上限为131,072而非136,429

    并非所有内存资源都能用于某个请求的缓存。每个正在处理的请求还需要固定大小的DeltaNet状态空间,而DFlash2还在其基础上为每个请求额外增加了八个推测性状态槽位。由于长配置模式下仅有四个可用位置,启动程序将最大上下文大小设定为131,072,这一数值约为内存池容量的4%,其余空间则用于存储那些状态页面以及处理数据块对齐问题。正是这些剩余空间保障了“不会内存不足”的承诺:启动时系统会检查最苛刻的场景,即单个最长长度的请求同时占用所有剩余空间时的情况。

    你需要放弃的东西

    更长的上下文处理需要以速度为代价,而非崩溃。令牌以int8格式存储,每个正在处理的请求会从现在数量更少的状态页池中分配页面。并行处理槽数从8个减少到4个,解码速度也从大约177令牌/秒降至大约122令牌/秒。该显卡能在相同的字节数据量下完成更多工作,其收费依据是吞吐量而非异常次数。

    它的不足之处:深度单请求上下文处理

    该模型在处理短上下文和中等长度上下文时表现最佳,而这正是智能体通常发送的内容类型。当单个请求的token数接近10万时,表现则有所不同。在某台3090显卡上测试显示:对于短提示语,解码速度约为107 token/秒;当token数约为1万时,速度降至约78 token/秒;在4.3万token左右时为38 token/秒;而接近10万token时则下降到约31 token/秒。其原因是模型对内容的接受率会随着上下文深度的增加而降低。该仓库观察到的现象也一致,无论缓存类型如何,接受率都稳定在0.29左右,因此这是模型及上下文深度本身的特性,而非尚未修复的漏洞。

    作为对比,基于llama.cpp的同类显卡版本在15万上下文长度下能保持65到80 tok/秒的稳定性能。它没有会导致预测质量下降的草拟器,也没有会停止产生收益的验证模块,因此既不会特别快也不会特别慢。下表将两者进行对比;请注意,vLLM的短上下文测试数据是将单次请求的数值与多槽代理的测量结果混合在一起的。

    | Context depth   | vLLM fork (DFlash2) | llama.cpp ATX fork |
    |-----------------|---------------------|--------------------|
    | short (<4k)     | 107–177 tok/s       | 65–80 tok/s        |
    | ~10k            | ~78 tok/s           | 65–80 tok/s        |
    | ~43k            | ~38 tok/s           | 65–80 tok/s        |
    | ~100k           | ~31 tok/s           | 65–80 tok/s        |
    

    实际选择原则很明确:如果你的工作主要是处理需要缓慢读取的极长文档,那么llama.cpp是更稳定的选择;关于这种权衡的更多细节,可参阅我们关于使用llama.cpp张量卸载技术在RTX 3090上运行Qwen3.8 MoE模型的指南。如果你的工作是许多中等长度的编程对话,那么vLLM版本则优势明显。

    为何它适合智能体工具包

    Pi、Hermes或DeepSeek Harness这类工具包无法处理单次对话。借助子智能体,它们可以同时生成多个并行任务,通常为5到12个,每个任务的长度适中,且大多会复用相同的系统提示和代码库上下文。这正是该分支所优化的负载场景:

    • 总处理能力随并发数提升而增长。在单块3090显卡上,4个并行流的总处理速度可超过300 tok/s。该仓库的参考测试数据显示,4个并行请求时的处理速度为279至335 tok/s,而使用MTP技术时8个并行请求的总处理速度可达到约400 tok/s。
  • 前缀缓存使得共享上下文的成本几乎为零。当设置PREFIX_CACHE=1时,64次请求共享同一个5.8千token的系统提示语仅需17秒即可完成,而该仓库的基准测试中则需要222秒,延迟中位数从95秒降至8秒。在第一次请求之后,子智能体获取共享指令几乎不需要任何成本。在这种混合模型中,缓存不仅存储注意力KV值,还存储重复出现的DeltaNet状态,因此处理24千token的文档时,后续响应仅需约1秒,而非23秒。
  • 查找式草拟功能能与智能体输出保持一致。那些不断进行重构、重写和编辑的智能体会反复引用其输入内容,正因如此,处理速度才能达到380多token/秒的峰值。
  • 存在上限。当同时处理的长期上下文流超过大约四个时,限制因素在于资源池的划分方式,而非计算能力。相关文档明确指出,八个并行处理的长期上下文请求的性能会远逊于四个,甚至被描述为“并非权衡,而是损失”。以中等深度设计约四个并行工作单元的架构,才能让显卡发挥最佳性能。如需了解本地 Qwen3.8-27B 环境在实际应用中的构建示例,请参阅使用 Qwen3.8-27B 和 Pi 构建本地 Angry Birds 克隆版。

    关键要点

    • 速度的提升源于软件层面:重新量化的嵌入向量、fp16 格式的 DeltaNet 状态、七个标记的 DFlash2 编码器,以及针对模型实际输出定制的词汇表。GPU 自身并未改变。
  • 在信任任何测试结果之前,先固定仓库中标记的所有版本,安装相应的头文件,然后运行verify.sh。
  • 启动时预留的KV池可将内存不足错误转化为启动阶段的决策,而高级模式则通过以占用更多存储空间和降低速度为代价切换到int8缓存来获取更多上下文信息。
  • 推测性解码的性能会随上下文深度的增加而下降,因此对于长度远超40k个标记的单一请求,使用llama.cpp可能更为合适。
  • 将处理进程的数量设置为约四个中等深度的worker,并启用前缀缓存,同时始终报告相应的吞吐量以及产生该数值的工作负载情况。
  • 如需更多详细信息,仓库中的docs/reproductions文件夹包含了逐步指导如何在3090显卡上实现非Docker环境下的运行的说明。