// ane_int8_bench.m — INT8 W8A8 benchmark on ANE via _ANEInMemoryModel // Build: xcrun clang -O2 -fobjc-arc -framework Foundation -framework IOSurface -ldl -o ane_int8_bench ane_int8_bench.m // Usage: ./ane_int8_bench // // Tests FP16 vs W8A8 (int8 weights + int8 activation caching) throughput. // Key MIL ops: constexpr_affine_dequantize, quantize, dequantize #import #import #import #import #import #import static mach_timebase_info_data_t g_tb; static double ticksToMs(uint64_t t) { return (double)t * g_tb.numer / g_tb.denom / 1e6; } // Weight blob for int8 weights (1 byte per element) static NSData *buildWeightBlobInt8(int ch, int depth) { NSUInteger wsize = ch * ch * 1; NSUInteger chunkSize = 64 + wsize; NSUInteger total = 64 + chunkSize * depth; uint8_t *buf = calloc(total, 1); buf[0] = 0x01; buf[4] = 0x02; for (int i = 0; i < depth; i++) { uint8_t *chunk = buf + 64 + i * chunkSize; chunk[0]=0xEF; chunk[1]=0xBE; chunk[2]=0xAD; chunk[3]=0xDE; chunk[4]=0x01; chunk[10]=0x08; int8_t *data = (int8_t*)(chunk + 64); for (NSUInteger j = 0; j < wsize; j++) data[j] = (int8_t)(arc4random() % 256 - 128); } return [NSData dataWithBytesNoCopy:buf length:total freeWhenDone:YES]; } // Weight blob for fp16 weights (2 bytes per element) static NSData *buildWeightBlobFP16(int ch, int depth) { NSUInteger wsize = ch * ch * 2; NSUInteger chunkSize = 64 + wsize; NSUInteger total = 64 + chunkSize * depth; uint8_t *buf = calloc(total, 1); buf[0] = 0x01; buf[4] = 0x02; for (int i = 0; i < depth; i++) { uint8_t *chunk = buf + 64 + i * chunkSize; chunk[0]=0xEF; chunk[1]=0xBE; chunk[2]=0xAD; chunk[3]=0xDE; chunk[4]=0x01; chunk[10]=0x10; _Float16 *data = (_Float16*)(chunk + 64); for (NSUInteger j = 0; j < (NSUInteger)(ch*ch); j++) data[j] = (_Float16)(((float)(arc4random()%1000) - 500.0f) * 0.001f); } return [NSData dataWithBytesNoCopy:buf length:total freeWhenDone:YES]; } // Generate W8A8 INT8 MIL: conv with int8 weights + quantize/dequantize between layers static NSString *genMILInt8(int ch, int sp, int depth) { NSMutableString *m = [NSMutableString string]; [m appendString:@"program(1.3)\n[buildInfo = dict({{\"coremlc-component-MIL\", \"3510.2.1\"}, {\"coremlc-version\", \"3505.4.1\"}, {\"coremltools-component-milinternal\", \"\"}, {\"coremltools-version\", \"9.0\"}})]\n{\n"]; [m appendFormat:@" func main(tensor x) {\n", ch, sp, sp]; // Conv constants [m appendString:@" string c_pad_type = const()[name = string(\"c_pad_type\"), val = string(\"valid\")];\n" @" tensor c_strides = const()[name = string(\"c_strides\"), val = tensor([1, 1])];\n" @" tensor c_pad = const()[name = string(\"c_pad\"), val = tensor([0, 0, 0, 0])];\n" @" tensor c_dilations = const()[name = string(\"c_dilations\"), val = tensor([1, 1])];\n" @" int32 c_groups = const()[name = string(\"c_groups\"), val = int32(1)];\n"]; // Quantize/dequantize scale [m appendString:@" fp16 q_scale = const()[name = string(\"q_scale\"), val = fp16(0x1p-3)];\n" @" string q_dtype = const()[name = string(\"q_dtype\"), val = string(\"int8\")];\n" @" fp16 dq_scale = const()[name = string(\"dq_scale\"), val = fp16(0x1p-3)];\n"]; NSUInteger cs = 64 + ch * ch * 1; // int8 chunk size NSString *prev = @"x"; for (int i = 0; i < depth; i++) { // constexpr_affine_dequantize: int8 weights → fp16 at compile time [m appendFormat: @" tensor W%d = constexpr_affine_dequantize()" @"[axis = int32(0), name = string(\"W%d\"), " @"quantized_data = tensor" @"(BLOBFILE(path = string(\"@model_path/weights/weight.bin\"), offset = uint64(%lu))), " @"scale = fp16(0x1p-3), zero_point = int8(0)];\n", ch, ch, i, i, ch, ch, (unsigned long)(64 + i * cs)]; // conv NSString *conv_out = [NSString stringWithFormat:@"c%d", i]; [m appendFormat:@" tensor %@ = conv(dilations = c_dilations, groups = c_groups, pad = c_pad, pad_type = c_pad_type, strides = c_strides, weight = W%d, x = %@)[name = string(\"%@\")];\n", ch, sp, sp, conv_out, i, prev, conv_out]; if (i < depth - 1) { // quantize: fp16 → int8 NSString *q_out = [NSString stringWithFormat:@"q%d", i]; [m appendFormat:@" tensor %@ = quantize(input = %@, output_dtype = q_dtype, scale = q_scale)[name = string(\"%@\")];\n", ch, sp, sp, q_out, conv_out, q_out]; // dequantize: int8 → fp16 NSString *dq_out = [NSString stringWithFormat:@"dq%d", i]; [m appendFormat:@" tensor %@ = dequantize(input = %@, scale = dq_scale)[name = string(\"%@\")];\n", ch, sp, sp, dq_out, q_out, dq_out]; prev = dq_out; } else { prev = conv_out; } } [m appendFormat:@" } -> (%@);\n}\n", prev]; return m; } // Generate FP16 baseline MIL: pure fp16 conv chain static NSString *genMILFP16(int ch, int sp, int depth) { NSMutableString *m = [NSMutableString string]; [m appendString:@"program(1.3)\n[buildInfo = dict({{\"coremlc-component-MIL\", \"3510.2.1\"}, {\"coremlc-version\", \"3505.4.1\"}, {\"coremltools-component-milinternal\", \"\"}, {\"coremltools-version\", \"9.0\"}})]\n{\n"]; [m appendFormat:@" func main(tensor x) {\n", ch, sp, sp]; [m appendString:@" string c_pad_type = const()[name = string(\"c_pad_type\"), val = string(\"valid\")];\n" @" tensor c_strides = const()[name = string(\"c_strides\"), val = tensor([1, 1])];\n" @" tensor c_pad = const()[name = string(\"c_pad\"), val = tensor([0, 0, 0, 0])];\n" @" tensor c_dilations = const()[name = string(\"c_dilations\"), val = tensor([1, 1])];\n" @" int32 c_groups = const()[name = string(\"c_groups\"), val = int32(1)];\n"]; NSUInteger cs = 64 + ch * ch * 2; // fp16 chunk size NSString *prev = @"x"; for (int i = 0; i < depth; i++) { // fp16 weights from blob [m appendFormat: @" tensor W%d = const()" @"[name = string(\"W%d\"), " @"val = tensor" @"(BLOBFILE(path = string(\"@model_path/weights/weight.bin\"), offset = uint64(%lu)))];\n", ch, ch, i, i, ch, ch, (unsigned long)(64 + i * cs)]; NSString *conv_out = [NSString stringWithFormat:@"c%d", i]; [m appendFormat:@" tensor %@ = conv(dilations = c_dilations, groups = c_groups, pad = c_pad, pad_type = c_pad_type, strides = c_strides, weight = W%d, x = %@)[name = string(\"%@\")];\n", ch, sp, sp, conv_out, i, prev, conv_out]; prev = conv_out; } [m appendFormat:@" } -> (%@);\n}\n", prev]; return m; } static double benchModel(NSString *milStr, NSData *wb, int ch, int sp, const char *label) { @autoreleasepool { NSError *e = nil; NSData *milData = [milStr dataUsingEncoding:NSUTF8StringEncoding]; Class D = NSClassFromString(@"_ANEInMemoryModelDescriptor"); Class I = NSClassFromString(@"_ANEInMemoryModel"); Class AR = NSClassFromString(@"_ANERequest"); Class AIO = NSClassFromString(@"_ANEIOSurfaceObject"); id desc = ((id(*)(Class,SEL,id,id,id))objc_msgSend)(D, @selector(modelWithMILText:weights:optionsPlist:), milData, @{@"@model_path/weights/weight.bin": @{@"offset": @0, @"data": wb}}, nil); if (!desc) { printf(" %s: desc FAIL\n", label); return -1; } id mdl = ((id(*)(Class,SEL,id))objc_msgSend)(I, @selector(inMemoryModelWithDescriptor:), desc); if (!mdl) { printf(" %s: mdl FAIL\n", label); return -2; } 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]; [milData writeToFile:[td stringByAppendingPathComponent:@"model.mil"] atomically:YES]; [wb writeToFile:[td stringByAppendingPathComponent:@"weights/weight.bin"] atomically:YES]; if (!((BOOL(*)(id,SEL,unsigned int,id,NSError**))objc_msgSend)( mdl, @selector(compileWithQoS:options:error:), 0, @{}, &e)) { printf(" %s: compile FAIL: %s\n", label, e ? [[e description] UTF8String] : "?"); [fm removeItemAtPath:td error:nil]; return -3; } if (!((BOOL(*)(id,SEL,unsigned int,id,NSError**))objc_msgSend)( mdl, @selector(loadWithQoS:options:error:), 0, @{}, &e)) { printf(" %s: load FAIL\n", label); [fm removeItemAtPath:td error:nil]; return -4; } NSUInteger bytes = (NSUInteger)ch * sp * sp * 2; // fp16 I/O IOSurfaceRef ioI = IOSurfaceCreate((__bridge CFDictionaryRef)@{ (id)kIOSurfaceWidth: @(bytes), (id)kIOSurfaceHeight: @1, (id)kIOSurfaceBytesPerElement: @1, (id)kIOSurfaceBytesPerRow: @(bytes), (id)kIOSurfaceAllocSize: @(bytes), (id)kIOSurfacePixelFormat: @0}); IOSurfaceRef ioO = IOSurfaceCreate((__bridge CFDictionaryRef)@{ (id)kIOSurfaceWidth: @(bytes), (id)kIOSurfaceHeight: @1, (id)kIOSurfaceBytesPerElement: @1, (id)kIOSurfaceBytesPerRow: @(bytes), (id)kIOSurfaceAllocSize: @(bytes), (id)kIOSurfacePixelFormat: @0}); id wI = ((id(*)(Class,SEL,IOSurfaceRef))objc_msgSend)(AIO, @selector(objectWithIOSurface:), ioI); id wO = ((id(*)(Class,SEL,IOSurfaceRef))objc_msgSend)(AIO, @selector(objectWithIOSurface:), ioO); id req = ((id(*)(Class,SEL,id,id,id,id,id,id,id))objc_msgSend)(AR, @selector(requestWithInputs:inputIndices:outputs:outputIndices:weightsBuffer:perfStats:procedureIndex:), @[wI], @[@0], @[wO], @[@0], nil, nil, @0); // Warmup for (int i = 0; i < 10; i++) ((BOOL(*)(id,SEL,unsigned int,id,id,NSError**))objc_msgSend)( mdl, @selector(evaluateWithQoS:options:request:error:), 0, @{}, req, &e); int iters = 50; uint64_t t0 = mach_absolute_time(); for (int i = 0; i < iters; i++) ((BOOL(*)(id,SEL,unsigned int,id,id,NSError**))objc_msgSend)( mdl, @selector(evaluateWithQoS:options:request:error:), 0, @{}, req, &e); double ms = ticksToMs(mach_absolute_time() - t0) / iters; ((BOOL(*)(id,SEL,unsigned int,NSError**))objc_msgSend)( mdl, @selector(unloadWithQoS:error:), 0, &e); CFRelease(ioI); CFRelease(ioO); [fm removeItemAtPath:td error:nil]; return ms; } } int main(void) { mach_timebase_info(&g_tb); dlopen("/System/Library/PrivateFrameworks/AppleNeuralEngine.framework/AppleNeuralEngine", RTLD_NOW); // Query HW info Class DI = NSClassFromString(@"_ANEDeviceInfo"); const char *ane_type = "unknown"; if (DI) { id subType = ((id(*)(Class,SEL))objc_msgSend)(DI, @selector(aneSubType)); if (subType) ane_type = [[subType description] UTF8String]; } printf("=== ANE INT8 W8A8 Benchmark (M4, %s) ===\n\n", ane_type); printf("%-30s %7s %7s %9s %7s %7s\n", "Config", "W(MB)", "GOP", "ms/eval", "TOPS", "Ratio"); printf("--------------------------------------------------------------------------------\n"); typedef struct { int ch; int sp; int depth; } Config; Config configs[] = { {512, 64, 128}, {512, 64, 64}, {256, 64, 256}, {256, 64, 128}, {384, 64, 128}, }; int ncfg = sizeof(configs) / sizeof(configs[0]); for (int ci = 0; ci < ncfg; ci++) { int ch = configs[ci].ch, sp = configs[ci].sp, depth = configs[ci].depth; double gop = 2.0 * ch * ch * sp * sp * depth / 1e9; // FP16 double w_fp16 = (double)ch * ch * 2 * depth / 1024 / 1024; NSString *milFP16 = genMILFP16(ch, sp, depth); NSData *wbFP16 = buildWeightBlobFP16(ch, depth); char lbl[64]; snprintf(lbl, 64, "FP16 %dx conv %dch", depth, ch); double ms_fp16 = benchModel(milFP16, wbFP16, ch, sp, lbl); // INT8 W8A8 double w_int8 = (double)ch * ch * 1 * depth / 1024 / 1024; NSString *milInt8 = genMILInt8(ch, sp, depth); NSData *wbInt8 = buildWeightBlobInt8(ch, depth); snprintf(lbl, 64, "W8A8 %dx conv %dch", depth, ch); double ms_int8 = benchModel(milInt8, wbInt8, ch, sp, lbl); if (ms_fp16 > 0 && ms_int8 > 0) { double tops_fp16 = gop / ms_fp16; double tops_int8 = gop / ms_int8; double ratio = ms_fp16 / ms_int8; printf("FP16 %-25s %6.1f %6.2f %7.3f ms %6.2f\n", [NSString stringWithFormat:@"%dx conv %dch %dx%d", depth, ch, sp, sp].UTF8String, w_fp16, gop, ms_fp16, tops_fp16); printf("W8A8 %-25s %6.1f %6.2f %7.3f ms %6.2f %.2fx\n", [NSString stringWithFormat:@"%dx conv %dch %dx%d", depth, ch, sp, sp].UTF8String, w_int8, gop, ms_int8, tops_int8, ratio); printf("\n"); } else { printf(" %dx conv %dch: FP16=%.1f INT8=%.1f (FAIL)\n", depth, ch, ms_fp16, ms_int8); } } printf("=== Done ===\n"); return 0; }