Hi Christian,
while testing this series (applied on top of 7.2.6) we hit a regression
in amdgpu_vm_update_leaves() that deterministically produces MMHUB
page faults under KFD SVM load on gfx906 (MI50 32GB, Vega 20, PCIe via
Thunderbolt 3 eGPU).
> + pt = cursor.parent->bo;
> + shift = amdgpu_vm_pt_level_shift(adev, cursor.level - 1);
> + mask = amdgpu_vm_pt_entries_mask(adev, cursor.level - 1);
> +
> + /* Looks good so far, calculate parameters for the update */
> + pe_start = ((cursor.pfn >> shift) & mask) * 8;
> +
> + entry_end = ((uint64_t)mask + 1) << shift;
> + entry_end += cursor.pfn & ~(entry_end - 1);
> + entry_end = min(entry_end, end);
> +
> + nptes = (entry_end - cursor.pfn) >> shift;
> + /*
> + * This can happen when we set higher level PDEs to unmap and/or
> + * silent to stop fault floods.
> + */
> + nptes = max(nptes, 1u);
> +
> + trace_amdgpu_vm_update_ptes(params, cursor.pfn, entry_end,
> + min(nptes, 32u), dst, 0, flags,
> + tgid,
> + vm->immediate.fence_context);
> + amdgpu_vm_pte_update_flags(params, to_amdgpu_bo_vm(pt),
> + cursor.level - 1, pe_start, dst,
> + nptes, 0, flags);
When amdgpu_vm_pt_descendant() cannot descend (a gap — the position has
no leaf table in the tree), the write targets the parent's slot at
level-1, i.e. a PDE slot. nptes as computed above covers *every*
remaining slot up to entry_end (the end of the parent-table span,
clamped to the op end) — not just the slot the cursor is at.
The old code had an explicit clamp for this exact case, in
amdgpu_vm_ptes_update() under params->unlocked:
if (cursor.level < AMDGPU_VM_PTB && params->unlocked)
/* only clear one entry. Next entry search again for PDE or
* PTE leave.
*/
entry_end = 1ULL << shift;
That clamp was lost in the rewrite. Without it, an unlocked unmap
(PATCH 08/13 wires amdgpu_vm_unmap_range() to update_leaves()) that
hits a gap *before* still-linked sibling subtrees will clear their
parent slots too:
- the gap's unmap write covers N parent slots (we observed runs like
pe 0x58 n15, pe 0x60 n14, pe 0x68 n13 — each gap position
re-clearing the whole remaining tail of the parent page),
- sibling PDE slots inside that span still hold live child pointers,
- the write overwrites them with the PDE_PTE-only marker
(0x40000000000000 in RAM — bit 54 set, no address),
- the in-memory PT tree still considers those subtrees linked, so
their needs_update is never set and a later amdgpu_vm_update_pdes()
pass does not re-link them,
- the next GPU page walk through that slot reads a non-pointer and
faults: "memory access fault ... reason: PTE not valid" (MMHUB
no-retry).
Captured write ordering (instrumented amdgpu_vm_pte_update_flags /
amdgpu_vm_pde_update, job ids tagged):
MAP_RANGE PTEs pe 0x368..0x800 (big map)
UPDATE_PDES pde 0x0..0x1a8 links, incl. [0x80] -> child 0x5d4dd0000
MAP_RANGE PTEs pe 0x58..0x1e0 (re-map)
UNMAP_RANGE clear pe 0x58 n15 -> covers 0x58..0xc8, incl. 0x80
UPDATE_PDES relinks 0x58..0x78, 0xf0..0x1f0 -- but not 0x80..0xe8
FAULT slot 0x80 reads 0x40000000000000,
tree still expects 0x5d4dd0001
The unmap's clear overshoots slot 0x80; the relink pass skips it
because the subtree was never dropped from the tree. This is fully
deterministic — our reproducer (vLLM/ROCm loading a 10-shard model,
i.e. heavy SVM map/unmap/re-add churn) faulted at the same stage on
every run.
Locally we fixed it by restoring the single-entry semantics for
non-leaf slot writes:
/* only clear one entry, next entry search again for PDE/PTE leaf */
if (cursor.level < AMDGPU_VM_PTB)
nptes = 1;
(nptes = 1 rather than re-clamping entry_end, since entry_end is only
used for the tracepoint afterward; either form works.)
With the clamp restored the full workload completes cleanly — all
checkpoint shards load, engine serves inference, zero page faults
across repeated runs and reboots.
Two questions:
1. Is this already fixed in a later revision of the series? I tested
the patches as posted on amd-gfx (2026-05-29), not a git branch.
2. If not — since 08/13 makes the unlocked unmap path use
update_leaves(), everyone driving SVM eviction/restore traffic will
hit this. Would you like a formal patch, or do you prefer to fold
the clamp into a v2?
Full debugging trail (STALE PDE dumps, write histories, vanilla diff)
is available if useful.
Regards,
Olivier
== Environment ==
GPU: AMD Radeon Instinct MI50 32GB, gfx906, 0000:05:00.0
(Vega 20, PCIe via Thunderbolt 3 eGPU enclosure)
Host: AMD Strix Halo (Ryzen AI Max+ 395), 28GB RAM, Proxmox VE 9
Kernel: 7.2.6 + amd-gfx VM critical-section series (vmcrit 1-14)
+ TLB invalidation V3 series
Workload: vLLM 0.28.0 (rocm, gfx906 build) loading GLM-4-9B 1M
via KFD SVM; deterministic fault during weight staging