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

In Jupyter notebook, if simulation ended with runtime exception, allocated qubits are not cleared up #1265

Open
tcNickolas opened this issue Mar 15, 2024 · 6 comments
Labels
design needed enhancement New feature or request

Comments

@tcNickolas
Copy link
Contributor

Describe the bug

In Jupyter notebook, if simulation ended with runtime exception, allocated qubits stay around. They show up in the next DumpMachine outputs, even though there's no way to access them or clear them up.

To Reproduce

open Microsoft.Quantum.Diagnostics;

operation Demo() : Unit {
    use q = Qubit();
    X(q);
    DumpMachine();
}

Demo()

As you re-run this code, it throws an exception each time, and the number of allocated qubits grows by 1 each time, all of them in |1> state.

Expected behavior

I expect the allocated qubits to not show up in consecutive runs.

System information

  • qsharp 1.2.0
@tcNickolas tcNickolas added bug Something isn't working needs triage labels Mar 15, 2024
@minestarks
Copy link
Member

@swernli I can see how the current behavior is by design, and technically, continuing to execute statements after a runtime error is inadvisable -- but this is super common to encounter as you're iterating on code and it's mildly annoying. I think it's simple enough to accommodate.

Is it worth fixing this by just calling sim.qubit_release() before throwing a runtime failure?

if sim.qubit_is_zero(qubit) {
sim.qubit_release(qubit);
Ok(Value::unit())
} else {
Err(Error::ReleasedQubitNotZero(qubit, arg_span))
}

@swernli
Copy link
Collaborator

swernli commented Apr 4, 2024

For this specific case of qubits released in a non-zero state, I think that is an excellent solution. We are already in the process of releasing the qubits, and the language makes it hard to continue referring to them to even have a way to clean them up manually, so we should just release unconditionally. Maybe even something like:

let is_zero = qubit_is_zero(qubit);
sim.qubit_release(qubit);
if is_zero {
    Ok(Value::unit())
} else {
    Err(Error::ReleasedQubitNotZero(qubit, arg_span))
}

It's worth noting that this would fix a very common problem but not all the problems, so code like:

open Microsoft.Quantum.Diagnostics;

operation Demo() : Unit {
    use q = Qubit();
    X(q);
    let _ = 1 / 0;
}

Demo()

Would still leak a qubit each time.

@tcNickolas
Copy link
Contributor Author

Can we do qubit_release upon any error, as part of error processing?

@swernli
Copy link
Collaborator

swernli commented Apr 5, 2024

Can we do qubit_release upon any error, as part of error processing?

There might be mechanisms we can employ, but it's tricky to differentiate between qubits in a contained scope that should be released:

{
    use q = Qubit();
    X(q);
    let _ = 1 / 0;
}

vs qubits from top-level statements that shouldn't be released:

use q = Qubit();
X(q);
let _ = 1 / 0;

The evaluator doesn't do any special tracking of qubits to know which ones are in scope, but rather depends on one of the transformation passes that runs during compilation that turns qubit statements into explicit calls to allocate and release. So for example, the first snippet above with the explicit scope will internally be transformed into:

{
    let q = __quantum__rt__qubit_allocate();
    X(q);
    let _ = 1 / 0;
    __quantum__rt__qubit_release(q);
}

That final statement is what performs the release, not any special tracking in the evaluator, and the error short-circuits that statement.

@minestarks
Copy link
Member

I think the feature suggestion here is essentially "stack unwinding" - making fails behave like exceptions do, deallocating resources as the stack gets unwound.

Currently fails in Q# don't have exception semantics: they don't unwind the stack, they can't be caught. Rather they're more of an "abort": program simply terminates (it's supposed to, anyway). This works fine in a standalone Q# program. But in a REPL/notebook environment, we get into weird "undefined behavior" territory. You can continue to execute statements, but we're essentially limping along at this point and can't guarantee a consistent program state.

Another hamfisted suggestion I have is to actually abort the program in Python when a fail occurs: throw away the Q# interpreter and reinitialize it. The downside is you may need to re-run some cells you've previously. The upside is that the state will always be consistent, no limping along.

@swernli
Copy link
Collaborator

swernli commented Apr 15, 2024

I think the feature suggestion here is essentially "stack unwinding" - making fails behave like exceptions do, deallocating resources as the stack gets unwound.

Yeah, that's what we'd need, and it would need to be smart enough to know not to deallocate qubits from the top-level/global scope of the notebook.

Another hamfisted suggestion I have is to actually abort the program in Python when a fail occurs: throw away the Q# interpreter and reinitialize it. The downside is you may need to re-run some cells you've previously. The upside is that the state will always be consistent, no limping along.

I think from the users perspective this would look awfully similar to a crash, so if we went this route we'd want to have a very clear message that guides them on how to recuperate their state (rerun appropriate notebook cells but don't rerun all if there's job submissions, for example).

@sezna sezna added enhancement New feature or request design needed and removed bug Something isn't working needs triage labels Aug 15, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
design needed enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

4 participants