Skip to content

Add permutedims!(C, A, p, α, β) with a direct path for small arrays - #99

Draft
lkdvos wants to merge 1 commit into
ld-kernel-trimfrom
ld-permutedims
Draft

lkdvos wants to merge 1 commit into
ld-kernel-trimfrom
ld-permutedims

Conversation

@lkdvos

@lkdvos lkdvos commented Oct 29, 2026

Copy link
Copy Markdown
Member

Builds on #98 (and #97). It only uses #98 to avoid looping over the padded singleton dimensions, and can be rebased onto #97 if #98 is dropped.

This adds permutedims!(C::StridedView, A::StridedView, p, α, β), computing C = β * C + α * permutedims(A, p) without reading C if β is zero. This is the operation behind every tensoradd! in TensorOperations, which currently goes through _mapreducedim! with a permuted view, and with a separate reduction kernel and initop pass for β ≠ 0.

  • Small arrays (C and A together fit in one block, both in CPU memory): the dimensions are ordered by the strides of C, fused, and passed to the kernel directly. This skips constructing the permuted view, promoteshape, the stride ranking, the blocking and the threading check. A nonzero β is handled in the same pass, as a map over (C, C, A).
  • Other arrays take the general path, as before. A single pass for β ≠ 0 was 5–10% slower than the reduction kernel there.

The 3-argument permutedims! is unchanged.

Benchmarks

p = (1, 4, 2, 5, 3, 6), single-threaded, ns. "TO-style" is the current TensorOperations call through _mapreducedim! on this branch.

shape elements β = 0 TO-style β = 0 permutedims! β ≠ 0 TO-style β ≠ 0 permutedims!
Float64
(2, 2, 1, 1, 1, 1) 4 155 80 168 78
(2, 2, 2, 2, 1, 1) 16 208 128 269 145
(2, 2, 2, 2, 2, 2) 64 301 224 518 267
(4, 4, 2, 2, 2, 2) 256 406 308 744 423
(8, 8, 2, 2, 2, 2) 1024 503 434 1401 1108
(16, 16, 4, 1, 4, 1) 4096 1187 1348 3376 3410
(32, 32, 8, 1, 8, 1) 65536 19352 19524 46753 49015
ComplexF64
(2, 2, 1, 1, 1, 1) 4 173 93 185 89
(2, 2, 2, 2, 1, 1) 16 232 145 280 162
(2, 2, 2, 2, 2, 2) 64 343 253 555 299
(4, 4, 2, 2, 2, 2) 256 466 382 840 488
(8, 8, 2, 2, 2, 2) 1024 921 850 1820 1185
(16, 16, 4, 1, 4, 1) 4096 2711 2553 6477 6128
(32, 32, 8, 1, 8, 1) 65536 76843 76553 92364 92079

The arrays from 4096 elements on take the general path, so the differences there are noise. For 4 elements, the remaining ~70 ns are mostly setup: checking the permutation (~8 ns) and the sizes (~6 ns), sorting (~9 ns) and fusing (~25 ns) the dimensions; the kernel itself takes ~20 ns.

I also tried a dedicated non-generated kernel for the small path, with an odometer over the outer dimensions. It halves the compile time of the small path, but is 2–4× slower from 64 elements on, because the innermost runs are short for these permutations.

Tests

There is a new testset covering ranks 1 to 6, real and complex element types, strided and conjugated views, β = 0 with NaN in C, the general path and the error paths. The GPU tests check that JLArray-backed views take the general path. The full test suite passes, single- and multi-threaded (4 threads).

A follow-up in TensorOperations can then call this method from stridedtensoradd!.

🤖 Generated with Claude Code

Computes `C = β * C + α * permutedims(A, p)`, as used by TensorOperations. Arrays that fit in
a single block skip the layout planning, blocking and threading machinery: the dimensions are
ordered by the strides of the destination, fused, and passed to the kernel directly. A nonzero
`β` is handled in the same pass instead of a separate reduction kernel.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@lkdvos
lkdvos marked this pull request as draft October 29, 2026 21:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant