Summary
On gfx1100 with ROCm 7.14, a host-to-device copy runs at ~9.4 GiB/s (9 575 MiB/s) when the source is a read-only file mapping (r--p), and at 2.0 MiB/s when the source is a writable private file mapping (rw-p) whose pages have already been faulted into the process.
The kernel decides how bad it gets, and the cause is identified and now confirmed by revert. Ubuntu's 7.0.0-28 picked up drm/amdgpu: fix amdgpu_hmm_range_get_pages (c08972f55594) without the follow-up that repairs its consequence, drm/amdgpu: drop retry loop in amdgpu_hmm_range_get_pages (342981fff328). The first moved mmu_interval_read_begin() out of the per-chunk loop, so notifier_seq is never refreshed across retries and the -EBUSY retry path can never succeed; it busy-spins for the whole HMM_RANGE_DEFAULT_TIMEOUT, which is 1000 ms, then bails with -EAGAIN and the caller tries again. Every timing in this report is an integer multiple of that constant. On the same guest with only the kernel changed, 7.0.0-14-generic does the same copy in 18.6 to 20.2 ms.
A separate and milder penalty, 4× to 8× against a read-only copy of the same bytes, is present on every kernel tested. @ashetaia-amd explained that one below: kfd_svm.c takes the fault permission from the VMA rather than from what the copy will do, so copy-on-write is broken on every resident page even though a host→device copy only reads. That break is also what advances the notifier sequence, which is why only the writable-and-resident case ever enters the futile retry.
Same file, same bytes, same size, same process. The only difference is the prot argument to mmap and whether the CPU has touched the pages first.
This is not a corner case: it is the default path for loading any safetensors checkpoint into PyTorch, because safetensors hands PyTorch a storage created by torch.UntypedStorage.from_file(..., shared=False), and PyTorch maps that writable. On the affected kernel it makes vLLM model loading 19–48x slower than the disk it reads from: a 15.26 GiB checkpoint takes 206 s, against ~19 s with the workaround below and ~11 s for the disk alone.
Measurements
| source mapping |
pages faulted in first |
32 MiB copy |
rate |
MAP_PRIVATE | PROT_READ (r--p) |
yes |
3.3 ms |
9 575 MiB/s |
MAP_PRIVATE | PROT_READ|PROT_WRITE (rw-p) |
no |
15.8 ms |
2 020 MiB/s |
MAP_PRIVATE | PROT_READ|PROT_WRITE (rw-p) |
yes |
16 020 ms |
2.0 MiB/s |
Four environments, one reproducer, and only one of them is slow:
| environment |
kernel |
rw-p resident, 32 MiB |
| VFIO guest |
7.0.0-28-generic |
16 019.3 / 16 019.6 / 16 019.9 / 16 020.1 ms |
| VFIO guest |
7.0.0-14-generic |
18.6 / 18.9 / 20.2 ms |
| VFIO guest |
6.8.0-136-generic |
22.1 / 23.3 / 26.0 ms |
| bare metal |
7.0.14-4-pve |
24.1 / 24.3 / 25.1 / 28.6 ms |
The first two rows hold everything constant but the kernel ABI — same guest, same ROCm 7.14 userspace, same container image — so passthrough is not a necessary ingredient, which had been the leading suspicion. iommu=pt is not the variable either: this host already boots with it, and the guest has no IOMMU of its own.
In timeout windows those numbers are 16.0, and 17.0 for the 17 020 ms the same reproducer gives on overlayfs and tmpfs. Per-tensor stalls during a real checkpoint load measured 1005, 1042 and 1522 ms, which is 1.0, 1.0 and 1.5. Those tensors are only partially resident, since nothing in the load path touches the pages before the copy, so their window counts are not comparable with the fully resident reproducer figures above. Same mechanism, different amount of resident memory entering it. "Why this hits real workloads" below has the detail.
The first copy any process makes costs about 40 ms whatever the mapping, which is why the reproducer discards one r--p copy before the reading that counts. The pathological case is repeatable to a degree worth stating: on ext4 it landed at 16 019.3, 16 019.6, 16 019.9 and 16 020.1 ms over four runs today, and on overlayfs and tmpfs at 17 020.5 and 17 020.4 ms. Under a millisecond of spread within a filesystem, and a flat 1 s offset between them, because the cost is a whole number of 1000 ms timeout windows and the container filesystems take one retry more.
Against the read-only case this is 4 388× to 4 787× depending on the run, but the ratio is the wrong way to think about it: the numerator is a count of timeout windows. At 256 MiB the copy takes 128 s, which is 128 windows for eight times the pages, so the window count tracks the number of resident pages while each window stays at its constant 1000 ms. The granularity works out to 2 MiB of resident source per window, and it is not specific to this machine: @shineday999 measured 8 MiB in exactly four windows below, on a different board under a different ROCm, and after subtracting the windows the residual real work lands in the same band on both, 1624 MiB/s here against 1575 to 1695 MiB/s there.
Anonymous memory of the same size copies at ~13 GiB/s, i.e. the hardware and the PCIe link are fine.
Reproducer
Dependency-free — PyTorch only, no safetensors, no vLLM. The backing filesystem does not matter either: the same pathological rate appears on ext4 (2.0 MiB/s), on a container's own overlayfs (1.9 MiB/s) and on tmpfs (1.9 MiB/s), so a plain container with no bind mount reproduces it.
import os, mmap, time, torch
PATH, N, DEV = "/data/repro.bin", 32 << 20, "cuda:0"
if not os.path.exists(PATH) or os.path.getsize(PATH) < N * 8:
with open(PATH, "wb") as f:
for _ in range(N * 8 >> 20):
f.write(os.urandom(1 << 20))
with open(PATH, "rb") as f: # warm the page cache
while f.read(1 << 24):
pass
def run(writable, pretouch):
fh = open(PATH, "r+b" if writable else "rb")
prot = mmap.PROT_READ | (mmap.PROT_WRITE if writable else 0)
mm = mmap.mmap(fh.fileno(), N * 8, flags=mmap.MAP_PRIVATE, prot=prot)
src = torch.frombuffer(mm, dtype=torch.uint8, count=N * 8)[:N]
if pretouch:
src.max() # bulk read: faults the pages in
torch.cuda.synchronize(); t0 = time.perf_counter()
g = src.to(DEV); torch.cuda.synchronize()
dt = time.perf_counter() - t0
perm = [l.split()[1] for l in open("/proc/self/maps") if "repro.bin" in l][0]
print(f"{perm} pretouch={pretouch!s:<5} {dt*1000:9.1f} ms {N/dt/2**20:8.1f} MiB/s")
del g, src; torch.cuda.empty_cache(); mm.close(); fh.close()
run(False, True) # warm-up: the first copy in a process costs ~40 ms whatever the mapping
run(False, True) # r--p, resident -> ~9500 MiB/s
run(True, False) # rw-p, cold -> ~2000 MiB/s
run(True, True) # rw-p, resident -> ~2 MiB/s
Where the time goes
perf on a vLLM worker during a real checkpoint load:
99.11% ioctl
└─ __x64_sys_ioctl → kfd_ioctl → kfd_ioctl_svm → svm_ioctl
└─ svm_range_set_attr → svm_range_validate_and_map
└─ 94.19% amdgpu_hmm_range_get_pages → 86.95% hmm_range_fault
strace over a 12 s window of the same load: 54 ioctls, ~189 ms each.
That profile is the busy spin, not work. svm_range_validate_and_map takes readonly from the VMA's VM_WRITE bit at kfd_svm.c:1777 and passes it to amdgpu_hmm_range_get_pages(), which turns !readonly into HMM_PFN_REQ_WRITE at amdgpu_hmm.c:188. So a copy that only reads its source asks for write access, copy-on-write is broken on every resident page, and that break advances the notifier sequence. On -28 the stale notifier_seq then makes the -EBUSY retry unable to succeed, and it spins out the full timeout. A read-only mapping never invalidates, so it never enters any of this.
Why this hits real workloads
safetensors, on its PyTorch path, does not use its own (read-only) mapping. It calls:
// safetensors/bindings/python/src/lib.rs
torch.UntypedStorage.from_file(filename, shared=False, nbytes=size)
PyTorch maps that file writable, so every tensor a framework loads from a safetensors checkpoint is backed by an rw-p mapping. Confirmed with /proc/<pid>/maps, same file opened two ways in one process:
rw-p ... model-00001-of-00005.safetensors <- safetensors framework="pt"
r--p ... model-00001-of-00005.safetensors <- safetensors framework="np"
Effect on vLLM startup on this machine, with and without a workaround that copies each tensor into anonymous memory before the device copy:
| model |
as shipped |
tensor cloned to anon memory first |
| Qwen3-8B, 15.26 GiB BF16 |
206 s |
18.7 s |
| gemma-4-12B, 9.56 GiB w4a16 |
328 s |
10.5 s |
| gemma-4-31B, 21.67 GiB w4a16 |
569 s |
25.1 s |
Those work out to 76, 30 and 39 MiB/s, which is milder than the 2.0 MiB/s of the microbenchmark and consistently so. All three sit between the two writable regimes measured above, which is what a partially resident mapping produces: nothing in the load path reads the pages before the copy, so they are faulted in by the copy itself and only the already-resident fraction pays the worst rate. Timed tensor by tensor inside one shard, the cost is roughly fixed at about 1 s per tensor above a threshold somewhere between 4 and 8 MiB, and negligible below it.
Possibly related, and one thing that does not help
ROCm/ROCm#5952 — "SVM mapping failure during sequential model loads (RDNA3 / RX 7900 GRE)" — is the nearest neighbour. It reports crashes and stalls rather than a reproducible slow copy, but it is the same subsystem on the same generation of hardware, its reporter describes VRAM filling extremely slowly, and in that thread @zichguan-amd reads the logs as an async copy of 27 648 bytes taking 25 s. That machine is bare metal (Ryzen 7800X3D), which our guest-only setup cannot match.
Two things keep us from calling it the same bug. The kernel-side analysis posted there by @Deaththegrim concerns svm_range_restore_work and the userptr restore worker, i.e. the eviction/restore path, whereas our profile sits in svm_range_set_attr → svm_range_validate_and_map → hmm_range_fault, the mapping path. And that thread's symptoms come and go with workflow, RAM pressure and kernel build, while the reproducer below is deterministic and finishes in half a minute. We offer this as an adjacent report rather than a duplicate; if the two do share a cause, the reproducer is probably the cheaper way in.
HSA_USE_SVM=0 does not help. Suggested by ROCm/ROCm#2433, where it recovers pre-5.6 hipHostRegister performance. Measured here: the pathological case is unchanged (16 036 ms vs 16 020 ms), and the read-only fast path gets worse (8 905 → 844 MiB/s) — so SVM is what makes read-only mappings fast, while the writable path is slow for some other reason.
Ruled out
| hypothesis |
result |
| disk throughput |
1.5 GB/s measured on the same file with dd iflag=direct |
HSA_USE_SVM=0 |
no effect on the pathological case; degrades the fast case |
| page cache cold vs warm |
192 vs 212 MiB/s, no meaningful difference |
| file-backed vs anonymous per se |
a read-only file mapping is full speed |
| pages not yet faulted in |
that is the faster of the two writable cases |
| a fresh source range per copy |
no effect on a read-only mapping |
| ext4 vs overlayfs vs tmpfs |
2.0, 1.9 and 1.9 MiB/s — the backing filesystem is not a factor |
| page-unaligned range start, bf16 vs uint8 |
no effect |
| new device allocation per copy vs one reused buffer |
no effect |
| swap / memory pressure |
swap usage 0 throughout |
Environment
- 2× Radeon RX 7900 XT (gfx1100), 20 GiB each
- ROCm 7.14, PyTorch 2.11, safetensors 0.8.0 — also reproduces on ROCm 7.0.0 with PyTorch 2.9, so the userspace version is not the variable
- Ubuntu 24.04.4 guest under Proxmox VE / QEMU with VFIO passthrough. The kernel that varies is the guest's:
7.0.0-28-generic for the severe case, 7.0.0-14-generic and 6.8.0-136-generic for the comparisons, all booted from the same install. The host runs 7.0.14-4-pve and is the bare-metal row. The GPUs report PCIE atomic ops is not supported on every guest kernel
- Checkpoints on ext4 on an NVMe SSD; the reproducer's test file likewise
Confirmed by revert. amdgpu.ko built from the linux-hwe-7.0 7.0.0-28.28~24.04.1 source with 342981fff328 as the only change, swapped into the running -28 kernel, same machine and userspace and reproducer: the copy goes from 16 019.7 ms to 17.0 ms, while the reproducer's other three cases stay where they were. Details in the comments below.
What is still open. Updated 2026-08-28: not the kernel question any more. Canonical shipped 7.0.0-30.30~24.04.1 on 2026-08-20 and it carries the follow-up in effect: the same reproducer on the same machine goes from 16 019.3 ms to 15.3 ms across that upgrade, with the two control rows unmoved (all three kernel states, raw, and the comment below). Bare metal came off this list earlier: @shineday999 reproduced the pathology below on an RX 7900 XTX under ROCm 7.2.1 on 7.0.0-28 with no VM involved, which settles the passthrough question from outside this machine. What remains open is the 4× to 8× permission penalty, which that kernel commit does not touch — it survives on -30 at 4.8× — and which is characterised on gfx1100 only, since we have no CDNA hardware.
What would help
- 342981fff328 in the stable trees. It carries neither a
Fixes: tag nor Cc: stable@vger.kernel.org, so it was never queued for backport, while c08972f55594 did reach -28. That asymmetry is the whole mechanism, and it is not Ubuntu-specific: any series tracking 7.0.y that took the first commit is in the same state. Per stable-kernel-rules.rst an already-mainlined commit can be requested by mail to stable@vger.kernel.org, and a request from the people who signed both commits would carry more weight than one from me.
- A backport into Ubuntu's 7.0.0 in the meantime.
-28 is the current HWE kernel, out of noble-updates and noble-security, so it reaches anyone who lets updates run on the distro ROCm supports first. Filed with Ubuntu as LP#2161985, which carries the revert result; raised here because the breaking commit is AMD's.
- The 4× to 8× that survives on every kernel is a separate question and the more durable one. Taking the fault permission from the VMA rather than from the requested access means a read-only copy pays for breaking COW on every resident page. Is there a cheaper path for
MAP_PRIVATE|PROT_WRITE ranges, or should the runtime stage them rather than map them for DMA?
- Independently,
torch.UntypedStorage.from_file(shared=False) mapping writable is what exposes every PyTorch user to this. That may be worth a separate conversation with the PyTorch maintainers.
Everything behind this report
https://github.com/cadamcat/dual-radeon-vllm
benchmarks/repro-mmap-prot.py is the reproducer above, with the file path and the repeat count made configurable and each case run twice.
docs/open-questions.md section 8 lists every hypothesis we tested and discarded, including an earlier root cause we published and then disproved with our own reproducer. If something here looks wrong, that section is the place to check first.
Summary
On gfx1100 with ROCm 7.14, a host-to-device copy runs at ~9.4 GiB/s (9 575 MiB/s) when the source is a read-only file mapping (
r--p), and at 2.0 MiB/s when the source is a writable private file mapping (rw-p) whose pages have already been faulted into the process.The kernel decides how bad it gets, and the cause is identified and now confirmed by revert. Ubuntu's
7.0.0-28picked updrm/amdgpu: fix amdgpu_hmm_range_get_pages(c08972f55594) without the follow-up that repairs its consequence,drm/amdgpu: drop retry loop in amdgpu_hmm_range_get_pages(342981fff328). The first movedmmu_interval_read_begin()out of the per-chunk loop, sonotifier_seqis never refreshed across retries and the-EBUSYretry path can never succeed; it busy-spins for the wholeHMM_RANGE_DEFAULT_TIMEOUT, which is 1000 ms, then bails with-EAGAINand the caller tries again. Every timing in this report is an integer multiple of that constant. On the same guest with only the kernel changed,7.0.0-14-genericdoes the same copy in 18.6 to 20.2 ms.A separate and milder penalty, 4× to 8× against a read-only copy of the same bytes, is present on every kernel tested. @ashetaia-amd explained that one below:
kfd_svm.ctakes the fault permission from the VMA rather than from what the copy will do, so copy-on-write is broken on every resident page even though a host→device copy only reads. That break is also what advances the notifier sequence, which is why only the writable-and-resident case ever enters the futile retry.Same file, same bytes, same size, same process. The only difference is the
protargument tommapand whether the CPU has touched the pages first.This is not a corner case: it is the default path for loading any
safetensorscheckpoint into PyTorch, becausesafetensorshands PyTorch a storage created bytorch.UntypedStorage.from_file(..., shared=False), and PyTorch maps that writable. On the affected kernel it makes vLLM model loading 19–48x slower than the disk it reads from: a 15.26 GiB checkpoint takes 206 s, against ~19 s with the workaround below and ~11 s for the disk alone.Measurements
MAP_PRIVATE | PROT_READ(r--p)MAP_PRIVATE | PROT_READ|PROT_WRITE(rw-p)MAP_PRIVATE | PROT_READ|PROT_WRITE(rw-p)Four environments, one reproducer, and only one of them is slow:
rw-president, 32 MiB7.0.0-28-generic7.0.0-14-generic6.8.0-136-generic7.0.14-4-pveThe first two rows hold everything constant but the kernel ABI — same guest, same ROCm 7.14 userspace, same container image — so passthrough is not a necessary ingredient, which had been the leading suspicion.
iommu=ptis not the variable either: this host already boots with it, and the guest has no IOMMU of its own.In timeout windows those numbers are 16.0, and 17.0 for the 17 020 ms the same reproducer gives on overlayfs and tmpfs. Per-tensor stalls during a real checkpoint load measured 1005, 1042 and 1522 ms, which is 1.0, 1.0 and 1.5. Those tensors are only partially resident, since nothing in the load path touches the pages before the copy, so their window counts are not comparable with the fully resident reproducer figures above. Same mechanism, different amount of resident memory entering it. "Why this hits real workloads" below has the detail.
The first copy any process makes costs about 40 ms whatever the mapping, which is why the reproducer discards one
r--pcopy before the reading that counts. The pathological case is repeatable to a degree worth stating: on ext4 it landed at 16 019.3, 16 019.6, 16 019.9 and 16 020.1 ms over four runs today, and on overlayfs and tmpfs at 17 020.5 and 17 020.4 ms. Under a millisecond of spread within a filesystem, and a flat 1 s offset between them, because the cost is a whole number of 1000 ms timeout windows and the container filesystems take one retry more.Against the read-only case this is 4 388× to 4 787× depending on the run, but the ratio is the wrong way to think about it: the numerator is a count of timeout windows. At 256 MiB the copy takes 128 s, which is 128 windows for eight times the pages, so the window count tracks the number of resident pages while each window stays at its constant 1000 ms. The granularity works out to 2 MiB of resident source per window, and it is not specific to this machine: @shineday999 measured 8 MiB in exactly four windows below, on a different board under a different ROCm, and after subtracting the windows the residual real work lands in the same band on both, 1624 MiB/s here against 1575 to 1695 MiB/s there.
Anonymous memory of the same size copies at ~13 GiB/s, i.e. the hardware and the PCIe link are fine.
Reproducer
Dependency-free — PyTorch only, no safetensors, no vLLM. The backing filesystem does not matter either: the same pathological rate appears on ext4 (2.0 MiB/s), on a container's own overlayfs (1.9 MiB/s) and on tmpfs (1.9 MiB/s), so a plain container with no bind mount reproduces it.
Where the time goes
perfon a vLLM worker during a real checkpoint load:straceover a 12 s window of the same load: 54 ioctls, ~189 ms each.That profile is the busy spin, not work.
svm_range_validate_and_maptakesreadonlyfrom the VMA'sVM_WRITEbit atkfd_svm.c:1777and passes it toamdgpu_hmm_range_get_pages(), which turns!readonlyintoHMM_PFN_REQ_WRITEatamdgpu_hmm.c:188. So a copy that only reads its source asks for write access, copy-on-write is broken on every resident page, and that break advances the notifier sequence. On-28the stalenotifier_seqthen makes the-EBUSYretry unable to succeed, and it spins out the full timeout. A read-only mapping never invalidates, so it never enters any of this.Why this hits real workloads
safetensors, on its PyTorch path, does not use its own (read-only) mapping. It calls:PyTorch maps that file writable, so every tensor a framework loads from a safetensors checkpoint is backed by an
rw-pmapping. Confirmed with/proc/<pid>/maps, same file opened two ways in one process:Effect on vLLM startup on this machine, with and without a workaround that copies each tensor into anonymous memory before the device copy:
Those work out to 76, 30 and 39 MiB/s, which is milder than the 2.0 MiB/s of the microbenchmark and consistently so. All three sit between the two writable regimes measured above, which is what a partially resident mapping produces: nothing in the load path reads the pages before the copy, so they are faulted in by the copy itself and only the already-resident fraction pays the worst rate. Timed tensor by tensor inside one shard, the cost is roughly fixed at about 1 s per tensor above a threshold somewhere between 4 and 8 MiB, and negligible below it.
Possibly related, and one thing that does not help
ROCm/ROCm#5952 — "SVM mapping failure during sequential model loads (RDNA3 / RX 7900 GRE)" — is the nearest neighbour. It reports crashes and stalls rather than a reproducible slow copy, but it is the same subsystem on the same generation of hardware, its reporter describes VRAM filling extremely slowly, and in that thread @zichguan-amd reads the logs as an async copy of 27 648 bytes taking 25 s. That machine is bare metal (Ryzen 7800X3D), which our guest-only setup cannot match.
Two things keep us from calling it the same bug. The kernel-side analysis posted there by @Deaththegrim concerns
svm_range_restore_workand the userptr restore worker, i.e. the eviction/restore path, whereas our profile sits insvm_range_set_attr → svm_range_validate_and_map → hmm_range_fault, the mapping path. And that thread's symptoms come and go with workflow, RAM pressure and kernel build, while the reproducer below is deterministic and finishes in half a minute. We offer this as an adjacent report rather than a duplicate; if the two do share a cause, the reproducer is probably the cheaper way in.HSA_USE_SVM=0does not help. Suggested by ROCm/ROCm#2433, where it recovers pre-5.6hipHostRegisterperformance. Measured here: the pathological case is unchanged (16 036 ms vs 16 020 ms), and the read-only fast path gets worse (8 905 → 844 MiB/s) — so SVM is what makes read-only mappings fast, while the writable path is slow for some other reason.Ruled out
dd iflag=directHSA_USE_SVM=0Environment
7.0.0-28-genericfor the severe case,7.0.0-14-genericand6.8.0-136-genericfor the comparisons, all booted from the same install. The host runs7.0.14-4-pveand is the bare-metal row. The GPUs reportPCIE atomic ops is not supportedon every guest kernelConfirmed by revert.
amdgpu.kobuilt from thelinux-hwe-7.07.0.0-28.28~24.04.1 source with 342981fff328 as the only change, swapped into the running-28kernel, same machine and userspace and reproducer: the copy goes from 16 019.7 ms to 17.0 ms, while the reproducer's other three cases stay where they were. Details in the comments below.What is still open. Updated 2026-08-28: not the kernel question any more. Canonical shipped
7.0.0-30.30~24.04.1on 2026-08-20 and it carries the follow-up in effect: the same reproducer on the same machine goes from 16 019.3 ms to 15.3 ms across that upgrade, with the two control rows unmoved (all three kernel states, raw, and the comment below). Bare metal came off this list earlier: @shineday999 reproduced the pathology below on an RX 7900 XTX under ROCm 7.2.1 on7.0.0-28with no VM involved, which settles the passthrough question from outside this machine. What remains open is the 4× to 8× permission penalty, which that kernel commit does not touch — it survives on-30at 4.8× — and which is characterised on gfx1100 only, since we have no CDNA hardware.What would help
Fixes:tag norCc: stable@vger.kernel.org, so it was never queued for backport, while c08972f55594 did reach-28. That asymmetry is the whole mechanism, and it is not Ubuntu-specific: any series tracking 7.0.y that took the first commit is in the same state. Perstable-kernel-rules.rstan already-mainlined commit can be requested by mail tostable@vger.kernel.org, and a request from the people who signed both commits would carry more weight than one from me.-28is the current HWE kernel, out ofnoble-updatesandnoble-security, so it reaches anyone who lets updates run on the distro ROCm supports first. Filed with Ubuntu as LP#2161985, which carries the revert result; raised here because the breaking commit is AMD's.MAP_PRIVATE|PROT_WRITEranges, or should the runtime stage them rather than map them for DMA?torch.UntypedStorage.from_file(shared=False)mapping writable is what exposes every PyTorch user to this. That may be worth a separate conversation with the PyTorch maintainers.Everything behind this report
https://github.com/cadamcat/dual-radeon-vllm
benchmarks/repro-mmap-prot.pyis the reproducer above, with the file path and the repeat count made configurable and each case run twice.docs/open-questions.mdsection 8 lists every hypothesis we tested and discarded, including an earlier root cause we published and then disproved with our own reproducer. If something here looks wrong, that section is the place to check first.