昇腾910B上跑DeepSeek多机分布式推理,很多人卡在第一眼:MindIE、HCCL、ranktable、hccn_tool,每个词都眼熟,串起来就不是那么回事。实际踩过一圈之后你会发现,真正决定能不能跑起来的不是模型代码,而是通信拓扑怎么编排、网络检查怎么做、rank表生成得对不对。这篇文章我就按实际部署的顺序,把昇腾910B多机跑DeepSeek的完整链路过一遍,从环境准备到HCCL参数,再到MindIE服务启动和性能调优。适合手里有910B集群、准备把DeepSeek真正用起来的人,也适合那些已经在单机跑通但一上多机就“各种玄学报错”的团队。
1. 先想清楚:多机推理到底难在哪
1.1 910B集群不是“拼积木”,先看网络拓扑
昇腾910B单机通常8卡,多机之后卡数线性涨上去,但推理时模型并不是简单拆成几块扔到不同机器上就完事。NPU之间的数据交换路径很不一样:单机内的8张卡可以通过HCCS高带宽互联,跨机器就必须走网卡和交换机,常见是RoCE网络或者IB网络。这里最容易被忽略的是拓扑形态。如果你的节点是8卡全连接,单机内通信基本可以当作“一块大显存”来用;一旦跨机,每一次Tensor Parallel通信都要经过网卡,延迟和带宽立刻变成瓶颈。不理解这一点,后面很多参数调不明白。
所以多机分布式推理,本质上是在解决两个问题:一是显存放不下,需要把权重和KV Cache拆到多卡上;二是通信开销不能把计算收益吃掉,要让卡和卡之间的数据搬运尽可能少、尽可能快。这也是为什么需要HCCL(昇腾集合通信库)和ranktable的原因。HCCL负责建立一张“全局设备视图”,让每个进程知道自己是谁、跟谁通信、走哪块网卡;ranktable就是这个视图的“户口本”。没有它,NPU只知道自己是第几号,不知道对面那台机器在哪里。
实操里我们会用npu-smi info看单卡状态,用hccn_tool看网卡健康度。很多团队跑到后面卡死,不是模型问题,而是某一块网卡link down,或者roce测试本来就丢包。所以我会把网络检查放在环境准备那一章重点讲,这是多机是否稳定的第一道闸门。
1.2 为什么选CubeStudio MindIE,而不是直接套vLLM
如果只跑单卡推理,昇腾生态里可选的推理框架已经不少,vLLM也支持昇腾后端。但多机分布式场景,我更建议直接用CubeStudio MindIE。原因有三个。
第一,MindIE是为昇腾NPU深度优化的推理引擎,融合了HCCL、图编译、算子融合和动态shape处理,对910B的硬件特性利用更充分。vLLM昇腾后端更多是“适配”,MindIE是“原生”。在DeepSeek这类MoE模型上,MindIE能对专家并行和稀疏激活做专门调度,吞吐差距比单卡场景更明显。
第二,MindIE的分布式推理架构把rank管理和serving能力统一在一起了。你只需要准备好ranktable和模型配置,它自己负责启动多进程、初始化HCCL、加载权重、路由请求。相比之下,自己用vLLM底层API搭多机分布式,要处理的东西更多,调试成本高得多。
第三,CubeStudio本身提供了相对统一的集群容器和调度入口,尤其在多机场景,容器网络、共享内存、进程间通信这些容易踩坑的点,比裸部署要稳。当然,不是说vLLM一无是处,它生态更丰富、社区资料多,如果你只是单卡或者单机8卡内跑,两个都可以。但标题既然点到多机分布式,我后面的步骤全部以MindIE为主线。
2. 环境准备:驱动、固件、MindIE一版都不能错
2.1 版本对齐:从CANN到MindIE
我在多机部署上遇到的大部分“疑难杂症”,最后都能追溯到版本不一致。昇腾跟CUDA生态有个很大区别:版本跳变很快,而且驱动、固件、CANN、MindIE之间有严格对应关系,不是随便装个最新版就万事大吉。建议先确定四件事:NPU固件版本、驱动版本、CANN版本、MindIE版本。安装完成后立刻检查:
npu-smi info这个命令会列出每张卡的芯片、温度、HBM使用量和驱动版本。接着用ascend-dmi -i可以查固件版本。现在很多生产环境用的是CANN 8.0 RC系列搭配MindIE 1.0 RC系列,但不同时期版本号差异很大,最稳妥的方式是看CubeStudio官方镜像的标签说明,哪个镜像对哪个CANN版本,直接跟着走。
CANN环境变量也要统一:
source /usr/local/Ascend/ascend-toolkit/set_env.sh export ASCEND_HOME=/usr/local/Ascend/ascend-toolkit/latest export LD_LIBRARY_PATH=/usr/local/Ascend/ascend-toolkit/latest/lib64:$LD_LIBRARY_PATH有的环境还用ASCEND_DEVICE_ID控制默认卡号,但在MindIE多机场景中,我们不直接依赖这个变量,而是通过ranktable来映射。需要注意的是,所有节点上的CANN和MindIE版本必须完全一致。曾经在一次部署中,node0是8.0.RC1,node1是8.0.RC2,服务都能起来,但HCCL初始化时不时就超时,排查到yml里面才看到两个节点版本差了一截。多机环境最忌讳“每个节点都是自己顺手装的版本”。
2.2 hccn_tool网卡检查:跨机通信丢包都靠它
hccn_tool在标题里单独出现,不是凑热度。它是昇腾网卡管理的常用工具,可以查链路状态、查RoCE配置、做通信测试。多机推理跨节点通信全靠网卡,如果网卡本身有问题,HCCL初始化再多次也救不回来。
我建议在跑任何模型前,先做一轮“网卡体检”。常用的排查命令大致是:
hccn_tool -i 0 -link_status hccn_tool -i 0 -roce_test-i 0表示第0张卡对应的网卡。-link_status会返回链路协商状态,如果link down,说明光纤、模块或者对端网卡配置有问题。-roce_test可以简单测试当前网卡到roce网络的连通性。不同版本的hccn_tool参数名可能略有差异,你可以先执行hccn_tool -h看帮助,但整体思路是一致的:先看link,再做收发测试,最后确认多机间的MTU和PFC流控配置。
另外,多机通信非常依赖时间同步。HCCL握手过程中如果节点间时钟偏差太大,会出现看起来“乱跳”的报错。建议所有节点提前部署NTP或者chrony,并确认时区一致。这里我的实际经验是:先把网卡体检和时间同步做完,再做CANN安装和rank表生成,顺序不要反过来。
2.3 MindIE部署形态:容器还是裸机
MindIE可以在裸机装,但多机场景我更建议用CubeStudio提供的容器镜像。镜像里已经打好了驱动对应的CANN和MindIE版本,能省去大量依赖问题。启动容器时要尤其注意两个参数:
- 共享内存:推荐至少
shm-size=16g,或者直接--ipc=host。分布式推理中,多个进程之间会通过IPC交换数据,默认64MB很容易踩坑。 - 设备映射:
--device=/dev/davinci0等,或者用Ascend Docker Runtime自动挂载。用了容器之后,npu-smi info是否能看到卡、hccn_tool是否可用,第一时间就要验证。
另外,容器内不要忘掉ulimit -l unlimited。MindIE和HCCL有些内存锁页操作需要这个限制打开,否则推理过程中可能莫名mmap失败。这些细节不写在官方快速开始里,但实测少了哪一个都会变成“某个随机时刻”的故障。
3. ranktable与HCCL:把“多机”翻译成一张卡表
3.1 HCCL初始化通信的“户口本”:ranktable结构
多机场景下HCCL需要一个统一的设备表,告诉每个进程:集群有几个节点、每个节点有哪些卡、每张卡的全局rank是多少。这个表就是hf_ranktable.json,一般通过环境变量RANK_TABLE_FILE指定。标准结构大概长这样:
{ "version": "1.0", "server_count": "2", "server_list": [ { "server_id": "192.168.1.10", "device": [ {"device_id": "0", "rank_id": "0"}, {"device_id": "1", "rank_id": "1"}, {"device_id": "2", "rank_id": "2"}, {"device_id": "3", "rank_id": "3"} ] }, { "server_id": "192.168.1.11", "device": [ {"device_id": "0", "rank_id": "4"}, {"device_id": "1", "rank_id": "5"}, {"device_id": "2", "rank_id": "6"}, {"device_id": "3", "rank_id": "7"} ] } ], "status": "completed" }这个例子是2个节点、每个节点4卡,共8卡的场景。真实的910B机器单机8卡,那么每个server_list项里就会列8个device。注意几个关键点:
server_id通常填节点IP,不能随便填主机名,除非你的网络平台能解析并保证每个节点看到的地址一致。rank_id是全局唯一的,从0到总卡数-1,不能重复。device_id是节点内的物理卡号,通常从0开始。如果用了ASCEND_RT_VISIBLE_DEVICES做卡号重映射,这里要特别小心,需要以实际可见的设备编号为准。
一旦这张表写错,最典型的现象是:服务启动时进程都在等对方握手,然后超时;或者rank错位,导致模型权重被加载到错误的卡上,推理结果是一堆乱码。所以生成ranktable之后,一定要先静态检查,再小规模验证。
3.2 多机HCCL环境变量全解析
HCCL在初始化阶段会读取一批环境变量,这些变量直接决定通信稳定性、调优空间和日志排查能力。下面这张表是我在部署时一定会核对的项目,供你参考:
| 环境变量 | 作用 | 建议值 |
|---|---|---|
RANK_TABLE_FILE | ranktable文件路径,多机必须配 | /xxx/ranktable.json |
RANK_ID | 当前进程的全局rank号 | 0~总卡数-1 |
DEVICE_ID | 当前进程绑定的物理卡号 | 与ranktable对应 |
HCCL_CONNECT_TIMEOUT | HCCL建链超时时间 | 建议1800~3600秒 |
HCCL_DETERMINISTIC | 是否启用确定性算法,用于对比结果 | 调试时置1,生产中通常0 |
HCCL_ALGO | 指定通信算法,比如AllReduce用的实现 | 不设置或用默认 |
HCCL_BUFSIZE | 通信缓冲区大小,影响大消息吞吐 | 视显存和网络调整,常见32~128MB |
HCCL_MAX_NCHANNELS | 最大通道数 | 默认即可,卡少时设低些省显存 |
HCCL_MIN_NCHANNELS | 最小通道数 | 一般默认 |
这里重点说HCCL_CONNECT_TIMEOUT。多机环境下,如果你同时拉起16张卡的进程,每个节点都要和其他节点建立连接。如果网络延迟高、防火墙策略严格、或者链路质量差,默认超时往往不够。我通常直接设到3600秒,第一次启动慢,但至少能让你看清日志里到底卡在哪一步。不要一上来就崩溃然后反复猜测。
RANK_ID和DEVICE_ID很多同学会搞混。RANK_ID是全局唯一的进程编号,DEVICE_ID是当前机器上的物理卡号。在MindIE场景里,这些变量通常由启动脚本根据ranktable自动注入,不需要手动设。但如果进程是自己用Python起的,就一定要自己注入,否则HCCL找不到自己的rank。
3.3 手工生成ranktable的脚本思路
尽可能不要手写几十个节点的ranktable JSON。我习惯用一个简单的Python脚本,输入IP列表和每节点卡数,自动生成。核心思路如下:
import json server_ips = ["192.168.1.10", "192.168.1.11"] cards_per_node = 8 server_list = [] rank_id = 0 for ip in server_ips: devices = [] for dev_id in range(cards_per_node): devices.append({ "device_id": str(dev_id), "rank_id": str(rank_id) }) rank_id += 1 server_list.append({ "server_id": ip, "device": devices }) ranktable = { "version": "1.0", "server_count": str(len(server_ips)), "server_list": server_list, "status": "completed" } with open("ranktable.json", "w") as f: json.dump(ranktable, f, indent=2)生成之后不要急着用,先做三件事:确认各节点IP连通性,比如用ping和hccn_tool看链路;确认每个节点是不是8卡都被系统识别;确认/etc/hosts和DNS解析结果不影响节点间互相访问。如果网络是双网卡、多网卡模式,还要确认HCCL走的是哪个网卡,避免它走了管理网口导致带宽不够。
4. 开始跑DeepSeek:从权重到MindIE推理
4.1 DeepSeek模型权重转换与目录布局
从HuggingFace等渠道拿到DeepSeek原始权重后,不能直接丢给MindIE加载,通常需要转成MindIE适配的格式。MindIE的仓库里一般会提供转换脚本,模型类型选deepseek,然后把--load-dir指向原始权重目录,--save-dir指向输出目录。举一个典型的命令行示例:
python convert_ckpt.py \ --model-type deepseek \ --load-dir /data/DeepSeek-R1-Distill-Qwen-14B \ --save-dir /data/DeepSeek-R1-Distill-Qwen-14B-mindie转换过程会扫描权重结构、重排张量布局,如果模型是MoE结构,还会把专家参数整理成更适合专家并行的布局。这一步耗时会比较长,建议放在高性能盘上执行。转换完成后,你会得到一个包含config.json、权重文件等内容的目录,这个目录就是MindIE推理服务的数据根目录。
我强烈建议把转换环境和推理环境分开。转换通常不需要多机,单机单卡或者CPU内存够就能跑;推理服务才需要多机。如果你把转换放在推理集群上,一旦显存占用波动,转换进程被OOM杀掉,整个流程会原地爆炸。
4.2 MindIE config参数:TP/PP/CP怎么定
MindIE服务启动时加载的config.json,是整个推理服务的“总纲”。里面参数很多,但多机场景下最关键的是并行策略相关字段,主要是三类:
tensor_parallel_size:张量并行度,把一层参数切到多卡上。单机8卡通常是8,但只要跨机,就要谨慎。pipeline_parallel_size:流水线并行度,按层切分。跨机时这个往往比TP更友好,因为它把通信频率降到更低。expert_parallel_size:专家并行度,专门针对MoE模型。DeepSeek这类模型有大量专家,专家并行可以让不同卡负责不同专家组,大幅降低单卡显存压力。
实际部署时,如果卡数超过单机8卡,我建议优先保持tensor_parallel_size为8,然后通过pipeline或expert并行扩展到多机。原因很简单:TP是细粒度通信,每个Transformer层里的矩阵运算都要跨机同步,通信次数多,延迟敏感;PP和EP都偏向粗粒度分发,跨机通信压力小,更适合RoCE网络。当然,PP会增加流水线气泡,EP会有all-to-all通信,不同模型最优解不同。我的调参顺序是:先固定TP,再加PP,再加EP,每加一个维度就压一次吞吐测试。
除了并行度,还需要关注max_seq_len、dtype、KV Cache相关配置。DeepSeek推理对KV Cache很敏感,max_seq_len设置得过大,会直接吃掉显存;设置得过小,又会影响长上下文场景。建议根据实际业务场景设置,不要盲目跟风。精度方面,昇腾910B对FP16、BF16支持都不错,DeepSeek模型如果原始权重是BF16,就不要强转FP16,可能会导致精度损失。
4.3 多机启动与验证
MindIE具体启动命令在不同版本有差异,但整体流程是一致的:先设置HCCL变量,再启动MindIE服务。一个常见的思路是:
export RANK_TABLE_FILE=/data/ranktable.json export HCCL_CONNECT_TIMEOUT=3600 export ASCEND_RT_VISIBLE_DEVICES=0,1,2,3,4,5,6,7 mindie --model /data/DeepSeek-R1-Distill-Qwen-14B-mindie \ --config /data/deepseek_config.json \ --rank_table_file /data/ranktable.json \ --port 8000如果你的集群通过CubeStudio调度,可能还需要先申请多机任务资源,由平台注入节点间网络变量。这里要提醒的是,所有节点上的模型目录路径最好保持一致,否则每台机器都要各自指定--model路径,很容易漏配。
服务起来之后,验证分三步。第一步:看日志,确认每个rank都完成了HCCL初始化,服务进入监听状态。第二步:用npu-smi info看多卡显存占用,正常情况每张卡都会分到权重和KV Cache,如果某张卡显存为0,说明rank映射错位或者权重没有被切分。第三步:开另一个终端发送一个测试请求,比如调OpenAI兼容的/v1/chat/completions接口,观察首token时间。首token正常、多卡利用率都有波动,基本就算跑通。如果首token特别慢,先怀疑跨机通信,不要先怀疑模型。
5. 踩坑记录与调优心得
5.1 典型故障排查速查表
多机部署的问题不会按顺序出现,但高频问题就那么几类。我把实际踩过的坑整理成速查表:
| 症状 | 可能原因 | 检查方法 | 解决办法 |
|---|---|---|---|
| HCCL初始化超时 | 网卡link down、防火墙拦端口、时间不同步 | hccn_tool -i 0 -link_status,ping对端IP,检查节点时间 | 调整网络,开放HCCL端口,同步时间,调大HCCL_CONNECT_TIMEOUT |
| 服务起来了但某卡显存0 | ranktable中rank与device映射错误 | 对比ranktable与npu-smi info实际卡号 | 重新生成ranktable,确认ASCEND_RT_VISIBLE_DEVICES |
| 输出乱码或NaN | 权重转换格式错误、精度不匹配 | 查看MindIE推理日志,确认dtype | 重新转换权重,保持BF16 |
| 推理过程中偶发卡死 | 网卡丢包、共享内存不足、内存锁页受限 | 检查ulimit -l、容器shm-size、网卡roce测试 | 扩大共享内存,开启PFC流控,设置ulimit -l unlimited |
| 多机吞吐远低于单机加总 | 跨机通信太多、TP跨机 | 统计各卡网卡流量 | 改成PP/EP,减少TP跨机,检查MTU |
这些排查步骤里最容易忽视的是防火墙。很多团队在机房网络里默认安全组全开,但如果你用的是云环境或者私有云,节点之间可能有安全策略。HCCL建链时要用到特定端口,建议直接测试节点间TCP连通性,别等日志超时才发现。
5.2 性能调优:从“能跑”到“跑得快”
跑通只是第一步,生产环境关心的还是吞吐和延迟。多机分布式调优,我建议按“山形路径”走:先打满计算,再调通信,最后调整调度参数。
第一步看计算是否打满。用npu-smi info观察AI Core利用率,如果偏低,说明并行策略或batch太小。DeepSeek这种MoE模型,batch适当增大后计算密度会显著提高,但也要小心KV Cache不够用。第二步看通信是否有瓶颈。可以同时开几个终端,跑一个持续请求,然后用hccn_tool查网卡流量。如果某台机器的网卡流量长时间接近上限,就要考虑降低TP跨机比例,或者增加EP。第三步看调度参数。MindIE的max_batch_size、max_seq_len、动态batch开关,这些会直接影响排队效果。建议实测不同并发下吞吐曲线,找到拐点。
一个很有效的技巧是prefill和decode分离。DeepSeek这种大模型,prefill阶段计算密集,decode阶段访存密集,两者混在一起会互相拖累。如果MindIE支持PD分离,可以把两块安排在不同卡或不同池,多机场景下链路调度更灵活。这个功能不一定每个版本都稳定,生产环境要小规模灰度验证。
5.3 给新人的最后提醒
从单机到多机,边界条件完全变了。我自己第一次跑16卡时,花了三天才意识到问题不在算法,而是node1的网卡光模块没有插紧,导致HCCL握手成功后通信质量极差。那几天看日志都以为是代码问题,后来静下心用hccn_tool一圈一圈测,问题才暴露。
所以经验是:多机部署前先花两小时做网络体检;不要跳过ranktable的静态校验;任何异常先看HCCL日志和MindIE日志,不要凭经验乱改代码;每个节点使用一致的路径、一致的版本、一致的环境变量来源。只要这三一致做到,昇腾910B跑DeepSeek多机推理并不比单机复杂太多,真正的坑其实都在几十行配置之前。
最后再说一个我常用的习惯:每次修改ranktable或并行配置后,先跑一个很短的“信号测试”——只加载模型的一部分,发一个极短请求,看通信是否正常。这个测试跑通了再上全量权重,能省掉大量反复排障时间。多机分布式不是堆卡,是把通信和组织做对。你把这张表看顺了,后面自然就顺了。