Skip to content

Commit

Permalink
first pass embedded radae_rx.c and embedded radae_rx.py working, exam…
Browse files Browse the repository at this point in the history
…ple in README.md
  • Loading branch information
drowe67 committed Sep 26, 2024
1 parent a106bed commit bcd28dd
Show file tree
Hide file tree
Showing 3 changed files with 163 additions and 5 deletions.
18 changes: 17 additions & 1 deletion embed/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ loss: 0.145

First pass at the a C callable version of `radae_tx`. Have hard coded a few arguments for convenience, and it's a C application (rather than a library). If this was in library form we would be ready for linking with other C applications.

1. Generate some test data, and run `embed/radae_tx.py` with Python top level to test Python code.
1. Generate some test data, and run `embed/radae_tx.py` to test Python side.
```
cd radae/build
cmake ..
Expand All @@ -70,3 +70,19 @@ First pass at the a C callable version of `radae_tx`. Have hard coded a few arg
```
`diff` shows Python ctest and C/Embedded version have same output. Haven't made this a ctest yet as not sure how to do `gcc` step in cmake such that's it's reasonably portable.

# Test 3 - radae_rx as C program

1. Generate some test data, and run `embed/radae_rx.py` to test Python side.
```
cd radae/build
cmake ..
ctest -V -R radae_rx_embed
```

2. Build and C top level/embedded Python version:

```
gcc radae_rx.c -o radae_rx $(python3.10-config --cflags) $(python3.10-config --ldflags --embed)
cat ../rx.f32 | PYTHONPATH=".:../" ./radae_rx > ../features_out.f32
diff features_out.f32 ../features_out.f32
```
132 changes: 132 additions & 0 deletions embed/radae_rx.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
/* Top level C program for embedded version of radae_tx.py */
#define PY_SSIZE_T_CLEAN
#include <Python.h>
#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION
#include "numpy/arrayobject.h"

#ifdef _WIN32
// For _setmode().
#include <io.h>
#include <fcntl.h>
#endif // _WIN32

void check_error(PyObject *p, char s1[], char s2[]) {
if (p == NULL) {
if (PyErr_Occurred()) {
PyErr_Print();
}
fprintf(stderr, "Error: %s %s\n", s1, s2);
// TODO: for library make this fail gracefully with return code or exception
exit(1);
}
}

void check_callable(PyObject *p, char s1[], char s2[]) {
if (!PyCallable_Check(p))
check_error(NULL, s1, s2);
}

/* helper function to call a Python "getter" method that return a long */
long call_getter(PyObject *pInst, char meth_name[]) {
PyObject *pMeth, *pArgs, *pValue;
long ret;

pMeth = PyObject_GetAttrString(pInst, meth_name);
check_error(pMeth, "can't find", meth_name);
check_callable(pMeth, meth_name, "is not callable");

pArgs = Py_BuildValue("()");
pValue = PyObject_CallObject(pMeth, pArgs);
check_error(pValue, "call to", meth_name);
ret = PyLong_AsLong(pValue);

Py_DECREF(pArgs);
Py_DECREF(pValue);
Py_XDECREF(pMeth);

return ret;
}

int main(void) {
PyObject *pName, *pModule, *pClass, *pInst, *pMeth;
PyObject *pValue;
PyObject *pArgs;
char *python_module_name = "radae_rx";
char *do_radae_rx_meth_name = "do_radae_rx";
npy_intp n_floats_out, nin_max, nin;

Py_Initialize();

// need import array for numpy
int ret = _import_array();
fprintf(stderr, "import_array returned: %d\n", ret);

// Load module of Python code
pName = PyUnicode_DecodeFSDefault(python_module_name);
pModule = PyImport_Import(pName);
check_error(pModule, "importing", python_module_name);
Py_DECREF(pName);

// Find class and creat an instance
pClass = PyObject_GetAttrString(pModule, "radae_rx");
check_error(pClass, "finding class", "radae_rx");
pArgs = Py_BuildValue("(s)", "../model19_check3/checkpoints/checkpoint_epoch_100.pth");
pInst = PyObject_CallObject(pClass, pArgs);
check_error(pInst, "Creating instance of class", "radae_rx");
Py_DECREF(pClass);
Py_DECREF(pArgs);

n_floats_out = (int)call_getter(pInst, "get_n_floats_out");
nin_max = (int)call_getter(pInst, "get_nin_max");
nin = (int)call_getter(pInst, "get_nin");
fprintf(stderr, "n_floats_out: %d nin_max: %d nin: %d\n", (int)n_floats_out, (int)nin_max, (int)nin);

pMeth = PyObject_GetAttrString(pInst, do_radae_rx_meth_name);
check_error(pMeth, "finding", do_radae_rx_meth_name);
check_callable(pMeth, do_radae_rx_meth_name, "not callable");

pArgs = PyTuple_New(2);

// 1st Python function arg - input numpy array of csingle rx samples
float buffer_complex[2*nin_max];
pValue = PyArray_SimpleNewFromData(1, &nin_max, NPY_CFLOAT, buffer_complex);
check_error(pValue, "setting up numpy array", "buffer_complex");
PyTuple_SetItem(pArgs, 0, pValue);

// 2nd Python arg - output numpy array of float features
float features_out[n_floats_out];
pValue = PyArray_SimpleNewFromData(1, &n_floats_out, NPY_FLOAT, features_out);
check_error(pValue, "setting up numpy array", "features_out");
PyTuple_SetItem(pArgs, 1, pValue);

#ifdef _WIN32
// Note: freopen() returns NULL if filename is NULL, so
// we have to use setmode() to make it a binary stream instead.
_setmode(_fileno(stdin), O_BINARY);
_setmode(_fileno(stdout), O_BINARY);
#endif // _WIN32

// We are assuming once args are set up we can make repeat call with the same args, even though
// data in arrays change
while(fread(buffer_complex, sizeof(float), 2*nin, stdin) == (size_t)(2*nin)) {
// do the function call
pValue = PyObject_CallObject(pMeth, pArgs);
check_error(pValue, "calling", do_radae_rx_meth_name);
long valid_out = PyLong_AsLong(pValue);
if (valid_out) {
fwrite(features_out, sizeof(float), n_floats_out, stdout);
fflush(stdout);
}
// note time varying, nin must be read before next call to do_radae_rx
nin = (int)call_getter(pInst, "get_nin");
}

Py_DECREF(pArgs);
Py_DECREF(pMeth);
Py_DECREF(pInst);

if (Py_FinalizeEx() < 0) {
return 120;
}
return 0;
}
18 changes: 14 additions & 4 deletions embed/radae_rx.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,12 +125,22 @@ def __init__(self,model_name):
# Stateful decoder wasn't present during training, so we need to load weights from existing decoder
model.core_decoder_statefull_load_state_dict()

def get_n_floats_out(self):
return model.Nzmf*model.dec_stride*nb_total_features

def get_nin_max(self):
return Nmf+M

def get_nin(self):
return self.nin

def do_radae_rx(self, buffer_complex, features_out):
with torch.inference_mode():
prev_state = self.state
valid_output = False
endofover = False
uw_fail = False
buffer_complex = buffer_complex[:self.nin]
if bpf:
buffer_complex = bpf.bpf(buffer_complex)
rx_buf[:-self.nin] = rx_buf[self.nin:] # out with the old
Expand Down Expand Up @@ -246,11 +256,11 @@ def do_radae_rx(self, buffer_complex, features_out):
if __name__ == '__main__':
rx = radae_rx(model_name = "../model19_check3/checkpoints/checkpoint_epoch_100.pth")

# TODO put this size in a getter func
features_out = np.zeros(model.Nzmf*model.dec_stride*nb_total_features,dtype=np.float32)
# allocate storage for output features
features_out = np.zeros(rx.get_n_floats_out(),dtype=np.float32)
while True:
buffer = sys.stdin.buffer.read(rx.nin*struct.calcsize("ff"))
if len(buffer) != rx.nin*struct.calcsize("ff"):
buffer = sys.stdin.buffer.read(rx.get_nin()*struct.calcsize("ff"))
if len(buffer) != rx.get_nin()*struct.calcsize("ff"):
break
buffer_complex = np.frombuffer(buffer,np.csingle)
valid_output = rx.do_radae_rx(buffer_complex, features_out)
Expand Down

0 comments on commit bcd28dd

Please sign in to comment.