Skip to content

Migrate: Gemma3nAudioConverter from TF ops to keras.ops - #2978

Open
maitry63 wants to merge 13 commits into
keras-team:masterfrom
maitry63:port_Gemma3n_AudioConverter
Open

Migrate: Gemma3nAudioConverter from TF ops to keras.ops#2978
maitry63 wants to merge 13 commits into
keras-team:masterfrom
maitry63:port_Gemma3n_AudioConverter

Conversation

@maitry63

@maitry63 maitry63 commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

This PR migrates Gemma3nAudioConverter from TensorFlow ops to keras.ops.

Replaces TensorFlow-specific audio operations with Keras equivalents, including

  • tf.signal.frame → keras.ops.extract_sequences
  • tf.signal.rfft → keras.ops.rfft,
    while preserving batched/unbatched audio handling, spectrogram padding, and attention-mask behavior.

Fixes: #2951 (1st part)

Numerical verification colab.

Numerical Verification Results

Verified numerical equivalence against pure NumPy reference :

  • Test 1 (Sine Wave): Log-Mel max diff = 9.64e-03 | Linear-Mel max diff = 1.14e-04 [PASS]
  • Test 2 (Normalized Audio): max diff = 2.19e-05 [PASS]
  • Test 3 (Silent Audio Floor): max diff = 3.17e-07 [PASS]
  • Test 4 (Batch Invariance): max diff = 0.00e+00 [PASS]

Checklist

  • I have added all the necessary unit tests for my change.
  • I have verified that my change does not break existing code and works with all backends (TensorFlow, JAX, and PyTorch).
  • My PR is based on the latest changes of the main branch (if unsure, rebase the code).
  • I have followed the Keras Hub Model contribution guidelines in making these changes.
  • I have followed the Keras Hub API design guidelines in making these changes.
  • I have signed the Contributor License Agreement.

@github-actions github-actions Bot added the Gemma Gemma model specific issues label Aug 21, 2026

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request refactors the Gemma3nAudioConverter to be backend-agnostic by replacing TensorFlow-specific operations with Keras ops and NumPy equivalents, and adds corresponding unit tests. While the migration is a great step, three critical issues were identified in the review: a runtime unpacking error due to ops.rfft returning a complex tensor instead of a tuple, a potential crash during symbolic tracing when calling np.asarray on a symbolic KerasTensor, and performance/compatibility bottlenecks on GPUs and TPUs caused by casting computations to float64 instead of using self.compute_dtype.

Comment on lines +263 to +264
real, imag = ops.rfft(fft_frames, fft_length=self.fft_length)
magnitude_spec = ops.sqrt(ops.square(real) + ops.square(imag))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

In Keras 3, ops.rfft returns a complex-valued tensor rather than a tuple of (real, imag). Unpacking it directly will result in a runtime ValueError (e.g., not enough values to unpack). To compute the magnitude of the STFT, you should use ops.abs on the complex tensor returned by ops.rfft.

Suggested change
real, imag = ops.rfft(fft_frames, fft_length=self.fft_length)
magnitude_spec = ops.sqrt(ops.square(real) + ops.square(imag))
stft = ops.rfft(fft_frames, fft_length=self.fft_length)
magnitude_spec = ops.abs(stft)

Comment on lines +472 to +474
if isinstance(raw_speech, (list, tuple)):
was_batched = True
raw_speech_list = [np.asarray(speech) for speech in raw_speech]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The layer will crash when called with a symbolic KerasTensor (e.g., during functional model building or tracing) because np.asarray(raw_speech) is called directly on the input. To support symbolic tracing and functional model building, we should add a defensive check at the beginning of call() to return symbolic KerasTensors with the expected shapes and dtypes when a symbolic tensor is passed.

        import keras
        if isinstance(raw_speech, keras.KerasTensor):
            was_batched = len(raw_speech.shape) > 1
            if was_batched:
                features_shape = (raw_speech.shape[0], None, self.feature_size)
                mask_shape = (raw_speech.shape[0], None)
            else:
                features_shape = (None, self.feature_size)
                mask_shape = (None,)
            return (
                keras.KerasTensor(shape=features_shape, dtype=self.compute_dtype),
                keras.KerasTensor(shape=mask_shape, dtype="int32"),
            )

        if isinstance(raw_speech, (list, tuple)):
            was_batched = True
            raw_speech_list = [np.asarray(speech) for speech in raw_speech]
References
  1. When handling inputs that may be passed as tensors in a Python-only execution path, perform defensive type conversion or handling at the entry point of the path rather than deep inside helper functions.

mel_spec = tf.matmul(magnitude_spec, self.mel_filters)
mel_floor_tensor = tf.constant(self.mel_floor, dtype=self.compute_dtype)
log_mel_spec = tf.math.log(tf.maximum(mel_spec, mel_floor_tensor))
fft_frames = ops.cast(frames, dtype="float64")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Casting to float64 for the entire spectrogram extraction in call() is a major performance bottleneck on GPUs and is not supported on TPUs (which can cause compilation errors or silent CPU fallbacks). Since this is a preprocessing layer, using self.compute_dtype (which defaults to float32) is much more efficient and device-compatible. Please update this and the other float64 casts in this method to use self.compute_dtype.

Suggested change
fft_frames = ops.cast(frames, dtype="float64")
fft_frames = ops.cast(frames, dtype=self.compute_dtype)

@maitry63
maitry63 marked this pull request as draft August 21, 2026 12:29
@maitry63
maitry63 marked this pull request as ready for review August 24, 2026 16:31
@maitry63
maitry63 force-pushed the port_Gemma3n_AudioConverter branch 2 times, most recently from ed17b60 to b967b7a Compare August 25, 2026 11:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Gemma Gemma model specific issues

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[PyGrain Migration] 7. Migrate AudioConverter(Whisper, Moonshine, Gemma3n, Gemma4)

1 participant