Repository
nemotron-omni-mlx
Run NVIDIA's tri-modal Nemotron Omni (text+vision+audio) entirely on Apple Silicon — pure MLX runtime, parity-verified against NVIDIA's reference
- Component
- Compared against
- Audio tower
- transformers ParakeetEncoder + NVIDIA SoundProjection , CPU fp32
- Audio frontend
- log-mel features
- Vision tower
- nvidia/C-RADIOv4-H via trust_remote_code , CPU fp32
Specs
- ComponentCompared against
- Audio towertransformers ParakeetEncoder + NVIDIA SoundProjection , CPU fp32
- Audio frontendlog-mel features
- Vision towernvidia/C-RADIOv4-H via trust_remote_code , CPU fp32
- Vision tower, MLX CPU streamsame
README
nemotron-omni-mlx
Run NVIDIA's tri-modal Nemotron Omni — text + vision + audio — entirely on an Apple Silicon Mac.
NVIDIA's Nemotron-3-Nano-Omni-30B-A3B
is an open-weights model that sees, hears, and reasons. A 4-bit MLX quantization already exists
(mlx-community, by yayr),
but as that model card notes, only the text backbone loads with standard MLX tooling:
The vision and audio towers require a multimodal runtime that implements the C-RADIO ViT-H and
Parakeet Conformer forward passes.
This repo is that runtime, in pure MLX. The vision tower, the audio tower, the processor, and the
token splicing — all ported from NVIDIA's reference implementation and verified numerically
against it, not by vibes.
Image: 67.7 tok/s · 22.1 GB peak
Audio: 147 tok/s · 21.0 GB peak
Text: 152 tok/s · 17.9 GB peak
(M5 Max MacBook Pro, 4-bit LLM + bf16 towers)
⚠️ On a 32 GB Mac, the image path may fail out of the box — macOS caps GPU memory at
~2/3 of RAM (~21.3 GB), just under the 22.1 GB image-path peak. Text and audio still work.
It's a one-line fix: see the 32 GB heads-up in Install before reporting a bug.
See it run
A screenshot of a real store cart, read by the model on a laptop with nothing in the cloud:
▶ Watch the full 35-second demo, with narration — or grab the mp4 directly.
The write-up on that page covers why this port exists, what the parity numbers mean, and the
three traps worth knowing before you attempt it yourself.
Parity — verified, not claimed
Every component is tested against NVIDIA's PyTorch reference on the same inputs with the same
weights. pytest tests/ — 23/23 passing.
| Component | Compared against | Result |
|---|---|---|
| Audio tower | transformers ParakeetEncoder + NVIDIA SoundProjection, CPU fp32 |
cos 0.99999130 (min/frame) |
| Audio frontend | log-mel features | max |Δ| 8.1e-6 |
| Vision tower | nvidia/C-RADIOv4-H via trust_remote_code, CPU fp32 |
cos 0.99996227 (min/token) |
| Vision tower, MLX CPU stream | same | 1.00000000 — graph-exact |
| Processor | NVIDIA's reference processor | token ids exact, pixels max |Δ| 1.55e-06 |
The GPU-vs-CPU gap on vision is Metal's lossy fp32 matmul (~7.7e-4 rel. vs a float64 ground
truth, where torch-CPU and MLX-CPU both sit at ~1.3e-6) — not a porting error. The
test_cpu_stream_is_graph_exact test locks that down as a regression guard.
Install
Requires an Apple Silicon Mac with ≥32 GB (the model needs ~22 GB at peak, measured on an
M5 Max 128 GB — the only machine it's been tested on).
A heads-up for 32 GB machines: macOS caps how much memory Metal will hand the GPU at
roughly 2/3 of physical RAM (recommendedMaxWorkingSetSize). On a 32 GB Mac that's about
21.3 GB — just under this model's ~22 GB image-path peak. So text and audio may work fine
while the image path fails, which looks like a model bug but is really the wired-memory
ceiling. You can check your own ceiling in seconds before downloading anything:
import Metal
let d = MTLCreateSystemDefaultDevice()!
print(Double(d.recommendedMaxWorkingSetSize) / 1_073_741_824.0)
Save that as check.swift, run swift check.swift, and it prints your ceiling in GB. If
it's under ~23, raise it before running the image path:
sudo sysctl iogpu.wired_limit_mb=24576
(Resets on reboot.) If you run this on a 32 GB machine, please open an issue with what that
snippet prints and what happened — real numbers from smaller hardware help everyone.
git clone https://github.com/nicedreamzapp/nemotron-omni-mlx
cd nemotron-omni-mlx
pip install -r requirements.txt
Then fetch the weights (~19 GB) and build the filtered text-backbone directory:
export HF_HUB_ENABLE_HF_TRANSFER=1 # ~30x faster downloads
python scripts/setup_weights.py # downloads + prepares model-4bit/ and text-only/
Use
# See
python -m src.omni --image photo.jpg --prompt "What's in this picture?"
# Hear
python -m src.omni --audio clip.wav --prompt "Transcribe this."
# Watch
python -m src.omni --video clip.mp4 --prompt "What happens here?"
# Both at once
python -m src.omni --image photo.jpg --audio clip.wav --prompt "Do these match?"
It's a reasoning model — it emits a <think> block before the answer. Give it room
(--max-tokens 900) or you'll get cut off mid-thought.
Notes for anyone porting this themselves
Three things cost real time. They're documented at length in PLAN.md, but the short version:
- The vision tower does not normalize its own input.
make_preprocessor_external()turns
input_conditionerinto an Identity, so its checkpoint weights are dead. The processor must
apply the CLIPnorm_mean/norm_std. Feed raw[0,1]pixels and you get plausible garbage,
silently. - Parts of
config.jsonare vestigial.use_thumbnail,force_image_size, and
image_tag_typeare ignored by the live code path (only dead code reads them) — it's one
dynamic-res tile, no thumbnail, not classic InternVL tiling. Trace the code, don't trust the
config. -infattention masking NaN-poisons padded audio batches — fully-masked softmax rows go
NaN and spread through k/v. NVIDIA's own reference does this: batch two clips of different
lengths and the shorter one comes back NaN. This port masks with-1e9instead (bit-identical
for valid rows, finite for padded ones).
Also: RADIO has enormous outlier activations (final features |max| ≈ 2370 vs median 2.5). Judge
correctness by cosine similarity, never absolute difference.
Credit where it's due
- NVIDIA built
the model and released the weights and the reference code openly. This port is a
transcription of their work into MLX; none of it happens otherwise. - yayr at
mlx-community did the 4-bit MLX quantization this runs on. Go like that upload. - Apple's MLX team, and whoever contributed
nemotron_hto mlx-lm — the text backbone needed
nothing from me because of them.
License
Runtime code: MIT (see LICENSE).
Model weights are NVIDIA's and carry the
NVIDIA Open Model License
— this repo ships no weights.
Built in Arcata, CA by Matt Macosko · Nice Dreamz LLC ·
more local-AI work