Batch Jobs¶
Note
For batching on System Model H2, see here.
This feature allows users to submit related jobs as a single batch instead of enqueueing them individually. It is only when the first job in a batch is selected to run that the batch actually begins. Jobs within a batch execute sequentially, and once the batch reaches the front of the queue, it runs uninterrupted until all jobs are complete. This guarantees that jobs/job chunks submitted by other users are not interleaved with the batch workload.
Jobs can also be added dynamically to an active batch, enabling iterative workflows.
Batching Requirements:
Requires membership of an organization with a non-zero batch limit.
Only compatible with Nexus Execute Jobs (
nexus:qnexus.jobs.ExecuteJob()) on hardware systems (e.g. Helios-1). Batching is not supported on syntax checkers or emulators.Batching is supported across multiple Nexus execute jobs, or a single execute job with multiple programs (an individual Nexus job has a 300 program hard limit).
Multiple batches can be enqueued, but these will be treated individually by fair queuing.
Batch termination conditions:
1 minute of inactivity (i.e. the last job in a batch entered the ‘completed’ status >=60 seconds ago).
The total job cost in the batch exceeds the organization’s batch limit (typically 2000 HQC when batching is enabled).
If a job is submitted to a terminated batch, that job will be enqueued as a regular job.
Basics of Batching¶
Batching is enabled and controlled primarily through the backend config.
import qnexus as qnx
import uuid
my_batch_id = uuid.uuid4() # Generate a batch ID to group related jobs together
config = qnx.models.HeliosConfig(
system_name="Helios-1",
attempt_batching=True, # Enable batching for jobs submitted with this config
max_batch_cost=1000, # Use this to set a batch upper bound, or leave unset to rely on your organization's default batch limit
batch_id=my_batch_id, # All jobs submitted with this config will be grouped together in a single batch (unless the batch has terminated: see above)
)
Jobs submitted with the above config will initiate a batch with the provided batch_id UUID. A single job submission with one or many programs will be enqueued and once the first program has started to run, all subsequent programs/jobs in the batch will skip the queue (provided the total HQC cost is under your batch limit).
If you create and submit jobs under configs with additional batch_ids, you can have multiple batches enqueued at the same time. These will be run as isolated batches, and each batch will be initiated based on the standard fair queueing behaviour. It is only when the first job in a batch is selected to run that the batch will begin.
If the batch_id is omitted, one will be automatically generated. The batch_id of a job can be obtained at any point, regardless of whether it was generated or manually specified.
In the below example you can see an instance where a group of programs are submitted as a batch. You will also see how an existing batch_id may be obtained.
Example Batching Flow¶
The following will demonstrate a single batch job submitted with multiple programs. We will show how the automatically-generated batch_id can be obtained for subsequent job submissions.
from guppylang import guppy
from guppylang.std.quantum import cx, h, measure_array, qubit
from guppylang.std.builtins import array, result, comptime, barrier
n_qubits = 8
@guppy
def main() -> None:
"""
The `main` method synthesizes an 8-qubit log-depth GHZ circuit.
"""
qubit_array: array[qubit, comptime(n_qubits)] = array(qubit() for _ in range(comptime(n_qubits)))
h(qubit_array[0])
barrier(qubit_array)
qubits_used: int = 1
i: int = 0
while qubits_used < comptime(n_qubits):
i += 1
for k in range(qubits_used):
cx(qubit_array[k], qubit_array[qubits_used])
qubits_used += 1
barrier(qubit_array)
mresults = measure_array(qubit_array)
result("measurementResults", mresults[0])
hugr = main.compile()
The Nexus project is initialized and a name suffix is defined.
import datetime
project = qnx.projects.get_or_create("release/helios", "A project on Quantinuum Helios.")
qnx.context.set_active_project(project)
name_suffix = datetime.datetime.now().strftime("%Y-%m-%d_%H-%M_%S")
The HUGR binary is uploaded to Nexus with a unique name (datetime) using the qnx.hugr module. The HUGR upload requires a user friendly name, which does not need to be unique across the Nexus project. A HUGR reference is returned after uploading to the Nexus database. This reference is required for execute jobs.
from importlib.metadata import version
pkg_version = version('hugr') # replace 'numpy' with your package name
hugr_ref = qnx.hugr.upload(
hugr_package=hugr,
name=f"ghz-{name_suffix}",
description=f"Log-depth GHZ circuit using {n_qubits} qubits. HUGR version {pkg_version}.",
)
The hugr reference is used to submit an execute job with four instances of the Bell State, each circuit with a different number of shots.
n_shots = 100
ref_execute_job = qnx.start_execute_job(
programs=[hugr_ref for _ in range(4)],
n_shots=[i*n_shots for i in range(1,5)],
backend_config=config,
name=f"execute-job-{name_suffix}"
)
The above job will be submitted to run each program in a single batch, you can append to an existing batch (within limits) by obtaining the job’s batch_id (even if one has automatically been created for you).
Remember that you only have 60 seconds to append to an existing batch once the final program in the batch has been executed to completion.
assigned_batch_id = qnx.jobs.get(id=ref_execute_job.id).backend_config.batch_id
config_for_appending_to_batch = qnx.models.HeliosConfig(
system_name="Helios-1",
attempt_batching=True,
batch_id=assigned_batch_id, # Append this job to the same batch as ref_execute_job by using the same batch ID
)
# Submit another job for execution...
qnx.jobs.wait_for(ref_execute_job)
results = [
ref_result.download_result()
for ref_result in qnx.jobs.results(ref_execute_job)
]
The probability distribution of measurements is printed below.
for i, r in enumerate(results):
print(f"result {i} with {n_shots * (i+1)} shots\n{r.get_distribution()}\n")
Cancellation of a batched job is the same as the cancellation of a non-batch job.
qnx.jobs.cancel(ref_execute_job)