Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migrating optimizer to new IR (part 1) #1725

Merged
merged 25 commits into from
Jul 17, 2024
Merged

Migrating optimizer to new IR (part 1) #1725

merged 25 commits into from
Jul 17, 2024

Conversation

gramalingam
Copy link
Collaborator

Migrated the core logic.

TO DO: decide how to handle functions. Optimizer currently incorporates function-specialization. Need to choose between function-specialization and function-inlining.

Comment on lines 653 to 658
# if isinstance(output, list):
# return output
# else:
# # Currently handles single output only
# self.add_count(node.op_type, output.size)
# return self.new_constant(node.output[0], output)

Check notice

Code scanning / CodeQL

Commented-out code Note

This comment appears to contain commented-out code.
Copy link

@github-advanced-security github-advanced-security bot left a comment

Choose a reason for hiding this comment

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

lintrunner found more than 20 potential problems in the proposed changes. Check the Files changed tab for more details.

Copy link

codecov bot commented Jul 9, 2024

Codecov Report

Attention: Patch coverage is 76.13169% with 116 lines in your changes missing coverage. Please review.

Project coverage is 74.88%. Comparing base (f8ee736) to head (61511ef).

Files Patch % Lines
onnxscript/optimizer/_constant_folding.py 74.31% 59 Missing and 54 partials ⚠️
onnxscript/optimizer/optimizer_test.py 83.33% 1 Missing and 1 partial ⚠️
onnxscript/optimizer/constant_folding_test.py 96.66% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1725      +/-   ##
==========================================
+ Coverage   74.84%   74.88%   +0.03%     
==========================================
  Files         242      244       +2     
  Lines       25882    26344     +462     
  Branches     4669     4788     +119     
==========================================
+ Hits        19371    19727     +356     
- Misses       5641     5696      +55     
- Partials      870      921      +51     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

Copy link

github-actions bot commented Jul 9, 2024

Test Results

     24 files  ±  0      24 suites  ±0   2h 4m 45s ⏱️ + 3m 10s
 12 915 tests + 16   9 788 ✅ + 12    3 122 💤 +1   5 ❌ +3 
311 007 runs  +144  73 913 ✅ +132  237 081 💤 +9  13 ❌ +3 

For more details on these failures, see this check.

Results for commit 61511ef. ± Comparison against base commit f8ee736.

This pull request removes 17 and adds 33 tests. Note that renamed tests count towards both.
onnxscript.optimizer.constant_folding_test.FoldConstantsTest ‑ test_fold_add
onnxscript.optimizer.constant_folding_test.FoldConstantsTest ‑ test_fold_cast_like
onnxscript.optimizer.constant_folding_test.FoldConstantsTest ‑ test_fold_if_cond
onnxscript.optimizer.constant_folding_test.FoldConstantsTest ‑ test_fold_if_propagate
onnxscript.optimizer.constant_folding_test.FoldConstantsTest ‑ test_fold_inside_if_branch
onnxscript.optimizer.constant_folding_test.FoldConstantsTest ‑ test_fold_redundant_cast
onnxscript.optimizer.constant_folding_test.FoldConstantsTest ‑ test_fold_redundant_cast2
onnxscript.optimizer.constant_folding_test.FoldConstantsTest ‑ test_fold_shape
onnxscript.optimizer.constant_folding_test.FoldConstantsTest ‑ test_fold_shape_slice
onnxscript.optimizer.constant_folding_test.FoldConstantsTest ‑ test_fold_undefined_vars
…
onnxscript.optimizer.constant_folding_test.FoldConstantsTest_0 ‑ test_fold_add
onnxscript.optimizer.constant_folding_test.FoldConstantsTest_0 ‑ test_fold_cast_like
onnxscript.optimizer.constant_folding_test.FoldConstantsTest_0 ‑ test_fold_if_cond
onnxscript.optimizer.constant_folding_test.FoldConstantsTest_0 ‑ test_fold_if_propagate
onnxscript.optimizer.constant_folding_test.FoldConstantsTest_0 ‑ test_fold_inside_if_branch
onnxscript.optimizer.constant_folding_test.FoldConstantsTest_0 ‑ test_fold_redundant_cast
onnxscript.optimizer.constant_folding_test.FoldConstantsTest_0 ‑ test_fold_redundant_cast2
onnxscript.optimizer.constant_folding_test.FoldConstantsTest_0 ‑ test_fold_shape
onnxscript.optimizer.constant_folding_test.FoldConstantsTest_0 ‑ test_fold_shape_slice
onnxscript.optimizer.constant_folding_test.FoldConstantsTest_0 ‑ test_fold_undefined_vars
…
This pull request removes 1 skipped test and adds 2 skipped tests. Note that renamed tests count towards both.
onnxscript.optimizer.constant_folding_test.FoldConstantsTest ‑ test_fold_undefined_vars
onnxscript.optimizer.constant_folding_test.FoldConstantsTest_0 ‑ test_fold_undefined_vars
onnxscript.optimizer.constant_folding_test.FoldConstantsTest_1 ‑ test_fold_undefined_vars

♻️ This comment has been updated with latest results.

def _get_int_attribute(node: ir.Node, name: str, default: int | None = None) -> int | None:
if name in node.attributes:
attr = node.attributes[name]
if isinstance(attr, ir.AttrInt64):
Copy link
Collaborator

Choose a reason for hiding this comment

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

We need to check the attr type because ir.Attr may carry an int64 attr as well.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Hmmm ... so there is no guarantee that the specialized derived classes of ir.Attr will be created? That seems potentially confusing.

Copy link
Collaborator

Choose a reason for hiding this comment

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

potentially confusing

that's true. Do you have suggestions? We can turn the specialized classes into functions that will produce Attr objects, for example.

Comment on lines 630 to 639
for old_value, new_value in zip(old_values, new_values):
# Propagate relevant info from old value to new value
# TODO(Rama): Perhaps we should merge old and new types. As of now, new
# values don't have type information. Note that this could be a problem
# for semantics-altering rewrite-rules: we should allow users to override
# this for such rules.
new_value.type = old_value.type
new_value.shape = old_value.shape
new_value.const_value = old_value.const_value
new_value.name = old_value.name
Copy link
Collaborator

Choose a reason for hiding this comment

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

We can create a copy() method for nodes

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

yes, at least, we need some common utility we can call here.


def __init__(
self,
external_data_folder: str,
Copy link
Collaborator

Choose a reason for hiding this comment

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

I would support os.PathLike as well (in general for paths)

Copy link
Collaborator

Choose a reason for hiding this comment

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

folder -> dir ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

So, this seems unused currently! Previously, it was used when converting an external tensor into a numpy array (using ONNX's numpy_helper, which only supports "str" for the external-dir incidentally). Now, this uses IR to convert to numpy array. What happens in the IR when external-tensors are converted to numpy array? Does the IR remember the external-data-folder? Or, do we need pass it as a parameter?

Copy link
Collaborator

Choose a reason for hiding this comment

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

It is currently incomplete, we need to update the deserialize function: #1701 (comment)

onnxscript/optimizer/evaluator_ir.py Fixed Show fixed Hide fixed
onnxscript/optimizer/evaluator_ir.py Fixed Show fixed Hide fixed
onnxscript/optimizer/evaluator_ir.py Fixed Show fixed Hide fixed
onnxscript/ir/serde.py Fixed Show fixed Hide fixed
val = _get_numpy_value(val)
if val is None:
return None
if isinstance(val, bool):
Copy link
Collaborator

Choose a reason for hiding this comment

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

Curious when this is needed? Do we need to update the typing annotation or tighten the invariance?

value.type = type


def _get_input_element_type(node: ir.Node, index: int) -> int:
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
def _get_input_element_type(node: ir.Node, index: int) -> int:
def _get_input_element_type(node: ir.Node, index: int) -> ir.DataType:

seems appropriate based on usage

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

skipping this for now. (will require dropping the .value in the implementation in two places as well.)

Copy link
Collaborator

@justinchuby justinchuby left a comment

Choose a reason for hiding this comment

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

lgtm w/ comments. In particular, I think const_value should always be ir.TensorProtocol and we can rely on this invariance.

self, opname: str, domain: str = "", version=None
) -> Callable[[PartialEvaluatorFunction], PartialEvaluatorFunction]:
if (domain, opname) not in self.op_evaluators:
evaluator_list = []

Check failure

Code scanning / lintrunner

MYPY/var-annotated Error

Need type annotation for "evaluator_list" (hint: "evaluator_list: list[] = ...") To disable, use # type: ignore[var-annotated]
def _get_bool_value(val: ir.Value | None) -> bool | None:
if val is None:
return None
val = _get_numpy_value(val)

Check failure

Code scanning / lintrunner

MYPY/assignment Error

Incompatible types in assignment (expression has type "ndarray[Any, Any] | None", variable has type "Value | None") To disable, use # type: ignore[assignment]

def _get_int_attribute(node: ir.Node, name: str, default: int | None = None) -> int | None:
if name in node.attributes:
attr = node.attributes[name].value

Check failure

Code scanning / lintrunner

MYPY/union-attr Error

Item "RefAttr" of "Attr | RefAttr" has no attribute "value" To disable, use # type: ignore[union-attr]
input = _get_input(node, 0)
output = _get_output(node, 0)
if input is not None and output is not None:
_update_type(output, input.type)

Check failure

Code scanning / lintrunner

MYPY/arg-type Error

Argument 2 to "_update_type" has incompatible type "TypeProtocol | None"; expected "TypeProtocol" To disable, use # type: ignore[arg-type]
@register("Shape")
def shape(node: ir.Node, op, state: OptimizerState) -> ReturnValue:
input = node.inputs[0]
shape = input.shape

Check failure

Code scanning / lintrunner

MYPY/union-attr Error

Item "None" of "Value | None" has no attribute "shape" To disable, use # type: ignore[union-attr]

def _init(self) -> None:
self.counts = {}
self.sizes = {}

Check failure

Code scanning / lintrunner

MYPY/var-annotated Error

Need type annotation for "sizes" (hint: "sizes: dict[, ] = ...") To disable, use # type: ignore[var-annotated]
node.op_type, self.opset_imports[node.domain], node.domain
)
output_types = onnx.shape_inference.infer_node_outputs(
schema, ir.serde.serialize_node(node), input_types, input_data

Check failure

Code scanning / lintrunner

MYPY/arg-type Error

Argument 3 to "infer_node_outputs" has incompatible type "dict[str | None, TypeProto | None]"; expected "dict[str, TypeProto]" To disable, use # type: ignore[arg-type]
node.op_type, self.opset_imports[node.domain], node.domain
)
output_types = onnx.shape_inference.infer_node_outputs(
schema, ir.serde.serialize_node(node), input_types, input_data

Check failure

Code scanning / lintrunner

MYPY/arg-type Error

Argument 4 to "infer_node_outputs" has incompatible type "dict[str | None, TensorProto | None]"; expected "dict[str, TensorProto] | None" To disable, use # type: ignore[arg-type]
if any((x is not None and x.const_value is None) for x in node.inputs):
return None

input_values = [x.const_value.numpy() if x is not None else None for x in node.inputs]

Check failure

Code scanning / lintrunner

MYPY/union-attr Error

Item "None" of "TensorProtocol | None" has no attribute "numpy" To disable, use # type: ignore[union-attr]
if replacement is None:
# No change. Process attributes.
for attr in node.attributes.values():
self.visit_attribute(attr)

Check failure

Code scanning / lintrunner

MYPY/arg-type Error

Argument 1 to "visit_attribute" of "ConstantFolder" has incompatible type "Attr | RefAttr"; expected "Attr" To disable, use # type: ignore[arg-type]
optimizer.remove_unused_nodes(ir_model)
return serde.serialize_model(ir_model)
else:
constant_folding.fold_constants(model, onnx_shape_inference=onnx_shape_inference)
Copy link
Contributor

Choose a reason for hiding this comment

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

Will we replace constant_folding with _constant_folding eventually? If so, _constant_folding should not import stuffs from constant_folding?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

yes ... they will migrate into this file eventually

node.op_type, self.opset_imports[node.domain], node.domain
)
output_types = onnx.shape_inference.infer_node_outputs(
schema, ir.serde.serialize_node(node), input_types, input_data

Check failure

Code scanning / lintrunner

MYPY/arg-type Error

Argument 3 to "infer_node_outputs" has incompatible type "dict[str | None, TypeProto | None]"; expected "dict[str, TypeProto]" To disable, use # type: ignore[arg-type]
node.op_type, self.opset_imports[node.domain], node.domain
)
output_types = onnx.shape_inference.infer_node_outputs(
schema, ir.serde.serialize_node(node), input_types, input_data

Check failure

Code scanning / lintrunner

MYPY/arg-type Error

Argument 4 to "infer_node_outputs" has incompatible type "dict[str | None, TensorProto | None]"; expected "dict[str, TensorProto] | None" To disable, use # type: ignore[arg-type]
output_types = onnx.shape_inference.infer_node_outputs(
schema,
ir.serde.serialize_node(node),
input_types,

Check failure

Code scanning / lintrunner

MYPY/arg-type Error

Argument 3 to "infer_node_outputs" has incompatible type "dict[str | None, TypeProto | None]"; expected "dict[str, TypeProto]" To disable, use # type: ignore[arg-type]
@gramalingam gramalingam merged commit d27aede into main Jul 17, 2024
30 of 43 checks passed
@gramalingam gramalingam deleted the rama/optimizer branch July 17, 2024 19:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Development

Successfully merging this pull request may close these issues.

3 participants