CUDA: unsupported dynamic function invocation for closure

I have a function f that takes a scalar parameter and a vector. It works well for CuVector:

xs = CuVector{Float64}(rand(10))
f.(param, xs)  # works well

However, if I create a closure out of f such that it holds param, then the closure generates an error:

g(x) = f(param, x)
g.(xs)  # error

The error message looks like

ERROR: InvalidIRError: compiling MethodInstance for (::GPUArrays.var"#34#36")(::CUDA.CuKernelContext, ::CuDeviceVector{…}, ::Base.Broadcast.Broadcasted{…}, ::Int64) resulted in invalid LLVM IR
Reason: unsupported dynamic function invocation (call to index)
Stacktrace:
 [1] #7
   @ ./REPL[11]:1
 [2] _broadcast_getindex_evalf
   @ ./broadcast.jl:709
 [3] _broadcast_getindex
   @ ./broadcast.jl:682
 [4] getindex
   @ ./broadcast.jl:636
 [5] #34
   @ ~/.julia/packages/GPUArrays/HjWFN/src/host/broadcast.jl:59
Hint: catch this exception as `err` and call `code_typed(err; interactive = true)` to introspect the erronous code with Cthulhu.jl
Stacktrace:
...

Is there a way to create a closure such that it works well with CuArray?

Closures are supported:

julia> using Metal

julia> f(a,b) = a+b
f (generic function with 1 method)

julia> xs = mtl([1])
1-element MtlVector{Int64, Private}:
 1

julia> const param = 2
2

julia> f.(param, xs)
1-element MtlVector{Int64, Private}:
 3

julia> g(x) = f(param, x)
g (generic function with 1 method)

julia> g.(xs)
1-element MtlVector{Int64, Private}:
 3

You are probably dealing with a badly typed param (e.g. non-const global or Core.Box cf. performance of captured variables in closures · Issue #15276 · JuliaLang/julia · GitHub).

1 Like

Yes, my param is non-const global. What should I do in that case? I tried to define g(x) using the let block as

g(x) = let param=param
    f(param, x)
end

but had no success.

Then that’s not a closure at all, it’s not capturing a local variable (capturing meaning that the local variable can live on despite the local scope ending) but accessing a global variable that persists unconditionally.

g still accesses the global param here, and f just takes a local param as an input.

const g = let p=param
  g1(x) = f(p, x)
end

This puts a closure in a local scope that captures a local variable, and the returned closure is assigned to a const global variable.

If you don’t want to deal with scoping, you could also make a struct to hold values for callable instances:

struct G{P}<:Function  p::P  end
(_g::G)(x) = f(_g.p, x)

const g = G(param)