Fix: dispatch device memory wrappers by exact source/destination device pair - #7941
Conversation
`synchronize_memory()` and `cast_memory()` select a compile-time specialization from a pair of runtime `AbacusDevice_t` values, but joined their branch conditions with `||`. The first branch therefore matched whenever *either* side was the CPU, so a CPU->GPU or GPU->CPU transfer was served by the CPU-to-CPU specialization, i.e. a plain host `memcpy`/cast on a device pointer. GPU->GPU could likewise match an earlier mixed-device branch. The four combinations are mutually exclusive and must be tested with `&&`. The GPU branches are now compiled only when a GPU backend is enabled, which matches the guard on the `*_op` GPU specializations in memory_op.h, and an unsupported device combination fails loudly instead of falling through silently. Both wrappers are declared in memory_op.h but were defined in the .cpp with no explicit instantiation, so they could not be linked from another translation unit and had no call sites -- which is why the defect was latent. Add the instantiations (for `cast_memory`, only the type pairs that `cast_memory_op` provides for all four device combinations) and unit tests that pin the dispatch: CPU-to-CPU everywhere, and all four pairs under __UT_USE_CUDA/__UT_USE_ROCM. No documentation change: this is an internal dispatch fix with no INPUT, output or user-visible behaviour change. Fixes deepmodeling#7553 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Note for reviewers, since attempt 1 of That run failed on The case's Rather than argue that, I reran the job: attempt 2 passed (1h8m). So this is a marginally-tuned GPU hybrid-functional case, in the same family as #7925, and not a regression from this PR. All 17 checks are green now. Worth someone's attention separately: a case sitting 3.9% from its threshold on GPU will keep producing spurious red PRs. |
Reminder
AGENTS.mdanddocs/developers_guide/agent_governance.md.source/changes.Linked Issue
Fix #7553
Unit Tests and/or Case Tests for my changes
cmake -B build -G Ninja -DBUILD_TESTING=ON -DENABLE_LCAO=ON -DENABLE_MPI=ON -DENABLE_OPENMP=ON):cmake --build build -j15 -- -k 0— full build, 3318/3318 targets, 0 errors.ctest --test-dir build -R MODULE_BASE_DEVICE -V— passed, including the three new CPU dispatch tests.ctest --test-dir build -R 'MODULE_PW_pw_test$|MODULE_ESTATE_elecstate_pw|MODULE_BASE_DEVICE'— 3/3 passed. These two targets compilememory_op.cppdirectly in a reduced configuration, so they confirm the newtool_quit.hdependency and the explicit instantiations link there as well.__UT_USE_CUDA/__UT_USE_ROCMand were not executed — no GPU on the machine used. They are the ones that exercise the actual host/device mis-dispatch and need a GPU validation run.What's changed?
synchronize_memory()andcast_memory()pick a compile-time specialization from a pair of runtimeAbacusDevice_tvalues, but joined their branch conditions with||:The first branch matches whenever either side is the CPU, so
(GPU, CPU)and(CPU, GPU)both reach the CPU-to-CPU specialization — a plain hostmemcpy/cast on a device pointer.(GPU, GPU)matches the second branch for the same reason. The four combinations are mutually exclusive, so each branch has to test both devices with&&. That is the fix.Two things fall out of it:
#if __CUDA || __UT_USE_CUDA || __ROCM || __UT_USE_ROCM, matching the guard on the*_opGPU specializations inmemory_op.h. Without it these wrappers cannot be instantiated at all in a CPU-only build, becausesynchronize_memory_op<T, DEVICE_CPU, DEVICE_GPU>then resolves to the primary template, whoseoperator()has no definition.DspDevice,UnKnown) now hitsWARNING_QUITinstead of falling out of theif/elsechain and silently leaving the destination buffer untouched.Severity, stated plainly: the defect is currently latent. Both wrappers are declared in
memory_op.hbut were defined in the.cppwith no explicit instantiation, so they cannot be linked from another translation unit, andgrepfinds no call site anywhere insource/. Nothing miscopies today. What this PR fixes is a trap: the first caller to use the documented API would have got a silent hostmemcpyon a device pointer, with no diagnostic.So the PR also makes the declared API real — explicit instantiations for
synchronize_memory(int,float,double,std::complex<float>,std::complex<double>) and forcast_memory(only the eight type pairs thatcast_memory_opprovides for all four device combinations, so the instantiation list is identical in CPU-only and GPU builds) — and adds unit tests insource_base/module_device/test/memory_test.cppthat pin the dispatch: CPU-to-CPU in every configuration, and all four pairs under__UT_USE_CUDA/__UT_USE_ROCM.Not changed, and worth a separate look:
resize_memory(),set_memory()anddelete_memory()dispatch on a single device, so they have no||defect, but they too fall through silently on an unrecognisedAbacusDevice_t. I left them alone to keep this diff scoped to the issue.Governance Notes
docs/parameters.yamlandinput-main.mdare untouched.source_base/module_device.memory_op.cppgains an#include "source_base/tool_quit.h", which is not a new module-level dependency —device.cppin the same directory already usesModuleBase::WARNING_QUIT.GlobalV/GlobalC/PARAMreferences; no new default arguments; no new source files.🤖 Generated with Claude Code