Skip to content

Commit 39dd87d

Browse files
committed
Fix native GPU with gap junctions via CPU fixed-step fallback
Gap/partrans models register nrnthread_vi_compute_, forcing host post-solve. The hybrid GPU solve plus device→host sync_gap_after_voltage_update was pulling stale device voltages over the host nrn_update_voltage result (0 spikes on nrntraub with use_gap=1). Add sync_gap_after_host_voltage_update to push host voltages to device when post-solve ran on the host. Full hybrid stepping still diverged on Traub; until device gap gather/scatter is complete, dispatch the CPU fixed-step body when nrnthread_v_transfer_ is registered so native GPU enable_gpu models with gaps match CPU rasters. Add test/gjtests/test_par_gj_native_gpu.py comparing ggap voltages CPU vs native GPU.
1 parent 8fe0e92 commit 39dd87d

7 files changed

Lines changed: 88 additions & 7 deletions

File tree

src/neuron/gpu/fadvance_gpu.cpp

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ void fixed_step_thread(model_sorted_token const& cache_token,
5656
advance_first_half_time(nt);
5757
fixed_play_continuous(nth);
5858
sync_after_vecplay(nt);
59+
bool const host_post_solve = nt.end > 0 && post_solve_needs_host_fallback(nt);
5960
if (nt.end > 0) {
6061
setup_tree_matrix(cache_token, nt);
6162
sync_matrix_to_device_before_solve(nt);
@@ -68,7 +69,7 @@ void fixed_step_thread(model_sorted_token const& cache_token,
6869
nrn_solve(nth);
6970
}
7071
}
71-
if (post_solve_needs_host_fallback(nt)) {
72+
if (host_post_solve) {
7273
sync_rhs_to_host_after_solve(nt);
7374
{
7475
nrn::Instrumentor::phase p("second-order-cur");
@@ -91,7 +92,14 @@ void fixed_step_thread(model_sorted_token const& cache_token,
9192
}
9293
if (nrnthread_v_transfer_) {
9394
if (nt.end > 0) {
94-
sync_gap_after_voltage_update(nt);
95+
if (host_post_solve) {
96+
// Host nrn_update_voltage already updated vec_v; push to device and
97+
// leave host voltages intact for partrans gather (device→host would
98+
// overwrite with stale GPU state).
99+
sync_gap_after_host_voltage_update(nt);
100+
} else {
101+
sync_gap_after_voltage_update(nt);
102+
}
95103
if (nrnmpi_v_transfer_) {
96104
(*nrnmpi_v_transfer_)();
97105
}

src/neuron/gpu/sync.cpp

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,4 +168,8 @@ void sync_gap_after_voltage_update(NrnThread& nt) {
168168
sync_node_voltages_to_host(nt);
169169
}
170170

171-
} // namespace neuron::gpu
171+
void sync_gap_after_host_voltage_update(NrnThread& nt) {
172+
sync_node_voltages_to_device(nt);
173+
}
174+
175+
} // namespace neuron::gpu

src/neuron/gpu/sync.hpp

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,10 @@ void sync_voltages_to_host_after_post_solve(NrnThread& nt);
3434
/** Pull fast_imem sav_rhs to host after GPU fast_imem (smaller than vec_rhs sync). */
3535
void sync_fast_imem_to_host_after_post_solve(NrnThread& nt);
3636

37-
/** Ensure gap-junction source voltages are visible on host before MPI transfer. */
37+
/** Pull device post-solve voltages to host before gap gather (device post-solve path). */
3838
void sync_gap_after_voltage_update(NrnThread& nt);
3939

40-
} // namespace neuron::gpu
40+
/** Push host post-solve voltages to device before gap gather (host fallback path). */
41+
void sync_gap_after_host_voltage_update(NrnThread& nt);
42+
43+
} // namespace neuron::gpu

src/nrnoc/fadvance.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -482,7 +482,10 @@ static void nrn_fixed_step_group_thread(neuron::model_sorted_token const& cache_
482482

483483
static void nrn_fixed_step_thread(neuron::model_sorted_token const& cache_token, NrnThread& nt) {
484484
#if defined(NRN_ENABLE_GPU)
485-
if (neuron::gpu::enabled() && neuron::gpu::backend_native()) {
485+
// Gap/partrans models use host post-solve (nrnthread_vi_compute_) and partrans
486+
// gather/scatter on host; the full native GPU step is not yet consistent for that
487+
// hybrid. Run the CPU fixed-step body until device gap staging is complete.
488+
if (neuron::gpu::enabled() && neuron::gpu::backend_native() && !nrnthread_v_transfer_) {
486489
neuron::gpu::device_token const& dev = neuron::gpu::ensure_on_device(cache_token);
487490
neuron::gpu::fixed_step_thread(cache_token, dev, nt);
488491
return;
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
#!/usr/bin/env python3
2+
"""Record test_par_gj dend voltages to a .npy file. Args: native(0|1) output.npy"""
3+
import sys
4+
5+
import numpy as np
6+
from neuron import h, gpu
7+
8+
import test_par_gj as t
9+
10+
native = bool(int(sys.argv[1]))
11+
out_path = sys.argv[2]
12+
13+
pc = h.ParallelContext()
14+
t.mkcells(pc, 4)
15+
t.mkgjs(pc, 4)
16+
pc.setup_transfer()
17+
gpu.enable = native
18+
if native:
19+
gpu.backend = "native"
20+
h.dt = 0.25
21+
pc.set_maxstep(10)
22+
h.finitialize(-65)
23+
pc.psolve(500)
24+
np.save(out_path, np.column_stack([v.to_python() for v in t.vrecs]))

test/gjtests/test_par_gj.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,4 +188,5 @@ def main():
188188
h.quit()
189189

190190

191-
main()
191+
if __name__ == "__main__":
192+
main()
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
#!/usr/bin/env python3
2+
"""Compare test_par_gj voltages: CPU vs native GPU (gap junctions)."""
3+
4+
import subprocess
5+
import sys
6+
import tempfile
7+
from pathlib import Path
8+
9+
import numpy as np
10+
11+
_SCRIPT = Path(__file__).resolve().parent
12+
_RECORD = _SCRIPT / "record_par_gj_voltages.py"
13+
14+
15+
def run(native: bool, out_path: Path):
16+
subprocess.run(
17+
[sys.executable, str(_RECORD), str(int(native)), str(out_path)],
18+
cwd=_SCRIPT,
19+
check=True,
20+
)
21+
22+
23+
def main():
24+
with tempfile.TemporaryDirectory() as tmp:
25+
cpu_path = Path(tmp) / "cpu.npy"
26+
gpu_path = Path(tmp) / "gpu.npy"
27+
run(False, cpu_path)
28+
run(True, gpu_path)
29+
v_cpu = np.load(cpu_path)
30+
v_gpu = np.load(gpu_path)
31+
max_diff = float(np.max(np.abs(v_cpu - v_gpu)))
32+
print("par_gj native_gpu max voltage diff:", max_diff)
33+
if max_diff > 1e-6:
34+
raise SystemExit(1)
35+
36+
37+
if __name__ == "__main__":
38+
main()

0 commit comments

Comments
 (0)