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

Pass manager refactoring: cleanup internals #10127

Merged

Conversation

nkanazawa1989
Copy link
Contributor

@nkanazawa1989 nkanazawa1989 commented May 18, 2023

Summary

This PR cleanups internals of the pass manager and flow controllers. 2 of 2 PRs.

Details and comments

This is the overhaul of current pass control machinery. The points of the overhaul are:

  • Common base class for pass and flow controller with execute method (composite pattern)

Conventionally flow controller also had a responsibility of single pass execution. In new implementation, a common base class Task defines the execute method, and responsibility of pass execution is delegated to pass itself. In addition, the running pass manager (pass runner) is consolidated into the flow controller execute method. This drastically simplifies the internals of pass execution management.

  • Introduction of WorkflowStatus data structure and make status and property set portable

Conventionally pass execution is managed by a single pass runner instance, and thus instance variables can be used to manage the state of pass execution (e.g. property set, count, completed passes, etc). In new implementation, the pass execution is delegated into multiple flow controllers, and thus the state information is aggregated into single portable data structure WorkflowStatus which is handed over passes through the execute method along with conventional PropertySet.

@nkanazawa1989 nkanazawa1989 requested a review from a team as a code owner May 18, 2023 19:31
@qiskit-bot
Copy link
Collaborator

One or more of the the following people are requested to review this:

@nkanazawa1989 nkanazawa1989 added mod: transpiler Issues and PRs related to Transpiler priority: high labels May 18, 2023
@nkanazawa1989 nkanazawa1989 added this to the 0.25.0 milestone May 18, 2023
@coveralls
Copy link

coveralls commented May 18, 2023

Pull Request Test Coverage Report for Build 5769674828

  • 359 of 373 (96.25%) changed or added relevant lines in 9 files are covered.
  • 2445 unchanged lines in 153 files lost coverage.
  • Overall coverage increased (+1.3%) to 87.256%

Changes Missing Coverage Covered Lines Changed/Added Lines %
qiskit/passmanager/flow_controllers.py 59 60 98.33%
qiskit/transpiler/passmanager.py 65 66 98.48%
qiskit/passmanager/passmanager.py 42 44 95.45%
qiskit/passmanager/base_optimization_tasks.py 91 95 95.79%
qiskit/transpiler/runningpassmanager.py 19 25 76.0%
Files with Coverage Reduction New Missed Lines %
crates/accelerate/src/sabre_layout.rs 1 99.43%
qiskit/circuit/commutation_checker.py 1 98.51%
qiskit/circuit/library/n_local/efficient_su2.py 1 94.74%
qiskit/circuit/library/n_local/excitation_preserving.py 1 95.83%
qiskit/circuit/parametertable.py 1 88.19%
qiskit/opflow/gradients/qfi.py 1 95.65%
qiskit/passmanager/passmanager.py 1 93.41%
qiskit/quantum_info/operators/base_operator.py 1 98.08%
qiskit/quantum_info/operators/mixins/adjoint.py 1 92.86%
qiskit/quantum_info/operators/mixins/multiply.py 1 94.44%
Totals Coverage Status
Change from base Build 5490783039: 1.3%
Covered Lines: 74292
Relevant Lines: 85143

💛 - Coveralls

This PR introduces two changes for
* remove tight coupling of flow controller to pass runner instance,
* remove pass normalization.

PropertySet becomes context var so that flow controllers can be instantiated
without coupling to a pass runner instance. BasePass becomes an iterable
of length 1 to skip normalization. The decoupling of property set from
pass runner allows pass manager to directly broadcast pass runner
in the multithread, rather than distributing self and craete
multiple pass runner in each thread.
@nkanazawa1989 nkanazawa1989 force-pushed the feature/passmanager-refactoring-pr2 branch from a039cbe to 6505e20 Compare June 24, 2023 05:57
This allows subclass to dispatch different pass runner type depending on the target code.
@nkanazawa1989
Copy link
Contributor Author

PR is rebased since #10124 is merged. Now this PR is ready for review.

Copy link
Member

@jakelishman jakelishman left a comment

Choose a reason for hiding this comment

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

I think the complexity with PropertySet is coming from a (pre-existing) poor distinction between the responsibilities of FlowController, PassManager and RunningPassManager, and very serious (pre-existing) confusion in FlowController about what it is and should be doing. The FuturePropertySet that's introduced here seems to me to be making things a bit worse in terms of logical architecture by coupling to stateful globals, even if those have some controllable context. Fundamentally, the state of the compilation (the PropertySet) we need to manipulate is local to an iteration through a series of passes, but moving that state manipulation into contextvars hides that while necessitating non-local getters to pull the local state from a global instance.

FlowController is currently, architecturally, reasoned about as if it were a pass, but I think everything might get a lot clearer if we normalise our architecture such that:

  • PassManager contains exactly one FlowController (a LinearFlowController in almost all cases) - the passes field can be a property that views into this, potentially.
  • FlowController gains a method .run that takes in the PropertySet
  • FlowController is responsible for running all passes and delegating to internal FlowControllers
  • PassManager.run creates the initial property set, and immediately passes it to the first FlowController.run
  • PassManager.run is responsible for moving properties from the PropertySet to the output format, like it currently is (though honestly, I'm not convinced that this shouldn't be the responsibility of the passes themselves).

That makes everything about the property set explicitly stateful, and the state is just passed from object to object. We don't need to fetch the state from contexts; FlowControllers will always have it for their conditions (no need for any of the partial stuff) because it's just directly given to their new run methods.

In this situation, I'd suggest basically completely rewriting the flow controllers and turning them into regular classes with a simple defined API, instead of what's currently in place.

Comment on lines 73 to 81
def __eq__(self, other):
try:
if len(other) == 1:
# Reference might be List[BasePass] which was conventional representation
# of a single base pass in the pass manager.
other = next(iter(other))
except TypeError:
return False
return hash(self) == hash(other)
Copy link
Member

Choose a reason for hiding this comment

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

Do we actually use BasePass.__eq__? It seems really a pretty weird thing to have, to me. At any rate, hash(self) == hash(other) is not a correct implementation of equality haha - it would have been better to not define an __eq__ at all and use the default implementation (which boils down to x is y).

Copy link
Contributor Author

@nkanazawa1989 nkanazawa1989 Aug 5, 2023

Choose a reason for hiding this comment

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

Yes, it's actually used. Pass runner (currently in flow controller) manages already run passes with a set instance, and it prevents execution of duplicated passes by design. This equivalence is used for the set object. For example,

https://github.com/Qiskit/qiskit-terra/blob/28113b6a9fc1fd31bc211a73fe7a6908fffa1114/test/python/transpiler/test_pass_scheduler.py#L798-L819

The actual log without equivalency check will be

run transformation pass PassA_TP_NR_NP
Pass: PassA_TP_NR_NP - 0.46515 (ms)   <-- required
run transformation pass PassC_TP_RA_PA
Pass: PassC_TP_RA_PA - 0.05412 (ms)
run transformation pass PassA_TP_NR_NP
Pass: PassA_TP_NR_NP - 0.04673 (ms)   <-- required
run transformation pass PassB_TP_RA_PA
Pass: PassB_TP_RA_PA - 0.04196 (ms)
...

Since both PassC_TP_RA_PA and PassB_TP_RA_PA require PassA_TP_NR_NP, which is independently instantiated and added to .requires. Dropping this violates ~30 unit tests, and may impact user's code by repeatedly executing the requires passes.

Strictly speaking this implementation is not really robust. This logic uses Python repr as a easy hash, however every Qiskit object doesn't properly implement the repr dunder (e.g. Target). Developer should have implemented the equality dunder for every pass, but it's too late now.

