提交 fc74d5f2 编写于 作者: M Megvii Engine Team

feat(mge/dump): add more options for megbrain_graph.dump_graph

GitOrigin-RevId: 848e3c87c183e716c750a2744f0dc27a50e5186a
上级 fade97d4
...@@ -11,6 +11,7 @@ import json ...@@ -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): ...@@ -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): ...@@ -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): CompGraphDumpResult = collections.namedtuple(
return _imperative_rt.dump_graph([i._node for i in args]) "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): ...@@ -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: ...@@ -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; ...@@ -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) { ...@@ -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) { ...@@ -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) { ...@@ -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) { m.def("load_graph", [](
using namespace mgb::serialization; std::string& buf,
auto file = InputFile::make_mem_proxy(buf.c_str(), buf.length()); py::list& output_var_map,
auto format = GraphLoader::identify_graph_dump_format(*file); py::list& output_var_list
auto loader = GraphLoader::make(std::move(file), format.val()); ) {
GraphLoader::LoadConfig config; 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; for (auto i : rst.output_var_map) {
SymbolVarArray output_var_list; output_var_map.append(py::make_tuple(i.first, i.second.node()));
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 : output_var_map){ for (auto i : rst.output_var_list) {
_output_var_map.append(py::make_tuple(i.first,i.second.node())); 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) { ...@@ -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) { for (const auto& var : rst.output_var_list) {
iter.add(var.node()->owner_opr()); iter.add(var);
} }
return rst.graph; return rst.graph;
......
...@@ -12,12 +12,10 @@ from tempfile import mkstemp ...@@ -12,12 +12,10 @@ from tempfile import mkstemp
import numpy as np import numpy as np
import pytest import pytest
import megengine import megengine.core.tensor.megbrain_graph as G
import megengine.module as M
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(): ...@@ -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)
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册