Repository navigation
USB4 eGPU on Apple Silicon: AMD RX 7600 runs llama.cpp on Vulkan (non-posted BARs, identity tunnel windows) - #188
Conversation
|
This PR replaces #184 (same head Review record, all on #184:
Still open before merge: a second human or maintainer GREEN, a maintainer "Approve and run" for CI, and the conditions listed in the description (MQD hunks on a posted host, commit 10 on a second SoC). |
|
GREEN at 3f5c88f |
[Medium impact] No new blocking defect was found, and the previous direct-firmware fault is addressed. SummaryAdds non-posted GPU memory access and Apple USB4 tunnel fixes for external GPUs.
|
|
GREEN at 3f5c88f. I read the whole diff of this head (25 files, ten commits) and not only the history of PR 184, which carried the same head. I did not build or boot it. My checks are reading only, plus the receipts named below. The non-posted changes are all gated on the BAR flag. In TTM, the new Notes, none of them blocking:
Nothing here was run by me, so this is a read review. The measured results in the description are the author's. |
3f5c88f to
11fd43b
Compare
|
New head Receipt at this head (CT-local, arm64 clang |
11fd43b to
654075e
Compare
|
New head |
|
GREEN at 654075e for the delta from 11fd43b and the two areas w7P named; the rest of the series was not re-read in this pass. Read: the ten commits of this head against the ones at 11fd43b, compared per commit as diffs with the hash lines removed. Nine commits are identical. The one that differs is "drm/amdgpu: access non-posted BO memory with I/O helpers", in dm_dmub_sw_init:
|
|
GREEN at 654075e Read the whole diff of the ten commits (25 files, +447/-111), including the DMUB hunk that changed since Checked
Findings (not blocking)
Not checkedCompile and checkpatch receipts (the author's), hardware, and the DCN build with clang 22 (first commit, Kconfig only). Merge still needs CI and the owner's word. |
|
Status at head |
|
Measured proof on hardware (2026-10-29), with the exact kernel provenance up front: this ran on a lab build of 7.2.11 (release string ending in Machine: 14-inch M2 Max MacBook Pro (T6021). Two cards in USB4 enclosures, both enumerated at boot:
Measured, per card, on the same boot:
Baseline: an earlier run on the same machine showed the stock integration tree initializing neither card (BAR 0 reads all-ones; PSP alignment faults), which is the "before" state this series removes. A stock user needs this series merged, amdgpu enabled in the config (#67), the |
|
Follow-up, as promised at the top of the earlier comment: the full series at this PR's exact head Kernel: Measured, same machine, same boot, both cards:
So the ten-commit series, exactly as it stands in this PR on the current integration tip, boots and computes end to end, and reproduces the earlier results byte for byte. The physical unplug/replug coverage remains the run described in the earlier comment (idle machine, one cable at a time, seven-commit kernel); it was not repeated on this boot. |
|
GREEN at 83994dd for the new commit Checked from the tree at that head:
CI at the time of this comment: Not checked: I did not compile the objects (the author reports a clean build with 0 warnings), and the reset and suspend restore paths were not run on hardware. The PR text says so. |
|
Third hardware data point, at this PR's current head Kernel: Same measurements as before, same boot, both cards:
Restore-path coverage (reset and suspend resume): NOT TESTED on hardware. The debugfs recovery trigger is not writable on this build (mode 0444 for root, module parameter |
The shared arm64 build uses clang 22. The existing condition removes DCN support from both Navi 33 and Navi 48 builds. Keep the old compiler guard and the other architecture exclusions. Enable the arm64 path for clang 22 and newer without changing frame warnings or floating-point file flags. Building with this change on clang 22 reports one new warning, -Wframe-larger-than for dml_core_mode_support() in display/dc/dml2_0/display_mode_core.c (2080 bytes against the 2056 limit). That warning only exists because DC_FP is now selected on arm64. A configuration with CONFIG_WERROR=y fails on it; this change does not raise the limit. Signed-off-by: Joshua Warren <816217+joshuaswarren@users.noreply.github.com>
… access On Apple Silicon PCIe tunnels every BAR resource carries IORESOURCE_MEM_NONPOSTED: CPU accesses must be mapped Device-nGnRnE (ioremap_np). A write-combined or cached mapping makes the fabric hang or faults on the first unaligned access. Add a per-resource is_nonposted flag to ttm_bus_placement. TTM maps such a resource with ioremap_np() and a noncached protection, and iterates it as __iomem. amdgpu sets the flag for VRAM, doorbell and MMIO remap resources from the owning BAR, maps the register BAR, the premapped VRAM aperture and the VRAM copy of the video BIOS with ioremap_np(), and reads the ROM through the BAR with explicit enable/disable. No change on hosts where the BAR is not flagged non-posted. The ROM read through the BAR checks the 0x55 0xAA signature and copies only the image, whose length the ROM header gives in 512-byte units, not the whole BAR window. Signed-off-by: Joshua Warren <816217+joshuaswarren@users.noreply.github.com>
A buffer object whose resource sits behind a non-posted BAR is I/O memory: plain memset(), memcpy() and struct stores through its kernel pointer take an alignment fault on Device-nGnRnE mappings. Add amdgpu_bo_memset(), amdgpu_bo_memcpy_to() and amdgpu_bo_memcpy_from(), which use memset_io(), memcpy_toio() and memcpy_fromio() when the resource is flagged non-posted and the plain functions otherwise, and use them where the driver writes or reads BO memory through a CPU pointer: firmware copy to the PSP staging buffer (psp_copy_fw), RAS TA shared memory, MES, CSA, GFX v11/v12 and SDMA v7 queue setup, SMU message buffers and the display manager. The helpers and the MES paths test the resource flag, so they behave as before on hosts without non-posted BARs. The GFX v11/v12 queue setup hunks (kgq, kiq, kcq) are different: they now compose the MQD in the host backup copy and copy it to the BO whenever a backup exists, on every host. That order is meant to be equivalent, but it has only been run on the non-posted host described in the pull request. The MES v12 MQD paths are not converted. Behind a non-posted BAR the DMUB buffer goes to GTT, and direct firmware loading there is refused with -EOPNOTSUPP: the backdoor loader converts addresses as VRAM addresses and fills the buffer with plain CPU copies. PSP loading is the supported path; on the RX 7600 the DMUB hardware initialised with it (firmware 0x07003300). Signed-off-by: Joshua Warren <816217+joshuaswarren@users.noreply.github.com>
Userspace that maps a VRAM buffer through a non-posted BAR gets Device-nGnRnE memory, where ordinary memcpy() faults. Report zero CPU-accessible VRAM in the INFO and MEMORY_INFO ioctls so that Mesa does not offer a host-visible device-local heap, and create buffers that ask for VRAM with CPU access required in GTT instead. The other creation flags are kept. AMDGPU_GEM_CREATE_VRAM_CONTIGUOUS only changes a VRAM placement (amdgpu_bo_placement_from_domain() and amdgpu_bo_pin()), so it has no effect on a GTT-only buffer. No change on hosts without non-posted BARs. Signed-off-by: Joshua Warren <816217+joshuaswarren@users.noreply.github.com>
The PSP command, fence and ring buffers and the TA shared buffers are read and written through plain CPU pointers. With the VRAM BAR non-posted a VRAM placement makes every such access a Device-nGnRnE access, and the plain stores in the PSP command path take an alignment fault during initialisation. Place the buffers in GTT on those hosts. No change when the BAR is not flagged non-posted. Signed-off-by: Joshua Warren <816217+joshuaswarren@users.noreply.github.com>
PCI DMA on the Apple Silicon tunnels is not cache coherent. A cached CPU mapping of a GTT page can hold dirty lines the GPU never sees. When the VRAM BAR is non-posted and the device is not DMA coherent, create GTT pages write-combined. No change on coherent devices or when the BAR is not flagged non-posted. Signed-off-by: Joshua Warren <816217+joshuaswarren@users.noreply.github.com>
Userspace maps a VRAM buffer through the BAR as Device-nGnRnE memory. There, memcpy() and any unaligned store raise an alignment fault, so the first write from the Vulkan driver into such a buffer ended in SIGBUS (Mesa RADV, llama.cpp KV-cache initialisation). When the VRAM BAR is non-posted, a CPU fault on a VRAM buffer now moves the buffer to GTT and restricts its preferred and allowed domains to GTT. Two consequences. The move is permanent: a large VRAM-only buffer that userspace maps once stays in system memory behind the tunnel, so the GPU reaches it over the link. A pinned buffer cannot move and a CPU fault on it now fails with SIGBUS, where it used to map through the BAR. Scanout and cursor buffers are pinned in VRAM; no display was attached when this was tested, so that case is untested. No change when the BAR is not flagged non-posted. Signed-off-by: Joshua Warren <816217+joshuaswarren@users.noreply.github.com>
…addressing The firmware's own device tree (ADT) describes the 64-bit prefetchable window of every USB4 PCIe tunnel root port with the PCI address equal to the CPU address. These device trees mapped PCI 0x8_00000000 to a different CPU base for tunnels 1, 2 and 3 (0xc, 0x10 and 0x14 in the high word). Linux then placed 64-bit BARs at 0x8_xxxxxxxx while the fabric forwarded the CPU window to 0xc_.... CPU reads of any 64-bit BAR returned all-ones and writes went nowhere, while 32-bit BARs and config space worked. Measured on a 14-inch M2 Max MacBook Pro (tunnel 1) with an AMD RX 7600: before the change every read of BAR0 (VRAM) and BAR2 (doorbells) returned 0xffffffff; with the first range entry changed to <0x43000000 0xc 0x0 0xc 0x0 0x2 0x0> BAR0 reads return data, the amdgpu VRAM test passes and the card runs llama.cpp on Vulkan. Tunnel 0 already used identity addressing. Tunnels 2 and 3 on T600x and T602x are changed by the same rule and are NOT tested on hardware. Signed-off-by: Joshua Warren <816217+joshuaswarren@users.noreply.github.com>
An eGPU behind an ASM2464PD USB4 controller sits on a switch Downstream Port that implements a slot but is not hot-plug capable. The tunnel host scans the hierarchy once, 1 s after the root port's link comes up. If the card's link trains after that, nothing scans again: pciehp never binds to the port, and the tunnel's bus numbers and bridge windows were sized for an empty slot. On a 14-inch M2 Max MacBook Pro with an RX 7600 the first scan found only the ASMedia bridges. Manual rescans later reached the GPU, but could not extend the bus ranges or grow the 1 MiB root port window, so BAR 5 and the audio function stayed unassigned. Memory decode on the root port also stayed off: it was enabled while its windows were still empty, and an ordinary rescan neither programs nor re-enables an enabled bridge. When the hierarchy is complete at that one scan, the existing root bus assignment sizes and programs every window before the port driver enables the root port. Hook pci_ops.add_bus, which runs before each new child bus is scanned. On tunneled hosts, if a slot Downstream Port without hot-plug capability reports no Data Link Layer Link Active, wait up to the same 1 s for it, then 100 ms more. PCIe r7.0 sec 6.6.1 requires that delay after training above 5 GT/s; the code waits it at every speed, which is conservative. This covers the first probe and apple_pcie_tunnel_restore(), whose rescan enumerates a fresh root port. Ports with an active link, hot-plug capable ports and native hosts are unchanged. The wait and the measured link-up time are logged. Boot cost: the wait runs once for every such port whose link is down, one after the other. A switch with two empty slot ports adds up to two seconds to the scan, each wait is bounded and logged. A card that is powered only after the tunnel has been enumerated is still not found: its slot reports no hot-plug capability, so it needs a reconnect. Signed-off-by: Joshua Warren <816217+joshuaswarren@users.noreply.github.com>
…zed tunnel ports apple_pcie_config_write() tells the port block about the root port's prefetchable window (PORT_PREFMEM_ENABLE, port register 0x994) when the PCI core programs PREF_MEMORY_BASE/LIMIT, but only if pcie->kernel_init is set. kernel_init is true for apple,t8103-pciec with apple,pciec-kernel-init. The T600x/T602x tunnel ports use pcie->tunnel_cold_init instead, so on those ports the hook never runs and the enable bit stays 0. Measured on a 16-inch M1 Max (T6001), Intel Arc Pro B60 in a Sonnet Breakaway Box on USB4 tunnel 2 (pcie@f30000000), kernel 7.1.12-2-20261013.18: - Every CPU read of a BAR behind the root port's prefetchable window returned all-ones, with no AER, abort or UR bits on any bridge. This held for the 64-bit window with the identity DT range, for the 32-bit prefetchable window, and with the BARs placed inside the working non-prefetchable CPU window. A function behind the same chain in the non-prefetchable window read correct data in every configuration. - A runtime bisect with setpci gave each bridge in turn its prefetchable window while the others forwarded through widened non-prefetchable windows: the windows of 04:01.0, 03:00.0, 02:00.0 and 01:00.0 each read data; only the root port's own prefetchable window (0001:00:00.0) read all-ones. - PORT_PREFMEM_ENABLE (phys 0xf21000994) read 0 on that boot (0x990 read 1). With the kernel's original bridge windows in place and the BARs reachable only through the prefetchable windows, reads returned all-ones; after one 32-bit write of 1 to the register (writel_relaxed, mb, isb, non-posted mapping) the same reads returned correct data (B60 BAR0 registers and VRAM). The register reads back 0 after the write, so it is a write strobe. Run the existing hook for tunnel_cold_init ports too. T6001 only until it is tested on T6021: on a 14-inch M2 Max (T6021) the prefetchable window forwarded once the DT range was identity. Tested: the register write above (direct, from a test module). The hook path itself has not been booted: a rebuilt kernel changes the BTF base of the modules. (cherry picked from commit 4466fe2ad955d47290fc79c13963c51c0b253706) Signed-off-by: Joshua Warren <816217+joshuaswarren@users.noreply.github.com>
The init paths of kgq/kiq/kcq (gfx v11 and v12) copy the composed MQD into the BO and flush the HDP before any register programming or KIQ map touches the queue. The restore paths (GPU reset, resume) copy the backup MQD back into the BO without the flush. The copy helper's I/O writes complete on return, but the init paths flush for a reason, so give the restore copies the same treatment. The MES v12 paths need no conversion: their MQD BOs are GTT-only and never sit behind a non-posted BAR (v11 allocates VRAM|GTT, which is why v11 converts). Signed-off-by: Joshua Warren <816217+joshuaswarren@users.noreply.github.com>
83994dd to
f3ea195
Compare
|
The series is now rebased onto The base is the one the hardware runs used: the kernel booted on the 14-inch M2 Max tonight (release string ending in |
|
GREEN at f3ea195 (rebase of 83994dd onto Checked:
CI at the time of this comment: Not checked: the hardware run on the rebased tree (the author reports the same tree minus a stats commit passed on the 14-inch M2 Max), and the two checks still running. |
|
No-eGPU regression check, maclab: PASS. The PR head |
chriskearney
left a comment
There was a problem hiding this comment.
Approve for integration/aurora at f3ea195773f9508f1aa4c8ff79f8336ec074d45d.
I reviewed the 25-file diff and the critical MQD, BIOS/ROM and TTM paths. An additional independent source pass found no concrete blocker in the unconditional GFX v11/v12 changes: composing in the host backup preserves the initialized MQD contents; both KIQ register-setup functions only read them; the restore copies gain the HDP flush. I also independently checked that the pre-rebase eleven-commit series and this PR have the same stable patch-id (7b01f645eadaac583c1d2b5c3e264d47f26314c3).
Code-owner disposition of the description's condition 1: I explicitly waive the posted-x86 hardware run for this ARM64-only integration merge. That test has not passed or been supplied. Posted-VRAM x86 MQDs are outside linux-aurora's targets; ARM64 GFX/compute MQDs use GTT. This is a scoped source-review decision, not an upstream/x86 qualification or a waiver of GitHub checks. A broader port still needs its own validation.
Evidence and limits:
- Exact-head CI is green. The package configuration does not build AMDGPU/TTM, so that CI does not compile-cover those changes or enable eGPU support by itself.
- The linked RX 7600/RX 9070 boot-and-compute results are author-reported, on the patch-identical series plus a statistics change and lab configuration. I did not independently reproduce those runs. Physical unplug/replug was on the earlier seven-commit kernel at idle, not repeated on the final head.
- GPU reset/suspend restore remains untested. The bounded PCI link-wait boot cost is unmeasured. The three-Mac no-eGPU comparison is also author-reported. None is a full release or cross-platform qualification.
These documented residuals are acceptable for this integration change. Merge remains through the required code-owner/CI/merge-queue rules.
c506e1c
This PR replaces #184. GitHub closed #184 when its head branch was deleted by mistake, and it would not reopen after the branch came back. The head commit is the same (3f5c88f). The review comments and GREEN receipts are on #184.
Summary
This series (ten commits) makes an AMD Radeon RX 7600 (Navi 33) in a USB4 enclosure work on an Apple Silicon Mac: amdgpu loads, RADV sees the card, and llama.cpp runs a 7B model on it through Vulkan. It fixes bugs that people hit when they run local AI on an eGPU on these Macs. It is not needed for the release candidate, and there is no rush to review or merge it.
The card was measured on a 14-inch M2 Max MacBook Pro (T6021) with 16 KiB pages. Everything below is either measured (a log or a number from that machine) or not tested.
What is wrong without it
0x8_00000000to CPU0xc,0x10or0x14in the high word. The firmware's own tables use identity addressing for every tunnel port (PCI address equals CPU address). Linux puts a 64-bit BAR at0x8_xxxxxxxx, the fabric forwards the CPU window to0xc_..., and every CPU read of the BAR returns all-ones. 32-bit BARs and config space still work, so the card looks healthy until a driver touches VRAM.memset(),memcpy()and struct stores into a BO that sits behind the BAR take an arm64 alignment fault in the kernel (PSP command, fence and ring buffers) and in user space (Vulkan driver writes to a VRAM buffer).DRM_AMD_DC_FP.What the series does
drm/amd: enable arm64 DCN with clang 22drm/ttm, drm/amdgpu: honor non-posted BARs for VRAM, doorbell and ROM accessttm_bus_placement.is_nonposted;ioremap_np()for VRAM, doorbells, registers, VBIOS.drm/amdgpu: access non-posted BO memory with I/O helpersamdgpu_bo_memset/memcpy_to/memcpy_fromuse*_ioon non-posted resources.drm/amdgpu: report no CPU-visible VRAM on non-posted hostsdrm/amdgpu: place PSP CPU-visible buffers in GTT behind non-posted BARsdrm/amdgpu: map GTT write-combined on non-coherent non-posted hostsdrm/amdgpu: move a CPU-faulted VRAM BO to GTT behind a non-posted BARarm64: dts: apple: map USB4 PCIe tunnel 64-bit windows with identity addressingPCI: apple: wait for tunneled slot links before scanningPCI: apple: enable the root-port prefetchable window on cold-initialized tunnel portsPORT_PREFMEM_ENABLEread 0 and every read through the root port's prefetchable window returned all-ones; one write of 1 made them return data. The hook path itself has not been booted as a kernel. On the 14-inch M2 Max the register also reads 0, but it is a write strobe (it reads 0 after a write), so that read says nothing; the M2 works with identity ranges alone.The TTM and amdgpu changes are conditional on
IORESOURCE_MEM_NONPOSTEDon BAR 0 (commit 6 also needs a non-coherent device), with one exception: the GFX v11/v12 queue setup hunks in commit 3 (kgq, kiq, kcq) now compose the MQD in the host backup copy and copy it to the BO whenever a backup exists, on every host. That order is meant to be equivalent. It has only run on the non-posted host above, so it needs a run on a posted host (an x86 AMD GPU) before merge. The MES v12 MQD paths (RDNA 4) are not converted.psp_cpu_buf_domain()has no override: on a non-posted host the PSP buffers are always GTT.Measured on the M2 Max (RX 7600, Mesa RADV 26.2.5, llama.cpp build 65840ed53)
The first runs used one CPU of the machine's 12. The device tree that made BAR 0 readable was loaded from the boot loader with a saved copy of the live device tree. That copy holds spin-table release addresses that change at every boot, so the other 11 cores failed to start. The numbers below were taken that way unless a line says 12 CPUs. The identity ranges ship properly inside the
boot.binthat m1n1 merges at boot; I then rebuiltboot.binwith the same 33-byte change (tunnels 1-3 of the 11 DTBs that have them), booted with all 12 CPUs, and reran the same commands:More CPUs help the small model a lot and the 7B not at all (decode at 1, 2, 4, 8 and 12 CPU threads is 29.6 t/s in each case, on P or E cores alike). So decode on the 7B is limited on the GPU side, not by the CPU. The 7B number moved between 28.9 and 31.7 t/s across runs and boots; I do not know why, and the earlier 31.65 is the high end, not a better configuration.
egpuVRAM self-test: 0 mismatches in 4 KiB through MM_INDEX, 512 qwords through BAR 0, 64 KiBwriteq/readqandmemset_io/memcpy_toio; PSP, SMU 82.97.0 and DMUB initialise; every ring registers;[drm] Initialized amdgpu 3.64.0. No amdgpu error, fault or alignment line indmesgafter the benchmark runs.vulkaninfo --summary:AMD Radeon RX 7600 (RADV NAVI33), conformance 1.4.5.3, 8 GiB device-local heap.GGML_VK_VISIBLE_DEVICES=0 llama-bench -m qwen2.5-7b-instruct-q4_k_m.gguf -p 512 -n 128 -r 3 -ngl 99: pp512 967.77 ± 1.09 t/s, tg128 31.65 ± 0.03 t/s (one CPU; see the table below for 12 CPUs).llama-cli --temp 0 -p "The capital of France is"answers "Paris."0xffffffff; after it BAR 0 reads return data.memcpyin the Vulkan driver.The run above used the same code on the 7.1.12-based tree this was developed on (same logic; the firmware-copy sites differ because this tree already routes them through
psp_copy_fw). The series in this PR is rebased onintegration/aurora; on this tree I compiled the touched objects with LLVM 22 (W=1) and built the Apple DTBs; I have not booted it on hardware. Compile: one warning,dml_core_mode_supportstack frame 2080 > 2056 indisplay_mode_core.c, which appears because commit 1 turns on DC_FP for arm64. A build withCONFIG_WERROR=yfails on it; I do not know which setting the package build uses.Decode speed against the llama.cpp Vulkan thread
The llama.cpp Vulkan benchmark thread (ggml-org/llama.cpp discussion 10879) lists an RX 7600 at pp512 1167.20 and tg128 58.03 t/s for Llama 2 7B Q4_0 (commit 91f8c9c, a much older build than the 65840ed53 used here). The same command on this eGPU (
RADV_PERFTEST=nogttspill llama-bench -m llama-2-7b.Q4_0.gguf -ngl 100 -fa 0,1 -r 3, 12 CPUs): fa 0 pp512 1005.43 ± 1.80, tg128 29.26 ± 0.03; fa 1 pp512 1226.01 ± 0.38, tg128 32.88 ± 0.01. Against the listed value that is 86 % of the prompt speed and 50 % of the decode speed. I have not run a native RX 7600 with the current llama.cpp, so the real gap today is unknown.What I tested for the decode gap, with results:
highDPM,sam,nogttspill: no change (under 2 %).test-backend-ops. I do not know why.GGML_VK_DISABLE_MMVQ=1gives +8 % decode (29.86 to 32.24 t/s on the Q4_0 model). I have not tried it on a native card.None of this is a kernel finding, and nothing here changes with this series.
Conditions before merge
PORT_PREFMEM_ENABLEwrite does no harm there.Not tested
rangesfollow the same rule as tunnel 1; tunnel 0 already used it.vkcube, Navi 48 (RDNA 4), and cards other than the RX 7600.Config and firmware notes
DRM_AMD_DC_FP).amdgpu/firmware set from linux-firmware for Navi 33 (PSP, SMU, SDMA, GC, VCN, DMCUB).ranges.Update (2026-10-29): second card (RX 9070, RDNA 4) and surprise unplug/replug
Tested on hardware, on a lab build of 7.2.11 (release string ending in
-7f91446; tree commit7f91446beon base7876c8e83, an ancestor ofintegration/aurora), carrying the amdgpu/ttm commits of this series (7 of the 10), an unrelated GPU statistics export, and a lab kernel config. This is not a hardware test of the current PR head: the three PCI/DTS commits (62f8a9ea,a43f1e95,654075e5) were not in the booted tree, and its device trees come from the base. The rebuilds have since run: the ten-commit head and the current eleven-commit head (with the MQD restore-flush commit) both boot and compute on the same machine with identical results and identical output hashes; see the two hardware follow-up comments (issuecomment-6508496398 and issuecomment-6509593634).Measured on that kernel, one boot, both cards:
[drm] Initialized amdgpu 3.64.0on both minors. Zero SError / alignment-fault / GPU-hang lines across every run of the day, including the hotplug below.So the earlier "hot-plug untested" and Navi 48 gaps in the table above are now measured on the kernel described above (idle machine, one cable at a time). An earlier run on the same machine showed the stock tree initializing neither card.