On 2026-09-01 12:54, William Palacek wrote:
svm_migrate_vma_to_vram() is called for windows that are already fully
resident in the target GPU's VRAM. migrate_vma_setup() raises an MMU
notifier invalidate before it can discover there is nothing to collect,
and with retry faults enabled svm_range_cpu_invalidate_pagetables()
special cases only MMU_NOTIFY_UNMAP, so MMU_NOTIFY_MIGRATE is handled by
its default arm, reaching svm_range_evict() and then
svm_range_unmap_from_gpus().

The attempt therefore drops the window's GPU mapping and then returns
having migrated nothing, so the next fault on that window is escalated
into another migration instead of being satisfied by mapping alone.

Measured on MI300X (gfx942, SPX/NPS1, xnack+) under an HMM
oversubscription workload, 96 to 97% of to-VRAM migration attempts
collected zero pages, and the driver unmapped a window roughly 35 times
for every migration that moved data.

Test whether every page of the faulting window is already resident in
this node's VRAM and return early if it is. prange->actual_loc alone is
not sufficient: since commit a546a2768440 ("drm/amdkfd: Use partial
migrations/mapping for GPU/CPU page faults in SVM") migration is per
window, so a range can report actual_loc == best_loc while part of it has
been evicted back to system memory. 3.3% of attempts were in exactly that
state and did have pages to move, so the per-page test is required rather
than the scalar alone. Instrumented over 100000 attempts on an earlier
run against a different driver build, the per-page test never skipped one
that had pages to move.

The actual_loc comparison is kept alongside the per-page test because
SVM_RANGE_VRAM_DOMAIN records only that a page is in some device's VRAM,
not which device's.

With this applied the fault driven phase of that workload falls from 270
to 348 s down to 45 to 48 s, across 9 patched and 7 unpatched runs with
no overlap at either oversubscription factor tested. page_in at the end
of that phase does not fall with it: 44.5 to 48.3M pages patched against
44.1 to 49.7M unpatched at factor 1.10, and 46.2 to 48.0M against 44.1 to
45.7M at factor 1.02. Those ranges overlap at 1.10, so the claim is that
no data is stranded, not that more moves. Unmaps per productive migration
fall from about 35 to about 2. Retry fault counts do not fall in any arm,
so this is not a refault loop; what changes is how often a fault
escalates into a migration.

The scan is bounded by the migration window, one granule on the fault
path and the whole range on the prefetch path. That is the same order as
the migrate_vma_setup() walk it avoids, and it returns at the first page
that is not resident, so the worst case is one extra pass over a range
that then migrates normally.

Signed-off-by: William Palacek <[email protected]>
---
  drivers/gpu/drm/amd/amdkfd/kfd_migrate.c | 46 ++++++++++++++++++++++++
  1 file changed, 46 insertions(+)

diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_migrate.c 
b/drivers/gpu/drm/amd/amdkfd/kfd_migrate.c
index c3411dacdf55..e91d0115cbba 100644
--- a/drivers/gpu/drm/amd/amdkfd/kfd_migrate.c
+++ b/drivers/gpu/drm/amd/amdkfd/kfd_migrate.c
@@ -384,6 +384,46 @@ svm_migrate_copy_to_vram(struct kfd_node *node, struct 
svm_range *prange,
        return r;
  }
+/* Check if a range of addresses is already resident in VRAM to avoid
+ * unnecessary migrations that would trigger MMU notifiers causing the range
+ * to be unmapped and faulted again unnecessarily.
+ *
+ * Called with prange->migrate_mutex held.
+ */
+static bool
+svm_range_window_resident(struct kfd_node *node, struct svm_range *prange,
+                         u64 start, u64 end)
+{
+       struct kfd_process *p = container_of(prange->svms, struct kfd_process, 
svms);
+       unsigned long i, offset, npages;
+       dma_addr_t *addr;
+       uint32_t gpuid;
+       int32_t gpuidx;
+
+       if (kfd_process_gpuid_from_node(p, node, &gpuid, &gpuidx))
+               return false;
+       if (prange->actual_loc != gpuid)
+               return false;
+
+       addr = prange->dma_addr[gpuidx];
+       if (!addr)
+               return false;
+
+       start = max(start >> PAGE_SHIFT, prange->start);
+       end = min(end >> PAGE_SHIFT, prange->last + 1);
+       if (end <= start)
+               return false;

This should probably return true. If the window does not overlap with prange, then there is also nothing to migrate. With that fixed, the patch is

Reviewed-by: Felix Kuehling <[email protected]>


+
+       offset = start - prange->start;
+       npages = end - start;
+
+       for (i = offset; i < offset + npages; i++)
+               if (!(addr[i] & SVM_RANGE_VRAM_DOMAIN))
+                       return false;
+
+       return true;
+}
+
  static long
  svm_migrate_vma_to_vram(struct kfd_node *node, struct svm_range *prange,
                        struct vm_area_struct *vma, u64 start,
@@ -401,6 +441,12 @@ svm_migrate_vma_to_vram(struct kfd_node *node, struct 
svm_range *prange,
        void *buf;
        int r = -ENOMEM;
+ /* Avoid unnecessary migrations and MMU notifiers if all the data is
+        * already resident in VRAM.
+        */
+       if (svm_range_window_resident(node, prange, start, end))
+               return 0;
+
        memset(&migrate, 0, sizeof(migrate));
        migrate.vma = vma;
        migrate.start = start;

Reply via email to