摘要

RTP-LLM 是阿里巴巴大模型预测团队自研并开源的大语言模型推理和加速引擎,广泛应用于阿里巴巴内部多个部门的大模型推理业务。面对昇腾硬件的上市,团队面临从 CUDA 到 CANN 的全栈迁移挑战,包括算子库差异、上下文管理差异、编译与调试工具链切换等。RTP-LLM 作为一个追求极致吞吐和低时延的推理引擎,其自定义多流调度与内存池、算子融合模式等特性在昇腾架构上需要重新设计。此外,大型推理框架代码量庞大,抽象层次多,调用链深,首次接触时难以快速定位硬件相关模块。为解决这些问题,团队采用了社区经验和 AI 大模型辅助的双轮驱动策略,通过借鉴 xLLM 和 vLLM 的适配经验,结合 AI 的代码理解与知识提取、适配方案生成、模式迁移、自动修复等能力,实现 Qwen3-0.6B 跑通的第一阶段目标。整个适配过程分为方案调研、计划制定、适配方案设计、编码实现、功能调测五个步骤,确保每个环节可验证、可回退,为后续大规模适配奠定了基础。


背景

RTP-LLM 是阿里巴巴大模型预测团队自研、开源的大语言模型推理和加速引擎,在阿里巴巴内部被广泛使用,支持了包括淘宝、天猫、闲鱼、菜鸟、高德、饿了么等多个部门的大模型推理业务,在众多 LLM 场景中得到实际应用与检验。

随着 A5 代际硬件的上市,昇腾与阿里建立起了沟通的桥梁,客户也提出了 RTP-LLM 适配昇腾的诉求。


技术挑战

生态差异:从 CUDA 到 CANN 的全栈迁移

RTP-LLM 主要是混合使用 C++ 和 Python 编写的(涉及 cuda 算子、runtime、torch 等不同层次的接口)。表面上是 API 的替换,实际是一整套开发范式与硬件心智模型的迁移。

  • 算子库差异:RTP-LLM 中大量高性能自定义 CUDA Kernel(融合 Attention、采样核等)无法直接运行,需改写为 AscendC 算子或使用 torch_npu 等价接口。
  • 上下文管理差异:runtime 层接口,CUDA 与 CANN 存在一定差异(CUDA Stream vs. ACL Stream、cudaMalloc vs. aclrtMalloc 等)。
  • 编译与调试工具链:切换昇腾后,精度与性能基线需要重新建立,同时也需要借助昇腾工具链进行精度、性能等问题的分析。

RTP-LLM 的特定挑战:为 CUDA 极致优化而生的框架

RTP-LLM 作为一个追求极致吞吐和低时延的推理引擎,其许多核心优化与昇腾架构并非天然适配。

  • 自定义多流调度与内存池:框架内部实现了紧耦合的 CUDA Stream 管理机制和自研显存池,直接移植到昇腾上会遇到 Stream 行为不一致、内存池对齐规则不同等问题。
  • 算子融合模式不兼容:RTP-LLM 内部可能将多个 CUDA 核融合为一个自定义大算子供 GPU 执行,这类融合算子在昇腾上可能需要拆解或重新设计 Tiling 策略。

"盲人摸象"的源码解读

大型推理框架代码量数十万行,抽象层次多,调用链深。

  • 首次接触 RTP-LLM 源码时,很难快速定位"哪些模块与硬件强相关"、“哪些是平台无关逻辑”。
  • 手工绘制调用链、类关系图耗时巨大,且容易因理解偏差引入适配错误。
  • 尤其在跨组件边界(如前端调度 -> 后端执行器 -> 自定义算子)的错误传播,问题定位犹如大海捞针。

LLM 场景的独特性加剧适配难度

如果说前面三个难题分别来自平台、框架和代码,那么第四个难题来自场景本身。为大语言模型做推理优化,并非一种技术,而是一个仍在极速膨胀的"技术武器库"。

  • 优化维度极其多元:计算层面的算子融合(Attention 融合、MLP 激活融合)、内存层面的 Paged Attention / vLLM 式的 KV Cache 分页管理、调度层面的 Continuous Batching 与抢占式调度、算法层面的投机采样(Speculative Decoding)与 KV Cache 压缩……任何一个维度的技术选型,都会直接影响昇腾适配的切入点和难度。
  • 优化手段组合爆炸:真实场景中,这些技术不是孤立使用的。比如"Continuous Batching + Paged Attention + 量化 + Flash Attention"的组合,在 CUDA 上已经过千锤百炼,但迁移到昇腾时,不是逐项平移就行——组合后产生的显存访问模式、计算-通信交织、动态 Shape 边界条件,可能完全不在单一技术的预期之内。
  • 每个新优化都是适配的新变量:RTP-LLM 正是这类优化技术的集成者。当框架版本迭代引入新的优化算法(比如从标准 Attention 升级到 MLA,或引入新的 KV Cache 压缩策略),对应的昇腾适配也需要重新审视。
  • 行业仍在快速演进:推理框架的军备竞赛远未结束。新模型结构(MoE、Mamba)、新压缩方法、新采样策略层出不穷。这意味着昇腾适配没有"完成时",只能保持一种持续跟进的能力。

正是因为这四重困境——生态差异、框架定制、源码迷雾、以及大模型推理优化技术本身的多样性——传统的人工适配模式已经捉襟见肘。我们需要两样东西:一是来自社区的经验地图(xLLM / vLLM),二是一个能快速消化海量信息、加速试错的 AI 副驾驶。


破局思路:两大杠杆——社区经验 + AI 大模型

在深入具体的适配动作之前,有必要先审视我们所处的时间点。2026 年,两个重要变化让异构推理框架的适配工作不再是一场"一个人的战斗"。

时代背景

昇腾从封闭走向开放

过去一到两年(2024–2026),昇腾生态变化很大。CANN、torch_npu、推理框架等组件不断开源并开始社区化运作,社区的 Issue 和 PR 翻了近十倍。围绕昇腾的推理框架(vLLM-Ascend、xLLM 等)形成了适配社区,适配好的典型模型、踩坑记录和调优经验以公开文档和代码的形式沉淀了下来。我们不再是面对一堵高墙,社区里已经有不少路标。

2026 年 AI 编程的变化:从 Copilot 到 Agentic

AI 辅助编程不再是代码补全工具,变成了能理解仓库、制定计划、调用工具链、自主走通编译-报错-修复闭环的工程伙伴。我们可以像指导一位新同事那样,给 AI 布置这样一个任务:"分析 xLLM 的昇腾适配层,提炼出通用模式,再为 RTP-LLM 的调度模块生成适配方案。"AI 自己查仓库、比接口、写代码草案、附上风险注释。原本需要数月的人工适配,几周就能贯通核心路径。

这两个东西叠加,就是我们说的"社区经验 + AI 大模型"双轮驱动。

杠杆一:社区经验——站在 xLLM 与 vLLM 的肩膀上

xLLM 和 vLLM 是两个在昇腾上已经成功运行的推理框架。从算子映射、并行方案适配、图模式支持等方面踩过的坑,他们都沉淀了下来。我们要做的不是从零开始,而是提炼一套可迁移的"通用适配模式"。

从 xLLM 学到的"算子层"直接经验

xLLM 是对昇腾支持较为成熟的 C++ 推理框架,其代码中直接展示了如何将 CUDA 生态的 C++ 接口映射到 Ascend 平台。在适配实践中,我们重点借鉴了以下 C++ 层面的实现模式:

  • Torch C++ 接口调用:xLLM 不使用 Python 层的 torch,所有 torch 调用都在 C++ 层通过 libtorch C++ API 完成;如 torch::kPrivateUse1c10_npu::NPUStream 等。
  • C++ 上下文管理:借鉴其设备上下文(Device Context)的 RAII 封装方式,将资源获取与释放绑定到对象生命周期,确保异常安全。
  • 图模式:使用 c10_npu::NPUGraph 替代 torch.cuda.CUDAGraph
从 vLLM 学到的"架构层"设计思路

vLLM 社区生态活跃,其 Ascend 适配项目(vLLM-Ascend)在 PR 和社区讨论中展现了细致且演进清晰的适配路径。与 xLLM 侧重于 C++ 实现细节不同,vLLM 在架构设计层面为我们提供了更宏观的借鉴价值:

  • KV Cache 管理策略及优化路径:vLLM 的核心竞争力之一在于其高效的 KV Cache 管理机制。通过分析 vLLM-Ascend 的演进过程,我们梳理出可借鉴的 KV 管理策略。
  • Python 层 API 调用逻辑与组图模式参考:RTP-LLM 采用 Python 组图的方式构建计算图,因此高层 API 的调用模式与 PyTorch 生态的兼容性直接决定了适配效率。vLLM-Ascend 已适配的模型中沉淀了完整的 torch_npu 调用链路,通过梳理其调用逻辑,可快速了解 NPU 特有 API(如 Attention 等)的使用范式。
  • 典型模型优化特性路标及组合:vLLM 社区对主流大模型(LLaMA、Qwen、DeepSeek 等)的优化特性有着清晰的路标规划,其适配顺序和优先级划分对 RTP-LLM 极具参考意义。
跨框架通用模式提炼

最终,我们将两大社区框架的经验融合为一张"异构适配模式地图",涵盖:

  • 算子替换模式
  • 设备上下文管理模式
  • 内存/显存池适配模式
  • 通信后端切换模式
  • 图模式

这张地图成为后续 AI 大模型进行自动化分析的"参考知识库",让 AI 生成的适配方案有据可依,而不是凭空猜测。

杠杆二:AI 大模型——从辅助编码到 Agentic 协同

2026 年的 AI 大模型不仅仅能生成代码片段,更具备了项目级的理解和规划能力。我们将 AI 定位为适配过程中的"副驾驶 + 导航员",在四个层面发挥了关键作用。

代码理解与知识提取:5 分钟厘清十万行代码

面对 RTP-LLM 数十万行源码,传统方法需数周阅读。我们将核心入口文件直接提供给 AI,并给出如下指令:

“分析这个推理引擎的模型加载、调度和推理主循环,画出调用序列图,并标注每个环节可能涉及 NPU 适配的硬件相关代码位置。”

AI 在几分钟内返回了清晰的调用链、风险点清单,甚至给出了"适配难度评级"。这使我们得以快速形成"适配作战地图",将精力精准投放在最关键的 20% 代码上。

适配方案生成与模式迁移:跨框架知识复用

我们要求 AI 完成一项更高级的任务:

“你已学习 xLLM 和 vLLM 的昇腾适配代码,现在请分析 RTP-LLM 的 Attention 模块,生成将其迁移至昇腾的详细方案,包括算子替换列表、修改点注释和潜在风险。”

AI 自主对比了三个框架的同名模块,识别出 RTP-LLM 中自定义的 Flash Attention 融合核,建议用 torch_npu.npu_fusion_attention 重构。它甚至生成了完整的 AscendC 算子草图作为备选方案,并标注了"性能可能不达优,需后续调优"的提示。这种"模式迁移"能力,让社区经验得以自动化地应用到新框架上,避免了人工逐行搬运。

Agentic 调试与自动修复:将报错闭环

在编译和运行阶段,我们将 CANN 的复杂错误日志直接喂给 AI,配合如下指令:

“以下错误发生在昇腾设备上执行自定义算子时,请分析错误原因,给出修改建议,并直接生成修复后的代码。”

AI 通过分析代码、日志,快速进行问题的综合诊断。更进一步的 Agentic 模式中,AI 会主动调用编译命令,获取新错误,再次修正,直到编译通过——形成一个自动化的"啄木鸟"循环。虽然仍需人工在关键节点确认,但这已经将调试效率提升了数倍。

文档与报告自动生成:让沉淀即时发生

我们让 AI 在适配全程中跟踪每次改动,自动生成适配日志、接口变更清单和性能回归测试报告。这确保了技术文档与代码同步演进,不仅为系列文章提供了丰富素材,也为后续团队接手或复现奠定了基础。

双轮驱动的协同效应

社区经验为 AI 提供了高质量的训练样本和约束域,让 AI 的生成不至过于天马行空;AI 则将静态的社区知识激活,转化为针对 RTP-LLM 的定制化、可执行方案。两者叠加,让我们在短短几周内完成了从"代码迷雾"到"Qwen3-0.6B 跑通"的第一阶段目标。这种"人机协作"范式,它不仅仅是一种效率工具,更是一种知识传递和复用的新模式——这也正是我们撰写本系列文章的初衷:将这套方法分享给更多昇腾适配迁移的开发者。

具体的,在当前 RTP-LLM 昇腾适配的实践中,遵循的与 AI 协作的流程,大致分五步:方案调研、计划制定、适配方案设计、编码实现、功能调测。这五步不是一条直线走到底,review 和测试穿插在每一步里,发现问题就回头改。

方案调研阶段,我们一边读 RTP-LLM 源码,一边对照 xLLM/vLLM 的昇腾适配文档,把大段原文丢给 AI 去梳理调用链、标出架构差异,再让它照着社区里的经验生成一份《适配风险清单》。清单先在团队里过一遍 review,确认哪些是要啃的硬骨头,然后才往下推进。AI 拿这份风险清单拆任务、估依赖和时间,排出适配计划草案,我们再根据人力和机器资源调一调,定下里程碑。

到了适配方案设计,做法是人定约束,AI 出初稿,再一起改。工程师把性能要求、精度指标、架构原则讲清楚,AI 按它从社区里学到的模式给每个模块(比如 KV Cache、Attention 算子)出一版方案。团队一起 review 技术上靠不靠谱、风险大不大,有问题就让 AI 吸收反馈重新出一版,直到定稿。

编码这一步最能看出 Agentic 协作的样子。AI 直接照着最终方案生成符合 CANN 规范的代码,算子替换、设备上下文切换、通信后端改写这些它都能写,工程师主要盯着架构有没有跑偏、关键逻辑对不对。编译或运行报错了,日志直接贴回给 AI,它自己迭代修,我们再局部 review 一下。这么来回几轮,编码和调试的周期明显短了。

功能调测也不是最后才做,而是和编码搅在一起。AI 帮忙生成单测用例、精度对比脚本和性能基准代码,代码一提交就跑测试,出回归报告。一看精度掉了或者性能退了,马上退回到方案或编码环节,让 AI 帮着定位、给修复建议。

从头到尾,review 和测试是两根线,把五步串起来。review 不是工程师单方面审 AI 的活儿,而是双向的:我们的工程经验、对架构的整体把握,还有昇腾硬件那些文档没写清楚的门道,都在 review 里喂回给 AI;反过来 AI 翻得快、记得住、擅长找模式,也帮我们看得更全。测试通过构造一些测试用例保证基础质量。这套流程跑下来,人虽然不多,基础的异构适配工作也能撑住。


一阶段适配要点

在 RTP-LLM 一阶段适配的各个环节中,我们都遵循"方案调研、计划制定、适配方案设计、编码实现、功能调测"的步骤进行。

整体计划制定

一阶段以 Qwen3-0.6B 为切入点,优先打通昇腾 + RTP-LLM 端到端推理流程,在推进过程中逐步熟悉框架设计与实现细节。本阶段严格控制复杂度,确保每个环节可验证、可回退,为后续大规模适配奠定坚实基础。

前期调研阶段,我们主要对 xLLM、vLLM-Ascend 进行了探索,通过代码走读、关键特性的针对性调研,按依赖关系拆分为四个核心模块,并行预研、串行落地,逐项验证:

  1. 编译构建:适配 NPU 依赖及编译方法,确保基础工程可构建、可运行。
  2. 设备抽象/内存管理:封装 NPU 设备管理、上下文初始化及运行时接口,隔离硬件差异;实现 NPU 侧显存分配/释放、Host-Device 数据传输,保障内存效率与安全性。
  3. 算子接入:逐个接入所需 NPU 算子,完成功能对齐与基础精度校验。
  4. KV 管理 & Attention 接入:实现 KV Cache 的 NPU 存储管理,并接入 Attention 计算核心,完成推理链路闭环。

每个模块完成后均进行独立功能测试,最后联合调试,确保 Qwen3-0.6B 推理流程完整跑通。

参考文档:

  1. xLLM 昇腾适配参考点:https://github.com/ningweikang/rtp-llm-ascend-adaption-plan/blob/main/xllm_ref/xllm_ascend_ref.md
  2. xLLM 在 Ascend NPU 上使用 Torch 的方式分析:https://github.com/ningweikang/rtp-llm-ascend-adaption-plan/blob/main/xllm_ref/xllm_torch_torch_npu.md
  3. RTP-LLM 华为 Ascend NPU 全量适配计划:https://github.com/ningweikang/rtp-llm-ascend-adaption-plan/blob/main/ascend_npu_adaptation_plan_v0.1.md

编译构建

编译构建层是整个适配工作的工程基础,决定了后续所有模块能否顺利编译、链接并运行。RTP-LLM 原有构建体系深度依赖 CUDA 工具链,采用 Bazel 作为构建系统,通过 .bazelrcWORKSPACE 和根目录 BUILD 文件管理编译配置与依赖。引入 Ascend NPU 支持后,构建层面临的主要挑战包括:

  • 工具链差异:Ascend CANN 工具链的头文件路径、库名称(libascendcl.solibaclblas.so 等)与 CUDA 完全不同,需新增专门的配置仓库。
  • 条件编译需求:需在统一代码库中区分 CUDA、ROCm、CPU、Ascend 四种后端,通过宏定义(如 USING_ASCEND)控制编译分支,避免代码膨胀。
  • 依赖隔离:Bazel 的 select() 机制需要扩展,以支持按后端选择不同的源文件、编译选项和链接库,同时确保不破坏现有 CUDA/ROCm 构建流程。

宏观目标:搭建完整的 Ascend NPU 编译基础设施,使开发者可通过 bazel build --config=ascend //... 一键构建支持 Ascend 的 RTP-LLM 可执行文件,并保证与原有后端(CUDA/ROCm/CPU)共存且互不干扰。

整体策略:

  • 参考已有模式:完全沿用 ROCm 后端的 rocm_configure.bzl 实现思路,新增 ascend_configure.bzl,最大限度地复用 Bazel 规则。
  • 最小改动原则:不修改 Bazel 核心规则,仅通过扩展 .bazelrc 和条件依赖来实现,确保对现有构建流程零影响。
  • 可验证性:每个构建配置变更后,均能在 CI 环境中验证编译通过性,避免累积集成风险。

具体实施内容:

RTP-LLM 采用 Bazel 作为构建系统,通过 .bazelrcWORKSPACE 及根目录 BUILD 文件统一管理编译配置与依赖。其中,WORKSPACE 负责初始化构建环境,依次加载 CUDA、ROCm、Python 等配置及外部依赖;.bazelrc 则定义了基础编译选项(C++17 标准、优化等级、调试信息等)及各平台(--config=cuda--config=rocm--config=cpu)的差异化编译参数。

Ascend 编译适配的核心工作包括:

  1. 新增 Ascend 自动配置仓库:参照 rocm_configure.bzl 实现 ascend_configure.bzl,支持通过环境变量 TF_NEED_ASCENDASCEND_TOOLKIT_PATH 自动探测 CANN 工具链(头文件 acl/acl.h、库文件 libascendcl.so 等),并生成 @local_config_ascend 仓库。
  2. 修改 WORKSPACE 与根 BUILD:在 WORKSPACE 中加载并调用 ascend_configure;在根目录 BUILD 中新增 config_setting(name = "using_ascend"),供后续条件编译使用。
  3. 扩展 def.bzl 构建规则:新增 if_ascend 条件判断与 ascend_copts() 编译选项函数,在 copts() 中为 Ascend 模式添加 -DUSING_ASCEND=1 等宏定义。
  4. 新增 .bazelrc 中的 build:ascend 配置段:设置 --define=using_ascend=true--action_env TF_NEED_ASCEND=1,指定 CANN 头文件与库路径,链接 -lascendcl-laclblas-lhccl 等运行时库,并通过 -DUSING_CUDA=0 禁用 CUDA 代码路径。

参考文档:

  1. RTP-LLM 编译配置过程可参考:https://github.com/ningweikang/rtp-llm-ascend-adaption-plan/blob/main/0-build/RTP-LLM%20%E7%BC%96%E8%AF%91%E9%85%8D%E7%BD%AE%E8%BF%87%E7%A8%8B.md
  2. 详细实施方案:Ascend NPU 编译基础设施搭建:https://github.com/ningweikang/rtp-llm-ascend-adaption-plan/blob/main/0-build/0-ascend_build-detailed_plan_v0.1.md

设备抽象/内存管理

设备抽象层与内存管理是 RTP-LLM 适配 Ascend NPU 的基础支撑模块。RTP-LLM 原有实现深度依赖 CUDA 生态,设备管理、流同步、显存分配等接口均围绕 torch::kCUDA 设计。为支持 Ascend NPU,本阶段需在不破坏原有架构的前提下,将关键路径上的设备操作抽象为可切换的接口,并实现 NPU 侧的内存管理能力。

宏观目标:

  • 设备抽象层:封装 NPU 设备初始化、上下文管理、流(Stream)创建与同步、设备查询等运行时能力,向上层提供统一的设备操作入口,隔离硬件差异。
  • 内存管理:实现 NPU 显存的分配与释放、Host–Device 间的数据拷贝(runtimeCopy),以及为 KV Cache 等高频场景提供高效的内存池管理机制。

整体策略:

  • 最小侵入:尽量复用现有 CUDA/ROCm 接口设计,不新增独立的设备类型枚举,通过编译宏 USING_ASCEND 和条件编译在关键分支中插入 Ascend 逻辑。
  • 语义复用:沿用 is_cuda() 表达"在任意加速器设备上"的概念,避免散落 is_npu 判断,降低改动面。
  • 渐进落地:先保证 Tensor 可正确分配到 NPU、基础拷贝和内存查询可用,再逐步扩展至复杂内存管理场景(如 Allocator 定制)。

基于上述策略,我们经过多轮评审与修正,已形成 Ascend NPU 设备抽象与内存管理的详细实施方案(v0.3)。

参考文档:

  1. 详细实施方案(v0.3):https://github.com/ningweikang/rtp-llm-ascend-adaption-plan/blob/main/1-memory_runtime/1-ascend_memory_management_device_runtime_v0.3.md

算子接入

RTP-LLM 框架在 Ascend NPU 适配方面已完成了编译基础设施的初步搭建——.bazelrc 中配置了 build:ascend 编译段,定义了 USING_ASCEND=1 宏、链接了 libascendcl.solibhccl.soWORKSPACE 中引入了 ascend_configure 仓库。在 rtp_llm/models_py/bindings/ascend/ 目录下也已有 ascend_host_utils(设备属性查询)和 ascend_types_hdr(ACL 类型定义)等基础库。然而这些工作仅覆盖了"让代码能在 Ascend 环境下编译通过"这一最基本层面,框架完全不具备接入 AscendC 自定义算子的能力,具体表现在以下几个方面:

  • 无算子工程规范:项目中不存在任何 AscendC 算子源码。CANN 自定义算子需要遵循严格的工程结构(op_host 侧的算子注册、Tiling 策略计算 + op_kernel 侧的 AI Core 核函数),需要 bisheng 专用编译器生成 kernel 二进制。框架内没有此类工程的目录模板、没有编译脚本、没有构建配置,使得开发者无从下手。
  • 无 Bazel 到 CANN 编译链的桥接:CANN 算子编译基于 CMake + bisheng 工具链,与 RTP-LLM 使用的 Bazel 构建系统是两套完全独立的体系。原代码中没有任何 genrule 或外部构建规则来驱动 CANN 算子编译,也没有将编译产物(libcust_opapi.so、OPP kernel 包等)收集到 Bazel 输出目录的机制。
  • 无运行时算子加载路径:CANN 运行时通过 ASCEND_CUSTOM_OPP_PATH 环境变量定位自定义算子包,通过 dlopen 加载 libcust_opapi.so 获取算子接口。原 .bazelrctest:ascend 配置中缺少这些路径设置,意味着即使算子被编译出来,运行时也无法找到和加载。
  • 无 ACLNN 调用封装基础设施:CANN 自定义算子的调用模式是"GetWorkspaceSize → 分配 workspace → Execute 提交到 stream"的两阶段异步调用,需要通过 EXEC_NPU_CMD 宏或等价封装来完成。框架内没有此类通用调用宏、没有 op_api_common.h 头文件、也没有 NPU Tensor 存储格式(aclFormat、NPUStorageDesc)到 PyTorch Tensor 的桥接组件(NPUBridge/NPUStorageImpl)。
  • Ascend 侧算子注册为空壳AscendRegister.cc 中的 registerPyModuleOps() 函数体为空,不注册任何算子。CudaSampleOp.cc#elif USING_ASCEND 分支虽有一个 sampleGreedy 实现,但它将全部张量搬回 CPU 执行纯 PyTorch 运算后再传回 NPU——这是一个临时 fallback 方案,完全无法发挥 NPU 算力,且在大 vocab 场景下存在严重的 Host-Device 数据搬运瓶颈。

综上,框架在 Ascend 自定义算子维度上是"编译能过、算子不通"的状态——能链接 CANN 基础库,但从算子开发、编译、部署到运行的完整能力链条全部缺失。

宏观目标

本次工作的核心目标是为 RTP-LLM 框架建立 AscendC 自定义算子的完整接入能力,使框架从"仅能编译通过 Ascend 平台"提升到"可以开发、编译、集成和运行 AscendC 自定义算子"的水平。具体而言:(1)建立标准化的 AscendC 算子工程模板,使后续任何新算子都可按固定结构快速开发;(2)打通 Bazel 构建体系与 CANN CMake/bisheng 编译链的自动化桥接,实现 bazel build --config=ascend 一键触发全部自定义算子编译;(3)建立运行时自定义算子加载机制和 ACLNN 调用封装基础设施,使上层 C++ 代码可像调用普通函数一样调用自定义算子;(4)以此为可复用模板,支撑后续约 15 个自定义 CUDA 算子向 Ascend 的迁移工作。

整体策略

采取"自底向上、三层解耦"的策略:

  • 第一层——算子 Kernel 层:在 3rdparty/aclnn_custom_ops/ 下创建独立的算子工程,按照 CANN 标准的 op_host(算子注册 + Tiling 分片计算)和 op_kernel(AI Core 核函数)目录结构组织。算子编译产物为三个动态库(libcust_opapi.solibcust_opsproto_rt2.0.solibcust_opmaster_rt2.0.so)加上 kernel 二进制的 OPP 包。工程模板设计为可扩展结构——后续新增算子只需在该目录下创建子文件夹并在 build_for_bazel.sh 参数中以分号分隔追加算子名。
  • 第二层——构建集成层:通过 Bazel genrule 调用 build_for_bazel.sh 脚本,该脚本自动探测 CANN 安装路径(支持 ASCEND_HOME_PATH / ASCEND_OPP_PATH / 默认路径三级 fallback)、加载 bisheng 编译环境、驱动 CMake 编译、收集产物到 Bazel 输出目录。同时修改 .bazelrctest:ascend 配置,添加 ASCEND_CUSTOM_OPP_PATH 环境变量指向编译产物路径,使运行时 CANN 可正确加载自定义算子。target_compatible_with 条件确保非 Ascend 平台编译时自动跳过。
  • 第三层——框架调用层:在 rtp_llm/models_py/bindings/ascend/ops/ 下创建通用调用基础设施,包括 op_api_common.hEXEC_NPU_CMD 宏及 ACLNN 通用调用封装)、NPUBridge/NPUStorageImpl(PyTorch Tensor 与 NPU 存储格式的桥接),以及按算子功能命名的封装头文件。上层业务代码只需 #include 对应头文件并调用一个内联函数,即可透明地完成 workspace 分配、stream 提交等全部流程。

具体实施内容

  • 算子工程模板建设:在 3rdparty/aclnn_custom_ops/ 下搭建完整的 CANN 自定义算子工程。op_host 侧包含算子形状推导(apply_top_k_top_p_custom_def.cpp)、Tiling 策略计算(根据硬件 UB 容量和 vocabSize 动态计算 AI Core 间并行分片和迭代次数)、ACLNN 接口封装(GetWorkspaceSize + Execute)。op_kernel 侧基于 AscendC 编程模型实现核函数,通过 TILING_KEY 实现多分支调度,精细管理 MTE2/MTE3/Vector 流水线间的同步事件,确保数据搬运与计算的正确流水化。配套 utils/ 工具库提供 tiling 基础设施、fallback 机制、日志系统等公共能力。
  • 混合构建体系实现:编写 CMakeLists.txt 配套完整的 cmake module 体系;build_for_bazel.sh 实现 CANN 环境自动检测、bisheng 编译器验证、CMake 配置编译、CPack 产物解包收集的全自动化流程。Bazel BUILD 文件中通过 genrule → cc_library 链式依赖,使上层 target 只需 deps = ["//3rdparty/aclnn_custom_ops:aclnn_custom_ops"] 即可触发编译并获得链接能力。
  • ACLNN 调用基础设施op_api_common.h 实现了 EXEC_NPU_CMD 宏体系,内部封装 dlopen 动态加载、函数地址查找缓存、workspace 自动分配、stream 自动获取与提交。NPUStorageImpl 继承 c10::StorageImpl,扩展了 NPUStorageDesc(含 aclFormat、base sizes/strides 等元信息);NPUBridge 提供静态方法从 at::Tensor / c10::Storage 直接获取 NPU 存储描述。每个算子的封装头文件(如 ascend_apply_top_k_top_p_custom.h)仅暴露一个语义化内联函数,内部通过 EXEC_NPU_CMD 一行代码完成调用。
  • 环境与测试配置.bazelrc 中扩展 test:ascend 配置,新增 ASCEND_CUSTOM_OPP_PATH(指向 genrule 输出的 opp 目录)、torch/torch_npu 库路径、TORCH_DEVICE_BACKEND_AUTOLOAD=0(防止自动加载冲突)。测试层面新增 GTest C++ 单元测试和 Python PyBind 测试,通过 torch::kPrivateUse1 设备类型适配 torch_npu 的注册机制,全面验证算子在真实 NPU 设备上的正确性。

