You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Updated. The original version of this issue concluded that CCE faults could not be attributed without a rebuild. That was wrong — see the correction. This body is the current state; measured and inferred claims are marked separately throughout.
Summary
GPU memory faults in CI were reported as a bare device address. All four GPU lanes can now name the faulting kernel. #1800 ships this. What remains open is listed at the bottom.
Method
A known out-of-bounds device write was injected into s_tvd_rk (m_time_steppers.fpp, inside the GPU_PARALLEL_LOOP, under if (igr), #ifdef MFC_GPU):
q_cons_ts(1)%vf(i)%sf(j +2000000000, k, l) =1._wp
Ground truth is therefore known exactly: m_time_steppers.fpp:486, s_tvd_rk. Every claim marked measured below was checked against it on real CI hardware. This mattered — four mechanisms that looked correct turned out to do nothing, and each was only caught because the right answer was known in advance.
ROCm debug agent → Disassembly for function s_tvd_rk$m_time_steppers_$ck_L486_6
measured
CCE/OpenMP
agent hooks ROCr, below both offload models, so it should fire — but the symbol above is CCE's OpenACC mangling, and whether module/subroutine/line survive in OpenMP-offload form is unknown
expected, untested
NVHPC and AFAR already printed the faulting kernel unaided on the first attempt. The premise the original design rested on — "a fault reports only an address, so spend a retry" — was false on both.
The real gap on NVHPC was different: is_gpu_memory_fault() matched only AMD's wording, so 189 faults on a Phoenix gpu-acc shard were never classified as GPU faults at all. NVHPC says Accelerator Fatal Error / CUDA_ERROR_ILLEGAL_ADDRESS.
CCE: what works and what doesn't
Works — the ROCm debug agent (HSA_TOOLS_LIB=librocm-debug-agent.so.2)
Measured on Frontier, CCE 19.0.0 / ROCm 6.3.1 / cpe/25.03, --gpu acc. Prints straight to the job log: the faulting kernel by name, the faulting instruction marked =>, per-wave scalar/vector/system/trap registers, and wave_N: pc=0x… (stopped, reason: MEMORY_VIOLATION). No recompile, no execution-model change, no core files, no post-mortem step.
Volume: ~14k lines for 125 faulting waves on a 49×39 case — one identical disassembly block and a 115-line register dump repeated per wave. summarize_rocm_debug_agent() collapses that to ~37 lines.
A fixed tail cannot substitute. On the real report the first 80 lines are one wave's registers and the last 80 are another's, and the kernel name appears in neither. The summary keeps the full stop-PC histogram because the waves halted at four distinct PCs whose modal one is a load while the injected fault is a write — quoting a single PC hands the reader the wrong instruction.
Doesn't work — CRAY_ACC_DEBUG
Deliberately not enabled. The last kernel logged before each fault was s_write_run_time_information81 of 102 traced faults, and the true culprit 0. CCE defaults to acc_model=auto_async_kernel, so the fault surfaces at whatever sync point catches it — here m_data_output's readback for the progress line. It names the routine owning the sync point, not the bad write. AMD_SERIALIZE_KERNEL/COPY do not help: HIP variables, and CCE's runtime is not HIP.
-hacc_model=auto_async_none would fix it, but it is a compile flag, and shipping it would stop CI exercising the asynchronous dispatch production uses — a coverage loss traded for fault messages. Rejected.
Doesn't work in CI — GPU core dumps
HSA_ENABLE_DEBUG=1 produces a dump, but fallocate() is not implemented on Lustre. Same binary/case/node/fault, varying only cwd:
cwd
gpucore
LOAD segments
result
/tmp (tmpfs)
305 MB
21
complete
/dev/shm (tmpfs)
305 MB
21
complete
Lustre
1296 B
0
Failed to allocate file: Bad file descriptor
MFC runs tests in tests/<UUID>/ on Lustre, so that lane can never produce a usable dump where it runs. This explains GPU core dump failed in the original CI logs. Superseded by the debug agent, which needs no workaround — and the two are mutually exclusive (with the agent loaded, ROCr reports Failed to enable debug interface and writes no dump).
If anyone pursues dumps anyway: ulimit -c unlimited is mandatory (with ulimit -c 0 ROCr silently skips the GPU dump — no file, no error), and a gpucore alone is not debuggable — rocgdb needs roccoremerge merged.core core gpucore.<pid> first.
Diagnostics on every run, not a retry. Both OFFLOAD_TRACK_* variables are inert until an abort, so withholding them only cost an extra run to learn what the first could have said. The diagnostic retry is removed.
OFFLOAD_TRACK_ALLOCATION_TRACES (was the address ever a real allocation) and OFFLOAD_TRACK_NUM_KERNEL_LAUNCH_TRACES=8 (launch stack traces).
HSA_TOOLS_LIB wherever the agent library is reachable, plus summarize_rocm_debug_agent().
NVHPC signatures, and a distinct "GPU memory fault" bucket in classify_error.
The availability gate is evaluated per call, never at import. On Frontier the library is on disk the whole time but only reaches LD_LIBRARY_PATH once mfc.sh load runs — an import-time gate reports "absent" on the one machine this exists for, indistinguishably from Phoenix where it truly is missing. It probes for the file rather than dlopening it, so testing the subprocess's environment does not load a debug agent into the harness.
Open
CCE OpenMP is untested. Every command in the investigation was --gpu acc. One --gpu mp run with the injection moves the last cell from expected to measured — the question is the symbol form, not whether the agent fires.
The AFAR interaction is unverified. Both the agent and the OFFLOAD_TRACK_* variables are reachable on frontier_amd. The agent is mutually exclusive with ROCr core dumps, so it may likewise supersede libomptarget's own fault report. Worst realistic case is one working diagnostic replacing a more detailed one; if a real fault shows otherwise, gate HSA_TOOLS_LIB on the lane.
Agent overhead is n=1. One paired A/B: 4.5645 ns/gp/eq/rhs against an agent-free spread of 4.5301–4.5614 — 0.07% above a range 0.69% wide, i.e. inside the noise. The repeats were cancelled by decision, not measured. This is no effect detected, not no effect.
The summarizer's regexes are anchored to ROCm 6.3.1's format, observed on one fault shape. A format change degrades silently: no wave match → empty summary → fall back to 14k raw lines, with nothing saying why. A test pins the field set against the real report's output, but the raw log did not survive teardown, so the input fixture is a reconstruction.
bench.py and run_case_optimization.sh still have nothing. Both run GPU cases; neither classifies a fault or gets any of the above.
Method notes
Two failure modes account for nearly every dead end here, and both are worth naming:
Checking that an artifact exists rather than that it contains anything. The original core-dump test checked find . -name 'gpucore.*' and scored any hit as success — but the Lustre stub is a valid AMD HSA core ELF with every program header NULL, so it passes a filename check while containing no GPU memory. Verify LOAD segments (readelf -l), not filenames. The same shape produced the original bug in DO NOT MERGE: diagnose a GPU memory fault on the retry that follows it #1800: tests asserted the source mentioned the right identifiers, so a marker written on one side and never read on the other passed seven of them.
Inference presented as measurement.AMD_SERIALIZE_* was asserted from the mechanism and did nothing. "CCE cannot attribute" generalised from CCE's own trace to the machine and was wrong. The CCE OpenMP row above was an inference sitting in a table of results. Where a claim here is not measured, it says so.
Summary
GPU memory faults in CI were reported as a bare device address. All four GPU lanes can now name the faulting kernel. #1800 ships this. What remains open is listed at the bottom.
Method
A known out-of-bounds device write was injected into
s_tvd_rk(m_time_steppers.fpp, inside theGPU_PARALLEL_LOOP, underif (igr),#ifdef MFC_GPU):Ground truth is therefore known exactly:
m_time_steppers.fpp:486,s_tvd_rk. Every claim marked measured below was checked against it on real CI hardware. This mattered — four mechanisms that looked correct turned out to do nothing, and each was only caught because the right answer was known in advance.Attribution, per lane
Function: s_tvd_rk:438,Line: 486— free, attempt 1Kernel 0: ..._QMm_time_steppersPs_tvd_rk @ 486, 189/189 — free, attempt 1Disassembly for function s_tvd_rk$m_time_steppers_$ck_L486_6NVHPC and AFAR already printed the faulting kernel unaided on the first attempt. The premise the original design rested on — "a fault reports only an address, so spend a retry" — was false on both.
The real gap on NVHPC was different:
is_gpu_memory_fault()matched only AMD's wording, so 189 faults on a Phoenixgpu-accshard were never classified as GPU faults at all. NVHPC saysAccelerator Fatal Error/CUDA_ERROR_ILLEGAL_ADDRESS.CCE: what works and what doesn't
Works — the ROCm debug agent (
HSA_TOOLS_LIB=librocm-debug-agent.so.2)Measured on Frontier, CCE 19.0.0 / ROCm 6.3.1 / cpe/25.03,
--gpu acc. Prints straight to the job log: the faulting kernel by name, the faulting instruction marked=>, per-wave scalar/vector/system/trap registers, andwave_N: pc=0x… (stopped, reason: MEMORY_VIOLATION). No recompile, no execution-model change, no core files, no post-mortem step.Volume: ~14k lines for 125 faulting waves on a 49×39 case — one identical disassembly block and a 115-line register dump repeated per wave.
summarize_rocm_debug_agent()collapses that to ~37 lines.A fixed tail cannot substitute. On the real report the first 80 lines are one wave's registers and the last 80 are another's, and the kernel name appears in neither. The summary keeps the full stop-PC histogram because the waves halted at four distinct PCs whose modal one is a load while the injected fault is a write — quoting a single PC hands the reader the wrong instruction.
Doesn't work —
CRAY_ACC_DEBUGDeliberately not enabled. The last kernel logged before each fault was
s_write_run_time_information81 of 102 traced faults, and the true culprit 0. CCE defaults toacc_model=auto_async_kernel, so the fault surfaces at whatever sync point catches it — herem_data_output's readback for the progress line. It names the routine owning the sync point, not the bad write.AMD_SERIALIZE_KERNEL/COPYdo not help: HIP variables, and CCE's runtime is not HIP.-hacc_model=auto_async_nonewould fix it, but it is a compile flag, and shipping it would stop CI exercising the asynchronous dispatch production uses — a coverage loss traded for fault messages. Rejected.Doesn't work in CI — GPU core dumps
HSA_ENABLE_DEBUG=1produces a dump, butfallocate()is not implemented on Lustre. Same binary/case/node/fault, varying only cwd:/tmp(tmpfs)/dev/shm(tmpfs)Failed to allocate file: Bad file descriptorMFC runs tests in
tests/<UUID>/on Lustre, so that lane can never produce a usable dump where it runs. This explainsGPU core dump failedin the original CI logs. Superseded by the debug agent, which needs no workaround — and the two are mutually exclusive (with the agent loaded, ROCr reportsFailed to enable debug interfaceand writes no dump).If anyone pursues dumps anyway:
ulimit -c unlimitedis mandatory (withulimit -c 0ROCr silently skips the GPU dump — no file, no error), and a gpucore alone is not debuggable —rocgdbneedsroccoremerge merged.core core gpucore.<pid>first.What #1800 ships
OFFLOAD_TRACK_*variables are inert until an abort, so withholding them only cost an extra run to learn what the first could have said. The diagnostic retry is removed.OFFLOAD_TRACK_ALLOCATION_TRACES(was the address ever a real allocation) andOFFLOAD_TRACK_NUM_KERNEL_LAUNCH_TRACES=8(launch stack traces).HSA_TOOLS_LIBwherever the agent library is reachable, plussummarize_rocm_debug_agent()."GPU memory fault"bucket inclassify_error.The availability gate is evaluated per call, never at import. On Frontier the library is on disk the whole time but only reaches
LD_LIBRARY_PATHoncemfc.sh loadruns — an import-time gate reports "absent" on the one machine this exists for, indistinguishably from Phoenix where it truly is missing. It probes for the file rather thandlopening it, so testing the subprocess's environment does not load a debug agent into the harness.Open
--gpu acc. One--gpu mprun with the injection moves the last cell from expected to measured — the question is the symbol form, not whether the agent fires.OFFLOAD_TRACK_*variables are reachable onfrontier_amd. The agent is mutually exclusive with ROCr core dumps, so it may likewise supersede libomptarget's own fault report. Worst realistic case is one working diagnostic replacing a more detailed one; if a real fault shows otherwise, gateHSA_TOOLS_LIBon the lane.bench.pyandrun_case_optimization.shstill have nothing. Both run GPU cases; neither classifies a fault or gets any of the above.Method notes
Two failure modes account for nearly every dead end here, and both are worth naming:
find . -name 'gpucore.*'and scored any hit as success — but the Lustre stub is a valid AMD HSA core ELF with every program header NULL, so it passes a filename check while containing no GPU memory. Verify LOAD segments (readelf -l), not filenames. The same shape produced the original bug in DO NOT MERGE: diagnose a GPU memory fault on the retry that follows it #1800: tests asserted the source mentioned the right identifiers, so a marker written on one side and never read on the other passed seven of them.AMD_SERIALIZE_*was asserted from the mechanism and did nothing. "CCE cannot attribute" generalised from CCE's own trace to the machine and was wrong. The CCE OpenMP row above was an inference sitting in a table of results. Where a claim here is not measured, it says so.