Skip to content

Commit

Permalink
Refactor pytest. (#1010)
Browse files Browse the repository at this point in the history
* Refactor pytest.

- Use toml instead of yaml
- Do not save bin/txt/kmodel files during CI
- Refacetor DataFactory into generator.py to support random/bin/image/constant_of_shape.
- Fix some test cases.
- Add ci_proxy.py and nuc_proxy.py to enable test on evb.

* Apply code-format changes

* Fix failed test cases and support pytest -n 4

* Update toml config of test cases for caffe/onnx/tflite.

* disable pytest -n 4

* Fix cpu target in CI.

---------

Co-authored-by: zhangyang2057 <zhangyang2057@users.noreply.github.com>
  • Loading branch information
zhangyang2057 and zhangyang2057 authored Jul 21, 2023
1 parent 289ecd8 commit 0b642a2
Show file tree
Hide file tree
Showing 50 changed files with 1,438 additions and 1,396 deletions.
100 changes: 45 additions & 55 deletions python/nncase/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,35 +53,37 @@ def __init__(self) -> None:


class PTQTensorOptions:
calibrate_method: str
input_mean: float
input_std: float
samples_count: int
quant_type: str
w_quant_type: str
finetune_weights_method: str
use_mix_quant: bool
quant_scheme: str
use_mse_quant_w: bool
export_quant_scheme: bool
export_weight_range_by_channel: bool
dump_quant_error: bool
dump_quant_error_symmetric_for_signed: bool
quant_type: str
w_quant_type: str
calibrate_method: str
finetune_weights_method: str
input_mean: float
input_std: float
quant_scheme: str
samples_count: int
cali_data: List[RuntimeTensor]

def __init__(self) -> None:
self.calibrate_method: str = "Kld"
self.input_mean: float = 0.5
self.input_std: float = 0.5
self.samples_count: int = 5
self.quant_type: str = "uint8"
self.w_quant_type: str = "uint8"
self.finetune_weights_method: str = "NoFineTuneWeights"
self.use_mix_quant: bool = False
self.quant_scheme: str = ""
self.use_mse_quant_w = False
self.export_quant_scheme: bool = False
self.export_weight_range_by_channel: bool = False
self.dump_quant_error: bool = False
self.dump_quant_error_symmetric_for_signed: bool = True
self.quant_type: str = "uint8"
self.w_quant_type: str = "uint8"
self.calibrate_method: str = "Kld"
self.finetune_weights_method: str = "NoFineTuneWeights"
self.input_mean: float = 0.5
self.input_std: float = 0.5
self.quant_scheme: str = ""
self.samples_count: int = 5
self.cali_data: List[RuntimeTensor] = []

def set_tensor_data(self, data: List[List[np.ndarray]]) -> None:
Expand Down Expand Up @@ -238,17 +240,9 @@ def dump_range_options(self) -> DumpRangeTensorOptions:

def __process_compile_options(self, compile_options: CompileOptions) -> ClCompileOptions:
self._target = _nncase.Target(compile_options.target)
dump_flags = _nncase.DumpFlags.Nothing if not compile_options.dump_ir else _nncase.DumpFlags(
_nncase.DumpFlags.PassIR)
if (compile_options.dump_asm):
dump_flags = _nncase.DumpFlags(dump_flags | _nncase.DumpFlags.CodeGen)
self._compile_options.dump_flags = dump_flags
self._compile_options.dump_dir = compile_options.dump_dir
self._compile_options.input_file = compile_options.input_file
if compile_options.preprocess:
self._compile_options.preprocess = compile_options.preprocess
self._compile_options.input_layout = compile_options.input_layout
self._compile_options.output_layout = compile_options.output_layout
self._compile_options.swapRB = compile_options.swapRB
if compile_options.input_type == "uint8":
self._compile_options.input_type = _nncase.InputType.Uint8
elif compile_options.input_type == "int8":
Expand All @@ -257,10 +251,19 @@ def __process_compile_options(self, compile_options: CompileOptions) -> ClCompil
self._compile_options.input_type = _nncase.InputType.Float32
self._compile_options.input_shape = str(compile_options.input_shape)[1:-1]
self._compile_options.input_range = str(compile_options.input_range)[1:-1]
self._compile_options.swapRB = compile_options.swapRB
self._compile_options.letterbox_value = compile_options.letterbox_value
self._compile_options.mean = str(compile_options.mean)[1:-1]
self._compile_options.std = str(compile_options.std)[1:-1]
self._compile_options.input_layout = compile_options.input_layout
self._compile_options.output_layout = compile_options.output_layout
self._compile_options.letterbox_value = compile_options.letterbox_value

self._compile_options.input_file = compile_options.input_file
dump_flags = _nncase.DumpFlags.Nothing if not compile_options.dump_ir else _nncase.DumpFlags(
_nncase.DumpFlags.PassIR)
if (compile_options.dump_asm):
dump_flags = _nncase.DumpFlags(dump_flags | _nncase.DumpFlags.CodeGen)
self._compile_options.dump_flags = dump_flags
self._compile_options.dump_dir = compile_options.dump_dir

def _import_module(self, model_content: bytes | io.RawIOBase) -> None:
stream = io.BytesIO(model_content) if isinstance(model_content, bytes) else model_content
Expand Down Expand Up @@ -329,52 +332,39 @@ class ClCompileOptions():


class CompileOptions:
benchmark_only: bool
dump_asm: bool
dump_dir: str
dump_ir: bool
target: str
preprocess: bool
swapRB: bool
input_file: str
input_range: List[float]
input_shape: List[int]
input_type: str
is_fpga: bool
input_shape: List[int]
input_range: List[float]
input_file: str
mean: List[float]
std: List[float]
output_type: str
preprocess: bool
quant_type: str
target: str
w_quant_type: str
use_mse_quant_w: bool
input_layout: str
output_layout: str
letterbox_value: float
tcu_num: int
dump_asm: bool
dump_ir: bool
dump_dir: str

def __init__(self) -> None:
self.benchmark_only = False
self.dump_asm = True
self.dump_dir = "tmp"
self.dump_ir = False
self.is_fpga = False
self.quant_type = "uint8"
self.target = "cpu"
self.w_quant_type = "uint8"
self.use_mse_quant_w = True
self.tcu_num = 0

self.target = "cpu"
self.preprocess = False
self.swapRB = False
self.input_file = ""
self.input_range = []
self.input_shape = []
self.input_type = "float32"
self.input_shape = []
self.input_range = []
self.input_file = ""
self.mean = [0, 0, 0]
self.std = [1, 1, 1]
self.input_layout = ""
self.output_layout = ""
self.letterbox_value = 0
self.dump_asm = True
self.dump_ir = False
self.dump_dir = "tmp"


class ShapeBucketOptions:
Expand Down
1 change: 1 addition & 0 deletions requirements.test.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,4 @@ pytest-xdist
pyyaml
pythonnet==3.0.1
clr_loader==0.2.4
toml==0.10.2
22 changes: 12 additions & 10 deletions tests/caffe_test_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,18 @@
import shutil
import numpy as np
# from typing import Dict, List, Tuple, Union
from test_utils import *


class CaffeTestRunner(TestRunner):
def __init__(self, case_name, targets=None, overwrite_configs: dict = None):
super().__init__(case_name, targets, overwrite_configs)
def __init__(self, case_name, overwrite_configs: dict = None):
super().__init__(case_name, overwrite_configs)
self.model_type = "caffe"

def run(self, model_file_list):
super().run(model_file_list)

def parse_model_input_output(self, model_path: Union[List[str], str]):
def parse_model(self, model_path: Union[List[str], str]):
caffe_model = caffe.Net(model_path[0], model_path[1], caffe.TEST)
for i, name in enumerate(caffe_model._layer_names):
if (caffe_model.layers[i].type == "Input"):
Expand All @@ -24,7 +25,7 @@ def parse_model_input_output(self, model_path: Union[List[str], str]):
input_dict['model_shape'] = list(caffe_model.blobs[name].data.shape)
self.inputs.append(input_dict)
self.calibs.append(copy.deepcopy(input_dict))
self.dump_range_data.append(copy.deepcopy(input_dict))
# self.dump_range_data.append(copy.deepcopy(input_dict))

used_inputs = set([name for _, l in caffe_model.bottom_names.items() for name in l])
seen_outputs = set()
Expand All @@ -37,7 +38,7 @@ def parse_model_input_output(self, model_path: Union[List[str], str]):
output_dict['model_shape'] = list(caffe_model.blobs[n].data.shape)
self.outputs.append(output_dict)

def cpu_infer(self, case_dir: str, model_file_list, type: str):
def cpu_infer(self, model_file_list):
caffe_model = caffe.Net(model_file_list[0], model_file_list[1], caffe.TEST)

for input in self.inputs:
Expand All @@ -46,14 +47,15 @@ def cpu_infer(self, case_dir: str, model_file_list, type: str):

outputs = caffe_model.forward()

results = []
for i, output in enumerate(self.outputs):
result = outputs[output['name']]
results.append(result)
if not test_utils.in_ci():
dump_bin_file(os.path.join(self.case_dir, f'cpu_result_{i}.bin'), result)
dump_txt_file(os.path.join(self.case_dir, f'cpu_result_{i}.txt'), result)

self.output_paths.append((
os.path.join(case_dir, f'cpu_result_{i}.bin'),
os.path.join(case_dir, f'cpu_result_{i}.txt')))
result.tofile(self.output_paths[-1][0])
self.totxtfile(self.output_paths[-1][1], result)
return results

def import_model(self, compiler, model_content, import_options):
compiler.import_caffe(model_content[1], model_content[0])
150 changes: 150 additions & 0 deletions tests/ci_proxy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
import os
import argparse
import stat
import socket
import json
import threading
import queue
import logging
import logging.handlers
import time


def recv_file(conn, target_root, mylogger):
conn.sendall(f"pls send file info".encode())
header = conn.recv(1024)
file_dict = json.loads(header.decode())
file_name = file_dict['file_name']
file_size = file_dict['file_size']
mylogger.debug('recv: file = {0}, size = {1}'.format(file_name, file_size))
conn.sendall(f"pls send {file_name}".encode())

full_file = os.path.join(target_root, file_name)
with open(full_file, 'wb') as f:
recv_size = 0
while recv_size < file_size:
slice = conn.recv(4096)
f.write(slice)
recv_size += len(slice)

os.chmod(full_file, stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO)
return file_name


def Consumer(target, q, nfs_root, ip, port):
# create target root
target_root = os.path.join(nfs_root, target)
if not os.path.exists(target_root):
os.makedirs(target_root)

# logging
mylogger = logging.getLogger()
mylogger.setLevel(logging.DEBUG)
rf_handler = logging.handlers.RotatingFileHandler(
f'ci_proxy_{target}.log', mode='a', maxBytes=32 * 1024 * 1024, backupCount=10)
# rf_handler.setLevel(logging.INFO)
mylogger.setLevel(logging.DEBUG)
rf_handler.setFormatter(logging.Formatter('%(asctime)s [%(levelname)s] %(message)s'))
mylogger.addHandler(rf_handler)

# telnet_client = TelnetClient(mylogger)
while True:
cmd = './'
conn = q.get()

# recv header
conn.sendall(f"pls send header".encode())
header = conn.recv(1024)
header_dict = json.loads(header.decode())
mylogger.info("test case = {0}".format(header_dict['case']))
file_num = header_dict['app'] + header_dict['kmodel'] + header_dict['inputs']

# recv all kinds of files(app + kmodel + inputs)
for i in range(file_num):
file = recv_file(conn, target_root, mylogger)
if i == 0:
cmd = cmd + file
else:
cmd = cmd + ' ' + file

# print('cmd = {0}'.format(cmd))

# connect nuc_proxy server
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client_socket.connect((ip, int(port)))

# send target
dummy = client_socket.recv(1024)
target_dict = {}
target_dict['target'] = target
client_socket.sendall(json.dumps(target_dict).encode())

# send header
dummy = client_socket.recv(1024)
dict = {}
dict['cmd'] = cmd
client_socket.sendall(json.dumps(dict).encode())

infer_result = client_socket.recv(1024).decode()
client_socket.close()
# print('infer_result = {0}'.format(infer_result))
if infer_result.find('succeed') == -1:
conn.sendall(f'infer failed on {target} board: {infer_result}'.encode())
else:
conn.sendall(f'infer succeed'.encode())
dummy = conn.recv(1024)

# send outputs
for i in range(header_dict['outputs']):
file = os.path.join(target_root, f'nncase_result_{i}.bin')
file_size = os.path.getsize(file)
conn.sendall(str(file_size).encode())
dummy = conn.recv(1024)

with open(file, 'rb') as f:
conn.sendall(f.read())
dummy = conn.recv(1024)
mylogger.debug('send: file = {0}, size = {1}'.format(file, file_size))

conn.close()


def main():
# args
parser = argparse.ArgumentParser(prog="ci_proxy")
parser.add_argument("--ci_proxy_port", help='listening port of ci_proxy',
type=int, default=10000)
parser.add_argument("--nfs_root", help='nfs root on pc', type=str, default='/data/nfs')
parser.add_argument("--nuc_proxy_ip", help='ip of nuc_proxy', type=str, default='localhost')
parser.add_argument("--nuc_proxy_port", help='listening port of nuc_proxy',
type=int, default=10001)

args = parser.parse_args()

dict = {}
size = 256

server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.bind(('localhost', args.ci_proxy_port))
server_socket.listen(size)
while True:
conn, addr = server_socket.accept()

# recv target
conn.sendall(f"pls send your target".encode())
info = conn.recv(1024)
target_dict = json.loads(info.decode())
target = target_dict['target']

if target not in dict:
q = queue.Queue(maxsize=size)
t_consumer = threading.Thread(target=Consumer, args=(
target, q, args.nfs_root, args.nuc_proxy_ip, args.nuc_proxy_port))
t_consumer.start()
dict[target] = q

dict[target].put(conn)


if __name__ == '__main__':
main()
Loading

0 comments on commit 0b642a2

Please sign in to comment.