Files
storyteller-ml/animation/MocapNET-kasisnu/src/Tensorflow2/tensorflow2.h
T
2024-01-18 19:24:33 +05:30

592 lines
19 KiB
C

/** @file tensorflow2.h
* @brief A header-only tensorflow 2.x wrapper automization to make your neuralnetwork-lives easier.
* Repository : https://github.com/AmmarkoV/PThreadWorkerPool
* @author Ammar Qammaz (AmmarkoV)
*/
#ifndef TENSORFLOW2_H_INCLUDED
#define TENSORFLOW2_H_INCLUDED
#ifdef __cplusplus
extern "C"
{
#endif
#include <tensorflow/c/c_api.h> // TensorFlow C API header
#include <stdio.h>
#include <stdlib.h>
//#define NORMAL "\033[0m"
//#define BLACK "\033[30m" /* Black */
//#define RED "\033[31m" /* Red */
//#define GREEN "\033[32m" /* Green */
//#define YELLOW "\033[33m" /* Yellow */
typedef unsigned short float16;
/**
* @brief A structure that holds all of the relevant information for a tensorflow instance
* This is used to simplify context switching and reduce the complexity of the library
*/
struct Tensorflow2Instance
{
char modelPath[1025];
//Network instance
//-------------------------
TF_Graph* graph;
TF_Status* status;
TF_Session* session;
TF_SessionOptions* sessionOptions;
//Input
//-------------------------
TF_Output input_operation;
TF_Output* input;
TF_Tensor* inputTensor;
TF_Tensor** inputValues;
char inputIsHalfFloats;
unsigned int inputElements;
int numberOfInputTensors;
//Output
//-------------------------
TF_Output output_operation;
TF_Output* output;
TF_Tensor* outputTensor;
TF_Tensor** outputValues;
char outputIsHalfFloats;
unsigned int outputElements;
int numberOfOutputTensors;
//Buffer for itermediate data
//-------------------------
unsigned int bufferSize;
float * buffer;
};
static void tf2_noOpDeallocator(void* data, size_t a, void* b) {}
static void tf2_deallocateBuffer(void* data, size_t)
{
if (data!=0)
{
fprintf(stderr,"tf2_deallocateBuffer\n");
free(data);
}
}
#if INTEL_OPTIMIZATIONS
#include <immintrin.h>
#include <emmintrin.h>
//https://software.intel.com/content/www/us/en/develop/documentation/cpp-compiler-developer-guide-and-reference/top/compiler-reference/intrinsics/intrinsics-for-converting-half-floats/intrinsics-for-converting-half-floats-1.html
//AVX
//This intrinsic takes a 32-bit float value, x , and converts it to a half-float value, which is returned.
//__m128 _mm_cvtph_ps(__m128i x);
//This intrinsic takes four packed half-float values and converts them to four 32-bit float values, which are returned. The upper 64-bits of x are ignored. The lower 64-bits are taken as four 16-bit float values for conversion.
//__m128i _mm_cvtps_ph(_m128 x, int imm);
static float16 convertFloat32ToFloat16(float f)
{
/*
(_MM_FROUND_TO_NEAREST_INT |_MM_FROUND_NO_EXC) // round to nearest, and suppress exceptions
(_MM_FROUND_TO_NEG_INF |_MM_FROUND_NO_EXC) // round down, and suppress exceptions
(_MM_FROUND_TO_POS_INF |_MM_FROUND_NO_EXC) // round up, and suppress exceptions
(_MM_FROUND_TO_ZERO |_MM_FROUND_NO_EXC) // truncate, and suppress exceptions
_MM_FROUND_CUR_DIRECTION // use MXCSR.RC; see _MM_SET_ROUNDING_MODE
* */
int imm = _MM_FROUND_TO_NEAREST_INT |_MM_FROUND_NO_EXC;
//https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_cvtss_sh&expand=4979,5019,4979,1898
return _cvtss_sh(f,imm);
}
static float convertFloat16ToFloat32(float16 h)
{
//https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_cvtsh_ss&expand=4979,5019,4979,1898,1873
return _cvtsh_ss(h);
}
#else
static float16 convertFloat32ToFloat16(float f)
{
//https://en.wikipedia.org/wiki/Half-precision_floating-point_format
//https://en.wikipedia.org/wiki/IEEE_754-2008_revision
//https://stackoverflow.com/questions/3026441/float32-to-float16/3026505
#warning "convertFloat32ToFloat16 is not correctly implemented on non SSE equipped builds"
unsigned int * fltInt32 = ( unsigned int * ) &f;
float16 h;
h = (*fltInt32 >> 31) << 5;
unsigned short tmp = (*fltInt32 >> 23) & 0xff;
tmp = (tmp - 0x70) & ((unsigned int)((int)(0x70 - tmp) >> 4) >> 27);
h = (h | tmp) << 10;
h |= (*fltInt32 >> 13) & 0x3ff;
return h;
}
static float convertFloat16ToFloat32(float16 h)
{
#warning "convertFloat16ToFloat32 is not properly implemented on non SSE equipped builds"
fprintf(stderr,"convertFloat16ToFloat32 not working.. ");
//http://www.fox-toolkit.org/ftp/fasthalffloatconversion.pdf
float f = ((h&0x8000)<<16) | (((h&0x7c00)+0x1C000)<<13) | ((h&0x03FF)<<13);
return f;
}
#endif
static int tf2_checkModelUsingExternalTool(const char * path)
{
char commandToCheck[4096];
snprintf(commandToCheck,2000,"saved_model_cli show --dir %s --tag_set serve --signature_def serving_default",path);
int i = system(commandToCheck);
return (i==0);
}
static int tf2_setCPUExecutionSessionConfiguration(struct Tensorflow2Instance * tf2i)
{
//How do you end up with this byte array you might ask ?
unsigned char config[] = {0xa,0x7,0xa,0x3,0x43,0x50,0x55,0x10,0x1,0xa,0x7,0xa,0x3,0x47,0x50,0x55,0x10,0x0,0x38,0x1};
//Good Question, you use the python code and extract the configuration bytes and copy paste them here..
//https://github.com/FORTH-ModelBasedTracker/MocapNET/blob/master/src/Tensorflow/createTensorflowConfigurationForC.py
/*net->session = tf.ConfigProto(
device_count={'CPU' : 1, 'GPU' : 0},
allow_soft_placement=True,
log_device_placement=False
);*/
TF_SetConfig(tf2i->sessionOptions, (void*)config, 20 , tf2i->status);
return (TF_GetCode(tf2i->status) == TF_OK);
}
static char tf2_fileExists(const char * filename)
{
FILE *fp = fopen(filename,"r");
if( fp ) { /* exists */ fclose(fp); return 1; }
return 0;
}
//https://github.com/AmmarkoV/AmmarServer/blob/master/src/AmmServerlib/AString/AString.c#L235
static char * tf2_readFileToMem(const char * filename,unsigned int *length )
{
if (length==0) { return 0; }
if (filename==0) { return 0; }
//-----------------------------------------
*length = 0;
FILE * pFile = fopen ( filename , "rb" );
if (pFile==0)
{
fprintf(stderr,"ERROR tf2_readFileToMem: failed\n");
fprintf(stderr,"Could not read file %s \n",filename);
return 0;
}
// obtain file size:
fseek (pFile , 0 , SEEK_END);
unsigned long lSize = ftell (pFile);
rewind (pFile);
// allocate memory to contain the whole file:
unsigned long bufferSize = sizeof(char)*(lSize+1);
char * buffer = (char*) malloc (bufferSize);
if (buffer == 0 )
{
fprintf(stderr,"ERROR tf2_readFileToMem: Could not allocate enough memory for file %s \n",filename);
fclose(pFile);
return 0;
}
// copy the file into the buffer:
size_t result = fread (buffer,1,lSize,pFile);
if (result != lSize)
{
free(buffer);
fprintf(stderr,"ERROR tf2_readFileToMem: Could not read the whole file onto memory %s \n",filename);
fclose(pFile);
return 0;
}
/* the whole file is now loaded in the memory buffer. */
// terminate
fclose (pFile);
buffer[lSize]=0; //Null Terminate Buffer!
*length = (unsigned int) lSize;
return buffer;
}
static int tf2_allocateIOTensors(
struct Tensorflow2Instance * tf2i,
const char * inputTensorName,
const char * outputTensorName,
unsigned int inputElements,
unsigned int outputElements,
unsigned int forceCPU
)
{
//Allocate input tensor
//--------------------------------------------------------------------------------
tf2i->numberOfInputTensors = 1;
tf2i->input = (TF_Output*) malloc(sizeof(TF_Output) * tf2i->numberOfInputTensors);
tf2i->input_operation = {TF_GraphOperationByName(tf2i->graph,inputTensorName), 0};
if(tf2i->input_operation.oper == NULL)
{
fprintf(stderr,"ERROR: Failed TF_GraphOperationByName %s\n",inputTensorName);
return 0;
}
tf2i->input[0] = tf2i->input_operation;
//--------------------------------------------------------------------------------
//Allocate Output tensor
//--------------------------------------------------------------------------------
tf2i->numberOfOutputTensors = 1;
tf2i->output = (TF_Output*) malloc(sizeof(TF_Output) * tf2i->numberOfOutputTensors);
tf2i->output_operation = {TF_GraphOperationByName(tf2i->graph,outputTensorName), 0};
if(tf2i->output_operation.oper == NULL)
{
fprintf(stderr,"ERROR: Failed TF_GraphOperationByName %s\n",outputTensorName);
return 0;
}
tf2i->output[0] = tf2i->output_operation;
//--------------------------------------------------------------------------------
//Allocate memory for input & output data
tf2i->inputValues = (TF_Tensor**) malloc(sizeof(TF_Tensor*)*tf2i->numberOfInputTensors);
tf2i->outputValues = (TF_Tensor**) malloc(sizeof(TF_Tensor*)*tf2i->numberOfOutputTensors);
return ( (tf2i->inputValues!=0) && (tf2i->outputValues!=0) );
}
static int tf2_loadFrozenGraph(
struct Tensorflow2Instance * tf2i,
const char* graphPath,
const char * inputTensorName,
const char * outputTensorName,
unsigned int inputElements,
unsigned int outputElements,
unsigned int forceCPU
)
{
if (graphPath == 0)
{
fprintf(stderr,"Cannot load graph with null path..\n");
return 0;
}
fprintf(stderr,"LoadGraph %s using TensorFlow Version: %s\n",graphPath,TF_Version());
unsigned int graphInMemorySize=0;
char * graphInMemory = tf2_readFileToMem(graphPath,&graphInMemorySize);
if (graphInMemory == 0)
{
fprintf(stderr,"Cannot read buffer from file %s ..\n",graphPath);
return 0;
}
//Remember parameters..
snprintf(tf2i->modelPath,1024,"%s",graphPath);
tf2i->inputElements=inputElements;
tf2i->outputElements=outputElements;
tf2i->graph = TF_NewGraph();
tf2i->status = TF_NewStatus();
TF_ImportGraphDefOptions* opts = TF_NewImportGraphDefOptions();
TF_Buffer* tfBufferWithGraph = TF_NewBuffer();
tfBufferWithGraph->data = graphInMemory;
tfBufferWithGraph->length = graphInMemorySize;
tfBufferWithGraph->data_deallocator = tf2_deallocateBuffer;
TF_GraphImportGraphDef(tf2i->graph,tfBufferWithGraph,opts,tf2i->status);
TF_DeleteImportGraphDefOptions(opts);
if (TF_GetCode(tf2i->status) != TF_OK)
{
fprintf(stderr,"ERROR importing graph definition.. (%u)\n",TF_GetCode(tf2i->status));
switch (TF_GetCode(tf2i->status))
{
case TF_INVALID_ARGUMENT :
fprintf(stderr,"Invalid Argument in graph..\n");
break;
};
TF_DeleteGraph(tf2i->graph);
tf2i->graph = 0;
}
TF_DeleteBuffer(tfBufferWithGraph);
graphInMemorySize=0;
//--------------------------------------------------------------------------------------------------------------
tf2i->sessionOptions = TF_NewSessionOptions();
if (forceCPU) { tf2_setCPUExecutionSessionConfiguration(tf2i); }
tf2i->session = TF_NewSession(tf2i->graph,tf2i->sessionOptions,tf2i->status);
//--------------------------------------------------------------------------------------------------------------
return tf2_allocateIOTensors(
tf2i,
inputTensorName,
outputTensorName,
inputElements,
outputElements,
forceCPU
);
}
static int tf2_loadModel(
struct Tensorflow2Instance * tf2i,
const char * path,
const char * inputTensor,
unsigned int inputElements,
unsigned int outputElements,
unsigned int forceCPU
)
{
//https://medium.com/analytics-vidhya/deploying-tensorflow-2-1-as-c-c-executable-1d090845055c
//Tensorflow boilerplate names, fingers crossed that they don't change them..
char inputTensorName[1025]={0}; //= {"serving_default_input_front"}; //Default input layer
snprintf(inputTensorName,1024,"serving_default_%s",inputTensor);
//snprintf(inputTensorName,1024,"%s",inputTensor);
char outputTensorName[1025]={0}; //= {"StatefulPartitionedCall"}; //Default output layer
snprintf(outputTensorName,1024,"StatefulPartitionedCall");
//--------------------------------------------------------------------------
int ntags = 1;
//char tags[1025]={0}; //= {"StatefulPartitionedCall"}; //Default output layer
//snprintf(tags,1024,"serve");
//const char* tags = "serve"; // default model serving tag; can change in future
const char* tags[] = {"serve"};
//--------------------------------------------------------------------------
//--------------------------------------------------------------------------
fprintf(stderr,"Loading model : %s | Input : %s | Output : %s\n",path,inputTensorName,outputTensorName);
//--------------------------------------------------------------------------
//Remember parameters..
snprintf(tf2i->modelPath,1024,"%s",path);
tf2i->inputElements=inputElements;
tf2i->outputElements=outputElements;
//Allocate stuff..
tf2i->graph = TF_NewGraph();
tf2i->status = TF_NewStatus();
tf2i->sessionOptions = TF_NewSessionOptions();
if (forceCPU) { tf2_setCPUExecutionSessionConfiguration(tf2i); }
TF_Buffer* runOptions = NULL;
//Actual loading from a saved model assuming :
//https://www.tensorflow.org/api_docs/python/tf/saved_model/save
tf2i->session = TF_LoadSessionFromSavedModel(
tf2i->sessionOptions,
runOptions,
path,
tags,
ntags,
tf2i->graph,
NULL,
tf2i->status
);
if(TF_GetCode(tf2i->status) != TF_OK)
{
fprintf(stderr,"Error loading model : %s \n",TF_Message(tf2i->status));
tf2_checkModelUsingExternalTool(tf2i->modelPath);
return 0;
}
return tf2_allocateIOTensors(
tf2i,
inputTensorName,
outputTensorName,
inputElements,
outputElements,
forceCPU
);
}
static int tf2_unloadModel(struct Tensorflow2Instance * tf2i)
{
// //Free memory
TF_DeleteGraph(tf2i->graph);
if(TF_GetCode(tf2i->status) != TF_OK)
{
fprintf(stderr,"tf2_unloadModel: Error deleting graph (%s) \n",TF_Message(tf2i->status));
return 0;
}
TF_DeleteSession(tf2i->session, tf2i->status);
if(TF_GetCode(tf2i->status) != TF_OK)
{
fprintf(stderr,"tf2_unloadModel: Error deleting session (%s) \n",TF_Message(tf2i->status));
return 0;
}
TF_DeleteSessionOptions(tf2i->sessionOptions);
if(TF_GetCode(tf2i->status) != TF_OK)
{
fprintf(stderr,"tf2_unloadModel: Error deleting session options (%s) \n",TF_Message(tf2i->status));
return 0;
}
TF_DeleteStatus(tf2i->status);
//Can't check status having deleted the status ..
if (tf2i->buffer!=0)
{
free(tf2i->buffer);
tf2i->buffer=0;
tf2i->bufferSize=0;
}
return 1;
}
static int tf2_run(
struct Tensorflow2Instance * tf2i,
int64_t * dimensions,
unsigned int dimensionsNumber,
float * data,
unsigned int dataSizeBytes
)
{
int result = 0;
TF_Tensor* int_tensor = NULL;
if (tf2i->inputIsHalfFloats)
{
//In an attempt not to perform unneccessary reallocations
//I rewrite the float array with halfs..
unsigned int dataSizeElements = dataSizeBytes / sizeof(float);
unsigned int convertedDataSizeBytes = sizeof(float16) * dataSizeElements;
float16 * dataAsFloat16 = (float16*) data;
float buffer=0.0;
for (int i=0; i<dataSizeElements; i++)
{
//Have an intermediate location..
buffer = data[i];
dataAsFloat16[i] = convertFloat32ToFloat16(buffer);
}
int_tensor = TF_NewTensor(
TF_HALF,
dimensions,
dimensionsNumber,
dataAsFloat16,
convertedDataSizeBytes,
&tf2_noOpDeallocator,
0
);
} else
{
//Regular floats ready to be transported to the tensor
int_tensor = TF_NewTensor(
TF_FLOAT,
dimensions,
dimensionsNumber,
data,
dataSizeBytes,
&tf2_noOpDeallocator,
0
);
}
//----------------------------------------------------------------
if (int_tensor == NULL)
{
fprintf(stderr,"ERROR tf2_run: Failed TF_NewTensor\n");
fprintf(stderr,"Trying to create a tensor with %u dimensions : ",dimensionsNumber);
fprintf(stderr,"{ ");
for (int i=0; i<dimensionsNumber; i++)
{
fprintf(stderr,"%ld ",dimensions[i]);
}
fprintf(stderr,"}\n");
fprintf(stderr,"Data output size : %u \n",dataSizeBytes);
return 0;
}
else
{
tf2i->inputValues[0] = int_tensor;
//Run the Session
TF_SessionRun(
tf2i->session,
NULL, //run options
tf2i->input, tf2i->inputValues, tf2i->numberOfInputTensors,
tf2i->output, tf2i->outputValues, tf2i->numberOfOutputTensors,
NULL, 0,
NULL, //Metadata
tf2i->status
);
if(TF_GetCode(tf2i->status) != TF_OK)
{
fprintf(stderr,"tf2_run: Error running %s network, %s\n",tf2i->modelPath,TF_Message(tf2i->status));
fprintf(stderr,"dimensionsNumber=%u\n",dimensionsNumber);
fprintf(stderr,"dataSizeBytes=%u\n",dataSizeBytes);
tf2_checkModelUsingExternalTool(tf2i->modelPath);
result = 0;
}
TF_DeleteTensor(int_tensor);
tf2i->inputValues[0] =0;
int_tensor=0;
result=1;
}
return result;
}
#ifdef __cplusplus
}
#endif
#endif