news 2026/4/21 23:49:48

PM4 / AQL 命令包

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
PM4 / AQL 命令包

PM4 是 AMD GCN/RDNA 图形 / 通用渲染的硬件原生命令包;AQL 是 ROCm/HSA 面向计算调度用户态队列标准包;

PM4 命令包(Packet Format 4)

定位:GPU 命令处理器(CP)直接执行的底层指令包,用于图形渲染、寄存器配置、IB 提交、同步等,是 amdgpu 驱动命令流的核心。

单位:32bit(DWORD)为基本单元,包头 1 DWORD, payload 可变。

类型:Type 0/2/3(Type 1 已废弃)。

PM4 通用头部规则

  • 单位:32bit(DWORD)
  • 最高 2bit [31:30] 决定类型:
    • 00= Type 0
    • 01= Type 1(废弃)
    • 10= Type 2
    • 11= Type 3(最常用)

IB (Indirect Buffer)

IB = 一段连续的 GPU 内存,里面从头到尾全是 PM4 命令包;

GPU 从 IB 首地址开始,按 DWORD 逐条解析 PM4,直到 IB 长度耗尽。

IB = Indirect Buffer

  • 一块GPU 可访问的显存缓冲区(BO)
  • 里面只存 PM4 命令流
  • 通过PKT3_IB命令让 GPU 跳过去执行

你可以理解成:

IB 是 PM4 命令的 “代码段”PKT3_IB 是一条 “call 指令”

IB 的完整结构

一个 IB 内部是连续平铺的 PM4 包,格式如下:

[IB 开始] DWORD0 : PM4 包1 头部 DWORD1 : PM4 包1 数据 DWORD2 : PM4 包2 头部 DWORD3 : PM4 包2 数据0 DWORD4 : PM4 包2 数据1 ... [IB 结束,共 N 个 DWORD]

GPU 执行规则:

  1. IB_BASE开始
  2. 每次读一个 DWORD 作为当前 PM4 包头部
  3. 根据头部类型(Type0/2/3)解析这个包占用多少 DWORD
  4. 指针跳过整个包,继续下一个
  5. 直到已读取 DWORD 总数 == IB 长度,停止

PM4 包头格式(32bit)

Type 0(寄存器批量写)

[31:30] type = 00 [29:0] addr = 寄存器基地址(29bit)
  • 作用:连续写 N 个 GPU 寄存器(如 CP、SH、CONTEXT 寄存器)
  • payload:N 个 DWORD 寄存器值

GPU 怎么知道要写多少个?

GPU 命令处理器(CP)只认一条规则

Type 0 会一直写,直到 IB 结束!

也就是说:

  • 你在 IB 里放:
    DWORD0: 0x0000F000 (Type0 + 基地址) DWORD1: 数据1 DWORD2: 数据2 DWORD3: 数据3
  • GPU 会写:
    • 地址 F000 ← 数据 1
    • 地址 F004 ← 数据 2
    • 地址 F008 ← 数据 3

写多少个?= IB 里 Type0 包头后面剩下多少 DWORD,就写多少个!

为什么这么设计?

因为Type0 是 “流模式”

  • 包头只告诉 GPU:开始写寄存器
  • 后面所有 DWORD 都是寄存器值
  • 直到IB 结束才停止

这是 AMD GPU 从 R600 到 RDNA3 一脉相承的硬件行为

Type 2(NOP 填充)

[31:30] type = 10 [29:0] 保留(无意义)
  • 作用:仅用于对齐(如 16 DWORD 对齐),不执行任何操作。

Type 3(功能命令,最常用)

[31:30] type = 11 [29:16] count = payload DWORD 数 - 1 [15:8] opcode = 命令码(如 SET_CONTEXT_REG、DRAW_INDEX2、IB) [7:1] 保留 [0] predicate = 条件执行开关
  • 作用:执行复杂操作(绘制、IB 提交、同步、 barrier、EOP 等)。

核心 Type 3 Opcode(常用)

  • PKT3_NOP:空操作
  • PKT3_SET_CONTEXT_REG:写上下文寄存器
  • PKT3_SET_SH_REG:写 shader 引擎寄存器
  • PKT3_IB:提交 Indirect Buffer(IB)
  • PKT3_DRAW_INDEX2:索引绘制
  • PKT3_RELEASE_MEM:写内存 / 信号(EOP 同步)
  • PKT3_WAIT_REG_MEM:等待寄存器 / 内存条件

PM4 完整结构总图

┌─────────────────────────────────────────────┐ │ PM4 Packet │ ├─────────────┬───────────────────────────────┤ │ DWORD0 头部 │ Type0 / Type2 / Type3 │ ├─────────────┼───────────────────────────────┤ │ DWORD1~N │ Payload(数据/参数) │ └─────────────┴───────────────────────────────┘ Type0: [31:30]=00 | [29:0]=寄存器基地址 → 连续写寄存器 Type2: [31:30]=10 | 全0 → 填充对齐 Type3: [31:30]=11 | count-1 | opcode | shader | P → 执行命令

PM4 提交流程(内核态)

用户态(Mesa)构建 PM4 流 → 写入 IB(GEM BO) →amdgpu_cs_ioctl→ 内核写入 Ring → GPU CP 解析执行 → 中断完成。

三种 PM4 包在 IB 中如何被解析

① Type3(带 count,最正常)

DWORD0: 11 | count-1 | opcode | ... ← 头部 DWORD1 ~ DWORD[count]:载荷
  • GPU 一看type=11
  • 取出count
  • 知道这个包总长度:1 + count DWORD
  • 读完直接跳到下一个包

② Type2(NOP)

DWORD0: 10 | xxxxxxxxxxxxxxxx ← 头部
  • 只有 1 个 DWORD
  • 读完就结束

③ Type0(无 count,读到 IB 结束

DWORD0: 00 | 寄存器基地址 ← 头部 DWORD1: 值1 DWORD2: 值2 ... 直到 IB 结束
  • 一旦解析到type=00
  • GPU 进入连续写寄存器模式
  • 后面所有剩余 DWORD 全是寄存器值
  • 直到 IB 长度耗尽才退出这个模式

一个真实可执行的 IB 示例

假设 IB 长度 =9 DWORD

IB 地址: 0x10000000 IB 大小: 9 DWORD DW0: 0xB8106E00 → Type3, SET_SH_REG, count=1 DW1: 0x00001234 → 寄存器偏移 DW2: 0xABCD0001 → 寄存器值 DW3: 0x0000F000 → Type0, 基地址 0xF000 DW4: 0x00000001 → 写 F000 DW5: 0x00000002 → 写 F004 DW6: 0x00000003 → 写 F008 DW7: 0xB8000000 → Type3 NOP DW8: 0xB8506000 → Type3 RELEASE_MEM(但不会执行!)

GPU 执行过程:

  1. 读 DW0 → Type3,count=1→ 包长度 = 1+1 = 2 DW→ 执行 DW0~DW1,跳过 DW2
  2. 当前位置 DW3
  3. 读 DW3 → Type0!→ 进入 “写寄存器直到 IB 结束” 模式→ 剩余 DW 数量 = 9 - 3 = 6 个→ 把 DW3 后面全部 6 个 DW 都当寄存器值写掉
  4. 到达 IB 长度 9 → IB 执行结束
  5. DW7/DW8 根本不会被当成 PM4 解析!

这就是为什么:

Type0 必须放在 IB 最后!

Type0 后面不能跟任何命令!

IB 是怎么被 “启动” 的?PKT3_IB

内核发给 Ring 的命令只有一条:

DW0: 0xB8304000 → Type3 | count=3 | opcode=IB DW1: IB 地址低 32 位 DW2: IB 地址高 32 位 DW3: IB 长度(DWORD 个数)

GPU 收到后:

  1. 跳转到IB 地址
  2. 按上面规则逐条解析 PM4
  3. 执行满IB 长度个 DWORD 后退出
  4. 回到 Ring,继续执行下一条命令

PKT3_IB 命令

PKT3_IB = PM4 Type3 命令,作用:让 GPU 跳转到指定 IB 缓冲区执行 PM4 命令流

  • 类型:Type3(11)
  • 总长度:4 DWORD(1 个头部 + 3 个数据 DWORD)
  • 核心:携带IB 64 位基地址+IB 长度(DWORD 数)

命令格式(完整 4 DWORD)

DWORD0:PM4 Type3 包头

bit31-30: type = 11 (Type3) bit29-16: count = 2 (因为数据段3 DWORD,count=3-1=2) bit15-8 : opcode = 0x40 (IB命令的操作码) bit7-0 : 保留/子功能(通常0)

包头十六进制示例0xB8204000

  • 0xB8= 10111000 → type=11
  • 0x20= count=2
  • 0x40= opcode=IB

DWORD1:IB_BASE_LOW(IB 地址低 32 位)

  • 存放IB 64 位地址的低 32 位(GPU 虚拟地址 / 物理地址)

DWORD2:IB_BASE_HIGH(IB 地址高 32 位)

  • 存放IB 64 位地址的高 32 位

DWORD3:IB_SIZE + 控制位

bit19-0 : ib_size = IB长度(DWORD数,最大1M) bit20 : chain = 0/1(是否链式IB,0=不链) bit21 : offload_polling = 0/1(是否轮询卸载) bit31-22: 保留(填0)

完整十六进制示例(可直接写入 Ring)

假设:

  • IB 地址:0x100000000(64 位)
  • IB 长度:9 DWORD
  • chain=0,offload_polling=0
DWORD0: 0xB8204000 → Type3 | count=2 | opcode=0x40 DWORD1: 0x00000000 → IB_BASE_LOW DWORD2: 0x00000001 → IB_BASE_HIGH DWORD3: 0x00000009 → IB_SIZE=9 | chain=0 | offload=0

GPU 执行流程(收到 PKT3_IB 后)

  1. 解析DWORD0:识别为 Type3 IB 命令,count=2 → 总长度 4 DWORD
  2. 读取DWORD1+2:得到IB 64 位基地址
  3. 读取DWORD3:得到IB 长度(DWORD)+ 控制位
  4. 初始化 IB 执行上下文:
    • IB 指针 = IB_BASE
    • 已读 DWORD = 0
  5. 进入IB 解析循环
  6. IB 执行完毕后,返回 Ring,继续执行下一条命令

关键规则

  1. IB 必须是 GPU 可访问的连续显存(BO 对象)
  2. IB_SIZE 单位是 DWORD(4 字节),不是字节
  3. Type0 必须放在 IB 末尾,否则会覆盖后续命令
  4. 一个 PKT3_IB 只能执行一个 IB;链式 IB 需 chain=1 并在 IB 末尾追加下一个 IB 地址

PM4命令流执行过程

  1. 驱动构建 IB:在显存中分配 IB 缓冲区,按顺序写入Type0/Type2/Type3PM4 包,Type0 必须放在 IB 末尾
  2. 提交 PKT3_IB:驱动向 GPU Ring 发送一条 Type3 命令PKT3_IB,携带 IB 的64 位地址长度(DWORD 数)
  3. CP 解析 PKT3_IB:命令处理器(CP)读取该命令,拿到 IB 的起始地址与总长度。
  4. 初始化执行上下文:设置 IB 读取指针 = IB 基地址,已读 DWORD 计数器 = 0。
  5. 循环解析 PM4 包
    • Type0:无 count,剩余所有 DWORD 都作为寄存器值写入,直到 IB 长度耗尽。
    • Type2:仅 1 个 DWORD,直接跳过(NOP)。
    • Type3:从包头提取 count,计算包总长 = 1+count,执行命令后跳过整个包。
  6. 结束:已读 DWORD 达到 IB 长度时,IB 执行完毕,CP 回到 Ring 继续执行后续命令。

完整流程图

用户态/内核构造 PM4 命令 ↓ 打包进显存 BO → 形成 IB ↓ 构造 PKT3_IB 命令(指向 IB) ↓ 将 PKT3_IB 写入 Ring Buffer ↓ 更新 WPTR 寄存器 → 通知 GPU ↓ GPU CP 读取 Ring → 解析 PKT3_IB ↓ 跳转到 IB → 执行 PM4 命令流 ↓ 执行完成 → 写中断/同步信号

AQL 命令包(Architected Queuing Language)

定位:HSA/ROCm 标准,用户态直接写入硬件队列,专用于计算核调度(Kernel Dispatch),不经过内核命令提交路径。固定大小64 字节(16 DWORD),所有 AQL 包统一长度。队列:用户态管理的环形队列(User Mode Queue),通过 Doorbell 寄存器通知 GPU。

1. AQL 核心包类型

  • AQL_DISPATCH_PACKET:计算核调度(最常用)
  • AQL_BARRIER_PACKET:队列 barrier 同步
  • AQL_AGENT_DISPATCH_PACKET:跨 agent 调度

2. AQL_DISPATCH_PACKET 结构(64 字节)

struct hsa_kernel_dispatch_packet { uint16_t setup; // 包类型/尺寸/完成信号使能 uint16_t header; // 包类型(DISPATCH=1) uint16_t x, y, z; // 网格维度(workgroup 数量) uint32_t reserved0; uint64_t kernel_object; // 内核代码对象指针 uint32_t reserved1; uint32_t private_size; // 私有内存大小 uint32_t group_size_x, group_size_y, group_size_z; // workgroup 大小 uint32_t reserved2[4]; uint64_t kern_arg_address; // 参数地址 uint64_t completion_signal; // 完成信号地址 };
  • 关键:kernel_object指向 ELF 格式的 GPU 内核代码;completion_signal用于用户态同步。

3. AQL 提交流程(用户态旁路内核)

用户态(ROCm/ROCr)构建 AQL 包 → 原子写入用户态队列 → 写 Doorbell 寄存器 → GPU 直接取包执行 → 写 completion_signal → 用户态轮询 / 等待完成。

PM4 vs AQL 核心对比

维度PM4AQL
用途图形渲染、寄存器控制、IB 提交、通用命令计算核(Kernel)调度、HSA/ROCm 专用
大小可变(1+N DWORD)固定 64 字节
执行单元GPU 命令处理器(CP)GPU 计算调度器(KFD)
提交路径内核态(amdgpu_cs_ioctl → Ring)用户态直接写队列(旁路内核)
队列内核管理的 Ring Buffer用户态管理的 AQL Queue
同步Fence、EOP、中断HSA Signal(用户态内存)
架构GCN/RDNA 全架构支持GFX9+/CDNA 计算架构
代码位置Mesa/amdgpu 驱动ROCr/ROCm 运行时

关键关联

  • PM4 是 AQL 的底层实现:AQL 包最终会被 GPU 翻译为 PM4 命令流执行。
  • IB 是 PM4 的容器:用户态构建的 PM4 流通常放在 IB 中,通过PKT3_IB提交。
  • AQL 是 PM4 的高层抽象:面向计算场景,简化核调度,提升用户态控制能力。
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/21 23:47:39

面向高校机房还原卡替代的vDisk云桌面选型与建设参考

面向高校机房还原卡替代的vDisk云桌面选型与建设参考本文针对高校公共教学机房老化硬件还原卡替换需求,提供vDisk云桌面的选型维度、建设步骤与方案对比参考,适合高校机房运维、教育信息化采购负责人参考,由上海澄成信息技术有限公司提供产品…

作者头像 李华
网站建设 2026/4/21 23:45:33

Pixel Aurora Engine实际项目:为Retro Game Jam快速生成200+原创素材

Pixel Aurora Engine实际项目:为Retro Game Jam快速生成200原创素材 1. 项目背景与挑战 1.1 Retro Game Jam的特殊需求 Retro Game Jam是一个专注于复古风格游戏的开发比赛,参与者需要在限定时间内完成一款像素风格的游戏。这类活动最关键的挑战之一就…

作者头像 李华
网站建设 2026/4/21 23:44:32

清洁设计:SaniSure推出PETG PharmaTainer™超洁净试剂瓶和发酵瓶

行业应用最广泛的PETG材料达到行业领先的洁净度、合规性和无RNase/DNase验证标准,现已覆盖完整生物工艺工作流程。SaniSure今日宣布推出PETG PharmaTainer™全新系列生物工艺用试剂瓶和发酵瓶,这些产品将广受认可的医用级Eastman Eastar PETG 6763树脂&a…

作者头像 李华
网站建设 2026/4/21 23:42:46

HTML怎么提升首屏加载_HTML关键资源内联策略【说明】

适合内联的是首屏必需、体积小、无外部依赖的资源:critical.css(≤10KB)和纯同步的theme.js;绝对不可内联fetch、React/Vue启动代码等有依赖或体积大的JS,以及受CSP限制的内联脚本和样式。哪些资源适合内联&#xff0c…

作者头像 李华
网站建设 2026/4/21 23:42:39

MySQL跨版本迁移数据格式不兼容_使用mysqldump全量导出导入

导出时未加--compatible导致5.7导入报错,因5.7默认启用STRICT_TRANS_TABLES而允许非法日期;应使用--compatiblemysql40导出,并统一字符集为utf8mb4,导入后执行ANALYZE TABLE更新统计信息。mysqldump导出时没加--compatible&#x…

作者头像 李华
网站建设 2026/4/21 23:42:18

GLM-OCR与LaTeX工作流集成:自动将手写公式转换为代码

GLM-OCR与LaTeX工作流集成:自动将手写公式转换为代码 每次写论文或者整理笔记,最头疼的是什么?对我而言,绝对是敲LaTeX公式。一个复杂的积分或者矩阵,手动输入不仅慢,还容易出错,反复检查括号和…

作者头像 李华