我们刚拿到一批国产算力设备,型号就是 Atlas 800I A2,要求在完全离线的内网环境里把推理服务跑起来,模型则锁定了 DeepSeek-R1-Distill-Llama-70B 和 Qwen3-32B。听起来就是一个典型的“单机多模型、离线装环境、上线接API”的活儿,真做起来却发现每一环都有讲究,尤其是离线环境下驱动、固件、推理引擎、模型量化这几大件,任何一环版本对不上,后面全是靠日志猜。
这篇文章就把我整套部署过程、踩过的坑、调参思路完整复盘一遍。材料全是自己实操记录,适合正在做国产化推理部署、刚刚接触昇腾离线环境的同学参考。文章不吹参数、不堆抽象概念,只讲一套能落地的路径:从硬件查看到离线依赖传递,从模型量化和目录规划到 MindIE 配置与双模型服务化,最后附上高频问题排查清单。
1. 项目概述与部署思路
1.1 这个项目到底在解决什么问题
标题里三样东西分开看大家都不陌生:Atlas 800I A2 是一台面向推理场景的服务器,MindIE 是适配昇腾芯片的推理引擎,DeepSeek-R1-Distill-Llama-70B 和 Qwen3-32B 是两个开源大模型。但把它们放进“离线部署”这个约束条件里,问题就变成了另一回事。
我先说说真实的业务场景。某内部业务平台需要给不同团队分别提供“推理问答”和“通用对话”能力,一个模型偏复杂推理、一个模型偏通用交互,所以必须同时挂载两个不同规模和不同风格的模型。网络环境是物理隔离的内网,无法在线拉取容器镜像、pip 包、预编译 wheel、模型权重,所有东西都必须先在外网或中转机上准备齐全,再通过移动介质或内网传输通道拷进去。
这种情况下最怕的不是模型本身装不好,而是“传进去之后缺一个依赖、版本验不过、驱动和固件不匹配”这类致命小问题。因为离线环境里每一个小补丁都要重新走一遍传输流程,成本非常高。所以整个项目的真正难点在于:如何把部署过程中所有可能的变数,在前置准备阶段就彻底压到最小。
1.2 方案选型:为什么是这套组合
有朋友问我,既然是要跑大模型,为什么不用更常见的英伟达 GPU 或者直接用 vLLM、TGI 这类推理框架?这个问题在拿到硬件清单之前是可以讨论的,但设备已经定了 Atlas 800I A2,且供应链上已经不再新购其他架构的卡,讨论就变成怎么在现有条件下做到最优。
Atlas 800I A2 属于昇腾生态的推理服务器,板卡本身对标的场景就是高并发、大吞吐的在线服务。MindIE 是昇腾上原生的推理引擎,它最大的优势是和底层芯片适配最紧密,图编译、算子融合、KV Cache 管理这些优化是原生内置的。如果你在昇腾卡上硬套一个为 CUDA 设计的框架,要么做一层转换层,要么大量算子走降级实现,性能损耗非常可观。
模型的选型则和推理引擎关系不大,主要是看场景。DeepSeek-R1-Distill-Llama-70B 属于蒸馏过的推理增强模型,在数学、逻辑、代码这类任务上的表现比较稳;Qwen3-32B 则是通用对话能力更强的模型,上下文理解、指令跟随表现均衡。两个模型一个偏重、一个偏轻,放到同一个服务层里正好互补。
这套方案的优点在于:MindIE 原生支持多模型服务化,多个模型可以注册到同一个框架内,对上层业务暴露统一的 OpenAI 风格接口。整体架构不复杂,后续如果新增第三个模型,不需要重新设计服务层。它的问题则是文档相对少、社区实践经验分散,离线部署更是没有太多现成“抄作业”的范本。这正是我写这篇文章的原因。
1.3 完整部署流程与阶段划分
整个部署可以拆成六个阶段,我按这个顺序执行,基本没有返工。
第一阶段是硬件摸底与环境检查,确认服务器固件、板卡状态、硬盘容量、系统版本,这个阶段花半天时间,主要是避免后面装到一半发现盘不够或者固件版本过低。
第二阶段是离线依赖准备,这是整个项目流程里最繁琐的一步。需要在外网环境把所有安装包、镜像、依赖库全部搜集齐全,然后按依赖关系整理成清单,逐个校验后传入内网。
第三阶段是驱动固件和基础软件栈安装,包括昇腾驱动、固件、CANN 工具包,以及后续 MindIE 所依赖的 Python 环境或容器环境。
第四阶段是模型文件的准备与量化处理。两个模型都需要先完成格式确认、权重校正、量化等操作,并规划好目录结构。
第五阶段是 MindIE 安装、模型注册、服务配置,将两个模型加载起来。
第六阶段是接口验证、并发测试和问题调优。
下面我从第一阶段开始逐步展开。整个流程里最容易被低估的是第二和第四阶段,因为它们表面上看起来只是“拷文件”和“跑一个转换脚本”,实际上恰恰是后续所有报错的源头。
2. 硬件与软件环境准备
2.1 硬件摸底:从板卡到硬盘都要查清
在线部署很多问题可以靠日志和重装解决,离线环境不行,所以在动手装第一行环境之前,我习惯把硬件信息全部摸一遍。
首先是板卡状态。通过昇腾设备的管理工具查看板卡数量和型号,确认能被系统正常识别。这里有一个关键点:如果服务器里插了多张卡,需要记录每张卡的物理槽位和 ID 对应关系。后面 MindIE 分配设备时如果指定了错误的 ID,模型会加载失败。虽然可以通过工具重新枚举,但在离线场景下每多一次操作就多一份风险。
其次是内存容量。这里指的不是显存,而是系统内存。加载一个 70B 模型,光权重文件就要占用相当大的内存来映射,如果系统内存不足,MindIE 会直接把模型加载阶段卡死。我当时专门看了机器的物理内存和 swap 配置,默认 swap 太小,在模型加载时出现过奇怪的中断,后来把 swap 调大后问题消失。
然后是硬盘容量与分区。两个模型的权重文件加上量化中间文件、日志、容器镜像,轻松占用数百 GB 空间。如果机器上本来还跑了其他业务,磁盘余量必须提前算清楚。我的经验是模型目录单独分一个区,避免系统盘被日志和临时文件写满。同时要确认文件系统类型,推荐 ext4 或 xfs,某些网络文件系统在大文件随机读取时性能衰减非常明显。
最后是固件和系统版本。Atlas 800I A2 对固件版本有明确要求,驱动和固件版本必须匹配。我见过很多装不上、加载失败的问题,最后查下来都是固件版本太老,而折旧的固件又需要单独的更新工具,离线环境里一旦缺了这个就非常被动。所以硬件摸底时一定要把固件版本作为必查项,提前和驱动版本需求校对好。
2.2 离线依赖包的准备与传递
离线部署最大的工作量不在安装,而在“准备”。我曾经吃过一个亏:外网拿到的驱动安装包,拷进内网后才发现缺少一个编译依赖库。所以现在我做离线环境都会列一张“三层依赖清单”。
第一层是核心安装包,包括系统 ISO 或 yum 源、昇腾驱动包、固件升级包、CANN 工具包、MindIE 安装包。第二层是运行依赖,包括 python 解释器、pip 依赖库、编译工具链、容器引擎(如果走容器方式)以及对应的离线镜像包。第三层是模型文件,包括权重、分词器、配置文件,以及量化工具可能的额外依赖。
这三层里最容易漏的是第三层的“工具依赖”。比如做模型量化时,脚本可能需要某个版本的 transformers 或 tokenizers,而你以为只是把模型拷过去就行。所以我的习惯是:在源机器上先完整跑通一遍部署流程图,把每一步实际用到的依赖都记录下来,再按照这个记录去内网准备。
文件传递方面,强烈建议在介质导入后先做 MD5 校验。我是吃过亏的,一个十几 GB 的镜像文件从移动硬盘拷到服务器后,导入时总是报损坏,最后发现是拷贝过程中文件字节不一致。所以准备了一个校验清单,每个文件对应一个校验值,传入内网后逐个核对。
2.3 驱动、固件与CANN的安装顺序
昇腾软件栈的安装顺序非常严格,通常是固件先于驱动,驱动先于 CANN,CANN 先于 MindIE。如果你先装了 MindIE 再回头升级驱动,很可能出现动态库链接版本对不上的问题,当时排查这类问题会比较痛苦。
固件升级我单独提醒一句:如果服务器已经承载了业务,固件升级一般需要重启,且升级过程中不能断电。这个操作通常建议在业务窗口期执行,不要和模型加载赶在一起。
驱动和 CANN 装完后,需要验证设备能否被正常枚举。标准做法是运行设备查询工具,看到板卡信息正常显示、芯片状态为健康,再继续往下走。同时确认 CANN 的环境变量是否写入配置文件,因为 MindIE 启动时会依赖这些环境变量来定位算子包和运行时库。
这里有一个容易忽略的细节:CANN 安装完成后,环境变量脚本可能不会被自动加载。之后你在终端手动 source 一次没问题,但一旦以服务方式启动 MindIE,它不会读取你的 shell 配置,所以要确保环境变量写入系统级 profile 文件。否则模型加载时会报找不到算子或 runtime 库的错误。
3. 模型文件准备与量化选择
3.1 模型目录规划与文件检查
模型文件不是简单仍在一个目录里就能被 MindIE 认出来的。每个模型应该独立一个目录,目录内部结构与源模型保持一致,权重文件、配置文件、分词器文件缺一不可。
我习惯的目录结构如下:
/models/ ├── module_info/ │ ├── deepseek-r1-distill-llama-70b/ │ │ ├── config.json │ │ ├── tokenizer.json │ │ ├── tokenizer_config.json │ │ ├── model.safetensors │ │ └── generation_config.json │ └── qwen3-32b/ │ ├── config.json │ ├── tokenizer.json │ ├── tokenizer_config.json │ ├── model.safetensors │ └── generation_config.json ├── mindie/ │ └── ... └── logs/模块信息目录和模型权重目录分开的好处是,模型文件可以躺在只读存储上,权重不会因为日志和临时文件污染。另外,强烈建议把 models 目录挂载到容量足够的单独分区。
文件检查有三个重点。第一是确认分词器文件齐全。很多模型加载失败并不是因为权重坏了,而是 tokenizer 文件缺失,MindIE 加载时报错信息又偏偏不明显。第二是检查权重文件是否完整。如果有分片权重,需要核对分片数量和总大小。第三是确认配置文件的字段是否能被当前 MindIE 版本解析。不同版本的 MindIE 对 config.json 的兼容性有差异,老版本解析新模型配置时会因为未知字段告警,有的告警直接中断加载,需要手动去掉配置里的多余字段。
3.2 量化格式选型与显存估算
大模型能不能跑、能跑多大批次,最核心的计算依据是显存。先给一个通用公式:
所需显存 ≈ 权重显存 + KV Cache 显存 + 激活与临时缓冲显存
其中权重显存等于参数总量乘以每个参数占用的字节数。FP16 下每个参数占 2 字节,INT8 占 1 字节,INT4 占 0.5 字节。所以:
- DeepSeek-R1-Distill-Llama-70B,FP16 权重大约需要 140GB 显存,W4A16 量化后大约只需要 35GB 左右。
- Qwen3-32B,FP16 权重约 64GB,W8A8 量化后约 32GB,W4A16 量化后约 16GB。
你以为量化只是为了省空间,其实它还直接影响推理吞吐。70B 模型如果不量化,单卡显存放不下,就必须多卡张量并行,卡间通信开销会摊薄性能。量化后权重缩小,单卡或双卡就能承载,通信开销小,实际端到端吞吐反而更高。
MindIE 支持的量化模式并不只有一种,常见的包括 W8A8、W4A16、以及带校准环节的智能量化。我的建议是:追求极致精度选 W8A8,追求高吞吐选 W4A16。但 W4A16 容易在低比特下造成精度损失,尤其是推理链较长的任务中,答案质量下降会很明显。所以我在实际项目中做了一个折中:70B 模型用 W4A16 换取能跑起来,32B 模型用 W8A8 保证通用对话质量。这个组合最后在显存占用和输出质量上表现都比较平衡。
有一个值得强调的概念:量化和校准不是一锤子买卖。量化过程需要使用有代表性的校准数据集,用少量样本统计出权重分布的截断范围。如果校准集选得不好,量化后的模型在某些场景下可能输出明显劣化。我通常会用一小段和业务数据分布相近的文本做校准,而不是直接用默认的随机样本。
3.3 双模型同时部署的资源分配
两个模型要同时常驻,就必须提前把显存分配方案定下来。假设部署机上单卡显存是 64GB,一共 8 张卡,那么理论显存总量是 512GB,但实际可用的要打折,因为保留少量显存给驱动和运行时。
我的分配原则是:先满足精度要求更高、需要复杂推理的模型,再满足并发量更高的模型,最后再用剩余显存放轻量模型。70B 模型如果使用 W4A16,量化后权重约 35GB,KV Cache 和激活再加一点,单卡 64GB 勉强可以跑但并发很低。实际部署中我更倾向于用 2 卡张量并行承载 70B,这样显存余量大,KV Cache 可以给足,并发能力明显上升。32B 模型 W8A8 量化后约 32GB,单卡即可承载,留一张卡给它就够了。
另外,MindIE 在分配设备时建议使用固定的卡组,不要任意跨卡。比如 0-1 号卡给 70B,2 号卡给 32B,剩余卡用于后续扩容或跑其他服务。规划好卡组后,把编号写进配置,后面所有模型实例都按这个拓扑来跑。如果你图省事让框架自动调度,一旦某个模型退出后残留资源碎片,反而更难排查。
4. MindIE 推理引擎配置与模型加载
4.1 部署模式:容器还是裸机
MindIE 本身的部署有两种主流方式,一是直接安装在物理机操作系统上,二是以容器方式运行。离线环境下我优先推荐容器。
原因是容器把大量依赖关系的管理问题收敛到了镜像内部。你只需要导入一个离线镜像,启动容器时把模型目录和配置文件挂载进去,容器里的环境是固定的,不会因为物理机系统里多装了一个 Python 库就把推理环境搞乱。而裸机方式一旦遇到系统库版本冲突,排查成本很高。
容器镜像的导入流程本身很简单:加载镜像 tar 包,确认镜像列表里有对应的镜像名,然后创建一个启动脚本。这里有一个非常容易被忽略的地方——离线导入的镜像是没有原本的 tag 信息的,导入后你可能看到一堆“None”标签,需要手动打 tag,否则后面 run 的时候找不到镜像名。
启动容器时要注意几个参数。一是挂载目录,模型目录和日志目录必须挂进去。二是共享内存大小,大模型加载时共享内存不够会直接失败,建议启动时加大共享内存参数。三是设备映射,容器内要能访问到昇腾设备,需要把对应设备节点和驱动目录都映射进去。
4.2 模型注册与服务配置
MindIE 启动后,模型并不自动处于可服务状态。需要先完成模型注册,也就是告诉推理框架“模型文件在哪个路径、用什么格式、占用哪些卡、服务端口多少”。这个信息通常写在一个配置文件里。
以前我在内网部署时,配置模型路径踩过一个坑:填写 absolute path 时手误把路径写错了一个层级,MindIE 启动后并没有立刻报错,而是等到第一个推理请求进来时才提示找不到权重文件。所以我现在写完配置文件后,第一件事就是手动确认路径存在、可读,并且用工具把目录下的文件树列出来核对。
配置文件中比较关键的字段包括模型路径、分词器路径、设备卡号、服务端口、最大输入长度、最大输出长度,以及 KV Cache 分配策略。其中最大输入输出长度直接影响显存占用。比如你把单次最大输出设置成 4096 tokens,而用户请求都比较短,那这部分预留给输出的显存就一直被占着,峰值并发利用率很低。建议按业务实际需要设置,不要贪大。
4.3 两个模型如何共用一个服务层
业务方通常希望能用一个统一的 API 地址访问两个模型,而不是分别给两个 IP 端口。MindIE 支持在服务层做多模型管理,给业务方两种选择。
第一种是每个模型一个独立端口,业务方按需切换。这种方式配置最简单,每个模型实例完全独立,互不影响。你可以把一个模型的服务端口设为 8001,另一个设为 8002,业务侧根据模型种类选择请求地址。
第二种是统一入口模式,通过路由参数指定目标模型。这样配置复杂一些,但对业务方友好,只在服务层暴露一个地址。两种模式的选择主要看上游系统的接入方式,如果上游是一个网关,它更希望每个模型有独立端口便于负载均衡,我会建议用第一种;如果是直接客户端调用,那统一入口更舒服。
我把两个模型分开配置、独立端口。这样做的另一个好处是模型 A 升级或重启时,模型 B 的服务不受影响。如果你的业务不允许两侧同时中断,独立端口是更稳的选择。
下面是一份参考用的配置片段,实际字段名请以你手里的 MindIE 版本手册为准,但整体结构是一致的:
model_a: model_path: /models/module_info/deepseek-r1-distill-llama-70b/ tokenizer_path: /models/module_info/deepseek-r1-distill-llama-70b/ device_ids: [0, 1] port: 8001 max_input_len: 2048 max_output_len: 1024 model_b: model_path: /models/module_info/qwen3-32b/ tokenizer_path: /models/module_info/qwen3-32b/ device_ids: [2] port: 8002 max_input_len: 4096 max_output_len: 20485. 推理验证与性能调优
5.1 用 curl 快速打通 API
服务配置完成后,先用最朴素的 curl 验证,而不是一上来就写复杂客户端。这一步的目的是确认链路通、模型已加载、接口格式正确。
我通常先请求一个很短的问题,例如“你好”,观察返回时间。如果这个请求能通,再测试长上下文、多轮对话、流式输出。
验证命令的通用格式如下:
curl -X POST http://127.0.0.1:8001/v1/chat/completions \ -H "Content-Type: application/json" \ -d '{ "model": "deepseek-r1", "messages": [{"role": "user", "content": "请用一句话介绍你自己"}], "max_tokens": 128, "stream": false }'如果返回结果正常,再测一下流式输出,把 stream 设为 true,观察是否按 token 陆续返回。这一步在实际体验上非常关键,因为流式输出能大大降低用户的感知延迟。
两个模型都要各跑一轮基础验证。很多人在部署多模型服务时,只验证了第一个模型能通就宣布完成。但实际上第二个模型因为端口占用、卡号写错、路径问题而加载失败的情况很常见,必须单独验证。
5.2 压测指标解读与调优方向
接口通了不代表能上生产。我习惯用一段简单的并发脚本,模拟不同并发数下的请求表现,重点看三个指标:首 Token 延迟、吞吐量(Tokens per second)、以及显存占用。
首 Token 延迟高,通常不是模型算得慢,而是请求排队的等待时间长。可以检查服务配置里的最大并发数是否太低,或者 KV Cache 分配不足。吞吐量上不去,先看是不是单卡算力瓶颈,如果单卡利用率已经接近上限,再调参意义不大,不如增加设备或减少权重位宽。显存占用异常则要看是否有模型实例跑飞了,或者最大输入长度设置过大导致显存预留给 KV Cache 的部分太多。
调优方向有四个值得优先尝试:
一是调整最大批处理 token 数。MindIE 支持动态批处理,一次性处理多个请求。这个值设置得太小,并发一高就排队;设置得太大,又会把显存撑爆。我一般从显存余量反推,先设一个中位数,压测后根据显存占用峰值上下调整。
二是检查 KV Cache 是否有足够余量。对话长度越长,KV Cache 占用越多。如果长对话场景占比高,需要压缩单实例并发数,否则到一定长度后服务会强制截断或者报显存不足。
三是确认是否启用连续批处理。连续批处理能在一个请求生成完之前插入新的请求,提高 GPU 利用率。这个功能在 MindIE 中通常默认开启,但如果你在配置时关闭了,并发能力会明显下降。
四是针对量化模型检查是否开启对应的算子优化。有的优化开关在模型注册阶段才生效,你改完配置后需要重启模型实例。
调优是一个反复迭代的过程,不要指望一把参数到位。我通常的做法是“先通再优”,先把功能跑通、测出基线数据,再逐项调整配置并对比指标,每次只改一个变量,这样得到的数据才有参考价值。
6. 常见问题与排查实录
6.1 离线安装阶段的高频坑
离线环境的问题大多集中在“装不上”和“起不来”两类。
先说“装不上”。最常见的错误是依赖库缺失。昇腾驱动包安装时会检查系统里是否具备某些运行库,缺了就会中断。解决办法是在源环境先装好所有依赖,再整体打包传入。如果内网有本地的软件源,直接配置源地址,效率更高。如果没有源,只能用 RPM/DEB 包逐层安装,注意依赖顺序。
其次是版本不匹配。驱动、固件、CANN、MindIE 四者之间存在版本对应关系。你单独看每个包都能装上,但组合起来就可能出现算子编译失败或运行时报错。我的经验是先确认固件版本满足驱动要求,再确认 MindIE 版本支持的 CANN 版本范围。版本对应表在离线文档包里通常有,先查再装。
还有一个很容易被忽略的点:安装包自带的后缀或位宽信息。比如某个安装包只支持 aarch64,但你确认安装的是 x86 系统,那装到一半就会失败。这类问题在核对安装包时就要注意。
6.2 模型加载与推理阶段的坑
模型加载阶段的报错信息通常比较笼统,不要指望它直接告诉你缺哪个文件。我的排查顺序是:先看路径权限,再看目录结构,最后看配置字段。
有一次模型加载失败,日志里只写了“model not found”,我检查路径后发现目录存在,文件也在。折腾了半天才想起是容器内和宿主机路径不一致,挂载目录后容器内路径和配置里写的路径差了一层,导致文件找不到。所以先确认容器内实际看到的是哪个路径,再写配置文件。
推理阶段的典型问题是输出质量差或者响应乱码。量化模型输出质量差,优先检查量化校准集是否有代表性,以及量化位宽是否过于激进。响应乱码则多半是 tokenizer 加载错误,或者请求里的 prompt 格式和模型训练时的格式不一致。
还有一类隐蔽问题是端口冲突。两个模型实例如果配了同一个端口,第二个模型可能不会立刻报错,而是在请求路由时随机打到某一个实例上。验证时表现出来就是明明访问的是模型 A,返回的内容却像模型 B 的风格。检查端口占用和进程状态非常必要。
下面是我整理的高频问题速查表:
| 问题现象 | 可能原因 | 排查方向 |
|---|---|---|
| 驱动安装失败 | 固件版本过低 | 先升级固件再装驱动 |
| 模型加载报 model not found | 容器内外路径不一致 | 确认挂载后实际路径并核对配置 |
| 请求超时 | 并发数或 KV Cache 分配不足 | 调大 max_batch 或压缩 max_output_len |
| 输出乱码 | tokenizer 路径指向错误 | 检查 tokenizer 文件和模型是否匹配 |
| 返回内容像是另一个模型 | 端口冲突 | 检查服务端口占用和模型路由 |
| 启动时找不到算子库 | CANN 环境变量未加载 | 确认环境变量已写入系统 profile |
6.3 排查工具与日志路径速查
离线环境里没有云上那种一键诊断平台,所有问题都要靠日志和命令行工具来定位。我建议从一开始就养成看日志的习惯,不要等报错才翻。
常用的日志路径包括 MindIE 服务日志、模型加载日志、以及昇腾设备运行时日志。这些日志的存放位置在安装后就能通过命令查到。日志级别建议一开始就调到 debug,等确认服务稳定后再降回 info,否则排查问题时会因为日志信息不足而无从下手。
设备侧的排查工具有两个关键命令,一个是查看设备状态和健康信息,另一个是监控实时利用率。当服务起来后,用监控命令观察卡上的算力占用、显存占用,如果模型已经加载但是显存占用一直上不去,说明请求没有被正确分发到设备上。
最后再说一个容易被忽略的验证手段:把日志和配置备份保留下来。每次你有动作之前,先备份当前能用的配置和启动命令。离线环境下试错成本高,备份一份可以随时回滚的现场,比什么排查技巧都管用。
这次项目结束复盘,我最大的体会是“离线部署没有捷径,但可以有清单”。把每一步依赖、每一个版本、每一条路径都核对清楚,绝大多数坑都可以在动手之前被提前排除。不要急着去调参、去压测,先把现场环境搞清楚,比什么都强。
最后再分享一个小技巧:如果你这台 Atlas 800I A2 后续还要再扩充模型,建议把驱动、固件、CANN、MindIE 的安装包和镜像单独归档到一个离线仓库目录,并维护一份版本清单。这样后续新增模型或重建环境时,就不用每次都在外网重新搜集材料了,一台服务器从接电到模型上线能省下大半天时间。