Skip to content

Python 3.12 / TensorFlow 2.16+ (Keras 3) Support#194

Open
warptengood wants to merge 1 commit into
spotify:mainfrom
warptengood:py312-keras3-support
Open

Python 3.12 / TensorFlow 2.16+ (Keras 3) Support#194
warptengood wants to merge 1 commit into
spotify:mainfrom
warptengood:py312-keras3-support

Conversation

@warptengood

Copy link
Copy Markdown

Summary

Adds support for Python 3.12 and modern dependency versions (TensorFlow >= 2.16,
which ships Keras 3, NumPy >= 1.21, SciPy >= 1.7, librosa >= 0.10). Drops end‑of‑life
Python 3.8/3.9. All tox environments pass on Python 3.10, 3.11, and 3.12.

The work spans four areas:

  1. Packaging -- declare 3.12 support, bump dependency floors, remove the deprecated
    tensorflow-macos.
  2. Keras 3 compatibility -- the model‑building and layer code used several APIs that
    Keras 3 changed or removed.
  3. Inference backend -- the bundled TF SavedModel cannot be loaded under Keras 3, so
    on TF >= 2.16 inference defaults to the bundled TFLite model, served by the modern
    ai-edge-litert runtime.
  4. Tooling / tests / CI -- toolchain pinned to 3.12, lint/format/type warnings cleared,
    CI matrix updated.

Motivation

  • Python 3.12 is widely deployed; the project previously capped TensorFlow at < 2.15.1,
    which has no Python 3.12 wheels. TensorFlow 2.16 is the first release with 3.12
    support -- but it also makes Keras 3 the default, which is not API‑compatible with the
    Keras 2 code the project was written against.
  • tensorflow-macos has been deprecated since TF 2.13; Apple‑silicon users now install the
    standard tensorflow package.
  • Python 3.8 and 3.9 are end‑of‑life and were dragging dependency floors down.

Key Decisions

1. Default to the TFLite model on TF >= 2.16 (instead of the TF SavedModel)

The bundled ICASSP‑2022 SavedModel (saved_models/icassp_2022/nmp) is a legacy
TF2 / Keras‑2 export containing optimizer slot variables. Loading it under TF 2.16+
fails with AttributeError: '_UserObject' object has no attribute 'add_slot' -- an
unfixable‑in‑place artifact incompatibility.

The repo already ships the same model as TFLite, CoreML, and ONNX. The TFLite file
loads cleanly under new TF and its signature matches exactly (input input_2; outputs
contour / note / onset), so predict() works unchanged.

Decision: select the default model format by TF version -- SavedModel on TF < 2.16
(Keras 2), TFLite on TF >= 2.16 (Keras 3). This keeps Python 3.10/3.11 behavior identical
and gives 3.12 a working, numerically‑equivalent path. Re‑exporting the SavedModel in a
Keras‑3 format was rejected as much heavier and risking numerical drift.

2. Serve TFLite via ai-edge-litert, not tensorflow.lite

On Python 3.12 there is no tflite_runtime wheel, so the TFLite path would fall back to
tensorflow.lite.Interpreter, which emits a deprecation warning (scheduled for deletion in TF 2.20; use LiteRT from ai_edge_litert). ai-edge-litert is Google's supported
successor and loads the model identically.

Decision: prefer ai-edge-litert when tflite_runtime is absent, falling back to
tensorflow.lite only if neither is installed. Added as a dependency for non‑Windows,
Python 3.12+ (where it has wheels and is the default backend).

3. Use keras.ops for symbolic ops, not a custom wrapper layer

Keras 3 forbids calling raw TensorFlow ops (e.g. tf.expand_dims) directly on a symbolic
KerasTensor during functional model construction. keras.ops.expand_dims is the
backend‑agnostic replacement and is valid on KerasTensors. Keras 2 has no keras.ops,
but raw tf.expand_dims works there and shares the same (x, axis) signature.

Decision: alias keras_ops = tf.keras.ops if hasattr(tf.keras, "ops") else tf and call
keras_ops.expand_dims(...). One line, no new layer class, works on both Keras 2 and 3.

4. Declare tensorboard explicitly

TensorFlow 2.21 no longer pulls in tensorboard transitively, but the training /
visualization path uses tf.summary.audio (provided by tensorboard). Added
tensorboard>=2.4.1 to the tf extra so training/visualization works out of the box.

5. Drop Python 3.8 / 3.9 entirely

Both are EOL. requires-python is now >=3.10; classifiers, the tox envlist, and the CI
matrix were trimmed accordingly. This is a breaking change for anyone on 3.8/3.9.

6. Do not silence upstream deprecation warnings

The remaining warnings originate in third‑party packages that are already at their latest
versions (see Known Remaining Warnings). Rather than filter them, they are left visible
and documented; there is no version bump that resolves them.


Full Changelog

Packaging & Python support (pyproject.toml)

  • Added requires-python = ">=3.10".
  • Classifiers: removed 3.8, 3.9; added 3.12.
  • Removed tensorflow-macos (deprecated since TF 2.13); macOS now uses tensorflow.
  • TensorFlow now version‑gated:
    • tensorflow>=2.4.1,<2.16.0 for non‑Darwin, Python 3.11 (Keras 2).
    • tensorflow>=2.16.0 for non‑Darwin, Python 3.12+ (Keras 3).
    • tensorflow>=2.13.0 for macOS, Python 3.12+.
  • Added ai-edge-litert (non‑Windows, Python 3.12+).
  • Added tensorboard>=2.4.1 to the tf extra.
  • Raised dependency floors to versions that ship 3.12 wheels and dropped a stale upper pin:
    • librosa>=0.8.0 -> >=0.10.0
    • numpy>=1.18 -> >=1.21
    • scipy>=1.4.1 -> >=1.7.0
    • resampy>=0.2.2,<0.4.3 -> >=0.2.2 (upper pin removed)

Keras 3 compatibility (source)

  • basic_pitch/models.py -- tf.expand_dims(...) on symbolic tensors -> keras_ops.expand_dims(...)
    (via the keras_ops = tf.keras.ops if hasattr(tf.keras, "ops") else tf alias). Affected the
    CQT input branch and the reduced‑contour -> notes branch.
  • basic_pitch/layers/signal.py -- input_shape.rank -> len(input_shape) (Keras 3 passes
    build() a plain tuple, which has no .rank). Two call sites (Stft.build, NormalizedLog.build).
  • basic_pitch/nn.py -- removed import tensorflow.keras.backend as K; replaced the removed
    K.int_shape(x) with x.shape (FlattenAudioCh, FlattenFreqCh).
  • basic_pitch/train.py -- dropped the sample_weight_mode=... argument to model.compile()
    (removed in Keras 3; the value was all‑None, i.e. the default, so behavior is unchanged).

Inference backend

  • basic_pitch/__init__.py -- default model format is now chosen by TF version:
    SavedModel (nmp) on TF < 2.16, TFLite (nmp.tflite) on TF >= 2.16.
  • basic_pitch/inference.py -- TFLite import order is now
    tflite_runtime -> ai_edge_litert -> tensorflow.lite.

Tests

  • tests/test_nn.py -- removed the now‑invalid sample_weight_mode=None from both compile() calls.
  • tests/test_callbacks.py -- vc.model = MockModel() -> vc.set_model(MockModel())
    (Keras 3 makes Callback.model a read‑only property; set_model() works on both Keras 2 and 3).
  • tests/test_inference.py -- librosa.get_duration(filename=...) -> path=...
    (renamed in librosa 0.10; old kwarg raised a FutureWarning).
  • tests/data/conftest.py -- fixture decorator type‑ignores updated from [misc] to
    [untyped-decorator] to match the current mypy/pytest typing.

Tooling & CI

  • tox.ini -- envlist now py310,py311,py312,...; full and data envs pinned to
    python3.12; lint/format/mypy envs moved to python3.12; black invoked with
    --target-version py312.
  • .github/workflows/tox.yml -- CI Python matrix is now 3.12, 3.11, 3.10 (3.8/3.9 removed),
    plus a macOS 3.12 job.
  • README.md -- compatibility line now lists Python 3.10, 3.11, 3.12.

Behavioral & Compatibility Notes

  • Python 3.10 / 3.11 behavior is unchanged: they continue to run TF < 2.16 / Keras 2 and
    load the TF SavedModel by default.
  • Python 3.12 uses the TFLite model by default. Outputs are numerically equivalent -- the
    existing test_predict asserts the TFLite output matches the committed reference
    (vocadito_10/model_output.npz) to atol=1e-4.
  • predict() and the public API are unchanged. Backend selection is internal.
  • Breaking: Python 3.8 and 3.9 are no longer supported.

Testing & Verification

All tox environments pass on this machine (3.10/3.11/3.12 installed; 3.8/3.9 removed):

py310 OK   py311 OK   py312 OK   full OK   data OK
manifest OK   check-formatting OK   lint OK   mypy OK

The CI matrix is what proves the migration: the same test suite runs under two genuinely
different stacks, so both code paths are exercised end‑to‑end:

Python TensorFlow Keras Default inference backend
3.10 / 3.11 2.15.x Keras 2 TF SavedModel
3.12 >= 2.16 (2.21 here) Keras 3 TFLite via ai-edge-litert

test_predict on 3.12 runs entirely through the TFLite path and checks numerical
equivalence against the reference output, so no new tests were required.


Known Remaining Warnings (upstream, not fixable by version bump)

  • audioread deprecation warnings (aifc / audioop / sunau): emitted from
    audioread/rawread.py. librosa 0.11.0 and audioread 3.1.0 are both the latest
    releases; the deprecated‑stdlib imports are hard‑coded upstream (PEP 594 removes those
    modules in Python 3.13). No release currently fixes this.
  • apache_beam PytestCollectionWarning for its TestPipeline class (data tests only):
    cosmetic, originates in apache‑beam's class naming.

These were intentionally left visible rather than filtered.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant