Skip to content

Commit fcf206a

Browse files
committed
⚡ Optimize portable wheels with Clang and ThinLTO
Use the manylinux Clang distribution and matching LLD, Apple Clang IPO, and speed optimization in nanobind modules. Keep macOS Python modules in a two-level namespace. Select portable CPU settings explicitly for wheels and retain native source-build defaults. Test installed CMake consumers with GCC and Clang, including all concrete DD operations, and document local build settings. Assisted-by: GPT-6 via Codex
1 parent 0ad5f53 commit fcf206a

11 files changed

Lines changed: 250 additions & 18 deletions

File tree

‎.github/workflows/cd.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ jobs:
2828
windows-2025,
2929
windows-11-arm,
3030
]
31-
uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-python-packaging-wheel-cibuildwheel.yml@fd3d75dece69c363989347869529a5e99cdcfffc # v2.5.1
31+
uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-python-packaging-wheel-cibuildwheel.yml@fd41eaef61a38f4675a11c4d766bb4c5c80affe4 # PR #464
3232
with:
3333
runs-on: ${{ matrix.runs-on }}
3434
setup-mlir: true

‎.github/workflows/ci.yml‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ jobs:
4646
preset: release
4747
run-on-draft: false
4848
setup-mlir: true
49-
uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-cpp-tests-ubuntu.yml@fd3d75dece69c363989347869529a5e99cdcfffc # v2.5.1
49+
uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-cpp-tests-ubuntu.yml@862a912fb48d265d877d6a37a097b135d9dac6d3 # PR #491
5050
with:
5151
runs-on: ${{ matrix.runs-on }}
5252
compiler: ${{ matrix.compiler }}
@@ -115,7 +115,7 @@ jobs:
115115
name: 🇨 Coverage
116116
needs: change-detection
117117
if: fromJSON(needs.change-detection.outputs.run-cpp-tests)
118-
uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-cpp-coverage.yml@fd3d75dece69c363989347869529a5e99cdcfffc # v2.5.1
118+
uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-cpp-coverage.yml@862a912fb48d265d877d6a37a097b135d9dac6d3 # PR #491
119119
with:
120120
setup-mlir: true
121121
llvm-version: 23.1.2
@@ -127,7 +127,7 @@ jobs:
127127
name: 🇨 Lint
128128
needs: change-detection
129129
if: fromJSON(needs.change-detection.outputs.run-cpp-linter)
130-
uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-cpp-linter.yml@fd3d75dece69c363989347869529a5e99cdcfffc # v2.5.1
130+
uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-cpp-linter.yml@862a912fb48d265d877d6a37a097b135d9dac6d3 # PR #491
131131
with:
132132
clang-version: 23
133133
build-project: true
@@ -154,7 +154,7 @@ jobs:
154154
fail-fast: false
155155
matrix:
156156
runs-on: [ubuntu-26.04, ubuntu-26.04-arm, macos-26, windows-2025]
157-
uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-python-tests.yml@fd3d75dece69c363989347869529a5e99cdcfffc # v2.5.1
157+
uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-python-tests.yml@862a912fb48d265d877d6a37a097b135d9dac6d3 # PR #491
158158
with:
159159
runs-on: ${{ matrix.runs-on }}
160160
setup-mlir: true
@@ -211,7 +211,7 @@ jobs:
211211
windows-2025,
212212
windows-11-arm,
213213
]
214-
uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-python-packaging-wheel-cibuildwheel.yml@fd3d75dece69c363989347869529a5e99cdcfffc # v2.5.1
214+
uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-python-packaging-wheel-cibuildwheel.yml@fd41eaef61a38f4675a11c4d766bb4c5c80affe4 # PR #464
215215
with:
216216
runs-on: ${{ matrix.runs-on }}
217217
setup-mlir: true

‎.github/workflows/upstream.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ jobs:
2121
fail-fast: false
2222
matrix:
2323
runs-on: [ubuntu-26.04, macos-15, windows-2025]
24-
uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-qiskit-upstream-tests.yml@fd3d75dece69c363989347869529a5e99cdcfffc # v2.5.1
24+
uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-qiskit-upstream-tests.yml@862a912fb48d265d877d6a37a097b135d9dac6d3 # PR #491
2525
with:
2626
runs-on: ${{ matrix.runs-on }}
2727
setup-z3: true

‎cmake/AddMQTPythonBinding.cmake‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ function(add_mqt_python_binding package_name target_name)
1515
${target_name}
1616
# Enable free-threaded support
1717
FREE_THREADED
18+
# Keep computation in bindings optimized for speed.
19+
NOMINSIZE
1820
# Suppress compiler warnings from the nanobind library
1921
NB_SUPPRESS_WARNINGS
2022
# Use nanobind's shared runtime. Split mode enables the Stable ABI internally.
@@ -40,6 +42,8 @@ function(add_mqt_python_binding package_name target_name)
4042

4143
# Keep statically linked dependencies local.
4244
if(APPLE)
45+
# Restore the Python module namespace changed by HandleLLVMOptions, as in AddMLIRPython.
46+
target_link_options(${target_name} PRIVATE "LINKER:-twolevel_namespace")
4347
target_link_options(${target_name} PRIVATE "LINKER:-exported_symbol,_PyInit_${module_name}")
4448
elseif(UNIX)
4549
target_link_options(${target_name} PRIVATE "LINKER:--exclude-libs,ALL")

‎cmake/StandardProjectSettings.cmake‎

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -49,11 +49,6 @@ if(DEFINED ENV{DEPLOY})
4949
$ENV{DEPLOY}
5050
CACHE BOOL "Use deployment configuration from environment" FORCE)
5151
message(STATUS "Setting deployment configuration to '${DEPLOY}' from environment")
52-
elseif(DEFINED ENV{CI})
53-
set(DEPLOY
54-
ON
55-
CACHE BOOL "Set deployment configuration to ON for CI" FORCE)
56-
message(STATUS "Setting deployment configuration to '${DEPLOY}' for CI")
5752
endif()
5853

5954
# set deployment specific options

‎docs/installation.md‎

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,10 +74,19 @@ python -c "import mqt.core; print(mqt.core.__version__)"
7474

7575
This prints the installed package version.
7676

77-
## Building from Source for Performance
77+
## Build performance
7878

79-
To get the best performance and enable platform-specific optimizations not
80-
available in portable wheels, we recommend building the library from source:
79+
Release wheels use portable CPU settings and the assertion-free LLVM/MLIR 23.1.2
80+
SDK. Linux wheels target manylinux_2_28 and use Clang 22 with LLD and ThinLTO;
81+
macOS wheels use Apple Clang and ThinLTO with a macOS 13.3 deployment target.
82+
The wheels include native shared libraries and a CMake package for C++
83+
applications. Consumers do not need the compiler's LTO plugin, but must use a
84+
compatible C++ standard library and ABI. Use the QDMI C interface for device
85+
plugins.
86+
87+
### Building from source
88+
89+
Build from source to tune Core for the machine that will run it:
8190

8291
::::{tab-set}
8392
:sync-group: installer
@@ -106,6 +115,32 @@ This requires a C++20-capable
106115
[C++ compiler](https://en.wikipedia.org/wiki/List_of_compilers#C++_compilers)
107116
and [CMake](https://cmake.org/) 3.28 or newer.
108117

118+
Release source builds default to `DEPLOY=OFF`, which enables native CPU tuning
119+
and LTO when the compiler supports them. On Linux, Clang with its matching LLD
120+
linker is a useful choice; on macOS, use Apple Clang from Xcode. For example,
121+
with Clang 23 installed on Linux:
122+
123+
```console
124+
CC=clang-23 CXX=clang++-23 uv pip install mqt.core --no-binary mqt.core \
125+
-Ccmake.define.CMAKE_LINKER_TYPE=LLD
126+
```
127+
128+
Keep native builds on compatible CPUs. For redistribution, set
129+
`-Ccmake.define.DEPLOY=ON` and choose the target platform's compiler and system
130+
baseline. Cibuildwheel sets deployment mode explicitly for release wheels. The
131+
`DEPLOY` environment variable overrides the CMake setting.
132+
133+
Clang and Apple Clang use ThinLTO through CMake's `ENABLE_IPO` option. For a
134+
local C++ build, `cmake --preset release` selects the same release defaults;
135+
pass `-DENABLE_IPO=OFF` to disable LTO. GCC can use mold 3 or newer with
136+
`-DCMAKE_LINKER_TYPE=MOLD`. Clang with mold also needs a matching LLVM LTO
137+
plugin; LLD includes the required support.
138+
139+
Native tuning and LTO apply to the Core code being compiled. Prebuilt LLVM/MLIR
140+
SDK libraries retain their own build settings, and LTO does not optimize across
141+
separate shared libraries. Benchmark your application before changing the
142+
compiler or LTO settings.
143+
109144
## Integrating MQT Core into Your Project
110145

111146
To use the MQT Core Python package in your project, add it as a dependency in

‎pyproject.toml‎

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -254,36 +254,54 @@ build = "cp3*"
254254
skip = [ "*-musllinux_*", "cp314t-*" ]
255255
archs = "auto64"
256256
build-frontend = "uv"
257-
test-command = "pytest test/python"
257+
test-command = "python test/cmake/check_installed.py && pytest test/python"
258258
test-groups = [ "test" ]
259259
test-sources = [
260260
"bindings/mlir/qiskit/SupportedVersions.inc",
261261
"json",
262262
"scripts/check_docs_links.py",
263263
"scripts/qiskit_c_api_adopt.py",
264+
"test/cmake",
264265
"test/circuits",
265266
"test/python",
266267
"test/slurm/run_integration.py",
267268
"vendor/qiskit-c-api/2.5.0",
268269
]
269270

270271
[tool.cibuildwheel.linux]
271-
environment = { DEPLOY = "ON", PATH = "$PATH:/root/.local/bin", MLIR_DIR = "/opt/llvm-23.1.2/lib/cmake/mlir" }
272+
config-settings = { "cmake.define.ENABLE_IPO" = "ON", "cmake.define.CMAKE_LINKER_TYPE" = "LLD" }
273+
environment = { DEPLOY = "ON", CC = "clang", CXX = "clang++", PATH = "/opt/clang/bin:$PATH:/root/.local/bin", MLIR_DIR = "/opt/llvm-23.1.2/lib/cmake/mlir" }
274+
environment-pass = [
275+
"CMAKE_C_COMPILER_LAUNCHER",
276+
"CMAKE_CXX_COMPILER_LAUNCHER",
277+
"SCCACHE_GHA_ENABLED",
278+
"SCCACHE_GHA_RW_MODE",
279+
"SCCACHE_IDLE_TIMEOUT",
280+
"ACTIONS_CACHE_SERVICE_V2",
281+
"ACTIONS_RESULTS_URL",
282+
"ACTIONS_RUNTIME_TOKEN",
283+
]
272284
before-all = """
285+
manylinux-install-clang -v 22.1.8.1
273286
uv tool install "sccache>=0.10.0"
274287
mkdir -p /tmp/setup-mlir
275288
curl -fLsS https://github.com/munich-quantum-software/setup-mlir/archive/8d3eae73d0f0196fd30c787d91ebd2018cb6c709.tar.gz | tar -xz -C /tmp/setup-mlir --strip-components=1
276289
bash /tmp/setup-mlir/installation/setup-mlir.sh -v 23.1.2 -p /opt/llvm-23.1.2 -a OFF
277290
"""
278291
before-test = "uvx sccache --show-stats"
292+
test-command = """\
293+
CC=gcc CXX=g++ python test/cmake/check_installed.py && python test/cmake/check_installed.py && pytest test/python\
294+
"""
279295
# QIR-Runner's Linux wheels require a newer glibc than the manylinux_2_28
280296
# containers used to build and test MQT Core's broadly compatible wheels.
281297
test-groups = [ "test-base" ]
282298

283299
[tool.cibuildwheel.macos]
284-
environment = { MACOSX_DEPLOYMENT_TARGET = "13.3" }
300+
config-settings = { "cmake.define.ENABLE_IPO" = "ON" }
301+
environment = { DEPLOY = "ON", MACOSX_DEPLOYMENT_TARGET = "13.3" }
285302

286303
[tool.cibuildwheel.windows]
304+
environment = { DEPLOY = "ON" }
287305
repair-wheel-command = "delvewheel repair -w {dest_dir} {wheel} --namespace-pkg mqt --ignore-existing"
288306

289307
[[tool.cibuildwheel.overrides]]

‎test/CMakeLists.txt‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,9 @@ add_subdirectory(bench)
1111
add_subdirectory(dd)
1212
add_subdirectory(qdmi)
1313

14+
# Wheel tests run this probe against the installed package; retain its compile command for lint.
15+
add_executable(mqt-core-dd-consumer EXCLUDE_FROM_ALL cmake/installed_consumer/dd_consumer.cpp)
16+
target_link_libraries(mqt-core-dd-consumer PRIVATE MQT::CoreDD)
17+
1418
# copy test circuits to build directory
1519
file(COPY ${PROJECT_SOURCE_DIR}/test/circuits DESTINATION ${CMAKE_CURRENT_BINARY_DIR})

‎test/cmake/check_installed.py‎

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# Copyright (c) 2023 - 2026 Chair for Design Automation, TUM
2+
# Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH
3+
# All rights reserved.
4+
#
5+
# SPDX-License-Identifier: MIT
6+
#
7+
# Licensed under the MIT License
8+
9+
"""Build and run a C++ consumer of the installed wheel."""
10+
11+
from __future__ import annotations
12+
13+
# This standalone check executes trusted build tools and the installed test binary.
14+
# ruff: file-ignore[implicit-namespace-package, subprocess-without-shell-equals-true, start-process-with-partial-path]
15+
import subprocess
16+
import tempfile
17+
from importlib.metadata import distribution
18+
from pathlib import Path
19+
20+
21+
def main() -> None:
22+
"""Check installed libraries, package settings, and the device helper."""
23+
source = Path(__file__).parent / "installed_consumer"
24+
with tempfile.TemporaryDirectory() as directory:
25+
subprocess.run(
26+
[
27+
"cmake",
28+
"-S",
29+
str(source),
30+
"-B",
31+
directory,
32+
"-DCMAKE_BUILD_TYPE=Release",
33+
"-DCMAKE_PREFIX_PATH=" + str(distribution("mqt-core").locate_file("mqt/core")),
34+
],
35+
check=True,
36+
)
37+
subprocess.run(["cmake", "--build", directory, "--config", "Release", "--parallel", "2"], check=True)
38+
subprocess.run(["ctest", "--test-dir", directory, "-C", "Release", "--output-on-failure"], check=True)
39+
40+
41+
if __name__ == "__main__":
42+
main()

‎test/cmake/installed_consumer/CMakeLists.txt‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,3 +96,13 @@ add_custom_command(
9696
${CMAKE_COMMAND} -E compare_files
9797
"${CMAKE_CURRENT_BINARY_DIR}/$<CONFIG>/consumer-device.qdmi.json"
9898
"$<TARGET_FILE_DIR:consumer-device>/consumer-device.qdmi.json")
99+
100+
enable_testing()
101+
add_executable(dd-consumer dd_consumer.cpp)
102+
target_link_libraries(dd-consumer PRIVATE MQT::CoreDD)
103+
add_test(NAME dd-consumer COMMAND dd-consumer)
104+
if(WIN32)
105+
set_tests_properties(
106+
dd-consumer PROPERTIES ENVIRONMENT_MODIFICATION
107+
"PATH=path_list_prepend:$<TARGET_FILE_DIR:MQT::CoreDD>")
108+
endif()

0 commit comments

Comments
 (0)