海光DCU大模型推理生产落地:从环境搭建到稳定运维的完整实战笔记
海光DCU大模型推理生产落地:从环境搭建到稳定运维的完整实战笔记
国产算力落地到生产环境,从来不是“把模型跑起来”这么简单。很多团队都遇到过类似的问题:实验室里单卡跑demo一切正常,一到线上高并发场景就性能跳水、显存泄漏、服务偶发崩溃,排查起来又缺乏成熟的方法论,只能靠反复试错踩坑。
事实上,海光DCU的推理落地是一套系统性工程:环境基线决定了性能的下限,部署选型决定了资源利用率的天花板,调优策略决定了最终的吞吐表现,运维体系则保障了长期运行的稳定性。四个环节环环相扣,任何一处短板都会拉低整体效果。本文结合多个信创项目的落地经验,把从环境搭建到生产运维的全流程实操方法整理出来,每一步附带可复用的脚本与参数建议,帮助团队少走弯路,快速实现从“跑通”到“用好”的跨越。
一、环境打底:三类隐性配置,决定性能下限
很多人容易忽略环境层的重要性,觉得“能识别设备、能启动模型”就算环境合格。实际上至少有三成的性能损耗,都来自环境配置的隐性问题——版本不匹配、路径优先级错误、NUMA跨节点调度,这些问题不会让程序报错,却会悄悄吃掉20%~40%的硬件性能。
1. DTK版本选型:不是越新越好
DTK作为DCU的核心工具链,版本选择的第一原则是“匹配优先,稳定优先”,而不是盲目追新。内核驱动、DTK、PyTorch三者必须版本对齐,任何一环错位都会出现“能启动但性能异常”的诡异问题。
- 生产环境优先选择经过厂商验证的稳定版:比如深算二号K100优先搭配DTK 26.04,深算一号Z100优先搭配DTK 25.04.4,这两个版本的算子适配、工具链完整度都经过了大规模场景验证;
- 版本一致性校验要做双端:既要查用户态DTK版本,也要查内核驱动版本,两者大版本必须对应。
# 查用户态DTK版本hipcc--version|grep"HIP version"# 查内核驱动版本lsmod|grephydcu modinfo hydcu|grepversion新手最容易踩的坑是:系统里预装了多个版本DTK,环境变量配置混乱,编译时用一个版本,运行时加载另一个版本的动态库,最终出现算子不兼容、性能骤降的问题。更稳妥的做法是在作业脚本开头显式加载指定版本,避免依赖系统默认环境。
2. PyTorch安装:版本对应是第一原则
PyTorch的安装是高频踩坑点,直接pip install torch默认下载CUDA版本,在DCU环境里永远识别不到设备。正确的安装逻辑是:先确认DTK对应的ROCm版本,再安装对应ROCm版本的PyTorch。
版本对应关系可以简单记为:DTK 25.x对应ROCm 5.x,DTK 26.x对应ROCm 6.x。以DTK 26.04为例,标准安装命令如下:
pipinstalltorch==2.4.0torchvision==0.19.0\--index-url https://download.pytorch.org/whl/rocm6.0安装完成后不能只看torch.cuda.is_available()返回True就完事,还要做两层校验:一是确认ROCm运行时版本正确,二是确认设备架构与硬件匹配。
importtorchprint(f"PyTorch版本:{torch.__version__}")print(f"HIP运行时版本:{torch.version.hip}")print(f"设备可用:{torch.cuda.is_available()}")print(f"设备架构:{torch.cuda.get_device_properties(0).gcnArchName}")如果架构名显示异常、或者HIP版本和DTK不对应,后续运行大概率会出现数值错误或者性能异常,越早排查越省时间。
3. NUMA亲和性:零成本的性能提升
主流双路服务器的DCU通常分属两个NUMA节点,组内卡间通过xGMI高速互联,跨组通信则要经过CPU总线,带宽只有组内的三分之一左右。如果进程不做绑核,操作系统会随机调度CPU核心,频繁跨NUMA传输数据,多卡并行效率可能连60%都到不了。
优化方法非常简单:启动任务前先查清楚显卡对应的NUMA节点,用numactl把进程绑定到对应NUMA节点的CPU和内存上,完全零代码改动,通常能带来20%~40%的性能提升。
# 第一步:查看DCU拓扑与NUMA归属rocm-smi--showtopo# 第二步:绑定对应NUMA节点启动,示例:前4卡绑定NUMA 0numactl--cpunodebind=0--membind=0\python your_inference_script.py单卡推理场景下,绑核的收益可能不明显,但在4卡、8卡多卡张量并行的场景下,这个优化的效果会非常显著,是所有优化里投入产出比最高的一项。
二、部署选型:模型、精度与框架的匹配逻辑
环境配置合格之后,下一步是选择合适的部署方案。同样的硬件和模型,不同的精度、框架、参数配置,最终的吞吐和延迟可能差出数倍。选型没有绝对的最优解,核心是在业务的精度要求、延迟要求、并发需求之间找平衡点。
1. 卡型与模型规格的匹配参考
不同量级的模型,对显存和算力的需求差异很大,选对配置才能兼顾成本和性能。以下是生产环境的常用匹配参考,基于BF16精度、vLLM框架:
- 7B及以下模型:单张K100(96GB显存)即可轻松承载,剩余显存可以支撑大量KV缓存,支持高并发请求;即使是Z100(64GB显存)也能单卡部署,适合边缘场景、低并发业务。
- **32B70B模型**:需要48张K100做张量并行,推荐8卡整机部署,xGMI全互联能保证多卡通信效率;如果是Z100机型,通常需要8卡以上才能承载70B级模型,且并发能力有限。
- 多模态模型:视觉塔会额外占用一部分显存和算力,同参数模型建议按提升一个量级配置硬件,比如7B级多模态模型建议预留至少24GB显存余量。
2. 精度选择:在性能、显存、精度之间做取舍
四种常见精度各有适用场景,不是精度越低越好,也不是越高越稳妥:
- FP32:精度最高但显存占用大、速度慢,除了训练梯度计算场景,推理基本不会使用;
- FP16/BF16:推理场景的主流选择,显存占用是FP32的一半,精度损失可忽略,DCU硬件原生支持,兼容性最好,业务对精度有要求时优先选这个;
- FP8:显存占用再减半,吞吐提升明显,但有两个注意点:一是DCU的FP8编码格式和NVIDIA不通用,不能直接复用CUDA环境的量化权重;二是部分复杂算子支持度一般,适合以自回归解码为主的纯推理场景。
- INT8:显存占用最低,但精度损失相对明显,适合对响应速度要求高、精度容忍度高的场景,比如检索、分类类任务。
3. vLLM生产级参数详解
vLLM是目前DCU上推理性能最优的框架之一,但很多人直接照搬CUDA的启动参数,导致稳定性和性能都不达预期。DCU环境有几个专属参数需要重点调整,下面是生产环境的标准启动模板,附带每个参数的设计逻辑:
#!/bin/bashsource/opt/dtk-26.04/env.shexportTRITON_HIP_LLD_PATH=/opt/dtk-26.04/llvm/bin/ld.lld numactl--cpunodebind=0--membind=0\vllm serve /data/models/Qwen2-7B-Instruct\--served-model-name Qwen2-7B-Instruct\--tensor-parallel-size1\--dtypebfloat16\--max-model-len8192\--gpu-memory-utilization0.9\--kv-cache-dtype fp8\--enforce-eager\--max-num-seqs256\--host0.0.0.0\--port8000几个关键参数的选型逻辑:
--gpu-memory-utilization 0.9:预留10%显存给运行时开销、临时张量,避免高并发下触发OOM;不要设成0.95以上,DCU运行时的显存开销比CUDA略高,打满显存很容易崩溃。--enforce-eager:关闭CUDA Graph,这是DCU环境的必加参数。CUDA Graph在部分算子上兼容度不佳,会导致偶发崩溃和长尾延迟飙升,开启eager模式后虽然单序列速度略有下降,但P99延迟和稳定性会大幅提升。--kv-cache-dtype fp8:开启FP8 KV缓存,显存占用直接减半,能支撑的并发数几乎翻倍,是性价比最高的优化项之一,且对最终生成质量影响极小。--max-num-seqs 256:最大并发序列数,这个值不是越大越好。太小会导致算力喂不饱,太大则会让每个请求的延迟升高,需要根据业务的吞吐和延迟要求平衡。
三、性能调优:三层优化路径,按需落地
性能优化不用上来就写自定义算子,可以按照从易到难的顺序分层落地:先做零代码的通用优化,再做框架级参数调优,最后针对热点算子做深度定制。大部分场景下,做完前两层就能达到80分的性能,足够满足生产需求。
第一层:通用基础优化(零代码改动)
这一层不需要改业务代码,只需要调整启动配置和环境参数,就能覆盖大部分性能问题,适合所有场景优先落地。
除了前面提到的NUMA绑核、开启eager模式、FP8 KV缓存之外,还有两个实用技巧:
- 固定显存分配阈值:避免运行时频繁申请释放显存,减少调度开销。可以通过环境变量设置PyTorch缓存分配器的行为,减少显存碎片。
- 避开显存残留卡:DCU的显存回收机制和CUDA不同,进程异常退出后显存不会立刻释放,新任务尽量选择空闲卡启动,通过
HIP_VISIBLE_DEVICES指定设备编号,避免和残留进程抢显存。
第二层:算子级融合优化(轻量代码改动)
如果基础优化后性能仍有瓶颈,可以针对高频算子做融合优化,核心思路是减少中间张量的读写,把多次显存访问合并成一次。
最典型的就是RMSNorm、Bias+GELU这类逐元素算子,原生PyTorch实现会产生多次显存读写,融合成单个核函数后性能能提升1.5倍左右。下面是一个简化的融合Bias+GELU核函数示例:
__global__voidfused_bias_gelu_kernel(constfloat*input,constfloat*bias,float*output,introws,inthidden){introw=blockIdx.y;intcol=blockIdx.x*blockDim.x+threadIdx.x;if(row>=rows||col>=hidden)return;intidx=row*hidden+col;floatval=input[idx]+bias[col];constexprfloatkSqrt2OverPi=0.79788456f;constexprfloatkCoeff=0.044715f;floatcubic=val*val*val;output[idx]=0.5f*val*(1.0f+tanhf(kSqrt2OverPi*(val+kCoeff*cubic)));}这类融合算子的开发成本不高,但收益非常明显,尤其适合Transformer里的高频小算子。如果团队有HIP开发能力,优先优化Top5热点算子,整体性能就能上一个台阶。
第三层:吞吐与延迟的平衡调优
当服务进入稳定运行阶段,就需要根据业务特征做精细化调优。核心是在吞吐和延迟之间找平衡点:
- 高吞吐优先场景:比如离线批量推理、非实时问答,适当调大
max-num-seqs,增加批处理大小,让硬件尽量跑满,追求单位时间处理的请求总数最大化; - 低延迟优先场景:比如实时对话、在线客服,适当减小最大并发数,避免请求排队,同时可以调小
max-model-len,缩短单次推理的计算量; - 长文本场景:重点优化KV缓存的分页效率,开启前缀缓存复用,减少长上下文的重复计算。
四、生产运维:稳定性与可观测性建设
生产环境和实验室demo最大的区别,就是需要保证7×24小时稳定运行。很多团队只关注上线时的性能,却忽略了运维体系建设,结果线上频繁出问题,排查又没有抓手。其实只要做好三层保障,就能大幅降低故障率。
1. 日常健康巡检:把问题扼杀在萌芽期
每天做一次基础巡检,提前发现潜在风险,比故障发生后再排查效率高得多。下面是一个通用巡检脚本,覆盖硬件状态、服务状态、资源占用三大类核心指标:
#!/bin/bashecho"=== DCU 推理服务日常巡检$(date'+%Y-%m-%d %H:%M')==="echo-e"\n1. 硬件状态检查"rocm-smi--showuse--showmemuse--showtemp--csv2>/dev/null|tail-n+2|\awk-F',''{printf " 卡%s: 使用率%s%%, 显存%s/%s, 温度%s℃\n", $1, $3, $5, $6, $8}'echo-e"\n2. 推理服务状态"if[-fvllm.pid]&&kill-0"$(catvllm.pid)"2>/dev/null;thenecho" 服务运行中,PID:$(catvllm.pid)"curl-shttp://localhost:8000/v1/models>/dev/null2>&1&&echo" 接口响应正常"||echo" 接口无响应"elseecho" 服务未运行"fiecho-e"\n3. 显存与进程检查"count=0fordevin/dev/dri/renderD*;dopids=$(fuser"$dev"2>/dev/null)if[-n"$pids"];thencount=$((count+$(echo "$pids"|wc-w)))fidoneecho" 共$count个进程占用DCU设备"echo-e"\n✅ 巡检完成"通过巡检可以提前发现显存异常占用、温度过高、服务无响应等问题,及时处理避免扩大成线上故障。
2. 常见故障的排查流程
三类最常见的线上故障,按优先级排查基本都能快速定位:
- 服务OOM崩溃:优先检查是否并发过高、KV缓存设置过大,再排查是否有显存泄漏,最后确认是否有其他进程抢占显存;
- 性能突然下降:先查是否有其他任务抢占资源,再查NUMA绑定是否失效、是否切换了运行设备,最后核对模型权重、精度是否被改动;
- 偶发返回异常:优先排查算子兼容性问题,尤其是自定义算子、量化算子,再检查是否有静默数值错误,必要时关闭对应优化项回退到稳定路径。
3. 简单的服务自愈机制
生产环境不可能时刻有人值守,给服务加一层简单的守护和自愈能力,能大幅降低运维压力。最基础的方式是写一个定时检测脚本,发现服务异常时自动重启:
#!/bin/bash# 简单的服务健康检查与自愈脚本LOG_FILE="./service_guard.log"if!curl-shttp://localhost:8000/v1/models>/dev/null2>&1;thenecho"$(date'+%Y-%m-%d %H:%M:%S')服务无响应,执行重启">>"$LOG_FILE"bashvllm_service.sh stopsleep5bashvllm_service.sh startecho"$(date'+%Y-%m-%d %H:%M:%S')服务重启完成">>"$LOG_FILE"fi配合crontab定时执行,就能实现基础的故障自愈,应对偶发的服务崩溃完全够用。
写在最后
海光DCU的推理落地,本质上是一个工程化问题。硬件参数决定了理论上限,但最终能发挥出多少性能,全看环境、部署、调优、运维每一个环节的打磨程度。
不用一开始就追求极致性能,也不用上来就做深度算子开发。先把环境基线对齐,把部署配置选对,把基础优化落地,再根据业务瓶颈逐步深入,大部分场景下都能获得非常不错的效果。国产算力的生态还在快速成熟,工程经验越沉淀,后续的落地成本就越低,长期收益也会越明显。
