Repository navigation
QFw: run Pulser programs on Pasqal devices through QRMI - #158
Conversation
A recipe and two scripts so QFw developers can run the Pulser example of openQSE/QFw#158 on Pasqal Cloud from a QFw checkout in one command. pasqal_token.py turns the developer's own Pasqal Cloud login into a token in a private env file; run_pulser_example.sh passes it to docker exec with --env-file, allocates the two-group job and runs qfw_pulser.sh. This is a testing aid for reviewing #158, not meant to merge as is. Assisted-by: Claude Opus 5.5 via Claude Code Signed-off-by: Aleksander Wennersteen <aleksander.wennersteen@pasqal.com>
|
An AI agent wrote this Design question: how a Pasqal QPM authenticates An open choice in this PR that we'd like your view on before it settles. What the PR does nowQFw opens every QRMI resource with
An operator can instead set a Pasqal service account as With What that costs
OptionsA. Keep it as is (token, or operator service account).
B. Let QRMI read Pasqal's own config when no reservation credential is bound.
C. Like B, but only forward an explicitly set
We lean towards C:
Happy to add it to this PR, or as a follow-up, whichever you prefer. |
A recipe and two scripts so QFw developers can run the Pulser example of openQSE/QFw#158 on Pasqal Cloud from a QFw checkout in one command. pasqal_token.py turns the developer's own Pasqal Cloud login into a token in a private env file; run_pulser_example.sh passes it to docker exec with --env-file, allocates the two-group job and runs qfw_pulser.sh. This is a testing aid for reviewing #158, not meant to merge as is. Assisted-by: Claude Opus 5.5 via Claude Code Signed-off-by: Aleksander Wennersteen <aleksander.wennersteen@pasqal.com>
ad0df1d to
bf81496
Compare
A recipe and two scripts so QFw developers can run the Pulser example of openQSE/QFw#158 on Pasqal Cloud from a QFw checkout in one command. pasqal_token.py turns the developer's own Pasqal Cloud login into a token in a private env file; run_pulser_example.sh passes it to docker exec with --env-file, allocates the two-group job and runs qfw_pulser.sh. This is a testing aid for reviewing #158, not meant to merge as is. Assisted-by: Claude Opus 5.5 via Claude Code Signed-off-by: Aleksander Wennersteen <aleksander.wennersteen@pasqal.com>
bf81496 to
725acf0
Compare
A recipe and two scripts so QFw developers can run the Pulser example of openQSE/QFw#158 on Pasqal Cloud from a QFw checkout in one command. pasqal_token.py turns the developer's own Pasqal Cloud login into a token in a private env file; run_pulser_example.sh passes it to docker exec with --env-file, allocates the two-group job and runs qfw_pulser.sh. This is a testing aid for reviewing #158, not meant to merge as is. Assisted-by: Claude Opus 5.5 via Claude Code Signed-off-by: Aleksander Wennersteen <aleksander.wennersteen@pasqal.com>
DougSO
left a comment
There was a problem hiding this comment.
Thanks for this, and for the end-to-end run on EMU_FREE. The design reads well and the code looks right to me. There is one change I'd still like to see, then answers to your questions.
1. After merging main, a reservation-bound get_backend_info fails on QDMI devices
Main has moved since this branch was cut. #163 gave the QDMI driver its own get_backend_info(), which takes no arguments (qdmi_driver.py L141 on main), and QDMI is the default library for that call. When a caller names a reservation, this PR passes a credential to whichever driver serves the call (frontend.py L146-L152). So on an IQM or Braket device, and on IBM once #156 lands, that call fails with TypeError: QdmiDriver.get_backend_info() got an unexpected keyword argument 'credential'. Nothing calls it that way yet, because only the Pulser client names a reservation, and only on Pasqal devices. But the telemetry API now offers reservation_id on every QPM, so the next caller would hit it.
When you merge main, could QdmiDriver.get_backend_info take credential=None and open the device with it, self._device(credential)? A test through the frontend for a QDMI-routed device would keep it working. The QRMI driver's IBM and IQM branches ignore the credential too (qrmi_driver.py L1073-L1092). Using it there as well would make a reservation-bound read mean the same thing on every device.
2. Smaller, optional
Main also added get_qpm_telemetry() in qfw_lookup_service.py (L84 on main). QFwConnection.fetch_available_devices builds the same telemetry binding by hand (L316-L317) and could use it instead.
Your questions, from #157 and your comment here
- Reusing
info["circuit"]andcircuit_formatsfor a Pulser sequence is fine for now. A rename can wait until a second non-circuit format comes along, as your design note says. - Bit 3 of
QPMCapabilityis free. Nothing in QFw, the DEFw v2 prototype branch, DEFw or the cluster repo uses it. setup/pulser-requirements.txtfits. Once this merges, the cluster image will need to install it, which is the first commit of QFw-SLURM-Cluster#60.- Authentication: what the PR does now, option C, looks right to me. A config root is forwarded only when the operator sets one, and
$HOMEnever is. Per-user tokens in a credential DB still expire, as your design note says. Calling the credential providers'refreshhook can be a follow-up.
725acf0 to
73294f2
Compare
|
An AI agent wrote this Thanks! Rebased on main 7e77742 and force-pushed; the changes are squashed into the existing commits.
Authentication: left as option C. Calling the providers' |
73294f2 to
fa6d7ce
Compare
|
An AI agent wrote this One more force-push (fa6d7ce, squashed into the third commit): |
|
Thanks, both of those look right to me, and good catch on The only thing left is a rebase. #156 merged just after you rebased, so main is
# The Pasqal Cloud project a job is billed to when the user's
# credential DB entry names none. See drivers/qrmi_driver.py.
"project_id": device.get("project-id"),
# The device's API URL, which a Pasqal resource opens against when
# nothing more specific names one.
"url": device.get("url"),
# Device-owned object storage for QRMI's IBMQuantumSystem.
# Storage credentials stay in the credential DB.
Nothing else in the two PRs overlaps. I resolved it this way locally and all |
A QPM runs gate circuits, sent in the circuit envelope as OpenQASM 2 or QPY. A Pasqal neutral-atom device runs a Pulser sequence instead. This adds the format, the way a client finds a QPM that runs it, and the design note for the Pasqal work that follows. util.circuit_payload gains `pulser-abstract-repr`: a Pulser sequence in Pulser's JSON abstract representation, read with pulser_sequence() and written with encode_pulser_sequence(). A QPM declares it in circuit_formats only if it runs it, and a Pasqal shim QPM declares it alone. A circuit QPM refuses a Pulser sequence by name, and a Pulser QPM refuses a circuit by name, before anything reaches a provider. A client looks a QPM up by capability bits, and the shim advertised the superconducting bit for every device. QPMCapability gains QPM_CAP_NEUTRAL_ATOM; the shim advertises it for a neutral-atom device and the superconducting bit for the others, as before. The client selection table gains `pasqal` (provider shim, hardware, neutral-atom), so a Pulser client finds the Pasqal QPM and a Qiskit client looking for a superconducting shim never does. The directory record carries technology neutral-atom for a Pasqal device. docs/design/pulser-programs.md describes the whole Pasqal path; the QPU front-end contract points to it. Verified: tests/mock (new: Pulser format reading and refusals, the declaration, capability bits against both selections), ci-syntax clean. Assisted-by: Claude Opus 5.5 via Claude Code Signed-off-by: Aleksander Wennersteen <aleksander.wennersteen@pasqal.com>
QRMI serves Pasqal Cloud devices (QPUs and emulators) as its PasqalCloud
resource type, which the shim's QRMI driver listed but ran as IQM. This
runs them.
Authentication: QRMI logs in and renews tokens; the driver picks whose
identity the resource uses and never mixes two, since QRMI renews an
expired token with whatever login the map holds. A reservation whose
credential carries a token runs as that user, with the operator's service
account and Pasqal login cleared from the map. Otherwise (no credential, a
reservation id alone from the no-secret provider, an entitlement) it runs
as the service: the backend's token or QFW_API_KEY, the service account
{backend}_QRMI_PASQAL_CLOUD_CLIENT_ID/_SECRET, or, set explicitly,
PASQAL_CONFIG_ROOT and PASQAL_USERNAME/PASQAL_PASSWORD, which Pasqal's own
tools read; $HOME is never passed. The resource opens only through
QuantumResource.from_config, since the environment path cannot keep the
identities apart.
The project is the user's project_id entry in the credential DB (an
entitlement passes it on too), else the backend's, else the device's new
project-id key in device-access config (forwarded by select_qpu, kept by
the descriptor, documented in qfw-device-access.yaml(5)); a config file
under PASQAL_CONFIG_ROOT may name it instead. A job needs one; opening the
resource does not, so public emulator specs can be read without it. A
user's resource is built from the credential and the device alone (URL:
credential, QFW_QC_URL, device url), never from the service user's
credential DB entry; a service's falls back to the device's url when
device access cannot be resolved.
Execution: run_circuit submits the Pulser sequence as
Payload.PasqalCloud(sequence, job_runs) and turns the batch's counter into a
qhw-result-v1 record. A finished task is not deleted with task_stop, which
on Pasqal cancels the batch and is rejected once the batch has ended. A
result without counts is an error rather than an empty success.
PasqalLocal is refused: it needs the Slurm job's identity and a session
acquired for it.
The provider deadline for a Pasqal job is the device's job-timeout-seconds
or the client's timeout, and reaching it cancels the job. Without one the
job waits as long as Pasqal queues it; a reservation that closes cancels
it. Other providers wait for job-timeout-seconds too, else 300 s as
before. A timeout that is not a number fails before anything is
submitted.
Capabilities: a Pasqal resource serves run_circuit, the task calls and
get_backend_info, which returns the Pulser devices QRMI's target() lists
(the QPU itself, or for an emulator the real devices it accepts sequences
for), read for each call so they follow recalibrations, for a Pulser client
to build against. The telemetry get_backend_info takes a reservation_id;
the shim QPM checks the reservation as for execution (a closing one is
refused) and resolves its credential from its own bindings, since a QPU's
specs need a login. Whichever library serves the call opens the device
with that credential: the QRMI driver for IBM and IQM as for Pasqal, and
the QDMI driver. The qhw introspection calls are a gap for it.
Verified: tests/mock (new: whose identity each kind of reservation runs
as, payload and result, PasqalLocal refusal, the finished-batch rule, the
provider deadline, the project from the credential DB, a reservation's
credential reaching get_backend_info on QRMI and QDMI); against qrmi 0.26
and a local stand-in for Pasqal's API, an expired user token is no longer renewed with
the operator's service account, and a service logs in from the config
file under PASQAL_CONFIG_ROOT; live on Pasqal Cloud EMU_FREE and FRESNEL
device specs through get_backend_info; ci-syntax clean.
Assisted-by: Claude Opus 5.5 via Claude Code
Signed-off-by: Aleksander Wennersteen <aleksander.wennersteen@pasqal.com>
backends/qfw_pulser.QFwConnection is a Pulser RemoteConnection, so a Pulser
program runs on a QFw QPM the way it runs on Pasqal Cloud:
backend = pulser.QPUBackend(sequence, connection=QFwConnection())
results = backend.run([{"runs": 100}], wait=True)
It finds the QPM through the `pasqal` selection or the reservation the
launcher exported, and checks the QPM reads Pulser sequences. submit()
checks the sequence against the device's current specs and sends that
sequence (QPUBackend.run checks too but hands submit the one it was
given), each job as one circuit through async_run, built with the job's
variables; every job is built before any is sent. A batch is the jobs of
one submit. Job state comes from the
QPM's completion queue: rc 0 is done, a QPM-side or provider cancel is
cancelled, anything else an error with the QPM's message, a lost
reservation an error.
Once a job is accepted its batch stays reachable. A submit that fails part
way cancels each accepted job on its own and raises QFwBatchError naming
the batch, with the submission failure as its cause and any cancel that
failed or the QPM refused; an interrupt cancels them too and is
re-raised. A wait that times out (timeout bounds the client's wait only)
or cannot poll raises it too and leaves the jobs running for
RemoteResults(batch_id, connection) to follow and cancel_batch() to stop.
cancel_batch() asks for every job not known to be finished, including
one whose completion cannot be read.
fetch_available_devices() reads get_backend_info through the QPM's
telemetry binding as the reservation, so QPUBackend checks a sequence
against the device's current specs; a QPM that answers with none raises
NotImplementedError,
which Pulser treats as keeping the sequence's device, and any other failure
is raised as it is. lib and QFW_SHIM_LIB pick the shim
library as for the Qiskit backend. failure_message() and SHIM_LIB_ENV move
into qfw_qiskit.qfw_job, which no longer needs a Qiskit backend import to
reach them.
setup/pulser-requirements.txt holds pulser-core, needed only by
applications that use the frontend. CMake installs qfw_pulser and the
install-tree smoke checks it.
examples/qfw_pulser.sh starts the shim service for a Pasqal device, reserves
it and runs examples/tests/test_pulser.py through QPUBackend. The program
fetches the device through the QPM, places atoms on a calibrated layout
(raising the count to the device's minimum layout filling when it has one,
42 on FRESNEL), and drives them with a pi/2 and a pi pulse in two jobs. It
prints, per shim library asked for, the excited fraction and top bitstrings
of each job. Devices: Pasqal Cloud EMU_FREE and the FRESNEL QPU.
tests/pulser runs the client against real Pulser with the QPM stood in
for: the sequence sent is the one checked against the device's current
specs, and results read in register order. CI runs it as ci-pulser;
ci-mock leaves it out.
Verified: tests/mock (new: submission, per-job variables, the completion
states, partial submission and cancel failures, a wait that times out or
cannot poll, cancel_batch, device fetch, the example config through the
descriptor); tests/pulser against Pulser 1.9.1; end to end on openQSE's QFw-SLURM-Cluster against EMU_FREE,
4 atoms built on FRESNEL_CAN1, 100 shots: pi/2 pulse excited fraction 0.51,
pi pulse 1111 in 100 of 100 shots; ci-syntax clean.
Assisted-by: Claude Opus 5.5 via Claude Code
Signed-off-by: Aleksander Wennersteen <aleksander.wennersteen@pasqal.com>
fa6d7ce to
c19f489
Compare
|
An AI agent wrote this @DougSO Rebased on main b93bc39 and force-pushed (c19f489). Both conflicts resolved the way you described: the Pasqal |
Fixes an AI agent caught in existing code while developing #158 (unreviewed)
Implements #157: Pulser programs on Pasqal devices through the shim QPM, via QRMI. The design is in
docs/design/pulser-programs.md.It is one PR so the whole path can be run end to end, split into three commits that build on each other and each pass
ci-syntaxandci-mockon their own:pulser-abstract-reprformat inutil.circuit_payload, its declaration by a Pasqal QPM,QPM_CAP_NEUTRAL_ATOMand thepasqalclient selection, and the design note.{backend}_QRMI_PASQAL_CLOUD_AUTH_TOKENorQFW_API_KEY), a service account, or an explicitly setPASQAL_CONFIG_ROOT/PASQAL_USERNAME/PASQAL_PASSWORD($HOMEis never passed). Theproject-iddevice key and the user'sproject_idcredential entry;Payload.PasqalCloudexecution toqhw-result-v1;get_backend_inforeturning the Pulser devices, read as the reservation when the client names one. A Pasqal job has no default provider deadline: a device can setjob-timeout-seconds, otherwise the job runs until it ends, the client cancels it, or its reservation closes.backends/qfw_pulser.QFwConnection,setup/pulser-requirements.txt, install, andexamples/qfw_pulser.sh.Try it
With a Pasqal Cloud account, from
examples/inside an allocation:Testing
ci-syntaxclean;tests/mock760 passed, 3 skipped;tests(unit) 130 passed;tests/pulseragainst Pulser 1.9.1 passed. Each commit passes on its own.End to end on openQSE's QFw-SLURM-Cluster (two-group heterogeneous allocation, job-owned directory and shim QPM, reservation through
qfw_slurm_driver.sh) against Pasqal Cloud EMU_FREE, 4 atoms built on FRESNEL_CAN1's layout, 100 shots:get_backend_infolive: EMU_FREE serves FRESNEL and FRESNEL_CAN1, FRESNEL serves FRESNEL.Not in this PR
PasqalLocal(an on-premises QPU behind Warden), which needs the Slurm job's identity and a session acquired for it.