Skip to content

🎉 RFC: Redesign of QDMI#440

Draft
ystade wants to merge 17 commits into
developfrom
v2
Draft

🎉 RFC: Redesign of QDMI#440
ystade wants to merge 17 commits into
developfrom
v2

Conversation

@ystade

@ystade ystade commented Jun 17, 2026

Copy link
Copy Markdown
Collaborator

Motivation

QDMI v1 was designed as a uniform interface for directly connected QPUs.
While it has proven valuable in that role, its monolithic structure makes it unsuitable for the full range of deployment scenarios encountered in modern HPC–quantum integration:

  • Provider pooling: A single cloud or on-premise provider may expose multiple QPUs behind one access point. In v1, this can only be approximated by misusing a single device session to connect multiple QPUs via different base URLs—providers are not first-class entities.
  • Federated HPC centers (stack-as-a-device): When another HPC center's entire software stack—including its own compilation, scheduling, and execution infrastructure—is accessed remotely, v1 provides no standardized abstraction. There is no way to distinguish such an orchestration layer from a raw QPU.
  • Technology-specific bloat: All device properties for all technologies are packed into shared enums (QDMI_Device_Property, QDMI_Site_Property). As the number of supported technologies grows, the interface becomes increasingly cluttered with properties irrelevant to most devices.
  • ABI rigidity: v1 exposes direct C symbol exports, so any addition to the interface risks breaking binary compatibility.

QDMI v2 resolves these issues through a modular architecture: a thin mandatory core captures what all devices share, while optional modules capture where they differ. This enables three integration scenarios—direct QPU access, provider pooling, and federated software stacks—through one uniform interface.

Changelog

New: Modular Architecture

The flat single-file interface is replaced by a structured module system:

  • Core interface (qdmi/core.h): Mandatory for all devices. Manages the device lifecycle (context, session), exposes device identity and version, and provides the module-loading mechanism. Accessed via a function pointer table (QDMI_Core_Interface) obtained through the single entry point QDMI_initialize.
  • QPU module (qdmi/qpu.h, ID "qpu"): Job submission and result retrieval for QPUs; replaces the v1 client job interface. Queried via QDMI_context_query_module_by_id(ctx, "qpu", &mod) followed by QDMI_context_get_module_interface.
  • Provider module (qdmi/provider.h, ID "provider"): New. Exposes managed devices and returns the QDMI_Core_Interface* for each, enabling recursive composition of providers and orchestration layers.
  • Orchestration layer module (qdmi/orchestration_layer.h, ID "ol"): New. Extends the provider module with job management functions and QDMI_job_set_device to designate the target QPU. Realizes the stack-as-a-device paradigm.
  • Technology-specific modules: Superconducting ("sc") and neutral atom ("na") modules replace technology-specific properties in the shared device/site/operation enums. Each module owns its types (QDMI_SCQubit, QDMI_SCOperation, etc.) and function table.
  • Custom modules: Any device may register modules with arbitrary IDs for vendor-specific extensions without modifying the standard interface.

New: Context and Function Tables

v1 relied on QDMI_device_initialize/QDMI_device_finalize as global lifecycle functions and exported individual symbols for each operation. v2 introduces:

  • QDMI_initialize(version, callback, user_data, &context, &library): Single entry point resolving all library-level state into an opaque QDMI_Context and a QDMI_Library function table. The version parameter encodes the semantic version for forward/backward compatibility checking.
  • All interfaces are exposed as function pointer structs (QDMI_Core_Interface, QDMI_QPU_Interface, etc.), not direct symbol exports. This decouples the ABI from the header layout and allows stable binary interfaces across releases.
  • QDMI_context_finalize replaces QDMI_device_finalize.

Changed: Session Management

  • Session allocation moves from the driver level to the context level: QDMI_context_allocate_session replaces QDMI_session_alloc.
  • Authentication parameter setting is replaced by typed functions (QDMI_session_set_token, QDMI_session_set_authentication_file, QDMI_session_set_authentication_url, QDMI_session_set_username, QDMI_session_set_password), replacing the generic QDMI_session_set_parameter with its untyped void* value and enum key.
  • QDMI_context_query_authentication_options allows clients to query which credential combinations a device accepts before allocating a session.

Changed: Job Interface

  • The generic QDMI_job_set_parameter/QDMI_job_get_results pattern is replaced by typed functions: QDMI_job_set_payload_string, QDMI_job_set_payload_binary, QDMI_job_set_shot_count.
  • Program formats are now first-class objects (QDMI_Program_Format) with a queryable string ID and packed version number. String and binary payload support are independently queryable per format.
  • Result retrieval is split into dedicated functions: QDMI_job_get_shots, QDMI_job_get_histogram, QDMI_job_get_state_vector_dense/sparse, QDMI_job_get_probabilities_dense/sparse.
  • The QDMI_Device type and QDMI_device_create_job are gone; jobs are created at the session level via QDMI_session_create_job within the QPU or orchestration layer module.

New: Logging

A QDMI_Log_Callback mechanism is introduced at context, session, and job level, replacing ad-hoc error return codes as the sole feedback channel.

Upgrade Guide

Migrating a v1 device implementation to v2 requires the following steps:

  1. Replace the entry point. Remove QDMI_device_initialize/QDMI_device_finalize and implement QDMI_initialize returning a QDMI_Context and a populated QDMI_Library struct. Use QDMI_context_finalize for teardown.

  2. Implement the core interface. Populate a QDMI_Core_Interface struct with function pointers for context queries (id, name, version, authentication_options, modules) and session management (allocate_session, set_token, initialize, free). Export this struct via the get_interface function pointer in the QDMI_Library returned by QDMI_initialize.

  3. Move device introspection to the QPU module. Remove implementations of QDMI_device_query_device_property, QDMI_device_query_site_property, and QDMI_device_query_operation_property. Technology-specific properties (coupling maps, T1/T2 times, site coordinates) move into the "sc" or "na" module function table respectively.

  4. Rewrite the job interface. Replace QDMI_job_set_parameter/QDMI_job_get_results implementations with the typed payload setters and result getters defined in qdmi/job/functions.h. Populate a QDMI_QPU_Interface struct and register it as the "qpu" module.

  5. Register modules. Implement QDMI_context_query_modules and QDMI_context_query_module_by_id to enumerate all modules the device supports. For each module, implement QDMI_context_get_module_interface to return the corresponding function table pointer.

  6. Update authentication. Replace QDMI_device_session_set_parameter logic with typed setters. Implement QDMI_context_query_authentication_options to declare supported credential combinations.

  7. Providers and orchestration layers (new implementations only). Implement the QDMI_Provider_Interface or QDMI_OrchestrationLayer_Interface struct and register it under the "provider" or "ol" module ID respectively.

Examples

The examples/ directory contains a self-contained proof-of-concept that exercises all three integration scenarios end-to-end. It is organised into four layers.

Client apps (examples/apps/)

Four numbered C programs correspond directly to the interaction patterns described above:

App What it shows
1_bootstrapping Dynamically loads a QDMI v2 shared library with dlopen/LoadLibrary, resolves QDMI_initialize, obtains a QDMI_Context and QDMI_Library, retrieves the QDMI_Core_Interface, allocates a session, sets a token, and initializes the session. This sequence is identical regardless of the device type behind the library.
2_job_lifecycle Continues from 1_bootstrapping against a QPU. Loads the "qpu" module, negotiates a program format (openqasm 3.0), creates a job, attaches a Bell-state circuit as a string payload, sets the shot count, submits the job, waits for completion, and retrieves measurement results as bitstrings.
3_stack-as-a-device Continues from 1_bootstrapping against an orchestration layer. Loads the "ol" module, queries the list of managed QPUs, retrieves the QDMI_Core_Interface and context of a managed device, and establishes a session with it—using the exact same bootstrapping sequence as app 1. Demonstrates the stack-as-a-device paradigm.
BONUS_end-to-end Combines all steps into a single program that runs against the orchestration layer device, queries a managed QPU, and executes a full job lifecycle through the orchestration layer (including QDMI_job_set_device).

Example device implementations (examples/src/ and examples/include/)

Three mock devices implement the v2 interface in C++ and serve as reference implementations as well as targets for the apps and tests:

Device Modules implemented Description
qpu/ core, "qpu", "sc" A simulated superconducting QPU with a five-qubit linear coupling map. Implements all core and QPU session functions, exposes superconducting qubit and operation handles, and executes submitted OpenQASM circuits with a trivial simulator.
provider/ core, "provider" A provider that manages two instances of the example QPU. Loads them as dynamic libraries at runtime using the library_wrapper utility, and returns their QDMI_Core_Interface* and QDMI_Context through the provider module.
orchestration_layer/ core, "provider", "ol" An orchestration layer that extends the provider by also accepting job submissions. Routes each job to the target QPU specified via QDMI_job_set_device, simulating a remote software stack.

Additionally, src/v1/ contains a legacy v1 device, and src/adapter/ provides a v1→v2 adapter that wraps any v1 device behind the new v2 interface, demonstrating a backwards-compatible migration path.

ystade added 17 commits June 5, 2026 13:52
The templates constitute a large amount of redundant code that must
additionally be maintained when the interface changes. At the same
time, the templates are hard to test as they do not actually
implement any logic. Hence, we decided to remove the templates and,
instead, in a future commit, enrich the reference (`examples/`)
implementations with comprehensive comments where user would need to
add or change code to adapt the reference implementation to their own
environment.
The `tool` was a simplistic mock-up of a compilation pass. This demonstration of how to use QDMI will be replaced by an overview of actual projects using QDMI.
From the experience using QDMI, it became clear that there will not be something like a unified FoMaC as a C++ abstraction. Instead, every compiler will implement its own C++ abstraction tailored to its use case. This renders the example implementation of a FoMaC in the QDMI repository superfluous and, hence, it is removed. It is removed togehter with all related test cases, which leads to a decreased test coverage. The test coverage will be fixed later with a proper test suite.
To find and recognize CMake variables that belong to the QDMI project more easily, they were renamed to all start with the prefix `QDMI_`. Additionally, `cmake_dependent_option` was added where meaningful.
The new interface consists of a core interface and loadable modules.
The core interface is purposefully kept minimal and only contains the
functionality required by any quantum device, e.g., superconducting
QPU, neutral-atom QPU, a simulator, or other devices like provider or
orchestration layer. Each device can provide additional functionality
that is encapsulated in a module. E.g., a superconducitng QUP would
implement the QPU and the superconducting module.

Please note that this commit leaves the project in a broken state.
Currently, it cannot be built. This will be fixed by future commits.
Also, the neutral atom module is still missing and will be added
later.
Update all the setup contained in `cmake/` to fit the new structure of the v2 headers. In particular, update the name-shifting and implement additional name-shifting by another prefix for lines that are marked with a certain label. Finally, update the `test_defs.cpp.in`, make it to a pure C-file. Now, it only contains one function that must be loaded via dlsym from a dynamic library.
To make the example implementations more modular, i.e., such that they can build on each other, their directory structure was entirely refactored.
The added test suite just need to be instantiated for a concrete
implementation. Then this implementation can immediately be tested
against the specification. While this test suite is not complete yet,
it  sets up the structure and can be extended to a truely complete
test suite testing the compliance with QDMIv2.

As part of this change, the old test suite in `test/` was removed
because the interface cannot be tested without an implementation.
Hence, all tests moved into the example directory where the
implementations are located.
The old example QPU implementation residing in `examples/src/device` was moved to `examples/{include,src}/qpu` and was updated from scratch to support QDMIv2. This implementation is based on two auxiliary classes, namely a singleton and a logger residing in `examples/{include,src}/common/`.
In the new interface there is no distinction between a client and a device interface. Different levels of abstraction are handled by different modules a device implements. The previous driver is best matched with a provider. Hence, the dirver was substituted with an updated implementation as a provider.
An entity that could not be represented by QDMIv1 and which is a unique feature of QDMIv2, is the orchestration layer, i.e., the entry point to an entire software stack. The example implementation of such an orchestration layer is very thin and does in particular not contain any compilation capabilities a normal orchestration layer would.
Many vendors have already implemented QDMIv1 to expose there devices. QDMIv2 must ensure that these devices are still compatible. To this end, we implemented an adapter as a provider that loads QDMIv1 devices instead of QDMIv2 devices and exposes them as QDMIv2 devices. It does all the required re-wiring internally.
The `example/apps` directory now contains C-executables that serve as demonstration examples how to use QDMI. They start very simple and gradually increase in complexity finally demonstrating the full job-lifecycle in the stack-as-a-device scenario. The bonus executable lets the user select the QPU a dummy job should run on.
The new example implementations use some new conventions that required to update the clang tidy configuration.
Device implementations may now be compiled with hidden symbol
visibility, which is a common best practice for C++ libraries to
reduce symbol clashes and improve load times. To ensure that all
necessary symbols are exported, a new header file `qdmi/export.h` is
provided. This header defines a macro for marking symbols for export,
and you should use this macro to annotate all public symbols in your
device implementation.
@ystade ystade self-assigned this Jun 17, 2026
@burgholzer burgholzer added major Changes that warrant a major version release feature New feature or feature request labels Jun 17, 2026
@burgholzer burgholzer added this to the v2.0.0 milestone Jun 17, 2026
@burgholzer burgholzer self-assigned this Jun 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature New feature or feature request major Changes that warrant a major version release

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants