def model2c(model, file, function_name, malloc=False): model_inputs, model_outputs = get_model_io_names(model) s = '#include <math.h> \n ' s += '#include <string.h> \n' s += '#include "./include/k2c_include.h" \n' s += '#include "./include/k2c_tensor_include.h" \n' s += '\n \n' file.write(s) print('Gathering Weights') stack_vars, malloc_vars, static_vars = Weights2C(model, function_name, malloc).write_weights() layers = Layers2C(model, malloc).write_layers() function_signature = 'void ' + function_name + '(' function_signature += ', '.join( ['k2c_tensor* ' + in_nm + '_input' for in_nm in model_inputs]) + ', ' function_signature += ', '.join( ['k2c_tensor* ' + out_nm + '_output' for out_nm in model_outputs]) if len(malloc_vars.keys()): function_signature += ',' + ','.join( ['float* ' + key for key in malloc_vars.keys()]) function_signature += ')' file.write(static_vars + '\n\n') file.write(function_signature) file.write(' { \n\n') file.write(stack_vars) file.write(layers) file.write('\n } \n\n') stateful = len(static_vars) > 0 init_sig = write_function_initialize(file, function_name, malloc_vars) term_sig = write_function_terminate(file, function_name, malloc_vars) if stateful: reset_sig = write_function_reset(file, function_name) with open(function_name + '1.h', 'x+') as header: header.write('#pragma once \n') header.write('#include "./include/k2c_tensor_include.h" \n') header.write(function_signature + '; \n') header.write(init_sig + '; \n') header.write(term_sig + '; \n') if stateful: header.write(reset_sig + '; \n') return malloc_vars.keys(), stateful
def model2c(model, function_name, malloc=False, verbose=True): """Generates C code for model Writes main function definition to "function_name.c" and a public header with declarations to "function_name.h" Args: model (keras Model): model to convert function_name (str): name of C function malloc (bool): whether to allocate variables on the stack or heap verbose (bool): whether to print info to stdout Returns: malloc_vars (list): names of variables loaded at runtime and stored on the heap stateful (bool): whether the model must maintain state between calls """ model_inputs, model_outputs = get_model_io_names(model) includes = '#include <math.h> \n ' includes += '#include <string.h> \n' includes += '#include "./include/k2c_include.h" \n' includes += '#include "./include/k2c_tensor_include.h" \n' includes += '\n \n' if verbose: print('Gathering Weights') stack_vars, malloc_vars, static_vars = Weights2C( model, function_name, malloc).write_weights(verbose) stateful = len(static_vars) > 0 layers = Layers2C(model, malloc).write_layers(verbose) function_signature = 'void ' + function_name + '(' function_signature += ', '.join( ['k2c_tensor* ' + in_nm + '_input' for in_nm in model_inputs]) + ', ' function_signature += ', '.join( ['k2c_tensor* ' + out_nm + '_output' for out_nm in model_outputs]) if len(malloc_vars.keys()): function_signature += ',' + ','.join( ['float* ' + key for key in malloc_vars.keys()]) function_signature += ')' init_sig, init_fun = gen_function_initialize(function_name, malloc_vars) term_sig, term_fun = gen_function_terminate(function_name, malloc_vars) reset_sig, reset_fun = gen_function_reset(function_name) with open(function_name + '.c', 'x+') as source: source.write(includes) source.write(static_vars + '\n\n') source.write(function_signature) source.write(' { \n\n') source.write(stack_vars) source.write(layers) source.write('\n } \n\n') source.write(init_fun) source.write(term_fun) if stateful: source.write(reset_fun) with open(function_name + '.h', 'x+') as header: header.write('#pragma once \n') header.write('#include "./include/k2c_tensor_include.h" \n') header.write(function_signature + '; \n') header.write(init_sig + '; \n') header.write(term_sig + '; \n') if stateful: header.write(reset_sig + '; \n') try: subprocess.run(['astyle', '-n', function_name + '.h']) subprocess.run(['astyle', '-n', function_name + '.c']) except FileNotFoundError: print("astyle not found, {} and {} will not be auto-formatted".format( function_name + ".h", function_name + ".c")) return malloc_vars.keys(), stateful
def make_test_suite(model, function_name, malloc_vars, num_tests=10, stateful=False, tol=1e-5): print('Writing tests') input_shape = [] # output_shape = [] model_inputs, model_outputs = get_model_io_names(model) num_inputs = len(model_inputs) num_outputs = len(model_outputs) for i in range(num_inputs): input_shape.insert( i, model.inputs[i].shape[:] if stateful else model.inputs[i].shape[1:]) # for i in range(num_outputs): # output_shape.insert(i, model.outputs[i].shape[1:]) file = open(function_name + '_test_suite.c', "x+") s = '#include <stdio.h> \n' s += '#include <math.h> \n' s += '#include <time.h> \n' s += '#include "./include/k2c_include.h" \n' s += '#include "' + function_name + '.h" \n\n' s += 'float maxabs(k2c_tensor *tensor1, k2c_tensor *tensor2);\n' s += 'struct timeval GetTimeStamp(); \n \n' file.write(s) s = 'int main(){\n' file.write(s) for i in range(num_tests): if i == num_tests // 2 and stateful: model.reset_states() # generate random input and write to file ct = 0 while True: rand_inputs = [] for j, _ in enumerate(model_inputs): rand_input = 4 * np.random.random(input_shape[j]) - 2 if not stateful: rand_input = rand_input[np.newaxis, ...] rand_inputs.insert(j, rand_input) # make predictions outputs = model.predict(rand_inputs) if np.isfinite(outputs).all(): break else: ct += 1 if ct > 20: raise Exception('Cannot find inputs to the \ network that result in a finite output') for j, _ in enumerate(model_inputs): file.write( Weights2C.array2c( (rand_inputs[j][0, :]), 'test' + str(i + 1) + '_' + model_inputs[j] + '_input')) # write predictions if not isinstance(outputs, list): outputs = [outputs] for j, _ in enumerate(model_outputs): output = outputs[j][0, :] file.write( Weights2C.array2c( output, 'keras_' + model_outputs[j] + '_test' + str(i + 1))) file.write( Weights2C.array2c( np.zeros(output.shape), 'c_' + model_outputs[j] + '_test' + str(i + 1))) s = ' float errors[' + str(num_tests * num_outputs) + '];\n' s += ' size_t num_tests = ' + str(num_tests) + '; \n' s += 'size_t num_outputs = ' + str(num_outputs) + '; \n' for var in malloc_vars: s += 'float* ' + var + '; \n' init_sig = function_name + '_initialize(' + \ ','.join(['&' + var for var in malloc_vars]) + '); \n' s += init_sig if stateful: reset_sig = function_name + '_reset_states();' s += reset_sig s += 'clock_t t0 = clock(); \n' file.write(s) for i in range(num_tests): if i == num_tests // 2 and stateful: file.write(reset_sig) s = function_name + '(' model_in = [ '&test' + str(i + 1) + '_' + inp + '_input' for inp in model_inputs ] model_out = [ '&c_' + outp + '_test' + str(i + 1) for outp in model_outputs ] s += ','.join(model_in + model_out + list(malloc_vars)) s += '); \n' file.write(s) file.write('\n') s = 'clock_t t1 = clock(); \n' s += 'printf("Average time over ' + str(num_tests) + \ ' tests: %e s \\n\",(double)(t1-t0)/(double)CLOCKS_PER_SEC/(double)' + \ str(num_tests) + '); \n' file.write(s) for i in range(num_tests): for j, _ in enumerate(model_outputs): s = 'errors[' + str(i*num_outputs+j) + '] = maxabs(&keras_' + model_outputs[j] + '_test' + \ str(i+1) + ',&c_' + \ model_outputs[j] + '_test' + str(i+1) + '); \n' file.write(s) s = 'float maxerror = errors[0]; \n' s += 'for(size_t i=1; i< num_tests*num_outputs;i++){ \n' s += 'if (errors[i] > maxerror) { \n' s += 'maxerror = errors[i];}} \n' s += 'printf("Max absolute error for ' + \ str(num_tests) + ' tests: %e \\n", maxerror);\n' file.write(s) # s = 'for(size_t i=0; i< num_tests*num_outputs;i++){ \n' # s += 'printf(\"Error, test %d: %f \\n \",i,errors[i]);} \n' # file.write(s) s = function_name + '_terminate(' + ','.join(malloc_vars) + '); \n' s += 'if (maxerror > ' + str(tol) + ') { \n' s += 'return 1;} \n' s += 'return 0;\n} \n\n' file.write(s) s = """float maxabs(k2c_tensor *tensor1, k2c_tensor *tensor2){ \n float x = 0; \n float y = 0; \n for(size_t i=0; i<tensor1->numel; i++){\n y = fabs(tensor1->array[i]-tensor2->array[i]); if (y>x) {x=y;}} return x;}\n\n""" file.write(s) file.close()
def make_test_suite(model, function_name, malloc_vars, num_tests=10, stateful=False, verbose=True, tol=1e-5): """Generates code to test the generated C function. Generates random inputs to the model, and gets the corresponding predictions for them. Writes input/output pairs to a C file, along with code to call the generated C function and compare the true outputs with the outputs from the generated code. Writes the test function to a file `<function_name>_test_suite.c` Args: model (keras Model): model being converted to C function_name (str): name of the neural net function being generated malloc_vars (dict): dictionary of names and values of variables allocated on the heap num_tests (int): number of tests to generate stateful (bool): whether the model contains layers that maintain state between calls. verbose (bool): whether to print output tol (float): tolerance for passing tests. Tests pass if the maximum error over all elements between the true output and generated code output is less than tol. Returns: None """ if verbose: print('Writing tests') input_shape = [] # output_shape = [] model_inputs, model_outputs = get_model_io_names(model) num_inputs = len(model_inputs) num_outputs = len(model_outputs) for i in range(num_inputs): temp_input_shape = np.array(model.inputs[i].shape) temp_input_shape = np.where(temp_input_shape == None, 1, temp_input_shape) if stateful: temp_input_shape = temp_input_shape[:] else: temp_input_shape = temp_input_shape[1:] input_shape.insert(i, temp_input_shape) # for i in range(num_outputs): # output_shape.insert(i, model.outputs[i].shape[1:]) file = open(function_name + '_test_suite.c', "x+") s = '#include <stdio.h> \n' s += '#include <math.h> \n' s += '#include <time.h> \n' s += '#include "./include/k2c_include.h" \n' s += '#include "' + function_name + '.h" \n\n' s += 'float maxabs(k2c_tensor *tensor1, k2c_tensor *tensor2);\n' s += 'struct timeval GetTimeStamp(); \n \n' file.write(s) s = 'int main(){\n' file.write(s) for i in range(num_tests): if i == num_tests // 2 and stateful: model.reset_states() # generate random input and write to file ct = 0 while True: rand_inputs = [] for j, _ in enumerate(model_inputs): rand_input = 4 * np.random.random(input_shape[j]) - 2 if not stateful: rand_input = rand_input[np.newaxis, ...] rand_inputs.insert(j, rand_input) # make predictions outputs = model.predict(rand_inputs) if np.isfinite(outputs).all(): break else: ct += 1 if ct > 20: raise Exception('Cannot find inputs to the \ network that result in a finite output') for j, _ in enumerate(model_inputs): file.write( Weights2C.array2c( (rand_inputs[j][0, :]), 'test' + str(i + 1) + '_' + model_inputs[j] + '_input')) # write predictions if not isinstance(outputs, list): outputs = [outputs] for j, _ in enumerate(model_outputs): output = outputs[j][0, :] file.write( Weights2C.array2c( output, 'keras_' + model_outputs[j] + '_test' + str(i + 1))) file.write( Weights2C.array2c( np.zeros(output.shape), 'c_' + model_outputs[j] + '_test' + str(i + 1))) s = ' float errors[' + str(num_tests * num_outputs) + '];\n' s += ' size_t num_tests = ' + str(num_tests) + '; \n' s += 'size_t num_outputs = ' + str(num_outputs) + '; \n' for var in malloc_vars: s += 'float* ' + var + '; \n' init_sig = function_name + '_initialize(' + \ ','.join(['&' + var for var in malloc_vars]) + '); \n' s += init_sig if stateful: reset_sig = function_name + '_reset_states();' s += reset_sig s += 'clock_t t0 = clock(); \n' file.write(s) for i in range(num_tests): if i == num_tests // 2 and stateful: file.write(reset_sig) s = function_name + '(' model_in = [ '&test' + str(i + 1) + '_' + inp + '_input' for inp in model_inputs ] model_out = [ '&c_' + outp + '_test' + str(i + 1) for outp in model_outputs ] s += ','.join(model_in + model_out + list(malloc_vars)) s += '); \n' file.write(s) file.write('\n') s = 'clock_t t1 = clock(); \n' s += 'printf("Average time over ' + str(num_tests) + \ ' tests: %e s \\n\", \n (double)(t1-t0)/(double)CLOCKS_PER_SEC/(double)' + \ str(num_tests) + '); \n' file.write(s) for i in range(num_tests): for j, _ in enumerate(model_outputs): s = 'errors[' + str(i*num_outputs+j) + '] = maxabs(&keras_' + model_outputs[j] + '_test' + \ str(i+1) + ',&c_' + \ model_outputs[j] + '_test' + str(i+1) + '); \n' file.write(s) s = 'float maxerror = errors[0]; \n' s += 'for(size_t i=1; i< num_tests*num_outputs;i++){ \n' s += 'if (errors[i] > maxerror) { \n' s += 'maxerror = errors[i];}} \n' s += 'printf("Max absolute error for ' + \ str(num_tests) + ' tests: %e \\n", maxerror);\n' file.write(s) # s = 'for(size_t i=0; i< num_tests*num_outputs;i++){ \n' # s += 'printf(\"Error, test %d: %f \\n \",i,errors[i]);} \n' # file.write(s) s = function_name + '_terminate(' + ','.join(malloc_vars) + '); \n' s += 'if (maxerror > ' + str(tol) + ') { \n' s += 'return 1;} \n' s += 'return 0;\n} \n\n' file.write(s) s = """float maxabs(k2c_tensor *tensor1, k2c_tensor *tensor2){ \n float x = 0; \n float y = 0; \n for(size_t i=0; i<tensor1->numel; i++){\n y = fabs(tensor1->array[i]-tensor2->array[i]); if (y>x) {x=y;}} return x;}\n\n""" file.write(s) file.close() if not subprocess.run(['astyle', '--version']).returncode: subprocess.run(['astyle', '-n', function_name + '_test_suite.c'])