Skip to content

Studio: pick the quant of a GGUF dictation model in Voice settings - #12900

Merged
mahiatlinux merged 10 commits into
unslothai:mainfrom
Etherll:feat/studio-audio-stt-settings-quant
Oct 7, 2026
Merged

mahiatlinux merged 10 commits into
unslothai:mainfrom
Etherll:feat/studio-audio-stt-settings-quant

Conversation

@Etherll

@Etherll Etherll commented Oct 6, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

A package-folder dictation model such as GigaAM ships several quants, but Settings > Voice could only run the folder's default one. Voice settings now has a Quantization select under the model picker when the selected folder has more than one quant. It lists each quant with its size and an on-device dot, and picking one unloads the current model and loads the new quant.

Changes

  • The voice store saves sttGgufVariant next to sttModel. It is cleared when the model changes (including the fallback to the default model on a language switch) and only rehydrated when the saved model is still the selected one.
  • sttModelVariant() and withSttVariant() in stt-model-catalog.ts apply the quant only to package-folder rows. Saved keys and the other engines ignore it.
  • The saved quant is passed on every Settings dictation path: the Voice tab load and download, the post-download auto-load, raw transcription (/transcribe/raw gets row:variant, so a cold sidecar does not fall back to the default quant), chat audio upload (pinned in the upload snapshot), the Audio page's reference transcription, and the download prompt. The download prompt now shows the quant's size, read from the cached GGUF listing.
  • The Voice tab treats a row as ready only when the resident quant matches the pinned one, and reports a pinned quant as missing when a different quant of the same row is on disk. The GGUF listing cache is invalidated when a folder download finishes, so the new quant shows as downloaded.
  • Backend: resolve_audio_cpp_stt_model raises SttModelNotDownloadedError instead of SttModelIdError when, offline, a picked quant is not on disk but its row is a runnable ASR model. Callers then show the download prompt instead of an unknown-variant error. A folder for another task (for example Kokoro with a quant) is still rejected as a wrong model.
  • New strings sttQuantLabel and sttQuantDescription in all 14 locales.

Testing

  • npm test: 10756 passed. New cases in tests/audio-cpp-catalog.test.ts cover which rows take a saved quant, clearing and rehydrating the quant with its model, the quant reaching every Settings dictation path, and when the Select appears.
  • npm run typecheck, strict i18n:check, catalog:check and npm run build pass.
  • Backend: 420 passed, 1 skipped across test_audio_cpp_models, test_stt_audiocpp_details, test_audio_cpp_umbrella_catalog, test_openai_audio_transcriptions_route, test_stt_sidecar and test_stt_transcription_cancellation. test_audio_cpp_models adds the offline not-downloaded quant case and the wrong-task case.
  • Live POST /api/inference/audio/transcribe/raw with GigaAM, on CPU and on an idle GPU: :v3-ctc/F16 loads that quant (loaded_variant matches), the bare row keeps the resident quant, :multilingual-ctc/F16 switches and returns the LibriSpeech transcript, and a quant that is not on disk returns 409 not downloaded (422 before the backend change).
  • Playwright on the head and base builds of Settings > Voice: the Select shows only for GigaAM (8 quants), not for Citrinet (1 quant) or a saved Moonshine key. Picking a quant that is not on disk shows Download, the real download loads that quant, and the pick survives a reload. With a pinned quant missing, the chat mic raises the download prompt with that quant's size.

Settings > Voice offered 6 of the 18 audio.cpp ASR folders the Transcribe
page runs, and a saved folder id was reset to the default on reload. The
picker now derives its GGUF rows from the same catalog Transcribe uses,
keeps the existing keys, adds a Moonshine medium key, accepts folder ids,
reads folder sizes from the cached variant listing, marks downloaded
models, finds listed models by name in search, and adds Danish for Hviske.
A package-folder dictation model such as GigaAM ships several quants,
but Settings > Voice could only run the folder's default one. The voice
store now remembers the picked quant beside the model, like Transcribe
does, and a Select under the model picker offers the folder's quants
with sizes and an on-device dot. The quant travels with every Settings
dictation path: load, download, raw transcription, chat audio upload,
the Audio page's reference transcription and the download prompt, which
now shows the quant's size. Offline, a picked quant that is not on disk
is reported as not downloaded instead of an unknown variant, so the
download prompt appears rather than an error.
@danielhanchen

Copy link
Copy Markdown
Member

The red Frontend unit tests (Windows) is not from this PR: reasoning-source-render.test.ts cold-starts Vite in a child process under a 30 s cap and timed out at 30.2 s on the loaded Windows runner. The fix is in #12903; a re-run should pass.

…Select

A stopped or failed download of a second quant read as complete, because
a row counts as downloaded once any quant is cached; the mirror now
checks cancel and error first. The upload dialog said Ready for a pinned
quant that was not on disk, and the download prompt took the quant from
live settings instead of the request; both now use the pinned quant.
Unpinned, the quant Select shows what a bare load runs: the resident
quant (mapping the cache's loose key), else the first cached one.
…dio-audio-stt-settings-quant

# Conflicts:
#	studio/backend/core/inference/audio_cpp_models.py
#	studio/backend/tests/test_audio_cpp_umbrella_catalog.py
#	studio/frontend/src/features/settings/stores/stt-model-catalog.ts
#	studio/frontend/src/features/settings/tabs/voice-tab.tsx
#	studio/frontend/tests/audio-cpp-catalog.test.ts
Without a browser speech service, Studio switched dictation to local as
soon as any quant of the model was on disk, so the next recording failed
on a pinned quant that was not. The switch now checks that quant first,
through the same helper upload readiness uses. A download's warm-up also
remembers which quant it fetched, so picking another quant meanwhile no
longer loads one that is not on disk.
…d quants

With no quant pinned, the quant Select showed the resident quant as its
value, so picking that same quant changed nothing and an idle unload
later fell back to another one. The Select now holds the pin and shows
the running quant as its placeholder, so any pick is saved. A quant the
listing leaves out (an offline, cache-only listing) now counts as not
downloaded, and a download adopted without a known quant only warms an
unpinned row.
An offline listing keys cached files by what tells them apart, such as
ctc/F16 for v3-ctc/F16, so treating a key it leaves out as not
downloaded showed a cached GigaAM quant as missing and blocked offline
dictation. A listing now marks the pinned quant missing only through its
own row; anything else defers to the row status, and the backend's
not-downloaded reply still raises the download prompt.
With another quant of the row cached, a failed download of the pinned
quant read as not downloaded, with a fresh Download button, hiding the
error. It now shows the same failed state and Retry a row gets.
…ll picked

The Voice tab loads the model when a download it watched finishes, but it
loaded the quant picked now, so switching quant during a download tried
to load one that was not on disk. It now remembers the quant it started
and loads only that one; a download it adopted loads only when no quant
is pinned.
@Etherll

Etherll commented Oct 7, 2026

Copy link
Copy Markdown
Collaborator Author

Settings > Voice now has a quantization choice for audio.cpp package-folder dictation models with several quants, such as GigaAM (eight). The pick is saved with the model and used by dictation, recorded-audio uploads, the Audio page's reference transcription and the download prompt, which shows that quant's size. Models with one quant and saved dictation keys show no extra control.

What changed

  • A cancelled or failed download of a second quant used to read as finished, then load the missing quant. It now ends as cancelled or failed, and a finished download loads only if that quant is still picked.
  • The recording-upload dialog and the automatic switch to local dictation counted a model as ready if any quant was on disk. Both now check the picked quant and offer its download, which fetches the quant requested, not whatever is selected later.
  • With no quant picked, the selector showed the default even when a different cached quant would run, and picking the quant on display did nothing. It now shows the quant that loads, and picking it saves the choice.
  • Offline, requesting a quant not on disk returned a 422 "bad model" error. It now returns 409 "not downloaded", so the download prompt appears instead of an error.

At 13a439d70, frontend tests (10760), typecheck, strict i18n, the build and the backend audio/STT tests (420 passed, 1 skipped) pass. Live on CPU, and earlier on a GPU with the same backend code, /api/inference/audio/transcribe/raw with a row:quant id loads that exact quant, and a quant not on disk returns 409 "not downloaded". In Chromium, picking a quant not on disk, downloading it and reloading keeps and loads that quant. Upstream CI was still running at the time of this comment.

Settings > Voice quantization picker, before and after

Before: e7cc9d5e4 (main) · After: 13a439d70 · Settings > Voice with GigaAM-ASR-GGUF, CPU.

@mahiatlinux

Copy link
Copy Markdown
Contributor

Verification

Reviewed and repaired source head: 6a1a3dd56e506a9b3e5fd8b2283074290c49f986

Required mirror convergence completed on mirror PR #119 at 97aa7403172fabacf3a8a7dda0a1f7b6de38d428, with no findings. The landed source and reviewed mirror have the identical tree 4d9b22a8e947d370ae2b4d49a6628fdd9a99aeaf.

  • Frontend full suite: 10,760 passed, 0 failed.
  • Final delta focused suite: audio-cpp-catalog.test.ts, 32 passed, 0 failed.
  • TypeScript source and test type-checks: passed.
  • Strict locale parity: all 14 overlays passed.
  • Model catalog validation and production build: passed.
  • Comment cleanup gate: Python AST and TypeScript printer output unchanged across all 11 cleanup files.
  • Backend focused audio.cpp model test: passed.
  • Backend target: 114 passed, 1 baseline-identical environment failure (test_one_failed_transcription_does_not_hide_the_engine, caused by the isolated host lacking the runtime binary). The same test failed on the merge base, original head, and repaired tree.
  • Negative control: the merge base raises SttModelIdError for an absent selected quant; the PR correctly raises SttModelNotDownloadedError.

UI evidence

Ubuntu 26.04.1 x86_64, headless Chromium 153.0.8010.12, 1440x900. Both sides used isolated Studio installs and isolated caches inside a separate process namespace. The settings endpoints were deterministically mocked; no real user session or desktop browser was used.

The pair compares merge base e7cc9d5e4 with reviewed code head b053172882. Later source commits only affect failed-download and post-download loading branches outside this static settings scene, and the final landed cleanup is executable-code-identical.

Observed: the PR adds the Quantization selector, keeps v3-ctc/F16 selected, lists all three quant options with sizes, and shows the pinned quant as not downloaded. BEFORE has no quant control and retains the ordinary Load state.

Voice settings quantization before and after

@mahiatlinux mahiatlinux 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.

LGTM! Ready to merge, all manually checked.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants