前段时间我们团队把水獭大模型安全卫士完整迁移到了海光GPU平台上从环境搭建到最终上线前后折腾了三周多。这篇文章记录的就是这次国产化适配的真实过程包括我们踩过的坑、查过的算子、调过的参数希望能给正在做同类事情的同行一点参考。先简单交代一下背景。水獭大模型安全卫士并不是跑在用户面前的那个大模型而是一套藏在应用背后做安全防护的系统。它负责拦截提示词注入、识别越狱攻击、对生成内容做敏感信息检测还要完成输出合规审核。之前这些检测模型一直跑在NVIDIA GPU上性能没问题但越来越多的生产环境已经换成了国产算力底座。要让安全卫士真正接入客户的生产链路就必须把检测引擎迁移到海光GPU上。刚开始我以为这只是换个驱动、重装一次PyTorch的事真正做起来才发现国产化适配远没有那么简单。1. 为什么大模型安全卫士也绕不开GPU适配1.1 安全卫士在整个AI链路里的位置水獭大模型安全卫士的典型部署形态是串联在模型网关和业务后端之间同时会以旁路模式监听请求与响应。换句话说用户每次调用大模型安全系统都要在请求进来时先做一轮输入检测在响应返回前再做一轮输出审核。这决定了它对延迟非常敏感不能因为加了一道安全防护就让业务方感觉“变卡了”。这套系统里的检测引擎并不是一个单一的大模型而是多个模型组合输入侧有意图分类模型和prompt攻击检测模型输出侧有内容安全审核模型、敏感信息识别模型还有用于语义向量检索的embedding模型。不同模型规模差异很大小的只有几千万参数大的则到了7B级别。以前在NVIDIA GPU上跑这些模型可以共享一张显卡资源通过动态调度来降低整体资源开销。换成海光GPU之后调度策略、显存分配、编译优化全部要重新来过。1.2 安全检测为什么不能只用CPU很多人会问安全检测模型都比较小为什么不用CPU跑非要适配GPU这个问题在团队内部也争论过。做过推理加速的人都知道CPU推理在小batch场景下并不是不能用但延迟和吞吐完全不是一个量级。我们实测过一组数据一个基于RoBERTa结构的内容审核模型输入长度为512 token单条推理在Hygon 7380 CPU上平均耗时接近300毫秒而同一模型在海光DCU上推理只需要5到8毫秒差了接近40倍。那个7B级别的安全判断模型差距更夸张CPU上单次推理需要几十秒完全没法在请求链路里实时拦截。再加上大模型每秒请求量通常不小如果每个请求都要额外多出几百毫秒的检测延迟业务方是绝对不会接受的。所以GPU适配不是可选项而是安全产品能否落地到国产算力环境的硬前提。2. 适配前的平台选型与技术摸底2.1 海光DCU和CUDA生态的真实差异在做适配之前我们先把海光GPU这层技术栈梳理了一遍。海光DCU的软件生态基于ROCm编程模型兼容HIP很多从CUDA迁移过来的代码可以通过HIPify工具做转换。但要特别提醒一句兼容不等于无脑照搬NVIDIA生态里的cuDNN、TensorRT、NCCL这些库在海光平台上都不能直接用需要找ROCm对应方案。PyTorch方面官方提供了针对ROCm编译的版本但版本对齐很讲究。我们用的时候必须确保PyTorch版本、ROCm版本和DCU驱动版本三者匹配任何一个对不上启动阶段就可能报各种奇怪的错误。另外即使按照文档装好了环境torch.cuda.is_available()返回True也不代表所有算子都能在DCU上正常执行我们后面就遇到了不少算子静默降级到CPU的情况这是整个适配过程中最隐蔽的坑。2.2 现有模型与推理链路的依赖盘点动手迁移前我们先把安全卫士所有模型和推理依赖列了一个清单。这个步骤千万别省越细越好。我们的清单主要包括四块模型原始框架、推理中间表示、运行环境依赖、数据处理管线。组件原始环境目标环境输入意图分类模型PyTorch CUDAPyTorch ROCm7B安全判断模型PyTorch FP16 LoRAPyTorch ROCm FP16内容审核模型ONNX Runtime CUDA EPONNX Runtime ROCm EP敏感信息识别模型TensorFlow SavedModel转ONNX后在ROCm上推理向量检索embedding模型PyTorch FAISSPyTorch ROCm FAISS CPU从表格里能看到我们不只是简单换GPU还涉及到推理中间表示的转换。比如敏感信息识别模型原本是TensorFlow的因为ROCm生态里TensorFlow支持相对弱一些我们直接把它转成了ONNX格式再用ROCm Execution Provider跑推理。这一步看起来轻松实际在算子映射阶段出了不少问题后面会细说。把依赖清单整理好之后我们给自己定了一条原则先让所有模型能在海光平台上“跑通”再谈优化。跑通的标准不是不报错而是输出结果和原有平台在业务指标上保持一致。3. 关键适配环节拆解与实操过程3.1 算子兼容性排查与等价替换真正开始迁移以后第一个拦路虎就是算子兼容性。我们用PyTorch的ROCm版本跑原始的推理脚本模型能加载前向传播能执行但测试结果准确率明显不对。后来逐个模块排查才定位到问题有些算子在DCU上并没有原生实现PyTorch会把它静默回退到CPU执行而CPU和GPU计算浮点累加的次序不同累积误差会在多层传播后被放大。排查方法其实不复杂但需要耐心。我们把模型拆成几个子图分别验证每个子图输出与CUDA环境下基准输出的偏差。发现偏差就继续拆直到定位到具体算子。我们这次遇到的典型算子有三类第一类是FlashAttention。7B安全判断模型的attention计算用了FlashAttention优化但海光DCU的ROCm版本对这个算子支持不完整导致阶段输出不一致。最后我们替换成了标准attention实现代价是推理速度下降一些但换来的是精度可靠。第二类是自定义的RoPE位置编码算子。之前为了极致性能团队自己写过一个fused CUDA版本的RoPE这个算子在海光上完全不能加载。我们改成了PyTorch原生实现通过rotate_half的方式计算输出结果和自定义算子一致。第三类是动态padding相关的mask算子在ONNX模型里被映射成了不兼容的节点。我们重新梳理了动态shape处理逻辑把mask计算挪到模型外完成彻底避开了这个算子在ROCm上的兼容问题。注意算子替换之后不要只看最终准确率最好把替换前后的中间张量也做一次对比确认影响范围只局限在某个子图内否则出现问题的时候很难定位。3.2 精度对齐验证不止是看loss曲线精度对齐这件事我多说几句。模型迁移最常见的误区是“训练loss降下来了就万事大吉”但安全检测场景对误报和漏报极其敏感0.1%的指标波动都可能带来生产问题。所以我们做了一套完整的精度对齐流程而不是简单跑几个case看看结果。我们在GPU和DCU上使用完全相同的权重、相同的随机种子、相同的batch组成分别跑前向推理保存每一层的输出logits然后计算余弦相似度和KL散度。这样做的目的是量化两个平台在数值层面的差异。最终数据如下模型Logits余弦相似度KL散度最大绝对误差输入意图分类模型0.99980.00070.027B安全判断模型0.99920.00180.05内容审核模型0.99960.00120.03敏感信息识别模型0.99950.00150.04从数值上看这些差异在可接受范围内但我们仍然发现了一个问题7B安全判断模型在长文本和特殊符号输入上偶尔会出现分类结果翻转的情况。原因是这类输入会触发某些算子的浮点累加路径差异导致置信度刚好落在阈值附近。为了解决这个问题我们对检测阈值做了小幅调整并且在敏感场景下增加了一次二次校验用另一个轻量模型做大数投票最终把误报率压回了和原平台一致的水平。所以我的建议是精度对齐不是只做一次就结束一定要准备一个覆盖正常请求、恶意攻击、敏感信息、超长文本、特殊编码等场景的回归测试集每次环境变更都要全量跑一遍。3.3 性能调优从“能用”变成“好用”跑通和精度对齐之后接下来就是把性能做上去。我们的目标是让水獭安全卫士在海光DCU上的整体检测链路延迟不超过原有NVIDIA平台的1.5倍吞吐率不能掉得太多。这个目标其实是和客户反复确认之后定的因为完全追平不太现实但也不能差得太悬殊。第一轮性能测试用的是默认配置结果并不理想。主要问题是batch_size设置太小DCU的计算能力没有完全发挥出来。我们把输入检测类模型的batch_size从1调到了8吞吐立刻提升了2倍多7B安全判断模型也从batch_size1调整到batch_size4同时打开KV Cache复用推理耗时有明显下降。第二轮的优化点在内存和线程。ROCm运行时对OMP_NUM_THREADS和HIP_VISIBLE_DEVICES这类环境变量特别敏感。我们一开始没有做任何设置导致多进程推理时线程抢占严重GPU利用率忽高忽低。后来统一在启动脚本里显式指定了环境变量只暴露需要的DCU设备编号并让每个推理进程绑定固定的CPU核心调度稳定了很多。第三轮用了profiling工具。海光ROCm携带的rocprof可以输出每个算子的执行时间我们发现7B模型里有相当一部分时间花在一个LayerNorm的CPU回退上查下来是算子实现里有一个分支走了CPU路径。换成更标准的实现之后这个算子在DCU上执行单次推理又快了15%。这些排查在文档里很难看到必须靠工具实测。最终性能对比如下场景NVIDIA T4原指标海光DCU优化后内容审核模型P99延迟7ms9ms7B安全判断模型单次推理1.8s2.3s系统整体吞吐QPS850690GPU显存峰值占用11.2GB12.6GB虽然性能没有完全打平但在容差范围内已经可以接受。有一个细节值得注意DCU的显存带宽特性与T4不太一样相同batch_size下占用会偏高我们通过限制推理队列长度和动态显存复用把显存峰值稳定控制在合理区间。3.4 推理引擎切换时的框架适配小记模型本身跑通之后我们对推理引擎也做了调整。原来的实现里不同模型分别使用PyTorch、ONNX Runtime、TensorFlow三套推理栈维护成本很高。趁这次适配我们把能统一的都统一到了PyTorch和ONNX Runtime两套上并按后端写了两层抽象。第一层是统一推理接口不管底层用CUDA、ROCm还是CPU对外都暴露同样的predict(sample)方法。第二层是推理后端选择器通过环境变量SAFETY_DEVICE_BACKEND决定加载哪个后端。这个设计让我们后续做其他硬件适配时不用改动业务代码实测下来收益很大。4. 部署环境里的那些坑与落地经验4.1 驱动、容器与框架版本锁定适配过程里环境版本一致性是最大的隐形炸弹。我们在测试环境调通之后信心满满地在客户现场部署结果第一个模型就报错原因是客户机器上的ROCm版本比我们低了一个小版本导致某个算子的二进制不兼容。从那之后我们把环境版本锁得死死的。驱动、ROCm runtime、PyTorch版本、ONNX Runtime版本全部记录在部署清单里并且使用容器镜像封装减少对宿主机环境的依赖。启动容器时别忘记映射设备文件我习惯用下面这个方式docker run -it --rm \ --device/dev/kfd \ --device/dev/dri \ --group-add video \ --security-opt seccompunconfined \ -e HIP_VISIBLE_DEVICES0 \ -v /data/models:/models \ rocm/pytorch:rocm6.2-pytorch2.3 \ python /workspace/run_server.py这里特别注意/dev/kfd和/dev/dri两个设备文件缺一个都会导致无法识别DCU。如果容器里执行rocm-smi看不到显卡几乎都是设备映射和用户组权限的问题。4.2 高可用设计与监控指标安全产品不能有任何单点故障所以GPU适配之外我们还重新设计了部署架构。每个安全检测节点至少双副本一个副本异常时流量自动切到另一个副本。尤其7B安全判断模型的推理服务要有独立的健康检查接口定时发一个假请求验证模型是否还能正常返回结果。监控方面我们除了常规的CPU和内存指标特别关注三个GPU维度指标DCU利用率、显存占用、核心温度。ROCm环境下可以用rocm-smi查这些信息建议统一接入Prometheus持久化历史曲线。另外推理延迟的P99和P99.9至少要分开监控安全检测对长尾延迟特别敏感有时候平均延迟正常但个别慢请求会把整个链路拖垮。我在实践中还把模型加载时间也列入监控了。因为DCU加载7B权重比NVIDIA慢不少如果服务刚启动就被大量请求打到还没准备好就会报错。我们的方案是做Kubernetes的readinessProbe在模型完全加载并预热完之前不让流量进来。4.3 常见问题速查表记录几个这次适配中真实遇到过的问题供参考。现象可能原因解决办法启动报错ATen not compiled with ROCmPyTorch安装成了CPU版本卸载重装ROCm版PyTorch模型能跑但速度特慢算子静默降级到CPU用rocprof定位热点替换算子推理结果和原平台偏差大浮点累加路径差异固定随机种子做余弦相似度对齐Python进程退出时卡死ROCm运行时资源没释放显式调用torch.cuda.empty_cache()并清理子进程容器看不到DCU设备缺少设备映射或用户组权限检查/dev/kfd、/dev/dri是否映射7B模型加载OOM权重型加载方式不对使用mmap加载或分片加载预留KVCache空间这些坑不是每个团队都会全踩一遍但一旦遇到如果没有排查思路很容易卡住一两天。建议把排查工具先准备好rocm-smi看卡状态rocprof看算子执行strace偶尔也能帮上大忙。4.4 一个容易被忽视的细节预热机制模型加载完成后直接处理线上请求往往会出现前几个请求延迟特别高的情况。这是因为推理框架在第一次执行时要做算子编译和显存分配DCU上这一步比NVIDIA GPU慢不少。我们在适配期间发现7B模型首次推理竟然耗时超过30秒后续才稳定到2秒多。这个问题的标准解法是预热。服务启动后先用一批构造好的假请求跑几次推理把算子编译缓存和显存分配都触发一遍再对外服务。我们把预热流程写进了启动脚本并且规定每次服务发布后必须等预热完成才算启动成功。这个细节虽然不起眼但直接影响客户第一眼体验。5. 适配完成后的一些实操心得5.1 先把“能跑”和“能上线”分开我见过不少团队做国产化适配跑通了就开始兴奋觉得马上能上线。但“能跑”和“能上线”之间差着十万八千里。我们这次严格分了两步走第一步是实验室环境验证确认功能和精度第二步是影子模式试运行把DCU节点的输出和原有NVIDIA节点的输出做实时对比持续观察48小时确认检测拦截率没有明显差异后才切正式流量。影子模式下我们还真发现了一个问题某个恶意prompt检测规则和7B模型的联合判断在DCU上出现了概率分布偏移导致两个节点一个拦截一个放行。如果直接切换线上这可能就是一次安全事件。所以强烈建议所有ACL和检测逻辑改动都加一个影子回归阶段让新旧节点并行跑一段时间。5.2 给后续其他硬件适配留好扩展位国产化硬件不止海光一家未来可能还要适配其他品牌的NPU或GPU。在代码层面我们做了推理后端抽象通过配置就能切换设备类型。在算法层面我们把所有模型导出成ONNX或TorchScript格式不再直接依赖某个框架的即时执行模式。这样每次适配新的硬件平台不需要改业务逻辑只需要处理算子兼容性和精度对齐两件事。对于一个要长期运营的安全产品来说这种扩展性设计和这次海光适配本身一样重要。我们团队内部还把这次适配过程中所有算子兼容记录沉淀成了一份内部文档以后无论换到哪个平台都能站在这次的基础上快速出发。最后再分享一个小技巧适配结束后建议把整条链路从“发起请求 → 安全检测 → 模型响应 → 输出审核”做成一个端到端的数据追踪记录每个环节的设备类型、推理耗时和拦截结果。这套数据既能用来做性能优化也能在出问题时快速定位是哪一段新环境引入的差异。这次的顺利落地很大程度上就靠这套追踪机制兜底。