引言:这是在做text2sql的项目时,使用了Ascend显卡来跑模型时出现了的问题,解决了,故此写了篇文章。

一.一阶段

在把项目转到那台搭载Ascend显卡服务器上的时候,其实一开始还是正常的,就是要改数据库连接,要换成达梦数据库,然后还要在dokcer容器里面的vllm部署好我要的模型并启动,这个部分的命令我是直接用团队大佬的,在原版的基础上修改参数变成我需要的参数,修改了模型文件啊,端口映射啊,这些东西,因为说白了我自己也不知道这个命令是咋写出来的,这个时候我还不了解dokcer

二.二阶段

项目移动了之后出现了一个问题,可能是之间数据库中的内容比较少,换了数据库并填充了内容之后,这次居然会出现超上下文长度的问题,后来检查出来了,是vanna架构里面,在训练时,塞入数据库表的ddl时,要记得分段,不然的话,在调取的时候,会直接把数据库全部的ddl都调出来(因为没分段就会当成一句话)当作提示词,那就会出现超上下文的情况。由于超出上下文了,这里其实一开始我没有意识到是全部的ddl都被拉出来了,所以一开始我的操作是把上下文的限制从4096调整为8192,后来才发现真正的问题是忘记分段了。

三.三阶段

在调整使用了一段时间后,都没有什么问题,只是效果不太理想。但是,后面同事调测多卡联调(就是把多张Ascend显卡一起用,具体是多张显卡跑一个模型还是多张显卡跑多个模型,这我就不清楚了,可能涉及到显卡的数据通信与交互,还需要调一些参数吧)。反正就是,在调试期间,我的服务就被关停了嘛,然后等调试完后,我再去使用就出问题了。反正就是用他们的说法是,我的服务测试期间被关了,然后只需要再起起来就可以连上,但是,很明显我去尝试起服务,就起了但是实际使用就会出现报错,就是没反应。这里就开始出现问题了。

由于调试了很久没有反应,我就放弃了,打算删了重头再来一遍,好,这就是噩梦的开始了。这里我就直接docker rm -f xxx就把我那个容器删了,好,先在这里告诉大家,不要这么干,一定不要这么干!!!!!!!在不确定这个容器进程是否结束,以及就算结束了,反正多查验一下吧,确保没有在进行中。为啥?!因为如果这个容器进程在进行中,而直接执行了docker rm -f xxx,那么你就会得到一个僵尸进程,一个很难查到并且难以结束的僵尸进程,至少先docker stop xxx先把你的容器停掉再做删除工作,当然了应该还有更合规的操作方式,这里自查吧,我还不太清楚最合规的操作方式是什么。(我为啥这么干,因为被要求要演示这个项目了,结果连不上模型没法输出,调了很久了都没有解决,就直接丢给AI让它帮我解决了,然后就放空脑袋听凭AI了)。为什么会发现这个问题,在删除了原本容器,有重建容器后,代码的前面部分跑起来了,但是在提出问题后,就出现了报错,说什么显存不够,我直接懵了,之前都没有出现显存不够的情况,咋就突然出现了,而且还是显存不够这种问题,肯定不对劲。为了解决这个问题,中间这里就出现了反复删除反复重建容器的过程。然后就出现了新的问题:说什么显存存在足够的显存空间,但是这些空闲的空间都被分割的比较小,而模型的需求,是一大块连续的空闲显存空间(具体为什么需要的是一大块连续的空闲显存空间我还不清楚,但是为什么会被分割的原因貌似就是我不断的删除后又重建的操作,所以再次提醒,一定要先停掉容器,再删除容器)。

大概的报错有这些:

Recv failure: Connection reset by peer(这是我去联通vllm时的报错)

