Skip to content
Snippets Groups Projects
Commit f69e540b authored by Olivier BICHLER's avatar Olivier BICHLER
Browse files

Merged with dev

parents c42b54b4 10ca7e16
No related branches found
No related tags found
2 merge requests!710.4.0,!59Continuous improvement of export_cpp
Showing
with 207 additions and 149 deletions
......@@ -29,10 +29,4 @@ test:ubuntu_python:
- DEPENDENCY_JOB="build:ubuntu_python"
- !reference [.ubuntu:download:artifacts, script]
# Need to install extra dependence for tests:
- python -m pip install torch torchvision
coverage:ubuntu_python:
before_script:
- !reference [.setup:coverage:ubuntu_python, before_script]
- DEPS_NAMES=("aidge_onnx" "aidge_quantization")
- DEPENDENCY_JOB="build:ubuntu_python"
- !reference [.ubuntu:download:artifacts, script]
\ No newline at end of file
- python -m pip install torch torchvision
\ No newline at end of file
......@@ -3,13 +3,14 @@ import shutil
import numpy as np
from pathlib import Path
from typing import List, Union
import json
import aidge_core
from aidge_core.mem_info import generate_optimized_memory_info
from aidge_core.export_utils import scheduler_export, generate_main_cpp, aidge2c, generate_file
from aidge_core.export_utils import scheduler_export, generate_main_cpp
from aidge_export_cpp import ExportLibCpp, ROOT
from aidge_export_cpp.export_utils import read_log_file
from aidge_export_cpp import ExportLibCpp
from aidge_export_cpp.export_utils import export_aidge_ifmaps
def export(export_folder_name: str,
......@@ -59,6 +60,25 @@ def export(export_folder_name: str,
aidge_core.adapt_fc_params_format(graphview)
graphview.forward_dims(dims=[inputs_tensor.dims()] if inputs_tensor is not None else [])
# --------------------------------------------------------------
# AIDGE CMP
# --------------------------------------------------------------
"""
If the --aidge_cmp option is enabled, the feature maps generated by aidge with the
backend cpu will be exported in the generated export. It will be used as reference
to verify that the results with the optimized kernels are correct for the exported
model.
This option has to be passed to each node in order to be used within the Export Nodes.
(JConv, JPad, ...) that you can find in the "operators" folder.
"""
if aidge_cmp:
for node in graphview.get_nodes():
node.attributes().aidge_cmp = True
graphview.save("exported_model")
scheduler.reset_scheduling()
scheduler.generate_scheduling()
......@@ -85,25 +105,5 @@ def export(export_folder_name: str,
generate_main_cpp(export_folder_name, graphview, labels=labels, inputs_tensor=inputs_tensor)
# Generate log files (aidge_cmp option)
"""
If the aidge_cmp option has been enabled, the generated log_outputs will
be copied into the generated export in order to be used as reference.
"""
if aidge_cmp:
ranked_nodes = graphview.get_ranked_nodes_name("{0}[{1}#{3}]")
os.makedirs(export_folder_name / "data" / "aidge_outputs")
os.makedirs(export_folder_name / "data" / "export_outputs")
for node in graphview.get_nodes():
if node.type() != "Producer":
file_path = 'log_outputs/' + ranked_nodes[node] + '/output_0.log'
data_t = aidge2c(node.get_operator().get_output(0).dtype())
name = node.name() + '_output_0_aidge'
dims = node.get_operator().get_output(0).dims()
values = read_log_file(file_path)
generate_file(export_folder_name / "data" / "aidge_outputs" / (node.name() + ".hpp"),
ROOT / "templates" / "data" / "aidge_tensor.jinja",
data_t=data_t,
name=name,
dims=dims,
values=values)
export_aidge_ifmaps(export_folder_name)
import os
import json
import numpy as np
from collections import OrderedDict
import aidge_core
from aidge_core.export_utils import get_node_from_metaop
from aidge_core.export_utils import get_node_from_metaop, aidge2c, generate_file
from aidge_export_cpp import ROOT
def cpp_fuse_to_metaops(graph_view: aidge_core.GraphView):
"""
Fuse nodes into metaops adapted for the CPP Export
TODO: These recipes should be into aidge_core
TODO: These recipes should be in aidge_core
:param graph_view: An instance of :py:class:`aidge_core.GraphView`, providing access to nodes and
ordered input/output data within the computational graph.
......@@ -152,25 +156,6 @@ def set_nodes_datatypes(graph_view: aidge_core.GraphView):
def read_log_file(file_path: str):
""" Read log file
Used to read the aidge generated log files containing the intermediate
tensors of the exported model.
:param file_path: Path to the file to read.
:type file_path: str
"""
# Check if the file exists
if not os.path.isfile(file_path):
print(f"File not found: {file_path}")
return None
with open(file_path, 'r') as file:
content = file.read()
return content
def exclude_unwanted_producers(model):
""" Exclude some producers not needed for the export
......@@ -226,3 +211,53 @@ def normalize(array):
array = (array - array.min()) / (array.max() - array.min())
return 2 * array - 1
def generate_aidge_ifmaps(model):
json_nodes = []
for node in model.get_nodes():
if node.type() != "Producer":
output = node.get_operator().get_output(0)
data = {
"name": node.name(),
"dims": output.dims(),
"dtype": aidge2c(output.dtype()),
"dformat": str(output.dformat()),
"values": np.array(output).tolist()
}
json_nodes.append(data)
# Write the entire list to the JSON file after the loop
with open('aidge_output.json', 'w') as file:
json.dump(json_nodes, file, indent=2, separators=(",", ": "))
def export_aidge_ifmaps(export_folder_name):
os.makedirs(export_folder_name / "data" / "aidge_outputs")
os.makedirs(export_folder_name / "data" / "export_outputs")
# Load the JSON data from the file
with open('aidge_output.json', 'r') as file:
json_nodes = json.load(file)
# Access the data
for node in json_nodes:
name = node["name"]
dims = node["dims"]
dtype = node["dtype"]
dformat = node["dformat"]
values = node["values"]
generate_file(export_folder_name / "data" / "aidge_outputs" / (name + ".hpp"),
ROOT / "templates" / "data" / "aidge_tensor.jinja",
dtype=dtype,
dformat=dformat,
name=name + "_output_0_aidge",
dims=dims,
values=values)
# Remove the JSON file
os.remove('aidge_output.json')
......@@ -2,29 +2,12 @@ import os
from pathlib import Path
import numpy as np
import aidge_core
from aidge_core.export_utils import ExportNodeCpp, generate_file
from aidge_core.export_utils import ExportNodeCpp, generate_file, aidge2c
from aidge_export_cpp import ROOT
from aidge_export_cpp import ExportLibCpp
def numpy_dtype2ctype(dtype):
if dtype == np.int8:
return "int8_t"
elif dtype == np.int16:
return "int16_t"
elif dtype == np.int32:
return "int32_t"
elif dtype == np.int64:
return "int64_t"
elif dtype == np.float32:
return "float"
elif dtype == np.float64:
return "double"
# Add more dtype mappings as needed
else:
raise ValueError(f"Unsupported {dtype} dtype")
def export_params(name: str,
array: np.ndarray,
output: aidge_core.Tensor,
filepath: str):
# Get directory name of the file
......@@ -38,15 +21,16 @@ def export_params(name: str,
filepath,
str(ROOT / "templates" / "data" / "parameters.jinja"),
name=name,
data_t=numpy_dtype2ctype(array.dtype),
values=array.tolist()
dims=output.dims(),
dtype=aidge2c(output.dtype()),
values=np.array(output).tolist()
)
@ExportLibCpp.register("Producer", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.any)))
class ProducerCPP(ExportNodeCpp):
def __init__(self, node, mem_info):
super().__init__(node, mem_info)
self.values = np.array(self.operator.get_output(0))
self.output = self.operator.get_output(0)
self.ignore = node.attributes().has_attr("ignore")
def export(self, export_folder: Path):
......@@ -67,7 +51,7 @@ class ProducerCPP(ExportNodeCpp):
header_path = f"include/parameters/{self.attributes['name']}.h"
export_params(
self.attributes['out_name'][0],
self.values.reshape(-1),
self.output,
str(export_folder / header_path))
return [path_to_definition, header_path]
......
......@@ -66,9 +66,10 @@ static inline OutputIt copy_n(InputIt first, Size count, OutputIt result) {
return result;
}
#if SAVE_OUTPUTS
#if SAVE_OUTPUTS || AIDGE_CMP
enum class Format {
Default,
DEFAULT,
NCHW,
NHWC,
CHWN,
......@@ -77,105 +78,129 @@ enum class Format {
CDHWN
};
#endif // SAVE_OUTPUTS || AIDGE_CMP
#if SAVE_OUTPUTS
template<int NB_OUTPUTS, int OUT_HEIGHT, int OUT_WIDTH, Format FMT, typename Output_T>
inline void saveOutputs(const Output_T* __restrict outputs, FILE* pFile) {
auto offset = 0;
// NCHW
if (FMT == Format::NCHW || FMT == Format::DEFAULT) {
fprintf(pFile, "{");
for (auto out = 0; out < NB_OUTPUTS; ++out) {
fprintf(pFile, "{");
for (auto h = 0; h < OUT_HEIGHT; ++h) {
fprintf(pFile, "{");
for (auto w = 0; w < OUT_WIDTH; ++w) {
template<typename Output_T>
inline void saveOutputs(
int NB_OUTPUTS,
int OUTPUTS_HEIGHT, int OUTPUTS_WIDTH,
// int OUTPUT_MEM_CONT_OFFSET,
// int OUTPUT_MEM_CONT_SIZE,
// int OUTPUT_MEM_WRAP_OFFSET,
// int OUTPUT_MEM_WRAP_SIZE,
// int OUTPUT_MEM_STRIDE,
const Output_T* __restrict outputs,
FILE* pFile,
Format format)
{
// default is NHCW !
if (format == Format::NHWC) {
fprintf(pFile, "(");
auto oOffset = 0;
for(int oy = 0; oy < OUTPUTS_HEIGHT; oy++) {
fprintf(pFile, "(");
for(int ox = 0; ox < OUTPUTS_WIDTH; ox++) {
fprintf(pFile, "(");
// const int oPos = (ox + OUTPUTS_WIDTH * oy);
// int oOffset = OUTPUT_MEM_STRIDE * oPos;
// if (OUTPUT_MEM_WRAP_SIZE > 0
// && oOffset >= OUTPUT_MEM_CONT_SIZE)
// {
// oOffset += OUTPUT_MEM_WRAP_OFFSET - OUTPUT_MEM_CONT_OFFSET
// - OUTPUT_MEM_CONT_SIZE;
// }
for (int output = 0; output < NB_OUTPUTS; output++) {
if (std::is_floating_point<Output_T>::value)
fprintf(pFile, "%f", static_cast<float>(outputs[oOffset]));
fprintf(pFile, "%f", static_cast<float>(outputs[offset]));
else
fprintf(pFile, "%d", static_cast<int>(outputs[oOffset]));
oOffset += 1;
fprintf(pFile, "%d", static_cast<int>(outputs[offset]));
offset += 1;
fprintf(pFile, ", ");
}
fprintf(pFile, "), \n");
}
fprintf(pFile, "}\n");
}
fprintf(pFile, "), \n");
fprintf(pFile, "}\n");
}
fprintf(pFile, "}\n");
fprintf(pFile, ")\n");
}
else if (format == Format::NCHW || format == Format::Default) {
auto ofst = 0;
for(int output = 0; output < NB_OUTPUTS; output++) {
fprintf(pFile, "%d:\n", output);
for(int oy = 0; oy < OUTPUTS_HEIGHT; oy++) {
for(int ox = 0; ox < OUTPUTS_WIDTH; ox++) {
fprintf(pFile, "%d", static_cast<int>(outputs[ofst]));
fprintf(pFile, " ");
ofst += 1;
}
// NHWC
} else if (FMT == Format::NHWC) {
fprintf(pFile, "{\n"); // Start outer brace
for (auto h = 0; h < OUT_HEIGHT; ++h) {
fprintf(pFile, " {\n"); // Indent level 1
for (auto w = 0; w < OUT_WIDTH; ++w) {
fprintf(pFile, " { "); // Indent level 2 and open inner brace
for (auto out = 0; out < NB_OUTPUTS; ++out) {
if (std::is_floating_point<Output_T>::value)
fprintf(pFile, "%f", static_cast<float>(outputs[offset]));
else
fprintf(pFile, "%4d", static_cast<int>(outputs[offset]));
offset += 1;
fprintf(pFile, "\n");
// Add comma except for last element
if (out != NB_OUTPUTS - 1)
fprintf(pFile, ",");
}
fprintf(pFile, " },\n"); // Close inner brace and newline
}
fprintf(pFile, "\n");
fprintf(pFile, " },\n"); // Close w-loop brace and newline
}
fprintf(pFile, "}\n"); // Close outer brace
fprintf(pFile, "\n");
}
else {
printf("Warning unsupported dataformat.\n");
} else {
printf("[ERROR] - Format is not supported.\n");
printf("[ERROR] - Aborting save outputs...\n");
return;
}
}
#endif // SAVE_OUTPUTS
#if AIDGE_CMP
template<int NB_OUTPUTS, int OUT_WIDTH, int OUT_HEIGHT, typename AidgeOutput_T, typename DevOutput_T>
template<int NB_OUTPUTS, int OUT_WIDTH, int OUT_HEIGHT, Format FMT>
int get_ofst_from_fmt(int out, int h, int w) {
if (FMT == Format::NCHW || FMT == Format::DEFAULT)
return out * OUT_HEIGHT * OUT_WIDTH + h * OUT_WIDTH + w;
else if (FMT == Format::NHWC)
return h * OUT_WIDTH * NB_OUTPUTS + w * NB_OUTPUTS + out;
else {
printf("[ERROR] - This data format is not supported.\n");
return -1;
}
}
template<int NB_OUTPUTS, int OUT_WIDTH, int OUT_HEIGHT, Format AIDGE_FMT, Format DEV_FMT, typename AidgeOutput_T, typename DevOutput_T>
void aidge_cmp(std::string layer_name, AidgeOutput_T* aidge_output, DevOutput_T* dev_output) {
printf("[AIDGE COMPARE] - %s\n", layer_name.c_str());
printf("[NOTICE] - Comparing with Aidge ref for node : %s -> ", layer_name.c_str());
const float atol = 1e-5f; // Absolute
const float rtol = 1e-3f; // Relative
for (auto out = 0; out < NB_OUTPUTS; ++out) {
for (auto h = 0; h < OUT_HEIGHT; ++h) {
for (auto w = 0; w < OUT_WIDTH; ++w) {
const int aidge_ofst = out * OUT_HEIGHT * OUT_WIDTH + h * OUT_WIDTH + w;
const int dev_ofst = h * OUT_WIDTH * NB_OUTPUTS + w * NB_OUTPUTS + out;
if (aidge_output[aidge_ofst] != dev_output[dev_ofst]) {
if (std::is_floating_point<DevOutput_T>::value) {
printf("[ERROR] - First error detected at %dx%dx%d (out x h x w) : aidge_out = %f vs dev_out = %f\n",
out, h, w, static_cast<double>(aidge_output[aidge_ofst]), static_cast<double>(dev_output[dev_ofst]));
} else {
const int aidge_ofst = get_ofst_from_fmt<NB_OUTPUTS, OUT_WIDTH, OUT_HEIGHT, AIDGE_FMT>(out, h, w);
const int dev_ofst = get_ofst_from_fmt<NB_OUTPUTS, OUT_WIDTH, OUT_HEIGHT, DEV_FMT>(out, h, w);
if (aidge_ofst == -1 || dev_ofst == -1) {
printf("[FAILURE]\n");
printf("[ERROR] - Aborting this layer comparison...\n");
return;
}
// Float Comparison
if (std::is_floating_point<DevOutput_T>::value) {
const float diff = std::abs(aidge_output[aidge_ofst] - dev_output[dev_ofst]);
const float tolerance = atol + rtol * std::abs(dev_output[dev_ofst]);
if (diff > tolerance) {
printf("[FAILURE]\n");
printf("[ERROR] - First error detected at %dx%dx%d (out x h x w) : aidge_out = %f vs dev_out = %f\n",
out, h, w, static_cast<double>(aidge_output[aidge_ofst]), static_cast<double>(dev_output[dev_ofst]));
printf("Abort program.\n");
exit(1);
}
// Int Comparison
} else {
if (aidge_output[aidge_ofst] != dev_output[dev_ofst]) {
printf("[FAILURE]\n");
printf("[ERROR] - First error detected at %dx%dx%d (out x h x w) : aidge_out = %d vs dev_out = %d\n",
out, h, w, static_cast<int>(aidge_output[aidge_ofst]), static_cast<int>(dev_output[dev_ofst]));
out, h, w, static_cast<int>(aidge_output[aidge_ofst]), static_cast<int>(dev_output[dev_ofst]));
printf("[ERROR] - Abort program.\n");
exit(1);
}
printf("Abort program.\n");
exit(1);
}
}
}
......
#define {{ out_name[0] | upper }}_DEV_FMT Format::{{ out_format[0] | upper }}
\ No newline at end of file
......@@ -6,6 +6,7 @@
{# For layer configuration -#}
#define {{ name|upper }}_NB_ELTS {{ in_dims[0]|join('*') }}
#define {{ name|upper }}_ACTIVATION {{ activation }}
{% include "./_save_outputs.jinja" %}
{% include "./_def_io.jinja" %}
{% include "./_meminfo.jinja" %}
{% include "./_rescaling.jinja" %}
......
......@@ -8,6 +8,7 @@
{% include "./_meminfo.jinja" %}
#define {{ name|upper }}_ACTIVATION {{ activation }}
#define {{ name|upper }}_EPSILON {{ epsilon }}
{% include "./_save_outputs.jinja" %}
{% include "./_rescaling.jinja" %}
#endif /* {{ name|upper }}_LAYER_H */
......@@ -14,4 +14,6 @@ constexpr int {{name|upper}}_AXIS_SIZE[] = { {{ axis_size|join(", ") }} };
#define {{ name|upper }}_AXIS_SIZE_POST {{ axis_size_post }}
#define {{ name|upper }}_AXIS_SIZE_PRE {{ axis_size_pre }}
{% include "./_save_outputs.jinja" %}
#endif /* {{ name|upper }}_LAYER_H */
......@@ -14,6 +14,7 @@
#define {{ name|upper }}_KERNEL_HEIGHT {{ kernel_dims[0] if kernel_dims|length > 1 else 1 }}
#define {{ name|upper }}_KERNEL_WIDTH {{ kernel_dims[1] if kernel_dims|length > 1 else kernel_dims[0] }}
#define {{ name|upper }}_ACTIVATION {{ activation }}
{% include "./_save_outputs.jinja" %}
{% include "./_rescaling.jinja" %}
{#- Calculate sizes #}
......
......@@ -16,6 +16,8 @@ constexpr int {{name|upper}}_OFFSET_IN2[] = { {{ offset_in2|join(", ") }} };
#define {{ name|upper }}_ACTIVATION {{ activation }}
#define {{ name|upper }}_ELEM_OP {{ elemwise_op }}
{% include "./_save_outputs.jinja" %}
{% include "./_rescaling.jinja" %}
#endif /* {{ name|upper }}_LAYER_H */
......@@ -6,5 +6,6 @@
{% include "./_def_io.jinja" %}
{% include "./_meminfo.jinja" %}
#define {{ name|upper }}_NB_ELTS {{ in_dims[0]|join('*') }}
{% include "./_save_outputs.jinja" %}
#endif /* {{ name|upper }}_LAYER_H */
\ No newline at end of file
......@@ -13,4 +13,6 @@
#define {{ name|upper }}_WEIGHTS_SIZE {{ weights_size }}
#define {{ name|upper }}_BIASES_SIZE {{ out_chan[0] }}
{% include "./_save_outputs.jinja" %}
#endif /* {{ name|upper }}_LAYER_H */
......@@ -10,5 +10,6 @@
#define {{ name|upper }}_AXIS_STRIDE {{ axis_stride }}
#define {{ name|upper }}_POSTAXIS_STRIDE {{ postaxis_stride }}
#define {{ name|upper }}_INOUT_NB_ELTS {{ out_nb_elts }}
{% include "./_save_outputs.jinja" %}
#endif /* {{ name|upper }}_LAYER_H */
......@@ -7,5 +7,6 @@
{% include "./_meminfo.jinja" %}
#define {{ name|upper }}_NB_ELTS {{ in_dims[0]|join('*') }}
#define {{ name|upper }}_ALPHA {{ alpha }}
{% include "./_save_outputs.jinja" %}
#endif /* {{ name|upper }}_LAYER_H */
......@@ -16,6 +16,8 @@ constexpr int {{name|upper}}_OFFSET_IN2[] = { {{ offset_in2|join(", ") }} };
#define {{ name|upper }}_ACTIVATION {{ activation }}
{% include "./_save_outputs.jinja" %}
{% include "./_rescaling.jinja" %}
{#- Calculate sizes #}
......
......@@ -9,5 +9,6 @@
#define {{ name|upper }}_PADDING_TOP {{ padding[0] }}
#define {{ name|upper }}_PADDING_LEFT {{ padding[1] }}
#define {{ name|upper }}_BORDER_VALUE {{ border_value }}
{% include "./_save_outputs.jinja" %}
#endif /* {{ name|upper }}_LAYER_H */
......@@ -13,5 +13,6 @@
#define {{ name|upper }}_KERNEL_WIDTH {{ kernel_dims[1] }}
#define {{ name|upper }}_POOLING_TYPE {{ pool_type }}
#define {{ name|upper }}_ACTIVATION {{ activation }}
{% include "./_save_outputs.jinja" %}
#endif /* {{ name|upper }}_LAYER_H */
......@@ -18,5 +18,7 @@ static const unsigned int {{ name|upper }}_PREAXIS_STRIDES[{{ in_nb_dims }}] =
static const unsigned int {{ name|upper }}_POSTAXIS_STRIDES[{{ in_nb_dims }}] =
{ {{ post_axis_strides|join(", ") }} };
{% include "./_save_outputs.jinja" %}
#endif /* {{ name|upper }}_LAYER_H */
......@@ -6,5 +6,6 @@
{% include "./_meminfo.jinja" %}
{# For layer configuration -#}
#define {{ name|upper }}_NB_ELTS {{ in_dims[0]|join('*') }}
{% include "./_save_outputs.jinja" %}
#endif /* {{ name|upper }}_LAYER_H */
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment