☰
RHEL 8显卡服务器上实时推荐系统GPU推理优化实践
2026/10/19 11:34:52 网站建设 项目流程

1. 实时推荐系统遇上显卡服务器:先看清延迟花在哪

如果你负责过实时推荐系统,应该知道最难受的不是模型 AUC 上不去,而是线上请求一多,响应时间立刻变得很难看。我最近把一套 AI 驱动的实时推荐服务跑在 RHEL 8 + 显卡服务器上,踩了一圈坑之后,最大的感受是:显卡不是装完驱动就能变快的,RHEL 8 上从驱动、算力运行时到推理服务的整个链路,任何一个环节配置不对,最终都会体现在用户体验的 P99 延迟上。

推荐系统不是单点模型。一次完整的线上推荐,通常要经历用户画像读取、候选集召回、特征拼接、排序模型推理、重排规则过滤、组装返回这么几步。这里面的延迟账单非常有意思:召回阶段可能占用 30% 的时间,特征处理占用 20%,排序模型推理占用 30%,剩下的花在网络和序列化上。很多人一开始只盯着模型推理时间,但其实特征服务、缓存命中率、召回候选数量,都会直接影响 GPU 服务吃到的请求量和 batch 形态。

所以,在 RHEL 8 上配置显卡服务器,本质上不是装一个驱动、跑一个模型那么简单,而是要重新梳理推荐链路,把最重的排序计算卸载到 GPU 上,同时把 CPU 资源留给召回和特征工程,让整个系统在同样的硬件成本下,把响应时间从“可用”压到“顺畅”。

1.1 推荐链路里那些被忽略的延迟

我遇到过一套线上推荐服务,最初所有模块都在 CPU 上跑。单路请求看,模型推理 40ms 左右,看起来还能接受,但一旦 QPS 上来,CPU 占用很快冲到 90% 以上。原因不是模型本身有多复杂,而是推荐排序模型里大量使用 Embedding 查表和特征交叉,这种计算模式在 CPU 上跑,单个请求不慢,但并发一高,线程切换和内存带宽就顶不住了。

用户能感知的延迟不是平均值,而是尾延迟。CPU 服务在低负载下可能 P99 只有 120ms,但高负载下能轻松飙到 300ms 以上。推荐页面每慢 100ms,用户跳出率都会明显上升。显卡服务器的价值就在这里:把排序这种计算密集型的环节放到 GPU 上,CPU 从繁忙的矩阵运算中解放出来,专心做召回和特征处理,整体延迟会更稳定。

还有一部分延迟是模型加载和框架初始化造成的。很多人部署推理服务的时候,只测了上线之后正常请求的耗时,却忽略了服务启动时模型加载、运行时初始化、首次推理预热这几个阶段。在 RHEL 8 上做显卡服务器优化,我会把模型预热也当成正式流程的一部分,否则上线后的第一个请求可能就会超时。

1.2 GPU 真正能加速的部分

需要澄清一点,GPU 不是对推荐系统里的所有环节都有用。候选召回如果是基于向量检索,比如用近邻检索库做相似度计算,显卡有帮助,但通常网络传输和特征过滤才是瓶颈。真正的深度排序模型,尤其是多任务学习、DNN 交叉层、Attention 结构这一类,计算量集中在矩阵乘法和激活函数上,这才是 GPU 发挥优势的主场。

以排序模型为例,一个请求进来,系统要为每个用户生成一个“用户 Embedding”,再和几百个候选物品的 Embedding 拼接,过几个全连接层。单看一个请求的 FLOPs 不高,但线上推荐往往是一次请求返回几十个甚至上百个物品,模型侧要同时计算多个候选的得分。这种批量候选计算,在 GPU 上可以被天然地并行化。

GPU 的另一个优势是“延迟换吞吐”的灵活性。如果每个请求都单独做一次推理,显卡的利用率会很低。所以我在实际项目中一定会做动态批处理,也就是把一小段时间窗口内的多个推理请求攒在一起,组成一个批次,再一次性交给 GPU。这样单个请求的延迟会增加几毫秒,但整体吞吐能翻好几倍。RHEL 8 下配置这类推理服务时,CPU 核数、内存和显存大小,决定了你能把批处理做到多激进。

1.3 RHEL 8 在整条链路里的位置

RHEL 8 在服务器市场很常见,但它的默认配置并不是为 GPU 推理准备的。系统里可能带有开源显卡驱动,内核启用了安全模块,网络栈和内存参数也都是通用默认值。要在这样的系统上跑 AI 实时推荐,需要做不少“面向推理场景”的调整。

很多团队用 CentOS 类系统习惯了“开箱即用”,到了 RHEL 8 上,会碰到几个特点:一是软件源里默认没有显卡厂商的驱动包,需要自行添加;二是默认的 Nouveau 开源驱动会和新驱动冲突,必须在内核层面禁用;三是 RHEL 8 使用 grubby 管理内核启动参数,和老的 grub 配置方式不完全一样。这些细节如果没处理好,显卡看起来被系统识别了,但实际算力不可用。

我通常会先把 RHEL 8 当做一个“需要主动喂驱动和运行时的底座”来对待。系统的定制化越少越好,驱动版本固定,内核版本锁定,算力运行时和模型推理框架的版本对应关系记录清楚。这样排障的时候,你才不会陷入“驱动显示正常,但程序报错”的尴尬境地。

2. 显卡服务器准备:先把地基打稳

2.1 硬件配置怎么定

聊显卡服务器之前,先说硬件选型。推荐系统的排序模型,参数量一般在几百 MB 到几个 GB 之间。如果模型是稀疏特征特别多的宽深模型,显存需求主要由 Embedding 表决定。我的经验是,排序模型的显存预算不要只算模型权重,还得把推理时的中间激活、动态批处理的临时张量、框架运行时的缓存都留出来。

简单算一本账:模型权重 1GB,Embedding 表 2GB,动态批处理最大 batch 32 时中间激活可能要占 500MB,再加上推理框架的缓存预留,一块 16GB 显存的显卡基本够用。但如果要同时部署多个模型,或者做模型版本灰度切换,最好选 24GB 或更高显存的计算卡。

CPU 方面,不要觉得上了显卡 CPU 就不重要。推荐系统的召回、特征拼接、序列化、网络传输都在 CPU 上跑。我建议至少配 16 核以上,且核心频率不要过低,因为很多特征处理是单请求串行完成的,主频高一些,P99 会更好看。内存最好 64GB 起步,特征服务和缓存也会占用不少。

硬盘反而不是重点,但模型文件和日志需要一定空间。建议系统盘和数据盘分开,避免日志写满后影响内核和驱动。RHEL 8 安装时我会把 /var、/opt 单独分区,避免模型长期堆积导致根分区爆掉。

2.2 系统分区与内核参数

系统安装阶段,尽量选择最小化安装,不要装不必要的图形界面组件。显卡服务器跑在线推理,稳定性和资源利用率优先,图形环境除了多占内存没有任何好处。

装完系统后,有几个内核参数值得调整。网络方面,如果推荐服务大量使用远程调用,我会调大 socket buffer 和 TCP 连接队列长度。内存方面,适当的 vm.swappiness 和 vm.overcommit_memory 可以避免推理进程在内存紧张时被频繁回收。还有 GPU 推理服务常用线程池,进程数、文件描述符上限要调大,否则高并发时会出现奇怪的 connection reset。

RHEL 8 上可以用系统自带的性能调优服务来管理。如果没有特殊需求,就不需要每台机器手工改一堆 sysctl 参数,而是把配置沉淀到统一的模板里。这样后续扩容显卡服务器时,可以直接复用。

2.3 选定驱动安装方式

显卡驱动安装方式,通常有两种:一种是用显卡厂商提供的软件仓库直接安装,另一种是下载 runfile 手动安装。RHEL 8 上我建议优先用仓库方式,因为能通过系统包管理统一升级和卸载,但前提是你对仓库的更新策略有控制。

手动 runfile 安装的好处是版本非常明确,不容易被系统自动更新破坏。坏处是,每次内核升级之后,驱动模块可能无法自动重新编译,如果你没给内核做版本锁定,一次普通的 system update 就能让 GPU 服务突然挂掉。我个人在实际项目中,会给内核包设置版本锁定,或者启用 DKMS 机制,让驱动在内核升级后自动重新编译。

另外,RHEL 8 默认可能开启了 Secure Boot。这时安装驱动会遇到模块签名问题,要么在固件里关闭,要么把驱动模块做 MOK 签名。不要图省事直接跳过签名,否则重启后模块加载失败,排查起来非常痛苦。

3. 驱动与算力环境安装实录

3.1 屏蔽开源驱动

在 RHEL 8 上,GPU 驱动安装的第一步往往不是装新驱动,而是先禁用系统自带的开源驱动。实际操作时,我会先查看系统是否识别到显卡设备,以及当前使用的驱动模块是什么。

cat /etc/redhat-release uname -r lspci | grep -i vga lsmod | grep nouveau

如果 lspci 能识别显卡,但 lsmod 里看到 nouveau,那就说明系统默认加载了开源驱动。直接安装新驱动时会因为模块冲突失败,或者装完不生效。RHEL 8 下推荐用 grubby 修改内核命令行参数,把 nouveau 屏蔽掉。

cat > /etc/modprobe.d/disable-nouveau.conf <<EOF blacklist nouveau options nouveau modeset=0 EOF grubby --update-kernel=ALL --args="rd.driver.blacklist=nouveau nouveau.modeset=0"

改完之后要执行一次 initramfs 重建,再重启系统。注意,RHEL 8 的 initramfs 命令是 dracut,如果忘了重建,重启后可能还是老样子。重启之后再用 lsmod 检查,确认 nouveau 不再加载,才能继续进行驱动安装。

提示:这步很多人会跳过,结果装完驱动后 nvidia-smi 报“couldn't load kernel module”。不要怀疑是安装包坏了,先检查是不是 nouveau 占着设备。

3.2 安装驱动与算力运行时

驱动安装成功之后,还需要安装算力运行时和深度神经网络加速库。这里的版本对应关系非常关键。显卡驱动、算力运行时和深度学习框架不是越新越好,而是要互相兼容。我见过太多因为“追新”导致的不兼容问题。

我的做法是,先确定推荐模型要用的深度学习框架版本,再根据框架的安装文档确定它支持哪一版算力运行时,最后反向选择显卡驱动的版本。整个版本组合确定之后,写到一个 requirement 文件里,作为这台服务器的标准配置。

RHEL 8 上安装驱动之后,可以用系统自带的状态工具确认显卡和算力运行时是否正常:

nvidia-smi

这个命令会输出显卡型号、驱动版本、显存占用和 GPU 利用率。如果这里能看到显卡,说明内核层面的驱动没问题。接着可以跑一个简单的 Python 脚本,确认算力运行时能被模型框架调用。

3.3 验证 GPU 是否真正可用

只看到 nvidia-smi 不算完,还要验证整条 GPU 计算链路。一个比较实用的验证方法是,把推荐模型里的一个中等规模的张量计算放到 GPU 上,对比 CPU 的时间。

很多框架默认使用显存的“缓存分配器”,第一次在 GPU 上创建张量时会把一整块显存预留出来,看起来显存占用很高,但这不能说明模型真的在 GPU 上跑。真正需要确认的是,模型推理时 GPU 利用率有没有上去,以及显存会不会持续增长。

我在生产环境里会记录两个指标:GPU 利用率和显存峰值。如果模型已经接流量,但 GPU 利用率不到 20%,大概率是批处理策略没生效;如果显存持续增长,那就是推理过程中有持久化张量没有被释放,需要排查代码或框架配置。

4. 模型服务化与实时推荐链路优化

4.1 模型导出与格式选择

在 RHEL 8 上跑实时推荐,模型不能直接用训练时的脚本方式上线。训练时模型是动态图,方便调试,但线上推理需要把计算图固定下来,减少 Python 解释层的开销。我通常会先把训练好的模型导出成静态计算图格式,再用推理框架加载。

