-
Notifications
You must be signed in to change notification settings - Fork 2.3k
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
[Oxidize BasisTranslator] Add rust-native compose_transforms()
#13137
base: main
Are you sure you want to change the base?
Conversation
- Use `PackedOperation.control_flow` instead of `CONTROL_FLOW_OP_NAMES` to check if an instructuion is a control flow operation. - Remove panic statement when checking for example gates. - Use qreg when adding an instruction to the mock_dag in `compose_transforms`. - Add missing comparison check in for loop to compare the mapped instructions. - Use borrowed `DAGCircuit` instances in the recursion of `get_example_gates`, do not use extract. - Fix behavior of `get_example_gates` to return `PackedInstruction` instances instead of `NodeIndex`.
- Use `circuit_to_dag` and `DAGCircuit::from_circuit_data` to convert `QuantumCircuit` instances.
One or more of the following people are relevant to this code:
|
Pull Request Test Coverage Report for Build 10925762356Warning: This coverage report may be inaccurate.This pull request's base commit is no longer the HEAD commit of its target branch. This means it includes changes from outside the original pull request, including, potentially, unrelated coverage changes.
Details
💛 - Coveralls |
Here are some benchmarks All benchmarks:
| Change | Before [81063a79] <fix-abi3-issues~1> | After [e8c957df] <rust-compose-trans> | Ratio | Benchmark (Parameter) |
|----------|-----------------------------------------|-----------------------------------------|---------|----------------------------------------------------------------------------------------------------------|
| | 306±1ms | 303±2ms | 0.99 | passes.MultipleBasisPassBenchmarks.time_basis_translator(14, 1024, ['rx', 'ry', 'rz', 'r', 'rxx', 'id']) |
| | 443±3ms | 437±3ms | 0.99 | passes.MultipleBasisPassBenchmarks.time_basis_translator(20, 1024, ['rx', 'ry', 'rz', 'r', 'rxx', 'id']) |
| | 188±0.8ms | 182±2ms | 0.97 | passes.MultipleBasisPassBenchmarks.time_basis_translator(14, 1024, ['rz', 'x', 'sx', 'cx', 'id']) |
| | 217±0.7ms | 207±0.6ms | 0.96 | passes.MultipleBasisPassBenchmarks.time_basis_translator(20, 1024, ['u', 'cx', 'id']) |
| | 102±0.5ms | 98.1±1ms | 0.96 | passes.MultipleBasisPassBenchmarks.time_basis_translator(5, 1024, ['rx', 'ry', 'rz', 'r', 'rxx', 'id']) |
| | 266±4ms | 253±0.5ms | 0.95 | passes.MultipleBasisPassBenchmarks.time_basis_translator(20, 1024, ['rz', 'x', 'sx', 'cx', 'id']) |
| | 155±3ms | 146±0.6ms | 0.94 | passes.MultipleBasisPassBenchmarks.time_basis_translator(14, 1024, ['u', 'cx', 'id']) |
| | 68.6±1ms | 62.6±0.2ms | 0.91 | passes.MultipleBasisPassBenchmarks.time_basis_translator(5, 1024, ['rz', 'x', 'sx', 'cx', 'id']) |
| - | 57.5±0.4ms | 51.8±1ms | 0.9 | passes.MultipleBasisPassBenchmarks.time_basis_translator(5, 1024, ['u', 'cx', 'id']) |
SOME BENCHMARKS HAVE CHANGED SIGNIFICANTLY.
PERFORMANCE INCREASED. All benchmarks:
| Change | Before [81063a79] <fix-abi3-issues~1> | After [e8c957df] <rust-compose-trans> | Ratio | Benchmark (Parameter) |
|----------|-----------------------------------------|-----------------------------------------|---------|-----------------------------------------------------------------------------------------------------------|
| | 5.91±0s | 5.13±0.01s | ~0.87 | randomized_benchmarking.RandomizedBenchmarkingBenchmark.time_ibmq_backend_transpile([0, 1]) |
| | 5.93±0.02s | 5.11±0s | ~0.86 | randomized_benchmarking.RandomizedBenchmarkingBenchmark.time_ibmq_backend_transpile_single_thread([0, 1]) |
| - | 2.53±0.01s | 2.26±0.01s | 0.89 | randomized_benchmarking.RandomizedBenchmarkingBenchmark.time_ibmq_backend_transpile([0]) |
| - | 2.56±0.01s | 2.25±0.01s | 0.88 | randomized_benchmarking.RandomizedBenchmarkingBenchmark.time_ibmq_backend_transpile_single_thread([0]) |
SOME BENCHMARKS HAVE CHANGED SIGNIFICANTLY.
PERFORMANCE INCREASED. |
compose_transforms()
compose_transforms()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like a good start, thanks for working on this.
Unfortunately, the Python code you're starting with is probably sorely in need of a rewrite. I'm not entirely familiar with it, but it seems riddled with wrong / bad docs and tuple abuse.
I'll need to do a second pass once you've had a look at the comments thus far. I haven't spent enough time reviewing the parameter mapping stuff, specifically.
@@ -0,0 +1,21 @@ | |||
// This code is part of Qiskit. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a clear benefit to having a separate folder here for basis_translator
?
I don't know that I mind it, but it's different from the folder structure we had in Python, and none of the other accelerate modules really go more than one module deep (except for synthesis
, which seems to copy the organization of the Python module).
pub type BasisTransforms = Vec<(String, u32, SmallVec<[Param; 3]>, CircuitRep)>; | ||
pub type MappedTransforms = HashMap<(String, u32), (SmallVec<[Param; 3]>, DAGCircuit)>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMO, type aliases are generally more helpful when used to break down complex types into logical ideas, rather than as a means of typing less.
For example, this:
pub type GateIdentifier = (String, u32);
pub type BasisTransformIn = (SmallVec<[Param; 3]>, CircuitRep);
pub type BasisTransformOut = (SmallVec<[Param; 3]>, DAGCircuit);
pub(super) fn py_compose_transforms(
py: Python,
basis_transforms: Vec<(GateIdenfitier, BasisTransformIn)>,
source_basis: HashSet<GateIdentifier>,
source_dag: &DAGCircuit,
) -> PyResult<HashMap<GateIdentifier, BasisTransformOut>>
gives the reader a lot more information about the call semantics of the function compared to this:
pub type BasisTransforms = Vec<(String, u32, SmallVec<[Param; 3]>, CircuitRep)>;
pub type MappedTransforms = HashMap<(String, u32), (SmallVec<[Param; 3]>, DAGCircuit)>;
pub(super) fn py_compose_transforms(
py: Python,
basis_transforms: BasisTransforms,
source_basis: HashSet<(String, u32)>,
source_dag: &DAGCircuit,
) -> PyResult<MappedTransforms>
And, as a general rule of thumb, it's usually best to avoid aliasing data structures like Vec
and HashMap
since it hides their otherwise very familiar semantics from the reader (...though, we're guilty of this in a few places, I expect.)
let data_downcast: Bound<CircuitData> = data.downcast_into()?; | ||
let data_extract: CircuitData = data_downcast.extract()?; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe you can extract from the Bound<PyAny>
without first downcasting to save an isinstance
check.
@@ -2861,7 +2861,7 @@ def _format(operand): | |||
/// Raises: | |||
/// DAGCircuitError: if met with unexpected predecessor/successors | |||
#[pyo3(signature = (node, input_dag, wires=None, propagate_condition=true))] | |||
fn substitute_node_with_dag( | |||
pub fn substitute_node_with_dag( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps a strange request, but I think it makes sense for us to rename any PyO3 methods to py_
when opening them up outside of DAGCircuit
. The reason being that it'll make it easier for us to notice calls to Python-intended methods from within Rust, which are likely to be ill-performing in contrast to anything proper Rust native.
example_gates: Option<Box<HashMap<(String, u32), PackedInstruction>>>, | ||
) -> PyResult<Box<HashMap<(String, u32), PackedInstruction>>> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The original Python function is strange. A more appropriate way to do this in Rust would be to require that the caller allocates the map and provides a mutable reference to it so all recursive calls to the function can keep appending to it.
example_gates: Option<Box<HashMap<(String, u32), PackedInstruction>>>, | |
) -> PyResult<Box<HashMap<(String, u32), PackedInstruction>>> { | |
example_gates: &mut HashMap<(String, u32), PackedInstruction>, | |
) -> PyResult<()> { |
|
||
for (gate_name, gate_num_qubits) in source_basis.iter().cloned() { | ||
// Need to grab a gate instance to find num_qubits and num_params. | ||
// Can be removed following https://github.com/Qiskit/qiskit-terra/pull/3947 . |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This issue appears to be closed. I think the comment may be outdated in your implementation, anyhow?
/// Representation of QuantumCircuit which the original circuit object + an | ||
/// instance of `CircuitData`. | ||
#[derive(Debug, Clone)] | ||
pub struct CircuitRep(pub CircuitData); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe CircuitFromPython
could be a nice name, similar to how we have OperationFromPython
.
dag.apply_operation_back(placeholder_gate, qr, (), check=False) | ||
mapped_instrs[gate_name, gate_num_qubits] = placeholder_params, dag | ||
|
||
for gate_name, gate_num_qubits, equiv_params, equiv in basis_transforms: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like the documentation is wrong then for basis_transforms
in the original Python code, no? Since the tuple does not have the number of bits there.
Some(gate.into()), | ||
)?; | ||
mapped_instructions.insert( | ||
(gate_name.clone(), gate_num_qubits), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need to clone gate_name
here, or can we just consume it? I don't see anymore uses after this :)
impl IntoPy<PyObject> for CircuitRep { | ||
fn into_py(self, py: Python<'_>) -> PyObject { | ||
QUANTUM_CIRCUIT | ||
.get_bound(py) | ||
.call_method1("_from_circuit_data", (self.0,)) | ||
.unwrap() | ||
.unbind() | ||
} | ||
} | ||
|
||
impl ToPyObject for CircuitRep { | ||
fn to_object(&self, py: Python<'_>) -> PyObject { | ||
self.clone().into_py(py) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are these used?
Summary
The following commits add the helper function
compose_transforms
from theBasisTranslator
transpiler pass into rust. This is a small progression towards completing #12246. This function will be temporarily exposed to Python until #12246 is fully realized.Details and comments
As we continue moving parts of the
BasisTranslator
into Rust thanks to #12585 and #12811. The next logical step is to bring in those functions that relied heavily on theDAGCircuit
and since it now lives in Rust we can now leverage its usage by moving this helper function into Rust.compose_transforms
basically returns a mapping of(gate_name, num_qubits) : (parameters, gate_definition_as_dag)
for each gate present in theDAGCircuit
that is currently being processed. It also breaks downControlFlowOp
to include its definitions as well.Using the latest additions by #13036 we can perform most of these conversions from rust using both
circuit_to_dag
orDAGCirctut::from_circuit_data
. This function will be temporarily exposed to Python until #12246 is fully realized.Blockers
apply_operation
methods #13143These are not strictly blockers but some nice-to-haves.
EquivalenceLibrary
#12585basis_search
andBasisSearchVisitor
to rust. #12811