Skip to content

Commit 5a388a1

Browse files
authored
[examples] Enable Nemotron 3 Diarization on XNNPACK (pytorch#23152)
### Summary Add XNNPACK CPU export and native runner build targets for the Transformers-based Nemotron 3 Diarization example. The CPU path defaults to BF16, with `--dtype fp32` available explicitly, and reuses the existing streaming runner and XNNPACK preprocessor. MLX-specific code and linking are enabled only for MLX builds. Rebased onto main after pytorch#23153 landed. This PR contains only the six example/build files; the CPU kernel fixes and tests are already in main. Vulkan support is separate in pytorch#23155. ### Test plan Built the native CPU runner using `make nemotron3-diarization-cpu` on macOS arm64, with the CPU fixes from main. Verified the runner links XNNPACK without either the MLX or Vulkan delegate. Fresh exports pass for the default BF16 mode and explicit FP32 mode. All eight production CLI runs (two dtypes across offline, low, very_low, and ultra_low) retain the previous segments. All eight streaming integration runs pass partitioning, reset, short final-window, and input-validation checks. Also exercised 11 method shapes per dtype and compared speech, partial-tail, and silence inputs across all four presets against saved Transformers references. The 12 FP32 audio comparisons pass at `rtol=1e-4, atol=1e-4`, with maximum probability error 1.3709068298339844e-6. The 12 BF16 audio comparisons have maximum probability error 0.015625; four of 52,896 values exceed `rtol=1e-2, atol=5e-3`. Both dtypes have zero speaker-decision disagreements. BF16 is approximate and is not claimed to pass that tight numerical tolerance. Export CLI, CMake presets, `lintrunner`, and `git diff --check` pass. The rebase preserves the example patch unchanged and removes the CPU commits already merged through pytorch#23153. Supplemental evaluation scripts and artifacts remain local. Authored with AI assistance from OpenAI Codex. cc @SS-JIA @manuelcandales @digantdesai @cbilgin
1 parent bc40309 commit 5a388a1

6 files changed

Lines changed: 119 additions & 38 deletions

File tree

‎Makefile‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
# - whisper: Speech recognition model (CPU, CUDA, Metal)
2020
# - parakeet: Speech recognition model (CPU, CUDA, Metal, MLX)
2121
# - sortformer: Speaker diarization model (CPU, CUDA)
22-
# - nemotron3-diarization: Speaker diarization model (MLX)
22+
# - nemotron3-diarization: Speaker diarization model (MLX, XNNPACK)
2323
# - supertonic: Text-to-speech model (MLX)
2424
# - silero_vad: Voice activity detection model (CPU)
2525
# - llama: Text generation model (CPU)
@@ -123,6 +123,7 @@ help:
123123
@echo " sortformer-cuda - Build Sortformer runner with CUDA backend"
124124
@echo " sortformer-cpu - Build Sortformer runner with CPU backend"
125125
@echo " nemotron3-diarization-mlx - Build Nemotron 3 Diarization runner with MLX backend"
126+
@echo " nemotron3-diarization-cpu - Build Nemotron 3 Diarization runner with XNNPACK backend"
126127
@echo " supertonic-mlx - Build Supertonic runner with MLX backend"
127128
@echo " silero-vad-cpu - Build Silero VAD runner with CPU backend"
128129
@echo " llama-cuda - Build Llama runner with CUDA backend"
@@ -293,6 +294,11 @@ nemotron3-diarization-mlx:
293294
cmake --build --preset mlx-release-install
294295
cd examples/models/nemotron3_diarization && cmake --workflow --preset nemotron3-diarization-mlx
295296

297+
.PHONY: nemotron3-diarization-cpu
298+
nemotron3-diarization-cpu:
299+
cmake --workflow --preset llm-release
300+
cd examples/models/nemotron3_diarization && cmake --workflow --preset nemotron3-diarization-cpu
301+
296302
sortformer-cuda:
297303
@echo "==> Building and installing ExecuTorch with CUDA..."
298304
cmake --workflow --preset llm-release-cuda

‎examples/models/nemotron3_diarization/CMakeLists.txt‎

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,11 @@ include(${EXECUTORCH_ROOT}/tools/cmake/Utils.cmake)
1515
set(gflags_DIR ${CMAKE_CURRENT_BINARY_DIR}/../../../third-party/gflags)
1616
find_package(gflags REQUIRED)
1717
find_package(executorch CONFIG REQUIRED FIND_ROOT_PATH_BOTH)
18-
if(NOT TARGET mlxdelegate
19-
OR NOT TARGET mlx
20-
OR NOT TARGET xnnpack_backend
21-
)
18+
if(NOT TARGET xnnpack_backend)
2219
message(
23-
FATAL_ERROR
24-
"Build and install ExecuTorch with EXECUTORCH_BUILD_MLX=ON and EXECUTORCH_BUILD_XNNPACK=ON"
20+
FATAL_ERROR "Build and install ExecuTorch with EXECUTORCH_BUILD_XNNPACK=ON"
2521
)
2622
endif()
27-
executorch_target_link_options_shared_lib(mlxdelegate)
2823
executorch_target_link_options_shared_lib(xnnpack_backend)
2924

3025
add_library(nemotron3_diarization STATIC runner.cpp)
@@ -53,7 +48,18 @@ target_include_directories(
5348
PRIVATE ${EXECUTORCH_ROOT}/third-party/json/include
5449
)
5550
target_link_libraries(
56-
nemotron3_diarization_runner PRIVATE nemotron3_diarization gflags mlxdelegate
57-
mlx
51+
nemotron3_diarization_runner PRIVATE nemotron3_diarization gflags
5852
)
59-
executorch_target_copy_mlx_metallib(nemotron3_diarization_runner)
53+
if(EXECUTORCH_BUILD_MLX)
54+
if(NOT TARGET mlxdelegate OR NOT TARGET mlx)
55+
message(
56+
FATAL_ERROR "Build and install ExecuTorch with EXECUTORCH_BUILD_MLX=ON"
57+
)
58+
endif()
59+
executorch_target_link_options_shared_lib(mlxdelegate)
60+
target_link_libraries(nemotron3_diarization_runner PRIVATE mlxdelegate mlx)
61+
target_compile_definitions(
62+
nemotron3_diarization_runner PRIVATE EXECUTORCH_BUILD_MLX
63+
)
64+
executorch_target_copy_mlx_metallib(nemotron3_diarization_runner)
65+
endif()

‎examples/models/nemotron3_diarization/CMakePresets.json‎

Lines changed: 52 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,32 +2,77 @@
22
"version": 6,
33
"configurePresets": [
44
{
5-
"name": "nemotron3-diarization-mlx",
6-
"displayName": "Nemotron 3 Diarization runner (MLX)",
5+
"name": "nemotron3-diarization-base",
6+
"hidden": true,
77
"binaryDir": "${sourceDir}/../../../cmake-out/examples/models/nemotron3_diarization",
88
"cacheVariables": {
9-
"EXECUTORCH_BUILD_MLX": "ON",
9+
"EXECUTORCH_BUILD_MLX": "OFF",
1010
"CMAKE_BUILD_TYPE": "Release",
11-
"CMAKE_OSX_DEPLOYMENT_TARGET": "14.0",
1211
"CMAKE_FIND_ROOT_PATH": "${sourceDir}/../../../cmake-out",
1312
"CMAKE_PREFIX_PATH": "${sourceDir}/../../../cmake-out"
13+
}
14+
},
15+
{
16+
"name": "nemotron3-diarization-mlx",
17+
"displayName": "Nemotron 3 Diarization runner (MLX)",
18+
"inherits": [
19+
"nemotron3-diarization-base"
20+
],
21+
"cacheVariables": {
22+
"EXECUTORCH_BUILD_MLX": "ON",
23+
"CMAKE_OSX_DEPLOYMENT_TARGET": "14.0"
1424
},
15-
"condition": {"type": "equals", "lhs": "${hostSystemName}", "rhs": "Darwin"}
25+
"condition": {
26+
"type": "equals",
27+
"lhs": "${hostSystemName}",
28+
"rhs": "Darwin"
29+
}
30+
},
31+
{
32+
"name": "nemotron3-diarization-cpu",
33+
"displayName": "Nemotron 3 Diarization runner (XNNPACK)",
34+
"inherits": [
35+
"nemotron3-diarization-base"
36+
]
1637
}
1738
],
1839
"buildPresets": [
1940
{
2041
"name": "nemotron3-diarization-mlx",
2142
"configurePreset": "nemotron3-diarization-mlx",
2243
"configuration": "Release"
44+
},
45+
{
46+
"name": "nemotron3-diarization-cpu",
47+
"configurePreset": "nemotron3-diarization-cpu",
48+
"configuration": "Release"
2349
}
2450
],
2551
"workflowPresets": [
2652
{
2753
"name": "nemotron3-diarization-mlx",
2854
"steps": [
29-
{"type": "configure", "name": "nemotron3-diarization-mlx"},
30-
{"type": "build", "name": "nemotron3-diarization-mlx"}
55+
{
56+
"type": "configure",
57+
"name": "nemotron3-diarization-mlx"
58+
},
59+
{
60+
"type": "build",
61+
"name": "nemotron3-diarization-mlx"
62+
}
63+
]
64+
},
65+
{
66+
"name": "nemotron3-diarization-cpu",
67+
"steps": [
68+
{
69+
"type": "configure",
70+
"name": "nemotron3-diarization-cpu"
71+
},
72+
{
73+
"type": "build",
74+
"name": "nemotron3-diarization-cpu"
75+
}
3176
]
3277
}
3378
]

‎examples/models/nemotron3_diarization/README.md‎

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,24 @@
1-
# Nemotron 3 Diarization on MLX
1+
# Nemotron 3 Diarization
22

33
```bash
4-
# From the repository root on Apple Silicon:
4+
# From the repository root:
55
python -m pip install git+https://github.com/huggingface/transformers "librosa>=0.10"
66

7-
# Export (approximate BF16; add --dtype fp32 for comparison with Transformers)
8-
# Uses the Transformers frontend.
7+
# Choose mlx (Apple Silicon) or xnnpack (CPU).
8+
BACKEND=mlx
9+
# Export: BF16 for MLX/XNNPACK. Add --dtype fp32 to use FP32.
910
python -m executorch.examples.models.nemotron3_diarization.export_nemotron \
1011
--hf-model nvidia/Nemotron-3-Diarization \
11-
--output-dir nemotron_exports
12+
--backend "$BACKEND" \
13+
--output-dir "nemotron_exports/$BACKEND"
1214

13-
# Build
14-
make nemotron3-diarization-mlx
15+
# Build the matching runner.
16+
make "nemotron3-diarization-${BACKEND/xnnpack/cpu}"
1517

1618
# Convert to mono 16 kHz PCM16 and run
1719
ffmpeg -i audio.wav -ac 1 -ar 16000 -c:a pcm_s16le audio_16khz_mono.wav
1820
cmake-out/examples/models/nemotron3_diarization/nemotron3_diarization_runner \
19-
--model_path=nemotron_exports/nemotron3_diarization.pte \
21+
--model_path="nemotron_exports/$BACKEND/nemotron3_diarization.pte" \
2022
--audio_path=audio_16khz_mono.wav \
2123
--preset=offline \
2224
--output=segments.json

‎examples/models/nemotron3_diarization/export_nemotron.py‎

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -157,13 +157,25 @@ def capture_model(model, feature_extractor):
157157
return programs, metadata
158158

159159

160-
def export_model(model_id, output_dir, revision=None, dtype=torch.bfloat16):
161-
from executorch.backends.mlx.partitioner import MLXPartitioner
162-
from executorch.backends.mlx.passes import get_default_passes
160+
def export_model(
161+
model_id, output_dir, revision=None, dtype=torch.bfloat16, backend="mlx"
162+
):
163163
from executorch.backends.xnnpack.partition.xnnpack_partitioner import (
164164
XnnpackPartitioner,
165165
)
166166

167+
passes = []
168+
if backend == "mlx":
169+
from executorch.backends.mlx.partitioner import MLXPartitioner
170+
from executorch.backends.mlx.passes import get_default_passes
171+
172+
partitioners = [MLXPartitioner()]
173+
passes = get_default_passes()
174+
elif backend == "xnnpack":
175+
partitioners = [XnnpackPartitioner(enable_bf16=dtype == torch.bfloat16)]
176+
else:
177+
raise ValueError(f"Unknown backend: {backend}")
178+
167179
model = Nemotron3DiarizationForAudioFrameClassification.from_pretrained(
168180
model_id, revision=revision, dtype=dtype
169181
)
@@ -175,13 +187,13 @@ def export_model(model_id, output_dir, revision=None, dtype=torch.bfloat16):
175187
programs,
176188
partitioner={
177189
"preprocessor": [XnnpackPartitioner()],
178-
"pre_encode": [MLXPartitioner()],
179-
"encode": [MLXPartitioner()],
190+
"pre_encode": partitioners,
191+
"encode": partitioners,
180192
},
181193
transform_passes={
182194
"preprocessor": [],
183-
"pre_encode": get_default_passes(),
184-
"encode": get_default_passes(),
195+
"pre_encode": passes,
196+
"encode": passes,
185197
},
186198
constant_methods=metadata,
187199
compile_config=EdgeCompileConfig(
@@ -211,6 +223,7 @@ def main():
211223
help="Hugging Face model ID or local Transformers checkpoint directory",
212224
)
213225
parser.add_argument("--revision", help="Hugging Face model revision")
226+
parser.add_argument("--backend", choices=("mlx", "xnnpack"), default="mlx")
214227
parser.add_argument("--output-dir", type=Path, default=Path("nemotron_exports"))
215228
parser.add_argument(
216229
"--dtype",
@@ -220,7 +233,7 @@ def main():
220233
)
221234
args = parser.parse_args()
222235
dtype = {"bf16": torch.bfloat16, "fp32": torch.float32}[args.dtype]
223-
export_model(args.hf_model, args.output_dir, args.revision, dtype)
236+
export_model(args.hf_model, args.output_dir, args.revision, dtype, args.backend)
224237

225238

226239
if __name__ == "__main__":

‎examples/models/nemotron3_diarization/main.cpp‎

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,12 @@
1515
#include <stdexcept>
1616

1717
#include <gflags/gflags.h>
18-
#include <mlx/memory.h>
1918
#include <nlohmann/json.hpp>
2019

20+
#ifdef EXECUTORCH_BUILD_MLX
21+
#include <mlx/memory.h>
22+
#endif
23+
2124
#include <executorch/extension/llm/runner/wav_loader.h>
2225

2326
DEFINE_string(
@@ -28,25 +31,31 @@ DEFINE_string(audio_path, "", "Mono 16 kHz WAV file (PCM16 or float32)");
2831
DEFINE_string(preset, "offline", "offline, low, very_low, or ultra_low");
2932
DEFINE_int32(feed_samples, 4096, "Samples per streaming feed");
3033
DEFINE_double(threshold, 0.5, "Speaker activity threshold");
34+
#ifdef EXECUTORCH_BUILD_MLX
3135
DEFINE_int32(mlx_cache_limit_mb, 128, "Maximum unused MLX buffer cache in MiB");
3236
DEFINE_int32(mlx_memory_limit_mb, 4096, "MLX working memory guideline in MiB");
37+
#endif
3338
DEFINE_string(output, "", "Optional JSON output file");
3439

3540
int main(int argc, char** argv) {
3641
gflags::ParseCommandLineFlags(&argc, &argv, true);
3742
try {
3843
if (FLAGS_audio_path.empty() || FLAGS_feed_samples <= 0 ||
39-
FLAGS_mlx_cache_limit_mb < 0 || FLAGS_mlx_memory_limit_mb <= 0 ||
4044
!std::isfinite(FLAGS_threshold) || FLAGS_threshold < 0 ||
4145
FLAGS_threshold > 1) {
4246
throw std::invalid_argument(
43-
"Provide --audio_path, positive --feed_samples and --mlx_memory_limit_mb, "
44-
"nonnegative --mlx_cache_limit_mb, and --threshold in [0,1]");
47+
"Provide --audio_path, positive --feed_samples, and --threshold in [0,1]");
48+
}
49+
#ifdef EXECUTORCH_BUILD_MLX
50+
if (FLAGS_mlx_cache_limit_mb < 0 || FLAGS_mlx_memory_limit_mb <= 0) {
51+
throw std::invalid_argument(
52+
"Provide nonnegative --mlx_cache_limit_mb and positive --mlx_memory_limit_mb");
4553
}
4654
mlx::core::set_cache_limit(
4755
static_cast<size_t>(FLAGS_mlx_cache_limit_mb) << 20);
4856
mlx::core::set_memory_limit(
4957
static_cast<size_t>(FLAGS_mlx_memory_limit_mb) << 20);
58+
#endif
5059
nemotron3::Runner runner(
5160
FLAGS_model_path,
5261
nemotron3::StreamingConfig::from_preset(FLAGS_preset));

0 commit comments

Comments
 (0)