Skip to content

Commit 4b471ee

Browse files
committed
sampler: update to not wait at end
Signed-off-by: vsoch <vsoch@users.noreply.github.com>
1 parent 6709596 commit 4b471ee

1 file changed

Lines changed: 97 additions & 194 deletions

File tree

‎examples/qrmi-workload/sampler.py‎

Lines changed: 97 additions & 194 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,10 @@
11
#!/usr/bin/env python3
22
"""A QRMI workload for a pod that fluence placed.
33
4-
fluence picked a backend for this pod and the webhook put its name in the
5-
environment as FLUXION_BACKEND, with the backend attributes as FLUXION_<KEY>.
6-
This script takes it from there with QRMI. It acquires a session on that
7-
backend with the credentials mounted on the pod, runs a Bell circuit through
8-
the QRMI sampler, prints the counts, and releases the session. fluence holds
9-
the graph allocation and this pod holds the session. A worker in a shared gang
10-
is handed the scout session instead and submits into it without acquiring.
11-
12-
For an IBM backend the credentials are the four <backend>_QRMI_IBM_QRS_*
13-
variables, or IBM_CLOUD_TOKEN and IBM_CLOUD_CRN from which they are derived.
14-
Without credentials this is a rehearsal. It prints what it would need and
15-
exits cleanly, so the same pod proves the placement and the handoff on a
16-
cluster with no account.
4+
fluence chose the backend and the webhook put its name in FLUXION_BACKEND.
5+
This acquires a session on it, runs a Bell circuit through the QRMI sampler,
6+
prints the counts, and releases the session. Without credentials it prints
7+
what it would need and exits, so the same pod works as a rehearsal.
178
"""
189

1910
import argparse
@@ -24,132 +15,83 @@
2415
from qiskit import QuantumCircuit
2516
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
2617
from qrmi import QuantumResource, ResourceType
27-
from qrmi.primitives.ibm import SamplerV2, get_target
18+
from qrmi.primitives.ibm import SamplerV2, get_backend
2819

29-
# QRMI type strings to ResourceType names
3020
RESOURCE_TYPES = {
31-
"qiskit-runtime-service": "IBMQiskitRuntimeService",
32-
"ibm-quantum-system": "IBMQuantumSystem",
33-
"pasqal-cloud": "PasqalCloud",
34-
"pasqal-local": "PasqalLocal",
35-
"iqm-server": "IQMServer",
36-
"alice-bob-felis": "AliceBobFelis",
37-
}
38-
39-
# what QRMI reads for each type, looked up as <backend><suffix>
40-
REQUIRED_ENV = {
41-
"qiskit-runtime-service": (
42-
"_QRMI_IBM_QRS_ENDPOINT",
43-
"_QRMI_IBM_QRS_IAM_ENDPOINT",
44-
"_QRMI_IBM_QRS_IAM_APIKEY",
45-
"_QRMI_IBM_QRS_SERVICE_CRN",
46-
),
47-
"ibm-quantum-system": (
48-
"_QRMI_IBM_QS_ENDPOINT",
49-
"_QRMI_IBM_QS_IAM_ENDPOINT",
50-
"_QRMI_IBM_QS_IAM_APIKEY",
51-
"_QRMI_IBM_QS_SERVICE_CRN",
52-
),
53-
"iqm-server": ("_QRMI_IQM_ISA_ENDPOINT", "_QRMI_IQM_ISA_TOKEN"),
21+
"qiskit-runtime-service": ("IBMQiskitRuntimeService", "_QRMI_IBM_QRS"),
22+
"ibm-quantum-system": ("IBMQuantumSystem", "_QRMI_IBM_QS"),
5423
}
55-
56-
# the IBM endpoints are fixed, so the credentials reduce to a key and a CRN
57-
IBM_QRS_DERIVED = {
58-
"_QRMI_IBM_QRS_ENDPOINT": "https://quantum.cloud.ibm.com/api/v1",
59-
"_QRMI_IBM_QRS_IAM_ENDPOINT": "https://iam.cloud.ibm.com",
24+
REQUIRED = ("_ENDPOINT", "_IAM_ENDPOINT", "_IAM_APIKEY", "_SERVICE_CRN")
25+
ENDPOINTS = {
26+
"_ENDPOINT": "https://quantum.cloud.ibm.com/api/v1",
27+
"_IAM_ENDPOINT": "https://iam.cloud.ibm.com",
6028
}
6129

6230

6331
def section(title):
64-
print()
65-
print("== {} ==".format(title), flush=True)
32+
print("\n== {} ==".format(title))
6633

6734

6835
def print_environment():
69-
"""What fluence injected, and which credentials are set, by name only."""
7036
section("environment from fluence")
7137
for name in sorted(os.environ):
72-
if name.startswith("FLUXION_") or name.startswith("FLUENCE_"):
73-
print("{:<40} {}".format(name, os.environ[name]))
74-
section("QRMI environment")
75-
shown = False
76-
for name in sorted(os.environ):
77-
if "QRMI" in name or name in ("IBM_CLOUD_TOKEN", "IBM_CLOUD_CRN"):
78-
print("{:<40} (set, value not shown)".format(name))
79-
shown = True
80-
if not shown:
81-
print("(none)")
38+
if name.startswith(("FLUXION_", "FLUENCE_")):
39+
print("{:<32} {}".format(name, os.environ[name]))
40+
section("credentials, names only")
41+
names = [n for n in sorted(os.environ) if "QRMI" in n or n.startswith("IBM_CLOUD_")]
42+
print("\n".join(names) or "(none)")
8243

8344

8445
def backend_from_fluence():
85-
"""The backend fluence chose and its QRMI type, from the injected env."""
8646
backend = os.environ.get("FLUXION_BACKEND")
8747
if not backend:
88-
sys.exit(
89-
"no FLUXION_BACKEND: this pod was not placed by fluence, or the "
90-
"webhook did not inject it (does the pod request a qpu?)"
91-
)
48+
sys.exit("no FLUXION_BACKEND, this pod was not placed by fluence")
9249
rtype = os.environ.get("FLUXION_QRMI_TYPE", "qiskit-runtime-service")
9350
if rtype not in RESOURCE_TYPES:
94-
sys.exit("unknown QRMI type {!r} for backend {}".format(rtype, backend))
51+
sys.exit("unsupported QRMI type {} for {}".format(rtype, backend))
9552
section("backend")
9653
print("backend: ", backend)
9754
print("type: ", rtype)
9855
return backend, rtype
9956

10057

101-
def credentials(backend, rtype):
102-
"""Derive the QRMI variables from the IBM Secret and return what is still missing."""
103-
if rtype == "qiskit-runtime-service":
104-
token = os.environ.get("IBM_CLOUD_TOKEN")
105-
crn = os.environ.get("IBM_CLOUD_CRN")
106-
derived = dict(IBM_QRS_DERIVED)
107-
if token:
108-
derived["_QRMI_IBM_QRS_IAM_APIKEY"] = token
109-
if crn:
110-
derived["_QRMI_IBM_QRS_SERVICE_CRN"] = crn
111-
for suffix, value in derived.items():
112-
os.environ.setdefault(backend + suffix, value)
113-
missing = [
114-
backend + s
115-
for s in REQUIRED_ENV.get(rtype, ())
116-
if not os.environ.get(backend + s)
117-
]
118-
if missing:
119-
print("missing: ", ", ".join(missing))
120-
else:
121-
print("credentials present for", backend)
122-
return missing
123-
124-
125-
def acquire(backend, rtype, ready_timeout):
126-
"""Acquire a QRMI session on the backend and wait until it accepts work."""
127-
qr = QuantumResource(backend, getattr(ResourceType, RESOURCE_TYPES[rtype]))
128-
lock = qr.acquire()
129-
print("session: ", lock)
130-
# so anything else in this pod that constructs the resource joins this session
131-
os.environ[backend + "_QRMI_JOB_ACQUISITION_TOKEN"] = str(lock)
132-
deadline = time.time() + ready_timeout
133-
while ready_timeout > 0:
134-
try:
135-
if qr.is_accessible():
136-
print("backend: ", backend, "is accepting work")
137-
break
138-
why = "not accepting work"
139-
except Exception as e: # keep polling until the deadline
140-
why = str(e)
58+
def missing_credentials(backend, rtype):
59+
"""Derive the QRMI variables from IBM_CLOUD_TOKEN and IBM_CLOUD_CRN, and
60+
return the names still unset."""
61+
prefix = backend + RESOURCE_TYPES[rtype][1]
62+
derived = dict(ENDPOINTS)
63+
if os.environ.get("IBM_CLOUD_TOKEN"):
64+
derived["_IAM_APIKEY"] = os.environ["IBM_CLOUD_TOKEN"]
65+
if os.environ.get("IBM_CLOUD_CRN"):
66+
derived["_SERVICE_CRN"] = os.environ["IBM_CLOUD_CRN"]
67+
for suffix, value in derived.items():
68+
os.environ.setdefault(prefix + suffix, value)
69+
return [prefix + s for s in REQUIRED if not os.environ.get(prefix + s)]
70+
71+
72+
def resource(backend, rtype):
73+
return QuantumResource(backend, getattr(ResourceType, RESOURCE_TYPES[rtype][0]))
74+
75+
76+
def wait_operational(qr, backend, timeout):
77+
"""The backend object, once it reports itself operational."""
78+
deadline = time.time() + timeout
79+
while True:
80+
dev = get_backend(qr)
81+
status = dev.status()
82+
if status.operational:
83+
print("backend: ", backend, status.status_msg)
84+
return dev
14185
if time.time() >= deadline:
142-
qr.release(lock)
14386
sys.exit(
144-
"{} did not become usable within {}s: {}".format(
145-
backend, ready_timeout, why
87+
"{} not operational after {}s: {}".format(
88+
backend, timeout, status.status_msg
14689
)
14790
)
14891
time.sleep(5)
149-
return qr, lock
15092

15193

152-
def build_circuit(qubits):
94+
def bell_circuit(qubits):
15395
qc = QuantumCircuit(qubits)
15496
qc.h(0)
15597
for q in range(1, qubits):
@@ -158,129 +100,90 @@ def build_circuit(qubits):
158100
return qc
159101

160102

161-
def transpile_for_device(qc, qr):
162-
target = get_target(qr)
163-
isa = generate_preset_pass_manager(optimization_level=1, target=target).run(qc)
103+
def run(qr, dev, qubits, shots):
104+
isa = generate_preset_pass_manager(optimization_level=1, target=dev.target).run(
105+
bell_circuit(qubits)
106+
)
164107
print("circuit: ", dict(isa.count_ops()))
165-
return isa
166-
167-
168-
def run_sampler(qr, isa, shots):
169-
sampler = SamplerV2(qr, options={"default_shots": shots})
170-
job = sampler.run([isa])
171-
print("task: ", job.job_id(), "submitted into the session", flush=True)
172-
return job
173-
174-
175-
def wait_for_task(job, interval=2.0):
108+
job = SamplerV2(qr, options={"default_shots": shots}).run([isa])
109+
print("task: ", job.job_id())
176110
started = time.time()
177111
last = None
178112
while not job.in_final_state():
179-
status = job.status()
180-
if status != last:
181-
print(
182-
"status: ",
183-
status,
184-
"at {:.0f}s".format(time.time() - started),
185-
flush=True,
186-
)
187-
last = status
188-
time.sleep(interval)
113+
if job.status() != last:
114+
last = job.status()
115+
print("status: ", last, "at {:.0f}s".format(time.time() - started))
116+
time.sleep(2)
189117
print("status: ", job.status(), "after {:.0f}s".format(time.time() - started))
190118
if job.errored():
191-
print("logs:")
192119
print(job.logs())
193120
sys.exit("the task failed")
194-
195-
196-
def report_counts(job, qubits, shots):
197121
counts = job.result()[0].data.meas.get_counts()
198122
print("counts: ", dict(sorted(counts.items(), key=lambda kv: -kv[1])))
199123
ideal = {"0" * qubits, "1" * qubits}
200124
good = sum(v for k, v in counts.items() if k in ideal)
201125
print("fidelity:", "{}/{} shots in {}".format(good, shots, sorted(ideal)))
202126

203127

204-
def release(qr, lock):
205-
qr.release(lock)
206-
print("released:", lock)
207-
208-
209-
def linger(seconds, step=5):
210-
if seconds <= 0:
211-
return
212-
section("lingering for {}s so the pod can be watched".format(seconds))
213-
left = seconds
214-
while left > 0:
215-
print("working, {}s left".format(left), flush=True)
216-
time.sleep(min(step, left))
217-
left -= step
218-
219-
220128
def main():
129+
sys.stdout.reconfigure(line_buffering=True)
221130
ap = argparse.ArgumentParser(description=__doc__.split("\n")[0])
222131
ap.add_argument("--shots", type=int, default=100)
223-
ap.add_argument(
224-
"--qubits", type=int, default=2, help="width of the GHZ/Bell circuit, 2"
225-
)
226-
ap.add_argument(
227-
"--linger",
228-
type=int,
229-
default=int(os.environ.get("LINGER", "0")),
230-
help="seconds to stay alive after the work, 0",
231-
)
132+
ap.add_argument("--qubits", type=int, default=2, help="width of the circuit")
232133
ap.add_argument(
233134
"--ready-timeout",
234135
type=float,
235-
default=float(os.environ.get("READY_TIMEOUT", "120")),
236-
help="seconds to wait for the backend to accept work after acquiring, 120",
136+
default=120,
137+
help="seconds to wait for the backend",
237138
)
238139
args = ap.parse_args()
239140

240141
print_environment()
241142
backend, rtype = backend_from_fluence()
242-
243-
section("credentials, names only")
244-
if credentials(backend, rtype):
245-
print()
246-
print("rehearsal: no credentials, so nothing is acquired or submitted. The")
247-
print("placement and the handoff from fluence worked. A Secret with the")
248-
print("variables above would make this a real run.")
249-
linger(args.linger)
143+
missing = missing_credentials(backend, rtype)
144+
if missing:
145+
print("missing: ", ", ".join(missing))
146+
print(
147+
"\nrehearsal, nothing acquired or submitted. The placement and the handoff worked."
148+
)
250149
return
251150

252-
role = os.environ.get("FLUENCE_COORDINATION_ROLE")
253151
session = os.environ.get("FLUENCE_QUANTUM_SESSION_ID")
254-
if role == "worker" and session:
255-
# a worker joins the scout session rather than acquiring its own
256-
section("circuit in the scout session, through QRMI")
257-
print("session: ", session, "from the scout")
152+
if os.environ.get("FLUENCE_COORDINATION_ROLE") == "worker":
153+
# a worker submits into the scout's session instead of acquiring one
154+
if not session:
155+
print(
156+
"worker with no session, task",
157+
os.environ.get("FLUENCE_QUANTUM_JOB_ID", "?"),
158+
)
159+
return
160+
section("circuit in the scout's session")
161+
print("session: ", session)
258162
os.environ[backend + "_QRMI_JOB_ACQUISITION_TOKEN"] = session
259-
qr = QuantumResource(backend, getattr(ResourceType, RESOURCE_TYPES[rtype]))
260-
isa = transpile_for_device(build_circuit(args.qubits), qr)
261-
job = run_sampler(qr, isa, args.shots)
262-
wait_for_task(job)
263-
report_counts(job, args.qubits, args.shots)
264-
linger(args.linger)
265-
return
266-
if role == "worker":
267-
print(
268-
"role: worker with no session, task",
269-
os.environ.get("FLUENCE_QUANTUM_JOB_ID", "?"),
163+
qr = resource(backend, rtype)
164+
run(
165+
qr,
166+
wait_operational(qr, backend, args.ready_timeout),
167+
args.qubits,
168+
args.shots,
270169
)
271-
linger(args.linger)
272170
return
273171

274172
section("session and circuit, through QRMI")
275-
qr, lock = acquire(backend, rtype, args.ready_timeout)
173+
qr = resource(backend, rtype)
174+
lock = qr.acquire()
175+
print("session: ", lock)
176+
os.environ[backend + "_QRMI_JOB_ACQUISITION_TOKEN"] = str(lock)
276177
try:
277-
isa = transpile_for_device(build_circuit(args.qubits), qr)
278-
job = run_sampler(qr, isa, args.shots)
279-
wait_for_task(job)
280-
report_counts(job, args.qubits, args.shots)
178+
run(
179+
qr,
180+
wait_operational(qr, backend, args.ready_timeout),
181+
args.qubits,
182+
args.shots,
183+
)
281184
finally:
282-
release(qr, lock)
283-
linger(args.linger)
185+
qr.release(lock)
186+
print("released:", lock)
284187

285188

286189
if __name__ == "__main__":

0 commit comments

Comments
 (0)