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

Oxidize parts of HLS #13369

Merged
merged 16 commits into from
Nov 6, 2024
Merged

Oxidize parts of HLS #13369

merged 16 commits into from
Nov 6, 2024

Conversation

alexanderivrii
Copy link
Contributor

Summary

This PR oxidize small parts of HighLevelSynthesis: QubitTracker and QubitContext. It is implemented on top of #13240 and replaces #13180.

Details and comments

This is a more or less mechanical porting of the python code in #13240. Ideally, I would also like to port the auxiliary function _definitely_skip_node in time for 1.3.0.

@alexanderivrii alexanderivrii added this to the 1.3.0 milestone Oct 24, 2024
@qiskit-bot
Copy link
Collaborator

One or more of the following people are relevant to this code:

  • @Cryoris
  • @Qiskit/terra-core
  • @ajavadia

@coveralls
Copy link

coveralls commented Oct 24, 2024

Pull Request Test Coverage Report for Build 11681228503

Details

  • 123 of 147 (83.67%) changed or added relevant lines in 4 files are covered.
  • 7 unchanged lines in 3 files lost coverage.
  • Overall coverage decreased (-0.004%) to 88.764%

Changes Missing Coverage Covered Lines Changed/Added Lines %
crates/accelerate/src/high_level_synthesis.rs 120 144 83.33%
Files with Coverage Reduction New Missed Lines %
crates/accelerate/src/two_qubit_decompose.rs 1 92.09%
crates/qasm2/src/expr.rs 1 94.02%
crates/qasm2/src/lex.rs 5 92.48%
Totals Coverage Status
Change from base Build 11669445061: -0.004%
Covered Lines: 76805
Relevant Lines: 86527

💛 - Coveralls

@mtreinish mtreinish added performance Rust This PR or issue is related to Rust code in the repository labels Oct 25, 2024
@ShellyGarion ShellyGarion added the Changelog: None Do not include in changelog label Nov 5, 2024
Copy link
Contributor

@raynelfss raynelfss left a comment

Choose a reason for hiding this comment

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

