☰
Qwen3.5-397B私有化部署实战:从MoE架构到SGLang性能调优
2026/10/22 8:22:35 网站建设 项目流程

最近我们团队刚完成了一个大模型私有化部署项目,模型是 Qwen3.5 系列里比较特殊的一个:Qwen3.5-397B-A17B,也就是常说的 Qwen3.5-Plus 底座模型。整个部署过程从硬件规划、框架选型到性能调优,前前后后折腾了两周多,中间踩了不少坑,也沉淀了一套可以复用的打法。这篇文章就把这次企业级部署案例完完整整拆开,把为什么这么选、每一步怎么操作、遇到了哪些问题都讲清楚,希望对正在做类似事情的团队有点帮助。

先交代下背景。我们这次部署的目标不是给内部几个工程师玩,而是要把模型作为基础能力开放给公司多个业务线,承担阅读理解、内容生成、信息抽取这类高并发任务。所以对吞吐量、延迟、稳定性都有明确要求。模型本身是 MoE 结构,397B 总参数量、17B 激活参数,这种规模在企业内网环境里落地,和跑一个 7B/14B 的稠密模型完全是两个量级的事。本文内容比较长,适合正在规划或已经上手做 300B 以上 MoE 模型私有化部署的读者,也会照顾到刚开始接触大模型推理的新手,关键概念我会尽量解释得直白一些。

1. 项目背景与部署目标

1.1 为什么企业需要私有化部署 Qwen3.5-Plus

先说结论:用 API 其实最省事,但很多企业场景根本不能把业务数据送出内网。

我们这次的需求来自几个核心业务系统,涉及内部文档、用户行为数据、敏感业务流程。数据合规要求所有处理和推理都必须发生在我们自己的机房里,数据不能出域。这就把方案直接圈定成私有化部署,而且最好是纯内网运行,连外网模型仓库都不能依赖。

另外一个原因是量和成本。业务高峰期的调用量非常大,如果全部走外部 API,按 token 计费,单月成本会高得惊人。自建推理服务,虽然前期要投入硬件,但模型是自己掌握的,单位成本可以随利用率下降。尤其 MoE 模型激活参数只有 17B,单位 token 的算力成本远低于同等总参数量的稠密模型,跑起来之后每 token 成本能压到 API 的几分之一。

还有一个容易被忽略的点:私有化部署之后,模型服务可以被当作一个内部基础设施,像数据库一样被各业务系统直接调用,周边工具链、评估体系、监控告警都可以围绕它搭建。这种可控性在长线业务规划里比省一点 API 费用更重要。

1.2 硬件环境与集群规划

这次部署的硬件是 8 卡节点组成的 GPU 集群,单卡显存统一是 80GB。我们用了 4 个节点共 32 张卡,型号是当前主流的旗舰训练卡。为什么是 32 卡?不是随手拍脑袋定的,后面会详细算。

先算模型本身需要多大显存。Qwen3.5-397B-A17B 总参数量 3970 亿,按 FP16/BF16 精度每个参数占 2 字节,光权重就要 397B × 2B ≈ 794GB。就算激活参数只有 17B,推理时所有专家权重还是得放在显存里,否则每次只加载激活的那部分,代价是磁盘和内存之间的高频换入换出,延迟会完全不可接受。所以 794GB 的权重必须常驻显存。

再看 KV Cache。我们业务以长上下文为主,平均输入长度约 2000 token,输出约 500 token,算上并发量,KV Cache 还会再占掉一块显存。按 32 卡来估算,单卡 80GB,总共 2560GB 显存,扣掉模型权重 794GB,还剩下 1766GB 给 KV Cache、激活值和计算临时缓冲,空间其实很宽裕。但我们目标是高并发,不是只跑单并发,所以多卡并行方案要在这个基础上继续抠显存和吞吐。

集群网络用的是 RoCE 高速网络,跨节点带宽 400Gbps,单节点内部 NVLink 互联。这种网络结构对后续的张量并行非常关键,如果节点间网络只有万兆,那 397B 模型的多机并行基本没法玩,通信开销会直接把计算时间吃掉。

2. 模型架构与部署方案选型

2.1 Qwen3.5-397B-A17B 的 MoE 架构要点

Qwen3.5-397B-A17B 是混合专家(MoE)结构。简单说,它不是每个 token 都让全部 3970 亿参数参与计算,而是通过一个路由(Router)机制,只激活其中的一部分参数。这里的 17B 就是每次计算实际被激活的参数量,所以虽然模型文件巨大,但单位 token 的推理算力需求接近一个 17B 的稠密模型。

MoE 模型内部通常分成两个部分:共享专家(Shared Experts)和路由专家(Routed Experts)。共享专家每个 token 都要经过,路由专家则由门控网络选择,只有少量 top-k 专家被激活。Qwen3.5-397B-A17B 在这一代强化了专家路由的负载均衡,避免某些专家过热、某些专家常年不干活。它还引入了跨层专家共享的机制,也就是不同层之间可以复用部分专家参数,这进一步提高了参数效率。

理解这个结构对部署的最大意义在于并行策略的选择。因为总参数很大,单张卡无法放下一层完整的模型,所以我们要用多种并行方式组合,把 397B 的权重切到 32 张卡上。而 MoE 的专家层天然适合做专家并行(EP),因为专家之间没有强依赖,可以把不同专家分布到不同设备上,路由结果只会让部分设备参与计算。

2.2 推理框架对比与选型

当前企业私有化部署里,主流的开源推理框架基本就那几个:vLLM、SGLang、TensorRT-LLM,还有 TGI。我们把这几个都做了一遍对比,最终选了 SGLang 作为主框架,vLLM 作为备用。

直接抛结论:对于 300B+ MoE 模型、多机多卡、追求高吞吐的场景,SGLang 的 RadixAttention 和更激进的调度策略优势明显。RadixAttention 会在 KV Cache 上做前缀复用,当多条请求共享相同前缀(比如系统提示词、历史对话)时,这部分 KV Cache 可以直接复用,能省掉一大块重复计算。我们的业务请求里有大量相同系统提示词,这个特性实测能把整机吞吐拉高 30% 以上。

vLLM 当然也很强,生态成熟、资料多,但它在处理超长上下文的连续前缀复用上不如 SGLang 细致。TGI 的优势是部署简单,但性能上限和灵活性差一些,动态 batching 不如前两者激进。TensorRT-LLM 需要自己处理模型编译,对 MoE 的 EP 支持配置复杂,后期维护成本高,不太适合我们这种需要频繁调优的团队。

选型还有一个隐藏考量:社区活跃度和问题响应速度。我们在实测过程中遇到过一个多机通信问题,SGLang 社区响应非常快,贡献者给了明确配置建议,这点在排障章节会提到。

框架吞吐表现长上下文支持MoE并行支持配置复杂度社区活跃
vLLM高良好支持 TP/EP中极活跃
SGLang极高优秀支持 TP/EP中高活跃
TensorRT-LLM高一般支持但配置复杂高中
TGI中一般支持有限低中

2.3 并行策略(TP/PP/EP)设计

397B 模型部署,最先回答的问题是:模型怎么放得下。答案是用张量并行(TP)把每一层权重切到多个 GPU 上。我们单节点 8 卡,先用 TP=8 保持一个节点的张量并行,这样每层的权重被切成 8 份,每个 GPU 只存一份切片。但跨节点做 TP 会引入大量 AllReduce 通信,两个节点之间的带宽再高也还是不如单节点 NVLink,所以我们不考虑跨节点 TP。

那 4 个节点之间怎么协作?两种思路:流水线并行(PP)或者专家并行(EP)。PP 是把不同的层分给不同节点,数据像流水线一样依次流过各层;EP 是把 MoE 的专家分布到不同节点。PP 的问题是流水线会引入气泡(bubble),有些设备在等上游数据,利用率打折扣,而且 4 个节点之间如果要严格控制每层的依赖,调度复杂度不低。EP 更适合 MoE 模型,因为路由专家天然相互独立,把不同专家副本散到节点间,token 被路由到对应节点只激活需要的专家即可,通信量可以控制。

最终我们的并行配置是:TP=8 × EP=4。单节点内做 TP,跨 4 个节点做 EP,这样既保证了单节点内的权重切片效率,又把跨节点通信降到相对可控的水平。这个方案下,每个 GPU 上除了有 TP 切分后的共享层权重,还持有 EP 切分后的一部分专家权重。激活参数 17B 与总参数 397B 的巨大差异,决定了 EP 是本方案最核心的加速手段:一次前向计算,实际参与的专家可能只覆盖全部专家的很小一部分,理论上可以大幅降低计算密度。

还有一点被很多人忽略:模型路由网络那一层也需要放到显存里,而且它在每层都要参与,所以路由网络一般不会被 EP 切分,而是跟随 TP 切分。这部分参数不大,但会影响内存排布,后面讲显存规划时会再提。

3. 实操部署全过程

3.1 环境准备:驱动、容器、CUDA

先说明我们最终使用的环境版本,不一定是最新的,但实测稳定。操作系统是 Ubuntu 22.04 LTS,内核 5.15。GPU 驱动版本 535.154.05,CUDA 工具链用 12.1,PyTorch 编译时用的 CUDA 版本也是 12.1。容器环境用的 CUDA 12.1 镜像,并在其中安装了最新版 SGLang 和 vLLM。

环境准备有一个常见的坑:驱动版本和 CUDA 版本不匹配,会导致容器内调用 GPU 失败,但表面症状是torch.cuda.is_available()返回 false,很多人会误以为是 PyTorch 装错了。建议先用nvidia-smi确认驱动支持的 CUDA 版本,再用nvcc --version看容器内 CUDA 版本,两者必须匹配。我们直接用 nvidia 官方 CUDA 容器镜像,内置了匹配驱动版本的库,基本避免了这类问题。

还需要设置好共享内存大小。多进程推理框架会频繁交换中间数据,容器默认的/dev/shm只有 64MB 肯定不够,建议启动容器时加上--shm-size=20g。我们的启动参数大致如下:

docker run -d --gpus all --ipc=host --shm-size=20g \ -v /data/models:/models \ -v /data/logs:/logs \ -e NCCL_SOCKET_IFNAME=ib0 \ -e NCCL_IB_GID_INDEX=3 \ sglang/sglang:latest \ python -m sglang.launch_server \ --model-path /models/Qwen3.5-397B-A17B \ --tp 8 --ep 4 --host 0.0.0.0 --port 8000

这里--ipc=host也很关键,可以让容器内进程共享宿主机的进程间通信资源,避免某些并操作时出现 shared memory 不足的报错。

3.2 模型权重下载与格式转换

模型权重是从官方渠道下载的原始格式,通常是多个分片文件,每个文件几个 GB 到十几 GB。SGLang 和 vLLM 都推荐使用 SafeTensors 格式,因为可以惰性加载权重,不用一次性把 794GB 全部读进内存再分发到显存。

如果下载下来的是 PyTorch 的.bin权重,需要先转成 SafeTensors。建议用transformers库的转换脚本,指令差不多是:

python -m transformers.convert_safetensors \ --input_dir /data/models/Qwen3.5-397B-A17B-bf16 \ --output_dir /data/models/Qwen3.5-397B-A17B-safetensors \ --override

转换完成后,再用quantization工具或直接让推理框架做量化和格式转换。我们当时为了稳妥,直接用 BF16 精度加载,并没有做更低比特的量化,原因后面单说。权重目录里必须有config.json、tokenizer.json和generation_config.json,否则启动时可能报缺少文件的错误。

3.3 基于 SGLang 启动多机服务

跨 4 个节点的启动需要区分主节点和工作节点。SGLang 通过环境变量来识别多机拓扑。我们用的启动脚本核心逻辑如下:

在主节点上:

python -m sglang.launch_server \ --model-path /models/Qwen3.5-397B-A17B-safetensors \ --tp 8 \ --ep 4 \ --host 0.0.0.0 \ --port 8000 \ --dist-init-addr 192.168.1.10:5000 \ --nnodes 4 \ --node-rank 0

在工作节点上,除了--node-rank分别设为 1、2、3,其他参数保持一致。SGLang 会通过dist-init-addr作为分布式初始化入口,所有节点必须能在同一网络平面访问该地址。我们当时踩的一个坑是:主节点选用了某个网段,但工作节点走的是另一个网卡,导致 NCCL 初始化超时。解决办法是显式指定NCCL_SOCKET_IFNAME为业务网卡名,四台机器统一。

启动成功后,SGLang 会打印模型加载日志,包括总显存占用、各个并行维度的大小、KV Cache 池的预估容量等等。这一步不要急着接业务流量,先看日志确认模型加载无误。

3.4 量化与 KV Cache 优化

很多教程一上来就推荐 INT8 或 INT4 量化,以此降低显存、提高吞吐。但我们这次没有做权重量化,核心原因是业务对输出质量非常敏感,特别是一些专业领域的术语生成,低比特量化带来的精度损失会在长尾样本上放大。企业级场景里,一个不稳定的模型比一个慢一点的模型危害更大。

不过不做权重量化不代表不做 KV Cache 优化。SGLang 支持 KV Cache 的 FP8 存储,也就是把 Key/Value 缓存从 BF16 降到 FP8。KV Cache 是动态生成的,对精度的影响比权重量化小很多,但能显著节省显存。我们在压测阶段做了一组对比,开启 FP8 KV Cache 后,同等显存下可以容纳的并发请求数提升约 40%,而最终生成结果和 BF16 没有可观察到的差异。

开启方式是在启动参数中加--kv-cache-dtype fp8_e5m2。需要留意的是,FP8 KV Cache 需要 GPU 支持 FP8 运算,当前主流的旗舰卡都支持,如果用的是较老的卡,可能不兼容。

还有一项优化是--chunked-prefill-threshold。长输入请求的前缀解码阶段如果一次性做,会占用大量显存,我们把它和--max-running-requests配合使用,确保首个 token 的延迟(TTFT)稳定而不被长输入请求拖到崩溃。

4. 性能调优与压测

4.1 吞吐量、TTFT、TPOT 指标观测

推理服务上线前必须做系统的性能摸底。我们主要看三个指标:TTFT(Time To First Token,首个 token 生成延迟)、TPOT(Time Per Output Token,每个输出 token 的平均耗时)、吞吐量(每秒生成 token 数)。

直接看我们的压测数据。在 32 卡、BF16 权重、FP8 KV Cache 配置下,用官方压测工具跑了一组 2000 并发请求,输入长度 1024 token,输出长度 512 token,结果如下:

指标数值
平均 TTFT480ms
平均 TPOT42ms(约 24 token/s)
总吞吐约 8500 token/s
显存峰值约 75GB/卡

8500 token/s 的吞吐对 397B 模型来说已经是不错的水平,主要归功于 MoE 激活参数只有 17B,以及 EP 并行让计算没有跑满 397B 的完整计算图。如果未来进一步压榨,可以考虑在业务允许范围内再做 4-bit 量化,吞吐可能翻倍,但精度风险要重新评估。

4.2 关键参数调优(max_num_seqs、gpu_memory_utilization 等)

SGLang 有几个参数直接决定性能上限。

--max-running-requests控制同时运行的最大请求数。调大这个值,理论上可以提升吞吐,但会占用更多 KV Cache 显存,也拉高 TPOT。我们最初设置为 512,压测发现 TTFT 涨到了 800ms,后来调回 256,TTFT 落到 480ms,吞吐几乎没有变化,这就是典型的过调度导致排队延迟。

--gpu-memory-utilization控制每个 GPU 上可用于 KV Cache 的显存比例。我们设置在 0.85 左右,预留 15% 给权重加载和计算临时缓冲。调成 0.9 虽然可以多放一点 KV Cache,但一旦有人请求触发了更大的临时张量,OOM 会突然出现,稳定性优先,所以建议保守一些。

--max-input-token-length和--max-total-token-length也需要按业务限制。如果业务输入最长只有 3000 token,就没必要让 KV Cache 默认给 32K 上下文预留空间,把上限调低,能腾出更多显存给并发。这个思路很简单,但很多人懒得调,结果在线上一跑就 OOM。

还有一个重要参数是--schedule-policy。SGLang 提供多种调度策略,我们实测在混合长短请求的场景下,lpm(最长前缀匹配)配合前缀缓存效果最好,因为业务里有大量共享系统提示词。

4.3 多机多卡通信优化

多机部署最大的瓶颈是通信。EP=4 意味着每个 token 的路由专家可能分布在另外的节点上,跨节点传输专家输入的张量。我们四台机器之间用的是 RoCE 网络,NCCL 配置直接影响通信效率。

首先,启用 GPUDirect RDMA,让 GPU 直接访问网卡,不经过 CPU 内存拷贝。环境变量如下:

export NCCL_IB_DISABLE=0 export NCCL_IB_HCA=mlx5_0,mlx5_1 export NCCL_IB_GID_INDEX=3 export NCCL_IB_TIMEOUT=22

NCCL_IB_GID_INDEX这个参数最容易出错,它需要根据当前 RoCE 网卡的 GID 配置来确定,不对的话会报No GID found for device。我们最开始统一设成 3,主节点正常,工作节点却一直初始化失败,后来检查发现工作节点上的 IB GID index 是 5。所以不要盲目复制别人的环境变量,每台机器用ibv_devinfo查看具体值再设置。

另外,我们打开了NCCL_DEBUG=INFO观察通信初始化过程,确认每个节点之间都能建立连接。这里有一个关键经验:如果看到NET/IB : no more ports to open,多半是网卡名称配置不对,需要用ibv_devinfo找出实际名称。

5. 常见问题与排查技巧实录

5.1 显存 OOM:反复出现的头号敌人

部署期间我们至少遇到三种 OOM。

第一种是加载权重时 OOM。原因是 SGLang 在加载模型时除了权重,还会分配一些临时的中间缓冲区,如果gpu-memory-utilization设置过高,比如 0.95,模型加载后立即报显存不足。解决办法是先调低到 0.8 完成加载,稳定后再逐步上调,每次上调后跑一遍健康检查。

第二种是 KV Cache OOM。这通常发生在高峰并发时,表现为某个请求直接报CUDA out of memory,服务自动重启。排查时先用nvidia-smi看显存占用,再用 SGLang 的日志确认KV Cache pool size。KV Cache 池太小,要么调高gpu-memory-utilization,要么限制最大并发,二选一,没有两全其美的办法。

第三种是碎片化 OOM。MoE 模型的激活值在 EP 切分后,不同批请求可能落在不同专家上,显存分配不是均匀的,长时间运行会导致显存碎片增多。我们最终用重启大法解决,把服务做成定时健康检查,如果发现显存碎片比例过高,就自动重启并预热。听起来粗暴,但实测稳定。

5.2 推理速度慢:瓶颈可能在路由

有段时间 TTFT 正常,但 TPOT 突然从 42ms 涨到 90ms。我们检查了 CPU 利用率,发现部分 GPU 利用率只有 40%,明显是负载不均衡。

后来在日志里看到路由分布,问题出在输入数据上。当时业务发来一批测试请求,内容高度相似,导致模型学到的路由规律把所有请求都路由到了同一组专家,造成某些 GPU 过载,另一些空闲。这不是框架问题,而是 MoE 模型的固有特性。解决办法是在服务层面做负载平滑,限制单批请求的相似度,或者调大 batch size 让路由尽量分散。

5.3 生成质量下降:FP8 KV Cache 不是永远安全

我们在评估阶段发现,当输入内容包含大量数字和代码片段时,开启 FP8 KV Cache 后偶尔会出现数字错位的问题。虽然从指标上看不出差异,但业务方对这类错误极其敏感。最后我们做了个折中:对代码和数学类请求,通过路由规则单独部署一个 BF16 KV Cache 的实例;对其他通用文本请求走 FP8 实例。成本高一点,但业务质量达标了。

这里也建议大家,上线前一定要用自己的业务数据做 A/B 评估,不要只信 benchmark 数据。FP8 KV Cache 带来的收益很明显,但风险也真实存在,是否启用必须结合业务场景。

5.4 分布式通信异常:多机初始化常见的四大报错

整理一份排查速查表,遇到可以直接对号入座。

报错现象可能原因解决办法
Timed out initializing NCCL节点间网络不通或防火墙拦截检查nccl-tests是否能跑通,确认四个节点的网段互相可达
No GID found for deviceIB GID index 配置错误用ibv_devinfo查看正确的 GID index
Connection refused主节点的dist-init-addr端口被占用换端口,确认主节点服务先启动
Failed to get peer memory handleGPUDirect RDMA 配置问题检查 GPU 驱动是否支持 peer memory,必要时关闭 GPUDirect,改用普通 TCP 通信

6. 成本收益与后续扩展

这次部署投入了 32 张 80GB 显存的 GPU,加上网络、服务器整机、机房资源,硬件成本是一笔不小的数字。但从业务价值看,上线以后接入的几条核心业务线不再需要外购 API,按之前的调用量折算,大约四个月能回收硬件成本。而且这只是直接成本,更关键的是数据安全这块保障,这个是没法用钱简单算的。

后续我们正在做三件事:一是尝试更激进的量化方案,比如把 FP8 权重和 INT4 权重分别部署成多个服务,按业务质量要求分流;二是把 SGLang 的 prefix cache 进一步利用起来,现在系统提示词共享已经很受益,后续想扩展到更长的对话前缀缓存;三是做服务的高可用方案,比如主备双活,避免单点故障导致全站服务不可用。

这里多说一句,企业级部署永远不是把模型跑起来就结束,后面还有监控、告警、模型更新、灰度发布、回滚机制这些工程问题。我们已经在用 Prometheus 采集 TTFT、TPOT、吞吐、显存利用率这些指标,并接入现有的告警系统。如果出现某个指标连续几分钟超过阈值,会给值班人员发告警。刚开始调阈值的时候也踩过坑,比如把 TTFT 阈值设得太低,导致高峰期频繁误报,后来改成按业务线分时段设置阈值,总算消停了。

根据我个人从头到尾实操下来的体会,部署 397B 这类大 MoE 模型,最大的门槛不是命令怎么敲,而是对整个并行架构、显存分配、网络通信的理解深度。很多人照着网上教程把服务跑起来,但一遇到并发上去就 OOM、一遇到多机就通信失败,就是因为没有真正理解每个参数背后的代价。建议准备做类似项目的人,先在一个小规模的 TP=8 单机环境把模型跑通,把显存分配、调度策略这些概念吃透,再扩展到多机 EP,这样能少走很多弯路。

最后再分享一个小技巧:上线前一定准备一套完整的回滚方案。我们有一次调大max-running-requests之后,压测数据看着挺好,上线没半小时就出现大量超时。当时紧急把参数回滚、重启服务,五分钟内恢复了。大模型服务的故障比普通应用更麻烦,因为光重启还不够,还需要预热权重、检查 KV Cache 池,所以一定要把回滚步骤写成脚本,反复演练过才放心。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询