QEMU 训练营 2026 专业阶段总结¶
主要贡献者
- 作者:@cn-sys
背景介绍¶
大三计算机学生,上学期学 C++ 和并发编程,下学期机缘巧合接触到 AI Infra 方向,正好碰上 QEMU 训练营。对 GPU 怎么工作的很好奇,于是选了 GPGPU 方向。
专业阶段¶
完成了 GPU 方向全部实验,17 道测试题覆盖了从 PCI 设备注册到低精度浮点转换的完整流程。以下按理解深度排序,记下印象最深的几个点。
设备模型:外设就是一块特殊的内存¶
实验从实现一个 PCI 设备开始。以前学计组只知道「MMIO 是内存映射 I/O」,但亲手写一遍才真正理解:
memory_region_init_io(&s->ctrl_mmio, OBJECT(s), &gpgpu_ctrl_ops, s,
"gpgpu-ctrl", GPGPU_CTRL_BAR_SIZE);
pci_register_bar(pdev, 0, ..., &s->ctrl_mmio);
CPU 往某个地址写一个值 → QEMU 拦截 → 路由到 gpgpu_ctrl_write → 根据偏移量决定写的是哪个寄存器。外设本质上就是一片有特殊含义的内存地址空间,写进去的值被解释成命令而非数据。
这个设备有三块 BAR 空间: - BAR0:控制寄存器(1MB MMIO),设备 ID、全局控制、中断、kernel 配置、DMA、SIMT 上下文全在这 - BAR2:显存(64MB),kernel 代码和数据放这里 - BAR4:Doorbell(64KB),MSI-X 配套
寄存器语义:比特位不是存数值的¶
DEV_ID 寄存器固定返回 0x47505055,按字节读出来是 "GPPU"——它存在的意义不是「存一个数」,而是告诉 CPU「我是谁」。
GLOBAL_CTRL 的 bit 0 是 ENABLE,bit 1 是 RESET。往 bit 0 写 1 是踩油门,往 bit 1 写 1 是重启。寄存器本质上是一组有语义的引脚,软件去拨弄它们的状态。
SIMT 执行模型:Block → Warp → Lane¶
这是最让我开窍的部分。GPU 没有几十个复杂核心,而是几千个傻子——Lane。
每个 Lane 就是一个极简的 RISC-V 处理器:32 个通用寄存器、32 个浮点寄存器、一个 PC。没有分支预测、没有乱序执行、没有缓存一致性,就是一个循环:取指令 → 执行 → PC += 4。
32 个 Lane 绑成一个 Warp,锁步执行同一条指令,只是操作的数据不同。这就是 SIMT。
Warp 之间通过时间片轮转隐藏内存延迟——一个 warp 等数据的时候切到另一个 warp 去算。虽然我实现的只是简单的遍历,但理解了为什么真实 GPU 需要上百个 warp:就是让计算单元永远有活干。
指令解释器:RISC-V 的简洁¶
我实现了 RV32I 整数子集和 RV32F 浮点子集的指令解释器。32 位指令编码极其规整:
bits [6:0] = opcode(指令大类)
bits [14:12] = funct3(大类内细分)
bits [11:7] = rd(目标寄存器)
bits [19:15] = rs1(源寄存器 1)
bits [24:20] = rs2(源寄存器 2)
核心就是一个大的 switch-case:
对于浮点指令,我用 bits[31:25] 直接区分 fadd、fmul、fcvt 等操作,比标准 RISC-V 的 funct5+rs2+funct7 组合编码简单得多。
低精度浮点:数学的取舍¶
FP8(E4M3)、FP8(E5M2)、FP4(E2M1)——之前只听说过这些词,这次亲手写了格式转换。
E4M3:1 位符号、4 位指数(偏置 7)、3 位尾数。最大只到 448。输入 1000 直接饱和成 448。
E2M1 更离谱:总共 4 位,2 位指数 + 1 位尾数,最大才 6。
写转换函数的时候才明白——浮点格式不过是对实数到二进制位的映射契约,调整指数和尾数的位数就是在精度和范围之间做取舍。
总结¶
前段时间对计算机组成原理提起了兴趣,又去好好学了一遍,接着就去完成本次实验,过程感觉非常顺畅,这个实验让我从「一个能跑操作系统的模拟器」往下挖了一层,看到了硬件是怎么被软件驱动的。