Skip to content

apply within kernels and Reactant #1736

Description

@maximilian-gelbrecht

I would like to apply/use NNs within Reactant/GPU kernels. Currently this doesn't seem to work. Searching a bit through the repo, I found an old documentation on this https://github.com/LuxDL/Lux.jl/blob/10ea255face7fdd6b198cf9e2e641a8414afe037/docs/src/manual/nn_inside_gpu_kernels.md

Is this not supported anymore? Or did I do something wrong.

Below the example I tried (with LLM help)

using Reactant, CUDA, Lux, Random, StaticArrays
using KernelAbstractions: @kernel, @index, get_backend
import Lux.MLDataDevices as MLDD

const dev = MLDD.reactant_device()

# A tiny MLP (1 → 4 → 1); move parameters and data to the device the standard Lux way.
mlp = Lux.Chain(Lux.Dense(1 => 4, tanh), Lux.Dense(4 => 1))
ps, st = Lux.setup(Random.Xoshiro(0), mlp) |> dev
x = collect(Float32, -3:0.5:3) |> dev
out = zero(x)

# ---------------------------------------------------------------------------------------------
# Control: the MLP's forward pass written out by hand (from the Lux parameters) compiles and runs.
# ---------------------------------------------------------------------------------------------
@kernel function manual_kernel!(out, @Const(x), @Const(W1), @Const(b1), @Const(W2), @Const(b2))
    i = @index(Global)
    xi = @inbounds x[i]
    acc = @inbounds b2[1]
    for h in 1:length(b1)
        acc += @inbounds W2[1, h] * tanh(W1[h, 1] * xi + b1[h])
    end
    @inbounds out[i] = acc
end
run_manual!(out, x, W1, b1, W2, b2) =
    (manual_kernel!(get_backend(out))(out, x, W1, b1, W2, b2; ndrange = length(out)); nothing)

W1, b1 = ps.layer_1.weight, ps.layer_1.bias
W2, b2 = ps.layer_2.weight, ps.layer_2.bias
compiled_manual! = @compile raise = true run_manual!(out, x, W1, b1, W2, b2)
compiled_manual!(out, x, W1, b1, W2, b2)
@info "control OK: hand-written forward compiled and ran" result = Array(out)

# ---------------------------------------------------------------------------------------------
# The failure: the same MLP applied with `Lux.apply` inside the kernel fails to compile.
# ---------------------------------------------------------------------------------------------
@kernel function apply_kernel!(out, @Const(x), model, ps, st)
    i = @index(Global)
    y, _ = Lux.apply(model, SA[x[i]], ps, st)
    @inbounds out[i] = y[1]
end
run_apply!(out, x, model, ps, st) =
    (apply_kernel!(get_backend(out))(out, x, model, ps, st; ndrange = length(out)); nothing)

@info "compiling the Lux.apply kernel (expected to fail with an InvalidIRError) ..."
@compile raise = true run_apply!(out, x, mlp, ps, st)

This gives a lot of InvalidIRError with Reason: unsupported call to a lazy-initialized function (call to utf8proc_isupper) from different parts of the code.

About the scope/context of this: I am interested in hybrid model combining physics with ML. One kind would just replace a point wise / column-wise parameterisation with an MLPs. The easiest way to integrate this within our models would be to just call them within the existing kernels. This approach is really limited to simple NN architectures like MLPs or maybe simple RNNs as well.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions