mirror of https://github.com/maderix/ANE.git
818 lines
38 KiB
Objective-C
818 lines
38 KiB
Objective-C
// test_e5_validate.m — Experiments W1-W5: E5 Runtime Validation & Deep API Exploration
|
|
// Build: make test_e5_validate && ./test_e5_validate
|
|
#import <Foundation/Foundation.h>
|
|
#import <CoreML/CoreML.h>
|
|
#import <objc/runtime.h>
|
|
#import <objc/message.h>
|
|
#import <dlfcn.h>
|
|
#import <mach/mach_time.h>
|
|
#import <IOSurface/IOSurface.h>
|
|
|
|
static mach_timebase_info_data_t g_tb;
|
|
static double tb_ms(uint64_t t) { return (double)t * g_tb.numer / g_tb.denom / 1e6; }
|
|
|
|
#pragma mark - Helpers
|
|
|
|
static void dump_all_methods(Class cls, const char *label) {
|
|
if (!cls) { printf(" %s: NOT FOUND\n", label); return; }
|
|
printf("\n--- %s ---\n", label);
|
|
|
|
unsigned int mc;
|
|
Method *cm = class_copyMethodList(object_getClass(cls), &mc);
|
|
if (mc > 0) {
|
|
printf(" Class methods (%u):\n", mc);
|
|
for (unsigned int i = 0; i < mc; i++) {
|
|
const char *sel = sel_getName(method_getName(cm[i]));
|
|
const char *enc = method_getTypeEncoding(cm[i]);
|
|
printf(" + %s [%s]\n", sel, enc ? enc : "?");
|
|
}
|
|
}
|
|
free(cm);
|
|
|
|
Method *im = class_copyMethodList(cls, &mc);
|
|
if (mc > 0) {
|
|
printf(" Instance methods (%u):\n", mc);
|
|
for (unsigned int i = 0; i < mc; i++) {
|
|
const char *sel = sel_getName(method_getName(im[i]));
|
|
const char *enc = method_getTypeEncoding(im[i]);
|
|
printf(" - %s [%s]\n", sel, enc ? enc : "?");
|
|
}
|
|
}
|
|
free(im);
|
|
|
|
unsigned int pc;
|
|
objc_property_t *props = class_copyPropertyList(cls, &pc);
|
|
if (pc > 0) {
|
|
printf(" Properties (%u):\n", pc);
|
|
for (unsigned int i = 0; i < pc; i++)
|
|
printf(" %s [%s]\n", property_getName(props[i]),
|
|
property_getAttributes(props[i]));
|
|
}
|
|
free(props);
|
|
|
|
unsigned int ic;
|
|
Ivar *ivars = class_copyIvarList(cls, &ic);
|
|
if (ic > 0) {
|
|
printf(" Ivars (%u):\n", ic);
|
|
for (unsigned int i = 0; i < ic; i++) {
|
|
const char *n = ivar_getName(ivars[i]);
|
|
const char *t = ivar_getTypeEncoding(ivars[i]);
|
|
printf(" %s type=%s\n", n, t ? t : "?");
|
|
}
|
|
}
|
|
free(ivars);
|
|
|
|
Class super = class_getSuperclass(cls);
|
|
if (super && super != [NSObject class])
|
|
printf(" Superclass: %s\n", class_getName(super));
|
|
}
|
|
|
|
static float max_abs_diff(float *a, float *b, int n) {
|
|
float m = 0;
|
|
for (int i = 0; i < n; i++) {
|
|
float d = fabsf(a[i] - b[i]);
|
|
if (d > m) m = d;
|
|
}
|
|
return m;
|
|
}
|
|
|
|
static float mean_abs(float *a, int n) {
|
|
float s = 0;
|
|
for (int i = 0; i < n; i++) s += fabsf(a[i]);
|
|
return s / n;
|
|
}
|
|
|
|
#pragma mark - Main
|
|
|
|
int main(int argc, const char *argv[]) {
|
|
(void)argc; (void)argv;
|
|
@autoreleasepool {
|
|
mach_timebase_info(&g_tb);
|
|
printf("================================================================\n");
|
|
printf(" E5 Runtime: Validation & Exhaustive API Documentation\n");
|
|
printf("================================================================\n\n");
|
|
|
|
dlopen("/System/Library/PrivateFrameworks/AppleNeuralEngine.framework/"
|
|
"AppleNeuralEngine", RTLD_NOW);
|
|
|
|
// ============================================================
|
|
// W2: Exhaustive API Documentation (dump first so we have it)
|
|
// ============================================================
|
|
printf("================================================================\n");
|
|
printf(" W2: Exhaustive E5 Runtime API Documentation\n");
|
|
printf("================================================================\n");
|
|
|
|
const char *classNames[] = {
|
|
"MLE5Engine",
|
|
"MLE5ProgramLibrary",
|
|
"MLE5ProgramLibraryOnDeviceAOTCompilationImpl",
|
|
"MLE5ProgramLibraryE5BundleImpl",
|
|
"MLE5ExecutionStreamOperation",
|
|
"MLE5ExecutionStream",
|
|
"MLE5ExecutionStreamPool",
|
|
"MLE5StaticShapeExecutionStreamOperationPool",
|
|
"MLE5RangeShapeExecutionStreamOperationPool",
|
|
"MLE5EnumeratedShapeExecutionStreamOperationPool",
|
|
"MLE5ExecutionStreamOperationPoolFactory",
|
|
"MLE5InputPort",
|
|
"MLE5OutputPort",
|
|
"MLE5InputPortBinder",
|
|
"MLE5OutputPortBinder",
|
|
"MLProgramE5Container",
|
|
NULL
|
|
};
|
|
for (int i = 0; classNames[i]; i++) {
|
|
Class cls = NSClassFromString(
|
|
[NSString stringWithUTF8String:classNames[i]]);
|
|
dump_all_methods(cls, classNames[i]);
|
|
}
|
|
|
|
printf("\n--- e5rt_* C API Symbols ---\n");
|
|
const char *cFuncs[] = {
|
|
"e5rt_program_library_create",
|
|
"e5rt_program_library_destroy",
|
|
"e5rt_program_library_compile",
|
|
"e5rt_program_library_get_function",
|
|
"e5rt_program_library_load_function",
|
|
"e5rt_execution_stream_create",
|
|
"e5rt_execution_stream_destroy",
|
|
"e5rt_execution_stream_submit",
|
|
"e5rt_execution_stream_wait",
|
|
"e5rt_execution_stream_execute",
|
|
"e5rt_execution_stream_sync",
|
|
"e5rt_execution_stream_operation_create",
|
|
"e5rt_execution_stream_operation_destroy",
|
|
"e5rt_execution_stream_operation_set_input",
|
|
"e5rt_execution_stream_operation_set_output",
|
|
"e5rt_execution_stream_operation_execute",
|
|
"e5rt_async_event_create",
|
|
"e5rt_async_event_destroy",
|
|
"e5rt_async_event_signal",
|
|
"e5rt_async_event_wait",
|
|
"e5rt_buffer_create",
|
|
"e5rt_buffer_destroy",
|
|
"e5rt_io_port_create",
|
|
"e5rt_io_port_bind",
|
|
"e5rt_context_create",
|
|
"e5rt_init",
|
|
"e5rt_get_version",
|
|
NULL
|
|
};
|
|
for (int i = 0; cFuncs[i]; i++) {
|
|
void *sym = dlsym(RTLD_DEFAULT, cFuncs[i]);
|
|
if (sym) printf(" FOUND: %s at %p\n", cFuncs[i], sym);
|
|
}
|
|
fflush(stdout);
|
|
|
|
// ============================================================
|
|
// W1: Output Validation
|
|
// ============================================================
|
|
printf("\n================================================================\n");
|
|
printf(" W1: Output Correctness Validation\n");
|
|
printf("================================================================\n\n");
|
|
|
|
int ch = 256, sp = 64;
|
|
NSString *pkgPath = [NSString stringWithFormat:
|
|
@"/tmp/ane_sram_%dch_%dsp.mlpackage", ch, sp];
|
|
if (![[NSFileManager defaultManager] fileExistsAtPath:pkgPath]) {
|
|
printf(" FATAL: %s not found. Run gen_mlpackages.py\n",
|
|
[pkgPath UTF8String]);
|
|
return 1;
|
|
}
|
|
|
|
NSError *err = nil;
|
|
MLModelConfiguration *cfg = [[MLModelConfiguration alloc] init];
|
|
cfg.computeUnits = MLComputeUnitsAll;
|
|
MLPredictionOptions *predOpts = [[MLPredictionOptions alloc] init];
|
|
Class opCls = NSClassFromString(@"MLE5ExecutionStreamOperation");
|
|
|
|
NSURL *compiled = [MLModel compileModelAtURL:
|
|
[NSURL fileURLWithPath:pkgPath] error:&err];
|
|
if (err) { printf(" Compile FAILED\n"); return 1; }
|
|
err = nil;
|
|
MLModel *model = [MLModel modelWithContentsOfURL:compiled
|
|
configuration:cfg error:&err];
|
|
if (err) { printf(" Load FAILED\n"); return 1; }
|
|
|
|
int nElems = 1 * ch * 1 * sp;
|
|
MLMultiArray *inputArr = [[MLMultiArray alloc]
|
|
initWithShape:@[@1, @(ch), @1, @(sp)]
|
|
dataType:MLMultiArrayDataTypeFloat32 error:nil];
|
|
|
|
float *inPtr = (float *)[inputArr dataPointer];
|
|
for (int i = 0; i < nElems; i++)
|
|
inPtr[i] = sinf((float)i * 0.01f) * 0.5f;
|
|
|
|
NSString *inName = [[[[model modelDescription] inputDescriptionsByName]
|
|
allKeys] firstObject];
|
|
NSString *outName = [[[[model modelDescription] outputDescriptionsByName]
|
|
allKeys] firstObject];
|
|
MLDictionaryFeatureProvider *fp = [[MLDictionaryFeatureProvider alloc]
|
|
initWithDictionary:@{inName: inputArr} error:nil];
|
|
|
|
printf(" Input: %s [1,%d,1,%d], first 5: [%.4f %.4f %.4f %.4f %.4f]\n",
|
|
[inName UTF8String], ch, sp,
|
|
inPtr[0], inPtr[1], inPtr[2], inPtr[3], inPtr[4]);
|
|
printf(" Output: %s\n", [outName UTF8String]);
|
|
fflush(stdout);
|
|
|
|
// --- Reference: CoreML sequential prediction ---
|
|
printf("\n --- W1.1: CoreML reference prediction ---\n");
|
|
err = nil;
|
|
id<MLFeatureProvider> refResult = [model predictionFromFeatures:fp error:&err];
|
|
if (err) { printf(" Prediction FAILED\n"); return 1; }
|
|
|
|
MLMultiArray *refOut = [refResult featureValueForName:outName].multiArrayValue;
|
|
float *refPtr = (float *)[refOut dataPointer];
|
|
int outElems = 1;
|
|
for (int d = 0; d < (int)refOut.shape.count; d++)
|
|
outElems *= [refOut.shape[d] intValue];
|
|
printf(" Output shape: [");
|
|
for (int d = 0; d < (int)refOut.shape.count; d++)
|
|
printf("%s%d", d ? "," : "", [refOut.shape[d] intValue]);
|
|
printf("] (%d elements)\n", outElems);
|
|
printf(" First 5 ref: [%.6f %.6f %.6f %.6f %.6f]\n",
|
|
refPtr[0], refPtr[1], refPtr[2], refPtr[3], refPtr[4]);
|
|
printf(" Mean |ref|: %.6f\n", mean_abs(refPtr, outElems));
|
|
fflush(stdout);
|
|
|
|
// --- E5 stream prediction ---
|
|
printf("\n --- W1.2: E5 stream prediction ---\n");
|
|
|
|
id e5engine = nil;
|
|
@try { e5engine = [model valueForKey:@"_internalEngine"]; }
|
|
@catch (NSException *e) { (void)e; }
|
|
id progLib = nil;
|
|
@try { progLib = [e5engine valueForKey:@"programLibrary"]; }
|
|
@catch (NSException *e) { (void)e; }
|
|
id streamPool = nil;
|
|
@try { streamPool = [e5engine valueForKey:@"streamPool"]; }
|
|
@catch (NSException *e) { (void)e; }
|
|
|
|
id op = ((id(*)(id,SEL,id,id,id,id,id,unsigned long long))objc_msgSend)(
|
|
[opCls alloc],
|
|
@selector(initWithProgramLibrary:functionName:modelDescription:
|
|
configuration:debugLabel:modelSignpostId:),
|
|
progLib, @"main", [model modelDescription], cfg,
|
|
@"validate_op", (unsigned long long)0);
|
|
|
|
NSError *plErr = nil;
|
|
BOOL plOk = ((BOOL(*)(id,SEL,NSError**))objc_msgSend)(
|
|
op, @selector(preloadAndReturnError:), &plErr);
|
|
printf(" preload: %s\n", plOk ? "YES" : "NO");
|
|
if (plErr) printf(" Error: %s\n", [[plErr description] UTF8String]);
|
|
fflush(stdout);
|
|
|
|
id stream = [streamPool performSelector:@selector(takeOut)];
|
|
Ivar shIvar = class_getInstanceVariable([stream class], "_streamHandle");
|
|
void *sh = (__bridge void *)object_getIvar(stream, shIvar);
|
|
printf(" stream: %p, handle: %p\n", (__bridge void *)stream, sh);
|
|
|
|
[stream setValue:@[op] forKey:@"operations"];
|
|
|
|
NSError *prepErr = nil;
|
|
BOOL prepOk = ((BOOL(*)(id,SEL,id,id,NSError**))objc_msgSend)(
|
|
op, @selector(prepareForInputFeatures:options:error:),
|
|
fp, predOpts, &prepErr);
|
|
printf(" prepare: %s\n", prepOk ? "YES" : "NO");
|
|
if (prepErr) printf(" Error: %s\n", [[prepErr description] UTF8String]);
|
|
fflush(stdout);
|
|
|
|
NSError *execErr = nil;
|
|
BOOL execOk = ((BOOL(*)(id,SEL,void*,NSError**))objc_msgSend)(
|
|
stream, @selector(_executeStream:error:), sh, &execErr);
|
|
printf(" execute: %s\n", execOk ? "YES" : "NO");
|
|
if (execErr) printf(" Error: %s\n", [[execErr description] UTF8String]);
|
|
fflush(stdout);
|
|
|
|
// Read output from the operation
|
|
printf("\n --- W1.3: Read E5 output features ---\n");
|
|
fflush(stdout);
|
|
id e5Result = nil;
|
|
@try {
|
|
e5Result = [op valueForKey:@"outputFeatures"];
|
|
printf(" outputFeatures: %s\n",
|
|
e5Result ? [NSStringFromClass([e5Result class]) UTF8String]
|
|
: "nil");
|
|
} @catch (NSException *ex) {
|
|
printf(" outputFeatures EXCEPTION: %s\n",
|
|
[[ex reason] UTF8String]);
|
|
}
|
|
|
|
if (e5Result && [e5Result conformsToProtocol:@protocol(MLFeatureProvider)]) {
|
|
MLMultiArray *e5Out = [(id<MLFeatureProvider>)e5Result
|
|
featureValueForName:outName].multiArrayValue;
|
|
if (e5Out) {
|
|
float *e5Ptr = (float *)[e5Out dataPointer];
|
|
printf(" E5 first 5: [%.6f %.6f %.6f %.6f %.6f]\n",
|
|
e5Ptr[0], e5Ptr[1], e5Ptr[2], e5Ptr[3], e5Ptr[4]);
|
|
printf(" Mean |e5|: %.6f\n", mean_abs(e5Ptr, outElems));
|
|
|
|
float mad = max_abs_diff(refPtr, e5Ptr, outElems);
|
|
printf(" Max abs diff: %.8f\n", mad);
|
|
printf(" Relative error: %.2e\n",
|
|
mad / (mean_abs(refPtr, outElems) + 1e-10f));
|
|
|
|
if (mad < 1e-3f) {
|
|
printf(" *** VALIDATION PASSED: outputs match ***\n");
|
|
} else if (mad < 1e-1f) {
|
|
printf(" VALIDATION WARNING: small differences (FP16 expected)\n");
|
|
} else {
|
|
printf(" VALIDATION FAILED: outputs diverge!\n");
|
|
}
|
|
} else {
|
|
printf(" E5 output array is nil for key '%s'\n",
|
|
[outName UTF8String]);
|
|
|
|
NSArray *ofNames = [(id<MLFeatureProvider>)e5Result
|
|
featureNames].allObjects;
|
|
printf(" Available features: %s\n",
|
|
[[ofNames description] UTF8String]);
|
|
}
|
|
} else {
|
|
printf(" Cannot read output features\n");
|
|
}
|
|
|
|
// Also read output via outputPorts
|
|
printf("\n --- W1.4: Read via output ports ---\n");
|
|
fflush(stdout);
|
|
@try {
|
|
id outPorts = [op valueForKey:@"outputPorts"];
|
|
printf(" outputPorts: %s (count=%lu)\n",
|
|
outPorts ? [NSStringFromClass([outPorts class]) UTF8String]
|
|
: "nil",
|
|
outPorts ? (unsigned long)[(NSArray *)outPorts count] : 0);
|
|
|
|
if (outPorts && [(NSArray *)outPorts count] > 0) {
|
|
for (NSUInteger pi = 0; pi < [(NSArray *)outPorts count]; pi++) {
|
|
id port = [(NSArray *)outPorts objectAtIndex:pi];
|
|
printf(" Port[%lu]: %s\n", (unsigned long)pi,
|
|
[[port description] UTF8String]);
|
|
@try {
|
|
id portName = [port valueForKey:@"name"];
|
|
printf(" name: %s\n",
|
|
portName ? [(NSString *)portName UTF8String] : "nil");
|
|
} @catch (NSException *ex) { (void)ex; }
|
|
@try {
|
|
id portFD = [port valueForKey:@"featureDescription"];
|
|
printf(" featureDescription: %s\n",
|
|
portFD ? [[portFD description] UTF8String] : "nil");
|
|
} @catch (NSException *ex) { (void)ex; }
|
|
@try {
|
|
id binder = [port valueForKey:@"binder"];
|
|
printf(" binder: %s\n",
|
|
binder ? [NSStringFromClass([binder class])
|
|
UTF8String] : "nil");
|
|
if (binder) {
|
|
@try {
|
|
id fv = [binder valueForKey:@"featureValue"];
|
|
printf(" featureValue: %s\n",
|
|
fv ? [NSStringFromClass([fv class])
|
|
UTF8String] : "nil");
|
|
if (fv) {
|
|
MLMultiArray *ma = [(MLFeatureValue *)fv
|
|
multiArrayValue];
|
|
if (ma) {
|
|
float *ptr = (float *)[ma dataPointer];
|
|
printf(" first 5: [%.6f %.6f %.6f"
|
|
" %.6f %.6f]\n",
|
|
ptr[0], ptr[1], ptr[2],
|
|
ptr[3], ptr[4]);
|
|
float mad2 = max_abs_diff(refPtr, ptr,
|
|
outElems);
|
|
printf(" Max abs diff vs ref: %.8f\n",
|
|
mad2);
|
|
}
|
|
}
|
|
} @catch (NSException *ex) {
|
|
printf(" featureValue EXCEPTION: %s\n",
|
|
[[ex reason] UTF8String]);
|
|
}
|
|
}
|
|
} @catch (NSException *ex) { (void)ex; }
|
|
}
|
|
}
|
|
} @catch (NSException *ex) {
|
|
printf(" outputPorts EXCEPTION: %s\n", [[ex reason] UTF8String]);
|
|
}
|
|
|
|
// Also read input ports
|
|
printf("\n --- W1.5: Inspect input ports ---\n");
|
|
fflush(stdout);
|
|
@try {
|
|
id inPorts = [op valueForKey:@"inputPorts"];
|
|
printf(" inputPorts: %s (count=%lu)\n",
|
|
inPorts ? [NSStringFromClass([inPorts class]) UTF8String]
|
|
: "nil",
|
|
inPorts ? (unsigned long)[(NSArray *)inPorts count] : 0);
|
|
if (inPorts) {
|
|
for (NSUInteger pi = 0; pi < [(NSArray *)inPorts count]; pi++) {
|
|
id port = [(NSArray *)inPorts objectAtIndex:pi];
|
|
printf(" Port[%lu]: %s\n", (unsigned long)pi,
|
|
[[port description] UTF8String]);
|
|
@try {
|
|
printf(" name: %s\n",
|
|
[[(id)[port valueForKey:@"name"] description]
|
|
UTF8String]);
|
|
printf(" portHandle: %p\n",
|
|
(__bridge void *)[port valueForKey:@"portHandle"]);
|
|
} @catch (NSException *ex) { (void)ex; }
|
|
@try {
|
|
id binder = [port valueForKey:@"binder"];
|
|
if (binder) {
|
|
printf(" binder: %s\n",
|
|
[NSStringFromClass([binder class]) UTF8String]);
|
|
printf(" bindingMode: %d\n",
|
|
((char(*)(id,SEL))objc_msgSend)(
|
|
binder, @selector(bindingMode)));
|
|
id dfv = nil;
|
|
@try {
|
|
dfv = [binder valueForKey:@"directlyBoundFeatureValue"];
|
|
} @catch (NSException *ex) { (void)ex; }
|
|
printf(" directlyBound: %s\n",
|
|
dfv ? "YES" : "NO");
|
|
}
|
|
} @catch (NSException *ex) { (void)ex; }
|
|
}
|
|
}
|
|
} @catch (NSException *ex) {
|
|
printf(" inputPorts EXCEPTION: %s\n", [[ex reason] UTF8String]);
|
|
}
|
|
|
|
// Return stream
|
|
[stream setValue:@[op] forKey:@"operations"];
|
|
((void(*)(id,SEL,id))objc_msgSend)(
|
|
streamPool, @selector(putBack:), stream);
|
|
|
|
// ============================================================
|
|
// W1.6: Multi-op output validation
|
|
// ============================================================
|
|
printf("\n --- W1.6: Multi-op output validation ---\n");
|
|
fflush(stdout);
|
|
|
|
{
|
|
NSString *pkg2Path = @"/tmp/ane_sram_512ch_64sp.mlpackage";
|
|
err = nil;
|
|
NSURL *c2 = [MLModel compileModelAtURL:
|
|
[NSURL fileURLWithPath:pkg2Path] error:&err];
|
|
if (err) { printf(" Compile2 FAILED\n"); goto skip_multiop; }
|
|
err = nil;
|
|
MLModel *model2 = [MLModel modelWithContentsOfURL:c2
|
|
configuration:cfg error:&err];
|
|
if (err) { printf(" Load2 FAILED\n"); goto skip_multiop; }
|
|
int ch2 = 512;
|
|
int nElems2 = 1 * ch2 * 1 * sp;
|
|
MLMultiArray *inputArr2 = [[MLMultiArray alloc]
|
|
initWithShape:@[@1, @(ch2), @1, @(sp)]
|
|
dataType:MLMultiArrayDataTypeFloat32 error:nil];
|
|
float *in2Ptr = (float *)[inputArr2 dataPointer];
|
|
for (int i = 0; i < nElems2; i++)
|
|
in2Ptr[i] = cosf((float)i * 0.02f) * 0.3f;
|
|
|
|
NSString *in2Name = [[[[model2 modelDescription] inputDescriptionsByName]
|
|
allKeys] firstObject];
|
|
NSString *out2Name = [[[[model2 modelDescription] outputDescriptionsByName]
|
|
allKeys] firstObject];
|
|
MLDictionaryFeatureProvider *fp2 = [[MLDictionaryFeatureProvider alloc]
|
|
initWithDictionary:@{in2Name: inputArr2} error:nil];
|
|
|
|
// Reference predictions
|
|
err = nil;
|
|
id<MLFeatureProvider> ref1 = [model predictionFromFeatures:fp error:&err];
|
|
err = nil;
|
|
id<MLFeatureProvider> ref2 = [model2 predictionFromFeatures:fp2 error:&err];
|
|
float *ref1Ptr = (float *)[[ref1 featureValueForName:outName].multiArrayValue dataPointer];
|
|
float *ref2Ptr = (float *)[[ref2 featureValueForName:out2Name].multiArrayValue dataPointer];
|
|
|
|
// E5 multi-op stream
|
|
id e5_2 = nil;
|
|
@try { e5_2 = [model2 valueForKey:@"_internalEngine"]; }
|
|
@catch (NSException *e) { (void)e; }
|
|
id pLib2 = nil;
|
|
@try { pLib2 = [e5_2 valueForKey:@"programLibrary"]; }
|
|
@catch (NSException *e) { (void)e; }
|
|
|
|
id op1 = ((id(*)(id,SEL,id,id,id,id,id,unsigned long long))objc_msgSend)(
|
|
[opCls alloc],
|
|
@selector(initWithProgramLibrary:functionName:modelDescription:
|
|
configuration:debugLabel:modelSignpostId:),
|
|
progLib, @"main", [model modelDescription], cfg,
|
|
@"val_op1", (unsigned long long)0);
|
|
id op2 = ((id(*)(id,SEL,id,id,id,id,id,unsigned long long))objc_msgSend)(
|
|
[opCls alloc],
|
|
@selector(initWithProgramLibrary:functionName:modelDescription:
|
|
configuration:debugLabel:modelSignpostId:),
|
|
pLib2, @"main", [model2 modelDescription], cfg,
|
|
@"val_op2", (unsigned long long)0);
|
|
|
|
((BOOL(*)(id,SEL,NSError**))objc_msgSend)(op1, @selector(preloadAndReturnError:), nil);
|
|
((BOOL(*)(id,SEL,NSError**))objc_msgSend)(op2, @selector(preloadAndReturnError:), nil);
|
|
|
|
id stream2 = [streamPool performSelector:@selector(takeOut)];
|
|
Ivar shIvar2 = class_getInstanceVariable([stream2 class], "_streamHandle");
|
|
void *sh2 = (__bridge void *)object_getIvar(stream2, shIvar2);
|
|
|
|
[stream2 setValue:@[op1, op2] forKey:@"operations"];
|
|
|
|
((BOOL(*)(id,SEL,id,id,NSError**))objc_msgSend)(
|
|
op1, @selector(prepareForInputFeatures:options:error:),
|
|
fp, predOpts, nil);
|
|
((BOOL(*)(id,SEL,id,id,NSError**))objc_msgSend)(
|
|
op2, @selector(prepareForInputFeatures:options:error:),
|
|
fp2, predOpts, nil);
|
|
|
|
NSError *mErr = nil;
|
|
BOOL mOk = ((BOOL(*)(id,SEL,void*,NSError**))objc_msgSend)(
|
|
stream2, @selector(_executeStream:error:), sh2, &mErr);
|
|
printf(" Multi-op execute: %s\n", mOk ? "YES" : "NO");
|
|
if (mErr) printf(" Error: %s\n", [[mErr description] UTF8String]);
|
|
fflush(stdout);
|
|
|
|
if (mOk) {
|
|
// Read outputs
|
|
@try {
|
|
id out1 = [op1 valueForKey:@"outputFeatures"];
|
|
id out2 = [op2 valueForKey:@"outputFeatures"];
|
|
|
|
if (out1 && out2) {
|
|
MLMultiArray *ma1 = [(id<MLFeatureProvider>)out1
|
|
featureValueForName:outName].multiArrayValue;
|
|
MLMultiArray *ma2 = [(id<MLFeatureProvider>)out2
|
|
featureValueForName:out2Name].multiArrayValue;
|
|
|
|
if (ma1 && ma2) {
|
|
float *p1 = (float *)[ma1 dataPointer];
|
|
float *p2 = (float *)[ma2 dataPointer];
|
|
|
|
float mad1 = max_abs_diff(ref1Ptr, p1, outElems);
|
|
float mad2 = max_abs_diff(ref2Ptr, p2, nElems2);
|
|
|
|
printf(" Op1 max diff: %.8f (mean_ref=%.6f)\n",
|
|
mad1, mean_abs(ref1Ptr, outElems));
|
|
printf(" Op2 max diff: %.8f (mean_ref=%.6f)\n",
|
|
mad2, mean_abs(ref2Ptr, nElems2));
|
|
|
|
if (mad1 < 1e-3f && mad2 < 1e-3f) {
|
|
printf(" *** MULTI-OP VALIDATION PASSED ***\n");
|
|
} else {
|
|
printf(" MULTI-OP VALIDATION: differences detected\n");
|
|
}
|
|
} else {
|
|
printf(" Could not extract MLMultiArray from outputs\n");
|
|
}
|
|
} else {
|
|
printf(" outputFeatures nil for op1 or op2\n");
|
|
}
|
|
} @catch (NSException *ex) {
|
|
printf(" Output read EXCEPTION: %s\n",
|
|
[[ex reason] UTF8String]);
|
|
}
|
|
}
|
|
|
|
[stream2 setValue:@[op1] forKey:@"operations"];
|
|
((void(*)(id,SEL,id))objc_msgSend)(
|
|
streamPool, @selector(putBack:), stream2);
|
|
}
|
|
skip_multiop:
|
|
|
|
// ============================================================
|
|
// W4: Async stream submission
|
|
// ============================================================
|
|
printf("\n================================================================\n");
|
|
printf(" W4: Async Stream Submission\n");
|
|
printf("================================================================\n\n");
|
|
fflush(stdout);
|
|
|
|
{
|
|
id asyncStream = [streamPool performSelector:@selector(takeOut)];
|
|
Ivar ashIvar = class_getInstanceVariable([asyncStream class], "_streamHandle");
|
|
void *ash = (__bridge void *)object_getIvar(asyncStream, ashIvar);
|
|
|
|
id asyncOp = ((id(*)(id,SEL,id,id,id,id,id,unsigned long long))
|
|
objc_msgSend)([opCls alloc],
|
|
@selector(initWithProgramLibrary:functionName:modelDescription:
|
|
configuration:debugLabel:modelSignpostId:),
|
|
progLib, @"main", [model modelDescription], cfg,
|
|
@"async_op", (unsigned long long)0);
|
|
((BOOL(*)(id,SEL,NSError**))objc_msgSend)(
|
|
asyncOp, @selector(preloadAndReturnError:), nil);
|
|
[asyncStream setValue:@[asyncOp] forKey:@"operations"];
|
|
|
|
((BOOL(*)(id,SEL,id,id,NSError**))objc_msgSend)(
|
|
asyncOp, @selector(prepareForInputFeatures:options:error:),
|
|
fp, predOpts, nil);
|
|
|
|
// Try async submission
|
|
__block BOOL asyncDone = NO;
|
|
__block double asyncMs = 0;
|
|
uint64_t asyncT0 = mach_absolute_time();
|
|
|
|
@try {
|
|
// prepareAsyncSubmissionForInputFeatures
|
|
NSError *asyncPrepErr = nil;
|
|
BOOL asyncPrepOk = ((BOOL(*)(id,SEL,id,id,NSError**))
|
|
objc_msgSend)(asyncStream,
|
|
@selector(prepareAsyncSubmissionForInputFeatures:options:error:),
|
|
fp, predOpts, &asyncPrepErr);
|
|
printf(" prepareAsyncSubmission: %s\n",
|
|
asyncPrepOk ? "YES" : "NO");
|
|
if (asyncPrepErr) printf(" Error: %s\n",
|
|
[[asyncPrepErr description] UTF8String]);
|
|
fflush(stdout);
|
|
|
|
if (asyncPrepOk) {
|
|
((void(*)(id,SEL,void(^)(void)))objc_msgSend)(
|
|
asyncStream, @selector(submitWithCompletionHandler:),
|
|
^{
|
|
asyncMs = tb_ms(mach_absolute_time() - asyncT0);
|
|
asyncDone = YES;
|
|
});
|
|
printf(" Submitted async, waiting...\n");
|
|
fflush(stdout);
|
|
|
|
for (int w = 0; w < 100 && !asyncDone; w++)
|
|
usleep(1000);
|
|
|
|
printf(" Async completed: %s (%.3f ms)\n",
|
|
asyncDone ? "YES" : "TIMEOUT", asyncMs);
|
|
fflush(stdout);
|
|
|
|
if (asyncDone) {
|
|
// Benchmark async vs sync
|
|
int N = 200;
|
|
|
|
// Sync benchmark
|
|
uint64_t t0 = mach_absolute_time();
|
|
for (int i = 0; i < N; i++) {
|
|
((BOOL(*)(id,SEL,id,id,NSError**))objc_msgSend)(
|
|
asyncOp,
|
|
@selector(prepareForInputFeatures:options:error:),
|
|
fp, predOpts, nil);
|
|
((BOOL(*)(id,SEL,void*,NSError**))objc_msgSend)(
|
|
asyncStream,
|
|
@selector(_executeStream:error:), ash, nil);
|
|
}
|
|
double syncMs = tb_ms(mach_absolute_time() - t0) / N;
|
|
|
|
// Async benchmark
|
|
t0 = mach_absolute_time();
|
|
for (int i = 0; i < N; i++) {
|
|
((BOOL(*)(id,SEL,id,id,NSError**))objc_msgSend)(
|
|
asyncOp,
|
|
@selector(prepareForInputFeatures:options:error:),
|
|
fp, predOpts, nil);
|
|
((BOOL(*)(id,SEL,id,id,NSError**))objc_msgSend)(
|
|
asyncStream,
|
|
@selector(prepareAsyncSubmissionForInputFeatures:
|
|
options:error:),
|
|
fp, predOpts, nil);
|
|
|
|
__block BOOL done = NO;
|
|
((void(*)(id,SEL,void(^)(void)))objc_msgSend)(
|
|
asyncStream,
|
|
@selector(submitWithCompletionHandler:),
|
|
^{ done = YES; });
|
|
while (!done) usleep(100);
|
|
}
|
|
double asyncBenchMs = tb_ms(mach_absolute_time() - t0) / N;
|
|
|
|
printf(" Sync: %.4f ms/eval\n", syncMs);
|
|
printf(" Async (wait): %.4f ms/eval\n", asyncBenchMs);
|
|
}
|
|
}
|
|
} @catch (NSException *ex) {
|
|
printf(" Async EXCEPTION: %s\n", [[ex reason] UTF8String]);
|
|
}
|
|
|
|
[asyncStream setValue:@[asyncOp] forKey:@"operations"];
|
|
((void(*)(id,SEL,id))objc_msgSend)(
|
|
streamPool, @selector(putBack:), asyncStream);
|
|
}
|
|
|
|
// ============================================================
|
|
// W5: Port-Based Data Flow
|
|
// ============================================================
|
|
printf("\n================================================================\n");
|
|
printf(" W5: Port-Based Data Flow Investigation\n");
|
|
printf("================================================================\n\n");
|
|
fflush(stdout);
|
|
|
|
{
|
|
id portOp = ((id(*)(id,SEL,id,id,id,id,id,unsigned long long))
|
|
objc_msgSend)([opCls alloc],
|
|
@selector(initWithProgramLibrary:functionName:modelDescription:
|
|
configuration:debugLabel:modelSignpostId:),
|
|
progLib, @"main", [model modelDescription], cfg,
|
|
@"port_op", (unsigned long long)0);
|
|
((BOOL(*)(id,SEL,NSError**))objc_msgSend)(
|
|
portOp, @selector(preloadAndReturnError:), nil);
|
|
|
|
// Inspect ports before prepare
|
|
printf(" --- Before prepare ---\n");
|
|
@try {
|
|
id inP = [portOp valueForKey:@"inputPorts"];
|
|
id outP = [portOp valueForKey:@"outputPorts"];
|
|
id stP = [portOp valueForKey:@"statePorts"];
|
|
printf(" inputPorts: %lu, outputPorts: %lu, statePorts: %lu\n",
|
|
inP ? (unsigned long)[(NSArray *)inP count] : 0,
|
|
outP ? (unsigned long)[(NSArray *)outP count] : 0,
|
|
stP ? (unsigned long)[(NSArray *)stP count] : 0);
|
|
|
|
if (inP) {
|
|
for (id p in (NSArray *)inP) {
|
|
printf(" in: %s portHandle=%p name=%s\n",
|
|
[NSStringFromClass([p class]) UTF8String],
|
|
(__bridge void *)[p valueForKey:@"portHandle"],
|
|
[[(id)[p valueForKey:@"name"] description] UTF8String]);
|
|
}
|
|
}
|
|
if (outP) {
|
|
for (id p in (NSArray *)outP) {
|
|
printf(" out: %s portHandle=%p name=%s\n",
|
|
[NSStringFromClass([p class]) UTF8String],
|
|
(__bridge void *)[p valueForKey:@"portHandle"],
|
|
[[(id)[p valueForKey:@"name"] description] UTF8String]);
|
|
@try {
|
|
id fd = [p valueForKey:@"featureDescription"];
|
|
if (fd) printf(" featureDesc: %s\n",
|
|
[[fd description] UTF8String]);
|
|
} @catch (NSException *ex) { (void)ex; }
|
|
}
|
|
}
|
|
} @catch (NSException *ex) {
|
|
printf(" Port inspection EXCEPTION: %s\n",
|
|
[[ex reason] UTF8String]);
|
|
}
|
|
|
|
// Prepare and inspect after
|
|
((BOOL(*)(id,SEL,id,id,NSError**))objc_msgSend)(
|
|
portOp, @selector(prepareForInputFeatures:options:error:),
|
|
fp, predOpts, nil);
|
|
|
|
printf("\n --- After prepare ---\n");
|
|
@try {
|
|
id inP = [portOp valueForKey:@"inputPorts"];
|
|
if (inP) {
|
|
for (id p in (NSArray *)inP) {
|
|
id binder = [p valueForKey:@"binder"];
|
|
BOOL directBound = ((BOOL(*)(id,SEL))objc_msgSend)(
|
|
p, @selector(boundFeatureDirectly));
|
|
printf(" in: name=%s directBound=%s binder=%s\n",
|
|
[[(id)[p valueForKey:@"name"] description] UTF8String],
|
|
directBound ? "YES" : "NO",
|
|
binder ? [NSStringFromClass([binder class])
|
|
UTF8String] : "nil");
|
|
if (binder) {
|
|
char mode = ((char(*)(id,SEL))objc_msgSend)(
|
|
binder, @selector(bindingMode));
|
|
printf(" bindingMode=%d\n", (int)mode);
|
|
}
|
|
}
|
|
}
|
|
id outP = [portOp valueForKey:@"outputPorts"];
|
|
if (outP) {
|
|
for (id p in (NSArray *)outP) {
|
|
BOOL directBound = ((BOOL(*)(id,SEL))objc_msgSend)(
|
|
p, @selector(boundFeatureDirectly));
|
|
BOOL obDirectBound = ((BOOL(*)(id,SEL))objc_msgSend)(
|
|
p, @selector(outputBackingWasDirectlyBound));
|
|
printf(" out: name=%s directBound=%s"
|
|
" outputBackingDirectBound=%s\n",
|
|
[[(id)[p valueForKey:@"name"] description] UTF8String],
|
|
directBound ? "YES" : "NO",
|
|
obDirectBound ? "YES" : "NO");
|
|
id binder = [p valueForKey:@"binder"];
|
|
if (binder) {
|
|
printf(" binder: %s\n",
|
|
[NSStringFromClass([binder class]) UTF8String]);
|
|
@try {
|
|
id ob = [binder valueForKey:@"outputBacking"];
|
|
printf(" outputBacking: %s\n",
|
|
ob ? [NSStringFromClass([ob class])
|
|
UTF8String] : "nil");
|
|
} @catch (NSException *ex) { (void)ex; }
|
|
}
|
|
}
|
|
}
|
|
} @catch (NSException *ex) {
|
|
printf(" Post-prepare EXCEPTION: %s\n",
|
|
[[ex reason] UTF8String]);
|
|
}
|
|
}
|
|
|
|
// ============================================================
|
|
// Summary
|
|
// ============================================================
|
|
printf("\n================================================================\n");
|
|
printf(" SUMMARY\n");
|
|
printf("================================================================\n");
|
|
printf(" W1: Output validation -- see above\n");
|
|
printf(" W2: API documentation -- complete (all classes dumped)\n");
|
|
printf(" W4: Async submission -- see above\n");
|
|
printf(" W5: Port data flow -- see above\n");
|
|
printf("================================================================\n");
|
|
printf("\nDone.\n");
|
|
}
|
|
return 0;
|
|
}
|