Skip to content

Hand-written CUDA vs code generation: port bstar_parallel_3form both ways (decision for #690) - #718

Draft
max-models wants to merge 4 commits into
mhd-equilibria-cupyfrom
cuda-codegen-spike
Draft

max-models wants to merge 4 commits into
mhd-equilibria-cupyfrom
cuda-codegen-spike

Conversation

@max-models

@max-models max-models commented Oct 7, 2026 •

Copy link
Copy Markdown
Member

Stack: part 8 of 14, based on #714 (merge that first), next: #720. Full order: #709 → #708 → #711 → #712 → #705 → #713 → #714 → #718 → #720 → #721 → #724 → #725 → #723 → #722.


Solves the following issue(s):

Solves #690 (the decision part), tracked in #650. Decides whether the guiding-center kernels (step 3 of the porting order in CUDA_STRATEGY.md) are ported by hand or generated from the pyccel source. To decide, one guiding-center kernel, bstar_parallel_3form, was ported both ways.

Decision: hand-written CUDA stays. The comparison table, the reasoning and what would change the decision are in the new CUDA_STRATEGY.md section Hand-written CUDA vs. code generation (decision for #690). Short version:

  • The shared code is already ported. Most of what a generator would save is the helper chain: mappings, metric, splines, linear algebra. PRs 10–19 have already ported and tested it by hand. What is left per kernel is its body: here 36 lines of CUDA plus one 5-line helper.
  • A generator would be a second compiler to maintain.
    • The numba prototype has 458 lines and covers only part of what the kernels use.
    • On all 60 pusher and accumulation entry kernels it translates 31. It fails on 2D slice assignments in the linear-algebra helpers and on numpy.abs in the SPH helpers.
    • Its mistakes are silent. It changes array lengths when a size is known only at run time, and it cannot tell which writes need atomics, so a translated accumulation kernel would race.
  • numba does not fit the existing infrastructure.
    • numba kernels do not take the C structs. The prototype needs 38 flat parameters, plus a second launch path next to cunumpy's CudaKernel.
    • The parity tests and the CPU emulation compile the real .cu. numba's simulator only interprets the Python.
    • numba adds about 175 MB of dependencies (numba, llvmlite, numba-cuda) and supports only a range of NumPy versions.
  • The other generators:
    • cupyx.jit would need the same rewriting of the pyccel source. Its reference lists no per-thread arrays and no struct arguments. Not run here (no CuPy).
    • pyccel 2.2.1 has no CUDA backend. The separate pyccel-cuda fork (last commit January 2025) still has open issues for thread indexing and passing arrays.
  • What would change the decision:
    • pyccel releases a CUDA backend that accepts our argument classes. It could then generate the .cu files in place, behind the same tests.
    • Porting by hand becomes the bottleneck. Then the next step is a generator that writes .cu text (not numba), reusing the prototype's AST transformations.

Core changes:

  • Hand-written port (a real port; it stays):
    • pic/pushing/kernels/bstar_parallel_3form/bstar_parallel_3form_cuda.cu, 1:1 with pyccel: same names and arguments, one thread per marker row, holes skipped as in pyccel (markers[ip, 0] == -1). numpy.mod(eta, 1.0) becomes eta - floor(eta) (Fortran MODULO, which pyccel generates).
    • It reuses df (every mapping), det, get_spans/SplineScratch and eval_spline_mpi_kernel.
    • New device helper eval_0form_spline_mpi in bsplines/evaluation_kernels_3d.cuh.
    • The pyccel kernel and its callers (KernelSetup in the guiding-center propagators) are unchanged.
  • Parity cases: 14 cases in pic/tests/cuda_parity_cases.py.
    • One per mapping: the 10 analytic mappings plus IGAPolarCylinder, IGAPolarTorus, Tokamak and a 3d spline.
    • α = 1, 0 and mixed. Shifts make eta + shift wrap around in mod, row 0 has a negative eta_n and row 2 is a hole.
    • Two output columns; launch size args_markers.n_markers.
    • The CPU emulation agrees with pyccel to at most 5.7e-14. Removing the mod from the CUDA kernel makes the emulation test fail.
  • Generated prototype (test-only): pic/tests/codegen_spike/ (numba_codegen.py, compare.py, test_numba_codegen.py).
    • It translates the pyccel source of the kernel and every helper it calls into one numba.cuda module (490 lines, 24 device functions).
    • With numba installed, the generated kernel agrees with pyccel on the same 14 cases (max 6.3e-13): in the numba.cuda simulator (NUMBA_ENABLE_CUDASIM=1) and compiled with numba.njit.
    • Not checked: whether numba.cuda compiles it for a GPU (needs NVVM).
    • Skipped without numba, which is not a struphy dependency. There is no kernels in the module names, so struphy compile ignores it.
    • It can be deleted once the decision is accepted.

Model-specific changes:

None. bstar_parallel_3form is one of the init kernels of PushGuidingCenterBxEstar and PushGuidingCenterParallel. Neither propagator runs on the GPU yet: the rest of their chain is not ported (driftkinetic_hamiltonian, unit_b_1form, bstar_2form, the discrete-gradient pushers, gc_density_0form).

Documentation changes:

CUDA_STRATEGY.md:

Testing (macOS, no GPU; pyccel kernels compiled with GNU/Fortran; GPU tests not run, so the new kernel has not run on a GPU):

before (devel) after
test_cuda_emulation.py, test_cuda_parity.py, test_kernel_backends.py, test_device_helpers.py 400 collected (83 passed, 317 skipped) 415 collected: 84 passed, 331 skipped (+1 emulation test with 14 cases, +14 GPU parity tests skipped)
mpirun -n 1 pytest test_kernel_setup.py test_pushers.py 181 passed 181 passed
codegen_spike/test_numba_codegen.py – 2 skipped without numba; 2 passed with numba 0.68 and numba-cuda 0.24 installed

The "before" pass/skip split is derived from the collected tests: this PR adds exactly one emulation test and 14 GPU parity tests.

🤖 Generated with Claude Code

…ways (decision for #690)

- bstar_parallel_3form_cuda.cu: hand-written CUDA version, 1:1 with pyccel, on the existing
  device helpers; new device helper eval_0form_spline_mpi in evaluation_kernels_3d.cuh.
- 14 parity cases (every mapping, alpha = 1/0/mixed, wrap-around in mod, hole) in
  cuda_parity_cases.py; passes the CPU emulation.
- pic/tests/codegen_spike: prototype generator translating the pyccel source (kernel and all
  helpers) into numba.cuda; agrees with pyccel in the numba.cuda simulator and under njit.
  Test-only, skipped without numba.
- CUDA_STRATEGY.md: decision section (hand-written stays), comparison and what would change it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Stack the PR on #714.

Conflicts in CUDA_STRATEGY.md: kept the linear_vlasov_ampere checklist entry and #690's decision in the
Next entry (without the steps done below), the marker exchange note of #712, and the polar-splines open question of #714.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@max-models
max-models changed the base branch from devel to mhd-equilibria-cupy October 7, 2026 22:09
max-models added a commit that referenced this pull request Oct 7, 2026
Stack the PR on #718.

Conflicts in CUDA_STRATEGY.md: porting-order gates and open questions now say that polar splines (#695) and
MHD equilibria (#696) both run on CuPy; kept the marker exchange, linear_vlasov_ampere, vlasov_maxwell and
polar splines notes, in PR order.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
max-models added a commit that referenced this pull request Oct 8, 2026
Stack the PR on #723.

Conflicts: eval_spline_mpi_{markers,matrix,sparse_meshgrid}_cuda.cu (SplineArgs of #721 with the qualified
evaluation_kernels_3d::eval_spline_mpi), linalg_kernels.cuh, filler_kernels.cuh, particle_to_mat_kernels.cuh
(helpers of #705/#713 moved into their module namespaces).
Semantic fixes: the struphy_cuda::<pyccel module> rule applied to the CUDA code of the PRs below:
linear_vlasov_ampere (#705), vlasov_maxwell (#713), bstar_parallel_3form (#718) and
eval_spline_mpi_tensor_product_fixed (#724) use 'using namespace struphy_cuda;' and qualified helper calls;
m_v_fill_b_v1_symm calls filler_kernels:: and evaluation_kernels_3d::; the fill_v1_symm test wrapper
calls particle_to_mat_kernels::m_v_fill_b_v1_symm.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@max-models
max-models added this pull request to stack #728 October 8, 2026 05:45
@max-models
max-models removed this pull request from stack #728 October 8, 2026 06:00
@max-models
max-models added this pull request to stack #729 October 8, 2026 06:00
@max-models
max-models marked this pull request as draft October 8, 2026 09:13

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant