Backends and devices

Radar runs propagation and synthesis in one native CUDA library and processing in PyTorch. What runs where, the device rules, and memory use.

Radar has one compute backend for simulation: a native CUDA library, _radar_native, that ships inside the wheel. PyTorch does the rest. This page explains what runs where, how the device is chosen, and what costs memory.

What runs where#

WorkRuns in
One-way propagation (line of sight, reflections)Channel's native CUDA kernels
Round-trip composition, scattering, antenna pattern weightsRadar, on the GPU
FMCW, OFDM and pulsed synthesis, and their VJP and JVPRadar's native CUDA library
Receiver chain (noise, LNA, AGC, ADC)Native CUDA for noise and LNA, PyTorch for the other stages
Signal processing (witwin.radar.processing)PyTorch, including torch.fft
Session orchestration, validation, scene authoringPython and PyTorch

The installed package never compiles CUDA code at run time, and there is no pure-PyTorch fallback for synthesis. If the native library cannot load, or was built for a different PyTorch version, CUDA version or platform, Radar raises an error instead of running a slower path.

The device#

Radar(device="cuda") is the default. The radar's device is where its antenna positions live and where target tensors and cubes are created.

  • If you ask for CUDA and torch.cuda.is_available() is false, construction raises RuntimeError. Pass device="cpu" explicitly to build a radar on a machine without a GPU.
  • A CPU radar can be constructed, configured and inspected, and the processing functions run on CPU tensors. trace, simulate and stream need CUDA.
  • radar.to(device) returns a copy on another device.

Targets do not carry a device: the radar supplies its own when the session starts. A position tensor you pass in PointTargets keeps its own device, so create it on radar.device.

Exact runtime identity#

The native library is compiled against one PyTorch and CUDA version. Release 0.4.1 needs PyTorch 2.10.0 with CUDA 12.8 exactly. A library whose recorded PyTorch version, CUDA version, C++ ABI or platform differs from the running one is refused at load time instead of risking a silent ABI mismatch. witwin.radar.deployment.require_supported_runtime() checks all of this; see Installation.

The library is compiled for compute capability 7.0 to 12.0 (see the list on the installation page), with PTX for 12.0 for forward compatibility. Release testing ran on compute capability 12.0 (RTX 50 series).

Memory#

GPU memory depends mostly on how many times the world is evaluated per frame and how many paths each evaluation has.

  • simulate keeps every frame's cube and stacks them at the end, so peak memory grows with the number of frames. On one 3-TX, 4-RX, 128-chirp, 256-sample walking-person scene, 128 stacked frames peaked at 1233 MiB against 73 MiB when streamed.
  • stream yields one frame at a time, so its peak stays flat. Use it for long sequences.
  • trace keeps every evaluated observation's rows in the returned Paths, about 20 bytes per live row per observation. With Motion.adc() a frame has chirps · num_tx · samples observations, so a rich scene can cost gigabytes per frame. Trace one frame at a time if the sequence is long.

Performance#

Speed depends on the scene, the motion sampling and the number of paths, so measure your own workload. Two reference points from the repository's PERFORMANCE.md, measured on an RTX 5080:

  • A full SMPL walking person (128 scatter sites, 3 TX by 4 RX, 128 chirps by 256 samples, adaptive motion, beat output) ran 300 frames in 14.3 s, a median of 45 ms per frame, with processing excluded.
  • On a 13-interval MIMO frame, raising adaptive nodes from 2 to 5 raised the probe count from 27 to 105 and the frame time from 95.5 ms to 173.5 ms. Keep the default nodes=2 unless the phase tolerance, not max_interval, is what shortens the intervals.

The main levers, from largest to smallest:

  1. motion: static is one evaluation per frame; adc is one per ADC sample; adaptive is usually in between.
  2. reflections: each extra bounce multiplies the path count.
  3. The number of targets, transmitters and receivers.
  4. rediscover_every_frames, which skips topology discovery between frames at the cost of completeness.