Instella-MoE 显存不够就切专家并行、驱动组合不匹配就先退回已验证环境

文章导读
部署 Instella-MoE 时同时撞上显存不足和算子报错,最忌讳把并行参数和运行环境一起改:改完之后服务能起来,也说不清是哪一步起了作用,问题复发时更难定位。可行做法是把两类报错先分开——日志里指向显存分配失败的,走专家并行与张量并行切分;日志里指向算子、内核或驱动符号不匹配的,先退回一套已验证的环境组合,再谈其他。两件事各自配一次最小前向请求做验证,并且分两次记录变更,才能保住归因链条。
📋 目录
  1. 壹 先判断当前报错属于显存类还是算子与驱动类
  2. 贰 显存不足时调整专家并行与张量并行参数
  3. 叁 算子不支持时核对框架与驱动版本组合
  4. 肆 用一次最小前向请求验证改动是否生效
  5. 伍 把两组改动分开记录,保证能归因
A A

部署 Instella-MoE 时同时撞上显存不足和算子报错,最忌讳把并行参数和运行环境一起改:改完之后服务能起来,也说不清是哪一步起了作用,问题复发时更难定位。可行做法是把两类报错先分开——日志里指向显存分配失败的,走专家并行与张量并行切分;日志里指向算子、内核或驱动符号不匹配的,先退回一套已验证的环境组合,再谈其他。两件事各自配一次最小前向请求做验证,并且分两次记录变更,才能保住归因链条。

判断顺序建议是:先读日志关键字归类,只改一条路径。显存类改动集中在并行切分与上下文长度,不改卡、不改版本;算子与驱动类改动集中在框架、CUDA runtime、算子库的版本组合,不改并行配置。每改一组参数就用一次最小前向请求复测,成功判据是返回 200 且日志无对应算子报错。风险边界在于:并行切分只在单卡能装下分片时有效,版本回退会牺牲新特性,两条路径都不能替代容量或兼容性评估。

先判断当前报错属于显存类还是算子与驱动类

服务起不来时先别动配置,把启动日志和第一次请求的日志抓下来,按关键字归类。显存类报错的特征是“有空间分配动作但失败”,算子类报错的特征是“代码路径走到了某个 kernel 但设备不支持或符号缺失”。两类混在同一份日志里时,先处理最早出现的那条。

日志关键字(示例)归类首查项
CUDA out of memory、OutOfMemoryError、KV cache 分配不足、gpu_memory_utilization 相关告警显存类每卡总显存与已占用、max-model-len、并发与 KV cache 占比、是否有其他进程占用
no kernel image is available、invalid device function、undefined symbol、算子名 + not supported、Triton 编译失败算子与驱动类驱动版本、CUDA runtime 版本、torch 编译的目标架构、注意力与量化算子库版本
NCCL 通信报错、进程被 kill需二次判断先确认是显存被 OOM killer 回收,还是通信库与驱动不匹配

首查动作也要分开:显存类先跑一次 nvidia-smi 看占用分布;算子类先打印版本三元组(驱动、CUDA runtime、框架)。归类没做清楚就改参数,等于同时动两个变量。

显存不足时调整专家并行与张量并行参数

目标是单机现有卡数下把模型装进去,不是提速。通常先确认专家总数能否被专家并行度整除,再把张量并行度设成与卡数匹配的值。下面是通用启动骨架,具体参数名以所用框架的启动帮助为准:

python -m <框架>.entrypoints.openai.api_server \
  `--model` /models/instella-moe \
  `--tensor-parallel-size` 4 \
  `--enable-expert-parallel` \
  `--expert-parallel-size` 4 \
  `--gpu-memory-utilization` 0.85 \
  `--max-model-len` 8192

约束关系需要先算清楚:张量并行度乘以流水并行度一般等于实际使用的卡数;专家并行度通常不超过总卡数,且专家总数要能被专家并行度整除,除不尽时框架会报切分失败而不是降级。显存吃紧时优先降低 max-model-len 和利用率上限,再看专家并行度是否还有上调空间。改完后复测方式是重启服务并观察启动日志里的分片数量与显存峰值,再发一次最小前向请求确认能出结果,而不是只看进程是否存活。

算子不支持时核对框架与驱动版本组合

算子报错先假定问题在版本搭配,不要先怀疑提示词或请求体。把环境信息一次性打印出来,留档比对:

Instella-MoE 显存不够就切专家并行、驱动组合不匹配就先退回已验证环境
nvidia-smi `--query-gpu`=driver_version,name,memory.total `--format`=csv
nvcc `--version`
python -c "import torch;print(torch.__version__, torch.version.cuda, torch.cuda.get_device_capability())"
python -c "import vllm;print(vllm.__version__)"
pip list | grep -Ei 'torch|triton|flash|vllm'

查法是拿框架的发布说明(release notes)对照三件事:该版本声明支持的 CUDA 版本区间、最低驱动版本、以及注意力或量化算子库与 torch 版本的绑定关系。若当前驱动低于声明下限,或 torch 编译的目标架构与卡的计算能力不匹配,就走回退:退回一套此前能跑通同一模型的环境组合,只改环境不改并行参数。回退后同样用最小前向请求验证,日志里不再出现具体算子名报错才算这一步生效。

用一次最小前向请求验证改动是否生效

每次只改一组参数,改完立刻发一次尽量短、确定性尽量高的请求,避免长输出掩盖问题:

curl -s -w '\nHTTP:%{http_code}\n' http://127.0.0.1:8000/v1/completions \
  -H 'Content-Type: application/json' \
  -d '{"model":"instella-moe","prompt":"你好","max_tokens":16,"temperature":0}'

成功判据:HTTP 200、返回体里有非空文本、服务日志没有对应的显存或算子报错。失败判据:HTTP 5xx、进程退出、日志重复出现同一关键字。若只改并行参数就成功,说明是显存路径起效;若只回退环境才成功,说明是版本路径起效;两条都改了还失败,需要回到第一步重新归类。

把两组改动分开记录,保证能归因

建议维护一张最小变更表,一次只填一行,改动前先写清改动项和改前值,复测后补报错变化与是否跑通。这样问题复发时能直接看出上次是哪条路径生效。

改动项改前值改后值报错变化是否跑通
张量并行度 / 专家并行度待填待填如 OOM 消失、切换到算子报错是 / 否
max-model-len / 显存利用率待填待填同上是 / 否
框架版本 / CUDA runtime / 驱动待填待填如特定算子报错消失是 / 否

记录时把“并行类”和“环境类”分成两个区块,不要交叉填写;同一行只允许一个变量变化。若某次复测报错从显存类变成算子类,说明容量问题已缓解、暴露出下一层问题,此时再进入环境核对,而不是同时回退两边。