当前位置: 首页 > news >正文

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 的高层抽象:面向计算场景,简化核调度,提升用户态控制能力。
http://www.jsqmd.com/news/679131/

相关文章:

  • 2026年Q2高性价比尼龙布厂家排行:结构粘接胶膜、绝缘与屏蔽膜、自修复车衣、航空级尼龙布、航空阻燃标准尼龙布选择指南 - 优质品牌商家
  • TVA技术在能源行业的应用综述
  • 2026年4月21日60秒读懂世界:阅读与手机时间、汽车价格战、脑机接口临床提速,今天最值得关注的6个信号
  • 你的服务器在“偷懒”吗?用turbostat揪出Linux下CPU空闲与C-State的真相
  • 深入Canfestival定时器内核:手把手解析TimeDispatch函数与STM32 HAL库适配
  • 组合导航 | 双目视觉 + 激光雷达 + NRTK的三融合方案
  • TVA技术在洗煤车间检测中的场景适配与工艺优化
  • 别只当数据搬运工了!深入STM32H7的DMA FIFO与突发传输,提升你的系统带宽(内存位宽不匹配怎么办)
  • 2026欧料小提琴排行:高端定制小提琴/大师级小提琴/天然虎纹小提琴/实木小提琴/意大利小提琴/收藏小提琴/欧料小提琴/选择指南 - 优质品牌商家
  • 大模型与端侧的握手:从0到1拆解侠客工坊手机真AI员工的底层技术链路
  • 2026彩钢活动房技术分享:兰州彩钢活动房、兰州箱式房、兰州钢结构公司、兰州钢结构加固、兰州钢结构加工厂、兰州钢结构厂房选择指南 - 优质品牌商家
  • C#调用本地大模型推理速度翻倍实录(.NET 11 JIT-AI协同编译深度拆解)
  • Unity基础:UI组件详解:Slider滑动条的用法与值获取
  • iTop开源ITSM平台:从混乱到秩序的IT服务管理转型实战
  • 碧蓝航线Alas脚本完整指南:自动化游戏终极解决方案
  • 企业网实战:如何用华为三层交换机Vlanif+OSPF,低成本搞定多部门隔离与互通?
  • 具身智能(32):Holo Brain开源模型
  • SAP PP模块实战:不用BDC,如何用ABAP代码批量导入生产版本(MKAL)并搞定红绿灯检查
  • TensorRT模型转换避坑实录:trtexec从编译到成功运行kp.trt,我踩过的那些坑
  • 业务决策者如何看懂iPaaS集成平台的投资价值
  • 应用监控详解
  • 终极高效炉石传说BepInEx插件完整指南:55+功能深度优化方案
  • 告别“一锤子买卖”:给你的Xilinx FPGA设计加上Multiboot双镜像冗余备份
  • 解决NaViL-9B部署常见问题:从环境配置到服务启动全攻略
  • HTML5中通过MessageChannel实现多个Worker间直接通信
  • 如何在Android应用中实现PDF打印功能:5个步骤集成AndroidPdfViewer与PrintManager
  • 从OOM到零事故:某支付平台迁移Java 25虚拟线程后,如何通过“可审计虚拟线程池+上下文签名链”实现100%调用链安全溯源
  • 日志体系详解
  • 深度解析:如何通过可视化即代码重塑神经网络架构设计思维
  • SSV6155/6255 WiFi驱动加载失败?从硬件检查到内核日志的完整调试指南