ri-kernels 0.2.0__tar.gz → 0.2.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {ri_kernels-0.2.0 → ri_kernels-0.2.2}/.github/workflows/wheels.yml +10 -6
- ri_kernels-0.2.2/.gitignore +4 -0
- {ri_kernels-0.2.0 → ri_kernels-0.2.2}/CMakeLists.txt +10 -1
- {ri_kernels-0.2.0 → ri_kernels-0.2.2}/PKG-INFO +142 -5
- ri_kernels-0.2.2/README.md +182 -0
- {ri_kernels-0.2.0 → ri_kernels-0.2.2}/pyproject.toml +4 -4
- ri_kernels-0.2.2/ri_kernels/jax_api/__init__.py +6 -0
- ri_kernels-0.2.2/ri_kernels/jax_api/rfi_analytic_vis_op.py +481 -0
- {ri_kernels-0.2.0 → ri_kernels-0.2.2}/src/parallel_for.hpp +1 -0
- ri_kernels-0.2.2/src/rfi_analytic_base.hpp +103 -0
- ri_kernels-0.2.2/src/rfi_analytic_common.hpp +202 -0
- ri_kernels-0.2.2/src/rfi_analytic_ffi.hpp +576 -0
- ri_kernels-0.2.2/src/rfi_analytic_kernel.cpp +164 -0
- ri_kernels-0.2.2/src/rfi_analytic_kernel_gpu.cu +411 -0
- ri_kernels-0.2.2/src/rfi_analytic_limits.hpp +81 -0
- ri_kernels-0.2.2/src/rfi_analytic_math.hpp +321 -0
- ri_kernels-0.2.2/src/rfi_analytic_scratch_gpu.cuh +108 -0
- ri_kernels-0.2.2/tests/analytic_reference.py +247 -0
- ri_kernels-0.2.2/tests/benchmark_rfi_analytic_vis_op.py +94 -0
- ri_kernels-0.2.2/tests/test_rfi_analytic_vis_op.py +538 -0
- ri_kernels-0.2.0/README.md +0 -45
- ri_kernels-0.2.0/ri_kernels/jax_api/__init__.py +0 -5
- {ri_kernels-0.2.0 → ri_kernels-0.2.2}/.github/workflows/tests.yml +0 -0
- {ri_kernels-0.2.0 → ri_kernels-0.2.2}/LICENSE +0 -0
- {ri_kernels-0.2.0 → ri_kernels-0.2.2}/ci/check_library.py +0 -0
- {ri_kernels-0.2.0 → ri_kernels-0.2.2}/ci/make_variant.py +0 -0
- {ri_kernels-0.2.0 → ri_kernels-0.2.2}/ri_kernels/__init__.py +0 -0
- {ri_kernels-0.2.0 → ri_kernels-0.2.2}/ri_kernels/jax_api/rfi_vis_op.py +0 -0
- {ri_kernels-0.2.0 → ri_kernels-0.2.2}/src/complex_vector_inl.hpp +0 -0
- {ri_kernels-0.2.0 → ri_kernels-0.2.2}/src/gpu_compat.h +0 -0
- {ri_kernels-0.2.0 → ri_kernels-0.2.2}/src/hwy_dispatch.hpp +0 -0
- {ri_kernels-0.2.0 → ri_kernels-0.2.2}/src/rfi_jvp_kernel.cpp +0 -0
- {ri_kernels-0.2.0 → ri_kernels-0.2.2}/src/rfi_jvp_kernel_gpu.cu +0 -0
- {ri_kernels-0.2.0 → ri_kernels-0.2.2}/src/rfi_kernel.cpp +0 -0
- {ri_kernels-0.2.0 → ri_kernels-0.2.2}/src/rfi_kernel_gpu.cu +0 -0
- {ri_kernels-0.2.0 → ri_kernels-0.2.2}/src/rfi_transpose_kernel.cpp +0 -0
- {ri_kernels-0.2.0 → ri_kernels-0.2.2}/src/rfi_transpose_kernel_gpu.cu +0 -0
- {ri_kernels-0.2.0 → ri_kernels-0.2.2}/src/tensor.hpp +0 -0
- {ri_kernels-0.2.0 → ri_kernels-0.2.2}/src/util_gpu.cu +0 -0
- {ri_kernels-0.2.0 → ri_kernels-0.2.2}/src/util_gpu.h +0 -0
- {ri_kernels-0.2.0 → ri_kernels-0.2.2}/src/visibility.h +0 -0
- {ri_kernels-0.2.0 → ri_kernels-0.2.2}/tests/conftest.py +0 -0
- {ri_kernels-0.2.0 → ri_kernels-0.2.2}/tests/test_rfi_vis_op.py +0 -0
|
@@ -81,9 +81,10 @@ jobs:
|
|
|
81
81
|
# (plus 7.5, the CUDA 13 floor): cubins are binary compatible upward
|
|
82
82
|
# within a major, so 8.0 covers 8.6/8.7/8.9, 10.0 covers 10.3 (B300)
|
|
83
83
|
# and 12.0 covers 12.1 (GB10). 10.x and 12.x are separate families and
|
|
84
|
-
# need separate cubins.
|
|
85
|
-
#
|
|
86
|
-
# `-virtual` embeds PTX once, as the JIT fallback for future
|
|
84
|
+
# need separate cubins. CUDA 12 starts at JAX's 5.2 support floor and
|
|
85
|
+
# CUDA 13 starts at 7.5 because that toolkit dropped older targets. The
|
|
86
|
+
# trailing `-virtual` embeds PTX once, as the JIT fallback for future
|
|
87
|
+
# GPUs.
|
|
87
88
|
cuda:
|
|
88
89
|
- major: "12"
|
|
89
90
|
pkg: "12-9"
|
|
@@ -95,8 +96,11 @@ jobs:
|
|
|
95
96
|
# CUDA 13 dropped Maxwell/Pascal/Volta; Turing is the floor.
|
|
96
97
|
archs: "75-real;80-real;90-real;100-real;120-real;120-virtual"
|
|
97
98
|
platform:
|
|
98
|
-
|
|
99
|
-
|
|
99
|
+
# Maxwell has no Linux SBSA target, so SM 5.2 belongs only in the
|
|
100
|
+
# x86_64 CUDA 12 wheel. Both platforms otherwise share the list from
|
|
101
|
+
# the CUDA entry above.
|
|
102
|
+
- { os: ubuntu-latest, arch: x86_64, repo_arch: x86_64, cuda12_prefix: "52-real;" }
|
|
103
|
+
- { os: ubuntu-24.04-arm, arch: aarch64, repo_arch: sbsa, cuda12_prefix: "" }
|
|
100
104
|
steps:
|
|
101
105
|
- uses: actions/checkout@v4
|
|
102
106
|
|
|
@@ -127,7 +131,7 @@ jobs:
|
|
|
127
131
|
CIBW_ENVIRONMENT_LINUX: >
|
|
128
132
|
CUDACXX=${{ matrix.cuda.home }}/bin/nvcc
|
|
129
133
|
PATH=${{ matrix.cuda.home }}/bin:$PATH
|
|
130
|
-
CMAKE_ARGS="-DCMAKE_CUDA_ARCHITECTURES=${{ matrix.cuda.archs }}"
|
|
134
|
+
CMAKE_ARGS="-DCMAKE_CUDA_ARCHITECTURES=${{ matrix.cuda.major == '12' && matrix.platform.cuda12_prefix || '' }}${{ matrix.cuda.archs }}"
|
|
131
135
|
# No test here: installing the wheel would pull ri_kernels==<version>
|
|
132
136
|
# from the index, which does not exist yet for an unreleased version. The
|
|
133
137
|
# library is smoke-tested below instead.
|
|
@@ -57,6 +57,7 @@ endif()
|
|
|
57
57
|
option(RI_KERNELS_CPU "Build CPU extension" ON)
|
|
58
58
|
option(RI_KERNELS_CUDA "Build CUDA extension" OFF)
|
|
59
59
|
option(RI_KERNELS_ROCM "Build ROCm extension" OFF)
|
|
60
|
+
option(RI_KERNELS_STATIC_CUDART "Link the CUDA runtime statically" ON)
|
|
60
61
|
|
|
61
62
|
option(RI_KERNELS_BUNDLED_LIBS "Use all bundled libraries" ON)
|
|
62
63
|
cmake_dependent_option(RI_KERNELS_BUNDLED_HIGHWAY "Use bundled highway lib" ON "RI_KERNELS_BUNDLED_LIBS" OFF)
|
|
@@ -95,12 +96,14 @@ set(RI_KERNELS_CPU_SOURCES
|
|
|
95
96
|
./src/rfi_kernel.cpp
|
|
96
97
|
./src/rfi_jvp_kernel.cpp
|
|
97
98
|
./src/rfi_transpose_kernel.cpp
|
|
99
|
+
./src/rfi_analytic_kernel.cpp
|
|
98
100
|
)
|
|
99
101
|
|
|
100
102
|
set(RI_KERNELS_GPU_SOURCES
|
|
101
103
|
./src/rfi_kernel_gpu.cu
|
|
102
104
|
./src/rfi_jvp_kernel_gpu.cu
|
|
103
105
|
./src/rfi_transpose_kernel_gpu.cu
|
|
106
|
+
./src/rfi_analytic_kernel_gpu.cu
|
|
104
107
|
./src/util_gpu.cu
|
|
105
108
|
)
|
|
106
109
|
|
|
@@ -150,7 +153,13 @@ if(RI_KERNELS_CUDA)
|
|
|
150
153
|
add_library(ri_kernels_cuda SHARED ${RI_KERNELS_GPU_SOURCES})
|
|
151
154
|
# set_target_properties(ri_kernels_cuda PROPERTIES OUTPUT_NAME "ri_kernels_cuda_${CUDAToolkit_VERSION_MAJOR}")
|
|
152
155
|
target_include_directories(ri_kernels_cuda PRIVATE ${JAXLIB_HOME}/include ./src)
|
|
153
|
-
|
|
156
|
+
if(RI_KERNELS_STATIC_CUDART)
|
|
157
|
+
set_property(TARGET ri_kernels_cuda PROPERTY CUDA_RUNTIME_LIBRARY Static)
|
|
158
|
+
target_link_libraries(ri_kernels_cuda PRIVATE CUDA::cudart_static)
|
|
159
|
+
else()
|
|
160
|
+
set_property(TARGET ri_kernels_cuda PROPERTY CUDA_RUNTIME_LIBRARY Shared)
|
|
161
|
+
target_link_libraries(ri_kernels_cuda PRIVATE CUDA::cudart)
|
|
162
|
+
endif()
|
|
154
163
|
target_compile_options(ri_kernels_cuda PRIVATE
|
|
155
164
|
$<$<COMPILE_LANGUAGE:CUDA>:--diag-suppress=940>
|
|
156
165
|
$<$<COMPILE_LANGUAGE:CUDA>:--diag-suppress=2473>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: ri_kernels
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: CPU and GPU kernels for radio interferometry, exposed to JAX as FFI custom calls
|
|
5
5
|
Author-Email: Chris Finlay <christopher.finlay@epfl.ch>, Simon Frasch <simon.frasch@cscs.ch>
|
|
6
6
|
Maintainer-Email: Chris Finlay <christopher.finlay@epfl.ch>, Simon Frasch <simon.frasch@cscs.ch>
|
|
@@ -679,7 +679,7 @@ License: GNU GENERAL PUBLIC LICENSE
|
|
|
679
679
|
Public License instead of this License. But first, please read
|
|
680
680
|
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
|
681
681
|
|
|
682
|
-
Project-URL: Repository, https://github.com/epfl-radio-astro/
|
|
682
|
+
Project-URL: Repository, https://github.com/epfl-radio-astro/ri-kernels
|
|
683
683
|
Requires-Python: >=3.10
|
|
684
684
|
Requires-Dist: jax>=0.6.0
|
|
685
685
|
Provides-Extra: test
|
|
@@ -687,10 +687,10 @@ Requires-Dist: pytest; extra == "test"
|
|
|
687
687
|
Requires-Dist: pytest-cov; extra == "test"
|
|
688
688
|
Provides-Extra: cuda12
|
|
689
689
|
Requires-Dist: jax[cuda12]; extra == "cuda12"
|
|
690
|
-
Requires-Dist: ri_kernels_cuda12==0.2.
|
|
690
|
+
Requires-Dist: ri_kernels_cuda12==0.2.2; extra == "cuda12"
|
|
691
691
|
Provides-Extra: cuda13
|
|
692
692
|
Requires-Dist: jax[cuda13]; extra == "cuda13"
|
|
693
|
-
Requires-Dist: ri_kernels_cuda13==0.2.
|
|
693
|
+
Requires-Dist: ri_kernels_cuda13==0.2.2; extra == "cuda13"
|
|
694
694
|
Description-Content-Type: text/markdown
|
|
695
695
|
|
|
696
696
|
# RI Kernels
|
|
@@ -729,7 +729,9 @@ needs a published release. To build the GPU kernels from the checkout, use
|
|
|
729
729
|
Other CMake options of note: `RI_KERNELS_CPU` (default `ON`),
|
|
730
730
|
`RI_KERNELS_MULTI_ARCH` (dynamic SIMD dispatch, default `ON` — turn it off and
|
|
731
731
|
set arch flags via `CMAKE_CXX_FLAGS` for a single-target build),
|
|
732
|
-
`RI_KERNELS_BUNDLED_HIGHWAY`,
|
|
732
|
+
`RI_KERNELS_BUNDLED_HIGHWAY`, `CMAKE_CUDA_ARCHITECTURES`, and
|
|
733
|
+
`RI_KERNELS_STATIC_CUDART` (default `ON`; set it to `OFF` when a package
|
|
734
|
+
manager should provide the shared CUDA runtime).
|
|
733
735
|
|
|
734
736
|
## Tests
|
|
735
737
|
|
|
@@ -738,3 +740,138 @@ Tests can be run with
|
|
|
738
740
|
```bash
|
|
739
741
|
python -m pytest tests
|
|
740
742
|
```
|
|
743
|
+
|
|
744
|
+
## RFI visibilities
|
|
745
|
+
|
|
746
|
+
`RFIVisOp` computes the per-baseline RFI visibility
|
|
747
|
+
|
|
748
|
+
```
|
|
749
|
+
vis[bl, f, t] = mean over (n_int_freq, n_int_time) of
|
|
750
|
+
sum over n_rfi of A[a1] conj(A[a2]) exp(i (φ[a1] - φ[a2]))
|
|
751
|
+
```
|
|
752
|
+
|
|
753
|
+
for amplitudes `A` shaped `(n_ant, n_freq, n_time, n_rfi, n_int_freq,
|
|
754
|
+
n_int_time)` and phases `φ` in radians of the same shape, giving an output of
|
|
755
|
+
shape `(n_baselines, n_freq, n_time)`. It is constructed from the baseline
|
|
756
|
+
layout and evaluated through `eval`:
|
|
757
|
+
|
|
758
|
+
```python
|
|
759
|
+
from ri_kernels.jax_api import RFIVisOp
|
|
760
|
+
|
|
761
|
+
vis = RFIVisOp(n_ant, a1, a2).eval(rfi_amp_fine, rfi_phase)
|
|
762
|
+
```
|
|
763
|
+
|
|
764
|
+
The operator has native primal, JVP, and transpose kernels for CPU, CUDA, and
|
|
765
|
+
ROCm, so both forward- and reverse-mode differentiation stay inside the
|
|
766
|
+
kernels. Precision has to match across the inputs: complex64 with float32, or
|
|
767
|
+
complex128 with float64. Both amplitudes and phases are differentiated.
|
|
768
|
+
|
|
769
|
+
## Analytic RFI visibilities
|
|
770
|
+
|
|
771
|
+
`RFIAnalyticVisOp` integrates the time polynomial against phase moments, with
|
|
772
|
+
frequency quadrature unchanged. It stages amplitude coefficients per antenna
|
|
773
|
+
and cell, then contracts pairs of antenna tiles. The default time scratch axis
|
|
774
|
+
has three coefficients, independent of fringe winding.
|
|
775
|
+
|
|
776
|
+
```python
|
|
777
|
+
from ri_kernels.jax_api import RFIAnalyticVisOp
|
|
778
|
+
|
|
779
|
+
vis = RFIAnalyticVisOp(n_ant, a1, a2).eval(
|
|
780
|
+
amp, phase, delay_us, w_freq, start_freq, g_time, start_time,
|
|
781
|
+
dnu_mhz, int_time, freq_mhz,
|
|
782
|
+
segments=2, terms=6, cubic_terms=3,
|
|
783
|
+
)
|
|
784
|
+
```
|
|
785
|
+
|
|
786
|
+
- `amp`, complex `(n_ant, n_rfi, n_freq, n_time)`: the signal on the data
|
|
787
|
+
grid.
|
|
788
|
+
- `phase`, real `(n_ant, n_rfi, n_freq, n_time)`: the phase at the channel and
|
|
789
|
+
cell centre, reduced to one turn (in float64, before casting).
|
|
790
|
+
- `delay_us`, real `(n_ant, n_rfi, n_time, n_path)`: the geometric delay in
|
|
791
|
+
microseconds, with the sign that makes the phase `2π f τ`, and its first
|
|
792
|
+
`n_path - 1` time derivatives at the cell centre, relative to the array mean
|
|
793
|
+
(a common term cancels in every baseline; the full delay's change across a
|
|
794
|
+
cell is ~1e4 wavelengths, beyond float32).
|
|
795
|
+
- `w_freq`, `start_freq`: per channel, the weights that turn its stencil of
|
|
796
|
+
`n_sf` neighbouring channels into its fine channels, and the first channel
|
|
797
|
+
of the stencil.
|
|
798
|
+
- `g_time`, `start_time`: per cell, monomial coefficients in
|
|
799
|
+
`x = 2*tau/int_time`, as `tabascal.poly_interp.monomial_tables` produces
|
|
800
|
+
them, including the shifted edge stencils, and the first cell of the
|
|
801
|
+
stencil. Each stencil must lie inside its axis and contain its cell.
|
|
802
|
+
- `dnu_mhz`: the fine offsets from the channel centre; `freq_mhz`: the channel
|
|
803
|
+
centres. Both are in **MHz**; divide `config.freqs` in Hz by `1e6`. MHz × μs
|
|
804
|
+
is cycles.
|
|
805
|
+
- `int_time`: a positive scalar in seconds.
|
|
806
|
+
|
|
807
|
+
Every real operand shares the precision of `phase`; the stencil starts are
|
|
808
|
+
int32. The constructor builds, per pair of 32-antenna tiles, the list of the
|
|
809
|
+
pairs the baselines cover there (`tile_pair_list`), which the staged GPU
|
|
810
|
+
kernels work through a tile pair per block; each `(a1, a2)` baseline may
|
|
811
|
+
appear at most once.
|
|
812
|
+
|
|
813
|
+
`amp` and `phase` are differentiated; `delay_us`, `int_time` and the tables
|
|
814
|
+
are constants whose gradients `eval` stops. Two kernel pairs carry the
|
|
815
|
+
derivatives: a JVP and transpose for the signal alone,
|
|
816
|
+
and a full pair that takes the phase tangent as well and returns its
|
|
817
|
+
cotangent. The phase enters every cell's weights as the one factor
|
|
818
|
+
`exp(i (phase[a1] - phase[a2]))`, so the phase tangent turns the pair's
|
|
819
|
+
product by `i` (`dV = i (dphase[a1] - dphase[a2]) V` per source and fine
|
|
820
|
+
channel) and the cotangent is `-Im(g V)` on `a1` and `+Im(g V)` on `a2`,
|
|
821
|
+
summed over the baseline's sources and fine channels; both cost one extra
|
|
822
|
+
contraction against weights the kernel has already formed. The JVP rule binds
|
|
823
|
+
the signal-only pair whenever the phase tangent is a symbolic zero, so a
|
|
824
|
+
fixed orbit computes no phase derivative. The compiled JVPs and transposes
|
|
825
|
+
follow JAX's complex cotangent convention.
|
|
826
|
+
|
|
827
|
+
The moment branches, segmentation and cubic translation follow
|
|
828
|
+
`tabascal.coarse_rfi_vis.analytic_rfi_vis`. `segments`, `terms`, and
|
|
829
|
+
`cubic_terms` are static options: close over them or mark them static when
|
|
830
|
+
jitting. The defaults use two pieces, six curvature terms and three cubic
|
|
831
|
+
terms. `segments=4, terms=16` selects the reference's conservative settings.
|
|
832
|
+
Zero cubic terms deliberately omits cubic phase; delay derivatives above
|
|
833
|
+
order three are always omitted. Coefficient counts 1–9, segments 1–1024,
|
|
834
|
+
curvature terms 1–32 and cubic terms 0–8 are supported. These limits bound
|
|
835
|
+
local storage; convergence still depends on the supplied delay and interval.
|
|
836
|
+
|
|
837
|
+
Every angle -- the centre phase, the winding and the segment translations --
|
|
838
|
+
is formed and reduced in double for both input precisions: a single-precision
|
|
839
|
+
angle has no fractional turn left at the magnitudes the delay reaches. The
|
|
840
|
+
moment recurrences that follow only multiply the coefficient buffers, so the
|
|
841
|
+
CPU kernels keep them in double, where it is free, while the GPU kernels run
|
|
842
|
+
them at the operator's own precision: a consumer card retires one
|
|
843
|
+
double-precision instruction per sixty-four single-precision ones, and a
|
|
844
|
+
complex64 weight costs well over an order of magnitude more in its double form
|
|
845
|
+
there. Fresnel seeds stay in double either way, and use a
|
|
846
|
+
small-argument series and rational auxiliary functions: a two-term asymptotic
|
|
847
|
+
near 2.5 is insufficient for the higher moments. The common three-coefficient,
|
|
848
|
+
six-term, three-cubic-term path has unrolled moment orders so its CUDA
|
|
849
|
+
recurrence can stay in registers. Wider configurations use a bounded general
|
|
850
|
+
implementation.
|
|
851
|
+
|
|
852
|
+
GPU scratch is bounded by the `scratch_mb` argument of `eval` (256 MiB by
|
|
853
|
+
default), with time chunks and, when necessary, frequency chunks. A smaller
|
|
854
|
+
budget means more chunks, and each chunk of a transpose passes over the whole
|
|
855
|
+
signal cotangent. The transpose reduces coefficient cotangents within each tile
|
|
856
|
+
in shared memory, writes separate partials for partner tiles, and gathers
|
|
857
|
+
through the interpolation stencils. Shared-memory atomics mean the last bits of
|
|
858
|
+
the GPU transpose can vary between runs.
|
|
859
|
+
|
|
860
|
+
After building on the GPU host, run:
|
|
861
|
+
|
|
862
|
+
```bash
|
|
863
|
+
python -m pytest tests/test_rfi_analytic_vis_op.py
|
|
864
|
+
python tests/benchmark_rfi_analytic_vis_op.py --antennas 256 512 --iterations 100
|
|
865
|
+
```
|
|
866
|
+
|
|
867
|
+
The analytic tests compare both precisions and the amplitude and phase
|
|
868
|
+
derivatives against a frozen float64 JAX reference. They cover zero winding, both recurrence
|
|
869
|
+
branches, the Fresnel boundary, cubic translation, wider coefficient counts,
|
|
870
|
+
sparse/reversed/autocorrelation baselines, partial antenna tiles and forced
|
|
871
|
+
scratch splitting. The benchmark reports synchronised forward, JVP and VJP
|
|
872
|
+
times, signal-only and with the phase, after compilation and warmup. It uses
|
|
873
|
+
synthetic inputs; repeat the scientific accuracy check and the 100-iteration
|
|
874
|
+
optimisation with the production SKA-Low data. Inspect ptxas register/spill
|
|
875
|
+
reports and Nsight local-memory traffic for the default specialisation before
|
|
876
|
+
interpreting its speedup. CUDA compilation, device tests and performance need
|
|
877
|
+
the GPU host; CPU tests do not establish those results.
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
# RI Kernels
|
|
2
|
+
|
|
3
|
+
CPU and GPU kernels for radio interferometry, exposed to JAX as FFI custom
|
|
4
|
+
calls with full support for automatic differentiation.
|
|
5
|
+
|
|
6
|
+
## Installation
|
|
7
|
+
|
|
8
|
+
The CPU kernels and all of the Python code live in `ri_kernels`. The GPU kernels
|
|
9
|
+
ship as add-on packages, one per CUDA major version, selected with an extra:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
pip install ri_kernels # CPU kernels only
|
|
13
|
+
pip install "ri_kernels[cuda12]" # + CUDA 12 kernels and jax[cuda12]
|
|
14
|
+
pip install "ri_kernels[cuda13]" # + CUDA 13 kernels and jax[cuda13]
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Building from source
|
|
18
|
+
|
|
19
|
+
Requires CMake >= 3.20, a C++20 compiler, and network access at configure time
|
|
20
|
+
(Google Highway is fetched by CMake).
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
pip install . # CPU only
|
|
24
|
+
RI_KERNELS_CUDA=1 pip install . # CPU + CUDA, needs nvcc
|
|
25
|
+
RI_KERNELS_ROCM=1 pip install . # CPU + ROCm, needs hipcc
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Both libraries land in the `ri_kernels` package directory in that case, which is
|
|
29
|
+
also a location the loader searches. Note that `pip install .[cuda12]` is a
|
|
30
|
+
different thing: the extra resolves `ri_kernels_cuda12` from the index, so it
|
|
31
|
+
needs a published release. To build the GPU kernels from the checkout, use
|
|
32
|
+
`RI_KERNELS_CUDA=1`.
|
|
33
|
+
|
|
34
|
+
Other CMake options of note: `RI_KERNELS_CPU` (default `ON`),
|
|
35
|
+
`RI_KERNELS_MULTI_ARCH` (dynamic SIMD dispatch, default `ON` — turn it off and
|
|
36
|
+
set arch flags via `CMAKE_CXX_FLAGS` for a single-target build),
|
|
37
|
+
`RI_KERNELS_BUNDLED_HIGHWAY`, `CMAKE_CUDA_ARCHITECTURES`, and
|
|
38
|
+
`RI_KERNELS_STATIC_CUDART` (default `ON`; set it to `OFF` when a package
|
|
39
|
+
manager should provide the shared CUDA runtime).
|
|
40
|
+
|
|
41
|
+
## Tests
|
|
42
|
+
|
|
43
|
+
Tests can be run with
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
python -m pytest tests
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## RFI visibilities
|
|
50
|
+
|
|
51
|
+
`RFIVisOp` computes the per-baseline RFI visibility
|
|
52
|
+
|
|
53
|
+
```
|
|
54
|
+
vis[bl, f, t] = mean over (n_int_freq, n_int_time) of
|
|
55
|
+
sum over n_rfi of A[a1] conj(A[a2]) exp(i (φ[a1] - φ[a2]))
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
for amplitudes `A` shaped `(n_ant, n_freq, n_time, n_rfi, n_int_freq,
|
|
59
|
+
n_int_time)` and phases `φ` in radians of the same shape, giving an output of
|
|
60
|
+
shape `(n_baselines, n_freq, n_time)`. It is constructed from the baseline
|
|
61
|
+
layout and evaluated through `eval`:
|
|
62
|
+
|
|
63
|
+
```python
|
|
64
|
+
from ri_kernels.jax_api import RFIVisOp
|
|
65
|
+
|
|
66
|
+
vis = RFIVisOp(n_ant, a1, a2).eval(rfi_amp_fine, rfi_phase)
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
The operator has native primal, JVP, and transpose kernels for CPU, CUDA, and
|
|
70
|
+
ROCm, so both forward- and reverse-mode differentiation stay inside the
|
|
71
|
+
kernels. Precision has to match across the inputs: complex64 with float32, or
|
|
72
|
+
complex128 with float64. Both amplitudes and phases are differentiated.
|
|
73
|
+
|
|
74
|
+
## Analytic RFI visibilities
|
|
75
|
+
|
|
76
|
+
`RFIAnalyticVisOp` integrates the time polynomial against phase moments, with
|
|
77
|
+
frequency quadrature unchanged. It stages amplitude coefficients per antenna
|
|
78
|
+
and cell, then contracts pairs of antenna tiles. The default time scratch axis
|
|
79
|
+
has three coefficients, independent of fringe winding.
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
from ri_kernels.jax_api import RFIAnalyticVisOp
|
|
83
|
+
|
|
84
|
+
vis = RFIAnalyticVisOp(n_ant, a1, a2).eval(
|
|
85
|
+
amp, phase, delay_us, w_freq, start_freq, g_time, start_time,
|
|
86
|
+
dnu_mhz, int_time, freq_mhz,
|
|
87
|
+
segments=2, terms=6, cubic_terms=3,
|
|
88
|
+
)
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
- `amp`, complex `(n_ant, n_rfi, n_freq, n_time)`: the signal on the data
|
|
92
|
+
grid.
|
|
93
|
+
- `phase`, real `(n_ant, n_rfi, n_freq, n_time)`: the phase at the channel and
|
|
94
|
+
cell centre, reduced to one turn (in float64, before casting).
|
|
95
|
+
- `delay_us`, real `(n_ant, n_rfi, n_time, n_path)`: the geometric delay in
|
|
96
|
+
microseconds, with the sign that makes the phase `2π f τ`, and its first
|
|
97
|
+
`n_path - 1` time derivatives at the cell centre, relative to the array mean
|
|
98
|
+
(a common term cancels in every baseline; the full delay's change across a
|
|
99
|
+
cell is ~1e4 wavelengths, beyond float32).
|
|
100
|
+
- `w_freq`, `start_freq`: per channel, the weights that turn its stencil of
|
|
101
|
+
`n_sf` neighbouring channels into its fine channels, and the first channel
|
|
102
|
+
of the stencil.
|
|
103
|
+
- `g_time`, `start_time`: per cell, monomial coefficients in
|
|
104
|
+
`x = 2*tau/int_time`, as `tabascal.poly_interp.monomial_tables` produces
|
|
105
|
+
them, including the shifted edge stencils, and the first cell of the
|
|
106
|
+
stencil. Each stencil must lie inside its axis and contain its cell.
|
|
107
|
+
- `dnu_mhz`: the fine offsets from the channel centre; `freq_mhz`: the channel
|
|
108
|
+
centres. Both are in **MHz**; divide `config.freqs` in Hz by `1e6`. MHz × μs
|
|
109
|
+
is cycles.
|
|
110
|
+
- `int_time`: a positive scalar in seconds.
|
|
111
|
+
|
|
112
|
+
Every real operand shares the precision of `phase`; the stencil starts are
|
|
113
|
+
int32. The constructor builds, per pair of 32-antenna tiles, the list of the
|
|
114
|
+
pairs the baselines cover there (`tile_pair_list`), which the staged GPU
|
|
115
|
+
kernels work through a tile pair per block; each `(a1, a2)` baseline may
|
|
116
|
+
appear at most once.
|
|
117
|
+
|
|
118
|
+
`amp` and `phase` are differentiated; `delay_us`, `int_time` and the tables
|
|
119
|
+
are constants whose gradients `eval` stops. Two kernel pairs carry the
|
|
120
|
+
derivatives: a JVP and transpose for the signal alone,
|
|
121
|
+
and a full pair that takes the phase tangent as well and returns its
|
|
122
|
+
cotangent. The phase enters every cell's weights as the one factor
|
|
123
|
+
`exp(i (phase[a1] - phase[a2]))`, so the phase tangent turns the pair's
|
|
124
|
+
product by `i` (`dV = i (dphase[a1] - dphase[a2]) V` per source and fine
|
|
125
|
+
channel) and the cotangent is `-Im(g V)` on `a1` and `+Im(g V)` on `a2`,
|
|
126
|
+
summed over the baseline's sources and fine channels; both cost one extra
|
|
127
|
+
contraction against weights the kernel has already formed. The JVP rule binds
|
|
128
|
+
the signal-only pair whenever the phase tangent is a symbolic zero, so a
|
|
129
|
+
fixed orbit computes no phase derivative. The compiled JVPs and transposes
|
|
130
|
+
follow JAX's complex cotangent convention.
|
|
131
|
+
|
|
132
|
+
The moment branches, segmentation and cubic translation follow
|
|
133
|
+
`tabascal.coarse_rfi_vis.analytic_rfi_vis`. `segments`, `terms`, and
|
|
134
|
+
`cubic_terms` are static options: close over them or mark them static when
|
|
135
|
+
jitting. The defaults use two pieces, six curvature terms and three cubic
|
|
136
|
+
terms. `segments=4, terms=16` selects the reference's conservative settings.
|
|
137
|
+
Zero cubic terms deliberately omits cubic phase; delay derivatives above
|
|
138
|
+
order three are always omitted. Coefficient counts 1–9, segments 1–1024,
|
|
139
|
+
curvature terms 1–32 and cubic terms 0–8 are supported. These limits bound
|
|
140
|
+
local storage; convergence still depends on the supplied delay and interval.
|
|
141
|
+
|
|
142
|
+
Every angle -- the centre phase, the winding and the segment translations --
|
|
143
|
+
is formed and reduced in double for both input precisions: a single-precision
|
|
144
|
+
angle has no fractional turn left at the magnitudes the delay reaches. The
|
|
145
|
+
moment recurrences that follow only multiply the coefficient buffers, so the
|
|
146
|
+
CPU kernels keep them in double, where it is free, while the GPU kernels run
|
|
147
|
+
them at the operator's own precision: a consumer card retires one
|
|
148
|
+
double-precision instruction per sixty-four single-precision ones, and a
|
|
149
|
+
complex64 weight costs well over an order of magnitude more in its double form
|
|
150
|
+
there. Fresnel seeds stay in double either way, and use a
|
|
151
|
+
small-argument series and rational auxiliary functions: a two-term asymptotic
|
|
152
|
+
near 2.5 is insufficient for the higher moments. The common three-coefficient,
|
|
153
|
+
six-term, three-cubic-term path has unrolled moment orders so its CUDA
|
|
154
|
+
recurrence can stay in registers. Wider configurations use a bounded general
|
|
155
|
+
implementation.
|
|
156
|
+
|
|
157
|
+
GPU scratch is bounded by the `scratch_mb` argument of `eval` (256 MiB by
|
|
158
|
+
default), with time chunks and, when necessary, frequency chunks. A smaller
|
|
159
|
+
budget means more chunks, and each chunk of a transpose passes over the whole
|
|
160
|
+
signal cotangent. The transpose reduces coefficient cotangents within each tile
|
|
161
|
+
in shared memory, writes separate partials for partner tiles, and gathers
|
|
162
|
+
through the interpolation stencils. Shared-memory atomics mean the last bits of
|
|
163
|
+
the GPU transpose can vary between runs.
|
|
164
|
+
|
|
165
|
+
After building on the GPU host, run:
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
python -m pytest tests/test_rfi_analytic_vis_op.py
|
|
169
|
+
python tests/benchmark_rfi_analytic_vis_op.py --antennas 256 512 --iterations 100
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
The analytic tests compare both precisions and the amplitude and phase
|
|
173
|
+
derivatives against a frozen float64 JAX reference. They cover zero winding, both recurrence
|
|
174
|
+
branches, the Fresnel boundary, cubic translation, wider coefficient counts,
|
|
175
|
+
sparse/reversed/autocorrelation baselines, partial antenna tiles and forced
|
|
176
|
+
scratch splitting. The benchmark reports synchronised forward, JVP and VJP
|
|
177
|
+
times, signal-only and with the phase, after compilation and warmup. It uses
|
|
178
|
+
synthetic inputs; repeat the scientific accuracy check and the 100-iteration
|
|
179
|
+
optimisation with the production SKA-Low data. Inspect ptxas register/spill
|
|
180
|
+
reports and Nsight local-memory traffic for the default specialisation before
|
|
181
|
+
interpreting its speedup. CUDA compilation, device tests and performance need
|
|
182
|
+
the GPU host; CPU tests do not establish those results.
|
|
@@ -24,7 +24,7 @@ cmake.define.RI_KERNELS_ROCM = "ON"
|
|
|
24
24
|
|
|
25
25
|
[project]
|
|
26
26
|
name = "ri_kernels"
|
|
27
|
-
version = "0.2.
|
|
27
|
+
version = "0.2.2"
|
|
28
28
|
license = {file = "LICENSE"}
|
|
29
29
|
readme = "README.md"
|
|
30
30
|
authors = [
|
|
@@ -57,11 +57,11 @@ test = ["pytest", "pytest-cov"]
|
|
|
57
57
|
#
|
|
58
58
|
# The pins here must track [project].version; ci/make_variant.py fails the build
|
|
59
59
|
# if they drift.
|
|
60
|
-
cuda12 = ["jax[cuda12]", "ri_kernels_cuda12==0.2.
|
|
61
|
-
cuda13 = ["jax[cuda13]", "ri_kernels_cuda13==0.2.
|
|
60
|
+
cuda12 = ["jax[cuda12]", "ri_kernels_cuda12==0.2.2"]
|
|
61
|
+
cuda13 = ["jax[cuda13]", "ri_kernels_cuda13==0.2.2"]
|
|
62
62
|
|
|
63
63
|
[project.urls]
|
|
64
|
-
Repository = "https://github.com/epfl-radio-astro/
|
|
64
|
+
Repository = "https://github.com/epfl-radio-astro/ri-kernels"
|
|
65
65
|
|
|
66
66
|
|
|
67
67
|
# ── Wheel building ─────────────────────────────────────────────────────────────
|