Skip to content

CPU can incur a slow path on non-contiguous magnitudes - #47351

Merged
vasqu merged 7 commits into
huggingface:mainfrom
vbayanag:whisper-mel-contiguous-cpu-speedup
Jul 22, 2026
Merged

CPU can incur a slow path on non-contiguous magnitudes#47351
vasqu merged 7 commits into
huggingface:mainfrom
vbayanag:whisper-mel-contiguous-cpu-speedup

Conversation

@vbayanag

@vbayanag vbayanag commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

CI

What does this PR do?

stft[..., :-1] produces a non-contiguous view. Asobserved on MI355X ROCm vLLM latest build image, the audio preprocessing function mel_filters.T @ magnitudes falls onto a strided-GEMM path that runs ~8x slower (~43 ms vs ~3 ms for a 30 s audio window), dominating Whisper feature extraction.

Forcing magnitudes contiguous costs ~0.18 ms but restores the fast matmul path, cutting per-request audio preprocessing substantially.

Fixes # (issue)

Make the CPU op run on contiguous memory layout for ~8x speedup

Before/after (single 30 s window, ROCm vLLM image)

stage non-contiguous contiguous
mel_filters.T @ magnitudes ~43 ms ~3 ms
.contiguous() overhead ~0.18 ms

Benchmark Code

import torch
import time 
import numpy as np
from transformers import WhisperFeatureExtractor

torch.manual_seed(0)
feature_extractor = WhisperFeatureExtractor()
n_fft = feature_extractor.n_fft
hop_length = feature_extractor.hop_length
# Mirror `_torch_extract_fbank_features`: the window is built from n_fft.
window = torch.hann_window(n_fft)
# One full Whisper window of audio (chunk_length * sampling_rate samples).
waveform = torch.randn(feature_extractor.n_samples, dtype=torch.float32)

stft = torch.stft(waveform, n_fft, hop_length, window=window, return_complex=True)
magnitudes_non_contiguous = stft[..., :-1].abs() ** 2
magnitudes_contiguous = magnitudes_non_contiguous.contiguous()

mel_filters = torch.from_numpy(feature_extractor.mel_filters).to(torch.float32)

def _median_ms(magnitudes, iters=20, warmup=5):
    for _ in range(warmup):
        mel_filters.T @ magnitudes
    timings = []
    for _ in range(iters):
        start = time.perf_counter()
        mel_filters.T @ magnitudes
        timings.append((time.perf_counter() - start) * 1e3)
    timings.sort()
    return np.mean(timings) #timings[len(timings) // 2]

with torch.inference_mode():
    non_contiguous_ms = _median_ms(magnitudes_non_contiguous)
    contiguous_ms = _median_ms(magnitudes_contiguous)

print(
    f"\n[whisper mel matmul] non-contiguous={non_contiguous_ms:.3f} ms "
    f"contiguous={contiguous_ms:.3f} ms "
    f"speedup={non_contiguous_ms / contiguous_ms:.2f}x"
)

Code Agent Policy

The Transformers repo is currently being overwhelmed by a large number of PRs and issue comments written by
code agents. These often are low-quality, or fix extremely minor issues that occur rarely or never in practice.
As a result, we're instituting a rule that first-time contributors should not use code agents to submit PRs or issues.
We'd also ask autonomous "OpenClaw"-like agents not to open any PRs or issues.

Issues/PRs from first-time contributors that violate this rule will probably just be closed without review, and we
might block you, especially if you open more than one or appear to be deliberately ignoring this. We especially do not
want new contributors to jump in on random issues to contribute an agent-written fix. This creates lots of noise
for reviewers and other users and will almost certainly get you blocked.

For more information, please read CONTRIBUTING.md.

  • (First-time contributors only): I confirm that this PR description and code is not written by an LLM or code agent

Before submitting

  • This PR fixes a typo or improves the docs (you can dismiss the other checks if that's the case).
  • Did you read the contributor guideline and the
    Pull Request checks?
  • Was this discussed/approved via a Github issue or the forum? Please add a link
    to it if that's the case.
  • Did you make sure to update the documentation with your changes according to the guidelines?
  • Did you write any new necessary tests?

Who can review?

@eustlb @ebezzam @vasqu

`stft[..., :-1]` produces a non-contiguous view, and squaring its
magnitude keeps it non-contiguous. On some CPU backends (observed on a
ROCm PyTorch CPU build) the subsequent `mel_filters.T @ magnitudes`
matmul falls onto a pathological strided-GEMM path that runs ~8x slower
(~43 ms vs ~3 ms for a 30 s audio window), dominating Whisper feature
extraction.

Forcing `magnitudes` contiguous costs ~0.18 ms but restores the fast
matmul path, cutting per-request audio preprocessing substantially with
no change in output.

Signed-off-by: Varalakshmi Bayanagari <varalakshmi.bayanagari@amd.com>
@vbayanag vbayanag changed the title Make Whisper mel magnitudes contiguous to avoid slow strided matmul Make Whisper mel magnitudes contiguous to avoid slow strided matmul on ROCm Jul 15, 2026

@vasqu vasqu left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Happy to add the fix on 2 conditions

  1. Shorten the comment --> cpu can incurr a slow path on non-contiguous magnitutes
  2. Add a fast test that can show this or a benchmark with this PR so we can verify as well

@vbayanag vbayanag changed the title Make Whisper mel magnitudes contiguous to avoid slow strided matmul on ROCm CPU can incur a slow path on non-contiguous magnitudes Jul 16, 2026
@vbayanag

Copy link
Copy Markdown
Contributor Author

Happy to add the fix on 2 conditions

  1. Shorten the comment --> cpu can incurr a slow path on non-contiguous magnitutes
  2. Add a fast test that can show this or a benchmark with this PR so we can verify as well

Hi @vasqu, the posted benchmarks in the description are from before/after change. Kindly let me know if you need anymore information.

@github-actions

Copy link
Copy Markdown
Contributor

Thank you for your contribution 🤗!

CI Security Gate — automatic approval blocked

This PR was not automatically approved for CI because the security gate failed.

Possible reasons:

  • The PR touches 50 or more files — only PRs with fewer than 50 changed files are automatically approved
  • A changed file is outside the allowed directories (src/, tests/, docs/, utils/), has a disallowed extension (only .py, .txt, .md permitted outside tests/ and docs/), or is not .md/.yml inside docs/
  • A new high-severity security issue was detected in the changed Python files (Bandit check)

See the workflow run for the exact violations.

A maintainer can review and manually approve CI if a finding is a false positive.

@vbayanag
vbayanag requested a review from vasqu July 16, 2026 21:04
@vasqu

vasqu commented Jul 17, 2026

Copy link
Copy Markdown
Collaborator

@vbayanag I meant like a small benchmark script :D the numbers are impressive but imo for anyone stumbling on this, it's always nice to have the exact code that produced the numbers along with it

@vbayanag

Copy link
Copy Markdown
Contributor Author

@vbayanag I meant like a small benchmark script :D the numbers are impressive but imo for anyone stumbling on this, it's always nice to have the exact code that produced the numbers along with it

Hi @vasqu, I added a test case in the relevant test location. Please check.

Local testing shows 5x uplift. This number is different from what's in description, likely due to difference in the way metrics were extracted. The initial posted numbers were extracted from feature_extractor API call from the trace file of end-to-end inference testing of whisper model, whereas the uplift observed now is localised only to the matmul operation.

[whisper mel matmul] non-contiguous=10.269 ms contiguous=1.999 ms speedup=5.14x

System details:

  1. Docker pull and launch vllm/vllm-openai-rocm:latest image
docker run -it  --group-add=video    --ipc=host    --cap-add=SYS_PTRACE    --security-opt seccomp=unconfined    --device /dev/kfd    --device /dev/dri -v /:/data --entrypoint ''  vllm/vllm-openai-rocm:latest /bin/bash
  1. Install depenencies (optional, only required for other test cases to run smoothly)
apt-get update && apt-get install -y ffmpeg
pip install soundfile librosa torchcodec==0.12
  1. Run the python unit test inside your clone dir
PYTHONPATH=src python3 -m pytest     tests/models/whisper/test_feature_extraction_whisper.py -k contiguous_magnitudes -sv

self.assertTrue(pt_processed.input_features.dtype == torch.float32)

@require_torch
def test_torch_extract_fbank_features_contiguous_magnitudes(self):

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Ok sorry for backtracking a bit but I'd rather have the benchmark within the PR description. A benchmark as test is probably brittle, wdyt?

My goal is just to have documentation that shows why it was done and for ppl to repro

@vbayanag vbayanag Jul 20, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Hi @vasqu, I think that this small test case would be evidence of the change I am submitting. By having a test case that's part of the code, it stays persistent and can be removed upon future rebuttals.

If you still think benchmark is unnecessary, I can remove that part from the test case and leave the rest alone. I can post the benchmark script on description. Kindly let me know what you think

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I can post the benchmark script on description.

Yea I think this is the better option tbh. It doesn't really make much sense as standalone test. We should refer to the benchmark script in some shape or form, e.g. a PR reference (that has it in the description) or a gh gist.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done.

  1. I removed benchmark code from test case and added in the description.
  2. The test case now only verifies the validity of the matrix when changed to contiguous.

Removed median timing measurement for mel_filters matrix multiplication.

@vasqu vasqu left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks, one small nit and can you run make style for CI?

Correctness (and the non-contiguity of the raw view) is asserted so this
stays stable in CI; the per-call timings are printed for reference
(run with `-s`) but not asserted, since the size of the speedup is
backend-dependent.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Let's also add a reference to this PR in the description please

@vbayanag

Copy link
Copy Markdown
Contributor Author

Thanks, one small nit and can you run make style for CI?

How to do that?

@vasqu

vasqu commented Jul 21, 2026

Copy link
Copy Markdown
Collaborator

You just have to run make style on the root of the folder

Signed-off-by: vara lakshmi bayanagari <varalakshmi.bayanagari@amd.com>
@vbayanag

Copy link
Copy Markdown
Contributor Author

You just have to run make style on the root of the folder

Done

@vbayanag
vbayanag requested a review from vasqu July 22, 2026 15:47
Signed-off-by: vara lakshmi bayanagari <varalakshmi.bayanagari@amd.com>
@github-actions

Copy link
Copy Markdown
Contributor

[For maintainers] Suggested jobs to run (before merge)

run-slow: whisper

@github-actions

Copy link
Copy Markdown
Contributor

CI recap

Dashboard: View test results in Grafana
Latest run: 29865079473:2
Result: success | Jobs: 6 | Tests: 981 | Failures: 0 | Duration: 5m 14s

@vasqu
vasqu enabled auto-merge July 22, 2026 16:12
@vasqu

vasqu commented Jul 22, 2026

Copy link
Copy Markdown
Collaborator

Merging it, thanks for your patience!

@vasqu
vasqu added this pull request to the merge queue Jul 22, 2026
@HuggingFaceDocBuilderDev

Copy link
Copy Markdown

The docs for this PR live here. All of your documentation changes will be reflected on that endpoint. The docs are available until 30 days after the last update.

Merged via the queue into huggingface:main with commit bb3ffb9 Jul 22, 2026
39 checks passed
stevhliu pushed a commit to stevhliu/transformers that referenced this pull request Jul 30, 2026
…7351)

* Make Whisper mel magnitudes contiguous to avoid slow strided matmul

`stft[..., :-1]` produces a non-contiguous view, and squaring its
magnitude keeps it non-contiguous. On some CPU backends (observed on a
ROCm PyTorch CPU build) the subsequent `mel_filters.T @ magnitudes`
matmul falls onto a pathological strided-GEMM path that runs ~8x slower
(~43 ms vs ~3 ms for a 30 s audio window), dominating Whisper feature
extraction.

Forcing `magnitudes` contiguous costs ~0.18 ms but restores the fast
matmul path, cutting per-request audio preprocessing substantially with
no change in output.

Signed-off-by: Varalakshmi Bayanagari <varalakshmi.bayanagari@amd.com>

* Added test case to assert and benchmark

* enabled warmup in test case

* Remove median timing function from tests

Removed median timing measurement for mel_filters matrix multiplication.

* Apply make style (ruff)

Signed-off-by: vara lakshmi bayanagari <varalakshmi.bayanagari@amd.com>

* Reference PR huggingface#47351 in test docstring; drop stale -s note

Signed-off-by: vara lakshmi bayanagari <varalakshmi.bayanagari@amd.com>

---------

Signed-off-by: Varalakshmi Bayanagari <varalakshmi.bayanagari@amd.com>
Signed-off-by: vara lakshmi bayanagari <varalakshmi.bayanagari@amd.com>
Sainava pushed a commit to Sainava/Sai-transformers that referenced this pull request Aug 3, 2026
…7351)

* Make Whisper mel magnitudes contiguous to avoid slow strided matmul

`stft[..., :-1]` produces a non-contiguous view, and squaring its
magnitude keeps it non-contiguous. On some CPU backends (observed on a
ROCm PyTorch CPU build) the subsequent `mel_filters.T @ magnitudes`
matmul falls onto a pathological strided-GEMM path that runs ~8x slower
(~43 ms vs ~3 ms for a 30 s audio window), dominating Whisper feature
extraction.

Forcing `magnitudes` contiguous costs ~0.18 ms but restores the fast
matmul path, cutting per-request audio preprocessing substantially with
no change in output.

Signed-off-by: Varalakshmi Bayanagari <varalakshmi.bayanagari@amd.com>

* Added test case to assert and benchmark

* enabled warmup in test case

* Remove median timing function from tests

Removed median timing measurement for mel_filters matrix multiplication.

* Apply make style (ruff)

Signed-off-by: vara lakshmi bayanagari <varalakshmi.bayanagari@amd.com>

* Reference PR huggingface#47351 in test docstring; drop stale -s note

Signed-off-by: vara lakshmi bayanagari <varalakshmi.bayanagari@amd.com>

---------

Signed-off-by: Varalakshmi Bayanagari <varalakshmi.bayanagari@amd.com>
Signed-off-by: vara lakshmi bayanagari <varalakshmi.bayanagari@amd.com>
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.

3 participants