Refactor Build Systems: CMake & PIP
Build Systems Refactor (CMake & PIP)
"Simplicity is not a simple thing" - Charlie Chaplin, maybe...
This document both presents & documents most changes made by the refactor. Features have changed little. Great care has been taken to simplify them and document them as much as possible.
The goals are to:
- improve stability across OSes and IDEs,
- reduce maintenance effort,
- enhance the experience for both maintainers & users,
- and fix known/some encountered bugs.
About performance
Some changes are related to improving performance. Some "low-hanging fruits" changes, like supporting CCache, are already available through this refactor. But the main work will be done in other merge requests. Already many things have been done like profiling and include-what-you-use, in order to carefully design our Precompiled Headers (PCH).
Breaking changes
- Modules external to the monorepo have not been updated and may fail to compile/find dependencies.
- Editable install is experimental.
🧹 Current State
Below is the list of affected modules and remaining tasks.
-
setup.ps1 -
setup.sh -
Core -
Export
-
-
Backend CPU -
Backend CUDA -
Backend OpenCV -
Model explorer -
Export CPP -
Export_cortex_arm -
Export_tensort -
Interop-torch -
ONNX -
Learning -
Quantization -
Fix OMP issue -
Fix half_float error
-
Tasks left:
-
setup.sh ! -
docs -
Release/debug with pip / multi-generators -
install pybind -
CCache warning not enabled ! -
Remove -ZC:preprocessor- from clang-cl -
Update setup.sh -
"cudnn.lib" is not found. -
Update README.md for each modules -
Test/Check coverage -
Update CI -
Check, test, validate, etc. -
Setup.ps1 does build tests event when not set (because it is a top project) -
Setup.ps1 does not detect fails -
Check if scikit does not require to specify the build type to release so the config file are generated & installed -
Handle build python bindings from cmake for dev ! -> AIDGE_BUILD_PYTHON AS A CACHE VARIABLE -
Remove version.hpp.in ! -
Improve ps1 with more checks -
Check cuda installation
-
-
CMAKE ARCH -
Fix installation path ?? -
OpenCV dll not found ??
-
-
Put back -config.cmake.in -
Switch to AIDGE_INSTALL_PREFIX
📝 Summary
This section summarize changes done by this MR. The next section will go into more details for some of these changes.
Note
Not all changes/experimentations have been explained / documented.
CI
- Updated CI to align with the refactor
Potential issues
If an installed python package is removed, either manually or from a MR, this may break the pipeline for every other jobs.
A solution would be to key the folder with the hash of files it watches (only docker/requirements.txt).
-
aidge_export_arm_cortexmtests requiresaidge_export_cpp, so it has been added as a dependency. - Add pip's cache folder to CI cache
- On ubuntu, permissions are missing for it to work ? Not a problem for now, as we are relying on the docker image.
- On windows, venv is sometimes still recreated even though there is no change ?
- Compute $PYTHONPATH beforehand to handle separation of aidge's module installation from
site-packages.- Rename
build:windows:dependenciestosetup:windows:dependencies - Add
setup:ubuntu:dependencies
- Rename
Note
One downside of this approach, is that every job requiring an up-to-date $PYTHONPATH, must include the relevant setup jobs in their needs.
C++ Related
- Integrate Aidge - CMake Kit.
- Add
cmake/get_ack.cmake, a stable script to fetch the latest version ofack.cmake. - ACK now handles common, cross-module configurations and API, leaving CML files to manage module-specific settings.
- Rename
aidge-*-config.cmake.intoconfig.cmake.in(easier to share). - Integrate CPM.cmake, a CMake Package Manager.
- This will download and build from source code.
-
ℹ️ Overall, configuration time is faster:- With
CPM_SOURCE_CACHE, a full reconfigure goes from52sto8s. - A cached configure goes from
14sto0.8s).
- With
- Fix some configurations issues, especially when it comes to Windows (msvc & clang-cl).
- Many useful utilities functions, like a handy summary to check if installation is correct during configuration !
- For example:
- If building python bindings, a warning is issued if no virtual environment is active.
-
aidge_backend_opencvhas a warning when building python bindings on windows ifOPENCV_DLL_DIRis not set.
- For example:
- Add
Reduce reliance on ACK
I somewhat backtracked by reducing ACK's importance.
Every C++ modules links to aidge_core, so many things could be done from aidge_core, rather than ACK.
However, due to not having a top-project (managing every modules), ACK, among others things, is still needed.
- Change CMake minimum version from
🔴 3.18to🟢 3.22.- Default version available in Ubuntu 22.04
- Required for CMake Presets (introduced in 3.19)
- For some goodies like
cmake_path(introduced in 3.20). -
ℹ️ 3.30is highly recommend due to MSVC and OpenMP, but also for some crucial generator expressions to improve cross-platform support, like$<CMAKE_CXX_COMPILER_FRONTEND_VARIANT>
Warning
The variable was added in 3.14, but the generator expression in 3.30 ! This is crucial for installable & relocatable package !
- Rework all cmake scripts
- Most of the work done was to simplify them as much as possible and match standard/conventions.
- Many simplification were possible thanks to the switch to scikit-build-core.
- Add CMake presets
-
⚠️ Ninjais used by default and should be installed. - Enable more warnings
-
⚠️ Expects a lot more warnings ! - Those from third-party libraries are suppressed ...
- ... but many more have been added.
- Some have been suppressed for Aidge, but maybe others could also be suppressed - to discuss.
-
- Improve and fix module's version.
- Each module now generate a unique header:
#include <aidge/[module]/version.hpp> - Configure file
include/aidge/version.hpp.inis removed from versionning- It is generated by ACK
-
sys_utils/...VersionInfo.hpphas been reworked to reflect changes and prevent ODR issue.- I would advise to remove the wrapper over the module version.
- Each version from a module is now namespaced.
- e.g. `Aidge::core::Version::full // returns "..".
- This would have led to an ODR violation, if versions from multiple modules were included.
- Each module now generate a unique header:
- For aidge_backend_cpu:
- CUDA
12is set as an hard-requirement (instead of a soft-requirement)- Otherwise, CUDA
13could be accepted, but it would raise an error, since it (Thrust) requiresC++17. - CUDA
12.4.1is troublesome, espcially on windows. I would recommend CUDA12.6.0+
- Otherwise, CUDA
- Change
ILayerNormImpl's forward dimension vector type fromvector<unsigned long int>tovector<DimSize_t>- Otherwise, it would not compile on Windows (msvc).
- It also prevent a downward cast and loss of precision.
- Idem for
ShiftGELUImplandShiftMax.
- CUDA
Note
Changes to the source code were intentionally limited to keep the MR focused.
🐍 Python Related
- Switch from
setuptoolsto a PEP 517 & PEP 518 build backend, called scikit-build-core- Remove
setup.py,setup.cfg,project_name.txt,manifest.in&cmake/pybind*.cmake - Changes
pyproject.tomlto adapt to new backend.- Python's version is deduced from
version.txt(as is CMake's version)
- Python's version is deduced from
- Remove
- Add dependencies to
pyproject.toml, e.gaidge_onnxtoaidge_model_explorer -
pybind11only needs to be fetched when it is not found.-
Scikitprovides it. - Only
aidge_coreactually fetchs it. It will then be installed, so others won't need to fetch it. Impact is minimal, but it simplifies CML.
-
🧰 Tools Related
- Apply changes to
setup.ps1.-
setup.ps1has been heavily reworked, to add more checks/hints. Now, there is only 1 script that works both from being called from aidge bundle or a module.
-
- Enable automatic support of CCache if installed.
- A new tool
aidge/tools/Sync-SharedFiles.ps1to sync shared files across modules.- A neat feature is the ability to open
git difftoolto merge conflicts. - Automatic relative path resolution
aidge/aidge_core/CMakeLists.txtis resolved to justCMakeLists.txtwhen copied insideaidge/aidge_backend_opencv/.
- A neat feature is the ability to open
Warning
When using a multi-config generator like MSBuild, it seems CMAKE_BUILD_TYPE is not respected. It may be why during installation, the correspoding files is not installed.
-
tools/ci_python_path.pyis a script meant exclusively for our CI to compute the PYTHONPATH.
📚 Details
This section goes into more details for some changes.
CMake Options / Cache Variables
Most CMake options & cache variables have been reworked.
- In order to prevent clashing with other projects, options and cache variables are prefixed by
AIDGE_. - You won't likely need to modify any option / cache variables.
- Defaults are good enough
- Some have been removed and are deduced by how you call CMake (from pip or not).
- Experimental option / cache variables are prefixed by
⚠️ .
| Old Option | New Option | Description | Default value |
|---|---|---|---|
PYBDIND
|
AIDGE_BUILD_PYTHON
|
Set automatically when run through pip
|
- |
TEST
|
AIDGE_BUILD_TESTING
|
Toggle C++ tests | ${PROJECT_IS_TOP_LEVEL} |
AIDGE_ENABLE_COVERAGE
|
Enable coverage | OFF | |
CUDA
|
AIDGE_ENABLE_CUDA
|
Only needed for modules with optional CUDA support, like aidge_quantization
|
OFF |
AIDGE_ENABLE_EXCEPTIONS
|
Enable C++ exceptions | ON | |
AIDGE_ENABLE_IWYU
|
Enable include-what-you-use | OFF | |
AIDGE_ENABLE_SANITIZER
|
Enable sanitizer | OFF | |
AIDGE_ENABLE_WERROR
|
Enable warnings as error | OFF | |
CMAKE_EXPORT_COMPILE_COMMANDS
|
Needed for LSP servers. | ON | |
PYBDIND11_FINDPYTHON
|
Handled Automatically | - |
Environment Variable Change (Old)
The confusing "_INSTALL" syntax has beed removed. It is usually an option to toggle install.
❌ AIDGE_INSTALL
Environment Variable Change (New)
The new, more explicit environment variable is AIDGE_INSTALL_PREFIX. This matches the usage in CI and some modules, and is expected by CMake presets.
✅ AIDGE_INSTALL_PREFIX
- Q: Why
AIDGE_BUILD_TESTING?:
The name aligns better with standard CMake practices.
It's unlikely to require manual override, as defaults are usually sufficient.
Also, BUILD_TESTING is not used as we don't include CTest.
Instead, we directly call enable_testing().
The reason is include(CTest) adds a bunch of clutter we don't use.
- Q: When are C++ tests build ?
By default, tests are only configured when project is top-level.
Tests can be forced by setting AIDGE_BUILD_TESTING to ON.
By default, C++ tests are not configured when building from pip.
Otherwise, change the following inside pyproject.toml.
[tool.scikit-build.cmake.define]
AIDGE_BUILD_TESTING = "OFF" # ⬅️ Set this to ON
Note
Scikit has three way to set/override an option.
The one from above is from the pyproject.toml.
But you can also do it from the command line.
I will let you check its documentation to see how.
- Q: Why
AIDGE_ENABLE_EXCEPTIONS?
Most libraries do not need/use exceptions, as many user have to disable them. But libraries that do use them, generally provide an option to toggle them. Currently, this will break aidge if disabled, as there are no fallback in source code.
Note
Both MSVC AND Clang-CL needs flags to enable exceptions.
When this flag is set to ON, ACK ensure this is done correctly.
- Q: When should I set
AIDGE_BUILD_PYTHON(formerly known asPYBIND) ?
This refactor include the "pip refactor" (more about this later) and now use scikit-build-core.
When using pip install ., this will run cmake and set/adjust some variables.
One of them is ${SKBUILD}. We can use it to infer if cmake is run through pip or not.
By default, AIDGE_BUILD_PYTHON is set with this value.
BUT, you may want to debug the build process when building python bindings, without the hassle of going through pip.
So, you may want to set AIDGE_BUILD_PYTHON to ON for this.
Otherwise, you do not need to set it manually.
- Q: Why
SANITIZERinstead ofASAN?
ACK provides experimental support for many sanitizers:
| Sanitizer | Description |
|---|---|
ADDRESS |
Enables AddressSanitizer (ASan) for memory errors (buffer overflows, use-after-free). |
LEAK |
Enables LeakSanitizer (LSan) for detecting memory leaks. |
UNDEFINED_BEHAVIOR |
Enables UndefinedBehaviorSanitizer (UBSan) for errors like integer overflow or null dereference. |
THREAD |
Enables ThreadSanitizer (TSan) for detecting data races and threading issues. |
MEMORY |
Enables MemorySanitizer (MSan) for detecting the use of uninitialized memory (Clang only). |
So this option will toggle all of these sanitizers, if used/specified in the correspoding CMake call. Note, that all of these are not compatible with each other and across compiler, host, etc. It is quite tricky, but ACK should guard you against misusage.
CPM.cmake
CPM.cmake is a small CMake script for setup-free, cross-platform, reproducible dependency management.
CPMAddPackage("gh:fmtlib/fmt#7.1.3")
It is installed and used by Aidge - CMake Kit to install :
- FMT
- Tracy
- Catch2 (only when C++ tests are configured)
- Google Benchmark (only when C++ benchmarks are configured)
From your point of view it changes nothing.
Recommended setting
It is highly recommended to set CPM_SOURCE_CACHE to a folder that you can share across you C++ projects (or aidge's module at least).
This will prevent you from having to redownload those libraries everytime.
You can set it either as a variable environment or through a CMake preset.
- Q: How to support offline mode ?
In some ways, it does support offline mode.
If user are able to do git clone, they will also be able to clone dependencies, by configuring once.
Next runs won't need a connection.
But if users receive directly an archive, whatever the means, then dependencies should also be bundled.
There are several ways to solve this (vendor folders, bundle the CPM_SOURCE_CACHE, etc.)
If need be, every source location can be overriden by setting <package_name>_SOURCE_DIR.
Aidge support for find_package()
As before, modules are installable as independent package, using Config Mode.
find_package(aidge_core 0.7.0 REQUIRED)
Note 1
Version is optional.
Note 2
Would you like components to be packaged ?
find_package(Aidge 0.7.0 REQUIRED backend_cpu export_cpp) #core always included
CMake Search Procedure will look inside the <prefix> folder.
Usually, it requires to manipulate:
-
CMAKE_INSTALL_PREFIX: where packages should be installed during$ cmake --install. -
CMAKE_PREFIX_PATH: where to look for packages during$ cmake -S ....
Default behaviour removed
CMake scripts do not override CMAKE_INSTALL_PREFIX anymore.
So, from a manual configure (not from a preset),
the following command will install packages in the default location (which is usually the build directory).
❌ cmake --install
Explicit command
Instead, CMAKE_INSTALL_PREFIX must be explicitly set !
# either during configuration
✅ cmake -S ... -B ... -DCMAKE_INSTALL_PREFIX <AIDGE_INSTALL_PREFIX>
# or installation
✅ cmake --install --prefix <AIDGE_INSTALL_PREFIX>
BUT ! The provided CMakePresets.json defaults CMAKE_INSTALL_PREFIX and CMAKE_PREFIX_PATH to $env{AIDGE_INSTALL_PREFIX}.
More on that later.
TL;DR: From a user perspective nothing changed, but it will be clearer for packagers !
For ease of use, it is still recommended to set environment variable AIDGE_INSTALL_PREFIX.
The major reason behind this change is to ensure to play nicely with packagers, by not enforcing it from CMake scripts.
Setting CMAKE_INSTALL_PREFIX during configuration does not ensure it will be taken into account.
For example, during pip installation, this will be overriden by Scikit-build-core.
The link does not mention it explicitly.
But, when you run `pip install . -v' in verbose mode, this will be called:
cmake --install --prefix <path/to/site-packages>
An upside of this change, beside working nicely with packagers, is a clear separation between C++ and python packages.
When installing modules through pip install, they will be available, by default, in path/to/site-packages.
When installing modules through cmake --install, they will be available, by default with a preset, to AIDGE_INSTALL_PREFIX.
This means you don't risk of overriding/using a wrong installed version if you were to mix & match installation, between C++ and python.
CMake Presets
A quick introduction from CMake:
"One problem that CMake users often face is sharing settings with other people for common ways to configure a project. This may be done to support CI builds, or for users who frequently use the same build. CMake supports two main files, CMakePresets.json and CMakeUserPresets.json, that allow users to specify common configure options and share them with others. CMake also supports files included with the include field."
Every refactored module has a CMakePresets.json with the following configurations:
Tip
This lead to another duplication of a file across modules. It would make sense to have one top project with these common files, especially since we are now changing to a monorepo.
| Configuration | Inherited Configurations |
|---|---|
| msvc-debug | [ "common", "ninja", "windows", "x64", "msvc", "debug" ] |
| msvc-release | [ "common", "ninja", "windows", "x64", "msvc", "release" ] |
| clang-cl-debug | [ "common", "ninja", "windows", "x64", "clang-cl", "debug" ] |
| clang-cl-release | [ "common", "ninja", "windows", "x64", "clang-cl", "release" ] |
| windows-gcc-debug | [ "common", "ninja", "windows", "x64", "gcc", "debug" ] |
| windows-gcc-release | [ "common", "ninja", "windows", "x64", "gcc", "release" ] |
| clang-debug | [ "common", "ninja", "unix-like", "clang", "debug" ] |
| clang-release | [ "common", "ninja", "unix-like", "clang", "release" ] |
| gcc-debug | [ "common", "ninja", "unix-like", "gcc", "debug" ] |
| gcc-release | [ "common", "ninja", "unix-like", "gcc", "release" ] |
There are additional configurations you can inherit from, like :
- "tracy" - to enable tracy
- "test" - to enable tests (
ONby default when building a module as a top project, more on that later.) - "vcpkg" - to integrate
vcpkgtoolchain if you ever want to use a great package manager. - "python" - to enable builiding python bindings.
⚠️ YOU DO NOT NEED TO SET IT WHEN USING PIP, this is only relevant for debugging python bindings's build process.
This file has been extensively tested across IDEs and OSes & carefully crafted to try as much as possible to provide a decent UX.
- Some configurations will be hidden when not suitable for the current host platform, like
msvc-*on unix platforms. - They will also try to load the corresponding toolchain (e.g. when using JetBrains IDE CLion)
- But there are still some manual settings to set correctly that can only be done by the user. For example, toolchain's name from CLion must match the ones set in
CMakePresets.json.
- But there are still some manual settings to set correctly that can only be done by the user. For example, toolchain's name from CLion must match the ones set in
No need to specify every individual settings again and again.
❌ cmake -S . -B out/build/... -DCMAKE_... -DAIDGE_... -DCMAKE_CXX_COMPILER=... -DPYBIND_INSTALL_PREFIX_PATH=...
Use CMake Presets !
You can them like so:
cmake --preset msvc-debug -DCMAKE_INSTALL_PREFIX=<...> # You can still override options if need be !
cmake --build --preset msvc-debug # You sadly have to specify a build preset which match your configure preset.
ctest --preset msvc-debug # idem
cmake --install <build-dir> # Sadly, you have to find the corresponding build directory to install it !
A downside of CMakePreset.json is that you cannot use multiple presets from the command line.
Like, if you wanted to test msvc-debug with tracy.
Having to modify CMakePreset.json would be cumbersome and dangerous, since we could commit our local configuration.
But as you can see, you can easily inherit from other configurations.
So you can define your own preset !
This is where having your CMakeUserPresets.json is great.
These should not be versionned.
For example, this is the one I have :
{
"version": 9,
"cmakeMinimumRequired": {
"major": 3,
"minor": 23,
"patch": 0
},
"configurePresets": [
{
"name": "dev",
"inherits": [ "msvc-debug" ],
"cacheVariables":{
"CMAKE_COLOR_DIAGNOSTICS": "1"
}
}
],
"buildPresets": [ {"name": "dev", "configurePreset": "dev" } ],
"testPresets": [ {"name": "dev", "configurePreset": "dev" } ],
"workflowPresets":[
{"name": "dev", "steps": [
{ "type": "configure", "name": "dev" },
{ "type": "build", "name": "dev" },
{ "type": "test", "name": "dev" }
]
}]
}
Why no `out/${presetName}` folder and `build` instead ?
When CMAKE_EXPORT_COMPILE_COMMANDS is set to 1 (done by default through ACK), a compile_command.json is outputed inside the build folder.
Tools such as clangd will look for it inside build/ folder.
Overall, I am testing these presets across 4 IDEs:
This play nicely will all of them, especially NeoVim, without having to do some annoying configuration.
The downside is that you will lose your previous artefacts when changing presets.
But don't forget you can tweak your CMakeUserPresets.json to match your preferences.
Powershell script: setup.ps1
Each module has a powershell script `setup.ps1'. This script is not meant, nor required to build and install a module, either in C++ or Python. You can use your usual cmake and pip commands.
Note
The powershell script is the same for everyone including the bundle
Like before, it is just an helper script to make it easier/quicker to run. They have been adjusted to work with the refactor.
It also makes it easier for new-comers/beginners to build a module. More checks have been added to ensure installation is correct. I won't list them here, feel free to look directly in the script !
After
# Configure, build and install current module (if from a module) or default modules (if from the bundle)...
.\setup.ps1
# ... this time clean build folder ...
.\setup.ps1 -Clean
# ... ran tests this time
.\setup.ps1 -Clean -Tests
# ... or build & install python package and run pytest
.\setup.ps1 -Python -Tests
# ... or build & install python package and run pytest for "aidge_core" and "aidge_backend_cpu"
.\setup.ps1 -Modules "core", "backend_cpu" -Python -Tests
Get Help
Script is fully compatible with standard powershell way to retrieve help:
Get-Help setup.ps1
Aidge - CMake Kit (ACK)
This section is not meant to present ACK. There are to many things and to prevent duplication, I advise to directly check the repository.
[!HINT] ACK is not meant to change how you write CMake files. It is designed to be as readable and explicit as possible, while being maintainable.
Aidge provides a few functions, targets and variables that are opt-in. All of them are meant to be cross-platform. It should be transparent for the reader.
Everything specific to Aidge in ACK is in src/cmake/8_aidge.cmake.
It is meant to be read alongside a module's CML.
Make sure to read the comments at the top of src/cmake/8_aidge.cmake.
This will explain a lot about how this file and modules' CML were written.
A quick note about provided targets:
ack::vars
This target provides many defines, like ACK_HOST_MSVC, ACK_CLANG_CL, etc.
Aidge::warnings
This target provides many warnings flags for C++14 and onwards.
It also takes care to apply AIDGE_ENABLE_WERROR.
Aidge::optimisation
This target provides optimisation flags.
These targets should not be installed !
So they should be guarded like so : target_link_libraries(<mytarget> PRIVATE $<$<BUILD_INTERFACE>: Aidge::warnings>).
To be fair, I don't really want to go into the details as to why.
Long story short, it simplify both ACK and CML to do it like this, otherwise we would have duplicated targets when importing targets.
CUDA Adventures - Episode 1
As always, Windows brings some annoying quirks.
The gist of the problem : NVCC treats /xxx flags as FILES.
But NVCC expects only a single file to compile.
So you would expect rightly so, that passing /xxx flags will result in an error.
nvcc error : A single input file is expected
But this is not the case for every flag ! Some are recognized and handled correctly, like /EHsc.
Identifying this problem was quite troublesome.
The fix was even more annoying.
Somehow, /utf-8, which is required for fmt on Windows, was tricky.
Multiple solutions have been tested.
Note
I already did a possible fix without ACK, but this was quite a nightmare. With ACK at least, it was more straightforward.
Solution 1
Protect every mscv / clang-cl flags using the /xxx notation with a generator expression : $<$<COMPILE_LANGUAGE:CUDA>:/xxx>.
It is hideous and must be replicated inside every target compiled for backend_cuda, (so aidge_core, aidge_backend_cpu, ...), including 3rd-party libraries !
This is error-prone and troublesome to maintain, especially since this leads to modifying other modules that should not know about CUDA. The other problem is I didn't find the way to control some flags that where added automatically, by CMake, by nvcc or the IDE.
Also, this may not work for every flag, because some do need to be enabled even when compiling from CUDA !
Solution 2
My favorite solution is to simply use an nvcc flag: -forward-slash-prefix-opts NVCC documentation.
I could not find a single soul on Stack Overflow, CMake Discourse, Nvidia forums, ChatGPT and the like that talk about this, even famous modern guides, like An Introduction to Modern CMake, which talks about this problem. I discovered it by chance, while reviewing nvcc's documentation.
While it works perfectly, the flag is not available in Cuda 12.4.1.
I could not find an official statement as to when it was added, but by checking every documentation's archive,
it seems it was added in CUDA 12.6.0.
Solution 3
Did you know MSVC flags may use either / or - ? I didn't.
Here is the a link where it subtly mentioned.
Nobody use - for MSVC flags, IDEs and NVCC itself included.
CUDA Adventures - Episode 2
Another issue annoying issue is that NVCC may not use the same host compiler.
It should be only relevant when compiling with clang-cl or gcc on windows.
NVCC is designed to work with msvc.
It is troublesome for several reasons, especially in regards to flags that have been set for the other compiler.
We therefore cannot set these flags during configuration time, but only during build time.
# ❌ Evaluated at configuration time
if(ACK_HOST_WINDOWS)
target_compile_options(mytarget PRIVATE -bigobj)
endif()
# ✅ Evaluated at build time
target_compile_options(mytarget PUBLIC $<$<PLATFORM_ID:Windows>:-bigobj>)
So we have to rely on generator expressions.
But some crucial ones, especially to improve cross-platform support, like to check the frontend variant, are only available in CMake 3.30+.
Anyways, there is some custom ones have been made to not require 3.30.
CuDNN
If you ever have a problem with cudnn.lib not being found, the most straightforward solution is to place every CUDNN files in their corresponding CUDA folders.
Somehow, this is what the official documentation recommends within their manual instructions. The graphical installation should work out of the box, but it didn't work on my end and I had to resolve to manual install them.
OpenMP Adventures
Signed vs Unsigned indices
(See issue ])
Unsigned indices are supported only in OpenMP 3.0.
In aidge_quantization, many for loop use either unsigned int or size_t, which are both unsigned.
MSVC only support OpenMP 2.0 via /openmp.
Note
Il also applies to clang-cl which is a frontend-variant of msvc.
To enable unsigned indices, one solution is to use /openmp:llvm.
The best way to set it would be through OpenMP_RUNTIME_MSVC, but it was added in CMake 3.30+, and we require only CMake 3.22.
The problem is that while it does work for aidge_quantization, it does not work for aidge_backend_cpu which raise a dreaded error:
fatal error C1001: Internal Compiler Error.
From my understanding, MSVC raised this error for some OpenMP calls that are not supported.
After an internal team meeting, it was choosen to use signed indices in aidge_quantization.
Quantization Adventures
OpenMP and Quantization
Currently, Quantization is not build with openmp: aidge_quantization/CMakeLists.txt does not link to it and others targets that are linked to OpenMP do it privately.
When compiling with openmp, there are a number of issues/errors, for both Ubuntu and MSVC (they are not necessarily the same).
In the previous section we talked one of them.
An other is the following:
/aidge/aidge/aidge_quantization/include/aidge/backend/cpu/operator/LSQImpl_kernels.hpp:64:19: error: user defined reduction not found for 'diffStepSize'
64 | reduction(+ : diffStepSize) if (inputLength > 16)
This is the case for both MSVC and GCC.
It works with clang.
Variable diffStepSize is a templated type, that can be either double, float or half_float.
The culprit is half_float.
After talking with Benjamin on this issue, he agreed that we could set it to a double instead.
I added a preprocessor switch to force a double, only on cases where it triggered an error.
#if defined(ACK_MSVC) || defined(ACK_GCC)
double diffStepSize = 0.0
#else
GI diffStepSize = GI(0.0)
#endif
I can't say if this is a regression or not, since I don't know if it was ever tested in these configurations. Still I don't find this solution ideal.
It is straightforward to define the missing reduction ourselves :
#pragma omp declare reduction (+ : half_float::half : omp_out += omp_in) initializer(omp_priv = static_cast<half_float::half>(0.0))
Straightforward, right ? BUT of course not ! This is only supported by OpenMP 4.5+ and I'll let you refer to a previous section about the problem with MSVC and variant.
So I had to mix and match these two solutions have been implemented, with preprocessor instructions to try and use the best solution (i.e. supporting half_float) when possible.
CI Adventures
Cyril has already done extensive work to improve CI and setup the monorepo. I only had to do some minor tweaks to support the refactor. But it turns out to be way more time consuming than I first anticipated, especially in regards to caching. This discussion is only relevant in the context of our CI and our caching system.
A note about $PYTHONPATH
One noteworthy changes to the CI is how aidge's python modules are found.
After many tests, I decided to go with Cyril option to use --target <prefix_dir> with pip install . .
This option install a package in the target's directory.
You cannot install multiple packages in this folder, when they are overlapping folders, like bin/, include/ and lib/.
This is a problem with the refactor and scikit-build-core.
Long story short:
- The venv is really heavy (several gigabytes) and we have more than 100 jobs. While not all needs the venv, relying on artifacts would be extremely slow and it seems we can't rely on the native
cachefeature of GitLab. -
scikit-build-coreinstall C++ artifacts (bin/,include/andlib/) and the python package (aidge_<name>/) in aprefix/folder, which is usuallysite-packages/.- This is great for users, since it is already included in search path ! But in our CI we cannot do it : due to our Ubunutu runner, modifications to the virtual environnement are not propagated to others jobs. So we have to install them somewhere else.
- Pure python package do not have overlapping folders so this is fine.
- C++ modules have overlapping folders (again:
bin/,include/,lib/) so only the first artifact will be installed and next ones will be silently ignored.
CMake side, without any modifications, we could just simplify install each module inside a subfolder inside the target's directory.
So, this means we could do: pip install aidge/aidge_<name> --target <prefix_dir>/aidge_<name>.
It won't bother CMake, since the search procedure support this structure.
Note
There are some considerations to take into account for Aidge, especially in regards to 3rd party libraries, like fmt.
(It would not be able to find fmt).
There is a straightforward trick that I use for aidge_core/test_export, if you are willing to find it.
But this is not ideal.
But Python requires some tweaking.
We can not just simply add the <prefix_dir> to $PYTHONPATH.
We have to add every module subfolders to it.
The most maintainable and effective solution I could find is to append every modules subfolders to the $PYTHONPATH beforehand.
Why ?
We cannot let each job append its folder to the $PYTHONPATH.
Let's say I have job C, which depends on job A & B.
A & B both install a module but do not depends on one another.
Job C fetches the dotenv (and the updated $PYTHONPATH) only from the latest job.
This means it will see either A or B but not both.
So in order to setup $PYTHONPATH before hand, 2 new jobs have been added:
setup:ubuntu:dependenciessetup:windows:dependencies
In order to be more maintenable, every subfolder in aidge/ is considered.
So we don't have to add them manually and it will work if we ever modify our modules.
I made a quick python script to do it, instead instead of relying on the script: section of the CI and the underlying shell.
It is called aidge/tools/ci_python_path.py.
Here is a example of its usage :
python .\tools\ci_python_path.py -p bonjour >> build.env && type build.env
PYTHONPATH=bonjour\aidge_backend_cpu;bonjour\aidge_backend_cuda;bonjour\aidge_backend_opencv;bonjour\aidge_benchmark;bonjour\aidge_core;bonjour\aidge_export_arm_cortexm;bonjour\aidge_export_cpp;bonjour\aidge_export_tensorrt;bonjour\aidge_interop_torch;bonjour\aidge_learning;bonjour\aidge_model_explorer;bonjour\aidge_onnx;bonjour\aidge_quantization;C:\Users\TD284617\dev\lib\opencv\build\x64\vc16\bin
A note about aidge_core/test_export
This is laughable, but this little annoying python test is the reason behing many changes done in the CI.
- It creates an export that can be build either with CMake or PIP.
- It tests to build it using CMake
But as always we can't mix and match C++ artifacts like that, so we have to make sure that the configuration is the same and the path are correctly setup. Don't laugh: this is way more annoying than it seems, to find a maintainable solution.
What is next / Suggestions
- Require CMake at least 3.30
- Easy to upgrade
- Could simplify & make some changes more robust, e.g.:
- Improve cross-platform & cross-compiler support, especially in regards to clang-cl.
- Improve cross-plaftorm support of OpenMP.
- Instrument source code with Tracy (See #311).
- An option to toggle Pre Compiled Headers
- Consider a top-level project (It goes hand in hand with the mono-repo) ?
- If we remove the ability to work directly from a module, a lot of shared could be removed
- Users will only have to recompile dependent projects if they have changed. They won't even need to install them.
- If we remove the ability to work directly from a module, a lot of shared could be removed
- Small MR to improve code quality in various places.
