Repository: huggingface/transformers
Pull request: Fix LASR feature extractor regression from invalid center argument

# What does this PR do?
  This PR fixes a LASR regression introduced in `#43769` (released in `v5.2.0`).

  `LasrFeatureExtractor.__call__` passes `center` into `_torch_extract_fbank_features(...)`, but
  `_torch_extract_fbank_features` did not accept that argument in LASR.
  This caused a runtime `TypeError` for LASR models (including `google/medasr`) during preprocessing.

  Fixes #44206

  ## Root cause
  The LASR call path was changed to:
  - `self._torch_extract_fbank_features(input_features, device, center)`

  while the target method signature remained:
  - `_torch_extract_fbank_features(self, waveform, device="cpu")`

  ## Changes
  - Kept `center` in `LasrFeatureExtractor.__call__` (API compatibility).
  - Updated LASR `_torch_extract_fbank_features` signature to accept:
    - `center: bool = True`
  - Kept the call site forwarding `center`:
    - `self._torch_extract_fbank_features(input_features, device, center)`
  - In LASR, `center` is intentionally a no-op (LASR uses `unfold + rfft`, not `torch.stft`).
  - Added regression tests in:
    - `tests/models/lasr/test_feature_extraction_lasr.py`

  ## Why this shape?
  Keeping `center` in the LASR public call signature avoids downstream modular-generation drift for
  `voxtral_realtime` (which inherits LASR feature extractor behavior), while still fixing the LASR crash.

  ## Tests
  Ran:
  - `python -m pytest -q tests/models/lasr/test_feature_extraction_lasr.py`
  - `python utils/check_modular_conversion.py --files src/transformers/models/voxtral_realtime/
  modular_voxtral_realtime.py`

  Result:
  - `2 passed`
  - modular conversion check passed (no diff)

  ## Impact
  - Fixes runtime crash for LASR preprocessing on current versions.
  - Preserves API compatibility for callers passing `center`.
  - No intended behavior change to LASR feature computation itself.
  - Docs update: N/A (inline API/docstring consistency only).

  ## Who can review?

  @eustlb

Changed files:
filename: src/transformers/models/lasr/feature_extraction_lasr.py
patch: @@ -149,7 +149,6 @@ def __call__(
         do_normalize: bool | None = None,
         device: str | None = "cpu",
         return_token_timestamps: bool | None = None,
-        center: bool = True,
         **kwargs,
     ) -> BatchFeature:
         """
@@ -204,8 +203,6 @@ def __call__(
 
                 Whether or not to return the number of frames of the input raw_speech.
                 These num_frames can be used by the model to compute word level timestamps.
-            center (`bool`, *optional*, defaults to `True`):
-                Whether to use centering for the STFT computation.
         """
         if sampling_rate is not None:
             if sampling_rate != self.sampling_rate:
@@ -263,7 +260,7 @@ def __call__(
             return_tensors="pt",
         )
         input_features = padded_inputs.input_features.squeeze(-1)
-        input_features = self._torch_extract_fbank_features(input_features, device, center)
+        input_features = self._torch_extract_fbank_features(input_features, device)
         data = {
             "input_features": input_features.to(torch.float32),
         }

filename: src/transformers/models/voxtral_realtime/feature_extraction_voxtral_realtime.py
patch: @@ -1,9 +1,3 @@
-#                🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨
-#           This file was automatically generated from src/transformers/models/voxtral_realtime/modular_voxtral_realtime.py.
-#               Do NOT edit this file manually as any edits will be overwritten by the generation of
-#             the file from the modular. If any change should be done, please apply the change to the
-#                          modular_voxtral_realtime.py file directly. One of our CI enforces this.
-#                🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨
 # Copyright 2025 The HuggingFace Inc. team. All rights reserved.
 #
 # Licensed under the Apache License, Version 2.0 (the "License");

filename: src/transformers/models/voxtral_realtime/modular_voxtral_realtime.py
patch: @@ -21,14 +21,12 @@
 
 from ... import initialization as init
 from ...activations import ACT2FN
-from ...audio_utils import mel_filter_bank
 from ...cache_utils import Cache, DynamicCache, StaticCache
 from ...generation import GenerationMixin
 from ...masking_utils import create_causal_mask, create_sliding_window_causal_mask
 from ...modeling_layers import GradientCheckpointingLayer
 from ...modeling_outputs import BaseModelOutputWithPast, BaseModelOutputWithPooling, CausalLMOutputWithPast
 from ...modeling_utils import PreTrainedModel
-from ...models.lasr.feature_extraction_lasr import LasrFeatureExtractor
 from ...models.llama.modeling_llama import LlamaRotaryEmbedding
 from ...models.mistral.modeling_mistral import (
     MistralAttention,
@@ -52,63 +50,6 @@
 logger = logging.get_logger(__name__)
 
 
-class VoxtralRealtimeFeatureExtractor(LasrFeatureExtractor):
-    def __init__(
-        self,
-        feature_size=128,
-        sampling_rate=16000,
-        hop_length=160,
-        n_fft=400,
-        win_length=400,
-        padding_value=0.0,
-        global_log_mel_max=1.5,
-        **kwargs,
-    ):
-        super().__init__(
-            feature_size=feature_size,
-            sampling_rate=sampling_rate,
-            hop_length=hop_length,
-            n_fft=n_fft,
-            win_length=win_length,
-            padding_value=padding_value,
-            **kwargs,
-        )
-        self.mel_filters = mel_filter_bank(
-            num_frequency_bins=1 + n_fft // 2,
-            num_mel_filters=feature_size,
-            min_frequency=0.0,
-            max_frequency=8000.0,
-            sampling_rate=sampling_rate,
-            norm="slaney",
-            mel_scale="slaney",
-        )
-        self.global_log_mel_max = global_log_mel_max
-
-    def _torch_extract_fbank_features(self, waveform, device: str = "cpu", center: bool = True):
-        window = torch.hann_window(self.n_fft, device=device)
-        stft = torch.stft(waveform, self.n_fft, self.hop_length, window=window, return_complex=True, center=center)
-        magnitudes = stft[..., :-1].abs() ** 2
-
-        mel_filters = torch.from_numpy(self.mel_filters).to(device, torch.float32)
-        mel_spec = mel_filters.T @ magnitudes
-
-        log_spec = torch.clamp(mel_spec, min=1e-10).log10()
-        if self.global_log_mel_max is not None:
-            log_spec_max = torch.tensor(
-                self.global_log_mel_max,
-                device=log_spec.device,
-                dtype=log_spec.dtype,
-            )
-        else:
-            log_spec_max = log_spec.max()
-
-        log_spec = torch.maximum(log_spec, log_spec_max - 8.0)
-        log_spec = (log_spec + 4.0) / 4.0
-        if device != "cpu":
-            log_spec = log_spec.detach().cpu()
-        return log_spec
-
-
 class VoxtralRealtimeConv1dCacheLayer:
     def __init__(self):
         self.cache: torch.Tensor | None = None
@@ -946,6 +887,5 @@ def _prepare_generated_length(
 __all__ = [
     "VoxtralRealtimeForConditionalGeneration",
     "VoxtralRealtimeEncoder",
-    "VoxtralRealtimeFeatureExtractor",
     "VoxtralRealtimePreTrainedModel",
 ]