amazon-braket / amazon-braket-sdk-python

A Python SDK for interacting with quantum devices on Amazon Braket
https://aws.amazon.com/braket/
Apache License 2.0
301 stars 117 forks source link

S3 fails to validate device.run_batch() with a named credentials profile #272

Closed eth-n closed 2 years ago

eth-n commented 3 years ago

Describe the bug device.run_batch() fails to validate with S3 when using a named credentials profile to authenticate the boto3 Session. Notably, device.run() works for a single circuit with no change to the authentication procedure. The error specifically is,

ValidationException: An error occurred (ValidationException) when calling the CreateQuantumTask operation: 
Caller doesn't have access to amazon-braket-my-bucket-name or it doesn't exist.

There is already a docs page about this error at https://docs.aws.amazon.com/braket/latest/developerguide/braket-troubleshooting-create-fail.html, but the proposed solution is to make sure the S3 resource exists.

To reproduce When the IAM user has PowerUserAccess attached, the below program fails. Even when the IAM user only has Braket and S3 FullAccess, (EC2 and billing read-only are in the user group as well, but those seem irrelevant here), the IAM policy simulator believes create_task should work correctly (if there's a mistake on my part below, maybe there's a bug in the policy simulator?).

Here's a standalone program that reproduces the bug, assuming profilename is a named profile in ~/.aws/credentials.

import boto3
from braket.aws import AwsDevice, AwsSession
from braket.circuits import Circuit
from braket.circuits.gates import *

c0 = Circuit([Instruction(Gate.I(), 0), Instruction(Gate.I(), 1)])
c1 = Circuit([Instruction(Gate.X(), 0), Instruction(Gate.I(), 1)])
c2 = Circuit([Instruction(Gate.I(), 0), Instruction(Gate.X(), 1)])
c3 = Circuit([Instruction(Gate.X(), 0), Instruction(Gate.X(), 1)])

list_of_circuits = [c0, c1, c2, c3]
some_circuit = Circuit([Instruction(Gate.H(), 0), Instruction(Gate.H(), 1)])

session = boto3.Session(profile_name="profilename", region_name="us-east-1")
brkt = session.client("braket")
braket_aws_session = AwsSession(session, brkt)

BUCKET = "amazon-braket-my-bucket-name"    # already exists in the same region as the IonQ machine
KEY_PREFIX = "my/key/folder"   # already exists in the bucket
s3_folder = AwsSession.S3DestinationFolder(BUCKET, KEY_PREFIX)

# I used this to verify that I can access the (existing) bucket
# s3 = session.client("s3")
# all_objects = s3.list_objects(Bucket=BUCKET) 
# print(all_objects)

device = AwsDevice("arn:aws:braket:::device/qpu/ionq/ionQdevice", aws_session=braket_aws_session)
batch = device.run_batch(list_of_circuits, s3_folder, shots=100)

# Also, passing an aws_session to device.run_batch throws,
#     TypeError: __init__() got multiple values for argument 'aws_session',
# with or without passing it to AwsDevice.
# device = AwsDevice("arn:aws:braket:::device/qpu/ionq/ionQdevice")
# batch_task = device.run_batch(list_of_circuits, s3_folder, shots=100,  aws_session=braket_aws_session)

# Passing a single circuit to device.run() works correctly
# single_task = device.run(some_circuit, s3_folder, shots=100)

Expected behavior A batch of tasks should run all of the circuits passed to device.run_batch().

System information

kshitijc commented 3 years ago

Thanks for reporting the issue @eth-n!

Could you please confirm whether the following snippet works correctly for you?

# Instead of
# batch_task = device.run_batch(list_of_circuits, s3_folder, shots=100,  aws_session=braket_aws_session)

# This is essentially what device.run_batch() does (with the exception of how the aws_session is handled)
batch_task = AwsQuantumTaskBatch(
            braket_aws_session,
            "arn:aws:braket:::device/qpu/ionq/ionQdevice",
            list_of_circuits,
            s3_folder,
            100,
            max_parallel=AwsDevice.DEFAULT_MAX_PARALLEL
        )

Additionally, another alternative to unblock this use case while we try to fix the issue here, might be updating the AWS_PROFILE to be used. You could execute the command export AWS_PROFILE=profilename to allow the profile profilename to be used by default.

christianbmadsen commented 3 years ago

@eth-n were you able to verify the code snippet provided from @kshitijc? Are you blocked on your end? Thank you!

eth-n commented 2 years ago

Sorry for the long pause, our focus shifted around a bit, but I'm back to running some experiments on the device. @kshitijc, your snippet does work. @christianbmadsen, I was not blocked, I'd just put the tasks in a for loop and pushed the result objects to a list, which worked well enough. I'm not running a very large number of circuits per batch at the moment, but had wanted to have the most streamlined process anyways, especially if I should need to do so in the future.

edit to add: In the workflow, I've mostly found I have to save the task IDs in a code cell as I submit tasks anyways to be able to recall them from S3 and do followup analysis from those result objects. That way if I kill my Jupyter session I can still tell which task is which from the growing task list (double trouble if I want to remember which id is which task from a few months ago...).

virajvchaudhari commented 2 years ago

Thanks for the feedback @eth-n; it appears that you have been unblocked. If you have any further problems, please feel free to re-open the issue.