You can but I definitly won’t recommand for begginers :
The neural network is compiled into a native library, with a custom ReLU activation passed as a Fortran function pointer.
One small hurdle: KernelAbstractions 0.10 (PoCL backend) caused unresolved GPU symbols during linking. Downgrading to 0.9 fixed it. And if you wonder you won’t be able to use gpu’s yet.
the files :
main.f90 main.jl Manifest.toml Project.toml
environement:
[587475ba] Flux v0.16.15
⌃ [63c18a36] KernelAbstractions v0.9.45 ⚲
using Flux
struct CActivation <: Function
ptr::Ptr{Cvoid}
end
@inline (f::CActivation)(x::Float64) =
ccall(f.ptr, Cdouble, (Cdouble,), x)
function build_nn(ni::Int64, no::Int64, nh::Int64, act::Ptr{Cvoid})
Flux.f64(Flux.Chain(
Flux.Dense(ni => nh, CActivation(act)),
Flux.Dense(nh => no)
))
end
const NNType = typeof(build_nn(1, 1, 1, C_NULL))
const NETWORKS = Dict{Int64, NNType}()
const NEXT_ID = Ref{Int64}(0)
Base.@ccallable function make_nn(
ni::Int64, no::Int64, nh::Int64,
act::Ptr{Cvoid}
)::Int64
(ni > 0 && no > 0 && nh > 0 && act != C_NULL) ||
return 0
nn = build_nn(ni, no, nh, act)
id = (NEXT_ID[] += 1)
NETWORKS[id] = nn
return id
end
Base.@ccallable function run_nn(
id::Int64,
px::Ptr{Float64},
py::Ptr{Float64},
batchsize::Int64
)::Cint
haskey(NETWORKS, id) || return -1
(px != C_NULL && py != C_NULL && batchsize >= 0) ||
return -1
nn = NETWORKS[id]
ni = size(nn.layers[1].weight, 2)
no = size(nn.layers[2].weight, 1)
x = unsafe_wrap(Matrix{Float64}, px, (ni, batchsize))
y = unsafe_wrap(Matrix{Float64}, py, (no, batchsize))
copyto!(y, nn(x))
return 0
end
Base.@ccallable function free_nn(id::Int64)::Cvoid
delete!(NETWORKS, id)
return nothing
end
fortran :
program test_nn
use iso_c_binding
implicit none
interface
function make_nn(ni, no, nh, act) &
bind(C, name="make_nn") result(id)
import :: c_int64_t, c_funptr
integer(c_int64_t), value :: ni, no, nh
type(c_funptr), value :: act
integer(c_int64_t) :: id
end function
function run_nn(id, px, py, batchsize) &
bind(C, name="run_nn") result(status)
import :: c_int64_t, c_ptr, c_int
integer(c_int64_t), value :: id, batchsize
type(c_ptr), value :: px, py
integer(c_int) :: status
end function
subroutine free_nn(id) bind(C, name="free_nn")
import :: c_int64_t
integer(c_int64_t), value :: id
end subroutine
end interface
integer(c_int64_t), parameter :: ni = 2, no = 1
integer(c_int64_t), parameter :: nh = 4, batch = 3
real(c_double), target :: x(ni,batch), y(no,batch)
integer(c_int64_t) :: id
integer(c_int) :: status
x(:,1) = [1.0d0, 2.0d0]
x(:,2) = [3.0d0, 4.0d0]
x(:,3) = [5.0d0, 6.0d0]
id = make_nn(ni, no, nh, c_funloc(relu))
if (id == 0) error stop "make_nn failed"
status = run_nn(id, c_loc(x), c_loc(y), batch)
if (status /= 0) error stop "run_nn failed"
print *, "Inputs:"
print *, x
print *, "Outputs:"
print *, y
call free_nn(id)
contains
function relu(v) bind(C) result(res)
real(c_double), value :: v
real(c_double) :: res
res = max(v, 0.0d0)
end function
end program test_nn
juliac --output-lib libcarre --project=. main.jl
then
gfortran main.f90 -L. -lcarre -o test_nn
finally
./test_nn
Inputs:
1.0000000000000000 2.0000000000000000 3.0000000000000000 4.0000000000000000 5.0000000000000000 6.0000000000000000
Outputs:
-1.0558196649046820 -1.9435691153369969 -2.8313185657693118
edit : oh my thats a 4 years revive of the thread lol.