Repository navigation
Conversation
nvcc was run without -arch, so it built only for its default sm_75 and newer GPUs ran the kernels through driver PTX JIT. Pass -arch=native by default (nvcc falls back to its default with a warning when no GPU is present) and add --gpu-arch to select one explicitly. The flag goes on the device compiler, so the kernel, runtime and link commands agree.
Under separate compilation the per-object CUDA device sources are concatenated into one .cu file, and every one of them defined the same _lfortran_cuda_registrar struct and _lfortran_cuda_reg variable, so nvcc rejected the link. Name the registrar after the first kernel, as the cuda_cpu emulation already does, and emit none for a translation unit without kernels. Enable the cuda label on the separately compiled GPU tests that now build.
An empty do concurrent iteration space gives a grid with a zero extent, which cudaLaunchKernel rejects with 'invalid argument'. A launch of no threads runs nothing, so lfortran_gpu_launch now returns before copying anything in or launching when a grid or block extent is not positive. The CPU emulation runtime does the same, so a negative extent is never turned into a huge unsigned block count there either.
Only four integration tests carried the cuda label, so `integration_tests -b cuda` checked almost nothing on a machine with an NVIDIA GPU. Label the 454 cuda_cpu tests that pass with --gpu=cuda on a real GPU (RTX PRO 6000 Blackwell, sm_120). The cuda_cpu tests that still fail there are fixed and labeled separately.
A fixed-size array that every do concurrent iteration defines before it references it is private to the iteration (F2018 11.1.7.5), but the GPU offload pass passed it to the kernel as one buffer that every thread wrote and read. On a real GPU the last writer won (gpu_metal_246 failed with --gpu=cuda); cuda_cpu runs threads serially and hid the race. The privatizer in gpu_offload_privatize.cpp already makes such arrays locals of a BLOCK around the body, but its flow reader: - treated any ASSOCIATE construct as a read, so `t = [...]` inside an ASSOCIATE kept `t` shared; - only recognized whole-array and DO-nest definitions, so the temporary an array constructor is lowered to (`k = lbound(c, 1); c(k) = a; k = k + 1; ...`) and arrays defined element by element stayed shared. It now follows integer locals with known values and counts each element defined, allowing reads of elements already defined. Variables with LOCAL locality (`local(t)`, the region's OMPPrivate clause) were never privatized: the array stayed a shared buffer and a scalar referenced after the loop produced a kernel that did not compile. They now become BLOCK locals unconditionally, so the variable outside the loop is also unaffected, as the standard requires. A LOCAL array a thread cannot hold (e.g. of derived type) is reported as not offloadable instead of racing. gpu_metal_246 now carries the cuda label; gpu_metal_483 covers the privatized cases with n = 100000; gpu_kernel_source_14 checks through CUDA and Metal kernel references that no shared buffer is passed, which CI can observe without an NVIDIA GPU; gpu_decline_17 checks the new diagnostic. gpu_metal_474's CUDA kernel reference changes only by making an array-constant temporary thread-local.
# Conflicts: # src/bin/lfortran.cpp
# Conflicts: # integration_tests/CMakeLists.txt # src/libasr/codegen/asr_to_cuda.cpp
# Conflicts: # integration_tests/CMakeLists.txt
…while merging The CMakeLists.txt conflicts of the label PRs were resolved with our side; re-add every cuda label those PRs carry.
# Conflicts: # integration_tests/CMakeLists.txt
Every generated CUDA kernel unpacked all scalar arguments and declared all named constants and locals, even those the body never uses, so nvcc warned with lfortran#177-D (and host c++ with -Wunused-variable for cuda_cpu). Collect the declarations of a kernel, device function or block, emit its body first, and write only the declarations the body (or another kept declaration) refers to. The scalar argument struct keeps every field, so the host/device launch layout is unchanged.
A type-bound defined operator such as `mk(3.) - mk(1.)` is represented as a call whose passed-object argument and dispatch expression (`m_dt`) are the same expression node. The array_struct_temporary and subroutine_from_function passes saved the argument in a temporary but then saved `m_dt` in a second temporary, so the left operand was evaluated twice (three function calls instead of two). This also affected operands returned as polymorphic allocatable results. When the passed-object argument has been replaced by a temporary, use that temporary as the dispatch expression too. Fixes lfortran#15572
Review follow-up to the per-iteration array privatization. The element tracking kept the known value of an integer local across an assignment like `y = f(k)`, where `f` may change `k` through an intent(inout) argument; `t(k) = ...` was then counted as the wrong element and `t` taken as fully defined. That analysis also runs on the statements after the loop, where impure calls are allowed, so a conforming program lost a value the loop left in `t(1)`. Now only `k = e` with `e` a known call-free value, and an element definition whose value calls nothing but pure functions, keep what is known; any other statement (assignment to anything else, including an associate name of a local, a call, a loop, input) forgets every counter. The diagnostic for an array of LOCAL locality that cannot be given a copy per thread now says why: its type (e.g. type(pair)), that it is a coarray, that an associate construct around the loop names it, or that the body has an associate construct the kernel cannot inline. Also factor the element-type check and the body-name collection into helpers, and document the contract of locality_spec_variables. New tests: gpu_kernel_source_16 (runs everywhere, with CUDA and Metal kernel references) keeps `t` a shared buffer when a call changes the counter after the loop and `u` shared when only part of it is defined before an untouched element is read; gpu_decline_18 and gpu_decline_19 cover the two associate diagnostics.
# Conflicts: # integration_tests/CMakeLists.txt # tests/reference/gpu_cuda_kernel-gpu_metal_474-0583450.json # tests/tests.toml
lfortran_gpu_set_scratch_arg did a cudaMalloc for every scratch buffer (each per-thread workspace) on every launch, freed at the next sync. A kernel with several workspaces launched in a loop spent most of its runtime API time in cudaMalloc/cudaFree. Take the scratch buffer at launch from the memory the kernel's argument slot keeps, as read-only buffers already do and as the Metal runtime does for scratch. It grows when a launch needs more, the old memory being freed at the next sync. What a launch leaves in a scratch buffer is dead once it ends (a part the host reads back is read before the next launch), and the default stream runs launches in order, so pending launches can share it. The new test runs two kernels with workspaces many times between syncs, with workspace sizes that grow, shrink and are zero, and checks the results against the same loops run on the host.
# Conflicts: # integration_tests/CMakeLists.txt
lfortran#15569 makes the array-constant temporary thread-local and lfortran#15570 drops the unused named constant; the merged output has both changes. Also run gpu_kernel_source_15 on the cuda backend.
… Formal ci/cuda_nvidia/README.md gives the exact steps to build LFortran, check the backend, run the GPU integration tests on a real GPU, and validate BerkeleyLab Formal (tests, both solvers, full-field comparisons against host LLVM and GFortran 16, cuobjdump, nsys and compute-sanitizer). run_formal_cuda.sh performs all of it and prints a summary; fpm/ and gfortran16/ are the Linux Pixi environments it uses.
Build all of Formal (library, tests, every example) with --gpu-allow-cpu-fallback, which reports each loop LFortran would run on the CPU instead of failing, and require that no loop is reported.
37 of 90 tasks
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Integration branch: LFortran's native
--gpu=cudabackend running BerkeleyLab Formal on a real NVIDIA GPU. Not for merging upstream. The individual fixes go upstream as their own PRs (listed below). This branch combines them, validated together, with a runbook.The base
formal-cuda-nvidia-baseis upstreammainat f7ce402.How to run it on hardware
See
ci/cuda_nvidia/README.md. In short, on a machine with an NVIDIA driver, CUDA toolkit and Pixi:The script clones Formal at the validated revision and checks all of the following, printing a PASS/FAIL summary:
--gpu=cuda_cpu;--gpu=cudawith PTX JIT disabled;cuobjdump,nsysandcompute-sanitizerevidence;integration_tests -b cuda.Results (RTX PRO 6000 Blackwell, sm_120, driver 610.43.02, CUDA 13.3)
This is the end-to-end run of
run_formal_cuda.shat this branch's head: 28/28 steps PASS.cuda_cpudo concurrentoffloaded (whole package built with--gpu-allow-cpu-fallback, no loop reported on the CPU)CUDA_DISABLE_PTX_JIT=1sm_120cubinsintegration_tests -b cuda(everycuda_cputest also run natively)Formal revision: ef4fb6e1cfb6eed69c588e1e92fbf739ebcb09bf (BerkeleyLab/formal#71, which includes lfortran#70).
What is merged here
--gpu-arch; native CUDA defaults to-arch=nativecudalabelscudalabel on the 454cuda_cputests that pass on a GPUIntegration-only commits
-archis applied to the device compiler before Fix CUDA offloading for separately compiled Formal libraries lfortran/lfortran#15546's per-translation-unit fragment loop, so every fragment, the runtime and the link use it.CMakeLists.txt/tests.tomlconflicts were resolved by test name, keeping every PR's tests and labels.gpu_metal_474CUDA kernel reference, which fix: give each GPU thread its own copy of per-iteration arrays lfortran/lfortran#15569 and fix: declare only the variables a GPU kernel scope refers to lfortran/lfortran#15570 both change; the output is exactly the union of the two. It also adds thecudalabel togpu_kernel_source_15.do concurrentfalls back to the CPU.ci/cuda_nvidia/(runbook, script, Linux Pixi manifests for FPM and GFortran 16, boundary-input generator).Known gaps (next steps)
do concurrentruns as a GPU kernel, but array expressions outsidedo concurrent(RK combination, boundary assembly, operator arithmetic) still run on the host. An audit of every loop and array statement is in progress; offloading those comes next, then performance (data residency across procedures).Issues filed along the way
lfortran#15561, lfortran#15572, lfortran#15579.