结论:tinygrad 的 GPU 内存层把职责切成三层——分配地址、建立映射、拥有资源——并把「映射建立」和「硬件可见」明确分开。通用函数只保证 PTE 文本与虚拟地址记账,真正的 TLB 可见性由设备专用钩子 on_range_mapped() 或外层队列承担。valloc() 还存在失败路径:物理页分配失败分支释放了物理页,却没有对应释放已取得的 VA,静态上存在失败后保留地址配额的条件性风险。
三层职责
基线是固定归档 memory.py 及其 PCI、AMD/NV/RDMA 调用方;本文解析计算后端的内存基础设施,不代表 openpilot 默认推理路径必经每种后端。仅静态阅读与小型无设备探针,未运行外部 GPU 页表测试,也未验证目标硬件或整模型。
分配地址由 BumpAllocator.alloc()(固定范围内的线性/环形偏移)和 TLSFAllocator.alloc()(两级桶找空闲块、按请求对齐并拆块,free() 插回并合并相邻空闲块)负责。MemoryManager 持有全局 VA TLSF,以及每设备 boot、页表和普通物理内存池。建立映射由 VirtMapping(记录 VA、大小、物理段、地址空间和缓存属性)和 PageTableTraverseContext.next() 完成;map_range() 先检查目标 PTE 未占用,再写 PTE 并调用设备钩子。拥有资源则收敛到 valloc()/vfree()。
在这三层之上,PCIIfaceBase.alloc() 用同一套机制分配 VRAM 或映射锁页系统内存,PCIIfaceBase.map() 则把本机 host 或同一 peer group 的 GPU 缓冲映射到设备 VA。也就是说,插件看到的「一块可读写内存」背后同时牵扯虚拟地址池、物理页池和页表遍历三种资源,任一层记账不一致,都可能表现为越界或数据不一致,而不是立刻报错。
重要函数与行为契约
| 函数 | 输入/输出 | 需要核对的契约 |
|---|---|---|
BumpAllocator.alloc() |
size, alignment 到地址 |
wrap=True 时满池重置指针,调用方需保证旧引用已失效;wrap=False 才在空间不足时报错。 |
TLSFAllocator.alloc() |
请求尺寸/显式 align 到地址 |
block_size 是最小分配量,不是默认返回地址对齐值;默认 align=1。 |
map_range() |
VA、物理段、地址空间/缓存位到 VirtMapping |
size == sum(segment sizes),拒绝已占用 PTE;实际 PTE 格式由设备 pt_t 实现。 |
unmap_range() |
VA/长度到清除 PTE | 对未映射 PTE 断言;是否需额外 TLB shootdown 取决于设备钩子/调用方,不能只凭通用函数认定完成。 |
valloc()/vfree() |
分配/释放 VRAM 及其映射 | GMMU=0 改为 identity VA;普通路径分配分段物理页并建页表。 |
此外,PageTableTraverseContext.level_down()/level_up() 按 create_pts/free_pts/inspect 模式创建、释放或只读遍历,释放模式要求映射存在;alloc_vaddr() 的请求使用 max(最高置位 2 的幂, align) 对齐,不同设备共享类级 VA 池。
页表遍历按页覆盖范围、对齐和设备支持的大页向下建表或向上归并;TLSF 侧的 _insert_block()、_split_block()、_merge_block() 依赖 blocks 中保存的前后邻接与空闲位,所以调用方必须成对管理 alloc/free,否则空闲桶与真实占用会失真。
可复现的边界与测试状态
| 边界 | 源码可见事实 |
|---|---|
| Bump 超大请求 | wrap=True 时只在第一次比较中决定回绕,没有再检验 size <= capacity。无设备探针 BumpAllocator(16).alloc(32) 返回地址 0,内部 ptr=32 > size=16;属通用分配器边界,不等于默认车端已触发。 |
| TLSF 对齐语义 | 探针 TLSFAllocator(128, block_size=16) 连续 alloc(20), alloc(35) 返回 0,20;显式 align=16 则返回 0,32。 |
| VA 失败回滚 | valloc() 取得 VA 后,物理页分配失败分支会释放已分配的物理页,但没有对应 va_allocator.free(va);map_range() 抛错时也未在 valloc() 层回收。 |
| 映射与缓存 | map_range() 对 PTE 重叠做预检查,但硬件 TLB 可见性由设备专用 on_range_mapped() 或外层队列承担。 |
| 测试状态 | external_test_tlsf.py 把 block_size 当默认对齐、使用旧的 start_addr 构造/属性;本轮运行 8 项,5 通过、1 失败、2 错误。不能把这 3 项直接归因于运行时内存损坏。 |
AddrSpace.PHYS/SYS/PEER、uncached/snooped 不是可随意互换的插件参数;错误声明可能导致一致性故障,不能用 Python 层写读一致代替 GPU 校验。未运行的硬件测试方面,external_test_am.py 涉及 AMD 页表/设备,external_fuzz_tlsf.py 默认分配 1 GiB 做长时间 fuzz,本轮均未执行。
对自制硬件及插件的约束
计算设备插件应声明可用 VA 区间、页大小/大页能力、物理内存区域、对齐、缓存与 snoop 位、host/P2P 映射能力,以及 map → submit → synchronize → unmap → free 的所有权顺序。验证路径优先用无设备分配器不变量测试和 mock 页表故障注入;再在目标板验证映射重叠拒绝、OOM 回滚、TLB 可见性、跨设备 P2P 与反复热重启。内存插件只能影响计算数据通路,不得自行取得车辆 CAN 发送或 panda 安全授权。
验收顺序也应与所有权顺序一致:先声明资源边界,再跑无设备不变量测试,最后才上目标板做映射重叠拒绝、OOM 回滚、TLB 可见性、跨设备 P2P 与反复热重启。任何一次「分配成功」都不能被解读为对车辆总线或安全策略的授权,因为内存层与车辆控制本就是两个相互独立的边界。
对自制硬件而言,更稳妥的做法是把内存层当作可隔离、可回滚的计算资源服务,而不是与车辆控制共享状态的可信组件。
常见问题
tinygrad 的 map_range() 成功就等于映射对 GPU 可见了吗?
不等于。map_range() 只做「检查 PTE 未占用、写 PTE、调用设备钩子」这一层,硬件 TLB 可见性由设备专用 on_range_mapped() 或外层队列承担;unmap_range() 是否需额外 TLB shootdown 也取决于具体设备钩子/调用方,不能只凭通用函数认定完成。
valloc() 失败时地址配额会被回收吗?
按源码存在条件性风险。valloc() 取得 VA 后,物理页分配失败分支会释放已分配的物理页,但没有对应 va_allocator.free(va);map_range() 抛错时也未在 valloc() 层回收 VA/物理页。静态上存在失败后保留地址配额的可能,需用故障注入验证具体设备恢复行为。
BumpAllocator 的 wrap=True 有什么坑?
wrap=True 时满池会重置指针,调用方需保证旧引用已失效;且源码只在第一次比较中决定回绕,没有再检验 size 是否小于等于 capacity。无设备探针 BumpAllocator(16).alloc(32) 返回地址 0、内部 ptr=32 大于 size=16。对永久 VA 或环形命令缓冲,应在调用侧/分配器层拒绝超大请求。
为什么不能把 uncached/snooped 当普通插件参数?
因为 AddrSpace.PHYS/SYS/PEER、uncached/snooped 决定映射的缓存与一致性属性,源码指出它们不是可随意互换的插件参数,错误声明可能导致一致性故障;也不能用 Python 层的写读一致代替 GPU 校验。
延伸阅读
- tinygrad 张量调度与内存 —— 承接本文分配器与映射之上的调度视角。
- tinygrad 的 QCOM KGSL 运行时 —— 说明车端后端如何消费这类内存基础设施。
- tinygrad AMD 设备启动与部分复位生命周期 —— 对照设备初始化/复位时的资源所有权。
- 硬件后端 USB 快照契约 —— 说明内存插件为何不能越界取得发送/安全授权。
参考资料
- 项目 02 ——
openpilot-research/TINYGRAD_GPU_MEMORY_ALLOCATOR_PAGETABLE_OWNERSHIP_DEEP_DIVE.md(取用了三层职责、函数契约表、Bump/TLSF/VA 回滚边界与自制硬件约束)。 - 项目 02 ——
openpilot-research/TINYGRAD_AMD_DEVICE_IP_BOOT_PARTIAL_RESET_LIFECYCLE_DEEP_DIVE.md(仅用于确认设备启动/复位专题的接续关系,未取用其未公开内容)。
说明:本文为对固定源码快照的静态结构分析,未运行完整测试套件、未连接硬件、未验证目标车型的真实参数组合。文中所有阈值、行号索引与配置均出自源文档,标注为未验证的结论保留其原有边界。
更新记录
首次发布 2026-10-11 最近核对:2026-10-11