blob: bb90f54713d11f9c696900577b3e052c6d44a893 [file] [view] [edit]
# Tensor and Thread-Pool Changes
Use this guide for `contrib/Eigen/Tensor`, `Eigen/ThreadPool`, Core's custom GEMM thread-pool backend, and explicit
thread-pool devices. The repository-root `AGENTS.md` still applies.
## Compatibility and risk
Tensor and ThreadPool are foundational to TensorFlow and other downstream users. The `contrib/` location describes
Tensor's API-stability policy, not its importance. Changes to signatures, header layout, evaluation order, allocation,
synchronization, numerical behavior, or performance can have a large downstream impact.
- Prefer additive changes and preserve public header paths. Use `<contrib/Eigen/Tensor>` and
`<Eigen/ThreadPool>`; never expose implementation-header includes to users.
- Paths below `unsupported/Eigen/`, including `unsupported/Eigen/CXX11/`, are backward-compatibility forwarding
shims only. New code must use the canonical `contrib/Eigen/` headers and must not add headers under either.
- Preserve `EIGEN_DEVICE_FUNC` on code reachable by CUDA, HIP, or SYCL device evaluation.
- Treat evaluator flags, layouts, scalar/packet/block paths, zero-sized tensors, aliasing, and asynchronous object
lifetimes as part of the behavior under test.
- Changes to contraction, reduction, convolution, morphing, scheduling, or the cost model are performance-sensitive.
Add or update a benchmark and compare representative shapes, layouts, thread counts, and scalar types.
- Call out intentional compatibility or performance changes prominently in the merge request.
## Keep the threading mechanisms separate
### OpenMP
OpenMP is Core's primary implicit multithreading mechanism and covers the algorithms listed in
`doc/TopicMultithreading.dox`. It is controlled through the compiler's OpenMP support, `Eigen::setNbThreads`, and the
OpenMP runtime. Do not infer that every algorithm in that list is also supported by the custom GEMM thread pool.
### `EIGEN_GEMM_THREADPOOL`
This macro selects Eigen's custom thread-pool backend for general dense matrix-matrix products only. It is mutually
exclusive with OpenMP. Define it before including Eigen, create an `Eigen::ThreadPool`, and register that pool with
`Eigen::setGemmThreadPool(&pool)` before concurrent GEMM work begins.
The registered pointer is process-global state and the pool remains caller-owned. It must outlive all GEMM using it;
do not replace it while a product is running. `Eigen::setNbThreads` controls the active thread limit, while registering
a pool resets that limit to the pool's thread count. Passing `nullptr` currently queries the registered pool; it does
not clear the registration. Treat `doc/TopicMultithreading.dox` and
`Eigen/src/Core/products/Parallelizer.h` as the current API and implementation references.
### `CoreThreadPoolDevice`
`Eigen::CoreThreadPoolDevice` is an explicit device for parallel Core coefficient-wise assignment:
```cpp
#include <Eigen/ThreadPool>
Eigen::ThreadPool pool(thread_count);
Eigen::CoreThreadPoolDevice device(pool);
destination.device(device) = expression;
```
It is distinct from implicit GEMM parallelization. Changes belong with the device/evaluator tests represented by
`test/assignment_threaded.cpp`, not only the GEMM tests.
### Tensor `ThreadPoolDevice`
Define `EIGEN_USE_THREADS` before `<contrib/Eigen/Tensor>`, then construct a `ThreadPoolDevice` over an existing
`ThreadPoolInterface` and evaluate explicitly:
```cpp
Eigen::ThreadPool pool(pool_threads);
Eigen::ThreadPoolDevice device(&pool, execution_threads);
output.device(device) = expression;
```
The device does not own the pool. The pool, allocator, input storage, output storage, and callback state must remain
alive until synchronous evaluation returns or asynchronous completion is signaled. Tensor's executor, contraction,
reduction, and device code have `ThreadPoolDevice`-specific paths; a serial `DefaultDevice` test alone is insufficient.
See `contrib/Eigen/src/Tensor/README.md` and `TensorDeviceThreadPool.h`.
## Evaluator capability flags and cost
Evaluator capabilities are independent claims the executor combines: vectorization follows `PacketAccess`, tiling
follows `BlockAccess && PreferBlockAccess`. Widening a flag widens a contract, and the execution paths treat evaluator
state differently — threaded coefficient evaluation copies the evaluator per worker range, while tiled evaluation
shares one evaluator across concurrent block tasks, so a functor with mutable state races there even though its
coefficient and packet paths are correct. A capability may legitimately depend on the `Device`; prefer the
conservative answer for stateful or unannotated user functors (see rule 6 in the root `AGENTS.md`).
`costPerCoeff()` drives thread-count selection and must describe the path actually taken: when a packet path is
conditional, mirror that condition in the cost and charge nested work as scalar where the packet path gathers lane by
lane (`TensorStriding.h` is the reference).
## Scheduling changes
- Preserve the `ThreadPoolInterface` contract, including `Schedule`, `ScheduleWithHint`, `CurrentThreadId`,
cancellation behavior, and caller ownership.
- Test one-thread and multi-thread execution, work invoked from a worker, completion/wakeup behavior, and shutdown with
pending or cancelled work when those paths are affected.
- Avoid blocking a worker on work that can only run on the same exhausted pool. Make callback and barrier lifetime
rules explicit in code when they are not self-evident.
- `DenseBase::Random()` and `setRandom()` use `std::rand` and are not re-entrant. Do not call them concurrently;
pre-generate inputs or use thread-local `<random>` generators through `NullaryExpr`.
- Cost-model and grain-size changes need both small-workload overhead measurements and large-workload throughput
measurements. Check oversubscription and nested parallelism rather than assuming more threads are faster.
- Benchmark only on an otherwise idle system, one benchmark process at a time, and report repeated measurements rather
than a single timing.
## Validation
- Thread-pool internals: run the affected `threads_*` target, especially event-count, run-queue, non-blocking-pool, or
fork-join tests.
- Custom GEMM pool: run `product_threaded` and the ordinary product tests affected by the change.
- Core explicit device: build and run the assignment-threaded test represented by `test/assignment_threaded.cpp` if
it is registered in the current test configuration.
- Tensor pool/device changes: run `tensor_thread_pool`, `tensor_executor`, and the focused operation tests such as
contraction or reduction.
- Tensor behavior shared with accelerators: also follow `simd-gpu.md` and run the locally available device tests.
- Report unavailable sanitizers, GPU toolchains, platforms, and downstream TensorFlow validation explicitly.
Use `test/CMakeLists.txt`, `contrib/test/CMakeLists.txt`, and the checked-out CMake configuration as the source of
truth for target names. Do not maintain a duplicate test or backend inventory here.