Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
647afa0
Support dynamic quantum architecture for IQM QPUs
iqm-bhoffmann Aug 4, 2025
b473371
Pull updates from branch main
iqm-bhoffmann Aug 4, 2025
09162a9
Merge branch 'main' into support-iqm-dynamic-quantum-architecture
sacpis Aug 4, 2025
63828da
removing yapf as spell checker is complaining about it
sacpis Aug 4, 2025
9914c16
Removed debug prints which are failing
iqm-bhoffmann Aug 5, 2025
30e7ddd
Merge branch 'main' into support-iqm-dynamic-quantum-architecture
sacpis Aug 5, 2025
56c5e17
Removed commented-out debug prints entirely
iqm-bhoffmann Aug 5, 2025
3f0d099
Merge branch 'main' into support-iqm-dynamic-quantum-architecture
sacpis Aug 5, 2025
1067441
Fixed emulation mode for IQM server
iqm-bhoffmann Aug 8, 2025
403d4ed
Merge branch 'main' into support-iqm-dynamic-quantum-architecture
iqm-bhoffmann Aug 11, 2025
a472db4
Fixed mapping file issue for emulation mode
iqm-bhoffmann Aug 12, 2025
41a4ce7
Merge branch 'main' into support-iqm-dynamic-quantum-architecture
sacpis Aug 12, 2025
2bbc8f0
Merge branch 'NVIDIA:main' into support-iqm-dynamic-quantum-architecture
iqm-bhoffmann Aug 13, 2025
cc3e9e0
Merge branch 'main' into support-iqm-dynamic-quantum-architecture
sacpis Aug 13, 2025
c922993
Merge branch 'main' into support-iqm-dynamic-quantum-architecture
iqm-bhoffmann Aug 14, 2025
5b6c2e8
Merge branch 'main' into support-iqm-dynamic-quantum-architecture
iqm-bhoffmann Aug 18, 2025
e0559db
Corrected comments and code readability
iqm-bhoffmann Aug 18, 2025
a59cfb3
Correction from clang-format
iqm-bhoffmann Aug 18, 2025
cd84da0
Merge branch 'main' into support-iqm-dynamic-quantum-architecture
iqm-bhoffmann Aug 19, 2025
fb5bf88
Merge branch 'main' into support-iqm-dynamic-quantum-architecture
sacpis Aug 19, 2025
48495aa
Merge branch 'main' into support-iqm-dynamic-quantum-architecture
sacpis Aug 20, 2025
6e7131f
Merge branch 'main' into support-iqm-dynamic-quantum-architecture
sacpis Aug 20, 2025
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
2 changes: 2 additions & 0 deletions .github/workflows/config/spelling_allowlist.txt
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,7 @@ prepend
preprocessor
probability
programmatically
prx
pybind
qaoa
qed
Expand Down Expand Up @@ -352,6 +353,7 @@ toolchain
toolchains
toolset
transmon
transpile
trotterization
uccsd
unary
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/integration_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -500,7 +500,7 @@ jobs:
# Only the following tests are currently supported on IQM
case $filename in
targettests/iqm/*)
nvq++ -DSYNTAX_CHECK --target iqm --iqm-machine Crystal_5 $filename
nvq++ -DSYNTAX_CHECK --target iqm $filename
Copy link
Collaborator

@bettinaheim bettinaheim Sep 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I gather the new setup is that different machines will be accessible via different server urls?
If so, would we be able to test against at least two different topologies for our integration tests?

test_status=$?
if [ $test_status -eq 0 ]; then
./a.out
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/test_in_devenv.yml
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ jobs:
shell: bash
run: |
cd $CUDAQ_REPO_ROOT
python3 -m pip install iqm-client==16.1
python3 -m pip install iqm-client==28.0.0
python3 -m pytest -v build/python/tests/interop/
pytest_status=$?
if [ ! $pytest_status -eq 0 ]; then
Expand Down
2 changes: 1 addition & 1 deletion docs/sphinx/targets/cpp/iqm.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Compile and run with:
// ```
// nvq++ --target iqm iqm.cpp --iqm-machine Crystal_5 -o out.x && ./out.x
// nvq++ --target iqm iqm.cpp -o out.x && ./out.x
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generally, we try to make sure that changes are non-breaking (current API still works but gives a deprecated warning for some time before we remove it in favor of a different API). It would be good if we could have one test for python and one for C++ that checks the old API still works (possibly giving a warning or error if necessary with instructions for what should be used instead).

Copy link
Author

@iqm-bhoffmann iqm-bhoffmann Sep 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the architecture information is now fetched from the target machine (URL) giving it as parameter is no longer necessary and the parsing of the parameter was removed. From backward compatibility point nothing bad will happen if it is still given. It can however be confusing that this parameter is ignored and we can give a warning/information about this if desired.
Good point about a testcase for the behaviour if the parameter is still given. I will add such test.

// ```
// Assumes a valid set of credentials have been stored.

Expand Down
4 changes: 1 addition & 3 deletions docs/sphinx/targets/python/iqm.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@
# for every execution call on your kernel.
# To use different targets in the same file, you must update
# it via another call to `cudaq.set_target()`
cudaq.set_target("iqm",
url="http://localhost/cocos",
**{"qpu-architecture": "Crystal_5"})
cudaq.set_target("iqm", url="http://localhost/")

# Crystal_5 QPU architecture:
# QB1
Expand Down
65 changes: 37 additions & 28 deletions docs/sphinx/using/backends/hardware/superconducting.rst
Original file line number Diff line number Diff line change
Expand Up @@ -113,34 +113,49 @@ IQM

.. _iqm-backend:

Support for submissions to IQM is currently under development.
In particular, two-qubit gates can only be performed on adjacent qubits. For more information, we refer to the respective hardware documentation.
The `IQM Resonance <https://meetiqm.com/products/iqm-resonance/>`__ portal offers access to various different IQM quantum computers.
The machines available will be constantly extended as development progresses.
Programmers of CUDA-Q may access the IQM Server from either C++ or Python.

With this version it is no longer necessary to define the target QPU architecture in the code or at compile-time.
The IQM backend integration now contacts at runtime the configured IQM server and fetches the active dynamic quantum architecture of the QPU.
This is then used as input to transpile the quantum kernel code just-in-time for the target QPU topology.
By setting the environment variable ``IQM_SERVER_URL`` the target server can be selected just when executing the program.
As result the python script or the compiled C++ program can be executed on different QPUs without any changes to the code.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit:

Suggested change
As result the python script or the compiled C++ program can be executed on different QPUs without any changes to the code.
As result the python script or the compiled C++ program can be executed on different QPUs without recompilation or code changes.


For IQM architectures two-qubit gates can only be performed on adjacent qubits. For more information, we refer to the respective hardware documentation.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These last two sentences here are outdated and should be removed. For application code, the execution on IQM backends should be supported regardless of what gates are used on which qubits.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the confirmation. I was unsure about this since these lines were recently rewritten. So I left them as they were. I will update accordingly.

Support for automatically injecting the necessary operations during compilation to execute arbitrary multi-qubit gates will be added in future versions.

Setting Credentials
`````````````````````````

Programmers of CUDA-Q may access the IQM Server from either C++ or Python. Following the `quick start guide <https://iqm-finland.github.io/cortex-cli/readme.html#using-cortex-cli>`__, install `iqm-cortex-cli` and login to initialize the tokens file.
The path to the tokens file can either be passed explicitly via an environment variable or it will be loaded automatically if located in
the default location :code:`~/.cache/iqm-cortex-cli/tokens.json`.
Create a free account on the `IQM Resonance <https://meetiqm.com/products/iqm-resonance/>`__ portal and log-in.
Navigate to the account profile (top right). There generate an "API Token" and copy the generated token-string.
Set the environment variable ``IQM_TOKEN`` to contain the value of the token-string.
The IQM backend integration will use this as authorization token at the IQM server.

Note: The previously used ``IQM_TOKENS_FILE`` environment variable can still be used to point to a tokens file but will be ignored if the ``IQM_TOKEN`` variable is set.
The tokens file cannot be generated by the ``iqmclient`` tool anymore but needs to be created manually using the token obtained from the Resonance profile page.
A token file can be created and the environment variable set like this:

.. code:: bash

export IQM_TOKENS_FILE="path/to/tokens.json"
echo '{ "access_token": "<put-your-token-here>" }' > resonance-token.json
export IQM_TOKENS_FILE="path/to/resonance-tokens.json"

Please find more documentation after logging in to the IQM Resonance portal.


Submitting
`````````````````````````
.. tab:: Python

.. tab:: Python

The target to which quantum kernels are submitted
can be controlled with the ``cudaq.set_target()`` function.

.. code:: python

cudaq.set_target("iqm", url="https://<IQM Server>/cocos",**{"qpu-architecture": "Crystal_5"})
cudaq.set_target("iqm", url="https://<IQM Server>/")

To emulate the IQM Server locally, without submitting to the IQM Server,
you can also set the ``emulate`` flag to ``True``. This will emit any target
Expand All @@ -157,39 +172,33 @@ Submitting
.. code:: python

cudaq.sample(kernel, shots_count=10000)


To see a complete example for using IQM server backends, take a look at our :doc:`Python examples<../../examples/examples>`.


.. tab:: C++

To target quantum kernel code for execution on an IQM Server,
pass the ``--target iqm`` flag to the ``nvq++`` compiler, along with a specified ``--iqm-machine``.

.. note::
The ``--iqm-machine`` is a mandatory argument. This provided architecture must match
the device architecture that the program has been compiled against. The hardware architecture for a
specific IQM Server may be checked via `https://<IQM server>/cocos/quantum-architecture`.
To target quantum kernel code for execution on an IQM Server,
pass the ``--target iqm`` flag to the ``nvq++`` compiler.

.. code:: bash

nvq++ --target iqm --iqm-machine Crystal_5 src.cpp
nvq++ --target iqm src.cpp

Once the binary for a specific IQM QPU architecture is compiled, it can be executed against any IQM Server with the same QPU architecture:
Once the binary for an IQM QPU is compiled, it can be executed against any IQM Server:

.. code:: bash

nvq++ --target iqm --iqm-machine Crystal_5 src.cpp -o program
IQM_SERVER_URL="https://demo.qc.iqm.fi/cocos" ./program

# Executing the same program against an IQM Server with a different underlying QPU
# architecture will result in an error.
IQM_SERVER_URL="https://<Crystal_20 IQM Server>/cocos" ./program
nvq++ --target iqm src.cpp -o program
IQM_SERVER_URL="https://demo.qc.iqm.fi/" ./program

To emulate the IQM machine locally, without submitting to the IQM Server,
you can also pass the ``--emulate`` flag to ``nvq++``. This will emit any target
specific compiler diagnostics, before running a noise free emulation.

.. code:: bash

nvq++ --emulate --target iqm --iqm-machine Crystal_5 src.cpp
nvq++ --emulate --target iqm src.cpp
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe emulation would not work fully as expected - it would not do include the layout for a specific device.
Let me get back on that with some options.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In case of emulation the code currently uses the static Crystal_20.txt file as architecture. This is more a workaround than a solution since there is no machine in this case. With this the emulation testcases pass while they stay below 20 qubits (basically). We still allow the user to pass a "mapping_file" but I would not expect that anybody would do this for the emulation mode. There is the "mapping" testcase which still uses and tests this parameter.
Because the emulator does not have a fixed architecture (I think it takes it from the architecture file) I at first thought that I could do without the architecture file but soon found that this ends in sporadic crashes. So I supplied the above mentioned static architecture file. But it could also be any other architecture file and does not even have to reflect an IQM architecture. The options I see are:

  1. we generate an generic square lattice architecture file with a defined number of qubits
  2. we generate an architecture file for an IQM crystal architecture of 20/54/150 qubits
  3. we enforce that the user has to give an architecture file in emulation mode
    The generated files would be temporary just like the ones the code generates with this change from the IQM machines. For solution 2 the size of the architecture could be either hardcoded or given as parameter. For simplification I would however just hardcode the biggest one. Solution 3 seems to be the most cumbersome for the user as it requires to be in possession of an architecture file. I would avoid that.
    I would be happy to know your take on this.


To see a complete example, take a look at :ref:`IQM examples <iqm-examples>`.

Expand Down
7 changes: 4 additions & 3 deletions lib/Optimizer/CodeGen/TranslateToIQMJson.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/*******************************************************************************
* Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. *
* All rights reserved. *
* Copyright 2025 IQM Quantum Computers *
* *
* This source code and the accompanying materials are made available under *
* the terms of the Apache License 2.0 which accompanies this distribution. *
Expand Down Expand Up @@ -121,10 +122,10 @@ static LogicalResult emitOperation(nlohmann::json &json,
emitter.getOrAssignName(optor->getResult(1),
emitter.getOrAssignName(optor.getTarget(0)).str());
} else {
json["name"] = name;
json["name"] = "prx";

if (optor.getParameters().size() != 2)
optor.emitError("IQM phased_rx gate expects exactly two parameters.");
optor.emitError("IQM prx gate expects exactly two parameters.");

auto parameter0 =
cudaq::getParameterValueAsDouble(optor.getParameters()[0]);
Expand Down Expand Up @@ -154,7 +155,7 @@ static LogicalResult emitOperation(nlohmann::json &json,

static LogicalResult emitOperation(nlohmann::json &json,
cudaq::Emitter &emitter, quake::MzOp op) {
json["name"] = "measurement";
json["name"] = "measure";
std::vector<std::string> qubits;
for (auto target : op.getTargets())
qubits.push_back(emitter.getOrAssignName(target).str());
Expand Down
22 changes: 2 additions & 20 deletions python/tests/backends/test_IQM.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# ============================================================================ #
# Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. #
# All rights reserved. #
# Copyright 2025 IQM Quantum Computers #
# #
# This source code and the accompanying materials are made available under #
# the terms of the Apache License 2.0 which accompanies this distribution. #
Expand Down Expand Up @@ -30,21 +31,6 @@
# Define the port for the mock server
port = 62443

# If we're in a git repo, test that we can provide a filename with spaces.
# If we are not in a git repo, then simply test without overriding
# mapping_file. (Testing a mapping_file with spaces is done elsewhere, and
# that isn't the main point of these tests.)
with os.popen("git rev-parse --show-toplevel") as f:
git_top = f.read().strip()
if os.path.isdir(git_top):
target_config_origin = os.path.join(
f"{git_top}", "runtime/cudaq/platform/default/rest/helpers/iqm")
target_config_dest = os.path.join(f"{git_top}", "targettests")
shutil.copy(os.path.join(target_config_origin, "Crystal_5.txt"),
os.path.join(target_config_dest, "Crystal_5_Variant.txt"))
shutil.copy(os.path.join(target_config_origin, "Crystal_20.txt"),
os.path.join(target_config_dest, "Crystal_20_Variant.txt"))


def assert_close(want, got, tolerance=1.0e-5) -> bool:
return abs(want - got) < tolerance
Expand All @@ -68,11 +54,7 @@ def startUpMockServer():
cudaq.set_random_seed(13)
# Set the targeted QPU
os.environ["IQM_TOKENS_FILE"] = tmp_tokens_file.name
kwargs = {"qpu-architecture": "Crystal_20"}
if os.path.isdir(git_top):
mapping_file = f"{git_top}/targettests/Crystal_20_Variant.txt"
kwargs["mapping_file"] = mapping_file
cudaq.set_target("iqm", url="http://localhost:{}".format(port), **kwargs)
cudaq.set_target("iqm", url="http://localhost:{}".format(port))

yield "Running the tests."

Expand Down
10 changes: 4 additions & 6 deletions runtime/cudaq/platform/default/rest/helpers/iqm/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,14 @@
# ============================================================================ #
target_sources(cudaq-rest-qpu PRIVATE IQMServerHelper.cpp)
add_target_config(iqm)
add_target_mapping_arch(iqm "Crystal_5.txt")
add_target_mapping_arch(iqm "Crystal_20.txt")
add_target_mapping_arch(iqm "Crystal_54.txt")
add_target_mapping_arch(iqm Crystal_20.txt)
Copy link
Collaborator

@bettinaheim bettinaheim Sep 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should also be removed here, and instead someone that uses emulation should create an appropriate file themselves (maybe we can have an option to dump the architecture file for a specific url instead of deleting it?). The section in the docs about emulation needs to clarify this, and could link to the files used for testing as an example.



add_library(cudaq-serverhelper-iqm SHARED IQMServerHelper.cpp )
target_link_libraries(cudaq-serverhelper-iqm
PUBLIC
cudaq-common
fmt::fmt-header-only
PUBLIC
cudaq-common
fmt::fmt-header-only
)
install(TARGETS cudaq-serverhelper-iqm DESTINATION lib)

78 changes: 0 additions & 78 deletions runtime/cudaq/platform/default/rest/helpers/iqm/Crystal_54.txt
Copy link
Collaborator

@bettinaheim bettinaheim Sep 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With this setup, I think we should no longer have any architecture files checked in in the runtime. The Crystal_5 and Crystal_20 files used for testing can be added to the appropriate test directory instead.

This file was deleted.

Loading
Loading