导出模型的时候,推荐系统有个坑:Embedding 查表通常是稀疏操作,动态图里可以接收任意长度的候选列表,但静态图导出如果没固定动态维度,推理引擎就会报 shape 相关的错误。我的经验是,导出时把候选数量设成线上可能出现的最大值,并且把用户特征和候选特征分开输入,避免在模型内部拼接时出现尺寸不匹配。

模型导出之后,还得做一个“等价性验证”。也就是用同一批样本,跑导出前后的模型,比较输出结果是否一致。不同框架导出时,数值精度可能有细微差异,而推荐排序结果是连续分数,微小的误差不影响排序,但需要设定一个可接受的阈值范围。

4.2 推理服务怎么接进推荐系统

实时推荐服务的架构上,我建议把 GPU 推理封装成一个独立的模型服务,不要和推荐业务主服务混在一起。业务主服务负责用户请求解析、召回、特征组装,模型服务只负责“接收特征向量,返回分数”。中间用高效的远程调用协议通信,避免大量 JSON 序列化。

RHEL 8 上跑模型服务,可以直接用系统服务托管,也可以用容器。系统服务方式更简单直接,资源隔离靠 systemd 的 cgroup 就能做到。容器方式便于版本管理和资源配额,但要额外处理驱动映射问题。我的建议是,如果只有一台或几台显卡服务器,用 systemd 直跑更省心;如果需要频繁扩缩容,容器更合适。

模型服务启动时要做模型预热。推荐模型里有些算子第一次执行时需要进行内核编译,延迟可能是正常推理的好几倍。预热的方式是启动后往回放一批历史请求,让所有算子都执行一遍,再把服务注册到可用列表里。否则优雅上线瞬间,进来的实际流量会直接撞上冷启动延迟。

4.3 动态批处理与并发控制

显卡推理服务最关键的优化点,就是动态批处理。简单说,就是不让每个请求单独进 GPU,而是让请求在队列里等一小段时间,凑成一个 batch 再统一计算。RHEL 8 上的推理服务,我会把批处理参数分成四个:

参数建议值说明
max_batch_size16~32超过这个值会执行拆分,避免单次耗时过长
max_wait_time3~5ms队列里等待的最长时间,超过就立刻推理
queue_capacity200~1000队列长度,防止流量洪峰时无限排队
timeout_ms100ms整次推理请求的超时时间,保护上游

动态批处理的收益非常明显。单请求推理时,显卡大多数计算单元是空闲的,但因为每个请求都要走一次调用,延迟并不低。把 8 个请求拼成一个 batch 后,总计算时间可能只比单个请求多 20% 到 40%,单请求平均耗时反而更可控。

但批处理不是越大越好。batch 太大会导致单个推理任务执行时间变长,P99 延迟上升;等待时间太短,批处理命中率就会很低。实际调参时,我会看一个指标:批处理命中率,也就是一个 batch 里平均装了多少个请求。如果平均 batch size 不到 2,说明等待窗口太短;如果平均 batch size 长期满额,说明 max_batch_size 可能太小,或者流量本身已经压满了。

4.4 缓存和召回侧配合

GPU 推理再快,也架不住每个请求都推全量候选。推荐系统里的“实时推荐”四个字,不代表每次都要重新计算所有环节。合理使用缓存,能把真正需要 GPU 推理的请求数量降一个数量级。

我常用的三种缓存策略:第一,用户画像和特征缓存,减少特征服务的重复计算;第二,召回候选集缓存,如果一个用户在短时间内重复访问,可以直接复用候选集合;第三,热门物品的模型预测分数缓存,对于少数高热物品,不必每次重新推理。

需要注意,缓存引入之后,推荐结果可能出现“不够实时”的问题。所以缓存命中率不是越高越好,而是要在响应时间和推荐新鲜度之间做平衡。实操中,我会给不同类型的推荐位设置不同的缓存 TTL,比如首屏推荐用短缓存,下拉刷新时用长缓存。

召回侧也要配合 GPU 负载。如果召回结果动辄上千个候选,排序模型即使再快,特征拼接和传输也会拖慢整体响应。我一般会把召回候选数量控制在几十到几百个,再根据 GPU 推理能力和业务需求调整。候选少了,推荐多样性可能下降;候选多了,延迟又会上升。

5. 性能验证、容量评估与常见问题

