Qiskit / qiskit-ibm-runtime

IBM Client for Qiskit Runtime
https://docs.quantum.ibm.com/api/qiskit-ibm-runtime
Apache License 2.0
149 stars 154 forks source link

KeyError: 'backend' in _decode_job #622

Closed mriedem closed 1 year ago

mriedem commented 1 year ago

Describe the bug

I submitted a job to cloud runtime (staging if that matters) without specifying a backend so that the scheduler would pick one. The job actually completed on the backend (I can see from the logs) but when I get details about the job I get a KeyError:

Traceback (most recent call last):
  Input In [8] in <cell line: 1>
    job = service.job('cdr4n5es1c6fcn0blivg')
  File /opt/conda/lib/python3.8/site-packages/qiskit_ibm_runtime/qiskit_runtime_service.py:1278 in job
    return self._decode_job(response)
  File /opt/conda/lib/python3.8/site-packages/qiskit_ibm_runtime/qiskit_runtime_service.py:1408 in _decode_job
    backend = self.backend(raw_data["backend"], instance=instance)
KeyError: 'backend'

That's this code:

https://github.com/Qiskit/qiskit-ibm-runtime/blob/5ef5711716b52a95b8a0211385f3b4450da82f74/qiskit_ibm_runtime/qiskit_runtime_service.py#L1408

In my case the job completed so I'd expect the backend to be set on the job, so it seems there might be a bug on the server side (I've also opened an issue for that internally), but also looking at the Swagger doc for GET /jobs/{id}:

https://us-east.quantum-computing.cloud.ibm.com/openapi/#/Jobs/get_job_details_jid

It says backend is optional in the response:

image

So the qiskit client code should be handling that and avoid the KeyError.

Steps to reproduce

Submit a cloud job without specifying an explicit backend.

Expected behavior

I should be able to get the cloud job details, like what backend it ran on once it's complete.

Suggested solutions

Handle the KeyError but not requiring backend to be in the raw JSON response, at least for cloud jobs (channel=ibm_cloud).

Additional Information

kt474 commented 1 year ago

If we need a quick fix, handling the KeyError here would be pretty straightforward. For now, I'll wait for further investigation on the server side - completed jobs should always have a backend set.

mriedem commented 1 year ago

completed jobs should always have a backend set.

Agree on this.

But what if I've just submitted the job, it's queued to run, and the backend hasn't yet been selected by the scheduler? The backend might not be in the job response then, it shouldn't result in a KeyError though. The client code seems to assume backend will always be there, which is true for the IBM Quantum Premium channel but not the cloud channel.

jyu00 commented 1 year ago

But what if I've just submitted the job, it's queued to run, and the backend hasn't yet been selected by the scheduler? The backend might not be in the job response then

Then there needs a way to "refresh" a job data, so job.backend() will return the data once a backend is selected.

kt474 commented 1 year ago

But what if I've just submitted the job, it's queued to run, and the backend hasn't yet been selected by the scheduler? The backend might not be in the job response then

Then there needs a way to "refresh" a job data, so job.backend() will return the data once a backend is selected.

Is there a way to replicate/test this? When not specifying a backend, all jobs I ran, including queued jobs, had a backend.

Screen Shot 2022-12-18 at 8 31 05 PM

Also, service.job() calls the _decode_job method which retrieves data from the server - wouldn't this refresh the backend data?

jyu00 commented 1 year ago

I had limited success with this. For some reason some jobs have backends assigned (although probably wrong ones) when they're queued but not all 🤷‍♀️

image

I think a unit test with an empty backend (using fake runtime client) is probably sufficient.

jyu00 commented 1 year ago

Also, service.job() calls the _decode_job method which retrieves data from the server - wouldn't this refresh the backend data?

I suppose that's one way of getting updated job data. But it'd be nicer for backend() to refresh under the cover if backend is empty, instead of the user having to know to re-retrieve the job again using service.job(). For example,

job = sampler.run()
job.backend()  # fetch new data if backend is empty

is nicer than

job = sampler.run()
rjob = service.job(job.job_id())
rjob.backend()