Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/master' into mbencer/TensorsSh…
Browse files Browse the repository at this point in the history
…areMemory
  • Loading branch information
mbencer committed Dec 2, 2024
2 parents 311cf7b + e5d253d commit a51addd
Show file tree
Hide file tree
Showing 12 changed files with 800 additions and 2 deletions.
17 changes: 17 additions & 0 deletions compiler/circle-interpreter-cffi-test/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
if(NOT ENABLE_TEST)
return()
endif(NOT ENABLE_TEST)

set(VIRTUALENV "${NNCC_OVERLAY_DIR}/venv_2_12_1")
set(TEST_LIST_FILE "test.lst")

get_target_property(ARTIFACTS_PATH testDataGenerator BINARY_DIR)

add_test(
NAME circle_interpreter_cffi_test
COMMAND ${VIRTUALENV}/bin/python infer.py
--lib_path $<TARGET_FILE:circle_interpreter_cffi>
--test_list ${TEST_LIST_FILE}
--artifact_dir ${ARTIFACTS_PATH}
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
)
15 changes: 15 additions & 0 deletions compiler/circle-interpreter-cffi-test/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# circle-interpreter-cffi-test

The `circle_interpereter_cffi` library wrapped with CFFI is designed to expose
an existing `luci-interpreter` class to Python. It simplifies the integration of
the class by creating a Python-compatible interface using CFFI.

`circle-interpreter-cffi-test` ensures that the Python bindings for the C++
library correctly. Specifically, it verifies that:

1. The CFFI-wrapped library can succesfully load the circle model.
2. Inputs passed from Python are correctly interpreted by the `luci-interpreter`.
3. The output generated by the interpreter matches the expected results.

This test provides confidence that `luci-interpter`, when accessed through
the Python interface, produces the same results as the original implementation.
112 changes: 112 additions & 0 deletions compiler/circle-interpreter-cffi-test/infer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import argparse
import h5py
import numpy as np
from pathlib import Path
import re
import sys

############ Managing paths for the artifacts required by the test.


def extract_test_args(s):
p = re.compile('eval\\((.*)\\)')
result = p.search(s)
return result.group(1)


parser = argparse.ArgumentParser()
parser.add_argument('--lib_path', type=str, required=True)
parser.add_argument('--test_list', type=str, required=True)
parser.add_argument('--artifact_dir', type=str, required=True)
args = parser.parse_args()

with open(args.test_list) as f:
contents = [line.rstrip() for line in f]
# remove newline and comments.
eval_lines = [line for line in contents if line.startswith('eval(')]
test_args = [extract_test_args(line) for line in eval_lines]
test_models = [Path(args.artifact_dir) / f'{arg}.circle' for arg in test_args]
input_data = [
Path(args.artifact_dir) / f'{arg}.opt/metadata/tc/input.h5' for arg in test_args
]
expected_output_data = [
Path(args.artifact_dir) / f'{arg}.opt/metadata/tc/expected.h5' for arg in test_args
]

############ CFFI test

from cffi import FFI

ffi = FFI()
ffi.cdef("""
typedef struct InterpreterWrapper InterpreterWrapper;
const char *get_last_error(void);
void clear_last_error(void);
InterpreterWrapper *Interpreter_new(const uint8_t *data, const size_t data_size);
void Interpreter_delete(InterpreterWrapper *intp);
void Interpreter_interpret(InterpreterWrapper *intp);
void Interpreter_writeInputTensor(InterpreterWrapper *intp, const int input_idx, const void *data, size_t input_size);
void Interpreter_readOutputTensor(InterpreterWrapper *intp, const int output_idx, void *output, size_t output_size);
""")
C = ffi.dlopen(args.lib_path)


def check_for_errors():
error_message = ffi.string(C.get_last_error()).decode('utf-8')
if error_message:
C.clear_last_error()
raise RuntimeError(f'C++ Exception: {error_message}')


def error_checked(func):
"""
Decorator to wrap functions with error checking.
"""
def wrapper(*args, **kwargs):
result = func(*args, **kwargs)
check_for_errors()
return result

return wrapper


