Skip to content

Braket hybrid - #2

Open
vsoch wants to merge 26 commits into
mainfrom
braket-hybrid
Open

vsoch wants to merge 26 commits into
mainfrom
braket-hybrid

Conversation

@vsoch

@vsoch vsoch commented Oct 6, 2026

Copy link
Copy Markdown
Member

This branch includes work for braket, qrmi, and IonQ. I am holding off on a final description until I finalize the designs and will redo the commits with a cleaner structure.

vsoch added 22 commits August 12, 2026 09:09
Signed-off-by: vsoch <vsoch@users.noreply.github.com>
Signed-off-by: vsoch <vsoch@users.noreply.github.com>
Signed-off-by: vsoch <vsoch@users.noreply.github.com>
qrmi with ibm quantum is too expensive for experiments.

Signed-off-by: vsoch <vsoch@users.noreply.github.com>
Signed-off-by: vsoch <vsoch@users.noreply.github.com>
Signed-off-by: vsoch <vsoch@users.noreply.github.com>
Signed-off-by: vsoch <vsoch@users.noreply.github.com>
Signed-off-by: vsoch <vsoch@users.noreply.github.com>
Signed-off-by: vsoch <vsoch@users.noreply.github.com>
validator.py, submit_scout and acquisition_token_env had no callers.
wrap.py imports the token suffix instead of repeating the literal.
open_session now checks its arguments before importing qrmi, so a
missing resource is reported even where qrmi is not installed, which
is what two of the unit tests already expected.
Only job.new is replayed on reload, so running jobs looked idle
afterwards and their cores could be handed out twice. The plugin now
uses the job state it is given. Grace timers are tracked and destroyed
on unload, since one firing later would crash the job manager.

The used_cores budget fed no decision and is gone. query reports the
capacity numbers instead. Rejected jobs are no longer marked protected,
which left a pending update for job.destroy to complain about. A new
integration test covers unload and reload.
The last section assumed 16 cores. Fluxion refuses a pair bigger than
the graph before the budget is asked, so on a smaller box the test
failed for the wrong reason.
The match_coschedule check in add-hold is a satisfiability test against
the empty graph, not a live fit, and the comments claimed otherwise. A
malformed request now warns and continues instead of refusing the
submit as if the pair did not fit.
The integration scripts live in tests/integration, the graph type is
qdevice, the CLI shim is cli-plugins/quantum.py, and the license text
was copied from another project. Ignore build artifacts.
Comments say what is non obvious and why, and no more. The README now
describes the pair, how to install and populate, and how to run every
test script.
…ruction

Vendor SDKs and the flux bindings were imported inside the functions that
used them, so a missing package or credential only surfaced once a session
was being opened. Every optional import now happens once at module level,
bound to None when absent, and constructing a backend is the check. A
missing SDK or missing credentials raise BackendError from __init__, the
selector and the CLI report the message, and the scout cancels the held
job instead of exiting and leaving it parked. credentials_present is gone
and credential_note says where the credentials came from.

The shared key names moved to keys.py so the wrapper, which runs in every
classical job, no longer imports the backends package or any SDK.
The probe never asked GetQuantumTask for QueueInfo, so it never saw a
task's queue or position and fell back to the device counters, which lag
a submit by minutes. It also gave up after a fixed three minutes whatever
the hold was, and submitted the token task first, so even a correct
finishing order proved nothing.

Now the control goes in first and the token task after it, both are
polled with QueueInfo until the token task finishes with the hold up for
the whole --max-seconds, and the verdict reads only per-task evidence:
the job association, which queue each waited in and where, and the
finishing order from the service timestamps. A device that served both
on arrival says NO CONTENTION rather than claiming an order.

Also: --log tees everything to a file, --inspect reads old tasks back,
--pace measures whether a device serves one task at a time, Aquila gets
the analog program it needs with --check-program to validate it locally,
and a heartbeat line says the wait is a wait and not a hang.

On IonQ Forte this showed a token task submitted after a control queue
at Priority position 1 against Normal position 3, and finish first.

Signed-off-by: vsoch <vsoch@users.noreply.github.com>
…ages

A user picks the vendor and the device and nothing else changes between
vendors. The plugin offers --quantum-device, --quantum-hold,
--quantum-hold-max, --quantum-wait and --quantum-dry-run, collects them
once, applies the dry run, and each backend maps them to its own terms in
scout_options. The session hold is Braket's hybrid job and IonQ's and
IBM's session; probe is a front of queue job on any vendor. A hold a
vendor cannot take fails at submit, before anything is held. The hold
limit follows the job's duration, so a walltime sets it, and
FLUX_QUANTUM_MOCK implies a dry run.

The twenty or so vendor flags are gone. What was tuning rather than a
description of the job, the Braket hold instance, warm-up shots, the
QRMI resource type, the mock's queue knobs, is a FLUX_QUANTUM_* variable
listed in each backend's docstring.

The base class carries the common logic: a simulator attribute, the holds
a vendor supports, dry_run and check_hold. Vendors with more than one
file are packages: braket/ holds the backend and the hybrid job entry
point, which uploads as a single file so no module named braket lands in
the container next to the SDK. Tests follow, under tests/unit/vendors/,
since a test package named braket on the test path shadows the SDK.

Signed-off-by: vsoch <vsoch@users.noreply.github.com>
…lator

Nothing but the standard library, the key from IONQ_API_KEY. The session
hold creates a session on the backend, warms it up with a one qubit job
inside it, and releases the classical job once IonQ reports the session
active. The session id is what the classical job is handed. Sessions are
in beta, and an account without them is told to hold with a probe.

A dry run sends every job to the simulator, which IonQ does not charge
for, with the noise model of the backend asked for so the circuit still
compiles the way the hardware would. The fake service in ionq/fake.py
stands in for api.ionq.co so the backend and the whole pipeline run with
no key at all, which CI now does, with and without sessions.

Signed-off-by: vsoch <vsoch@users.noreply.github.com>
Sessions go created, started, ended, which are the service's status values,
and jobs carry submitted_at, started_at, completed_at and
execution_duration_ms. FAKE_IONQ_SECONDS gives a job a minimum time in each
state, so an experiment against the fake has timings with a shape. The
nested experiments checkout is ignored.

Signed-off-by: vsoch <vsoch@users.noreply.github.com>
As the service's do. A session past its limit reads as ended, a job
submitted into it is refused, and anything queued in it is cancelled. The
experiments' hold-max check needs that to be exercisable without a key.

Signed-off-by: vsoch <vsoch@users.noreply.github.com>
Signed-off-by: vsoch <vsoch@users.noreply.github.com>
@vsoch
vsoch force-pushed the braket-hybrid branch 6 times, most recently from 635e6bb to dc1378a Compare October 8, 2026 22:37
Signed-off-by: vsoch <vsoch@users.noreply.github.com>
Signed-off-by: vsoch <vsoch@users.noreply.github.com>
vsoch added 2 commits October 21, 2026 10:10
Signed-off-by: vsoch <vsoch@users.noreply.github.com>
Signed-off-by: vsoch <vsoch@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant