从生态兼容到能力分派:海光 DCU 上的 vLLM 适配方法

从生态兼容到能力分派:海光 DCU 上的 vLLM 适配方法 实验环境单张海光 DCUgfx936Python 3.10、PyTorch 2.10、HIP 6.2.0、vLLM 0.18.1Qwen3.5-27B 官方 BF16 权重最大上下文长度 32,768。将 Qwen3.5-27B 和 vLLM 迁移到海光 DCU 时最先看到的是高度熟悉的上层环境模型结构没有变化Python 代码仍然使用 PyTorch服务仍然暴露 OpenAI 兼容接口部分设备操作甚至继续沿用torch.cuda命名空间。成熟生态被保留下来迁移不必从模型和服务端重新开始。不过上层接口相同并不表示底层实现天然等价。模型返回第一个 token只能证明基本执行链已经接通一条来自既有 ROCm 路径的 native kernel 能否面向当前 DCU 构建能否处理模型真实传入的张量又能否被 vLLM 正确追踪和回退仍然是彼此独立的问题。本文以单张海光 DCUgfx936上的 Qwen3.5-27B BF16 推理为观察样本讨论一个比“是否兼容”更具体的问题开源框架中的既有实现怎样被转化为边界明确、可以验证的 DCU 能力。全文沿着以下主线展开-上层承接复用模型、调度与通用算子-三类边界架构与指令、真实张量、框架执行-能力分派精确选择、验证、关闭与回退这条路径体现了 DCU 的两层工程价值兼容软件栈承接 PyTorch、vLLM 等开源生态降低模型迁移成本HIP/native kernel 又保留面向目标硬件的控制能力。前者提供完整、稳定的起点后者扩展设备能够承担的工作适配发生在两者的交界处。1. DCU 为什么能够承接 vLLM讨论适配之前需要先回答一个容易被忽略的问题一个主要由 Python、PyTorch、Triton 和 C 扩展组成的大模型推理框架为什么能够在海光 DCU 上运行答案不是“DCU 模拟成了另一种 GPU”也不是 vLLM 为 Qwen3.5 重写了一套模型代码而是海光 DCU 在通用 GPGPU 硬件之上提供了 DTK 及其兼容的 ROCm/HIP 编程接口。上层框架可以继续使用成熟的张量、算子和异构计算抽象底层代码则由对应工具链编译并提交到 DCU。公开资料将海光 DCU 描述为面向计算密集型和人工智能场景的通用 GPGPU/深度计算处理器并强调其对 ROCm 计算生态的兼容飞桨的海光 DCU 支持也建立在 ROCm 版框架与 DTK 运行环境之上。这种生态关系决定了 DCU 的迁移方式开发者不必从模型定义和服务接口重新开始而是可以复用上层软件再处理目标设备真正不同的部分。1.1 一条执行链五层职责以 vLLM 中的一次 Qwen3.5 Decode 为例完整链路可以简化为Qwen3.5 模型定义 Linear / FullAttention / Gated DeltaNet / MLP | v vLLM 模型执行与服务运行时 scheduler / model runner / KV manager / backend dispatch | v PyTorch 与算子实现 ATen / torch.compile / Triton / custom op | v DTK、HIP 与设备运行时 编译、kernel launch、stream、event、内存管理 | v 海光 DCU gfx936 CU / wavefront / VGPR / LDS / HBM / 目标指令Qwen3.5 模型层定义数学语义例如如何从 hidden state 生成 Q/K/V、哪些层使用 FullAttention、哪些层维护 GDN 循环状态。它并不负责管理设备 stream也不会指定某个gfx936kernel。vLLM 负责把模型变成在线推理服务。它组织 Prefill 和 Decode维护请求状态和 KV-cache为每一层准备张量与 metadata并根据平台、dtype、shape 和 backend 能力选择算子路径。PyTorch 提供张量和高层算子语义。通用 Linear 可以从torch.nn.functional.linear进入 ATen/ROCm 实现Triton kernel 可以在运行时针对目标后端生成代码对线程布局或指令有更强控制需求的算子则可以通过 PyTorch custom op 调用预先构建的 C/HIP 扩展。DTK/HIP 位于框架与设备之间。它负责把高层提交转换为 DCU 能够执行的 kernel、内存操作和同步关系。最终到达 gfx936 时已经没有 Python 类或 vLLM backend 的概念只剩下目标代码、workgroup、wavefront、寄存器、LDS 和 HBM 访问。这条链路揭示了 DCU 生态兼容的实际含义复用的是模型、框架和编程接口最终执行的仍是面向 DCU 构建的设备代码。兼容显著缩小了需要改动的范围却不会自动消除目标架构之间的差异。1.2 复用到哪里设备边界就从哪里开始在这套分层中Qwen3.5 的 BF16 权重、tokenizer、模型结构和自回归语义都不需要改变vLLM 的服务接口、请求调度、流式输出和大部分模型 runner 也可以继续使用。PyTorch 则提供 Tensor 与通用算子语义使模型在专用 kernel 尚未适配时仍然拥有可运行的路径。这种复用并不要求上层隐藏所有平台差异。HIP 版本的 PyTorch 为保持 API 兼容Python 侧仍大量沿用torch.cuda命名空间。名称延续的是编程接口而非硬件身份真正决定执行平台的是 PyTorch 的 HIP 构建、DTK/ROCm runtime、目标架构以及实际加载的扩展。当执行进入具体 kernel 时兼容的含义随之改变。通用 PyTorch 算子主要由软件栈承担跨设备实现手写 C/HIP kernel 则可能依赖特定目标指令、wave 组织和内存布局。它能在某个 ROCm 设备上运行不代表能直接用于另一目标能够为 DCU 编译也不代表在模型的实际 shape 上更快。因此上层与下层不是“兼容”与“不兼容”的二选一关系而是不同程度的复用模型和服务语义可以整体承接native kernel 则需要逐项验证。DCU 兼容开源生态的意义正是把原本需要重写整套系统的问题缩小为少量可以定位、测试和回退的设备边界。1.3 跟随一个 token 观察这条边界仅有软件分层仍然比较抽象。跟随一个新 token 经过 Qwen3.5可以看到同一套 vLLM 上层逻辑怎样在下层产生不同工作OpenAI 请求 - vLLM scheduler / EngineCore - Qwen3.5 单 token Decode | -- FullAttention 层 | qkv Linear | - QK RMSNorm RoPE | - 分页 KV Attention | - o Linear | -- GDN 层 | 输入投影 | - 读取并更新固定循环状态 | - 输出投影 | -- 每层 MLP gate/up Linear - 门控激活 - down Linear - logits / sampling - 下一个 tokenFullAttention 与 GDN 对历史状态的保存方式不同vLLM 会分别准备分页 KV 和固定循环状态。它们说明框架不能在进入设备前丢掉模型结构与请求状态。为了把问题收束到一条清晰的适配路径后文选择每层都会出现的 MLP Linear 作为案例。MLP 中的 gate/up 与 down projection 在模型层都属于 Linear在一次单 token Decode 中却形成不同矩阵gate/up 为34816 x 5120 x 1down 为5120 x 17408 x 1。两者使用相同公式Y X W T YXW^TYXWT最合适的 DCU 实现却不同。前者可以从 LLMM1 获益后者继续使用F.linear更快。这正是 vLLM 与 DCU 的连接点。模型层只要求得到正确的 Linear 结果vLLM 还掌握 dtype、shape、bias 和执行阶段设备 kernel 则规定自己能够处理的输入和工作划分。只有这些信息在分派位置汇合DCU 的专用能力才可能被准确使用。至此迁移范围已经清晰Qwen3.5 与 vLLM 的上层执行体系可以整体承接真正需要逐项确认的是 native kernel 与目标设备、真实输入及框架语义之间的关系。2. 从能够编译到可以调用三类边界模型和框架可以整体复用native kernel 却必须逐条判断。源码来自 ROCm 生态、扩展成功构建、独立测试返回结果分别只能证明一部分事实要进入 vLLM 服务一条实现还要跨过架构与指令、真实张量、框架执行三类边界。2.1 架构与指令边界能构建哪部分代码本次环境向工具链报告的目标架构为gfx936。这个字符串让编译器知道应当生成哪类设备代码却不是一张现成的 kernel 能力表。vLLM 的 ROCm 平台代码中存在_ON_GFX9一类设备判断其中列出gfx90a、gfx942、gfx950等已有覆盖目标。面对同样以gfx9开头的gfx936真正重要的不是名称是否相似而是这个判断会放行哪些实现。一个平台开关可能同时控制多个互不相关的 native 路径加入设备名称就等于为这些路径同时背书。实际源码很快暴露出这种整体判断的风险。同一份skinny_gemms.cu包含前提不同的实现LLMM1 面向单行激活通过通用乘加、wave shuffle 和 LDS 完成归约wvSplitK 的 FP16 分支则直接使用v_dot2c_f32_f16内联指令。扩展文件能够为 gfx936 构建不等于其中每个模板、dtype 分支和目标指令都已经得到验证。因此架构识别只负责回答“当前设备是谁”kernel 能力还要继续回答“这份实现的哪些部分已经在该设备上成立”。在这次适配中gfx936 被单独识别验证范围也停留在 LLMM1 候选wvSplitK 以及其他共享全局判断的路径并未随之开放。2.2 真实张量边界能处理哪类模型输入通过架构与指令检查后kernel 仍需满足模型实际传入的张量。数学上同为Y X W T YXW^TYXWT的 Linear到了 native 实现中还包含一组更窄的条件。LLMM1 的入口要求激活只有一行即N1并且权重与输入使用 FP16 或 BF16。继续向下还要检查权重的M/K、是否带 bias、内存连续性和输出 shape。Qwen3.5 的 gate/up、qkv 与 down 在模型层都叫 Linear真实矩阵却不同某个 shape 的验证结果不能自动覆盖另外两个调用点。这也是为什么适配测试必须来自真实模型执行。随机生成一个满足矩阵乘公式的小张量只能验证数学结果带入 vLLM 实际使用的 dtype、shape、stride 和 bias才能验证完整调用契约。同环境下的对照最终把 LLMM1 收窄到特定 gate/up shape而没有推广到所有单 token Linear。2.3 框架执行边界能否成为 PyTorch/vLLM 算子即使一个 C/HIP 函数能够正确处理真实张量它也还不是完整的框架能力。vLLM 的执行可能经过 PyTorch custom op、编译捕获和 Graph 重放这些系统需要理解算子的输入、输出和副作用而不能只知道底层有一个可调用的函数地址。custom op 的 schema 描述参数与返回值真实实现负责提交 DCU kernelfake/meta 实现则在不执行设备代码的情况下给出输出的 shape、dtype 和 device使编译系统能够继续推导计算图。若算子会原地修改张量还必须明确这种副作用。Graph 重放进一步要求捕获后的地址关系和执行依赖保持稳定。这里存在两个不同的执行世界真实执行阶段需要把工作提交给 DCU图构建阶段则需要在不运行 kernel 的情况下推导张量关系。前者缺少实现设备无法计算后者缺少正确的 fake/meta 语义编译系统无法继续追踪。若副作用或地址关系不稳定eager 模式下能够运行的 kernel 也可能在 Graph 中失效。2.4 三类边界共同定义一项 DCU 能力三类边界解决的是不同问题不能互相替代边界核心问题所需证据条件不成立时架构与指令kernel 的目标代码和指令前提是否适用于当前 DCU目标构建、源码/指令审计、独立 smoke不开放该 kernel真实张量模型输入是否满足实现约束dtype、shape、layout 与数值对照回到通用算子框架执行PyTorch/vLLM 是否能正确追踪、捕获和调用custom op 语义、fake/meta、Graph smoke使用原执行路径由此“支持海光 DCU”不应被压缩成一个设备白名单。更准确的表达是在特定工具链上每项算子能力都有自己的设备目标、输入范围和框架语义。只有三类边界同时成立它才适合进入下一步的能力分派。3. 让设备特化成为可维护的框架能力跨过三类边界只能说明一条 kernel 具备使用资格。要成为稳定的框架能力验证结论还必须转化为清晰的选择规则何时进入专用实现何时留在通用路径软硬件变化后又应重新检查哪些条件。3.1 能力的单位应当是 kernel而不是设备家族设备识别通常是分派的第一步却不应该是最后一步。若用一个全局“gfx9 可用”或“DCU 优化开启”判断控制所有底层实现那么验证 LLMM1 的同时也可能放行 wvSplitK、PagedAttention 等没有共享输入和指令前提的代码。更稳妥的做法是保留上游已经覆盖的完整 gfx9 集合同时单独识别 gfx936只有执行到 skinny GEMM 的选择位置时才检查对应候选。由此得到的不是“gfx936 支持全部 skinny kernel”而是下面这样的局部能力Linear 实现在本文环境中的作用使用边界PyTorchF.linear通用基线与回退覆盖未进入专用路径的 LinearLLMM1gfx936 上的一个 BF16、单 token 候选继续检查精确 shape、bias 和布局wvSplitK不向 gfx936 开放当前环境未建立所需指令路径的安全证据这样组织后一个 kernel 的加入或关闭只改变对应算子的能力不会连带改变其他 backend。设备家族用于快速排除无关候选真正的支持范围则由每条 kernel 自己声明。3.2 分派条件来自证据而不是层名称Qwen 模型将 gate/up、qkv 和 down 都表达为 Linear。如果按模块类别分派三者会进入同一实现第二章已经说明它们的真实输入契约并不相同。因此选择必须发生在 dtype 和 shape 已经可见的位置。在本文环境中这项选择可以压缩为一条很短的规则gfx936 BF16 DecodeN1 gate/up: M34816K5120 无 bias布局满足要求 - LLMM1 任一条件不成立 - F.linear这些数值不是面向所有 Qwen 或所有 DCU 的固定规则而是当前模型、权重和工具链共同限定的结果。模型版本一旦改变 intermediate size或者请求进入 Prefill、批处理与带 bias 的 Linear原有证据就不再覆盖这次调用。精确分派的意义不只是避免较慢路径。native kernel 往往只实现了通用算子的一个子集把适用范围写成代码条件才能同时约束正确性和性能。条件不满足时回到F.linear也不是“优化失败后的补丁”而是借助兼容软件栈保留完整模型覆盖的正常路径。3.3 验证必须从独立 kernel 一直延伸到服务进程把规则写入框架并不证明真实请求已经使用它。可靠的证据需要沿执行范围逐步扩大目标架构构建 - 独立 kernel 数值对照 - vLLM 真实 tensor 条件验证 - 分派命中确认 - 冷启动与短请求 smoke - 编译/Graph 场景 - 同口径端到端测试这些步骤分别排除不同问题。构建成功只能排除编译错误独立对照确认 kernel 的局部数学行为命中信息证明真实请求确实经过目标实现服务验证再检查模型加载、调度、Graph 和输出流程没有被破坏。在这次适配中skinny 路径被单独隔离验证并依次通过模型冷启动、服务健康检查和短请求推理。这些结果证明精确 LLMM1 分派已经进入真实服务却不等同于完整吞吐或模型精度结论。局部微基准、框架接入与端到端收益必须保持各自的证据边界。同理配置已经写入启动环境并不能代替运行时命中证据。日志、计数或 profiler 需要证明目标 kernel 确实出现在服务执行中并与数值和性能结果使用同一组输入条件。3.4 能力记录使适配可以随版本演进DCU、DTK、PyTorch、vLLM 和模型都会演进。若适配只表现为一个设备白名单升级后很难判断哪些假设已经失效若为每项能力保留完整边界影响范围就会清晰得多。一份可维护的能力描述至少包含五个维度记录项需要保存的内容设备与工具链DCU 目标、DTK/HIP、PyTorch 与扩展构建版本kernel 前提dtype、shape、layout、bias、目标指令或资源假设框架语义custom op schema、fake/meta、原地行为与 Graph 限制验证证据数值对照、真实请求命中、服务 smoke 与端到端结果回退路径条件失配或开关关闭时采用的通用实现更换模型时主要复验张量契约和分派更换 DCU 或工具链时重新检查目标代码与指令能力升级 PyTorch/vLLM 时则重点确认 custom op、编译和 Graph 语义。这样不必把整个平台重新判定为“支持”或“不支持”只需更新受到影响的能力项。设备专用 kernel 只有被表达为细粒度能力并同时具备精确条件、框架语义、验证证据和原生回退才能真正成为可维护的 vLLM 执行路径。结语兼容提供起点能力分派完成适配海光 DCU 通过 DTK/ROCm/HIP 承接 PyTorch 与 vLLM 生态使模型定义、服务调度和大量通用算子可以直接复用。这种兼容首先提供的是一个正确、完整的执行起点。到了 native kernel适配必须继续回答三个问题目标代码与指令是否适用于当前 DCU模型真实张量是否满足实现前提PyTorch/vLLM 是否能够正确追踪和组织该算子。任何一项缺少证据都不应由设备名称或一次编译成功代替。vLLM 中的能力分派把这三类证据连接起来经过验证的条件进入专用实现其他输入保留通用路径开关与回退保证软件演进时能够恢复完整覆盖。DCU 适配的最终产物因而不是一个架构字符串补丁也不是孤立的 HIP kernel而是一组可被框架选择、验证、关闭和维护的设备能力。让 DCU 发挥开源推理生态的价值并不要求所有算子都专用化。更实际的路径是在兼容栈提供的稳定基线上逐项加入证据充分的设备实现兼容降低迁移成本设备能力扩展性能边界细粒度分派负责让二者在同一套服务中长期共存。参考资料海光信息招股说明书海光 DCU、GPGPU 架构与 ROCm 生态兼容的公开说明。飞桨海光 DCU 芯片运行文档Paddle ROCm 版与海光 DTK/DCU 支持关系。ROCm HIPHIP 的可移植 C runtime 与 kernel language。PyTorch HIP semanticsROCm/HIP 构建继续使用torch.cuda接口命名的兼容语义。vLLM本文所用模型执行、平台分派与 custom op 集成框架。