Repository navigation
Device support for TA::Tensor via TA::UMTensor - #554
Conversation
|
@ajay-mk if we can ever hope to be able to use this for CSV-CC we should probably avoid duplicating To minimize the amount of boilerplate in the tile ops it should be possible to use macros. |
|
I like the idea. I think the runtime flag can also give us control the execution space of specific operations, so that we can push only certain terms/operations into device. I will look into this. |
Adds the entry point for native TA::Tensor support on devices, paralleling
the existing btas_um_tensor path. UMTensor<T> is a TA::Tensor instantiated
on device_um_allocator<T>, which already maps to MemorySpace::Device_UM
via platform.h:90.
* fwd.h: UMTensor<T> typedef under TILEDARRAY_HAS_DEVICE.
* device/tensor.h: is_device_tile<UMTensor<T>> partial specialization so
the existing pass-through specs for Tile<T> and LazyArrayTile<T, Op>
(tensor/type_traits.h) classify UMTensor-backed tiles as device tiles
-- this is the gate that routes the expression engine through
madness::add_device_task at binary_eval/unary_eval/contraction_eval and
the 6 sites in expressions/expr.h. Also adds detail::to_device /
detail::to_host prefetch helpers that go directly through
device::memPrefetchAsync (TA::Tensor stores data via shared_ptr<T[]>
rather than a varray, so we cannot route through to_execution_space).
* device/tensor.cpp: static_asserts pinning down that the trait fires
for UMTensor<{double,float,complex<double>}>, propagates through
Tile<>, and does not misclassify plain Tensor<double>. Placeholder
for the explicit instantiations that will land in Phase 4.
* src/CMakeLists.txt: hook the two files into the TILEDARRAY_HAS_HIP OR
TILEDARRAY_HAS_CUDA source list, alongside btas_um_tensor.{h,cpp}.
No tile-op overloads yet -- Phase 1 is compile-only.
Adds the smallest tile-op set that the expression engine needs in order
to evaluate a `C("ij") = a*A("ik")*B("kj")`-style expression plus a norm
on UMTensor: clone, scale/scale_to, neg/neg_to, add/add_to (+ scaled
forms), subt/subt_to (+ scaled forms), dot, squared_norm, norm, and gemm
(returning + accumulating). Element-wise mult, permute, shift, and
batched paths are deliberately not included yet -- they need librett /
custom kernels and a clear nbatch story, and dragging them in here
would obscure whether the dispatch surface alone is correct.
Each overload is a concrete-type free function in `namespace TiledArray`
so ADL prefers it over the generic templated forwarders in
`tile_op/tile_interface.h`. No constraint relaxation, no member-function
revert -- the dispatch falls out of overload partial ordering.
The kernel pattern mirrors `device/btas.h`: resolve a BLAS++ queue from
`blasqueue_for(range)`, prefetch every input + result to the device,
call into BLAS++, then `sync_madness_task_with(stream)` so the wrapping
MADNESS device task waits for the queue to drain. We do NOT thread an
explicit `blas::Queue&` through composite ops -- `stream_for(range)` in
`external/device.h` already returns the current task's stream when
invoked inside a device task, which is what we want.
For now batched tiles (`nbatch_ > 1`) are asserted away; the expression
engine doesn't currently route batched UMTensor through these ops and
silently miscomputing would be worse than a clear assert.
`device/tensor.cpp` grows a tiny instantiation probe that exercises the
full Tier-1 surface for `double` and `float`, so anything that doesn't
type-check breaks the build immediately rather than waiting on Phase 5
tests. Real explicit instantiations land in Phase 4.
Rounds out Tier 1 with the ops that require librett or the device
element-wise kernel:
* permute(arg, Permutation) -- librett_permute on the tile data
* permute(arg, BipartitePermutation)-- forwards to outer(perm); required
to win ADL against the generic
member-delegating overload (the
analogous comment lives in
device/btas_um_tensor.h:193)
* shift(arg, bound_shift) -- copy + Range::inplace_shift
* shift_to(arg, bound_shift) -- in-place range shift, no copy
* mult / mult_to (+ scaled + permuted variants) via device::mult_kernel
and device::mult_to_kernel
* scale(a, f, perm), neg(a, perm), add(..., perm), subt(..., perm), and
their scaled-with-perm forms -- thin compositions over the
non-permuted core and the new permute
All sit in `namespace TiledArray`; ADL wins over the
`tile_op/tile_interface.h` defaults the same way it does for the
non-permuted core. The instantiation probe in device/tensor.cpp now
exercises the full Tier-1 surface (Phase 2a + 2b).
…ssion tests Three bug categories surfaced once expressions actually ran end-to-end: 1. **Scale-factor semantics on `*_to(arg, factor)` were inverted.** `TA::Tensor::add_to(r, factor)` is `(this += r) *= factor`, and `TA::Tensor::subt_to(r, factor)` is `(this -= r) *= factor` -- the scaling applies to the result of the in-place op, not to `arg` alone. My initial implementations computed `result += factor * arg` and `result -= factor * arg`, which gives wrong values whenever the engine calls `subt_to(std::move(second), first, -1)` from `tile_op/subt.h:117` (the "right is consumable" branch -- expected to compute `(second - first) * -1 = first - second`). The new implementations chain `add_to/subt_to(no factor)` + `scale_to`, matching TA::Tensor's `(l += r) *= factor` pattern. 2. **Engine passes consumable operands as rvalues.** Subt::eval and ScalSubt::eval at `tile_op/subt.h:117,302` pass the result tile via `std::move(...)`. A plain `UMTensor<T>&` overload is not a viable candidate for an rvalue argument, so overload resolution falls through to the generic forwarder in `tile_op/tile_interface.h` (and `tile_interface/scale.h`) that delegates to TA::Tensor's CPU member function. The CPU member reads UM memory while the prior device kernel is still in flight on the queue -- silently miscomputing. Every in-place op (scale_to, neg_to, add_to[x2], subt_to[x2], mult_to[x2], shift_to) now has two concrete-type overloads: `UMTensor<T>&` and `UMTensor<T>&&`. Concrete types beat the templated forwarding reference in partial ordering regardless of constraint shape -- a constrained forwarding-ref version would in principle also win (constrained > unconstrained per [temp.constr. order]) but g++ does not consistently treat tile_interface's `enable_if`-only forwarders as unconstrained for this purpose; the two-concrete-overload form is robust. 3. **No correctness coverage existed for the dispatch path.** The previous `tensor_device.cpp` exercised tile-level ops directly from the main thread, which is the wrong contract: tile ops cooperate with the enclosing `madness::add_device_task` for stream sync and never run that way in production. Replaced with `tests/expressions_device_tensor.cpp`, mirroring the structure of `expressions_device_um.cpp` (the existing btas device test). 18 cases including: trait classification, direct assign, permute, scale, neg, add/subt (+ with-permute / -to / -with-factor variants), scaled-subt isolations on left/right, mixed linear combination (catches bug #1), Hadamard, contraction, norm2 / dot reductions, and a `reuse_stress` case repeating `dot(a, a)` 8x (catches the LazyArrayTile conversion race in MPQC's pattern -- expected to be a master-branch baseline failure not introduced here). All 18 pass. Also documents the `UMTensorArg` concept inline as the marker for "this is a UMTensor (any cv/ref qualifier)" -- kept around as documentation of intent even where the dispatch tiebreak forced us to use concrete-type overloads instead.
…m, contraction variants)
Goes from 18 to 59 test cases, covering the patterns the expression
engine uses in production but the original cut didn't exercise. New
coverage:
Elementary ops + permutations:
* In-place expression operators (+=, -=, *=, += with permute).
* Negation in compounds: -(2*(a+b)), -a("c,b,a").
* All scale/permute combinations for add/subt/mult: scale_add,
scale_add_permute, subt_permute, scale_subt, scale_subt_permute,
mult_permute, scale_mult, scale_mult_permute.
Dataflow + reduction:
* Multi-step dataflow chain (t = a + b; c = 2*t - a) -- engine wires
t's Future<Tile> into the next dist-eval without an intervening
fence (per CLAUDE.md's synchronization hierarchy).
* Contraction-plus-reduce (norm2 of a contraction result).
* no_alias() + reduce -- exercises the LHS-doesn't-alias-RHS
optimization through to a reduction.
Block expressions (PR 531 trouble area):
* Basic block assign / scaled-sum / accumulate.
* const_block: block from a const reference.
* scal_block: 2 * a.block(...).
* permute_block: a("c,b,a").block(...).
* assign_sub_block: write into a tile sub-block of an existing array.
* block_contract, block_permute_contract: blocks fed into GEMM.
Contraction variants:
* Outer product (rank-changing GEMM, no shared contraction index).
* Permuted result (c("k,i") = a("i,j") * b("j,k")).
* Transpose-on-right input (c("i,k") = a("i,j") * b("k,j")).
* CC-style rank-4: r("a,c") = t("a,b,k,l") * v("c,b,k,l").
* scale_cont, scale_cont_permute, scale_cont_with_input_transpose
-- scale-fuse-into-GEMM paths.
* cont_non_uniform_split_inner / _two_inner -- catches GEMM kernels
that silently assume uniform tile shapes.
TA::einsum entry point:
* Matmul, Hadamard, two-index contraction. Documents the one pattern
not covered (`einsum("ij,jk->ijk")` with an index in both inputs
and output) -- it segfaults inside einsum's internals on master
regardless of allocator, out of scope for this branch.
Dot variants:
* dot_permute: dot of permuted arrays.
* dot_contr: dot of two contraction expressions (one tier deeper than
btas-device's NO_THROW-only version; we validate the scalar value).
Tolerances are 5e-14 for non-GEMM ops, 1e-10 to 1e-9 for GEMM-bearing
paths to absorb summation-order differences between BLAS++ on the
device and the Eigen-based CPU reference.
Validation:
* All 59 device-tensor cases pass.
* Full np=1 ta_test suite (1880 cases, 12.56M assertions) still
green -- no regressions from this branch.
Phase 3 surface: serialization plus the bulk DistArray-level helpers (prefetch and host<->device conversion) needed by downstream code that wants to round-trip UMTensor data through MADNESS archives or shuttle DistArrays between memory spaces. src/TiledArray/device/tensor.h: * `to_host(DistArray<UMTensor<T>, P>&)` and `to_device(DistArray<UMTensor<T>, P>&)`: bulk prefetch every local tile, fence the world, deviceSynchronize on exit. Mirrors the btas helpers in btas_um_tensor.h:567-617 but takes the bare UMTensor (no `TA::Tile<>` wrapper, per CLAUDE.md). * `um_tensor_to_ta_tensor(DistArray<UMTensor<T>, P>)` and `ta_tensor_to_um_tensor(DistArray<TA::Tensor<T>, P>)`: tile-by-tile conversion via `to_new_tile_type`. The per-tile lambda allocates a result of the target tile type, prefetches the source as needed, and memcpys -- since both sides are TA::Tensor and only the allocator differs, no per-element conversion is required. * `madness::archive::ArchiveStoreImpl<Archive, UMTensor<T>>`: prefetches the tile to host before serializing, then writes the same fields TA::Tensor::serialize would (empty/range/nbatch/wrap(data)). The default member serialize is not safe to use as-is because UM data may be stale on the host while a device kernel is in flight. * `madness::archive::ArchiveLoadImpl<Archive, UMTensor<T>>`: reconstructs the tensor in UM (writes go through host pages of UM -- if downstream code wants the data on the device, it should call `to_device` explicitly). tests/expressions_device_tensor.cpp: Five new cases (now 64 total): * serialize_um_tensor: single-tile round-trip through BufferOutput/ Input archives. * serialize_um_tensor_empty: empty branch in Store/Load. * um_to_ta_round_trip: device array -> host array -> device array, values preserved across both legs. * um_to_ta_then_expression: a host expression on a converted-from- device array matches the same expression on the host mirror. * bulk_prefetch_round_trip: to_host/to_device on a DistArray are no-ops for correctness (they only adjust page residency hints). Validation: * All 64 device-tensor cases pass. * Full np=1 ta_test suite: 1885 cases, 12.64M assertions -- still green; no regressions from this branch.
Phase 4: instantiate `Tensor<T, device_um_allocator<T>>` once in
device/tensor.cpp and `extern template`-declare them in
device/tensor.h, so each TU including device/tensor.h does not
re-instantiate the full ~3000-line Tensor class body.
Mirrors the pattern at the bottom of src/TiledArray/tensor/tensor.h
+ src/TiledArray/tensor/tensor.cpp for the host-side instantiations.
The instantiated set is double, float, complex<{double,float}>, int,
long -- a superset of the host set (which omits int/long) for parity
with btas_um_tensor.cpp.
BLAS-bearing free functions (gemm, scale, axpy-driven add/subt, ...)
are left as header-defined templates; explicitly instantiating them
would pull the full BLAS++/librett surface into device/tensor.cpp,
and the build-time saving from extern-templating them does not
justify it. They get instantiated lazily in whichever TU actually
calls them (typically the test or example TU).
Validation:
* All 64 device-tensor cases still pass.
* Full np=1 ta_test suite: 1885 cases, 12.63M assertions -- still
green; no regressions from this change.
Phase 6: two example programs that exercise the UMTensor surface
end-to-end through real timing loops, mirroring the existing
btas-based ta_dense_device.cpp and ta_vector_device.cpp but using
the bare UMTensor tile type (no `TA::Tile<>` wrapper).
* examples/device/ta_dense_um_tensor.cpp:
c(Nm,Nn) = a(Nm,Nk) * b(Nk,Nn) on UMTensor<double> tiles, blocked
by user-supplied Bm/Bn/Bk. Reports per-iteration wall time and
GFLOPS, then verifies every result element equals the analytic
Nk * val_a * val_b. Honors `cudaProfilerStart/Stop` when built
with CUDA so the timed loop is profilable separately from setup.
* examples/device/ta_vector_um_tensor.cpp:
Element-wise op benchmark -- add, subt, scale, Hadamard, permute,
in-place axpy -- on Nm x Nn UMTensor matrices. Reports per-op
average wall time and effective bandwidth (counting one read +
one write per element for unary ops, two reads + one write for
binary ops).
Both examples follow the convention in CLAUDE.md / the rest of TA's
device examples: TA_SCOPED_INITIALIZE for runtime setup, world fence
before reading device-side results, exception-catching `main` wrapper.
Hooked into examples/device/CMakeLists.txt's `foreach(_exec ...)`
list so they build alongside the existing examples whenever
TILEDARRAY_HAS_CUDA OR TILEDARRAY_HAS_HIP.
Smoke-validated locally:
ta_dense_um_tensor 256 64 256 64 256 64 3
-> Verification PASSED
ta_vector_um_tensor 512 128 512 128 3
-> all six ops reported timings without error
The runtime instantiation probe (`compile_test_tier1<T>` plus the two `instantiate_tier1_*` function pointers) was added in the Phase 2 commits to force template type-checking of the device tile-op overloads before any test exercised them. It is no longer load-bearing: * tests/expressions_device_tensor.cpp (64 cases) calls every tier-1 overload through the expression engine for `double`, so any instantiation breakage shows up at test-build time. * device/tensor.cpp's explicit `template class Tensor<T, device_um_allocator<T>>` instantiations cover the class members authoritatively. Removing the probe trims ~75 lines of dead code from the .cpp. The `is_device_tile_v` static_asserts are kept -- they are zero-cost and guarantee trait correctness even when BUILD_TESTING=OFF.
* Remove the `UMTensorArg` concept and the `detail::is_um_tensor` trait it was built on. Both were introduced during the dispatch debugging to try a constrained forwarding-reference approach (per the comment block at the top of the section); that approach was abandoned in favor of two concrete-type overloads (`UMTensor<T>&` and `UMTensor<T>&&`) per in-place op, which beat the templated forwarding-ref candidate in partial ordering. The concept and trait are dead declarations; the explanatory comment is kept and trimmed. * Rewording: two inline comments referencing "Phase 2" and "Phase 3" development sequencing -- meaningless to a future reader -- are reworded to describe the rule without the historical label. Verified: 64-case device_tensor_expressions_suite still passes; no behavior change.
Widen um_tensor_to_ta_tensor / ta_tensor_to_um_tensor from <T, Policy> to <UMTile, HostTile, Policy>, mirroring the signature in device/btas_um_tensor.h:619+. Identity overloads cover the case where source and destination tiles coincide.
The tile-op overloads for UMTensor<T> dispatch BLAS-on-pointer kernels (scal, copy, gemm, dot, etc.). When CCk is instantiated for the device tile, it composes nested tiles of the form UMTensor<UMTensor<double>> to represent its outer/inner structure; those nested tiles must route through TA::Tensor member ops, not the device kernels. Add `requires TiledArray::detail::is_numeric_v<T>` to each device tile-op overload, and gate `is_device_tile<UMTensor<T>>` and the archive store helper on the same predicate so non-numeric element types fall through to the host tile path.
…A_EXCEPTION, doc fixups, formatting)
…nces to at least 1e-12
The returning overload was missing left_right_congruent checks, and the in-place overload was missing the full left_result/right_result/left_right congruence set. Silent geometry mismatches would have produced wrong results instead of asserting. Mirrors the asserts in tensor/kernels.h's host-side detail::gemm worker.
detail::to_host wraps memPrefetchAsync, so the immediate host reads in um_tensor_to_ta_tensor's convert_tile lambda and in ArchiveStoreImpl::store were racing the prefetch on Pascal+ devices with concurrent_managed_access. Drain the stream via sync_madness_task_with before the host walks the tile.
- shift_to: call Tensor::shift_to instead of const_cast'ing the range. TA::Tensor exposes a public shift_to member (unlike btas::Tensor), so the const_cast inherited from btas_um_tensor.h is unnecessary here. - apply_scale_factor: flatten 3-level nested if constexpr into one else-if-constexpr cascade.
sync_madness_task_with() records a stream so the enclosing MADNESS device task can defer completion until it drains; it never blocks itself. Outside a device task there is nothing to defer, and the thread-local it consults is pre-initialized non-empty, so such calls synchronized nothing. Device ops do reach that path: foreach.h, dist_array.h, array_impl.h and dist_eval/array_eval.h submit with plain taskq.add and carry no is_device_tile branch, and contraction_eval.h guards 1 of 18 submissions. An op reached that way was fire-and-forget -- the caller read a tile whose kernel was still writing it, silently and nondeterministically. Found via a downstream iterative solver whose residual stalled instead of converging.
detail::gemm calls a device gemm when every operand is a UMTensor of a BLAS type and execution_space() is Device; the UMTensor gemm overloads are removed, so Tensor::gemm's per-batch loop now covers nbatch > 1. execution_space() defaults to Device, and ExecutionSpaceScope switches a region to the host code.
clone, permute, scale, neg, add, subt, mult and their in-place, scaled and permuted forms branch to device kernels through TA_ON_DEVICE, and the matching UMTensor overloads in device/tensor.h are removed. The device declarations are visible in every build, so Tensor needs no preprocessor guards.
dot maps to the unconjugated device dot and inner_product to the conjugated one; the remaining UMTensor reduction overloads are removed.
A reduce task converts lazy tiles inside the task, so a block's shift can queue a device copy that the host reduction then reads unfinished.
# Conflicts: # .gitignore
54b09f9 to
fe37cbf
Compare
…pletion - Device branches of add_to/subt_to/axpy_to/scale_to/mult/mult_to use total_size() and check nbatch() congruence, so batched tiles are processed in full (previously only batch 0 in Release builds); device permute loops over batches. - UMTensor storage freed inside a device task is returned to the Umpire pool only after the task's stream work completes (DeferredReleases held by deviceTaskFn). Replaces permute_temporary and the blocking synchronize in axpy_to(right, factor, perm), and also covers lazily evaluated temporaries (e.g. BinaryWrapper). - Tensor::serialize prefetches UM storage to host before writing; drop the separate ArchiveStoreImpl<UMTensor> specialization. Move UMTensor extern template declarations into tensor/tensor.h so every TU sees them. - Document ExecutionSpaceScope limits and that TA_ON_DEVICE is usable in templates only; rename ponytail: tags to TODO:.
Summary
Adds device (CUDA/HIP) support to
TA::TensorthroughTA::UMTensor<T>, aTA::Tensorbacked by Umpire-managed unified memory (Tensor<T, device_um_allocator<T>>).Following the review discussion, the device code now lives inside
TA::Tensoritself instead of a separate set ofUMTensoroverloads: each tile op checks whether its operands are UM tensors of a BLAS element type (float, double and their complex types) and, if so, runs a device kernel, otherwise the existing host code. Nothing changes for host tiles.Design
Each
TA::Tensormember that has a device kernel wraps the device branch inTA_ON_DEVICE(types...)(device/dispatch.h). The macro expands to anifchain whose lastelseis the block written after it:flowchart TD A["tile op, e.g. add_to"] --> B{"device_executable_v of the operand types<br/>(if constexpr)"} B -- "false: other tile types, CPU-only builds<br/>(discarded at compile time)" --> H["existing host code"] B -- true --> C{"execution_space() == Device<br/>(runtime)"} C -- "false: inside ExecutionSpaceScope(Host)" --> S["device::synchronize():<br/>wait for kernels this task already queued"] --> H C -- true --> D["device kernel<br/>(blaspp, thrust, librett)"] --> R["return result"]For example,
Tensor::add_to(tensor/tensor.h), after its existing early exits for empty tiles:Dispatch. The device branches are discarded at compile time for every other tile type and in builds without device support, so
TA::Tensorneeds no#ifdefs. Which tile types qualify is decided by the allocator's traits:allocator_memory_space_v<A>(platform.h) gives the memory space an allocator type allocates in (Device_UMfordevice_um_allocator), anddevice_executable_vrequires a device-visible space, a BLAS element type, and the same type for every operand.is_device_tile<UMTensor<T>>is defined next to it, so every translation unit that usesTA::Tensorsees it.Kernels.
gemm,copy,scal,axpy,mult,mult_to,permute,dotandinner_productare declared indevice/dispatch.hand compiled once indevice/tensor.cpp(blaspp, thrust, librett), so user translation units do not include device headers.Members with device branches.
gemm, scaling, negation, add/subt (with factor and permutation), Hadamard product, permute, clone (also used byshift), the in-place forms,dot,inner_productandsquared_norm. Ops with no device kernel (e.g.conj) synchronize the task's device work, then run the host code.ExecutionSpaceScope(platform.h) switches UMTensor ops to the host code for its lifetime, for debugging and as a reference. The setting is process-wide and read when each task runs; open scopes from the main thread only, afterTA::initialize(the constructor throws otherwise). Under a Host scope,UnaryEvalImpl/BinaryEvalImplskip the device task for these tiles. A task already running when a scope opens may have queued kernels before the switch; the Host branch ofTA_ON_DEVICEwaits for them before the host code reads the data.Expression engine.
is_device_tile<UMTensor<T>>routes tile ops throughmadness::add_device_task, as for the existing BTAS device tile.Helpers (
device/tensor.h): tile and arrayto_device/to_hostprefetch,ta_tensor_to_um_tensor/um_tensor_to_ta_tensor(keeping every batch; a no-op when both tile types are the same, as in code generic over the tile type), and the tile and array conversions. Archiving needs no specialization:Tensor::serializeof a UMTensor waits for the task's device work and prefetches the data to the host before storing it, in every translation unit.Changes that affect existing code
sync_madness_task_withoutside a device task now blocks until the stream finishes (external/device.h). Before, the thread-local stream was preset, so a call from a plain task (e.g.foreach,truncate) recorded nothing and returned while the kernel was still running. This applies to every device tile, includingbtasUMTensor. Outside device tasks,stream_for/blasqueue_fornow pick the stream by tile offset instead of always using stream 0.cmake/modules/FindOrFetchMADWorld.cmake). With it on, each PaRSEC context reserves 95% of each visible GPU's free memory at startup, and TA does not run PaRSEC device tasks. These are cache defaults, so a parent project that setsPARSEC_GPU_WITH_CUDA(or_HIP,_LEVEL_ZERO) first keeps its value.external/device.h,device/device_task_fn.h). A UMTensor temporary (e.g. a lazy block argument materialized byBinaryWrapper, or the unpermuted sum inadd(right, perm)) can drop its last reference while kernels reading it are still queued; returning it to Umpire's pool then would let another thread reuse the block under those kernels.Tensor's deleter now hands the release to the enclosing device task (device::defer_until_task_completes), which runs it when the task is destroyed, after its stream callback. Outside device tasks every kernel has already waited for its stream, so storage is released at once. This replaces the blockingdevice::synchronize()calls that the permuted add/subt/mult andaxpy_to(perm)used to make before freeing their temporaries.Tensor::reduce): a reduction task converts lazy tiles inside a device task, so a block'sshiftcould queue a device copy that the host reduction read before it finished.3ff5ac06d(the merge of world: let several foreign threads schedule on the PaRSEC backend m-a-d-n-e-s-s/madness#849; the range from the previous pin also brings in unrelatedworlddc/cloudarchive-chunking andmracommits, none of which TA uses): with the PaRSEC backend, several threads that PaRSEC did not create, such as the thread that runs CUDA stream callbacks, can now submit tasks. Before, the second such thread aborted at 2 or more ranks.detail::gemmcomputesldconce, used by the host, device and logging paths.TA_ON_DEVICEends in its ownelse, so following it with anotherelseis a compile error rather than silently dead host code.Limits
device_um_allocatormaps to a device memory space today; aTA::Tensorwith another allocator runs the host code. A future device-only allocator would also need its host branch disabled, since the host code reads the same memory.INT_MAXelements (blaspp's length type isint); device permutes aboveINT_MAXelements throwTiledArray::Exception(librett uses 32-bit indices).ExecutionSpaceScope).nbatch() > 1): every device branch covers all batches. The host code does not, which predates this PR: the value-returning ops andclone(built onTensor(other, op)and friends) return one batch,scale_to/neg_totouch only the first, andpermuteassertsnbatch() == 1; only the binary in-place ops cover every batch.Follow-up
Once this PR is merged, a follow-up PR will remove the BTAS-based device tile (
btasUMTensor), whichUMTensorreplaces.Tests and examples
tests/expressions_device_tensor.cpp: 79 cases comparing UMTensor expressions with the same expressions on hostTA::Tensorarrays, and, for complex tiles and batched products, with the same UMTensor arrays under a Host scope. Also covers conversions, archives, the stream choice outside device tasks, the reduction race, a Host scope opened while device tasks run, tile ops on batched (nbatch() > 1) tiles,SparsePolicyarrays (shape estimation, contraction, norm, truncate), and release of storage freed inside a device task only after its stream drains.device_tensor_large_suite(disabled by default, about 18 GB per case): theINT_MAXchunking and the permute exception.examples/device/ta_dense_um_tensor.cpp,ta_vector_um_tensor.cpp.New public API
TA::UMTensor<T>(fwd.h);ExecutionSpace,execution_space(),ExecutionSpaceScopeandallocator_memory_space_v(platform.h);TA_ON_DEVICEanddevice_executable_v(device/dispatch.h).UMTensoris now anis_device_tile.Testing
ta_test, CUDA (A30), 1 rankta_test, CUDA (A30), 2 ranksum_expressions_suite/dot(BTAS tiles, unchanged here)ta_testbuilt from the device, expressions, sparse expressions and range units, 1 rank / 2 ranks3ff5ac06dh2o-ccsd-631g-pvdz-gputest crashes at exit in MPQC's teardown, after the result)