Skip to content
This repository has been archived by the owner on Dec 1, 2021. It is now read-only.

Added pattern matching and new optimization passes #53

Merged
merged 48 commits into from
Jan 31, 2019

Conversation

antonionevado
Copy link
Contributor

@antonionevado antonionevado commented Nov 29, 2018

This PR simplify the current optimization mechanism.
The idea is to only have a collection of simple passes that perform some actions over every sub-graph that match some given pattern.

The benefits of this PR are:

  • Improve speed: previous implementation compilation time was around 80 seconds. The new implementation takes around 1~2 seconds. Thanks to Joel-san and Neil-san for finding a way of computing thresholds in constant time!
  • The code is very simple and easy to understand: several functions each one applying a different pass.
  • The code is also very short compared to the previous implementation.

@antonionevado antonionevado added the enhancement New feature or request label Nov 29, 2018
@antonionevado antonionevado self-assigned this Nov 29, 2018
@antonionevado antonionevado changed the title [WIP] Added pattern matching and new optimization passes Added pattern matching and new optimization passes Dec 14, 2018
@antonionevado antonionevado requested review from leapmindadmin, ruimashita and nlpng and removed request for leapmindadmin December 17, 2018 00:54
Copy link
Contributor

@ruimashita ruimashita left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How effective this PR when we add new operator ?

@antonionevado
Copy link
Contributor Author

Adding a new operator require change much less code.
The main advantage is that the old code was very difficult to understand anyway. And very slow.

@ruimashita
Copy link
Contributor

@antonionevado How to test this?

@@ -56,6 +55,8 @@ def __init__(self,
self._check_consistency()
self._rank = len(shape)
self._available_buffer = ''
self._visited = False
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it need?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, will delete.

@@ -56,6 +55,8 @@ def __init__(self,
self._check_consistency()
self._rank = len(shape)
self._available_buffer = ''
self._visited = False
self._prop_details = Dict
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, Does it need ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably not, old code I forgot to delete. I will delete it. Thank you!

from core.operators import Add, AveragePool, BatchNormalization, Constant, Conv, Identity, Input, \
MaxPool, Operator, Output, Transpose, QTZ_binary_mean_scaling, QTZ_linear_mid_tread_half, Reshape, Softmax

import numpy as np
from typing import Any, Dict, List, Tuple
from typing import Tuple


class TestOptimizer(unittest.TestCase):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't understand what is verified in this test. Is it not unit test ?
I think need to add unit tests for each passes, and some public graph matcher functions and classes.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@antonionevado @nlpng How is this?

Copy link
Contributor

@ruimashita ruimashita left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is better, The passes of changing node property like pass_compute_thresholds(), pass_propagate_quantization_details_into_conv() have document of which property change to, I think.

BTW, what is different of run() and run_forward() in Operator class ?

"""
def __init__(self, op=str(), inputs=list()):
self.op = op
self.inputs = inputs
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What case self.inputs is used ? It seems not be used.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is necessary to support more complex patterns. For example, imagine you want to write a pass that need to process sub-graphs where you have a quantizer with an input which is a batch normalization. And that batch normalization node has an input which is a convolution node. Then you can specify the pattern:

    p = Pattern('QTZ_linear_mid_tread_half',
                [
                    Pattern('BatchNormalization',
                            [
                                Pattern('Conv'),
                                Pattern('*'),
                                Pattern('*'),
                                Pattern('*'),
                                Pattern('*')
                            ]),
                    Pattern('*'),
                    Pattern('*'),
                ])

Note that the pattern * means that you don't care about that node, could be any type of operator.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Which pass is used the pattern?
I think it is no need to implement this, If the pattern is not used.
Or, should implement unit test and write document at least for future work.


Returns
-------
result : [NodeMatch]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why this is a list of NodeMatch object, not list of Operator ?
If we can use list of Operator, Maybe we don't need NodeMatch class.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is necessary to have this class because the return type is a list of sub-graphs. The NodeMatch class is a graph type (resursive) data structure.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NodeMatch is not subclass of Graph, but is it sub graph? it looks strange implementation.
Operator objects have inputs and outputs, that means you can retrieve path and nodes, it is already graph type structure.
why it is necessary?

dlk/python/dlk/templates/Makefile.tpl Outdated Show resolved Hide resolved
dlk/python/dlk/scripts/generate_project.py Outdated Show resolved Hide resolved
dlk/python/dlk/core/operators.py Outdated Show resolved Hide resolved
dlk/python/dlk/core/operators.py Show resolved Hide resolved
mean = np.float64(self._input_ops['mean'].data)
var = np.float64(self._input_ops['var'].data)

kwargs['nega_idx'] = [v for v in range(len(scale)) if scale[v] < 0]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nega_idx is not relate invert function of batch norm, it is related only for threshold skip. Why the nega_idx calculation is not move into pass of threshold skipping?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since it was more straightforward to retrieve the values from the batch norm rather than the threshold skipping pass, but I agree this will go to the optimization pass.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now the nega_idx is moved inside the optimization pass with a7f73bc

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why the line remain ?

p = Pattern('*')
matches = find_pattern(graph, p)

def find_input(node, otype):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The purpose of the find_input function is set dtype.
The find_input function name is confuse for me.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, maybe rename it to dtype_changer or output_type_changer

dlk/python/dlk/core/optimizer.py Show resolved Hide resolved

if p[-1].op_type != 'Conv':
continue
quantizer_conv_output_node = p[0]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is quantizer_conv_output_node activation quantization node ? confuse...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think @nlpng already changed the name. Yes, sorry, it is a bad name.

@nlpng nlpng force-pushed the simplify_optimizer branch from 5bdce90 to 46207f1 Compare January 8, 2019 08:01
"""
def __init__(self, op=str(), inputs=list()):
self.op = op
self.inputs = inputs
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Which pass is used the pattern?
I think it is no need to implement this, If the pattern is not used.
Or, should implement unit test and write document at least for future work.


Returns
-------
result : [NodeMatch]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NodeMatch is not subclass of Graph, but is it sub graph? it looks strange implementation.
Operator objects have inputs and outputs, that means you can retrieve path and nodes, it is already graph type structure.
why it is necessary?

mean = np.float64(self._input_ops['mean'].data)
var = np.float64(self._input_ops['var'].data)

kwargs['nega_idx'] = [v for v in range(len(scale)) if scale[v] < 0]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why the line remain ?

dlk/python/dlk/core/optimizer.py Show resolved Hide resolved
n = 2 ** nbit - 1
ch = conv_node.channel
# assume that the threshold values will be a 13-bit signed integer
max_th_value = 2 ** 12 - 1
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if we can change, why 12 is not variable?

# get nodes to be removed after being disconnected
get_nodes_in_branch(m.node, None, to_be_removed)

new_constant.add_outputs({'output': m.node.output_ops.values()})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why these several lines are in here? looks general manupulation of graph and op.
How is to implement like replace_op() in Graph class and use graph.replace_op(m.node, new_constant) ?

from core.operators import Add, AveragePool, BatchNormalization, Constant, Conv, Identity, Input, \
MaxPool, Operator, Output, Transpose, QTZ_binary_mean_scaling, QTZ_linear_mid_tread_half, Reshape, Softmax

import numpy as np
from typing import Any, Dict, List, Tuple
from typing import Tuple


class TestOptimizer(unittest.TestCase):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@antonionevado @nlpng How is this?

continue

quantizer_conv_weights = conv_node.quantizer
quantizer_conv_weights.run_forward_no_scaling_factor()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@antonionevado @nlpng How is this ?

dlk/python/dlk/core/optimizer.py Show resolved Hide resolved
dlk/python/dlk/core/operators.py Show resolved Hide resolved
@ruimashita
Copy link
Contributor

@antonionevado @nlpng Thanks, I will review this PR next week 👍

Copy link
Contributor

@ruimashita ruimashita left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@antonionevado @nlpng
Thanks, so so nice refactoring.
I wrote some nite things. Plz see it.

dlk/python/dlk/core/graph_pattern_matching.py Outdated Show resolved Hide resolved
dlk/python/dlk/core/optimizer.py Show resolved Hide resolved
dlk/python/dlk/core/optimizer.py Outdated Show resolved Hide resolved
dlk/tests/test_optimizer.py Show resolved Hide resolved
continue

quantizer_conv_weights = conv_node.quantizer
quantizer_conv_weights.run_forward_no_scaling_factor()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think run_forward_no_scaling_factor() should be in scaling_factor getter.
But it's not need to change.

dlk/tests/test_optimizer.py Show resolved Hide resolved
Copy link
Contributor

@ruimashita ruimashita left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@antonionevado @nlpng
Thanks a lot 😄 😄 😄
we can get very simple and easy to understand code, and improve graph optimize speed 🥇

@ruimashita ruimashita merged commit fe7b69f into blue-oil:master Jan 31, 2019
@iizukak iizukak added this to the v0.3 milestone Feb 6, 2019
ruimashita added a commit that referenced this pull request Jun 26, 2019
Added pattern matching and new optimization passes
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants