blob: 9266fff7755480486c52bc66291d91db14435b86 [file] [view] [edit]
# SIMD and GPU Changes
Use this guide for packet math, architecture backends, device annotations, CUDA/HIP/SYCL code, Tensor device
execution, and the `contrib/Eigen/GPU` module. The repository-root `AGENTS.md` still applies.
## Packet math
Eigen's vectorization API is the `Eigen::internal` packet layer. `packet_traits` and `unpacket_traits` describe a
packet type and its capabilities; `p*` operations such as `pload`, `pstore`, `padd`, and `pmul` provide the common
interface used by evaluators.
- Scalar fallbacks and the default traits live in `Eigen/src/Core/GenericPacketMath.h`. Shared vector
implementations live under `Eigen/src/Core/arch/Default/`; backend specializations live in the relevant sibling
directories under `Eigen/src/Core/arch/`.
- Start a new operation with a correct generic fallback when one is possible. Add specializations only for relevant
backends that support the operation; do not require an implementation in every backend merely because one backend
gains an intrinsic.
- Guard each intrinsic with the feature macro that enables it, even inside a broader backend directory. For example,
AVX2 or FMA intrinsics in `arch/AVX/` still require `EIGEN_VECTORIZE_AVX2` or `EIGEN_VECTORIZE_FMA`, plus a fallback
for narrower configurations. Consult `Eigen/src/Core/util/ConfigureVectorization.h` for the current feature macros.
- Keep capability flags (`Has*`), packet and half-packet types, alignment, masked access, casts, and cost metadata
consistent with the implementation. A capability flag must not advertise an unavailable or semantically different
operation.
- Preserve scalar-remainder behavior and unaligned paths. Packet-sized inputs alone do not cover an evaluator.
- Standard mathematical functions should match the scalar contract for special values. Measure ordinary-input error
in ULPs against an appropriate scalar or higher-precision reference; test NaN, infinities, signed zero, subnormals,
and domain boundaries explicitly where the platform exposes those IEEE-754 behaviors.
A missing specialization is not always a compile error: some generic fallbacks in
`Eigen/src/Core/GenericPacketMath.h` are semantically the identity or a single-lane version of the real operation, so
a backend without the specialization computes silently wrong results rather than failing to build. Before calling a
`p*` operation from code every backend instantiates, confirm the backends that will reach it implement it (SYCL's
packet surface is the usual gap), and keep a scalar fallback for those that do not.
The current source tree and `test/CMakeLists.txt` are authoritative for supported backends and configuration options;
do not copy an architecture inventory into documentation.
## Device-callable code
For CUDA and HIP, `EIGEN_DEVICE_FUNC` supplies the host/device qualifiers required by functions reached from device
code. Under SYCL device compilation it supplies Eigen's required flattening and inlining attributes rather than alone
determining callability. Preserve it on coefficient accessors, evaluators, functors, small helpers, constructors, and
operators reached from device code.
- Keep device code allocation-free unless the specific backend and API deliberately provide an allocator.
- Avoid host-only standard-library calls, exceptions, RTTI assumptions, and function-local static state on device
paths.
- Define configuration macros before the first Eigen public header and keep index configuration consistent across
translation units that exchange Eigen objects.
- Include public module headers in tests and examples. Implementation headers under `Eigen/src/` and
`contrib/Eigen/src/` are not user include points.
## Three GPU models
### Core types inside kernels
CUDA and HIP kernels can use fixed-size owning matrices, vectors, and arrays through public Eigen headers; see
`doc/UsingNVCC.dox`. Dynamic owning `Matrix` and `Array` objects require allocation and are generally unsuitable
inside kernels. Runtime dimensions are not inherently unsupported: `Map` over caller-managed device memory can use
dynamic dimensions when every operation reached by the expression is device-callable.
CUDA/HIP compilation disables host SIMD. Move substantial host-side Eigen work to a normal `.cpp` translation unit.
Use `EIGEN_NO_CUDA` or `EIGEN_NO_HIP` only when the corresponding compiler processes Eigen exclusively for host use.
If device code requires a different dense index type, define `EIGEN_DEFAULT_DENSE_INDEX_TYPE` consistently wherever
objects cross the host/device boundary.
### Tensor devices
`contrib/Eigen/Tensor` evaluates expressions through an explicit device. `GpuDevice` handles CUDA/HIP and
`SyclDevice` handles SYCL; Tensor GPU kernels remain part of the Tensor implementation. Device-resident storage is
normally supplied through `TensorMap`, and the destination selects execution with `out.device(device) = expression`.
Host-side runtime calls in the device go through `EIGEN_GPU_RUNTIME_CHECK` in `Eigen/src/Core/util/GpuRuntime.h`.
Kernel launches use `LAUNCH_GPU_KERNEL` in `contrib/Eigen/src/Tensor/TensorDeviceGpu.h`, which forwards to
`internal::gpu_launch` in `GpuRuntime.h`. A result stored into a variable that only a `gpu_assert` inspects is
unchecked in every release build.
Consult `contrib/Eigen/src/Tensor/README.md` and the nearby device implementation before changing memory,
synchronization, or callback semantics.
### `contrib/Eigen/GPU`
This is a host-side NVIDIA-library wrapper selected explicitly with `Eigen::gpu` types. `gpu::DeviceMatrix` is not a
`MatrixBase` expression, and a supported expression maps to a CUDA library operation rather than Core coefficient
evaluation or packet fusion. Define `EIGEN_USE_GPU` before including `<contrib/Eigen/GPU>`, and consult
`contrib/Eigen/src/GPU/README.md`. Its tests under `contrib/test/GPU/` are intentionally host-compiled `.cpp`
files.
Asynchrony makes otherwise-ordinary refactors unsafe in this module. Freeing, reusing, or destroying memory, streams,
events, and handles must respect stream order — synchronize or event-fence first, and treat a wait removed by a
cleanup as correct only if it was redundant on every ownership mode (a borrowed handle's no-op deleter supplies none
of the synchronization an owned one's teardown does). Do not encode a mode in a value the user can legitimately pass:
a null stream is a valid stream with build-configurable meaning, not a "none" sentinel. Caches keyed on host identity
(pointer, extent, nnz) are spoofable because reassignment reuses allocations; key on content or a generation counter.
## Validation
- Packet API or math changes: run the relevant parts of `packetmath`, the generic packet tests, and
`special_packetmath`; exercise every locally available affected backend.
- Core device-callability changes: build and run the relevant `gpu_basic` or `gpu_example` target with the available
CUDA/HIP compiler.
- Tensor device changes: run the operation's CPU Tensor test plus the matching CUDA/HIP/SYCL test where available.
- `contrib/Eigen/GPU` changes: run the focused target under `contrib/test/GPU/` and any affected library
integration target enabled by the local toolkit.
- Report backends or hardware that were unavailable. Do not claim cross-backend validation from a host-only build.
Performance-sensitive packet or GPU changes require a representative benchmark under identical compiler flags,
device state, and workload conditions. Correctness tests are not performance evidence.