5.1 上线前的压测指标

上线前,我会搭一套和线上环境一致的压测环境,用历史流量回放来做验证。压测的目的不是看 GPU 能不能跑,而是确定这台显卡服务器到底能扛多少 QPS,以及不同并发下的延迟表现。

我习惯记录四个指标:

指标关注原因
P50 延迟大多数用户体验
P99 延迟尾延迟,决定用户是否会投诉
GPU 利用率显存是否成为瓶颈
错误率超时、排队丢弃、服务不可用

压测时,我会从低并发开始,逐步增加,直到 P99 延迟超过业务容忍线。记录这个临界 QPS 作为容量基准。之后所有模型更新或配置调整,都要用同一套压测流程做回归。只有这样做,你才能回答“显卡服务器到底值不值”这个问题。

一个参考经验值是,在 CPU 方案下 P99 可能在 200ms 以上的系统,换到 GPU 推理并且做好动态批处理后,P99 有可能压到原生 CPU 方案的一半以下。但要强调的是,数值完全取决于模型复杂度、特征数量和流量模型,不能拿别人的压测数据直接当自己的容量规划。

5.2 驱动与进程常见故障

驱动层面的故障,大多是版本和内核不匹配造成的。比如升了一次内核后,驱动模块没有重新编译,nvidia-smi 命令会消失,或者程序报找不到驱动设备。遇到这种情况,先看系统日志里是不是 module load error,再决定是回滚内核还是重新编译驱动模块。

另一种常见问题是驱动安装完成,但 Python 推理脚本仍然报算力运行时不可用。这类问题通常不是驱动的问题,而是动态库路径没配对。RHEL 8 上,我会把算力运行时的库路径写进 ld.so.conf,并执行 ldconfig 刷新缓存,避免每次都靠手动设置环境变量。

进程层面,最容易踩到的是显存泄漏。推荐模型上线后,显存占用缓慢上涨,几天后突然 OOM。这类问题大多来自推理框架的显存缓存策略,而不是代码真的泄漏。解决思路是设置显存分配器的最大缓存比例,或者定期释放缓存。直接重启服务治标不治本,关键是找到哪个环节在持续累积显存。

5.3 延迟抖动排查

有时 GPU 利用率不高,但 P99 延迟就是不稳定。我排查过几个方向,比较有效的是:

检查 CPU 核数是否正确绑核。Python 推理服务如果线程数很多,但 CPU 配额不足,调度延迟会直接反映在 P99 上。我会用 taskset 把推理进程固定到一组专属 CPU 核,避免和其他服务抢调度。

检查模型服务的线程池是否被阻塞。推理框架里获取 GPU 结果通常是阻塞式的,如果线程池满了,后续请求只能排队。这时候需要调大线程池,或者减少同步调用,把推理请求改成异步回调。

检查缓存命中率波动。缓存过期时间设置不合理,会导致缓存整片失效,流量瞬间透传到 GPU 服务,延迟在一个时间点突然飙高。我的做法是给缓存增加随机偏移,避免所有 key 在同一时刻过期。

5.4 长期运行稳定性保障

显卡服务器长期运行,还有两个不能忽略的维度:温度和驱动升级。

GPU 在高负载下发热量很大,机房散热如果压不住,显卡会自动降频,表现为延迟缓慢上升。我会在压测时同步查看显卡温度和核心频率,如果温度过高,先解决散热再谈优化。

驱动升级这件事,很多团队要么完全不升,要么一升就升最新版。我的建议是,固定在一个经过验证的驱动版本上,除非有安全漏洞或者新算力运行时强依赖,否则不要动。每次升级都要做完整回归,包括 nvidia-smi 输出、模型推理正确性、压测延迟曲线。

最后分享一个我自己的体会:在 RHEL 8 上做 AI 推荐系统的 GPU 优化,本质上是在“算力、延迟、成本”之间找平衡。驱动和算力环境只是地基,真正让用户体验变好的,是动态批处理、缓存策略、特征链路这些系统工程层面的细节。参数可以照着别人的经验调,但一定要结合自己的流量模型去压测验证,这样才不会在线上被 P99 打脸。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询