前段时间社区的雾佬发了一篇使用 gem5 模拟 MI300X 的知乎,正好我最近在验证 AMDGPU 的浮点运算精度,就想着对比一下 gem5 MI300X 的 model 浮点精度和真实硬件有没有什么差异。
这里推荐使用服务器或者工作站来运行 gem5,个人电脑资源可能不太够用。
主要参考雾佬的文章,以及官方提供的文档 Full System AMD GPU model 来搭建。
基本思路是:
- 使用 qemu-system-x86_64 来制作包含 AMDGPU 驱动和 ROCm 环境的镜像,以及所需的内核;
- 使用 gem5 的 kvm 模式,模拟一个 x86 ,将 MI300X 的 model 作为 PCIe 卡集成进去;
- 最后使用 gem5 加载制作好的镜像和内核,就可以使用了。
为了方便,我使用 gem5 提供的 docker 环境来构建。
docker pull ghcr.io/gem5/ubuntu-24.04_all-dependencies:v24-0
在本地拉取 gem5 相关仓库,并创建 gem5 的 docker 容器,然后将仓库所在目录共享给容器:
git clone https://github.com/gem5/gem5.gitgit clone https://github.com/gem5/gem5-resources.git gem5/gem5-resourcesdocker run --name gem5-amdgpu \--device /dev/kvm \--volume /home/zevorn/gem5:/gem5 \--privileged \-it ghcr.io/gem5/ubuntu-24.04_all-dependencies:v24-0
接下来就是编译环节,分两步。
首先在容器外面的 gem5/gem5-resources 仓库里制作需要的镜像和内核:
cd gem5/gem5-resources/src/x86-ubuntu-gpu-ml/./build.sh
这里需要耐心等待,这个构建脚本会使用 packer 来制作镜像,制作成功以后,会生成以下文件:
├── disk-image│ └── x86-ubuntu-gpu-ml├── vmlinux-gpu-ml
然后进入容器内,开始构建 gem5:
cd /gem5scons build/VEGA_X86/gem5.opt -j$(nproc)
我们需要修改默认的 MI300X 的 python 文件,避免运行完退出:
$ git diff configs/example/gpufs/mi300.py@@ -153,7 +153,7 @@ def runMI300GPUFS( ) b64file.write(runscriptStr)- args.script = tempRunscript+ # args.script = tempRunscript # Defaults for CPU args.cpu_type = "X86KvmCPU"
在 gem5 源码路径下新建一个 pytorch_test.py 文件,用于 gem5 运行 mi300 时加载,但实际我们不会执行它:
#!/usr/bin/env python3import torchx = torch.rand(5, 3).to('cuda')y = torch.rand(3, 5).to('cuda')z = x @ y
我们在容器内安装一个 tmux,方便启动 gem5 以后,通过另一个窗格连接进终端:
apt updateapt install tmux
然后启动试试:
build/VEGA_X86/gem5.opt configs/example/gpufs/mi300.py --disk-image gem5-resources/src/x86-ubuntu-gpu-ml/disk-image/x86-ubuntu-gpu-ml --kernel gem5-resources/src/x86-ubuntu-gpu-ml/vmlinux-gpu-ml --app pytorch_test.py
我们使用tmux,在当前窗口再新建一个终端,然后使用下面的命令连接到 gem5:
./util/term/gem5term localhost 3456root@gem5:~#
然后我们需要手动加载 AMDGPU 驱动,可以直接在 gem5 终端输入:
export LD_LIBRARY_PATH=/opt/rocm/lib:$LD_LIBRARY_PATHexport HSA_ENABLE_INTERRUPT=0export HCC_AMDGPU_TARGET=gfx942dmesg -n8cat /proc/cpuinfodd if=/root/roms/mi300.rom of=/dev/mem bs=1k seek=768 count=128
我们来验证一下是否加载成功:
root@gem5:~# rocminfoROCk module version 6.12.12 is loaded=====================HSA System Attributes=====================Runtime Version: 1.15Runtime Ext Version: 1.7System Timestamp Freq.: 0.001000MHzSig. Max Wait Duration: 18446744073709551615 (0xFFFFFFFFFFFFFFFF) (timestamp count)Machine Model: LARGESystem Endianness: LITTLEMwaitx: DISABLEDXNACK enabled: NODMAbuf Support: YESVMM Support: YES...*******Agent 2******* Name: gfx942 Uuid: GPU-XX Marketing Name: AMD Instinct MI300X Vendor Name: AMD Feature: KERNEL_DISPATCH Profile: BASE_PROFILE Float Round Mode: NEAR Max Queue Number: 128(0x80) Queue Min Size: 64(0x40) Queue Max Size: 131072(0x20000) Queue Type: MULTI Node: 1 Device Type: GPU...
接下来就可以正常使用了。
我在测试 gem5 MI300X 的 fma 指令的运算结果是,和 cpu 以及 MI300X 真实硬件进行 bit 级对比后,发现总是相差 3~4 ulp,于是我阅读 fma 的源码,发现是 gem5 用乘加两步运算来模拟的,这导致了和真实硬件的浮点精度存在误差,于是我修改源码:
$ git diff src/arch/amdgpu/vega/insts/instructions.hhdiff --git a/src/arch/amdgpu/vega/insts/instructions.hh b/src/arch/amdgpu/vega/insts/instructions.hhindex 7a328f9230..76bd96beee 100644--- a/src/arch/amdgpu/vega/insts/instructions.hh+++ b/src/arch/amdgpu/vega/insts/instructions.hh@@ -44352,8 +44352,9 @@ namespace VegaISA int lane_A = i + M * (block + B * (k / K_L)); int lane_B = j + N * (block + B * (k / K_L)); int item = k % K_L;- result[i][j] +=- src0[item][lane_A] * src1[item][lane_B];+ result[i][j] =+ std::fma(src0[item][lane_A], src1[item][lane_B],+ result[i][j]); } } }
之后测试就正常了,我将这笔 bugfix,贡献到了上游社区,目前已被合并:arch-vega: Improve MFMA precision to match MI300X hardware