Skip to content

GPU rules for vcat, cat, hcat, permutedims.#1225

Merged
sunxd3 merged 27 commits into
mainfrom
cuda-gpu-rules
Jul 9, 2026
Merged

GPU rules for vcat, cat, hcat, permutedims.#1225
sunxd3 merged 27 commits into
mainfrom
cuda-gpu-rules

Conversation

@AstitvaAggarwal

@AstitvaAggarwal AstitvaAggarwal commented Jun 23, 2026

Copy link
Copy Markdown
Member

CI Summary — GitHub Actions

Documentation Preview

Mooncake.jl documentation for PR #1225 is available at:
https://chalk-lab.github.io/Mooncake.jl/previews/PR1225/

Performance

Performance Ratio:
Ratio of time to compute gradient and time to compute function.
Warning: results are very approximate! See here for more context.

┌────────────────────────────┬──────────┬──────────┬─────────────┬─────────┬─────────────┬────────┐
│                      Label │   Primal │ Mooncake │ MooncakeFwd │  Zygote │ ReverseDiff │ Enzyme │
│                     String │   String │   String │      String │  String │      String │ String │
├────────────────────────────┼──────────┼──────────┼─────────────┼─────────┼─────────────┼────────┤
│                   sum_1000 │ 180.0 ns │     1.62 │        1.72 │   0.667 │        3.28 │   6.23 │
│                  _sum_1000 │ 971.0 ns │      6.8 │        1.01 │  1630.0 │        41.6 │   1.06 │
│               sum_sin_1000 │  7.23 μs │     3.51 │        1.33 │    1.46 │        10.9 │   1.72 │
│              _sum_sin_1000 │  6.58 μs │     3.46 │        1.72 │   211.0 │        12.2 │   1.98 │
│                   kron_sum │ 219.0 μs │     12.8 │        3.26 │    8.21 │       363.0 │   18.6 │
│              kron_view_sum │ 301.0 μs │     10.7 │        4.92 │    23.7 │       308.0 │   11.7 │
│      naive_map_sin_cos_exp │  2.39 μs │     2.99 │        1.49 │ missing │         7.5 │   2.08 │
│            map_sin_cos_exp │  2.28 μs │      3.7 │        1.68 │    1.56 │         6.7 │   2.69 │
│      broadcast_sin_cos_exp │  2.34 μs │     3.15 │        1.67 │    4.19 │        1.46 │   2.13 │
│                 simple_mlp │ 375.0 μs │     4.51 │        2.52 │     1.4 │        8.24 │   2.77 │
│                     gp_lml │ 389.0 μs │     5.61 │        1.66 │    4.69 │     missing │   3.25 │
│ turing_broadcast_benchmark │  1.66 ms │     6.94 │        3.58 │ missing │        43.5 │   2.36 │
│         large_single_block │ 501.0 ns │     7.24 │        1.72 │  3930.0 │        26.7 │   1.72 │
└────────────────────────────┴──────────┴──────────┴─────────────┴─────────┴─────────────┴────────┘

SkipConnection(Dense(2 => 2), vcat),
randn(Float32, 2, 3),
"SkipConnection(Dense(2 => 2), vcat)",
), # vcat tangent mixes CPU/GPU: Illegal conversion of CUDA.DeviceMemory to Ptr{Float32}

@yebai yebai Jun 23, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The rules for vcat/hcat/cat on CuArrays are left unimplemented on purpose — if the underlying issue has been fixed, let's investigate what changed.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So I tested further by disabling the rules, the three functions being diffed fail as the following places:

  • vcat -> Base._typed_vcat -> CUDAKernels.##_#6 -> kernel_compile -> cufunction (contains Base.@lock (try/finally)
  • hcat -> Base._typed_hcat -> CUDAKernels.##_#6 -> kernel_compile -> cufunction
  • cat -> Base._copy_or_fill! -> CUDAKernels.##_#6 -> kernel_compile -> cufunction

The root cause is Base.@lock inside cufunction (used for thread-safe kernel caching), which Mooncake can't handle in reverse mode due to the try/finally block. I tried adding a @zero_derivative rule for cufunction (to skip past the @lock), and then prepare_cuda_state (skip cuCtxGetCurrent ccall inside). After both, CUDAKernels.##_#6 itself then hits cuOccupancyMaxPotentialBlockSize (raw ccall). (this continues)

The explicit rules at the vcat/hcat/cat level might be the right intervention point. But in case Im seeing something incorrectly, happy to be corrected to the right approach ahead.

@AstitvaAggarwal AstitvaAggarwal Jun 24, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for clarity, I'm of the understanding that the underlying issue was handling the relevant CUDA ccalls along these paths. Is it preferred to mark all these calls as non differentiable/with their respective rules?

@codecov

codecov Bot commented Jun 23, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 89.51613% with 13 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
ext/MooncakeCUDAExt/MooncakeCUDAExt.jl 89.51% 13 Missing ⚠️

📢 Thoughts on this report? Let us know!

@AstitvaAggarwal AstitvaAggarwal changed the title GPU rules for vcat, cat, hcat, permutedims and NNLib.batchnorm GPU rules for vcat, cat, hcat, permutedims. Jun 24, 2026
Comment thread ext/MooncakeCUDAExt/MooncakeCUDAExt.jl Outdated
_throw_mixed_cat_error($_fn)
end
@eval function rrule!!(
::CoDual{typeof($_fn)}, ::CoDual{<:CuMaybeComplexArray}, ::CoDual{<:AbstractArray}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this only cover the two argument case

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

Comment on lines +1436 to +1453
function rrule!!(
::CoDual{typeof(Core.kwcall)},
kw::CoDual{<:NamedTuple},
::CoDual{typeof(cat)},
args::CoDual{<:CuMaybeComplexArray}...,
)
pkw = primal(kw)
dim = pkw.dims
primals = map(primal, args)
fdatas = map(tangent, args)
out = cat(primals...; pkw...)
dy_out = zero(out)
pb!!(::NoRData) = (
_cu_concat_pb!(fdatas, dy_out, dim);
(NoRData(), NoRData(), NoRData(), map(_ -> NoRData(), args)...)
)
return CoDual(out, dy_out), pb!!
end

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cat's dim argument could take Tuple and Val, this assumes dims to be a single integer?

Comment thread ext/MooncakeCUDAExt/MooncakeCUDAExt.jl Outdated
end
end

@is_primitive(MinimalCtx, Tuple{typeof(vcat),Vararg{CuMaybeComplexArray}})

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

old error message functions would match Numbers, consider add them back for better error message?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

Comment thread ext/MooncakeCUDAExt/MooncakeCUDAExt.jl Outdated
# Vararg{CuMaybeComplexArray} rules above, so pure-GPU calls still dispatch to those rules.
# For mixed GPU/CPU calls _throw_if_any_cu detects the CuArray at any position and throws.
# For pure-CPU calls the rules compute correct pullbacks using the same _cu_concat_pb! helper.
@is_primitive(MinimalCtx, Tuple{typeof(vcat),Vararg{AbstractArray}})

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure AbstractArray is a good idea: could you verify view, adjoints etc. not breaking on CPU with the extension loaded?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR is still WIP, will request a review when ready

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changed the PR to draft as well for this, I think this needs more thought. sorry for the delay

@AstitvaAggarwal AstitvaAggarwal marked this pull request as draft June 30, 2026 11:31
AstitvaAggarwal and others added 9 commits June 30, 2026 14:26
  - Restore the mixed CPU/GPU device guard (deleted in b26d1a0), this time
    only intercepting genuinely mixed calls, so pure-CPU/pure-GPU are unaffected.
  - Support cat(...; dims=(1,2)) (block-diagonal), not just Integer/Val.
  - Support Adjoint/Transpose/SubArray wrapping a CuArray.
  - Support Integer/Bool-eltype CuArrays.
  - Add matching tests.
@AstitvaAggarwal AstitvaAggarwal marked this pull request as ready for review July 7, 2026 09:35
@AstitvaAggarwal AstitvaAggarwal requested a review from sunxd3 July 7, 2026 09:35
…A is loaded

  - Replace @generated _is_primitive with a plain, defensive method (handles
    Vararg/UnionAll call-site types safely); widen it to also cover GPU+scalar
    mixing at any arity, fixing the N-arg regression and dropping ~18 now-dead
    methods.
  - Exclude Float16 from CuMaybeWrappedArray's SubArray branch (arrayify
    doesn't support it); reword the error message; fix a zero-arg edge case.
  - Restore deleted flux.jl comments; add a regression test; bump version.

@sunxd3 sunxd3 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work, we are really close.

Two smaller items left worth fixing:

1. CuMaybeWrappedArray declares ComplexF16 wrappers the rule can't handle

ext/MooncakeCUDAExt/MooncakeCUDAExt.jl:127-131

The Adjoint/Transpose arms use CuFloatOrComplex, which includes Complex{Float16}:

Adjoint{<:CuFloatOrComplex,<:CuMaybeComplexArray},
Transpose{<:CuFloatOrComplex,<:CuMaybeComplexArray},

But these rules canonicalise through arrayify, whose Adjoint/Transpose methods
(src/rules/blas.jl) are bounded Union{IEEEFloat,BlasFloat}, and ComplexF16 is not in
that set. So Adjoint{ComplexF16}/Transpose{ComplexF16} — and bare CuArray{ComplexF16}
(CuMaybeComplexArray on L128) — are declared primitives but then fail inside the rule
(MethodError from arrayify, or a kernel exception), instead of falling through to the
interpreter like other unsupported cases.

Fix: bound the complex arms to ComplexF32/ComplexF64 so the primitive declaration matches
what the rule can actually canonicalise, mirroring the BlasFloat bound already used on the
SubArray arm (L131).

Also fix the comment at L119-126, which is inaccurate on two points:

  • The example vcat(view(cu_f16_vec, 1:4), y) — a 1-D contiguous view of a CuArray
    returns a CuArray, not a SubArray, so it is unaffected by the SubArray bound and
    works. Use a strided/2-D view (e.g. view(cu_f16_mat, 1:2, :)) to illustrate the point.
  • "excludes Float16/Complex{Float16}, unlike its Adjoint/Transpose/Symmetric siblings" —
    the siblings do handle Float16 but not Complex{Float16} (same Union{IEEEFloat, BlasFloat} bound). Tightening the complex arms above makes this consistent.

2. The f_view test does not exercise what it claims

test/ext/cuda/cuda.jl:1325-1327

# Float16 SubArrays are excluded from CuMaybeWrappedArray (Finding 3);
# confirm this now falls through cleanly instead of erroring.
f_view(x, y) = sum(vcat(view(x, 1:2), y))

view(x16, 1:2) on a 1-D contiguous CuArray returns a CuArray, not a SubArray, so this
hits the normal pure-GPU vcat rule and duplicates the "Bare Float16" test just above it
(L1314). It never touches the SubArray-exclusion path the comment describes.

Fix: to test the exclusion, use a strided view that stays a SubArray
(e.g. view(cu_f16_mat, 1:2, :)) and assert it errors (@test_throws); otherwise remove
the test as redundant.

@AstitvaAggarwal AstitvaAggarwal requested a review from sunxd3 July 9, 2026 09:07

@sunxd3 sunxd3 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LG, thanks!

@sunxd3

sunxd3 commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator

Don't release yet until we look into the CI failures
c.f. TuringLang/Bijectors.jl#482

@sunxd3 sunxd3 merged commit 71b34d3 into main Jul 9, 2026
94 of 97 checks passed
@sunxd3 sunxd3 deleted the cuda-gpu-rules branch July 9, 2026 10:12
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.

3 participants