Skip to content

LFM3-Omni-2B on Metal and CUDA: speech in, streamed text and speech out (gate and split) #1627

Open

Description

@drowzeys

Proposal

Serve LiquidAI's LFM3-Omni-2B (speech in, streamed text and 24 kHz speech out) natively on Metal and CUDA as a standalone family, families/lfm3_omni, in the shape of Drama 3's standalone engine. Before opening pull requests, this asks to agree on the gate and the split.

The family doesn't depend on the LFM3 text series (#1508, #1513, #1514): one generic Liquid stack drives the model's four networks, all in bf16 as released:

  • the speech encoder (LFM2 layers) and its projector;
  • the backbone, with interleaved text and audio tokens;
  • the depthformer audio head;
  • the LiquidCodec decoder.

It's built as zig build lfm3-omni-generate. lfm3-omni-generate --serve HOST:PORT serves:

  • POST /v1/audio/chat, taking a base64 WAV. It streams server-sent events: text deltas, 16-bit 24 kHz PCM chunks, then a done event with timings. Without streaming, it returns the whole reply as one JSON.
  • GET /health.

It serves one request at a time.

Gate we propose

Parity against the checkpoint's own code (transformers with liquid-audio) on the same machine, with 16 synthetic utterances (generated voices only), greedy, stage by stage. The figures are Metal on an M5 Ultra against MPS bf16, then CUDA on a GB10 against CUDA bf16:

Stage Metal (M5 Ultra) CUDA (GB10)
Log-mel cos 1.000000, max abs 3.1e-4 cos 1.000000, max abs 2.8e-4
Encoder / projector, minimum cosine 0.99999 / 0.99990 0.99999 / 0.99990
Prompt token ids 16/16 identical 16/16 identical
Backbone prefill rows, median cosine 0.99982 0.99976
Text top-1, teacher-forced 520/524 (99.2%) 466/473 (98.5%)
Depthformer on the reference's own states, greedy agreement 95.5% 94.4%
Codes, teacher-forced through the whole loop 89.2% 89.7%
Codec on the reference's codes, waveform SNR median (lowest) 36.6 dB (25.1) 36.3 dB (24.1)
  • Precision: as in d1-2B (LFM3-VL decisions) on native CUDA: bf16 text decoder, LFM3-VL tower, set attention #1581, we propose "no further from an fp32 run than the reference's own bf16 is". On Metal against MPS fp32, the encoder and the prefill rows sit exactly where the reference's bf16 sits (0.999995 and a 0.99988 median).
  • Text and code misses are near-ties: every miss is within 2 bf16 ulps of the reference's pick. 15% of the reference's own greedy codebook picks are exact bf16 ties, so free-running codes diverge after a frame or two, and identical audio codes aren't reachable on either backend.
  • Quality instead of identical audio: UTMOS22 and word error between the reply's speech and its own text:
Metal Reference, Mac (MPS bf16) CUDA Reference, GB10
UTMOS22, median (lowest) 4.43 (4.35) 4.44 (4.15) 4.42 (3.91) 4.42 (4.24)
Speech vs its own text, WER 1.5% 1.2% 3.0% 1.7%
  • Streamed vs whole codec decode: 34–43 dB SNR, not bit-identical. The first one-frame chunk takes a different product kernel than the eight-frame chunks; using one product for every chunk size would make it exact, if you want that.
  • Kernel contract: every new Metal and CUDA kernel registered against a host replay of the reference's rounding points, with a planted control each. This isn't done yet.
  • Lean checker and host tests: zero problems added over the base; host tests for the family.

Latency

The same 16 turns through the server, with a streaming client, median:

Metal, M5 Ultra Reference, M5 Ultra (MPS) CUDA, GB10 Reference, GB10
First text 0.033 s n/a 0.036 s n/a
First audio 0.062 s 0.280 s 0.144–0.156 s 0.236 s
Whole reply (~6.5 s of speech) 0.64 s 2.71 s 2.0–2.2 s 4.14 s
  • Machines were shared: both had other GPU work running during these measurements, so we alternated runs and took two-pass medians. We'll redo them on an idle machine for the receipts.
  • Metal: prompt products run on the M5 tensor units. Earlier chips fall back to a simdgroup GEMM that is correct but slow (about 1.6 s of prefill) and untested on that hardware.
  • CUDA: the backbone step is bandwidth-bound (2.9 GB of bf16 weights per step). There are CUDA graphs for the backbone step and the speech frame, a few-row GEMV, and mel on the GPU.

Questions

  1. Split. Would you take two pull requests (Metal first, then CUDA), or three (the shared family with host tests, then each backend)?
  2. Server. Is a standalone tool with its own /v1/audio/chat fine for now, as with Drama 3? Or would you rather have it as a route of tensorfold-native serve (OpenAI input_audio in, audio out)?
  3. Exactness of the streamed codec. Should streamed chunks equal a whole decode bit for bit, at a small cost in first audio?
  4. Gate. Is the stage table above, plus UTMOS and WER on the replies, the right shape?

Work in progress

drowzeys/TensorFold branches lfm3-omni-cuda and lfm3-omni-metal. They currently sit on our d1-2B work (#1581); for pull requests we'll rebase the family alone onto main. Licence note: the weights are under the LFM Open License v1.0, which allows commercial use only for organisations under US$10M annual revenue. The recipes say so.

Activity

  1. drowzeys commented on Oct 30, 2026

    @drowzeys
    ContributorAuthor

    /atc claim

  2. ashhart commented on Oct 31, 2026

    @ashhart
    Owner

    Thanks @drowzeys for agreeing the gate and the split before the code, and for the stage-by-stage parity table.

    Yes, we'll take LFM3-Omni-2B as a standalone family, families/lfm3_omni, in the shape of Drama 3's engine: its own build step and its own --serve with POST /v1/audio/chat, one request at a time.

    The gate you propose stands, with these points:

    • Precision: the bar is the one from d1-2B (LFM3-VL decisions) on native CUDA: bf16 text decoder, LFM3-VL tower, set attention #1581, no further from an fp32 run than the reference's own bf16. Please keep the per-stage table in the PR for both backends, with the fp32 comparison beside it.
    • Text: every teacher-forced text miss listed with its margin, as you describe. Please check each is within 2 bf16 ulps.
    • Audio: identical codes aren't reachable when 15% of the reference's own greedy picks are exact bf16 ties, so judge it by quality: UTMOS22 and word error against the reference on the same utterances, plus the codec's SNR on the reference's codes. Please state the margins you'll hold to before the runs.
    • Kernels: any new kernel goes in the kernel contract with planted controls, on both backends.
    • Speech input: Nemotron 3.5 Transcribe (Nemotron 3.5 Transcribe: audio input, the 65,536-token window, an hour of audio, MTP drafts #1632) also adds audio input. Please share the log-mel front end with it where the two match, rather than keep two.

    Please split it as Metal first, then CUDA stacked on it, each with its own receipts. Nothing in a text family may change. A before/after token check on one text model in each PR makes that plain.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions