Commit d3f3bb1b authored by Bob Kimball's avatar Bob Kimball Committed by Robert Kimball

add liveness pass

add naming of tensors and tensor views
add visualization pass
add graph dump pass
parent d93260a9
......@@ -24,6 +24,7 @@ set (SRC
ngraph/shape.cpp
ngraph/pass/assign_tensors.cpp
ngraph/pass/call_pass.cpp
ngraph/pass/dump_sorted.cpp
ngraph/pass/liveness.cpp
ngraph/pass/manager.cpp
ngraph/pass/memory_layout.cpp
......@@ -31,6 +32,7 @@ set (SRC
ngraph/pass/propagate_types.cpp
ngraph/pass/topological_sort.cpp
ngraph/pass/tree_pass.cpp
ngraph/pass/visualize_tree.cpp
ngraph/runtime/call_frame.cpp
ngraph/runtime/eigen/external_function.cpp
ngraph/runtime/eigen/tensor_view.cpp
......
......@@ -33,3 +33,13 @@ std::shared_ptr<Node> Input::get_node()
{
return m_node->shared_from_this();
}
const Tensor& Input::get_tensor() const
{
return m_output.get_tensor();
}
Tensor& Input::get_tensor()
{
return m_output.get_tensor();
}
......@@ -16,6 +16,8 @@
#include <memory>
#include "ngraph/descriptor/tensor.hpp"
namespace ngraph
{
class Node;
......@@ -47,6 +49,8 @@ namespace ngraph
size_t get_index() const { return m_index; }
const Output& get_output() const { return m_output; }
Output& get_output() { return m_output; }
const Tensor& get_tensor() const;
Tensor& get_tensor();
protected:
Node* m_node; // The node we are an input for
......
......@@ -35,3 +35,13 @@ std::shared_ptr<Node> Output::get_node() const
{
return m_node->shared_from_this();
}
const Tensor& Output::get_tensor() const
{
return m_tensor_view->get_tensor();
}
Tensor& Output::get_tensor()
{
return m_tensor_view->get_tensor();
}
......@@ -42,6 +42,8 @@ namespace ngraph
std::shared_ptr<TensorView> get_tensor_view() const { return m_tensor_view; }
void add_input(Input* input);
const std::set<Input*>& get_inputs() const { return m_inputs; }
const Tensor& get_tensor() const;
Tensor& get_tensor();
protected:
Node* m_node;
......
......@@ -13,12 +13,30 @@
// ----------------------------------------------------------------------------
#include "ngraph/descriptor/tensor.hpp"
#include "ngraph/node.hpp"
using namespace ngraph;
using namespace descriptor;
Tensor::Tensor(const element::Type& element_type, PrimaryTensorView* primary_tensor_view)
Tensor::Tensor(const element::Type& element_type, PrimaryTensorView* primary_tensor_view,
const Node* parent, size_t value_index)
: m_element_type(element_type)
, m_primary_tensor_view(primary_tensor_view)
, m_is_output{false}
, m_is_input{parent->is_parameter()}
, m_is_persistent{false}
, m_name{parent->get_node_id()+"_"+std::to_string(value_index)}
, m_next_view_id{0}
{
}
std::string Tensor::get_next_view_name()
{
return m_name + "_TV" + std::to_string(m_next_view_id++);
}
std::ostream& ngraph::descriptor::operator<<(std::ostream& out, const Tensor& tensor)
{
out << "Tensor(" << tensor.get_name() << ")";
return out;
}
......@@ -16,9 +16,12 @@
#include <memory>
#include <vector>
#include <iostream>
namespace ngraph
{
class Node;
namespace element
{
class Type;
......@@ -33,14 +36,31 @@ namespace ngraph
{
friend class PrimaryTensorView;
private:
Tensor(const Tensor&) = delete;
Tensor& operator=(const Tensor&) = delete;
Tensor(const element::Type& element_type, PrimaryTensorView* tensor_view);
Tensor(const element::Type& element_type, PrimaryTensorView* tensor_view,
const Node* parent, size_t value_index);
std::string get_next_view_name();
public:
bool is_output() const { return m_is_output; }
bool is_input() const { return m_is_input; }
bool is_persistent() const { return m_is_persistent; }
const std::string& get_name() const { return m_name; }
friend std::ostream& operator<<(std::ostream&, const Tensor&);
protected:
const element::Type& m_element_type;
PrimaryTensorView* m_primary_tensor_view;
bool m_is_output;
bool m_is_input;
bool m_is_persistent;
std::string m_name;
size_t m_next_view_id;
};
}
}
......@@ -17,9 +17,12 @@
#include "ngraph/descriptor/tensor.hpp"
#include "ngraph/shape.hpp"
#include "ngraph/type.hpp"
#include "log.hpp"
namespace ngraph
{
class Node;
namespace descriptor
{
class Tensor;
......@@ -41,6 +44,7 @@ namespace ngraph
virtual ~TensorView() {}
virtual const Tensor& get_tensor() const = 0;
virtual Tensor& get_tensor() = 0;
const std::string& get_name() const { return m_name; }
std::shared_ptr<const TensorViewType> get_tensor_view_type() const
{
......@@ -51,6 +55,7 @@ namespace ngraph
{
return m_tensor_view_layout;
}
void set_tensor_view_layout(const std::shared_ptr<TensorViewLayout>& tensor_view_layout)
{
m_tensor_view_layout = tensor_view_layout;
......@@ -59,6 +64,7 @@ namespace ngraph
protected:
std::shared_ptr<const TensorViewType> m_tensor_view_type;
std::shared_ptr<TensorViewLayout> m_tensor_view_layout;
std::string m_name;
};
// A PrimaryTensorView owns the tensor. All other views are the result
......@@ -66,10 +72,14 @@ namespace ngraph
class PrimaryTensorView : public TensorView
{
public:
PrimaryTensorView(const std::shared_ptr<const TensorViewType>& tensor_view_type)
PrimaryTensorView(const std::shared_ptr<const TensorViewType>& tensor_view_type,
const Node* parent, size_t value_index)
: TensorView(tensor_view_type)
, m_tensor(tensor_view_type->get_element_type(), this)
, m_tensor(tensor_view_type->get_element_type(), this, parent, value_index)
{
// Set the name in the parent TensorView.
// This can't be done until after the m_tensor is constructed.
m_name = m_tensor.get_next_view_name();
}
virtual const Tensor& get_tensor() const override;
......
......@@ -57,8 +57,9 @@ void Node::assign_tensors()
size_t i = 0;
for (auto tvt : tensor_view_types)
{
auto tensor_view_descriptor = make_shared<descriptor::PrimaryTensorView>(tvt);
m_outputs.emplace_back(this, i++, tensor_view_descriptor);
auto tensor_view_descriptor = make_shared<descriptor::PrimaryTensorView>(tvt, this, i);
m_outputs.emplace_back(this, i, tensor_view_descriptor);
i++;
}
i = 0;
......@@ -68,7 +69,8 @@ void Node::assign_tensors()
size_t arg_index = 0;
for (descriptor::Output& output : arg->get_outputs())
{
m_inputs.emplace_back(this, i++, argno, arg_index++, output);
m_inputs.emplace_back(this, i, argno, arg_index++, output);
i++;
}
argno++;
}
......
......@@ -17,6 +17,7 @@
#include <set>
#include <string>
#include <vector>
#include <unordered_set>
#include <iostream>
......@@ -29,6 +30,7 @@ namespace ngraph
{
class Input;
class Output;
class Tensor;
}
/// Nodes are the backbone of the graph of Value dataflow. Every node has
......@@ -51,7 +53,7 @@ namespace ngraph
virtual ~Node();
public:
/// A "one-liner" describing this node.
/// The class name, must not contain spaces
virtual std::string description() const = 0;
/// Propagate types and check arguments for consistency
......@@ -66,9 +68,6 @@ namespace ngraph
const std::multiset<Node*>& users() const { return m_users; }
std::string get_name() const { return m_name; }
void set_name(const std::string& name) { m_name = name; }
virtual std::string get_node_id() const;
/// Return true if this has the same implementing class as node. This
......@@ -104,7 +103,13 @@ namespace ngraph
friend std::ostream& operator<<(std::ostream&, const Node&);
std::vector<descriptor::Input>& get_inputs() { return m_inputs; }
const std::vector<descriptor::Input>& get_inputs() const { return m_inputs; }
std::vector<descriptor::Output>& get_outputs() { return m_outputs; }
const std::vector<descriptor::Output>& get_outputs() const { return m_outputs; }
std::unordered_set<descriptor::Tensor*> liveness_live_list;
std::unordered_set<descriptor::Tensor*> liveness_new_list;
std::unordered_set<descriptor::Tensor*> liveness_free_list;
protected:
Nodes m_arguments;
......
......@@ -42,7 +42,6 @@ namespace ngraph
std::string description() const override { return "Parameter"; }
virtual void propagate_types() override;
virtual std::string get_node_id() const override;
protected:
Function* m_function;
......
// ----------------------------------------------------------------------------
// Copyright 2017 Nervana Systems Inc.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// ----------------------------------------------------------------------------
#include <fstream>
#include "dump_sorted.hpp"
#include "ngraph/ngraph.hpp"
#include "util.hpp"
using namespace ngraph;
using namespace std;
pass::DumpSorted::DumpSorted(const string& output_file)
: m_output_file{output_file}
{
}
bool pass::DumpSorted::run_on_call_list(list<Node*>& nodes)
{
ofstream out{m_output_file};
if (out)
{
for (const Node* node : nodes)
{
out << node->get_node_id() << "(";
vector<string> inputs;
for (const descriptor::Input& input : node->get_inputs())
{
inputs.push_back(input.get_tensor().get_name());
}
out << join(inputs);
out << ") -> ";
vector<string> outputs;
for (const descriptor::Output& output : node->get_outputs())
{
outputs.push_back(output.get_tensor().get_name());
}
out << join(outputs);
out << "\n";
for (const descriptor::Tensor* tensor : node->liveness_live_list)
{
out << " ";
if (contains(node->liveness_new_list, tensor))
{
out << "N ";
}
else if (contains(node->liveness_free_list, tensor))
{
out << "F ";
}
else
{
out << "L ";
}
out << tensor->get_name() << "\n";
}
}
}
return false;
}
// ----------------------------------------------------------------------------
// Copyright 2017 Nervana Systems Inc.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// ----------------------------------------------------------------------------
#pragma once
#include <string>
#include "call_pass.hpp"
namespace ngraph
{
namespace pass
{
class DumpSorted;
}
class Node;
}
class ngraph::pass::DumpSorted : public CallBase
{
public:
DumpSorted(const std::string& output_file);
virtual bool run_on_call_list(std::list<Node*>&) override;
private:
const std::string m_output_file;
};
......@@ -14,111 +14,112 @@
#include <exception>
#include <sstream>
#include <unordered_set>
#include "log.hpp"
#include "ngraph/ngraph.hpp"
#include "ngraph/pass/assign_tensors.hpp"
#include "ngraph/pass/liveness.hpp"
#include "util.hpp"
#include "log.hpp"
using namespace std;
using namespace ngraph;
bool pass::Liveness::run_on_call_list(list<Node*>& ops)
{
// list<Node*> live_list;
// list<Node*> free_list;
// list<Node*> new_list;
// currently_live = list();
// size_t i = 0;
// for (i, exop in enumerate(reversed(ops)
// for(auto it=ops.rbegin(); it!=ops.rend(); it++)
// {
// Node& exop = **it;
// input_tensor_decls = list()
// for (auto input_decl : exop.get_inputs())
// {
// if (is_interesting(input_decl.tensor_decl))
// {
// input_tensor_decls.append(input_decl.tensor_decl);
// }
// }
// output_tensor_decls = list()
// for (output_decl : exop.output_decls)
// {
// if (is_interesting(output_decl.tensor_decl))
// {
// output_tensor_decls.append(output_decl.tensor_decl);
// }
// }
// free_tensor_decls = list();
// new_tensor_decls = list();
// for tensor_decl in input_tensor_decls + output_tensor_decls
// {
// if tensor_decl not in currently_live
// {
// // this is the last node that value is seen in
// // delete it at the end of the op
// currently_live.append(tensor_decl);
// free_tensor_decls.append(tensor_decl);
// }
// }
// live_list.insert(0, list(currently_live))
// for output_decl in output_tensor_decls
// {
// if output_decl in currently_live
// {
// new_tensor_decls.append(output_decl);
// currently_live.remove(output_decl);
// }
// }
// free_list.insert(0, free_tensor_decls);
// new_list.insert(0, new_tensor_decls);
// }
// // Anything marked as output must remain live for the remainder of the graph
// // Add outputs to live_list and remove from free_list
// outputs = list();
// seen = list();
// for i, exop in enumerate(ops)
// {
// for tensor in live_list[i]
// {
// if tensor.is_output and tensor not in outputs
// {
// outputs.append(tensor);
// }
// }
// for tensor in outputs
// {
// if tensor not in live_list[i]
// {
// live_list[i].append(tensor);
// }
// if tensor in free_list[i]
// {
// free_list[i].remove(tensor);
// }
// if tensor in new_list[i]
// {
// if tensor in seen
// {
// new_list[i].remove(tensor);
// }
// else
// {
// seen.append(tensor);
// }
// }
// }
// exop.liveness_live_list = live_list[i];
// exop.liveness_new_list = new_list[i];
// exop.liveness_free_list = free_list[i];
// }
// self.validate_liveness(ops)
unordered_set<descriptor::Tensor*> currently_live;
for(auto it=ops.rbegin(); it!=ops.rend(); it++)
{
Node& exop = **it;
exop.liveness_live_list.clear();
exop.liveness_new_list.clear();
exop.liveness_free_list.clear();
unordered_set<descriptor::Tensor*> input_tensor_decls;
for (auto input_decl : exop.get_inputs())
{
descriptor::Tensor& tensor = input_decl.get_tensor();
if (is_temporary(tensor))
{
input_tensor_decls.insert(&tensor);
}
}
unordered_set<descriptor::Tensor*> output_tensor_decls;
for (auto output_decl : exop.get_outputs())
{
descriptor::Tensor& tensor = output_decl.get_tensor();
if (is_temporary(tensor))
{
output_tensor_decls.insert(&tensor);
}
}
unordered_set<descriptor::Tensor*> free_tensor_decls;
unordered_set<descriptor::Tensor*> new_tensor_decls;
unordered_set<descriptor::Tensor*> all_tensor_decls = input_tensor_decls;
for (auto decls : {input_tensor_decls, output_tensor_decls})
{
for (descriptor::Tensor* tensor_decl : decls)
{
if (!contains(currently_live, tensor_decl))
{
// this is the last node that value is seen in
// delete it at the end of the op
currently_live.insert(tensor_decl);
free_tensor_decls.insert(tensor_decl);
}
}
}
exop.liveness_live_list = currently_live;
for (descriptor::Tensor* output_decl : output_tensor_decls)
{
if (contains(currently_live, output_decl))
{
new_tensor_decls.insert(output_decl);
currently_live.erase(output_decl);
}
}
exop.liveness_free_list = free_tensor_decls;
exop.liveness_new_list = new_tensor_decls;
}
// Anything marked as output must remain live for the remainder of the graph
// Add outputs to live_list and remove from free_list
unordered_set<descriptor::Tensor*> outputs;
unordered_set<descriptor::Tensor*> seen;
for (Node* exop : ops)
{
for (descriptor::Tensor* tensor : exop->liveness_live_list)
{
if (tensor->is_output())
{
outputs.insert(tensor);
}
}
for (descriptor::Tensor* tensor : outputs)
{
exop->liveness_live_list.insert(tensor);
exop->liveness_free_list.erase(tensor);
if (contains(exop->liveness_new_list, tensor))
{
if (contains(seen, tensor))
{
exop->liveness_new_list.erase(tensor);
}
else
{
seen.insert(tensor);
}
}
}
}
validate_liveness(ops);
return false;
}
......@@ -140,30 +141,32 @@ void pass::Liveness::check_dependencies(
}
}
// bool pass::Liveness::is_interesting(tensor_decl)
// {
// return
// tensor_decl.is_persistent == false &&
// tensor_decl.is_constant == false &&
// tensor_decl.is_compile_only == false;
// }
// void pass::Liveness::validate_liveness(ops)
// {
// dead_tensors = set();
// for i, exop in enumerate(ops)
// {
// active = set(exop.liveness_live_list);
// active |= set(exop.liveness_new_list);
// active |= set(exop.liveness_free_list);
// if bool(dead_tensors.intersection(active)) is True
// {
// raise RuntimeError("Liveness: Dead tensors intersect active tensors");
// }
// for tensor in exop.liveness_free_list
// {
// dead_tensors.add(tensor);
// }
// }
// }
bool pass::Liveness::is_temporary(const descriptor::Tensor& tensor)
{
return
tensor.is_persistent() == false
&& tensor.is_input() == false
;
// && tensor.is_constant() == false
// && tensor.is_compile_only() == false;
}
void pass::Liveness::validate_liveness(const list<Node*>& ops)
{
unordered_set<descriptor::Tensor*> dead_tensors;
for (const Node* exop : ops)
{
auto active = exop->liveness_live_list;
active.insert(exop->liveness_new_list.begin(), exop->liveness_new_list.end());
active.insert(exop->liveness_free_list.begin(), exop->liveness_free_list.end());
for (const descriptor::Tensor* tensor : active)
{
if (contains(dead_tensors, tensor))
{
throw runtime_error("Liveness: Dead tensors intersect active tensors");
}
}
dead_tensors.insert(exop->liveness_free_list.begin(), exop->liveness_free_list.end());
}
}
......@@ -15,6 +15,7 @@
#pragma once
#include "call_pass.hpp"
#include "ngraph/descriptor/tensor.hpp"
namespace ngraph
{
......@@ -33,6 +34,6 @@ public:
void check_dependencies(const std::vector<std::shared_ptr<CallBase>>&) const override;
private:
// bool is_interesting(tensor_decl);
// void validate_liveness(std::list<Node*> ops);
bool is_temporary(const descriptor::Tensor&);
void validate_liveness(const std::list<Node*>& ops);
};
// ----------------------------------------------------------------------------
// Copyright 2017 Nervana Systems Inc.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// ----------------------------------------------------------------------------
#include <fstream>
#include "visualize_tree.hpp"
#include "ngraph/node.hpp"
#include "util.hpp"
using namespace ngraph;
using namespace std;
bool pass::VisualizeTree::run_on_tree(std::shared_ptr<Node> base_node)
{
// map<size_t, list<node_ptr>> dependent_nodes;
traverse_nodes(base_node, [&](Node* node)
{
for (auto arg : node->get_arguments())
{
m_ss << add_attributes(arg.get());
m_ss << add_attributes(node);
m_ss << " " << arg->get_node_id() << " -> " << node->get_node_id();
m_ss << ";\n";
}
});
render();
return false;
}
pass::VisualizeTree::VisualizeTree(const string& file_name)
: m_name{file_name}
{
}
std::string pass::VisualizeTree::add_attributes(const Node* node)
{
string rc;
if (!contains(m_nodes_with_attributes, node))
{
m_nodes_with_attributes.insert(node);
rc = get_attributes(node);
}
return rc;
}
std::string pass::VisualizeTree::get_attributes(const Node* node)
{
stringstream ss;
if (node->is_parameter())
{
ss << " " << node->get_node_id() << " [shape=box color=blue]\n";
}
else
{
ss << " " << node->get_node_id() << " [shape=ellipse color=black]\n";
}
return ss.str();
}
void pass::VisualizeTree::render() const
{
#if GRAPHVIZ_FOUND
auto tmp_file = m_name + ".tmp";
ofstream out(tmp_file);
if (out)
{
out << "digraph ngraph\n{\n";
out << m_ss.str();
out << "}\n";
out.close();
stringstream ss;
ss << "dot -Tpng " << tmp_file << " -o " << m_name;
auto cmd = ss.str();
auto stream = popen(cmd.c_str(), "r");
pclose(stream);
remove(tmp_file.c_str());
}
#endif
}
// ----------------------------------------------------------------------------
// Copyright 2017 Nervana Systems Inc.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// ----------------------------------------------------------------------------
#pragma once
#include <sstream>
#include <string>
#include <set>
#include "ngraph/pass/tree_pass.hpp"
namespace ngraph
{
namespace pass
{
class VisualizeTree;
}
class Node;
}
class ngraph::pass::VisualizeTree : public TreeBase
{
public:
VisualizeTree(const std::string& file_name);
bool run_on_tree(std::shared_ptr<Node>) override;
private:
std::string add_attributes(const Node* node);
std::string get_attributes(const Node* node);
void render() const;
std::stringstream m_ss;
std::string m_name;
std::set<const Node*> m_nodes_with_attributes;
};
......@@ -42,10 +42,3 @@ void Parameter::assign_function(Function* function, size_t index)
}
void Parameter::propagate_types() {}
std::string ngraph::op::Parameter::get_node_id() const
{
stringstream ss;
ss << "parameter_" << m_instance_id;
return ss.str();
}
......@@ -64,5 +64,5 @@ target_link_libraries(unit-test ${CMAKE_DL_LIBS})
add_dependencies(unit-test ngraph libgtest eigen)
add_custom_target(check
COMMAND ${PROJECT_BINARY_DIR}/test/unit-test
COMMAND ${PROJECT_BINARY_DIR}/test/unit-test \${ARGS}
DEPENDS unit-test)
......@@ -20,14 +20,54 @@
#include "gtest/gtest.h"
#include "ngraph/pass/liveness.hpp"
#include "ngraph/pass/assign_tensors.hpp"
#include "ngraph/pass/manager.hpp"
#include "ngraph/pass/propagate_types.hpp"
#include "ngraph/pass/topological_sort.hpp"
#include "ngraph/pass/liveness.hpp"
#include "ngraph/pass/visualize_tree.hpp"
#include "ngraph/pass/dump_sorted.hpp"
#include "ngraph/ngraph.hpp"
#include "test_tools.hpp"
#include "log.hpp"
using namespace std;
using namespace ngraph;
namespace ng = ngraph;
TEST(liveness, test)
TEST(pass, liveness)
{
string image = "liveness.png";
string dump_file = "liveness.txt";
pass::Manager pass_manager;
auto visualize = make_shared<pass::VisualizeTree>(image);
auto topological_sort = make_shared<pass::TopologicalSort>();
auto propagate_types = make_shared<pass::PropagateTypes>();
auto assign_tensors = make_shared<pass::AssignTensors>();
auto liveness = make_shared<pass::Liveness>();
auto dump_sorted = make_shared<pass::DumpSorted>(dump_file);
pass_manager.register_pass(visualize);
pass_manager.register_pass(topological_sort);
pass_manager.register_pass(propagate_types);
pass_manager.register_pass(assign_tensors);
pass_manager.register_pass(liveness);
pass_manager.register_pass(dump_sorted);
auto graph = make_test_graph();
size_t node_count = get_node_count(graph);
pass_manager.run_passes(graph);
auto sorted = pass_manager.get_sorted_list();
// for (const Node* node : sorted)
// {
// INFO << *node;
// for (const descriptor::Tensor* tensor : node->liveness_live_list)
// {
// INFO << " " << *tensor;
// }
// }
// auto x = ng.variable(axes=[]).named('x');
// auto y = ng.variable(axes=[]).named('y');
// auto w1 = ng.variable(axes=[]).named('w1');
......@@ -48,7 +88,7 @@ TEST(liveness, test)
// return exc;
// lg = LivenessGraph(exc.exop.ops)
// lg.layout_memory()
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment