BNNSNDArrayDescriptor rejects manually calculated strides?

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.

Answered by Engineer in 915489022

This is an issue we're aware of, but we suggest that as a workaround you call the C API initialiser instead (or use the solution you posted above).

Even though we're aware of this issue, we still encourage you to open a bug report, and post the FB number here once you do.

The specific info you include in your bug report might help our investigation, and filing the bug report allows you to get notified when it is resolved.

Also, it's probably a Swift overlay thing, because I can set the stride after the fact, and run matmul on it fine. It's just the initializer that hates the stride.

let sizeA = BNNS.Shape.matrixFirstMajor(M, P, stride: (1, 0))
...
guard var descA = BNNSNDArrayDescriptor(
                  data: Mat1.extracting(0..<M*P),
                  shape: sizeA)
...
descA.stride.1 = P
...
try BNNS.applyMatrixMultiplication(
                inputA: descA,
                ...)

This is an issue we're aware of, but we suggest that as a workaround you call the C API initialiser instead (or use the solution you posted above).

Even though we're aware of this issue, we still encourage you to open a bug report, and post the FB number here once you do.

The specific info you include in your bug report might help our investigation, and filing the bug report allows you to get notified when it is resolved.