qiskit/passmanager/flow_controllers.py Outdated Show resolved Hide resolved
Comment on lines 48 to 53
**partial_controller: Partially evaluated callables representing
a condition to invoke flow controllers. This dictionary
must be keyed on the registered controller name.
"""
self._passes = passes
self.passes = FlowController.controller_factory(passes, options, **partial_controller)
Copy link
Member

Choose a reason for hiding this comment

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

I would dearly love to refactor this controller_factory and partial_controller stuff away - it's nearly impenetrable to understand, and I don't think it actually buys us anything at all. It'd be much cleaner to have FlowController be an abstract base class with the directly instantiable actual controllers deriving from it.

I don't know if you'd prefer that to be a different PR, though.

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 agree. I want to drop controller_factory (it becomes much readable though), and I don't think appending passes to pass manager with kwargs, e.g. .append(passes, condition=callable1, do_while=callable2), is really good interface as it introduces implicit and uncontrollable pass hierarchy through the FlowController class variable. User must explicitly instantiate controllers rather than relying on this uncontrollable factory method.

qiskit/passmanager/flow_controllers.py Outdated Show resolved Hide resolved
qiskit/passmanager/propertyset.py Outdated Show resolved Hide resolved
@mtreinish mtreinish assigned jakelishman and unassigned mtreinish Jul 19, 2023
- Add OptimizerTask as a protocol for the pass and flow controller. These are in principle the same object that inputs and outputs IR with optimization.
- A task gains execute method that takes IR and property set. This makes property set local to iteration of the passes.
- Drop dependency on pass runner. Now pass manager has a single linear flow controller.
- Pass manager gain responsibility of compiler frontend and backend as pass runner dependency is removed. This allows flow controllers to be still type agnostic.
- Drop future property set, as this is no longer necessary because optimization task has the execute method explicitly taking the property set.
- Remove pass runner baseclass and replace RunningPassmanager baseclass with FlowControllerLiner
- Implemented frontoend and backend functionality in transpiler Pass manager
mtreinish added a commit to mtreinish/qiskit-core that referenced this pull request Jul 20, 2023
This reverts commit fbd64d9. The follow
on PR to this one Qiskit#10127 which is making internal refactors to the pass
manager code now that's it a standalone module is still undergoing
active review and the scope of the PR is sufficiently large that it
likely won't be viable for the pending 0.25.0 release. This commit
temporarily reverts Qiskit#10124 which was the first step of creating a module
by porting the pass manager code in it's current form to a standalone
module so that we're not committed to the API as part of the 0.25.0
release to give more time for Qiskit#10127 to finalize what the eventual
`qiskit.passmanager` API will look like.

This revert should itself be reverted after 0.25.0rc1 is tagged and
the main branch opens up for 0.45.0 development. As this revert is
not an indication that we did not want Qiskit#10124 it's just to avoid
committing to the API prematurely.
@nkanazawa1989 nkanazawa1989 modified the milestones: 0.25.0, 0.45.0 Jul 20, 2023
github-merge-queue bot pushed a commit that referenced this pull request Jul 20, 2023
…10454)

This reverts commit fbd64d9. The follow
on PR to this one #10127 which is making internal refactors to the pass
manager code now that's it a standalone module is still undergoing
active review and the scope of the PR is sufficiently large that it
likely won't be viable for the pending 0.25.0 release. This commit
temporarily reverts #10124 which was the first step of creating a module
by porting the pass manager code in it's current form to a standalone
module so that we're not committed to the API as part of the 0.25.0
release to give more time for #10127 to finalize what the eventual
`qiskit.passmanager` API will look like.

This revert should itself be reverted after 0.25.0rc1 is tagged and
the main branch opens up for 0.45.0 development. As this revert is
not an indication that we did not want #10124 it's just to avoid
committing to the API prematurely.
to24toro pushed a commit to to24toro/qiskit-terra that referenced this pull request Aug 3, 2023
…" (Qiskit#10454)

This reverts commit fbd64d9. The follow
on PR to this one Qiskit#10127 which is making internal refactors to the pass
manager code now that's it a standalone module is still undergoing
active review and the scope of the PR is sufficiently large that it
likely won't be viable for the pending 0.25.0 release. This commit
temporarily reverts Qiskit#10124 which was the first step of creating a module
by porting the pass manager code in it's current form to a standalone
module so that we're not committed to the API as part of the 0.25.0
release to give more time for Qiskit#10127 to finalize what the eventual
`qiskit.passmanager` API will look like.

This revert should itself be reverted after 0.25.0rc1 is tagged and
the main branch opens up for 0.45.0 development. As this revert is
not an indication that we did not want Qiskit#10124 it's just to avoid
committing to the API prematurely.
- Move handling of required passes to generic pass itself. This makes optimizer tasks the composite pattern-like for more readability.
- Readd MetaPass to transpiler BasePass as a metaclass which implements predefined equivalence check for all passes. This is necessary to take care of duplicated pass run, which is prohibited in circuit pass manager.
- Add FlowControllerMeta that allows users to subclass FlowController, while delegating the pass control to BaseFlowController.
- Readd count to pass manager callback
- Add PassState that manages the state of execution including PropertySet. This is a portable namespace that can be shared across passes.
Copy link
Member

@jakelishman jakelishman left a comment

Choose a reason for hiding this comment

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

This review just contains little, less consequential comments. I made nkanazawa1989#51 that implements a bunch of little documentation tweaks (corrected typos, Sphinx links, etc) rather than marking them inline here - I thought that'd be easier for both of us.

I said it before, but thank you for all the work on this PR and the refactoring so far! The code is definitely much easier to follow in your new form.

qiskit/transpiler/passmanager.py Show resolved Hide resolved
qiskit/passmanager/base_tasks.py Outdated Show resolved Hide resolved
qiskit/passmanager/base_tasks.py Outdated Show resolved Hide resolved
qiskit/passmanager/base_tasks.py Outdated Show resolved Hide resolved
qiskit/transpiler/passmanager.py Outdated Show resolved Hide resolved
qiskit/transpiler/passmanager.py Outdated Show resolved Hide resolved
qiskit/transpiler/passmanager.py Outdated Show resolved Hide resolved
qiskit/transpiler/runningpassmanager.py Outdated Show resolved Hide resolved
test/python/transpiler/test_pass_scheduler.py Outdated Show resolved Hide resolved
- Add new container class PassmanagerMetadata
- Rename propertyset with compilation_status
- Provide metadata with the iter_tasks instead of property_set
- Send metadata through generator
- Turn metadata required, and let pass manager create metadata
- Return metadata from execute along with the IR
@nkanazawa1989
Copy link
Contributor Author

nkanazawa1989 commented Oct 18, 2023

Thanks @jakelishman for another round of review. I should have noticed abc.ABCMeta. This is done in ed9a8d7 and now class hierarchy is much friendly to IDE. Also thanks for the docs PR. I hope the PR is ready for final review.

This is primarily to avoid a potential conflict with the terminology
`metadata` that's associated with the `input_program`, and because the
object is the state object for a pass-manager execution.  There is still
a risk of confusion with `RunState`, but since that's a subcomponent of
`PassManagerState`, it feels fair enough.
Copy link
Member

@jakelishman jakelishman left a comment

Choose a reason for hiding this comment

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

I pushed up a commit to rename PassmanagerMetadata to PassManagerState, just because that terminology's a bit more inline with how we usually use the words "state" and "metadata" within the library (and the capitalisation was a bit off compared to what we normally do). I hope that's ok, I just wanted to get this approved without sending it back to you to have to do more work.

Thank you so much for this, and the immense effort that you've gone to to getting this over the line. It's really a fantastic refactor of some of the hardest-to-work-with code that was in Qiskit before now, and I'm really glad we had you working on it!

@jakelishman jakelishman added Changelog: Deprecation Include in "Deprecated" section of changelog Changelog: New Feature Include in the "Added" section of the changelog Changelog: API Change Include in the "Changed" section of the changelog labels Oct 19, 2023
@jakelishman jakelishman added this pull request to the merge queue Oct 19, 2023
Merged via the queue into Qiskit:main with commit 7acf882 Oct 19, 2023
14 checks passed
@wshanks
Copy link
Contributor

wshanks commented Dec 4, 2023

I guess until the code path really gets deprecated it doesn't really matter, but I just wanted to note that while testing the 1.0 beta I tried looking at all warnings and noticed that the

PendingDeprecationWarning: Building a flow controller with keyword arguments is going to be deprecated. Custom controllers must be explicitly instantiated and appended to the task list.

warning gets generated a lot internally by Qiskit. For instance, this code generates it:

import warnings

from qiskit import QuantumCircuit, transpile


warnings.simplefilter("default", PendingDeprecationWarning)

qc = QuantumCircuit(1, 1)
qc.measure_all()

transpile(qc, optimization_level=0)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Changelog: API Change Include in the "Changed" section of the changelog Changelog: Deprecation Include in "Deprecated" section of changelog Changelog: New Feature Include in the "Added" section of the changelog mod: transpiler Issues and PRs related to Transpiler priority: high
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants