AMDGPU KFD驱动中,Queue Quiesce/Restore机制是保证GPU内存一致性的关键,但其粗粒度设计在性能敏感场景下可能成为瓶颈。本文深入分析该机制的实现原理、触发场景,并探讨可行的优化路径,帮助开发者理解并改进驱动性能。
1. 核心问题:Per-Process粒度的Queue管理
在AMDGPU KFD驱动中,当某个BO或SVM range需要被evict或invalidate时,驱动会quiesce(停止)该进程的所有user queues,而不仅仅是引用了该BO的queues。如amdgpu_amdkfd_fence.c中的注释所述:
这意味着,即使某个queue完全不访问被evict的BO,它也会被一起停掉。这是一个per-process、all-or-nothing的操作,粒度较粗。Big Idea - Since KFD submissions are done by user queues, a BO cannot be
evicted unless all the user queues for that process are evicted.
2. 触发Quiesce/Restore的9大场景
当前内核代码中共有9个场景会触发进程级别的queue quiesce或restore,最终都通过kfd_process_evict_queues()/kfd_process_restore_queues()执行。这些场景可分为三类:
- SVM相关(3个场景):包括MMU Notifier Invalidation(XNACK Off)、Queue-Vital Buffer被Unmap、SVM Range Restore Work。其中MMU notifier路径是运行时触发频率最高的。
- TTM BO Eviction相关(2个场景):VRAM压力Eviction和TTM Restore,影响较大但频率较低。
- 系统级与CRIU相关(4个场景):System Suspend/Resume和CRIU Checkpoint/Restore,属于低频操作。
具体触发条件与行为如下:
- 场景1(SVM MMU Notifier):CPU page table变更且XNACK关闭时,第一个range被evict时quiesce所有queues,后续range只递增计数器。恢复时调度
svm_range_restore_work,延迟1ms后尝试restore。/* kfd_svm.c - svm_range_evict() */ evicted_ranges = atomic_inc_return(&svms->evicted_ranges); if (evicted_ranges != 1) return r; /* First eviction, stop the queues */ r = kgd2kfd_quiesce_mm(mm, KFD_QUEUE_EVICTION_TRIGGER_SVM); - 场景2(Queue-Vital Buffer Unmap):被unmap的range有非零
queue_refcount时,立即quiesce所有queues,无对应restore路径。/* kfd_svm.c - svm_range_unmap_from_cpu() */ if (atomic_read(&prange->queue_refcount)) { pr_warn("Freeing queue vital buffer 0x%lx, queue evicted\n", ...); r = kgd2kfd_quiesce_mm(mm, KFD_QUEUE_EVICTION_TRIGGER_SVM); } - 场景3(SVM Range Restore Work):遍历所有evicted ranges,重建GPU映射,成功后恢复queues。失败时重调度restore work。
- 场景4-9:涵盖TTM eviction/restore、系统挂起/恢复、CRIU checkpoint/restore等,具体细节见原文。
所有路径的共同特征:quiesce/restore都是per-process粒度,没有per-queue或per-BO的细粒度控制。
| 类别 | 场景 | Quiesce 入口 | Restore 入口 | 触发频率 |
|---|---|---|---|---|
| SVM | MMU notifier invalidation | 中-高 | ||
| SVM | Queue-vital buffer unmap | 无 | 极低 | |
| TTM | VRAM 压力 eviction | 低-中 | ||
| 系统 | Suspend / Resume | 极低 | ||
| CRIU | Checkpoint / Restore | 后续流程 | 极低 |
3. 架构原因:为何选择Per-Process粒度
这种设计并非疏忽,而是由架构约束决定的:
- Per-Process VM共享:KFD的GPU虚拟内存管理采用per-process VM模型,同一进程的所有user queues共享同一个GPU page table。在queue创建时,page table base取自进程级的
qcm_process_device,而非queue自身:/* kfd_device_queue_manager.c - add_queue_mes() */ queue_input.process_id = pdd->pasid; queue_input.page_table_base_addr = qpd->page_table_base; // 进程级 page table queue_input.process_va_start = 0; queue_input.process_va_end = adev->vm_manager.max_pfn - 1;qpd->page_table_base是在register_process()时设置的进程级page directory。当某个BO或SVM range的GPU page table entry被invalidate后,任意queue的shader都可能访问到已失效的VA,驱动只能保守地停掉所有queues。 - 无法建立BO→Queue映射:User Queue模型下,内核不参与提交过程,无法知道每个queue访问了哪些BO。与传统图形提交(如amdgpu CS ioctl)不同,KFD的queue创建和内存分配是独立的ioctl,没有建立关联。
图形提交模型: App → ioctl(CS, bo_list) → Kernel(知道 BO 集合) → GPU User Queue 模型:App → 直接写 doorbell → GPU(内核不可见)
| 约束 | 影响 |
|---|---|
| User Queue 模型,内核不参与提交 | 无法得知每个 queue 访问了哪些 BO |
| 无 BO → Queue 映射关系且无法有效建立 | 无法做选择性 eviction |
4. 性能影响分析与优化方向
在9个场景中,SVM路径(场景1)是运行时主要瓶颈,其性能影响体现在三个阶段:
- Quiesce阶段:开销较小,有去重机制控制频率。
/* 只在第一个 range 被 evict 时 quiesce,后续只递增计数器 */ evicted_ranges = atomic_inc_return(&svms->evicted_ranges); if (evicted_ranges != 1) return r; - Restore阶段:主要性能瓶颈,
svm_range_restore_work()的开销包括:三把锁串行获取(process_info->lock→mmap_write_lock→svms->lock),全量遍历range list:
即使只有1个range被invalidate,也要遍历整个list。逐range validate_and_map涉及list_for_each_entry(prange, &svms->list, list) { invalid = atomic_read(&prange->invalid); if (!invalid) continue; // 大量 range 在这里跳过 svm_range_validate_and_map(...); }get_user_pages和GPU page table写操作。 - Thrashing风险:restore过程中若有新的invalidation,
atomic_cmpxchg会失败,导致restore重来:
在CPU内存活动频繁的场景下,可能出现反复抖动。if (atomic_cmpxchg(&svms->evicted_ranges, evicted_ranges, 0) != evicted_ranges) goto out_reschedule; // 全部重来
TTM路径(场景4)触发频率较低,但单次开销更大,包括等待GPU工作完成、BO migration和100ms的restore延迟。
| 因素 | SVM 路径 | TTM 路径 |
|---|---|---|
| 触发频率 | 中-高(CPU 内存活动驱动) | 低-中(VRAM 压力驱动) |
| Quiesce 开销 | 低(有去重) | 低 |
| Restore 延迟 | 1ms 起步 + validate 时间 | 100ms 起步 + BO restore 时间 |
| 主要瓶颈 | 全量遍历 range list + 锁竞争 | BO validate + VRAM 分配 |
| Thrashing 风险 | 高(CAS 失败重调度) | 中(进程间互相 evict) |
| GPU 空转时间 | 与 invalid range 数量成正比 | 与 BO 总量成正比 |
在不改变XNACK off的前提下,以下优化值得探索:
- 优化1:Evicted List替代全量遍历(推荐,低成本高收益):引入独立的evicted list,只追踪被invalidate的ranges:
收益:restore时间从O(total_ranges)降为O(evicted_ranges)。/* 当前实现:O(total_ranges) */ list_for_each_entry(prange, &svms->list, list) { if (!atomic_read(&prange->invalid)) continue; svm_range_validate_and_map(...); } /* 优化后:O(evicted_ranges) */ list_for_each_entry_safe(prange, next, &svms->evicted_list, evict_link) { svm_range_validate_and_map(...); list_del(&prange->evict_link); } - 优化2:Batch Coalescing(不可行):MMU notifier的正确性contract要求回调返回前必须停止GPU访问,不能延迟quiesce。
- 优化3:锁粒度优化(值得探索):将restore拆分为两阶段,锁外准备page数据,短暂持锁更新GPU page table,减少对并发路径的阻塞。
| 方向 | 原因 |
|---|---|
| Per-queue eviction | User queue 模型下无法得知 queue 访问了哪些 BO(见第二章分析) |
| 延迟 quiesce + GPU fault recovery | XNACK off 下 VM fault 是 fatal 的,等于重新实现 XNACK |
[AFFILIATE_SLOT_1]
5. 终极方案:XNACK On模式
在XNACK on模式下,GPU MMU支持retry fault,当shader访问无效页面时,GPU会触发page fault并重试指令,而不是崩溃。这意味着:
- SVM路径的quiesce可以完全消除:MMU notifier回调中不再需要quiesce queues,只需更新GPU page table并invalidate TLB。shader在访问旧映射时会触发fault,GPU重试后读取新映射。
- 性能收益显著:避免per-process quiesce/restore的开销,GPU无需等待CPU完成page table更新,大幅降低SVM路径的延迟。
- 适用场景:对于HPC、AI训练等频繁内存访问和迁移的场景,XNACK on能显著提升GPU利用率。
然而,XNACK on需要硬件支持(如AMD MI200系列及以上),且可能引入额外的fault处理开销。对于不支持XNACK的硬件,仍需依赖per-process quiesce/restore机制。
[AFFILIATE_SLOT_2]
6. 总结与建议
AMDGPU KFD驱动的Queue Quiesce/Restore机制是保证GPU内存一致性的关键,但其per-process粒度在性能敏感场景下成为瓶颈。通过引入evicted list、优化锁粒度等渐进式改良,可以在XNACK off模式下缓解性能问题。对于支持XNACK的硬件,启用XNACK on模式可从根本上消除SVM路径的quiesce/restore开销,是终极优化方案。
开发者应根据硬件能力和应用场景选择合适的优化策略,平衡正确性与性能。未来,随着硬件和驱动的演进,更细粒度的queue管理机制有望进一步释放GPU性能。
svm_range_evict()svm_range_restore_work()svm_range_unmap_from_cpu()evict_process_worker()restore_process_worker()kfd_suspend_all_processes()kfd_resume_all_processes()criu_checkpoint/restore()
浙公网安备 33010602011771号