2
DCU技术达人
从生态兼容到能力分派:海光 DCU 上的 vLLM 适配方法
2026-10-19 21:36:42
38
# 从生态兼容到能力分派:海光 DCU 上的 vLLM 适配方法 > **实验环境**:单张海光 DCU `gfx936`,Python 3.10、PyTorch 2.10、HIP 6.2.0、vLLM 0.18.1,Qwen3.5-27B 官方 BF16 权重,最大上下文长度 32,768。 将 Qwen3.5-27B 和 vLLM 迁移到海光 DCU 时,最先看到的是高度熟悉的上层环境:模型结构没有变化,Python 代码仍然使用 PyTorch,服务仍然暴露 OpenAI 兼容接口,部分设备操作甚至继续沿用 `torch.cuda` 命名空间。成熟生态被保留下来,迁移不必从模型和服务端重新开始。 不过,上层接口相同,并不表示底层实现天然等价。模型返回第一个 token,只能证明基本执行链已经接通;一条来自既有 ROCm 路径的 native kernel 能否面向当前 DCU 构建,能否处理模型真实传入的张量,又能否被 vLLM 正确追踪和回退,仍然是彼此独立的问题。 本文以单张海光 DCU `gfx936` 上的 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 为例,完整链路可以简化为: ```text 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,也不会指定某个 `gfx936` kernel。 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 上层逻辑怎样在下层产生不同工作: ```text 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 -> 下一个 token ``` FullAttention 与 GDN 对历史状态的保存方式不同,vLLM 会分别准备分页 KV 和固定循环状态。它们说明框架不能在进入设备前丢掉模型结构与请求状态。为了把问题收束到一条清晰的适配路径,后文选择每层都会出现的 MLP Linear 作为案例。 MLP 中的 gate/up 与 down projection 在模型层都属于 Linear,在一次单 token Decode 中却形成不同矩阵:gate/up 为 `34816 x 5120 x 1`,down 为 `5120 x 17408 x 1`。两者使用相同公式 $Y=XW^T$,最合适的 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=XW^T$ 的 Linear,到了 native 实现中还包含一组更窄的条件。 LLMM1 的入口要求激活只有一行,即 `N=1`,并且权重与输入使用 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 kernel;fake/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 实现 | 在本文环境中的作用 | 使用边界 | | --- | --- | --- | | PyTorch `F.linear` | 通用基线与回退 | 覆盖未进入专用路径的 Linear | | LLMM1 | gfx936 上的一个 BF16、单 token 候选 | 继续检查精确 shape、bias 和布局 | | wvSplitK | 不向 gfx936 开放 | 当前环境未建立所需指令路径的安全证据 | 这样组织后,一个 kernel 的加入或关闭只改变对应算子的能力,不会连带改变其他 backend。设备家族用于快速排除无关候选,真正的支持范围则由每条 kernel 自己声明。 ### 3.2 分派条件来自证据,而不是层名称 Qwen 模型将 gate/up、qkv 和 down 都表达为 Linear。如果按模块类别分派,三者会进入同一实现;第二章已经说明,它们的真实输入契约并不相同。因此,选择必须发生在 dtype 和 shape 已经可见的位置。 在本文环境中,这项选择可以压缩为一条很短的规则: ```text gfx936 + BF16 Decode,N=1 + gate/up: M=34816,K=5120 + 无 bias,布局满足要求 -> LLMM1 任一条件不成立 -> F.linear ``` 这些数值不是面向所有 Qwen 或所有 DCU 的固定规则,而是当前模型、权重和工具链共同限定的结果。模型版本一旦改变 intermediate size,或者请求进入 Prefill、批处理与带 bias 的 Linear,原有证据就不再覆盖这次调用。 精确分派的意义不只是避免较慢路径。native kernel 往往只实现了通用算子的一个子集;把适用范围写成代码条件,才能同时约束正确性和性能。条件不满足时回到 `F.linear`,也不是“优化失败后的补丁”,而是借助兼容软件栈保留完整模型覆盖的正常路径。 ### 3.3 验证必须从独立 kernel 一直延伸到服务进程 把规则写入框架,并不证明真实请求已经使用它。可靠的证据需要沿执行范围逐步扩大: ```text 目标架构构建 -> 独立 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 发挥开源推理生态的价值,并不要求所有算子都专用化。更实际的路径是在兼容栈提供的稳定基线上,逐项加入证据充分的设备实现;兼容降低迁移成本,设备能力扩展性能边界,细粒度分派负责让二者在同一套服务中长期共存。 --- ## 参考资料 - [海光信息招股说明书](https://static.sse.com.cn/stock/disclosure/announcement/c/202203/001043_20220304_7DDI.pdf):海光 DCU、GPGPU 架构与 ROCm 生态兼容的公开说明。 - [飞桨:海光 DCU 芯片运行文档](https://www.paddlepaddle.org.cn/documentation/docs/zh/2.5/guides/hardware_support/rocm_docs/index_cn.html):Paddle ROCm 版与海光 DTK/DCU 支持关系。 - [ROCm HIP](https://github.com/ROCm/HIP):HIP 的可移植 C++ runtime 与 kernel language。 - [PyTorch HIP semantics](https://docs.pytorch.org/docs/stable/notes/hip.html):ROCm/HIP 构建继续使用 `torch.cuda` 接口命名的兼容语义。 - [vLLM](https://github.com/vllm-project/vllm):本文所用模型执行、平台分派与 custom op 集成框架。
DCU达人计划
gfx936
全部评论(0)
只看楼主
暂无数据
--
楼主
Level.2
0 帖子
楼主热帖
暂无数据