Skip to content

Commit d151dc5

Browse files
Add batched Qiskit parallel execution
1 parent 0e3db60 commit d151dc5

1 file changed

Lines changed: 116 additions & 12 deletions

File tree

qedclib/qiskit/execute_parallel.py

Lines changed: 116 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,74 @@ def _localize_counts(counts, num_qubits):
305305

306306
return local
307307

308+
def _run_parallel_batch_with_retry(batch, num_shots):
309+
"""
310+
Try to execute a batch in parallel. If the partitioner rejects the full
311+
batch, recursively split it into smaller batches.
312+
313+
This prevents one bad batch from forcing the entire benchmark to fall back
314+
to sequential execution.
315+
"""
316+
if len(batch) == 1:
317+
return _run_qiskit_parallel_experiment(batch, num_shots)
318+
319+
try:
320+
return _run_qiskit_parallel_experiment(batch, num_shots)
321+
322+
except Exception as err:
323+
print(f"... batch of {len(batch)} circuits failed: {err}")
324+
print("... splitting batch into smaller parallel batches")
325+
326+
mid = len(batch) // 2
327+
328+
left_counts = _run_parallel_batch_with_retry(batch[:mid], num_shots)
329+
right_counts = _run_parallel_batch_with_retry(batch[mid:], num_shots)
330+
331+
return left_counts + right_counts
332+
333+
def _run_parallel_batch_with_retry(batch, num_shots):
334+
import execute as ex
335+
336+
if len(batch) == 1:
337+
print(
338+
"... single-circuit batch:",
339+
[c.num_qubits for c in batch],
340+
"running sequentially",
341+
)
342+
343+
ex.parallel_execution = False
344+
try:
345+
_, result = ex.execute_circuits(batch, num_shots)
346+
finally:
347+
ex.parallel_execution = True
348+
349+
counts = result.get_counts()
350+
351+
if isinstance(counts, dict):
352+
return [counts]
353+
354+
return counts
355+
356+
try:
357+
print(
358+
"... running parallel batch:",
359+
[c.num_qubits for c in batch],
360+
"total_qubits =",
361+
sum(c.num_qubits for c in batch),
362+
)
363+
364+
return _run_qiskit_parallel_experiment(batch, num_shots)
365+
366+
except Exception as err:
367+
print(f"... batch failed: {err}")
368+
print("... splitting batch")
369+
370+
mid = len(batch) // 2
371+
left_counts = _run_parallel_batch_with_retry(batch[:mid], num_shots)
372+
right_counts = _run_parallel_batch_with_retry(batch[mid:], num_shots)
373+
374+
return left_counts + right_counts
375+
308376
def execute_circuits_parallel(circuits, num_shots):
309377
"""
310378
Execute a list of QED-C circuits using the integrated Qiskit
@@ -351,18 +419,54 @@ def execute_circuits_parallel(circuits, num_shots):
351419
print(f">>> execute_circuits_parallel [qiskit]: {len(circuits)} circuits, {num_shots} shots")
352420

353421
try:
354-
# Run the circuits through the custom multiprogramming +
355-
# Qiskit ParallelExperiment pipeline.
356-
print("Uses the integrated Qiskit ParallelExperiment workflow. If the parallel path fails, execution automatically falls back to the standard QED-C execution path.")
357-
counts_list = _run_qiskit_parallel_experiment(circuits, num_shots)
358-
359-
# ParallelExperiment may return each child result with extra classical
360-
# bits from the full combined circuit. Convert each result back to the
361-
# local bitstring width expected by QED-C.
362-
counts_list = [
363-
_localize_counts(counts_list[i], circuits[i].num_qubits)
364-
for i in range(len(counts_list))
365-
]
422+
# Maximum total logical qubits allowed in one ParallelExperiment batch.
423+
# This prevents trying to place too many circuits on the hardware at once.
424+
MAX_PARALLEL_QUBITS = 15
425+
426+
all_counts = []
427+
428+
# Current batch of circuits to run together.
429+
batch = []
430+
batch_qubits = 0
431+
432+
for circuit in circuits:
433+
circuit_qubits = circuit.num_qubits
434+
435+
# If adding this circuit would exceed the batch capacity, run the current
436+
# batch first, then start a new batch.
437+
if batch and batch_qubits + circuit_qubits > MAX_PARALLEL_QUBITS:
438+
batch_counts = _run_parallel_batch_with_retry(batch, num_shots)
439+
440+
# Convert each batch result back to QED-C's expected local bit width.
441+
batch_counts = [
442+
_localize_counts(batch_counts[i], batch[i].num_qubits)
443+
for i in range(len(batch_counts))
444+
]
445+
446+
# Preserve original circuit order by appending this batch's results.
447+
all_counts.extend(batch_counts)
448+
449+
# Start a fresh batch.
450+
batch = []
451+
batch_qubits = 0
452+
453+
# Add the current circuit to the active batch.
454+
batch.append(circuit)
455+
batch_qubits += circuit_qubits
456+
457+
# Run the final partially filled batch.
458+
if batch:
459+
batch_counts = _run_parallel_batch_with_retry(batch, num_shots)
460+
461+
batch_counts = [
462+
_localize_counts(batch_counts[i], batch[i].num_qubits)
463+
for i in range(len(batch_counts))
464+
]
465+
466+
all_counts.extend(batch_counts)
467+
468+
# This list now has one counts dictionary per original input circuit.
469+
counts_list = all_counts
366470

367471
# Convert the raw counts list into QED-C's standard result object so the
368472
# rest of the benchmark framework does not need to know that the circuits

0 commit comments

Comments
 (0)