Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions .agent/plans/qdmi-client-runtime-c1.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Replaceable QDMI Client driver

Status: independent rebase validated locally.

## Scope and decisions

Core's Client wrappers load a validated implementation of the standard QDMI
Client ABI rather than link to Core's packaged driver. Driver selection is
process-wide; failed validation or allocation must leave retry possible. Owning
wrappers retain their originating session and the loaded function table.

This change depends only on QDMI #511. Keep the existing program-format enum,
single-program APIs, calibration submission, and current compiler and SDK
behavior. Multi-program adoption and payload capabilities are independent work.
The optional private discovery/configuration extension is in Core PR #2230.
Installed deployment is in Core PR #2231. Standardizing that extension belongs
to QDMI v2, not this Client ABI change.

Target Core 4.1 / QDMI 1.4, never Core 4.0. Development uses the isolated QDMI
driver branch; published artifacts require a released dependency version.

## Implementation boundary

The runtime is in `src/qdmi/Client.cpp` and `include/mqt-core/qdmi/Client.hpp`.
The packaged driver reports stable catalogue IDs through the standard property.
Bindings, SDK entry points, Slurm selection, and compiler device opening route
through the Client session; they must not call the packaged registry directly.
Existing compiler target inference still rejects unknown topology and gate sets.

## Validation

Validate ABI/symbol rejection, retry after failed allocation, process-wide
selection, session lifetime, malformed results, and packaged-driver loading.
Retain current optional-device builds and Slurm status semantics. Run
independent release build/CTest, QDMI and SDK Python suites, generated stubs,
repository lint, and C++ lint before publication. The release suite passed 3,869
tests with one existing skip; all 455 selected Python tests passed. Stub
generation, repository lint, and C++ lint passed.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,10 @@ releases may include breaking changes.
- 💥 Replace the QDMI-specific primitives with native Qiskit primitives and
typed backend factories. Sampler and `memory=True` require genuine QDMI
`SHOTS` ([#2358]) ([**@burgholzer**])
- 💥 Load one replaceable QDMI 1.4 Client driver through a validated function
table, split `MQT::CoreQDMI` from the packaged `MQT::CoreQDMIDriver`, and use
stable Client device IDs across C++, Python, MLIR, Qiskit, PennyLane, and
Slurm ([#2229]) ([**@burgholzer**])
- 💥 Drop support for x86 macOS and stop publishing the respective wheels
([#2259]) ([**@denialhaag**])
- ⬆️ Raise the macOS deployment target to 13.3 to enable `std::format` in libc++
Expand Down Expand Up @@ -907,6 +911,7 @@ for previous changelogs._
[#2257]: https://github.com/munich-quantum-toolkit/core/pull/2257
[#2246]: https://github.com/munich-quantum-toolkit/core/pull/2246
[#2240]: https://github.com/munich-quantum-toolkit/core/pull/2240
[#2229]: https://github.com/munich-quantum-toolkit/core/pull/2229
[#2232]: https://github.com/munich-quantum-toolkit/core/pull/2232
[#2228]: https://github.com/munich-quantum-toolkit/core/pull/2228
[#2224]: https://github.com/munich-quantum-toolkit/core/pull/2224
Expand Down
42 changes: 36 additions & 6 deletions UPGRADING.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,38 @@ of changes including minor and patch releases, please refer to the

## [Unreleased]

### QDMI Client driver boundary

`MQT::CoreQDMI` and `MQT::CoreQDMIDriver` are now separate shared libraries.
`MQT::CoreQDMI` loads one implementation of the standard QDMI 1.4 Client
interface. It no longer links to MQT Core's packaged Driver. Set
`qdmi::SessionConfig::driverPath`, pass Python `driver_path`, or set the UTF-8
`MQT_CORE_QDMI_DRIVER` environment value to select another Driver. The first
Driver that passes validation and allocates a raw session fixes the selection
for the process. A failed load or raw-session allocation can be retried.

Python no longer exposes the `mqt.core.qdmi.driver` registry submodule. Import
`ClientSession` and `open_device` from `mqt.core.qdmi`:

```python
from mqt.core.qdmi import ClientSession, open_device

device_ids = [device.id for device in ClientSession().devices]
device = open_device(device_ids[0], token="access-token")
```

`Device.id` and `Device::getId()` return the stable ID reported by the Driver.
The generic session parameters are `token`, `auth_file`, `auth_url`, `username`,
`password`, `project_id`, and `custom1` through `custom5`. The selected Driver
owns their validation and meaning. The former Python `base_url`,
`device_config`, and `device_config_file` keywords were tied to MQT Core's
Driver and are not part of the generic Client boundary. Use persistent Driver
configuration when that Driver supports these settings.

The MLIR `from_device_id` helpers and the Qiskit and PennyLane adapters accept
the same generic session parameters. Device, site, operation, and job wrappers
keep their originating Client session alive.

### Removal of the classic circuit representation

MQT Core 4 removes the complete classic circuit surface. This includes the C++
Expand Down Expand Up @@ -210,13 +242,11 @@ MQT Core removed the following names:

MQT Core 4 removes the deprecated FoMaC names that MQT Core 3.9 kept as
compatibility aliases. Replace Python imports of QDMI entities from
`mqt.core.fomac` with imports from `mqt.core.qdmi`. Import registry functions
and `DeviceDefinition` from `mqt.core.qdmi.driver`.
`mqt.core.fomac` with imports from `mqt.core.qdmi`.

MQT Core 4 also removes `mqt.core.qdmi.driver.Session`. Use
`registered_device_ids()` to discover devices and `open_device()` to open a
fresh device session. Pass provider configuration overrides to `open_device()`
when a device needs per-open configuration.
MQT Core 4 also removes `mqt.core.qdmi.driver.Session` and the remaining
`mqt.core.qdmi.driver` registry API. Use `ClientSession().devices` to discover
devices and top-level `open_device()` to open a fresh Client session.

Apply these replacements to C++ and MLIR code:

Expand Down
67 changes: 42 additions & 25 deletions bindings/mlir/register_mlir.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
#include "mlir/Dialect/QCO/Utils/DDFunctionality.h"
#include "mlir/bench/Generate.h"
#include "qdmi/Client.hpp"
#include "qdmi/driver/SessionConfig.hpp"
#include "qiskit/Qiskit.h"

#include <llvm/Support/Error.h>
Expand Down Expand Up @@ -189,6 +188,33 @@ template <nb::exception_type Exception = nb::exception_type::value_error,
return *std::move(result);
}

[[nodiscard]] static qdmi::Device openQDMIDevice(
const std::string& deviceId,
std::optional<std::filesystem::path> driverPath,
std::optional<std::string> token,
std::optional<std::filesystem::path> authFile,
std::optional<std::string> authUrl, std::optional<std::string> username,
std::optional<std::string> password, std::optional<std::string> projectId,
std::optional<std::string> custom1, std::optional<std::string> custom2,
std::optional<std::string> custom3, std::optional<std::string> custom4,
std::optional<std::string> custom5) {
return qdmi::Session::openDevice(deviceId,
{
.driverPath = std::move(driverPath),
.token = std::move(token),
.authFile = std::move(authFile),
.authUrl = std::move(authUrl),
.username = std::move(username),
.password = std::move(password),
.projectId = std::move(projectId),
.custom1 = std::move(custom1),
.custom2 = std::move(custom2),
.custom3 = std::move(custom3),
.custom4 = std::move(custom4),
.custom5 = std::move(custom5),
});
}

template <class ProgramType>
[[nodiscard]] static ProgramType copiedOrConsumed(ProgramType& program,
const bool copy) {
Expand Down Expand Up @@ -826,44 +852,35 @@ either unrestricted or explicitly enumerated native-operation support.)pb");
"device"_a, "Snapshot a circuit-model QDMI device.")
.def_static(
"from_device_id",
[](const std::string& deviceId, std::optional<std::string> baseUrl,
[](const std::string& deviceId,
std::optional<std::filesystem::path> driverPath,
std::optional<std::string> token,
std::optional<std::filesystem::path> authFile,
std::optional<std::string> authUrl,
std::optional<std::string> username,
std::optional<std::string> password,
std::optional<std::string> deviceConfig,
std::optional<std::filesystem::path> deviceConfigFile,
std::optional<std::string> projectId,
std::optional<std::string> custom1,
std::optional<std::string> custom2,
std::optional<std::string> custom3,
std::optional<std::string> custom4,
std::optional<std::string> custom5) {
// Keep this preflight at the Python boundary so the public
// ValueError does not depend on cross-extension exception
// translation.
if (deviceConfig && deviceConfigFile) {
throw nb::value_error(
"device_config and device_config_file are mutually "
"exclusive");
}
const auto overrides = qdmi::makeDeviceSessionConfig(
std::move(baseUrl), std::move(token), std::move(authFile),
std::move(authUrl), std::move(username), std::move(password),
std::move(deviceConfig), std::move(deviceConfigFile),
std::move(custom1), std::move(custom2), std::move(custom3),
std::move(custom4), std::move(custom5));
auto device = qdmi::Session::openDevice(deviceId, overrides);
auto device = openQDMIDevice(
deviceId, std::move(driverPath), std::move(token),
std::move(authFile), std::move(authUrl), std::move(username),
std::move(password), std::move(projectId), std::move(custom1),
std::move(custom2), std::move(custom3), std::move(custom4),
std::move(custom5));
return takeResult(mlir::compilerTargetFromDevice(device));
},
"device_id"_a, nb::kw_only(), "base_url"_a = std::nullopt,
"device_id"_a, nb::kw_only(), "driver_path"_a = std::nullopt,
"token"_a = std::nullopt, "auth_file"_a = std::nullopt,
"auth_url"_a = std::nullopt, "username"_a = std::nullopt,
"password"_a = std::nullopt, "device_config"_a = std::nullopt,
"device_config_file"_a = std::nullopt, "custom1"_a = std::nullopt,
"custom2"_a = std::nullopt, "custom3"_a = std::nullopt,
"custom4"_a = std::nullopt, "custom5"_a = std::nullopt,
"Open a registered device and snapshot its compiler target.")
"password"_a = std::nullopt, "project_id"_a = std::nullopt,
"custom1"_a = std::nullopt, "custom2"_a = std::nullopt,
"custom3"_a = std::nullopt, "custom4"_a = std::nullopt,
"custom5"_a = std::nullopt,
"Open a Client-visible device and snapshot its compiler target.")
.def_prop_ro(
"name",
[](const mlir::CompilerTarget& target) {
Expand Down
Loading
Loading