From 5386e4df72fdf96b63165602fa32ebfa2adcf835 Mon Sep 17 00:00:00 2001 From: Alex-Wengg Date: Thu, 24 Sep 2026 20:00:15 -0400 Subject: [PATCH] feat(diarizer/nemotron3): load monolithic presets from monolithic/v2 The shipped monolithic bundles never compile for the ANE on M3 (h15g): ANECCompile() fails with InvalidMILProgram on every load (~3 s retry), and CoreML falls back to a mixed CPU/ANE plan (#951). H15 can't run the int16 gather or the integer index casts, so the input-end packing is split off to the CPU and the first ANE function takes fp32 inputs, which h15g rejects. M5 keeps the packing on the ANE and never saw it. monolithic/v2/ on the HF repo holds all five presets re-exported with fp16 inputs and outputs and gather-free (one-hot matmul) state packing. Same checkpoint and weights; the originals stay in monolithic/ so older releases keep working and we can roll back by path. No weightsVersion bump: the new path is a fresh download, and the split bundles and .bin assets stay cached. - M3 Max (reporter): fast32 v2 compiles, 100% frame decisions vs shipped. - M5 Pro, AMI 16 meetings, shipped -> v2: DER identical on .all for all five presets, within +-0.06 on ANE; RTFx within a few percent. - Downloaded v2 fast32 is byte-identical to the local build and its --dump-preds match exactly; split presets unaffected. --- .../FluidAudio/Diarizer/Nemotron3/Nemotron3Models.swift | 2 +- Sources/FluidAudio/Diarizer/Nemotron3/Nemotron3Types.swift | 6 +++++- .../Diarizer/Nemotron3/Nemotron3CacheVersionTests.swift | 7 +++++++ 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/Sources/FluidAudio/Diarizer/Nemotron3/Nemotron3Models.swift b/Sources/FluidAudio/Diarizer/Nemotron3/Nemotron3Models.swift index 17e99aaaf..a3e90d571 100644 --- a/Sources/FluidAudio/Diarizer/Nemotron3/Nemotron3Models.swift +++ b/Sources/FluidAudio/Diarizer/Nemotron3/Nemotron3Models.swift @@ -137,7 +137,7 @@ public struct Nemotron3Models { /// (if not cached) and load it. /// /// Layout under `cacheDirectory` (default `~/Library/Application Support/FluidAudio/Models`): - /// `nemotron-3-diarization/{monolithic,split}/.mlmodelc` plus the root `.bin` assets. + /// `nemotron-3-diarization/{monolithic/v2,split}/.mlmodelc` plus the root `.bin` assets. /// Only the requested preset's bundle is fetched. public static func loadFromHuggingFace( config: Nemotron3Config, diff --git a/Sources/FluidAudio/Diarizer/Nemotron3/Nemotron3Types.swift b/Sources/FluidAudio/Diarizer/Nemotron3/Nemotron3Types.swift index be7c0ca39..0278e446e 100644 --- a/Sources/FluidAudio/Diarizer/Nemotron3/Nemotron3Types.swift +++ b/Sources/FluidAudio/Diarizer/Nemotron3/Nemotron3Types.swift @@ -82,7 +82,11 @@ public struct Nemotron3Config: Sendable { } /// Subdirectory of `FluidInference/nemotron-3-diarization-coreml` holding this preset's bundle. - public var hubSubdirectory: String { splitGraph ? "split" : "monolithic" } + /// + /// Monolithic presets load from `monolithic/v2/`: fp16-I/O, gather-free re-exports that + /// compile for the ANE on M3-class chips (#951). The originals stay in `monolithic/` for + /// older releases. + public var hubSubdirectory: String { splitGraph ? "split" : "monolithic/v2" } // MARK: Presets (model card recommended profiles) diff --git a/Tests/FluidAudioTests/Diarizer/Nemotron3/Nemotron3CacheVersionTests.swift b/Tests/FluidAudioTests/Diarizer/Nemotron3/Nemotron3CacheVersionTests.swift index 6dcd3462d..7d141aab9 100644 --- a/Tests/FluidAudioTests/Diarizer/Nemotron3/Nemotron3CacheVersionTests.swift +++ b/Tests/FluidAudioTests/Diarizer/Nemotron3/Nemotron3CacheVersionTests.swift @@ -79,4 +79,11 @@ final class Nemotron3CacheVersionTests: XCTestCase { let absent = root.appendingPathComponent("does-not-exist") XCTAssertNoThrow(try Nemotron3Models.discardStaleCache(at: absent)) } + + func testMonolithicPresetsLoadFromV2AndSplitPresetsFromSplit() { + for config in [Nemotron3Config.low, .offline, .fast, .fast32, .fast128] { + XCTAssertEqual(config.hubSubdirectory, "monolithic/v2", config.modelFileName) + } + XCTAssertEqual(Nemotron3Config.preset(named: "fast32-split-w8a8")?.hubSubdirectory, "split") + } }