I believe this is about ready to merge (I'll let @Cryoris have the last word). I just left some suggestions about ignored_qubits only to test whether a HashSet would be the better approach here. I left the transformed functions as suggestions here.

crates/accelerate/src/high_level_synthesis.rs Show resolved Hide resolved
Comment on lines +29 to +30
/// Used internally for keeping the computations in `O(n)`
ignored: Vec<bool>,
Copy link
Contributor

Choose a reason for hiding this comment

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

Since this isn't used as much here, maybe we could remove it in favor of providing a HashSet<usize> whenever needed? Performing a hash lookup for a usize shouldn't be very expensive, and since we're only using this as a way of checking a value should be ignored, a HashSet<usize> as a function argument should suffice. We'd have to benchmark it to see if it affects performance in any way.

Comment on lines +73 to +89
/// Returns the number of enabled clean qubits, ignoring the given qubits
/// ToDo: check if it's faster to avoid using ignored
fn num_clean(&mut self, ignored_qubits: Vec<usize>) -> usize {
for q in &ignored_qubits {
self.ignored[*q] = true;
}

let count = (0..self.num_qubits)
.filter(|q| !self.ignored[*q] && self.enabled[*q] && self.state[*q])
.count();

for q in &ignored_qubits {
self.ignored[*q] = false;
}

count
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Just so you're able to test quickly, see if using a hashset affects this in any way:

Suggested change
/// Returns the number of enabled clean qubits, ignoring the given qubits
/// ToDo: check if it's faster to avoid using ignored
fn num_clean(&mut self, ignored_qubits: Vec<usize>) -> usize {
for q in &ignored_qubits {
self.ignored[*q] = true;
}
let count = (0..self.num_qubits)
.filter(|q| !self.ignored[*q] && self.enabled[*q] && self.state[*q])
.count();
for q in &ignored_qubits {
self.ignored[*q] = false;
}
count
}
/// Returns the number of enabled clean qubits, ignoring the given qubits
/// ToDo: check if it's faster to avoid using ignored
fn num_clean(&mut self, ignored_qubits: HashSet<usize>) -> usize {
(0..self.num_qubits)
.filter(|q| !ignored_qubits.contains(q) && self.enabled[*q] && self.state[*q])
.count()
}

Comment on lines +91 to +107
/// Returns the number of enabled dirty qubits, ignoring the given qubits
/// ToDo: check if it's faster to avoid using ignored
fn num_dirty(&mut self, ignored_qubits: Vec<usize>) -> usize {
for q in &ignored_qubits {
self.ignored[*q] = true;
}

let count = (0..self.num_qubits)
.filter(|q| !self.ignored[*q] && self.enabled[*q] && !self.state[*q])
.count();

for q in &ignored_qubits {
self.ignored[*q] = false;
}

count
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Same case here (for testing):

Suggested change
/// Returns the number of enabled dirty qubits, ignoring the given qubits
/// ToDo: check if it's faster to avoid using ignored
fn num_dirty(&mut self, ignored_qubits: Vec<usize>) -> usize {
for q in &ignored_qubits {
self.ignored[*q] = true;
}
let count = (0..self.num_qubits)
.filter(|q| !self.ignored[*q] && self.enabled[*q] && !self.state[*q])
.count();
for q in &ignored_qubits {
self.ignored[*q] = false;
}
count
}
/// Returns the number of enabled dirty qubits, ignoring the given qubits
/// ToDo: check if it's faster to avoid using ignored
fn num_dirty(&mut self, ignored_qubits: HashSet<usize>) -> usize {
(0..self.num_qubits)
.filter(|q| !ignored_qubits.contains(q) && self.enabled[*q] && !self.state[*q])
.count()

Comment on lines +109 to +130
/// Get `num_qubits` enabled qubits, excluding `ignored_qubits`, and returning the
/// clean qubits first.
/// ToDo: check if it's faster to avoid using ignored
fn borrow(&mut self, num_qubits: usize, ignored_qubits: Vec<usize>) -> Vec<usize> {
for q in &ignored_qubits {
self.ignored[*q] = true;
}

let clean_ancillas = (0..self.num_qubits)
.filter(|q| !self.ignored[*q] && self.enabled[*q] && self.state[*q]);
let dirty_ancillas = (0..self.num_qubits)
.filter(|q| !self.ignored[*q] && self.enabled[*q] && !self.state[*q]);
let out: Vec<usize> = clean_ancillas
.chain(dirty_ancillas)
.take(num_qubits)
.collect();

for q in &ignored_qubits {
self.ignored[*q] = false;
}
out
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Here's how it would look with the Hashset:

Suggested change
/// Get `num_qubits` enabled qubits, excluding `ignored_qubits`, and returning the
/// clean qubits first.
/// ToDo: check if it's faster to avoid using ignored
fn borrow(&mut self, num_qubits: usize, ignored_qubits: Vec<usize>) -> Vec<usize> {
for q in &ignored_qubits {
self.ignored[*q] = true;
}
let clean_ancillas = (0..self.num_qubits)
.filter(|q| !self.ignored[*q] && self.enabled[*q] && self.state[*q]);
let dirty_ancillas = (0..self.num_qubits)
.filter(|q| !self.ignored[*q] && self.enabled[*q] && !self.state[*q]);
let out: Vec<usize> = clean_ancillas
.chain(dirty_ancillas)
.take(num_qubits)
.collect();
for q in &ignored_qubits {
self.ignored[*q] = false;
}
out
}
/// Get `num_qubits` enabled qubits, excluding `ignored_qubits`, and returning the
/// clean qubits first.
/// ToDo: check if it's faster to avoid using ignored
fn borrow(&mut self, num_qubits: usize, ignored_qubits: HashSet<usize>) -> Vec<usize> {
let clean_ancillas = (0..self.num_qubits)
.filter(|q| !ignored_qubits.contains(q) && self.enabled[*q] && self.state[*q]);
let dirty_ancillas = (0..self.num_qubits)
.filter(|q| !self.ignored[*q] && self.enabled[*q] && !self.state[*q]);
clean_ancillas
.chain(dirty_ancillas)
.take(num_qubits)
.collect()
}

@raynelfss raynelfss self-assigned this Nov 6, 2024
Copy link
Contributor

@raynelfss raynelfss left a comment

Choose a reason for hiding this comment

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

After doing some testing with sets, the performance is almost the same. If anything I could suggest using the HashSet route once we have the HighLevelSynthesis entirely in Rust just to avoid doing the extra conversions (from list to set in Python and from PySet to HashSet in Rust).
This code looks great and is ready to merge as is, thank you for your contribution 🚀

@raynelfss raynelfss added this pull request to the merge queue Nov 6, 2024
Merged via the queue into Qiskit:main with commit 640957a Nov 6, 2024
18 checks passed
@alexanderivrii
Copy link
Contributor Author

Thanks @raynelfss! I have just run a few experiments (after you have already merged the code) to see what happens on circuits with many small gates:

qc = QuantumCircuit(100)
qc.mcx([0, 1, 2, 3], 4)  # make sure that HLS does not follow fast-track
for _ in range(100):
    for i in range(49):
        qc.cx(i, i+1)
qct = HighLevelSynthesis()(qc)

and on circuits with many large gates that do not need to be synthesized:

pattern = list(range(99, -1, -1))
qc = QuantumCircuit(100)
for _ in range(1000):
    qc.append(PermutationGate(pattern), qc.qubits)
config = HLSConfig(permutation=[])  # this means: do not actually synthesize permutation gates
qct = HighLevelSynthesis(hls_config=config)(qc)

In fact, the performance even becomes a tiny bit better when using hashbrown::HashSet (despite, as you have mentioned, having to convert from list to set in Python) -- and I am actually a bit surprised by this (using the "scratch" bitvector was a very common and performance-critical trick in the C++ projects I worked on in the past).

So, indeed, I am 100% switching to the HashSet route in the future.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Changelog: None Do not include in changelog performance Rust This PR or issue is related to Rust code in the repository
Projects
Status: done
Development

Successfully merging this pull request may close these issues.

7 participants