Refactoring of the Qrisp Backend structure#331
Refactoring of the Qrisp Backend structure#331PietropaoloFrisoni wants to merge 31 commits intoeclipse-qrisp:mainfrom
Conversation
|
It is important to mention here that this is specifically not intended to fit "only" IQM Backends but we are dedicated to make this as vendor agnostic as possible. Any input or features request from within or outside of the Qrisp development community is welcome. |
Aerylia
left a comment
There was a problem hiding this comment.
Summary of changes proposed/discussed during the meeting today
src/qrisp/interface/backend.py
Outdated
| self._options[key] = val | ||
|
|
||
| # ---------------------------------------------------------------------- | ||
| # Optional hardware/backend-specific metadata |
There was a problem hiding this comment.
These properties should at least be typed and they can only return None if that is meaningful. i.e. what does it mean for a backend to have backend.num_qubits is None ? And how would the transpiler deal with that?
There was a problem hiding this comment.
(see answer below regarding hardware metadata)
| return None | ||
|
|
||
| @property | ||
| def error_rates(self): |
There was a problem hiding this comment.
These are calibration dependent, we probably want the backend to somehow track which calibrated state of the hardware it is refering to.
There was a problem hiding this comment.
(see answer below regarding hardware metadata)
|
|
||
|
|
||
| class BatchedBackend(VirtualBackend): | ||
| class BatchedBackend(Backend): |
There was a problem hiding this comment.
We probably want to merge this with the regular backend with a run_batched that by default sequentially calls the run method and can be overloaded by a child instance.
There was a problem hiding this comment.
Right now, this BatchedBackend class inherits from Backend, but takes care of:
- Enqueuing the backend calls from multiple threads
- Synchronizing multiple threads
- Dispatching the batch to the backend
Therefore, it acts primarily as a scheduler / execution coordinator, not as a real backend in the strict sense (i.e. an object that defines execution semantics for a single circuit).
I don't think this should inherit from Backend (this was the quickest solution to make tests passing since it was inheriting from VirtualBackend before, which now is deprecated), but I am also not convinced about putting everything into a unique Backend class.
If we do that, then we need to take care of execution semantics, batching strategy, scheduling and sync. in the same class, which might be a little too messy.
I think this is something we should revisit once the design of the Backend class is fully finalized.
There was a problem hiding this comment.
Agreed! We should also gather feedback on how necessary the batching feature is (both from benchmarks and stakeholder interaction). According to Joni (SWE at IQM) the overhead from executing non-batched got significantly reduced.
| return None | ||
|
|
||
| @property | ||
| def gate_set(self): |
There was a problem hiding this comment.
How to deal with overlapping gate sets in the connectivity. e.g. some pairs with CZ, some pairs with iSWAP, some pairs with both.
There was a problem hiding this comment.
(see answer below regarding hardware metadata)
src/qrisp/interface/backend.py
Outdated
|
|
||
| @property | ||
| def gate_set(self): | ||
| """Set of gates supported by the backend.""" |
There was a problem hiding this comment.
What are the assumptions of the universality of the gate set or the qubits? Are qubits assumed to have a measurement implemented? - Document these assumptions
There was a problem hiding this comment.
(see answer below regarding hardware metadata)
src/qrisp/interface/backend.py
Outdated
| # ---------------------------------------------------------------------- | ||
|
|
||
| @abstractmethod | ||
| def run(self, *args, **kwargs) -> Any: |
There was a problem hiding this comment.
If transpilation is done as part of the run, we will need run(..., transpile_method=Somefunc/object) and we will also want a backend.transpile() that is used in the run() so that users can inspect the transpile result before running the circuit on the hardware.
There was a problem hiding this comment.
Thanks for bringing this up. Conceptually, I would like to keep transpilation and execution as distinct phases. That is, keep them separable and inspectable. At the same time, I agree that for usability reasons run() may invoke transpilation as a convenience. If that is the case, the run method can be overridden by specifying this keyword argument.
A possible design is to expose a backend.transpile() in this class.
For example, in the PlasmaSabre package we indeed have a transpile_to_iqm function, and I don't see why we cannot implement it in the IQM backend as concrete implementation of the transpile method. But I do not have a lot of experience with transpilation processes for different backends.
What do you think @positr0nium ?
There was a problem hiding this comment.
The backend might also solve this issue (that is, inspect the final circuits that would be submitted for execution before actually submitting them, which can be useful for debugging purposes) with another method such as create_run_request (as IQMBackend currently does). I would not add this method to the base class though
|
I reply here regarding the observations about the hardware metadata (gate_set, connectivity, etc.). Thanks for the very good observations! I agree that raw properties such as I think the intent of the base In practice, I would like vendor backends to reuse data structures already provided by the vendor whenever possible. For example, in the case of IQM, we have an In this model, I think I agree that, in this context, the |
src/qrisp/default_backend.py
Outdated
| def run(self, circuit, **kwargs): | ||
| shots = kwargs.get("shots", self.options.get("shots", None)) | ||
| token = kwargs.get("token", self.options.get("token", "")) | ||
| return default_run(circuit, shots, token) |
There was a problem hiding this comment.
| def run(self, circuit, **kwargs): | |
| shots = kwargs.get("shots", self.options.get("shots", None)) | |
| token = kwargs.get("token", self.options.get("token", "")) | |
| return default_run(circuit, shots, token) | |
| def run(self, circuit, *, shots) -> Job: | |
| token = self._default_options[token] # This should probably get the options from the specific instance | |
| return default_run(circuit, shots, token) | |
| def run_await_results(self, circuit, *, shots, timeout=1000) -> dict[str, int]: | |
| return run(self, circuit, shots=shots).results(timeout=timeout) |
Job here should be an abstract class that you can query for results so that it can be run asynchronously
|
First of all: thank you everyone for your efforts so far in refactoring the backend class. However, as indicated in your comments, there are some open points, that could and should be discussed together, to shape the backend class, and also the compilation pipeline (where does what step of compilation/transpilation take place), in an optimal way. Maybe we could start a discussion together about this topic in the beginning of next year. |
|
Hi @bastibock, nice to meet you! Right now, we are getting very useful feedback from IQM's devs and Product Team, who have a lot of experience with real physical quantum backends and therefore can provide good suggestions for such a class. Happy to talk to you next year! |
Context: The current backend implementation in Qrisp works as an extremely abstract "one-fits-all" tool. Essentially, the only existing feature is to send quantum circuits and receive results.
Furthermore, it is embedded into a network interface that was part of a requirement of the
SeQuenCproject during the early stages of Qrisp. This network interface eventually never used and should now be deprecated.Description of the Change:
We introduce a custom
QrispDeprecationWarningin a new module.We use the new
QrispDeprecationWarningto deprecate the currentBackendClient,BackendServer, andVirtualBackendclasses (as well as a few other functionalities that were already marked as deprecated).We introduce a new
Backendclass in a new module (the latter is described in the associated docstring).We modify all the existing backends so that they inherit from
Backendrather than the deprecated backends. These areDefaultBackend,BatchedBackend, andQiskitBackend. A few backends are excluded from this:QiskitRuntimeBackend(since I do not have an IBM token to test it) and all the docker backends.We introduce a new
qiskit-ibm-runtimedependency to test a specific backend forQiskitBackend(this test was commented out), and we replaceqiskit-iqmwithiqm-client[qiskit](as the former was causing the following error:RuntimeError: The qiskit-iqm package is obsolete (...))Finally, the changes implemented in this PR are also part of the associated PR in the
plasma_sabrerepository on GitLab (I cannot link it here as it is part of IQM Finland).Benefits: Much better structure and deprecated Qunicorn servers.
Possible Drawbacks: We cannot exclude at 100% edge-case incompatibilities with the backends we could not test because of missing tokens and api accesses (especially considering that several tests were missing and/or commented out).
Related GitHub Issues: None