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中的注释所述:

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.

这意味着,即使某个queue完全不访问被evict的BO,它也会被一起停掉。这是一个per-process、all-or-nothing的操作,粒度较粗。

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 入口触发频率
SVMMMU notifier invalidation中-高
SVMQueue-vital buffer unmap极低
TTMVRAM 压力 eviction低-中
系统Suspend / Resume极低
CRIUCheckpoint / 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
这是一个典型的correctness vs. performance的trade-off:以粗粒度保证正确性,用简单的实现换取可维护性。

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->lockmmap_write_locksvms->lock),全量遍历range list:
    list_for_each_entry(prange, &svms->list, list) {
    invalid = atomic_read(&prange->invalid);
    if (!invalid)
    continue;   // 大量 range 在这里跳过
    svm_range_validate_and_map(...);
    }
    即使只有1个range被invalidate,也要遍历整个list。逐range validate_and_map涉及get_user_pages和GPU page table写操作。
  • Thrashing风险:restore过程中若有新的invalidation,atomic_cmpxchg会失败,导致restore重来:
    if (atomic_cmpxchg(&svms->evicted_ranges, evicted_ranges, 0) !=
    evicted_ranges)
    goto out_reschedule;   // 全部重来
    在CPU内存活动频繁的场景下,可能出现反复抖动。

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:
    /* 当前实现: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);
    }
    收益:restore时间从O(total_ranges)降为O(evicted_ranges)。
  • 优化2:Batch Coalescing(不可行):MMU notifier的正确性contract要求回调返回前必须停止GPU访问,不能延迟quiesce。
  • 优化3:锁粒度优化(值得探索):将restore拆分为两阶段,锁外准备page数据,短暂持锁更新GPU page table,减少对并发路径的阻塞。

方向原因
Per-queue evictionUser queue 模型下无法得知 queue 访问了哪些 BO(见第二章分析)
延迟 quiesce + GPU fault recoveryXNACK 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()