Supporse, in a tight loop, I want to matrix MxP with PxN to get MxN (in rowNum x colNum convention). The call to BNNSNDArrayDescriptor.init(data:shape:) always fails if I (correctly) specify all the strides.
/*
The simplest and fastest matmul(A,B) is when both A and B are in inner-
dimension-major order - with the size P dimension stored continuous.
i.e. A*(B^T) when A,B are stored in C-like row-major order,
or,
first-dimension-major order, but first index is the row index, like BLAS
*/
import Accelerate
// Different sizes, so incorrectly-ordered sizes/strides/indices generate error
let M: Int = 16
let N: Int = 8
let P: Int = 32
let sizeA = BNNS.Shape.matrixFirstMajor(M, P, stride: (0, 0))
// OK: stride:(1, 0)
// bad: stride:(0, 1) -- Matmul failed, because 1 is given to the wrong dimension: should be at least P
// stride:(1, P) -- Failed to create BNNSNDArrayDescriptor???
let sizeB = BNNS.Shape.matrixFirstMajor(N, P, stride: (0, 0))
// OK: stride:(1, 0) bad: stride:(0, 1), stride:(1, P)
let sizeC = BNNS.Shape.matrixFirstMajor(M, N, stride: (0, 0))
// OK: stride:(1, 0) bad: stride:(0, 1), stride:(1, N)
let Mat1 = UnsafeMutableRawBufferPointer
.allocate(byteCount: (M+N)*P*4, alignment: 128)
.initializeMemory(as: Float.self, repeating: 1.234)
let Mat2 = UnsafeMutableRawBufferPointer
.allocate(byteCount: M*N*4, alignment: 128)
.initializeMemory(as: Float.self, repeating: 0.0)
guard let descA = BNNSNDArrayDescriptor(
data: Mat1.extracting(0..<M*P),
shape: sizeA),
let descB = BNNSNDArrayDescriptor(
data: Mat1.extracting(M*P..<(M+N)*P),
shape: sizeB),
let descC = BNNSNDArrayDescriptor(
data: Mat2,
shape: sizeC)
else { print("Failed to create BNNSNDArrayDescriptor."); exit(1) }
print(descA)
// workspaceSize = BNNS.matrixMultiplicationWorkspaceSize(...)
// withUnsafeTemporaryAllocation(byteCount: workspaceSize, alignment: ...) { wksp in
var iter = 0
while iter < 1000{
do {
try BNNS.applyMatrixMultiplication(
inputA: descA,
transposed: false,
inputB: descB,
transposed: true,
output: descC,
alpha: 1.0,
workspace: /*wksp*/ nil)
} catch {
// Probably got the dimensions backwards, so two matrices are not multiplicable
print("Matmul failed: \(error)")
break
}
iter += 1
}
I guess in the Swift overlay, the verifying initializer checked a wrong arrangement of stride[k] >= size[k-1] * stride[k-1]. These RowMajor FirstMajor, etc. with all different size and index orderings, are surely confusing. bnns_constants.h:1394 gives the right description.
PS: Please don't educate me about deprecation of BNNS. context.executeFunction(arguments: [Aptr, Bptr, Cptr]) is nearly 5x slower for this one-matmul-in-a-tight-loop case, and there's no way to reuse the underlying graph in another thread yet, not withBNNSGraph.Builder. The (im)practical alternative is calling sgemm() that is even more verbose than this.