Section 4/171 menit

4. MLMultiArray dan Tipe Data Spesialis

4. MLMultiArray dan Tipe Data Spesialis

MLMultiArray adalah kontainer tensor n-dimensi untuk input/output yang bukan gambar atau string. Penggunaannya yang efisien sangat krusial untuk performa.

Membuat MLMultiArray

swift
import CoreML

// MLMultiArray 1D: vektor 512 elemen
let embedding = try MLMultiArray(shape: [512], dataType: .float32)

// MLMultiArray 2D: batch 8 sequence, panjang 128
let tokenIDs = try MLMultiArray(shape: [8, 128], dataType: .int32)

// MLMultiArray 3D: batch 4, 3 channel, 224x224
let imageTensor = try MLMultiArray(shape: [4, 3, 224, 224], dataType: .float16)

// Akses elemen via subscript (lambat untuk loop besar)
embedding[0] = 0.5
embedding[[0] as [NSNumber]] = 0.5  // ekuivalen

// Akses elemen via pointer (cepat untuk batch)
let pointer = UnsafeMutablePointer<Float>(ObjCBool(false).map { _ in embedding.dataPointer.assumingMemoryBound(to: Float.self) })

Zero-Copy dengan UnsafeMutablePointer

Untuk performa maksimal, langsung akses memory pointer MLMultiArray tanpa konversi:

swift
// Mengisi MLMultiArray dari array Swift tanpa copy
func fillMultiArray(_ array: MLMultiArray, from source: [Float]) {
    assert(source.count == array.count)
    let pointer = array.dataPointer.assumingMemoryBound(to: Float.self)
    source.withUnsafeBufferPointer { buffer in
        pointer.initialize(from: buffer.baseAddress!, count: buffer.count)
    }
}

// Membaca MLMultiArray ke array Swift tanpa copy
func readMultiArray(_ array: MLMultiArray) -> [Float] {
    let pointer = array.dataPointer.assumingMemoryBound(to: Float.self)
    return Array(UnsafeBufferPointer(start: pointer, count: array.count))
}

// Operasi in-place pada MLMultiArray dengan Accelerate
import Accelerate

func normalizeMultiArray(_ array: MLMultiArray, mean: Float, std: Float) {
    let count = array.count
    let pointer = array.dataPointer.assumingMemoryBound(to: Float.self)

    // Kurangi mean
    var negativeMean = -mean
    vDSP_vsadd(pointer, 1, &negativeMean, pointer, 1, vDSP_Length(count))

    // Bagi dengan std
    var reciprocalStd = 1.0 / std
    vDSP_vsmul(pointer, 1, &reciprocalStd, pointer, 1, vDSP_Length(count))
}

Strides dan Non-Contiguous Memory

MLMultiArray menyimpan data dalam row-major order. Untuk operasi yang perlu akses per-channel atau per-batch, strides sangat penting:

swift
// Inspect strides (bytes per elemen di setiap dimensi)
let array = try MLMultiArray(shape: [2, 3, 224, 224], dataType: .float32)
print(array.strides)  // [3*224*224, 224*224, 224, 1] × 4 bytes

// Manual indexing dengan strides untuk akses per-channel
func getPixelValue(from array: MLMultiArray, batch: Int, channel: Int, row: Int, col: Int) -> Float {
    let strides = array.strides.map { $0.intValue }
    let offset = batch * strides[0] + channel * strides[1] + row * strides[2] + col * strides[3]
    let pointer = array.dataPointer.assumingMemoryBound(to: Float.self)
    return pointer[offset / MemoryLayout<Float>.stride]
}

MLShapedArray (iOS 15+)

MLShapedArray adalah wrapper yang lebih ergonomis dan type-safe di atas MLMultiArray:

swift
import CoreML

// MLShapedArray — type-safe, Sendable, bisa dipakai di Swift Concurrency
let shaped = MLShapedArray<Float>(shape: [1, 512], scalars: Array(repeating: 0, count: 512))

// Akses elemen
print(shaped[[0, 0]])  // elemen pertama

// Slice
let slice = shaped[0...0, 0...255]  // sub-array tanpa copy

// Konversi ke MLMultiArray untuk API lama
let multiArray = try MLMultiArray(shaped)

// Dari MLMultiArray ke MLShapedArray
let backToShaped = MLShapedArray<Float>(multiArray)

// Buat dari flat array
let fromFlat = MLShapedArray<Float>(scalars: [1.0, 2.0, 3.0, 4.0], shape: [2, 2])

Tipe Data dan Presisi

swift
// Float32: presisi penuh, kompatibel paling luas
let f32 = try MLMultiArray(shape: [512], dataType: .float32)

// Float16: lebih hemat memori, lebih cepat di ANE/GPU
// Core ML otomatis mengkonversi internal jika model membutuhkan
let f16 = try MLMultiArray(shape: [512], dataType: .float16)

// Int32: untuk token IDs, indeks, label integer
let int32 = try MLMultiArray(shape: [128], dataType: .int32)

// Double: jarang dibutuhkan, lebih lambat di hardware ML
let f64 = try MLMultiArray(shape: [512], dataType: .double)

// Tips: model quantized ke INT8 tetap menerima Float input —
// Core ML yang handle konversi internal