Neural Network Primitives (BNNS)

AppleAccelerate wraps a small, numerically verified subset of Apple's BNNS (Basic Neural Network Subroutines) library — the primitives that are broadly useful and that can be checked against a plain-Julia reference. These wrappers are Float32-centric, matching BNNS's native precision for inference.

Namespace

Like the rest of the package, these functions are not exported. Access them via the AppleAccelerate. prefix (e.g. AppleAccelerate.bnns_matmul).

Descriptors

BNNSArray builds a GC-safe BNNSNDArrayDescriptor view of a dense, contiguous Julia array. It keeps the backing array alive and reports a column-major-friendly BNNS layout (1D vectors as BNNSDataLayoutVector, 2D matrices as BNNSDataLayoutColumnMajorMatrix). Most users do not need to construct one directly — the matmul and activation helpers build descriptors internally — but it is the building block if you reach into the raw layer.

Matrix multiply

bnns_matmul computes alpha * (A * B) via BNNSMatMul. A is m×k, B is k×n, and the result is m×n. The in-place variant bnns_matmul! writes into a preallocated C.

A = rand(Float32, 4, 3)
B = rand(Float32, 3, 5)

C = AppleAccelerate.bnns_matmul(A, B)          # == A * B
C2 = AppleAccelerate.bnns_matmul(A, B; alpha = 2.0f0)  # == 2 .* (A * B)

@assert C  ≈ A * B
@assert C2 ≈ 2 .* (A * B)

Activations

bnns_activation applies a pointwise activation function to every element of a Float32 array using the BNNS activation-layer filter API, returning a new array; bnns_activation! writes into a preallocated output of the same shape. Supported functions are :identity, :relu, :sigmoid, :tanh, and :abs.

x = Float32[-2, -1, 0, 1, 2]

AppleAccelerate.bnns_activation(:relu, x)     # max.(x, 0)
AppleAccelerate.bnns_activation(:abs, x)      # abs.(x)
y = AppleAccelerate.bnns_activation(:sigmoid, x)
@assert y ≈ 1f0 ./ (1f0 .+ exp.(-x))
AppleAccelerate.BNNSArrayType
BNNSArray(A::AbstractArray)

A GC-safe BNNSNDArrayDescriptor view of a Julia array A, suitable for passing to BNNS routines via Ref. The wrapper keeps a reference to the backing array so it is not collected while the descriptor is alive; pass the underlying descriptor with Base.cconvert/Ref only inside a GC.@preserve block guarding A.

BNNS descriptors are layout aware. Julia stores arrays in column-major order, so this constructor reports the array using a column-major-friendly BNNS layout:

  • 1D Vector -> BNNSDataLayoutVector
  • 2D Matrix -> BNNSDataLayoutColumnMajorMatrix (BNNS size = (rows, cols)).

Only Float32 (and Int32) dense, contiguous arrays are supported here; other element types or strided/transposed arrays should use the raw LibAccelerate layer directly.

See also bnns_matmul, bnns_activation.

source
AppleAccelerate.bnns_matmulFunction
bnns_matmul(A::Matrix{Float32}, B::Matrix{Float32}; alpha = 1.0f0) -> Matrix{Float32}

Compute alpha * (A * B) using BNNS (BNNSMatMul). A is m×k, B is k×n, and the result is m×n. Equivalent to alpha .* (A * B).

See also bnns_matmul!.

source
AppleAccelerate.bnns_matmul!Function
bnns_matmul!(C, A, B; alpha = 1.0f0) -> C

In-place matrix multiply: C .= alpha .* (A * B). All arguments are Float32 matrices with conforming dimensions. Returns C.

source
AppleAccelerate.bnns_activationFunction
bnns_activation(f::Symbol, X::AbstractArray{Float32}; alpha=0.0f0, beta=0.0f0) -> AbstractArray{Float32}

Apply a pointwise activation function f to every element of X using the BNNS activation-layer filter API, returning a new array. Supported f:

  • :identity
  • :relu — rectified linear
  • :sigmoid
  • :tanh
  • :abs

alpha/beta are passed to BNNS for activations that take parameters (e.g. leaky variants); they are ignored by the activations listed above.

See also bnns_activation!.

source
AppleAccelerate.bnns_activation!Function
bnns_activation!(f::Symbol, Y::AbstractArray{Float32}, X::AbstractArray{Float32}; alpha=0.0f0, beta=0.0f0) -> Y

In-place pointwise activation: Y .= f.(X). See bnns_activation for the list of supported f. X and Y must have the same shape. Returns Y.

source

Using BNNS with NNlib

AppleAccelerate defines no methods on NNlib's functions — loading it never changes what batched_mul! or an activation function does. To use BNNS for a batched Float32 matmul, call bnns_matmul! per slice explicitly:

A = rand(Float32, 4, 3, 8)   # 8 batched 4x3 matrices
B = rand(Float32, 3, 5, 8)   # 8 batched 3x5 matrices
C = Array{Float32}(undef, 4, 5, 8)

for i in axes(C, 3)
    AppleAccelerate.bnns_matmul!(view(C, :, :, i), view(A, :, :, i), view(B, :, :, i))
end
@assert C[:, :, 1] ≈ A[:, :, 1] * B[:, :, 1]

For activations, bnns_activation takes a Symbol (:relu, :sigmoid, :tanh, :abs, :identity) rather than an NNlib function object, so no NNlib dependency is involved:

x = randn(Float32, 6)
@assert AppleAccelerate.bnns_activation(:relu, x) ≈ max.(x, 0f0)

What's left to the raw layer

BNNS is large (~139 C functions, 110+ structs). Deliberately not given idiomatic wrappers — use the raw AppleAccelerate.LibAccelerate layer directly if you need them (see Architecture) — are the BNNS graph-compiler API (bnns_graph.h), convolution / pooling / normalization / LSTM / attention layers, quantization, and training. These require substantial stateful plumbing or are hard to verify generically, so they fall outside this idiomatic core.