Skip to content

Gate the native Whisper speech_to_text serving end-to-end - #186

Merged
ausimian merged 3 commits into
feat/expr-compilerfrom
feat/expr-compiler-whisper-native-gate
Jun 6, 2026
Merged

Gate the native Whisper speech_to_text serving end-to-end#186
ausimian merged 3 commits into
feat/expr-compilerfrom
feat/expr-compiler-whisper-native-gate

Conversation

@ausimian

@ausimian ausimian commented Jun 6, 2026

Copy link
Copy Markdown
Owner

Locks in this session's milestone: the full Whisper speech_to_text
serving compiles fully native
(featurizer STFT + encoder/decoder + decode
loop). Without a gate, the FFT / indexed_put / tuple-cond lowerings could
silently regress and re-introduce fallbacks — and the existing Whisper tests
wouldn't catch it.

Why the existing tests don't cover this

whisper_test.exs / whisper_full_test.exs feed pre-computed mel features
through a single Axon.predict forward. They never run the featurizer (so
:fft was untested) or the generation decode loop (where the tuple cond
lived). generation_native_test.exs covers text generation, not Whisper. So
all three of this session's gaps sat in an untested seam.

The gate

A :whisper_full case runs the real openai/whisper-tiny speech_to_text
serving on ~1 s of synthetic audio under
compiler: Emily.Compiler, native: true, native_fallback: :raise. :raise
makes it a no-fallback gate over the whole graph — the featurizer's fft,
the encoder/decoder forward, and the autoregressive decode loop (the
multi-output cond, indexed_put cache writes, dynamic slices). An
un-lowerable op raises instead of degrading. Reuses the checkpoint
whisper_full_test already downloads — no new fetch.

The transcription itself isn't pinned (synthetic audio → arbitrary tokens, and
greedy decode can wobble); reaching the end under :raise is the assertion.

Verified locally (cached checkpoint): 1 test, 0 failures, 2.6 s.

Why tiny-random can't host this

I tried — tiny-random Whisper's encoder maxes at 30 source positions, but the
featurizer pads to Whisper's 30 s window (1500 positions), and fractional
num_seconds breaks the integer pad. So the end-to-end serving needs a
real-size model; hence :whisper_full (opt-in), reusing the existing download.

Also here

The RELEASE.md "Fixed" note for the tuple-cond crash (PR #185 didn't carry
one) + the "Whisper now fully native end-to-end" milestone line.


Top of the stack: …-scatter (#184) → …-cond-tuple (#185) → this. Base
retargets to feat/expr-compiler as the lower PRs merge.

ausimian added 3 commits June 6, 2026 16:33
Nx.indexed_put and Nx.indexed_add (scatter) now lower under
`compiler: Emily.Compiler, native: true` for MLX-scatter-compatible index
layouts, instead of forcing a graceful fallback to the evaluator. The MLX
ops and eager NIFs already existed (Native.{scatter,scatter_add} ->
mx::{scatter,scatter_add}); only the compiler path was missing.

- opcodes.hpp: add Scatter/ScatterAdd (92-93), bump kOpcodeCount to 94,
  dispatch to mx::scatter (overwrite) / mx::scatter_add (accumulate).
  operands [target, updates, idx0, ...]; iattrs [[axes...]].
- ir.ex: add the opcodes; lower :indexed_put/:indexed_add (one shared
  clause), mirroring Emily.Backend's apply_scatter — reuse the existing
  gather index-split + scatter_gather_compatible? helpers, port
  updates_shape_for_scatter. Incompatible index layouts raise (no
  fallback), matching the native gather; the evaluator handles them under
  native_fallback: :eval.
- compiler_equivalence_test.exs: native-vs-evaluator bit-identical cases
  for indexed_put/indexed_add (2-D grid, duplicate-index accumulation,
  partial-axis whole-row writes).

Another op surfaced while running the native compiler on the Whisper
livebook.
A `cond`/`if` whose branches return a tuple crashed the native compiler
with a FunctionClauseError: the :cond lowering passed the tuple branch
straight to lower_node/2, which only matches a single tensor. Worse, a
FunctionClauseError escapes the compiler's graceful-fallback rescue (which
only traps ArgumentError), so it hard-faulted instead of degrading to the
evaluator. Surfaced by a Whisper encoder forward under native: true
(transpose(elem(cond -> {f32[1,1500,6,64] x N}, i))).

Lower a tuple cond to one where-chain per leaf position — identical
wholesale-select semantics to the single-output cond (the predicate is a
whole-tensor scalar bool; every branch is still computed) — and return a
{:multi_refs, [...]} handle that :elem projects, shared across sibling
:elems via lower_node's memo (like while). A nested / non-tensor container
raises a clean ArgumentError, so it falls back gracefully rather than
crashing.

- ir.ex: split the :cond clause (single-tensor head unchanged; new
  is_tuple head for the multi-output case); extend :elem to project
  {:multi_refs, refs}; add the tensors?/1 guard.
- compiler_control_flow_test.exs: tuple if + multi-clause tuple cond with
  mixed-shape (vector + scalar) leaves, native-vs-evaluator bit-identical.
Add a :whisper_full case that runs the real openai/whisper-tiny
speech_to_text serving on synthetic audio under native_fallback: :raise
— a no-fallback gate over the WHOLE graph: the mel featurizer's STFT
(fft), the encoder/decoder forward, and the autoregressive decode loop
(the multi-output cond, indexed_put cache writes, dynamic slices). An
un-lowerable op raises rather than silently degrading.

The existing whisper forward mode_test never reaches these — it feeds
pre-computed mel features through a single Axon.predict, exercising
neither the featurizer nor the generation loop. That blind spot is
exactly where this session's fft / indexed_put / tuple-cond gaps lived;
this locks the composed path so it can't silently regress. Reuses the
checkpoint whisper_full_test already downloads (no new fetch).

Also adds the RELEASE.md "Fixed" note for the tuple-cond crash and the
"Whisper now fully native end-to-end" milestone.
@ausimian
ausimian changed the base branch from feat/expr-compiler-cond-tuple to feat/expr-compiler June 6, 2026 07:04
@ausimian ausimian closed this Jun 6, 2026
@ausimian ausimian reopened this Jun 6, 2026
@ausimian
ausimian merged commit bd35cac into feat/expr-compiler Jun 6, 2026
3 checks passed
@ausimian
ausimian deleted the feat/expr-compiler-whisper-native-gate branch June 6, 2026 07:08
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