(EngineCore pid=344) ERROR 08-03 02:38:39 [core.py:1136] raise ValueError( (EngineCore pid=344) ERROR 08-03 02:38:39 [core.py:1136] ValueError: Free memory on device (16.58/43.24 GiB) on startup is less than desired GPU memory utilization (0.85, 36.75 GiB). Decrease GPU memory utilization or reduce GPU memory used by other processes.(APIServer pid=1) WARNING 08-03 02:40:01 [platform.py:848] Parameter '--disable-cascade-attn' is a GPU-specific feature. Resetting to False for Ascend. (APIServer pid=1) WARNING 08-03 02:40:01 [platform.py:937] Ignored parameter 'disable_flashinfer_prefill'. This is a GPU-specific feature not supported on Ascend. Resetting to False.(这是我查询容器日志发现的错误)

这里面有三个冲突:1.空闲的显卡内存 2.显存占用率参数--gpu_memory_utilization 3.模型必须占用的显存空间。(暂时还没有搞懂)、

然后这里AI给出了一个解决方法:就是让我杀掉显卡上的全部进程,然后我去查进程杀进程,但是前面我说了,我的进程由于错误操作其实已经变成僵尸进程了,所以根本没法查到进程号,当时我是不知道的,所以无论我怎么杀进程其实都解决不了问题。或者降低显存需求,就是把--gpu_memory_utilization参数降低,刚好匹配当前的剩余空闲空间(此时还有16多个G的显存空间),当然了,当时我认为减少这个占占用率会让大模型变笨,效率变慢所以我就没有使用这个解决方法(但其实不会变笨、但效率会变慢)。

后面我还是查到了僵尸进程,然后结束了进程,此时我的显存占用都清零了,空闲显存空间也恢复到几乎满的左右,然后这里我以为都要拯救成功了,在重启服务之后,日志还是继续报显存不够的问题。然后给出的修改方式是降低显存占用率--gpu_memory_utilization,减少并发请求压缩kv缓存占用(其实我都不知道KV缓存是什么)--max_num_seqs,以及减半上下文窗口大幅降低峰值显存需求--max_model_len;但是我还是没有选择这种方式,因为我还是觉得很奇怪,明明显存清空了,但是还是会说显存不够的情况。

为什么删了容器还存在VLLMEngineCore进程?核心原因:vLLM使用多进程MP引擎(主进程+EngineCore子进程),执行docker rm -f只干掉了容器bash主进程,子VLLM引擎进程脱离容器cgroup变成宿主机僵尸进程,依然持有Ascend NPU 现存句柄,CANN驱动不会自动回收内存,所以npu-smi能看到26G占用。

两个关键诱因:

1.使用bash -lc 嵌套启动vllm,bash作为容器1号进程,子进程脱离后无法被容器信号回收;

2.docker rm -f发送SIGKILL暴力销毁,没有给vllm引擎执行资源释放的时间。

后面我又根据操作结束进程,但是还是报错啊,这里就是新的问题

总显存43多个G,参数--gpu_memory_utilization 假设为0.8就会要求43*0.8 G的连续整块空闲内存。vLLM启动时会做内存预校验:不是简单读取宿主机npu-smi的空闲值,而是容器内CANN运行时读取NPU真实连续可用HBM,同时自动扣除:1.CANN驱动内核常驻缓存(2~4GB固定占用,宿主机npu-smi不统计这部分)2.ACL算子编译临时缓冲区(启动编译模型时占用巨大)3.内存碎片:之前vllm进程残留销毁后,显存碎成几百MB小块,总空闲有但是无连续大块内存4.Ascend vLLM框架自身运行时常驻内存(算子库、调度器)

宿主机npu-smi proc-mem 只统计用户态业务进程,不会统计驱动内核、编译缓存、内存碎片占用,所以看起来”空卡“,但容器内真实连续大块内存远远不够34G。

之前由于反复启停vllm,虽然手动kill了VLLMEngine进程,但Ascend CANN不会自动回收全部内存块,只会释放进程专属内存,驱动缓存、编译缓存、碎片永久留存,必须重启NPU驱动/服务器才能彻底清空整块HBM(其实后面AI给出了要我去重启这个方案,包括但不限于物理重启,但是我不敢,怕把显卡、服务器搞坏了,所以后面其实是另一种方式解决的)。

14B bfloat16 模型在310P的硬性内存需求

XiYanSQL-QwenCoder-14B(这个就是我用来text2sql的模型) bfloat16 权重本身就要26GB 左右,再加上 8192 上下文、4 并发 KV 缓存、算子临时内存,总峰值接近 34G。 当前容器内仅 16.58G 连续可用,差了一倍,0.8 的利用率参数必然校验失败。

vLLM启动校验硬性要求--模型权重+KV缓存需要一整块不中断的HBM,不能碎片化拼接。Ascend CANN采用伙伴内存分配器,频繁启停vLLM会把完整HBM切成几百MB、几GB的碎片,显存总和看着很大,但不存在一整块连续空间,直接触发报错。

隐形占用,npu-smi完全不统计:1.CANN驱动内核常驻显存(宿主机命令看不到)NPU固件、AI Core调度、HBM内存管理内核模块会长期占用3~6GB固定空间,这部分不在用户进程统计里,npu-smi info -t proc-mem完全不显示,直接从连续大块里扣除。2.算子编译永久缓存(碎片元凶)第一次启动模型时,CANN会编译大模型所有算子,编译缓存会占大量离散小块;哪怕杀掉vLLM进程,编译缓存不会自动清空,永久留在HBM里制造碎片。3.容器与宿PID隔离的僵尸显存,之前docker rm -f暴力杀容器,vLLM多进程(主进程+Engine Core子进程)脱离容器PID命名空间,宿主机kill -9只能回收一部分显存,驱动侧张量缓冲区不会释放,碎块永久残留。

在经过一些列操作之后,问题没有解决,反而更加严重了,出现了NPU out of memory. Tried to allocate 136.00 MiB这种报错,核心问题:NPU显存碎片化严重,剩余可用显存只剩307MB,加载模型权重直接OOM。然后这里被提议不得不进行NPU复位,然后就提供了一些NPU信息(工作模式等)这样防止复位NPU对设备产生影响,本来是要执行npu-smi set -t reset -i 6,但是我还是有点觉得不对劲,不敢直接复位操作,就再继续查询了一些NPU的状态,就发现:总显存使用率很高,大量显存被锁定,大页内存Hugepages Usage Rate=100%,大页内存被占满无法释放,AI 算力(Aicore/Aicpu/Vectorcore)全部0%,模型没有在做推理计算 → 典型容器异常导致的 NPU 内核僵尸显存 + 大页内存卡死,容器进程看似在运行,但模型推理逻辑卡死、仅占用显存不干活。所以就只能先关停容器再删除容器,再清楚宿主机Ascend大页共享内存残留(解决 Hugepages 100%占满的问题)ipcs -m | grep ascend | awk '{print $2}' | xargs -I {} ipcrm -m {},执行上面的命令后其实就好了,我的问题解决了。还有就是提醒,反正我是被提醒了,一个NPU只部署一个vLLM推理容器,绝对不能同时跑两个模型,可以不做显存大块分割max_split_size_mb,保留按需扩容释放expandable_segments:True(避免一次性把整张卡显存直接占死),同时保留TORCH_NPU_CACHE_CLEAN_AT_EXIT=1这个变量是进程退出主动清缓存门用来防止僵尸显存残留。

Q(这是我心里的问题):什么叫大页内存(Hugepages Usage Rate),这个翻译感觉有问题哈,其次,为什么执行 ipcs -m | grep ascend | awk '{print $2}' | xargs -I {} ipcrm -m {},清理了这个大页内存就没有问题了,显存就是显存,显存碎片化了或者被占满就应该对显存操作,为什么会是对大页内存进行清理就好了。

A(这是AI的解答):

一、先纠正名词:Hugepages = 巨页内存(Linux 内核大页内存,属于主机系统物理内存 (RAM),不是 NPU 显存

翻译没问题,行业标准叫法:

  • HugePages / 巨页 / 大页内存:Linux 操作系统对主机 CPU 内存(DDR 内存)的内存页管理机制,和昇腾 NPU 卡上的 HBM 显存是两块完全独立硬件。 普通 Linux 默认内存页:4KB 小页 HugePages 常用规格:2MB / 1GB 巨页

核心原理区别

  1. NPU 显存(HBM):昇腾 Davinci6 板载显存,模型权重、KV 缓存、算子临时张量存在这里,vLLM-Ascend 直接通过昇腾驱动访问;
  2. 系统 HugePages(主机物理内存):服务器主板 DDR 内存,是CPU 侧操作系统内存

二、为什么昇腾(Ascend)驱动会大量占用 Linux HugePages?

昇腾异构计算(NPU<->CPU 数据交互、HCCL 集合通信、算子主机侧临时缓冲区、DMA 直接内存存取)强制依赖大页内存

  1. NPU 和 CPU 之间做数据 DMA 拷贝时,使用 HugePages 可以大幅减少页表项、降低 TLB 缓存缺失、减少内核态开销,提升 NPU 和 CPU 之间传输带宽;
  2. HCCL(昇腾多卡通信库)、算子前置 / 后置 CPU 缓存、Runtime 常驻缓冲区,都会直接申请系统 HugePages;
  3. vLLM-Ascend 推理框架、ATC 算子编译、CANN 运行时,都会持续向操作系统申请预留巨页内存。

关键痛点:HugePages 是内核静态预留内存,不会自动释放

普通 4KB 小内存:进程退出后,操作系统自动回收内存。 HugePages 特性:

  • 一旦被 CANN / 昇腾驱动申请占用,容器进程 docker stop、容器销毁、推理服务 kill 之后,巨页不会自动还给系统
  • 多次启停 vLLM、重启推理容器、异常崩溃后,大量 HugePages 变成僵尸共享内存段(ipcs -m 可以看到)
  • 系统可用巨页持续下降,使用率拉满。

三、为什么 HugePages 打满,会导致 NPU 显存报错、推理 OOM、启动异常?(最关键逻辑)

你疑惑:明明是显存问题,清主机大页内存就恢复,逻辑在这里:

场景 1:CANN Runtime 需要主机巨页做显存交换 / 临时缓存

当 NPU 显存紧张、出现显存碎片化时,昇腾驱动会利用主机 HugePages 作为 NPU 显存的 Swap 后备、算子 Host 侧临时缓冲。 系统 HugePages 耗尽 → CANN 无法分配主机侧辅助缓存 → NPU 显存分配逻辑直接报错、碎片化急剧恶化、算子创建失败、KV 缓存初始化失败。

场景 2:容器内 NPU 通信、DMA 通路阻塞

HugePages 占满后,CPU↔NPU 高速 DMA 通道降级为普通 4KB 小页传输:

  • 数据拷贝时延暴涨;
  • 算子调度卡顿;
  • 运行时出现隐性内存泄漏,持续蚕食 NPU 可用显存; 最终表现就是:明明 NPU 显存数值够,但是 vLLM 报显存不足、缓存创建失败,重启没用,只有删掉残留巨页共享内存才能复位。

场景 3:Ascend 驱动共享内存僵尸段冲突

命令 ipcs -m | grep ascend 筛选的是昇腾驱动创建的 System V 共享内存段,全部依托 HugePages 分配: 进程异常退出后,共享内存段在内核残留,新启动的 vLLM 容器的 CANN Runtime 在初始化时:

  1. 尝试申请同名 / 同地址共享内存;
  2. 旧僵尸段残留导致初始化冲突、Runtime 内存管理错乱;
  3. 直接导致 NPU 显存池划分异常,可用 KV 缓存显存被异常挤占。 执行ipcrm -m删除残留共享内存,CANN 运行时完全重置,显存管理恢复正常。

场景 4:shm-size 容器共享内存联动

你 docker 启动配置了--shm-size=20g,容器共享内存同样优先使用系统 HugePages。 宿主机巨页耗尽 → 容器 /dev/shm 分配失败 → vLLM 内部进程间通信缓冲区分配失败 → 引擎初始化异常,间接影响 NPU 显存调度。

四、拆解你这条清理命令作用

bash

ipcs -m | grep ascend | awk '{print $2}' | xargs -I {} ipcrm -m {}
  1. ipcs -m:列出系统所有 System V 共享内存段;
  2. grep ascend:过滤所有昇腾 CANN、驱动、推理业务创建的共享内存;
  3. 提取 shmid,ipcrm -m强制删除内核态残留共享内存,释放被长期占用的 HugePages 大页内存

五、长期规避方案(不用反复手动清巨页)

方案 1:容器停止时自动清理宿主机 Ascend 共享内存

编写 docker 容器停止钩子,容器销毁自动执行 ipcrm 清理。

方案 2:配置 HugePages 按需动态分配(CANN 推荐)

关闭静态固定巨页预留,开启透明巨页(THP),操作系统自动回收:

bash

# 开启系统透明大页
echo always > /sys/kernel/mm/transparent_hugepage/enabled
echo always > /sys/kernel/mm/transparent_hugepage/defrag

静态 HugePages 不要配置过大,避免无法回收。

方案 3:推理服务正常优雅停止,避免暴力 kill -9

暴力杀死 vLLM 进程、docker kill 会导致 CANN 无法执行析构逻辑,巨页与共享内存无法释放; 正常用docker stop优雅停止容器,驱动可以主动释放大部分 HugePages 资源。

方案 4:定时巡检清理

定时脚本检测 HugePages 使用率,高于阈值自动清理昇腾残留共享内存。

六、总结一句话

HugePages 是宿主机 CPU 大页内存,是昇腾 CANN 驱动、NPU 通信、显存辅助缓存的底层依赖资源; 推理服务异常退出会造成巨页共享内存内核残留无法释放,HugePages 耗尽会造成昇腾 Runtime 运行异常、显存管理错乱、假性显存不足; 清理ipcs -m里的 ascend 共享内存,是清理驱动遗留的巨页内存垃圾,让 CANN Runtime 初始化恢复正常,NPU 显存分配逻辑回归正常,问题直接解决。 它不是直接清理 NPU 显存,是修复NPU 驱动运行的主机侧底层环境

Logo

鲲鹏昇腾开发者社区是面向全社会开放的“联接全球计算开发者,聚合华为+生态”的社区,内容涵盖鲲鹏、昇腾资源,帮助开发者快速获取所需的知识、经验、软件、工具、算力,支撑开发者易学、好用、成功,成为核心开发者。

更多推荐