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

ISA circuit support for latest Runtime #164

Open
woodsp-ibm opened this issue Mar 8, 2024 · 8 comments
Open

ISA circuit support for latest Runtime #164

woodsp-ibm opened this issue Mar 8, 2024 · 8 comments

Comments

@woodsp-ibm
Copy link
Member

woodsp-ibm commented Mar 8, 2024

https://docs.quantum.ibm.com/announcements/product-updates/2024-02-14-qiskit-runtime-primitives-update

beginning 1 March 2024, Qiskit Runtime will require that circuits and observables are transformed to use only instructions supported by the system (referred to as instruction set architecture (ISA) circuits and observables) before being submitted to the primitives

This affects the existing V1 primitives, as supported here, as well as the newly introduced V2 primitives which are yet to be supported, see #136

While some algorithms, like VQE, can already be passed a transpiled ansatz and adjusted operator(s), others build out the circuit internally to run so to continue supporting these they require change. Adding measure_all to a transpiled circuit, instead of a logical one, is going to span all the qubits so probably needs to be measure_active is that techjnique is continued,

Any solution to this, to update algorithms here, needs further discussion etc

@woodsp-ibm
Copy link
Member Author

woodsp-ibm commented Mar 24, 2024

I am just going to add these thoughts. I do not have a specific solution and the thought I had about what might be a compatible path for users so existing code could continue to work is not without problems. Anyway here is the thought....

And as far as any fixes in general I did think perhaps a TranspileEngine or some common class that could be called upon to transpile by any of the algos. I was thinking a global instance being created of that, like the random number generator in utils, that an algo could just use. Nothing would be needed to be passed into any algo so the code would work as is. The engine could have some settings for skip transpilation, backend, opt level etc. And maybe even allow you to give it a passmanager etc. Evidently all algos would need to call this global instance to transpile their circuits, and if skipping it would just return the same deal. Algos could even add a new parameter for a transpile engine so you could do things differently/independently and not have to worry about coordinating on the global instance. Custom versions of the engine leveraging alternative transpilation mechanisms etc could be done allowing flexibility for the future. I was imagining an algo would use this to transpile the circuits it needs to run and then call the Estimator/Sampler primitive using the transpiled version.

Now this thought is not without problems. Lets take say QSVC in Qiskit Machine Learning. It knows the circuit, which is passed to it, and if it did things directly it would transpile (any resultant from) that circuit once and then use it. However it uses ComputeUncompute which composes the circuit to a inverse of itself and it's that which needs transpiling - passing a transpiled circuit to ComputeUncompute does not work (its still an ISA circuit to the runtime). Now ComputeUncompute could transpile at it's level for each fidelity computation, which would work, but it would be less inefficient since its the same circuit where only QSVC knows this. (Well ComputeUncompute does manage a cache today for combined circuits ie the circuit + circuit.inverse() so a transpiled version is not so much of a stretch) V1 primitives did do things like id circuits to avoid this so that's possible here (as has been done so far) or do some managed dictionary of them in UncomputeCompute where a circuit is added to dictionary and that dict key is what is used to do the fidelity not the circuit itself. Of course that diverges the interface away from how other primitives look (the Fidelities being consider to look/function like primitives). I imagine Gradients here may have similar considerations.

The above problem stated succinctly is if there is a separation between the algorithm that owns/manages the original circuits which then passes it/them to another which does changes to the circuit, how to manage the transpilation to just when needed - the original algo may well know the circuits are the same being passed each time, the receiver just sees circuits each time and has no idea - without resort to id'ing them as did the V1 primitives. As circuits are mutable that adds to the complexity which is why V1 primitives used id and would compute it for the each time it was passed,

Another aspect to consider is that transpile needs to be done against a specific backend. So having a global instance would "lock" things to one backend, though of course such an engine could have the backend settable, or perhaps less problematic have backend as part of engine init and replace the instance. Changing the backend mayl cause problems - any existing instances might have transpiled circuits for the prior backend - eg ComputeUncompute which caches stuff based on id may have the same input circuit and hence if it sees it the same may decide it already has the transpiled circuit without further checks. If the engine is not updateable then we could just check the id of the engine and if different we know things have changed (it might be a different instance with exact same transpilation but thats tough!) And of course being "locked" to one backend could make running on different backends at the same time problematic. Passing an optional transpile engine to an algorithm, whereby it would use the global instance, if not given something specific, might be a solution to that - here a user would create and pass an engine in rather than having it default to the global instance.

@BrunoRosendo
Copy link

BrunoRosendo commented Apr 17, 2024

Is there a workaround for this problem? I am solving a QUBO formulation with QAOA/eigen solver and am stuck on getting it to run on a real machine.

My flow is more or less this (pseudo-code):

service = QiskitRuntimeService(token="TOKEN", channel="ibm_quantum")
backend = service.backend("name")
session = Session(service, backend)
sampler = SamplerV1(session=session)

qaoa = QAOA(
    sampler=sampler,
    optimizer=classic_optimizer,
)

if warm_start:
    optimizer = WarmStartQAOAOptimizer(
        pre_solver=pre_solver, qaoa=qaoa
    )
else:
    optimizer = MinimumEigenOptimizer(qaoa)

return optimizer.solve(quadratic_program) # QP defined somewhere else

@woodsp-ibm Do you have any tips you could give me? It is important for my master's thesis to run this in the near future.

Fun fact, the algorithm works by using BackendSampler instead of qiskit-ibm-runtime. However, this is also infeasible since sessions are not used, resulting in a queue every single iteration. I don't understand why the issue only pops up with runtime service

@BrunoRosendo
Copy link

BrunoRosendo commented Apr 17, 2024

I'm thinking about this workaround: getting a local copy of qiskit-algorithms and change these 2 lines (mixer and initial_state) to the transpiled versions of the circuits

Would something like this work?

self.mixer = qiskit.compiler.transpile(mixer, backend=backend)
self.initial_state = qiskit.compiler.transpile(initial_state, backend=backend)

Or maybe directly in the ansatz? I don't fully understand which one is being used

@BrunoRosendo
Copy link

Hi. I am commenting again to ask if there's a fix coming anytime soon or if a workaround with a local copy of the library is the only way to go about this

@woodsp-ibm
Copy link
Member Author

I will also note that for IBM Runtime that the ISA circuit is just one aspect. As of Runtime 0.23 the V1 primitives, which is all the algorithms support, are deprecated and would be removed in the not too distant future. If this QAOA code is you want/need to use then doing a local workaround for just your needs, and not waiting for some more general change to come, seems like the practical/sensible choice - a) to deal with ISA and b) to use V2 primitive instead of V1.

@BrunoRosendo
Copy link

I went with @woodsp-ibm's suggestion and implemented a solution for my use case in a local copy of mine. I created a fork, in case anybody is in the same situation and needs a working version immediately: https://github.com/BrunoRosendo/qiskit-algorithms

However, note that this was only tested for MinimumEigenOptimizer. This should work for other solvers if the operator passed is a SparsePauliOp. This is the case because I use the apply_layout method, which is not present in the abstract BaseOperator accepted by QAOA/SamplingVQE.

As for the solution, it is surprisingly simple, being composed by the ansatz transpilation and applying its layout to the operator.

@woodsp-ibm
Copy link
Member Author

As for the solution, it is surprisingly simple,

Yes, for some algos, where the circuit passed is what ends up being given to primitive that works out simple enough. Some algos here, e.g. grover are more complicated. Same with ComputeUncompute fidelty which appends the inverse of the circuit to whatever is given - the result gets rejected as not ISA even with the input being ISA. Hence the discussion here being to have some transpile engine algos can use - VQE could support no transpilation too allowing one to pass their own ISA circuit and suitable operators as you did. This is not the case for everything though so a solution that spans the codebase here is rather more work.

@woodsp-ibm
Copy link
Member Author

woodsp-ibm commented May 24, 2024

A callback, as an alternative implementation to a transpile engine object/class, was discussed (the latter I had always thought would have a method to be called that perhaps was the same as the callback but whose construction may take various parameters to set it up and possibly include setting backend after the fact too - potentially raising an error if ever used without a backend being set). Either way calling that for object would transpile the circuits (and layout any operators to match). Either way an identity transpile engine is also possible allowing, where its possible with the algorithm, for a user to supply their own transpiled circuit - i.e. akin to skip_transpilation as the primitives had it before when they did transpilation.

Whether a global/default transpilation exists or not (instance or function) depends on what we want to happen about existing code. Clearly for runtime circuits need transpilation - can we have some default whereby it works no matter simulator or runtime. Clearly the code cannot work exactly as it stands, at least for runtime, since at least this engine would need the backend set so a suitable ISA circuit results. Hence perhaps a default, other than giving back identity, still would not get existing code to work - it still needs augmenting to define the backend target for transpilation however that manifests in the solution. Defining backend for given callbacks (functions) versus being able to set a property (and/or parameter on construction for an object) maybe more problematic for a user. Seems like we would end up with partials here so the user can define the backend to the callback that finally gets used but where the callback to the algorithm does not need to supply this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants