diff --git a/docs/api/migration-guides/_toc.json b/docs/api/migration-guides/_toc.json
index 1ed9f031e89..62b570ce225 100644
--- a/docs/api/migration-guides/_toc.json
+++ b/docs/api/migration-guides/_toc.json
@@ -31,6 +31,10 @@
"title": "Migrate to local simulators",
"url": "/api/migration-guides/local-simulators"
},
+ {
+ "title": "Execution modes changes",
+ "url": "/api/migration-guides/sessions"
+ },
{
"title": "Migrate to Qiskit Runtime",
"children": [
@@ -60,6 +64,19 @@
}
]
},
+ {
+ "title": "Qiskit Runtime 0.20 changes",
+ "children": [
+ {
+ "title": "V2 primitives",
+ "url": "/api/migration-guides/v2-primitives"
+ },
+ {
+ "title": "qiskit_ibm_provider to qiskit_ibm_runtime",
+ "url": "/api/migration-guides/qiskit-runtime-from-provider"
+ }
+ ]
+ },
{
"title": "Qiskit 0.44 changes",
"children": [
diff --git a/docs/api/migration-guides/sessions.mdx b/docs/api/migration-guides/sessions.mdx
new file mode 100644
index 00000000000..f06ce9ee282
--- /dev/null
+++ b/docs/api/migration-guides/sessions.mdx
@@ -0,0 +1,97 @@
+---
+title: Execution mode changes
+description: Learn about the changes to execution modes (sessions, batch, and single jobs)
+
+---
+
+
+
+# Execution modes changes
+
+Utility-scale workloads can take many hours to complete, so it is important that both the classical and quantum resources are scheduled efficiently to streamline the execution. The improved execution modes provide more flexibility than ever in balancing the cost and time tradeoff to use resources optimally for your workloads.
+
+Workloads can be run as single jobs, sessions, or in a batch:
+
+- Use **session** mode for iterative workloads, or if you need dedicated access to the system
+- Use **batch** mode to submit multiple primitive jobs simultaneously to shorten processing time
+- Use **job** mode to submit a single primitive request for quick testing
+
+The following table summarizes the differences:
+
+| Mode | Usage | Benefit |
+|:------------:|:---------------------------------------:|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------:|
+| Job mode | Quantum computation only. | Easiest to use when running a small experiment. Might run sooner than batch mode. |
+| Batch mode | Quantum computation only. | The entire batch of jobs is scheduled together and there is no additional queuing time for each. Jobs in a batch are usually run close together. |
+| Session mode | Both classical and quantum computation. | Dedicated and exclusive access to the system during the session active window, and no other users’ or system jobs can run. This is particularly useful for workloads that don’t have all inputs ready at the outset. |
+
+## Best practices
+
+To ensure the most efficient use of the execution modes, the following practices are recommended:
+
+- Always close your session, either by using a context manager or by specifying `session.close()`.
+- There is a fixed overhead associated with running a job. In general, if each job uses less than one minute of QPU time, consider combining several into one larger job (this applies to all execution modes). "QPU time" refers to time spent by the QPU complex to process your job.
+
+ A job's QPU time is listed in the **Usage** column on the IBM Quantum Platform [Jobs](https://quantum.ibm.com/jobs) page, or you can query it by using `qiskit-ibm-runtime` with this command `job.metrics()["usage"]["quantum_seconds"]`.
+
+- If each of your jobs consumes more than one minute of QPU time, or if combining jobs is not practical, you can still run multiple jobs in parallel. Every job goes through both classical and quantum processing. While a QPU can process only one job at a time, classical processing can be done in parallel. You can take advantage of this by submitting multiple jobs in [batch](#divide) or [session](#two-vqe) execution mode.
+
+The above are general guidelines, and you should tune your workload to find the optimal ratio, especially when using sessions. For example, if you are using a session to get exclusive access to a backend, consider breaking up large jobs into smaller ones and running them in parallel. This might be more cost effective because it can reduce wall clock time.
+
+
+## Sessions
+
+Sessions are designed for iterative workloads to avoid queuing delays between each iteration. All sessions now run in *dedicated* mode, so that when running a session, you have exclusive access to the backend. Because of this, you are now charged for the total wall clock time that the system is reserved for your use. Additionally, sessions are now thread safe. That is, you can run multiple workloads within a session.
+
+
+Session execution mode is not supported in the Open Plan. Jobs will run in job mode instead.
+
+
+
+### Example: Run two VQE algorithms in a session by using threading
+
+```python
+from concurrent.futures import ThreadPoolExecutor
+from qiskit_ibm_runtime import Session, EstimatorV2 as Estimator
+
+def minimize_thread(estimator, method):
+ minimize(cost_func, x0, args=(ansatz, hamiltonian, estimator), method=method)
+
+with Session(backend=backend), ThreadPoolExecutor() as executor:
+ # Add tags to differentiate jobs from different workloads.
+ estimator1.options.environment.job_tags = "cobyla"
+ estimator1.options.environment.job_tags = "nelder-mead"
+
+ cobyla_result = executor.submit(minimize_thread, estimator1, "cobyla").result()
+ nelder_mead_result = executor.submit(minimize_thread, estimator2, "nelder-mead").result()
+```
+
+## Batch
+
+Submit multiple primitive jobs simultaneously. When batching, classical processing is done in parallel. No session jobs, or jobs from another batch, can start when batch jobs are being processed; however, individual jobs might run between batch jobs.
+
+
+### Example: Partition a 500-circuit job into five 100-circuit jobs and run them in batch
+```python
+from qiskit_ibm_runtime import Batch, SamplerV2 as Sampler
+
+max_circuits = 100
+jobs = []
+start_idx = 0
+
+with Batch(backend):
+ sampler = Sampler()
+ while start_idx < len(circuits):
+ end_idx = start_idx + max_circuits
+ jobs.append(sampler.run([(circuits[start_ids:end_idx],)]))
+ start_idx = end_idx
+```
+
+## Sessions versus batch usage
+
+Usage is a measurement of the amount of time the system is locked for your workload.
+
+* Session usage is the time from when the first job starts until the session goes inactive, is closed, or when its last job completes, whichever happens **last**.
+* Batch usage is the sum of quantum time of all jobs in the batch.
+* Single job usage is the quantum time the job uses in processing.
+
+![This image shows multiple sets of jobs. One set is being run in session mode and the other is being run in batch mode. For session mode, between each job is the interactive TTL (time to live). The active window starts when the first job starts and ends after the last job is completed. After the final job of the first set of jobs completes, the active window ends and the session is paused (but not closed). Another set of jobs then starts and jobs continue in a similar manner. The system is reserved for your use during the entire session. For batch mode, the classical computation part of each job happens simultaneously, then all jobs are sent to the system. The system is locked for your use from the time the first job reaches the system until the last job is done processing on the system. There is no gap between jobs where the system is idle.](/images/run/execution-modes/SessionVsBatch.svg 'Sessions compared to batch')
diff --git a/docs/run/_toc.json b/docs/run/_toc.json
index 07897016605..f331f133478 100644
--- a/docs/run/_toc.json
+++ b/docs/run/_toc.json
@@ -44,6 +44,9 @@
"title": "Execution modes",
"children": [
{
+ "title": "Introduction to execution modes",
+ "url": "/run/execution-modes"
+ },{
"title": "About sessions",
"url": "/run/sessions"
},
@@ -54,6 +57,10 @@
{
"title": "Run jobs in a batch",
"url": "/run/run-jobs-batch"
+ },
+ {
+ "title": "FAQs",
+ "url": "/run/execution-modes-faq"
}
]
},
diff --git a/docs/run/execution-modes-faq.mdx b/docs/run/execution-modes-faq.mdx
new file mode 100644
index 00000000000..f8d674d7ea1
--- /dev/null
+++ b/docs/run/execution-modes-faq.mdx
@@ -0,0 +1,177 @@
+---
+title: Execution modes FAQs
+description: Answers to commonly asked questions about Qiskit Runtime execution modes
+
+---
+# Qiskit Runtime execution modes FAQs
+
+
+
+ Does Qiskit Runtime local testing mode support different execution modes?
+
+
+Local testing mode supports the syntax for the different execution modes, but because there is no scheduling involved when testing locally, the modes are ignored.
+
+
+
+
+ How many jobs can run in parallel for a specific backend?
+
+
+The number of jobs running in parallel is based on the degree of parallelism configured for the backend, which is five for most backends today.
+
+
+
+## Sessions
+
+
+
+ What happens to my jobs if a session is closed?
+
+
+If you are using the `Session` class in `qiskit-ibm-runtime`:
+
+ - `Session.close()` means the session no longer accepts new jobs, but existing jobs run to completion.
+ - `Session.cancel()` cancels all pending session jobs.
+
+If you are using the REST API directly:
+
+ - `PATCH /sessions/{id}` with `accepting_jobs=False` means the session no longer accepts new jobs, but existing jobs run to completion.
+ - `DELETE /sessions/{id}/close` cancels all pending session jobs.
+
+
+
+
+ If I am using session mode and expect my experiment to take many hours, is there a way to ask for calibrations to happen?
+
+
+No. On-demand calibration is not available.
+
+
+
+
+ Is there an interactive timeout (ITTL) with session mode?
+
+
+Yes. This reduces unwanted cost if a user forgets to close their session.
+
+
+
+
+ How does session usage impact IBM Quantum Network members who are not billed by usage?
+
+
+IBM Quantum Network members gain reserved capacity on IBM Quantum™ systems. Usage is deducted from this capacity and hubs with lower capacity have longer queueing time.
+
+
+
+
+ Do I get the same parallelism in session mode that I get with batch mode?
+
+
+Yes. If you submit multiple jobs simultaneously in a session, these jobs will run in parallel.
+
+
+
+
+ Can sessions be interrupted by system jobs?
+
+
+No. Sessions run in dedicated mode, which means that the user has total access to the backend. Sessions are never interrupted by system jobs, such as calibrations or software upgrades.
+
+
+
+
+ Is compilation time counted as usage in session mode?
+
+
+Yes. In session mode, usage is the wall clock time the system is **committed to the session**. It starts when the first session job starts and ends when the session goes inactive, is closed, or when the last job completes, whichever happens **last**. Thus, usage continues to accumulate after a session ends if the system is still running a job. Additionally, time after a job completes while the system waits for another session job (the interactive time to live (ITTL)) counts as usage. This is why you should ensure the session is closed as soon as you are done submitting jobs to it.
+
+
+## Batch
+
+
+
+ How many jobs run in parallel in batch mode?
+
+
+The number of jobs running in parallel is based on the degree of parallelism configured for the backend, which is five for most backends. However, the number of concurrent jobs in an active batch could be lower because there could be other jobs already running when the batch becomes active.
+
+
+
+
+ How is running _N_ PUBs in job mode different from running _N_ single-PUB jobs in batch mode?
+
+
+The main difference is the time and cost tradeoff:
+
+Batch mode:
+
+- The total run time is less because the classical processing might run in parallel.
+- There is a slight overhead for running each job, so you end up paying a little more for batched jobs. This overhead correlates to the size of the job. For example, the total usage of two jobs, each containing 40 100x100 circuits, is six seconds more than a single job containing 80 circuits.
+- Because batch mode doesn't give you exclusive access to a backend, jobs inside a batch might run with other users' jobs or system calibration jobs.
+- If some jobs fail, you still get results from the completed jobs.
+- You can take action in the middle of a batch workload based on the results of completed jobs. For example, you can cancel the rest of the jobs if the initial results look incorrect.
+
+Job mode:
+
+- The total run time is likely to be higher because there is no parallelism.
+- You don't pay for the extra per-job overhead associated with batch workloads.
+- All of your circuits will run together.
+- If this single job fails, you don't get partial results.
+- Your job might hit the system limit if it contains too many circuits or if the circuits are too large.
+
+In general, if your each of your jobs consumes less than a minute of QPU time, consider combining them into a larger job (this applies to all execution modes).
+
+
+
+
+ How many jobs I can submit in a batch?
+
+
+There are no limits to how many jobs you can submit in a Batch. There are, however, limits on how much usage your jobs can consume based on your plan.
+
+
+
+
+ When would my batch mode jobs run in parallel with other users' jobs?
+
+
+The degree of parallelism configured for a backend is also called "execution lanes". If there are one or more execution lanes available, and your batch jobs are next in line to be run, the scheduler starts enough jobs to fill the lanes. Similarly, if your batch doesn't have enough jobs to fill the lanes, the scheduler starts other users' jobs.
+
+Example: The backend you choose has five execution lanes, and two of them are currently occupied by other users' jobs. Your batch of six jobs is next in line to be run.
+
+Because there are three available lanes, the scheduler starts three of your six batch jobs. It continues to start jobs in your batch as jobs finish and execution lanes become available. If a lane becomes available and there are no more jobs in your batch, the scheduler starts the next job in line.
+
+
+
+
+ Do all of my batch jobs need to wait in the queue?
+
+
+Because QPUs are limited and shared resources, all jobs need to wait in the queue. However, when the first job in your batch starts running, all the other jobs in that batch essentially jump to the front of the queue and are prioritized by the scheduler.
+
+
+
+
+ Does a batch end automatically when the last associated job ends?
+
+
+Yes. However, there is a slight overhead associated with this auto-detection, so you should always close your batch and session.
+
+
+
+
+ Can batches be interrupted by system jobs?
+
+
+Yes. Batch workloads might be interrupted by system jobs, such as calibrations or software upgrades.
+
+
+
+
+ Is compilation time counted as usage in batch mode?
+
+
+No. In batch mode, only time spent on the quantum hardware counts as usage.
+
\ No newline at end of file
diff --git a/docs/run/execution-modes.mdx b/docs/run/execution-modes.mdx
new file mode 100644
index 00000000000..e42439ab2ac
--- /dev/null
+++ b/docs/run/execution-modes.mdx
@@ -0,0 +1,50 @@
+---
+title: Introduction to execution modes
+description: An overview of the available execution modes in Qiskit Runtime; sessions, batch, and single jobs.
+
+---
+# Introduction to Qiskit Runtime execution modes
+
+There are several ways to run workloads, depending on your needs.
+
+**Job mode**: A single primitive request of the estimator or the sampler made without a context manager. Circuits and inputs are packaged as primitive unified blocs (PUBs) and submitted as an execution task on the quantum computer.
+
+[**Session mode**](sessions): A dedicated window for running a multi-job workload. This allows users to experiment with variational algorithms in a more predictable way and even run multiple experiments simultaneously taking advantage of parallelism in the stack. Use sessions for iterative workloads or experiments that require dedicated access. See [Run jobs in a session](run-jobs-in-session) for examples.
+
+[**Batch mode**](run-jobs-batch): A multi-job manager for efficiently running an experiment that is comprised of bundle of independent jobs. Use batch mode to submit multiple primitive jobs simultaneously.
+
+The differences are summarized in the following table:
+
+| Mode | Usage | Benefit |
+|:------------:|:---------------------------------------:|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------:|
+| Job mode | Quantum computation only. | Easiest to use when running a small experiment. Might run sooner than batch mode. |
+| Batch mode | Quantum computation only. | The entire batch of jobs is scheduled together and there is no additional queuing time for each. Jobs in a batch are usually run close together. |
+| Session mode | Both classical and quantum computation. | Dedicated and exclusive access to the system during the session active window, and no other users’ or system jobs can run. This is particularly useful for workloads that don’t have all inputs ready at the outset. |
+
+
+## Best practices
+
+To ensure the most efficient use of the execution modes, the following practices are recommended:
+
+- Always close your session, either by using a context manager or by specifying `session.close()`.
+- There is a fixed overhead associated with running a job. In general, if your each of your job uses less than one minute of QPU time, consider combining several into one larger job (this applies to all execution modes). "QPU time" refers to time spent by the QPU complex to process your job.
+
+ A job's QPU time is listed in the **Usage** column on the IBM Quantum Platform [Jobs](https://quantum.ibm.com/jobs) page, or you can query it by using `qiskit-ibm-runtime` with this command `job.metrics()["usage"]["quantum_seconds"]`
+
+- If each of your jobs consumes more than one minute of QPU time, or if combining jobs is not practical, you can still run multiple jobs in parallel. Every job goes through both classical and quantum processing. While a QPU can process only one job at a time, up to five classical jobs can be processed in parallel. You can take advantage of this by submitting multiple jobs in [batch](run-jobs-batch#partition) or [session](run-jobs-in-session#two-vqe) execution mode.
+
+The above are general guidelines, and you should tune your workload to find the optimal ratio, especially when using sessions. For example, if you are using a session to get exclusive access to a backend, consider breaking up large jobs into smaller ones and running them in parallel. This might be more cost effective because it can reduce wall clock time.
+
+## Sessions versus batch usage
+
+Usage is a measurement of the amount of time the system is locked for your workload.
+
+* Session usage is the time from when the first job starts until the session goes inactive, is closed, or when its last job completes, whichever happens **last**.
+* Batch usage is the amount of time all jobs spend on the system.
+* Single job usage is the quantum time they use in processing.
+
+To find the usage time for your session or batch, in `qiskit-ibm-runtime` 0.23 or later, run `session.details()["usage_time"]` or `batch.details()["usage_time"]`.
+
+If you are calling the REST API directly, the usage time is the `elapsed_time` value returned by the `GET /sessions/{id}` endpoint, for both session and batch workloads.
+
+![This image shows multiple sets of jobs. One set is being run in session mode and the other is being run in batch mode. For session mode, between each job is the interactive TTL (time to live). The active window starts when the first job starts and ends after the last job is completed. After the final job of the first set of jobs completes, the active window ends and the session is paused (but not closed). Another set of jobs then starts and jobs continue in a similar manner. The system is reserved for your use during the entire session. For batch mode, the classical computation part of each job happens simultaneously, then all jobs are sent to the system. The system is locked for your use from the time the first job reaches the system until the last job is done processing on the system. There is no gap between jobs where the system is idle.](/images/run/execution-modes/SessionVsBatch.svg 'Sessions compared to batch')
diff --git a/docs/run/primitives-examples.mdx b/docs/run/primitives-examples.mdx
index 223b696a479..a7885bc664e 100644
--- a/docs/run/primitives-examples.mdx
+++ b/docs/run/primitives-examples.mdx
@@ -20,7 +20,7 @@ service = QiskitRuntimeService(channel="ibm_cloud", channel_strategy="q-ctrl")
- These examples all use the primitives from Qiskit Runtime, but you could use the base primitives instead.
+ These examples all use the primitives from Qiskit Runtime, but you could use the base primitives instead.
## Estimator examples
@@ -34,7 +34,7 @@ Use Estimator to determine the expectation value of a single circuit-observable
-```python
+```python
import numpy as np
from qiskit.circuit.library import IQP
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
@@ -46,25 +46,25 @@ backend = service.least_busy(operational=True, simulator=False, min_num_qubits=1
estimator = Estimator(backend)
n_qubits = 127
-
+
mat = np.real(random_hermitian(n_qubits, seed=1234))
circuit = IQP(mat)
observable = SparsePauliOp("Z" * n_qubits)
pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
isa_circuit = pm.run(circuit)
-isa_observable = observable.apply_layout(isa_circuit.layout)
+isa_observable = observable.apply_layout(isa_circuit.layout)
job = estimator.run([(isa_circuit, isa_observable)])
result = job.result()
-
+
print(f" > Expectation value: {result[0].data.evs}")
print(f" > Metadata: {result[0].metadata}")
```
-```python
+```python
import numpy as np
from qiskit.circuit.library import IQP
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
@@ -142,11 +142,11 @@ for idx in range(len(pubs)):
pub_result = job_result[idx]
print(f">>> Expectation values for PUB {idx}: {pub_result.data.evs}")
print(f">>> Standard errors for PUB {idx}: {pub_result.data.stds}")
-```
+```
-```python
+```python
import numpy as np
from qiskit.circuit.library import IQP
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
@@ -188,7 +188,7 @@ Use Estimator to run three experiments in a single job, leveraging parameter val
-```python
+```python
import numpy as np
from qiskit.circuit import QuantumCircuit, Parameter
@@ -243,7 +243,7 @@ print(f">>> Metadta: {pub_result.metadata}")
-```python
+```python
import numpy as np
from qiskit.circuit.library import RealAmplitudes
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
@@ -287,9 +287,9 @@ from qiskit.circuit.library import IQP
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit.quantum_info import SparsePauliOp, random_hermitian
from qiskit_ibm_runtime import QiskitRuntimeService, Session, EstimatorV2 as Estimator
-
+
n_qubits = 127
-
+
rng = np.random.default_rng(1234)
mat = np.real(random_hermitian(n_qubits, seed=rng))
circuit = IQP(mat)
@@ -297,17 +297,17 @@ mat = np.real(random_hermitian(n_qubits, seed=rng))
another_circuit = IQP(mat)
observable = SparsePauliOp("X" * n_qubits)
another_observable = SparsePauliOp("Y" * n_qubits)
-
+
pm = generate_preset_pass_manager(optimization_level=1, backend=backend)
isa_circuit = pm.run(circuit)
another_isa_circuit = pm.run(another_circuit)
isa_observable = observable.apply_layout(isa_circuit.layout)
another_isa_observable = another_observable.apply_layout(another_isa_circuit.layout)
-
+
service = QiskitRuntimeService()
-
+
backend = service.least_busy(operational=True, simulator=False, min_num_qubits=127)
-
+
with Session(service=service, backend=backend) as session:
estimator = Estimator(session=session)
@@ -326,11 +326,11 @@ with Session(service=service, backend=backend) as session:
# second job
print(f" > Another Expectation value: {another_result[0].data.evs}")
print(f" > More Metadata: {another_result[0].metadata}")
-```
+```
-```python
+```python
import numpy as np
from qiskit.circuit.library import IQP
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
@@ -384,33 +384,34 @@ Generate entire error-mitigated quasi-probability distributions sampled from qua
### Run a single experiment
Use Sampler to return the measurement outcome as a quasi-probability distribution (V1) or as **bitstrings** or
- **counts** (V2) of a single circuit.
+ **counts** (V2) of a single circuit.
```python
import numpy as np
+from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit.circuit.library import IQP
from qiskit.quantum_info import random_hermitian
from qiskit_ibm_runtime import QiskitRuntimeService, SamplerV2 as Sampler
-
+
service = QiskitRuntimeService()
-
+
backend = service.least_busy(operational=True, simulator=False, min_num_qubits=127)
-
+
n_qubits = 127
-
+
mat = np.real(random_hermitian(n_qubits, seed=1234))
circuit = IQP(mat)
circuit.measure_all()
-
+
pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
isa_circuit = pm.run(circuit)
-
+
sampler = Sampler(backend)
job = sampler.run([isa_circuit])
result = job.result()
-```
+```
@@ -472,10 +473,10 @@ for circuit in circuits:
pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
isa_circuits = pm.run(circuits)
-
-sampler = Sampler(backend)
+
+sampler = Sampler(backend)
job = sampler.run(isa_circuits)
-result = job.result()
+result = job.result()
for idx, pub_result in enumerate(result):
print(f" > Counts for pub {idx}: {pub_result.data.meas.get_counts()}")
@@ -488,7 +489,7 @@ import numpy as np
from qiskit.circuit.library import IQP
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit.quantum_info import random_hermitian
-from qiskit_ibm_runtime import QiskitRuntimeService, Sampler
+from qiskit_ibm_runtime import QiskitRuntimeService, Sampler
service = QiskitRuntimeService()
@@ -505,9 +506,9 @@ for circuit in circuits:
pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
isa_circuits = pm.run(circuits)
-sampler = Sampler(backend)
+sampler = Sampler(backend)
job = sampler.run(isa_circuits)
-result = job.result()
+result = job.result()
print(f" > Quasi-probability distribution: {result.quasi_dists}")
```
@@ -522,7 +523,7 @@ Run several experiments in a single job, leveraging parameter values to increase
-```python
+```python
import numpy as np
from qiskit.circuit.library import RealAmplitudes
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
@@ -556,7 +557,7 @@ job = sampler.run([(isa_circuit, parameter_values)])
result = job.result()
# Get results for the first (and only) PUB
pub_result = result[0]
-# Get counts from the classical register "meas".
+# Get counts from the classical register "meas".
print(f" >> Counts for the meas output register: {pub_result.data.meas.get_counts()}")
```
@@ -584,9 +585,9 @@ parameter_values = [
rng.uniform(-np.pi, np.pi, size=circuit.num_parameters) for _ in range(3)
]
-sampler = Sampler(backend)
-job = sampler.run([isa_circuit] * 3, parameter_values)
-result = job.result()
+sampler = Sampler(backend)
+job = sampler.run([isa_circuit] * 3, parameter_values)
+result = job.result()
print(f" > Quasi-probability distribution: {result.quasi_dists}")
```
@@ -608,7 +609,7 @@ from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit_ibm_runtime import QiskitRuntimeService, SamplerV2 as Sampler, Session
n_qubits = 127
-
+
rng = np.random.default_rng(1234)
mat = np.real(random_hermitian(n_qubits, seed=rng))
circuit = IQP(mat)
@@ -618,26 +619,26 @@ another_circuit = IQP(mat)
another_circuit.measure_all()
service = QiskitRuntimeService()
-
+
backend = service.least_busy(operational=True, simulator=False, min_num_qubits=n_qubits)
pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
isa_circuit = pm.run(circuit)
another_isa_circuit = pm.run(another_circuit)
-
-with Session(service=service, backend=backend) as session:
- sampler = Sampler(session=session)
- job = sampler.run([isa_circuit])
- another_job = sampler.run([another_isa_circuit])
+
+with Session(service=service, backend=backend) as session:
+ sampler = Sampler(session=session)
+ job = sampler.run([isa_circuit])
+ another_job = sampler.run([another_isa_circuit])
result = job.result()
- another_result = another_job.result()
-
-# first job
+ another_result = another_job.result()
+
+# first job
print(f" > Counts for job 1: {result[0].data.meas.get_counts()}")
-
-# second job
+
+# second job
print(f" > Counts for job 2: {another_result[0].data.meas.get_counts()}")
-```
+```
@@ -667,21 +668,21 @@ pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
isa_circuit = pm.run(circuit)
another_isa_circuit = pm.run(another_circuit)
-options = Options()
-options.optimization_level = 2
-options.resilience_level = 0
+options = Options()
+options.optimization_level = 2
+options.resilience_level = 0
-with Session(service=service, backend=backend) as session:
- sampler = Sampler(session=session, options=options)
- job = sampler.run(isa_circuit)
- another_job = sampler.run(another_isa_circuit)
+with Session(service=service, backend=backend) as session:
+ sampler = Sampler(session=session, options=options)
+ job = sampler.run(isa_circuit)
+ another_job = sampler.run(another_isa_circuit)
result = job.result()
- another_result = another_job.result()
+ another_result = another_job.result()
-# first job
-print(f" > Quasi-probability distribution job 1: {result.quasi_dists}")
+# first job
+print(f" > Quasi-probability distribution job 1: {result.quasi_dists}")
-# second job
+# second job
print(f" > Quasi-probability distribution job 2: {another_result.quasi_dists}")
```
diff --git a/docs/run/primitives-get-started.mdx b/docs/run/primitives-get-started.mdx
index c1048b5d1ce..35d5befd797 100644
--- a/docs/run/primitives-get-started.mdx
+++ b/docs/run/primitives-get-started.mdx
@@ -57,6 +57,10 @@ mat = np.real(random_hermitian(n_qubits, seed=1234))
circuit = IQP(mat)
observable = SparsePauliOp("Z" * n_qubits)
print(f">>> Observable: {observable.paulis}")
+```
+Output
+```text
+>>> Observable: ['ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ...']
```
The circuit and observable need to be transformed to only use instructions supported by the system (referred to as *instruction set architecture (ISA)* circuits). We'll use the transpiler to do this.
@@ -96,7 +100,7 @@ estimator = Estimator(backend=backend)
Next, invoke the `run()` method to calculate expectation values for the input circuits and observables.
-For Estimator V2, the circuit, observable, and optional parameter value sets are input as *primitive unified bloc* (PUB) tuples.
+For Estimator V2, the circuit, observable, and optional parameter value sets are input as *primitive unified bloc* (PUB) tuples.
@@ -105,12 +109,23 @@ job = estimator.run([(isa_circuit, isa_observable)])
print(f">>> Job ID: {job.job_id()}")
print(f">>> Job Status: {job.status()}")
```
+Output
+```text
+>>> Job ID: 8874203f-f5a0-4491-b093-3f29a1adcae4
+>>> Job Status: JobStatus.RUNNING
+```
```python
result = job.result()
print(f">>> {result}")
print(f" > Expectation value: {result[0].data.evs}")
print(f" > Metadata: {result[0].metadata}")
+```
+Output
+```text
+>>> PrimitiveResult([PubResult(data=DataBin<>(evs=0.013671875, stds=0.0156235396179561), metadata={'target_precision': 0.015625})], metadata={})
+ > Expectation value: 0.013671875
+ > Metadata: {'target_precision': 0.015625}
```
@@ -148,7 +163,7 @@ backend = service.least_busy(operational=True, simulator=False, min_num_qubits=1
### 2. Create a circuit
-You need at least one circuit as the input to the `Sampler` primitive.
+You need at least one circuit as the input to the `Sampler` primitive.
```python
import numpy as np
@@ -174,7 +189,7 @@ isa_circuit = pm.run(circuit)
When you initialize the `Sampler`, pass in the simulator or system you previously selected as the target.
-To use the Sampler V2, import the **SamplerV2** class. The **Sampler** class still refers to the version 1 Sampler, for backwards compatibility.
+To use the Sampler V2, import the **SamplerV2** class. The **Sampler** class still refers to the version 1 Sampler, for backward compatibility.
@@ -209,8 +224,14 @@ job = sampler.run([isa_circuit])
print(f">>> Job ID: {job.job_id()}")
print(f">>> Job Status: {job.status()}")
```
+Output
-You need the classical register name to get the results. By default, it is named `meas` when you use `measure_all()`. When defining your circuit, if you create one or more classical registers with a non-default name, use that name to get the results. You can find the classical register name by running `.cregs`. For example, `qc.cregs`.
+```text
+>>> Job ID: 58223448-5100-4dec-a47a-942fb30edced
+>>> Job Status: JobStatus.RUNNING
+```
+
+ You need the classical register name to get the results. By default, it is named `meas` when you use `measure_all()`. When defining your circuit, if you create one or more classical registers with a non-default name, use that name to get the results. You can find the classical register name by running `.cregs`. For example, `qc.cregs`.
```python
result = job.result()
@@ -218,6 +239,10 @@ result = job.result()
# Get results for the first (and only) PUB
pub_result = result[0]
print(f"Counts for the meas output register: {pub_result.data.meas.get_counts()}")
+```
+Output
+```text
+Counts for the meas output register: {'0111': 50, '0000': 243, '0001': 101, '0101': 93, '0100': 188, '0011': 128, '1011': 22, '0110': 13, '1100': 10, '1000': 24, '0010': 29, '1010': 26, '1101': 20, '1110': 45, '1001': 16, '1111': 16}
```
diff --git a/docs/run/run-jobs-batch.mdx b/docs/run/run-jobs-batch.mdx
index cc4d9b38e92..2041e1136b2 100644
--- a/docs/run/run-jobs-batch.mdx
+++ b/docs/run/run-jobs-batch.mdx
@@ -6,10 +6,16 @@ description: How to run quantum computing jobs in batch mode using Qiskit Runtim
# Run jobs in a batch
+
+
+The way batches are started within Qiskit Runtime has changed. **By 1 April 2024**, upgrade to `qiskit-ibm-runtime` version 0.20.0 or later, or `qiskit-ibm-provider` version 0.10.0 or later. In addition, ensure you are using Qiskit version 0.45.0 or later. Starting 1 April, batch calls made in earlier versions of these packages will fail.
+
+
Batch mode can shorten processing time if all jobs can be provided at the outset. If you want to submit iterative jobs, use [sessions](sessions) instead. Using batch mode has these benefits:
-- The jobs' classical computation, such as compilation, is run in parallel. Thus, running multiple jobs in a batch is significantly faster than running them serially.
-- There is minimal delay between jobs, which can help avoid drift.
+- The jobs' classical computation, such as compilation, is run in parallel. Thus, running multiple jobs in a batch is significantly faster than running them serially.
+- There is usually minimal delay between jobs, which can help avoid drift.
+- If you partition your workload into multiple jobs and run them in batch mode, you can get results from individual jobs, which makes them more flexible to work with. For example, if a job's results don't meet your expectations, you can cancel the remaining jobs. Also, if one job fails, you can re-submit it instead of re-running the entire workload.
When batching, jobs are not guaranteed to run in the order they are submitted. Also, while your batch jobs will run as closely together as possible, they don't get exclusive access to the backend. Therefore, your batch jobs might run in parallel with other users' jobs if there is enough processing capacity on the system. Additionally, system calibration jobs could run between the batched jobs.
@@ -17,21 +23,26 @@ Batch mode can shorten processing time if all jobs can be provided at the outset
![This diagram illustrates jobs submitted in a batch. It shows five jobs, numbered 0 through 4, in a queue. The jobs are a mix of Estimator and Sampler.](/images/run/batch.png 'Figure 1: Batch execution')
-The following example shows how you can divide up a long list of circuits into multiple jobs and run them as a batch to take advantage of the parallel processing.
+There are multiple ways you can reconfigure your jobs to take advantage of the parallel processing provided by batching.
+
+
+## Example: Partition a list of circuits into multiple jobs and run them in batch
+
+The following example shows how you can partition a long list of circuits into multiple jobs and run them as a batch to take advantage of the parallel processing.
-```python
-from qiskit_ibm_runtime import SamplerV2 as Sampler, Batch
+ ```python
+ from qiskit_ibm_runtime import SamplerV2 as Sampler, Batch
-max_circuits = 100
-all_partitioned_circuits = []
-for i in range(0, len(circuits), max_circuits):
- all_partitioned_circuits.append(circuits[i : i + max_circuits])
-jobs = []
-start_idx = 0
+ max_circuits = 100
+ all_partitioned_circuits = []
+ for i in range(0, len(circuits), max_circuits):
+ all_partitioned_circuits.append(circuits[i : i + max_circuits])
+ jobs = []
+ start_idx = 0
-with Batch(backend=backend):
- sampler = Sampler()
- for partitioned_circuits in all_partitioned_circuits:
- job = sampler.run(partitioned_circuits)
- jobs.append(job)
-```
+ with Batch(backend=backend):
+ sampler = Sampler()
+ for partitioned_circuits in all_partitioned_circuits:
+ job = sampler.run(partitioned_circuits)
+ jobs.append(job)
+ ```
diff --git a/docs/run/run-jobs-in-session.mdx b/docs/run/run-jobs-in-session.mdx
index c84f5e65c24..553a124cfbd 100644
--- a/docs/run/run-jobs-in-session.mdx
+++ b/docs/run/run-jobs-in-session.mdx
@@ -8,6 +8,10 @@ description: How to run a quantum computing job in a Qiskit Runtime session.
There are several ways to set up and use [sessions](sessions). It is recommended that you do not run a session with a single job in it.
+
+Session execution mode is not supported in the Open Plan. Jobs will run in job mode instead.
+
+
The way sessions are started within Qiskit Runtime has changed. **By 1 April 2024**, upgrade to `qiskit-ibm-runtime` version 0.20.0 or later, or `qiskit-ibm-provider` version 0.10.0 or later. In addition, ensure you are using Qiskit version 0.45.0 or later. Starting 1 April, session calls made in earlier versions of these packages will fail.
@@ -25,7 +29,7 @@ service = QiskitRuntimeService()
## Open a session
You can open a runtime session by using the context manager `with Session(...)` or by initializing the `Session`
-class. The session starts when its first job begins execution.
+class. The session starts when its first job begins execution.
If the first session job is canceled, subsequent session jobs will all fail.
@@ -65,7 +69,7 @@ The context manager automatically opens and closes the session.
``` python
-backend = service.least_busy(operational=True, simulator=False)
+backend = service.least_busy(operational=True, simulator=False)
with Session(service=service, backend=backend):
estimatorv2 = EstimatorV2()
samplerv2 = SamplerV2()
@@ -74,7 +78,7 @@ with Session(service=service, backend=backend):
``` python
-backend = service.least_busy(operational=True, simulator=False)
+backend = service.least_busy(operational=True, simulator=False)
with Session(service=service, backend=backend):
estimator = Estimator()
sampler = Sampler()
@@ -82,7 +86,7 @@ with Session(service=service, backend=backend):
-When you start a session, you must specify a system. This can be done by specifying its name or by passing a `backend` object.
+When you start a session, you must specify a system. This can be done by specifying its name or by passing a `backend` object.
**Specify a system by name**
@@ -92,7 +96,7 @@ with Session(service=service, backend="ibm_sherbrooke"):
...
```
-**Pass a `backend` object**
+**Pass a `backend` object**
``` python
backend = service.least_busy(operational=True, simulator=False)
@@ -103,32 +107,32 @@ with Session(backend=backend):
## Session length
-You can define the maximum session timeout with the `max_time` parameter. This should exceed the longest job's execution time and be within the system's limits.
+You can define the maximum session timeout with the `max_time` parameter. This should exceed the longest job's execution time and be within the system's limits.
``` python
with Session(service=service, backend=backend, max_time="25m"):
- ...
+ ...
```
There is also an interactive timeout value (ITTL, or interactive time to live) that cannot be configured. If no session jobs are queued within that window, the session is temporarily deactivated. To determine a session's ITTL, follow the instructions in [Determine session details](#session-details) and look for the `interactive_timeout` value.
-## Close a session
+## Close a session
-A session automatically closes when it exits the context manager. With qiskit-ibm-runtime 0.13 or later releases, when the session context manager is exited, the session is put into "In progress, not accepting new jobs" status. This means that the session finishes processing all running or queued jobs until the maximum timeout value is reached. After all jobs are completed, the session is immediately closed. This allows the scheduler to run the next job without waiting for the session interactive timeout, thereby reducing the average job queuing time. You cannot submit jobs to a closed session.
+A session automatically closes when it exits the context manager. When the session context manager is exited, the session is put into "In progress, not accepting new jobs" status. This means that the session finishes processing all running or queued jobs until the maximum timeout value is reached. After all jobs are completed, the session is immediately closed. This allows the scheduler to run the next job without waiting for the session interactive timeout, thereby reducing the average job queuing time. You cannot submit jobs to a closed session.
``` python
with Session(service=service, backend=backend) as session:
estimator = Estimator()
job1 = estimator.run(...)
job2 = estimator.run(...)
-
-# The session is no longer accepting jobs but the submitted job will run to completion.
+
+# The session is no longer accepting jobs but the submitted job will run to completion.
result = job1.result()
result2 = job2.result()
```
-If you are not using a context manager, it's good practice to manually close the session once all the necessary results have been retrieved. With qiskit-ibm-runtime 0.13 or later releases, when a session is closed with `session.close()`, it no longer accepts new jobs, but the already submitted jobs will still run until completion and their results can be retrieved. Prior to qiskit-ibm-runtime 0.13, when a session is closed with `session.close()`, any jobs that are already running continue to run, but any queued jobs remaining in the session are put into a failed state.
+If you are not using a context manager, manually close the session to avoid unwanted cost. You can close a session as soon as you are done submitting jobs to it. When a session is closed with `session.close()`, it no longer accepts new jobs, but the already submitted jobs will still run until completion and their results can be retrieved.
``` python
session = Session(backend=backend)
@@ -142,29 +146,25 @@ print(f"Result2: {job2.result()}")
session.close()
```
-
-Note that when you cancel the root job in the session (the job which has the same ID as the session), the session closes and fails any remaining queued jobs in the session.
-
-
-## Cancel a session
+## Cancel a session
-Canceling a session immediately closes it, failing all queued jobs and preventing new submission. Use the `session.cancel()` method to cancel a session. Any jobs that are already running continue to run but queued jobs are put into a failed state and no further jobs can be submitted to the session. This is a convenient way to quickly fail all queued jobs within a session.
+Canceling a session immediately closes it, failing all queued jobs and preventing new submission. Use the `session.cancel()` method to cancel a session. Any jobs that are already running continue to run but queued jobs are put into a failed state and no further jobs can be submitted to the session. This is a convenient way to quickly fail all queued jobs within a session.
``` python
with Session(service=service, backend=backend) as session:
estimator = Estimator()
job1 = estimator.run(...)
job2 = estimator.run(...)
- # You can use session.cancel() to fail all pending jobs, for example,
+ # You can use session.cancel() to fail all pending jobs, for example,
# if you realize you made a mistake.
session.cancel()
```
-
+
## Invoke multiple primitives in a session
-A session can handle multiple primitives, allowing for various operations within a single session. The following example shows how you can create both an instance of the `Sampler` class and one of the `Estimator` class and invoke their `run()` methods within a session.
+A session can handle multiple primitives, allowing for various operations within a single session. The following example shows how you can create both an instance of the `Sampler` class and one of the `Estimator` class, then invoke their `run()` methods within a session.
@@ -203,6 +203,26 @@ with Session(backend=backend):
+
+## Run two VQE algorithms in a session by using threading
+
+You can get more out of a session by running multiple workloads simultaneously. The following example shows how you can run two VQE algorithms, each using a different classical optimizer, simultaneously inside a single session. Job tags are also used to differentiate jobs from each workload.
+```python
+from concurrent.futures import ThreadPoolExecutor
+from qiskit_ibm_runtime import Session, EstimatorV2 as Estimator
+
+def minimize_thread(estimator, method):
+ minimize(cost_func, x0, args=(ansatz, hamiltonian, estimator), method=method)
+
+with Session(backend=backend), ThreadPoolExecutor() as executor:
+ # Add tags to differentiate jobs from different workloads.
+ estimator1.options.environment.job_tags = "cobyla"
+ estimator1.options.environment.job_tags = "nelder-mead"
+
+ cobyla_result = executor.submit(minimize_thread, estimator1, "cobyla").result()
+ nelder_mead_result = executor.submit(minimize_thread, estimator2, "nelder-mead").result()
+```
+
## Check session status
@@ -210,15 +230,15 @@ You can query a session's status to understand its current state by using `sess
Session status can be one of the following:
-- `Pending`: The session has not started or has been deactivated. The next session job needs to wait in the queue like other jobs.
+- `Pending`: The session has not started or has been deactivated. The next session job needs to wait in the queue like other jobs.
- `In progress, accepting new jobs`: The session is active and accepting new jobs.
-- `In progress, not accepting new jobs`: The session is active but not accepting new jobs. Job submission to the session is rejected, but outstanding session jobs will run to completion. The session is automatically closed once all jobs finish.
+- `In progress, not accepting new jobs`: The session is active but not accepting new jobs. Job submission to the session is rejected, but outstanding session jobs will run to completion. The session is automatically closed once all jobs finish.
- `Closed`: The session's maximum timeout value has been reached or the session was explicitly closed.
## Determine session details
-For a comprehensive overview of a session's configuration and status, use the `session.details() method`.
+For a comprehensive overview of a session's configuration and status, use the `session.details() method`.
@@ -253,6 +273,6 @@ with Session(service=service, backend=backend) as session:
## Next steps
- - Try an example in the [Quantum approximate optimization algorithm (QAOA)](https://learning.quantum.ibm.com/tutorial/quantum-approximate-optimization-algorithm) tutorial.
- - Review the [Session API](https://docs.quantum.ibm.com/api/qiskit-ibm-runtime/qiskit_ibm_runtime.Session) reference.
+ - Try an example in the [Quantum approximate optimization algorithm (QAOA)](https://learning.quantum.ibm.com/tutorial/quantum-approximate-optimization-algorithm) tutorial.
+ - Review the [Session API](/api/qiskit-ibm-runtime/qiskit_ibm_runtime.Session) reference.
\ No newline at end of file
diff --git a/docs/run/sessions.mdx b/docs/run/sessions.mdx
index a8c7107ab31..bb6cc3b3f76 100644
--- a/docs/run/sessions.mdx
+++ b/docs/run/sessions.mdx
@@ -28,13 +28,15 @@ There are several benefits to using sessions:
The basic workflow for sessions is as follows:
-1. The first job in a session enters the normal queue.
-2. When the first job starts running, the _maximum timeout_ clock starts.
-2. Subsequent jobs within the session are prioritized over others, reducing wait times.
-3. The _interactive timeout_ runs between the jobs in a session. Every session has an interactive timeout value (ITTL, or interactive time to live). If there are no session jobs queued within the ITTL window, the session is temporarily deactivated and normal job selection resumes. A deactivated session can be resumed for the next job* if the session has not reached its maximum timeout value.
-3. If the maximum timeout value is reached, the sessions end and any remaining queued jobs fail.
+1. The first job in a session enters the normal queue.
+2. When the first job starts running, the _maximum timeout_ clock starts.
+2. Subsequent jobs within the session are prioritized over other jobs, reducing wait times.
+3. The _interactive timeout_ runs between the jobs in a session. Every session has an interactive timeout value (ITTL, or interactive time to live). If there are no session jobs queued within the ITTL window, the session is temporarily deactivated and normal job selection resumes. A job can reactivate the deactivated session* if the session has not reached its maximum timeout value.
+3. If the maximum timeout value is reached, the session ends and any remaining queued jobs fail. However, any jobs already running will run to completion.
-