参考文档:

  1. AscendC 自定义算子适配文档:https://github.com/ningweikang/rtp-llm-ascend-adaption-plan/blob/main/2-op/ascend_custom_op_integration_guide.md

KV cache适配&Attention算子接入

KV Cache 适配与 Attention 算子接入是 NPU 推理链路中改动量最大、问题最密集的模块,直接决定模型能否在 Ascend NPU 上完成完整的 prefill → decode → 多轮对话推理。rtp-llm 原有的 KV Cache 管理(C++ BlockPool / MemoryLayoutStrategy / KVCacheManager)和 Attention 调度(Python ascend_prefill.py / ascend_decode.py)围绕 GPU FlashInfer 的合并 KV 布局和 paged attention 接口设计,迁移到 Ascend NPU 后面临以下主要挑战:

  • K/V Cache 拆分要求:rtp-llm 原生将 K/V 合并在一个 Tensor 中(dim=1 区分:[blocks, 2, kv_heads, seq, head_dim]),而昇腾 torch_npu 的算子(_npu_reshape_and_cachenpu_fused_infer_attention_score)要求 key_cachevalue_cache 作为两个独立的 Tensor 传入。早期 FIA 算子只接受连续内存,从合并 buffer slice 出来的非连续 view 无法处理,迫使适配初期在 C++ 层物理拆分出 k_cache_buffer_ + v_cache_buffer_ 两块独立内存(separate_kv_cache 机制)。这条第二通路横跨 BlockPoolMemoryLayoutStrategyCacheConfigOpDefsConfigInit 等全部 cache 核心模块,改动面大且与 GPU/AMD 路径分叉。
  • 存储布局读写不一致:写入算子(_npu_reshape_and_cache)期望 BNDH [blocks, seq, kv_heads, dim],读取算子(npu_fused_infer_attention_score)期望 BNSD [blocks, kv_heads, seq, dim]。GPU 路径上 FlashInfer 读写统一使用 HND 布局,不存在此问题。
  • 算子 format 约束:PyTorch 标准 torch::from_blob 在 NPU 上创建的张量 format 字段未初始化(垃圾值),下游算子检查 ACL format 时崩溃或报 unknown format type
  • FIA 参数语义差异actual_seq_lengths 以元素个数作为 batch 值、Q 使用 cumsum 而 KV 使用原始长度、context_lens 是长度还是位置索引、sparse_mode 对 atten_mask 的要求——每个参数都与 GPU FlashInfer 不同,需逐个验证。
  • paged attention 算子缺陷_npu_paged_attention 在特定 block_table 配置下触发 aicore 异常(CANN 已知问题,vllm-ascend issue #3263),且 _npu_paged_attention_v2 在当前 CANN 版本不存在,需 fallback 到 FIA 路径。
  • 采样后处理缺失:NPU 上无专用采样 kernel,sampleGreedy 需从 CPU fallback 逐步迁移到 NPU 原生实现。

宏观目标:

打通 Ascend NPU 上 KV Cache 的完整生命周期管理——从 buffer 分配、RoPE 变换、cache 写入、attention 读取到采样后处理——使 Qwen3 等标准 MHA 模型能在 NPU 上完成端到端推理,且推理精度与 GPU 对齐。

整体策略:

  • C++ 层物理拆分 K/V buffer:将合并 buffer 拆成 k_cache_buffer_ + v_cache_buffer_ 两块独立连续内存(separate_kv_cache 机制),满足 torch_npu 算子对独立连续 tensor 的要求。
  • Prefill / Decode 统一走 FIA:放弃存在 aicore 缺陷的 _npu_paged_attention,prefill 和 decode 统一使用 npu_fused_infer_attention_score(FIA)+ TND layout + block_table,与 vllm-ascend 默认路径对齐。
  • 采样后处理 NPU 化:引入 aclnnApplyTopKTopPCustom 自定义 AscendC 算子做 top-k/top-p 过滤,将采样主路径从 CPU 搬到 NPU。
  • NPU 专属逻辑收敛:NPU 分支放在 ascend_impl/ 下少数 Python 文件和 C++ bindings 的 #elif USING_ASCEND 中,尽量不侵入 GPU/ROCm 路径。

具体实施内容:

1. C++ 基础通路打通

在 KV Cache 适配之前,需先解决三个基础阻塞问题,否则模型无法启动:

  • fusedCopy SIGSEGVFusedCopyOp.cc 缺少 USING_ASCEND 分支,Ascend 编译时走 #elsememcpy 访问 NPU device 地址,直接段错误。修复方式为新增 #elif USING_ASCEND 分支,使用 aclrtMemcpyAsync(ACL_MEMCPY_DEVICE_TO_DEVICE)
  • AttentionConfigs 缺 scale 属性AttentionConfigs C++ 绑定无 scale 字段(仅有 softmax_extra_scaleq_scaling),导致所有 FMHA 实现实例化失败。修复方式为 getattr(attn_configs, "scale", None) or attn_configs.q_scaling * head_dim ** -0.5
  • PyAttentionInputs 缺 kv_cache 字段AscendPrefillImpl.support() 需访问 attn_inputs.kv_cache.separate_kv_cache,但 C++ 结构体无此成员。修复方式为 OpDefs.h 新增 std::optional<KVCache> kv_cache 字段并暴露 pybind 绑定。

2. KV Cache 存储布局适配(separate_kv_cache 物理拆分)

rtp-llm 原生用合并 KV 存储(一整块 buffer,每层 reshape 成 [blocks, 2, kv_heads, seq, head_dim],dim=1 区分 K/V),FlashInfer 和 AITer 直接吃这个布局。NPU 的 torch_npu 算子要求 K/V 以独立 tensor 传入,早期 FIA 只接受连续内存。

适配方案是在 C++ 层将合并 buffer 拆成 k_cache_buffer_ + v_cache_buffer_ 两块独立内存(40 文件,+455/-87 行),涉及 BlockPool(分离 K/V buffer 分配、at_npu::native::from_blob 替代 torch::from_blob)、MemoryLayoutStrategy(新增 separate_kv_cache_ 分支,按层逐层创建视图)、CacheConfig(新增 separate_kv_cachek_block_stride_bytesv_block_stride_bytes 字段)、OpDefsgetLayerCache() 新增 if (separate_kv_cache) BNDH reshape 路径)、ConfigInit(Pickle 序列化新增 separate_kv_cache 项)等全部 cache 核心模块。写入走 _npu_reshape_and_cache(BNDH 布局),读取走 FIA + permute(0,2,1,3).contiguous() 转 BNSD。

3. Attention 算子接入

Prefill 路径(AscendPrefillImpl)组合 RoPE → KVCacheWrite → FIA。QKV 经融合线性层产出后 split,key/value 做 .contiguous()torch.split 返回 strided view,非连续),写入 KV cache,再用 FIA(TND + block_table)做 attention 计算。Decode 路径(AscendDecodeImpl)同样走 FIA,放弃 _npu_paged_attentioncontext_lens = sequence_lengths + 1sequence_lengths 是 0-indexed 位置索引,不 +1 会漏读当前 decode 步写入的 token)。

关键参数适配(每个均踩过坑):

参数 正确值 常见错误
actual_seq_lengths(Q) cumsum 前缀和 前导 0 导致 batch 翻倍
actual_seq_lengths_kv(KV) 原始长度,不 cumsum prefill/decode 均误用过 cumsum,多并发时 KV 长度超范围
context_lens(decode) sequence_lengths + 1 不 +1 导致 off-by-one,输出乱码但不崩溃
sparse_mode=3 必须传 atten_mask CANN 在 sparse_mode≠0 时要求 atten_mask 不为 null
block_table 2D、NPU device 上、kernel 块粒度 C++ 传来 3D / 在 CPU 上 / 用了物理块粒度

4. C++ 侧 format 修复

MemoryLayoutStrategy::processKVTensor() 使用 torch::from_blob 创建每层 tensor,在 NPU 上 format 字段为垃圾值(如 1752432660),导致下游算子报 unknown format type 或 SIGSEGV。修复方式为用 narrow().view(dtype).reshape() 替代 from_blob,继承 torch::empty 分配时正确的 ACL_FORMAT_ND;V cache 使用 at_npu::native::from_blob(torch_npu 实现,正确设置 format)替代。此问题为 NPU 适配 torch 时的通用注意点。

5. 采样后处理 NPU 化

sampleGreedy 从 CPU 逐 batch 循环改为 NPU 原生实现:temperature 走 logits.div_(),top-k/top-p 走 aclnnApplyTopKTopPCustom 自定义 AscendC 算子,multinomial 走 PyTorch(torch_npu 映射到 NPU)。bindings 层新增 ascend/ops/(NPUBridge、op_api_common.hascend_apply_top_k_top_p_custom.h),通过 EXEC_NPU_CMD 宏动态加载算子 .so。具体介入细节见算子接入章节。

参考文档:

  1. 40 文件适配改动总结:rtp-llm-ascend-adaptation-summary.md
  2. 逐文件详细代码修改点:rtp-llm-ascend-adaptation-detail.md
  3. KV Cache 格式问题(from_blob format 垃圾值根因分析):0525-from_blob-npu-format-garbage-value-fix.md
  4. KV Cache 适配方案设计 v0.3:ascend_kv-cache-adaptation_v0.3.md
  5. Attention 详细设计方案 v0.2:ascend_attention_detailed_plan_v0.2.md
  6. Attention bugfix 汇总:ascend_attention_bugfix_summary.md
  7. Decode context_lens off-by-one 精度问题:ascend_decode_bug_report.md
  8. vLLM-Ascend KV Cache 管理机制:kv cache manage-vLLM-Ascend.md
  9. upstream PR(applyTopKTopP + NPU sampleGreedy):https://github.com/alibaba/rtp-llm/commit/1e93ecc130bda678d627493c6b51f226aa7bfaff

反思与后续计划

反思

在我们用 AI 大模型加速昇腾适配的过程中,一个清晰的层次结构逐渐浮现——不同工作对 AI 的友好程度天差地别,而这背后折射的正是昇腾平台易用性的梯度挑战。

  • 第一层是语法翻译,规则明确、语义封闭的场景,AI 几乎可以胜任 90% 以上的初稿生成。
  • 第二层是 API 替换,例如用 torch_npu 对标 CUDA 接口,AI 在持有社区参考代码时表现尚可,但一旦遇上 CANN 版本间的隐性行为变化,仍需人介入校验。
  • 第三层是精度对齐,AI 能快速生成对比脚本、统计误差分布,但数值差异的根因分析(例如一次求和顺序导致的累积误差)常常超出其推理深度,必须依赖工程师对硬件浮点特性的理解。
  • 第四层是性能调优,这是当前 AI 能力的软肋。基于达芬奇架构的 Cube/Vector 流水排布、L2 Cache 策略、Tiling 切分规则等"暗知识"归纳、汇总较少,AI 几乎无法自主给出最优实现,只能充当人类专家的数据整理员。
  • 最高层是生态理解,框架的设计意图、闭源组件的隐含假设、业务场景的折中取舍——这些全局上下文完全不在 AI 的感知范围内,每一点都需要工程师直接注入。

这五层困境既是人机分工的边界地图,更是对昇腾易用性提出的清晰诉求:只有当硬件文档结构化、昇腾术语/错误信息可解释、暗知识逐步外化,AI 辅助的天花板才能被不断抬高,异构适配才能真正进入智能协作的新阶段。

AI 辅助对昇腾易用性提出的新要求

用 AI 辅助适配,本质上是在把"人理解生态"转变成"AI 理解生态"。所以昇腾工具链、文档、社区需要变得更加机器可读、可推理、可交互。

1. 文档与 API 的"结构化 & 可检索"

  • 现状痛点:大量昇腾 CANN 文档是 PDF 或非结构化网页,算子参数说明散落在长篇描述中。AI 难以高效提取"函数签名-约束-示例"三元组。
  • AI 时代要求:提供 JSON/YAML 格式的 API Schema,包含昇腾算子(如 AscendC)的参数类型、硬件限制、性能建议等机器可读信息。最好有官方维护的 CANN 知识库向量化接口,让 AI 能直接 RAG(检索增强生成)。

2. 错误信息的"可定位 & 可解释"

  • 现状痛点:CANN 报错往往是一串错误码 + 内存地址,缺少从"用户 API 调用"到"底层故障"的映射。

  • AI 时代要求:错误日志应分层输出,包含:

    • 用户侧触发点(哪行 Python 代码调用了哪个 AscendCL 接口)
    • 中间层错误摘要(如"算子 shape 不匹配,期望 4D 输入但收到 3D")
    • 底层硬件状态

    这样 AI 可以快速定位到代码修改点,而不是只给一个堆栈。

3. 调试工具的"AI 友好接口"

  • 现状痛点:msprof 等 profiling 工具输出报告以 GUI 或静态表格为主,难以被 AI 批量解析。
  • AI 时代要求:提供 CLI 式的、机器可读的 profiling 数据导出(JSON/CSV),包含算子执行时间、内存带宽、流水线气泡等指标的时间线,方便 AI 自动分析瓶颈、给出融合建议。

4. 社区知识的"结构化开源"

  • 现状痛点:大量昇腾适配经验存在于论坛帖、内部 issue,没有形成可被 AI 学习的结构化 FAQ。
  • AI 时代要求:官方维护一个昇腾适配模式库,把常见的"CUDA 算子→AscendC 映射"、“通信库切换方式”、"内存池管理差异"等做成结构化案例,供 AI Fine-tuning 或 RAG 使用。

AI 辅助适配面临的现实挑战

即使上述要求全部满足,AI 也不能包办一切,主要挑战如下:

1. 硬件抽象层的"暗知识"难以传递

昇腾的很多性能关键行为(如 Cube 运算单元数据排布、L2 Cache 策略、Tiling 切分规则)属于硬件私有实现,公开文档少,AI 无法从公开互联网习得。这些"暗知识"仍需资深昇腾工程师口传心授,AI 此时只能给出通用、但不是最优的算子实现。

2. CANN、vLLM-Ascend 等社区版本迭代快,AI 知识容易滞后

CANN、vLLM-Ascend 等社区快速演进,接口可能废弃或行为变更。AI 模型的训练数据截止日期之后的新特性、新限制完全不知,之前借助大模型调研的昇腾背景知识也会过时,导致可能生成出过时或被禁用的 API 调用,需要人工逐行核对 Release Notes。

3. 跨框架"适配模式"提取依赖专家经验,AI 缺乏判断力

AI 可以模仿 xLLM、vLLM 来适配 RTP-LLM,但无法判断哪种模式是"最优"的,甚至可能生搬硬套:例如 vLLM-Ascend 用 torch_npu 重写了某个 kernel,AI 可能直接建议 RTP-LLM 也这么做,却忽略了 RTP-LLM 本身的算子融合策略更优,只需替换底层设备 API 即可。这种决策仍需人对整体架构的把握。

4. 验证成本高:AI 生成的代码"看起来对,跑起来崩"

AI 生成的代码在语法上可能无误,但精度或性能有问题,需要反复调优。这个迭代循环目前很难由 AI 自动完成,所以人还是要在环里做大量微调。

后续计划

下一阶段 RTP-LLM 适配计划围绕以下几个核心方向展开:

KV cache管理重构(魏珊)

一阶段虽然跑通了 Qwen3-0.6B 端到端推理,但 separate_kv_cache 机制存在明显的结构性问题。该机制在 C++ cache 核心里开了一条 NPU 专属的第二存储通路——BlockPool 分配两块独立 buffer、MemoryLayoutStrategy 维护两套 per-layer tensor、ConfigInit pickle 序列化多一项、读写路径布局不一致需要 Python permute 桥接。这意味着框架后续每一次 cache 管理演进(block pool 扩容、TP 切分、scale 支持、MLA、hybrid attention),都得在 NPU 分支里再实现一遍,跟 GPU/AMD 的合并 KV 路径长期分叉维护。

二阶段计划移除整套 separate_kv_cache 机制,回归与 GPU 一致的合并 KV 布局。前提条件是新版 FIA 算子 .so 支持非连续(strided)访问——从合并 buffer slice 出来的非连续 view 能直接喂给 FIA,不再需要物理拆分。重构方向包括:C++ 层删除 k_cache_buffer_ / v_cache_buffer_ / separate_kv_cache 全部字段和分支(预计净删约 320 行),Python 层用 kv_cache_base[:, 0] 零拷贝 view 替代 permute+contiguous,写入路径评估 npu_scatter_pa_kv_cache 算子替代 _npu_reshape_and_cache。目标是让 NPU 适配收敛到 ascend_impl/ 下少数 Python 文件,C++ cache 核心零 NPU 分支,与 GPU/AMD 三端共享同一套合并 KV + 分页 attention 逻辑。

核心特性适配

RTP-LLM 拥有众多针对 NVIDIA GPU 和 CUDA 生态优化的核心特性。本阶段需要将这些特性适配到 Ascend NPU 平台,确保在 NPU 上也能发挥其性能优势。主要工作包括:图模式、量化算法、并行推理方案、投机解码、新模型适配(Qwen3.5/3.6 等)。

DFX 能力

DFX(Design for X,表示面向产品生命周期各环节的设计)能力是保障 RTP-LLM 在 Ascend NPU 上稳定、高效运行的关键。本阶段将建设以下 DFX 能力:

  • 可测试性:建立完善的单元测试、集成测试和性能基准测试体系,确保每次代码变更都能被快速验证。
  • 可诊断性:完善日志、监控和链路追踪体系,提供详细的运行时信息,便于快速定位和诊断问题。
  • 可维护性:遵循清晰的代码规范和架构设计,确保代码易于理解、维护和扩展。
  • 可靠性:增加异常处理、资源泄漏检测和自动恢复机制,提升系统的稳定性和鲁棒性。
Logo

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

更多推荐