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.
Files changed (43) hide show
  1. {ri_kernels-0.2.0 → ri_kernels-0.2.2}/.github/workflows/wheels.yml +10 -6
  2. ri_kernels-0.2.2/.gitignore +4 -0
  3. {ri_kernels-0.2.0 → ri_kernels-0.2.2}/CMakeLists.txt +10 -1
  4. {ri_kernels-0.2.0 → ri_kernels-0.2.2}/PKG-INFO +142 -5
  5. ri_kernels-0.2.2/README.md +182 -0
  6. {ri_kernels-0.2.0 → ri_kernels-0.2.2}/pyproject.toml +4 -4
  7. ri_kernels-0.2.2/ri_kernels/jax_api/__init__.py +6 -0
  8. ri_kernels-0.2.2/ri_kernels/jax_api/rfi_analytic_vis_op.py +481 -0
  9. {ri_kernels-0.2.0 → ri_kernels-0.2.2}/src/parallel_for.hpp +1 -0
  10. ri_kernels-0.2.2/src/rfi_analytic_base.hpp +103 -0
  11. ri_kernels-0.2.2/src/rfi_analytic_common.hpp +202 -0
  12. ri_kernels-0.2.2/src/rfi_analytic_ffi.hpp +576 -0
  13. ri_kernels-0.2.2/src/rfi_analytic_kernel.cpp +164 -0
  14. ri_kernels-0.2.2/src/rfi_analytic_kernel_gpu.cu +411 -0
  15. ri_kernels-0.2.2/src/rfi_analytic_limits.hpp +81 -0
  16. ri_kernels-0.2.2/src/rfi_analytic_math.hpp +321 -0
  17. ri_kernels-0.2.2/src/rfi_analytic_scratch_gpu.cuh +108 -0
  18. ri_kernels-0.2.2/tests/analytic_reference.py +247 -0
  19. ri_kernels-0.2.2/tests/benchmark_rfi_analytic_vis_op.py +94 -0
  20. ri_kernels-0.2.2/tests/test_rfi_analytic_vis_op.py +538 -0
  21. ri_kernels-0.2.0/README.md +0 -45
  22. ri_kernels-0.2.0/ri_kernels/jax_api/__init__.py +0 -5
  23. {ri_kernels-0.2.0 → ri_kernels-0.2.2}/.github/workflows/tests.yml +0 -0
  24. {ri_kernels-0.2.0 → ri_kernels-0.2.2}/LICENSE +0 -0
  25. {ri_kernels-0.2.0 → ri_kernels-0.2.2}/ci/check_library.py +0 -0
  26. {ri_kernels-0.2.0 → ri_kernels-0.2.2}/ci/make_variant.py +0 -0
  27. {ri_kernels-0.2.0 → ri_kernels-0.2.2}/ri_kernels/__init__.py +0 -0
  28. {ri_kernels-0.2.0 → ri_kernels-0.2.2}/ri_kernels/jax_api/rfi_vis_op.py +0 -0
  29. {ri_kernels-0.2.0 → ri_kernels-0.2.2}/src/complex_vector_inl.hpp +0 -0
  30. {ri_kernels-0.2.0 → ri_kernels-0.2.2}/src/gpu_compat.h +0 -0
  31. {ri_kernels-0.2.0 → ri_kernels-0.2.2}/src/hwy_dispatch.hpp +0 -0
  32. {ri_kernels-0.2.0 → ri_kernels-0.2.2}/src/rfi_jvp_kernel.cpp +0 -0
  33. {ri_kernels-0.2.0 → ri_kernels-0.2.2}/src/rfi_jvp_kernel_gpu.cu +0 -0
  34. {ri_kernels-0.2.0 → ri_kernels-0.2.2}/src/rfi_kernel.cpp +0 -0
  35. {ri_kernels-0.2.0 → ri_kernels-0.2.2}/src/rfi_kernel_gpu.cu +0 -0
  36. {ri_kernels-0.2.0 → ri_kernels-0.2.2}/src/rfi_transpose_kernel.cpp +0 -0
  37. {ri_kernels-0.2.0 → ri_kernels-0.2.2}/src/rfi_transpose_kernel_gpu.cu +0 -0
  38. {ri_kernels-0.2.0 → ri_kernels-0.2.2}/src/tensor.hpp +0 -0
  39. {ri_kernels-0.2.0 → ri_kernels-0.2.2}/src/util_gpu.cu +0 -0
  40. {ri_kernels-0.2.0 → ri_kernels-0.2.2}/src/util_gpu.h +0 -0
  41. {ri_kernels-0.2.0 → ri_kernels-0.2.2}/src/visibility.h +0 -0
  42. {ri_kernels-0.2.0 → ri_kernels-0.2.2}/tests/conftest.py +0 -0
  43. {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. This is the set the jax CUDA plugin itself
85
- # ships, minus 7.0, which only CUDA 12 can still build. The trailing
86
- # `-virtual` embeds PTX once, as the JIT fallback for future GPUs.
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
- - { os: ubuntu-latest, arch: x86_64, repo_arch: x86_64 }
99
- - { os: ubuntu-24.04-arm, arch: aarch64, repo_arch: sbsa }
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.
@@ -0,0 +1,4 @@
1
+ **/__pycache__
2
+
3
+ # Local FFI builds are platform-specific artifacts, never source changes.
4
+ /ri_kernels/*.so
@@ -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
- target_link_libraries(ri_kernels_cuda PRIVATE CUDA::cudart_static)
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.0
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/ri_kernels.git
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.0; extra == "cuda12"
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.0; extra == "cuda13"
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`, and `CMAKE_CUDA_ARCHITECTURES`.
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.0"
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.0"]
61
- cuda13 = ["jax[cuda13]", "ri_kernels_cuda13==0.2.0"]
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/ri_kernels.git"
64
+ Repository = "https://github.com/epfl-radio-astro/ri-kernels"
65
65
 
66
66
 
67
67
  # ── Wheel building ─────────────────────────────────────────────────────────────
@@ -0,0 +1,6 @@
1
+ """JAX bindings for the RI kernels."""
2
+
3
+ from .rfi_analytic_vis_op import RFIAnalyticVisOp
4
+ from .rfi_vis_op import RFIVisOp, prepare_indices
5
+
6
+ __all__ = ["RFIAnalyticVisOp", "RFIVisOp", "prepare_indices"]