mirror of
https://github.com/hanzoai/ANE.git
synced 2026-10-30 18:53:51 +00:00
The original probes used a 64-byte zero header for weight blobs, which the M1 ANE compiler silently rejects. The correct format requires a 128-byte header with magic bytes 0xEFBEADDE at offset 64, data size at offset 72, and data offset (128) at offset 80. With the correct blob format, _ANEInMemoryModel works on ALL Apple Silicon (M1+), not just M4. Conv ops compile and run successfully. The dynamic pipeline's matmul+slice_by_size pattern still fails on M1-M3 (different hardware limitation). M1 Max results: train_large runs at 206ms/step, 0.45 TFLOPS ANE. Changes: - ane_universal.h: Fix detection probe blob format, update comments - ane_universal_test.m: Fix test blob format, M1 now uses InMemory path - ane_m1_multiweight.m: New test validating multi-weight compilation - .gitignore: Add new binaries, training data files
475 lines
20 KiB
Objective-C
475 lines
20 KiB
Objective-C
// ane_universal.h — Universal ANE runtime for M1→M4+ Apple Silicon
|
|
// Strategy:
|
|
// All chips: _ANEInMemoryModel with proper ANE blob format (128-byte header)
|
|
// Fallback: CoreML compile → _ANEModel (slower, ~60-280ms)
|
|
// Both: same IOSurface I/O for evaluation
|
|
//
|
|
// Key finding: The private API works on ALL Apple Silicon (M1+) when weight
|
|
// blobs use the correct format: 128-byte header with magic 0xEFBEADDE at
|
|
// offset 64, data size at 72, data offset (128) at 80. Conv ops with separate
|
|
// weight files work across all chips. Matmul/slice_by_size fails on M1-M3.
|
|
//
|
|
// Usage:
|
|
// ane_universal_init()
|
|
// UANEKernel *k = ane_universal_compile(milText, weightData, ...)
|
|
// ane_universal_write_input(k, 0, data, bytes)
|
|
// ane_universal_eval(k)
|
|
// ane_universal_read_output(k, 0, data, bytes)
|
|
// ane_universal_free(k)
|
|
#pragma once
|
|
#import <Foundation/Foundation.h>
|
|
#import <CoreML/CoreML.h>
|
|
#import <objc/runtime.h>
|
|
#import <objc/message.h>
|
|
#import <dlfcn.h>
|
|
#import <IOSurface/IOSurface.h>
|
|
|
|
typedef enum {
|
|
ANE_BACKEND_NONE = 0,
|
|
ANE_BACKEND_INMEM, // M4+ private API (_ANEInMemoryModel)
|
|
ANE_BACKEND_COREML, // M1-M3 CoreML fallback
|
|
} ANEBackendType;
|
|
|
|
typedef struct {
|
|
ANEBackendType backend;
|
|
|
|
// Shared I/O
|
|
IOSurfaceRef *ioInputs;
|
|
IOSurfaceRef *ioOutputs;
|
|
int nInputs, nOutputs;
|
|
size_t *inputBytes;
|
|
size_t *outputBytes;
|
|
|
|
// InMem backend (M4+)
|
|
id inMemModel;
|
|
id inMemRequest;
|
|
NSString *tmpDir;
|
|
|
|
// CoreML backend (M1-M3)
|
|
MLModel *coremlModel;
|
|
NSString *coremlInputName;
|
|
NSString *coremlOutputName;
|
|
int *inputShapes; // [B, C, H, W] per input
|
|
int *outputShapes; // [B, C, H, W] per output
|
|
NSString *coremlCompiledPath;
|
|
} UANEKernel;
|
|
|
|
static Class g_UANEDesc, g_UANEInMem, g_UANEReq, g_UANEIO;
|
|
static ANEBackendType g_ane_backend = ANE_BACKEND_NONE;
|
|
static bool g_ane_universal_inited = false;
|
|
|
|
// ============================================================
|
|
// Detect which backend works on this hardware
|
|
// ============================================================
|
|
static ANEBackendType ane_detect_backend(void) {
|
|
dlopen("/System/Library/PrivateFrameworks/AppleNeuralEngine.framework/AppleNeuralEngine", RTLD_NOW);
|
|
g_UANEDesc = NSClassFromString(@"_ANEInMemoryModelDescriptor");
|
|
g_UANEInMem = NSClassFromString(@"_ANEInMemoryModel");
|
|
g_UANEReq = NSClassFromString(@"_ANERequest");
|
|
g_UANEIO = NSClassFromString(@"_ANEIOSurfaceObject");
|
|
|
|
if (!g_UANEDesc || !g_UANEInMem || !g_UANEReq || !g_UANEIO) {
|
|
fprintf(stderr, "[ANE] Private classes not found, using CoreML fallback\n");
|
|
return ANE_BACKEND_COREML;
|
|
}
|
|
|
|
// Test compile a minimal MIL program to check if private API works
|
|
// NOTE: Weight blob must use proper ANE format (128-byte header with magic)
|
|
int C = 16, S = 4;
|
|
int wsize = C * C * 2;
|
|
int total = 128 + wsize;
|
|
NSMutableData *wd = [NSMutableData dataWithLength:total];
|
|
uint8_t *wb = (uint8_t *)wd.mutableBytes;
|
|
wb[0] = 1; wb[4] = 2;
|
|
wb[64] = 0xEF; wb[65] = 0xBE; wb[66] = 0xAD; wb[67] = 0xDE; wb[68] = 1;
|
|
*(uint32_t *)(wb + 72) = wsize;
|
|
*(uint32_t *)(wb + 80) = 128;
|
|
uint16_t *wp = (uint16_t *)(wb + 128);
|
|
for (int i = 0; i < C * C; i++) wp[i] = 0x3C00;
|
|
|
|
NSString *testMil = [NSString stringWithFormat:
|
|
@"program(1.3)\n"
|
|
"[buildInfo = dict<string, string>({{\"coremlc-component-MIL\", \"3510.2.1\"}, "
|
|
"{\"coremlc-version\", \"3505.4.1\"}, {\"coremltools-component-milinternal\", \"\"}, "
|
|
"{\"coremltools-version\", \"9.0\"}})]\n"
|
|
"{\n"
|
|
" func main<ios18>(tensor<fp16, [1, %d, 1, %d]> x) {\n"
|
|
" tensor<fp16, [%d,%d,1,1]> W = const()[name=string(\"W\"), val=tensor<fp16, [%d,%d,1,1]>(BLOBFILE(path=string(\"@model_path/weights/weight.bin\"), offset=uint64(64)))];\n"
|
|
" string pt = const()[name=string(\"pt\"), val=string(\"valid\")];\n"
|
|
" tensor<int32, [2]> st = const()[name=string(\"st\"), val=tensor<int32, [2]>([1,1])];\n"
|
|
" tensor<int32, [4]> pd = const()[name=string(\"pd\"), val=tensor<int32, [4]>([0,0,0,0])];\n"
|
|
" tensor<int32, [2]> dl = const()[name=string(\"dl\"), val=tensor<int32, [2]>([1,1])];\n"
|
|
" int32 gr = const()[name=string(\"gr\"), val=int32(1)];\n"
|
|
" tensor<fp16, [1,%d,1,%d]> out = conv(dilations=dl,groups=gr,pad=pd,pad_type=pt,strides=st,weight=W,x=x)[name=string(\"out\")];\n"
|
|
" } -> (out);\n"
|
|
"}\n",
|
|
C, S, C, C, C, C, C, S];
|
|
|
|
NSData *milData = [testMil dataUsingEncoding:NSUTF8StringEncoding];
|
|
NSDictionary *wdict = @{@"@model_path/weights/weight.bin": @{@"offset": @0, @"data": wd}};
|
|
|
|
id desc = ((id(*)(Class,SEL,id,id,id))objc_msgSend)(
|
|
g_UANEDesc, @selector(modelWithMILText:weights:optionsPlist:),
|
|
milData, wdict, nil);
|
|
if (!desc) {
|
|
fprintf(stderr, "[ANE] Descriptor creation failed, using CoreML fallback\n");
|
|
return ANE_BACKEND_COREML;
|
|
}
|
|
|
|
id model = ((id(*)(Class,SEL,id))objc_msgSend)(
|
|
g_UANEInMem, @selector(inMemoryModelWithDescriptor:), desc);
|
|
if (!model) {
|
|
fprintf(stderr, "[ANE] Model creation failed, using CoreML fallback\n");
|
|
return ANE_BACKEND_COREML;
|
|
}
|
|
|
|
id hexId = ((id(*)(id,SEL))objc_msgSend)(model, @selector(hexStringIdentifier));
|
|
NSString *td = [NSTemporaryDirectory() stringByAppendingPathComponent:hexId];
|
|
NSFileManager *fm = [NSFileManager defaultManager];
|
|
[fm createDirectoryAtPath:[td stringByAppendingPathComponent:@"weights"]
|
|
withIntermediateDirectories:YES attributes:nil error:nil];
|
|
[milData writeToFile:[td stringByAppendingPathComponent:@"model.mil"] atomically:YES];
|
|
[wd writeToFile:[td stringByAppendingPathComponent:@"weights/weight.bin"] atomically:YES];
|
|
|
|
NSError *e = nil;
|
|
BOOL ok = ((BOOL(*)(id,SEL,unsigned int,id,NSError**))objc_msgSend)(
|
|
model, @selector(compileWithQoS:options:error:), 21, @{}, &e);
|
|
|
|
// Cleanup
|
|
((BOOL(*)(id,SEL,unsigned int,NSError**))objc_msgSend)(
|
|
model, @selector(unloadWithQoS:error:), 21, &e);
|
|
[fm removeItemAtPath:td error:nil];
|
|
|
|
if (ok) {
|
|
fprintf(stderr, "[ANE] In-memory compilation works → using private API\n");
|
|
return ANE_BACKEND_INMEM;
|
|
} else {
|
|
fprintf(stderr, "[ANE] In-memory compilation failed → using CoreML fallback\n");
|
|
return ANE_BACKEND_COREML;
|
|
}
|
|
}
|
|
|
|
static void ane_universal_init(void) {
|
|
if (g_ane_universal_inited) return;
|
|
g_ane_backend = ane_detect_backend();
|
|
g_ane_universal_inited = true;
|
|
}
|
|
|
|
static IOSurfaceRef ane_u_create_surface(size_t bytes) {
|
|
return IOSurfaceCreate((__bridge CFDictionaryRef)@{
|
|
(id)kIOSurfaceWidth: @(bytes),
|
|
(id)kIOSurfaceHeight: @1,
|
|
(id)kIOSurfaceBytesPerElement: @1,
|
|
(id)kIOSurfaceBytesPerRow: @(bytes),
|
|
(id)kIOSurfaceAllocSize: @(bytes),
|
|
(id)kIOSurfacePixelFormat: @0
|
|
});
|
|
}
|
|
|
|
// ============================================================
|
|
// InMem backend (M4+) — same as original ane_runtime.h
|
|
// ============================================================
|
|
static UANEKernel *ane_compile_inmem(NSData *milText, NSData *weightData,
|
|
int nInputs, size_t *inputSizes,
|
|
int nOutputs, size_t *outputSizes) {
|
|
NSError *e = nil;
|
|
NSDictionary *wdict = weightData ?
|
|
@{@"@model_path/weights/weight.bin": @{@"offset": @0, @"data": weightData}} : nil;
|
|
|
|
id desc = ((id(*)(Class,SEL,id,id,id))objc_msgSend)(
|
|
g_UANEDesc, @selector(modelWithMILText:weights:optionsPlist:),
|
|
milText, wdict, nil);
|
|
if (!desc) return NULL;
|
|
|
|
id mdl = ((id(*)(Class,SEL,id))objc_msgSend)(
|
|
g_UANEInMem, @selector(inMemoryModelWithDescriptor:), desc);
|
|
if (!mdl) return NULL;
|
|
|
|
id hx = ((id(*)(id,SEL))objc_msgSend)(mdl, @selector(hexStringIdentifier));
|
|
NSString *td = [NSTemporaryDirectory() stringByAppendingPathComponent:hx];
|
|
NSFileManager *fm = [NSFileManager defaultManager];
|
|
[fm createDirectoryAtPath:[td stringByAppendingPathComponent:@"weights"]
|
|
withIntermediateDirectories:YES attributes:nil error:nil];
|
|
[milText writeToFile:[td stringByAppendingPathComponent:@"model.mil"] atomically:YES];
|
|
if (weightData)
|
|
[weightData writeToFile:[td stringByAppendingPathComponent:@"weights/weight.bin"] atomically:YES];
|
|
|
|
if (!((BOOL(*)(id,SEL,unsigned int,id,NSError**))objc_msgSend)(
|
|
mdl, @selector(compileWithQoS:options:error:), 21, @{}, &e)) {
|
|
[fm removeItemAtPath:td error:nil];
|
|
return NULL;
|
|
}
|
|
if (!((BOOL(*)(id,SEL,unsigned int,id,NSError**))objc_msgSend)(
|
|
mdl, @selector(loadWithQoS:options:error:), 21, @{}, &e)) {
|
|
[fm removeItemAtPath:td error:nil];
|
|
return NULL;
|
|
}
|
|
|
|
UANEKernel *k = calloc(1, sizeof(UANEKernel));
|
|
k->backend = ANE_BACKEND_INMEM;
|
|
k->inMemModel = mdl;
|
|
k->tmpDir = td;
|
|
k->nInputs = nInputs;
|
|
k->nOutputs = nOutputs;
|
|
k->inputBytes = malloc(nInputs * sizeof(size_t));
|
|
k->outputBytes = malloc(nOutputs * sizeof(size_t));
|
|
memcpy(k->inputBytes, inputSizes, nInputs * sizeof(size_t));
|
|
memcpy(k->outputBytes, outputSizes, nOutputs * sizeof(size_t));
|
|
|
|
k->ioInputs = malloc(nInputs * sizeof(IOSurfaceRef));
|
|
k->ioOutputs = malloc(nOutputs * sizeof(IOSurfaceRef));
|
|
for (int i = 0; i < nInputs; i++)
|
|
k->ioInputs[i] = ane_u_create_surface(inputSizes[i]);
|
|
for (int i = 0; i < nOutputs; i++)
|
|
k->ioOutputs[i] = ane_u_create_surface(outputSizes[i]);
|
|
|
|
NSMutableArray *wIns = [NSMutableArray arrayWithCapacity:nInputs];
|
|
NSMutableArray *iIdx = [NSMutableArray arrayWithCapacity:nInputs];
|
|
for (int i = 0; i < nInputs; i++) {
|
|
[wIns addObject:((id(*)(Class,SEL,IOSurfaceRef))objc_msgSend)(
|
|
g_UANEIO, @selector(objectWithIOSurface:), k->ioInputs[i])];
|
|
[iIdx addObject:@(i)];
|
|
}
|
|
NSMutableArray *wOuts = [NSMutableArray arrayWithCapacity:nOutputs];
|
|
NSMutableArray *oIdx = [NSMutableArray arrayWithCapacity:nOutputs];
|
|
for (int i = 0; i < nOutputs; i++) {
|
|
[wOuts addObject:((id(*)(Class,SEL,IOSurfaceRef))objc_msgSend)(
|
|
g_UANEIO, @selector(objectWithIOSurface:), k->ioOutputs[i])];
|
|
[oIdx addObject:@(i)];
|
|
}
|
|
k->inMemRequest = ((id(*)(Class,SEL,id,id,id,id,id,id,id))objc_msgSend)(
|
|
g_UANEReq, @selector(requestWithInputs:inputIndices:outputs:outputIndices:weightsBuffer:perfStats:procedureIndex:),
|
|
wIns, iIdx, wOuts, oIdx, nil, nil, @0);
|
|
|
|
return k;
|
|
}
|
|
|
|
// ============================================================
|
|
// CoreML backend (M1-M3) — compile mlpackage, load, predict
|
|
// ============================================================
|
|
|
|
// Generate an mlpackage on disk with given weights for CoreML to compile
|
|
static NSString *ane_coreml_gen_package(NSData *milText, NSData *weightData) {
|
|
static int counter = 0;
|
|
NSString *pkgDir = [NSTemporaryDirectory() stringByAppendingPathComponent:
|
|
[NSString stringWithFormat:@"ane_coreml_%d_%d", getpid(), counter++]];
|
|
NSString *dataDir = [pkgDir stringByAppendingPathComponent:@"Data/com.apple.CoreML"];
|
|
NSString *weightsDir = [dataDir stringByAppendingPathComponent:@"weights"];
|
|
|
|
NSFileManager *fm = [NSFileManager defaultManager];
|
|
[fm createDirectoryAtPath:weightsDir withIntermediateDirectories:YES attributes:nil error:nil];
|
|
|
|
// Write MIL text
|
|
[milText writeToFile:[dataDir stringByAppendingPathComponent:@"model.mil"] atomically:YES];
|
|
|
|
// Write weights
|
|
if (weightData)
|
|
[weightData writeToFile:[weightsDir stringByAppendingPathComponent:@"weight.bin"] atomically:YES];
|
|
|
|
// Write Manifest.json
|
|
NSString *manifest = @"{\n"
|
|
" \"fileFormatVersion\": \"1.0.0\",\n"
|
|
" \"itemInfoEntries\": {\n"
|
|
" \"WEIGHTS\": {\n"
|
|
" \"author\": \"com.apple.CoreML\",\n"
|
|
" \"description\": \"CoreML Model Weights\",\n"
|
|
" \"name\": \"weights\",\n"
|
|
" \"path\": \"com.apple.CoreML/weights\"\n"
|
|
" },\n"
|
|
" \"MODEL\": {\n"
|
|
" \"author\": \"com.apple.CoreML\",\n"
|
|
" \"description\": \"CoreML Model Specification\",\n"
|
|
" \"name\": \"model.mlmodel\",\n"
|
|
" \"path\": \"com.apple.CoreML/model.mlmodel\"\n"
|
|
" }\n"
|
|
" },\n"
|
|
" \"rootModelIdentifier\": \"MODEL\"\n"
|
|
"}\n";
|
|
[manifest writeToFile:[pkgDir stringByAppendingPathComponent:@"Manifest.json"]
|
|
atomically:YES encoding:NSUTF8StringEncoding error:nil];
|
|
|
|
return pkgDir;
|
|
}
|
|
|
|
static UANEKernel *ane_compile_coreml(NSString *milText, NSData *weightData,
|
|
int nInputs, size_t *inputSizes,
|
|
int nOutputs, size_t *outputSizes) {
|
|
// For CoreML path, we need a proper mlpackage
|
|
// The caller provides MIL text in v1.3 format
|
|
NSData *milData = [milText dataUsingEncoding:NSUTF8StringEncoding];
|
|
NSString *pkgPath = ane_coreml_gen_package(milData, weightData);
|
|
|
|
NSError *e = nil;
|
|
NSURL *compiledURL = [MLModel compileModelAtURL:
|
|
[NSURL fileURLWithPath:[pkgPath stringByAppendingPathExtension:@"mlpackage"]]
|
|
error:&e];
|
|
|
|
// The mlpackage approach won't work with raw MIL text since CoreML expects
|
|
// protobuf model.mlmodel. Instead, use coremltools from Python.
|
|
// For the C-only path, we compile via _ANEModel using the mlmodelc from CoreML.
|
|
|
|
// Cleanup
|
|
[[NSFileManager defaultManager] removeItemAtPath:pkgPath error:nil];
|
|
|
|
if (!compiledURL) {
|
|
fprintf(stderr, "[ANE-CoreML] Compile failed: %s\n", [[e description] UTF8String]);
|
|
return NULL;
|
|
}
|
|
|
|
// Load with ANE compute units
|
|
MLModelConfiguration *config = [[MLModelConfiguration alloc] init];
|
|
config.computeUnits = MLComputeUnitsCPUAndNeuralEngine;
|
|
|
|
MLModel *model = [MLModel modelWithContentsOfURL:compiledURL
|
|
configuration:config error:&e];
|
|
if (!model) {
|
|
fprintf(stderr, "[ANE-CoreML] Load failed: %s\n", [[e description] UTF8String]);
|
|
return NULL;
|
|
}
|
|
|
|
UANEKernel *k = calloc(1, sizeof(UANEKernel));
|
|
k->backend = ANE_BACKEND_COREML;
|
|
k->coremlModel = model;
|
|
k->coremlCompiledPath = [compiledURL path];
|
|
k->nInputs = nInputs;
|
|
k->nOutputs = nOutputs;
|
|
k->inputBytes = malloc(nInputs * sizeof(size_t));
|
|
k->outputBytes = malloc(nOutputs * sizeof(size_t));
|
|
memcpy(k->inputBytes, inputSizes, nInputs * sizeof(size_t));
|
|
memcpy(k->outputBytes, outputSizes, nOutputs * sizeof(size_t));
|
|
|
|
// Also create IOSurfaces for compatibility
|
|
k->ioInputs = malloc(nInputs * sizeof(IOSurfaceRef));
|
|
k->ioOutputs = malloc(nOutputs * sizeof(IOSurfaceRef));
|
|
for (int i = 0; i < nInputs; i++)
|
|
k->ioInputs[i] = ane_u_create_surface(inputSizes[i]);
|
|
for (int i = 0; i < nOutputs; i++)
|
|
k->ioOutputs[i] = ane_u_create_surface(outputSizes[i]);
|
|
|
|
// Get input/output names
|
|
MLModelDescription *desc = model.modelDescription;
|
|
k->coremlInputName = desc.inputDescriptionsByName.allKeys.firstObject;
|
|
k->coremlOutputName = desc.outputDescriptionsByName.allKeys.firstObject;
|
|
|
|
return k;
|
|
}
|
|
|
|
// ============================================================
|
|
// Unified API
|
|
// ============================================================
|
|
|
|
static void ane_universal_write_input(UANEKernel *k, int idx, const void *data, size_t bytes) {
|
|
IOSurfaceLock(k->ioInputs[idx], 0, NULL);
|
|
memcpy(IOSurfaceGetBaseAddress(k->ioInputs[idx]), data, bytes);
|
|
IOSurfaceUnlock(k->ioInputs[idx], 0, NULL);
|
|
}
|
|
|
|
static void ane_universal_read_output(UANEKernel *k, int idx, void *data, size_t bytes) {
|
|
IOSurfaceLock(k->ioOutputs[idx], kIOSurfaceLockReadOnly, NULL);
|
|
memcpy(data, IOSurfaceGetBaseAddress(k->ioOutputs[idx]), bytes);
|
|
IOSurfaceUnlock(k->ioOutputs[idx], kIOSurfaceLockReadOnly, NULL);
|
|
}
|
|
|
|
static bool ane_universal_eval(UANEKernel *k) {
|
|
if (k->backend == ANE_BACKEND_INMEM) {
|
|
NSError *e = nil;
|
|
return ((BOOL(*)(id,SEL,unsigned int,id,id,NSError**))objc_msgSend)(
|
|
k->inMemModel, @selector(evaluateWithQoS:options:request:error:),
|
|
21, @{}, k->inMemRequest, &e);
|
|
} else if (k->backend == ANE_BACKEND_COREML) {
|
|
// Read from IOSurface into MLMultiArray, predict, write back
|
|
NSError *e = nil;
|
|
size_t nElems = k->inputBytes[0] / 2; // fp16 = 2 bytes each
|
|
|
|
// Create float32 input array from fp16 IOSurface data
|
|
// Determine shape from bytes (assuming [1, C, 1, S])
|
|
MLMultiArray *inputArray = [[MLMultiArray alloc]
|
|
initWithShape:@[@1, @(nElems)]
|
|
dataType:MLMultiArrayDataTypeFloat32 error:&e];
|
|
if (!inputArray) return false;
|
|
|
|
IOSurfaceLock(k->ioInputs[0], kIOSurfaceLockReadOnly, NULL);
|
|
uint16_t *fp16_in = (uint16_t *)IOSurfaceGetBaseAddress(k->ioInputs[0]);
|
|
float *fp32_out = (float *)inputArray.dataPointer;
|
|
// fp16 → fp32 conversion
|
|
for (size_t i = 0; i < nElems; i++) {
|
|
// Simple fp16 decode (sufficient for benchmarking)
|
|
uint16_t h = fp16_in[i];
|
|
uint32_t sign = (h >> 15) & 1;
|
|
uint32_t exp = (h >> 10) & 0x1F;
|
|
uint32_t mant = h & 0x3FF;
|
|
uint32_t f;
|
|
if (exp == 0) {
|
|
f = sign << 31;
|
|
} else if (exp == 31) {
|
|
f = (sign << 31) | 0x7F800000 | (mant << 13);
|
|
} else {
|
|
f = (sign << 31) | ((exp + 112) << 23) | (mant << 13);
|
|
}
|
|
memcpy(&fp32_out[i], &f, 4);
|
|
}
|
|
IOSurfaceUnlock(k->ioInputs[0], kIOSurfaceLockReadOnly, NULL);
|
|
|
|
MLDictionaryFeatureProvider *provider = [[MLDictionaryFeatureProvider alloc]
|
|
initWithDictionary:@{k->coremlInputName: inputArray} error:&e];
|
|
if (!provider) return false;
|
|
|
|
id<MLFeatureProvider> result = [k->coremlModel predictionFromFeatures:provider error:&e];
|
|
if (!result) return false;
|
|
|
|
// Write output back to IOSurface
|
|
MLMultiArray *outArray = [result featureValueForName:k->coremlOutputName].multiArrayValue;
|
|
if (outArray) {
|
|
size_t outElems = k->outputBytes[0] / 2;
|
|
float *fp32_res = (float *)outArray.dataPointer;
|
|
IOSurfaceLock(k->ioOutputs[0], 0, NULL);
|
|
uint16_t *fp16_dst = (uint16_t *)IOSurfaceGetBaseAddress(k->ioOutputs[0]);
|
|
for (size_t i = 0; i < outElems && i < (size_t)outArray.count; i++) {
|
|
// fp32 → fp16 (simple truncation)
|
|
float val = fp32_res[i];
|
|
uint32_t f;
|
|
memcpy(&f, &val, 4);
|
|
uint32_t sign = (f >> 31) & 1;
|
|
int32_t exp = ((f >> 23) & 0xFF) - 127 + 15;
|
|
uint32_t mant = (f >> 13) & 0x3FF;
|
|
if (exp <= 0) fp16_dst[i] = sign << 15;
|
|
else if (exp >= 31) fp16_dst[i] = (sign << 15) | 0x7C00;
|
|
else fp16_dst[i] = (sign << 15) | (exp << 10) | mant;
|
|
}
|
|
IOSurfaceUnlock(k->ioOutputs[0], 0, NULL);
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static void ane_universal_free(UANEKernel *k) {
|
|
if (!k) return;
|
|
NSError *e = nil;
|
|
|
|
if (k->backend == ANE_BACKEND_INMEM) {
|
|
((BOOL(*)(id,SEL,unsigned int,NSError**))objc_msgSend)(
|
|
k->inMemModel, @selector(unloadWithQoS:error:), 21, &e);
|
|
[[NSFileManager defaultManager] removeItemAtPath:k->tmpDir error:nil];
|
|
} else if (k->backend == ANE_BACKEND_COREML) {
|
|
if (k->coremlCompiledPath)
|
|
[[NSFileManager defaultManager] removeItemAtPath:k->coremlCompiledPath error:nil];
|
|
}
|
|
|
|
for (int i = 0; i < k->nInputs; i++) CFRelease(k->ioInputs[i]);
|
|
for (int i = 0; i < k->nOutputs; i++) CFRelease(k->ioOutputs[i]);
|
|
free(k->ioInputs);
|
|
free(k->ioOutputs);
|
|
free(k->inputBytes);
|
|
free(k->outputBytes);
|
|
free(k);
|
|
}
|
|
|
|
static const char *ane_backend_name(void) {
|
|
switch (g_ane_backend) {
|
|
case ANE_BACKEND_INMEM: return "InMemory (M4+)";
|
|
case ANE_BACKEND_COREML: return "CoreML (M1-M3)";
|
|
default: return "None";
|
|
}
|
|
}
|