Skip to content

Refactor Build Systems: CMake & PIP

Tristan de Blauwe requested to merge tblauwe/aidge:MonoRepoTest into main

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

  1. Modules external to the monorepo have not been updated and may fail to compile/find dependencies.
  2. 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_cortexm tests requires aidge_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:dependencies to setup:windows:dependencies
    • Add setup:ubuntu:dependencies

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 of ack.cmake.
    • ACK now handles common, cross-module configurations and API, leaving CML files to manage module-specific settings.
    • Rename aidge-*-config.cmake.in to config.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 from 52s to 8s.
        • A cached configure goes from 14s to 0.8s).
    • 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_opencv has a warning when building python bindings on windows if OPENCV_DLL_DIR is not set.

FA0114DA-3EB1-4BFB-ACF1-87443AA30E34

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.18 to 🟢 3.22.

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
  • ⚠️ Ninja is 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.in is removed from versionning
      • It is generated by ACK
    • sys_utils/...VersionInfo.hpp has 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.
  • For aidge_backend_cpu:
    • CUDA 12 is set as an hard-requirement (instead of a soft-requirement)
      • Otherwise, CUDA 13 could be accepted, but it would raise an error, since it (Thrust) requires C++17.
      • CUDA 12.4.1 is troublesome, espcially on windows. I would recommend CUDA 12.6.0+
    • Change ILayerNormImpl's forward dimension vector type from vector<unsigned long int> to vector<DimSize_t>
      • Otherwise, it would not compile on Windows (msvc).
      • It also prevent a downward cast and loss of precision.
      • Idem for ShiftGELUImpl and ShiftMax.

Note

Changes to the source code were intentionally limited to keep the MR focused.

🐍 Python Related

  • Switch from setuptools to 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.toml to adapt to new backend.
      • Python's version is deduced from version.txt (as is CMake's version)
  • Add dependencies to pyproject.toml, e.g aidge_onnx to aidge_model_explorer
  • pybind11 only needs to be fetched when it is not found.
    • Scikit provides it.
    • Only aidge_core actually 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.ps1 has 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.ps1 to sync shared files across modules.
    • A neat feature is the ability to open git difftool to merge conflicts.
    • Automatic relative path resolution aidge/aidge_core/CMakeLists.txt is resolved to just CMakeLists.txt when copied inside aidge/aidge_backend_opencv/.

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.py is 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 removed 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 as PYBIND) ?

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 SANITIZER instead of ASAN ?

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 :

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.

ℹ️ This will also lead to faster configure times !

  • 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 (ON by default when building a module as a top project, more on that later.)
  • "vcpkg" - to integrate vcpkg toolchain 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.

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 cache feature of GitLab.
  • scikit-build-core install C++ artifacts (bin/, include/ and lib/) and the python package (aidge_<name>/) in a prefix/ folder, which is usually site-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:dependencies
  • setup: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.

  1. It creates an export that can be build either with CMake or PIP.
  2. 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.
  • Small MR to improve code quality in various places.
Edited by Tristan de Blauwe

Merge request reports

Loading