llm-gen

title: Python 3.14 free-threaded support description: Research summary and implementation plan for cp314t wheels

Issue: #1187

背景

  • CPython 3.14 将 GIL 变成可选配置,新的“free-threaded”构建以 cp314t tag 分发(参考 PEP 703PyO3 Guide - Supporting Free-Threaded CPython).
  • 在 PEP 803 指定的稳定 ABI 落地前,free-threaded 版本没有 limited API,因此我们不能复用 abi3 wheel,必须单独构建 cp314t 版本的 wheel,并确保这些 wheel 只在禁用 GIL 的 Python 中安装。
  • PyO3 自 0.23 起支持 free-threaded 扩展;我们当前锁定在 0.25.x,因此无需额外升级即可兼容。

必备背景知识(给第一次接触的人)

1. 什么是 GIL?什么是 free-threaded?

  • GIL (Global Interpreter Lock) 是 CPython 为了简化内存管理而引入的一把互斥锁。即使你开了多个线程,任意时刻也只有一个线程能执行 Python 字节码。
  • free-threaded 构建在 3.13 引入、3.14 进入正式支持:编译 CPython 时加 --disable-gil,让每个线程都能同时执行 Python 代码。
  • 在 free-threaded 解释器中运行 python -c "import sys; print(sys._is_gil_enabled())" 会输出 False,这是最快的自测方法。
  • 由于内部实现差异,free-threaded CPython 和普通 CPython 不共享 ABI,所以二进制扩展(Rust/C/C++)必须单独构建。

参考链接:

2. Wheel 名字怎么看?

wheel 文件的命名规则来源于 PEP 427packaging tags 规范

示例:cocoindex-1.2.3-cp314t-cp314t-manylinux_2_28_x86_64.whl

片段含义
cocoindex包名
1.2.3版本号
cp314t(第 1 个)解释器 tag:CPython 3.14 free-threaded
cp314t(第 2 个)ABI tag:同样表示 cp314 free-threaded ABI
manylinux_2_28_x86_64平台,表示 Linux glibc>=2.28, x86_64

pip 根据这些 tag 匹配当前环境,只有同时满足解释器、ABI、平台才会安装成功。所以要支持 free-threaded,必须打出 cp314t 标签的 wheel。

更多资料:

3. abi3 是什么,为什么和 free-threaded 冲突?

  • abi3(PEP 384)是 CPython 提供的“稳定 ABI”,只要使用 limited API,就能一个 wheel 兼容多个 Python 版本。
  • 我们当前用 PyO3 的 abi3-py311 特性来生成单个 wheel,兼容 3.11~3.14。
  • 但是 free-threaded 的 ABI 完全不同,目前没有 limited API,PyO3 文档也写了“built with abi3 features 会被忽略”。
  • 结论:要支持 free-threaded,就需要:
    1. 在普通 wheel 中继续使用 abi3
    2. 在 free-threaded wheel 中关闭 abi3,改用 pyo3/free-threaded feature。

4. PyO3 feature 应该怎么切?

PyO3 提供了多个 feature:

Feature作用
abi3-py311启用 limited API,目标 Python >= 3.11
extension-module让生成的 crate 可被 Python import
auto-initialize在首次调用 PyO3 API 时自动初始化 Python 运行时
free-threaded切换到 free-threaded ABI,同时禁用 GIL 相关辅助结构

实践方案:

  1. workspace 只保留公共功能(auto-initialize, chrono, uuid 等)。
  2. 每个 PyO3 crate(rust/cocoindex, rust/py_utils)定义 feature:
    [features]
    default = ["abi3"]
    abi3 = ["pyo3/abi3-py311"]
    free-threaded = ["pyo3/free-threaded"]
  3. 构建普通 wheel:cargo build -p cocoindex --features abi3.
  4. 构建 free-threaded wheel:cargo build -p cocoindex --no-default-features --features free-threaded.

5. CI 里怎么获取 free-threaded Python?

  • GitHub Actions 官方的 actions/setup-python@v5 新增了 freethreaded: true3.14t 语法。例如:
    - uses: actions/setup-python@v5
      with:
        python-version: '3.14'
        allow-prereleases: true
        freethreaded: true
    注意 allow-prereleases: true,因为目前只有 3.14.0-beta
  • 但是 manylinux 容器里不能运行 setup-python(没有 systemd)。PyO3 官方 cross 镜像 /opt/python/ 目录已经预装了 cp314t,直接指定即可:
    args: --release --no-default-features -F free-threaded -i /opt/python/cp314-cp314t/bin/python3 --out dist
  • 验证方法:python -c "import sys; print(sys.version, sys._is_gil_enabled())",应该输出 False

6. 本地怎么装 free-threaded Python?

步骤(建议使用 pyenv,不然要手动编译):

# 安装依赖 (Debian/Ubuntu)
sudo apt-get update && sudo apt-get install -y build-essential libffi-dev libssl-dev zlib1g-dev libbz2-dev libreadline-dev libsqlite3-dev tk-dev libncursesw5-dev xz-utils
 
# 设置编译参数
export PYTHON_CONFIGURE_OPTS="--disable-gil --enable-shared"
 
# 安装
pyenv install 3.14-dev
pyenv shell 3.14-dev
 
# 验证
python -c "import sys; print(sys.version); print('GIL enabled?', sys._is_gil_enabled())"
 
# 清理环境变量(避免影响其他 Python 安装)
unset PYTHON_CONFIGURE_OPTS

如果没有 pyenv,可以使用 Docker(示例:ghcr.io/rust-cross/manylinux_2_28-cross:x86_64)进入容器,再运行 /opt/python/cp314-cp314t/bin/python3

仓库现状

  1. Cargo.toml 的 workspace 依赖为 pyo3 = { features = ["abi3-py311", ...] },意味着默认使用 limited API 构建版本无关的 wheel。
  2. rust/cocoindex/Cargo.toml 曾预留 free-threaded feature(目前被注释),可以用来切换 pyo3/free-threaded 功能。
  3. .github/workflows/release.yml 只构建/测试 abi3 wheel;CI 没有安装 free-threaded 解释器、也没有上传 cp314t 工件。
  4. 文档和贡献者指南尚未提示如何在本地构建或验证 free-threaded 版本。

参考资料

实现建议

1. Cargo / PyO3 配置

  1. 把 workspace 级别的 pyo3 依赖拆成“基础功能”+“abi3 特性”:
    • Cargo.toml (workspace.dependencies):仅保留通用 feature(auto-initialize, chrono, uuid 等)。
    • rust/cocoindexrust/py_utils 中新增 feature 开关,例如:
      [features]
      abi3 = ["pyo3/abi3-py311"]
      free-threaded = ["pyo3/free-threaded"]
      default = ["abi3"]
    • 正常构建继续走 --features abi3(生成当前 abi3 wheel);free-threaded 构建使用 --no-default-features --features free-threaded
  2. 审核代码里和 GIL 相关的类型:
    • 继续使用 Python::detach 包裹长时间的 Rust 工作线程。
    • 如果未来引入 GILProtectedOnceCell 等,需要根据 PyO3 指南切换到 Mutex + lock_py_attachedPyOnceLock,避免 free-threaded 模式下死锁。

2. 构建 & 交付流程

  1. LinuxPyO3/maturin-action 使用的 ghcr.io/rust-cross/manylinux_2_28-cross:* 镜像已经预装 /opt/python/cp314-cp314t/bin/python3,可以通过 args: -i /opt/python/cp314-cp314t/bin/python3 生成 cp314t-manylinux wheel。
  2. macOS / Windows
    • actions/setup-python@v5 指定 python-version: '3.14', allow-prereleases: true, freethreaded: true(或 3.14t-dev)。
    • maturin 使用 -i python(workflow 为 GIL-disabled 解释器)。
  3. .github/workflows/release.yml 中新增 build-free-threaded job:
    • 复制现有 matrix(linux x86_64/aarch64, macOS arm64/x86_64, Windows x64)。
    • 传入 --no-default-features --features free-threadedmaturin(可通过 MATURIN_FEATURESargs 中加 -F free-threaded --no-default-features 达成)。
    • 工件命名类似 wheels-free-threaded-${{ matrix.platform.os }}-${{ matrix.platform.target }}
  4. 发布阶段 release job 下载/上传所有 wheel,确保 PyPI 上既有 abi3(cp311-abi3)也有 cp314t 版本。

3. 测试策略

  1. 保留现有 test-abi3 job。
  2. 新增 test-free-threaded job:
    • 下载 wheels-free-threaded-linux-x86_64
    • actions/setup-python 安装 3.14 free-threaded 解释器。
    • pip install --find-links=./ cocoindex
    • 使用 python -c "import cocoindex, sys; assert not sys._is_gil_enabled()" 验证 wheel 构建目标正确。
  3. 可选:补充一个 maturin develop -F free-threaded 的本地说明,帮助贡献者在 pyenv 中自测:
    PYTHON_CONFIGURE_OPTS="--disable-gil --enable-shared" pyenv install 3.14-dev
    pyenv shell 3.14-dev
    maturin develop --manifest-path rust/cocoindex/Cargo.toml -F free-threaded --interpreter python

4. 发布注意事项

  • cp314t wheel 只能服务 Python 3.14 free-threaded;pip 会根据 cp314t tag 进行匹配,因此必须保持与平台对应的 manylinux/macos/windows tag。
  • 直到 PEP 803 提供稳定 ABI 前,cp314t 每个版本都要重新构建;release 脚本不能再假设“一个 wheel 通吃所有 Python 版本”。
  • pyproject.tomlclassifiers 已包含 Programming Language :: Python :: 3.14;若后续官方提供 Programming Language :: Python :: Implementation :: CPython :: Free Threaded classifier,可在此处补充。

下一步

  1. 拆分 pyo3 feature 配置,完成 free-threaded feature wiring。
  2. 依“构建 & 测试”章节修改 .github/workflows/release.yml,并验证本地/CI 可产出 cp314t 工件。
  3. 在贡献者指南或 README 中加入“如何本地安装 free-threaded Python 并测试 cocoindex”段落,降低后续维护成本。

给实习生的执行手册

下面是一步步的 checklist,按顺序完成即可,中途不要跳步骤。每完成一项在 PR/issue 里回报一下进度;如果遇到问题,先对照“常见坑”排查,再把尝试过的命令附在评论里。

0. 预备条件

  • 本地已经配置好 Rust 1.89+、Python 3.11/3.12(推荐 pyenv)、maturin>=1.7.8
  • 熟悉基本的 git / github 流程,能够在 fork 分支上提交 PR。
  • 提前准备分支:
    git remote -v              # 确认 upstream 指向 cocoindex-io/cocoindex
    git checkout main
    git pull upstream main
    git checkout -b feat/free-threaded-wheels

1. 梳理 PyO3 feature

  1. 打开根目录 Cargo.toml,将 [workspace.dependencies].pyo3 的 feature 列表删到只剩公共功能:
    pyo3 = { version = "0.25.1", features = ["auto-initialize", "chrono", "uuid"] }
    • 修改完成后 rg -n "abi3" -n Cargo.toml,确保 workspace 级别不再直接声明 abi3-*
  2. rust/cocoindex/Cargo.tomlrust/py_utils/Cargo.toml 里新增 feature 块(若已有 default 字段就合并):
    [features]
    default = ["abi3"]
    abi3 = ["pyo3/abi3-py311"]
    free-threaded = ["pyo3/free-threaded"]
    • 这两个 crate 是唯一暴露给 Python 的模块,确保都具备一致配置;改完后 rg -n "free-threaded" -n rust -g"*.toml" 能看到这两处声明。
  3. 在项目根目录运行一次:
    cargo check -p cocoindex
    Cargo.lock 会被刷新。
    • 如果 cargo check 报错 feature 未定义,检查是否在 Cargo.toml 写错名称(比如 free_threaded vs free-threaded)。
  4. pyproject.toml 或 README 的“本地构建”章节补充示例命令:
    maturin develop --manifest-path rust/cocoindex/Cargo.toml --no-default-features -F free-threaded
    • 同时注明需要 Python >= 3.14 (free-threaded),避免读者误以为普通 Python 可用。
  5. git status 确认上述文件都已修改,并截图(命令+输出)上传到 issue 以备查证。

2. CI: 构建 cp314t wheel

  1. .github/workflows/release.yml
    • 复制 build job,命名 build-free-threaded
    • matrix 同步 platform 列表(linux x86_64、linux aarch64、macOS arm64、macOS x86_64、windows x64)。
    • needs 仍然是 [create-versioned-toml, generate-3p-notices]
  2. Linux 步骤中把 PyO3/maturin-action 的参数改成:
    args: --release --no-default-features -F free-threaded -i /opt/python/cp314-cp314t/bin/python3 --out dist
    • 保留 manylinux / container 配置。
    • 通过 -i /opt/python/... 指定解释器,以确保 wheel filename 包含 cp314t-manylinux_2_28_x86_64
  3. 非 Linux 平台:
    • PyO3/maturin-action 之前新增:
      - uses: actions/setup-python@v5
        with:
          python-version: '3.14'
          allow-prereleases: true
          freethreaded: true
    • args 改成 --release --no-default-features -F free-threaded -i python --out dist
  4. 上传工件时命名成 wheels-free-threaded-${{ matrix.platform.os }}-${{ matrix.platform.target }}
  5. 保存后运行 yamllint .github/workflows/release.yml(如果装了)或至少 act -j build-free-threaded 检查语法。

3. CI: 测试 cp314t wheel

  1. 新建 job test-free-threaded,依赖 build-free-threaded
  2. 步骤:
    • 下载 wheels-free-threaded-linux-x86_64
    • actions/setup-python 同样装 3.14t-dev
    • pip install --find-links=./ cocoindex
    • 运行 python -c "import cocoindex, sys; assert not sys._is_gil_enabled()"
  3. Release job 要 needs: [build-free-threaded, test-free-threaded],并在下载工件时把 wheels-free-threaded-* 包含进去:
    - uses: actions/download-artifact@v4
      with:
      pattern: wheels-free-threaded-*
  4. test-free-threaded 任务中额外执行:
    - run: python -m pip debug --verbose
    - run: python - <<'PY'

import sys, sysconfig print(‘platlib:’, sysconfig.get_path(‘platlib’)) print(‘is_gil_enabled:’, getattr(sys, ‘_is_gil_enabled’, ‘missing’)) PY

这样 reviewer 能看到 pip/解释器信息。

### 4. 本地验证

1. 安装 free-threaded Python(如果没有 pyenv 就在容器内):
```bash
PYTHON_CONFIGURE_OPTS="--disable-gil --enable-shared" pyenv install 3.14-dev
pyenv shell 3.14-dev
  1. (可选) python -c "import sys; print(sys._is_gil_enabled())" 确认输出 False
  2. 在项目根目录运行:
    maturin develop --manifest-path rust/cocoindex/Cargo.toml --no-default-features -F free-threaded --interpreter python
    python -c "import cocoindex, sys; assert not sys._is_gil_enabled(); print('ok', sys.version)"
  3. 把成功/失败日志贴到 issue 里,便于 reviewers 知道你测试过。
  4. 如果需要生成 wheel(非 develop 模式)方便本地检查,运行:
    maturin build --manifest-path rust/cocoindex/Cargo.toml --release --no-default-features -F free-threaded -i python
    ls target/wheels
    确保文件名类似 cocoindex-*.whl 且包含 cp314t.
  5. 使用 pip uninstall -y cocoindex 清理环境,避免下次测试使用旧 wheel。

5. 常见坑

  • 忘记 --no-default-features,会导致 free-threaded build 仍然启用 abi3。
  • actions/setup-python 没有加 allow-prereleases: true 时下载不到 3.14t,需要回头检查 workflow。
  • Linux build 必须用 /opt/python/cp314-cp314t/bin/python3,不要用系统 Python(否则 wheel 不是 cp314t)。
  • 如果 pip install 时提示 “ABI mismatch”,说明 wheel 没有打出 cp314t tag,回到 maturin 命令检查 -i 和 feature。
  • 忘记给 release job 增加 wheels-free-threaded-* 的 download 步骤,会导致发布阶段缺包。
  • 如果 python -c "import cocoindex" 没有 sys._is_gil_enabled 属性,说明 interpreter 不是 free-threaded,通常是 freethreaded: true 漏掉。
  • macOS runner 默认拉取 3.14 普通解释器,必须设置 freethreaded: true;否则 wheel tag 会是 cp314 而不是 cp314t
  • Windows 机器上 python 命令可能指向 pre-installed 3.11,需要依赖 setup-python 输出 ${{ steps.py.outputs.python-path }} 或直接使用 python-version 引用的解释器。
  • 如果 CI 报 maturin develop 找不到 Py_LIMITED_API,说明 free-threaded 构建仍启用了 abi3,需要检查 default feature。

6. 提交 PR

  1. 确认 git status 只包含预期文件,然后运行:
    pre-commit run --all-files
  2. 提交并推送:
    git add .
    git commit -m "feat: add cp314t wheel pipeline"
    git push origin feat/free-threaded-wheels
  3. 在 GitHub 上发起 PR,模板里要注明:
    • 修改了哪些文件/工作流;
    • 本地是否完成 free-threaded wheel 的 maturin develop 验证;
    • CI 结果截图或链接。
  4. reviewers 反馈如果需要改动,按照同样的流程(修改 pre-commit commit —amend or new commit push)。

附录:如果你不懂 Rust / PyO3

Rust 是什么?

  • 一种系统编程语言,语法类似 C++,主打“内存安全 + 零成本抽象”。
  • 我们的核心逻辑(Index Engine)使用 Rust 实现,再通过 PyO3 暴露给 Python。
  • 你不需要写 Rust 代码,但需要知道如何运行 cargo(Rust 的包管理和构建工具),类似 Python 里的 pip + setup.py
  • 基本命令:
    • cargo check: 只做语法/类型检查,快;
    • cargo build --release: 编译生成产物;
    • cargo test: 运行测试。

什么是 crate / workspace?

  • Rust 项目叫 crate(可理解为 package)。Cargo.toml 是 crate 的配置文件。
  • CocoIndex 在仓库根目录有一个 workspace,包含多个子 crate(rust/cocoindex, rust/py_utils, …)。
  • Cargo.lock 记录了整个 workspace 的依赖版本,类似 poetry.lock

PyO3 是干嘛的?

  • PyO3 是一个 Rust crate,用来写 Python 扩展模块(跟 C 扩展/pybind11 类似)。
  • 它可以把 Rust 编译成 .so/.pyd,供 Python import cocoindex 使用。
  • Feature 切换(abi3, free-threaded)本质上是告诉 PyO3 生成哪种 ABI。

maturin 又是什么?

  • maturin 是 PyO3 官方推荐的构建/打包工具,相当于 setuptools + cffi 的结合体。
  • maturin develop:在当前 Python 环境安装一个开发版(带符号链接);
  • maturin build:生成 wheel;
  • maturin upload:上传到 PyPI。

不懂 Rust 但可以做什么?

  1. 改配置:比如 Cargo.toml、GitHub Actions、pyproject.toml。
  2. 跑命令:根据文档执行 cargo check, maturin build, python -c ...
  3. 看输出:如果命令报错,把完整日志贴在 issue/PR,用于诊断。

入门资料: