Batch Jobs¶
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. H2-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.QuantinuumConfig(
device_name="H2-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 an existing batch_id can be obtained for subsequent job submissions.
from pytket.circuit import Circuit
# The Bell state is prepared on 2-qubits with a final measurement on all qubits.
circuit = Circuit(2, name="Bell State")
circuit.H(0)
circuit.CX(0, 1)
circuit.measure_all()
The Nexus project is initialized and a name suffix is defined.
import datetime
project = qnx.projects.get_or_create("Batching")
qnx.context.set_active_project(project)
name_suffix = datetime.datetime.now().strftime("%Y-%m-%d_%H-%M_%S")
The Bell state circuit is uploaded to the Nexus database. The circuit reference is used to start a compile job.
ref_circuit = qnx.circuits.upload(
circuit=circuit,
name=f"pre-compiled-circuit-{name_suffix}"
)
ref_compile_job = qnx.start_compile_job(
circuits=[ref_circuit],
optimisation_level=2,
backend_config=config,
name=f"compile-job-{name_suffix}"
)
qnx.jobs.wait_for(ref_compile_job)
ref_compiled_circuit = qnx.jobs.results(ref_compile_job)[0].get_output()
The compiled circuit reference is used to submit an execute job with four instances of the Bell State, each circuit with a different number of shots. The execute job uses batching since it is specified QuantinuumConfig instance passed to the function.
n_shots = 100
ref_execute_job = qnx.start_execute_job(
programs=[ref_compiled_circuit 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.QuantinuumConfig(
device_name="H2-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)