When a provider's describe answer states errors per qubit or per pair, the host reads it as a device target and places the program on the qubits and routes most likely to succeed (engine::targets::layout::place), in the device's own gates; otherwise the compiler's own layout is kept. The program is checked against the circuit on the device's connectivity before it is sent, as before. The plan and the sealed result's compiled block say how it was placed, with the target's hash and the expected success beside the fewest-swaps layout's. The provider protocol now states the durations field's form, and that a client may place by the described calibration. Co-Authored-By: Claude Opus 5.5 <[email protected]>
330 lines
16 KiB
Markdown
330 lines
16 KiB
Markdown
# WAI Extension: Quantum Providers (`wai.quantum.provider/1`)
|
||
|
||
Status: **Draft.** Client, reference provider and conformance checker:
|
||
`wai-quantum-qpu` (`src/open_provider.rs`, `src/reference.rs`,
|
||
`src/conformance.rs`); vectors: `wai-quantum-qpu/conformance/provider-v1.json`.
|
||
The engine's side of a provider job: `wai-quantum/src/engine/provider.rs`.
|
||
|
||
Keywords **MUST**, **MUST NOT**, **SHOULD** and **MAY** are RFC 2119/8174.
|
||
|
||
## 0. What this extension adds
|
||
|
||
Bring your own quantum. WAI provides the engine, the free job service and the
|
||
verifier at no cost, and never pays for or resells time on anyone's hardware.
|
||
A person connects their own quantum hardware, provider accounts, on-premises
|
||
devices or simulators, and every surface treats a provider's result as it
|
||
treats a WAI result: planned, its cost shown before it is submitted, checked,
|
||
and sealed in a receipt.
|
||
|
||
This text is the protocol a device speaks so that it can be brought without a
|
||
client written for it: JSON messages, one request and one answer at a time,
|
||
over HTTP or to a local process (§2). Six operations (§5) describe the
|
||
device, estimate a job, submit it, follow it, read its counts and cancel it.
|
||
A program travels in the device's own native gates, with every angle an exact
|
||
fraction of a turn (§4), so the program the device runs is the one the client
|
||
compiled and checked.
|
||
|
||
A provider is the program or service that answers the messages. A device is
|
||
what it runs programs on: a processor, an emulator or a simulator. A client
|
||
is what sends the messages; a host is the client's process, which holds the
|
||
person's credential and signing key (the command line, the desktop app).
|
||
|
||
## 1. Principles
|
||
|
||
1. **The credential stays with the person.** A host keeps a provider's
|
||
credential in the operating system's keychain (or, where there is none, a
|
||
file only its owner can read, labelled as such) and sends it only to that
|
||
provider (§2). A credential MUST NOT appear in a browser page, a log, a
|
||
receipt, a command line, an answer the host prints, or the hosted job
|
||
service. The job service never routes a job to a provider and never takes
|
||
a credential: a request that names a provider is refused there.
|
||
2. **Cost before submit.** A client MUST show a job's estimate (§5.2) before
|
||
it submits the job, and MUST NOT submit a job that is not free without the
|
||
person's explicit confirmation (§7). An unknown cost is not free.
|
||
3. **Exact programs.** A client compiles a circuit exactly to the device's
|
||
native gates and connectivity and checks the compiled program against the
|
||
circuit before sending it. Angles cross the wire exactly (§4).
|
||
4. **Measured, not recomputed.** A provider's counts are sealed as measured on
|
||
the device it names (§8). Nothing recomputes them.
|
||
|
||
## 2. Transports
|
||
|
||
Every message is a JSON object, and so is every answer. A client sends one
|
||
message and reads its answer before it sends the next on the same transport.
|
||
|
||
### 2.1 HTTP
|
||
|
||
A message is the body of a `POST` to the provider's endpoint (one URL for
|
||
every operation), with `Content-Type: application/json`. The answer is the
|
||
body of a `200` response, also for an answer with `"ok": false` (§3). Any
|
||
other status is a transport failure, not an answer.
|
||
|
||
A credential, when there is one, goes in `Authorization: Bearer <credential>`.
|
||
A client MUST NOT send a credential over `http://` to any host but this
|
||
machine (`localhost`, a loopback address); over the network it uses
|
||
`https://`.
|
||
|
||
### 2.2 A local process
|
||
|
||
The provider is a program the person names, with its arguments, started by
|
||
the client without a shell. Each message is one line of JSON on the program's
|
||
standard input; each answer is one line of JSON on its standard output, in
|
||
order. The program MUST write nothing else to its standard output. Its
|
||
standard error is its own log, and a client MAY discard it. The program runs
|
||
until its standard input ends.
|
||
|
||
A credential, when there is one, is in the program's environment as
|
||
`WAI_PROVIDER_CREDENTIAL`, never on its command line. A client SHOULD NOT
|
||
pass the program another provider's credential.
|
||
|
||
## 3. Messages and errors
|
||
|
||
Every message carries:
|
||
|
||
| field | |
|
||
|---|---|
|
||
| `protocol` | `"wai.quantum.provider/1"` |
|
||
| `op` | `describe`, `estimate`, `submit`, `status`, `result` or `cancel` |
|
||
| `settings` | optional: the person's non-secret settings for this provider (a device name, a region), passed as they are |
|
||
|
||
and the operation's own fields (§5). Every answer carries `"ok": true` and
|
||
the operation's fields, or:
|
||
|
||
```json
|
||
{ "ok": false, "error": { "code": "not_found", "message": "no job j-17" } }
|
||
```
|
||
|
||
The `message` is for a person; it MUST NOT quote a credential. The codes:
|
||
|
||
| code | when |
|
||
|---|---|
|
||
| `invalid` | the message is malformed: a field missing or of the wrong type, a program that does not read (§4), zero shots, a message without `protocol` |
|
||
| `unsupported` | an operation the provider does not offer (`cancel`, `estimate`), or an `op` it does not know |
|
||
| `unsupported_version` | a `protocol` other than this one |
|
||
| `unauthorized` | no credential, or one the provider does not accept |
|
||
| `not_found` | a job the provider does not hold |
|
||
| `refused` | a well-formed program the device cannot run: a gate outside its basis, a qubit it does not have, a pair it does not connect, too many shots; or a result asked of a job that has not finished or was cancelled |
|
||
| `quota` | the person's account cannot pay for or schedule the job |
|
||
| `unavailable` | the device is offline or in maintenance |
|
||
| `internal` | anything else |
|
||
|
||
## 4. The program
|
||
|
||
`estimate` and `submit` carry a `program` in the device's own gates, on its
|
||
physical qubits:
|
||
|
||
```json
|
||
{
|
||
"format": "wai.native/1",
|
||
"basis": "rzsxxcz",
|
||
"bits": 2,
|
||
"gates": [
|
||
{ "gate": "rz", "qubits": [0], "turns": [[1, 2]] },
|
||
{ "gate": "sx", "qubits": [0] },
|
||
{ "gate": "cz", "qubits": [0, 1] }
|
||
],
|
||
"measure": [{ "qubit": 0, "bit": 0 }, { "qubit": 1, "bit": 1 }],
|
||
"qasm3": "OPENQASM 3.0; …"
|
||
}
|
||
```
|
||
|
||
- `basis` is one of three gate sets, and every gate is of it:
|
||
|
||
| basis | gates |
|
||
|---|---|
|
||
| `rzsxxcz` | `rz(t)`, `sx`, `x`, `cz` |
|
||
| `prxcz` | `prx(t, p)`, `cz` |
|
||
| `gpizz` | `gpi(p)`, `gpi2(p)`, `zz(t)` |
|
||
|
||
- An angle is a fraction of a full turn, `[m, k]` meaning `m/2^k` of 2π, with
|
||
`0 ≤ m < 2^k` and `k ≤ 32`, so every angle crosses the wire exactly. The
|
||
gates, as operators on the named qubits, up to a global phase:
|
||
- `rz(t)` = exp(−iπt·Z); `sx` = √X; `x` = X; `cz` = diag(1, 1, 1, −1);
|
||
- `prx(t, p)` = Rz(2πp)·Rx(2πt)·Rz(−2πp);
|
||
- `gpi(p)` = Rz(2πp)·X·Rz(−2πp); `gpi2(p)` = Rz(2πp)·Rx(π/2)·Rz(−2πp);
|
||
- `zz(t)` = exp(−iπt·Z⊗Z).
|
||
- `measure` gives, for each classical bit `0 … bits − 1`, the physical qubit it
|
||
reads at the end of the program. Every bit reads exactly one qubit, and no
|
||
qubit is read twice.
|
||
- `qasm3` is the same program as OpenQASM 3 on physical qubits (`$p`), the
|
||
`prxcz` and `gpizz` gates defined in it from the standard gates, for a
|
||
provider that reads OpenQASM. It is informative: where the two differ, the
|
||
`gates` are the program.
|
||
|
||
A provider MUST refuse (`invalid`) a program of another `format`, a gate
|
||
outside its `basis`, an angle out of range, or a bit that reads no qubit; and
|
||
(`refused`) a program in a basis its device does not run, or on qubits or
|
||
pairs it does not have.
|
||
|
||
## 5. Operations
|
||
|
||
### 5.1 `describe`
|
||
|
||
No fields. The answer describes the device:
|
||
|
||
| field | |
|
||
|---|---|
|
||
| `protocol` | `"wai.quantum.provider/1"` |
|
||
| `provider`, `device` | the provider's name for itself, and the device's |
|
||
| `provenance` | `silicon` (a processor), `emulator`, or `simulator`; with `simulator` (or `emulator`) a sentence naming what it is |
|
||
| `qubits` | the device's qubit count |
|
||
| `native_gates` | the gates it runs; a client compiles to the first of the bases of §4 whose every gate is listed |
|
||
| `connectivity` | `"all"`, or the pairs `[a, b]` a two-qubit gate may act on, in either order |
|
||
| `durations` | optional: `{"unit", "gates", "measure"}`: the unit (`ns`, `us`, `ms` or `s`), each native gate's duration wherever it runs (`{"sx": 35, "cz": 68}`), and the measurement's |
|
||
| `calibration` | `{"id"}`, the calibration snapshot the device is under; optionally `qubits` (`{"qubit", "readout_error", "error_1q", "t1_us", "t2_us"}`) and `edges` (`{"qubits": [a, b], "error"}`), each error from 0 to 1 |
|
||
| `billing` | `{"free": true}`, or `{"unit", "price", "currency"}`: the billing unit (`shot`, `job`, `second`, …), the price of one as a decimal string (`"0.0003"`), and the currency or credit it is in; optionally `free_tier`, the free use offered and its limits, as the provider states them |
|
||
| `max_shots` | optional: the most shots a job may ask |
|
||
| `seeded` | optional: `true` when a job's `seed` makes its counts reproducible (a simulator) |
|
||
|
||
A client MUST NOT take a device to be free unless `billing` says
|
||
`"free": true`, or an estimate says so (§5.2).
|
||
|
||
A client MAY read a `describe` answer whose `calibration` states errors as a
|
||
device target (`wai.quantum.target/1`, `wai-quantum/src/engine/targets`)
|
||
and place the program on the qubits and pairs most likely to succeed. The
|
||
program it sends is checked against the circuit as every program is (§1), and
|
||
the receipt names how it was placed (§8).
|
||
|
||
### 5.2 `estimate`
|
||
|
||
Fields: `program` (§4), `shots`. The answer, before anything is submitted:
|
||
|
||
| field | |
|
||
|---|---|
|
||
| `free` | whether this job is free |
|
||
| `amount`, `currency` | when it is not: the cost, the amount as a decimal string |
|
||
| `unit` | optional: the billing unit the cost counts |
|
||
| `seconds` | optional: how long it is expected to take, queue included |
|
||
| `note` | optional |
|
||
|
||
A provider that cannot estimate answers `unsupported`; the client then treats
|
||
the cost as unknown (§7).
|
||
|
||
### 5.3 `submit`
|
||
|
||
Fields: `program`, `shots`, and optionally `seed` (a seeded simulator uses
|
||
it; a device ignores it). The answer's `job` is the job's id.
|
||
|
||
### 5.4 `status`
|
||
|
||
Field: `job`. The answer's `status` is `queued`, `running`, `done`, `failed`
|
||
or `cancelled`, with an optional `reason`.
|
||
|
||
### 5.5 `result`
|
||
|
||
Field: `job`. The answer, once the job is `done`:
|
||
|
||
| field | |
|
||
|---|---|
|
||
| `counts` | an object from outcomes to counts (§6), summing to the shots |
|
||
| `shots` | the shots |
|
||
| `calibration` | optional: the calibration snapshot the job ran under, when it is not the one `describe` names |
|
||
| `charged` | optional: `{"amount", "currency"}`, what the job cost |
|
||
| `energy` | optional: `{"kwh"}`, the provider's own figure for the job's energy |
|
||
|
||
A `result` asked twice of the same job MUST give the same counts.
|
||
|
||
### 5.6 `cancel`
|
||
|
||
Field: `job`. The answer's `status` is `cancelled`, or `done` for a job that
|
||
had already finished. A provider that cannot cancel answers `unsupported`.
|
||
|
||
## 6. Counts and bit order
|
||
|
||
An outcome is a string of `bits` characters, `0` or `1`, the highest bit
|
||
first: its last character is bit 0. `"01"` is bit 0 read as 1 and bit 1 read
|
||
as 0. This is OpenQASM's order, and the job service's.
|
||
|
||
The client compiles each logical qubit `i` of the person's circuit to end on
|
||
the physical qubit that bit `i` measures, and reads the counts back onto the
|
||
program's own classical bits. The first job on a new provider SHOULD be the
|
||
probe (an `X` on qubit 0, every qubit measured), whose counts must all land
|
||
on `…01`; the conformance vectors begin with it (§9).
|
||
|
||
## 7. Cost and confirmation
|
||
|
||
A job is free when the provider's estimate says `"free": true`, or, for a
|
||
provider that gives no estimate, when its `billing` says so. Every other job,
|
||
an unknown cost included, is not free. A client:
|
||
|
||
- MUST show the estimate (or that the cost is unknown) before submitting;
|
||
- MUST NOT submit a job that is not free without the person's explicit
|
||
confirmation for that run: a command-line flag (`wai-quantum run --provider
|
||
NAME --yes`), an agent tool's argument the agent sets only after the person
|
||
agreed (`run_on_provider` with `confirm_cost: true`), or a dialog (the
|
||
desktop app);
|
||
- MUST NOT hold a standing confirmation across runs.
|
||
|
||
A tool that can submit a job that is not free MUST say in its description
|
||
that it can spend the person's money.
|
||
|
||
## 8. The receipt
|
||
|
||
The host seals each provider job as the job service seals its own: an Ed25519
|
||
signature, by the host's key, over the request as submitted and the result
|
||
as returned, each in its RFC 8785 canonical text, verified by the same
|
||
verifier. The result's `method` is `provider` and its accuracy class is
|
||
`measured`. It carries:
|
||
|
||
- `provider`: the person's name for it, its kind, its protocol, the device,
|
||
its provenance, and the BLAKE3 hash of its `describe` answer;
|
||
- `calibration`: the calibration snapshot the job ran under;
|
||
- `compiled`: the basis, the BLAKE3 hash of the compiled program's OpenQASM 3
|
||
text, its gate and swap counts, its initial and final layout, the check it
|
||
passed before it was sent, and how it was placed (`placement`: with a
|
||
calibration read as a target, the target's hash, the layout's expected
|
||
success and the fewest-swaps layout's);
|
||
- `job`: the provider's job id and the status checks before it finished;
|
||
- `counts`, keyed by the program's classical bits (§6);
|
||
- `ideal`: the circuit's exact distribution set beside the counts (up to 22
|
||
qubits): the total variation distance, the Hellinger fidelity, and the
|
||
distance the shots alone would leave on average;
|
||
- `cost`: the estimate shown before submitting, what the provider says it
|
||
charged, and whether the person confirmed a run that was not free;
|
||
- `energy`: unmetered here, with the provider's own figure, labelled as its
|
||
own, when it gives one.
|
||
|
||
What the receipt proves: that this result, with these counts, is what the
|
||
signer's host received for this request from the provider and device it
|
||
names, under the calibration snapshot named, for the compiled program whose
|
||
hash it carries; and that the host compiled that program exactly and checked
|
||
it against the circuit before sending it. What it does not prove: that the
|
||
device ran that program, or ran anything. The counts are the provider's word,
|
||
and they are not reproducible by recomputation: the same job run again gives
|
||
other counts. The `ideal` distance says how far they are from what the
|
||
circuit gives; a distance well above the sampling distance is the device's
|
||
error, or a program the device did not run as sent.
|
||
|
||
## 9. Conformance
|
||
|
||
`wai-quantum-qpu/conformance/provider-v1.json` holds the vectors:
|
||
|
||
- **messages**, sent as they are: an unknown operation (`unsupported`),
|
||
another version (`unsupported_version`), a message without its protocol
|
||
(`invalid`), a job that does not exist (`not_found`), malformed programs
|
||
(`invalid`);
|
||
- **programs**, each compiled to the device, estimated, run with seed 1, and
|
||
checked by its counts: the probe, bits named out of order, a phase only
|
||
interference shows, a phase kicked back by two quarter-turn controlled
|
||
phases, a Bell pair, and a triangle of two-qubit gates that needs routing
|
||
on a line. The thresholds leave room for a device's noise;
|
||
- **reference**: the counts the reference provider gives for each program at
|
||
seed 1, so its determinism is held on every platform.
|
||
|
||
The checker (`wai-quantum providers test NAME`) also checks the `describe`
|
||
answer in full, that zero shots are `invalid`, and that a program in another
|
||
basis is refused. It runs the programs only when every estimate says free,
|
||
or the person allows the spend (`--yes`).
|
||
|
||
The reference provider (`wai-quantum providers reference --stdio`, or
|
||
`--http ADDR`) is this protocol around WAI's exact simulator. It is the
|
||
provider the tests run against over both transports, and the template to
|
||
copy: a provider for any device is the reference with its simulation replaced
|
||
by the device.
|
||
|
||
## 10. Versioning
|
||
|
||
`wai.quantum.provider/1` is this text. A later version is a new `protocol`
|
||
string; a provider that speaks only this one answers `unsupported_version` to
|
||
another. Fields marked optional may be added to answers without a new
|
||
version, and a client MUST ignore fields it does not know.
|