From ba342f0e99825d8fe398db7c8c6ac9c7370d62f9 Mon Sep 17 00:00:00 2001 From: vchamarthi Date: Tue, 11 Aug 2026 10:37:40 -0500 Subject: [PATCH 1/4] enable free threading support --- .github/workflows/build-with-clang.yml | 4 +- .../workflows/build-with-standard-clang.yml | 4 +- .github/workflows/build_pip.yml | 19 +++- .github/workflows/conda-package-cf.yml | 82 ++++++++++++---- .github/workflows/conda-package.yml | 86 ++++++++++++----- AGENTS.md | 6 +- CHANGELOG.md | 5 + README.md | 2 +- conda-recipe-cf/meta.yaml | 4 +- conda-recipe/meta.yaml | 4 +- mkl_umath/src/_patch_numpy.pyx | 1 + mkl_umath/src/ufuncsmodule.c | 4 + mkl_umath/tests/test_freethreading.py | 96 +++++++++++++++++++ pyproject.toml | 2 +- 14 files changed, 259 insertions(+), 60 deletions(-) create mode 100644 mkl_umath/tests/test_freethreading.py diff --git a/.github/workflows/build-with-clang.yml b/.github/workflows/build-with-clang.yml index 658e5283..d42b52bf 100644 --- a/.github/workflows/build-with-clang.yml +++ b/.github/workflows/build-with-clang.yml @@ -13,7 +13,7 @@ jobs: strategy: matrix: - python: ["3.10", "3.11", "3.12", "3.13", "3.14"] + python: ["3.10", "3.11", "3.12", "3.13", "3.14", "3.14t"] numpy_version: ["'numpy>=2'"] env: @@ -55,7 +55,7 @@ jobs: - name: Install mkl_umath dependencies run: | - pip install meson-python ninja cython cmake + pip install meson-python ninja "cython>=3.1.0" cmake pip install ${{ matrix.numpy_version }} - name: List oneAPI folder content diff --git a/.github/workflows/build-with-standard-clang.yml b/.github/workflows/build-with-standard-clang.yml index cb9ceb4b..0c97e73b 100644 --- a/.github/workflows/build-with-standard-clang.yml +++ b/.github/workflows/build-with-standard-clang.yml @@ -13,7 +13,7 @@ jobs: strategy: matrix: - python: ["3.10", "3.11", "3.12", "3.13", "3.14"] + python: ["3.10", "3.11", "3.12", "3.13", "3.14", "3.14t"] numpy_version: ["'numpy>=2'"] env: @@ -46,7 +46,7 @@ jobs: - name: Install mkl_umath dependencies run: | - pip install meson-python ninja cmake cython mkl-devel + pip install meson-python ninja cmake "cython>=3.1.0" mkl-devel pip install ${{ matrix.numpy_version }} - name: Build mkl_umath diff --git a/.github/workflows/build_pip.yml b/.github/workflows/build_pip.yml index f4555de1..36699dcf 100644 --- a/.github/workflows/build_pip.yml +++ b/.github/workflows/build_pip.yml @@ -17,8 +17,21 @@ jobs: strategy: matrix: - python: ['3.10', '3.11', '3.12', '3.13', '3.14'] + python: ['3.10', '3.11', '3.12', '3.13'] use_pre: ["", "--pre"] + include: + - python: "3.14" + python_spec: "3.14.* *_cp314" + use_pre: "" + - python: "3.14" + python_spec: "3.14.* *_cp314" + use_pre: "--pre" + - python: "3.14" + python_spec: "3.14.* *_cp314t" + use_pre: "" + - python: "3.14" + python_spec: "3.14.* *_cp314t" + use_pre: "--pre" steps: - name: Checkout repo @@ -37,13 +50,13 @@ jobs: - name: Install Compiler and MKL run: | - conda install mkl-devel dpcpp_linux-64 + conda install mkl-devel dpcpp_linux-64 "python=${{ matrix.python_spec || matrix.python }}" python -c "import sys; print(sys.executable)" which python - name: Build with pip run: | - pip install --no-cache-dir meson-python ninja cmake cython + pip install --no-cache-dir meson-python ninja cmake "cython>=3.1.0" pip install --no-cache-dir numpy ${{ matrix.use_pre }} CC=icx pip install -e . --no-build-isolation --no-deps --verbose pip install --no-cache-dir pytest diff --git a/.github/workflows/conda-package-cf.yml b/.github/workflows/conda-package-cf.yml index 55462bc6..51fecfeb 100644 --- a/.github/workflows/conda-package-cf.yml +++ b/.github/workflows/conda-package-cf.yml @@ -32,6 +32,14 @@ jobs: numpy: "2.3" - python: "3.14" numpy: "2.3" + - python: "3.14" + numpy: "2.3" + python_spec: "3.14.* *_cp314" + python_tag: "3.14" + - python: "3.14" + numpy: "2.3" + python_spec: "3.14.* *_cp314t" + python_tag: "3.14t" steps: - name: Cancel Previous Runs uses: styfle/cancel-workflow-action@d07a454dad7609a92316b57b23c9ccfd4f59af66 # 0.13.1 @@ -52,9 +60,9 @@ jobs: with: path: ~/.conda/pkgs key: - ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}-python-${{ matrix.python }}-${{hashFiles('**/meta.yaml') }} + ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}-python-${{ matrix.python_tag || matrix.python }}-${{hashFiles('**/meta.yaml') }} restore-keys: | - ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}-python-${{ matrix.python }}- + ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}-python-${{ matrix.python_tag || matrix.python }}- ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}- - name: Add conda to system path @@ -78,7 +86,7 @@ jobs: - name: Build conda package run: | CHANNELS=(-c "conda-forge" --override-channels) - VERSIONS=(--python "${{ matrix.python }}" --numpy "${{ matrix.numpy }}") + VERSIONS=(--python "${{ matrix.python_spec || matrix.python }}" --numpy "${{ matrix.numpy }}") TEST=(--no-test) echo "CONDA_BLD=${CONDA}/conda-bld/linux-64" >> "$GITHUB_ENV" @@ -91,7 +99,7 @@ jobs: - name: Upload artifact uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: - name: ${{ env.PACKAGE_NAME }} ${{ runner.os }} Python ${{ matrix.python }} + name: ${{ env.PACKAGE_NAME }} ${{ runner.os }} Python ${{ matrix.python_tag || matrix.python }} path: ${{ env.CONDA_BLD }}/${{ env.PACKAGE_NAME }}-*.conda test_linux: @@ -100,10 +108,23 @@ jobs: strategy: matrix: - python: ["3.10", "3.11", "3.12", "3.13", "3.14"] + python: ["3.10", "3.11", "3.12", "3.13"] numpy: ['numpy">=2"'] experimental: [false] runner: [ubuntu-latest] + include: + - python: "3.14" + python_spec: "3.14.* *_cp314" + python_tag: "3.14" + numpy: 'numpy">=2"' + experimental: false + runner: ubuntu-latest + - python: "3.14" + python_spec: "3.14.* *_cp314t" + python_tag: "3.14t" + numpy: 'numpy">=2"' + experimental: false + runner: ubuntu-latest continue-on-error: ${{ matrix.experimental }} env: CHANNELS: -c conda-forge --override-channels @@ -112,7 +133,7 @@ jobs: - name: Download artifact uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: - name: ${{ env.PACKAGE_NAME }} ${{ runner.os }} Python ${{ matrix.python }} + name: ${{ env.PACKAGE_NAME }} ${{ runner.os }} Python ${{ matrix.python_tag || matrix.python }} - name: Update conda run: | conda update -n base --all @@ -142,7 +163,7 @@ jobs: run: | CHANNELS=(-c "$GITHUB_WORKSPACE/channel" -c "conda-forge" --override-channels) PACKAGE_VERSION="$(python -c "${VER_SCRIPT1} ${VER_SCRIPT2}")" - conda create -n "${{ env.TEST_ENV_NAME }}" "${PACKAGE_NAME}=${PACKAGE_VERSION}" "python=${{ matrix.python }}" ${{ matrix.numpy }} "${CHANNELS[@]}" --only-deps --dry-run > lockfile + conda create -n "${{ env.TEST_ENV_NAME }}" "${PACKAGE_NAME}=${PACKAGE_VERSION}" "python=${{ matrix.python_spec || matrix.python }}" ${{ matrix.numpy }} "${CHANNELS[@]}" --only-deps --dry-run > lockfile - name: Display lockfile run: cat lockfile @@ -157,16 +178,16 @@ jobs: with: path: ~/.conda/pkgs key: - ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}-python-${{ matrix.python }}-${{hashFiles('lockfile') }} + ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}-python-${{ matrix.python_tag || matrix.python }}-${{hashFiles('lockfile') }} restore-keys: | - ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}-python-${{ matrix.python }}- + ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}-python-${{ matrix.python_tag || matrix.python }}- ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}- - name: Install mkl_umath run: | CHANNELS=(-c "$GITHUB_WORKSPACE/channel" -c "conda-forge" --override-channels) PACKAGE_VERSION="$(python -c "${VER_SCRIPT1} ${VER_SCRIPT2}")" - conda create -n "${{ env.TEST_ENV_NAME }}" "python=${{ matrix.python }}" ${{ matrix.numpy }} "$PACKAGE_NAME=${PACKAGE_VERSION}" pytest "${CHANNELS[@]}" + conda create -n "${{ env.TEST_ENV_NAME }}" "python=${{ matrix.python_spec || matrix.python }}" ${{ matrix.numpy }} "$PACKAGE_NAME=${PACKAGE_VERSION}" pytest "${CHANNELS[@]}" # Test installed packages conda list -n "${{ env.TEST_ENV_NAME }}" @@ -198,6 +219,14 @@ jobs: numpy: "2.3" - python: "3.14" numpy: "2.3" + - python: "3.14" + numpy: "2.3" + python_spec: "3.14.* *_cp314" + python_tag: "3.14" + - python: "3.14" + numpy: "2.3" + python_spec: "3.14.* *_cp314t" + python_tag: "3.14t" env: conda-bld: C:\Miniconda\conda-bld\win-64\ steps: @@ -225,9 +254,9 @@ jobs: with: path: /home/runner/conda_pkgs_dir key: - ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}-python-${{ matrix.python }}-${{hashFiles('**/meta.yaml') }} + ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}-python-${{ matrix.python_tag || matrix.python }}-${{hashFiles('**/meta.yaml') }} restore-keys: | - ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}-python-${{ matrix.python }}- + ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}-python-${{ matrix.python_tag || matrix.python }}- ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}- - name: Store conda paths as envs @@ -244,12 +273,12 @@ jobs: - name: Build conda package run: | conda activate - conda build --no-test --python ${{ matrix.python }} --numpy ${{ matrix.numpy }} -c conda-forge --override-channels conda-recipe-cf + conda build --no-test --python "${{ matrix.python_spec || matrix.python }}" --numpy ${{ matrix.numpy }} -c conda-forge --override-channels conda-recipe-cf - name: Upload artifact uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: - name: ${{ env.PACKAGE_NAME }} ${{ runner.os }} Python ${{ matrix.python }} + name: ${{ env.PACKAGE_NAME }} ${{ runner.os }} Python ${{ matrix.python_tag || matrix.python }} path: ${{ env.CONDA_BLD }}${{ env.PACKAGE_NAME }}-*.conda test_windows: @@ -260,10 +289,23 @@ jobs: shell: cmd /C CALL {0} strategy: matrix: - python: ["3.10", "3.11", "3.12", "3.13", "3.14"] + python: ["3.10", "3.11", "3.12", "3.13"] numpy: ['numpy">=2"'] experimental: [false] runner: [windows-latest] + include: + - python: "3.14" + python_spec: "3.14.* *_cp314" + python_tag: "3.14" + numpy: 'numpy">=2"' + experimental: false + runner: windows-latest + - python: "3.14" + python_spec: "3.14.* *_cp314t" + python_tag: "3.14t" + numpy: 'numpy">=2"' + experimental: false + runner: windows-latest continue-on-error: ${{ matrix.experimental }} env: workdir: '${{ github.workspace }}' @@ -273,7 +315,7 @@ jobs: - name: Download artifact uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: - name: ${{ env.PACKAGE_NAME }} ${{ runner.os }} Python ${{ matrix.python }} + name: ${{ env.PACKAGE_NAME }} ${{ runner.os }} Python ${{ matrix.python_tag || matrix.python }} - uses: conda-incubator/setup-miniconda@8ee1f361103df19b6f8c8655fd3967a8ecb162d5 # v4.0.1 with: @@ -339,7 +381,7 @@ jobs: FOR /F "tokens=* USEBACKQ" %%F IN (`python -c "%SCRIPT%"`) DO ( SET PACKAGE_VERSION=%%F ) - conda install -n ${{ env.TEST_ENV_NAME }} ${{ env.PACKAGE_NAME }}=%PACKAGE_VERSION% python=${{ matrix.python }} ${{ matrix.numpy }} -c ${{ env.workdir }}/channel ${{ env.CHANNELS }} --only-deps --dry-run > lockfile + conda install -n ${{ env.TEST_ENV_NAME }} ${{ env.PACKAGE_NAME }}=%PACKAGE_VERSION% "python=${{ matrix.python_spec || matrix.python }}" ${{ matrix.numpy }} -c ${{ env.workdir }}/channel ${{ env.CHANNELS }} --only-deps --dry-run > lockfile - name: Display lockfile content shell: pwsh @@ -352,9 +394,9 @@ jobs: with: path: /home/runner/conda_pkgs_dir key: - ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}-python-${{ matrix.python }}-${{hashFiles('lockfile') }} + ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}-python-${{ matrix.python_tag || matrix.python }}-${{hashFiles('lockfile') }} restore-keys: | - ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}-python-${{ matrix.python }}- + ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}-python-${{ matrix.python_tag || matrix.python }}- ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}- - name: Install mkl_umath @@ -369,7 +411,7 @@ jobs: SET PACKAGE_VERSION=%%F ) SET "TEST_DEPENDENCIES=pytest pytest-cov" - conda install -n ${{ env.TEST_ENV_NAME }} ${{ env.PACKAGE_NAME }}=%PACKAGE_VERSION% %TEST_DEPENDENCIES% python=${{ matrix.python }} ${{ matrix.numpy }} -c ${{ env.workdir }}/channel ${{ env.CHANNELS }} + conda install -n ${{ env.TEST_ENV_NAME }} ${{ env.PACKAGE_NAME }}=%PACKAGE_VERSION% %TEST_DEPENDENCIES% "python=${{ matrix.python_spec || matrix.python }}" ${{ matrix.numpy }} -c ${{ env.workdir }}/channel ${{ env.CHANNELS }} - name: Report content of test environment shell: cmd /C CALL {0} diff --git a/.github/workflows/conda-package.yml b/.github/workflows/conda-package.yml index c0add3a8..fb592088 100644 --- a/.github/workflows/conda-package.yml +++ b/.github/workflows/conda-package.yml @@ -32,6 +32,14 @@ jobs: numpy: "2.3" - python: "3.14" numpy: "2.3" + - python: "3.14" + numpy: "2.3" + python_spec: "3.14.* *_cp314" + python_tag: "3.14" + - python: "3.14" + numpy: "2.3" + python_spec: "3.14.* *_cp314t" + python_tag: "3.14t" steps: - name: Cancel Previous Runs uses: styfle/cancel-workflow-action@d07a454dad7609a92316b57b23c9ccfd4f59af66 # 0.13.1 @@ -52,9 +60,9 @@ jobs: with: path: ~/.conda/pkgs key: - ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}-python-${{ matrix.python }}-${{hashFiles('**/meta.yaml') }} + ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}-python-${{ matrix.python_tag || matrix.python }}-${{hashFiles('**/meta.yaml') }} restore-keys: | - ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}-python-${{ matrix.python }}- + ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}-python-${{ matrix.python_tag || matrix.python }}- ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}- - name: Add conda to system path @@ -83,7 +91,7 @@ jobs: - name: Build conda package run: | CHANNELS=(-c "https://software.repos.intel.com/python/conda" -c "conda-forge" --override-channels) - VERSIONS=(--python "${{ matrix.python }}" --numpy "${{ matrix.numpy }}") + VERSIONS=(--python "${{ matrix.python_spec || matrix.python }}" --numpy "${{ matrix.numpy }}") TEST=(--no-test) conda build \ @@ -95,13 +103,13 @@ jobs: - name: Upload artifact uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: - name: ${{ env.PACKAGE_NAME }} ${{ runner.os }} Python ${{ matrix.python }} + name: ${{ env.PACKAGE_NAME }} ${{ runner.os }} Python ${{ matrix.python_tag || matrix.python }} path: ${{ env.CONDA_BLD }}/${{ env.PACKAGE_NAME }}-*.conda - name: Upload wheels artifact uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: - name: ${{ env.PACKAGE_NAME }} ${{ runner.os }} Wheels Python ${{ matrix.python }} + name: ${{ env.PACKAGE_NAME }} ${{ runner.os }} Wheels Python ${{ matrix.python_tag || matrix.python }} path: ${{ env.WHEELS_OUTPUT_FOLDER }}${{ env.PACKAGE_NAME }}-*.whl test_linux: @@ -110,10 +118,23 @@ jobs: strategy: matrix: - python: ["3.10", "3.11", "3.12", "3.13", "3.14"] + python: ["3.10", "3.11", "3.12", "3.13"] numpy: ['numpy">=2"'] experimental: [false] runner: [ubuntu-latest] + include: + - python: "3.14" + python_spec: "3.14.* *_cp314" + python_tag: "3.14" + numpy: 'numpy">=2"' + experimental: false + runner: ubuntu-latest + - python: "3.14" + python_spec: "3.14.* *_cp314t" + python_tag: "3.14t" + numpy: 'numpy">=2"' + experimental: false + runner: ubuntu-latest continue-on-error: ${{ matrix.experimental }} env: CHANNELS: -c https://software.repos.intel.com/python/conda -c conda-forge --override-channels @@ -122,7 +143,7 @@ jobs: - name: Download artifact uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: - name: ${{ env.PACKAGE_NAME }} ${{ runner.os }} Python ${{ matrix.python }} + name: ${{ env.PACKAGE_NAME }} ${{ runner.os }} Python ${{ matrix.python_tag || matrix.python }} - name: Update conda run: | conda update -n base --all @@ -152,7 +173,7 @@ jobs: run: | CHANNELS=(-c "$GITHUB_WORKSPACE/channel" -c "https://software.repos.intel.com/python/conda" -c "conda-forge" --override-channels) PACKAGE_VERSION="$(python -c "${VER_SCRIPT1} ${VER_SCRIPT2}")" - conda create -n "${{ env.TEST_ENV_NAME }}" "${PACKAGE_NAME}=${PACKAGE_VERSION}" "python=${{ matrix.python }}" ${{ matrix.numpy }} "${CHANNELS[@]}" --only-deps --dry-run > lockfile + conda create -n "${{ env.TEST_ENV_NAME }}" "${PACKAGE_NAME}=${PACKAGE_VERSION}" "python=${{ matrix.python_spec || matrix.python }}" ${{ matrix.numpy }} "${CHANNELS[@]}" --only-deps --dry-run > lockfile - name: Display lockfile run: cat lockfile @@ -167,16 +188,16 @@ jobs: with: path: ~/.conda/pkgs key: - ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}-python-${{ matrix.python }}-${{hashFiles('lockfile') }} + ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}-python-${{ matrix.python_tag || matrix.python }}-${{hashFiles('lockfile') }} restore-keys: | - ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}-python-${{ matrix.python }}- + ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}-python-${{ matrix.python_tag || matrix.python }}- ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}- - name: Install mkl_umath run: | CHANNELS=(-c "$GITHUB_WORKSPACE/channel" -c "https://software.repos.intel.com/python/conda" -c "conda-forge" --override-channels) PACKAGE_VERSION="$(python -c "${VER_SCRIPT1} ${VER_SCRIPT2}")" - conda create -n "${{ env.TEST_ENV_NAME }}" "python=${{ matrix.python }}" ${{ matrix.numpy }} "$PACKAGE_NAME=${PACKAGE_VERSION}" pytest "${CHANNELS[@]}" + conda create -n "${{ env.TEST_ENV_NAME }}" "python=${{ matrix.python_spec || matrix.python }}" ${{ matrix.numpy }} "$PACKAGE_NAME=${PACKAGE_VERSION}" pytest "${CHANNELS[@]}" # Test installed packages conda list -n "${{ env.TEST_ENV_NAME }}" @@ -208,6 +229,14 @@ jobs: numpy: "2.3" - python: "3.14" numpy: "2.3" + - python: "3.14" + numpy: "2.3" + python_spec: "3.14.* *_cp314" + python_tag: "3.14" + - python: "3.14" + numpy: "2.3" + python_spec: "3.14.* *_cp314t" + python_tag: "3.14t" env: conda-bld: C:\Miniconda\conda-bld\win-64\ steps: @@ -235,9 +264,9 @@ jobs: with: path: /home/runner/conda_pkgs_dir key: - ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}-python-${{ matrix.python }}-${{hashFiles('**/meta.yaml') }} + ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}-python-${{ matrix.python_tag || matrix.python }}-${{hashFiles('**/meta.yaml') }} restore-keys: | - ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}-python-${{ matrix.python }}- + ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}-python-${{ matrix.python_tag || matrix.python }}- ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}- - name: Store conda paths as envs @@ -255,18 +284,18 @@ jobs: - name: Build conda package run: | conda activate - conda build --no-test --python ${{ matrix.python }} --numpy ${{ matrix.numpy }} -c https://software.repos.intel.com/python/conda -c conda-forge --override-channels conda-recipe + conda build --no-test --python "${{ matrix.python_spec || matrix.python }}" --numpy ${{ matrix.numpy }} -c https://software.repos.intel.com/python/conda -c conda-forge --override-channels conda-recipe - name: Upload artifact uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: - name: ${{ env.PACKAGE_NAME }} ${{ runner.os }} Python ${{ matrix.python }} + name: ${{ env.PACKAGE_NAME }} ${{ runner.os }} Python ${{ matrix.python_tag || matrix.python }} path: ${{ env.CONDA_BLD }}${{ env.PACKAGE_NAME }}-*.conda - name: Upload wheels artifact uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: - name: ${{ env.PACKAGE_NAME }} ${{ runner.os }} Wheels Python ${{ matrix.python }} + name: ${{ env.PACKAGE_NAME }} ${{ runner.os }} Wheels Python ${{ matrix.python_tag || matrix.python }} path: ${{ env.WHEELS_OUTPUT_FOLDER }}${{ env.PACKAGE_NAME }}-*.whl test_windows: @@ -277,10 +306,23 @@ jobs: shell: cmd /C CALL {0} strategy: matrix: - python: ["3.10", "3.11", "3.12", "3.13", "3.14"] + python: ["3.10", "3.11", "3.12", "3.13"] numpy: ['numpy">=2"'] experimental: [false] runner: [windows-latest] + include: + - python: "3.14" + python_spec: "3.14.* *_cp314" + python_tag: "3.14" + numpy: 'numpy">=2"' + experimental: false + runner: windows-latest + - python: "3.14" + python_spec: "3.14.* *_cp314t" + python_tag: "3.14t" + numpy: 'numpy">=2"' + experimental: false + runner: windows-latest continue-on-error: ${{ matrix.experimental }} env: workdir: '${{ github.workspace }}' @@ -290,7 +332,7 @@ jobs: - name: Download artifact uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: - name: ${{ env.PACKAGE_NAME }} ${{ runner.os }} Python ${{ matrix.python }} + name: ${{ env.PACKAGE_NAME }} ${{ runner.os }} Python ${{ matrix.python_tag || matrix.python }} - uses: conda-incubator/setup-miniconda@8ee1f361103df19b6f8c8655fd3967a8ecb162d5 # v4.0.1 with: @@ -356,7 +398,7 @@ jobs: FOR /F "tokens=* USEBACKQ" %%F IN (`python -c "%SCRIPT%"`) DO ( SET PACKAGE_VERSION=%%F ) - conda install -n ${{ env.TEST_ENV_NAME }} ${{ env.PACKAGE_NAME }}=%PACKAGE_VERSION% python=${{ matrix.python }} ${{ matrix.numpy }} -c ${{ env.workdir }}/channel ${{ env.CHANNELS }} --only-deps --dry-run > lockfile + conda install -n ${{ env.TEST_ENV_NAME }} ${{ env.PACKAGE_NAME }}=%PACKAGE_VERSION% "python=${{ matrix.python_spec || matrix.python }}" ${{ matrix.numpy }} -c ${{ env.workdir }}/channel ${{ env.CHANNELS }} --only-deps --dry-run > lockfile - name: Display lockfile content shell: pwsh @@ -369,9 +411,9 @@ jobs: with: path: /home/runner/conda_pkgs_dir key: - ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}-python-${{ matrix.python }}-${{hashFiles('lockfile') }} + ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}-python-${{ matrix.python_tag || matrix.python }}-${{hashFiles('lockfile') }} restore-keys: | - ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}-python-${{ matrix.python }}- + ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}-python-${{ matrix.python_tag || matrix.python }}- ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}- # add intel-openmp as an explicit dependency @@ -390,7 +432,7 @@ jobs: SET "TEST_DEPENDENCIES=pytest pytest-cov" SET "WORKAROUND_DEPENDENCIES=intel-openmp" SET "DEPENDENCIES=%TEST_DEPENDENCIES% %WORKAROUND_DEPENDENCIES%" - conda install -n ${{ env.TEST_ENV_NAME }} ${{ env.PACKAGE_NAME }}=%PACKAGE_VERSION% %DEPENDENCIES% python=${{ matrix.python }} ${{ matrix.numpy }} -c ${{ env.workdir }}/channel ${{ env.CHANNELS }} + conda install -n ${{ env.TEST_ENV_NAME }} ${{ env.PACKAGE_NAME }}=%PACKAGE_VERSION% %DEPENDENCIES% "python=${{ matrix.python_spec || matrix.python }}" ${{ matrix.numpy }} -c ${{ env.workdir }}/channel ${{ env.CHANNELS }} - name: Report content of test environment shell: cmd /C CALL {0} diff --git a/AGENTS.md b/AGENTS.md index a47d433b..01525618 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -21,18 +21,18 @@ It provides: **Required:** - Compiler toolchain: Intel `icx` or `clang` (with Intel-only flags gated when using clang) - IntelĀ® oneMKL (`mkl-devel`) -- meson-python, CMake, Ninja, Cython, NumPy +- meson-python, CMake, Ninja, Cython>=3.1.0, NumPy **Build against an existing `mkl` installation:** Install the build dependencies via Conda: ```bash conda install -c https://software.repos.intel.com/python/conda \ - mkl-devel dpcpp_linux-64 cython meson-python cmake ninja numpy + mkl-devel dpcpp_linux-64 "cython>=3.1.0" meson-python cmake ninja numpy ``` or via pip: ```bash -pip install mkl-devel cython meson-python cmake ninja numpy +pip install mkl-devel "cython>=3.1.0" meson-python cmake ninja numpy ``` then build: ```bash diff --git a/CHANGELOG.md b/CHANGELOG.md index 108c47fa..00c7d5c8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,11 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [dev] - YYYY-MM-DD ### Added +* Added support for free-threaded (GIL-disabled) CPython builds: the Cython extension is compiled with `freethreading_compatible=True` and `_ufuncs` declares `Py_MOD_GIL_NOT_USED`, so importing `mkl_umath` no longer re-enables the GIL [gh-NNN](https://github.com/IntelPython/mkl_umath/pull/NNN) ### Changed +* Raised the minimum build-time `Cython` requirement to `3.1.0`, the first release providing the `freethreading_compatible` directive [gh-NNN](https://github.com/IntelPython/mkl_umath/pull/NNN) ### Fixed +### Removed +* Removed the `python-gil` constraint from the conda recipes, which pinned `mkl_umath` to GIL-enabled Python 3.14 builds [gh-NNN](https://github.com/IntelPython/mkl_umath/pull/NNN) + ## [0.5.0] - 2026-08-06 ### Added diff --git a/README.md b/README.md index 6db5fe81..9e6cafad 100644 --- a/README.md +++ b/README.md @@ -114,7 +114,7 @@ If you already have `mkl` and `numpy` installed (from your system or a conda env and want to reuse them instead of pulling fresh copies into an isolated build, first install the build dependencies: ```sh -pip install meson-python ninja cmake cython numpy mkl-devel +pip install meson-python ninja cmake "cython>=3.1.0" numpy mkl-devel ``` then build against the existing installation with: diff --git a/conda-recipe-cf/meta.yaml b/conda-recipe-cf/meta.yaml index 54bc235d..311b08d3 100644 --- a/conda-recipe-cf/meta.yaml +++ b/conda-recipe-cf/meta.yaml @@ -26,16 +26,14 @@ requirements: - cmake - ninja - git - - cython + - cython >=3.1.0 - python - - python-gil # [py>=314] - mkl-devel - numpy - llvm-openmp - pip run: - python - - python-gil # [py>=314] - {{ pin_compatible('mkl') }} - {{ pin_compatible('numpy') }} - {{ pin_compatible('intel-cmplr-lib-rt') }} diff --git a/conda-recipe/meta.yaml b/conda-recipe/meta.yaml index 628f49d6..44932c77 100644 --- a/conda-recipe/meta.yaml +++ b/conda-recipe/meta.yaml @@ -25,9 +25,8 @@ requirements: - cmake - ninja - git - - cython + - cython >=3.1.0 - python - - python-gil # [py>=314] - python-build >=1.2.2 - mkl-devel - intel-openmp @@ -37,7 +36,6 @@ requirements: - pip run: - python - - python-gil # [py>=314] - {{ pin_compatible('mkl') }} - {{ pin_compatible('intel-openmp') }} - {{ pin_compatible('intel-cmplr-lib-rt') }} diff --git a/mkl_umath/src/_patch_numpy.pyx b/mkl_umath/src/_patch_numpy.pyx index 4338bf2d..fb114dd3 100644 --- a/mkl_umath/src/_patch_numpy.pyx +++ b/mkl_umath/src/_patch_numpy.pyx @@ -25,6 +25,7 @@ # distutils: language = c # cython: language_level=3 +# cython: freethreading_compatible=True import warnings from contextlib import ContextDecorator diff --git a/mkl_umath/src/ufuncsmodule.c b/mkl_umath/src/ufuncsmodule.c index 50dd7790..e9ef51ee 100644 --- a/mkl_umath/src/ufuncsmodule.c +++ b/mkl_umath/src/ufuncsmodule.c @@ -59,5 +59,9 @@ PyMODINIT_FUNC PyInit__ufuncs(void) return NULL; } +#ifdef Py_GIL_DISABLED + PyUnstable_Module_SetGIL(m, Py_MOD_GIL_NOT_USED); +#endif + return m; } diff --git a/mkl_umath/tests/test_freethreading.py b/mkl_umath/tests/test_freethreading.py new file mode 100644 index 00000000..8967f69a --- /dev/null +++ b/mkl_umath/tests/test_freethreading.py @@ -0,0 +1,96 @@ +# Copyright (c) 2026, Intel Corporation +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of Intel Corporation nor the names of its contributors +# may be used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +import os +import sys +import sysconfig +import threading + +import numpy as np +import pytest + +# Oversubscription: MKL spawns its own thread pool per calling thread, so +# running MKL VM calls from many Python threads concurrently can spawn +# far more OS threads than cores. Cap it before mkl_umath/MKL initialize. +os.environ.setdefault("MKL_NUM_THREADS", "1") + +import mkl_umath # noqa: E402 +import mkl_umath._patch_numpy # noqa: E402, F401 +import mkl_umath._ufuncs as mu # noqa: E402 + +FREE_THREADED = bool(sysconfig.get_config_var("Py_GIL_DISABLED")) + + +def test_concurrent_ufunc_execution(): + n_threads = 4 + size = 10**5 + 1 # exceed the MKL threshold to exercise the MKL path + + arrays = [np.random.uniform(-10, 10, size) for _ in range(n_threads)] + results = [None] * n_threads + errors = [] + + def worker(i): + try: + results[i] = mu.sin(arrays[i]) + except Exception as exc: + errors.append(exc) + + threads = [ + threading.Thread(target=worker, args=(i,)) for i in range(n_threads) + ] + for t in threads: + t.start() + for t in threads: + t.join() + + assert not errors + + for i in range(n_threads): + np.testing.assert_allclose(results[i], np.sin(arrays[i])) + + +def test_concurrent_patch_restore(): + n_threads = 8 + n_iters = 20 + + def worker(): + for _ in range(n_iters): + mkl_umath.patch_numpy_umath() + mkl_umath.restore_numpy_umath() + + threads = [threading.Thread(target=worker) for _ in range(n_threads)] + for t in threads: + t.start() + for t in threads: + t.join() + + assert not mkl_umath.is_patched() + + +@pytest.mark.skipif( + not FREE_THREADED, reason="requires a free-threaded CPython build" +) +def test_gil_not_reenabled_on_import(): + assert not sys._is_gil_enabled() # pylint: disable=no-member diff --git a/pyproject.toml b/pyproject.toml index 96251f41..bb5f6c8c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,7 +28,7 @@ build-backend = "mesonpy" requires = [ "meson-python>=0.16.0", "ninja", - "Cython", + "Cython>=3.1.0", "numpy", "mkl-devel", "cmake" From 6558aa5ae419a3cd47b3820daadfe59355d1aabb Mon Sep 17 00:00:00 2001 From: vchamarthi Date: Tue, 11 Aug 2026 11:45:55 -0500 Subject: [PATCH 2/4] update changelog, remove duplicate ci matrix entry --- .github/workflows/conda-package-cf.yml | 4 ---- .github/workflows/conda-package.yml | 4 ---- CHANGELOG.md | 6 +++--- 3 files changed, 3 insertions(+), 11 deletions(-) diff --git a/.github/workflows/conda-package-cf.yml b/.github/workflows/conda-package-cf.yml index 51fecfeb..0bf2d194 100644 --- a/.github/workflows/conda-package-cf.yml +++ b/.github/workflows/conda-package-cf.yml @@ -30,8 +30,6 @@ jobs: numpy: "2.3" - python: "3.13" numpy: "2.3" - - python: "3.14" - numpy: "2.3" - python: "3.14" numpy: "2.3" python_spec: "3.14.* *_cp314" @@ -217,8 +215,6 @@ jobs: numpy: "2.3" - python: "3.13" numpy: "2.3" - - python: "3.14" - numpy: "2.3" - python: "3.14" numpy: "2.3" python_spec: "3.14.* *_cp314" diff --git a/.github/workflows/conda-package.yml b/.github/workflows/conda-package.yml index fb592088..5348040c 100644 --- a/.github/workflows/conda-package.yml +++ b/.github/workflows/conda-package.yml @@ -30,8 +30,6 @@ jobs: numpy: "2.3" - python: "3.13" numpy: "2.3" - - python: "3.14" - numpy: "2.3" - python: "3.14" numpy: "2.3" python_spec: "3.14.* *_cp314" @@ -227,8 +225,6 @@ jobs: numpy: "2.3" - python: "3.13" numpy: "2.3" - - python: "3.14" - numpy: "2.3" - python: "3.14" numpy: "2.3" python_spec: "3.14.* *_cp314" diff --git a/CHANGELOG.md b/CHANGELOG.md index 00c7d5c8..34bc4c97 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,15 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [dev] - YYYY-MM-DD ### Added -* Added support for free-threaded (GIL-disabled) CPython builds: the Cython extension is compiled with `freethreading_compatible=True` and `_ufuncs` declares `Py_MOD_GIL_NOT_USED`, so importing `mkl_umath` no longer re-enables the GIL [gh-NNN](https://github.com/IntelPython/mkl_umath/pull/NNN) +* Added support for free-threaded (GIL-disabled) CPython builds: the Cython extension is compiled with `freethreading_compatible=True` and `_ufuncs` declares `Py_MOD_GIL_NOT_USED`, so importing `mkl_umath` no longer re-enables the GIL [gh-255](https://github.com/IntelPython/mkl_umath/pull/255) ### Changed -* Raised the minimum build-time `Cython` requirement to `3.1.0`, the first release providing the `freethreading_compatible` directive [gh-NNN](https://github.com/IntelPython/mkl_umath/pull/NNN) +* Raised the minimum build-time `Cython` requirement to `3.1.0`, the first release providing the `freethreading_compatible` directive [gh-255](https://github.com/IntelPython/mkl_umath/pull/255) ### Fixed ### Removed -* Removed the `python-gil` constraint from the conda recipes, which pinned `mkl_umath` to GIL-enabled Python 3.14 builds [gh-NNN](https://github.com/IntelPython/mkl_umath/pull/NNN) +* Removed the `python-gil` constraint from the conda recipes, which pinned `mkl_umath` to GIL-enabled Python 3.14 builds [gh-255](https://github.com/IntelPython/mkl_umath/pull/255) ## [0.5.0] - 2026-08-06 From 7707a9d1a96dadda6ed548f25d97ce532835ecc6 Mon Sep 17 00:00:00 2001 From: vchamarthi Date: Thu, 13 Aug 2026 10:43:36 -0500 Subject: [PATCH 3/4] chore: retrigger CI From 2cf44d6174307928bf2e916306a441a62ae8c168 Mon Sep 17 00:00:00 2001 From: vchamarthi Date: Thu, 13 Aug 2026 11:10:04 -0500 Subject: [PATCH 4/4] update python free threading classifiers --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index bb5f6c8c..a5c760d6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -50,6 +50,7 @@ classifiers = [ "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.14", + "Programming Language :: Python :: Free Threading :: 2 - Beta", "Programming Language :: Python :: Implementation :: CPython", "Topic :: Software Development", "Topic :: Scientific/Engineering",