# CPython 3.14 Free-Threaded TensorFlow Support — Upstream PR Series and Validation

**URL:** <https://discuss.ai.google.dev/t/cpython-3-14-free-threaded-tensorflow-support-upstream-pr-series-and-validation/180353>\
**Category:** TensorFlow\
**Tags:** python\
**Created:** [September 1, 2026, 4:11am UTC](https://discuss.ai.google.dev/t/cpython-3-14-free-threaded-tensorflow-support-upstream-pr-series-and-validation/180353 "2026-09-01T04:11:40Z")\
**Posts on this page:** 1\
**Page:** 1

<div class="post-metadata">

**Author:** ![Mina\_Ibrahim](https://d1dlmcr85iqnpo.cloudfront.net/user_avatar/discuss.ai.google.dev/mina_ibrahim/32/143010_2.png) [@Mina\_Ibrahim](https://discuss.ai.google.dev/u/Mina_Ibrahim)\
**Post date:** [September 1, 2026, 4:11am UTC](https://discuss.ai.google.dev/t/cpython-3-14-free-threaded-tensorflow-support-upstream-pr-series-and-validation/180353/1 "2026-09-01T04:11:41Z")

</div>

# CPython 3.14 Free-Threaded TensorFlow Support — Upstream PR Series and Validation

Hi everyone,

I have been working on making TensorFlow and parts of its Python runtime ecosystem compatible with **CPython 3.14 free-threaded mode** (PEP 703).

The work started as an audit of TensorFlow native Python extensions for places that still implicitly relied on the GIL for synchronization, Python object lifetime, callback safety, or mutable native state.

As the work progressed, some issues also turned out to belong to adjacent upstream projects such as **LiteRT** and **gRPC** , so those fixes are being submitted to their respective repositories rather than being kept inside TensorFlow.

This post summarizes the current upstream work, the validation performed so far, and the areas where feedback from maintainers and contributors would be useful.

## Background

With a traditional CPython build, many native extensions can accidentally rely on the GIL as a process-wide synchronization mechanism.

Under free-threaded Python, that assumption no longer holds.

For TensorFlow, this means native bindings need to be reviewed for issues such as:

- mutable C++ state accessed concurrently from Python
- borrowed Python references whose lifetime may change concurrently
- Python callbacks invoked while native locks are held
- global initialization paths that previously relied on GIL serialization
- races between object shutdown/destruction and concurrent operations
- native modules that need to explicitly declare free-threaded compatibility

The goal of this work is not simply to add `py::mod_gil_not_used()` to extensions.

The underlying code paths also need to be safe when Python threads can execute them concurrently.

## TensorFlow Core Runtime

The main TensorFlow contribution is:

**tensorflow/tensorflow#125652**

> <https://github.com/tensorflow/tensorflow/pull/125652>
>
> \## Summary
> 
> Make the remaining core TensorFlow Python-native runtime bindings …compatible with CPython free-threaded execution.
> 
> This PR consolidates the final core-runtime changes from the archived working CPython 3.14 free-threaded TensorFlow snapshot.
> 
> The affected bindings can execute concurrently when Python no longer provides process-wide GIL serialization, so mutable native state, Python object ownership, and callback lifetimes must be handled explicitly.
> 
> \## Scope
> 
> This PR covers:
> 
> \- TensorFlow session bindings
> \- event and debug-event writer bindings
> \- eager/TFE runtime and callback handling
> \- Python object ownership and reference safety
> \- file I/O bindings
> \- stacktrace handling
> \- SavedModel bindings
> \- quantization bindings
> \- Python utility bindings
> \- \`nest\`
> \- \`tf\_stack\`
> \- \`fast\_module\_type\`
> \- supporting free-threading regression tests
> 
> \## Changes
> 
> \### Session and event writer synchronization
> 
> Add explicit synchronization and safer Python object handling around TensorFlow session and event writer state that could previously rely on the legacy GIL for serialization.
> 
> This includes:
> 
> \- \`tf\_session\`
> \- event writers
> \- debug-event writers
> 
> \### Eager / TFE runtime safety
> 
> Update eager/TFE native runtime handling for concurrent execution under free-threaded Python.
> 
> The changes cover mutable runtime state, Python callbacks, weak references, object ownership, and callback lifetimes.
> 
> Where Python callbacks are involved, native synchronization is structured so Python-level execution is not performed while holding locks that could cause re-entrancy or deadlock problems.
> 
> \### Python reference ownership
> 
> Replace borrowed-reference assumptions with owned references or stable snapshots where concurrent container mutation or object lifetime changes could otherwise invalidate references.
> 
> This includes utility and runtime paths that access Python dictionaries, lists, tuples, and registered Python objects.
> 
> \### File I/O synchronization
> 
> Protect mutable per-object file I/O state so concurrent access from free-threaded Python does not rely on the legacy GIL as an implicit lock.
> 
> \### Stacktrace handling
> 
> Synchronize stacktrace handler installation and related global native state.
> 
> The corresponding Python native module is updated for free-threaded execution.
> 
> \### SavedModel
> 
> Mark the SavedModel native binding as compatible with GIL-independent execution after auditing its underlying native state and ownership behavior.
> 
> \### Quantization bindings
> 
> Update remaining quantization-related native bindings for free-threaded Python execution.
> 
> \### \`nest\`
> 
> Update Python object access so container elements are held through owned references where required under concurrent execution.
> 
> \### \`tf\_stack\`
> 
> Synchronize mutable stack-trace mapping/filtering state and use stable snapshots where Python objects can be accessed concurrently.
> 
> \### Utility bindings
> 
> Update remaining utility bindings, including shared registration/cache state and Python object ownership rules, for free-threaded execution.
> 
> \### \`fast\_module\_type\`
> 
> Protect mutable state used by the fast module type implementation and avoid relying on the GIL for synchronization.
> 
> \### Regression coverage
> 
> Add or update regression coverage for:
> 
> \- eager backprop
> \- eager forwardprop
> \- framework operations
> \- stacktrace handling
> 
> \## Validation
> 
> The complete working free-threaded TensorFlow snapshot was previously validated with a freshly built and freshly installed CPython 3.14 free-threaded TensorFlow wheel.
> 
> Observed natural import result:
> 
> GIL\_BEFORE\_IMPORT: False
> TF\_VERSION: 2.22.0-dev0+selfbuilt
> GIL\_AFTER\_IMPORT: False
> TOTAL\_WARNING\_COUNT: 0
> GIL\_WARNING\_COUNT: 0
> NATURAL\_LOADED\_TF\_NATIVE\_MODULES: 54
> NATURAL\_IMPORT\_GIL: False -\> False
> NATURAL\_IMPORT\_NO\_GIL=PASS
> 
> This means the fresh CPython 3.14t wheel had no GIL-declaration blockers among TensorFlow native extensions reached by natural:
> 
> import tensorflow as tf
> 
> The GIL remained disabled before and after the import.
> 
> The complete working snapshot also underwent subsystem-specific build, runtime, stress, and official-test validation during the free-threading audit.
> 
> For this clean final consolidation branch, the aggregate native target build was intentionally interrupted after expanding into a very large dependency graph. No compile failure in the changed sources was observed before interruption.
> 
> The Python regression files in this PR were syntax-checked successfully with CPython 3.14t.
> 
> Final branch scope checks:
> 
> CORE\_RUNTIME\_FINAL\_FILESET=PASS
> CORE\_RUNTIME\_FINAL\_DIFF\_CHECK\_EXIT=0
> CORE\_RUNTIME\_FINAL\_FILE\_COUNT=29
> CORE\_RUNTIME\_PYCOMPILE\_EXIT=0
> 
> \## Dependencies for local CPython 3.14t validation
> 
> TensorFlow hermetic Python support:
> 
> https://github.com/tensorflow/tensorflow/pull/125634
> 
> rules\_ml\_toolchain free-threaded Python version-kind support:
> 
> https://github.com/tensorflow/rules\_ml\_toolchain/pull/302
> 
> \## Scope note
> 
> This is the final core-runtime consolidation PR from the archived working CPython 3.14 free-threaded TensorFlow snapshot.
> 
> Other subsystem-specific free-threading changes have been submitted separately in focused PRs.

**Make core Python runtime bindings safe for free-threaded Python**

This PR consolidates the remaining core-runtime work from the free-threading audit.

The affected areas include:

- TensorFlow session bindings
- eager / TFE runtime bindings
- event and debug-event writers
- Python object ownership and reference handling
- file I/O bindings
- stacktrace handling
- SavedModel bindings
- quantization-related bindings
- Python utility bindings
- `nest`
- `tf_stack`
- `fast_module_type`
- supporting regression coverage

## Synchronization and lifetime changes

A recurring pattern in the audit was code that was safe only because the traditional GIL implicitly serialized access.

For free-threaded execution, synchronization has to become explicit.

Examples include:

- protecting mutable native state with appropriate mutexes
- taking stable or owned references to Python objects when concurrent mutation is possible
- avoiding Python callback execution while holding native locks that could create re-entrancy or deadlock problems
- protecting shared registration/cache state
- synchronizing event writer and file I/O state
- auditing weak-reference and callback lifetime handling in eager runtime paths

## Python object ownership

Another important area is borrowed-reference behavior.

With free-threaded execution, code cannot always assume that an object retrieved from a Python container will remain alive while another thread may mutate that container.

Some bindings therefore need to use owned references or stable snapshots before continuing native processing.

This affected several runtime and utility paths that interact with:

- dictionaries
- lists
- tuples
- registered Python objects
- callback collections
- stack-trace mapping/filtering state

## Validation

The complete working free-threaded TensorFlow snapshot was tested with a freshly built CPython 3.14 free-threaded TensorFlow wheel.

One important validation was checking whether a normal TensorFlow import caused CPython to fall back to GIL-enabled execution.

Observed result:

```plaintext
GIL_BEFORE_IMPORT: False
TF_VERSION: 2.22.0-dev0+selfbuilt
GIL_AFTER_IMPORT: False
TOTAL_WARNING_COUNT: 0
GIL_WARNING_COUNT: 0
NATURAL_LOADED_TF_NATIVE_MODULES: 54
NATURAL_IMPORT_GIL: False -> False
NATURAL_IMPORT_NO_GIL=PASS

```

In that environment:

- the GIL was disabled before importing TensorFlow
- the GIL remained disabled after `import tensorflow as tf`
- 54 TensorFlow native modules were reached through the natural import path
- no GIL-declaration warnings were observed

The work also included subsystem-specific build, runtime, regression, and stress testing during the audit.

This does **not** mean that every possible TensorFlow execution path has been proven free-threading safe yet, but it provides a useful baseline for continuing the upstream review.

## LiteRT / TensorFlow Lite Runtime

Part of the original work covered the TensorFlow Lite Python runtime.

That work was initially submitted as:

**tensorflow/tensorflow#125644**

> <https://github.com/tensorflow/tensorflow/pull/125644>
>
> \## Summary
> 
> Make TensorFlow Lite Python runtime bindings compatible with CPyth…on free-threaded builds.
> 
> This PR covers:
> 
> \- model analyzer bindings
> \- TFLite interpreter bindings
> \- TFLite runtime metrics bindings
> 
> The changes focus on free-threaded pybind declarations and synchronization of mutable native state that may now be accessed concurrently from Python threads.
> 
> \## Changes
> 
> \### Analyzer binding
> 
> Declare:
> 
> \`\`\`text
> pybind11::mod\_gil\_not\_used()
> \`\`\`
> 
> for:
> 
> \`\`\`text
> \_pywrap\_analyzer\_wrapper
> \`\`\`
> 
> \### Interpreter binding
> 
> Declare:
> 
> \`\`\`text
> py::mod\_gil\_not\_used()
> \`\`\`
> 
> for:
> 
> \`\`\`text
> \_pywrap\_tensorflow\_interpreter\_wrapper
> \`\`\`
> 
> Add a native per-instance \`absl::Mutex\` to serialize access to the mutable TFLite \`Interpreter\`.
> 
> Python-facing methods that access or mutate interpreter state now take this lock, including operations such as:
> 
> \- tensor allocation
> \- invocation
> \- input/output queries
> \- resize
> \- tensor reads/writes
> \- signature queries
> \- graph mutation
> \- thread-count changes
> 
> \### Avoid legacy-GIL deadlocks
> 
> \`Invoke()\` intentionally releases the legacy GIL while native execution proceeds.
> 
> To avoid lock-order deadlocks on GIL-enabled Python builds, waiting for the interpreter mutex is done without holding the legacy GIL.
> 
> On free-threaded Python, where there is no global GIL, the native mutex is acquired directly.
> 
> \### Runtime metrics binding
> 
> Declare:
> 
> \`\`\`text
> py::mod\_gil\_not\_used()
> \`\`\`
> 
> for:
> 
> \`\`\`text
> \_pywrap\_tensorflow\_lite\_metrics\_wrapper
> \`\`\`
> 
> Add a native \`absl::Mutex\` around the shared \`MetricsExporter\` so concurrent calls to \`ExportMetrics()\` on the same wrapper instance are serialized safely.
> 
> The required synchronization dependencies are added to the corresponding BUILD targets.
> 
> \## Validation
> 
> Validated with CPython 3.14 free-threaded hermetic Python using:
> 
> \`\`\`text
> \--repo\_env=HERMETIC\_PYTHON\_VERSION=3.14-freethreaded
> \--repo\_env=USE\_PYWRAP\_RULES=True
> \--@rules\_python//python/config\_settings:py\_freethreaded=yes
> \`\`\`
> 
> The affected targets built successfully:
> 
> \`\`\`text
> //tensorflow/lite/python/analyzer\_wrapper:\_pywrap\_analyzer\_wrapper
> //tensorflow/lite/python/interpreter\_wrapper:\_pywrap\_tensorflow\_interpreter\_wrapper
> //tensorflow/lite/python/metrics:\_pywrap\_tensorflow\_lite\_metrics\_wrapper
> \`\`\`
> 
> Result:
> 
> \`\`\`text
> TFLITE\_RUNTIME\_WARM\_BUILD\_EXIT=0
> TFLITE\_RUNTIME\_FINAL\_DIFF\_CHECK\_EXIT=0
> \`\`\`
> 
> The interpreter and runtime-metrics concurrency behavior was also validated earlier on the full working free-threaded TensorFlow snapshot.
> 
> \## Dependency
> 
> Local CPython 3.14 free-threaded validation uses:
> 
> TensorFlow hermetic Python support:
> https://github.com/tensorflow/tensorflow/pull/125634
> 
> and:
> 
> rules\_ml\_toolchain:
> https://github.com/tensorflow/rules\_ml\_toolchain/pull/302
> 
> \## Scope
> 
> This PR is intentionally limited to TFLite runtime bindings.
> 
> TFLite conversion and quantization bindings are handled separately to keep review scope focused.

During review, it was clarified that TFLite / LiteRT development and runtime bindings have moved to the dedicated **google-ai-edge/LiteRT** repository.

The runtime changes were therefore migrated to:

**google-ai-edge/LiteRT#9687**

> <https://github.com/google-ai-edge/LiteRT/pull/9687>
>
> \## Summary
> 
> Make the LiteRT Python runtime bindings safe for CPython free-thre…aded execution.
> 
> This work was originally submitted in tensorflow/tensorflow#125644. TensorFlow review requested that TFLite / LiteRT runtime changes be moved to the dedicated \`google-ai-edge/LiteRT\` repository. This PR contains only the LiteRT runtime changes; no TensorFlow fuzzing tests are included.
> 
> \## Changes
> 
> \- Mark the analyzer, interpreter, and metrics pybind modules with \`py::mod\_gil\_not\_used()\`.
> \- Add per-interpreter synchronization around mutable interpreter access.
> \- Avoid waiting on the interpreter mutex while holding the legacy GIL on GIL-enabled Python builds.
> \- Make NumPy C API initialization thread-safe with \`absl::call\_once\`.
> \- Serialize metrics exporter access.
> \- Add the required Abseil synchronization/base BUILD dependencies.
> 
> \## Scope
> 
> Runtime-only changes under:
> 
> \- \`tflite/python/analyzer\_wrapper\`
> \- \`tflite/python/interpreter\_wrapper\`
> \- \`tflite/python/metrics\`
> 
> TensorFlow fuzzing tests are intentionally excluded, per review guidance on tensorflow/tensorflow#125644.
> 
> \## Validation
> 
> \- \`git diff --check\` passes.
> \- The corresponding TensorFlow-side implementation was previously reviewed and iterated on before the repository transition request.
> 
> A full local LiteRT build was not rerun as part of this repository migration.

**Make LiteRT Python runtime bindings safe for free-threaded Python**

The LiteRT PR is intentionally limited to runtime changes and does not include TensorFlow fuzzing tests.

The LiteRT work includes:

- marking analyzer, interpreter, and metrics pybind modules with `py::mod_gil_not_used()`
- adding per-interpreter synchronization
- protecting mutable interpreter operations
- avoiding waits on the interpreter mutex while holding the legacy GIL on normal CPython builds
- making NumPy C API initialization thread-safe
- serializing metrics exporter access
- adding the corresponding Abseil synchronization dependencies

One example of a race exposed by free-threaded execution was NumPy initialization.

Previously:

```cpp
void ImportNumpy() { import_array1(); }

```

Multiple free-threaded interpreter creation calls could reach this initialization concurrently.

The migrated implementation uses one-time initialization:

```cpp
void ImportNumpy() {
  static absl::once_flag flag;
  absl::call_once(flag, []() {
    import_array1();
  });
}

```

This ensures the NumPy C API initialization path is executed once in a thread-safe manner.

## gRPC Python

The free-threading work also exposed an independent concurrency issue in gRPC Python.

That fix is being handled upstream in:

**grpc/grpc#43278**

> <https://github.com/grpc/grpc/pull/43278>
>
> \## Summary
> 
> Enable \`grpc.\_cython.cygrpc\` to remain compatible with free-thread…ed CPython by declaring the Cython extension as free-threading compatible.
> 
> Without this directive, Cython generates the module with \`Py\_MOD\_GIL\_USED\`, causing CPython 3.14t to automatically re-enable the GIL when importing \`grpc.\_cython.cygrpc\`.
> 
> This change adds:
> 
> \- \`# cython: freethreading\_compatible=True\` to \`cygrpc.pyx\`
> \- A regression test verifying that importing \`cygrpc\` does not enable the GIL on a free-threaded Python build
> 
> Partially addresses #38762.
> 
> \## Validation
> 
> Tested locally with CPython 3.14.7 free-threaded (\`cp314t\`) and grpcio 1.83.0.
> 
> \### Import
> 
> \- GIL before \`import grpc\`: disabled
> \- GIL after \`import grpc\`: disabled
> \- No free-threading runtime warning
> 
> \### Synchronous gRPC stress test
> 
> \- 8 concurrent Python threads
> \- 100 RPCs per thread
> \- 800 total RPCs
> \- 0 errors
> \- GIL remained disabled
> 
> \### AsyncIO gRPC stress test
> 
> \- 500 RPCs
> \- concurrency: 50
> \- 0 errors
> \- GIL remained disabled
> 
> \### Regression test
> 
> The new test passes with the patched \`cygrpc\`.
> 
> As a negative control, the same test fails against an unpatched grpcio build because importing \`grpc.\_cython.cygrpc\` re-enables the GIL.

The issue involved a race between:

- retrieving or creating a registered call handle
- closing the channel concurrently

The fix synchronizes access through the channel state condition so the registered call handle cannot race with channel shutdown.

The relevant validation included:

- CPython 3.14t wheel build/install
- checking that importing gRPC does not enable the GIL
- channel-close race stress testing
- `ChannelCloseTest`
- type smoke tests
- repeated concurrency stress runs

I mention this here because it illustrates an important aspect of the TensorFlow free-threading effort: some failures observed while validating TensorFlow originate in lower-level or adjacent Python runtime dependencies and need to be fixed in the appropriate upstream project.

## Repository Boundaries

One principle I am trying to follow throughout this work is to keep fixes in the project that actually owns the affected runtime.

So far this has resulted in:

- **TensorFlow core runtime fixes → `tensorflow/tensorflow`**
- **TFLite / LiteRT runtime fixes → `google-ai-edge/LiteRT`**
- **gRPC Python runtime race fixes → `grpc/grpc`**

This makes the work easier to review and avoids introducing cross-project fixes into TensorFlow simply because TensorFlow happened to expose the problem during testing.

## What I Would Appreciate Feedback On

I would especially appreciate feedback from TensorFlow maintainers and contributors on:

1. Whether there are additional TensorFlow native modules that should be prioritized for free-threading audit coverage.

2. Whether there are preferred upstream tests for detecting accidental GIL fallback in TensorFlow native extensions.

3. Whether the synchronization patterns used in the core-runtime PR align with TensorFlow’s preferred locking and callback-lifetime conventions.

4. Whether there are existing stress or concurrency suites that would be useful to run specifically under CPython 3.14t.

5. How maintainers would prefer the remaining free-threading work to be divided across PRs as CPython free-threaded support evolves.

## Current Status

The effort is still ongoing.

The important milestone so far is that a working TensorFlow build has been able to execute a natural import under **CPython 3.14 free-threaded mode without enabling the GIL** , while a number of concrete synchronization and lifetime issues have already been identified and moved upstream to the repositories that own them.

Relevant PRs:

- TensorFlow core runtime:  
[https://github.com/tensorflow/tensorflow/pull/125652](https://github.com/tensorflow/tensorflow/pull/125652)

- LiteRT Python runtime:  
[Make LiteRT Python runtime bindings safe for free-threaded Python by MinaIbrahim10 · Pull Request #9687 · google-ai-edge/LiteRT · GitHub](https://github.com/google-ai-edge/LiteRT/pull/9687)

- gRPC Python concurrency fix:  
[[Python] Enable free-threaded support for cygrpc by MinaIbrahim10 · Pull Request #43278 · grpc/grpc · GitHub](https://github.com/grpc/grpc/pull/43278)

Feedback, additional test suggestions, and review from people familiar with TensorFlow’s native Python runtime would be very welcome.

Thanks!
