GitOrigin-RevId: 848e3c87c1
tags/v1.0.0-rc1
| @@ -11,6 +11,7 @@ import json | |||||
| import threading | import threading | ||||
| import weakref | import weakref | ||||
| from concurrent.futures import Future, ThreadPoolExecutor | from concurrent.futures import Future, ThreadPoolExecutor | ||||
| from typing import Dict, List, Union | |||||
| import numpy as np | import numpy as np | ||||
| @@ -85,6 +86,97 @@ class Graph(_imperative_rt.ComputingGraph): | |||||
| return self._wrap(_imperative_rt.make_h2d(self, device, dtype, shape, name)) | return self._wrap(_imperative_rt.make_h2d(self, device, dtype, shape, name)) | ||||
| class VarNode(TensorBase): | |||||
| def __init__(self, node: _imperative_rt.VarNode): | |||||
| self._node = node | |||||
| if hasattr(self.graph, "_var_cache"): | |||||
| self.graph._var_cache[node] = self | |||||
| @property | |||||
| def graph(self) -> Graph: | |||||
| return self._node.graph | |||||
| @property | |||||
| def op(self): | |||||
| if hasattr(self.graph, "_wrap"): | |||||
| return self.graph._wrap(self._node.owner) | |||||
| else: | |||||
| return self._node.owner | |||||
| @property | |||||
| def name(self): | |||||
| return self._node.name | |||||
| @property | |||||
| def id(self): | |||||
| return self._node.id | |||||
| @name.setter | |||||
| def name(self, name): | |||||
| self._node.name = name | |||||
| @property | |||||
| def dtype(self): | |||||
| return self._node.dtype | |||||
| @property | |||||
| def device(self): | |||||
| return as_device(self._node.comp_node) | |||||
| @property | |||||
| def shape(self): | |||||
| return self._node.shape | |||||
| @property | |||||
| def value(self): | |||||
| return self._node.value | |||||
| class OpNode: | |||||
| def __init__(self, node: _imperative_rt.OperatorNode): | |||||
| self._node = node | |||||
| if hasattr(self.graph, "_op_cache"): | |||||
| self.graph._op_cache[node] = self | |||||
| @property | |||||
| def graph(self) -> Graph: | |||||
| return self._node.graph | |||||
| @property | |||||
| def name(self): | |||||
| return self._node.name | |||||
| @property | |||||
| def id(self): | |||||
| return self._node.id | |||||
| @name.setter | |||||
| def name(self, name): | |||||
| self._node.name = name | |||||
| @property | |||||
| def inputs(self): | |||||
| if hasattr(self.graph, "_wrap"): | |||||
| return tuple(map(self.graph._wrap, self._node.inputs)) | |||||
| else: | |||||
| return self._node.inputs | |||||
| @property | |||||
| def outputs(self): | |||||
| if hasattr(self.graph, "_wrap"): | |||||
| return tuple(map(self.graph._wrap, self._node.outputs)) | |||||
| else: | |||||
| return self._node.outputs | |||||
| @property | |||||
| def params(self): | |||||
| return json.loads(self._node.params) | |||||
| @property | |||||
| def type(self): | |||||
| return self._node.type | |||||
| def optimize_for_inference(dest_vars, **kwargs): | def optimize_for_inference(dest_vars, **kwargs): | ||||
| r"""Applies optimize_for_inference pass for computing graph. | r"""Applies optimize_for_inference pass for computing graph. | ||||
| @@ -162,8 +254,100 @@ def optimize_for_inference(dest_vars, **kwargs): | |||||
| return [VarNode(i) for i in res_vars] | return [VarNode(i) for i in res_vars] | ||||
| def dump_graph(*args): | |||||
| return _imperative_rt.dump_graph([i._node for i in args]) | |||||
| CompGraphDumpResult = collections.namedtuple( | |||||
| "CompGraphDumpResult", | |||||
| [ | |||||
| "nr_opr", | |||||
| "tot_bytes", | |||||
| "tensor_value_bytes", | |||||
| "content_hash", | |||||
| "inputs", | |||||
| "outputs", | |||||
| "params", | |||||
| ], | |||||
| ) | |||||
| def dump_graph( | |||||
| output_vars: Union[Dict[str, VarNode], List[VarNode]], | |||||
| *, | |||||
| keep_var_name: int = 1, | |||||
| keep_param_name: bool = False, | |||||
| keep_opr_priority: bool = False, | |||||
| strip_info_file=None, | |||||
| ): | |||||
| """serialize the computing graph of `output_vars` and get byte result. | |||||
| :param output_vars: output variables which are the graph's end point. | |||||
| .. note:: | |||||
| The underlying C++ API only accepts a var list. If a dict is given, | |||||
| the vars would be renamed to the given names. | |||||
| :param keep_var_name: level for keeping variable names: | |||||
| * 0: none of the names are kept | |||||
| * 1: (default)keep names of output vars | |||||
| * 2: keep names of all (output and internal) vars | |||||
| :param keep_param_name: whether to keep param names, so param values can be | |||||
| easily manipulated after loading model | |||||
| :param keep_opr_priority: whether to keep priority setting for operators | |||||
| :param strip_info_file: a string for path or a file handler. if is not None, | |||||
| then the dump information for code strip would be written to ``strip_info_file`` | |||||
| :return: dump result as byte string, and an instance of namedtuple | |||||
| :class:`CompGraphDumpResult`, whose fields are: | |||||
| * ``nr_opr`` number of operators dumped | |||||
| * ``tot_bytes`` total bytes for the whole graph | |||||
| * ``tensor_value_bytes`` bytes consumed for dumping tensor values | |||||
| * ``inputs`` names of input tensors | |||||
| * ``params`` list of names of dumped params | |||||
| * ``outputs`` names of output vars | |||||
| """ | |||||
| ov = [] | |||||
| if isinstance(output_vars, dict): | |||||
| used_vars = set() | |||||
| for name, var in output_vars.items(): | |||||
| assert isinstance(var, VarNode), "bad output var: {!r}".format(var) | |||||
| assert var.id not in used_vars, ( | |||||
| "var name is associated with a var object, so we can not have " | |||||
| "two names given to the same var: {}".format(var) | |||||
| ) | |||||
| used_vars.add(var.id) | |||||
| var.name = name | |||||
| ov.append(var._node) | |||||
| else: | |||||
| for var in output_vars: | |||||
| assert isinstance(var, VarNode), "bad output var: {!r}".format(var) | |||||
| ov.append(var._node) | |||||
| stat = [] | |||||
| inputs = [] | |||||
| outputs = [] | |||||
| params = [] | |||||
| dump_content = _imperative_rt.dump_graph( | |||||
| ov, | |||||
| keep_var_name, | |||||
| keep_param_name, | |||||
| keep_opr_priority, | |||||
| stat, | |||||
| inputs, | |||||
| outputs, | |||||
| params, | |||||
| ) | |||||
| dump_info = CompGraphDumpResult(*stat, inputs, outputs, params) | |||||
| if strip_info_file is not None: | |||||
| if isinstance(strip_info_file, str): | |||||
| strip_info_file = open(strip_info_file, "w") | |||||
| strip_info = json.loads(_imperative_rt.get_info_for_strip(ov)) | |||||
| strip_info["hash"] = dump_info.content_hash | |||||
| json.dump(strip_info, strip_info_file) | |||||
| return dump_content, dump_info | |||||
| CompGraphLoadResult = collections.namedtuple( | CompGraphLoadResult = collections.namedtuple( | ||||
| @@ -193,97 +377,6 @@ def load_graph(fpath): | |||||
| return CompGraphLoadResult(cg, dict(output_vars_map), output_vars_list) | return CompGraphLoadResult(cg, dict(output_vars_map), output_vars_list) | ||||
| class VarNode(TensorBase): | |||||
| def __init__(self, node: _imperative_rt.VarNode): | |||||
| self._node = node | |||||
| if hasattr(self.graph, "_var_cache"): | |||||
| self.graph._var_cache[node] = self | |||||
| @property | |||||
| def graph(self) -> Graph: | |||||
| return self._node.graph | |||||
| @property | |||||
| def op(self): | |||||
| if hasattr(self.graph, "_wrap"): | |||||
| return self.graph._wrap(self._node.owner) | |||||
| else: | |||||
| return self._node.owner | |||||
| @property | |||||
| def name(self): | |||||
| return self._node.name | |||||
| @property | |||||
| def id(self): | |||||
| return self._node.id | |||||
| @name.setter | |||||
| def name(self, name): | |||||
| self._node.name = name | |||||
| @property | |||||
| def dtype(self): | |||||
| return self._node.dtype | |||||
| @property | |||||
| def device(self): | |||||
| return as_device(self._node.comp_node) | |||||
| @property | |||||
| def shape(self): | |||||
| return self._node.shape | |||||
| @property | |||||
| def value(self): | |||||
| return self._node.value | |||||
| class OpNode: | |||||
| def __init__(self, node: _imperative_rt.OperatorNode): | |||||
| self._node = node | |||||
| if hasattr(self.graph, "_op_cache"): | |||||
| self.graph._op_cache[node] = self | |||||
| @property | |||||
| def graph(self) -> Graph: | |||||
| return self._node.graph | |||||
| @property | |||||
| def name(self): | |||||
| return self._node.name | |||||
| @property | |||||
| def id(self): | |||||
| return self._node.id | |||||
| @name.setter | |||||
| def name(self, name): | |||||
| self._node.name = name | |||||
| @property | |||||
| def inputs(self): | |||||
| if hasattr(self.graph, "_wrap"): | |||||
| return tuple(map(self.graph._wrap, self._node.inputs)) | |||||
| else: | |||||
| return self._node.inputs | |||||
| @property | |||||
| def outputs(self): | |||||
| if hasattr(self.graph, "_wrap"): | |||||
| return tuple(map(self.graph._wrap, self._node.outputs)) | |||||
| else: | |||||
| return self._node.outputs | |||||
| @property | |||||
| def params(self): | |||||
| return json.loads(self._node.params) | |||||
| @property | |||||
| def type(self): | |||||
| return self._node.type | |||||
| def _wrap(x): | def _wrap(x): | ||||
| if isinstance(x, collections.abc.Sequence): | if isinstance(x, collections.abc.Sequence): | ||||
| return type(x)(map(_wrap, x)) | return type(x)(map(_wrap, x)) | ||||
| @@ -589,7 +589,9 @@ class trace: | |||||
| if isinstance(file, str): | if isinstance(file, str): | ||||
| permission = "wb" if append == False else "ab" | permission = "wb" if append == False else "ab" | ||||
| file = open(file, permission) | file = open(file, permission) | ||||
| file.write(G.dump_graph(*dest_vars)) | |||||
| dump_content, dump_info = G.dump_graph(dest_vars) | |||||
| file.write(dump_content) | |||||
| return dump_info | |||||
| def _process_inputs(self, *args, **kwargs): | def _process_inputs(self, *args, **kwargs): | ||||
| if self._untraced: | if self._untraced: | ||||
| @@ -27,6 +27,7 @@ namespace py = pybind11; | |||||
| using namespace mgb; | using namespace mgb; | ||||
| using namespace imperative; | using namespace imperative; | ||||
| namespace ser = mgb::serialization; | |||||
| using _OptimizeForInferenceOptions = mgb::gopt::OptimizeForInferenceOptions; | using _OptimizeForInferenceOptions = mgb::gopt::OptimizeForInferenceOptions; | ||||
| using _LayoutTransform = _OptimizeForInferenceOptions::LayoutTransform; | using _LayoutTransform = _OptimizeForInferenceOptions::LayoutTransform; | ||||
| @@ -183,7 +184,6 @@ void init_graph_rt(py::module m) { | |||||
| return "Opr:" + opr->name(); | return "Opr:" + opr->name(); | ||||
| }); | }); | ||||
| py::class_<cg::AsyncExecutable>(m, "AsyncExecutable") | py::class_<cg::AsyncExecutable>(m, "AsyncExecutable") | ||||
| .def("execute", &cg::AsyncExecutable::execute, py::call_guard<py::gil_scoped_release>()) | .def("execute", &cg::AsyncExecutable::execute, py::call_guard<py::gil_scoped_release>()) | ||||
| .def("wait", &cg::AsyncExecutable::wait, py::call_guard<py::gil_scoped_release>()); | .def("wait", &cg::AsyncExecutable::wait, py::call_guard<py::gil_scoped_release>()); | ||||
| @@ -206,15 +206,6 @@ void init_graph_rt(py::module m) { | |||||
| })) | })) | ||||
| .def("get", [](_CompGraphProfilerImpl& profiler) { return profiler._get_result(); }); | .def("get", [](_CompGraphProfilerImpl& profiler) { return profiler._get_result(); }); | ||||
| m.def("dump_graph", [](const std::vector<VarNode*>& dest_vars) { | |||||
| using namespace mgb::serialization; | |||||
| std::vector<uint8_t> buf; | |||||
| auto dumper = GraphDumper::make(OutputFile::make_vector_proxy(&buf)); | |||||
| SymbolVarArray symvars(dest_vars.begin(), dest_vars.end()); | |||||
| dumper->dump(symvars); | |||||
| return py::bytes(reinterpret_cast<const char*>(&buf[0]), buf.size()); | |||||
| }); | |||||
| auto GraphOptimizeOptions = py::class_<_OptimizeForInferenceOptions>(m, "GraphOptimizeOptions") | auto GraphOptimizeOptions = py::class_<_OptimizeForInferenceOptions>(m, "GraphOptimizeOptions") | ||||
| .def(py::init()) | .def(py::init()) | ||||
| .def_readwrite("f16_io_f32_comp", &_OptimizeForInferenceOptions::f16_io_f32_comp) | .def_readwrite("f16_io_f32_comp", &_OptimizeForInferenceOptions::f16_io_f32_comp) | ||||
| @@ -245,23 +236,91 @@ void init_graph_rt(py::module m) { | |||||
| return vars; | return vars; | ||||
| }); | }); | ||||
| m.def("get_info_for_strip", [](const std::vector<VarNode*>& dest_vars) { | |||||
| std::unordered_set<const char*> opr_types, dtype_names, elemwise_modes; | |||||
| auto on_opr = [&](cg::OperatorNodeBase *opr) { | |||||
| if (ser::GraphDumper::should_remove_in_dump(opr)) | |||||
| return; | |||||
| opr_types.insert(opr->dyn_typeinfo()->name); | |||||
| for (auto i : opr->output()) | |||||
| dtype_names.insert(i->dtype().name()); | |||||
| if (opr->same_type<opr::Elemwise>()) { | |||||
| auto mode = opr->cast_final<opr::Elemwise>().param().mode; | |||||
| elemwise_modes.insert( | |||||
| megdnn::Elemwise::ModeTrait::from_mode(mode).name); | |||||
| } | |||||
| }; | |||||
| cg::DepOprIter opr_iter{on_opr}; | |||||
| for (auto i : dest_vars) | |||||
| opr_iter.add(i->owner_opr()); | |||||
| auto to_json = [](const std::unordered_set<const char*> &v) { | |||||
| std::vector<std::string> vs(v.begin(), v.end()); | |||||
| std::sort(vs.begin(), vs.end()); | |||||
| auto ret = json::Array::make(); | |||||
| for (auto &&i : vs) | |||||
| ret->add(json::String::make(i)); | |||||
| return ret; | |||||
| }; | |||||
| return json::Object::make({ | |||||
| {"opr_types", to_json(opr_types)}, | |||||
| {"dtypes", to_json(dtype_names)}, | |||||
| {"elemwise_modes", to_json(elemwise_modes)}, | |||||
| }); | |||||
| }); | |||||
| m.def("dump_graph", []( | |||||
| const std::vector<VarNode*>& dest_vars, | |||||
| int keep_var_name, | |||||
| bool keep_param_name, | |||||
| bool keep_opr_priority, | |||||
| py::list& stat, | |||||
| py::list& inputs, | |||||
| py::list& outputs, | |||||
| py::list& params | |||||
| ) { | |||||
| std::vector<uint8_t> buf; | |||||
| auto dumper = ser::GraphDumper::make(ser::OutputFile::make_vector_proxy(&buf)); | |||||
| SymbolVarArray symvars(dest_vars.begin(), dest_vars.end()); | |||||
| ser::GraphDumper::DumpConfig config{keep_var_name, keep_param_name, | |||||
| keep_opr_priority}; | |||||
| auto rst = dumper->dump(symvars, config); | |||||
| for (auto i : rst.inputs) { | |||||
| inputs.append(py::cast(i)); | |||||
| } | |||||
| for (auto i : rst.outputs) { | |||||
| outputs.append(py::cast(i)); | |||||
| } | |||||
| for (auto i : rst.params) { | |||||
| params.append(py::cast(i)); | |||||
| } | |||||
| auto rst_stat = std::vector{ | |||||
| rst.nr_opr, rst.tot_bytes, rst.tensor_value_bytes, rst.content_hash | |||||
| }; | |||||
| for (auto i : rst_stat) { | |||||
| stat.append(py::cast(i)); | |||||
| } | |||||
| return py::bytes(reinterpret_cast<const char*>(&buf[0]), buf.size()); | |||||
| }); | |||||
| m.def("load_graph", [](std::string& buf, py::list& _output_var_map, py::list& _output_var_list) { | |||||
| using namespace mgb::serialization; | |||||
| auto file = InputFile::make_mem_proxy(buf.c_str(), buf.length()); | |||||
| auto format = GraphLoader::identify_graph_dump_format(*file); | |||||
| auto loader = GraphLoader::make(std::move(file), format.val()); | |||||
| GraphLoader::LoadConfig config; | |||||
| m.def("load_graph", []( | |||||
| std::string& buf, | |||||
| py::list& output_var_map, | |||||
| py::list& output_var_list | |||||
| ) { | |||||
| auto file = ser::InputFile::make_mem_proxy(buf.c_str(), buf.length()); | |||||
| auto format = ser::GraphLoader::identify_graph_dump_format(*file); | |||||
| auto loader = ser::GraphLoader::make(std::move(file), format.val()); | |||||
| ser::GraphLoader::LoadConfig config; | |||||
| auto rst = loader->load(config); | auto rst = loader->load(config); | ||||
| std::vector<std::pair<std::string, SymbolVar>> output_var_map; | |||||
| SymbolVarArray output_var_list; | |||||
| output_var_map = {rst.output_var_map.begin(), rst.output_var_map.end()}; | |||||
| output_var_list = std::move(rst.output_var_list); | |||||
| for (auto i : output_var_list){ | |||||
| _output_var_list.append(i.node()); | |||||
| for (auto i : rst.output_var_map) { | |||||
| output_var_map.append(py::make_tuple(i.first, i.second.node())); | |||||
| } | } | ||||
| for (auto i : output_var_map){ | |||||
| _output_var_map.append(py::make_tuple(i.first,i.second.node())); | |||||
| for (auto i : rst.output_var_list) { | |||||
| output_var_list.append(i.node()); | |||||
| } | } | ||||
| std::unordered_map<HostTensorND*, const std::string*> tensor2name; | std::unordered_map<HostTensorND*, const std::string*> tensor2name; | ||||
| for (const auto& pair : rst.tensor_map) { | for (const auto& pair : rst.tensor_map) { | ||||
| @@ -277,8 +336,8 @@ void init_graph_rt(py::module m) { | |||||
| h2d.output(0)->name(*it->second); | h2d.output(0)->name(*it->second); | ||||
| }; | }; | ||||
| cg::DepOprIter iter{cb}; | cg::DepOprIter iter{cb}; | ||||
| for (const auto& var : output_var_list) { | |||||
| iter.add(var.node()->owner_opr()); | |||||
| for (const auto& var : rst.output_var_list) { | |||||
| iter.add(var); | |||||
| } | } | ||||
| return rst.graph; | return rst.graph; | ||||
| @@ -12,12 +12,10 @@ from tempfile import mkstemp | |||||
| import numpy as np | import numpy as np | ||||
| import pytest | import pytest | ||||
| import megengine | |||||
| import megengine.module as M | |||||
| import megengine.core.tensor.megbrain_graph as G | |||||
| from megengine import cgtools, tensor | from megengine import cgtools, tensor | ||||
| from megengine.core._trace_option import set_tensor_shape | from megengine.core._trace_option import set_tensor_shape | ||||
| from megengine.core.ops import builtin as ops | from megengine.core.ops import builtin as ops | ||||
| from megengine.core.tensor import megbrain_graph as G | |||||
| from megengine.core.tensor.core import apply | from megengine.core.tensor.core import apply | ||||
| from megengine.core.tensor.raw_tensor import as_raw_tensor | from megengine.core.tensor.raw_tensor import as_raw_tensor | ||||
| from megengine.functional import exp, log | from megengine.functional import exp, log | ||||
| @@ -121,7 +119,10 @@ def test_dump(): | |||||
| np.testing.assert_equal(f(as_raw_tensor(a), as_raw_tensor(b)).numpy(), y) | np.testing.assert_equal(f(as_raw_tensor(a), as_raw_tensor(b)).numpy(), y) | ||||
| file = io.BytesIO() | file = io.BytesIO() | ||||
| f.dump(file) | |||||
| dump_info = f.dump(file) | |||||
| assert dump_info.nr_opr == 3 | |||||
| np.testing.assert_equal(dump_info.inputs, ["h2d[0]", "h2d[2]"]) | |||||
| np.testing.assert_equal(dump_info.outputs, ["ADD(h2d[0],h2d[2])[4]"]) | |||||
| file.seek(0) | file.seek(0) | ||||
| result = load_and_inference(file, [a, b]) | result = load_and_inference(file, [a, b]) | ||||
| np.testing.assert_equal(result[0], y) | np.testing.assert_equal(result[0], y) | ||||