

# Working with Amazon Braket Hybrid Jobs
Amazon Braket Hybrid Jobs

Amazon Braket Hybrid Jobs offers a way for you to run hybrid quantum-classical algorithms requiring both classical AWS resources and quantum processing units (QPUs). Hybrid Jobs is designed to spin up the requested classical resources, run your algorithm, and release the instances after completion so you only pay for *what you use*. 

Hybrid Jobs is ideal for long-running, iterative algorithms that involve the use of both classical computing resources and quantum computing resources. With Hybrid Jobs, after submitting your algorithm to run, Braket will run your algorithm in a scalable, containerized environment. Once the algorithm has completed, you can then retrieve the results.

Additionally, quantum tasks that are created from a hybrid job benefit from higher priority queueing to the target QPU device. This prioritization ensures that your quantum computations are processed and ran ahead of other tasks waiting in the queue. This is particularly advantageous for iterative hybrid algorithms, where the results of one quantum task depend on the outcomes of prior quantum tasks. Examples of such algorithms include the [ Quantum Approximate Optimization Algorithm (QAOA)](https://github.com/amazon-braket/amazon-braket-examples/blob/main/examples/hybrid_quantum_algorithms/QAOA/QAOA_braket.ipynb), [ variational quantum eigensolver](https://github.com/amazon-braket/amazon-braket-examples/blob/main/examples/hybrid_quantum_algorithms/VQE_Chemistry/VQE_chemistry_braket.ipynb), or [ quantum machine learning](https://github.com/amazon-braket/amazon-braket-examples/blob/main/examples/hybrid_jobs/1_Quantum_machine_learning_in_Amazon_Braket_Hybrid_Jobs/Quantum_machine_learning_in_Amazon_Braket_Hybrid_Jobs.ipynb). You can also monitor your algorithm progress in near-real time, enabling you to keep track of costs, budget, or custom metrics such as training loss or expectation values. 

You can access hybrid jobs in Braket using:
+ The [Amazon Braket Python SDK](https://github.com/aws/amazon-braket-sdk-python).
+ The [Amazon Braket console](https://console.aws.amazon.com/braket/home).
+ The Amazon Braket API.

**Topics**
+ [

## When to use Amazon Braket Hybrid Jobs
](#braket-jobs-use)
+ [

## Running a hybrid job with Amazon Braket Hybrid Jobs
](#braket-jobs-works)
+ [

# Key concepts for Hybrid Jobs
](braket-jobs-concepts.md)
+ [

# Prerequisites
](braket-jobs-prerequisites.md)
+ [

# Create a Hybrid Job
](braket-jobs-first.md)
+ [

# Cancel a Hybrid Job
](braket-jobs-cancel.md)
+ [

# Customizing your Hybrid Job
](braket-jobs-customize.md)
+ [

# Using PennyLane with Amazon Braket
](hybrid.md)
+ [

# Using CUDA-Q with Amazon Braket
](braket-using-cuda-q.md)

## When to use Amazon Braket Hybrid Jobs


 Amazon Braket Hybrid Jobs enables you to run hybrid quantum-classical algorithms, such as the Variational Quantum Eigensolver (VQE) and the Quantum Approximate Optimization Algorithm (QAOA), that combine classical compute resources with quantum computing devices to optimize the performance of today's quantum systems. Amazon Braket Hybrid Jobs provides three main benefits:

1.  **Performance**: Amazon Braket Hybrid Jobs provides better performance than running hybrid algorithms from your own environment. While your job is running, it has priority access to the selected target QPU. Tasks from your job run ahead of other tasks queued on the device. This results in shorter and more predictable runtimes for hybrid algorithms. Amazon Braket Hybrid Jobs also supports parametric compilation. You can submit a circuit using free parameters and Braket compiles the circuit once, without the need to recompile for subsequent parameter updates to the same circuit, resulting in even faster runtimes.

1.  **Convenience**: Amazon Braket Hybrid Jobs simplifies setting up and managing your compute environment and keeping it running while your hybrid algorithm runs. You just provide your algorithm script and select a quantum device (either a quantum processing unit or a simulator) on which to run. Amazon Braket waits for the target device to become available, spins up the classical resources, runs the workload in pre-built container environments, returns the results to Amazon Simple Storage Service (Amazon S3), and releases the compute resources.

1.  **Metrics**: Amazon Braket Hybrid Jobs provides on-the-fly insights into running algorithms and delivers customizable algorithm metrics in near real-time to Amazon CloudWatch and the Amazon Braket console so you can track the progress of your algorithms.

## Running a hybrid job with Amazon Braket Hybrid Jobs


To run a hybrid job with Amazon Braket Hybrid Jobs, you first need to define your algorithm. You can define it by writing the *algorithm script* and, optionally, other dependency files using the [Amazon Braket Python SDK](https://github.com/aws/amazon-braket-sdk-python) or [PennyLane](https://pennylane.ai). If you want to use other (open source or proprietary) libraries, you can define your own custom container image using Docker, which includes these libraries. For more information, see [Bring your own container (BYOC)](braket-jobs-byoc.md).

In either case, next you create a hybrid job using the Amazon Braket API, where you provide your algorithm script or container, select the target quantum device the hybrid job is to use, and then choose from a variety of optional settings. The default values provided for these optional settings work for the majority of use cases. For the target device to run your Hybrid Job, you have a choice between a QPU, an on-demand simulator (such as SV1, DM1 or TN1), or the classical hybrid job instance itself. With an on-demand simulator or QPU, your hybrid jobs container makes API calls to a remote device. With the embedded simulators, the simulator is embedded in the same container as your algorithm script. The [lightning simulators](https://github.com/PennyLaneAI/pennylane-lightning) from PennyLane are embedded with the default pre-built hybrid jobs container for you to use. If you run your code using an embedded PennyLane simulator or a custom simulator, you can specify an instance type as well as how many instances you wish to use. Refer to the [Amazon Braket Pricing page](https://aws.amazon.com/braket/pricing/) for the costs associated with each choice.

![\[Flowchart diagram showing the user interactions with Amazon Braket components, API, Jobs Instance, and simulators for hybrid, QPU, on-demand, and embedded tasks. Results are stored in Amazon Simple Storage Service bucket and analyzed using Amazon CloudWatch on the Amazon Braket console.\]](http://docs.aws.amazon.com/braket/latest/developerguide/images/braket-hybrid-job-run.png)


If your target device is an on-demand or embedded simulator, Amazon Braket starts running the hybrid job right away. It spins up the hybrid job instance (you can customize the instance type in the API call), runs your algorithm, writes the results to Amazon S3, and releases your resources. This release of resources ensures that you only pay for what you use.

The total number of concurrent hybrid jobs per quantum processing unit (QPU) is restricted. Today, only one hybrid job can run on a QPU at any given time. Queues are used to control the number of hybrid jobs allowed to run so as not to exceed the limit allowed. If your target device is a QPU, your hybrid job first enters the *job queue* of the selected QPU. Amazon Braket spins up the hybrid job instance needed and runs your hybrid job on the device. For the duration of your algorithm, your hybrid job has priority access, meaning that quantum tasks from your hybrid job run ahead of other Braket quantum tasks queued up on the device, provided the job quantum tasks are submitted to the QPU once every few minutes. Once your hybrid job is complete, resources are released, meaning you only pay for what you use.

**Note**  
Devices are regional and your hybrid job runs in the same AWS Region as your primary device.

In both the simulator and QPU target scenarios, you have the option to define custom algorithm metrics, such as the energy of your Hamiltonian, as part of your algorithm. These metrics are automatically reported to Amazon CloudWatch and from there, they display in near real-time in the Amazon Braket console.

**Note**  
If you wish to use a GPU based instance, be sure to use one of the GPU-based simulators available with the embedded simulators on Braket (for example, `lightning.gpu`). If you choose one of the CPU-based embedded simulators (for example, `lightning.qubit`, or `braket:default-simulator`), the GPU will not be used and you may incur unnecessary costs.

# Key concepts for Hybrid Jobs
Key concepts

This section explains the key concepts of the `AwsQuantumJob.create` function provided by the Amazon Braket Python SDK and mapping to the container file structure.

In addition to the file or files that makes up your complete algorithm script, your hybrid job can have additional inputs and outputs. When your hybrid job starts, Amazon Braket copies inputs provided as part of the hybrid job creation into the container that runs the algorithm script. When the hybrid job completes, all outputs defined during the algorithm are copied to the Amazon S3 location specified.

**Note**  
 *Algorithm metrics* are reported in real time and do not follow this output procedure.

Amazon Braket also provides several environment variables and helper functions to simplify the interactions with container inputs and outputs. For more information, see the [braket.jobs package](https://amazon-braket-sdk-python.readthedocs.io/en/latest/_apidoc/braket.jobs.html) in the *Amazon Braket SDK*. 

**Topics**
+ [

## Inputs
](#braket-jobs-inputs)
+ [

## Outputs
](#braket-jobs-outputs)
+ [

## Environmental variables
](#braket-jobs-environmental-variables)
+ [

## Helper functions
](#braket-jobs-helper-functions)

## Inputs


 **Input data**: Input data can be provided to the hybrid algorithm by specifying the input data file, which is set up as a dictionary, with the `input_data` argument. The user defines the `input_data` argument within the `AwsQuantumJob.create` function in the SDK. This copies the input data to to the container file system at the location given by the environment variable `"AMZN_BRAKET_INPUT_DIR"`. For a couple examples of how input data is used in a hybrid algorithm, see the [QAOA with Amazon Braket Hybrid Jobs and PennyLane](https://github.com/amazon-braket/amazon-braket-examples/blob/main/examples/hybrid_jobs/2_Using_PennyLane_with_Braket_Hybrid_Jobs/Using_PennyLane_with_Braket_Hybrid_Jobs.ipynb) and [Quantum machine learning in Amazon Braket Hybrid Jobs](https://github.com/amazon-braket/amazon-braket-examples/blob/main/examples/hybrid_jobs/1_Quantum_machine_learning_in_Amazon_Braket_Hybrid_Jobs/Quantum_machine_learning_in_Amazon_Braket_Hybrid_Jobs.ipynb) Jupyter notebooks.

**Note**  
When the input data is large (>1GB), there will be a long wait time before the hybrid job is submitted. This is due to the fact that the local input data will first be uploaded to an S3 bucket, then the S3 path will be added to the hybrid job request, and, finally, the hybrid job request is submitted to Braket service.

 **Hyperparameters**: If you pass in `hyperparameters`, they are available under the environment variable `"AMZN_BRAKET_HP_FILE"`.

**Note**  
For more information about how to create hyperparameters and input data and then pass this information to the hybrid job script, see the [Use hyperparameters](braket-jobs-hyperparameters.md) section and this github [page](https://github.com/amazon-braket/amazon-braket-examples/blob/main/examples/hybrid_jobs/1_Quantum_machine_learning_in_Amazon_Braket_Hybrid_Jobs/qcbm/qcbm.py).

 **Checkpoints**: To specify a `job-arn` whose checkpoint you want to use in a new hybrid job, use the `copy_checkpoints_from_job` command. This command copies over the checkpoint data to the `checkpoint_configs3Uri` of the new hybrid job, making it available at the path given by the environment variable `AMZN_BRAKET_CHECKPOINT_DIR` while the job runs. The default is `None`, meaning checkpoint data from another hybrid job will not be used in the new hybrid job.

## Outputs


 **Quantum Tasks**: Quantum task results are stored in the S3 location `s3://amazon-braket-<region>-<accountID>/jobs/<job-name>/tasks`.

 **Job results**: Everything that your algorithm script saves to the directory given by the environment variable `"AMZN_BRAKET_JOB_RESULTS_DIR"` is copied to the S3 location specified in `output_data_config`. If the value is not specified, it defaults to `s3://amazon-braket-<region>-<accountID>/jobs/<job-name>/<timestamp>/data`. We provide the SDK helper function ** `save_job_result` **, which you can use to store results conveniently in the form of a dictionary when called from your algorithm script.

 **Checkpoints**: If you want to use checkpoints, you can save them in the directory given by the environment variable `"AMZN_BRAKET_CHECKPOINT_DIR"`. You can also use the SDK helper function `save_job_checkpoint` instead.

 **Algorithm metrics**: You can define algorithm metrics as part of your algorithm script that are emitted to Amazon CloudWatch and displayed in real time in the Amazon Braket console while your hybrid job is running. For an example of how to use algorithm metrics, see [Use Amazon Braket Hybrid Jobs to run a QAOA algorithm](braket-jobs-run-qaoa-algorithm.md).

For more information on saving your job outputs, see [Save your results](https://docs.aws.amazon.com/braket/latest/developerguide/braket-jobs-first.html#braket-jobs-save-results) in the Hybrid Jobs documentation. 

## Environmental variables


Amazon Braket provides several environment variables to simplify the interactions with container inputs and outputs. The folllowing code lists the environmental variables that Braket uses.
+ `AMZN_BRAKET_INPUT_DIR` – The input data directory opt/braket/input/data.
+ `AMZN_BRAKET_JOB_RESULTS_DIR` – The output directory opt/braket/model to write job results to.
+ `AMZN_BRAKET_JOB_NAME` – The name of the job.
+ `AMZN_BRAKET_CHECKPOINT_DIR` – The checkpoint directory.
+ `AMZN_BRAKET_HP_FILE` – The file containing the hyperparameters.
+ `AMZN_BRAKET_DEVICE_ARN` – The device ARN (AWS Resource Name).
+ `AMZN_BRAKET_OUT_S3_BUCKET` – The output Amazon S3 bucket, as specified in the `CreateJob` request's `OutputDataConfig`.
+ `AMZN_BRAKET_SCRIPT_ENTRY_POINT` – The entry point as specified in the `CreateJob` request's `ScriptModeConfig`.
+ `AMZN_BRAKET_SCRIPT_COMPRESSION_TYPE` – The compression type as specified in the `CreateJob` request's `ScriptModeConfig`.
+ `AMZN_BRAKET_SCRIPT_S3_URI` – The Amazon S3 location of the user's script as specified in the `CreateJob` request's `ScriptModeConfig`.
+ `AMZN_BRAKET_TASK_RESULTS_S3_URI` – The Amazon S3 location where the SDK would store the quantum task results by default for the job.
+ `AMZN_BRAKET_JOB_RESULTS_S3_PATH` – The Amazon S3 location where the job results would be stored, as specified in `CreateJob` request's `OutputDataConfig`.
+ `AMZN_BRAKET_JOB_TOKEN` – The string that should be passed to `CreateQuantumTask`'s `jobToken` parameter for quantum tasks created in the job container.

## Helper functions


Amazon Braket provides several helper functions to simplify the interactions with container inputs and outputs. These helper functions would be called from within the algorithm script that is used to run your Hybrid Job. The following example demonstrates how to use them.

```
from braket.jobs import get_checkpoint_dir, get_hyperparameters, get_input_data_dir, get_job_device_arn, get_job_name, get_results_dir, save_job_result, save_job_checkpoint, load_job_checkpoint

get_checkpoint_dir() # Get the checkpoint directory
get_hyperparameters() # Get the hyperparameters as strings
get_input_data_dir() # Get the input data directory
get_job_device_arn() # Get the device specified by the hybrid job
get_job_name() # Get the name of the hybrid job.
get_results_dir() # Get the path to a results directory
save_job_result(result_data='data') # Save hybrid job results
save_job_checkpoint(checkpoint_data={'key': 'value'}) # Save a checkpoint
load_job_checkpoint() # Load a previously saved checkpoint
```

# Prerequisites


Before you run your first hybrid job, you must ensure that you have sufficient permissions to proceed with this task. To determine that you have the correct permissions, select **Permissions** from the menu on left side of the Braket Console. The **Permissions management for Amazon Braket ** page helps you verify whether one of your existing roles has permissions that are sufficient to run your hybrid job or guides you through the creation of a default role that can be used to run your hybrid job if you do not already have such a role.

![\[Permissions and settings page for Amazon Braket service showing a service-linked role and option to verify existing roles for Hybrid Jobs execution role.\]](http://docs.aws.amazon.com/braket/latest/developerguide/images/braket-jobs-first-permissions.png)


To verify that you have roles with sufficient permissions to run a hybrid job, select the **Verify existing role** button. If you do, you get a message that the roles were found. To see the names of the roles and their role ARNs, select the **Show roles** button.

![\[Amazon Braket permissions and settings screen showing a service-linked role found and existing roles with sufficient permissions to execute hybrid jobs.\]](http://docs.aws.amazon.com/braket/latest/developerguide/images/braket-jobs-first-permissions-verify-yes.png)


If you do not have a role with sufficient permissions to run a hybrid job, you get a message that no such role was found. Select the **Create default role** button to obtain a role with sufficient permissions.

![\[Amazon Braket permissions and settings page showing service-linked role found and no hybrid jobs execution roles found.\]](http://docs.aws.amazon.com/braket/latest/developerguide/images/braket-jobs-first-permissions-verify-no.png)


If the role was created successfully, you get a message confirming this.

![\[Amazon Braket permissions and settings page showing a service-linked role found and a Hybrid jobs execution role created successfully.\]](http://docs.aws.amazon.com/braket/latest/developerguide/images/braket-jobs-first-permissions-verify-created.png)


If you do not have permissions to make this inquiry, you will be denied access. In this case, contact your internal AWS administrator.

![\[AccessDenied error message indicating user is not authorized to perform iam:ListAttachedRolePolicies on an AmazonBraketJobsExecutionRole with an explicit deny.\]](http://docs.aws.amazon.com/braket/latest/developerguide/images/braket-jobs-first-permissions-access-denied.png)


# Create a Hybrid Job


 This section shows you how to create a Hybrid Job using a Python script. Alternatively, to create a hybrid job from local Python code, such as your preferred integrated development environment (IDE) or a Braket notebook, see [Run your local code as a hybrid job](braket-hybrid-job-decorator.md).

**Topics**
+ [

## Create and run
](#braket-jobs-first-create)
+ [

## Monitor your results
](#braket-jobs-first-monitor-results)
+ [

## Save your results
](#braket-jobs-save-results)
+ [

## Using checkpoints
](#braket-jobs-checkpoints)
+ [

# Run your local code as a hybrid job
](braket-hybrid-job-decorator.md)
+ [

# Using the API with Hybrid Jobs
](braket-jobs-api.md)
+ [

# Create and debug a hybrid job with local mode
](braket-jobs-local-mode.md)

## Create and run


Once you have a role with permissions to run a hybrid job, you are ready to proceed. The key piece of your first Braket hybrid job is the *algorithm script*. It defines the algorithm you want to run and contains the classical logic and quantum tasks that are part of your algorithm. In addition to your algorithm script, you can provide other dependency files. The algorithm script together with its dependencies is called the *source module*. The *entry point* defines the first file or function to run in your source module when the hybrid job starts.

![\[Diagram showing the workflow of creating a quantum job using a console or notebook, running the algorithm script on a quantum device, and analyzing results.\]](http://docs.aws.amazon.com/braket/latest/developerguide/images/braket-jobs-first-workflow.jpg)


First, consider the following basic example of an algorithm script that creates five bell states and prints the corresponding measurement results.

```
import os

from braket.aws import AwsDevice
from braket.circuits import Circuit


def start_here():

    print("Test job started!")

    # Use the device declared in the job script
    device = AwsDevice(os.environ["AMZN_BRAKET_DEVICE_ARN"])

    bell = Circuit().h(0).cnot(0, 1)
    for count in range(5):
        task = device.run(bell, shots=100)
        print(task.result().measurement_counts)

    print("Test job completed!")
```

Save this file with the name *algorithm\$1script.py* in your current working directory on your Braket notebook or local environment. The algorithm\$1script.py file has `start_here()` as the planned entry point.

Next, create a Python file or Python notebook in the same directory as the algorithm\$1script.py file. This script kicks off the hybrid job and handles any asynchronous processing, such as printing the status or key outcomes that we are interested in. At a minimum, this script needs to specify your hybrid job script and your primary device.

**Note**  
For more information about how to create a Braket notebook or upload a file, such as the *algorithm\$1script.py* file, in the same directory as the notebooks, see [Run your first circuit using the Amazon Braket Python SDK](braket-get-started-run-circuit.md) 

For this basic first case, you target a simulator. Whichever type of quantum device you target, a simulator or an actual quantum processing unit (QPU), the device you specify with `device` in the following script is used to schedule the hybrid job and is available to the algorithm scripts as the environment variable `AMZN_BRAKET_DEVICE_ARN`.

**Note**  
You can only use devices that are available in the AWS Region of your hybrid job. The Amazon Braket SDK auto selects this AWS Region. For example, a hybrid job in us-east-1 can use IonQ, SV1, DM1, and TN1 devices, but not Rigetti devices.

If you choose a quantum computer instead of a simulator, Braket schedules your hybrid jobs to run all of their quantum tasks with priority access.

```
from braket.aws import AwsQuantumJob
from braket.devices import Devices

job = AwsQuantumJob.create(
    Devices.Amazon.SV1,
    source_module="algorithm_script.py",
    entry_point="algorithm_script:start_here",
    wait_until_complete=True
)
```

The parameter `wait_until_complete=True` sets a verbose mode so that your job prints output from the actual job as it's running. You should see an output similar to the following example.

```
Initializing Braket Job: arn:aws:braket:us-west-2:111122223333:job/braket-job-default-123456789012
Job queue position: 1
Job queue position: 1
Job queue position: 1
..............
.
.
.
Beginning Setup
Checking for Additional Requirements
Additional Requirements Check Finished
Running Code As Process
Test job started!
Counter({'00': 58, '11': 42})
Counter({'00': 55, '11': 45})
Counter({'11': 51, '00': 49})
Counter({'00': 56, '11': 44})
Counter({'11': 56, '00': 44})
Test job completed!
Code Run Finished
2025-09-24 23:13:40,962 sagemaker-training-toolkit INFO     Reporting training SUCCESS
```

**Note**  
You can also use your custom-made module with the [AwsQuantumJob.create](https://amazon-braket-sdk-python.readthedocs.io/en/latest/_apidoc/braket.aws.aws_quantum_job.html#braket.aws.aws_quantum_job.AwsQuantumJob.create) method by passing its location (either the path to a local directory or file, or an S3 URI of a tar.gz file). For a working example, see [Parallelize\$1training\$1for\$1QML.ipynb](https://github.com/amazon-braket/amazon-braket-examples/blob/main/examples/hybrid_jobs/5_Parallelize_training_for_QML/Parallelize_training_for_QML.ipynb) file in the hybrid jobs folder in the [Amazon Braket examples Github repo](https://github.com/amazon-braket/amazon-braket-examples/tree/main).

## Monitor your results


Alternatively, you can access the log output from Amazon CloudWatch. To do this, go to the **Log groups** tab on the left menu of the job detail page, select the log group `aws/braket/jobs`, and then choose the log stream that contains the job name. In the example above, this is `braket-job-default-1631915042705/algo-1-1631915190`.

![\[CloudWatch log group showing list of log events with file paths and timestamps for Amazon Braket SDK Python tests.\]](http://docs.aws.amazon.com/braket/latest/developerguide/images/braket-jobs-first-cw-log.png)


You can also view the status of the hybrid job in the console by selecting the **Hybrid Jobs** page and then choose **Settings**.

![\[Amazon Braket hybrid job details showing summary, event times, source code and instance configuration, and stopping conditions.\]](http://docs.aws.amazon.com/braket/latest/developerguide/images/braket-jobs-first-console-status.png)


Your hybrid job produces some artifacts in Amazon S3 while it runs. The default S3 bucket name is `amazon-braket-<region>-<accountid>` and the content is in the `jobs/<jobname>/<timestamp>` directory. You can configure the S3 locations where these artifacts are stored by specifying a different `code_location` when the hybrid job is created with the Braket Python SDK.

**Note**  
This S3 bucket must be located in the same AWS Region as your job script.

The `jobs/<jobname>/<timestamp>` directory contains a subfolder with the output from the entry point script in a `model.tar.gz` file. There is also a directory called `script` that contains your algorithm script artifacts in a `source.tar.gz` file. The results from your actual quantum tasks are in the directory named `jobs/<jobname>/tasks`.

## Save your results


You can save the results generated by the algorithm script so that they are available from the hybrid job object in the hybrid job script as well as from the output folder in Amazon S3 (in a tar-zipped file named model.tar.gz).

The output must be saved in a file using a JavaScript Object Notation (JSON) format. If the data can not be readily serialized to text, as in the case of a numpy array, you could pass in an option to serialize using a pickled data format. See the [braket.jobs.data\$1persistence module](https://amazon-braket-sdk-python.readthedocs.io/en/latest/_apidoc/braket.jobs.data_persistence.html#braket.jobs.data_persistence.save_job_result) for more details.

To save the results of the hybrid jobs, add the following lines commented with \$1ADD to the algorithm\$1script.py file.

```
import os

from braket.aws import AwsDevice
from braket.circuits import Circuit
from braket.jobs import save_job_result  # ADD


def start_here():

    print("Test job started!")

    device = AwsDevice(os.environ['AMZN_BRAKET_DEVICE_ARN'])

    results = []  # ADD

    bell = Circuit().h(0).cnot(0, 1)
    for count in range(5):
        task = device.run(bell, shots=100)
        print(task.result().measurement_counts)
        results.append(task.result().measurement_counts)  # ADD

        save_job_result({"measurement_counts": results})  # ADD

    print("Test job completed!")
```

You can then display the results of the job from your job script by appending the line ** `print(job.result())` ** commented with \$1ADD.

```
import time
from braket.aws import AwsQuantumJob

job = AwsQuantumJob.create(
    source_module="algorithm_script.py",
    entry_point="algorithm_script:start_here",
    device="arn:aws:braket:::device/quantum-simulator/amazon/sv1",
)

print(job.arn)
while job.state() not in AwsQuantumJob.TERMINAL_STATES:
    print(job.state())
    time.sleep(10)

print(job.state())
print(job.result())   # ADD
```

In this example, we have removed `wait_until_complete=True` to suppress verbose output. You can add it back in for debugging. When you run this hybrid job, it outputs the identifier and the `job-arn`, followed by the state of the hybrid job every 10 seconds until the hybrid job is `COMPLETED`, after which it shows you the results of the bell circuit. See the following example.

```
arn:aws:braket:us-west-2:111122223333:job/braket-job-default-123456789012
INITIALIZED
RUNNING
RUNNING
RUNNING
RUNNING
RUNNING
RUNNING
RUNNING
RUNNING
RUNNING
RUNNING
...
RUNNING
RUNNING
COMPLETED
{'measurement_counts': [{'11': 53, '00': 47},..., {'00': 51, '11': 49}]}
```

## Using checkpoints


You can save intermediate iterations of your hybrid jobs using checkpoints. In the algorithm script example from the previous section, you would add the following lines commented with \$1ADD to create checkpoint files.

```
from braket.aws import AwsDevice
from braket.circuits import Circuit
from braket.jobs import save_job_checkpoint  # ADD
import os


def start_here():

    print("Test job starts!")

    device = AwsDevice(os.environ["AMZN_BRAKET_DEVICE_ARN"])

    # ADD the following code
    job_name = os.environ["AMZN_BRAKET_JOB_NAME"]
    save_job_checkpoint(checkpoint_data={"data": f"data for checkpoint from {job_name}"}, checkpoint_file_suffix="checkpoint-1")  # End of ADD

    bell = Circuit().h(0).cnot(0, 1)
    for count in range(5):
        task = device.run(bell, shots=100)
        print(task.result().measurement_counts)

    print("Test hybrid job completed!")
```

When you run the hybrid job, it creates the file *<jobname>-checkpoint-1.json* in your hybrid job artifacts in the checkpoints directory with a default `/opt/jobs/checkpoints` path. The hybrid job script remains unchanged unless you want to change this default path.

If you want to load a hybrid job from a checkpoint generated by a previous hybrid job, the algorithm script uses `from braket.jobs import load_job_checkpoint`. The logic to load in your algorithm script is as follows.

```
from braket.jobs import load_job_checkpoint

checkpoint_1 = load_job_checkpoint(
    "previous_job_name",
    checkpoint_file_suffix="checkpoint-1",
)
```

After loading this checkpoint, you can continue your logic based on the content loaded to `checkpoint-1`.

**Note**  
The *checkpoint\$1file\$1suffix* must match the suffix previously specified when creating the checkpoint.

Your orchestration script needs to specify the `job-arn` from the previous hybrid job with the line commented with \$1ADD.

```
from braket.aws import AwsQuantumJob

job = AwsQuantumJob.create(
    source_module="source_dir",
    entry_point="source_dir.algorithm_script:start_here",
    device="arn:aws:braket:::device/quantum-simulator/amazon/sv1",
    copy_checkpoints_from_job="<previous-job-ARN>", #ADD
    )
```

# Run your local code as a hybrid job


Amazon Braket Hybrid Jobs provides a fully managed orchestration of hybrid quantum-classical algorithms, combining Amazon EC2 compute resources with Amazon Braket Quantum Processing Unit (QPU) access. Quantum tasks created in a hybrid job have priority queueing over individual quantum tasks so that your algorithms won't be interrupted by fluctuations in the quantum task queue. Each QPU maintains a separate hybrid jobs queue, ensuring that only one hybrid job can run at any given time.

**Topics**
+ [

## Create a hybrid job from local Python code
](#create-hybrid-job-from-local-python-code)
+ [

## Install additional Python packages and source code
](#install-python-packages-and-code)
+ [

## Save and load data into a hybrid job instance
](#save-load-data-into-instance)
+ [

## Best practices for hybrid job decorators
](#best-practices)

## Create a hybrid job from local Python code


You can run your local Python code as an Amazon Braket Hybrid Job. You can do this by annotating your code with an `@hybrid_job` decorator, as shown in the following code example. For custom environments, you can opt to [use a custom container](braket-jobs-byoc.md) from Amazon Elastic Container Registry (ECR). 

**Note**  
Only Python 3.12 is supported by default.

 You can use the `@hybrid_job` decorator to annotate a function. Braket transforms the code inside the decorator into a Braket hybrid job [algorithm script](braket-jobs-first.md). The hybrid job then invokes the function inside the decorator on an Amazon EC2 instance. You can monitor the progress of the job with `job.state()` or with the Braket console. The following code example shows how to run a sequence of five states on the State Vector Simulator (SV1) device. 

```
from braket.aws import AwsDevice
from braket.circuits import Circuit, FreeParameter, Observable
from braket.devices import Devices
from braket.jobs.hybrid_job import hybrid_job
from braket.jobs.metrics import log_metric

device_arn = Devices.Amazon.SV1


@hybrid_job(device=device_arn)  # Choose priority device
def run_hybrid_job(num_tasks=1):
    device = AwsDevice(device_arn)  # Declare AwsDevice within the hybrid job

    # Create a parametric circuit
    circ = Circuit()
    circ.rx(0, FreeParameter("theta"))
    circ.cnot(0, 1)
    circ.expectation(observable=Observable.X(), target=0)

    theta = 0.0  # Initial parameter

    for i in range(num_tasks):
        task = device.run(circ, shots=100, inputs={"theta": theta})  # Input parameters
        exp_val = task.result().values[0]

        theta += exp_val  # Modify the parameter (possibly gradient descent)

        log_metric(metric_name="exp_val", value=exp_val, iteration_number=i)

    return {"final_theta": theta, "final_exp_val": exp_val}
```

You create the hybrid job by invoking the function as you would normal Python functions. However, the decorator function returns the hybrid job handle rather than the result of the function. To retrieve the results after it has completed, use `job.result()`. 

```
job = run_hybrid_job(num_tasks=1)
result = job.result()
```

The device argument in the `@hybrid_job` decorator specifies the device that the hybrid job has priority access to - in this case, the SV1 simulator. To get QPU priority, you must ensure that the device ARN used within the function matches that specified in the decorator. For convenience, you can use the helper function `get_job_device_arn()` to capture the device ARN declared in `@hybrid_job`. 

**Note**  
Each hybrid job has at least a one minute startup time since it creates a containerized environment on Amazon EC2. So for very short workloads, such as a single circuit or a batch of circuits, it may suffice for you to use quantum tasks.

**Hyperparameters** 

The `run_hybrid_job()` function takes the argument `num_tasks` to control the number of quantum tasks created. The hybrid job automatically captures this as a [hyperparameter](braket-jobs-hyperparameters.md).

**Note**  
Hyperparameters are displayed in the Braket console as strings, that are limited to 2500 characters. 

**Metrics and logging** 

Within the `run_hybrid_job()` function, metrics from iterative algorithms are recorded with `log_metrics`. Metrics are automatically plotted in the Braket console page under the hybrid job tab. You can use metrics to track the quantum task costs in near-real time during the hybrid job run with the [Braket cost tracker](braket-pricing.md). The example above uses the metric name “probability” that records the first probability from the [result type](braket-result-types.md).

**Retrieving results** 

After the hybrid job has completed, you use `job.result()` to retrieve the hybrid jobs results. Any objects in the return statement are automatically captured by Braket. Note that the objects returned by the function must be a tuple with each element being serializable. For example, the following code shows a working, and a failing example. 

```
import numpy as np


# Working example
@hybrid_job(device=Devices.Amazon.SV1)
def passing():
    np_array = np.random.rand(5)
    return np_array  # Serializable

# # Failing example
# @hybrid_job(device=Devices.Amazon.SV1)
# def failing():
#     return MyObject() # Not serializable
```

**Job name** 

By default, the name for this hybrid job is inferred from the function name. You may also specify a custom name up to 50 characters long. For example, in the following code the job name is "my-job-name".

```
@hybrid_job(device=Devices.Amazon.SV1, job_name="my-job-name")
def function():
    pass
```

**Local mode** 

[Local jobs](braket-jobs-local-mode.md) are be created by adding the argument `local=True` to the decorator. This runs the hybrid job in a containerized environment on your local compute environment, such as your laptop. Local jobs **do not** have priority queueing for quantum tasks. For advanced cases such as multi-node or MPI, local jobs may have access to the required Braket environment variables. The following code creates a local hybrid job with the device as the SV1 simulator. 

```
@hybrid_job(device=Devices.Amazon.SV1, local=True)
def run_hybrid_job(num_tasks=1):
    return ...
```

All other hybrid job options are supported. For a list of options see the [braket.jobs.quantum\$1job\$1creation module](https://amazon-braket-sdk-python.readthedocs.io/en/stable/_apidoc/braket.jobs.quantum_job_creation.html). 

## Install additional Python packages and source code


You can customize your runtime environment to use your preferred Python packages. You can use either a `requirements.txt` file, a list of package names, or [bring your own container (BYOC)](braket-jobs-byoc.md). For example, the `requirements.txt` file may include other packages to install.

```
qiskit 
pennylane >= 0.31
mitiq == 0.29
```

To customize a runtime environment using a `requirements.txt` file, refer to the following code example.

```
@hybrid_job(device=Devices.Amazon.SV1, dependencies="requirements.txt")
def run_hybrid_job(num_tasks=1):
    return ...
```

Alternatively, you may supply the package names as a Python list as follows.

```
@hybrid_job(device=Devices.Amazon.SV1, dependencies=["qiskit", "pennylane>=0.31", "mitiq==0.29"])
def run_hybrid_job(num_tasks=1):
    return ...
```

Additional source code can be specified either as a list of modules, or a single module as in the following code example. 

```
@hybrid_job(device=Devices.Amazon.SV1, include_modules=["my_module1", "my_module2"])
def run_hybrid_job(num_tasks=1):
    return ...
```

## Save and load data into a hybrid job instance


**Specifying input training data**

When you create a hybrid job, you may provide an input training datasets by specifying an Amazon Simple Storage Service (Amazon S3) bucket. You may also specify a local path, then Braket automatically uploads the data to Amazon S3 at `s3://<default_bucket_name>/jobs/<job_name>/<timestamp>/data/<channel_name>` . If you specify a local path, the channel name defaults to “input”. The following code shows a numpy file from the local path `data/file.npy`. 

```
import numpy as np


@hybrid_job(device=Devices.Amazon.SV1, input_data="data/file.npy")
def run_hybrid_job(num_tasks=1):
    data = np.load("data/file.npy")
    return ...
```

For S3, you must use the `get_input_data_dir()` helper funciton.

```
import numpy as np
from braket.jobs import get_input_data_dir

s3_path = "s3://amazon-braket-us-east-1-123456789012/job-data/file.npy"


@hybrid_job(device=None, input_data=s3_path)
def job_s3_input():
    np.load(get_input_data_dir() + "/file.npy")


@hybrid_job(device=None, input_data={"channel": s3_path})
def job_s3_input_channel():
    np.load(get_input_data_dir("channel") + "/file.npy")
```

You can specify multiple input data sources by providing a dictionary of channel values and S3 URIs or local paths. 

```
import numpy as np
from braket.jobs import get_input_data_dir

input_data = {
    "input": "data/file.npy",
    "input_2": "s3://amzn-s3-demo-bucket/data.json"
}


@hybrid_job(device=None, input_data=input_data)
def multiple_input_job():
    np.load(get_input_data_dir("input") + "/file.npy")
    np.load(get_input_data_dir("input_2") + "/data.json")
```

**Note**  
When the input data is large (>1GB), there is a long wait time before the job is created. This is due to the local input data when it is first uploaded to an S3 bucket, then the S3 path is added to the job request. Finally, the job request is submitted to the Braket service.

**Saving results to S3**

To save results not included in the return statement of the decorated function, you must append the correct directory to all file writing operations. The following example, shows saving a numpy array and a matplotlib figure.

```
import matplotlib.pyplot as plt
import numpy as np


@hybrid_job(device=Devices.Amazon.SV1)
def run_hybrid_job(num_tasks=1):
    result = np.random.rand(5)

    # Save a numpy array
    np.save("result.npy", result)

    # Save a matplotlib figure
    plt.plot(result)
    plt.savefig("fig.png")
    return ...
```

All results are compressed into a file named `model.tar.gz`. You can download the results with the Python function `job.result()` , or by navigating to the results folder from the hybrid job page in the Braket management console. 

**Saving and resuming from checkpoints**

For long-running hybrid jobs, its recommended to periodically save the intermediate state of the algorithm. You can use the built-in `save_job_checkpoint()` helper function, or save files to the `AMZN_BRAKET_JOB_RESULTS_DIR` path. The later is available with the helper function `get_job_results_dir()`.

The following is a minimal working example for saving and loading checkpoints with a hybrid job decorator:

```
from braket.jobs import save_job_checkpoint, load_job_checkpoint, hybrid_job


@hybrid_job(device=None, wait_until_complete=True)
def function():
    save_job_checkpoint({"a": 1})


job = function()
job_name = job.name
job_arn = job.arn


@hybrid_job(device=None, wait_until_complete=True, copy_checkpoints_from_job=job_arn)
def continued_function():
    load_job_checkpoint(job_name)


continued_job = continued_function()
```

In the first hybrid job, `save_job_checkpoint()` is called with a dictionary containing the data we want to save. By default, every value must be serializable as text. For checkpointing more complex Python objects, such as numpy arrays, you can set `data_format = PersistedJobDataFormat.PICKLED_V4`. This code creates and overwrites a checkpoint file with default name `<jobname>.json` in your hybrid job artifacts under a subfolder called "checkpoints".

To create a new hybrid job to continue from the checkpoint, we need to pass `copy_checkpoints_from_job=job_arn` where `job_arn` is the hybrid job ARN of the previous job. Then we use `load_job_checkpoint(job_name)` to load from the checkpoint.

## Best practices for hybrid job decorators


**Embrace asynchronicity**

Hybrid jobs created with the decorator annotation are asynchronous - they run once the classical and quantum resources are available. You monitor the progress of the algorithm using the Braket Management Console or Amazon CloudWatch. When you submit your algorithm to run, Braket runs your algorithm in a scalable containerized environment and results are retrieved when the algorithm is complete.

**Run iterative variational algorithms**

Hybrid jobs gives you the tools to run iterative quantum-classical algorithms. For purely quantum problems, use [quantum tasks](braket-submit-tasks.md) or a [batch of quantum tasks](braket-batching-tasks.md). The priority access to certain QPUs is most beneficial for long-running variational algorithms requiring multiple iterative calls to the QPUs with classical processing in between. 

**Debug using local mode**

Before you run a hybrid job on a QPU, its recommended to first run on the simulator SV1 to confirm it runs as expected. For small scale tests, you can run with local mode for rapid iteration and debugging. 

**Improve reproducibility with [Bring your own container (BYOC)](braket-jobs-byoc.md)**

Create a reproducible experiment by encapsulating your software and its dependencies within a containerized environment. By packaging all your code, dependencies, and settings in a container, you prevent potential conflicts and versioning issues. 

**Multi-instance distributed simulators**

To run a large number of circuits, consider using built-in MPI support to run local simulators on multiple instances within a single hybrid job. For more information, see [embedded simulators](pennylane-embedded-simulators.md).

**Use parametric circuits**

Parametric circuits that you submit from a hybrid job are automatically compiled on certain QPUs using [parametric compilation](braket-jobs-parametric-compilation.md) to improve the runtimes of your algorithms. 

**Checkpoint periodically **

For long-running hybrid jobs, its recommended to periodically save the intermediate state of the algorithm. 

**For further examples, use cases, and best-practices, see [Amazon Braket examples GitHub](https://github.com/amazon-braket/amazon-braket-examples).**

# Using the API with Hybrid Jobs


You can access and interact with Amazon Braket Hybrid Jobs directly using the API. However, defaults and convenience methods are not available when using the API directly.

**Note**  
We strongly recommend that you interact with Amazon Braket Hybrid Jobs using the [Amazon Braket Python SDK](https://github.com/aws/amazon-braket-sdk-python). It offers convenient defaults and protections that help your hybrid jobs run successfully.

This topic covers the basics of using the API. If you choose to use the API, keep in mind that this approach can be more complex and be prepared for several iterations to get your hybrid job to run.

To use the API, your account should have a role with the `AmazonBraketFullAccess` managed policy.

**Note**  
For more information on how to obtain a role with the `AmazonBraketFullAccess` managed policy, see the [Enable Amazon Braket ](braket-enable-overview.md) page.

Additionally, you need an **execution role**. This role will be passed to the service. You can create the role using the ** Amazon Braket console**. Use the **Execution roles** tab on the **Permissions and settings** page to create a default role for hybrid jobs.

The `CreateJob` API requires that you specify all the required parameters for the hybrid job. To use Python, compress your algorithm script files to a tar bundle, such as an input.tar.gz file, and run the following script. Update the parts of the code within angled brackets (`<>`) to match your account information and entry point that specify the path, file, and method where your hybrid job starts.

```
from braket.aws import AwsDevice, AwsSession
import boto3
from datetime import datetime

s3_client = boto3.client("s3")
client = boto3.client("braket")

project_name = "job-test"
job_name = project_name + "-" + datetime.strftime(datetime.now(), "%Y%m%d%H%M%S")
bucket = "amazon-braket-<your_bucket>"
s3_prefix = job_name

job_script = "input.tar.gz"
job_object = f"{s3_prefix}/script/{job_script}"
s3_client.upload_file(job_script, bucket, job_object)

input_data = "inputdata.csv"
input_object = f"{s3_prefix}/input/{input_data}"
s3_client.upload_file(input_data, bucket, input_object)

job = client.create_job(
    jobName=job_name,
    roleArn="arn:aws:iam::<your_account>:role/service-role/AmazonBraketJobsExecutionRole",  # https://docs.aws.amazon.com/braket/latest/developerguide/braket-manage-access.html#about-amazonbraketjobsexecution
    algorithmSpecification={
        "scriptModeConfig": {
            "entryPoint": "<your_execution_module>:<your_execution_method>",
            "containerImage": {"uri": "292282985366.dkr.ecr.us-west-1.amazonaws.com/amazon-braket-base-jobs:1.0-cpu-py37-ubuntu18.04"},   # Change to the specific region you are using
            "s3Uri": f"s3://{bucket}/{job_object}",
            "compressionType": "GZIP"
        }
    },
    inputDataConfig=[
        {
            "channelName": "hellothere",
            "compressionType": "NONE",
            "dataSource": {
                "s3DataSource": {
                    "s3Uri": f"s3://{bucket}/{s3_prefix}/input",
                    "s3DataType": "S3_PREFIX"
                }
            }
        }
    ],
    outputDataConfig={
        "s3Path": f"s3://{bucket}/{s3_prefix}/output"
    },
    instanceConfig={
        "instanceType": "ml.m5.large",
        "instanceCount": 1,
        "volumeSizeInGb": 1
    },
    checkpointConfig={
        "s3Uri":  f"s3://{bucket}/{s3_prefix}/checkpoints",
        "localPath": "/opt/omega/checkpoints"
    },
    deviceConfig={
        "priorityAccess": {
            "devices": [
                "arn:aws:braket:us-west-1::device/qpu/rigetti/Ankaa-3"
            ]
        }
    },
    hyperParameters={
        "hyperparameter key you wish to pass": "<hyperparameter value you wish to pass>",
    },
    stoppingCondition={
        "maxRuntimeInSeconds": 1200,
        "maximumTaskLimit": 10
    },
)
```

Once you create your hybrid job, you can access the hybrid job details through the `GetJob` API or the console. To get the hybrid job details from the Python session in which you ran the `createJob` code as in the previous example, use the following Python command.

```
getJob = client.get_job(jobArn=job["jobArn"])
```

To cancel a hybrid job, call the `CancelJob` API with the Amazon Resource Name of the job ('JobArn').

```
cancelJob = client.cancel_job(jobArn=job["jobArn"])
```

You can specify checkpoints as part of the `createJob` API using the `checkpointConfig` parameter.

```
    checkpointConfig = {
        "localPath" : "/opt/omega/checkpoints",
        "s3Uri": f"s3://{bucket}/{s3_prefix}/checkpoints"
    },
```

**Note**  
The localPath of `checkpointConfig` cannot start with any of the following reserved paths: `/opt/ml`, `/opt/braket`, `/tmp`, or `/usr/local/nvidia`.

# Create and debug a hybrid job with local mode


When you are building a new hybrid algorithm, local mode helps you to debug and test your algorithm script. Local mode is a feature that allows you to run code you plan to use in Amazon Braket Hybrid Jobs, but without needing Braket to manage the infrastructure for running the hybrid job. Instead, run hybrid jobs locally on your Amazon Braket Notebook instance or on a preferred client, such as a laptop or desktop computer. 

In local mode, you can still send quantum tasks to actual devices, but you do not get the performance benefits when running against an actual Quantum processing unit (QPU) while in local mode.

To use local mode, modify `AwsQuantumJob` to `LocalQuantumJob` wherever it occurs inside of your program. For instance, to run the example from [Create your first hybrid job](braket-jobs-first.md), edit the hybrid job script in the code as follows.

```
from braket.jobs.local import LocalQuantumJob

job = LocalQuantumJob.create(
    device="arn:aws:braket:::device/quantum-simulator/amazon/sv1",
    source_module="algorithm_script.py",
    entry_point="algorithm_script:start_here",
)
```

**Note**  
Docker, which is already pre-installed in the Amazon Braket notebooks, needs to be installed in your local environment to use this feature. Instructions for installing Docker can be found on the [Get Docker](https://docs.docker.com/get-started/get-docker/) page. In addition, not all parameters are supported in local mode.

# Cancel a Hybrid Job


You may need to cancel a hybrid job in a non-terminal state. This can be done either in the console or with code.

To cancel your hybrid job in the console, select the hybrid job to cancel from the **Hybrid Jobs** page and then select **Cancel hybrid job** from the **Actions** dropdown menu.

![\[Amazon Braket hybrid jobs table with 4 jobs showing their names, status, device information, and timestamps. The Actions dropdown contains options to view new hybrid job, cancel, or manage tags.\]](http://docs.aws.amazon.com/braket/latest/developerguide/images/braket-hybrid-cancel-job.png)


To confirm the cancellation, enter *cancel* into the input field when prompted and then select **OK**.

![\[Dialog box to cancel a specific job with warnings about the cancellation process and a text input field to confirm by entering "cancel".\]](http://docs.aws.amazon.com/braket/latest/developerguide/images/braket-hybrid-cancel-job-confirm.png)


To cancel your hybrid job using code from the Braket Python SDK, use the `job_arn` to identify the hybrid job and then call the `cancel` command on it as shown in following code.

```
job = AwsQuantumJob(arn=job_arn)
job.cancel()
```

The `cancel` command terminates the classical hybrid job container immediately and does a best effort to cancel all of the related quantum tasks that are still in a non-terminal state.

# Customizing your Hybrid Job


Amazon Braket provides several ways to customize how your hybrid jobs run, allowing you to tailor the environment to your specific needs. This section explores options for customizing hybrid jobs, from defining the algorithm script environment to bringing your own container. You'll learn how to optimize your workflow using hyperparameters, configure job instances, and leverage parametric compilation for improved performance. These customization techniques help you maximize the potential of your hybrid quantum computations on Amazon Braket.

**Topics**
+ [

# Define the environment for your algorithm script
](braket-jobs-script-environment.md)
+ [

# Using hyperparameters
](braket-jobs-hyperparameters.md)
+ [

# Configure your hybrid job instance
](braket-jobs-configure-job-instance-for-script.md)
+ [

# Using parametric compilation to speed up Hybrid Jobs
](braket-jobs-parametric-compilation.md)

# Define the environment for your algorithm script


Amazon Braket supports environments defined by containers for your algorithm script:
+ A base container (the default, if no `image_uri` is specified)
+ A container with CUDA-Q
+ A container with Tensorflow and PennyLane
+ A container with PyTorch, PennyLane, and CUDA-Q

The following table provides details about the containers and the libraries they include.


**Amazon Braket containers**  

| Type | Base | CUDA-Q | TensorFlow | PyTorch | 
| --- | --- | --- | --- | --- | 
|   **Image URI**   |  292282985366.dkr.ecr.us-west-2.amazonaws.com/amazon-braket-base-jobs:latest  |  292282985366.dkr.ecr.us-west-2.amazonaws.com/amazon-braket-cudaq-jobs:latest  |  292282985366.dkr.ecr.us-east-1.amazonaws.com/amazon-braket-tensorflow-jobs:latest  |  292282985366.dkr.ecr.us-west-2.amazonaws.com/amazon-braket-pytorch-jobs:latest  | 
|   **Inherited Libraries**   |  |  [\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/braket/latest/developerguide/braket-jobs-script-environment.html)  |  [\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/braket/latest/developerguide/braket-jobs-script-environment.html)  |  [\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/braket/latest/developerguide/braket-jobs-script-environment.html)  | 
|   **Additional Libraries**   |  [\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/braket/latest/developerguide/braket-jobs-script-environment.html)  |  [\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/braket/latest/developerguide/braket-jobs-script-environment.html)  |  [\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/braket/latest/developerguide/braket-jobs-script-environment.html)  |  [\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/braket/latest/developerguide/braket-jobs-script-environment.html)  | 

You can view and access the open source container definitions at [aws/amazon-braket-containers](https://github.com/aws/amazon-braket-containers). Choose the container that best matches your use case. You can use any of the available AWS Regions in Braket (us-east-1, us-west-1, us-west-2, eu-north-1, eu-west-2), but the container Region must match the Region for your hybrid job. Specify the container image when you create a hybrid job by adding one of the following three arguments to your `create(…​)` call in the hybrid job script. You can install additional dependencies into the container you choose at runtime (at the cost of startup or runtime) because the Amazon Braket containers have internet connectivity. The following example is for the us-west-2 Region.
+  **Base image:** image\$1uri="292282985366.dkr.ecr.us-west-2.amazonaws.com/amazon-braket-base-jobs:latest"
+  **CUDA-Q image:** image\$1uri="292282985366.dkr.ecr.us-west-2.amazonaws.com/amazon-braket-cudaq-jobs:latest"
+  **Tensorflow image:** image\$1uri="292282985366.dkr.ecr.us-west-2.amazonaws.com/amazon-braket-tensorflow-jobs:latest"
+  **PyTorch image:** image\$1uri="292282985366.dkr.ecr.us-west-2.amazonaws.com/amazon-braket-pytorch-jobs:latest"

The `image-uris` can also be retrieved using the `retrieve_image()` function in the Amazon Braket SDK. The following example shows how to retrieve them from the us-west-2 AWS Region.

```
from braket.jobs.image_uris import retrieve_image, Framework

image_uri_base = retrieve_image(Framework.BASE, "us-west-2")
image_uri_cudaq = retrieve_image(Framework.CUDAQ, "us-west-2")
image_uri_tf = retrieve_image(Framework.PL_TENSORFLOW, "us-west-2")
image_uri_pytorch = retrieve_image(Framework.PL_PYTORCH, "us-west-2")
```

# Bring your own container (BYOC)


Amazon Braket Hybrid Jobs provides three pre-built containers for running code in different environments. If one of these containers supports your use case, you only have to provide your algorithm script when you create a hybrid job. Minor missing dependencies can be added from your algorithm script or from a `requirements.txt` file using `pip`.

If none of these containers support your use case, or if you wish to expand on them, Braket Hybrid Jobs supports running hybrid jobs with your own custom Docker container image, or bring your own container (BYOC). Make sure it is the right feature for your use case. 

**Topics**
+ [

## When is bringing my own container the right decision?
](#bring-own-container-decision)
+ [

# Recipe for bringing your own container
](bring-own-container-recipe.md)
+ [

# Running Braket hybrid jobs in your own container
](running-hybrid-jobs-in-own-container.md)

## When is bringing my own container the right decision?


Bringing your own container (BYOC) to Braket Hybrid Jobs offers the flexibility to use your own software by installing it in a packaged environment. Depending on your specific needs, there may be ways to achieve the same flexibility without having to go through the full BYOC Docker build - Amazon ECR upload - custom image URI cycle.

**Note**  
BYOC may not be the right choice if you want to add a small number of additional Python packages (generally fewer than 10) which are publicly available. For example, if you're using PyPi.

In this case, you can use one of the pre-built Braket images, and then include a `requirements.txt` file in your source directory at the job submission. The file is automatically read, and `pip` will install the packages with the specified versions as normal. If you're installing a large number of packages, the runtime of your jobs may be substantially increased. Check the Python and, if applicable, CUDA version of the prebuilt container you want to use to test if your software will work.

BYOC is necessary when you want to use a non-Python language (like C\$1\$1 or Rust) for your job script, or if you want to use a Python version not available through the Braket pre-built containers. It is also a good choice if:
+ You're using software with a license key, and you need to authenticate that key against a licensing server to run the software. With BYOC, you can embed the license key in your Docker image and include code to authenticate it.
+ You are using software that is not publicly available. For example, the software is hosted on a private GitLab or GitHub repository that you need a particular SSH key to access.
+ You need to install a large suite of software that is not packaged in the Braket provided containers. BYOC will allow you to eliminate long startup times for your hybrid jobs containers due to software installation.

BYOC also enables you to make your custom SDK or algorithm available to customers by building a Docker container with your software and making it available to your users. You can do this by setting appropriate permissions in Amazon ECR.

**Note**  
You must comply with all applicable software licenses.

# Recipe for bringing your own container


In this section, we provide a step-by-step guide of what you need to bring your own container (BYOC) to Braket Hybrid Jobs — the scripts, files, and steps to combine them to get up and running with your custom Docker images. The recipes for two common cases:

1. Install additional software in a Docker image and use only Python algorithm scripts in your jobs.

1. Use algorithm scripts written in a non-Python language with Hybrid Jobs, or a CPU architecture besides x86.

Defining the *container entry script* is more complex for case 2.

When Braket runs your Hybrid Job, it launches the requested number and type of Amazon EC2 instances, then runs the Docker image specified by the image URI input to job creation on them. When using the BYOC feature, you specify an image URI hosted in a [private Amazon ECR repository](https://docs.aws.amazon.com/AmazonECR/latest/userguide/Repositories.html) that you have Read access to. Braket Hybrid Jobs uses that custom image to run the job.

The specific components you need to build a Docker image that can be used with Hybrid Jobs. If you are unfamiliar with writing and building `Dockerfiles`, refer to the [Dockerfile documentation](https://docs.docker.com/reference/dockerfile/) and the [Amazon ECR CLI documentation](https://docs.aws.amazon.com/AmazonECR/latest/userguide/getting-started-cli.html).

**Topics**
+ [

## A base image for your Dockerfile
](#base-image-dockerfile)
+ [

## (Optional) A modified container entry point script
](#modified-container-entry-point)
+ [

## Install needed software and container script with `Dockerfile`
](#install-docketfile)

## A base image for your Dockerfile


If you are using Python and want to install software on top of what is provided in the Braket provided containers, an option for a base image is one of the Braket container images, hosted in our [GitHub repo](https://github.com/amazon-braket/amazon-braket-containers) and on Amazon ECR. You will need to [authenticate to Amazon ECR](https://docs.aws.amazon.com/AmazonECR/latest/userguide/getting-started-cli.html#cli-authenticate-registry) to pull the image and build on top of it. For example, the first line of your BYOC Docker file could be: `FROM [IMAGE_URI_HERE]`

Next, fill out the rest of the Dockerfile to install and set up the software that you want to add to the container. The pre-built Braket images will already contain the appropriate container entry point script, so you do not need to worry about including that.

If you want to use a non-Python language, such as C\$1\$1, Rust, or Julia, or if you want to build an image for a non-x86 CPU architecture, like ARM, you may need to build on top of a barebones public image. You can find many such images at the [Amazon Elastic Container Registry Public Gallery](https://gallery.ecr.aws/). Make sure you choose one that is appropriate for the CPU architecture, and if necessary, the GPU you want to use.

## (Optional) A modified container entry point script


**Note**  
If you're only adding additional software to a pre-built Braket image, you can skip this section.

To run non-Python code as part of your hybrid job, modify the Python script which defines the container entry point. For example, the [`braket_container.py` python script on the Amazon Braket Github ](https://github.com/amazon-braket/amazon-braket-containers/blob/main/src/braket_container.py). This is the script the images pre-built by Braket use to launch your algorithm script and set appropriate environment variables. The container entry point script itself **must** be in Python, but can launch non-Python scripts. In the pre-built example, you can see that Python algorithm scripts are launched either as a [Python subprocess](https://github.com/amazon-braket/amazon-braket-containers/blob/main/src/braket_container.py#L274) or as a [fully new process](https://github.com/amazon-braket/amazon-braket-containers/blob/main/src/braket_container.py#L257). By modifying this logic, you can enable the entry point script to launch non-Python algorithm scripts. For example, you could modify [https://github.com/amazon-braket/amazon-braket-containers/blob/main/src/braket_container.py#L139](https://github.com/amazon-braket/amazon-braket-containers/blob/main/src/braket_container.py#L139) function to launch Rust processes dependent on the file extension ending.

You can also choose to write a completely new `braket_container.py`. It should copy input data, source archives, and other necessary files from Amazon S3 into the container, and define the appropriate environment variables.

## Install needed software and container script with `Dockerfile`


**Note**  
If you use a pre-built Braket image as your Docker base image, the container script is already present.

If you created a modified container script in the previous step, you'll need to copy it into the container **and** define the environment variable `SAGEMAKER_PROGRAM` to `braket_container.py`, or what you have named your new container entry point script.

The following is an example of a `Dockerfile` that allows you to use Julia on GPU-accelerated Jobs instances:

```
FROM nvidia/cuda:12.2.0-devel-ubuntu22.04

    
 ARG DEBIAN_FRONTEND=noninteractive
 ARG JULIA_RELEASE=1.8
 ARG JULIA_VERSION=1.8.3


 ARG PYTHON=python3.11 
 ARG PYTHON_PIP=python3-pip
 ARG PIP=pip


 ARG JULIA_URL = https://julialang-s3.julialang.org/bin/linux/x64/${JULIA_RELEASE}/
 ARG TAR_NAME = julia-${JULIA_VERSION}-linux-x86_64.tar.gz


 ARG PYTHON_PKGS = # list your Python packages and versions here


 RUN curl -s -L ${JULIA_URL}/${TAR_NAME} | tar -C /usr/local -x -z --strip-components=1 -f -


 RUN apt-get update \

    && apt-get install -y --no-install-recommends \

    build-essential \

    tzdata \

    openssh-client \

    openssh-server \

    ca-certificates \

    curl \

    git \

    libtemplate-perl \

    libssl1.1 \

    openssl \

    unzip \ 

    wget \

    zlib1g-dev \

    ${PYTHON_PIP} \

    ${PYTHON}-dev \




 RUN ${PIP} install --no-cache --upgrade ${PYTHON_PKGS}


 RUN ${PIP} install --no-cache --upgrade sagemaker-training==4.1.3


 # Add EFA and SMDDP to LD library path
 ENV LD_LIBRARY_PATH="/opt/conda/lib/python${PYTHON_SHORT_VERSION}/site-packages/smdistributed/dataparallel/lib:$LD_LIBRARY_PATH"
 ENV LD_LIBRARY_PATH=/opt/amazon/efa/lib/:$LD_LIBRARY_PATH


 # Julia specific installation instructions
 COPY Project.toml /usr/local/share/julia/environments/v${JULIA_RELEASE}/
 RUN JULIA_DEPOT_PATH=/usr/local/share/julia \

    julia -e 'using Pkg; Pkg.instantiate(); Pkg.API.precompile()'
 # generate the device runtime library for all known and supported devices
 RUN JULIA_DEPOT_PATH=/usr/local/share/julia \

    julia -e 'using CUDA; CUDA.precompile_runtime()'


 # Open source compliance scripts
 RUN HOME_DIR=/root \

 && curl -o ${HOME_DIR}/oss_compliance.zip https://aws-dlinfra-utilities.s3.amazonaws.com/oss_compliance.zip \

 && unzip ${HOME_DIR}/oss_compliance.zip -d ${HOME_DIR}/ \

 && cp ${HOME_DIR}/oss_compliance/test/testOSSCompliance /usr/local/bin/testOSSCompliance \

 && chmod +x /usr/local/bin/testOSSCompliance \

 && chmod +x ${HOME_DIR}/oss_compliance/generate_oss_compliance.sh \

 && ${HOME_DIR}/oss_compliance/generate_oss_compliance.sh ${HOME_DIR} ${PYTHON} \

 && rm -rf ${HOME_DIR}/oss_compliance*


 # Copying the container entry point script
 COPY braket_container.py /opt/ml/code/braket_container.py
 ENV SAGEMAKER_PROGRAM braket_container.py
```

This example, downloads and runs scripts provided by AWS to ensure compliance with all relevant Open-Source licenses. For example, by properly attributing any installed code governed by an MIT license.

If you need to include non-public code, for instance code that is hosted in a private GitHub or GitLab repository, **do not** embed SSH keys in the Docker image to access it. Instead, use Docker Compose when you build to allow Docker to access SSH on the host machine it is built on. For more information, see the [Securely using SSH keys in Docker to access private Github repositories](https://www.fastruby.io/blog/docker/docker-ssh-keys.html) guide.

**Building and uploading your Docker image**

With a properly defined `Dockerfile`, you are now ready to follow the steps to [create a private Amazon ECR repository](https://docs.aws.amazon.com/AmazonECR/latest/userguide/repository-create.html), if one does not already exist. You can also build, tag, and upload your container image to the repository.

You are ready to build, tag, and push the image. See the [Docker build documentation](https://docs.docker.com/reference/cli/docker/buildx/build/) for a full explanation of options to `docker build` and some examples.

For the sample file defined above, you could run:

```
aws ecr get-login-password --region ${your_region} | docker login --username AWS --password-stdin ${aws_account_id}.dkr.ecr.${your_region}.amazonaws.com
 docker build -t braket-julia .
 docker tag braket-julia:latest ${aws_account_id}.dkr.ecr.${your_region}.amazonaws.com/braket-julia:latest
 docker push ${aws_account_id}.dkr.ecr.${your_region}.amazonaws.com/braket-julia:latest
```

**Assigning appropriate Amazon ECR permissions**

Braket Hybrid Jobs Docker images must be hosted in private Amazon ECR repositories. By default, a private Amazon ECR repo does **not** provide read access to the Braket Hybrid Jobs IAM role or to any other users that want to use your image, such as a collaborator or student. You must [set a repository policy](https://docs.aws.amazon.com/AmazonECR/latest/userguide/set-repository-policy.html) to grant the appropriate permissions. In general, only give permission to those specific users and IAM roles you want to access your images, rather than allowing anyone with the image URI to pull them.

# Running Braket hybrid jobs in your own container


To create a hybrid job with your own container, call `AwsQuantumJob.create()` with the argument `image_uri` specified. You can use a QPU, an on-demand simulator, or run your code locally on the classical processor available with Braket Hybrid Jobs. We recommend testing your code out on a simulator like SV1, DM1, or TN1 before running on a real QPU.

To run your code on the classical processor, specify the `instanceType` and the `instanceCount` you use by updating the `InstanceConfig`. Note that if you specify an `instance_count` > 1, you need to make sure that your code can run across multiple hosts. The upper limit for the number of instances you can choose is 5. For example:

```
job = AwsQuantumJob.create(
    source_module="source_dir",
    entry_point="source_dir.algorithm_script:start_here",
    image_uri="111122223333.dkr.ecr.us-west-2.amazonaws.com/my-byoc-container:latest",
    instance_config=InstanceConfig(instanceType="ml.g4dn.xlarge", instanceCount=3),
    device="local:braket/braket.local.qubit",
    # ...)
```

**Note**  
Use the device ARN to track the simulator you used as hybrid job metadata. Acceptable values must follow the format `device = "local:<provider>/<simulator_name>"`. Remember that `<provider>` and `<simulator_name>` must consist only of letters, numbers, `_`, `-`, and `.` . The string is limited to 256 characters.  
If you plan to use BYOC and you're not using the Braket SDK to create quantum tasks, you should pass the value of the environmental variable `AMZN_BRAKET_JOB_TOKEN` to the `jobToken` parameter in the `CreateQuantumTask` request. If you don't, the quantum tasks don't get priority and are billed as regular standalone quantum tasks.

# Using hyperparameters


You can define hyperparameters needed by your algorithm, such as the learning rate or step size, when you create a hybrid job. Hyperparameter values are typically used to control various aspects of the algorithm, and can often be tuned to optimize the algorithm's performance. To use hyperparameters in a Braket hybrid job, you need to specify their names and values explicitly as a dictionary. Specify the hyperparameter values to test when searching for the optimal set of values. The first step to using hyperparameters is to set up and define the hyperparameters as a dictionary, which can be seen in the following code.

```
from braket.devices import Devices

device_arn = Devices.Amazon.SV1

hyperparameters = {"shots": 1_000}
```

Then pass the hyperparameters defined in the code snippet given above to be used in the algorithm of your choice. To run the following code example, create a directory named “src” in the same path as your hyperparameter file. Inside of the "src" directory, add [0\$1Getting\$1started\$1papermill.ipynb](https://github.com/amazon-braket/amazon-braket-examples/blob/main/examples/hybrid_jobs/7_Running_notebooks_as_hybrid_jobs/src/0_Getting_started_papermill.ipynb), [notebook\$1runner.py](https://github.com/amazon-braket/amazon-braket-examples/blob/main/examples/hybrid_jobs/7_Running_notebooks_as_hybrid_jobs/src/notebook_runner.py), and [requirements.txt](https://github.com/amazon-braket/amazon-braket-examples/blob/main/examples/hybrid_jobs/7_Running_notebooks_as_hybrid_jobs/src/requirements.txt) code files. 

```
import time
from braket.aws import AwsQuantumJob

job = AwsQuantumJob.create(
    device=device_arn,
    source_module="src",
    entry_point="src.notebook_runner:run_notebook",
    input_data="src/0_Getting_started_papermill.ipynb",
    hyperparameters=hyperparameters,
    job_name=f"papermill-job-demo-{int(time.time())}",
)

# Print job to record the ARN
print(job)
```

To access your hyperparameters from *within* your hybrid job script, see the `load_jobs_hyperparams()` function in the [notebook\$1runner.py](https://github.com/amazon-braket/amazon-braket-examples/blob/main/examples/hybrid_jobs/7_Running_notebooks_as_hybrid_jobs/src/notebook_runner.py) python file. To access your hyperparameters *outside* of your hybrid job script, run the following code. 

```
from braket.aws import AwsQuantumJob

# Get the job using the ARN
job_arn = "arn:aws:braket:us-east-1:111122223333:job/5eabb790-d3ff-47cc-98ed-b4025e9e296f"  # Replace with your job ARN
job = AwsQuantumJob(arn=job_arn)

# Access the hyperparameters
job_metadata = job.metadata()
hyperparameters = job_metadata.get("hyperParameters", {})
print(hyperparameters)
```

For more information on learning how to use hyperparamters, see the [QAOA with Amazon Braket Hybrid Jobs and PennyLane](https://github.com/amazon-braket/amazon-braket-examples/blob/main/examples/hybrid_jobs/2_Using_PennyLane_with_Braket_Hybrid_Jobs/Using_PennyLane_with_Braket_Hybrid_Jobs.ipynb) and [Quantum machine learning in Amazon Braket Hybrid Jobs](https://github.com/amazon-braket/amazon-braket-examples/blob/main/examples/hybrid_jobs/1_Quantum_machine_learning_in_Amazon_Braket_Hybrid_Jobs/Quantum_machine_learning_in_Amazon_Braket_Hybrid_Jobs.ipynb) tutorials.

# Configure your hybrid job instance


Depending on your algorithm, you may have different requirements. By default, Amazon Braket runs your algorithm script on an `ml.m5.large` instance. However, you can customize this instance type when you create a hybrid job using the following import and configuration argument.

```
from braket.jobs.config import InstanceConfig

job = AwsQuantumJob.create(
    ...
    instance_config=InstanceConfig(instanceType="ml.g4dn.xlarge"), # Use NVIDIA T4 instance with 4 GPUs.
    ...
    ),
```

If you are running an embedded simulation and have specified a local device in the device configuration, you can additionally request more than one instance in the `InstanceConfig` by specifying the `instanceCount` and setting it to be greater than one. The upper limit is 5. For instance, you can choose 3 instances as follows.

```
from braket.jobs.config import InstanceConfig
job = AwsQuantumJob.create(
    ...
    instance_config=InstanceConfig(instanceType="ml.g4dn.xlarge", instanceCount=3), # Use 3 NVIDIA T4 instances
    ...
    ),
```

When you use multiple instances, consider distributing your hybrid job using the data parallel feature. See the following example notebook for more details on how-to see this [Parallelize training for QML](https://github.com/amazon-braket/amazon-braket-examples/blob/main/examples/hybrid_jobs/5_Parallelize_training_for_QML/Parallelize_training_for_QML.ipynb) example.

The following three tables list the available instance types and specs for standard, high performance, and GPU accelerated instances.

**Note**  
To view the default classical compute instance quotas for Hybrid Jobs, see the [Amazon Braket Quotas](braket-quotas.md) page.


| Standard Instances | vCPU | Memory (GiB) | 
| --- | --- | --- | 
|  ml.m5.large (default)  |  4  |  16  | 
|  ml.m5.xlarge  |  4  |  16  | 
|  ml.m5.2xlarge  |  8  |  32  | 
|  ml.m5.4xlarge  |  16  |  64  | 
|  ml.m5.12xlarge  |  48  |  192  | 
|  ml.m5.24xlarge  |  96  |  384  | 


| High performance Instances | vCPU | Memory (GiB) | 
| --- | --- | --- | 
|  ml.c5.xlarge  |  4  |  8  | 
|  ml.c5.2xlarge  |  8  |  16  | 
|  ml.c5.4xlarge  |  16  |  32  | 
|  ml.c5.9xlarge  |  36  |  72  | 
|  ml.c5.18xlarge  |  72  |  144  | 
|  ml.c5n.xlarge  |  4  |  10.5  | 
|  ml.c5n.2xlarge  |  8  |  21  | 
|  ml.c5n.4xlarge  |  16  |  32  | 
|  ml.c5n.9xlarge  |  36  |  72  | 
|  ml.c5n.18xlarge  |  72  |  192  | 


| GPU accelerated Instances | GPUs | vCPU | Memory (GiB) | GPU Memory (GiB) | 
| --- | --- | --- | --- | --- | 
|  ml.p4d.24xlarge  |  8  |  96  |  1152  |  320  | 
|  ml.g4dn.xlarge  |  1  |  4  |  16  |  16  | 
|  ml.g4dn.2xlarge  |  1  |  8  |  32  |  16  | 
|  ml.g4dn.4xlarge  |  1  |  16  |  64  |  16  | 
|  ml.g4dn.8xlarge  |  1  |  32  |  128  |  16  | 
|  ml.g4dn.12xlarge  |  4  |  48  |  192  |  64  | 
|  ml.g4dn.16xlarge  |  1  |  64  |  256  |  16  | 

Each instance uses a default configuration of data storage (SSD) of 30 GB. But you can adjust the storage in the same way that you configure the `instanceType`. The following example shows how to increase the total storage to 50 GB.

```
from braket.jobs.config import InstanceConfig

job = AwsQuantumJob.create(
    ...
    instance_config=InstanceConfig(
        instanceType="ml.g4dn.xlarge",
        volumeSizeInGb=50,
    ),
    ...
    ),
```

## Configure the default bucket in `AwsSession`


Utilizing your own `AwsSession` instance provides you with enhanced flexibility, such as the ability to specify a custom location for your default Amazon S3 bucket. By default, an `AwsSession` has a pre-configured Amazon S3 bucket location of `"amazon-braket-{id}-{region}"`. However, you have the option to override the default Amazon S3 bucket location when creating an `AwsSession`. Users can optionally pass in an `AwsSession` object into the `AwsQuantumJob.create()` method, by providing the `aws_session` parameter as demonstrated in the following code example.

```
aws_session = AwsSession(default_bucket="amazon-braket-s3-demo-bucket")

# Then you can use that AwsSession when creating a hybrid job
job = AwsQuantumJob.create(
    ...
    aws_session=aws_session
)
```

# Using parametric compilation to speed up Hybrid Jobs


 Amazon Braket supports parametric compilation on certain QPUs. This enables you to reduce the overhead associated with the computationally expensive compilation step by compiling a circuit only once and not for every iteration in your hybrid algorithm. This can improve runtimes dramatically for Hybrid Jobs, since you avoid the need to recompile your circuit at each step. Just submit parametrized circuits to one of our supported QPUs as a Braket Hybrid Job. For long running hybrid jobs, Braket automatically uses the updated calibration data from the hardware provider when compiling your circuit to ensure the highest quality results.

To create a parametric circuit, you first need to provide parameters as inputs in your algorithm script. In this example, we use a small parametric circuit and ignore any classical processing between each iteration. For typical workloads, you would submit many circuits in batch and perform classical processing such as updating the parameters in each iteration.

```
import os

from braket.aws import AwsDevice
from braket.circuits import Circuit, FreeParameter

def start_here():

    print("Test job started.")

    # Use the device declared in the job script
    device = AwsDevice(os.environ["AMZN_BRAKET_DEVICE_ARN"])

    circuit = Circuit().rx(0, FreeParameter("theta"))
    parameter_list = [0.1, 0.2, 0.3]
    
    for parameter in parameter_list:
        result = device.run(circuit, shots=1000, inputs={"theta": parameter})

    print("Test job completed.")
```

You can submit the algorithm script to run as a Hybrid Job with the following job script. When running the Hybrid Job on a QPU that supports parametric compilation, the circuit is compiled only on the first run. In following runs, the compiled circuit is reused, increasing the runtime performance of the Hybrid Job without any additional lines of code. 

```
from braket.aws import AwsQuantumJob

job = AwsQuantumJob.create(
    device=device_arn,
    source_module="algorithm_script.py",
)
```

**Note**  
Parametric compilation is supported on all superconducting, gate-based QPUs from Rigetti Computing with the exception of pulse level programs.

# Using PennyLane with Amazon Braket
(Advanced) PennyLane with Amazon Braket

Hybrid algorithms are algorithms that contain both classical and quantum instructions. The classical instructions are ran on classical hardware (an EC2 instance or your laptop), and the quantum instructions are ran either on a simulator or on a quantum computer. We recommend that you run hybrid algorithms using the Hybrid Jobs feature. For more information, see [When to use Amazon Braket Jobs](braket-jobs.md#braket-jobs-use).

Amazon Braket enables you to set up and run hybrid quantum algorithms with the assistance of the ** Amazon Braket PennyLane plugin**, or with the ** Amazon Braket Python SDK** and example notebook repositories. Amazon Braket example notebooks, based on the SDK, enable you to set up and run certain hybrid algorithms without the PennyLane plugin. However, we recommend PennyLane because it provides a richer experience.

 **About hybrid quantum algorithms** 

Hybrid quantum algorithms are important to the industry today because contemporary quantum computing devices generally produce noise, and therefore, errors. Every quantum gate added to a computation increases the chance of adding noise; therefore, long-running algorithms can be overwhelmed by noise, which results in faulty computation.

Pure quantum algorithms such as Shor's [(Quantum Phase Estimation example)](https://github.com/amazon-braket/amazon-braket-examples/tree/main/examples/advanced_circuits_algorithms/Quantum_Phase_Estimation) or Grover's [(Grover's example)](https://github.com/aws/amazon-braket-examples/tree/main/examples/advanced_circuits_algorithms/Grover) require thousands, or millions, of operations. For this reason, they can be impractical for existing quantum devices, which are generally referred to as *noisy intermediate-scale quantum* (NISQ) devices.

In hybrid quantum algorithms, quantum processing units (QPUs) work as co-processors for classic CPUs, specifically to speed up certain calculations in a classical algorithm. Circuit executions become much shorter, within reach of the capabilities of today's devices.

**Topics**
+ [

## Amazon Braket with PennyLane
](#pennylane-option)
+ [

## Hybrid algorithms in Amazon Braket example notebooks
](#braket-hybrid-workflow)
+ [

## Hybrid algorithms with embedded PennyLane simulators
](#hybrid-alorithms-pennylane)
+ [

## Adjoint gradient on PennyLane with Amazon Braket simulators
](#adjoint-gradient-pennylane)
+ [

# Using Hybrid Jobs and PennyLane to run a QAOA algorithm
](braket-jobs-run-qaoa-algorithm.md)
+ [

# Run hybrid workloads with PennyLane embedded simulators
](pennylane-embedded-simulators.md)

## Amazon Braket with PennyLane


Amazon Braket provides support for [PennyLane](https://pennylane.ai), an open-source software framework built around the concept of *quantum differentiable programming*. You can use this framework to train quantum circuits in the same way that you would train a neural network to find solutions for computational problems in quantum chemistry, quantum machine learning, and optimization.

The PennyLane library provides interfaces to familiar machine learning tools, including PyTorch and TensorFlow, to make training quantum circuits quick and intuitive.
+  **The PennyLane Library** -– PennyLane is pre-installed in Amazon Braket notebooks. For access to Amazon Braket devices from PennyLane, open a notebook and import the PennyLane library with the following command.

```
import pennylane as qml
```

Tutorial notebooks help you get started quickly. Alternatively, you can use PennyLane on Amazon Braket from an IDE of your choice.
+  **The Amazon Braket PennyLane plugin** — To use your own IDE, you can install the Amazon Braket PennyLane plugin manually. The plugin connects PennyLane with the [Amazon Braket Python SDK](https://github.com/aws/amazon-braket-sdk-python), so you can run circuits in PennyLane on Amazon Braket devices. To install the the PennyLane plugin, use the following command.

```
pip install amazon-braket-pennylane-plugin
```

The following example demonstrates how to set up access to Amazon Braket devices in PennyLane:

```
# to use SV1
import pennylane as qml
sv1 = qml.device("braket.aws.qubit", device_arn="arn:aws:braket:::device/quantum-simulator/amazon/sv1", wires=2)

# to run a circuit:
@qml.qnode(sv1)
def circuit(x):
    qml.RZ(x, wires=0)
    qml.CNOT(wires=[0,1])
    qml.RY(x, wires=1)
    return qml.expval(qml.PauliZ(1))

result = circuit(0.543)


#To use the local sim:
local = qml.device("braket.local.qubit", wires=2)
```

For tutorial examples and more information about PennyLane, see the [Amazon Braket examples repository](https://github.com/aws/amazon-braket-examples/tree/main/examples/pennylane).

The Amazon Braket PennyLane plugin enables you to switch between Amazon Braket QPU and embedded simulator devices in PennyLane with a single line of code. It offers two Amazon Braket quantum devices to work with PennyLane:
+  `braket.aws.qubit` for running with the Amazon Braket service's quantum devices, including QPUs and simulators
+  `braket.local.qubit` for running with the Amazon Braket SDK's local simulator

The Amazon Braket PennyLane plugin is open source. You can install it from the [PennyLane Plugin GitHub repository](https://github.com/aws/amazon-braket-pennylane-plugin-python).

For more information about PennyLane, see the documentation on the [PennyLane website](https://pennylane.ai).

## Hybrid algorithms in Amazon Braket example notebooks


Amazon Braket does provide a variety of example notebooks that do not rely on the PennyLane plugin for running hybrid algorithms. You can get started with any of these [Amazon Braket hybrid example notebooks](https://github.com/aws/amazon-braket-examples/tree/main/examples/hybrid_quantum_algorithms) that illustrate *variational methods*, such as the Quantum Approximate Optimization Algorithm (QAOA) or Variational Quantum Eigensolver (VQE).

The Amazon Braket example notebooks rely on the [Amazon Braket Python SDK](https://github.com/aws/amazon-braket-sdk-python). The SDK provides a framework to interact with quantum computing hardware devices through Amazon Braket. It is an open source library that is designed to assist you with the quantum portion of your hybrid workflow.

You can explore Amazon Braket further with our [example notebooks](https://github.com/aws/amazon-braket-examples).

## Hybrid algorithms with embedded PennyLane simulators


Amazon Braket Hybrid Jobs now comes with high performance CPU- and GPU-based embedded simulators from [PennyLane](https://github.com/PennyLaneAI/pennylane-lightning). This family of embedded simulators can be embedded directly within your hybrid jobs container and includes the fast state-vector `lightning.qubit` simulator, the `lightning.gpu` simulator accelerated using NVIDIA's [cuQuantum library](https://developer.nvidia.com/cuquantum-sdk), and others. These embedded simulators are ideally suited for variational algorithms such as quantum machine learning that can benefit from advanced methods such as the [adjoint differentiation method](https://docs.pennylane.ai/en/stable/introduction/interfaces.html#simulation-based-differentiation). You can run these embedded simulators on one or multiple CPU or GPU instances.

With Hybrid Jobs, you can now run your variational algorithm code using a combination of a classical co-processor and a QPU, an Amazon Braket on-demand simulator such as SV1, or directly using the embedded simulator from PennyLane.

The embedded simulator is already available with the Hybrid Jobs container, you need to decorate your main Python function with the `@hybrid_job` decorator. To use the PennyLane `lightning.gpu` simulator, you also need to specify a GPU instance in the `InstanceConfig` as shown in the following code snippet:

```
import pennylane as qml
from braket.jobs import hybrid_job
from braket.jobs.config import InstanceConfig


@hybrid_job(device="local:pennylane/lightning.gpu", instance_config=InstanceConfig(instanceType="ml.g4dn.xlarge"))
def function(wires):
    dev = qml.device("lightning.gpu", wires=wires)
    ...
```

Refer to the [example notebook](https://github.com/aws/amazon-braket-examples/blob/main/examples/hybrid_jobs/4_Embedded_simulators_in_Braket_Hybrid_Jobs/Embedded_simulators_in_Braket_Hybrid_Jobs.ipynb) to get started with using a PennyLane embedded simulator with Hybrid Jobs.

## Adjoint gradient on PennyLane with Amazon Braket simulators


With the PennyLane plugin for Amazon Braket, you can compute gradients using the adjoint differentiation method when running on the local state vector simulator or SV1.

 **Note:** To use the adjoint differentiation method, you must specify `diff_method='device'` in your `qnode`, and **not** `diff_method='adjoint'`. See the following example.

```
device_arn = "arn:aws:braket:::device/quantum-simulator/amazon/sv1"
dev = qml.device("braket.aws.qubit", wires=wires, shots=0, device_arn=device_arn)
                
@qml.qnode(dev, diff_method="device")
def cost_function(params):
    circuit(params)
    return qml.expval(cost_h)

gradient = qml.grad(circuit)
initial_gradient = gradient(params0)
```

**Note**  
Currently, PennyLane will compute grouping indices for QAOA Hamiltonians and use them to split the Hamiltonian into multiple expectation values. If you want to use SV1's adjoint differentiation capability when running QAOA from PennyLane, you will need reconstruct the cost Hamiltonian by removing the grouping indices, like so: `cost_h, mixer_h = qml.qaoa.max_clique(g, constrained=False) cost_h = qml.Hamiltonian(cost_h.coeffs, cost_h.ops)` 

# Using Hybrid Jobs and PennyLane to run a QAOA algorithm


In this section, you will use what you have learned to write an actual hybrid program using PennyLane with parametric compilation. You use the algorithm script to address a Quantum Approximate Optimization Algorithm (QAOA) problem. The program creates a cost function corresponding to a classical Max Cut optimization problem, specifies a parametrized quantum circuit, and uses a gradient descent method to optimize the parameters so that the cost function is minimized. In this example, we generate the problem graph in the algorithm script for simplicity, but for more typical use cases the best practice is to provide the problem specification through a dedicated channel in the input data configuration. The flag `parametrize_differentiable` defaults to `True` so you automatically get the benefits of improved runtime performance from parametric compilation on supported QPUs.

```
import os
import json
import time

from braket.jobs import save_job_result
from braket.jobs.metrics import log_metric

import networkx as nx
import pennylane as qml
from pennylane import numpy as np
from matplotlib import pyplot as plt

def init_pl_device(device_arn, num_nodes, shots, max_parallel):
    return qml.device(
        "braket.aws.qubit",
        device_arn=device_arn,
        wires=num_nodes,
        shots=shots,
        # Set s3_destination_folder=None to output task results to a default folder
        s3_destination_folder=None,
        parallel=True,
        max_parallel=max_parallel,
        parametrize_differentiable=True, # This flag is True by default.
    )

def start_here():
    input_dir = os.environ["AMZN_BRAKET_INPUT_DIR"]
    output_dir = os.environ["AMZN_BRAKET_JOB_RESULTS_DIR"]
    job_name = os.environ["AMZN_BRAKET_JOB_NAME"]
    checkpoint_dir = os.environ["AMZN_BRAKET_CHECKPOINT_DIR"]
    hp_file = os.environ["AMZN_BRAKET_HP_FILE"]
    device_arn = os.environ["AMZN_BRAKET_DEVICE_ARN"]

    # Read the hyperparameters
    with open(hp_file, "r") as f:
        hyperparams = json.load(f)

    p = int(hyperparams["p"])
    seed = int(hyperparams["seed"])
    max_parallel = int(hyperparams["max_parallel"])
    num_iterations = int(hyperparams["num_iterations"])
    stepsize = float(hyperparams["stepsize"])
    shots = int(hyperparams["shots"])

    # Generate random graph
    num_nodes = 6
    num_edges = 8
    graph_seed = 1967
    g = nx.gnm_random_graph(num_nodes, num_edges, seed=graph_seed)

    # Output figure to file
    positions = nx.spring_layout(g, seed=seed)
    nx.draw(g, with_labels=True, pos=positions, node_size=600)
    plt.savefig(f"{output_dir}/graph.png")

    # Set up the QAOA problem
    cost_h, mixer_h = qml.qaoa.maxcut(g)

    def qaoa_layer(gamma, alpha):
        qml.qaoa.cost_layer(gamma, cost_h)
        qml.qaoa.mixer_layer(alpha, mixer_h)

    def circuit(params, **kwargs):
        for i in range(num_nodes):
            qml.Hadamard(wires=i)
        qml.layer(qaoa_layer, p, params[0], params[1])

    dev = init_pl_device(device_arn, num_nodes, shots, max_parallel)

    np.random.seed(seed)
    cost_function = qml.ExpvalCost(circuit, cost_h, dev, optimize=True)
    params = 0.01 * np.random.uniform(size=[2, p])

    optimizer = qml.GradientDescentOptimizer(stepsize=stepsize)
    print("Optimization start")

    for iteration in range(num_iterations):
        t0 = time.time()

        # Evaluates the cost, then does a gradient step to new params
        params, cost_before = optimizer.step_and_cost(cost_function, params)
        # Convert cost_before to a float so it's easier to handle
        cost_before = float(cost_before)

        t1 = time.time()

        if iteration == 0:
            print("Initial cost:", cost_before)
        else:
            print(f"Cost at step {iteration}:", cost_before)

        # Log the current loss as a metric
        log_metric(
            metric_name="Cost",
            value=cost_before,
            iteration_number=iteration,
        )

        print(f"Completed iteration {iteration + 1}")
        print(f"Time to complete iteration: {t1 - t0} seconds")

    final_cost = float(cost_function(params))
    log_metric(
        metric_name="Cost",
        value=final_cost,
        iteration_number=num_iterations,
    )

    # We're done with the hybrid job, so save the result.
    # This will be returned in job.result()
    save_job_result({"params": params.numpy().tolist(), "cost": final_cost})
```

**Note**  
Parametric compilation is supported on all superconducting, gate-based QPUs from Rigetti Computing with the exception of pulse level programs.

# Run hybrid workloads with PennyLane embedded simulators


Lets look at how you can use embedded simulators from PennyLane on Amazon Braket Hybrid Jobs to run hybrid workloads. Pennylane's GPU-based embedded simulator, `lightning.gpu`, uses the [Nvidia cuQuantum library](https://developer.nvidia.com/cuquantum-sdk) to accelerate circuit simulations. The embedded GPU simulator is pre-configured in all of the Braket [job containers](https://github.com/amazon-braket/amazon-braket-containers) that users can use out of the box. In this page, we show you how to use `lightning.gpu` to speed up your hybrid workloads.

## Using `lightning.gpu` for QAOA workloads


Consider the Quantum Approximate Optimization Algorithm (QAOA) examples from this [notebook](https://github.com/amazon-braket/amazon-braket-examples/tree/main/examples/hybrid_jobs/2_Using_PennyLane_with_Braket_Hybrid_Jobs). To select an embedded simulator, you specify the `device` argument to be a string of the form: `"local:<provider>/<simulator_name>"`. For example, you would set `"local:pennylane/lightning.gpu"` for `lightning.gpu`. The device string you give to the Hybrid Job when you launch is passed to the job as the environment variable `"AMZN_BRAKET_DEVICE_ARN"`.

```
device_string = os.environ["AMZN_BRAKET_DEVICE_ARN"]
prefix, device_name = device_string.split("/")
device = qml.device(simulator_name, wires=n_wires)
```

In this page, compare the two embedded PennyLane state vector simulators `lightning.qubit` (which is CPU-based) and `lightning.gpu` (which is GPU-based). Provide the simulators with custom gate decompositions to compute various gradients.

Now you are ready to prepare the hybrid job launching script. Run the QAOA algorithm using two instance types: `ml.m5.2xlarge` and `ml.g4dn.xlarge`. The `ml.m5.2xlarge` instance type is comparable to a standard developer laptop. The `ml.g4dn.xlarge` is an accelerated computing instance that has a single NVIDIA T4 GPU with 16GB of memory.

To run the GPU, we first need to specify a compatible image and the correct instance (which defaults to a `ml.m5.2xlarge` instance).

```
from braket.aws import AwsSession
from braket.jobs.image_uris import Framework, retrieve_image

image_uri = retrieve_image(Framework.PL_PYTORCH, AwsSession().region)
instance_config = InstanceConfig(instanceType="ml.g4dn.xlarge")
```

We then need to input these to the hybrid job decorator, along with updated device parameters in both the system and hybrid job arguments.

```
@hybrid_job(
        device="local:pennylane/lightning.gpu",
        input_data=input_file_path,
        image_uri=image_uri,
        instance_config=instance_config)
def run_qaoa_hybrid_job_gpu(p=1, steps=10):
    params = np.random.rand(2, p)

    braket_task_tracker = Tracker()

    graph = nx.read_adjlist(input_file_path, nodetype=int)
    wires = list(graph.nodes)
    cost_h, _mixer_h = qaoa.maxcut(graph)

    device_string = os.environ["AMZN_BRAKET_DEVICE_ARN"]
    prefix, device_name = device_string.split("/")
    dev= qml.device(simulator_name, wires=len(wires))
    ...
```

**Note**  
If you specify the `instance_config` as using a GPU-based instance, but choose the `device` to be the embedded CPU-based simulator (`lightning.qubit`), the GPU will not be used. Make sure to use the embedded GPU-based simulator if you wish to target the GPU\$1

The mean iteration time for the `m5.2xlarge` instance is about 73 seconds, while for the `ml.g4dn.xlarge` instance it is about 0.6 seconds. For this 21-qubit workflow, the GPU instance gives us a 100x speedup. If you look at the Amazon Braket Hybrid Jobs [pricing page](https://aws.amazon.com/braket/pricing/), you can see that the cost per minute for an `m5.2xlarge` instance is \$10.00768, while for the `ml.g4dn.xlarge` instance it is \$10.01227. In this instance it is faster and cheaper to run on the GPU instance.

## Quantum machine learning and data parallelism


If your workload type is quantum machine learning (QML) that trains on datasets, you can further accelerate your workload using data parallelism. In QML, the model contains one or more quantum circuits. The model may or may not also contain classical neural nets. When training the model with the dataset, the parameters in the model are updated to minimize the loss function. A loss function is usually defined for a single data point, and the total loss for the average loss over the whole dataset. In QML, the losses are usually computed in serial before averaging to total loss for gradient computations. This procedure is time consuming, especially when there are hundreds of data points.

Because the loss from one data point does not depend on other data points, the losses can be evaluated in parallel\$1 Losses and gradients associated with different data points can be evaluated at the same time. This is known as data parallelism. With SageMaker's distributed data parallel library, Amazon Braket Hybrid Jobs make it easier for you to use data parallelism to accelerate your training.

Consider the following QML workload for data parallelism which uses the [Sonar dataset](https://archive.ics.uci.edu/dataset/151/connectionist+bench+sonar+mines+vs+rocks) dataset from the well-known UCI repository as an example for binary classification. The Sonar dataset have 208 data points each with 60 features that are collected from sonar signals bouncing off materials. Each data points is either labeled as "M" for mines or "R" for rocks. Our QML model consists of an input layer, a quantum circuit as a hidden layer, and an output layer. The input and output layers are classical neural nets implemented in PyTorch. The quantum circuit is integrated with the PyTorch neural nets using PennyLane's qml.qnn module. See our [example notebooks](https://github.com/aws/amazon-braket-examples) for more detail about the workload. Like the QAOA example above, you can harness the power of GPU by using embedded GPU-based simulators like PennyLane's `lightning.gpu` to improve the performance over embedded CPU-based simulators.

To create a hybrid job, you can call `AwsQuantumJob.create` and specify the algorithm script, device, and other configurations through its keyword arguments.

```
instance_config = InstanceConfig(instanceType='ml.g4dn.xlarge')

hyperparameters={"nwires": "10",
                 "ndata": "32",
                 ...
}

job = AwsQuantumJob.create(
    device="local:pennylane/lightning.gpu",
    source_module="qml_source",
    entry_point="qml_source.train_single",
    hyperparameters=hyperparameters,
    instance_config=instance_config,
    ...
)
```

In order to use data parallelism, you need to modify few lines of code in the algorithm script for the SageMaker distributed library to correctly parallelize the training. First, you import the `smdistributed` package which does most of the heavy-lifting for distributing your workloads across multiple GPUs and multiple instances. This package is preconfigured in the Braket PyTorch and TensorFlow containers. The `dist` module tells our algorithm script what the total number of GPUs for the training (`world_size`) is as well as the `rank` and `local_rank` of a GPU core. `rank` is the absolute index of a GPU across all instances, while `local_rank` is the index of a GPU within an instance. For example, if there are four instances each with eight GPUs allocated for the training, the `rank` ranges from 0 to 31 and the `local_rank` ranges from 0 to 7.

```
import smdistributed.dataparallel.torch.distributed as dist

dp_info = {
    "world_size": dist.get_world_size(),
    "rank": dist.get_rank(),
    "local_rank": dist.get_local_rank(),
}
batch_size //= dp_info["world_size"] // 8
batch_size = max(batch_size, 1)
```

Next, you define a `DistributedSampler` according to the `world_size` and `rank` and then pass it into the data loader. This sampler avoids GPUs accessing the same slice of a dataset.

```
train_sampler = torch.utils.data.distributed.DistributedSampler(
    train_dataset,
    num_replicas=dp_info["world_size"],
    rank=dp_info["rank"]
)
train_loader = torch.utils.data.DataLoader(
    train_dataset,
    batch_size=batch_size,
    shuffle=False,
    num_workers=0,
    pin_memory=True,
    sampler=train_sampler,
)
```

Next, you use the `DistributedDataParallel` class to enable data parallelism.

```
from smdistributed.dataparallel.torch.parallel.distributed import DistributedDataParallel as DDP

model = DressedQNN(qc_dev).to(device)
model = DDP(model)
torch.cuda.set_device(dp_info["local_rank"])
model.cuda(dp_info["local_rank"])
```

The above are the changes you need to use data parallelism. In QML, you often want to save results and print training progress. If each GPU runs the saving and printing command, the log will be flooded with the repeated information and the results will overwrite each other. To avoid this, you can only save and print from the GPU that has `rank` 0.

```
if dp_info["rank"]==0:
    print('elapsed time: ', elapsed)
    torch.save(model.state_dict(), f"{output_dir}/test_local.pt")
    save_job_result({"last loss": loss_before})
```

 Amazon Braket Hybrid Jobs supports `ml.g4dn.12xlarge` instance types for the SageMaker distributed data parallel library. You configure the instance type through the `InstanceConfig` argument in Hybrid Jobs. For the SageMaker distributed data parallel library to know that data parallelism is enabled, you need to add two additional hyperparameters, `"sagemaker_distributed_dataparallel_enabled"` setting to `"true"` and `"sagemaker_instance_type"` setting to the instance type you are using. These two hyperparameters are used by `smdistributed` package. Your algorithm script does not need to explicitly use them. In Amazon Braket SDK, it provides a convenient keyword argument `distribution`. With `distribution="data_parallel"` in hybrid job creation, the Amazon Braket SDK automatically inserts the two hyperparameters for you. If you use the Amazon Braket API, you need to include these two hyperparameters.

With the instance and data parallelism configured, you can now submit your hybrid job. There are 4 GPUs in a `ml.g4dn.12xlarge` instance. When you set `instanceCount=1` , the workload is distributed across the 8 GPUs in the instance. When you set `instanceCount` greater than one, the workload is distributed across GPUs available in all instances. When using multiple instances, each instance incurs a charge based on how much time you use it. For example, when you use four instances, the billable time is four times the run time per instance because there are four instances running your workloads at the same time.

```
instance_config = InstanceConfig(instanceType='ml.g4dn.12xlarge',
                                 instanceCount=1,
)

hyperparameters={"nwires": "10",
                 "ndata": "32",
                 ...,
}

job = AwsQuantumJob.create(
    device="local:pennylane/lightning.gpu",
    source_module="qml_source",
    entry_point="qml_source.train_dp",
    hyperparameters=hyperparameters,
    instance_config=instance_config,
    distribution="data_parallel",
    ...
)
```

**Note**  
In the above hybrid job creation, `train_dp.py` is the modified algorithm script for using data parallelism. Keep in mind that data parallelism only works correctly when you modify your algorithm script according to the above section. If the data parallelism option is enabled without a correctly modified algorithm script, the hybrid job may throw errors, or each GPU may repeatedly process the same data slice, which is inefficient.

If used correctly, using multiple instances can lead to orders of magnitude reduction in both time and cost. See the [ example notebook for more details](https://github.com/amazon-braket/amazon-braket-examples/blob/main/examples/hybrid_jobs/5_Parallelize_training_for_QML/Parallelize_training_for_QML.ipynb).

# Using CUDA-Q with Amazon Braket
(Advanced) CUDA-Q with Amazon Braket

NVIDIA's CUDA-Q is a software library designed for programming hybrid quantum algorithms that combine CPUs, GPUs, and Quantum processing units (QPUs). It provides a unified programming model, allowing developers to express both classical and quantum instructions within a single program, streamlining workflows. CUDA-Q accelerates quantum program simulation and runtime with its built-in CPU and GPU simulators. CUDA-Q is available with native Braket notebook instances (NBIs) and Amazon Braket Hybrid Jobs.

**Topics**
+ [

## CUDA-Q in NBIs
](#braket-cuda-q-nbis)
+ [

## CUDA-Q in Hybrid Jobs
](#braket-cuda-q-hybrid-jobs)

## CUDA-Q in NBIs


CUDA-Q is installed by default in the Braket NBI environment. You can open a CUDA-Q example notebook by going to the Jupyter launcher page and selecting the CUDA-Q and Braket tile. This opens the example notebook `0_Getting_started_with_CUDA-Q.ipynb` in the main window. For more CUDA-Q examples, see the left panel in the `nvidia_cuda_q/` directory.

You can also verify the version of CUDA-Q or any other third-party package installed in your NBI. For example, you can run the following command in a notebook code cell to verify the versions of CUDA-Q, Qiskit, PennyLane, and Braket packages that are installed in the environment.

```
%pip freeze | grep -i -e cudaq -e qiskit -e pennylane -e braket
```

## CUDA-Q in Hybrid Jobs


Using CUDA-Q on [Amazon Braket Hybrid Jobs](https://docs.aws.amazon.com/braket/latest/developerguide/braket-jobs.html) offers a flexible, on-demand computing environment. Computational instances run only for the duration of your workload, ensuring you pay only for what you use. Amazon Braket Hybrid Jobs also provides a scalable experience. Users can start with smaller instances for prototyping and testing, then scale up to larger instances capable of handling greater workloads for full experiments.

Amazon Braket Hybrid Jobs support GPUs that are essential for maximizing CUDA-Q's potential. GPUs significantly speed up quantum program simulations compared to CPU-based simulators, especially when working with high qubit count circuits. Parallelization becomes straightforward when using CUDA-Q on Amazon Braket Hybrid Jobs. Hybrid Jobs simplifies the distribution of circuit sampling and observable evaluations across multiple computational nodes. This seamless parallelization of CUDA-Q workloads allows users to focus more on developing their workloads rather than setting up infrastructure for large-scale experiments.

To get started, see the [CUDA-Q starter example](https://github.com/amazon-braket/amazon-braket-examples/blob/main/examples/nvidia_cuda_q/0_Getting_started_with_CUDA-Q.ipynb) on the Amazon Braket examples Github to use a CUDA-Q hybrid jobs container provided by Braket.

The following code snippet is a `hello-world` example for running a CUDA-Q program with Amazon Braket Hybrid Jobs.

```
image_uri = retrieve_image(Framework.CUDAQ, AwsSession().region)

@hybrid_job(device='local:nvidia/qpp-cpu', image_uri=image_uri)
def hello_quantum():
    import cudaq

    # define the backend
    device=get_job_device_arn()
    cudaq.set_target(device.split('/')[-1])

    # define the Bell circuit
    kernel = cudaq.make_kernel()
    qubits = kernel.qalloc(2)
    kernel.h(qubits[0])
    kernel.cx(qubits[0], qubits[1])

    # sample the Bell circuit
    result = cudaq.sample(kernel, shots_count=1000)
    measurement_probabilities = dict(result.items())
    
    return measurement_probabilities
```

The above example simulates a Bell circuit on a CPU simulator. This example runs locally on your laptop or Braket Jupyter notebook. Because of the `local=True` setting, when you run this script, a container will start in your local environment to run the CUDA-Q program for testing and debugging. After you finish testing, you can remove the `local=True` flag and run your job on AWS. To learn more, see [Working with Amazon Braket Hybrid Jobs](braket-jobs.md).

If your workloads have a high qubit count, a large number of circuits or a large number of iterations, you can use more powerful CPU computing resources by specifying the `instance_config` setting. The following code snippet shows how to configure the `instance_config` setting in the `hybrid_job` decorator. For more information about supported instance types, see [Configure your hybrid job instance](braket-jobs-configure-job-instance-for-script.md). For a list of instance types, see [Amazon EC2 Instance types](https://aws.amazon.com/ec2/instance-types/).

```
@hybrid_job(
    device="local:nvidia/qpp-cpu",
    image_uri=image_uri,
    instance_config=InstanceConfig(instanceType="ml.c5.2xlarge"),
)
def my_job_script():
    ...
```

For more demanding workloads, you can run your workloads on a CUDA-Q GPU simulator. To enable a GPU simulator, use the backend name `nvidia`. The `nvidia` backend operates as a CUDA-Q GPU simulator. Next, select an Amazon EC2 instance type that supports an NVIDIA GPU. The following code snippet shows the GPU-configured `hybrid_job` decorator.

```
@hybrid_job(
    device="local:nvidia/nvidia",
    image_uri=image_uri,
    instance_config=InstanceConfig(instanceType="ml.g4dn.xlarge"),
)
def my_job_script():
    ...
```

Amazon Braket Hybrid Jobs and NBIs support parallel GPU simulations with CUDA-Q. You can parallelize the evaluation of multiple observables or multiple circuits to boost the performance of your workload. To parallelize multiple observables, make the following changes to your algorithm script.

Set the `mgpu` option of the `nvidia` backend. This is required to parallelize the observables. The parallelization uses MPI for communication between GPUs, so MPI needs to be initialized before the execution and finalized after it.

Next, specify the execution mode by setting `execution=cudaq.parallel.mpi`. The following code snippet shows these changes.

```
cudaq.set_target("nvidia", option="mqpu")
cudaq.mpi.initialize()
result = cudaq.observe(
    kernel, hamiltonian, shots_count=n_shots, execution=cudaq.parallel.mpi
)
cudaq.mpi.finalize()
```

In the `hybrid_job` decorator specify an instance type that hosts multiple GPUs as shown in the following code snippet.

```
@hybrid_job(
    device="local:nvidia/nvidia-mqpu",
    instance_config=InstanceConfig(instanceType="ml.g4dn.12xlarge", instanceCount=1),
    image_uri=image_uri,
)
def parallel_observables_gpu_job(sagemaker_mpi_enabled=True):
    ...
```

The [parallel simulations notebook](https://github.com/amazon-braket/amazon-braket-examples/blob/main/examples/nvidia_cuda_q/5_Multiple_GPU_simulations.ipynb) in the Amazon Braket examples Github provide end-to-end examples that demonstrate how to run quantum program simulations on GPU backends and perform parallel simulations of observables and circuit batches.

### Running your workloads on quantum computers


After completing simulator testing, you can transition to running experiments on QPUs. Just switch the target to an Amazon Braket QPU, such as the IQM, IonQ, or Rigetti devices. The following code snippet illustrates how to set the target to the IQM Garnet device. For a list of available QPUs, see the [Amazon Braket console](https://console.aws.amazon.com/braket/home).

```
device_arn = "arn:aws:braket:eu-north-1::device/qpu/iqm/Garnet"
cudaq.set_target("braket", machine=device_arn)
```

For more information about Hybrid Jobs, see [Working with Amazon Braket Hybrid Jobs](braket-jobs.md) in the developer guide. To learn more about CUDA-Q, see the [NVIDIA CUDA-Q documentation](https://nvidia.github.io/cuda-quantum/latest/index.html).