Building an LLM Inference Engine from ScratchPart II / Module 5
Part II · GPU Compute

Module 5:GPU Compute API 版图

理解 kernel、buffer、command queue、同步这些共通概念,并比较 CUDA、Vulkan、Metal、OpenCL、Direct3D 12 + DirectML 的取舍。

学习目标

  • 抽象出所有 GPU compute API 的共同模型:buffer、kernel、dispatch、queue、barrier、fence。
  • 理解 CUDA 为什么是深度学习事实标准,以及它的 vendor lock-in 代价。
  • 理解 Vulkan / SPIR-V 的跨平台能力与复杂度。
  • 理解 Metal、OpenCL 的定位。
  • 解释为什么本项目选择 Direct3D 12 + DirectML,并为什么仍要设计 HAL。

5.1大图景:所有 API 都在表达同一件事

CUDA、Vulkan、Metal、OpenCL 与 Direct3D 12 的术语和对象体系不同,但它们表达的计算流程相近:分配或上传 buffer,编译 kernel/shader,建立资源绑定,向 command queue 提交 dispatch,再用 fence、event 或等价机制确认 GPU 工作完成。理解这条公共路径,比记忆某个 API 的函数名更重要。

Buffers A, W, Out Kernel HLSL/CUDA/... Bind resources SRV/UAV/params Dispatch grid / groups Synchronize barrier / fence API 之间的语法差异很大,但抽象动作高度相似。
图 5-1:GPU compute API 的共同执行模型。

5.2CUDA:深度学习的事实标准

CUDA 是 NVIDIA 的专有 GPU compute 平台,也是深度学习中最成熟的工程生态之一。其优势不只来自 kernel 语言本身,还来自 cuBLAS、cuDNN、NCCL、TensorRT、Nsight 以及主流框架长期优化出的后端路径。代价同样明确:程序与 NVIDIA 硬件和 CUDA 运行时绑定。

优点代价
生态成熟,资料丰富绑定 NVIDIA GPU
cuBLAS/cuDNN 性能极强Windows/Xbox/AMD/Apple 路径不适用
调试和 profiling 工具完善跨平台抽象需要额外层
很多论文和 kernel 以 CUDA 为参考实现教学上容易把 matmul 直接交给库,隐藏细节
本教程视角

若目标是在 NVIDIA GPU 上尽快获得高性能,CUDA + cuBLAS 是合理方案。本教程的目标包括 Windows / Xbox 路径、D3D12 资源模型和 custom kernel 机制,因此不能把 CUDA 作为唯一后端。

5.3Vulkan compute 与 SPIR-V

Vulkan 是跨厂商、跨平台的低层图形与计算 API,shader 通常编译为 SPIR-V。它覆盖 Windows、Linux、Android 以及 NVIDIA、AMD、Intel 等硬件,适合构建可移植 backend。相应地,Vulkan 把设备选择、内存分配、descriptor set、pipeline、barrier 和 queue family 等细节显式交给程序管理,代码量和概念负担较高。

Vulkan 适合构建跨平台底层 backend,但教学门槛比 D3D12/HLSL 更高;而且 Xbox 原生路径不是 Vulkan。

图 5-2:几种 GPU compute API 的相对取舍(示意,不是绝对评分)。

5.4Metal 与 OpenCL:作为背景

Metal 是 Apple 平台的现代 GPU API,在 macOS / iOS 上具有统一的工具链和较低的集成成本,但不覆盖 Windows / Xbox。本项目主线面向 Windows PC 与 Xbox GDK,因此不会以 Metal 为实现基础。

OpenCL 曾长期承担跨平台通用计算角色,概念上有助于理解 GPU compute 的历史演进。不过,在深度学习生态、调试工具和现代图形 API 互操作方面,它已不是本教程的最佳主线。

5.5Direct3D 12 + DirectML:本项目的选择

xinfer 选择 Direct3D 12 作为资源与执行基础,并使用 DirectML 研究 operator 生命周期,同时为 transformer 主路径保留 custom HLSL kernel。这个选择来自以下工程约束:

  • **Windows 原生:**目标机器是 Windows PC,D3D12 是第一等公民。
  • **Xbox 路径:**Xbox GDK 使用 D3D12 风格设备创建;host 可以把设备注入 Rust core。
  • **HLSL 教学友好:**HLSL compute shader 语法直观,适合讲 threadgroup、SRV/UAV、barrier。
  • **DirectML 可作为 operator API:**DirectML 展示了 create → compile → initialize → bind → dispatch 的算子生命周期;核心 transformer 路径仍可改用自写 HLSL kernel。
真实案例:为什么不能盲信库算子

项目源码同时保留 DirectML operator 封装和 custom HLSL kernel 路径。实际工程中,库算子在特定 shape、驱动或硬件组合下可能出现不可接受的行为;此时能够退回到自写 kernel,是推理引擎必须具备的能力。

xinfer-model Qwen2 layer graph xinfer-backend HAL Device / Buffer / Kernel xinfer-dml D3D12 + DirectML HLSL kernels linear / rms / attn D3D12 queues direct / copy / fences DirectML operator lifecycle 模型层不直接依赖 D3D12;平台细节收敛到 xinfer-dml 与 FFI host。
图 5-3:xinfer 的 backend 分层:模型通过 HAL 使用 D3D12/HLSL/DirectML。

5.6可移植性策略:为什么要建 HAL?

模型层不应直接依赖 ID3D12Resource、root signature 或 descriptor heap。HAL(Hardware Abstraction Layer)把模型需要的能力收敛为更稳定的 Device、Buffer、Kernel / Operator 接口,使模型结构、runtime 与具体 GPU API 分离:

Model codeDevice / Buffer / Kernel traitsBackend implementation\text{Model code} \rightarrow \text{Device / Buffer / Kernel traits} \rightarrow \text{Backend implementation}

在 xinfer 中,xinfer-backend 定义 HAL 边界,xinfer-dml 在其后实现 D3D12/DirectML backend。DmlDevice::from_existingfrom_raw_pointers 允许外部 host 注入已创建的 ID3D12Device 和 command queue;这对应 Xbox GDK 由 native host 创建设备,再交给 Rust core 使用的路径。若未来加入 Vulkan backend,应替换的是 backend crate,而不是 Qwen 模型的前向逻辑。

5.7如何选择 backend?一个工程决策表

候选适合场景不适合本项目的点
CUDANVIDIA-only,高性能深度学习不能覆盖 AMD / Xbox;vendor lock-in
Vulkan跨平台、跨厂商底层 backend学习曲线陡;Xbox 不是原生 Vulkan 路线
MetalApple 平台最佳体验不覆盖 Windows/Xbox
OpenCL历史上的跨平台 compute生态与现代调优体验较弱
D3D12 + DirectMLWindows / Xbox;HLSL;可注入 host-created device非 Windows 平台不可用;某些 DML operator 可能有坑

本教程选择 D3D12 + DirectML,是因为它与项目目标匹配:Windows 原生可运行,Xbox 路径清晰,HLSL 便于展示 threadgroup、SRV/UAV、barrier 与 dispatch,底层对象也足够透明。

小结

本章比较了几类 GPU compute API。CUDA 代表最成熟的深度学习生态,但与 NVIDIA 平台绑定;Vulkan 提供跨厂商底层能力,但显式管理成本高;Metal 适合 Apple 平台;OpenCL 更适合作为历史背景。xinfer 的当前路线是以 D3D12 管理资源、命令队列和同步,以 DirectML 理解 operator 生命周期,并在核心 transformer 算子上使用 HLSL kernels。HAL 的作用是让模型与 runtime 依赖抽象设备和 buffer,而不是依赖某个具体 API;因此将来可以把 xinfer-dml 替换为 Vulkan 等 backend,而不重写模型结构。

Lab 5概念比较:两个 API 的 Hello Compute

选择 CUDA、Vulkan、Metal、D3D12 中任意两个,比较它们完成 out[i]=in[i]*2+1 需要哪些概念步骤。 不要求写代码,但要画出概念流程。

  1. 如何创建 device / context?
  2. 如何分配 GPU buffer?
  3. kernel / shader 用什么语言写?编译成什么格式?
  4. 如何绑定 input/output buffer?
  5. 如何提交 dispatch?
  6. 如何等待完成并读回结果?

思考与练习

基础列出所有 GPU compute API 都需要表达的 5 个共同概念。

① 设备/队列(device & command queue,提交工作);② 缓冲区/资源(buffer/memory,存数据);③ kernel/shader(要执行的程序);④ 资源绑定(descriptor/binding,告诉 kernel 用哪些 buffer);⑤ dispatch + 同步(启动 N 个线程组并用 fence/barrier 同步)。无论 CUDA、D3D12、Vulkan、Metal,都要表达这五件事。

基础为什么 CUDA 在深度学习中很强,但不适合作为本教程唯一 backend?

CUDA 生态成熟、kernel 库丰富,但它只支持 NVIDIA GPU。本教程目标包括 Windows PC 与 Xbox GDK 路线,不能把 NVIDIA-only 后端作为唯一实现。D3D12/DirectML 更符合这些平台约束;同时保留 HAL,可避免模型层被某个后端锁定。

进阶解释 descriptor / resource binding 在 D3D12 或 Vulkan 中解决了什么问题。

它解决“shader 如何知道该访问哪块 GPU 内存”的问题。shader 里只写逻辑寄存器槽(如 register(u0)),descriptor 把这些槽映射到具体的 GPU 资源(buffer/texture 的地址、格式、范围)。这层间接让同一个 shader 可在不同帧绑定不同资源,并让驱动统一管理资源生命周期与访问权限。xinfer 用 root descriptor 直接绑定 buffer 地址,省去描述符堆重绑。

进阶如果模型代码直接依赖 D3D12 COM 类型,会怎样影响未来移植?

模型层会和 Windows/D3D12 紧耦合:想换 Vulkan/Metal 或上别的平台就得改 Qwen 前向逻辑本身,而不仅是 backend。这违背关注点分离。正确做法是把 D3D12 细节藏在 backend trait(Device/Buffer/Kernel)后面,让 xinfer-model 只依赖抽象接口,移植时只替换 xinfer-dml 这一层。

挑战为 xinfer 设计一个假想 Vulkan backend 的 crate 边界,说明哪些 trait 可以复用。

新增 xinfer-vk crate,实现 xinfer-backend 中已有的 DeviceBuffer 抽象,并补齐 kernel dispatch / executor 边界。Vulkan 概念可对应为:VkDevice/VkQueue ↔ Device,VkBuffer + VkDeviceMemory ↔ Buffer,compute pipeline + descriptor set ↔ Kernel/绑定,VkCommandBuffer + VkFence ↔ Executor。可复用的是上层模型、runtime、loader 与 tokenizer;需要替换的是 backend 实现和 shader 编译目标,例如把 HLSL 路径迁移到 SPIR-V 或重写 shader。