Interpreter_new = error_checked(C.Interpreter_new)
Interpreter_delete = error_checked(C.Interpreter_delete)
Interpreter_interpret = error_checked(C.Interpreter_interpret)
Interpreter_writeInputTensor = error_checked(C.Interpreter_writeInputTensor)
Interpreter_readOutputTensor = error_checked(C.Interpreter_readOutputTensor)

for idx, model_path in enumerate(test_models):
with open(model_path, "rb") as f:
model_data = ffi.from_buffer(bytearray(f.read()))

try:
intp = Interpreter_new(model_data, len(model_data))

# Set inputs
h5 = h5py.File(input_data[idx])
input_values = h5.get('value')
input_num = len(input_values)
for input_idx in range(input_num):
arr = np.array(input_values.get(str(input_idx)))
c_arr = ffi.from_buffer(arr)
Interpreter_writeInputTensor(intp, input_idx, c_arr, arr.nbytes)
# Do inference
Interpreter_interpret(intp)
# Check outputs
h5 = h5py.File(expected_output_data[idx])
output_values = h5.get('value')
output_num = len(output_values)
for output_idx in range(output_num):
arr = np.array(output_values.get(str(output_idx)))
result = np.empty(arr.shape, dtype=arr.dtype)
Interpreter_readOutputTensor(intp, output_idx, ffi.from_buffer(result),
arr.nbytes)
if not np.allclose(result, arr):
raise RuntimeError("Wrong outputs")

Interpreter_delete(intp)
except RuntimeError as e:
print(e)
sys.exit(-1)
2 changes: 2 additions & 0 deletions compiler/circle-interpreter-cffi-test/requires.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
require("common-artifacts")
require("circle-interpreter")
17 changes: 17 additions & 0 deletions compiler/circle-interpreter-cffi-test/test.lst
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
eval(Add_000)
eval(Add_U8_000)
eval(AveragePool2D_000)
eval(Concatenation_000)
eval(Conv2D_000)
eval(Conv2D_001)
eval(Conv2D_002)
eval(DepthwiseConv2D_000)
eval(FullyConnected_000)
eval(FullyConnected_001)
eval(MaxPool2D_000)
eval(Mul_000)
eval(Pad_000)
eval(Reshape_000)
eval(Reshape_001)
eval(Reshape_002)
eval(Softmax_000)
9 changes: 9 additions & 0 deletions tests/nnfw_api/lib/CircleGen.cc
Original file line number Diff line number Diff line change
Expand Up @@ -582,6 +582,15 @@ uint32_t CircleGen::addOperatorSquare(const OperatorParams &params)
circle::BuiltinOptions_SquareOptions, options);
}

uint32_t CircleGen::addOperatorSqueeze(const OperatorParams &params,
const std::vector<int32_t> &squeeze_dims)
{
auto squeeze_dims_vec = _fbb.CreateVector(squeeze_dims.data(), squeeze_dims.size());
auto options = circle::CreateSqueezeOptions(_fbb, squeeze_dims_vec).Union();
return addOperatorWithOptions(params, circle::BuiltinOperator_SQUEEZE,
circle::BuiltinOptions_SqueezeOptions, options);
}

uint32_t CircleGen::addOperatorBatchToSpaceND(const OperatorParams &params)
{
auto options = circle::CreateBatchToSpaceNDOptions(_fbb).Union();
Expand Down
2 changes: 2 additions & 0 deletions tests/nnfw_api/lib/CircleGen.h
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,8 @@ class CircleGen
uint32_t addOperatorSplit(const OperatorParams &params, int32_t num_split);
uint32_t addOperatorSqrt(const OperatorParams &params);
uint32_t addOperatorSquare(const OperatorParams &params);
uint32_t addOperatorSqueeze(const OperatorParams &params,
const std::vector<int32_t> &squeeze_dims);
uint32_t addOperatorStridedSlice(const OperatorParams &params, int32_t begin_mask = 0,
int32_t end_mask = 0, int32_t ellipsis_mask = 0,
int32_t new_axis_mask = 0, int32_t shrink_axis_mask = 0);
Expand Down
Loading

0 comments on commit a51addd

Please sign in to comment.