Skip to content

Commit

Permalink
Release GIL during parallel portion
Browse files Browse the repository at this point in the history
  • Loading branch information
mtreinish committed Dec 2, 2024
1 parent cb6b70f commit f06a070
Showing 1 changed file with 118 additions and 111 deletions.
229 changes: 118 additions & 111 deletions crates/accelerate/src/two_qubit_peephole.rs
Original file line number Diff line number Diff line change
Expand Up @@ -193,35 +193,41 @@ pub(crate) fn two_qubit_unitary_peephole_optimize(
HashMap::with_capacity(runs.iter().map(|run| run.len()).sum());
let locked_node_mapping = Mutex::new(node_mapping);

// Build a vec of all the best synthesized two qubit gate sequences from the collected runs.
// This is done in parallel
let run_mapping: PyResult<Vec<MappingIterItem>> = runs
.par_iter()
.enumerate()
.map(|(run_index, node_indices)| {
let block_qubit_map = node_indices
.iter()
.find_map(|node_index| {
let inst = dag.dag()[*node_index].unwrap_operation();
let qubits = dag.get_qargs(inst.qubits);
if qubits.len() == 2 {
if qubits[0] > qubits[1] {
Some([qubits[1], qubits[0]])
let run_mapping: PyResult<Vec<MappingIterItem>> = py.allow_threads(|| {
// Build a vec of all the best synthesized two qubit gate sequences from the collected runs.
// This is done in parallel
runs.par_iter()
.enumerate()
.map(|(run_index, node_indices)| {
let block_qubit_map = node_indices
.iter()
.find_map(|node_index| {
let inst = dag.dag()[*node_index].unwrap_operation();
let qubits = dag.get_qargs(inst.qubits);
if qubits.len() == 2 {
if qubits[0] > qubits[1] {
Some([qubits[1], qubits[0]])
} else {
Some([qubits[0], qubits[1]])
}
} else {
Some([qubits[0], qubits[1]])
None
}
} else {
None
}
})
.unwrap();
let matrix = blocks_to_matrix(dag, node_indices, block_qubit_map)?;
let decomposers = get_decomposers_from_target(target, &block_qubit_map, fidelity)?;
let mut decomposer_scores: Vec<Option<(f64, usize)>> = vec![None; decomposers.len()];
})
.unwrap();
let matrix = blocks_to_matrix(dag, node_indices, block_qubit_map)?;
let decomposers = get_decomposers_from_target(target, &block_qubit_map, fidelity)?;
let mut decomposer_scores: Vec<Option<(f64, usize)>> =
vec![None; decomposers.len()];

let order_sequence =
|(index_a, sequence_a): &(usize, (TwoQubitGateSequence, String)),
(index_b, sequence_b): &(usize, (TwoQubitGateSequence, String))| {
let order_sequence = |(index_a, sequence_a): &(
usize,
(TwoQubitGateSequence, String),
),
(index_b, sequence_b): &(
usize,
(TwoQubitGateSequence, String),
)| {
let score_a = match decomposer_scores[*index_a] {
Some(score) => score,
None => {
Expand Down Expand Up @@ -268,95 +274,96 @@ pub(crate) fn two_qubit_unitary_peephole_optimize(
score_a.partial_cmp(&score_b).unwrap_or(Ordering::Equal)
};

let sequence = decomposers
.iter()
.map(|decomposer| match decomposer {
TwoQubitDecomposer::Basis(decomposer) => (
decomposer
.call_inner(matrix.view(), None, true, None)
.unwrap(),
decomposer.gate_name().to_string(),
),
TwoQubitDecomposer::ControlledU(decomposer) => (
decomposer.call_inner(matrix.view(), 1e-12).unwrap(),
match decomposer.rxx_equivalent_gate {
RXXEquivalent::Standard(gate) => gate.name().to_string(),
RXXEquivalent::CustomPython(_) => {
unreachable!("Decomposer only uses standard gates")
let sequence = decomposers
.iter()
.map(|decomposer| match decomposer {
TwoQubitDecomposer::Basis(decomposer) => (
decomposer
.call_inner(matrix.view(), None, true, None)
.unwrap(),
decomposer.gate_name().to_string(),
),
TwoQubitDecomposer::ControlledU(decomposer) => (
decomposer.call_inner(matrix.view(), 1e-12).unwrap(),
match decomposer.rxx_equivalent_gate {
RXXEquivalent::Standard(gate) => gate.name().to_string(),
RXXEquivalent::CustomPython(_) => {
unreachable!("Decomposer only uses standard gates")
}
},
),
})
.enumerate()
.min_by(order_sequence)
.unwrap();
let mut original_err: f64 = 1.;
let mut original_count: usize = 0;
let mut outside_target = false;
for node_index in node_indices {
let NodeType::Operation(ref inst) = dag.dag()[*node_index] else {
unreachable!("All run nodes will be ops")
};
let qubits = dag
.get_qargs(inst.qubits)
.iter()
.map(|qubit| PhysicalQubit(qubit.0))
.collect::<Vec<_>>();
if qubits.len() == 2 {
original_count += 1;
}
let name = inst.op.name();
let gate_err = match target.get_error(name, qubits.as_slice()) {
Some(err) => 1. - err,
None => {
// If error rate is None this can mean either the gate is not supported
// in the target or the gate is ideal. We need to do a second lookup
// to determine if the gate is supported, and if it isn't we don't need
// to finish scoring because we know we'll use the synthesis output
let physical_qargs =
qubits.iter().map(|bit| PhysicalQubit(bit.0)).collect();
if !target.instruction_supported(name, Some(&physical_qargs)) {
outside_target = true;
break;
}
},
1.
}
};
original_err *= gate_err;
}
let original_score = (1. - original_err, original_count);
let new_score: (f64, usize) = match decomposer_scores[sequence.0] {
Some(score) => score,
None => score_sequence(
target,
sequence.1 .1.as_str(),
sequence
.1
.0
.gates
.iter()
.map(|(gate, _params, local_qubits)| {
let qubits = local_qubits
.iter()
.map(|qubit| block_qubit_map[*qubit as usize])
.collect();
(*gate, qubits)
}),
),
})
.enumerate()
.min_by(order_sequence)
.unwrap();
let mut original_err: f64 = 1.;
let mut original_count: usize = 0;
let mut outside_target = false;
for node_index in node_indices {
let NodeType::Operation(ref inst) = dag.dag()[*node_index] else {
unreachable!("All run nodes will be ops")
};
let qubits = dag
.get_qargs(inst.qubits)
.iter()
.map(|qubit| PhysicalQubit(qubit.0))
.collect::<Vec<_>>();
if qubits.len() == 2 {
original_count += 1;
if !outside_target && new_score > original_score {
return Ok(None);
}
let name = inst.op.name();
let gate_err = match target.get_error(name, qubits.as_slice()) {
Some(err) => 1. - err,
None => {
// If error rate is None this can mean either the gate is not supported
// in the target or the gate is ideal. We need to do a second lookup
// to determine if the gate is supported, and if it isn't we don't need
// to finish scoring because we know we'll use the synthesis output
let physical_qargs =
qubits.iter().map(|bit| PhysicalQubit(bit.0)).collect();
if !target.instruction_supported(name, Some(&physical_qargs)) {
outside_target = true;
break;
}
1.
}
};
original_err *= gate_err;
}
let original_score = (1. - original_err, original_count);
let new_score: (f64, usize) = match decomposer_scores[sequence.0] {
Some(score) => score,
None => score_sequence(
target,
sequence.1 .1.as_str(),
sequence
.1
.0
.gates
.iter()
.map(|(gate, _params, local_qubits)| {
let qubits = local_qubits
.iter()
.map(|qubit| block_qubit_map[*qubit as usize])
.collect();
(*gate, qubits)
}),
),
};
if !outside_target && new_score > original_score {
return Ok(None);
}
// This is done at the end of the map in some attempt to minimize
// lock contention. If this were serial code it'd make more sense
// to do this as part of the iteration building the
let mut node_mapping = locked_node_mapping.lock().unwrap();
for node in node_indices {
node_mapping.insert(*node, run_index);
}
Ok(Some((sequence.1, block_qubit_map)))
})
.collect();
// This is done at the end of the map in some attempt to minimize
// lock contention. If this were serial code it'd make more sense
// to do this as part of the iteration building the
let mut node_mapping = locked_node_mapping.lock().unwrap();
for node in node_indices {
node_mapping.insert(*node, run_index);
}
Ok(Some((sequence.1, block_qubit_map)))
})
.collect()
});

let run_mapping = run_mapping?;
// After we've computed all the sequences to execute now serially build up a new dag.
Expand Down

0 comments on commit f06a070

Please sign in to comment.