diff --git a/bindings/python/src/transcribe_cpp/__init__.py b/bindings/python/src/transcribe_cpp/__init__.py index 51687230..fc750249 100644 --- a/bindings/python/src/transcribe_cpp/__init__.py +++ b/bindings/python/src/transcribe_cpp/__init__.py @@ -57,6 +57,8 @@ Itn = Literal["default", "off", "on"] Diarize = Literal["default", "off", "on"] SortformerPreset = Literal["default", "very_high_latency", "high_latency", "low_latency"] +Nemotron3DiarPreset = Literal["default", "very_high_latency", "low_latency", + "very_low_latency", "ultra_low_latency"] CommitPolicy = Literal["auto", "on_finalize", "stable_prefix"] Feature = Literal[ "initial_prompt", "temperature_fallback", "long_form", @@ -85,9 +87,12 @@ "ParakeetStreamOptions", "ParakeetBufferedStreamOptions", "SortformerStreamOptions", + "Nemotron3DiarRunOptions", + "Nemotron3DiarStreamOptions", "VoxtralRealtimeStreamOptions", "Backend", "SortformerPreset", + "Nemotron3DiarPreset", "KVType", "Task", "Timestamps", @@ -870,6 +875,57 @@ def _apply(self, ext) -> None: ext.preset = self._presets[self.preset] +class _Nemotron3DiarPresetOptions(FamilyExtension): + """Shared preset mapping for the two Nemotron-3-Diarization slots.""" + + _presets = { + "default": _generated.TRANSCRIBE_NEMOTRON3_DIAR_PRESET_DEFAULT, + "very_high_latency": _generated.TRANSCRIBE_NEMOTRON3_DIAR_PRESET_VERY_HIGH_LATENCY, + "low_latency": _generated.TRANSCRIBE_NEMOTRON3_DIAR_PRESET_LOW_LATENCY, + "very_low_latency": _generated.TRANSCRIBE_NEMOTRON3_DIAR_PRESET_VERY_LOW_LATENCY, + "ultra_low_latency": _generated.TRANSCRIBE_NEMOTRON3_DIAR_PRESET_ULTRA_LOW_LATENCY, + } + + def __init__(self, *, preset: Nemotron3DiarPreset | None = None): + if preset is not None and preset not in self._presets: + raise ValueError(f"unknown nemotron3_diar preset {preset!r}; " + f"expected one of {sorted(self._presets)}") + self.preset = preset + + def _apply(self, ext) -> None: + if self.preset is not None: + ext.preset = self._presets[self.preset] + + +class Nemotron3DiarRunOptions(_Nemotron3DiarPresetOptions): + """Nemotron-3-Diarization operating point for a whole-file run (run slot). + + A diarizer: a run produces speaker segments (up to 8 speakers), no text. + ``preset`` picks the model card's latency / accuracy bundle: + ``"very_high_latency"`` (30.4 s, the offline point; also ``"default"``), + ``"low_latency"`` (1.04 s), ``"very_low_latency"`` (0.64 s), + ``"ultra_low_latency"`` (0.32 s). Smaller chunks cost more compute per + audio second.""" + + _slot = "run" + _kind = _generated.TRANSCRIBE_EXT_KIND_NEMOTRON3_DIAR_RUN + _struct = _generated.transcribe_nemotron3_diar_run_ext + _init = "transcribe_nemotron3_diar_run_ext_init" + + +class Nemotron3DiarStreamOptions(_Nemotron3DiarPresetOptions): + """Nemotron-3-Diarization operating point for push-audio streaming + (stream slot). After each feed the speaker segments cover all audio + processed so far (a turn still in progress is open-ended and may extend); + finalize equals a whole-file run at the same preset. Presets as for + :class:`Nemotron3DiarRunOptions`.""" + + _slot = "stream" + _kind = _generated.TRANSCRIBE_EXT_KIND_NEMOTRON3_DIAR_STREAM + _struct = _generated.transcribe_nemotron3_diar_stream_ext + _init = "transcribe_nemotron3_diar_stream_ext_init" + + # --- high-level handles --------------------------------------------------- diff --git a/bindings/python/src/transcribe_cpp/_generated.py b/bindings/python/src/transcribe_cpp/_generated.py index ac9763c2..065bd4bf 100644 --- a/bindings/python/src/transcribe_cpp/_generated.py +++ b/bindings/python/src/transcribe_cpp/_generated.py @@ -13,7 +13,7 @@ # Stable digest of the ABI surface below (structs, enums, macros, layout, # prototypes). A native provider package echoes this back so the API # package can reject an ABI-mismatched provider before dlopen. -PUBLIC_HEADER_HASH = "7df72bf9e667b8c2" +PUBLIC_HEADER_HASH = "ae25d09c2b7b325b" # === enum constants === TRANSCRIBE_OK = 0 @@ -102,6 +102,11 @@ TRANSCRIBE_STREAM_COMMIT_AUTO = 0 TRANSCRIBE_STREAM_COMMIT_ON_FINALIZE = 1 TRANSCRIBE_STREAM_COMMIT_STABLE_PREFIX = 2 +TRANSCRIBE_NEMOTRON3_DIAR_PRESET_DEFAULT = 0 +TRANSCRIBE_NEMOTRON3_DIAR_PRESET_VERY_HIGH_LATENCY = 1 +TRANSCRIBE_NEMOTRON3_DIAR_PRESET_LOW_LATENCY = 2 +TRANSCRIBE_NEMOTRON3_DIAR_PRESET_VERY_LOW_LATENCY = 3 +TRANSCRIBE_NEMOTRON3_DIAR_PRESET_ULTRA_LOW_LATENCY = 4 TRANSCRIBE_SORTFORMER_PRESET_DEFAULT = 0 TRANSCRIBE_SORTFORMER_PRESET_VERY_HIGH_LATENCY = 1 TRANSCRIBE_SORTFORMER_PRESET_HIGH_LATENCY = 2 @@ -111,6 +116,8 @@ # === macro constants (integer object-like macros) === TRANSCRIBE_EXT_KIND_MOONSHINE_STREAMING_STREAM = 1414746957 +TRANSCRIBE_EXT_KIND_NEMOTRON3_DIAR_RUN = 1380201294 +TRANSCRIBE_EXT_KIND_NEMOTRON3_DIAR_STREAM = 1396978510 TRANSCRIBE_EXT_KIND_PARAKEET_BUFFERED_STREAM = 1396853584 TRANSCRIBE_EXT_KIND_PARAKEET_STREAM = 1414744912 TRANSCRIBE_EXT_KIND_SORTFORMER_STREAM = 1414743635 @@ -150,6 +157,10 @@ class transcribe_speaker_segment(_c.Structure): pass class transcribe_moonshine_streaming_stream_ext(_c.Structure): pass +class transcribe_nemotron3_diar_run_ext(_c.Structure): + pass +class transcribe_nemotron3_diar_stream_ext(_c.Structure): + pass class transcribe_parakeet_stream_ext(_c.Structure): pass class transcribe_parakeet_buffered_stream_ext(_c.Structure): @@ -179,6 +190,8 @@ class transcribe_whisper_chunk_trace(_c.Structure): transcribe_token._fields_ = [("struct_size", _c.c_uint64), ("id", _c.c_int), ("p", _c.c_float), ("t0_ms", _c.c_int64), ("t1_ms", _c.c_int64), ("seg_index", _c.c_int), ("word_index", _c.c_int), ("text", _c.c_char_p)] transcribe_speaker_segment._fields_ = [("struct_size", _c.c_uint64), ("t0_ms", _c.c_int64), ("t1_ms", _c.c_int64), ("speaker_id", _c.c_int32), ("p", _c.c_float)] transcribe_moonshine_streaming_stream_ext._fields_ = [("ext", transcribe_ext), ("min_decode_interval_ms", _c.c_int32)] +transcribe_nemotron3_diar_run_ext._fields_ = [("ext", transcribe_ext), ("preset", _c.c_int)] +transcribe_nemotron3_diar_stream_ext._fields_ = [("ext", transcribe_ext), ("preset", _c.c_int)] transcribe_parakeet_stream_ext._fields_ = [("ext", transcribe_ext), ("att_context_right", _c.c_int32)] transcribe_parakeet_buffered_stream_ext._fields_ = [("ext", transcribe_ext), ("left_ms", _c.c_int32), ("chunk_ms", _c.c_int32), ("right_ms", _c.c_int32)] transcribe_sortformer_stream_ext._fields_ = [("ext", transcribe_ext), ("preset", _c.c_int)] @@ -224,6 +237,8 @@ class transcribe_whisper_chunk_trace(_c.Structure): 'transcribe_token': {'size': 48, 'align': 8, 'offsets': {'struct_size': 0, 'id': 8, 'p': 12, 't0_ms': 16, 't1_ms': 24, 'seg_index': 32, 'word_index': 36, 'text': 40}}, 'transcribe_speaker_segment': {'size': 32, 'align': 8, 'offsets': {'struct_size': 0, 't0_ms': 8, 't1_ms': 16, 'speaker_id': 24, 'p': 28}}, 'transcribe_moonshine_streaming_stream_ext': {'size': 24, 'align': 8, 'offsets': {'ext': 0, 'min_decode_interval_ms': 16}}, + 'transcribe_nemotron3_diar_run_ext': {'size': 24, 'align': 8, 'offsets': {'ext': 0, 'preset': 16}}, + 'transcribe_nemotron3_diar_stream_ext': {'size': 24, 'align': 8, 'offsets': {'ext': 0, 'preset': 16}}, 'transcribe_parakeet_stream_ext': {'size': 24, 'align': 8, 'offsets': {'ext': 0, 'att_context_right': 16}}, 'transcribe_parakeet_buffered_stream_ext': {'size': 32, 'align': 8, 'offsets': {'ext': 0, 'left_ms': 16, 'chunk_ms': 20, 'right_ms': 24}}, 'transcribe_sortformer_stream_ext': {'size': 24, 'align': 8, 'offsets': {'ext': 0, 'preset': 16}}, @@ -343,6 +358,10 @@ def configure(lib): lib.transcribe_n_tokens.argtypes = [_c.c_void_p] lib.transcribe_n_words.restype = _c.c_int lib.transcribe_n_words.argtypes = [_c.c_void_p] + lib.transcribe_nemotron3_diar_run_ext_init.restype = None + lib.transcribe_nemotron3_diar_run_ext_init.argtypes = [_c.POINTER(transcribe_nemotron3_diar_run_ext)] + lib.transcribe_nemotron3_diar_stream_ext_init.restype = None + lib.transcribe_nemotron3_diar_stream_ext_init.argtypes = [_c.POINTER(transcribe_nemotron3_diar_stream_ext)] lib.transcribe_open.restype = _c.c_int lib.transcribe_open.argtypes = [_c.c_char_p, _c.POINTER(transcribe_model_load_params), _c.POINTER(transcribe_session_params), _c.POINTER(_c.c_void_p)] lib.transcribe_parakeet_buffered_stream_ext_init.restype = None diff --git a/bindings/python/tests/test_family_ext.py b/bindings/python/tests/test_family_ext.py index 3b0a433e..117339a9 100644 --- a/bindings/python/tests/test_family_ext.py +++ b/bindings/python/tests/test_family_ext.py @@ -23,6 +23,8 @@ t.ParakeetStreamOptions, t.ParakeetBufferedStreamOptions, t.SortformerStreamOptions, + t.Nemotron3DiarRunOptions, + t.Nemotron3DiarStreamOptions, t.VoxtralRealtimeStreamOptions, ] @@ -87,6 +89,22 @@ def test_sortformer_unknown_preset_rejected(): t.SortformerStreamOptions(preset="ultra_low_latency") # type: ignore[arg-type] +@pytest.mark.parametrize("cls,slot,kind", [ + (t.Nemotron3DiarRunOptions, "run", _generated.TRANSCRIBE_EXT_KIND_NEMOTRON3_DIAR_RUN), + (t.Nemotron3DiarStreamOptions, "stream", _generated.TRANSCRIBE_EXT_KIND_NEMOTRON3_DIAR_STREAM), +]) +def test_nemotron3_diar_preset_maps_to_enum_value(cls, slot, kind): + assert cls._slot == slot and cls._kind == kind + built = cls(preset="ultra_low_latency")._build() + assert built.preset == _generated.TRANSCRIBE_NEMOTRON3_DIAR_PRESET_ULTRA_LOW_LATENCY + assert cls()._build().preset == _generated.TRANSCRIBE_NEMOTRON3_DIAR_PRESET_DEFAULT + + +def test_nemotron3_diar_unknown_preset_rejected(): + with pytest.raises(ValueError, match="preset"): + t.Nemotron3DiarRunOptions(preset="high_latency") # type: ignore[arg-type] + + # --- model-gated: resolve_family validation + a real extension run ---------- diff --git a/bindings/rust/sys/src/transcribe_sys.rs b/bindings/rust/sys/src/transcribe_sys.rs index 363cd6e3..7e6098b1 100644 --- a/bindings/rust/sys/src/transcribe_sys.rs +++ b/bindings/rust/sys/src/transcribe_sys.rs @@ -1,15 +1,17 @@ // @generated by `cargo xtask bindgen` from include/transcribe/extensions.h // DO NOT EDIT BY HAND. Regenerate: `cargo xtask bindgen`. -// Pinned to include/transcribe.abihash = 7df72bf9e667b8c2 +// Pinned to include/transcribe.abihash = ae25d09c2b7b325b /// The public-ABI digest these bindings were generated against /// (sha256/16 over the normalized FFI surface). The load-time version /// gate and the CI drift check both anchor on this value. -pub const PUBLIC_HEADER_HASH: &str = "7df72bf9e667b8c2"; +pub const PUBLIC_HEADER_HASH: &str = "ae25d09c2b7b325b"; /* automatically generated by rust-bindgen 0.72.1 */ pub const TRANSCRIBE_EXT_KIND_MOONSHINE_STREAMING_STREAM: u32 = 1414746957; +pub const TRANSCRIBE_EXT_KIND_NEMOTRON3_DIAR_RUN: u32 = 1380201294; +pub const TRANSCRIBE_EXT_KIND_NEMOTRON3_DIAR_STREAM: u32 = 1396978510; pub const TRANSCRIBE_EXT_KIND_PARAKEET_STREAM: u32 = 1414744912; pub const TRANSCRIBE_EXT_KIND_PARAKEET_BUFFERED_STREAM: u32 = 1396853584; pub const TRANSCRIBE_EXT_KIND_SORTFORMER_STREAM: u32 = 1414743635; @@ -1115,6 +1117,63 @@ unsafe extern "C" { ext: *mut transcribe_moonshine_streaming_stream_ext, ); } +impl transcribe_nemotron3_diar_preset { + pub const TRANSCRIBE_NEMOTRON3_DIAR_PRESET_DEFAULT: transcribe_nemotron3_diar_preset = + transcribe_nemotron3_diar_preset(0); + pub const TRANSCRIBE_NEMOTRON3_DIAR_PRESET_VERY_HIGH_LATENCY: transcribe_nemotron3_diar_preset = + transcribe_nemotron3_diar_preset(1); + pub const TRANSCRIBE_NEMOTRON3_DIAR_PRESET_LOW_LATENCY: transcribe_nemotron3_diar_preset = + transcribe_nemotron3_diar_preset(2); + pub const TRANSCRIBE_NEMOTRON3_DIAR_PRESET_VERY_LOW_LATENCY: transcribe_nemotron3_diar_preset = + transcribe_nemotron3_diar_preset(3); + pub const TRANSCRIBE_NEMOTRON3_DIAR_PRESET_ULTRA_LOW_LATENCY: transcribe_nemotron3_diar_preset = + transcribe_nemotron3_diar_preset(4); +} +#[repr(transparent)] +#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] +pub struct transcribe_nemotron3_diar_preset(pub ::std::os::raw::c_uint); +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct transcribe_nemotron3_diar_run_ext { + pub ext: transcribe_ext, + pub preset: transcribe_nemotron3_diar_preset, +} +#[allow(clippy::unnecessary_operation, clippy::identity_op)] +const _: () = { + ["Size of transcribe_nemotron3_diar_run_ext"] + [::std::mem::size_of::() - 24usize]; + ["Alignment of transcribe_nemotron3_diar_run_ext"] + [::std::mem::align_of::() - 8usize]; + ["Offset of field: transcribe_nemotron3_diar_run_ext::ext"] + [::std::mem::offset_of!(transcribe_nemotron3_diar_run_ext, ext) - 0usize]; + ["Offset of field: transcribe_nemotron3_diar_run_ext::preset"] + [::std::mem::offset_of!(transcribe_nemotron3_diar_run_ext, preset) - 16usize]; +}; +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct transcribe_nemotron3_diar_stream_ext { + pub ext: transcribe_ext, + pub preset: transcribe_nemotron3_diar_preset, +} +#[allow(clippy::unnecessary_operation, clippy::identity_op)] +const _: () = { + ["Size of transcribe_nemotron3_diar_stream_ext"] + [::std::mem::size_of::() - 24usize]; + ["Alignment of transcribe_nemotron3_diar_stream_ext"] + [::std::mem::align_of::() - 8usize]; + ["Offset of field: transcribe_nemotron3_diar_stream_ext::ext"] + [::std::mem::offset_of!(transcribe_nemotron3_diar_stream_ext, ext) - 0usize]; + ["Offset of field: transcribe_nemotron3_diar_stream_ext::preset"] + [::std::mem::offset_of!(transcribe_nemotron3_diar_stream_ext, preset) - 16usize]; +}; +unsafe extern "C" { + pub fn transcribe_nemotron3_diar_run_ext_init(ext: *mut transcribe_nemotron3_diar_run_ext); +} +unsafe extern "C" { + pub fn transcribe_nemotron3_diar_stream_ext_init( + ext: *mut transcribe_nemotron3_diar_stream_ext, + ); +} #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct transcribe_parakeet_stream_ext { diff --git a/bindings/rust/transcribe-cpp/src/family.rs b/bindings/rust/transcribe-cpp/src/family.rs index 78b7b877..a4768430 100644 --- a/bindings/rust/transcribe-cpp/src/family.rs +++ b/bindings/rust/transcribe-cpp/src/family.rs @@ -103,12 +103,58 @@ pub struct SortformerStreamOptions { pub preset: Option, } +/// Nemotron-3-Diarization operating point (model-card latency / accuracy +/// bundle). `Default` keeps the GGUF-shipped configuration (= `VeryHighLatency`). +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] +pub enum Nemotron3DiarPreset { + #[default] + Default, + /// 30.4 s (chunk 340 + lookahead 40 frames); the offline point. + VeryHighLatency, + /// 1.04 s (chunk 9 + lookahead 4). + LowLatency, + /// 0.64 s (chunk 6 + lookahead 2). + VeryLowLatency, + /// 0.32 s (chunk 3 + lookahead 1). Most compute per audio second. + UltraLowLatency, +} + +impl Nemotron3DiarPreset { + fn to_sys(self) -> sys::transcribe_nemotron3_diar_preset { + use sys::transcribe_nemotron3_diar_preset as P; + match self { + Nemotron3DiarPreset::Default => P::TRANSCRIBE_NEMOTRON3_DIAR_PRESET_DEFAULT, + Nemotron3DiarPreset::VeryHighLatency => { + P::TRANSCRIBE_NEMOTRON3_DIAR_PRESET_VERY_HIGH_LATENCY + } + Nemotron3DiarPreset::LowLatency => P::TRANSCRIBE_NEMOTRON3_DIAR_PRESET_LOW_LATENCY, + Nemotron3DiarPreset::VeryLowLatency => { + P::TRANSCRIBE_NEMOTRON3_DIAR_PRESET_VERY_LOW_LATENCY + } + Nemotron3DiarPreset::UltraLowLatency => { + P::TRANSCRIBE_NEMOTRON3_DIAR_PRESET_ULTRA_LOW_LATENCY + } + } + } +} + +/// Nemotron-3-Diarization knobs, used on both slots: as +/// [`RunExtension::Nemotron3Diar`] for a whole-file run and as +/// [`StreamExtension::Nemotron3Diar`] for push-audio streaming (segments cover +/// all audio processed so far; finalize equals a whole-file run). A run +/// produces speaker segments (up to 8 speakers), no text. +#[derive(Debug, Clone, Default, PartialEq, Eq)] +pub struct Nemotron3DiarOptions { + pub preset: Option, +} + /// A family extension for the run slot (offline `run`/`run_batch`). #[derive(Debug, Clone, PartialEq)] #[non_exhaustive] pub enum RunExtension { Whisper(WhisperRunOptions), Sortformer(SortformerStreamOptions), + Nemotron3Diar(Nemotron3DiarOptions), } /// A family extension for the stream slot. @@ -119,6 +165,7 @@ pub enum StreamExtension { ParakeetBuffered(ParakeetBufferedStreamOptions), MoonshineStreaming(MoonshineStreamingOptions), VoxtralRealtime(VoxtralRealtimeStreamOptions), + Nemotron3Diar(Nemotron3DiarOptions), } /// Owns a materialized run-slot C extension struct (and any strings it points @@ -131,6 +178,7 @@ pub(crate) enum RunExtRaw { _prompt: Option, }, Sortformer(Box), + Nemotron3Diar(Box), } impl RunExtRaw { @@ -143,6 +191,10 @@ impl RunExtRaw { RunExtRaw::Sortformer(e) => { (&**e) as *const sys::transcribe_sortformer_stream_ext as *const sys::transcribe_ext } + RunExtRaw::Nemotron3Diar(e) => { + (&**e) as *const sys::transcribe_nemotron3_diar_run_ext + as *const sys::transcribe_ext + } } } } @@ -182,6 +234,12 @@ impl RunExtension { set(&mut ext.preset, o.preset.map(SortformerPreset::to_sys)); Ok(RunExtRaw::Sortformer(Box::new(ext))) } + RunExtension::Nemotron3Diar(o) => { + let mut ext: sys::transcribe_nemotron3_diar_run_ext = unsafe { std::mem::zeroed() }; + unsafe { sys::transcribe_nemotron3_diar_run_ext_init(&mut ext) }; + set(&mut ext.preset, o.preset.map(Nemotron3DiarPreset::to_sys)); + Ok(RunExtRaw::Nemotron3Diar(Box::new(ext))) + } } } } @@ -192,6 +250,7 @@ pub(crate) enum StreamExtRaw { ParakeetBuffered(Box), MoonshineStreaming(Box), VoxtralRealtime(Box), + Nemotron3Diar(Box), } impl StreamExtRaw { @@ -212,6 +271,10 @@ impl StreamExtRaw { (&**e) as *const sys::transcribe_voxtral_realtime_stream_ext as *const sys::transcribe_ext } + StreamExtRaw::Nemotron3Diar(e) => { + (&**e) as *const sys::transcribe_nemotron3_diar_stream_ext + as *const sys::transcribe_ext + } } } } @@ -249,6 +312,13 @@ impl StreamExtension { set(&mut e.min_decode_interval_ms, o.min_decode_interval_ms); StreamExtRaw::VoxtralRealtime(Box::new(e)) } + StreamExtension::Nemotron3Diar(o) => { + let mut e: sys::transcribe_nemotron3_diar_stream_ext = + unsafe { std::mem::zeroed() }; + unsafe { sys::transcribe_nemotron3_diar_stream_ext_init(&mut e) }; + set(&mut e.preset, o.preset.map(Nemotron3DiarPreset::to_sys)); + StreamExtRaw::Nemotron3Diar(Box::new(e)) + } } } } diff --git a/bindings/rust/transcribe-cpp/src/lib.rs b/bindings/rust/transcribe-cpp/src/lib.rs index d2dc48c7..e163e465 100644 --- a/bindings/rust/transcribe-cpp/src/lib.rs +++ b/bindings/rust/transcribe-cpp/src/lib.rs @@ -64,9 +64,9 @@ pub use backend::{ pub use cancel::CancelToken; pub use error::{Error, Result}; pub use family::{ - MoonshineStreamingOptions, ParakeetBufferedStreamOptions, ParakeetStreamOptions, RunExtension, - SortformerPreset, SortformerStreamOptions, StreamExtension, VoxtralRealtimeStreamOptions, - WhisperRunOptions, + MoonshineStreamingOptions, Nemotron3DiarOptions, Nemotron3DiarPreset, + ParakeetBufferedStreamOptions, ParakeetStreamOptions, RunExtension, SortformerPreset, + SortformerStreamOptions, StreamExtension, VoxtralRealtimeStreamOptions, WhisperRunOptions, }; pub use logging::{disable_logging, init_logging}; pub use model::{Capabilities, Model, ModelOptions, SessionLimits, SessionOptions}; diff --git a/bindings/rust/transcribe-cpp/tests/common/mod.rs b/bindings/rust/transcribe-cpp/tests/common/mod.rs index 3c5ebfa0..b90fa000 100644 --- a/bindings/rust/transcribe-cpp/tests/common/mod.rs +++ b/bindings/rust/transcribe-cpp/tests/common/mod.rs @@ -136,3 +136,11 @@ fn load_wav(path: &std::path::Path) -> Vec { .map(|s| s.expect("wav sample") as f32 / 32768.0) .collect() } + +/// Nemotron-3-Diarization (accepts NEMOTRON3_DIAR_RUN / _STREAM). Local-only. +pub fn smoke_nemotron3_diar_model() -> Option { + family_model( + "TRANSCRIBE_SMOKE_NEMOTRON3_DIAR_MODEL", + "models/Nemotron-3-Diarization/Nemotron-3-Diarization-BF16.gguf", + ) +} diff --git a/bindings/rust/transcribe-cpp/tests/extensions.rs b/bindings/rust/transcribe-cpp/tests/extensions.rs index 32ac6ace..c458ae64 100644 --- a/bindings/rust/transcribe-cpp/tests/extensions.rs +++ b/bindings/rust/transcribe-cpp/tests/extensions.rs @@ -57,3 +57,46 @@ fn tokenize_round_trips_nonempty() { let short = model.tokenize("ask").unwrap(); assert!(tokens.len() >= short.len()); } + +#[test] +fn nemotron3_diar_run_and_stream_extensions() { + use transcribe_cpp::sys::{ + TRANSCRIBE_EXT_KIND_NEMOTRON3_DIAR_RUN, TRANSCRIBE_EXT_KIND_NEMOTRON3_DIAR_STREAM, + }; + use transcribe_cpp::{ + Nemotron3DiarOptions, Nemotron3DiarPreset, StreamExtension, StreamOptions, + }; + let (Some(model_path), Some(pcm)) = + (common::smoke_nemotron3_diar_model(), common::smoke_audio()) + else { + eprintln!("skip nemotron3_diar_run_and_stream_extensions: model not present"); + return; + }; + let model = Model::load(&model_path).unwrap(); + assert!(model.accepts_ext(ExtSlot::Run, TRANSCRIBE_EXT_KIND_NEMOTRON3_DIAR_RUN)); + assert!(model.accepts_ext(ExtSlot::Stream, TRANSCRIBE_EXT_KIND_NEMOTRON3_DIAR_STREAM)); + assert!(!model.accepts_ext(ExtSlot::Stream, TRANSCRIBE_EXT_KIND_NEMOTRON3_DIAR_RUN)); + let opts = Nemotron3DiarOptions { + preset: Some(Nemotron3DiarPreset::LowLatency), + }; + let mut session = model.session().unwrap(); + let run = RunOptions { + family: Some(RunExtension::Nemotron3Diar(opts.clone())), + ..Default::default() + }; + let result = session.run(&pcm, &run).unwrap(); + assert!(!result.speaker_segments.is_empty()); + + let stream_opts = StreamOptions { + family: Some(StreamExtension::Nemotron3Diar(opts)), + ..Default::default() + }; + let mut stream = session + .stream(&RunOptions::default(), &stream_opts) + .unwrap(); + for piece in pcm.chunks(2768) { + stream.feed(piece).unwrap(); + } + let fin = stream.finalize().unwrap(); + assert!(fin.is_final); +} diff --git a/bindings/swift/Sources/TranscribeCpp/Family.swift b/bindings/swift/Sources/TranscribeCpp/Family.swift index 59233a88..bd6cb087 100644 --- a/bindings/swift/Sources/TranscribeCpp/Family.swift +++ b/bindings/swift/Sources/TranscribeCpp/Family.swift @@ -7,7 +7,7 @@ import CTranscribe // `transcribe_ext` pointer is handed to the run/begin call; the library copies // what it needs before returning. -// MARK: - Run-slot extensions (whisper, sortformer) +// MARK: - Run-slot extensions (whisper, sortformer, nemotron3_diar) public struct WhisperRunOptions: Sendable { public var initialPrompt: String? @@ -74,14 +74,48 @@ public struct SortformerStreamOptions: Sendable { public init(preset: SortformerPreset? = nil) { self.preset = preset } } +/// Nemotron-3-Diarization operating point (model-card latency / accuracy +/// bundle). `.default` keeps the GGUF-shipped configuration +/// (= `.veryHighLatency`, 30.4 s, the offline point); `.lowLatency` 1.04 s, +/// `.veryLowLatency` 0.64 s, `.ultraLowLatency` 0.32 s. Smaller chunks cost +/// more compute per audio second. +public enum Nemotron3DiarPreset: Sendable { + case `default` + case veryHighLatency + case lowLatency + case veryLowLatency + case ultraLowLatency + + var cValue: transcribe_nemotron3_diar_preset { + switch self { + case .default: return TRANSCRIBE_NEMOTRON3_DIAR_PRESET_DEFAULT + case .veryHighLatency: return TRANSCRIBE_NEMOTRON3_DIAR_PRESET_VERY_HIGH_LATENCY + case .lowLatency: return TRANSCRIBE_NEMOTRON3_DIAR_PRESET_LOW_LATENCY + case .veryLowLatency: return TRANSCRIBE_NEMOTRON3_DIAR_PRESET_VERY_LOW_LATENCY + case .ultraLowLatency: return TRANSCRIBE_NEMOTRON3_DIAR_PRESET_ULTRA_LOW_LATENCY + } + } +} + +/// Nemotron-3-Diarization options, used on both slots: `RunExtension.nemotron3Diar` +/// for a whole-file run, `StreamExtension.nemotron3Diar` for push-audio +/// streaming (segments cover all audio processed so far; finalize equals a +/// whole-file run). A run produces speaker segments (up to 8 speakers), no text. +public struct Nemotron3DiarOptions: Sendable { + public var preset: Nemotron3DiarPreset? + public init(preset: Nemotron3DiarPreset? = nil) { self.preset = preset } +} + public enum RunExtension: Sendable { case whisper(WhisperRunOptions) case sortformer(SortformerStreamOptions) + case nemotron3Diar(Nemotron3DiarOptions) var kind: UInt32 { switch self { case .whisper: return TRANSCRIBE_EXT_KIND_WHISPER_RUN case .sortformer: return TRANSCRIBE_EXT_KIND_SORTFORMER_STREAM + case .nemotron3Diar: return TRANSCRIBE_EXT_KIND_NEMOTRON3_DIAR_RUN } } } @@ -114,6 +148,11 @@ func withRunExtension( transcribe_sortformer_stream_ext_init(&c) if let v = o.preset { c.preset = v.cValue } return try withUnsafePointer(to: &c.ext) { try body($0) } + case .nemotron3Diar(let o): + var c = transcribe_nemotron3_diar_run_ext() + transcribe_nemotron3_diar_run_ext_init(&c) + if let v = o.preset { c.preset = v.cValue } + return try withUnsafePointer(to: &c.ext) { try body($0) } } } @@ -151,6 +190,7 @@ public enum StreamExtension: Sendable { case parakeetBuffered(ParakeetBufferedStreamOptions) case moonshineStreaming(MoonshineStreamingOptions) case voxtralRealtime(VoxtralRealtimeStreamOptions) + case nemotron3Diar(Nemotron3DiarOptions) var kind: UInt32 { switch self { @@ -158,6 +198,7 @@ public enum StreamExtension: Sendable { case .parakeetBuffered: return TRANSCRIBE_EXT_KIND_PARAKEET_BUFFERED_STREAM case .moonshineStreaming: return TRANSCRIBE_EXT_KIND_MOONSHINE_STREAMING_STREAM case .voxtralRealtime: return TRANSCRIBE_EXT_KIND_VOXTRAL_REALTIME_STREAM + case .nemotron3Diar: return TRANSCRIBE_EXT_KIND_NEMOTRON3_DIAR_STREAM } } } @@ -190,6 +231,11 @@ func withStreamExtension( if let v = o.numDelayTokens { c.num_delay_tokens = v } if let v = o.minDecodeIntervalMs { c.min_decode_interval_ms = v } return try withUnsafePointer(to: &c.ext) { try body($0) } + case .nemotron3Diar(let o): + var c = transcribe_nemotron3_diar_stream_ext() + transcribe_nemotron3_diar_stream_ext_init(&c) + if let v = o.preset { c.preset = v.cValue } + return try withUnsafePointer(to: &c.ext) { try body($0) } } } diff --git a/bindings/typescript/src/_generated.ts b/bindings/typescript/src/_generated.ts index fff2ca59..581e6b59 100644 --- a/bindings/typescript/src/_generated.ts +++ b/bindings/typescript/src/_generated.ts @@ -11,7 +11,7 @@ // Stable digest of the ABI surface (structs, enums, macros, layout, // prototypes), computed by the Python oracle and pinned here so a header // ABI change turns this binding's drift check red for conscious review. -export const PUBLIC_HEADER_HASH = "7df72bf9e667b8c2"; +export const PUBLIC_HEADER_HASH = "ae25d09c2b7b325b"; // === enum constants === export const TRANSCRIBE_OK = 0; @@ -100,6 +100,11 @@ export const TRANSCRIBE_STREAM_FAILED = 3; export const TRANSCRIBE_STREAM_COMMIT_AUTO = 0; export const TRANSCRIBE_STREAM_COMMIT_ON_FINALIZE = 1; export const TRANSCRIBE_STREAM_COMMIT_STABLE_PREFIX = 2; +export const TRANSCRIBE_NEMOTRON3_DIAR_PRESET_DEFAULT = 0; +export const TRANSCRIBE_NEMOTRON3_DIAR_PRESET_VERY_HIGH_LATENCY = 1; +export const TRANSCRIBE_NEMOTRON3_DIAR_PRESET_LOW_LATENCY = 2; +export const TRANSCRIBE_NEMOTRON3_DIAR_PRESET_VERY_LOW_LATENCY = 3; +export const TRANSCRIBE_NEMOTRON3_DIAR_PRESET_ULTRA_LOW_LATENCY = 4; export const TRANSCRIBE_SORTFORMER_PRESET_DEFAULT = 0; export const TRANSCRIBE_SORTFORMER_PRESET_VERY_HIGH_LATENCY = 1; export const TRANSCRIBE_SORTFORMER_PRESET_HIGH_LATENCY = 2; @@ -109,6 +114,8 @@ export const TRANSCRIBE_WHISPER_PROMPT_ALL_SEGMENTS = 1; // === macro constants (integer object-like macros) === export const TRANSCRIBE_EXT_KIND_MOONSHINE_STREAMING_STREAM = 1414746957; +export const TRANSCRIBE_EXT_KIND_NEMOTRON3_DIAR_RUN = 1380201294; +export const TRANSCRIBE_EXT_KIND_NEMOTRON3_DIAR_STREAM = 1396978510; export const TRANSCRIBE_EXT_KIND_PARAKEET_BUFFERED_STREAM = 1396853584; export const TRANSCRIBE_EXT_KIND_PARAKEET_STREAM = 1414744912; export const TRANSCRIBE_EXT_KIND_SORTFORMER_STREAM = 1414743635; @@ -133,6 +140,8 @@ export const STRUCT_LAYOUT: Record = { 'transcribe_token': { size: 48, align: 8, offsets: {'struct_size': 0, 'id': 8, 'p': 12, 't0_ms': 16, 't1_ms': 24, 'seg_index': 32, 'word_index': 36, 'text': 40} }, 'transcribe_speaker_segment': { size: 32, align: 8, offsets: {'struct_size': 0, 't0_ms': 8, 't1_ms': 16, 'speaker_id': 24, 'p': 28} }, 'transcribe_moonshine_streaming_stream_ext': { size: 24, align: 8, offsets: {'ext': 0, 'min_decode_interval_ms': 16} }, + 'transcribe_nemotron3_diar_run_ext': { size: 24, align: 8, offsets: {'ext': 0, 'preset': 16} }, + 'transcribe_nemotron3_diar_stream_ext': { size: 24, align: 8, offsets: {'ext': 0, 'preset': 16} }, 'transcribe_parakeet_stream_ext': { size: 24, align: 8, offsets: {'ext': 0, 'att_context_right': 16} }, 'transcribe_parakeet_buffered_stream_ext': { size: 32, align: 8, offsets: {'ext': 0, 'left_ms': 16, 'chunk_ms': 20, 'right_ms': 24} }, 'transcribe_sortformer_stream_ext': { size: 24, align: 8, offsets: {'ext': 0, 'preset': 16} }, @@ -178,6 +187,8 @@ export function defineTypes(koffi: any): Record { T['transcribe_token'] = koffi.struct({ struct_size: 'uint64_t', id: 'int', p: 'float', t0_ms: 'int64_t', t1_ms: 'int64_t', seg_index: 'int', word_index: 'int', text: 'char *' }); T['transcribe_speaker_segment'] = koffi.struct({ struct_size: 'uint64_t', t0_ms: 'int64_t', t1_ms: 'int64_t', speaker_id: 'int32_t', p: 'float' }); T['transcribe_moonshine_streaming_stream_ext'] = koffi.struct({ ext: T['transcribe_ext'], min_decode_interval_ms: 'int32_t' }); + T['transcribe_nemotron3_diar_run_ext'] = koffi.struct({ ext: T['transcribe_ext'], preset: 'int' }); + T['transcribe_nemotron3_diar_stream_ext'] = koffi.struct({ ext: T['transcribe_ext'], preset: 'int' }); T['transcribe_parakeet_stream_ext'] = koffi.struct({ ext: T['transcribe_ext'], att_context_right: 'int32_t' }); T['transcribe_parakeet_buffered_stream_ext'] = koffi.struct({ ext: T['transcribe_ext'], left_ms: 'int32_t', chunk_ms: 'int32_t', right_ms: 'int32_t' }); T['transcribe_sortformer_stream_ext'] = koffi.struct({ ext: T['transcribe_ext'], preset: 'int' }); @@ -243,6 +254,8 @@ export const FUNCTION_SIGNATURES: Record = { 'transcribe_n_speaker_segments': { ret: 'int', args: ['const struct transcribe_session *'] }, 'transcribe_n_tokens': { ret: 'int', args: ['const struct transcribe_session *'] }, 'transcribe_n_words': { ret: 'int', args: ['const struct transcribe_session *'] }, + 'transcribe_nemotron3_diar_run_ext_init': { ret: 'void', args: ['struct transcribe_nemotron3_diar_run_ext *'] }, + 'transcribe_nemotron3_diar_stream_ext_init': { ret: 'void', args: ['struct transcribe_nemotron3_diar_stream_ext *'] }, 'transcribe_open': { ret: 'transcribe_status', args: ['const char *', 'const struct transcribe_model_load_params *', 'const struct transcribe_session_params *', 'struct transcribe_session **'] }, 'transcribe_parakeet_buffered_stream_ext_init': { ret: 'void', args: ['struct transcribe_parakeet_buffered_stream_ext *'] }, 'transcribe_parakeet_stream_ext_init': { ret: 'void', args: ['struct transcribe_parakeet_stream_ext *'] }, diff --git a/bindings/typescript/src/ffi.ts b/bindings/typescript/src/ffi.ts index a02226db..233ec551 100644 --- a/bindings/typescript/src/ffi.ts +++ b/bindings/typescript/src/ffi.ts @@ -176,6 +176,12 @@ export function bindLibrary(libraryPath: string): Bound { sortformerStreamExtInit: lib.func("transcribe_sortformer_stream_ext_init", "void", [ outp(T.transcribe_sortformer_stream_ext), ]), + nemotron3DiarRunExtInit: lib.func("transcribe_nemotron3_diar_run_ext_init", "void", [ + outp(T.transcribe_nemotron3_diar_run_ext), + ]), + nemotron3DiarStreamExtInit: lib.func("transcribe_nemotron3_diar_stream_ext_init", "void", [ + outp(T.transcribe_nemotron3_diar_stream_ext), + ]), // batch (offline) runBatch: lib.func("transcribe_run_batch", "int", [ diff --git a/bindings/typescript/src/index.ts b/bindings/typescript/src/index.ts index 54a651e8..2000c35b 100644 --- a/bindings/typescript/src/index.ts +++ b/bindings/typescript/src/index.ts @@ -616,6 +616,28 @@ const FAMILY: Record = { init: "sortformerStreamExtInit", map: (o) => ({ preset: SORTFORMER_PRESET[o.preset as string] }), }, + nemotron3_diar: { + slot: "run", + kind: g.TRANSCRIBE_EXT_KIND_NEMOTRON3_DIAR_RUN, + type: "transcribe_nemotron3_diar_run_ext", + init: "nemotron3DiarRunExtInit", + map: (o) => ({ preset: NEMOTRON3_DIAR_PRESET[o.preset as string] }), + }, + nemotron3_diar_stream: { + slot: "stream", + kind: g.TRANSCRIBE_EXT_KIND_NEMOTRON3_DIAR_STREAM, + type: "transcribe_nemotron3_diar_stream_ext", + init: "nemotron3DiarStreamExtInit", + map: (o) => ({ preset: NEMOTRON3_DIAR_PRESET[o.preset as string] }), + }, +}; + +const NEMOTRON3_DIAR_PRESET: Record = { + default: g.TRANSCRIBE_NEMOTRON3_DIAR_PRESET_DEFAULT, + very_high_latency: g.TRANSCRIBE_NEMOTRON3_DIAR_PRESET_VERY_HIGH_LATENCY, + low_latency: g.TRANSCRIBE_NEMOTRON3_DIAR_PRESET_LOW_LATENCY, + very_low_latency: g.TRANSCRIBE_NEMOTRON3_DIAR_PRESET_VERY_LOW_LATENCY, + ultra_low_latency: g.TRANSCRIBE_NEMOTRON3_DIAR_PRESET_ULTRA_LOW_LATENCY, }; const SORTFORMER_PRESET: Record = { diff --git a/bindings/typescript/src/types.ts b/bindings/typescript/src/types.ts index 0a439518..376bcd44 100644 --- a/bindings/typescript/src/types.ts +++ b/bindings/typescript/src/types.ts @@ -261,6 +261,24 @@ export type SortformerPreset = export interface SortformerStreamOptions { preset?: SortformerPreset; } +/** Nemotron-3-Diarization operating point (model-card latency / accuracy + * bundle): "very_high_latency" 30.4 s (offline point, = "default"), + * "low_latency" 1.04 s, "very_low_latency" 0.64 s, "ultra_low_latency" + * 0.32 s. Smaller chunks cost more compute per audio second. */ +export type Nemotron3DiarPreset = + | "default" + | "very_high_latency" + | "low_latency" + | "very_low_latency" + | "ultra_low_latency"; +/** Nemotron-3-Diarization options. `kind: "nemotron3_diar"` is the run slot + * (whole file); `kind: "nemotron3_diar_stream"` is the stream slot + * (push-audio: segments cover all audio processed so far, finalize equals a + * whole-file run). A run produces speaker segments (up to 8 speakers), no + * text. */ +export interface Nemotron3DiarOptions { + preset?: Nemotron3DiarPreset; +} export type FamilyExtension = | ({ kind: "whisper" } & WhisperRunOptions) @@ -268,4 +286,6 @@ export type FamilyExtension = | ({ kind: "parakeet" } & ParakeetStreamOptions) | ({ kind: "parakeet_buffered" } & ParakeetBufferedStreamOptions) | ({ kind: "voxtral" } & VoxtralRealtimeStreamOptions) - | ({ kind: "sortformer" } & SortformerStreamOptions); + | ({ kind: "sortformer" } & SortformerStreamOptions) + | ({ kind: "nemotron3_diar" } & Nemotron3DiarOptions) + | ({ kind: "nemotron3_diar_stream" } & Nemotron3DiarOptions); diff --git a/bindings/typescript/test/common.mjs b/bindings/typescript/test/common.mjs index e26d09ca..0ff16e68 100644 --- a/bindings/typescript/test/common.mjs +++ b/bindings/typescript/test/common.mjs @@ -22,6 +22,8 @@ export const ITN_MODEL = // Voxtral realtime is local-only (~2.5 GB+, too heavy for the CI canary set): // its env var is NOT exported by fetch-canary, so this skips cleanly in CI. export const VOXTRAL_MODEL = process.env.TRANSCRIBE_SMOKE_VOXTRAL_MODEL || ""; +// Nemotron-3-Diarization (not exported by fetch-canary; skips in CI). +export const NEMOTRON3_DIAR_MODEL = process.env.TRANSCRIBE_SMOKE_NEMOTRON3_DIAR_MODEL || ""; // jfk.wav ships in-repo; fetch-canary exports only the model paths. export const AUDIO = diff --git a/bindings/typescript/test/family.test.mjs b/bindings/typescript/test/family.test.mjs index 92a15233..bd7c69bf 100644 --- a/bindings/typescript/test/family.test.mjs +++ b/bindings/typescript/test/family.test.mjs @@ -5,6 +5,7 @@ import { PARAKEET_STREAM_MODEL, PARAKEET_BUFFERED_MODEL, VOXTRAL_MODEL, + NEMOTRON3_DIAR_MODEL, jfk, feedChunks, } from "./common.mjs"; @@ -91,3 +92,22 @@ modelTest("voxtral realtime stream extension", VOXTRAL_MODEL, async () => { m.dispose(); } }); + +modelTest("nemotron3_diar run + stream preset extensions", NEMOTRON3_DIAR_MODEL, async () => { + const m = await TranscribeModel.load(NEMOTRON3_DIAR_MODEL); + try { + assert.equal(m.accepts({ kind: "nemotron3_diar" }), true); + assert.equal(m.accepts({ kind: "nemotron3_diar_stream" }), true); + assert.equal(m.accepts({ kind: "sortformer" }), false); + const r = await m.transcribe(jfk(), { family: { kind: "nemotron3_diar", preset: "low_latency" } }); + assert.ok(r.speakerSegments.length > 0); + const s = m.createSession(); + const stream = await s.stream({ family: { kind: "nemotron3_diar_stream", preset: "low_latency" } }); + await feedChunks(stream, jfk()); + await stream.finalize(); + stream.reset(); + s.dispose(); + } finally { + m.dispose(); + } +}); diff --git a/docs/environment-variables.md b/docs/environment-variables.md index 5d6927fa..af25b2db 100644 --- a/docs/environment-variables.md +++ b/docs/environment-variables.md @@ -30,6 +30,11 @@ of tests. | `TRANSCRIBE_CONV_DIRECT_PW` / `TRANSCRIBE_CONV_NO_DIRECT_PW` | Force the pointwise-conv dispatch to direct `mul_mat` / im2col, overriding the backend default. | | `TRANSCRIBE_DUMP_DIR=` | Enable the per-stage tensor dumper; writes `.f32` + `.json` per dumped tensor into ``. The basis for the numerical-comparison harness (`scripts/compare_tensors.py`). | | `TRANSCRIBE_PERF_DEBUG` | Print a per-stage timing breakdown to stderr (DEBUG log) on the families that profile (`cohere`, `granite`, `canary`, `canary_qwen`, `moonshine`, `moonshine_streaming`, `moss`, `qwen3_asr`, `whisper`). For whisper, a value containing `cpu` or `all` additionally prints the CPU sub-section breakdown. | +| `TRANSCRIBE_NEMOTRON3_DIAR_PRESET=` | nemotron3_diar: force the streaming operating point (`very_high_latency`, `low_latency`, `very_low_latency`, `ultra_low_latency`, or the validation-only `small`) over the run/stream extension. Used by `scripts/validate.py` and `scripts/diar/run_cpp_nemotron3_diar.py`. | +| `TRANSCRIBE_NEMOTRON3_DIAR_NATIVE_BF16` | nemotron3_diar on CPU: keep the BF16 matmul weights instead of the default load-time F32 upcast. ggml-cpu then rounds activations to BF16, which flips speaker-cache selections vs the fp32 reference (see `tests/tolerances/nemotron3_diar.json`). Diagnostic. | +| `TRANSCRIBE_NEMOTRON3_DIAR_F32_WEIGHTS` | nemotron3_diar: force the F32 weight upcast on non-CPU backends too. | +| `TRANSCRIBE_NEMOTRON3_DIAR_COMPRESS_FROM_REF=` | nemotron3_diar, only with `TRANSCRIBE_DUMP_DIR`: take each speaker-cache compression's selected frames from the reference dumper's `compress.NNN.{topk_indices,is_disabled}.npy` in `` (validation isolation of the discontinuous top-k; set by `scripts/validate.py`). | +| `TRANSCRIBE_NEMOTRON3_DIAR_ENCODER_DUMP` / `TRANSCRIBE_NEMOTRON3_DIAR_COMPRESS_DUMP` | nemotron3_diar, only with `TRANSCRIBE_DUMP_DIR`: dump the first streaming step's encoder-stage tensors / every speaker-cache compression (`compress.NNN.*`) for parity with the reference dumper. | | `TRANSCRIBE_VOXTRAL_REALTIME_STREAM_TIMING` | Print a per-component streaming wall-time breakdown at stream finalize (voxtral_realtime). | | `TRANSCRIBE_TEST_DEV_INIT_THROW=` | Fault injection: backend device init (`ggml_backend_dev_init`) throws for devices whose name contains `` (`*` matches every device). Exercises throw → skip → CPU-fallback in backend probing; an explicit backend request fails with `TRANSCRIBE_ERR_BACKEND`. Used by `backend_init_throw_unit` and `scripts/ci/vulkan_degradation_check.py`. | | `TRANSCRIBE_TEST_TEARDOWN_THROW` | Fault injection: any non-empty value injects a throw after each real free inside the `transcribe::safe_*` teardown wrappers, proving containment without leaking the handle. Used by `teardown_safety_unit`. | @@ -85,6 +90,7 @@ its var is unset. Convention: `TRANSCRIBE__GGUF`. | `TRANSCRIBE_GIGAAM_GGUF` | `gigaam_workspace_release_smoke` | | `TRANSCRIBE_MULTITALKER_BUNDLE_GGUF` | `parakeet_multitalker_e2e_smoke` | | `TRANSCRIBE_SORTFORMER_GGUF` | `sortformer_stream_ext_unit` | +| `TRANSCRIBE_NEMOTRON3_DIAR_GGUF` | `nemotron3_diar_stream_unit` | | `TRANSCRIBE_COHERE_GGUF` | `cohere_real_smoke`, `cohere_e2e_smoke` | | `TRANSCRIBE_GRANITE5_CTC_GGUF` | `granite5_ctc_real_smoke`, `granite5_ctc_e2e_smoke` | | `TRANSCRIBE_WHISPER_GGUF` | `whisper_e2e_smoke`, `whisper_tokenize_parity` | diff --git a/docs/extension-kinds.md b/docs/extension-kinds.md index cb8dd94d..c779d190 100644 --- a/docs/extension-kinds.md +++ b/docs/extension-kinds.md @@ -48,6 +48,8 @@ parameter; existing kinds keep their slot for life. | `0x4E524857` | `WHRN` | `RUN` | `TRANSCRIBE_EXT_KIND_WHISPER_RUN` | whisper | `include/transcribe/whisper.h` | | `0x54535256` | `VRST` | `STREAM` | `TRANSCRIBE_EXT_KIND_VOXTRAL_REALTIME_STREAM` | voxtral_realtime | `include/transcribe/voxtral_realtime.h` | | `0x54534653` | `SFST` | `RUN` | `TRANSCRIBE_EXT_KIND_SORTFORMER_STREAM` | sortformer | `include/transcribe/sortformer.h` | +| `0x5244334E` | `N3DR` | `RUN` | `TRANSCRIBE_EXT_KIND_NEMOTRON3_DIAR_RUN` | nemotron3_diar | `include/transcribe/nemotron3_diar.h` | +| `0x5344334E` | `N3DS` | `STREAM` | `TRANSCRIBE_EXT_KIND_NEMOTRON3_DIAR_STREAM` | nemotron3_diar | `include/transcribe/nemotron3_diar.h` | Empty rows reserved for future allocations: diff --git a/docs/input-limits.md b/docs/input-limits.md index e53123f8..1a2258d3 100644 --- a/docs/input-limits.md +++ b/docs/input-limits.md @@ -67,6 +67,15 @@ constant-memory caches (`cache_last_channel` / `cache_last_time` + the decoder LSTM state) rather than a growing KV, so it is unbounded. These families do not need and do not have a length gate. +nemotron3_diar (Nemotron-3-Diarization) is also unbounded (`max_audio_ms = 0`, +**ignores `n_ctx`**). Every streaming step re-encodes a bounded window: speaker +cache + FIFO + chunk + lookahead (at most 264 + 264 + 13 = 541 encoder frames at +`low_latency`; 264 + 40 + 380 = 684 at `very_high_latency`), so per-step compute +and graph memory are constant in audio length. Only the stored mel and the +10 ms output probabilities (8 floats per 10 ms, ~11.5 MB per hour) grow with the +input. Input shorter than one hop (160 samples) returns `TRANSCRIBE_OK` with an +empty result (0 frames, no speaker rows), matching NeMo's floor(n / 160) framing. + ### 2. Hard context cap — reject up front | Families | Limit source | Behavior | diff --git a/docs/porting/families/nemotron3_diar.md b/docs/porting/families/nemotron3_diar.md new file mode 100644 index 00000000..57eee957 --- /dev/null +++ b/docs/porting/families/nemotron3_diar.md @@ -0,0 +1,237 @@ +# Nemotron3 Diarization + +Status: ported, Stage 4 complete pending tolerance review (Stage 1 intake 2026-09-24; Stage 2 oracle, Stage 3 BF16 GGUF, Stage 4 C++ 2026-09-25) + +Intake signed off 2026-09-24: capability scope approved as drafted, with +live push-audio diarization added as MUST PASS. Acceptance set = AMI Test +MHM forced-alignment (primary) + NOTSOFAR1 Eval MHM (5-8 speaker +coverage); DER + JER vs the measured NeMo reference, plus prob-tensor +parity. Reference dtype BF16. License (`openmdw-1.1`) still to be +interpreted before ship. + +Nemotron 3 Diarization is a **frame-level end-to-end neural speaker +diarizer** (architecture pattern `encoder-diarizer`) in the Streaming +Sortformer lineage. It is NOT a transcription model: it consumes 16 kHz +mono audio and emits a `T x 8` matrix of per-frame per-speaker activity +probabilities. There is one row per **10 ms** frame, up to 8 speakers, and +the columns are in speaker arrival order. It runs online with the same +Arrival-Order Speaker Cache (AOSC) + FIFO scheme as `sortformer`. + +It is a separate family from `sortformer` (v2.1), not a new variant of it. +The two share the AOSC/FIFO streaming algorithm, but the forward graph is +different: + +| | `sortformer` (v2.1) | `nemotron3_diar` | +| --- | --- | --- | +| Encoder | 17L FastConformer (dw_striding x8, rel_pos) + 18L post-LN Transformer (d=192) | 31L pre-LN Transformer (d=512), RoPE, feature stacking x8 | +| Head | 2 FF -> sigmoid at 80 ms | proj 512->192 -> subpixel Conv1d x8 -> 2 FF -> sigmoid at 10 ms | +| Speakers | 4 | 8 | +| Cache silence | running mean | learned embedding | +| Storage dtype | F32 | BF16 | + +## Identity + +- Family key: `nemotron3_diar` +- Upstream architecture string: `nemo.collections.asr.models.sortformer_diar_models.SortformerEncLabelModel` (NeMo); `Nemotron3DiarizationForAudioFrameClassification` (HF transformers) +- Architecture pattern: `encoder-diarizer` +- Hugging Face repo: `nvidia/Nemotron-3-Diarization` +- Hugging Face revision: `f667ed73aee57d40cc39428eb768b4fd87a0a29e` +- License: `openmdw-1.1` (needs human interpretation before ship) +- Variants: + - `Nemotron-3-Diarization` — this port. Reference dtype **BF16**: the `.nemo` state_dict is all BF16, and the HF `model.safetensors` F32 is a verified lossless upcast. + +## References + +- Canonical reference: NeMo Speech `SortformerEncLabelModel.restore_from(Nemotron-3-Diarization.nemo)` + `sortformer_modules.SortformerModules` + `modules.transformer_encoder.TransformerEncoder` (`refs/NVIDIA-NeMo/Speech`, nemo 3.x). The existing `scripts/envs/sortformer` NeMo 2.x pin cannot load this encoder. +- Instrumented reference: `scripts/envs/nemotron3_diar/` (to be created at Stage 2, pinned to NVIDIA-NeMo/Speech). +- Cross-check references: + - HF transformers `models/nemotron3_diarization/modular_nemotron3_diarization.py` (transformers main, 5.18.0.dev0; strict-load converter from the `.nemo`). + - Sortformer paper: https://arxiv.org/abs/2409.06656 + - Streaming Sortformer (AOSC): https://arxiv.org/abs/2507.18446 + - NeMo eval script: `examples/speaker_tasks/diarization/neural_diarizer/e2e_diarize_speech.py` + - C++ sibling: `src/arch/sortformer/stream.cpp` (AOSC/FIFO bookkeeping) + +## Commands + +Oracle audio (deterministic 8-speaker mix, committed): + +```bash +uv run --project scripts/envs/nemotron3_diar scripts/gen_nemotron3_diar_oracle_audio.py +# -> samples/nemotron3-diar-8spk-mix.wav + tests/golden/nemotron3_diar/nemotron3-diar-8spk-mix.rttm +``` + +Reference dumps (NeMo Speech, via scripts/envs/nemotron3_diar; bit-deterministic): + +```bash +BASE=build/validate/nemotron3_diar/Nemotron-3-Diarization/nemotron3-diar-8spk-mix +REF="--model nvidia/Nemotron-3-Diarization --revision f667ed73aee57d40cc39428eb768b4fd87a0a29e --audio samples/nemotron3-diar-8spk-mix.wav" +D="uv run --project scripts/envs/nemotron3_diar scripts/dump_reference_nemotron3_diar_nemo.py" +$D encoder $REF --out $BASE/encoder/ref # first streaming step, per-stage +$D diarize --preset very_high_latency $REF --out $BASE/diarize/ref --dump-compress $BASE/diarize/compress +for P in low_latency very_low_latency ultra_low_latency small; do + $D diarize --preset $P $REF --out $BASE/diarize-$P/ref --dump-compress $BASE/diarize-$P/compress +done +# HF transformers cross-check (not the gate): hf-offline, hf-stream --mode _latency +``` + +Acceptance baselines (DER + JER, forced-alignment RTTMs, collar 0, overlap included, +onset/offset 0.5 with no post-processing filter = the model-card protocol): + +```bash +uv run scripts/diar/ingest_ami.py --config ihm --split test +uv run scripts/diar/fetch_ami_forced_alignment.py --config ihm --split test +uv run scripts/diar/ingest_notsofar.py # 80 eval meetings, close-talk mix +uv run python -c "import json; open('samples/diar/notsofar-mhm-eval-fa-5plus.manifest.jsonl','w').writelines(l for l in open('samples/diar/notsofar-mhm-eval-fa.manifest.jsonl') if json.loads(l)['num_speakers'] >= 5)" # 45 meetings +V=Nemotron-3-Diarization +for DS in ami-ihm-test-fa notsofar-mhm-eval-fa; do + uv run --project scripts/envs/nemotron3_diar scripts/diar/run_reference_nemotron3_diar_nemo.py \ + --manifest samples/diar/$DS.manifest.jsonl --model nvidia/Nemotron-3-Diarization \ + --revision f667ed73aee57d40cc39428eb768b4fd87a0a29e --preset very_high_latency \ + --pred-dir reports/diar/pred/$V-REF-$DS-very_high_latency \ + --out reports/diar/$V-REF.$DS.very_high_latency.jsonl + uv run scripts/diar/score_der.py --manifest samples/diar/$DS.manifest.jsonl \ + --pred-dir reports/diar/pred/$V-REF-$DS-very_high_latency \ + --out reports/diar/$V-REF.$DS.very_high_latency.score.json +done +``` + +Conversion: + +```bash +uv run --project scripts/envs/nemotron3_diar \ + scripts/convert-nemotron3_diar.py nvidia/Nemotron-3-Diarization \ + --repo-id nvidia/Nemotron-3-Diarization \ + --revision f667ed73aee57d40cc39428eb768b4fd87a0a29e +# -> models/Nemotron-3-Diarization/Nemotron-3-Diarization-BF16.gguf (arch `nemotron3_diar`) +# manifest: reports/convert/Nemotron-3-Diarization-BF16.json +``` + +Validation: + +```bash +# Tensor parity: 2 cases x (encoder step 0 + diar.probs at 5 presets + push-audio at 5 presets) +VALIDATE_CPP_THREADS=8 uv run scripts/validate.py all --family nemotron3_diar --variant Nemotron-3-Diarization --backend cpu +# VALIDATE_NEMOTRON3_DIAR_PRESETS=low_latency,small restricts presets +# VALIDATE_NEMOTRON3_DIAR_STREAM_MS=173 push-audio piece size (0 = skip push-audio stages) + +# Push-audio / extension contract (real model) +TRANSCRIBE_NEMOTRON3_DIAR_GGUF=models/Nemotron-3-Diarization/Nemotron-3-Diarization-BF16.gguf \ + build/bin/transcribe_nemotron3_diar_stream_unit + +# Batch: segment parity vs serial + frozen golden, and bit-exact diar.probs +uv run scripts/batch_parity.py --model models/Nemotron-3-Diarization/Nemotron-3-Diarization-BF16.gguf \ + --list tests/golden/batch/Nemotron-3-Diarization.list --batch-sizes 2,4,8 --backend cpu --speakers \ + --golden-in tests/golden/batch/Nemotron-3-Diarization.cpu.json +uv run --with numpy scripts/batch_tensor_parity.py --model models/Nemotron-3-Diarization/Nemotron-3-Diarization-BF16.gguf \ + --list tests/golden/batch/Nemotron-3-Diarization.list --batch 8 --backend cpu --dump-name diar.probs + +# DER/JER gate: C++ over the acceptance sets, same post-processing as the reference +V=Nemotron-3-Diarization; DS=ami-ihm-test-fa; P=very_high_latency +uv run --project scripts/envs/nemotron3_diar scripts/diar/run_cpp_nemotron3_diar.py \ + --manifest samples/diar/$DS.manifest.jsonl --gguf models/$V/$V-BF16.gguf --preset $P \ + --pred-dir reports/diar/pred/$V-BF16-$DS-$P --ref-pred-dir reports/diar/pred/$V-REF-$DS-$P \ + --out reports/diar/$V-BF16.$DS.$P.jsonl +uv run scripts/diar/score_der.py --manifest samples/diar/$DS.manifest.jsonl \ + --pred-dir reports/diar/pred/$V-BF16-$DS-$P --out reports/diar/$V-BF16.$DS.$P.score.json +``` + +Benchmarks: + +```bash +TODO +``` + +## Capability Validation + +> NOTE (non-standard family, same substitution as `sortformer`): the +> template's forced transcription rows are `OUT OF SCOPE — not a +> transcription model`. The obligated rows are the diarization ones +> instead: streaming diarization, offline diarization, and the raw +> activity tensor. These substitutions were user-signed at intake (2026-09-24). +> +> Streaming covers both paths: whole-file streaming via `transcribe_run` +> + the preset run extension (the v2.1 path), and live push-audio input +> via `transcribe_stream_*` (new for diarizers; user-requested +> 2026-09-24). + +Preset geometry (spkcache/fifo/chunk/rc/update, 80 ms frames), from the +model card: very_high_latency 30.4 s = 264/40/340/40/300; low_latency +1.04 s = 264/264/9/4/222; very_low_latency 0.64 s = 264/264/6/2/222; +ultra_low_latency 0.32 s = 264/264/3/1/222. + +| Capability | Mode | Command / test | Expected observable | Target | Status | +|------------|------|----------------|---------------------|--------|--------| +| Transcribe (explicit language) | n/a | n/a | model produces no text | OUT OF SCOPE — not a transcription model | SKIP — not exposed by runtime (no text output) | +| Transcribe (auto / no hint) | n/a | n/a | model produces no text | OUT OF SCOPE — not a transcription model | SKIP — not exposed by runtime (no text output) | +| Streaming diarization | online AOSC/FIFO, `low_latency` 1.04 s | port vs NeMo reference at the preset, multi-chunk with compression exercised | 10 ms prob-tensor parity; DER + JER within tolerance of the measured reference on the acceptance set | MUST PASS | PASS — low_latency diar.probs 1.3e-5 vs NeMo on both oracle cases; AMI low_latency DER 9.530% / JER 13.103% vs reference 9.529% / 13.104% (16 meetings; +0.001pp / -0.001pp) | +| Offline diarization | `very_high_latency` 30.4 s | port vs NeMo reference | prob-tensor parity; DER + JER within tolerance of the measured reference on AMI | MUST PASS | PASS — diar.probs 1.1e-5 vs NeMo on both oracle cases; AMI DER 9.212% / JER 12.886% = reference 9.212% / 12.886% (16 meetings) | +| Speaker-activity tensor | `include_tensor_outputs` | T x 8 sigmoid probs at 10 ms, port vs reference on the same audio | max-abs-diff within tolerance, arrival-order columns aligned | MUST PASS | PASS — `diar.probs` [T x 8] at every preset within 1.8e-5 (tolerance 9.9e-5), columns arrival-order aligned; encoder-stage tensors 39/39 | +| 5-8 speakers | any preset | 8-speaker oracle `nemotron3-diar-8spk-mix` (all 8 channels active at every card preset) + NOTSOFAR eval MHM >=5-spk subset (`notsofar-mhm-eval-fa-5plus`, 45 meetings) | channels 5-8 active and prob parity holds; DER vs measured reference | MUST PASS | PASS — 8-speaker oracle: all 8 channels, prob parity 1.1e-5..1.8e-5 at every preset; NOTSOFAR 5-7 spk DER 12.135% / JER 15.858% = reference (45 meetings); full NOTSOFAR 9.853% / 12.809% = reference (80) | +| Low-latency presets | `very_low_latency` 0.64 s, `ultra_low_latency` 0.32 s | port vs NeMo reference at each preset | prob-tensor parity on the oracle clip | MUST PASS | PASS — very_low_latency diar.probs 1.4e-5, ultra_low_latency 8.5e-6 on both oracle cases | +| Output-resolution override | `output_subsampling_factor` > 1 (e.g. 80 ms) | n/a | coarser output grid | OUT OF SCOPE — native 10 ms only; downsampling is post-processing. Back in scope if a consumer needs a coarser public grid | SKIP — not exposed by runtime (native 10 ms only) | +| Multitalker interop | feed diar supervision to parakeet multitalker (8 spk) | Nemotron3 diar T x 8 drives the multitalker speaker kernel | speaker-attributed transcript | OUT OF SCOPE — follow-up port; `multitalker.cpp` hard-binds `sortformer::SortformerEmbedded`, 4 speakers and 80 ms | SKIP — not exposed by runtime (follow-up port) | +| Push-audio live diarization | `transcribe_stream_begin/feed/finalize` + a new STREAM-slot ext kind taking the preset | feed the oracle clip in small audio pieces at each streaming preset | incremental speaker segments during feed; final segments and 10 ms probs match the reference streaming runner within tolerance | MUST PASS | PASS — `transcribe_nemotron3_diar_stream_unit` (rows during feed, open turns extend, finalize == run at LL/VHL on both clips); validate.py stream- diar.probs bit-identical to the whole-file run at all 5 presets x 2 clips | +| Translation | n/a | n/a | n/a | OUT OF SCOPE — not a transcription model | SKIP — not exposed by runtime | +| Timestamps (transcription) | n/a | n/a | segment times are intrinsic to diarization output | OUT OF SCOPE — not a transcription model | SKIP — not exposed by runtime | +| Batch (offline) | run_batch vs serial | `scripts/batch_parity.py` compares hypothesis text only, so it can't check a text-less model as-is; needs a speaker-segment/tensor comparison (v2.1 resolved this row as a user-approved deferral) | identical speaker segments + CPU tensor parity, or `ACCEPTED GAP — serial fallback` | MUST PASS | PASS — explicit lockstep `run_batch`; `batch_parity.py --speakers` identical at 2/4/8 + golden `tests/golden/batch/Nemotron-3-Diarization.cpu.json`; `batch_tensor_parity.py --dump-name diar.probs` bit-exact (8 recordings). CPU throughput unchanged vs serial (compute-bound; see Notes); acceptance sets at batch 8 (30.4 s): all 96 meetings bit-identical to batch 1 (max|d| 0), DER/JER unchanged (AMI 9.212% / 12.886%, NOTSOFAR 9.853% / 12.809%) | + +## Reference baselines (Stage 2 Oracle) + +Measured NeMo reference (NeMo Speech `cf724ac337d1`, BF16 .nemo, fp32 on CPU; +forced-alignment RTTMs, collar 0, overlap included, onset/offset 0.5 with no +post-processing filter). These are the gates, not the card numbers. + +| Dataset | Preset | Meetings | REF DER | REF JER | Card DER | Spk count correct | C++ DER / JER (Stage 4) | +| --- | --- | ---: | ---: | ---: | ---: | ---: | --- | +| AMI test MHM (`ami-ihm-test-fa`) | very_high_latency | 16 | 9.21% | 12.89% | 9.25% | 14/16 (card SCA 87.50%) | 9.212% / 12.886% (= ref) | +| AMI test MHM | low_latency | 16 | 9.53% | 13.10% | 9.48% | 13/16 (card SCA 81.25%) | 9.530% / 13.103% (ref 9.529% / 13.104%) | +| NOTSOFAR eval MHM, all (`notsofar-mhm-eval-fa`) | very_high_latency | 80 | 9.85% | 12.81% | 6.77% | 78/80 (card 93.75%) | 9.853% / 12.809% (= ref) | +| NOTSOFAR eval MHM, 3-4 spk | very_high_latency | 35 | 6.71% | 7.04% | 5.25% | | | +| NOTSOFAR eval MHM, 5-7 spk (`-5plus`) | very_high_latency | 45 | 12.14% | 15.86% | 7.86% | | 12.135% / 15.858% (= ref) | + +AMI reproduces the card (MHM == the ingested `ihm` mix-headset condition). +NOTSOFAR MHM runs 1.3-1.5x the card. NVIDIA does not publish its MHM mixing +recipe; ours is the plain zero-padded sum of the close-talk channels. The +reference setup itself is sound. The meeting set matches the card exactly +(35 + 45 meetings x 2 devices = the card's 70 + 90 recordings), speaker +counting is 78/80 correct, and a one-off check on the single-channel +condition (first `sc_*` device per meeting, no mixing) scores DER 9.85% +over 80 meetings (card SC 11.00%) and 11.55% on the 5-7 speaker subset +(card 13.21%). So the MHM gap comes from the mix: MHM false alarms are 4.47% +vs 2.50% on SC, consistent with headset crosstalk being summed in. The gate +compares reference and C++ on this same MHM audio, so the mix only has to be +fixed. + +8-speaker oracle (`samples/nemotron3-diar-8spk-mix.wav`, 92 s, generated by +`scripts/gen_nemotron3_diar_oracle_audio.py`): every card preset finds 8 +active channels, and authored speakers A..H map to channels 0..7 in arrival +order; returning speakers keep their channel. uk-long.wav was rejected as +speaker H because the reference merges it into speaker E's channel. + +## Notes + +### Stage 4 (C++ port) notes + +- Implementation: `src/arch/nemotron3_diar/` (model.cpp load / step graph / chunk driver / run / run_batch / push-audio; stream.cpp host AOSC state machine + incremental segments; torch_logf.h). Public API: `include/transcribe/nemotron3_diar.h` — preset enum + `N3DR` (RUN slot) / `N3DS` (STREAM slot) extensions; CLI `--diar-preset`. Typed wrappers in the Python / TypeScript / Rust / Swift bindings. Forward map: `reports/porting/nemotron3_diar/forward-map.md`. +- **CPU compute is fp32.** The loader upcasts every BF16/F16 matmul weight to an exact F32 copy on CPU (+~400 MB RAM; the GGUF stays BF16). ggml-cpu's BF16 matmul rounds activations to BF16, which moved `enc.pre_encode.out` by 2.6e-1 and flipped 43 speaker-cache picks at compression #1 on the oracle; F32 matches NeMo to 1e-5 and is ~7x faster on this CPU. `TRANSCRIBE_NEMOTRON3_DIAR_NATIVE_BF16=1` opts out. GPU backends keep BF16 (Stage 6 measures them). +- **Compression is reproduced bit-for-bit against torch CPU.** Boundary scores in the top-k are routinely ~1e-7 apart or exactly tied, so the host code mirrors ATen's `topk(sorted=False)` (libc++ partial_sort / nth_element on (value, index) pairs), torch's Sleef `logf_u10`, and ATen's 4-accumulator 8-term sum. Gate: `scripts/diar/check_nemotron3_diar_compress.py` replays NeMo on the port's own inputs (370/370 compressions identical: every oracle preset x both clips, a 39-min AMI meeting at 30.4 s, and the 46-min TS3003d at 1.04 s - the one 1.04 s meeting whose run diverged). This matches the reference build used here (macOS arm64, torch 2.14, libc++, NEON). A Linux/x86 reference (libstdc++ tie order, AVX2 8-lane sums) could select differently on exact ties; the DER gate is unaffected, and the replay gate would catch it. +- **Residual divergence is fp32 GEMM noise, not a bug.** ~3e-6 input noise can move a near-tied boundary score across a ~1e-5 gap and flip one pick, after which the run differs (6 of 96 acceptance meetings at 30.4 s; the 0.64 s oracle unforced). NeMo run with different thread counts shows the same 1e-5 noise. Tensor validation therefore injects the reference's picks (forced-picks gate, like reference-mel injection) and gates selection by replay and end to end by DER/JER. +- Input / memory contract (`docs/input-limits.md`, bucket 1): no length cap (`max_audio_ms = 0`), `n_ctx` ignored, per-step compute/memory bounded by spkcache + FIFO + chunk + lookahead; only the stored mel and 10 ms probs grow with length. Input under one hop (160 samples) is an empty OK result on run / push-audio / batch (NeMo: 0 frames). Short clips match NeMo to ~1e-7 (160 / 400 / 8000 samples). Abort is polled per chunk (`TRANSCRIBE_ERR_ABORTED`). All pinned in `nemotron3_diar_stream_unit`. +- Frames: NeMo emits floor(n / 160) mel frames (1,471,999 samples -> 9199), not ceil; the driver consumes exactly those, so run, push-audio finalize and batch all return NeMo's frame count. +- Push-audio: mel frames are computed incrementally from a PCM segment starting 2 hops before the first new frame (normalize=none makes frames independent), and a chunk runs once its full window (chunk + lookahead) is final; finalize runs the tail with NeMo's end-of-audio clamps. Output is bit-identical to `transcribe_run` at the same preset. +- Batch: `run_batch` advances recordings in lockstep (in sync streaming the cache/FIFO geometry depends only on the step index), batching steps with identical geometry. Output is bit-identical to serial; on CPU it is no faster (7.1 s vs 6.5 s for 8 recordings at 30.4 s; 135 s vs 138 s at 1.04 s) because the per-step encode is already compute-bound. GPU throughput is a Stage 6 question. +- Throughput (CPU, M4 Max, 12 threads): 30.4 s preset RTF ~0.02; 1.04 s preset RTF ~0.40 (every 0.72 s chunk re-encodes ~540 cache+FIFO+chunk frames through 31 layers). NeMo's CPU reference: RTF 0.25 at 1.04 s. Stage 6 owns tuning. +- Validation hooks (not compile-gated, following the sortformer precedent so `validate.py` works on a normal build; dump-only ones are inert without `TRANSCRIBE_DUMP_DIR`): see `docs/environment-variables.md` (`TRANSCRIBE_NEMOTRON3_DIAR_*`). + +- Intake: `reports/porting/nemotron3_diar/Nemotron-3-Diarization/intake.json` (the `known_risks` list there is the authoritative risk inventory). +- NVIDIA ships `Nemotron-3-Diarization.q8_0.gguf` for NeMo-Speech.cpp with `general.architecture = sortformer`. It is not consumed here. Our GGUF uses a distinct arch string. +- The `.nemo` `sortformer_modules` streaming values (fifo 0, chunk 264, rc 0) are training-time settings. Runtime defaults come from the card's preset table. +- Checkpoint tensors not used by the forward, which the converter drops: `sortformer_modules.hidden_to_spks.*` (legacy head, frozen, never called) and `sortformer_modules.activity_head.*` (training-only aux head). +- `preprocessor.featurizer.{window,fb}` are stored in **BF16**, and NeMo's reference mel uses them. The converter embeds them as `frontend.window` [400] and `frontend.mel_filterbank` [128, 257] (F32 storage, BF16-exact values), so the C++ mel must read them rather than recompute. Recomputing in fp32 diverges from NeMo by the BF16 rounding (window max |diff| 1.9e-3). +- Conversion (Stage 3) decisions: + - GGUF arch `nemotron3_diar`, KV prefix `stt.nemotron3_diar.*`. The streaming defaults written to the GGUF are the card's very_high_latency preset (264/40/340/40/300), not the `.nemo` training values. The AOSC compression constants (`sil_threshold`, boost rates, etc.) come from the `.nemo` `sortformer_modules`. + - The fused `attn.w_qkv` stays fused as `enc.blocks.{i}.attn.qkv.weight` [1536, 512], rows `[q | k | v]`, each head-major (NeMo views it as `(T, 3, H, D)`). + - Names: `norm1/norm2` -> `norm_1/norm_2`, `embed_norm` -> `enc.embed.norm`, `ffn.net.{0,3}` -> `ff.{in,out}`, `first_hidden_to_hidden` -> `diar.fc1`, `single_hidden_to_spks` -> `diar.single_spk_head`, `subpixel_upsample` -> `diar.upsample.conv`, `learnable_sil_emb` -> `diar.sil_emb`. + - Storage: Linear weights BF16 (bit-exact); biases, LayerNorms, `diar.sil_emb` and frontend buffers F32 (lossless upcast). `diar.upsample.conv.weight` is F16 because the loader has no BF16 conv kernel (voxtral precedent). BF16 -> F16 rounds only its sub-6e-5 weights, max |diff| 2.98e-8. + - New quant-policy rule: `diar.sil_emb` -> Norm (F32), added to both `reference_dtype_for` and `policy.cpp::classify_tensor`, and pinned in `test_quant_policy_sync.py`. +- Frontend: symmetric Hann window (`periodic=False`), STFT `center=True` with zero (`constant`) padding, preemphasis 0.97, `normalize=NA`, dither 1e-5. diff --git a/docs/porting/families/sortformer.md b/docs/porting/families/sortformer.md index 118c2014..1228f71a 100644 --- a/docs/porting/families/sortformer.md +++ b/docs/porting/families/sortformer.md @@ -45,21 +45,6 @@ dependency of the Parakeet multitalker speaker-attributed ASR path - `diar_streaming_sortformer_4spk-v2.1` — this port (best meeting DER; multitalker's named dependency). - `streaming-4spk-v2` — architecturally identical, CC-BY-4.0. Deferred; add by dropping in weights once v2.1 is validated. -### Diarization scope policy - -Sortformer is the repo's first diarization-only family. transcribe.cpp -remains a transcription library; diarization-only models are in scope only -when they (a) feed a transcription pipeline in-repo (here: the named -`spk_supervision='diar'` dependency of parakeet multitalker speaker- -attributed ASR), or (b) reuse an encoder family the repo already maintains -(here: the NEST FastConformer is parakeet's ConformerEncoder, reused -verbatim). A diarizer meeting neither clause (e.g. a pyannote -segmentation+clustering port: new architecture, new dependency surface, no -in-repo ASR consumer) is out of scope. Standalone diarization output is -exposed because it falls out of the multitalker dependency for free, via -the pre-existing transcript-independent `transcribe_speaker_segment` ABI — -no diarizer-specific output surface was added. - ## Public API (run extension) `include/transcribe/sortformer.h` — `TRANSCRIBE_EXT_KIND_SORTFORMER_STREAM` diff --git a/examples/cli/main.cpp b/examples/cli/main.cpp index cce1a200..84d95c04 100644 --- a/examples/cli/main.cpp +++ b/examples/cli/main.cpp @@ -6,6 +6,7 @@ // Run with --help for the full option list. #include "transcribe.h" +#include "transcribe/nemotron3_diar.h" #include "transcribe/parakeet.h" #include "transcribe/voxtral_realtime.h" #include "transcribe/whisper.h" @@ -281,6 +282,11 @@ struct cli_args { // --stream-voxtral-delay N. Ignored when stream_chunk_ms == 0 or when // the model is not voxtral_realtime. int stream_voxtral_delay = -1; + // Nemotron-3-Diarization operating point (transcribe_nemotron3_diar_preset; + // 0 = model default). Applied as the RUN-slot extension for a whole-file + // run and as the STREAM-slot extension with --stream-chunk-ms. Set by + // --diar-preset NAME. + int diar_preset = 0; // Speculative-decode draft length passed through to // transcribe_run_params::spec_k_drafts on the offline path. -1 = family // default (each family picks its tuned K). 0 = explicitly off. >0 = @@ -344,6 +350,9 @@ void print_usage(const char * argv0) { " --stream-buf-right-ms N (parakeet-unified buffered streaming)\n" " right-context (lookahead) size in ms;\n" " -1 = model default\n" + " --diar-preset NAME (nemotron3_diar) streaming operating point:\n" + " very_high_latency | low_latency |\n" + " very_low_latency | ultra_low_latency\n" " --stream-voxtral-delay N (voxtral_realtime streaming) transcription\n" " delay in 12.5 Hz tokens (80 ms each); valid\n" " N = 1..15 (80..1200 ms) or 30 (2400 ms);\n" @@ -648,6 +657,24 @@ bool parse_args(int argc, char ** argv, cli_args & out) { return false; } out.stream_buf_right_ms = std::atoi(v); + } else if (a == "--diar-preset") { + const char * v = take_value(a.c_str()); + if (!v) { + return false; + } + const std::string name = v; + if (name == "very_high_latency") { + out.diar_preset = TRANSCRIBE_NEMOTRON3_DIAR_PRESET_VERY_HIGH_LATENCY; + } else if (name == "low_latency") { + out.diar_preset = TRANSCRIBE_NEMOTRON3_DIAR_PRESET_LOW_LATENCY; + } else if (name == "very_low_latency") { + out.diar_preset = TRANSCRIBE_NEMOTRON3_DIAR_PRESET_VERY_LOW_LATENCY; + } else if (name == "ultra_low_latency") { + out.diar_preset = TRANSCRIBE_NEMOTRON3_DIAR_PRESET_ULTRA_LOW_LATENCY; + } else { + std::fprintf(stderr, "error: unknown --diar-preset '%s'\n", v); + return false; + } } else if (a == "--stream-voxtral-delay") { const char * v = take_value(a.c_str()); if (!v) { @@ -882,6 +909,16 @@ int main(int argc, char ** argv) { } } + // Nemotron-3-Diarization operating point (whole-file runs only; the + // streaming path passes the STREAM-slot twin below). + struct transcribe_nemotron3_diar_run_ext n3d_rx; + transcribe_nemotron3_diar_run_ext_init(&n3d_rx); + if (args.diar_preset != 0 && args.stream_chunk_ms <= 0 && + transcribe_model_accepts_ext_kind(model, TRANSCRIBE_EXT_SLOT_RUN, TRANSCRIBE_EXT_KIND_NEMOTRON3_DIAR_RUN)) { + n3d_rx.preset = static_cast(args.diar_preset); + rp.family = &n3d_rx.ext; + } + if (args.keep_special_tags) { rp.keep_special_tags = true; } @@ -1063,6 +1100,8 @@ int main(int argc, char ** argv) { transcribe_parakeet_buffered_stream_ext_init(&pkt_buf_sp); struct transcribe_voxtral_realtime_stream_ext vx_sp; transcribe_voxtral_realtime_stream_ext_init(&vx_sp); + struct transcribe_nemotron3_diar_stream_ext n3d_sx; + transcribe_nemotron3_diar_stream_ext_init(&n3d_sx); const bool want_cache_aware = (args.stream_att_right >= 0); const bool want_buffered = args.stream_buf_left_ms >= 0 || args.stream_buf_chunk_ms >= 0 || args.stream_buf_right_ms >= 0; @@ -1082,6 +1121,11 @@ int main(int argc, char ** argv) { TRANSCRIBE_EXT_KIND_VOXTRAL_REALTIME_STREAM)) { vx_sp.num_delay_tokens = args.stream_voxtral_delay; sp.family = &vx_sp.ext; + } else if (args.diar_preset != 0 && + transcribe_model_accepts_ext_kind(model, TRANSCRIBE_EXT_SLOT_STREAM, + TRANSCRIBE_EXT_KIND_NEMOTRON3_DIAR_STREAM)) { + n3d_sx.preset = static_cast(args.diar_preset); + sp.family = &n3d_sx.ext; } run_st = transcribe_stream_begin(ctx, &rp, &sp); if (run_st == TRANSCRIBE_OK) { @@ -1308,6 +1352,16 @@ int main(int argc, char ** argv) { } } + // Nemotron-3-Diarization operating point (whole-file runs only; the + // streaming path passes the STREAM-slot twin below). + struct transcribe_nemotron3_diar_run_ext n3d_rx; + transcribe_nemotron3_diar_run_ext_init(&n3d_rx); + if (args.diar_preset != 0 && args.stream_chunk_ms <= 0 && + transcribe_model_accepts_ext_kind(model, TRANSCRIBE_EXT_SLOT_RUN, TRANSCRIBE_EXT_KIND_NEMOTRON3_DIAR_RUN)) { + n3d_rx.preset = static_cast(args.diar_preset); + rp.family = &n3d_rx.ext; + } + if (args.keep_special_tags) { rp.keep_special_tags = true; } @@ -1345,6 +1399,8 @@ int main(int argc, char ** argv) { transcribe_parakeet_buffered_stream_ext_init(&pkt_buf_sp); struct transcribe_voxtral_realtime_stream_ext vx_sp; transcribe_voxtral_realtime_stream_ext_init(&vx_sp); + struct transcribe_nemotron3_diar_stream_ext n3d_sx; + transcribe_nemotron3_diar_stream_ext_init(&n3d_sx); const bool want_cache_aware = (args.stream_att_right >= 0); const bool want_buffered = args.stream_buf_left_ms >= 0 || args.stream_buf_chunk_ms >= 0 || args.stream_buf_right_ms >= 0; @@ -1368,6 +1424,12 @@ int main(int argc, char ** argv) { vx_sp.num_delay_tokens = args.stream_voxtral_delay; sp.family = &vx_sp.ext; std::printf("stream: voxtral num_delay_tokens=%d\n", args.stream_voxtral_delay); + } else if (args.diar_preset != 0 && + transcribe_model_accepts_ext_kind(model, TRANSCRIBE_EXT_SLOT_STREAM, + TRANSCRIBE_EXT_KIND_NEMOTRON3_DIAR_STREAM)) { + n3d_sx.preset = static_cast(args.diar_preset); + sp.family = &n3d_sx.ext; + std::printf("stream: nemotron3_diar preset=%d\n", args.diar_preset); } run_st = transcribe_stream_begin(ctx, &rp, &sp); if (run_st != TRANSCRIBE_OK) { diff --git a/include/transcribe.abihash b/include/transcribe.abihash index b0e23c5d..ee383e74 100644 --- a/include/transcribe.abihash +++ b/include/transcribe.abihash @@ -1 +1 @@ -7df72bf9e667b8c2 +ae25d09c2b7b325b diff --git a/include/transcribe/extensions.h b/include/transcribe/extensions.h index b93bf146..c6c0e10f 100644 --- a/include/transcribe/extensions.h +++ b/include/transcribe/extensions.h @@ -15,6 +15,7 @@ #include "transcribe.h" #include "transcribe/moonshine_streaming.h" +#include "transcribe/nemotron3_diar.h" #include "transcribe/parakeet.h" #include "transcribe/sortformer.h" #include "transcribe/voxtral_realtime.h" diff --git a/include/transcribe/nemotron3_diar.h b/include/transcribe/nemotron3_diar.h new file mode 100644 index 00000000..05fbfa03 --- /dev/null +++ b/include/transcribe/nemotron3_diar.h @@ -0,0 +1,99 @@ +/* + * include/transcribe/nemotron3_diar.h - Nemotron-3-Diarization public + * extensions. + * + * Includes transcribe.h; safe to include in C or C++ TUs. Holds the + * streaming-operating-point extensions for both entry points, their kind + * constants, and their init functions. + * + * Nemotron-3-Diarization is a diarization-only model (up to 8 speakers, + * 10 ms output): a run produces no text; the product is the who-spoke-when + * rows read back via transcribe_n_speaker_segments / + * transcribe_get_speaker_segment (TRANSCRIBE_FEATURE_DIARIZATION). The + * compute core is streaming (arrival-order speaker cache + FIFO) and is + * reachable two ways: + * + * transcribe_run whole recording in one call. Operating point + * via transcribe_nemotron3_diar_run_ext on the + * RUN slot. + * transcribe_stream_* push-audio live diarization. Operating point + * via transcribe_nemotron3_diar_stream_ext on + * the STREAM slot. After every feed the speaker + * segments cover all audio processed so far: a + * finished turn is final, a turn still in + * progress is reported open-ended up to the + * latest processed frame and may extend on later + * feeds. Finalize flushes the tail; the final + * segments equal a transcribe_run over the same + * audio at the same preset. + * + * Probe via transcribe_model_accepts_ext_kind(model, slot, kind) before + * pointing run_params::family / stream_params::family at the struct. + * + * FourCC kinds are reserved in docs/extension-kinds.md. + */ + +#ifndef TRANSCRIBE_NEMOTRON3_DIAR_H +#define TRANSCRIBE_NEMOTRON3_DIAR_H + +#include "transcribe.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* 'N3DR' little-endian = 0x5244334E (RUN slot) */ +#define TRANSCRIBE_EXT_KIND_NEMOTRON3_DIAR_RUN 0x5244334Eu +/* 'N3DS' little-endian = 0x5344334E (STREAM slot) */ +#define TRANSCRIBE_EXT_KIND_NEMOTRON3_DIAR_STREAM 0x5344334Eu + +/* + * Streaming operating point (latency / accuracy trade-off). + * + * The model processes audio in fixed chunks with a fixed lookahead and + * carries speaker identity across chunks in a bounded cache. Each named + * preset is a jointly-tuned bundle published on the upstream model card + * (chunk length, lookahead, FIFO and speaker-cache geometry); the menu is + * discrete, not a continuous latency dial. Latency = chunk + lookahead. + * + * DEFAULT The GGUF-shipped configuration (= VERY_HIGH_LATENCY). + * VERY_HIGH_LATENCY 30.4 s (chunk 340 + lookahead 40 frames @ 80 ms). + * Highest accuracy; the offline operating point. + * LOW_LATENCY 1.04 s (chunk 9 + lookahead 4). + * VERY_LOW_LATENCY 0.64 s (chunk 6 + lookahead 2). + * ULTRA_LOW_LATENCY 0.32 s (chunk 3 + lookahead 1). + * + * Smaller chunks cost more compute per audio second (the whole speaker + * cache is re-encoded every chunk). Values outside the enum range are + * rejected with TRANSCRIBE_ERR_INVALID_ARG before the previous result is + * cleared. + */ +typedef enum { + TRANSCRIBE_NEMOTRON3_DIAR_PRESET_DEFAULT = 0, + TRANSCRIBE_NEMOTRON3_DIAR_PRESET_VERY_HIGH_LATENCY = 1, + TRANSCRIBE_NEMOTRON3_DIAR_PRESET_LOW_LATENCY = 2, + TRANSCRIBE_NEMOTRON3_DIAR_PRESET_VERY_LOW_LATENCY = 3, + TRANSCRIBE_NEMOTRON3_DIAR_PRESET_ULTRA_LOW_LATENCY = 4, +} transcribe_nemotron3_diar_preset; + +/* RUN slot (transcribe_run_params::family). */ +struct transcribe_nemotron3_diar_run_ext { + struct transcribe_ext ext; + transcribe_nemotron3_diar_preset preset; +}; + +/* STREAM slot (transcribe_stream_params::family). */ +struct transcribe_nemotron3_diar_stream_ext { + struct transcribe_ext ext; + transcribe_nemotron3_diar_preset preset; +}; + +/* Fill ext.size/kind and preset = DEFAULT. */ +TRANSCRIBE_API void transcribe_nemotron3_diar_run_ext_init(struct transcribe_nemotron3_diar_run_ext * ext); +TRANSCRIBE_API void transcribe_nemotron3_diar_stream_ext_init(struct transcribe_nemotron3_diar_stream_ext * ext); + +#ifdef __cplusplus +} +#endif + +#endif /* TRANSCRIBE_NEMOTRON3_DIAR_H */ diff --git a/reports/porting/nemotron3_diar/Nemotron-3-Diarization/intake.json b/reports/porting/nemotron3_diar/Nemotron-3-Diarization/intake.json new file mode 100644 index 00000000..7542ee46 --- /dev/null +++ b/reports/porting/nemotron3_diar/Nemotron-3-Diarization/intake.json @@ -0,0 +1,329 @@ +{ + "schema_version": "transcribe-intake-v1", + "family": "nemotron3_diar", + "hf_repo": "nvidia/Nemotron-3-Diarization", + "hf_revision": "f667ed73aee57d40cc39428eb768b4fd87a0a29e", + "sources": { + "config": { + "kind": "hf_file", + "path": "config.json", + "status": "found" + }, + "preprocessor": { + "kind": "hf_file", + "path": "processor_config.json::feature_extractor", + "status": "found", + "detail": "HF processor_config.json nests the feature extractor (NemotronAsrStreamingFeatureExtractor: 128 mels, n_fft=512, win=400, hop=160, preemphasis=0.97, 16 kHz); intake.py did not look inside the nested block. Canonical values are the .nemo model_config.yaml preprocessor block (AudioToMelSpectrogramPreprocessor, normalize=NA, dither=1e-5, window=hann), which agrees." + }, + "tokenizer_config": { + "kind": "hf_file", + "path": "tokenizer_config.json", + "status": "missing", + "detail": "No tokenizer. Diarizer: output is a T x 8 speaker-activity probability matrix, not text." + }, + "tokenizer_json": { + "kind": "hf_file", + "path": "tokenizer.json", + "status": "missing", + "detail": "N/A for a diarizer." + }, + "generation_config": { + "kind": "hf_file", + "path": "generation_config.json", + "status": "missing", + "detail": "N/A; no autoregressive decoding." + }, + "safetensors_metadata": { + "kind": "hf_api", + "path": "HfApi.get_safetensors_metadata", + "status": "found", + "detail": "HF model.safetensors: 417 F32 tensors. Verified in-session that every value is exactly representable in BF16 and matches the .nemo BF16 state_dict (e.g. q_proj = w_qkv[:512], silence_embeds, upsampler conv): the F32 file is a lossless upcast made by the HF converter (dtype='float32'), not the authors' storage dtype." + }, + "model_card": { + "kind": "hf_api", + "path": "https://huggingface.co/nvidia/Nemotron-3-Diarization", + "status": "found", + "detail": "Nemotron 3 Diarization, released 2026-09-23, ~100M params, up to 8 speakers, streaming Sortformer (AOSC+FIFO), 10 ms output frames. DER published per dataset at 30.4 s / 1.04 s / 0.64 s / 0.32 s input-buffer latency, measured with NeMo e2e_diarize_speech.py at BF16 on an RTX PRO 5000." + }, + "nemo_model_config": { + "kind": "reference_code", + "path": "Nemotron-3-Diarization.nemo::model_config.yaml", + "status": "found", + "detail": "target: nemo.collections.asr.models.sortformer_diar_models.SortformerEncLabelModel, nemo_version 3.0.0. encoder: nemo.collections.asr.modules.TransformerEncoder (31L, d=512, 8 heads, feature_stacking x8, rope, pre_block_norm, qkv_bias=false). high_resolution=true, output_subsampling_factor=1, max_num_of_spks=8, use_learnable_sil_emb=true. Its sortformer_modules streaming values (fifo_len=0, chunk_len=264, rc=0, update=264) are training-time settings, not an inference preset." + }, + "nemo_state_dict": { + "kind": "reference_code", + "path": "Nemotron-3-Diarization.nemo::model_weights.ckpt", + "status": "found", + "detail": "363 tensors, all torch.bfloat16 (99,263,843 elements). Includes tensors unused at inference: preprocessor.featurizer.{window,fb}, sortformer_modules.hidden_to_spks.* (legacy 384->8 head, frozen, never called), sortformer_modules.activity_head.* (training-only aux 3-class head)." + }, + "reference_modeling_code": { + "kind": "reference_code", + "path": "refs/NVIDIA-NeMo/Speech @ cf724ac337d1 (nemo 3.1.0): nemo/collections/asr/models/sortformer_diar_models.py, modules/sortformer_modules.py, modules/transformer_encoder.py", + "status": "found", + "detail": "NeMo moved to github.com/NVIDIA-NeMo/Speech; the older refs/NVIDIA-NeMo/NeMo checkout (Apr 2026) and the scripts/envs/sortformer pin cannot load this encoder (no TransformerEncoder rope/feature_stacking, no subpixel head / learnable silence emb). Freshly cloned into refs/ for this intake." + }, + "hf_transformers_code": { + "kind": "reference_code", + "path": "refs/huggingface/transformers origin/main @ 4b28d51d0d5f: src/transformers/models/nemotron3_diarization/{modular,processing,convert}_nemotron3_diarization.py", + "status": "found", + "detail": "Clean single-file HF port (transformers 5.18.0.dev0, main only; not in a release at intake time). Strict-load converter from the .nemo. Used as the instrumented cross-check, not the source of truth." + }, + "nvidia_gguf": { + "kind": "hf_file", + "path": "Nemotron-3-Diarization.q8_0.gguf", + "status": "found", + "detail": "NVIDIA ships a Q8_0 GGUF for their NeMo-Speech.cpp runtime with general.architecture='sortformer'. Not consumed by this port (different loader contract; Q8_0 is not the reference dtype). Our GGUF must NOT reuse arch string 'sortformer' or it would route into the v2.1 loader." + } + }, + "variants": [ + { + "name": "Nemotron-3-Diarization", + "memory_gb": 1, + "files": [ + "Nemotron-3-Diarization.nemo" + ] + } + ], + "config": { + "architecture_candidates": [ + "encoder-diarizer" + ], + "key_fields": { + "architectures": [ + "Nemotron3DiarizationForAudioFrameClassification" + ], + "model_type": "nemotron3_diarization", + "target": "nemo.collections.asr.models.sortformer_diar_models.SortformerEncLabelModel", + "max_num_of_spks": 8, + "streaming_mode": true, + "high_resolution": true, + "output_subsampling_factor": 1, + "encoder_frame_period_seconds": 0.08, + "output_frame_period_seconds": 0.01, + "output_shape": "T x 8 sigmoid speaker-activity probabilities at 10 ms (arrival-order columns)", + "encoder.type": "nemo TransformerEncoder", + "encoder.n_layers": 31, + "encoder.d_model": 512, + "encoder.n_heads": 8, + "encoder.head_dim": 64, + "encoder.ff_expansion": 4.0, + "encoder.ffn_activation": "GELU (exact, erf; nn.GELU())", + "encoder.subsampling": "feature_stacking (stack 8 x 128 mels -> Linear 1024->512, no bias; zero-pad last partial group)", + "encoder.self_attention_model": "rope (full rotary, theta 10000, rotate_half/NeoX layout)", + "encoder.pre_block_norm": true, + "encoder.embed_norm": "LayerNorm after feature stacking", + "encoder.final_norm": true, + "encoder.qkv_bias": false, + "encoder.out_proj_bias": true, + "encoder.xscaling": false, + "encoder.attn_mode": "full", + "head.encoder_proj": "Linear 512->192", + "head.subpixel_upsample": "Conv1d 192 -> 192*8, k=3, pad=1, reshape to 8x frames (80 ms -> 10 ms)", + "head.classifier": "ReLU -> Linear 192->192 -> ReLU -> Linear 192->8 -> sigmoid", + "sortformer_modules.use_learnable_sil_emb": true, + "sortformer_modules.spkcache_sil_frames_per_spk": 1, + "sortformer_modules.pred_score_threshold": 0.25, + "sortformer_modules.scores_boost_latest": 0.05, + "sortformer_modules.strong_boost_rate": 0.75, + "sortformer_modules.weak_boost_rate": 1.5, + "sortformer_modules.min_pos_scores_rate": 0.5, + "sortformer_modules.sil_threshold": 0.2, + "presets (spkcache/fifo/chunk/rc/update, 80 ms frames)": { + "very_high_latency_30.4s (offline)": [ + 264, + 40, + 340, + 40, + 300 + ], + "low_latency_1.04s": [ + 264, + 264, + 9, + 4, + 222 + ], + "very_low_latency_0.64s": [ + 264, + 264, + 6, + 2, + 222 + ], + "ultra_low_latency_0.32s": [ + 264, + 264, + 3, + 1, + 222 + ] + } + }, + "varying_across_variants": [] + }, + "dtype": { + "expected": "bfloat16", + "source": "manual", + "evidence": "Authors' canonical checkpoint (.nemo model_weights.ckpt) stores all 363 tensors as torch.bfloat16. HF config.json dtype=float32 and model.safetensors F32 are a lossless upcast by the HF converter (verified bit-exact vs the BF16 state_dict). Policy 2 => reference-dtype GGUF is BF16. Oracle compute is fp32 on CPU over the BF16 weights (Policy 1), unlike the card's BF16-autocast GPU eval.", + "details": { + "config_declared": "float32", + "header_distribution": { + "F32": 417 + }, + "nemo_state_dict_distribution": { + "BF16": 363 + } + }, + "expected_f32_tensors": [] + }, + "frontend": { + "sample_rate": 16000, + "n_mels": 128, + "hop_length": 160, + "fft_size": 512, + "window": "hann_symmetric", + "normalization": "none", + "preemphasis": 0.97, + "dither": 0.0, + "center": true, + "padding_mode": "constant", + "mel_filterbank_norm": "slaney" + }, + "tokenizer": { + "type": "other", + "vocab_size": 0, + "special_tokens": {}, + "has_language_tokens": false, + "vocab_sha256": null + }, + "capabilities": { + "languages": [ + "en" + ], + "language_detection": false, + "translation": false, + "timestamps": [], + "streaming": true, + "speaker_diarization": true + }, + "upstream_benchmarks": [ + { + "dataset": "AMI Test MHM (forced-alignment RTTM, nttcslab-sp)", + "language": "en", + "metric": "other", + "score": 9.25, + "score_unit": "percent DER", + "source": "model card", + "notes": "DER, collar 0.0 s, overlap included, NeMo e2e_diarize_speech.py, BF16 GPU. 30.4 s latency (very_high_latency). 1.04 s: 9.48; 0.64 s: 9.62; 0.32 s: 10.05. PROPOSED PRIMARY ACCEPTANCE SET (freely downloadable; the AMI forced-alignment pipeline already exists from the sortformer v2.1 port, whose card-reported v2.1 MHM 30.4 s DER 15.81 matches our measured 15.96, confirming MHM == our ingested condition). 3-4 speakers only. Publisher score is context; gate = measured Oracle reference." + }, + { + "dataset": "AMI Test SDM (forced-alignment RTTM)", + "language": "en", + "metric": "other", + "score": 11.14, + "score_unit": "percent DER", + "source": "model card", + "notes": "DER, collar 0.0 s, overlap included, NeMo e2e_diarize_speech.py, BF16 GPU. 30.4 s. 1.04 s: 12.80; 0.32 s: 12.95." + }, + { + "dataset": "NOTSOFAR1 Eval MHM (forced-alignment RTTM, FastMSS)", + "language": "en", + "metric": "other", + "score": 6.77, + "score_unit": "percent DER", + "source": "model card", + "notes": "DER, collar 0.0 s, overlap included, NeMo e2e_diarize_speech.py, BF16 GPU. full set, 30.4 s. 5-7 spk subset: 7.86 (30.4 s), 9.02 (1.04 s). PROPOSED SECONDARY ACCEPTANCE SET covering >4 speakers (AMI never exercises channels 5-8); availability of eval audio to be confirmed at Stage 2." + }, + { + "dataset": "NOTSOFAR1 Eval SC (forced-alignment RTTM)", + "language": "en", + "metric": "other", + "score": 11.0, + "score_unit": "percent DER", + "source": "model card", + "notes": "DER, collar 0.0 s, overlap included, NeMo e2e_diarize_speech.py, BF16 GPU. full set, 30.4 s." + }, + { + "dataset": "DIHARD III Eval full (1-9 spk)", + "language": null, + "metric": "other", + "score": 12.73, + "score_unit": "percent DER", + "source": "model card", + "notes": "DER, collar 0.0 s, overlap included, NeMo e2e_diarize_speech.py, BF16 GPU. 30.4 s. Context only: LDC-gated." + }, + { + "dataset": "CALLHOME-Part2 full (2-6 spk)", + "language": null, + "metric": "other", + "score": 9.1, + "score_unit": "percent DER", + "source": "model card", + "notes": "DER, collar 0.25 s, 30.4 s latency. Context only: LDC-gated." + }, + { + "dataset": "AliMeeting Test Near (forced-alignment RTTM)", + "language": "zh", + "metric": "other", + "score": 6.4, + "score_unit": "percent DER", + "source": "model card", + "notes": "DER, collar 0.0 s, overlap included, NeMo e2e_diarize_speech.py, BF16 GPU. 30.4 s. Mandarin; context only." + } + ], + "reference_framework": "nemo", + "reference_rationale": "The authors' canonical checkpoint is the BF16 .nemo, loaded by nemo.collections.asr.models.SortformerEncLabelModel (NeMo Speech 3.x; the card names NeMo Framework v3.0 as the runtime), and every published DER comes from NeMo's e2e_diarize_speech.py. The HF transformers Nemotron3Diarization class is a derived port: its weights come from the .nemo via a strict-load converter and it is only on transformers main (5.18.0.dev0). NeMo is therefore the source of truth, matching the sortformer v2.1 precedent. HF transformers is the instrumented cross-check: it is a compact single-file implementation of the same forward and AOSC cache, useful for layer dumps and for arbitrating NeMo-vs-C++ disagreements. The existing scripts/envs/sortformer NeMo pin (2.x) cannot load this encoder, so Stage 2 needs a new env pinned to NVIDIA-NeMo/Speech 3.x. NeMo's TransformerEncoder has an explicit CPU math-attention path (non-CUDA inputs avoid the fused kernels); fp32-on-CPU execution is confirmed empirically at Stage 2.", + "architecture_pattern": "encoder-diarizer", + "known_risks": [ + "NEW FAMILY, NOT A SORTFORMER VARIANT. Same Sortformer/AOSC lineage as sortformer v2.1, but the encoder (31L RoPE Transformer vs 17L FastConformer + 18L post-LN Transformer), head (subpixel upsampler, 10 ms output), speaker count (8 vs 4) and silence handling (learned embedding) all differ. src/arch/sortformer hardcodes max_speakers=4 and an 80 ms output grid. The AOSC/FIFO streaming logic is algorithmically shared and may be reused or mirrored, but the forward graph is new. The GGUF arch string must differ from 'sortformer' (NVIDIA's own Q8_0 GGUF uses 'sortformer').", + "ENCODER FRONT: feature stacking reshapes 8 consecutive 128-mel frames into 1024 features (frame-major order), Linear 1024->512 with no bias, zero-padding the final partial group; then an input LayerNorm (embed_norm) before layer 0. Stacking order and the padded tail frame both affect the first/last encoder frame.", + "ATTENTION: fused w_qkv [1536,512] with no bias, rows ordered [q|k|v]; out_proj has a bias. Pre-LN blocks (norm1 -> attn -> residual, norm2 -> FFN -> residual), final LayerNorm. Full bidirectional attention within each step, with a padding mask only.", + "FRONTEND BUFFERS STORED IN BF16: the .nemo carries preprocessor.featurizer.window [400] and .fb [1,128,257] as persistent BF16 buffers, which restore_from loads, so the NeMo reference computes mel with BF16-rounded window/filterbank (window max |diff| vs fp32 symmetric Hann = 1.9e-3). HF recomputes both in fp32, so NeMo and HF will not agree bit-for-bit at the mel stage; C++ follows NeMo. Stage 3 decision: embed the stored buffers in the GGUF (cohere/canary-qwen converter pattern) vs recompute in C++ (sortformer converter skips preprocessor.*). The window is SYMMETRIC Hann (stored buffer == torch.hann_window(400, periodic=False) in BF16, bit-exact; NeMo features.py builds it with periodic=False) and the STFT uses center=True with pad_mode='constant' (zero). The sortformer v2.1 intake's 'hann_periodic'/'reflect' values were not carried over.", + "ROPE: full rotary (rotary_fraction 1.0, head_dim 64), theta 10000, rotate_half (NeoX) layout => ggml_rope with GGML_ROPE_TYPE_NEOX, not the default interleaved mode. Positions restart at 0 at every streaming step and span the concatenated [speaker cache | FIFO | chunk | right context] sequence, so cache frames get new positions each step.", + "GELU: FFN uses nn.GELU() (exact erf). ggml_gelu is the tanh approximation; use ggml_gelu_erf or accept and document a tolerance.", + "SUBPIXEL UPSAMPLER: Conv1d 192 -> 1536 (k=3, pad=1) over encoder frames, then reshape [T, 1536] -> [8T, 192]; channel c*... ordering after reshape must match exactly (sub-frame index is the slow axis of the 1536 channels). Output rate is 10 ms (high_resolution=true, output_subsampling_factor=1); T_out = n_mel_frames (trimmed), not ceil(n/8).", + "AOSC SCORING AT 80 ms: cache/FIFO bookkeeping stays at the encoder rate. Speaker-cache frame scores use sigmoid probs average-pooled (factor 8) back to 80 ms, zeroed on padding frames. Streaming and cache-compression parity therefore depend on the pooled probs, not the 10 ms output.", + "LEARNED SILENCE EMBEDDING: use_learnable_sil_emb=true, so compressed-cache silence slots are filled with the learned sortformer_modules.learnable_sil_emb vector (BF16 [512]), not v2.1's running mean of silence frames. sil_threshold / mean-silence update code paths are inactive for this checkpoint.", + "CACHE GEOMETRY: 8 speakers x 264-frame cache => per-speaker budget 264/8 - 1 = 32 frames (v2.1: 188/4 - 1 = 46). min_positive_scores = 16, strong boost = 24, weak boost = 48 frames. Top-k near-ties in compression can permute arrival-order labels, the same chaotic-quant hazard that withdrew k-quants for sortformer v2.1.", + "PRESETS: offline and streaming use different cache sizes (offline fifo 40 / update 300; streaming fifo 264 / update 222; spkcache 264 for all). The .nemo's sortformer_modules values (fifo 0, chunk 264, rc 0, update 264) are training-time settings, so runtime defaults must come from the card's preset table (30.4 / 1.04 / 0.64 / 0.32 s), not from the checkpoint config. HF config.json encodes the offline preset at top level and the streaming sizes under streaming_config.", + "DTYPE: authors' storage is BF16 (.nemo), while the HF safetensors is an F32 upcast. The reference GGUF is BF16. The card's DER was measured under BF16 autocast on GPU, but the Oracle measures fp32 compute on CPU over the BF16 weights, so small DER drift vs the card is expected; gate against the measured Oracle.", + "REFERENCE ENV: NeMo moved to NVIDIA-NeMo/Speech (3.x). scripts/envs/sortformer pins NeMo 2.x at 6967f48fda2a, which lacks this encoder. Stage 2 needs a new scripts/envs/nemotron3_diar env; the card asks for Python >= 3.12.", + "OUTPUT IS PROBABILITIES. DER/JER need thresholding plus optional post-processing. Parity gate = raw 10 ms prob-tensor parity vs the NeMo reference (arrival-order columns directly comparable). DER is compared to the reference model on the same audio/RTTMs/post-processing, not to card numbers.", + "ACCEPTANCE DATA: LibriSpeech is single-speaker and degenerate for diarization. AMI MHM forced-alignment (3-4 spk) reuses the existing pipeline but never exercises speaker channels 5-8, the headline capability. A >4-speaker set (NOTSOFAR1 eval, 3-7 spk, FA RTTMs) and a >4-speaker oracle clip are needed to cover it.", + "MULTITALKER INTEROP: NVIDIA's ASR integration guide pairs this model with multitalker-parakeet-streaming-0.6b-v1 (max_num_of_spks=8, fifo_len=264, spkcache_update_period=222) and nemotron-3.5-asr-streaming-0.6b (masked_asr). src/arch/parakeet/multitalker.cpp hard-binds sortformer::SortformerEmbedded, 80 ms indexing and hp.max_speakers (4). Feeding it this diarizer needs 10 ms -> 80 ms pooling and an 8-speaker path.", + "LICENSE: openmdw-1.1 (differs from v2.1's NVIDIA Open Model License). Needs human interpretation before shipping; not interpreted here.", + "PUSH-AUDIO LIVE DIARIZATION IS IN SCOPE (user decision 2026-09-24), and no diarizer has it today. The transcribe_stream_* API reports progress as text (committed/tentative text, words, tokens), and sortformer registers nullptr stream hooks. It needs: (1) public-API design for exposing speaker segments mid-stream (a committed vs still-open segment boundary, revision semantics while the last segment's end moves, arrival-order ids stable across feeds); (2) a STREAM-slot ext kind carrying the preset; (3) incremental mel frontend state. API/ABI shape is a human decision.", + "STREAMING FRONTEND CONVENTION (resolved at Stage 2): the push-audio parity reference is NeMo diarize() at the same preset (whole-utterance mel). C++ push-audio output must match the C++ run-level output at that preset. Evidence (92 s 8-speaker oracle): within HF, the per-chunk streaming mel (center only on the first chunk, cut exactly as the processor streams it) is bit-identical to the whole-utterance mel on every shared frame in all three streaming modes (method: concatenate the processor's per-chunk features [:num_mel_frames_per_step], plus the whole last chunk, and compare with proc(audio)), so incremental mel costs no accuracy. HF vs NeMo diar.probs differ by mean|d| ~3e-4 regardless of streaming because of framework differences (NeMo uses the BF16-stored window/fb, HF recomputes them in fp32). FRAME-COUNT EDGE for Stage 4: NeMo emits floor(n_samples/160) frames (9200 for 92 s), HF offline 9201, HF stream 9199. C++ must emit NeMo's count in both paths, i.e. push-audio finalize must flush the tail frames that HF streaming drops." + ], + "intake_gaps": [ + { + "field": "frontend", + "reason": "RESOLVED from primary sources: .nemo model_config.yaml preprocessor block (canonical), NeMo Speech features.py (window built with periodic=False, stft pad_mode='constant', center=True unless exact_pad), and the stored BF16 window buffer (bit-exact vs symmetric Hann). Cross-checked against HF processor_config.json feature_extractor (intake.py missed the nested block). mel_filterbank_norm slaney = FilterbankFeatures default mel_norm. Stage 2 frontend dump re-confirms. Stage 2: inference dither is 0 (NeMo applies the config dither=1e-5 only when self.training); the reference is bit-deterministic across runs. The loaded window/fb buffers carry the checkpoint's BF16-rounded values." + }, + { + "field": "dtype.expected", + "reason": "RESOLVED and user-signed 2026-09-24: bfloat16 (the .nemo state_dict, i.e. the authors' storage). HF float32 is a verified lossless upcast." + }, + { + "field": "capabilities.languages", + "reason": "Set to ['en'] (acceptance language). Diarization is largely language-independent and training/eval include Mandarin, Hindi and multilingual sets (DIHARD III, CALLHOME); card makes no language restriction claim. No language knob exists." + }, + { + "field": "capabilities.timestamps", + "reason": "Left empty: diarization segment times are intrinsic, not a transcript-timestamp capability (no transcript is emitted)." + }, + { + "field": "upstream_benchmarks.acceptance_dataset", + "reason": "RESOLVED and user-signed 2026-09-24: AMI Test MHM forced-alignment RTTMs (primary) + NOTSOFAR1 Eval MHM forced-alignment (secondary, covers 5-8 speakers). Gate = DER + JER vs the measured NeMo reference on the same audio/RTTMs/post-processing, plus prob-tensor parity. Not LibriSpeech. Stage 2 confirms that the NOTSOFAR1 eval audio is available." + }, + { + "field": "family/variant naming", + "reason": "RESOLVED and user-signed 2026-09-24: family 'nemotron3_diar', variant 'Nemotron-3-Diarization'." + }, + { + "field": "license", + "reason": "OPEN: openmdw-1.1 needs human interpretation before ship (Stage 8). Does not block Stages 2-4." + } + ] +} diff --git a/reports/porting/nemotron3_diar/forward-map.md b/reports/porting/nemotron3_diar/forward-map.md new file mode 100644 index 00000000..407aa594 --- /dev/null +++ b/reports/porting/nemotron3_diar/forward-map.md @@ -0,0 +1,129 @@ +# Forward map - nemotron3_diar + +Reference: NeMo Speech `SortformerEncLabelModel` (nemo.collections.asr.models.sortformer_diar_models) + `modules/transformer_encoder.py::TransformerEncoder` + `modules/sortformer_modules.py::SortformerModules` @ `cf724ac337d1` (`refs/NVIDIA-NeMo/Speech`) +Closest in-tree analog: src/arch/sortformer/ (AOSC/FIFO streaming driver, host state machine, diar head, speaker-segment surface). The encoder has no in-tree analog; RoPE/GELU pre-LN attention patterns follow ggml-reference-map. + +`encoder-diarizer`: no tokenizer, no decoder, no text. Product = `T x 8` +per-frame speaker-activity probabilities at **10 ms** (high_resolution=True, +output_subsampling_factor=1), arrival-order columns, max 8 speakers. The +inference product path is ALWAYS the sync streaming loop +(`streaming_mode=True`, `async_streaming=False`, `forward_streaming`); there +is no separate offline forward (the "offline" capability = the +very_high_latency preset). + +Dims (GGUF `stt.nemotron3_diar.*`): encoder 31L pre-LN d=512 h=8 (head_dim 64) +dff=2048 GELU(exact), RoPE NEOX theta 1e4 rotary_fraction 1, no qkv bias, +out_proj bias, LN eps 1e-5; feature stacking x8 (1024->512, no bias); +encoder_proj 512->192; subpixel Conv1d 192->1536 k3 pad1 -> [T,8,192] -> +[8T,192]; head relu -> fc1 192->192 -> relu -> single_spk_head 192->8 -> +sigmoid. AOSC cache holds 512-dim pre_encode embeddings at 80 ms. + +## Frontend + +| Stage | Reference location | Output shape | Gate tensor | ggml / C++ pattern | In-tree analog | +|-------|--------------------|--------------|-------------|--------------------|----------------| +| Log-mel | `AudioToMelSpectrogramPreprocessor` / `FilterbankFeatures` (`process_signal`; streaming_mode => no peak normalization) | [128, T_mel], T_mel = floor(n/160) (9200 for 92 s, 9133 for 91.337 s) | `enc.mel.in` | `transcribe::MelFrontend`: preemph 0.97, n_fft 512, win 400, hop 160, constant center pad, normalize none, log(x+2^-24), dither 0; **window + filterbank from GGUF** (`frontend.window` / `frontend.mel_filterbank`, BF16-exact); the driver keeps the first floor(n/160) frames | sortformer load() MelConfig + cohere GGUF-buffer injection | + +## Encoder (per streaming step, over the `[spkcache | fifo | chunk]` concat) + +| Stage | Reference location | Output shape | Gate tensor | ggml / C++ pattern | In-tree analog | +|-------|--------------------|--------------|-------------|--------------------|----------------| +| Feature stacking (pre_encode) | `subsampling.py::FeatureStacking.forward` (via `_call_pre_encode`): (B,C,T)->(B,T,C), zero-pad T to x8, reshape (T/8, 8*128) frame-major, `proj` Linear no bias | [T_diar, 512] | `enc.pre_encode.out` | mel window [T_win,128] (time-major) -> pad -> view [1024, T/8] (8 consecutive 128-dim frames per row, frame-major) -> mul_mat(proj) | new (reshape + linear) | +| Concat cache | `forward_streaming_step`: `concat_embs([spkcache, fifo, chunk_pre_encode_embs])` | [S+F+T_diar, 512] | (host) | host concat, as sortformer | sortformer `run_diar_streaming_core` | +| embed_norm | `TransformerEncoder.forward_internal` (rope branch: no xscale, dropout no-op) -> `embed_norm` LN | [T_cat, 512] | `enc.embed_norm.out` | `ggml_norm` + mul + add | sortformer `layer_norm` | +| Pre-LN block x31 | `TransformerBlock.forward`: `x += attn(norm1(x))`; `x += ffn(norm2(x))` | [T_cat, 512] | `enc.layers.{0,1,15,30}.out` | per block below | cohere/qwen3 pre-LN blocks (structure only) | +| final_norm | `final_norm` LN | [T_cat, 512] | `enc.final_norm.out` | layer_norm | - | +| encoder_proj | `frontend_encoder`: transpose + `sortformer_modules.encoder_proj` 512->192 | [T_cat, 192] | `diar.encoder_proj.out` | linear | sortformer | + +### Attention (`MultiHeadAttention.forward`, rope branch) +``` +qkv = w_qkv(x).view(T, 3, H, D) # rows [q | k | v], head-major; no bias +q,k = rope(q,k) # positions 0..T_cat-1 (cache_len=0: t_q == t_k); NEOX rotate_half +out = softmax(q k^T / sqrt(D) + padmask) v # flex attention, default scale; sync batch=1 => no padding +out_proj(out) # bias +``` +ggml (as built): one mul_mat for qkv, `ggml_view_4d` q/k/v head slices, `ggml_rope_ext(..., GGML_ROPE_TYPE_NEOX, n_dims=64, freq_base=1e4)`, +manual F32 attention `mul_mat(k, q)` -> `soft_max_ext(scale=1/8)` -> `mul_mat(v^T, kq)` (no flash; K/V stay F32). Positions restart at 0 every step (RoPE over the concat, +not absolute stream time). FFN: `net.0` Linear -> `nn.GELU()` (exact erf) -> `net.3` Linear => `ggml_gelu_erf`. + +## Decoder + +No decoder. The head is the upsampler + sigmoid stack (`forward_infer`). + +| Stage | Reference location | Output shape | Gate tensor | ggml / C++ pattern | In-tree analog | +|-------|--------------------|--------------|-------------|--------------------|----------------| +| (transformer_encoder) | `transformer_encoder` is None for this model (no post-LN stack) | - | - | skipped | differs from sortformer | +| Subpixel conv | `SortformerModules.upsample_hidden`: Conv1d(192, 1536, k=3, pad=1) over time | [T_cat, 1536] | `diar.subpixel_conv.out` | `ggml_im2col` (F32, pad 1) + mul_mat with the kernel viewed [576, 1536] (channels in ne0); F16 kernel upcast to F32 on CPU | conformer conv helpers | +| Pixel shuffle | reshape (T, 8, 192) -> (8T, 192): channel block j of frame t = output frame 8t+j | [8*T_cat, 192] | `diar.upsample.out` | ne [1536, T] -> reshape [192, 8T] (contiguous, no permute) | new | +| Speaker logits | `forward_speaker_logits`: relu -> fc1 -> relu -> single_hidden_to_spks | [8*T_cat, 8] | `diar.logits` | linear/relu | sortformer head | +| Sigmoid | `forward_infer`: `sigmoid(logits) * output_mask` (mask all-ones in sync) | [8*T_cat, 8] | (inside `diar.probs`) | `ggml_sigmoid` | sortformer | + +## Streaming path (`forward_streaming` / `forward_streaming_step`, sync) + +Per chunk from `streaming_feat_loader` (lc=0 for every card preset; rc per preset): +1. pre_encode(chunk mel window incl. rc) -> chunk_embs [T_diar, 512] (T_diar = ceil(win/8)). +2. concat [spkcache | fifo | chunk_embs] -> encoder -> encoder_proj -> upsample -> head -> **hi-res preds [8*T_cat, 8]**. +3. `downsample_preds(hi, 8)`: avg-pool x8 (ceil_mode, count_include_pad=False) -> preds80 [T_cat, 8] (drives the cache). +4. `streaming_update(state, chunk_embs, preds80, lc, rc)` (host): identical to sortformer's sync update EXCEPT + - `use_learnable_sil_emb=True`: NO `_get_silence_profile` update; `_compress_spkcache` fills disabled slots + with `learnable_sil_emb` (GGUF `diar.sil_emb`) instead of the running mean; + - `spkcache_preds` before first compression = `preds80[:S] ++ pop_preds` (equivalent to sortformer's seed-on-first-compress). +5. chunk output = `hi[(S+F+lc)*8 : (S+F+lc+C)*8]` (10 ms), appended to total_preds. +6. End: `total_preds[:T_mel]` (output_subsampling_factor 1) -> T_mel rows. + +Presets (80 ms frames; spkcache/fifo/chunk/rc/update): very_high_latency 264/40/340/40/300; +low_latency 264/264/9/4/222; very_low_latency 264/264/6/2/222; ultra_low_latency 264/264/3/1/222; +small (validation only) 24/10/20/2/20. `spkcache_sil_frames_per_spk=1`, compress constants from GGUF KVs. + +Push-audio (`transcribe_stream_*`): the chunk loop is causal given the right context, and the per-chunk mel +equals the whole-utterance mel on shared frames (Stage 2), so a feed can run every chunk whose window +(chunk + rc mel frames) is fully available, and finalize runs the tail with the utterance-final geometry +(`min(...)` clamps in `streaming_feat_loader`). Final output length = NeMo's T_mel. + +### Implementation + validation status (Stage 4) + +All rows above are implemented in `src/arch/nemotron3_diar/` (model.cpp: load, step graph, chunk driver, +run / run_batch / push-audio; stream.cpp: host AOSC state machine + segment tracker; torch_logf.h) and gated: +every Stage-2 tensor passes on both oracle cases (92 s and the non-aligned 91.337 s), diar.probs at every +preset within 1.8e-5 of NeMo, push-audio bit-identical to the whole-file run, batched bit-identical to serial. + +Parity-critical details found during bring-up (all verified against the reference): +- CPU compute must be fp32: ggml-cpu BF16 matmuls round activations to BF16, which flips cache-compression + picks. The loader upcasts BF16/F16 matmul weights to exact F32 on CPU (default; `..._NATIVE_BF16=1` opts out). +- The compression top-k boundary is routinely separated by ~1e-7 (and exact ties are common), so the host + compression reproduces torch CPU arithmetic bit-for-bit: `torch.topk(sorted=False)` = ATen's + partial_sort / nth_element on (value, index) pairs; `torch.log` = Sleef logf_u10 (torch_logf.h); the + 8-speaker sum = ATen's 4-accumulator order. With that, C++ compression == torch compression on identical + inputs (replay gate: 370/370 compressions). The remaining source of divergence is fp32 GEMM noise (~1e-5) moving a boundary score across a + ~5e-6 gap on long meetings (6 of 96 acceptance meetings at very_high_latency); DER/JER are unchanged + to 0.001pp (see the family doc). +- NeMo frames: floor(n / 160) valid mel frames (not ceil); the chunk driver consumes exactly those. + +## Generation / KV Path + +No autoregressive generation. N/A. + +## Capabilities And Language Controls + +| Capability | Reference behavior | C++ API behavior | Family-doc Capability Validation row | +|------------|--------------------|------------------|--------------------------------------| +| Offline diarization | `diarize()` at very_high_latency | `transcribe_run` + preset run ext -> speaker segments; `diar.probs` dump | Offline diarization | +| Streaming diarization | `diarize()` at low_latency | same, preset LOW_LATENCY | Streaming diarization | +| Low-latency presets | very_low / ultra_low | preset ext | Low-latency presets | +| Push-audio | reference streaming at the same preset | `transcribe_stream_begin/feed/finalize` + STREAM-slot preset ext; incremental speaker segments | Push-audio live diarization | +| Speaker-activity tensor | preds [T,8] | `diar.probs` dump | Speaker-activity tensor | +| Batch | n/a | `run_batch` | Batch (offline) | +| Text / translate / timestamps | none | not exposed | OUT OF SCOPE rows | + +## Deviations From Closest Analog + +- Encoder is a 31L pre-LN RoPE Transformer over the concat (sortformer: FastConformer + 18L post-LN transformer). No transformer_encoder stage, no xscale, no rel-pos table. +- pre_encode is non-overlapping feature stacking (no conv subsampling, no left context needed). +- Output is 10 ms (x8 subpixel upsampler); the cache/FIFO machinery still runs on 80 ms avg-pooled probs. +- Learned silence embedding replaces the running-mean silence profile. +- 8 speakers, 4 card presets (different geometry from v2.1). +- BF16 weights (sortformer F32); mel window/fb are GGUF buffers. + +## Variant Notes + +- `Nemotron-3-Diarization`: family baseline (this port). diff --git a/samples/README.md b/samples/README.md index d3fd433f..5f4b2b07 100644 --- a/samples/README.md +++ b/samples/README.md @@ -65,3 +65,10 @@ resolve, not as a statement that the files are unencumbered. The bare `ja.wav`, `ko.wav`, `zh.wav`, `yue.wav` and `ru.wav` clips are referenced by family-doc smoke tests and by gigaam's profile override; they stay where they are. The `-short` / `-long` pairs above are the bench fixtures. + +## Generated diarization fixtures + +| file | duration | generator | sources | +| --- | ---: | --- | --- | +| `nemotron3-diar-8spk-mix.wav` | 92.0 s | `scripts/gen_nemotron3_diar_oracle_audio.py` (deterministic; RTTM in `tests/golden/nemotron3_diar/`) | `jfk.wav`, `product-names.wav` (provenance unrecorded, see above) and the FLEURS clips `zh-long`, `ru-long`, `ja-long`, `ko-long`, `vi-long`, `ar-long` (CC-BY-4.0, ids in the table above), each trimmed of leading/trailing silence and laid on an authored 8-speaker timeline | +| `nemotron3-diar-8spk-mix-trunc.wav` | 91.337 s | `scripts/gen_nemotron3_diar_oracle_audio.py` (first 1,461,392 samples of the mix above) | same as `nemotron3-diar-8spk-mix.wav`; a non-hop-aligned length (9133 mel frames) that exercises floor(n/160) framing and the partial final chunk | diff --git a/samples/nemotron3-diar-8spk-mix-trunc.wav b/samples/nemotron3-diar-8spk-mix-trunc.wav new file mode 100644 index 00000000..e43afe52 Binary files /dev/null and b/samples/nemotron3-diar-8spk-mix-trunc.wav differ diff --git a/samples/nemotron3-diar-8spk-mix.wav b/samples/nemotron3-diar-8spk-mix.wav new file mode 100644 index 00000000..54f5bb0d Binary files /dev/null and b/samples/nemotron3-diar-8spk-mix.wav differ diff --git a/scripts/batch_parity.py b/scripts/batch_parity.py index d99b98d1..df29aa88 100644 --- a/scripts/batch_parity.py +++ b/scripts/batch_parity.py @@ -14,7 +14,10 @@ - batched : --batch --batch-size N (transcribe_run_batch groups) and asserts the per-file hypothesis text is identical between them, for every -requested batch size. Because the serial path is the established source of +requested batch size. With --speakers, the CLI's "who spoke when" rows (the +`speakers` array) are part of the compared value too, so batched speaker +segments must equal serial ones exactly; use it for text-less diarizers +(nemotron3_diar), whose hypothesis text is always empty. Because the serial path is the established source of truth, "batched == serial in the same build" catches any regression the batched encoder/dispatch introduces, with no stale fixture to maintain. @@ -59,8 +62,9 @@ def find_repo_root(start: Path) -> Path: def run_cli(cli: Path, model: Path, list_file: Path, backend: str, - language: str | None, batch_size: int) -> dict[str, str]: - """Run transcribe-cli in batch JSONL mode; return {file: text}.""" + language: str | None, batch_size: int, + speakers: bool = False) -> dict[str, str]: + """Run transcribe-cli in batch JSONL mode; return {file: text[+speakers]}.""" cmd = [ str(cli), "-m", str(model), "--batch", str(list_file), "--batch-jsonl", @@ -82,7 +86,10 @@ def run_cli(cli: Path, model: Path, list_file: Path, backend: str, d = json.loads(line) if d.get("type") == "batch_header": continue - out[d["file"]] = d.get("text", "") + value = d.get("text", "") + if speakers and d.get("speakers"): + value += " |speakers=" + json.dumps(d["speakers"], sort_keys=True) + out[d["file"]] = value return out @@ -125,6 +132,8 @@ def main() -> int: help="cpu (default, deterministic) / auto / metal / ...") ap.add_argument("--language", default=None, help="optional BCP-47 hint passed to every utterance") + ap.add_argument("--speakers", action="store_true", + help="also compare the speaker-segment rows (diarizers)") ap.add_argument("--golden-out", type=Path, help="capture the serial baseline to this JSON and exit") ap.add_argument("--golden-in", type=Path, @@ -154,7 +163,7 @@ def main() -> int: # Serial baseline (source of truth for same-build parity). serial = run_cli(args.cli, args.model, list_file, args.backend, - args.language, batch_size=1) + args.language, batch_size=1, speakers=args.speakers) print(f"serial: {len(serial)} utterances") # Golden capture mode: freeze serial and exit. @@ -184,7 +193,7 @@ def main() -> int: # Batched vs serial (same build) for each requested batch size. for n in sizes: batched = run_cli(args.cli, args.model, list_file, args.backend, - args.language, batch_size=n) + args.language, batch_size=n, speakers=args.speakers) ok &= report(f"batch-size {n} vs serial", serial, batched) if args.golden_in: golden = json.loads(Path(args.golden_in).read_text())["texts"] diff --git a/scripts/convert-nemotron3_diar.py b/scripts/convert-nemotron3_diar.py new file mode 100644 index 00000000..56213c53 --- /dev/null +++ b/scripts/convert-nemotron3_diar.py @@ -0,0 +1,316 @@ +#!/usr/bin/env python3 +""" +convert-nemotron3_diar.py - convert NVIDIA Nemotron-3-Diarization (.nemo, +NeMo SortformerEncLabelModel with a RoPE TransformerEncoder) into a +reference-dtype GGUF. + +Nemotron-3-Diarization is an `encoder-diarizer` (Streaming Sortformer +lineage, 8 speakers, 10 ms output): no tokenizer, no decoder, no text. +Pipeline and tensor sources (363 tensors in the .nemo state_dict): + + preprocessor.featurizer.window -> frontend.window (BF16-stored symmetric Hann, 400) + preprocessor.featurizer.fb -> frontend.mel_filterbank (BF16-stored slaney mel fb, [128, 257]) + encoder.pre_encode.proj.* -> enc.pre_encode.proj.* (feature stacking x8, Linear 1024->512, no bias) + encoder.embed_norm.* -> enc.embed.norm.* (pre-block LayerNorm) + encoder.layers.{i}.* -> enc.blocks.{i}.* (31 pre-LN Transformer blocks, d=512, RoPE) + encoder.final_norm.* -> enc.final_norm.* + sortformer_modules.encoder_proj.* -> diar.encoder_proj.* (Linear 512 -> 192) + sortformer_modules.subpixel_upsample.* -> diar.upsample.conv.* (Conv1d 192 -> 192*8, k=3, pad=1) + sortformer_modules.first_hidden_to_hidden.* -> diar.fc1.* + sortformer_modules.single_hidden_to_spks.* -> diar.single_spk_head.* (8 sigmoid outputs) + sortformer_modules.learnable_sil_emb -> diar.sil_emb (AOSC silence embedding, 512) + +Dropped (never used by the inference forward): + sortformer_modules.hidden_to_spks.* legacy 384-in head, never called + sortformer_modules.activity_head.* training-only auxiliary head + +Reference dtype is BF16: the .nemo state_dict is all bfloat16. NeMo's +restore_from hands back fp32 tensors, so every tensor is checked to be +BF16-exact before it is written. Storage follows reference_dtype_for: + - Linear weights -> BF16 (lossless) + - biases, LayerNorms, sil_emb, frontend -> F32 (lossless upcast) + - diar.upsample.conv.weight -> F16 (the loader has no BF16 + conv kernel; BF16 -> F16 rounds only sub-6e-5 weights, max |diff| 3e-8) + +The frontend window + filterbank are embedded because NeMo's reference mel +uses the BF16-stored buffers; an fp32 recompute diverges by up to 1.9e-3 +in the window. + +The fused attention projection is kept fused: enc.blocks.{i}.attn.qkv.weight +is [3*d_model, d_model] in torch order, rows = [q | k | v], each block +laid out head-major (NeMo views it as (T, 3, H, D)). + +Usage (via the Nemotron-3-Diarization reference env, which has NeMo Speech): + uv run --project scripts/envs/nemotron3_diar \ + scripts/convert-nemotron3_diar.py nvidia/Nemotron-3-Diarization \ + --repo-id nvidia/Nemotron-3-Diarization \ + --revision f667ed73aee57d40cc39428eb768b4fd87a0a29e +""" + +from __future__ import annotations + +import argparse +import sys +from pathlib import Path + +import numpy as np +from gguf import GGMLQuantizationType, LlamaFileType + +sys.path.insert(0, str(Path(__file__).resolve().parent)) +from lib.gguf_common import ( # noqa: E402 + add_general_identity, + canonicalize_normalize, + encode_for_gguf, + gguf_name, + gguf_writer, + reference_dtype_for, + slug_from_repo_id, +) + +REFERENCE_TYPE = GGMLQuantizationType.BF16 +REFERENCE_DTYPE_LABEL = "BF16" +REFERENCE_FILE_TYPE = LlamaFileType.MOSTLY_BF16 + +ARCH = "nemotron3_diar" + +FRONTEND_TABLE = [ + ("preprocessor.featurizer.window", "frontend.window"), + ("preprocessor.featurizer.fb", "frontend.mel_filterbank"), +] + +ENCODER_TABLE = [ + ("encoder.pre_encode.proj.weight", "enc.pre_encode.proj.weight"), + ("encoder.embed_norm.weight", "enc.embed.norm.weight"), + ("encoder.embed_norm.bias", "enc.embed.norm.bias"), +] + +# Per-block suffix map (source suffix under encoder.layers.{i}. -> GGUF suffix +# under enc.blocks.{i}.). Norms named norm_1/norm_2 so the "norm_" F32 rule in +# reference_dtype_for / policy.cpp catches them. +BLOCK_TABLE = [ + ("norm1.weight", "norm_1.weight"), + ("norm1.bias", "norm_1.bias"), + ("attn.w_qkv.weight", "attn.qkv.weight"), + ("attn.out_proj.weight", "attn.out.weight"), + ("attn.out_proj.bias", "attn.out.bias"), + ("norm2.weight", "norm_2.weight"), + ("norm2.bias", "norm_2.bias"), + ("ffn.net.0.weight", "ff.in.weight"), + ("ffn.net.0.bias", "ff.in.bias"), + ("ffn.net.3.weight", "ff.out.weight"), + ("ffn.net.3.bias", "ff.out.bias"), +] + +FINAL_TABLE = [ + ("encoder.final_norm.weight", "enc.final_norm.weight"), + ("encoder.final_norm.bias", "enc.final_norm.bias"), +] + +# Diarization projection, subpixel upsampler, head, AOSC silence embedding. +HEAD_TABLE = [ + ("sortformer_modules.encoder_proj.weight", "diar.encoder_proj.weight"), + ("sortformer_modules.encoder_proj.bias", "diar.encoder_proj.bias"), + ("sortformer_modules.subpixel_upsample.weight", "diar.upsample.conv.weight"), + ("sortformer_modules.subpixel_upsample.bias", "diar.upsample.conv.bias"), + ("sortformer_modules.first_hidden_to_hidden.weight", "diar.fc1.weight"), + ("sortformer_modules.first_hidden_to_hidden.bias", "diar.fc1.bias"), + ("sortformer_modules.single_hidden_to_spks.weight", "diar.single_spk_head.weight"), + ("sortformer_modules.single_hidden_to_spks.bias", "diar.single_spk_head.bias"), + ("sortformer_modules.learnable_sil_emb", "diar.sil_emb"), +] + +# state_dict tensors the inference forward never reads. +EXPECTED_UNUSED_PREFIXES = ( + "sortformer_modules.hidden_to_spks.", + "sortformer_modules.activity_head.", +) + + +def _to_fp32_bf16_exact(name: str, t) -> np.ndarray: + import torch + if not isinstance(t, torch.Tensor): + raise TypeError(f"{name}: expected torch.Tensor, got {type(t).__name__}") + t = t.detach().cpu() + if not torch.equal(t.float(), t.to(torch.bfloat16).float()): + raise ValueError(f"{name}: not BF16-exact; the .nemo is expected to be all bfloat16") + return np.ascontiguousarray(t.float().numpy()) + + +def _add(writer, name: str, arr: np.ndarray, counts: dict[str, int]) -> None: + ggml_type = reference_dtype_for(name, REFERENCE_TYPE) + data, out_type = encode_for_gguf(arr, ggml_type) + writer.add_tensor(name, data, raw_dtype=out_type) + counts[out_type.name] = counts.get(out_type.name, 0) + 1 + + +def _resolve_nemo(model_spec: str, revision: str | None) -> str: + if model_spec.endswith(".nemo") or Path(model_spec).exists(): + return model_spec + from huggingface_hub import hf_hub_download + return hf_hub_download(model_spec, "Nemotron-3-Diarization.nemo", revision=revision) + + +def convert(model_spec: str, out_path: Path, repo_id: str | None = None, revision: str | None = None) -> None: + from omegaconf import OmegaConf + from nemo.collections.asr.models import SortformerEncLabelModel + + print(f"Output dtype: {REFERENCE_DTYPE_LABEL} (source/reference dtype)") + nemo_path = _resolve_nemo(model_spec, revision) + model = SortformerEncLabelModel.restore_from(restore_path=nemo_path, map_location="cpu", strict=False) + model.eval() + cfg = OmegaConf.to_container(model.cfg, resolve=True) + sd = model.state_dict() + sd_keys = set(sd) + + enc = cfg["encoder"] + pre = cfg["preprocessor"] + sm = cfg["sortformer_modules"] + if enc.get("self_attention_model") != "rope" or enc.get("subsampling") != "feature_stacking": + raise ValueError(f"unexpected encoder config: {enc.get('self_attention_model')=} {enc.get('subsampling')=}") + if enc.get("qkv_bias") or enc.get("qk_norm") or enc.get("xscaling") or not enc.get("pre_block_norm", True): + raise ValueError("encoder flags differ from the ported graph (qkv_bias/qk_norm/xscaling off, pre_block_norm on)") + n_layers = int(enc["n_layers"]) + d_model = int(enc["d_model"]) + n_heads = int(enc["n_heads"]) + subsampling = int(enc["subsampling_factor"]) + max_spk = int(cfg.get("max_num_of_spks", sm["num_spks"])) + out_sub = int(cfg.get("output_subsampling_factor", 1)) + upsample = subsampling // out_sub if cfg.get("high_resolution", False) else 1 + print(f"layers={n_layers} d_model={d_model} heads={n_heads} max_speakers={max_spk} upsample={upsample}") + + writer = gguf_writer(str(out_path), ARCH) + + add_general_identity( + writer, + name="Nemotron 3 Diarization", + basename="nemotron3-diar", + size_label="100M", + file_type=REFERENCE_FILE_TYPE, + languages=["en"], + author="NVIDIA", + organization="nvidia", + license="other", + license_name="openmdw-1.1", + repo_url=f"https://huggingface.co/{repo_id}" if repo_id else None, + description="End-to-end streaming speaker diarizer (encoder-diarizer): 31-layer RoPE Transformer + subpixel upsampler, 8 sigmoid speaker-activity outputs per 10ms frame.", + ) + + writer.add_string("stt.variant", out_path.parent.name) + + # ----- frontend (buffers embedded as frontend.window / frontend.mel_filterbank) ----- + sr = int(pre["sample_rate"]) + hop = int(round(float(pre["window_stride"]) * sr)) + writer.add_uint32("stt.frontend.sample_rate", sr) + writer.add_uint32("stt.frontend.num_mels", int(pre["features"])) + writer.add_uint32("stt.frontend.n_fft", int(pre["n_fft"])) + writer.add_uint32("stt.frontend.hop_length", hop) + writer.add_uint32("stt.frontend.win_length", int(round(float(pre["window_size"]) * sr))) + writer.add_string("stt.frontend.window", "hann_symmetric") + writer.add_string("stt.frontend.normalize", canonicalize_normalize(pre.get("normalize"))) + writer.add_float32("stt.frontend.pre_emphasis", float(pre.get("preemph") if pre.get("preemph") is not None else 0.97)) + # The cfg dither (1e-5) is training-only in NeMo; inference applies none. + writer.add_float32("stt.frontend.dither", 0.0) + + # ----- capabilities ----- + writer.add_bool("stt.capability.streaming", True) + writer.add_bool("stt.capability.speaker_diarization", True) + writer.add_bool("stt.capability.lang_detect", False) + writer.add_bool("stt.capability.translate", False) + writer.add_bool("stt.capability.timestamps", False) + + # ----- architecture dims ----- + p = f"stt.{ARCH}" + writer.add_uint32(f"{p}.max_speakers", max_spk) + writer.add_uint32(f"{p}.frame_hop", hop * subsampling) # encoder / AOSC frame, samples + writer.add_uint32(f"{p}.output_hop", hop * out_sub) # output frame, samples + writer.add_uint32(f"{p}.upsample_factor", upsample) + writer.add_uint32(f"{p}.encoder.n_layers", n_layers) + writer.add_uint32(f"{p}.encoder.d_model", d_model) + writer.add_uint32(f"{p}.encoder.n_heads", n_heads) + writer.add_uint32(f"{p}.encoder.d_ff", int(round(d_model * float(enc["ff_expansion"])))) + writer.add_uint32(f"{p}.encoder.feat_in", int(enc["feat_in"])) + writer.add_uint32(f"{p}.encoder.subsampling_factor", subsampling) + writer.add_string(f"{p}.encoder.subsampling", str(enc["subsampling"])) + writer.add_float32(f"{p}.encoder.rope_base", float(enc.get("rope_base", 10000.0))) + writer.add_float32(f"{p}.encoder.rotary_fraction", float(enc.get("rotary_fraction", 1.0))) + writer.add_float32(f"{p}.encoder.layer_norm_eps", 1e-5) + writer.add_string(f"{p}.encoder.activation", "gelu") + writer.add_uint32(f"{p}.head.d_model", int(sm["tf_d_model"])) + + # ----- AOSC compression constants (sortformer_modules) ----- + writer.add_uint32(f"{p}.aosc.spkcache_sil_frames_per_spk", int(sm["spkcache_sil_frames_per_spk"])) + writer.add_float32(f"{p}.aosc.pred_score_threshold", float(sm["pred_score_threshold"])) + writer.add_float32(f"{p}.aosc.scores_boost_latest", float(sm["scores_boost_latest"])) + writer.add_float32(f"{p}.aosc.sil_threshold", float(sm["sil_threshold"])) + writer.add_float32(f"{p}.aosc.strong_boost_rate", float(sm["strong_boost_rate"])) + writer.add_float32(f"{p}.aosc.weak_boost_rate", float(sm["weak_boost_rate"])) + writer.add_float32(f"{p}.aosc.min_pos_scores_rate", float(sm["min_pos_scores_rate"])) + writer.add_uint32(f"{p}.aosc.max_index", int(sm["max_index"])) + + # ----- default streaming preset (model card very_high_latency, 30.4 s) ----- + # The .nemo sortformer_modules values (fifo 0, chunk 264, rc 0) are + # training-time settings; the runtime presets live in the runner. + writer.add_uint32(f"{p}.stream.spkcache_len", 264) + writer.add_uint32(f"{p}.stream.fifo_len", 40) + writer.add_uint32(f"{p}.stream.chunk_len", 340) + writer.add_uint32(f"{p}.stream.chunk_right_context", 40) + writer.add_uint32(f"{p}.stream.spkcache_update_period", 300) + + # ----- tensors ----- + used: set[str] = set() + counts: dict[str, int] = {} + + def emit(src: str, dst: str): + if src not in sd: + raise KeyError(f"missing expected tensor: {src}") + arr = _to_fp32_bf16_exact(src, sd[src]) + if dst == "frontend.mel_filterbank" and arr.ndim == 3: + arr = np.ascontiguousarray(arr[0]) # [1, 128, 257] -> [128, 257] + _add(writer, dst, arr, counts) + used.add(src) + + for src, dst in FRONTEND_TABLE + ENCODER_TABLE: + emit(src, dst) + for i in range(n_layers): + for s_suf, d_suf in BLOCK_TABLE: + emit(f"encoder.layers.{i}.{s_suf}", f"enc.blocks.{i}.{d_suf}") + for src, dst in FINAL_TABLE + HEAD_TABLE: + emit(src, dst) + + # ----- unused-key audit ----- + skipped = sorted(sd_keys - used) + unexpected = [k for k in skipped if not k.startswith(EXPECTED_UNUSED_PREFIXES)] + if unexpected: + raise ValueError(f"{len(unexpected)} unmapped state_dict tensors, e.g. {unexpected[:8]}") + print(f"Emitted {len(used)} tensors {counts}; skipped {len(skipped)}: {skipped}") + + writer.write_header_to_file() + writer.write_kv_data_to_file() + writer.write_tensors_to_file() + writer.close() + print(f"Wrote GGUF: {out_path} ({out_path.stat().st_size/1e6:.1f} MB)") + + +def main() -> int: + ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) + ap.add_argument("model", help="HF repo id or path to a .nemo checkpoint") + ap.add_argument("--repo-id", default=None, help="HF repo id (for slug + provenance)") + ap.add_argument("--revision", default=None, help="HF revision to download the .nemo from") + ap.add_argument("--out", default=None, help="Override output GGUF path") + args = ap.parse_args() + + repo_id = args.repo_id or (args.model if "/" in args.model and not Path(args.model).exists() else None) + if args.out: + out_path = Path(args.out) + else: + if not repo_id: + raise SystemExit("error: pass --repo-id (or a HF repo id) so the output slug can be derived") + slug = slug_from_repo_id(repo_id) + out_path = Path("models") / slug / gguf_name(slug, REFERENCE_DTYPE_LABEL) + out_path.parent.mkdir(parents=True, exist_ok=True) + convert(args.model, out_path, repo_id=repo_id, revision=args.revision) + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/scripts/diar/check_nemotron3_diar_compress.py b/scripts/diar/check_nemotron3_diar_compress.py new file mode 100644 index 00000000..5f01e73d --- /dev/null +++ b/scripts/diar/check_nemotron3_diar_compress.py @@ -0,0 +1,97 @@ +#!/usr/bin/env python3 +""" +check_nemotron3_diar_compress.py - selection-emulation gate for the +Nemotron-3-Diarization speaker-cache compression. + +The C++ port (src/arch/nemotron3_diar/stream.cpp) re-implements NeMo's +_compress_spkcache on the host. Its correctness property is: GIVEN THE SAME +INPUT PREDICTIONS, it keeps exactly the frames NeMo keeps. (Comparing picks +against a reference *run* conflates this with fp32 GEMM noise in the inputs, +which can legitimately flip a near-tied pick.) + +For every C++ compression dump (TRANSCRIBE_NEMOTRON3_DIAR_COMPRESS_DUMP=1, +unforced run), this replays NeMo's SortformerModules._compress_spkcache on the +C++ `input_preds` and requires `topk_indices` and `is_disabled` to be +identical. Exit 1 on any mismatch. + + TRANSCRIBE_DUMP_DIR= TRANSCRIBE_NEMOTRON3_DIAR_COMPRESS_DUMP=1 \\ + TRANSCRIBE_NEMOTRON3_DIAR_PRESET= build/bin/transcribe-cli ... + uv run --project scripts/envs/nemotron3_diar scripts/diar/check_nemotron3_diar_compress.py \\ + --run : [--run : ...] +""" + +from __future__ import annotations + +import argparse +import json +import sys +from pathlib import Path + +import numpy as np + +REPO = Path(__file__).resolve().parent.parent.parent +sys.path.insert(0, str(REPO / "scripts")) +import dump_reference_nemotron3_diar_nemo as dumper # noqa: E402 + +MODEL = "nvidia/Nemotron-3-Diarization" +REVISION = "f667ed73aee57d40cc39428eb768b4fd87a0a29e" + + +def _load_f32(d: Path, name: str) -> np.ndarray: + meta = json.loads((d / f"{name}.json").read_text()) + return np.fromfile(d / f"{name}.f32", dtype=" int: + ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) + ap.add_argument("--run", action="append", required=True, help=":") + args = ap.parse_args() + + import torch + + m = dumper._load_nemo(MODEL, REVISION) + sm = m.sortformer_modules + captured: dict[str, np.ndarray] = {} + orig = sm._get_topk_indices + + def hook(scores): + idx, dis = orig(scores) + captured["idx"] = idx[0].numpy().astype(np.int64) + captured["dis"] = dis[0].numpy().astype(bool) + return idx, dis + + sm._get_topk_indices = hook + + total = bad = 0 + for spec in args.run: + d, preset = spec.rsplit(":", 1) + d = Path(d) + dumper._apply_preset(m, preset) + ks = sorted(int(p.name.split(".")[1]) for p in d.glob("compress.*.input_preds.json")) + if not ks: + print(f"FAIL {d}: no compress dumps (run with TRANSCRIBE_NEMOTRON3_DIAR_COMPRESS_DUMP=1)") + return 1 + n_bad = 0 + for k in ks: + preds = _load_f32(d, f"compress.{k:03d}.input_preds") + c_idx = _load_f32(d, f"compress.{k:03d}.topk_indices").astype(np.int64) + c_dis = _load_f32(d, f"compress.{k:03d}.is_disabled") != 0 + with torch.no_grad(): + sm._compress_spkcache(torch.zeros(1, preds.shape[0], sm.fc_d_model), + torch.from_numpy(np.ascontiguousarray(preds))[None].clone(), + torch.zeros(1, sm.fc_d_model)) + if not (np.array_equal(captured["idx"], c_idx) and np.array_equal(captured["dis"], c_dis)): + n_bad += 1 + print(f" MISMATCH {d.name} compression {k}: " + f"{int((captured['idx'] != c_idx).sum())} picks, " + f"{int((captured['dis'] != c_dis).sum())} disabled flags differ") + total += len(ks) + bad += n_bad + print(f"{'ok ' if n_bad == 0 else 'FAIL'} {d} [{preset}]: {len(ks) - n_bad}/{len(ks)} compressions " + f"select NeMo's frames on the port's own inputs") + print(f"{total - bad}/{total} compressions identical") + return 0 if bad == 0 else 1 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/scripts/diar/ingest_notsofar.py b/scripts/diar/ingest_notsofar.py new file mode 100644 index 00000000..6914e99e --- /dev/null +++ b/scripts/diar/ingest_notsofar.py @@ -0,0 +1,167 @@ +#!/usr/bin/env -S uv run --script +# /// script +# requires-python = ">=3.11" +# dependencies = [ +# "huggingface-hub>=0.20", +# "numpy>=1.26", +# "soundfile>=0.12", +# ] +# /// +""" +ingest_notsofar.py - build a NOTSOFAR-1 eval "MHM" diarization manifest. + +Diarization analog of scripts/wer/ingest.py for the NOTSOFAR-1 meeting +corpus (3-7 speakers). It covers speaker counts above AMI's 3-4, which +8-speaker diarizers (Nemotron-3-Diarization) need. + +Sources: + audio microsoft/NOTSOFAR (HF dataset, CC-BY-4.0, ungated), + benchmark-datasets/eval_set/240825.1_eval_full_with_GT/MTG// + close_talk/CT_*.wav: one headset per participant. Downloaded into + the HF hub cache. + labels forced-alignment RTTMs from popcornell/FastMSS + (resources/notsofar1-channels_mfa_rttms.tar.gz, `eval/` split), + the reference labels NVIDIA's Nemotron-3-Diarization card uses. + The per-recording RTTMs of one meeting are identical (devices share + the meeting timeline), so one RTTM labels the meeting. + +"MHM" (mix of headset microphones) is built here as the sample-wise sum +of the meeting's close-talk channels, zero-padded to the longest channel +(truncating would drop labeled speech if one headset stops early) and +peak-normalized to 0.9, then written as 16-bit PCM mono 16 kHz. +NVIDIA does not publish its exact mixing recipe; our DER gate compares +the reference model and the C++ port on this same audio, so the mix only +needs to be fixed and documented. + +Only the eval meetings with a forced-alignment RTTM are ingested (80). + +Output: + samples/diar/notsofar-mhm-eval/.wav + samples/diar/notsofar-mhm-eval/.rttm (FA labels, uri = ) + samples/diar/notsofar-mhm-eval-fa.manifest.jsonl + {"id","audio","rttm","duration","num_speakers"} + +Usage: + uv run scripts/diar/ingest_notsofar.py [--min-speakers 5] [--limit N] +""" + +from __future__ import annotations + +import argparse +import io +import json +import sys +import tarfile +import urllib.request +from pathlib import Path + +import numpy as np +import soundfile as sf + +SR = 16000 +REPO = Path(__file__).resolve().parent.parent.parent +HF_REPO = "microsoft/NOTSOFAR" +EVAL_PREFIX = "benchmark-datasets/eval_set/240825.1_eval_full_with_GT/MTG" +FA_URL = "https://raw.githubusercontent.com/popcornell/FastMSS/master/resources/notsofar1-channels_mfa_rttms.tar.gz" +DS_ID = "notsofar-mhm-eval" + + +def _fa_rttms() -> dict[str, list[str]]: + """meeting ('MTG_32000') -> RTTM lines of its first eval recording.""" + raw = urllib.request.urlopen(FA_URL).read() + out: dict[str, list[str]] = {} + with tarfile.open(fileobj=io.BytesIO(raw), mode="r:gz") as tar: + for member in sorted(tar.getmembers(), key=lambda m: m.name): + parts = member.name.split("/") + if len(parts) != 2 or parts[0] != "eval" or not parts[1].endswith(".rttm"): + continue + meeting = f"MTG_{parts[1][1:6]}" + if meeting in out: + continue + text = tar.extractfile(member).read().decode() + out[meeting] = [l for l in text.splitlines() if l.strip()] + return out + + +def _mix_close_talk(meeting: str, listing: list[str]) -> tuple[np.ndarray, list[float]]: + from huggingface_hub import hf_hub_download + + prefix = f"{EVAL_PREFIX}/{meeting}/close_talk/" + files = sorted(f for f in listing if f.startswith(prefix) and f.endswith(".wav")) + if not files: + raise SystemExit(f"error: no close-talk channels for {meeting}") + chans = [] + for f in files: + audio, sr = sf.read(hf_hub_download(HF_REPO, f, repo_type="dataset"), dtype="float32", always_2d=False) + if audio.ndim > 1: + audio = audio.mean(axis=1) + if sr != SR: + raise SystemExit(f"error: {f} is {sr} Hz, expected {SR}") + chans.append(audio) + mix = np.zeros(max(len(c) for c in chans), dtype=np.float64) + for c in chans: + mix[: len(c)] += c + peak = float(np.max(np.abs(mix))) or 1.0 + return (mix / peak * 0.9).astype(np.float32), [len(c) / SR for c in chans] + + +def main() -> int: + ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) + ap.add_argument("--min-speakers", type=int, default=0, help="Keep only meetings with >= N speakers.") + ap.add_argument("--limit", type=int, default=0, help="Ingest only the first N meetings (smoke).") + ap.add_argument("--force", action="store_true") + args = ap.parse_args() + + from huggingface_hub import HfApi, hf_hub_download + + out_dir = REPO / "samples" / "diar" / DS_ID + manifest = REPO / "samples" / "diar" / f"{DS_ID}-fa.manifest.jsonl" + if manifest.exists() and not args.force: + n = sum(1 for _ in open(manifest)) + print(f"OK already exists: {manifest} ({n} meetings). Use --force to rebuild.") + return 0 + out_dir.mkdir(parents=True, exist_ok=True) + + listing = HfApi().list_repo_files(HF_REPO, repo_type="dataset") + rttms = _fa_rttms() + meetings = sorted(rttms) + print(f"{len(meetings)} eval meetings with forced-alignment RTTMs", flush=True) + + entries, total_dur = [], 0.0 + for meeting in meetings: + lines = rttms[meeting] + n_spk = len({l.split()[7] for l in lines}) + if n_spk < args.min_speakers: + continue + if args.limit and len(entries) >= args.limit: + break + audio, ch_secs = _mix_close_talk(meeting, listing) + meta = json.load(open(hf_hub_download(HF_REPO, f"{EVAL_PREFIX}/{meeting}/gt_meeting_metadata.json", + repo_type="dataset"))) + last_label = max(float(l.split()[3]) + float(l.split()[4]) for l in lines) + wav, rttm = out_dir / f"{meeting}.wav", out_dir / f"{meeting}.rttm" + sf.write(str(wav), audio, SR, subtype="PCM_16") + # Re-key the RTTM uri from the recording id (S32000201) to the meeting. + rttm.write_text("\n".join(" ".join([p[0], meeting, *p[2:]]) for p in (l.split() for l in lines)) + "\n") + dur = len(audio) / SR + total_dur += dur + entries.append({"id": meeting, "audio": str(wav.relative_to(REPO)), "rttm": str(rttm.relative_to(REPO)), + "duration": round(dur, 2), "num_speakers": n_spk, + "close_talk_sec": [round(x, 2) for x in ch_secs], + "meeting_duration_sec": round(float(meta["MeetingDurationSec"]), 2), + "last_label_end_sec": round(last_label, 2)}) + flag = " WARN labels past audio" if last_label > dur + 0.5 else "" + print(f" [{len(entries) - 1}] {meeting}: {dur / 60:.1f}min, {n_spk} spk, {len(lines)} turns, " + f"ct {min(ch_secs):.1f}-{max(ch_secs):.1f}s, meta {float(meta['MeetingDurationSec']):.1f}s, " + f"last label {last_label:.1f}s{flag}", flush=True) + + with open(manifest, "w") as f: + for e in entries: + f.write(json.dumps(e) + "\n") + print(f"\nmanifest: {manifest.relative_to(REPO)}") + print(f"{len(entries)} meetings, {total_dur / 60:.1f} min total audio") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/scripts/diar/run_cpp_nemotron3_diar.py b/scripts/diar/run_cpp_nemotron3_diar.py new file mode 100644 index 00000000..e0c209b0 --- /dev/null +++ b/scripts/diar/run_cpp_nemotron3_diar.py @@ -0,0 +1,167 @@ +#!/usr/bin/env python3 +""" +run_cpp_nemotron3_diar.py - C++ Nemotron-3-Diarization over a diarization +manifest (the C++ counterpart of run_reference_nemotron3_diar_nemo.py). + +For each meeting this runs transcribe-cli with the streaming operating point +pinned (--diar-preset, the public RUN-slot extension; `small` goes through +the TRANSCRIBE_NEMOTRON3_DIAR_PRESET validation hook) and the tensor dump +enabled. The dumped raw `diar.probs` [T, 8] (10 ms) is post-processed with the +SAME function the reference runner uses (probs_to_rttm: NeMo +ts_vad_post_processing, onset/offset 0.5, no filtering), so only the +probabilities differ between reference and port. Writes per meeting: + /.probs.npy, /.rttm +and a summary JSONL. With --ref-pred-dir, each meeting's probs are also +diffed against the reference run's saved .probs.npy (the one-for-one +debug oracle) and the per-meeting max |d| is recorded. + + uv run --project scripts/envs/nemotron3_diar scripts/diar/run_cpp_nemotron3_diar.py \\ + --manifest samples/diar/ami-ihm-test-fa.manifest.jsonl \\ + --gguf models/Nemotron-3-Diarization/Nemotron-3-Diarization-BF16.gguf \\ + --preset very_high_latency \\ + --pred-dir reports/diar/pred/Nemotron-3-Diarization-BF16-ami-ihm-test-fa-very_high_latency \\ + --ref-pred-dir reports/diar/pred/Nemotron-3-Diarization-REF-ami-ihm-test-fa-very_high_latency \\ + --out reports/diar/Nemotron-3-Diarization-BF16.ami-ihm-test-fa.very_high_latency.jsonl + uv run scripts/diar/score_der.py --manifest ... --pred-dir ... --out ....score.json +""" + +from __future__ import annotations + +import argparse +import json +import os +import subprocess +import sys +import tempfile +import time +from pathlib import Path + +import numpy as np + +REPO = Path(__file__).resolve().parent.parent.parent +sys.path.insert(0, str(REPO / "scripts" / "diar")) +from run_reference_nemotron3_diar_nemo import load_vad_cfg, probs_to_rttm # noqa: E402 + +CARD_PRESETS = ("very_high_latency", "low_latency", "very_low_latency", "ultra_low_latency") + + +def _load_probs(dump_dir: Path) -> np.ndarray: + meta = json.loads((dump_dir / "diar.probs.json").read_text()) + shape = tuple(int(x) for x in meta["shape"]) + data = np.fromfile(dump_dir / "diar.probs.f32", dtype=" int: + ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) + ap.add_argument("--manifest", required=True) + ap.add_argument("--gguf", required=True) + ap.add_argument("--pred-dir", required=True) + ap.add_argument("--out", required=True) + ap.add_argument("--preset", default="very_high_latency", choices=CARD_PRESETS + ("small",)) + ap.add_argument("--ref-pred-dir", default=None, help="Reference run's pred dir (.probs.npy) to diff against.") + ap.add_argument("--cli", default=str(REPO / "build" / "bin" / "transcribe-cli")) + ap.add_argument("--backend", default="cpu") + ap.add_argument("--threads", type=int, default=0, help="0 -> CLI default") + ap.add_argument("--batch-size", type=int, default=1, + help="> 1: run groups of N meetings through one transcribe_run_batch call " + "(per-meeting probs from diar.probs.b); compute_sec is then the group's " + "wall time split evenly.") + ap.add_argument("--postprocessing-yaml", default=None, + help="Optional NeMo post-processing YAML; default = onset/offset 0.5, no filtering.") + ap.add_argument("--limit", type=int, default=0, help="Run only the first N meetings (smoke).") + args = ap.parse_args() + + entries = [json.loads(l) for l in open(args.manifest) if l.strip()] + if args.limit: + entries = entries[: args.limit] + pred_dir, out_path = Path(args.pred_dir), Path(args.out) + pred_dir.mkdir(parents=True, exist_ok=True) + out_path.parent.mkdir(parents=True, exist_ok=True) + cfg_vad = load_vad_cfg(args.postprocessing_yaml) + + def run_group(group: list[dict]) -> tuple[list[np.ndarray], float]: + """One CLI call: a single meeting, or one transcribe_run_batch over the group.""" + wavs = [e["audio"] if Path(e["audio"]).is_absolute() else str(REPO / e["audio"]) for e in group] + t0 = time.time() + with tempfile.TemporaryDirectory(prefix="n3d-cpp-", dir=REPO.parent / "tmp") as tmp: + env = os.environ.copy() + env["TRANSCRIBE_DUMP_DIR"] = tmp + cmd = [args.cli, "--backend", args.backend, "-m", args.gguf] + if args.preset in CARD_PRESETS: + cmd += ["--diar-preset", args.preset] + else: + env["TRANSCRIBE_NEMOTRON3_DIAR_PRESET"] = args.preset + if args.threads > 0: + cmd += ["--threads", str(args.threads)] + if args.batch_size > 1: + lst = Path(tmp) / "list.txt" + lst.write_text("\n".join(wavs) + "\n") + cmd += ["--batch", str(lst), "--batch-jsonl", "-q", "--batch-size", str(len(group))] + else: + cmd.append(wavs[0]) + res = subprocess.run(cmd, cwd=REPO, env=env, stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, text=True, errors="replace") + if res.returncode != 0: + sys.stderr.write(res.stdout or "") + raise SystemExit(f"error: transcribe-cli failed on {[e['id'] for e in group]} (exit {res.returncode})") + if args.batch_size > 1: + probs = [] + for i in range(len(group)): + d = Path(tmp) / f"b{i}" + d.mkdir() + for ext in ("f32", "json"): + (Path(tmp) / f"diar.probs.b{i}.{ext}").rename(d / f"diar.probs.{ext}") + probs.append(_load_probs(d)) + else: + probs = [_load_probs(Path(tmp))] + return probs, time.time() - t0 + + rows = [] + step = max(1, args.batch_size) + for g0 in range(0, len(entries), step): + group = entries[g0:g0 + step] + group_probs, group_dt = run_group(group) + for i, (e, probs) in enumerate(zip(group, group_probs), start=g0): + dt = group_dt / len(group) + rows.append(_emit(args, pred_dir, cfg_vad, i, e, probs, dt)) + + with open(out_path, "w") as f: + for r in rows: + f.write(json.dumps(r) + "\n") + print(f"wrote {out_path} ({len(rows)} meetings)") + return 0 + + +def _emit(args, pred_dir: Path, cfg_vad, i: int, e: dict, probs: np.ndarray, dt: float) -> dict: + """Save one meeting's probs + RTTM and return its summary row.""" + uri = e["id"] + np.save(pred_dir / f"{uri}.probs.npy", probs) + lines = probs_to_rttm(uri, probs, cfg_vad, offset=float(e.get("offset", 0.0))) + (pred_dir / f"{uri}.rttm").write_text("\n".join(lines) + "\n") + dur = e.get("duration") or 1.0 + row = {"id": uri, "hyp_rttm": str(pred_dir / f"{uri}.rttm"), "probs": str(pred_dir / f"{uri}.probs.npy"), + "n_segments": len(lines), "n_frames": int(probs.shape[0]), + "n_active_speakers": int((probs.max(0) > 0.5).sum()), "num_speakers_ref": e.get("num_speakers"), + "duration": dur, "compute_sec": round(dt, 2), "rtf": round(dt / max(dur, 1e-6), 4), + "preset": args.preset, "batch_size": args.batch_size} + msg = "" + if args.ref_pred_dir: + ref = np.load(Path(args.ref_pred_dir) / f"{uri}.probs.npy") + n = min(len(ref), len(probs)) + d = np.abs(ref[:n] - probs[:n]) + row.update({"ref_frames": int(len(ref)), "probs_max_abs": float(d.max()), + "probs_mean_abs": float(d.mean()), + "frames_flipped": int(((ref[:n] > 0.5) != (probs[:n] > 0.5)).sum())}) + msg = f", vs ref max|d| {row['probs_max_abs']:.2e} flips {row['frames_flipped']}" + if len(ref) != len(probs): + msg += f" FRAMES {len(probs)} != ref {len(ref)}" + print(f" [{i}] {uri}: {len(lines)} segs, {row['n_active_speakers']} spk, {dt:.1f}s " + f"(rtf {row['rtf']:.3f}){msg}", flush=True) + return row + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/scripts/diar/run_reference_nemotron3_diar_nemo.py b/scripts/diar/run_reference_nemotron3_diar_nemo.py new file mode 100644 index 00000000..7f083c96 --- /dev/null +++ b/scripts/diar/run_reference_nemotron3_diar_nemo.py @@ -0,0 +1,144 @@ +#!/usr/bin/env python3 +""" +run_reference_nemotron3_diar_nemo.py - measured Nemotron-3-Diarization +reference baseline over a diarization manifest (diarization analog of +scripts/wer/run_reference__*.py). + +Runs the NeMo Speech reference (the BF16 .nemo, fp32 compute on CPU) over +every meeting of an AMI/NOTSOFAR-style manifest at one streaming preset +and writes, per meeting: + /.probs.npy raw [T, 8] sigmoid activity at 10 ms (the + one-for-one debug oracle for C++ drift) + /.rttm segments from those probs +plus a summary JSONL. Score with scripts/diar/score_der.py. + +Segments are derived from the saved probs with NeMo's +ts_vad_post_processing (the same call the C++ runner uses on the C++ +probs), so reference and port are post-processed identically. Without +--postprocessing-yaml the thresholds are onset=offset=0.5 with no +padding / min-duration filtering, which is what NeMo's +e2e_diarize_speech.py applies when no post-processing YAML is given (the +model-card protocol). + +This is the ONE-TIME reference run: Stage 4 and Stage 7 compare the C++ +port's DER/JER against this measured baseline, not the publisher's number. + + uv run --project scripts/envs/nemotron3_diar \\ + scripts/diar/run_reference_nemotron3_diar_nemo.py \\ + --manifest samples/diar/ami-ihm-test-fa.manifest.jsonl \\ + --model nvidia/Nemotron-3-Diarization --revision f667ed73aee57d40cc39428eb768b4fd87a0a29e \\ + --preset very_high_latency \\ + --pred-dir reports/diar/pred/Nemotron-3-Diarization-REF-ami-ihm-test-fa-very_high_latency \\ + --out reports/diar/Nemotron-3-Diarization-REF.ami-ihm-test-fa.very_high_latency.jsonl +""" + +from __future__ import annotations + +import argparse +import json +import sys +import time +from pathlib import Path + +import numpy as np + +REPO = Path(__file__).resolve().parent.parent.parent +sys.path.insert(0, str(REPO / "scripts")) +import dump_reference_nemotron3_diar_nemo as dumper # noqa: E402 + +# Nemotron-3-Diarization emits one prediction per 10 ms feature frame. +UNIT_10MS_FRAME_COUNT = 1 + + +def probs_to_rttm(uri: str, probs: np.ndarray, cfg_vad, offset: float = 0.0) -> list[str]: + """Apply NeMo ts_vad_post_processing per speaker and emit RTTM lines.""" + import torch + from nemo.collections.asr.parts.utils.vad_utils import ts_vad_post_processing + + lines: list[str] = [] + for spk in range(probs.shape[1]): + ts_mat = ts_vad_post_processing( + torch.from_numpy(np.ascontiguousarray(probs[:, spk])), + cfg_vad_params=cfg_vad, + unit_10ms_frame_count=UNIT_10MS_FRAME_COUNT, + bypass_postprocessing=False, + ) + for stt, end in ts_mat.tolist(): + stt, end = round(stt + offset, 2), round(end + offset, 2) + if end - stt <= 0: + continue + lines.append(f"SPEAKER {uri} 1 {stt:.3f} {end - stt:.3f} speaker_{spk} ") + return lines + + +def load_vad_cfg(postprocessing_yaml: str | None): + from omegaconf import OmegaConf + + if postprocessing_yaml: + pp = OmegaConf.load(postprocessing_yaml) + return pp.parameters if "parameters" in pp else pp + return OmegaConf.create({"onset": 0.5, "offset": 0.5, "pad_onset": 0.0, "pad_offset": 0.0, + "min_duration_on": 0.0, "min_duration_off": 0.0}) + + +def main() -> int: + ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) + ap.add_argument("--manifest", required=True) + ap.add_argument("--model", required=True, help="HF repo id or path to the .nemo checkpoint") + ap.add_argument("--revision", default=None) + ap.add_argument("--preset", default="very_high_latency", choices=list(dumper.PRESETS)) + ap.add_argument("--pred-dir", required=True) + ap.add_argument("--out", required=True) + ap.add_argument("--device", default="cpu") + ap.add_argument("--batch-size", type=int, default=1, help="Accepted for the runner contract; meetings run one at a time.") + ap.add_argument("--postprocessing-yaml", default=None, + help="Optional NeMo post-processing YAML; default = onset/offset 0.5, no filtering.") + ap.add_argument("--limit", type=int, default=0, help="Run only the first N meetings (smoke).") + args = ap.parse_args() + + import soundfile as sf + import torch + + entries = [json.loads(l) for l in open(args.manifest) if l.strip()] + if args.limit: + entries = entries[: args.limit] + pred_dir, out_path = Path(args.pred_dir), Path(args.out) + pred_dir.mkdir(parents=True, exist_ok=True) + out_path.parent.mkdir(parents=True, exist_ok=True) + + m = dumper._load_nemo(args.model, args.revision).to(args.device) + geom = dumper._apply_preset(m, args.preset) + cfg_vad = load_vad_cfg(args.postprocessing_yaml) + print(f"preset={args.preset} {geom}", flush=True) + + rows = [] + for i, e in enumerate(entries): + wav = e["audio"] if Path(e["audio"]).is_absolute() else str(REPO / e["audio"]) + uri = e["id"] + audio, sr = sf.read(wav, dtype="float32", always_2d=False) + t0 = time.time() + with torch.no_grad(): + _, probs = m.diarize(audio=[audio], batch_size=1, sample_rate=sr, include_tensor_outputs=True) + dt = time.time() - t0 + p = dumper._f32(torch.as_tensor(np.asarray(probs[0]))) + np.save(pred_dir / f"{uri}.probs.npy", p) + lines = probs_to_rttm(uri, p, cfg_vad, offset=float(e.get("offset", 0.0))) + (pred_dir / f"{uri}.rttm").write_text("\n".join(lines) + "\n") + dur = e.get("duration") or len(audio) / sr + rows.append({"id": uri, "hyp_rttm": str(pred_dir / f"{uri}.rttm"), "probs": str(pred_dir / f"{uri}.probs.npy"), + "n_segments": len(lines), "n_frames": int(p.shape[0]), + "n_active_speakers": int((p.max(0) > 0.5).sum()), "num_speakers_ref": e.get("num_speakers"), + "duration": dur, "compute_sec": round(dt, 2), "rtf": round(dt / max(dur, 1e-6), 4), + "preset": args.preset}) + print(f" [{i}] {uri}: {len(lines)} segs, {rows[-1]['n_active_speakers']}/{e.get('num_speakers')} spk, " + f"{dt:.1f}s (rtf {rows[-1]['rtf']:.3f})", flush=True) + + with open(out_path, "w") as f: + for r in rows: + f.write(json.dumps(r) + "\n") + print(f"wrote {out_path} ({len(rows)} meetings)") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/scripts/dump_reference_nemotron3_diar_nemo.py b/scripts/dump_reference_nemotron3_diar_nemo.py new file mode 100644 index 00000000..27c49c5d --- /dev/null +++ b/scripts/dump_reference_nemotron3_diar_nemo.py @@ -0,0 +1,398 @@ +#!/usr/bin/env python3 +""" +dump_reference_nemotron3_diar_nemo.py - Nemotron-3-Diarization reference +tensors from NVIDIA NeMo Speech (the canonical reference), plus HF +transformers cross-check / push-audio dumps. + +Nemotron-3-Diarization is a streaming Sortformer diarizer (arch pattern +`encoder-diarizer`), NOT a transcription model: the output is a T x 8 +speaker-activity probability matrix at 10 ms (arrival-order columns) plus +the derived speaker segments. + +Forward (one streaming step; cache/FIFO frames are pre_encode embeddings): + mel [128, T_mel] (AudioToMelSpectrogramPreprocessor, normalize=NA, + BF16-rounded window/fb buffers from the checkpoint) + -> pre_encode FeatureStacking x8 + Linear 1024->512 (no bias) + -> [spkcache | fifo | chunk | rc] concat + -> embed_norm LayerNorm + -> 31 x pre-LN Transformer layer (RoPE, positions restart per step) + -> final_norm LayerNorm [T_step, 512] + -> encoder_proj Linear 512->192 [T_step, 192] + -> subpixel_upsample Conv1d 192->1536 k3, reshape [8*T_step, 192] + -> ReLU -> first_hidden_to_hidden -> ReLU -> single_hidden_to_spks + (dropout is inactive at eval) + [8*T_step, 8] logits + -> sigmoid; AOSC scoring uses the x8 average-pooled probs. + +The model always runs the streaming path (streaming_mode=true in the +checkpoint cfg); "offline" is the very_high_latency preset. + +Subcommands: + encoder Per-stage activations of the FIRST streaming step (empty + cache, input = chunk + right context) at --preset (default + very_high_latency): a single clean forward to bring up the + graph against. Also the full-clip mel. + diarize Streaming m.diarize() at --preset: diar.probs [T_mel, 8] + + speaker segments, optional AOSC compression internals. + hf-stream HF transformers push-audio reference: the processor cuts + audio chunk by chunk (per-chunk mel, center only on the + first chunk) in streaming_mode low_latency / very_low_latency + / ultra_low_latency. Dumps diar.probs. + hf-offline HF transformers whole-recording forward (config offline + preset == very_high_latency). Dumps diar.probs. + +Weights: the .nemo stores BF16; NeMo restore_from upcasts parameters to +fp32 (values stay BF16-exact) and this dumper computes in fp32 on CPU. + + uv run --project scripts/envs/nemotron3_diar \ + scripts/dump_reference_nemotron3_diar_nemo.py encoder \ + --model nvidia/Nemotron-3-Diarization \ + --audio samples/nemotron3-diar-8spk-mix.wav \ + --out build/validate/nemotron3_diar/Nemotron-3-Diarization/nemotron3-diar-8spk-mix/encoder/ref +""" + +from __future__ import annotations + +import argparse +import json +import sys +from pathlib import Path +from typing import Any + +import numpy as np +import soundfile as sf +import torch + +sys.path.insert(0, str(Path(__file__).resolve().parent)) +from lib import ref_dump # noqa: E402 + +write_tensor = ref_dump.write_tensor +write_transcript = ref_dump.write_transcript + +NEMO_FILENAME = "Nemotron-3-Diarization.nemo" + +# Streaming presets, 80 ms encoder frames. Model-card table, plus `small` +# (diagnostic only: forces multi-chunk FIFO + several compressions on a +# short clip). Keep in sync with the C++ preset table. +PRESETS = { + "very_high_latency": dict(spkcache_len=264, fifo_len=40, chunk_len=340, + chunk_right_context=40, spkcache_update_period=300), + "low_latency": dict(spkcache_len=264, fifo_len=264, chunk_len=9, + chunk_right_context=4, spkcache_update_period=222), + "very_low_latency": dict(spkcache_len=264, fifo_len=264, chunk_len=6, + chunk_right_context=2, spkcache_update_period=222), + "ultra_low_latency": dict(spkcache_len=264, fifo_len=264, chunk_len=3, + chunk_right_context=1, spkcache_update_period=222), + "small": dict(spkcache_len=24, fifo_len=10, chunk_len=20, + chunk_right_context=2, spkcache_update_period=20), +} +HF_STREAMING_MODES = ("low_latency", "very_low_latency", "ultra_low_latency") + +# Encoder layers dumped from the first step (all 31 would be ~6 MB each on +# the 380-frame step; first/second/middle/last localize drift well enough). +DUMP_LAYERS = (0, 1, 15, 30) + + +def _resolve_nemo(model: str, revision: str | None) -> str: + if model.endswith(".nemo") or Path(model).exists(): + return model + from huggingface_hub import hf_hub_download + + return hf_hub_download(model, NEMO_FILENAME, revision=revision) + + +def _load_nemo(model: str, revision: str | None): + from nemo.collections.asr.models import SortformerEncLabelModel + + m = SortformerEncLabelModel.restore_from( + restore_path=_resolve_nemo(model, revision), map_location="cpu", strict=False + ) + m = m.float().eval() + return m + + +def _apply_preset(m, preset: str) -> dict[str, int]: + cfg = PRESETS[preset] + sm = m.sortformer_modules + for k, v in cfg.items(): + setattr(sm, k, v) + m._check_streaming_parameters() + return {k: int(getattr(sm, k)) for k in cfg} + + +def _read_audio(path: str) -> tuple[np.ndarray, int]: + audio, sr = sf.read(path, dtype="float32", always_2d=False) + if audio.ndim > 1: + audio = audio.mean(axis=1) + if sr != 16000: + raise SystemExit(f"error: {path} is {sr} Hz; the reference expects 16 kHz") + return audio.astype(np.float32), int(sr) + + +def _f32(t: torch.Tensor) -> np.ndarray: + a = t.detach().to(torch.float32).cpu().numpy() + if a.ndim == 3 and a.shape[0] == 1: + a = a[0] + return np.ascontiguousarray(a.astype(np.float32)) + + +def _src(model: str, hook: str, **extra: Any) -> dict[str, Any]: + return {"framework": "nemo", "model": model, "hook": hook, **extra} + + +def cmd_encoder(args: argparse.Namespace) -> int: + m = _load_nemo(args.model, args.revision) + geom = _apply_preset(m, args.preset) + audio, _ = _read_audio(args.audio) + out_dir = Path(args.out) + sig = torch.tensor(audio).unsqueeze(0) + length = torch.tensor([audio.shape[0]]) + + with torch.no_grad(): + mel, mel_len = m.process_signal(audio_signal=sig, audio_signal_length=length) + n_mel = int(mel_len[0]) + write_tensor("enc.mel.in", _f32(mel[0, :, :n_mel]), "frontend", + _src(args.model, "process_signal (trimmed to valid length)", + layout="[n_mels, T_mel]"), out_dir=out_dir) + + captured: dict[str, torch.Tensor] = {} + + def grab(name, pick=lambda inp, out: out): + def hook(_mod, inp, out): + if name in captured: # first streaming step only + return + t = pick(inp, out) + t = t[0] if isinstance(t, (tuple, list)) else t + captured[name] = t.detach().to(torch.float32).cpu() + return hook + + enc, sm = m.encoder, m.sortformer_modules + hooks = [ + (enc.pre_encode, grab("enc.pre_encode.out")), + (enc.embed_norm, grab("enc.embed_norm.out")), + *[(enc.layers[i], grab(f"enc.layers.{i}.out")) for i in DUMP_LAYERS], + (enc.final_norm, grab("enc.final_norm.out")), + (sm.encoder_proj, grab("diar.encoder_proj.out")), + (sm.subpixel_upsample, grab("diar.subpixel_conv.out")), + (sm.single_hidden_to_spks, grab("diar.logits")), + ] + handles = [mod.register_forward_hook(fn) for mod, fn in hooks] + # upsample_hidden is a method (Conv1d + [T, 8, H] reshape), not a module. + orig_upsample = sm.upsample_hidden + + def upsample_hidden(hidden_states): + out = orig_upsample(hidden_states) + captured.setdefault("diar.upsample.out", out.detach().to(torch.float32).cpu()) + return out + + sm.upsample_hidden = upsample_hidden + try: + with torch.no_grad(): + m.forward(audio_signal=sig, audio_signal_length=length) + finally: + for h in handles: + h.remove() + sm.upsample_hidden = orig_upsample + + step_frames = min(geom["chunk_len"] + geom["chunk_right_context"], -(-n_mel // 8)) + layouts = { + "enc.pre_encode.out": "[T_step, 512] feature-stacked projection (cached state lives at this point)", + "diar.subpixel_conv.out": "[T_step, 1536] conv output before the x8 reshape", + "diar.upsample.out": "[8*T_step, 192] upsampled hidden after the [T, 8, H] reshape (classifier input, pre-ReLU)", + "diar.logits": "[8*T_step, 8] pre-sigmoid speaker logits, arrival order", + } + for name, t in captured.items(): + a = _f32(t) + if name == "diar.subpixel_conv.out" and a.shape[0] == 1536: + a = np.ascontiguousarray(a.T) # Conv1d emits [C, T]; store time-major + write_tensor(name, a, "encoder", + _src(args.model, f"{name} (first streaming step)", preset=args.preset, + streaming_cfg=geom, step_encoder_frames=step_frames, + layout=layouts.get(name, "[T_step, D] time-major")), + out_dir=out_dir) + missing = [n for n in ("enc.pre_encode.out", "diar.logits") if n not in captured] + if missing: + raise SystemExit(f"error: hooks did not fire for {missing}") + print(f"wrote {len(captured) + 1} encoder-stage tensors (step0: {step_frames} enc frames) to {out_dir}") + return 0 + + +def _install_compress_hooks(m) -> list[dict]: + """Record, per _compress_spkcache call, the selected (sorted) frame indices, + the is_disabled mask, and the input / gathered preds (batch=1, squeezed).""" + sm = m.sortformer_modules + calls: list[dict] = [] + orig_topk = sm._get_topk_indices + orig_compress = sm._compress_spkcache + + def wrapped_topk(scores): + topk_indices, is_disabled = orig_topk(scores) + calls.append({ + "n_frames": int(scores.shape[1]), + "topk_indices": topk_indices[0].detach().cpu().numpy().astype(np.int64), + "is_disabled": is_disabled[0].detach().cpu().numpy().astype(bool), + }) + return topk_indices, is_disabled + + def wrapped_compress(*a, **kw): + in_preds = kw["preds"] if "preds" in kw else a[1] + in_preds = in_preds[0].detach().cpu().numpy().astype(np.float32) + spkcache, spkcache_preds, spk_perm = orig_compress(*a, **kw) + if calls: + calls[-1]["input_preds"] = in_preds + calls[-1]["spkcache_preds"] = spkcache_preds[0].detach().cpu().numpy().astype(np.float32) + return spkcache, spkcache_preds, spk_perm + + sm._get_topk_indices = wrapped_topk + sm._compress_spkcache = wrapped_compress + return calls + + +def _write_compress_dump(calls: list[dict], dump_dir: Path) -> None: + dump_dir.mkdir(parents=True, exist_ok=True) + for k, c in enumerate(calls): + for key in ("topk_indices", "is_disabled", "spkcache_preds", "input_preds"): + if key in c: + np.save(dump_dir / f"compress.{k:03d}.{key}.npy", c[key]) + summary = {"n_calls": len(calls), "n_frames_in": [c["n_frames"] for c in calls]} + (dump_dir / "compress.summary.json").write_text(json.dumps(summary, indent=2)) + print(f"wrote {len(calls)} _compress_spkcache dumps to {dump_dir}") + + +def _segments_text(segs) -> str: + return "\n".join(str(s).strip() for s in segs) + + +def cmd_diarize(args: argparse.Namespace) -> int: + m = _load_nemo(args.model, args.revision) + geom = _apply_preset(m, args.preset) + audio, sr = _read_audio(args.audio) + out_dir = Path(args.out) + print(f"diarize preset={args.preset} {geom}", flush=True) + + calls = _install_compress_hooks(m) + segs, probs = m.diarize(audio=[audio], batch_size=1, sample_rate=sr, include_tensor_outputs=True) + p = _f32(torch.as_tensor(np.asarray(probs[0]))) + write_tensor("diar.probs", p, "diarize", + _src(args.model, "diarize.probs(streaming)", preset=args.preset, streaming_cfg=geom, + layout="[T_mel, 8] sigmoid activity at 10 ms, arrival order", + n_compressions=len(calls)), + out_dir=out_dir) + if args.dump_compress: + _write_compress_dump(calls, Path(args.dump_compress)) + write_transcript(out_dir, _segments_text(segs[0]), + source=_src(args.model, "diarize.segments(streaming)", preset=args.preset)) + active = int((p.max(0) > 0.5).sum()) + print(f"wrote diar.probs {list(p.shape)} ({active} active speakers, " + f"{len(calls)} compressions) + {len(segs[0])} segments to {out_dir}") + return 0 + + +def _load_hf(model: str, revision: str | None): + from transformers import AutoModelForAudioFrameClassification, AutoProcessor + + proc = AutoProcessor.from_pretrained(model, revision=revision) + hf = AutoModelForAudioFrameClassification.from_pretrained( + model, revision=revision, dtype=torch.float32).eval() + return proc, hf + + +def _hf_src(model: str, hook: str, **extra: Any) -> dict[str, Any]: + return {"framework": "transformers", "model": model, "hook": hook, **extra} + + +def _write_hf_probs(args, probs: np.ndarray, hook: str, **extra) -> None: + out_dir = Path(args.out) + write_tensor("diar.probs", probs, "diarize", + _hf_src(args.model, hook, layout="[T_mel, 8] sigmoid activity at 10 ms", **extra), + out_dir=out_dir) + active = int((probs.max(0) > 0.5).sum()) + print(f"wrote HF diar.probs {list(probs.shape)} ({active} active speakers) to {out_dir}") + + +def cmd_hf_stream(args: argparse.Namespace) -> int: + proc, hf = _load_hf(args.model, args.revision) + proc.set_streaming_mode(args.mode) + audio, sr = _read_audio(args.audio) + + def chunks(): + yield proc(audio[: proc.num_samples_first_audio_chunk], sampling_rate=sr, + is_streaming=True, is_first_audio_chunk=True) + mel_idx = proc.num_mel_frames_per_step + start = proc.audio_chunk_start(mel_idx) + while (end := start + proc.num_samples_per_audio_chunk) <= audio.shape[0]: + yield proc(audio[start:end], sampling_rate=sr, is_streaming=True, is_first_audio_chunk=False) + mel_idx += proc.num_mel_frames_per_step + start = proc.audio_chunk_start(mel_idx) + yield proc(audio[start:], sampling_rate=sr, is_streaming=True, + is_first_audio_chunk=False, is_last_audio_chunk=True) + + cache, logits, n_steps = None, [], 0 + with torch.inference_mode(): + for inputs in chunks(): + out = hf(**inputs, speaker_cache=cache) + logits.append(out.logits) + cache = out.speaker_cache + n_steps += 1 + probs = _f32(torch.cat(logits, dim=1).sigmoid()) + _write_hf_probs(args, probs, "processor push-audio streaming (per-chunk mel)", + streaming_mode=args.mode, n_steps=n_steps, + streaming_cfg=dict(zip(("chunk_len", "chunk_right_context"), + proc.streaming_modes[args.mode]))) + return 0 + + +def cmd_hf_offline(args: argparse.Namespace) -> int: + proc, hf = _load_hf(args.model, args.revision) + audio, sr = _read_audio(args.audio) + inputs = proc(audio, sampling_rate=sr) + with torch.inference_mode(): + logits = hf(**inputs).logits + probs = _f32(logits.sigmoid()) + _write_hf_probs(args, probs, "whole-recording forward (config offline preset)", + streaming_cfg={"chunk_len": hf.config.chunk_length, + "chunk_right_context": hf.config.chunk_right_context, + "fifo_len": hf.config.fifo_length, + "spkcache_update_period": hf.config.speaker_cache_update_period}) + return 0 + + +def add_common_args(p: argparse.ArgumentParser) -> None: + p.add_argument("--model", required=True, help="HF repo id or path to the .nemo checkpoint") + p.add_argument("--revision", default=None, help="HF revision (pinned in the golden manifest)") + p.add_argument("--audio", required=True, help="Path to a 16 kHz mono WAV") + p.add_argument("--out", required=True, help="Output ref/ directory") + # Accepted for the validate.py harness contract; a diarizer takes no language. + p.add_argument("--torch-threads", type=int, default=None, help=argparse.SUPPRESS) + p.add_argument("--language", default=None, help=argparse.SUPPRESS) + + +def main() -> int: + p = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) + sub = p.add_subparsers(dest="cmd", required=True) + ep = sub.add_parser("encoder", help="Per-stage activations of the first streaming step") + add_common_args(ep) + ep.add_argument("--preset", default="very_high_latency", choices=list(PRESETS)) + ep.set_defaults(func=cmd_encoder) + dp = sub.add_parser("diarize", help="Streaming T x 8 probs + speaker segments") + add_common_args(dp) + dp.add_argument("--preset", default="very_high_latency", choices=list(PRESETS), + help="Streaming operating point; the C++ side must run the same preset for parity.") + dp.add_argument("--dump-compress", default=None, + help="Directory for per-_compress_spkcache selected indices + preds.") + dp.set_defaults(func=cmd_diarize) + hp = sub.add_parser("hf-stream", help="HF transformers push-audio streaming probs") + add_common_args(hp) + hp.add_argument("--mode", default="low_latency", choices=HF_STREAMING_MODES) + hp.set_defaults(func=cmd_hf_stream) + op = sub.add_parser("hf-offline", help="HF transformers whole-recording probs") + add_common_args(op) + op.set_defaults(func=cmd_hf_offline) + args = p.parse_args() + if args.torch_threads: + torch.set_num_threads(args.torch_threads) + return args.func(args) + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/scripts/envs/nemotron3_diar/pyproject.toml b/scripts/envs/nemotron3_diar/pyproject.toml new file mode 100644 index 00000000..e20fde99 --- /dev/null +++ b/scripts/envs/nemotron3_diar/pyproject.toml @@ -0,0 +1,37 @@ +[project] +name = "transcribe-nemotron3-diar-env" +version = "0.1.0" +description = "Python env for Nemotron-3-Diarization reference dumps via NeMo Speech (SortformerEncLabelModel) + HF transformers cross-check" +requires-python = ">=3.12,<3.13" +dependencies = [ + "torch>=2.7", + # NeMo moved to NVIDIA-NeMo/Speech (nemo 3.x). The NeMo 2.x pin used by + # scripts/envs/sortformer cannot load this checkpoint (TransformerEncoder + # with rope + feature_stacking, subpixel head, learnable silence emb). + "nemo-toolkit[asr] @ git+https://github.com/NVIDIA-NeMo/Speech.git@cf724ac337d1ebc7d0dda1e23fb80916f52927a5", + # Cross-check / push-audio reference: Nemotron3Diarization is only on + # transformers main (5.18.0.dev0) at intake time. + "transformers @ git+https://github.com/huggingface/transformers.git@4b28d51d0d5f17ec20c23a187d0475a8e68810c8", + "huggingface-hub>=0.20", + "soundfile>=0.12", + "numpy>=1.26", + "gguf>=0.10.0", + "meeteval>=0.4.3", +] + +[tool.uv] +environments = [ + "sys_platform == 'darwin'", + "sys_platform == 'linux'", +] + +# NeMo Speech declares per-platform/per-extra torch indexes in its own +# [tool.uv.sources]; pin torch to PyPI here so the resolver sees a single +# torch source (uv 0.5.x otherwise reports a duplicate torch package). +[tool.uv.sources] +torch = { index = "pypi-torch" } + +[[tool.uv.index]] +name = "pypi-torch" +url = "https://pypi.org/simple" +explicit = true diff --git a/scripts/envs/nemotron3_diar/uv.lock b/scripts/envs/nemotron3_diar/uv.lock new file mode 100644 index 00000000..7045b096 --- /dev/null +++ b/scripts/envs/nemotron3_diar/uv.lock @@ -0,0 +1,2244 @@ +version = 1 +requires-python = "==3.12.*" +resolution-markers = [ + "sys_platform == 'darwin'", + "sys_platform == 'linux'", +] +supported-markers = [ + "sys_platform == 'darwin'", + "sys_platform == 'linux'", +] + +[[package]] +name = "absl-py" +version = "2.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d0/4f/d79676ab82f2e42fc3611618139f13a9c4c31d0cff4b486982047679a802/absl_py-2.5.0.tar.gz", hash = "sha256:0c996f25c0490700fadabe6351630f6111534fa0ae252cc6d2014ea3b141135f", size = 118119 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/58/0a/a10b45aab35b175aded078a462dc8d0c698f5b13946e7cb0869097b78bb6/absl_py-2.5.0-py3-none-any.whl", hash = "sha256:0f17b89f2a4eaaedc4f28c622998aa690564b3012a396a4ffad0821007fe03ba", size = 137410 }, +] + +[[package]] +name = "aiohappyeyeballs" +version = "2.7.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ce/f4/eec0465c2f67b2664688d0240b3212d5196fd89e741df67ddb81f8d35658/aiohappyeyeballs-2.7.1.tar.gz", hash = "sha256:065665c041c42a5938ed220bdcd7230f22527fbec085e1853d2402c8a3615d9d", size = 24757 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/71/43/1947f06babed6b3f1d7f38b0c767f52df66bfb2bc10b468c4a7de9eceff2/aiohappyeyeballs-2.7.1-py3-none-any.whl", hash = "sha256:9243213661e29250eb41368e5daa826fc017156c3b8a11440826b2e3ed376472", size = 15038 }, +] + +[[package]] +name = "aiohttp" +version = "3.14.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiohappyeyeballs", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "aiosignal", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "attrs", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "frozenlist", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "multidict", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "propcache", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "typing-extensions", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "yarl", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/58/d9/22ce5786ac0c1653ae8b6c23bded02c1686d11f0dbb45b31ce128e0df985/aiohttp-3.14.3.tar.gz", hash = "sha256:9491196535a88924a60afd5b5f434b5b203b6cc616250878dbdb223a8f7844bc", size = 7971213 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/d4/eb96299230e20acf2efae207cb8d69051f1f68e357e5ea5e479bf6fb097a/aiohttp-3.14.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:39aded8c7f3b935b54aab1d8d73c70ec0ee2d3ec3b943e0e86611bc150ba47f5", size = 754690 }, + { url = "https://files.pythonhosted.org/packages/88/11/e7a70a209eb9a067c0d3212b518a0134e3484f5178c7533878b6b514d469/aiohttp-3.14.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5bcb6ff3fdab1258a192679ff1a05d44f59626430aa05cd1a9d2447423599228", size = 509484 }, + { url = "https://files.pythonhosted.org/packages/30/07/4bbc222cc8dbe31d4c3e8a5baad2286e4d42026ac0c570027b89afce6344/aiohttp-3.14.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:617105e2c3018ee38d0c8ce5ee3c84f621a6d8b9f723202aacaff28449ca91ee", size = 511949 }, + { url = "https://files.pythonhosted.org/packages/54/b9/42e74c46b7b7c794b995bbc1f573fb48950c38b19d8600c62a6804ee2d67/aiohttp-3.14.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f631fe87a6f30df5fbe6d79640b25e4cffb38c31c7fb6f10871517b84b0f8c1a", size = 1765282 }, + { url = "https://files.pythonhosted.org/packages/6b/ed/62bc4d74363ad346d518e0720363a949f63e2e23439a79eb5813d4d29bb3/aiohttp-3.14.3-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a94dbaae5ae27bd849c93570669bff91e0510f33a80805738e3de72a7be0447b", size = 1741511 }, + { url = "https://files.pythonhosted.org/packages/d0/9f/181e8a8bc79e47d13c7fc4540bd7a3b729d9505609c61f392a8dd2fbfe55/aiohttp-3.14.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8f2f1c4c032c7cedd7d8da6f54c97b70266c6570c3108d3fdffee7188bb70529", size = 1810680 }, + { url = "https://files.pythonhosted.org/packages/5c/9a/dec94d6ad694552fe3424e3f1928d7a606a5d9d9433a04e7ecdd9d38ae7f/aiohttp-3.14.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ea05e1f97ceea523942d9b2a7d7c0359d781d683d6b043f5943a602b14da4787", size = 1905646 }, + { url = "https://files.pythonhosted.org/packages/52/b7/7cd31f29d6055bd711ae6e669367fba6f5ae9de463910a793e30556a8db7/aiohttp-3.14.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:543906c127fb1d929b95076db19b83fa2d46751006ff1e23b093aa5ac4d8db42", size = 1792122 }, + { url = "https://files.pythonhosted.org/packages/66/73/10b1ef93afa61f4963c746257b70ced619cf31a4798671de5fdb2608501d/aiohttp-3.14.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0a5ff2dfbb9ce645fa5b8ef3e02c6c0b9cc3f6030ff863d0c51fffc50cb5541b", size = 1591127 }, + { url = "https://files.pythonhosted.org/packages/49/ed/3b203fa6de1b338c14acdc06bf6ca9b043b7944f005966958c2ced932cde/aiohttp-3.14.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:041badb8f84396357c4d3ad26de6afd7a32b112f43d3c63045c0c8278cfd2043", size = 1725210 }, + { url = "https://files.pythonhosted.org/packages/28/b7/1c2aab8c706436dcc28598452488ac9cd7c409da815237c28c27d58993e6/aiohttp-3.14.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:530125ee1163c4219af35dc3aa1206e541e7b31b6efc1a3f93b70a136f65d427", size = 1764848 }, + { url = "https://files.pythonhosted.org/packages/54/50/94c28f08b131c4bf10984ea2c7a536c9920608bb2d6e7f95642c30cc87b7/aiohttp-3.14.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c8653fd547c93a61aadc612007790f5555cdd18946fa48cf45e26d8ea4ea473d", size = 1777102 }, + { url = "https://files.pythonhosted.org/packages/13/d4/e7d09ba7d345fb2d74440fd2fa033c5e079fac05552927705986f41a364f/aiohttp-3.14.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:89176250f686cb9853c0fb7ead90e639e915b84a6f43eedc2a4e7ec21f1037f0", size = 1580205 }, + { url = "https://files.pythonhosted.org/packages/a3/84/072a91d68e1e1eb587985b54baab94221277f877e8ef274fc213a0ceae28/aiohttp-3.14.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:3a26434dafe408229ff3403458ca58de24fb51936504decac49ce6755f77e59d", size = 1797219 }, + { url = "https://files.pythonhosted.org/packages/e0/eb/aad34e897e668424d6e995da5dff8a4a09af93363d3392488772957a63aa/aiohttp-3.14.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d1558173930a5a8d3069cee5c92fc91c87c4dbcb099debbb3622053717145a19", size = 1768629 }, +] + +[[package]] +name = "aiosignal" +version = "1.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "frozenlist", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "typing-extensions", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/61/62/06741b579156360248d1ec624842ad0edf697050bbaf7c3e46394e106ad1/aiosignal-1.4.0.tar.gz", hash = "sha256:f47eecd9468083c2029cc99945502cb7708b082c232f9aca65da147157b251c7", size = 25007 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e", size = 7490 }, +] + +[[package]] +name = "aistore" +version = "1.26.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "braceexpand", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "cloudpickle", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "humanize", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "msgspec", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "overrides", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "packaging", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "pydantic", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "python-dateutil", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "pyyaml", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "requests", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "tenacity", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "urllib3", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "xxhash", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3d/6f/dda8b38ccf750cb63fe3ae67f3611aed5d3d08c47dd626140b83b99c251c/aistore-1.26.0.tar.gz", hash = "sha256:7936d687a22d9442c5e7b8a543cc0e2790353e65fc734b2e57c9acd9987bdc63", size = 173269 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/85/ce/db93c0232c7e7a0046e517e8ae0f534817179631b6f37262623e453a0c8d/aistore-1.26.0-py3-none-any.whl", hash = "sha256:769b54c0fe4a917ae2f504e4e3a4f2685f7fe536226d49d818448366f1ecd4ad", size = 242796 }, +] + +[[package]] +name = "annotated-doc" +version = "0.0.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5a/8e/38aa427ed5402449e226975b649c5dc73ccadfefeb95e6aecb8f8ea4b6b6/annotated_doc-0.0.5.tar.gz", hash = "sha256:c7e58ce09192557605d8bbd92836d7e1d520ac9580096042c0bfd197efacf1bb", size = 10758 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3e/30/e900b21425a860e195f32e37657aa1f7c7f2b1bfb26f03ca209b90933c06/annotated_doc-0.0.5-py3-none-any.whl", hash = "sha256:117bac03a25ede5df5440e855b32d556049ca169ead221505badf432fed4b101", size = 5302 }, +] + +[[package]] +name = "annotated-types" +version = "0.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5f/56/a8120250d128bed162cd73c76d45f6ef9991f3e068f62a8ee060afa3104a/annotated_types-0.8.0.tar.gz", hash = "sha256:13b2beaad985e05e2d6407ee4c4f35590b11f8d693a258a561055cac8f64cab7", size = 15893 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/99/91/8acff4f5e50511b911bbccb72b8628a49c68ce14148cd9f6431094859a90/annotated_types-0.8.0-py3-none-any.whl", hash = "sha256:f072f4d804ea359e4eaf198b1af7a8b0943881a87f31bb764f8bf219bb9419e0", size = 13427 }, +] + +[[package]] +name = "antlr4-python3-runtime" +version = "4.9.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3e/38/7859ff46355f76f8d19459005ca000b6e7012f2f1ca597746cbcd1fbfe5e/antlr4-python3-runtime-4.9.3.tar.gz", hash = "sha256:f224469b4168294902bb1efa80a8bf7855f24c99aef99cbefc1bcd3cce77881b", size = 117034 } + +[[package]] +name = "anyio" +version = "4.15.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "typing-extensions", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a9/d2/f4d173e22df740bc37b1db102b386ba719b66e95b0f0d751f556b387e6d2/anyio-4.15.1.tar.gz", hash = "sha256:9f28306018cbd6d329e64a36d58256edff76dd996fe423bc957326e578b82a94", size = 276966 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/b8/4bd346e22b28902df4d651910f5242c28d84e4a5c2435ca5c3f797ed7e2e/anyio-4.15.1-py3-none-any.whl", hash = "sha256:6152fdbbf9a77fdec97731721bebf7c4c44f7c29b424b0065826173efc7ed101", size = 132079 }, +] + +[[package]] +name = "attrs" +version = "26.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9a/8e/82a0fe20a541c03148528be8cac2408564a6c9a0cc7e9171802bc1d26985/attrs-26.1.0.tar.gz", hash = "sha256:d03ceb89cb322a8fd706d4fb91940737b6642aa36998fe130a9bc96c985eff32", size = 952055 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl", hash = "sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309", size = 67548 }, +] + +[[package]] +name = "audioread" +version = "3.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/4a/874ecf9b472f998130c2b5e145dcdb9f6131e84786111489103b66772143/audioread-3.1.0.tar.gz", hash = "sha256:1c4ab2f2972764c896a8ac61ac53e261c8d29f0c6ccd652f84e18f08a4cab190", size = 20082 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/16/fbe8e1e185a45042f7cd3a282def5bb8d95bb69ab9e9ef6a5368aa17e426/audioread-3.1.0-py3-none-any.whl", hash = "sha256:b30d1df6c5d3de5dcef0fb0e256f6ea17bdcf5f979408df0297d8a408e2971b4", size = 23143 }, +] + +[[package]] +name = "braceexpand" +version = "0.1.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/54/93/badd4f5ccf25209f3fef2573073da9fe4a45a3da99fca2f800f942130c0f/braceexpand-0.1.7.tar.gz", hash = "sha256:e6e539bd20eaea53547472ff94f4fb5c3d3bf9d0a89388c4b56663aba765f705", size = 7777 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fa/93/e8c04e80e82391a6e51f218ca49720f64236bc824e92152a2633b74cf7ab/braceexpand-0.1.7-py2.py3-none-any.whl", hash = "sha256:91332d53de7828103dcae5773fb43bc34950b0c8160e35e0f44c4427a3b85014", size = 5923 }, +] + +[[package]] +name = "certifi" +version = "2026.7.22" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a3/c2/24167ea9858356b47a87a50d39908bfdb72ceeefe0041586e704e5376b3a/certifi-2026.7.22.tar.gz", hash = "sha256:741e2c3b351ddf169a738da9f2c048608ff7f2c5cc02f1ebc6b118bb090d5d55", size = 138112 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0b/a7/71ac2cff56fec219ed242bb11b8efb69fcc4bec75db06fb7bfe35de520e6/certifi-2026.7.22-py3-none-any.whl", hash = "sha256:62f22742b58a1a33014a2b6b706588a8d7e2a88ae7bd1a6ebe8c992928483775", size = 136983 }, +] + +[[package]] +name = "cffi" +version = "2.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pycparser", marker = "(implementation_name != 'PyPy' and sys_platform == 'darwin') or (implementation_name != 'PyPy' and sys_platform == 'linux')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9e/ef/008a1939e372c06329a3fce4279c02f328488f3526744906eeec3da7ad5f/cffi-2.1.1.tar.gz", hash = "sha256:dd31f52ea1086513bb9df30f8fcee9b8918323ae067a3d5b78bc826a000712be", size = 530807 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/69/43965eccfdead3b9220015fd1320e117be8c6ed01a62ffab76eeb752f5d5/cffi-2.1.1-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:c8c69575568085ba0b1b10c0249d779a214aea6f6522e949a0fc9fb0fcb449d0", size = 184821 }, + { url = "https://files.pythonhosted.org/packages/54/7d/16e5a096677b5e313ca80cd5e5170efa3ea44624a82bb111925522da64b1/cffi-2.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f81b3b8f3d4e343550fa4baa0e479bba9f2d29ce9c2e9b51d1ce1718d7442fcf", size = 184719 }, + { url = "https://files.pythonhosted.org/packages/56/e6/8941622732edec876dd17d0453dce07317ae96db34f2ec1436c9d3785986/cffi-2.1.1-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:811bd1e21d32de12efca32393a0ab3f5133b54fce9bd44b8bd77ab07da14bf6a", size = 214799 }, + { url = "https://files.pythonhosted.org/packages/44/de/f98430906df1545ffde0d543dd124a7a439bc2cd32b36b9c53f805df7333/cffi-2.1.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:68e62fe11f30d5ca8289242866f0a5291402d8529ca2178ab8afc5c9694ae890", size = 222389 }, + { url = "https://files.pythonhosted.org/packages/6a/5b/717f1526b9957b34456313c31645c5b82b8fb5c3fe9e4752999be7128bfc/cffi-2.1.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:4a7c934f7360e8cd64fe9efadcbd10c7c6364f531e432b9a4bf5ccbc9e0e8b50", size = 210249 }, + { url = "https://files.pythonhosted.org/packages/64/b3/f8aa4f3e34986c7e4ec45072d1b1b9dd295b6b18007b45518d79726dd725/cffi-2.1.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:3143d81e29e1e20a9ce10901ec369012947876596f75a222235965f2b7ae832e", size = 208775 }, + { url = "https://files.pythonhosted.org/packages/b1/db/dceb9dd5b231e1da801793f8acc9f3c52a7e1afe40bb1aae37e02b0faad5/cffi-2.1.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c1453022f490d2459a11819d83ad1d586e9ff65a12ac3e705ffebd46d3685dcf", size = 221822 }, + { url = "https://files.pythonhosted.org/packages/a0/d2/6cd24ae3be000a634109c247d1475d62e5616d0dc78c82770942ec384248/cffi-2.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:208f941bb9d18e768138677f0a6d2ce01f590df56043dda1df1535ac57c88517", size = 225232 }, + { url = "https://files.pythonhosted.org/packages/cb/52/3fa190537004dd7f0ab860a6dc7c0175b8667f68d1e618a46f5498d30250/cffi-2.1.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:210019b6c7cf07f081b4c54635c8cf744377001350e29cc0f81c4377b4797735", size = 223597 }, +] + +[[package]] +name = "charset-normalizer" +version = "3.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e5/3f/143b048436775b0f76ac3eec145c019e8173ccc2885c8f20319b996d5e83/charset_normalizer-3.5.1.tar.gz", hash = "sha256:6117b84ea48435e5356dc737f5121485c30920ba43375fa7b434fd753df0eac3", size = 171764 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/30/27/78873dc8b6a56357517b74b6bb9568b80450e7bb4f6ef7e3fa9d22aa0bd7/charset_normalizer-3.5.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:5b6d1386bf0096d26d3a863dc0a487a5b4eb9aa93cf5ba69683d29dde6b9d60f", size = 344456 }, + { url = "https://files.pythonhosted.org/packages/9a/4c/be49ada26b1f0232d57aa89bbebf997a5cc2332a5616b6eca26ff680044d/charset_normalizer-3.5.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4582c27e8c889d64811987b5967fbd3ae0c823fe1fd933b543d55ac20bb475fa", size = 238530 }, + { url = "https://files.pythonhosted.org/packages/76/84/6f1290fa07ae6978d3960caa3eb1b8019bf9284ab7c2297b00c099ef4250/charset_normalizer-3.5.1-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:1d1c7a53a6c2103925cdd6d7229f8c567379f211c869793df679f2e9f738c369", size = 230200 }, + { url = "https://files.pythonhosted.org/packages/e7/a0/47b18adeed31c8f16ba9700f32c1b18594cfa09f47eb672a488c273c22bf/charset_normalizer-3.5.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e6621fb2a4988d6e53eedc455e5903e2679f3967b8acb3d639f1b63c14a2e893", size = 262222 }, + { url = "https://files.pythonhosted.org/packages/38/fe/341861ac118dae06f3ec0eb487488af52128f2ef2faf0b11003944d22259/charset_normalizer-3.5.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7c0c10730342b0c9b35dd1d619beb8214e520bd96a1f870f452680b238aab3e0", size = 258951 }, + { url = "https://files.pythonhosted.org/packages/6f/89/bb5108dc6c3651dca963f2b0a3ba19bbcb370c94e1b6d3e0e844a58e6dca/charset_normalizer-3.5.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b9af956078716df40d985fb0dfeb2c2120c5ca92ba4ff4b388acfd01cdc14d08", size = 248801 }, + { url = "https://files.pythonhosted.org/packages/b1/ba/ef83ae3aca816393decfa3530976f38a79812d707b80b580ac33b83f9877/charset_normalizer-3.5.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f9f8405c2c758532c74fed975dbee57be1f31a6e865c031870c79a6ed3212ada", size = 244070 }, + { url = "https://files.pythonhosted.org/packages/f6/0b/c5292a2462d69b7378ea89793bbb5b2b6fcf6f7dd6d1667f9619094ad553/charset_normalizer-3.5.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:96fef3e886d6a9874b14f27fc193fbdc69d5d8035783d86aa4e1cea594e695f9", size = 240110 }, + { url = "https://files.pythonhosted.org/packages/46/22/111e5be3b740d5c2a5bfcedb3d237b6591e5c2e82ae9d6ffcb121fe0909c/charset_normalizer-3.5.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5d8531a6569d025f68e2321e7638fb7978f23db58e5f69f56913837aae03816e", size = 232836 }, + { url = "https://files.pythonhosted.org/packages/f9/d2/d2aad6fe0dbb44b194bf3becb60f5a0ac48446ade999a47fe7bb41eb09a7/charset_normalizer-3.5.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:aae2ee51122d3ae968a3837d97dc24a0aeebb0dea23694422cd172bd30017cd6", size = 262712 }, + { url = "https://files.pythonhosted.org/packages/35/5a/337e4663a5eae6de99db940ee8066d4145caafb61327db62deda15313cce/charset_normalizer-3.5.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:7235dc28fc6dd9d832ac7c7bce95367dedb85929f17368a0c2bee1e080b9acbf", size = 242977 }, + { url = "https://files.pythonhosted.org/packages/ca/85/f82f8a92e31c7519410e2e1afdc630f28ec47490ce2c09a11c1a43cbb459/charset_normalizer-3.5.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:4abdc5f9ad448c1ecbfae2974b820535d6bc6e7eef63babbab3d81cf46968c71", size = 260207 }, + { url = "https://files.pythonhosted.org/packages/b7/52/643d11ffd60e9ac2fd1fb87e167a19285b9eefeff4a40e63c87cbfbeab36/charset_normalizer-3.5.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ba501e667c17d8411f98e67a022d9604ef179aff0e459b7e292c796837c13573", size = 250562 }, + { url = "https://files.pythonhosted.org/packages/5b/97/fb4e82231aba271ffd775a1b4993b0defc4e3059f286ae41d9433409fe85/charset_normalizer-3.5.1-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:41876ee62a3dddf48ff1121ad8f0798032aa03f2fd35f21f34a4cab14f18d8d2", size = 331467 }, + { url = "https://files.pythonhosted.org/packages/9f/2f/fe3f187327aac18e2d54e9d2b08e15d27bf9b642d9e51c219f130fc34d1a/charset_normalizer-3.5.1-cp37-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a6dac12ff6b846103483683f60c5f8fee205121adc58ffd87e90a90a3af69e99", size = 253057 }, + { url = "https://files.pythonhosted.org/packages/d7/c7/9e48cee5c161fe24da823b61bf381921d77cb994a0a4de148e95018c1984/charset_normalizer-3.5.1-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cee5dd7c6fb5dd52a0fe2a740f9bc6e3593f5f8b1788bde49de02086f30182b2", size = 240930 }, + { url = "https://files.pythonhosted.org/packages/49/e0/716601f3cc69be7b198951150c75ead1ece33c3c8036ff6ffa46029659a0/charset_normalizer-3.5.1-cp37-abi3-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:343fb4f2821043bd87095f7b08a1a181febc8e36ac64212143bbfd0a0e1bc235", size = 230822 }, + { url = "https://files.pythonhosted.org/packages/d3/05/71bfc5caa0abcc45aea1f6a4d50ac68e59605ddc7666fe8494f4cd229665/charset_normalizer-3.5.1-cp37-abi3-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ae4a097991662cd4fff0ddc74e0fe7874f82e00042fa0ea00855645ed0c79598", size = 260037 }, + { url = "https://files.pythonhosted.org/packages/c3/92/de7e32ed05341e7a9c4c877c318418197b7f2d66a3b68d561bf2ac57ca3e/charset_normalizer-3.5.1-cp37-abi3-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4b599739b93b2cbeded49645ae3c8d1405c29ddfbceac1545c87a3f9580a9e96", size = 255097 }, + { url = "https://files.pythonhosted.org/packages/f5/7b/ade0a122600319dfa0b1000ab0f9731c94a817904cf3c5de408c73a4ede7/charset_normalizer-3.5.1-cp37-abi3-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b39b69b347e5e47a3b5b8cfc005c68c1ba347474e3960236c4944a8ecd174962", size = 250166 }, + { url = "https://files.pythonhosted.org/packages/75/9c/019fbb9f4834491a160951349b1a3714439376f66e5f7cf18b4f18f0c7aa/charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:a2028475ba855475b8b4d3cfeb4994269c967aea8b9892dfba907f4263a863a3", size = 241821 }, + { url = "https://files.pythonhosted.org/packages/2b/b8/11d4840bfc99330cc7fbcc2681ee5a044553a6e77655508d8f9b2bff7b34/charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:36047af20e17097c3bb9476c2b7655f2f7aa51322c0ba58c07695bedf755a950", size = 232529 }, + { url = "https://files.pythonhosted.org/packages/18/96/2b3a21492d9f65171ac75d872f5018260013d00bfa0ff70ec9f179148cbd/charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_ppc64le.whl", hash = "sha256:4c4fb141a727957c93edfe5c32a26ceb6b5f6461d67146e2d39f51e16170bea8", size = 260348 }, + { url = "https://files.pythonhosted.org/packages/d6/aa/a69a2028e8bd052476c245460ab19d7de595de084dd968f2d75cd50c3e25/charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_riscv64.whl", hash = "sha256:2f293479cce755c75f1697e87c409b7ae4c555c7dfecb6e988ad13abba943031", size = 247234 }, + { url = "https://files.pythonhosted.org/packages/35/8a/3d130aeabcaf3d2466af76b7b141c08d9e89c9016ab4b7cdd0f7dc2d1c62/charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_s390x.whl", hash = "sha256:3588e376b3ea2eea84976f67273d679f229e24c66dce7b82ae45aef04ff6e072", size = 256917 }, + { url = "https://files.pythonhosted.org/packages/80/c2/a7379b840292d0c1ab9fbd17d1f3967aa81794dc95bc74be8999d7fedcf7/charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:e199fb99720074809a7720f1c0b4d919eea8b87e88713e0f8f602f7bef543d9d", size = 254846 }, + { url = "https://files.pythonhosted.org/packages/cc/61/d01fc49b8dea277640b55a9e15960dbca9fdc8c9fde18e572d39c59f4019/charset_normalizer-3.5.1-py3-none-any.whl", hash = "sha256:6df0ec430f9a831772c23ca5a224cba36517a58a84bb32c32bb59a9fa67c47f6", size = 68658 }, +] + +[[package]] +name = "click" +version = "8.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c7/0e/7fa0ef50764b67090eca4114772a2abf8b6148198475e54c660b97caeee6/click-8.5.0.tar.gz", hash = "sha256:ba0d2089de75ea0310e2dde03160e6ca10009947fb95a182f9b54021bb272e34", size = 382235 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/58/50/6c0d534c5f134586a8e1ba4e330569e32f057e33372ae556463212fb4cd3/click-8.5.0-py3-none-any.whl", hash = "sha256:255bc9599cf7748b4b1a446ccc735421bd08a2ae529a8b88597d3de5664ee360", size = 125251 }, +] + +[[package]] +name = "cloudpickle" +version = "3.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/27/fb/576f067976d320f5f0114a8d9fa1215425441bb35627b1993e5afd8111e5/cloudpickle-3.1.2.tar.gz", hash = "sha256:7fda9eb655c9c230dab534f1983763de5835249750e85fbcef43aaa30a9a2414", size = 22330 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl", hash = "sha256:9acb47f6afd73f60dc1df93bb801b472f05ff42fa6c84167d25cb206be1fbf4a", size = 22228 }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, +] + +[[package]] +name = "cuda-bindings" +version = "13.4.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cuda-pathfinder", marker = "sys_platform == 'linux'" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/65/11/1293429c1c3a3e19b551275e65efddd122a905bbe7e368816a59f3ef2a41/cuda_bindings-13.4.3-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bfbd3f7d4ac04dd41dc49121b9e408c8283992f47124c2290ecb79bbbadcca8e", size = 6488226 }, + { url = "https://files.pythonhosted.org/packages/b8/c3/efb6bbb7307bf5c83dc4acca650280b210c67ed1a1a60f898a90e7c82e38/cuda_bindings-13.4.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d5f72bcfcdf3be23e1da3c792f68f508586f48d037bca8b10f552c4cca5971f2", size = 7172185 }, +] + +[[package]] +name = "cuda-pathfinder" +version = "1.8.2" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/98/59/239c7259e669c46ddbcac0aa60e3a0ef00bfeaaa687f905b24dd6a7a10fe/cuda_pathfinder-1.8.2-py3-none-any.whl", hash = "sha256:4e65059febdb4d19d5cbc4798677e19db2b582f2f702f457b609e571690d357e", size = 62551 }, +] + +[[package]] +name = "cuda-toolkit" +version = "13.0.3.0" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/c7/a79086a62c98befcdb8349656c6f114e2db3b8b2422f6e25c97a7f2a9a3c/cuda_toolkit-13.0.3.0-py2.py3-none-any.whl", hash = "sha256:d693caaa261214ddd7dbb60d68e71cbed884e68c2be7509778f3051da0b91c3f", size = 2512 }, +] + +[package.optional-dependencies] +cublas = [ + { name = "nvidia-cublas", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, + { name = "nvidia-cuda-nvrtc", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, +] +cudart = [ + { name = "nvidia-cuda-runtime", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, +] +cufft = [ + { name = "nvidia-cufft", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, + { name = "nvidia-nvjitlink", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, +] +cufile = [ + { name = "nvidia-cufile", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, +] +cupti = [ + { name = "nvidia-cuda-cupti", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, +] +curand = [ + { name = "nvidia-curand", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, +] +cusolver = [ + { name = "nvidia-cublas", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, + { name = "nvidia-cusolver", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, + { name = "nvidia-cusparse", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, + { name = "nvidia-nvjitlink", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, +] +cusparse = [ + { name = "nvidia-cusparse", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, + { name = "nvidia-nvjitlink", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, +] +nvjitlink = [ + { name = "nvidia-nvjitlink", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, +] +nvrtc = [ + { name = "nvidia-cuda-nvrtc", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, +] +nvtx = [ + { name = "nvidia-nvtx", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, +] + +[[package]] +name = "cython" +version = "3.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a9/d8/4981ef716ad0e3ff0d3ef383aefc6b03c4a88dee33b272bf8e0d833001ca/cython-3.3.0.tar.gz", hash = "sha256:eed0d93fbca7087f143b42c34b05a825849bdf17f101572c2105acfa49aa88b8", size = 3727515 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/da/0f/95bad838a80ac52c9e982dad00bd9a0b2bad57fb4c688e5f53ac3ef65ff0/cython-3.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:03bc5333932f5dda3ba9315298ecdd21daa1b58410bb1f8ce04c78ec8337130a", size = 3143472 }, + { url = "https://files.pythonhosted.org/packages/91/8b/53d4a84de853b39940a0e35a6a2a9ed5f54cb05468daee95bc0fd1c2a178/cython-3.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7e321ae700995a16dc3055ada06ffb8d61e1a7434e5d0e811547a45ac1015ebd", size = 3258974 }, + { url = "https://files.pythonhosted.org/packages/6a/4e/6b1c5a4e6bbe1726104de007aa2fdf01a3e2e386b4ec93c7be5f5085d53f/cython-3.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:428fafed98ea26927000a287b4dfc9ef07339f56656a5329a34eaa593f79a4f8", size = 3412225 }, + { url = "https://files.pythonhosted.org/packages/14/59/bc1a84b434cb5bebb0cd6f50da8f239d35a5c141b20fdeafc2817fd87778/cython-3.3.0-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:e0d2713d2b292c826bc21dc8732bd9e47628103aa3764180c881e04b3fef95dc", size = 3063660 }, + { url = "https://files.pythonhosted.org/packages/ba/6d/542e32908fb421d88354f327ed6450e14240f9825d25393065bc65f4723f/cython-3.3.0-cp39-abi3-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:169e56fd411f4cd5bba51c82f8239421d547a846099db2b261e4aed48ba9f51f", size = 3358395 }, + { url = "https://files.pythonhosted.org/packages/9c/7c/ddaf197bc65b581e1891657940bc4f7cb1f740e822115e828920b3a119ce/cython-3.3.0-cp39-abi3-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:29f38ebafdf23e3da2516f40c4d065da38bfe002181bf93e2b8cf1262449aba6", size = 3041760 }, + { url = "https://files.pythonhosted.org/packages/19/a7/ae5ec3e34d43da846ed4c425734752d83aae0dae49feb929f09c90fc9afa/cython-3.3.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:75c4ae8a6d3a5ccf3cdaba8ab32e6a8d0cd38e3a476aa7ac12df8f8171a8d570", size = 3156152 }, + { url = "https://files.pythonhosted.org/packages/31/44/c60b601fc43f0b08e9d6f14b94e0dd02eb0ca8d60f46e242ace7191ac1be/cython-3.3.0-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:b94fb5613b9fe34c27d13ec9972dc0dcd2a2155db2902e93921cadc162610a38", size = 3046690 }, + { url = "https://files.pythonhosted.org/packages/b0/9e/d735c26ed907563d3365534006acb263651c2d3b87fee804f7a483dd1714/cython-3.3.0-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:c4558ba85849ab65dc57e10fd0efb13fabd9d3c09981a2566e18dec7cf47586a", size = 3373534 }, + { url = "https://files.pythonhosted.org/packages/e0/e8/aa7b4f3a28d6e8117c76e2cf78a0df7a503486cdf7243c5b53200c9533a1/cython-3.3.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:311a016369adfd1e0015c4f9819168fc0e518451d7efb4435c30d65a3a26d52b", size = 3265110 }, + { url = "https://files.pythonhosted.org/packages/bf/77/67b0b24e45073a699610e50f00c18474ff9b09ea29ecc95083bdf5e60acd/cython-3.3.0-py3-none-any.whl", hash = "sha256:9b24b5c8cd536946b62086fcafee6d5509d3f549f72d553d2336af87ffbe0da1", size = 1349151 }, +] + +[[package]] +name = "cytoolz" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "toolz", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bd/d4/16916f3dc20a3f5455b63c35dcb260b3716f59ce27a93586804e70e431d5/cytoolz-1.1.0.tar.gz", hash = "sha256:13a7bf254c3c0d28b12e2290b82aed0f0977a4c2a2bf84854fcdc7796a29f3b0", size = 642510 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c6/ec/01426224f7acf60183d3921b25e1a8e71713d3d39cb464d64ac7aace6ea6/cytoolz-1.1.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:99f8e134c9be11649342853ec8c90837af4089fc8ff1e8f9a024a57d1fa08514", size = 1327800 }, + { url = "https://files.pythonhosted.org/packages/b4/07/e07e8fedd332ac9626ad58bea31416dda19bfd14310731fa38b16a97e15f/cytoolz-1.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0a6f44cf9319c30feb9a50aa513d777ef51efec16f31c404409e7deb8063df64", size = 997118 }, + { url = "https://files.pythonhosted.org/packages/ab/72/c0f766d63ed2f9ea8dc8e1628d385d99b41fb834ce17ac3669e3f91e115d/cytoolz-1.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:945580dc158c557172fca899a35a99a16fbcebf6db0c77cb6621084bc82189f9", size = 991169 }, + { url = "https://files.pythonhosted.org/packages/df/4b/1f757353d1bf33e56a7391ecc9bc49c1e529803b93a9d2f67fe5f92906fe/cytoolz-1.1.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:257905ec050d04f2f856854620d1e25556fd735064cebd81b460f54939b9f9d5", size = 2700680 }, + { url = "https://files.pythonhosted.org/packages/25/73/9b25bb7ed8d419b9d6ff2ae0b3d06694de79a3f98f5169a1293ff7ad3a3f/cytoolz-1.1.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:82779049f352fb3ab5e8c993ab45edbb6e02efb1f17f0b50f4972c706cc51d76", size = 2824951 }, + { url = "https://files.pythonhosted.org/packages/0c/93/9c787f7c909e75670fff467f2504725d06d8c3f51d6dfe22c55a08c8ccd4/cytoolz-1.1.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7d3e405e435320e08c5a1633afaf285a392e2d9cef35c925d91e2a31dfd7a688", size = 2679635 }, + { url = "https://files.pythonhosted.org/packages/50/aa/9ee92c302cccf7a41a7311b325b51ebeff25d36c1f82bdc1bbe3f58dc947/cytoolz-1.1.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:923df8f5591e0d20543060c29909c149ab1963a7267037b39eee03a83dbc50a8", size = 2938352 }, + { url = "https://files.pythonhosted.org/packages/6a/a3/3b58c5c1692c3bacd65640d0d5c7267a7ebb76204f7507aec29de7063d2f/cytoolz-1.1.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:25db9e4862f22ea0ae2e56c8bec9fc9fd756b655ae13e8c7b5625d7ed1c582d4", size = 3022121 }, + { url = "https://files.pythonhosted.org/packages/e1/93/c647bc3334355088c57351a536c2d4a83dd45f7de591fab383975e45bff9/cytoolz-1.1.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c7a98deb11ccd8e5d9f9441ef2ff3352aab52226a2b7d04756caaa53cd612363", size = 2857656 }, + { url = "https://files.pythonhosted.org/packages/b2/c2/43fea146bf4141deea959e19dcddf268c5ed759dec5c2ed4a6941d711933/cytoolz-1.1.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:dce4ee9fc99104bc77efdea80f32ca5a650cd653bcc8a1d984a931153d3d9b58", size = 2551284 }, + { url = "https://files.pythonhosted.org/packages/6f/df/cdc7a81ce5cfcde7ef523143d545635fc37e80ccacce140ae58483a21da3/cytoolz-1.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:80d6da158f7d20c15819701bbda1c041f0944ede2f564f5c739b1bc80a9ffb8b", size = 2721673 }, + { url = "https://files.pythonhosted.org/packages/45/be/f8524bb9ad8812ad375e61238dcaa3177628234d1b908ad0b74e3657cafd/cytoolz-1.1.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:3b5c5a192abda123ad45ef716ec9082b4cf7d95e9ada8291c5c2cc5558be858b", size = 2722884 }, + { url = "https://files.pythonhosted.org/packages/23/e6/6bb8e4f9c267ad42d1ff77b6d2e4984665505afae50a216290e1d7311431/cytoolz-1.1.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5b399ce7d967b1cb6280250818b786be652aa8ddffd3c0bb5c48c6220d945ab5", size = 2685486 }, + { url = "https://files.pythonhosted.org/packages/d7/dd/88619f9c8d2b682562c0c886bbb7c35720cb83fda2ac9a41bdd14073d9bd/cytoolz-1.1.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:e7e29a1a03f00b4322196cfe8e2c38da9a6c8d573566052c586df83aacc5663c", size = 2839661 }, + { url = "https://files.pythonhosted.org/packages/b8/8d/4478ebf471ee78dd496d254dc0f4ad729cd8e6ba8257de4f0a98a2838ef2/cytoolz-1.1.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:5291b117d71652a817ec164e7011f18e6a51f8a352cc9a70ed5b976c51102fda", size = 2547095 }, + { url = "https://files.pythonhosted.org/packages/e6/68/f1dea33367b0b3f64e199c230a14a6b6f243c189020effafd31e970ca527/cytoolz-1.1.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:8caef62f846a9011676c51bda9189ae394cdd6bb17f2946ecaedc23243268320", size = 2870901 }, + { url = "https://files.pythonhosted.org/packages/4a/9a/33591c09dfe799b8fb692cf2ad383e2c41ab6593cc960b00d1fc8a145655/cytoolz-1.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:de425c5a8e3be7bb3a195e19191d28d9eb3c2038046064a92edc4505033ec9cb", size = 2765422 }, +] + +[[package]] +name = "datasets" +version = "5.0.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "dill", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "filelock", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "fsspec", extra = ["http"], marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "httpx", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "huggingface-hub", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "multiprocess", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "numpy", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "packaging", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "pandas", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "pyarrow", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "pyyaml", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "requests", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "tqdm", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "xxhash", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0a/5b/836516269d4f618efe621661cfb6f9acc57e6f95265db3efaee48a5ffe04/datasets-5.0.1.tar.gz", hash = "sha256:ce22bb851efd7494f08aad33b940803784434f6e77763d00679a0dc45fcf686a", size = 641498 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/44/0b/98fc6eb83333508ca5f44c52b3e287ea8137a0ad582714e2cbc67a02154b/datasets-5.0.1-py3-none-any.whl", hash = "sha256:9fbf73688f8c18f7529b4fe592abd04015f81d1e58001e4bac73ffb2b39d7cc4", size = 559079 }, +] + +[[package]] +name = "decorator" +version = "5.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/60/8b/32f9823da46cde7df2087faa08cd98d01b908f8dcab982cdba9c84e85355/decorator-5.3.1.tar.gz", hash = "sha256:4cbcdd55a6efadb9dbea26b858f4fb3264567b52d69ca0d25b721b553f60ea82", size = 58084 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/05/7f/798705f5296a58ca505d600456748d1be48078eac8a7050d8a98bc9edb89/decorator-5.3.1-py3-none-any.whl", hash = "sha256:f47fe6fdbd2edd623ecfe36875d37aba411624e2670dd395dddae1358689bb3c", size = 10365 }, +] + +[[package]] +name = "dill" +version = "0.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/81/e1/56027a71e31b02ddc53c7d65b01e68edf64dea2932122fe7746a516f75d5/dill-0.4.1.tar.gz", hash = "sha256:423092df4182177d4d8ba8290c8a5b640c66ab35ec7da59ccfa00f6fa3eea5fa", size = 187315 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl", hash = "sha256:1e1ce33e978ae97fcfcff5638477032b801c46c7c65cf717f95fbc2248f79a9d", size = 120019 }, +] + +[[package]] +name = "einops" +version = "0.8.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2c/77/850bef8d72ffb9219f0b1aac23fbc1bf7d038ee6ea666f331fa273031aa2/einops-0.8.2.tar.gz", hash = "sha256:609da665570e5e265e27283aab09e7f279ade90c4f01bcfca111f3d3e13f2827", size = 56261 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/09/f8d8f8f31e4483c10a906437b4ce31bdf3d6d417b73fe33f1a8b59e34228/einops-0.8.2-py3-none-any.whl", hash = "sha256:54058201ac7087911181bfec4af6091bb59380360f069276601256a76af08193", size = 65638 }, +] + +[[package]] +name = "filelock" +version = "4.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4f/b8/9ba8f569df649beb7058db5eb392a5f779bdbc3b82cf3942f0be439fb99e/filelock-4.0.3.tar.gz", hash = "sha256:87296d60478e14204fd9406e79831400fef76693bae2895deec236c98e87a8aa", size = 244309 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7a/6d/db8b86e168fef0cf904737e275bccb683558088ef971e92d3b41ce980699/filelock-4.0.3-py3-none-any.whl", hash = "sha256:30cd166e2aee2c7534ce2c33c6367c4cd051b8368e20e2c4eb0c34f699bacfab", size = 108444 }, +] + +[[package]] +name = "frozenlist" +version = "1.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2d/f5/c831fac6cc817d26fd54c7eaccd04ef7e0288806943f7cc5bbf69f3ac1f0/frozenlist-1.8.0.tar.gz", hash = "sha256:3ede829ed8d842f6cd48fc7081d7a41001a56f1f38603f9d49bf3020d59a31ad", size = 45875 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/69/29/948b9aa87e75820a38650af445d2ef2b6b8a6fab1a23b6bb9e4ef0be2d59/frozenlist-1.8.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:78f7b9e5d6f2fdb88cdde9440dc147259b62b9d3b019924def9f6478be254ac1", size = 87782 }, + { url = "https://files.pythonhosted.org/packages/64/80/4f6e318ee2a7c0750ed724fa33a4bdf1eacdc5a39a7a24e818a773cd91af/frozenlist-1.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:229bf37d2e4acdaf808fd3f06e854a4a7a3661e871b10dc1f8f1896a3b05f18b", size = 50594 }, + { url = "https://files.pythonhosted.org/packages/2b/94/5c8a2b50a496b11dd519f4a24cb5496cf125681dd99e94c604ccdea9419a/frozenlist-1.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f833670942247a14eafbb675458b4e61c82e002a148f49e68257b79296e865c4", size = 50448 }, + { url = "https://files.pythonhosted.org/packages/6a/bd/d91c5e39f490a49df14320f4e8c80161cfcce09f1e2cde1edd16a551abb3/frozenlist-1.8.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:494a5952b1c597ba44e0e78113a7266e656b9794eec897b19ead706bd7074383", size = 242411 }, + { url = "https://files.pythonhosted.org/packages/8f/83/f61505a05109ef3293dfb1ff594d13d64a2324ac3482be2cedc2be818256/frozenlist-1.8.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96f423a119f4777a4a056b66ce11527366a8bb92f54e541ade21f2374433f6d4", size = 243014 }, + { url = "https://files.pythonhosted.org/packages/d8/cb/cb6c7b0f7d4023ddda30cf56b8b17494eb3a79e3fda666bf735f63118b35/frozenlist-1.8.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3462dd9475af2025c31cc61be6652dfa25cbfb56cbbf52f4ccfe029f38decaf8", size = 234909 }, + { url = "https://files.pythonhosted.org/packages/31/c5/cd7a1f3b8b34af009fb17d4123c5a778b44ae2804e3ad6b86204255f9ec5/frozenlist-1.8.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c4c800524c9cd9bac5166cd6f55285957fcfc907db323e193f2afcd4d9abd69b", size = 250049 }, + { url = "https://files.pythonhosted.org/packages/c0/01/2f95d3b416c584a1e7f0e1d6d31998c4a795f7544069ee2e0962a4b60740/frozenlist-1.8.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d6a5df73acd3399d893dafc71663ad22534b5aa4f94e8a2fabfe856c3c1b6a52", size = 256485 }, + { url = "https://files.pythonhosted.org/packages/ce/03/024bf7720b3abaebcff6d0793d73c154237b85bdf67b7ed55e5e9596dc9a/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:405e8fe955c2280ce66428b3ca55e12b3c4e9c336fb2103a4937e891c69a4a29", size = 237619 }, + { url = "https://files.pythonhosted.org/packages/69/fa/f8abdfe7d76b731f5d8bd217827cf6764d4f1d9763407e42717b4bed50a0/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:908bd3f6439f2fef9e85031b59fd4f1297af54415fb60e4254a95f75b3cab3f3", size = 250320 }, + { url = "https://files.pythonhosted.org/packages/f5/3c/b051329f718b463b22613e269ad72138cc256c540f78a6de89452803a47d/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:294e487f9ec720bd8ffcebc99d575f7eff3568a08a253d1ee1a0378754b74143", size = 246820 }, + { url = "https://files.pythonhosted.org/packages/0f/ae/58282e8f98e444b3f4dd42448ff36fa38bef29e40d40f330b22e7108f565/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:74c51543498289c0c43656701be6b077f4b265868fa7f8a8859c197006efb608", size = 250518 }, + { url = "https://files.pythonhosted.org/packages/8f/96/007e5944694d66123183845a106547a15944fbbb7154788cbf7272789536/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:776f352e8329135506a1d6bf16ac3f87bc25b28e765949282dcc627af36123aa", size = 239096 }, + { url = "https://files.pythonhosted.org/packages/9a/9a/e35b4a917281c0b8419d4207f4334c8e8c5dbf4f3f5f9ada73958d937dcc/frozenlist-1.8.0-py3-none-any.whl", hash = "sha256:0c18a16eab41e82c295618a77502e17b195883241c563b00f0aa5106fc4eaa0d", size = 13409 }, +] + +[[package]] +name = "fsspec" +version = "2025.12.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b6/27/954057b0d1f53f086f681755207dda6de6c660ce133c829158e8e8fe7895/fsspec-2025.12.0.tar.gz", hash = "sha256:c505de011584597b1060ff778bb664c1bc022e87921b0e4f10cc9c44f9635973", size = 309748 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/c7/b64cae5dba3a1b138d7123ec36bb5ccd39d39939f18454407e5468f4763f/fsspec-2025.12.0-py3-none-any.whl", hash = "sha256:8bf1fe301b7d8acfa6e8571e3b1c3d158f909666642431cc78a1b7b4dbc5ec5b", size = 201422 }, +] + +[package.optional-dependencies] +http = [ + { name = "aiohttp", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] + +[[package]] +name = "gguf" +version = "0.19.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "pyyaml", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "requests", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "tqdm", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/48/ae/17f1308ae45cd7b08ebb521747d5b23f4efc4d172038a4e228dd5106c3ff/gguf-0.19.0.tar.gz", hash = "sha256:dbadcd6cc7ccd44256f2229fe7c2dff5e8aa5cf0612ab987fd2b1a57e428923f", size = 111220 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/bb/d71d6da82763528c2c2ed6b59a9d6142c6595545a4c448e2085d155e88c2/gguf-0.19.0-py3-none-any.whl", hash = "sha256:70bcd10edfe697fb2dad6e40af2234b9d8ece9a41a99761405121ebda1c3c1cd", size = 118475 }, +] + +[[package]] +name = "googleapis-common-protos" +version = "1.75.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "protobuf", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8a/c5/4353a188e2c335aee33269e8b654af228278cca8e5f0b4b5f11e5d0e9adb/googleapis_common_protos-1.75.3.tar.gz", hash = "sha256:57c435ac2c68b108999b6db075d9053e4d7a936ba57b4a3d45667b1346f1738a", size = 153905 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1a/7a/7d79170c6ce6f12e109df2b3879d6b934010cf4f99aea8de8b7e5408c174/googleapis_common_protos-1.75.3-py3-none-any.whl", hash = "sha256:a018d2bf098ca9fb6faa08d5bb780e2a2c2f73c566f069761331386c9596d3f2", size = 306984 }, +] + +[[package]] +name = "grpcio" +version = "1.84.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3f/4f/4435c0aae54657258d9cfcba78598f3d9e5fe4c82ff18d78558567b90faf/grpcio-1.84.0.tar.gz", hash = "sha256:19aaf172fc2edbefccce3f6e92c5150975dbe56c45744e9e87cf72ebdf85bfbe", size = 13493876 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0a/c1/4c9a2e0e6b0aaf02781404cad2f79211f989f2c827cf672a4a48d1604d3e/grpcio-1.84.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:b5c6f20d657ae09ae4e30d9d3a21edd13f1219d58cc6f999b9d1bb63be9c1baa", size = 6415756 }, + { url = "https://files.pythonhosted.org/packages/b1/57/131e7007bdee9acb77a8dbe8a16fa9fef75f88c1695242d8ee0993ac2d3d/grpcio-1.84.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:406583b4e8fb2282ebd392e12b963e601c1f82e07125a8c2cb5b144e7e024796", size = 12339195 }, + { url = "https://files.pythonhosted.org/packages/db/d1/a7b7cda98fcab9b3d2916204a872d87371158a7a34e41768f524584fb64d/grpcio-1.84.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fbdbcd06986ede3ce584083b1dc2afe6808e8943e5cf50ad11183c03aceda25a", size = 6984468 }, + { url = "https://files.pythonhosted.org/packages/19/81/c5be83e3ac9416f73c4c51fe1ea9c41a0c42fc3509e3505faa46f5046abe/grpcio-1.84.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:23e6e8e8a75cff88e0a793bfd3becea03a13e2763ae90c1ff573bc19ca5b429a", size = 7749432 }, + { url = "https://files.pythonhosted.org/packages/a0/bf/258cd7c0a7ed92745dc93c31666d462d05b702807a689744bd49fb833bde/grpcio-1.84.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b44f0a0fc7bc6677d38cc80bca1a32814ce6c8f200fb8b3c1a61c9d77eaefbf3", size = 7156115 }, + { url = "https://files.pythonhosted.org/packages/2b/4b/7f829418dbfcf91b875e55e2973f1059a95decb4f081313416317ef04ec1/grpcio-1.84.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:210e4c32f907045eb8158273e60c6ab69a3947697df6245dbda381f26c59485b", size = 7708010 }, + { url = "https://files.pythonhosted.org/packages/34/f0/9932e2fec6a04205f8bf3f8f4d2020479dcdac88feb6f93822ed31bf0eba/grpcio-1.84.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a71d24f40b0cc6798feaa978c7411dc1135b7018e9fc0442db611c139bf58344", size = 8759980 }, + { url = "https://files.pythonhosted.org/packages/2c/5c/b67407c6dbc480dfc0715f6eccdb1061e7c88d85f9a330a241d357a538c5/grpcio-1.84.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f6c972474ce691aca74e58d17625450cef153dc4760364cadeb167983ea6d589", size = 8124904 }, +] + +[[package]] +name = "h11" +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515 }, +] + +[[package]] +name = "hf-xet" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/ab/522a2ab67f27971a9d48ca666d4fca85ef7d5282d142e31fd087e27b1bbe/hf_xet-1.6.0.tar.gz", hash = "sha256:2e58454a340b3556dfa4972d5451aff4fba8dd42a236600ba1a1d2b1514f0fef", size = 920527 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a2/50/7afa2c9c787405864fc47a0d1bbc02c62e9101947ed43c1f43899fc7d91d/hf_xet-1.6.0-cp38-abi3-macosx_10_12_x86_64.whl", hash = "sha256:633dc0cd71d32da58ab8c03ad38e2fac452c15c2b0a2866ebf6ededfe0a5061d", size = 4071729 }, + { url = "https://files.pythonhosted.org/packages/4b/69/55b8dcf636142ae660fec1869fcac14c4da2e8412e14d6eee1523be77e9f/hf_xet-1.6.0-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:f0906082d9932ae0c0057fa194041c22b4e2cdb46b2592ef3b91f020d62a081a", size = 3876287 }, + { url = "https://files.pythonhosted.org/packages/67/4e/a28359bf1c1ecf11eba22123168c138698f7cb576ac678f5a2e16cd5da08/hf_xet-1.6.0-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d62671bb130879cef0ee4c9ebe47a14af6c66ec53e6d84dc15936e5ffdfac82f", size = 4464663 }, + { url = "https://files.pythonhosted.org/packages/9a/69/1f0cbc2fb22ae6082d094f743d1b8945a3f36f6089cb95f42b7ee348cda7/hf_xet-1.6.0-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:0e6e21fa3cdfcdcd76748564bf593870a5e013f47d97cf10aed63aa222cff5b7", size = 4262538 }, + { url = "https://files.pythonhosted.org/packages/d1/3a/4f4f2301ade26e404462d3336fa11f7958d914cabbabdd6e03c3c5d5658c/hf_xet-1.6.0-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:4fc74352a17015bd0ee90038bc9efe38db894cde45f268b6712b04fce8cd0acb", size = 4460520 }, + { url = "https://files.pythonhosted.org/packages/ab/5f/311725e2a905534dfee2dcb5b08414f249147f1f12252bfc2bd24caa075c/hf_xet-1.6.0-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:8fb4f71cba6129110c3374a33f919001ff130488fc23553698e34cc1c2a1198c", size = 4675937 }, +] + +[[package]] +name = "httpcore" +version = "1.0.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "h11", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784 }, +] + +[[package]] +name = "httpx" +version = "0.28.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "certifi", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "httpcore", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "idna", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517 }, +] + +[[package]] +name = "huggingface-hub" +version = "1.32.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "filelock", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "fsspec", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "hf-xet", marker = "(platform_machine == 'AMD64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'darwin') or (platform_machine == 'amd64' and sys_platform == 'darwin') or (platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'x86_64' and sys_platform == 'darwin') or (platform_machine == 'AMD64' and sys_platform == 'linux') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'amd64' and sys_platform == 'linux') or (platform_machine == 'arm64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, + { name = "httpx", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "packaging", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "pyyaml", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "tqdm", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "typing-extensions", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fe/0f/e83fdd856da8fca26bf78d71709ebd120432a0ce535e72b9597cab1eb5bf/huggingface_hub-1.32.0.tar.gz", hash = "sha256:ed70a45498abe86039df7c2f4e5f7575de524be908d3840e8f828d5525eafd6a", size = 1038662 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1b/cf/d98dd561d6d0d7b7d7a64d1563f8aaaa7c235daee41c1c9bcc3da62420ed/huggingface_hub-1.32.0-py3-none-any.whl", hash = "sha256:b0c7c80561969d9cdacdd55fce67ba9584cca0b9d4ea80957a3a5c1445fac5c8", size = 842906 }, +] + +[[package]] +name = "humanize" +version = "4.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0a/ea/13a1ef3c12d12662905801495283530251918b70d62d368f1d2e0272c70d/humanize-4.16.0.tar.gz", hash = "sha256:7dc2244a2f84a4bfb1d36c37bac80cd78e35cdc5c119206d87b018e1445f3a3f", size = 89515 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b0/aa/0b7365d30fed43e7a3449aba1fe20a0a7174d9cf13e282af4e69ac825441/humanize-4.16.0-py3-none-any.whl", hash = "sha256:353eb2f34c09d098b2880eee8bef21832eae6d174f48c5762fff7e5fcb74d01d", size = 137209 }, +] + +[[package]] +name = "hydra-core" +version = "1.3.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "antlr4-python3-runtime", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "omegaconf", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "packaging", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6d/8e/07e42bc434a847154083b315779b0a81d567154504624e181caf2c71cd98/hydra-core-1.3.2.tar.gz", hash = "sha256:8a878ed67216997c3e9d88a8e72e7b4767e81af37afb4ea3334b269a4390a824", size = 3263494 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c6/50/e0edd38dcd63fb26a8547f13d28f7a008bc4a3fd4eb4ff030673f22ad41a/hydra_core-1.3.2-py3-none-any.whl", hash = "sha256:fa0238a9e31df3373b35b0bfb672c34cc92718d21f81311d8996a16de1141d8b", size = 154547 }, +] + +[[package]] +name = "idna" +version = "3.20" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f5/08/8eea9d4b8302028f3abb2c0813953f7aec26d33b7a8960ed760e65ff29fa/idna-3.20.tar.gz", hash = "sha256:a7db850025b95ded1eae8a46181a1a6c56c92c96f0e2b005d9ff8dc0210cab44", size = 216463 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/58/a2/bb081bab032533a855d44de1d56f8e8426114ff1ba5d1f07a438a0a654f8/idna-3.20-py3-none-any.whl", hash = "sha256:ab7ae7122974553370f0bdb919e1a960b2cd1bc1ef0276416d896db81c14582c", size = 69583 }, +] + +[[package]] +name = "indic-numtowords" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/aa/46/683e92580d9c1752917d2f9ec2a44d2adc21cdfe4deeaa0fe87fc23dbea8/indic_numtowords-1.1.0.tar.gz", hash = "sha256:d1addc21444c332e05bfd8726af427960c096c2a16776d98bee4fbc36ade5d25", size = 44220 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/16/b2b6491d95a15bda712163b4e27428f2cb1ac3a1b4fb59b140dbc76f6ce5/indic_numtowords-1.1.0-py3-none-any.whl", hash = "sha256:bf4b7b9e539323d9b00bc868caa2d9369170b8f3ac4d19619bf9c6cdc6f89572", size = 71635 }, +] + +[[package]] +name = "intervaltree" +version = "3.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "sortedcontainers", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/53/c3/b2afa612aa0373f3e6bb190e6de35f293b307d1537f109e3e25dbfcdf212/intervaltree-3.2.1.tar.gz", hash = "sha256:f3f7e8baeb7dd75b9f7a6d33cf3ec10025984a8e66e3016d537e52130c73cfe2", size = 1231531 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/83/7f/8a80a1c7c2ed05822b5a2b312d2995f30c533641f8198366ba2e26a7bb03/intervaltree-3.2.1-py2.py3-none-any.whl", hash = "sha256:a8a8381bbd35d48ceebee932c77ffc988492d22fb1d27d0ba1d74a7694eb8f0b", size = 25929 }, +] + +[[package]] +name = "jinja2" +version = "3.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899 }, +] + +[[package]] +name = "joblib" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cloudpickle", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d5/1d/537ab090f302b838943a1b56497dd53059b9a9b46a074936470173a2e207/joblib-1.6.0.tar.gz", hash = "sha256:2ccc96785b12046c08fd6d55839c12857831b54a3c1673ffadd2f04bfc4eda03", size = 327903 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/53/84099323c2ec4be98d935f63c033ac4151ee83836ca1050ede3b3aadf155/joblib-1.6.0-py3-none-any.whl", hash = "sha256:3dbbf9f6e4b592a2357b854608e980fe6390d131d7a82f011a377ef2ebef7aba", size = 306115 }, +] + +[[package]] +name = "kaldialign" +version = "0.12.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/58/bd83457ab1f4296cb48da2057330fd669e14d0139353d87f9c138fc055af/kaldialign-0.12.0.tar.gz", hash = "sha256:f25743ef7dcf15716c5ac47e164dd7191cab791196a78c27cff6a99c253e1a14", size = 31415 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/cd/f7c852333a77887ce9166415512d19da329c4896ec0a16127b250c20fd5e/kaldialign-0.12.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3a4f914f4971d20784710944a965de5ce0d1aa1ce26934767c0aee95194e0d98", size = 166411 }, + { url = "https://files.pythonhosted.org/packages/35/c9/a01c036b6e0afef2139bcc3088d3ce9fbad111864457c89ac059b8fbaee4/kaldialign-0.12.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:62694144a5e0ae086910bd979bc6b99b36c97a4276614bd28b5b06fed133abbf", size = 99987 }, + { url = "https://files.pythonhosted.org/packages/d0/00/77885350408ef2ec6281cafd3dfbec099c25893d502bcc7ac819637309c5/kaldialign-0.12.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8189d32046b3a865b6f147662620feba736580b5424368164c6a7bc927d3e0a8", size = 110985 }, +] + +[[package]] +name = "lazy-loader" +version = "0.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/19/8c/0f2ff2a8b7513e68871a74740c17a504b0488c679b379e6445ebb7bd78dc/lazy_loader-0.6.tar.gz", hash = "sha256:2f4b7824d6401958639008a0cae20c776b61dff619accd6890693e2de8260167", size = 18438 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/23/93/4e45e37f26c820216704b36b8e67cc55c490a7abd287a78a26a2c0e21386/lazy_loader-0.6-py3-none-any.whl", hash = "sha256:77253be3391b06124a0e16105bd663b6c54470af1a9ca8e1cf026f38d58ed056", size = 8791 }, +] + +[[package]] +name = "lhotse" +version = "2.0.0a6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "audioread", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "click", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "cytoolz", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "intervaltree", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "numpy", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "packaging", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "pyyaml", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "soundfile", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "tabulate", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "torch", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "tqdm", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e0/b8/9537e1f5e364026d178c96502afdafafa779425802f54e1cbc6772d98bea/lhotse-2.0.0a6.tar.gz", hash = "sha256:c79f2bb7f39c39386e8bb9df79923633eddb0ed64949c89ac15502ee23fea4ea", size = 757425 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/ae/8ba7ee0b1db7fadf8de8f58121723a6b1ae3d88bdb0a1ffeee165b62d4ba/lhotse-2.0.0a6-py3-none-any.whl", hash = "sha256:15145bd072a3d6c5f6b52b07e40f0425f7189ec3a409fedd416c04331422cac3", size = 984574 }, +] + +[[package]] +name = "librosa" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "decorator", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "joblib", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "lazy-loader", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "msgpack", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "numba", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "numpy", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "pooch", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "scikit-learn", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "scipy", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "soundfile", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "soxr", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8e/c1/ce66b20953b7370421dd13d22b4e04e401346c824ceacb9f4e722eec84dc/librosa-1.0.0.tar.gz", hash = "sha256:73ed480d4022e436e85dfa6f6b06ff38a259b9210039ac99939cd64854b61a57", size = 377965 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/96/14/0b2e4cc8017796dc8f69de740708601832c1e8d5377166eedb4db0a92264/librosa-1.0.0-py3-none-any.whl", hash = "sha256:5910a6c0e1b2e494b92758c1615a7acbd0515a2315e138927ba2982f2af88857", size = 294628 }, +] + +[[package]] +name = "lightning" +version = "2.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "fsspec", extra = ["http"], marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "lightning-utilities", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "packaging", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "pytorch-lightning", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "pyyaml", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "torch", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "torchmetrics", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "tqdm", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "typing-extensions", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/56/d0/78ea244ac044cd4df15aa8294a50ff3561fb177e7e5ba788aaa542046cae/lightning-2.4.0.tar.gz", hash = "sha256:9156604cc56e4b2b603f34fa7f0fe5107375c8e6d85e74544b319a15faa9ed0e", size = 620632 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a3/2c/85eaf42c983b0cd81bcda5876da2c8e2a9fd347908666ea9855724369171/lightning-2.4.0-py3-none-any.whl", hash = "sha256:560163af9711cf59055c448232c473150a299089efce0d2be3cc3288082d8768", size = 810971 }, +] + +[[package]] +name = "lightning-utilities" +version = "0.15.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "typing-extensions", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f1/45/7fa8f56b17dc0f0a41ec70dd307ecd6787254483549843bef4c30ab5adce/lightning_utilities-0.15.3.tar.gz", hash = "sha256:792ae0204c79f6859721ac7f386c237a33b0ed06ba775009cb894e010a842033", size = 33553 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/25/f4/ead6e0e37209b07c9baa3e984ccdb0348ca370b77cea3aaea8ddbb097e00/lightning_utilities-0.15.3-py3-none-any.whl", hash = "sha256:6c55f1bee70084a1cbeaa41ada96e4b3a0fea5909e844dd335bd80f5a73c5f91", size = 31906 }, +] + +[[package]] +name = "llvmlite" +version = "0.49.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/27/72ae94ea5c8f7349ec1c229d4cd058feb799cbd0833ad6d1b47c919b37b7/llvmlite-0.49.0.tar.gz", hash = "sha256:00f16db782f4a13c78c5804aedc434e46794a77e89999a168f9401106270e50a", size = 194467 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6b/ae/3f699ebe3590e15e023a6372dd147526fd8ec398aacf9ceb844e854964a8/llvmlite-0.49.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:b541c8fac3450db7574d1f53cf9dff83f285bfed9d69bf81fe71fc2a7d4f97fe", size = 40479231 }, + { url = "https://files.pythonhosted.org/packages/be/3c/e97f69c62a2d972066d9a2612ce1f3de313035ac897a5b9f787cad8b55f7/llvmlite-0.49.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6acba646d88abbc87d5c113a3d62c1fbf8b8fee11c6493f516803e30f21ae870", size = 59890658 }, + { url = "https://files.pythonhosted.org/packages/69/e6/e942ee08605fc0526ff3854260c384d8315a5830e16c4c2a5aebc14dc9bf/llvmlite-0.49.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4ec8ad805e7515cb8440a690eb3cef4d34acb29eef80b705ec4e1c1ad3c43c68", size = 58344481 }, +] + +[[package]] +name = "lxml" +version = "6.1.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/23/ad/28ecd7cb894d172f3c9c80a075eeeb2017ac62e3632cee05a5f9493547eb/lxml-6.1.3.tar.gz", hash = "sha256:45222d94ddd511536f3b2f7d9deae3b2339b4ce0f075f1ca25703b07cad9dd21", size = 4211198 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dd/1f/a180b57d9eeabaab77f9d5aa30356898ea749c4795596a8f66d1eb6bef2e/lxml-6.1.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0c0710ac085a157b593c38fbcacd950f15c4afa8e2057527185875ab302752bc", size = 8602094 }, + { url = "https://files.pythonhosted.org/packages/a8/25/070c92013a1c029a602b03560d68772313d918268667fa993da7961759c9/lxml-6.1.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:623c8799c17128753c65699f1c3aa32402657393a9ad6db09ed8b98ddf76611d", size = 4638308 }, + { url = "https://files.pythonhosted.org/packages/1e/1c/722e88883173097a1a375153e3c2447eba3060d0231522cf6596e99f4195/lxml-6.1.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f683dc6300317700025e41d89a43e0276692ded16113a3c43eab704d605c58e5", size = 4939696 }, + { url = "https://files.pythonhosted.org/packages/db/36/aa413bc214dc4f785ad2b2ddd8cc99aae7062d49ab155e91e6011af00daf/lxml-6.1.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:379f8a75cf6eb7eef0af074b55f49ab73b868388a98de14646abcdfa4564bb11", size = 5105247 }, + { url = "https://files.pythonhosted.org/packages/a3/a0/a1f7f1313795bfec67b77f01ef3b1128d49f2d7f66a8413fa55d47f4e25f/lxml-6.1.3-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b37772102d44bb6628186accca3a121b1fa3a6b3d97518a8c29a5229ca4c0d0a", size = 5011915 }, + { url = "https://files.pythonhosted.org/packages/b9/78/840e7e3f1d0cc7a5cfac5d8505b97e25b6427fd774ac4bae672aaebfb4b5/lxml-6.1.3-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ddcf547bea2aee967d6a77779376a45e77e610e8465147a1f3d7e20d539d6e32", size = 5638175 }, + { url = "https://files.pythonhosted.org/packages/0a/20/e022dbc6b4753a9bc9fc5fb28a27163430c1731b9913997f6544c1b2518c/lxml-6.1.3-cp312-cp312-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:909f4e927bb051f7740d6367285fc60cdcfdaf0258c2dba4ff5ba7eadadc250c", size = 5244675 }, + { url = "https://files.pythonhosted.org/packages/99/83/82cde81d2b5eb38d1539fdfdf318abdd014a7e604f4df01c9cd3deb18f2a/lxml-6.1.3-cp312-cp312-manylinux_2_28_i686.whl", hash = "sha256:a5c18810318303ce9afb3f95e2ddb54834f96fa699a8600433fd5a93dcf44c56", size = 5358205 }, + { url = "https://files.pythonhosted.org/packages/d2/a1/f3b057371c8cb29f2a9c9c44ea320592446e40b74a4b0af68c3d8e65bc73/lxml-6.1.3-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:3e42265103fb385d8642a78672edf376c6f7e1d3598a7a4f9cb1278f2f6b5f6f", size = 4704495 }, + { url = "https://files.pythonhosted.org/packages/1a/a4/230eb28be5d412152ffc3c679b51fe1aeede5a53f3a8eb6e9748f2f4754f/lxml-6.1.3-cp312-cp312-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:21402998e4b78e7cce237d2788841aaa21ac9a4d1574d04dc2d12ee41ae807b5", size = 5255117 }, + { url = "https://files.pythonhosted.org/packages/a3/18/1969f56763af24ce42ea156007b0b2d73fddea552e283b2010416394f0f4/lxml-6.1.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:38fc4e4e4e084e0bd491949482527d406788045c546d4f8789e93fc527b91385", size = 5054424 }, + { url = "https://files.pythonhosted.org/packages/f4/d4/2a90acc1f6fabaa3a8db9340437822bd8d041b205d626a4b3e8621aaa390/lxml-6.1.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5609efdb0d3c95499c00046bc53648b3482ec2175b5503d6e611b3f0555dc71d", size = 4785572 }, + { url = "https://files.pythonhosted.org/packages/a5/1e/b90e845b1dcd0f2f3f26b98283d857f25909223aacd265eee032c34ab8b1/lxml-6.1.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:97ce49699d87ebf8aad631b55d65b33219a4f1bfefbbf5bff19dc9af160aeaf9", size = 5656516 }, + { url = "https://files.pythonhosted.org/packages/eb/ab/0a1b802c57f3fba5c4efd77d5c6b78adaa8f7b681f0c90456b140fe8bf6c/lxml-6.1.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:48542c9acba9ff9450bd18d871d2c2c8787fdb283572b623d206f1b927cd7d9e", size = 5245982 }, + { url = "https://files.pythonhosted.org/packages/da/ee/2c016fbceb3778137459292538d9dfa7e3ad9070fe409c15254ddd90d2cc/lxml-6.1.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c55e71a9b1db1f107efb60da49c093689b74c5c31a708e5379e2fd9439d4fbb5", size = 5267340 }, +] + +[[package]] +name = "markdown" +version = "3.10.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/29/6f/da4c6aea59b3001f2e8c0ec7497475aadaf3b021c10cab5b2858f0f32b26/markdown-3.10.3.tar.gz", hash = "sha256:3589362618f743188b4d955b874402bc814f4f83f544dc207719f4baa7d9c45f", size = 372596 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/64/69/4a5af2bc115a9a33fefe51709749de8262be3f9ba063d1753a837cdbc49c/markdown-3.10.3-py3-none-any.whl", hash = "sha256:fa6c92a00a4a3c98b22728c64a935ae1928250ae65058a6ded814d2cc29a4cea", size = 110757 }, +] + +[[package]] +name = "markdown-it-py" +version = "4.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mdurl", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/ff/7841249c247aa650a76b9ee4bbaeae59370dc8bfd2f6c01f3630c35eb134/markdown_it_py-4.2.0.tar.gz", hash = "sha256:04a21681d6fbb623de53f6f364d352309d4094dd4194040a10fd51833e418d49", size = 82454 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/81/4da04ced5a082363ecfa159c010d200ecbd959ae410c10c0264a38cac0f5/markdown_it_py-4.2.0-py3-none-any.whl", hash = "sha256:9f7ebbcd14fe59494226453aed97c1070d83f8d24b6fc3a3bcf9a38092641c4a", size = 91687 }, +] + +[[package]] +name = "markupsafe" +version = "3.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", size = 11615 }, + { url = "https://files.pythonhosted.org/packages/9a/81/7e4e08678a1f98521201c3079f77db69fb552acd56067661f8c2f534a718/markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", size = 12020 }, + { url = "https://files.pythonhosted.org/packages/1e/2c/799f4742efc39633a1b54a92eec4082e4f815314869865d876824c257c1e/markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", size = 24332 }, + { url = "https://files.pythonhosted.org/packages/3c/2e/8d0c2ab90a8c1d9a24f0399058ab8519a3279d1bd4289511d74e909f060e/markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d", size = 22947 }, + { url = "https://files.pythonhosted.org/packages/2c/54/887f3092a85238093a0b2154bd629c89444f395618842e8b0c41783898ea/markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a", size = 21962 }, + { url = "https://files.pythonhosted.org/packages/c9/2f/336b8c7b6f4a4d95e91119dc8521402461b74a485558d8f238a68312f11c/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b", size = 23760 }, + { url = "https://files.pythonhosted.org/packages/32/43/67935f2b7e4982ffb50a4d169b724d74b62a3964bc1a9a527f5ac4f1ee2b/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f", size = 21529 }, + { url = "https://files.pythonhosted.org/packages/89/e0/4486f11e51bbba8b0c041098859e869e304d1c261e59244baa3d295d47b7/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b", size = 23015 }, +] + +[[package]] +name = "mdurl" +version = "0.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979 }, +] + +[[package]] +name = "meeteval" +version = "0.4.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cython", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "kaldialign", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "numpy", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "packaging", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "scipy", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d4/13/10edb36a8881b24690e27213bfb2395e8857a70eeef61d241a463cb5522c/meeteval-0.4.3.tar.gz", hash = "sha256:02d3a359f375d39c67dfb8fe1c061e7dac19d6fc1fb89ee72d793a5813dafeb2", size = 843558 } + +[[package]] +name = "ml-dtypes" +version = "0.6.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/12/72/307d7c4bd0600601c7133fba5cb78af7db968152951c1cd473abb1cda782/ml_dtypes-0.6.0.tar.gz", hash = "sha256:5e60251d32ced5598972e4d5e06a2f044341f9291402551a3f6f0ec44f9299b0", size = 3032327 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/84/6a/441eb053b078954f7fea284dfb288701884d0a1404d39babb858e1649023/ml_dtypes-0.6.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:5359c588cc62de6f78d7430f06b65853d884955494d86d6ad90b6dd64a3f3a08", size = 565447 }, + { url = "https://files.pythonhosted.org/packages/ed/cf/87e8a6c57eed63a91782a0d229856ddf73e138ce004dd71e2799a9dcdb33/ml_dtypes-0.6.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:37da32aa97749251025666d62372775019594577b9c9e9cfda83bed48d778fdb", size = 360227 }, + { url = "https://files.pythonhosted.org/packages/c7/f9/7d76c1eae866f5d4636401b31b6d6dd90e4b4ced1fa7cfdfcca9c60e4bd3/ml_dtypes-0.6.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3b4a480aa8fd54a1805b8ac10f3f91763926a74f73c0c364c10f9231854f4170", size = 409890 }, +] + +[[package]] +name = "more-itertools" +version = "11.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/de/1d/f4da6f02cdffe04d6362210b807146a26044c88d839208aec273bb0d9184/more_itertools-11.1.0.tar.gz", hash = "sha256:48e8f4d9e7e5878571ecf6f2b4e57634f93cd474cc8cfbd2376f2d11b396e30d", size = 145772 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e8/3d/1087453384dbde46a8c7f9356eead2c58be8a7bf156bca40243377c85715/more_itertools-11.1.0-py3-none-any.whl", hash = "sha256:4b65538ae22f6fed0ce4874efd317463a7489796a0939fa66824dd542125a192", size = 72226 }, +] + +[[package]] +name = "mpmath" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e0/47/dd32fa426cc72114383ac549964eecb20ecfd886d1e5ccf5340b55b02f57/mpmath-1.3.0.tar.gz", hash = "sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f", size = 508106 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c", size = 536198 }, +] + +[[package]] +name = "msgpack" +version = "1.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6d/44/ea2100ec54d30c46ee9dba10a3bfb79b655e96c6df237238a3234c75869b/msgpack-1.2.2.tar.gz", hash = "sha256:9eb0b0e602064527a045ea28c4f174ed69383587e29cebe28947e3b84106eb2a", size = 187025 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/31/78/90c15bebb1a72667349ca62d4507e9d9369e7f8f76b95f490b823d3622e5/msgpack-1.2.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a4348705be86e029d04e741cf9ed0dfe03e942d7d3b92e838fa80d3aa2c3ebc", size = 84275 }, + { url = "https://files.pythonhosted.org/packages/88/88/c2b6d8e81571da87aa232c0e34a3f3a0e618e6235892065ec82d1d81fc7a/msgpack-1.2.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0a652ceeededf71d3fa40c303a02a149d42338d310162367b91c539d4bd6e0a3", size = 83970 }, + { url = "https://files.pythonhosted.org/packages/da/c0/d3ede9f5d16acb4c05a9281859f1e99ef9f877a928eb78454c37f70db001/msgpack-1.2.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:90986cc9aab9d7d1d8f38bcbf65d3f7ac83bdd90c35765db7d691b4829698cba", size = 409401 }, + { url = "https://files.pythonhosted.org/packages/41/f0/29f591bea185616cf417645ac03bd3ad9b317483ad8572160e325f7fe777/msgpack-1.2.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:77c2e018417dc1d66f235e383877ee885b60ade9d29e494dd581e08af2cb1923", size = 420619 }, + { url = "https://files.pythonhosted.org/packages/4e/8e/c70c8c9180c5ddf4440eb8658ebead98e22e7686fbf84f6b165031430750/msgpack-1.2.2-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0e91332144f69bc3018c91232fac26da580ef748fb8eaddd7914d4458001cc4f", size = 379747 }, + { url = "https://files.pythonhosted.org/packages/50/9a/f10ce11fa62700c9ab87a22e65b9ca272f7f673ddd31aeb2de6ae272ad35/msgpack-1.2.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3e915d390d7068b257ca8b62f3fc59fad135c8631d1017ab03b0b924b07c5367", size = 398944 }, + { url = "https://files.pythonhosted.org/packages/82/fe/d7be978456ff8552e69a8e270d882e7530e01513c096b293d83df03753ea/msgpack-1.2.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:c522420d78db2431887d45b518e304d86e27b9ad0b30f24e3806a6ad5d8bdbfc", size = 373979 }, + { url = "https://files.pythonhosted.org/packages/be/af/91b0d8d3fb3063e259daee3ea8515cea6282f68f4b0e5f0b6fea25762c6e/msgpack-1.2.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4b554d8164ebb526892194f71dcd96ef1fefe0c250087498785d3ffc04a80be3", size = 417781 }, +] + +[[package]] +name = "msgspec" +version = "0.21.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e3/60/f79b9b013a16fa3a58350c9295ddc6789f2e335f36ea61ed10a21b215364/msgspec-0.21.1.tar.gz", hash = "sha256:2313508e394b0d208f8f56892ca9b2799e2561329de9763b19619595a6c0f72c", size = 319193 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6e/cf/317224852c00248c620a9bcf4b26e2e4ab8afd752f18d2a6ef73ebd423b6/msgspec-0.21.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d4248cf0b6129b7d230eacd493c17cc2d4f3989f3bb7f633a928a85b7dcfa251", size = 196188 }, + { url = "https://files.pythonhosted.org/packages/6d/81/074612945c0666078f7366f40000013de9f6ba687491d450df699bceebc9/msgspec-0.21.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5102c7e9b3acff82178449b85006d96310e690291bb1ea0142f1b24bcb8aabcb", size = 188473 }, + { url = "https://files.pythonhosted.org/packages/8a/37/655101799590bcc5fddb2bd3fe0e6194e816c2d1da7c361725f5eb89a910/msgspec-0.21.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:846758412e9518252b2ac9bffd6f0e54d9ff614f5f9488df7749f81ff5c80920", size = 218871 }, + { url = "https://files.pythonhosted.org/packages/b5/d1/d4cd9fe89c7d400d7a18f86ccc94daa3f0927f53558846fcb60791dce5d6/msgspec-0.21.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:21995e74b5c598c2e004110ad66ec7f1b8c20bf2bcf3b2de8fd9a3094422d3ff", size = 225025 }, + { url = "https://files.pythonhosted.org/packages/24/bf/e20549e602b9edccadeeff98760345a416f9cce846a657e8b18e3396b212/msgspec-0.21.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6129f0cca52992e898fd5344187f7c8127b63d810b2fd73e36fca73b4c6475ee", size = 222672 }, + { url = "https://files.pythonhosted.org/packages/b4/68/04d7a8f0f786545cf9b8c280c57aa6befb5977af6e884b8b54191cbe44b3/msgspec-0.21.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ef3ec2296248d1f8b9231acb051b6d471dfde8f21819e86c9adaaa9f42918521", size = 227303 }, +] + +[[package]] +name = "multidict" +version = "6.9.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/99/1d4d69c3512d0ddbfa3a1b69cfd9a151012ab2eb4eabbb096201b1f0b7d8/multidict-6.9.1.tar.gz", hash = "sha256:0f06e60fa190aa7abd0914c2a766736fdc8e9f34878c4346338534b73d1b20e2", size = 182404 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d9/0d/4b5afb6d3e545c9af0cdfe2db8f6f4c6664568c23d863d888674e447e6a4/multidict-6.9.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:29138fef49828542e859828107e42e50d0e587c513b7eb4b2d92bade2b0860fe", size = 98360 }, + { url = "https://files.pythonhosted.org/packages/89/ab/1b9ca66251899981b21138b87da9d5a9c2c81af12b1ea7d19466972f7fe2/multidict-6.9.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:19e31815d41cefc365489e591d105d2baceb2f65aa75d29471fbdbda8651e006", size = 59979 }, + { url = "https://files.pythonhosted.org/packages/36/eb/6ae44062466c26c8469ef43f2481a6a48d8cea0587b2d54514ec92e2adfd/multidict-6.9.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6ed30be8918e18c8bed0a2e8b70639ecf02feb61ed00ca2e41cfcb2a50fa3f42", size = 57736 }, + { url = "https://files.pythonhosted.org/packages/b9/5c/a67817593019257a4ac8b0d1b4c426030e637c047b0692ef405439ecea7a/multidict-6.9.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:637f4ae36264bd7b8d9a60193acddc1d735ad52e8ed53a19931ea6d921fea8e5", size = 333097 }, + { url = "https://files.pythonhosted.org/packages/90/bf/599ae2e6222822d88a247a8a7ae82fe6fd25d5700757b79603d5edafe6a0/multidict-6.9.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:35fc236507fb1b3138f0af5ecd5f94ed752d4d6d826248eae425f86204013eea", size = 334196 }, + { url = "https://files.pythonhosted.org/packages/15/10/d8aac5acacbe7f5c117866c776ec26d5f15a868759b6f37ad8e7ed3b5b02/multidict-6.9.1-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e58952f04772f59f11c6e007471449809a30165188669bca8fdb19dde40a8f24", size = 317465 }, + { url = "https://files.pythonhosted.org/packages/19/0a/714f796f7293a8b1c5c3f465a26996231d5450c54e85d64ef1258c091134/multidict-6.9.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d35a4f1c63f07fbb8c8f9946dea98b21eddf6c57421585f71d91864be3ba2a24", size = 345914 }, + { url = "https://files.pythonhosted.org/packages/da/b1/e37fbf769c567be277bcf32df6234035a4384677fcc1bd852752be3d6b93/multidict-6.9.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b5f8771aaaed7f80e84a4e471d2f29ab6721e4595075e54d03ae1ed951b2000a", size = 351163 }, + { url = "https://files.pythonhosted.org/packages/ed/5b/db08419c1e1f7c9d60cfd2787b2b517d7ae4ebbda8281b48a33eb5141467/multidict-6.9.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:976fd7689d69ec78d67d31d38d396d8adb562f7e8368279f76aed4aa451fa06d", size = 336881 }, + { url = "https://files.pythonhosted.org/packages/9b/07/cc9bc8a62651d2d53ab93ee4993b3a71b7cb78eb8ebfc9c757a5b6698617/multidict-6.9.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:95052e8777a86bae87c0bd0b5ab22d809e3d1d02bf69e3e66ddda5ba75a05805", size = 303405 }, + { url = "https://files.pythonhosted.org/packages/b6/0c/8e912afafa70e944dbb8bec4b66ca6e008511395278c0d3dd0e89567536a/multidict-6.9.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1a8adfcaf96f587ab138476eaddef95f29b8a2a8a9afbfea8d2fd62180995d02", size = 324265 }, + { url = "https://files.pythonhosted.org/packages/66/6a/62c2af80fb085e6234805017af857b8e913dfac7a55e9c1349c27768c58a/multidict-6.9.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:1a53de2772cfb74559df2eb4456ec4eeb908435ec55a84b69370d9d745d62aa8", size = 322085 }, + { url = "https://files.pythonhosted.org/packages/f8/6b/35bf801b336fd960811207203ffdcdc24acc3558b3e7ff2e1c914b141e70/multidict-6.9.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f2ce963299d42fa3f22a90adc0fdf174792ffef5ff4c7ffb68260548fb05580", size = 338107 }, + { url = "https://files.pythonhosted.org/packages/80/41/495ef65bf5bba29d142d81b3fe1b8154b919bed70e96491b1e93c3c26f0f/multidict-6.9.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5b30ddf7234e611ca877575b62840e6af5977f92f1f9d532eedbb05a44ff8004", size = 339631 }, + { url = "https://files.pythonhosted.org/packages/19/bd/057fdff5f4e04dcd40a960e38f77d19d3c4b67dd243ffa5f43718edb29fc/multidict-6.9.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:63ada7ee2e9345695f9e9bc4c65d72222253f07b1ac94fd0e37555cc6f3c7f60", size = 302080 }, + { url = "https://files.pythonhosted.org/packages/3f/de/9ace933ee8dad808632523726f42255b09600087219e3d4ead7369820910/multidict-6.9.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:3c95601ed98fad3f6e2f8fe809c3b526b0fab31ef525e00a155e227f3d17f58a", size = 341107 }, + { url = "https://files.pythonhosted.org/packages/d8/ac/7c1204406097bfc5c283d4a3287d61166807d189917567df4c1318484fc4/multidict-6.9.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c148e8b596000dd3e4bfe206e70f3e666be18d72032e0012555f2373c52e35d6", size = 333482 }, + { url = "https://files.pythonhosted.org/packages/be/59/e26cb779be4c591d1a910f59d29aca9fba4de70349840a833beba2652371/multidict-6.9.1-py3-none-any.whl", hash = "sha256:7bf6478188f4e47bf5686e8a33da4ae28bf43b1b2528d9ee144d28492bfac60b", size = 19176 }, +] + +[[package]] +name = "multiprocess" +version = "0.70.19" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "dill", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a2/f2/e783ac7f2aeeed14e9e12801f22529cc7e6b7ab80928d6dcce4e9f00922d/multiprocess-0.70.19.tar.gz", hash = "sha256:952021e0e6c55a4a9fe4cd787895b86e239a40e76802a789d6305398d3975897", size = 2079989 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e3/45/8004d1e6b9185c1a444d6b55ac5682acf9d98035e54386d967366035a03a/multiprocess-0.70.19-py310-none-any.whl", hash = "sha256:97404393419dcb2a8385910864eedf47a3cadf82c66345b44f036420eb0b5d87", size = 134948 }, + { url = "https://files.pythonhosted.org/packages/86/c2/dec9722dc3474c164a0b6bcd9a7ed7da542c98af8cabce05374abab35edd/multiprocess-0.70.19-py311-none-any.whl", hash = "sha256:928851ae7973aea4ce0eaf330bbdafb2e01398a91518d5c8818802845564f45c", size = 144457 }, + { url = "https://files.pythonhosted.org/packages/71/70/38998b950a97ea279e6bd657575d22d1a2047256caf707d9a10fbce4f065/multiprocess-0.70.19-py312-none-any.whl", hash = "sha256:3a56c0e85dd5025161bac5ce138dcac1e49174c7d8e74596537e729fd5c53c28", size = 150281 }, + { url = "https://files.pythonhosted.org/packages/7e/82/69e539c4c2027f1e1697e09aaa2449243085a0edf81ae2c6341e84d769b6/multiprocess-0.70.19-py39-none-any.whl", hash = "sha256:0d4b4397ed669d371c81dcd1ef33fd384a44d6c3de1bd0ca7ac06d837720d3c5", size = 133477 }, +] + +[[package]] +name = "narwhals" +version = "2.26.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/35/68/5351e34623d253423240ea7de3f8fc74fa8ab14b1ab3c0ec4ac8997413c9/narwhals-2.26.0.tar.gz", hash = "sha256:6b9cadca82f375c7e4cf584fdc86ca25da54827307a9c58f94547ee6104b82dd", size = 686970 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/40/b5/1b84b2c784db76d69442334bc8b8748c840f13ca53be086f4f250ad4a0bc/narwhals-2.26.0-py3-none-any.whl", hash = "sha256:29326d74f107c347fd1009bd58e38d9f7c7c5b51e6de97bc93dbc325d9038b54", size = 474034 }, +] + +[[package]] +name = "nemo-toolkit" +version = "3.1.0+cf724ac33" +source = { git = "https://github.com/NVIDIA-NeMo/Speech.git?rev=cf724ac337d1ebc7d0dda1e23fb80916f52927a5#cf724ac337d1ebc7d0dda1e23fb80916f52927a5" } +dependencies = [ + { name = "aistore", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "cuda-bindings", marker = "sys_platform == 'linux'" }, + { name = "huggingface-hub", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "numba", marker = "sys_platform == 'darwin'" }, + { name = "numpy", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "onnx", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "scikit-learn", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "setuptools", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "smart-open", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "tensorboard", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "text-unidecode", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "torch", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "tqdm", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "wrapt", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] + +[package.optional-dependencies] +asr = [ + { name = "braceexpand", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "datasets", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "einops", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "hydra-core", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "kaldialign", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "lhotse", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "librosa", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "lightning", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "nv-one-logger-core", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "nv-one-logger-pytorch-lightning-integration", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "nv-one-logger-training-telemetry", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "omegaconf", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "packaging", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "pandas", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "sacrebleu", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "scipy", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "sentencepiece", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "soundfile", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "torchmetrics", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "transformers", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "wandb", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "webdataset", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "whisper-normalizer", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] + +[[package]] +name = "networkx" +version = "3.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/dc/76/3af777226b63a5e64a6b36b1ec5855c14e2b94a37096d4760e595fc43511/networkx-3.7.tar.gz", hash = "sha256:fd77a511bd90f39f3d016351345b52cf5319b813bdca01de3f755d3cca62e96a", size = 1866482 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/cd/fe58041e9011f307c490e3e17dd48cc516448f7c698a3f2d9d9d65d7e6a8/networkx-3.7-py3-none-any.whl", hash = "sha256:e3fd2c13a7814cee3746340d8d7f8598a67f16a58bf47fb7f8793fab6efca1b0", size = 2142205 }, +] + +[[package]] +name = "numba" +version = "0.67.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "llvmlite", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "numpy", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7a/90/2544f4e3a61e501d6c9a5418fd4b905323222693d54a02cab0106a0af865/numba-0.67.0.tar.gz", hash = "sha256:cd75aa535b33fa05d9d930b1ae8af9f97a2881e96d72dfb38ec9b78284d9f851", size = 2836515 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0d/58/915cddba90010348ed0444451132fdde9b000bcbaff1582029b5bf115d11/numba-0.67.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:6004d8d5f28d4028687fb2d972d629295b13685943bd2ed5cd8810c3b848e219", size = 2745050 }, + { url = "https://files.pythonhosted.org/packages/bb/38/926757caaac18a66f057d7544a63620bf360a07d281c9f7ecadd2aa83963/numba-0.67.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f63d43db06b4756424d6d2484737c902e0ae944a0eec3e8b0b4de2c695b15caa", size = 3884596 }, + { url = "https://files.pythonhosted.org/packages/4d/6d/58291dc58da39d98b32db7f044729f6d8d4920cd9622fbab3179b54ff4c4/numba-0.67.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:76d3335aaeffb9dc88309420890e73497a00be08a7530441bc2b58ffe025bfa5", size = 3585290 }, +] + +[[package]] +name = "numpy" +version = "2.5.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/13/01/11703282db468b85f6f7b8c7f22d058de5970d5c7e60a3a8aaa313c3de36/numpy-2.5.3.tar.gz", hash = "sha256:df2d5874ff183595a4ba404edd04f6bd9b5505c1d7708573f6a6c17489a67563", size = 20791231 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d6/50/8fdbb16af64895706a45f06a4068e29db732ec180f3c1375f14123359138/numpy-2.5.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cb189f09db39283b26bfd061ec16189e14f71c6755207f72a0f7540867afe5b9", size = 16994982 }, + { url = "https://files.pythonhosted.org/packages/60/39/789131c1188c078dcb3a1692e72e1e050c68b88ffe72c9ccaac9bcd7a9cd/numpy-2.5.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f59a878c33d6b88122d80d239bb3b845d58708750b0cb06a09aebb9b18ec696c", size = 12009327 }, + { url = "https://files.pythonhosted.org/packages/9c/59/a312e95696e5f601914dd8b6dd844692ba61670807417e24b68e337b5c70/numpy-2.5.3-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:a72f874bc9e10e4b8f80426fb49716d5141f64442a0c8418065093ec8017fbb0", size = 5445405 }, + { url = "https://files.pythonhosted.org/packages/30/d0/5623a1707ed4fe16e3909fe3cf5ee3da004ae677ad23d83bbf3adf1a6faf/numpy-2.5.3-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:fc36dc566135b5eceec4cf89758fcb719266a019ef07dae1754ae7c9f617ef3e", size = 6783213 }, + { url = "https://files.pythonhosted.org/packages/f1/32/84146fc020ad3c25f805f70ab60da46fe3c540a21369754a7e4369754b6f/numpy-2.5.3-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:76c2c1e6bfa5c84adc6434dfbf013aa92096a7985221762c8f11fedfd20fff58", size = 15687872 }, + { url = "https://files.pythonhosted.org/packages/65/af/aa78d1a88805456e212b65461354cd943197fb9acecc4c90fd12295123a3/numpy-2.5.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7e18c623bb5c95acb3b3328861272816ba199fb531921c5d6d0b675f1fde9e3", size = 16717410 }, + { url = "https://files.pythonhosted.org/packages/3b/24/faa79d865e69a97ba17473b23a1b74094b2259c03e820c70297293b9ea49/numpy-2.5.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4f8929ee6c96bfbd7b4ed2032e0c03af86fe1826740ab61ddabf9072d06e57ff", size = 17040975 }, + { url = "https://files.pythonhosted.org/packages/62/4a/8877e629445a7176297dffcaf9c485faa96a95d81728a62521ad55bd4c0f/numpy-2.5.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b5d93cf48f687479941d12b69c873ad2cc76bbd487f0091c2200636497f34034", size = 18476479 }, +] + +[[package]] +name = "nv-one-logger-core" +version = "2.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "overrides", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "pydantic", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "strenum", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "toml", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "typing-extensions", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3b/37/963095797035f371e0db6ea761f5aaccb624fc786af217115b423baeb0e2/nv_one_logger_core-2.3.1.tar.gz", hash = "sha256:cbb2f87604c78b96a302f32d87199902129d76153a73a20f8455a250b3246c1d", size = 52640 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ee/c4/ea91554c4fcbff66057f667690101d7a4b965605741350ac661b03fa6c46/nv_one_logger_core-2.3.1-py3-none-any.whl", hash = "sha256:0c8b77bcdac4daa1ea913bf8d4afd2a057bd5526e3654ac39f67caba157341a6", size = 63066 }, +] + +[[package]] +name = "nv-one-logger-pytorch-lightning-integration" +version = "2.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "lightning", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "nv-one-logger-core", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "nv-one-logger-training-telemetry", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "setuptools", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "strenum", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0c/d0/3475b7ab17d367362f650fb0419e8669f41e63c1018f4a8ac2fbecfd2e85/nv_one_logger_pytorch_lightning_integration-2.3.1.tar.gz", hash = "sha256:b32d99b6a8f02a16538bcade939b0a7edd7249e936aacefe336b5519447340c3", size = 10979 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f0/56/01a55efb365b6864646b4ac941a1d9de66f024e880764510ba5a7a63f62c/nv_one_logger_pytorch_lightning_integration-2.3.1-py3-none-any.whl", hash = "sha256:f92904055fb0082516480cc1e3dd0bb6cedb2b033985ebfd4814b9cbf7da2cb2", size = 9822 }, +] + +[[package]] +name = "nv-one-logger-training-telemetry" +version = "2.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nv-one-logger-core", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "strenum", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "typing-extensions", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c5/21/016fa067967734d52f1ccf5a2a37a1a65216f2d7053bc2b85872cce956ca/nv_one_logger_training_telemetry-2.3.1.tar.gz", hash = "sha256:8c67940ea71799afaf1f46df3ba2f52f93aea26321c6f1c1d54aae02efc2a4af", size = 44435 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/15/97e6e4ddfe5fc35bcee74a45b7c33fb73abb83713c7dfa26420b971a86c3/nv_one_logger_training_telemetry-2.3.1-py3-none-any.whl", hash = "sha256:5319443829b59378a498c3c62ac98973e14f31be675c229ff2b14e2fe109aa0b", size = 44140 }, +] + +[[package]] +name = "nvidia-cublas" +version = "13.1.1.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-cuda-nvrtc", marker = "sys_platform == 'linux'" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/a1/0bd24ee8c8d03adac032fd2909426a00c88f8c57961b1277ded97f91119f/nvidia_cublas-13.1.1.3-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:b7a210458267ac818974c53038fbec2e969d5c99f305ab15c72522fa9f001dd5", size = 542848918 }, + { url = "https://files.pythonhosted.org/packages/3b/cd/154ca20c38269e05eff77c1464e6c1da89f50a6390b565e9d82e06bc11e1/nvidia_cublas-13.1.1.3-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:37936a16db8fe4ac1f065c2139360608a543a09275cb1a1af612e08cfa065436", size = 423138758 }, +] + +[[package]] +name = "nvidia-cuda-cupti" +version = "13.0.85" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/2a/80353b103fc20ce05ef51e928daed4b6015db4aaa9162ed0997090fe2250/nvidia_cuda_cupti-13.0.85-py3-none-manylinux_2_25_aarch64.whl", hash = "sha256:796bd679890ee55fb14a94629b698b6db54bcfd833d391d5e94017dd9d7d3151", size = 10310827 }, + { url = "https://files.pythonhosted.org/packages/33/6d/737d164b4837a9bbd202f5ae3078975f0525a55730fe871d8ed4e3b952b0/nvidia_cuda_cupti-13.0.85-py3-none-manylinux_2_25_x86_64.whl", hash = "sha256:4eb01c08e859bf924d222250d2e8f8b8ff6d3db4721288cf35d14252a4d933c8", size = 10715597 }, +] + +[[package]] +name = "nvidia-cuda-nvrtc" +version = "13.0.88" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c3/68/483a78f5e8f31b08fb1bb671559968c0ca3a065ac7acabfc7cee55214fd6/nvidia_cuda_nvrtc-13.0.88-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:ad9b6d2ead2435f11cbb6868809d2adeeee302e9bb94bcf0539c7a40d80e8575", size = 90215200 }, + { url = "https://files.pythonhosted.org/packages/b7/dc/6bb80850e0b7edd6588d560758f17e0550893a1feaf436807d64d2da040f/nvidia_cuda_nvrtc-13.0.88-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d27f20a0ca67a4bb34268a5e951033496c5b74870b868bacd046b1b8e0c3267b", size = 43015449 }, +] + +[[package]] +name = "nvidia-cuda-runtime" +version = "13.0.96" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/87/4f/17d7b9b8e285199c58ce28e31b5c5bbaa4d8271af06a89b6405258245de2/nvidia_cuda_runtime-13.0.96-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ef9bcbe90493a2b9d810e43d249adb3d02e98dd30200d86607d8d02687c43f55", size = 2261060 }, + { url = "https://files.pythonhosted.org/packages/2e/24/d1558f3b68b1d26e706813b1d10aa1d785e4698c425af8db8edc3dced472/nvidia_cuda_runtime-13.0.96-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7f82250d7782aa23b6cfe765ecc7db554bd3c2870c43f3d1821f1d18aebf0548", size = 2243632 }, +] + +[[package]] +name = "nvidia-cudnn-cu13" +version = "9.24.0.43" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-cublas", marker = "sys_platform == 'linux'" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/ca/30/7c257e3d5cb4fecb147b93895c66e29c93f8e76d74b45bb418ff0587c4ec/nvidia_cudnn_cu13-9.24.0.43-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:a6812a554a1ff0413e9c52b84c26c050380649ab9615f9c16bded368ce9f421f", size = 650976863 }, + { url = "https://files.pythonhosted.org/packages/5c/ba/791cffd048fe5b044e620df55267e3e95c0e6e07d50b41e377c03dfc910f/nvidia_cudnn_cu13-9.24.0.43-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:71f181cd810e90f9b6023b01186fe82d13d65f0ec098581ee201d39fad769e4b", size = 553099438 }, +] + +[[package]] +name = "nvidia-cufft" +version = "12.0.0.61" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-nvjitlink", marker = "sys_platform == 'linux'" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/8b/ae/f417a75c0259e85c1d2f83ca4e960289a5f814ed0cea74d18c353d3e989d/nvidia_cufft-12.0.0.61-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2708c852ef8cd89d1d2068bdbece0aa188813a0c934db3779b9b1faa8442e5f5", size = 214053554 }, + { url = "https://files.pythonhosted.org/packages/a8/2f/7b57e29836ea8714f81e9898409196f47d772d5ddedddf1592eadb8ab743/nvidia_cufft-12.0.0.61-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6c44f692dce8fd5ffd3e3df134b6cdb9c2f72d99cf40b62c32dde45eea9ddad3", size = 214085489 }, +] + +[[package]] +name = "nvidia-cufile" +version = "1.15.1.6" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3f/70/4f193de89a48b71714e74602ee14d04e4019ad36a5a9f20c425776e72cd6/nvidia_cufile-1.15.1.6-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:08a3ecefae5a01c7f5117351c64f17c7c62efa5fffdbe24fc7d298da19cd0b44", size = 1223672 }, + { url = "https://files.pythonhosted.org/packages/ab/73/cc4a14c9813a8a0d509417cf5f4bdaba76e924d58beb9864f5a7baceefbf/nvidia_cufile-1.15.1.6-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:bdc0deedc61f548bddf7733bdc216456c2fdb101d020e1ab4b88d232d5e2f6d1", size = 1136992 }, +] + +[[package]] +name = "nvidia-curand" +version = "10.4.0.35" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/72/7c2ae24fb6b63a32e6ae5d241cc65263ea18d08802aaae087d9f013335a2/nvidia_curand-10.4.0.35-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:133df5a7509c3e292aaa2b477afd0194f06ce4ea24d714d616ff36439cee349a", size = 61962106 }, + { url = "https://files.pythonhosted.org/packages/a5/9f/be0a41ca4a4917abf5cb9ae0daff1a6060cc5de950aec0396de9f3b52bc5/nvidia_curand-10.4.0.35-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:1aee33a5da6e1db083fe2b90082def8915f30f3248d5896bcec36a579d941bfc", size = 59544258 }, +] + +[[package]] +name = "nvidia-cusolver" +version = "12.0.4.66" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-cublas", marker = "sys_platform == 'linux'" }, + { name = "nvidia-cusparse", marker = "sys_platform == 'linux'" }, + { name = "nvidia-nvjitlink", marker = "sys_platform == 'linux'" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/c3/b30c9e935fc01e3da443ec0116ed1b2a009bb867f5324d3f2d7e533e776b/nvidia_cusolver-12.0.4.66-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:02c2457eaa9e39de20f880f4bd8820e6a1cfb9f9a34f820eb12a155aa5bc92d2", size = 223467760 }, + { url = "https://files.pythonhosted.org/packages/5f/67/cba3777620cdacb99102da4042883709c41c709f4b6323c10781a9c3aa34/nvidia_cusolver-12.0.4.66-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:0a759da5dea5c0ea10fd307de75cdeb59e7ea4fcb8add0924859b944babf1112", size = 200941980 }, +] + +[[package]] +name = "nvidia-cusparse" +version = "12.6.3.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-nvjitlink", marker = "sys_platform == 'linux'" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/f8/94/5c26f33738ae35276672f12615a64bd008ed5be6d1ebcb23579285d960a9/nvidia_cusparse-12.6.3.3-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:80bcc4662f23f1054ee334a15c72b8940402975e0eab63178fc7e670aa59472c", size = 162155568 }, + { url = "https://files.pythonhosted.org/packages/fa/18/623c77619c31d62efd55302939756966f3ecc8d724a14dab2b75f1508850/nvidia_cusparse-12.6.3.3-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2b3c89c88d01ee0e477cb7f82ef60a11a4bcd57b6b87c33f789350b59759360b", size = 145942937 }, +] + +[[package]] +name = "nvidia-cusparselt-cu13" +version = "0.8.1" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/46/e1/cdc1797eadf82d3a9a575a19b33fdc871a97edbec42c00b5b5e914f4aff4/nvidia_cusparselt_cu13-0.8.1-py3-none-manylinux2014_aarch64.whl", hash = "sha256:4dca476c50bf4780d46cd0bfbd82e2bc10a08e4fef7950917ce8d7578d22a23f", size = 221051344 }, + { url = "https://files.pythonhosted.org/packages/34/7d/2661f2fb3ac4302f3a246f5fc030213ac60c1fe0bce84f9783dbd831dbb7/nvidia_cusparselt_cu13-0.8.1-py3-none-manylinux2014_x86_64.whl", hash = "sha256:786ce87568c303fadb5afcc7102d454cd3040d75f6f8626f5db460d1871f4dd0", size = 170148586 }, +] + +[[package]] +name = "nvidia-nccl-cu13" +version = "2.30.7" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/21/a73174c6157101bdf1ffc22b517f76ff0082613989dd9bc8f43e8034caac/nvidia_nccl_cu13-2.30.7-py3-none-manylinux_2_18_aarch64.whl", hash = "sha256:ca786ffa5a647c75d4d1f5cc72a6c4f537947e2ba8823d7c8aaf768e7a7b9f77", size = 215983881 }, + { url = "https://files.pythonhosted.org/packages/3f/34/c500f90c7ae641b8e0f98965b36b8a7ac79cc8b296e8d251fe3eb592ee54/nvidia_nccl_cu13-2.30.7-py3-none-manylinux_2_18_x86_64.whl", hash = "sha256:cefa7fdb9710efd0f39c5f1be1d61ff6fc9a996c451265bd7fbdcf9455ed4b50", size = 215965170 }, +] + +[[package]] +name = "nvidia-nvjitlink" +version = "13.4.92" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1d/6b/eef7a9e32872b8f41e145bf10cddc9af26e153c338852811fe9a9baddf9e/nvidia_nvjitlink-13.4.92-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:e0391f24ed94ec879b84e3da4d4ec320c879aff681f2c7a638462f7199284323", size = 42452378 }, + { url = "https://files.pythonhosted.org/packages/1f/a8/1cbd4014898af8b419e69b0d7dbc63da2121ee92d92b47d59f4fe9075349/nvidia_nvjitlink-13.4.92-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:25f74fad0d654271c921ac4dca614bd6258bc21791242fc7b2289dad7ae9c099", size = 40420120 }, +] + +[[package]] +name = "nvidia-nvshmem-cu13" +version = "3.4.5" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dc/0f/05cc9c720236dcd2db9c1ab97fff629e96821be2e63103569da0c9b72f19/nvidia_nvshmem_cu13-3.4.5-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6dc2a197f38e5d0376ad52cd1a2a3617d3cdc150fd5966f4aee9bcebb1d68fe9", size = 60215947 }, + { url = "https://files.pythonhosted.org/packages/3c/35/a9bf80a609e74e3b000fef598933235c908fcefcef9026042b8e6dfde2a9/nvidia_nvshmem_cu13-3.4.5-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:290f0a2ee94c9f3687a02502f3b9299a9f9fe826e6d0287ee18482e78d495b80", size = 60412546 }, +] + +[[package]] +name = "nvidia-nvtx" +version = "13.0.85" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/f3/d86c845465a2723ad7e1e5c36dcd75ddb82898b3f53be47ebd429fb2fa5d/nvidia_nvtx-13.0.85-py3-none-manylinux1_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4936d1d6780fbe68db454f5e72a42ff64d1fd6397df9f363ae786930fd5c1cd4", size = 148047 }, + { url = "https://files.pythonhosted.org/packages/a8/64/3708a90d1ebe202ffdeb7185f878a3c84d15c2b2c31858da2ce0583e2def/nvidia_nvtx-13.0.85-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cb7780edb6b14107373c835bf8b72e7a178bac7367e23da7acb108f973f157a6", size = 148878 }, +] + +[[package]] +name = "omegaconf" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "antlr4-python3-runtime", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "pyyaml", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/09/48/6388f1bb9da707110532cb70ec4d2822858ddfb44f1cdf1233c20a80ea4b/omegaconf-2.3.0.tar.gz", hash = "sha256:d5d4b6d29955cc50ad50c46dc269bcd92c6e00f5f90d23ab5fee7bfca4ba4cc7", size = 3298120 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e3/94/1843518e420fa3ed6919835845df698c7e27e183cb997394e4a670973a65/omegaconf-2.3.0-py3-none-any.whl", hash = "sha256:7b4df175cdb08ba400f45cae3bdcae7ba8365db4d165fc65fd04b050ab63b46b", size = 79500 }, +] + +[[package]] +name = "onnx" +version = "1.23.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ml-dtypes", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "numpy", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "protobuf", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "typing-extensions", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/29/3a/b73bfb8f506a7b5be43eebf71dbdd351c4116f8100e1103a91bfcbcd3502/onnx-1.23.0.tar.gz", hash = "sha256:3f4b7323ea3397c63aa6ff5d43abe16013ffefc0a96cb8a0be6527ff1e1add40", size = 6022619 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/63/f9/54d8047c2f409cfc314a0df520c6e5703afe78b1395e8c44287724c4b37e/onnx-1.23.0-cp312-abi3-macosx_13_0_universal2.whl", hash = "sha256:6cc93aa89502acbf453a16d79b465d47d9c454f3a3cb3fc5507a8cb59ab53208", size = 9725329 }, + { url = "https://files.pythonhosted.org/packages/c6/ff/a7d3fe41debd5b8947182a632b4e001bd3a7a103212e58ef6ca0c3daab91/onnx-1.23.0-cp312-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:95b77466b4f6bc7c2c63d6004b8db7d963fb17bf24aff13007752e9d2ebf9390", size = 8640232 }, + { url = "https://files.pythonhosted.org/packages/09/86/63cdd5740f566fd9de2cadc106529adee0566f2fcafe69a65767eedc1681/onnx-1.23.0-cp312-abi3-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f336004196a22fbdc16c62e7f26f20635af1826db6147c80ff3b4b8d428fc7ef", size = 8881347 }, +] + +[[package]] +name = "opentelemetry-api" +version = "1.44.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ee/8b/aa9e2d8b8dfa7c946f7dec5d1f8f6ba8eca062f43509a06bdb5ce93d26c0/opentelemetry_api-1.44.0.tar.gz", hash = "sha256:67647e5e9566edcf421166fdf022b3537f818635daa852b289e34604dc6fb33a", size = 72406 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ca/6f/a04e900f465ff3221ccc395522503e2d10e79fa21f2723c8e177aae1e0d1/opentelemetry_api-1.44.0-py3-none-any.whl", hash = "sha256:94b98c893a91b88657eaac1e3ba89618cdb85be6918196705354f34728b2cdef", size = 60018 }, +] + +[[package]] +name = "opentelemetry-exporter-otlp-proto-common" +version = "1.44.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-proto", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/61/09/4d717852c1cf3f854b76c7110a5d00883bc3c99288b9b0dbcbeb9e306eb6/opentelemetry_exporter_otlp_proto_common-1.44.0.tar.gz", hash = "sha256:dc87a5a5bc58f149a56d1547e4691588fa12994cdc3bc039a694ccb3375862ac", size = 20202 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5e/71/65fd9d54c10b860f87c045ccee1264cab7011268895d3528818a29c1172a/opentelemetry_exporter_otlp_proto_common-1.44.0-py3-none-any.whl", hash = "sha256:9a9fe61bba73d802904bc989f1d6b4a7b1ee40f06c40e98d6f85af65aaebb694", size = 17045 }, +] + +[[package]] +name = "opentelemetry-exporter-otlp-proto-http" +version = "1.44.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "googleapis-common-protos", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "opentelemetry-api", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "opentelemetry-exporter-otlp-proto-common", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "opentelemetry-proto", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "opentelemetry-sdk", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "requests", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "typing-extensions", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1a/87/95e2a5aaa795b4e2260d74e16df2d5541deb2ea9de010bcd615f4dee2654/opentelemetry_exporter_otlp_proto_http-1.44.0.tar.gz", hash = "sha256:c633d7270ad6b57cd4cfbe8b0007a9e2e7c0cb50bd6c50fe2a7b245f721a09d8", size = 25806 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cd/d0/fdeb1a98d8d3a6205f5f297c51b4a9bfe65126ab60339669bbe3dd54c2e2/opentelemetry_exporter_otlp_proto_http-1.44.0-py3-none-any.whl", hash = "sha256:838592fce774c1c8bb7b9a0a7facbfa82e17be5a8a4e94cef10cb84ae026bae3", size = 21850 }, +] + +[[package]] +name = "opentelemetry-proto" +version = "1.44.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "protobuf", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/64/01/40ac4ae9a149263cc52c2cee200ddd80cb6d8db1a4610abf8eabce0fe771/opentelemetry_proto-1.44.0.tar.gz", hash = "sha256:c547a79c2f8c0c515d31509154682e5921c7cfd5ca67b70e1f9266e2c3e103f3", size = 46488 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/7c/8be563d68e93bbefa5c8affb82ddcff91b3ad858ce49957ba7b16fd3e0ab/opentelemetry_proto-1.44.0-py3-none-any.whl", hash = "sha256:898b155a0e1557afd867478fb6158e8122a46329ca0bb8dc53cc55e98f017f56", size = 72483 }, +] + +[[package]] +name = "opentelemetry-sdk" +version = "1.44.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "opentelemetry-semantic-conventions", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "typing-extensions", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5d/77/a6592cbc7c8d9bcc9d6757a9df45e04a7c585e3e6e7a13456da522b21109/opentelemetry_sdk-1.44.0.tar.gz", hash = "sha256:cebe7f65dc12f26ead75c6064de12fd2a9052e5060c0272d402cfa203aae123b", size = 208624 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/23/ff077e61886ee020a17ce9c8b6fa11c601c8d8345b09ea24f605445df62a/opentelemetry_sdk-1.44.0-py3-none-any.whl", hash = "sha256:df081c4c6bcfdb1211e3e86140376792643128a25f8d72d1d27675936e7e96ad", size = 137221 }, +] + +[[package]] +name = "opentelemetry-semantic-conventions" +version = "0.65b0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "typing-extensions", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8f/73/0cbdebcb4cf545fdd328da14f5137e37d0770c3f26185e478b0d15d94f50/opentelemetry_semantic_conventions-0.65b0.tar.gz", hash = "sha256:f9b2b81e9d5b64f11bc952075e7e9c7fb0aab075c7fd1c46d597f1b919852d60", size = 148774 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a6/0e/49df70d9b81fb5cbae4bbf2a49d865b09bcbcbc4eb53f5851b1027738d78/opentelemetry_semantic_conventions-0.65b0-py3-none-any.whl", hash = "sha256:1cacde7b0ad306f84c5ef08c3dbe1bbaf20165bba6f8bff43b670e555a086bcb", size = 204645 }, +] + +[[package]] +name = "overrides" +version = "7.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/36/86/b585f53236dec60aba864e050778b25045f857e17f6e5ea0ae95fe80edd2/overrides-7.7.0.tar.gz", hash = "sha256:55158fa3d93b98cc75299b1e67078ad9003ca27945c76162c1c0766d6f91820a", size = 22812 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/ab/fc8290c6a4c722e5514d80f62b2dc4c4df1a68a41d1364e625c35990fcf3/overrides-7.7.0-py3-none-any.whl", hash = "sha256:c7ed9d062f78b8e4c1a7b70bd8796b35ead4d9f510227ef9c5dc7626c60d7e49", size = 17832 }, +] + +[[package]] +name = "packaging" +version = "24.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d0/63/68dbb6eb2de9cb10ee4c9c14a0148804425e13c4fb20d61cce69f53106da/packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f", size = 163950 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451 }, +] + +[[package]] +name = "pandas" +version = "3.0.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "python-dateutil", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e2/17/d7b106e05bfa642e8694451e7d3d759c6a241c5386a5d962e4f66c047e06/pandas-3.0.6.tar.gz", hash = "sha256:66b07ef7315a31bfe1089cd3d71a7de781c9dca986762d0b4fe7c0ef17465d10", size = 4667686 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/77/4c/597d588c055d4373cff19cbbc32d4dd046c7be8fadee957585b5ba9e5b24/pandas-3.0.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7dac2d65e9087e8e7b5a45fe15c4920911a221df061ab629943ce016489145c7", size = 10397411 }, + { url = "https://files.pythonhosted.org/packages/18/8f/48907c7c707b61a8e5018c32e1a3f70623209bfb59020a2f6196159d3aa7/pandas-3.0.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9dab635a549e58a053c7b0fa054dc0bd7be22f0ed9a720f4a85d5fb993276172", size = 10051279 }, + { url = "https://files.pythonhosted.org/packages/67/fa/613d867c3d9554a61bafdec6f79565c8a3e73235feb52cc4a72ad2e0fa6a/pandas-3.0.6-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e3dccb584123b399c07562ac4d62543e90ede49ddf8ce3c13ffc64cbe828c281", size = 10303429 }, + { url = "https://files.pythonhosted.org/packages/cb/67/0c0f18e38d7f2d2af8c24b3315bc4046e73bbdd4a5540405506671ad0c0d/pandas-3.0.6-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0704044b676496b8350e023b09f174a26772456c974a2b11c36bebb558c9490d", size = 10788193 }, + { url = "https://files.pythonhosted.org/packages/39/53/1b57f3162501fe36687e4870e1b918a6458ca7386b173af75663ace95857/pandas-3.0.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e7c1905ef02c3d6d43d9dbd5b6ccb4da4870a0b0c821bbc103fbdb6f3ad2707b", size = 11323501 }, + { url = "https://files.pythonhosted.org/packages/f2/d2/b1182e8d39100369d7f13f4a125a3fb6b096fef112c46d0566c25781ff68/pandas-3.0.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:569e114072b24fc4970c12e2b4bab252671668a40b324318903380cab0254c0c", size = 11838173 }, +] + +[[package]] +name = "pillow" +version = "12.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1c/3d/bb7fca845737cf9d7dbde16ed1843984665ff2e0a518f5db43e77ec540b9/pillow-12.3.0.tar.gz", hash = "sha256:3b8182a766685eaa002637e28b4ec8d6b18819a0c71f579bf0dbaa5830297cce", size = 47025035 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/37/bf/fb3ebff8ddcb76aac5a01389251bbbb9519922a9b520d8247c1ca864a25d/pillow-12.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ba09209fbe443b4acccebe845d8a138b89a8f4fbaeedd44953490b5315d5e965", size = 5345969 }, + { url = "https://files.pythonhosted.org/packages/d8/66/9a386a92561f402389a4fc70c18838bf6d35eb5eb5c6850b4b2dc64f5048/pillow-12.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ffd0c5368496f41b0944be820fcb7a838aa6e623d250b01acf2643939c3f99d7", size = 4780323 }, + { url = "https://files.pythonhosted.org/packages/25/27/ac8f99618ffd3dde21db0f4d4b1d2ab00c0880595bfd17df103f7f39fd0c/pillow-12.3.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d9c7f76c0673154f044e9d78c8655fb4213f6ca31a836df48b40fe5d187717b9", size = 6266838 }, + { url = "https://files.pythonhosted.org/packages/84/21/a35af28dcc61f37ed850a2d64c65c701321dfbf25085e469d5559360cbbf/pillow-12.3.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:78cb2c6865a35ab8ff8b75fd122f6033b92a62c82801110e48ddd6c936a45d91", size = 6940830 }, + { url = "https://files.pythonhosted.org/packages/eb/51/8b08617af3ad95e33ce6d7dd2c99ed6c8298f7fb131636303956be022e25/pillow-12.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e491916b378fba47242221bb9ead245211b70d504f495d105d17b14a24b4907c", size = 6344383 }, + { url = "https://files.pythonhosted.org/packages/1d/72/cf78ac9780bb93c28328f408973845a309d4d145041665f734572ced1b52/pillow-12.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0dd2064cbc55aaec028ef5fbb60fa47bb6c3e7918e07ff17935284b227a9d2df", size = 7052934 }, +] + +[[package]] +name = "platformdirs" +version = "4.11.12" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ea/dd/65804b0c2925a1c821a05502ea57517b69a073ff400d25ab9faa3a2cf012/platformdirs-4.11.12.tar.gz", hash = "sha256:e8dc1cb58f1153fd7f61db1374317770baababec2480b37b8f01c6cc25b45267", size = 40016 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/2b/69de6317fbf48865bb12aa56c9cfcfff46b3add0cd45250725d96146dce0/platformdirs-4.11.12-py3-none-any.whl", hash = "sha256:b1ba966ac76153d9985a0787ffab951e7c18416189873d4e4ddb2c8a04a0a254", size = 24976 }, +] + +[[package]] +name = "pooch" +version = "1.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "platformdirs", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "requests", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/83/43/85ef45e8b36c6a48546af7b266592dc32d7f67837a6514d111bced6d7d75/pooch-1.9.0.tar.gz", hash = "sha256:de46729579b9857ffd3e741987a2f6d5e0e03219892c167c6578c0091fb511ed", size = 61788 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/2d/d4bf65e47cea8ff2c794a600c4fd1273a7902f268757c531e0ee9f18aa58/pooch-1.9.0-py3-none-any.whl", hash = "sha256:f265597baa9f760d25ceb29d0beb8186c243d6607b0f60b83ecf14078dbc703b", size = 67175 }, +] + +[[package]] +name = "portalocker" +version = "4.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/81/cd/d2a23fc80c26f539ac77e7d61bd5e4af5d0b3a09a78ac4c716eead345129/portalocker-4.4.0.tar.gz", hash = "sha256:90c0df939d4ffba121f8e925bbf98ecea8b9381718666ab871a226938d2b63b2", size = 304906 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/52/6c/07fa2775e87b6e241c2aca5cef79f65e450bc115de55f03cda6251f81690/portalocker-4.4.0-py3-none-any.whl", hash = "sha256:a8e99ea29bfb61766ee0b4008cbaf0651e8e050f7a2485ebf54316480e99226e", size = 129647 }, +] + +[[package]] +name = "propcache" +version = "0.5.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b3/9a/9fbf4e4ec0c2d7f1c32519fff782ef467859b8faa9fbc5331a96f6395d43/propcache-0.5.4.tar.gz", hash = "sha256:ff6b113f50bc066a698db5d944d2c6dc7507168dd3341e255a8892fd0715a558", size = 61545 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/71/cd/348d58f142aebc4873345c6b31087629182ca6e0f2b3caeaa528cf882eba/propcache-0.5.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:b28f41fa3b8c6900457f858ec5b03998f3a6d535fbc1bb2edec5961ea05ec429", size = 87285 }, + { url = "https://files.pythonhosted.org/packages/df/f4/f3ffaee281b276da854ac1d7a6a506d26cbc62ea2e623756f1d0a4a1ba1a/propcache-0.5.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:dcbf346a318a5e30063f547630b02bb787ce2f45b6368d5da143660b6a3835d8", size = 50984 }, + { url = "https://files.pythonhosted.org/packages/25/88/1d7df7201750b37765ef2b23bc1c526c028dadde80afa0f57a118fc01182/propcache-0.5.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:87a3caecf8095e48dc72f84bfa42e23a848cf410cc9cc13031fba4869b706a21", size = 52460 }, + { url = "https://files.pythonhosted.org/packages/83/4f/48865bd02a16ee5236bc46166b2946f37b93e07b0eae355dac0be0b216ca/propcache-0.5.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:60a64cbccaa11b7760ce705a14ada17ba459e7ca9f23ba587eb013821032d7ef", size = 251768 }, + { url = "https://files.pythonhosted.org/packages/b0/19/3742a5eed62317b03b4002ee865dc9fd720308bdd0da1f29a5786c630311/propcache-0.5.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a74bfa37147cc08fb29df10bd9c16f40fa7f860cd3a6d2fff853323a94f6e17f", size = 257723 }, + { url = "https://files.pythonhosted.org/packages/cb/d5/ee6350fb0be9122bb6c67082a876d34b90d980d100c106af4b81023e04f4/propcache-0.5.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a4d7a54719b67338a305dca2ce6aafe366817df94ddfd4b5514374356f5ca546", size = 265597 }, + { url = "https://files.pythonhosted.org/packages/85/9f/83a07b6ec0e043c050cfdd35fb0cf1b7897b91d554d6eea293740309afe7/propcache-0.5.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2814ecd8e818f487bee4b0f921bc4d1c176cc5fc71ac0f072d0fa67eda4ac14b", size = 250424 }, + { url = "https://files.pythonhosted.org/packages/33/2c/a763a8251f50fba042af0fb1f02bfec4b31381e40aff760db2be7b2e1f84/propcache-0.5.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6af4693716bfb03f1752ef1b30faa593db2c01d5272e9b8564a1549452a979ab", size = 216748 }, + { url = "https://files.pythonhosted.org/packages/6a/e2/4d11bea8fd6a777149c6c20645f873952eab5de3a2497aa11648ec9ab6ab/propcache-0.5.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4fbc1a15dc8cd1689508758d626b372b1f09d28d9577667feaf9e6bfcd8efcbc", size = 246533 }, + { url = "https://files.pythonhosted.org/packages/9f/36/6683597de4907e70c717e3588c541202c66086a72ff3db58be49de66e72c/propcache-0.5.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:cdee8205a44d0be91bbac4c41b95d86641b72dfc7aef1279400e4fda3f26a937", size = 238173 }, + { url = "https://files.pythonhosted.org/packages/85/84/cb08d79f1762daafeb2b030c470cd0c725c97b8ad67412457c6f35c53e9d/propcache-0.5.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:9a2a8a50a93dee0268a860a07fa3b4bd968f8ce4dbd794957da772f395368526", size = 251128 }, + { url = "https://files.pythonhosted.org/packages/c2/0d/41b848036db6621370c1f2e5471a7da8149c730f8552a5257567721f4576/propcache-0.5.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:7ffafcbfc7b549ab940047e505c831eabac5e67de53e1bc174adbc5285c55944", size = 214821 }, + { url = "https://files.pythonhosted.org/packages/f1/b7/adfae4bf9c63bccf12e2d9690a175c6579047a6eec3b5a6a5f51428c15e2/propcache-0.5.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:d1f5a500bfcbb2c0ab85e98a0dcd70f5899d34efe365a0187700369a79603031", size = 254793 }, + { url = "https://files.pythonhosted.org/packages/51/6f/eeca9647245d5f92e87d53e5f14335bb42fce1a7e6842c8045b364eded8b/propcache-0.5.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8a235f73d6e020855dc29dff012d920c02ee0feab8d73a24185a7569f4be1161", size = 247134 }, + { url = "https://files.pythonhosted.org/packages/f5/cd/785c64ed382f3f04201870267b02783f63b4678c2acfddc177a3ebcc2727/propcache-0.5.4-py3-none-any.whl", hash = "sha256:62c60aec739ed00124573cce1178138fd690c7676352d67a37328c1cf51d7468", size = 16338 }, +] + +[[package]] +name = "protobuf" +version = "7.36.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d9/89/5b8517baa72f84a67b8a307ba953c91057af618bf40bf676f3c03551f8f0/protobuf-7.36.2.tar.gz", hash = "sha256:497d0463ff3316681da6c0b9e8d06cb465d61abce00b613ab42226175644d1bb", size = 512737 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/32/72/98342feb672507c8f3a69e34b4fa8961f608edba5c1a48a6f47156d92cb5/protobuf-7.36.2-cp310-abi3-macosx_10_9_universal2.whl", hash = "sha256:cbc70b17ee27e28894c7fee8bb04be1abead49e936bc70eb60052531eee2079e", size = 456039 }, + { url = "https://files.pythonhosted.org/packages/b6/ea/91fdf7c2b8bbd49cde056f00a9df6773532987e1c00fe2830b895af95c7e/protobuf-7.36.2-cp310-abi3-manylinux2014_aarch64.whl", hash = "sha256:e11e1f0180583a2af89db6a2ecd9e8dc40aa6d2988ca175bfd0e6d12ea72d74e", size = 344219 }, + { url = "https://files.pythonhosted.org/packages/17/ab/5fd5f8ece73fad885c5a09aa849b32d70472f954ba3a92d3bb5974ea953b/protobuf-7.36.2-cp310-abi3-manylinux2014_s390x.whl", hash = "sha256:f4fee11ec330d238b34a05c9b675f693c20415d1c5bd7d5320cc2f8a798eb9cf", size = 357223 }, + { url = "https://files.pythonhosted.org/packages/db/f3/3996583dd2906297a637af12114deddf7658af6e683fedb83be061983fb5/protobuf-7.36.2-cp310-abi3-manylinux2014_x86_64.whl", hash = "sha256:89f23aa53c24553a2416fd4fd1ec06f74fa42b14b546d8883128813f775bbfd2", size = 343223 }, + { url = "https://files.pythonhosted.org/packages/e4/04/d52c7016b04b6c5108f26691f9d33ec82a9b65d041f1a9c771137693d618/protobuf-7.36.2-py3-none-any.whl", hash = "sha256:bdb3a345d48db958e6ce1f18e508beb0cc981d64f24088427549c866cd039f1e", size = 179806 }, +] + +[[package]] +name = "pyarrow" +version = "25.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3d/e3/27f57f80141379d60defe6703eb50a707325706f07fedfd1312c7a751995/pyarrow-25.0.1.tar.gz", hash = "sha256:9150a83248bfed9813ea3c3af74c3856c1984d444aa28e58bf7733b9750ddf6a", size = 1201653 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a6/e2/9ab15b88cbfac28e16419ce5439ec29234c5172cb8259301b4ba639bdec0/pyarrow-25.0.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:df961f2e7ae9cf496459259d798652c70625f6c080650d6952f8c04053c58ee9", size = 35861559 }, + { url = "https://files.pythonhosted.org/packages/58/79/a0036dbe1eabe1f73127427342f1d99982584c4a2cde2651d6c93499c6f6/pyarrow-25.0.1-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:cc4aa407fde9fc660be3939e49ea31f50f3e9fec17c0ec63159f7711edd3efc9", size = 37628383 }, + { url = "https://files.pythonhosted.org/packages/13/49/d93a57d375f4bf0cf82913dd6bb54acafde83dd993be2282c81ac5616cad/pyarrow-25.0.1-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:4340f0ba6c1d2e13f21658de1d7c662ca2545018568d0030a1e9afca159d87e3", size = 46820190 }, + { url = "https://files.pythonhosted.org/packages/60/c9/711ca85d79f1ec98f29a5eae2b051e25b4ecec5de3e3c0e2d5c5dcb15664/pyarrow-25.0.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:5389cdf79447ed1515c9e31620e6e1e2302249564d603f2ad727d4f6d313e4c3", size = 50102437 }, + { url = "https://files.pythonhosted.org/packages/80/53/8fb8359ff17cfb6263a1cf3ebf7caec9fe197de118719e84fcb1d0618026/pyarrow-25.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d51592cb7561e87877c506113e7adbf1342ab579e6c21f0ef44b8ba41cb74c80", size = 49942424 }, + { url = "https://files.pythonhosted.org/packages/e8/83/4e5ae02a9341571b18a6fca380ac7a58ce6ddae7ab3c060208c0a1e79f02/pyarrow-25.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6109c94d8b9f3b17a041daca16cacb2f651ad8f1ef70a4232c2c0f37a23da2a8", size = 53144206 }, +] + +[[package]] +name = "pycparser" +version = "3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/7d/92392ff7815c21062bea51aa7b87d45576f649f16458d78b7cf94b9ab2e6/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", size = 103492 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172 }, +] + +[[package]] +name = "pydantic" +version = "2.13.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-types", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "pydantic-core", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "typing-extensions", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "typing-inspection", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/53/ef/fc4f868f4e2cee79f863883abffceff107875f569b848507319842d2a681/pydantic-2.13.5.tar.gz", hash = "sha256:51a9c5f7b2f8e636f04c6cada605d9b6a3bf1348fdf945a3d8869b19bba0ee08", size = 845750 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/eb/47/c95ffc2009878c7aac0c5e08528022dcb885933252a88b5f170058014464/pydantic-2.13.5-py3-none-any.whl", hash = "sha256:346a034f080da3755d8e9cb5e00e8b07de1d39e4f6e2c87d8ab7cafa0b269a73", size = 472589 }, +] + +[[package]] +name = "pydantic-core" +version = "2.46.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/af/f9/8a06bea35ef8daf588f707784c973a7046e0034c8d8cfb08828eeffb8b75/pydantic_core-2.46.5.tar.gz", hash = "sha256:10416c15b8839ecc4ef4d0885da76da6fd0f67333a0eb8aff6d93c4b8f2910fc", size = 472262 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/82/3f/76358795aa7a8c6d4f36e2cb828ad1c90ee118e1393a9281664f5aade9d4/pydantic_core-2.46.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:b9fe6fb92520e3fd61f2e49000b6911b188824f089b75973ea06d6267f0b476d", size = 2076516 }, + { url = "https://files.pythonhosted.org/packages/db/50/26b091836076ce4cb2fac264186936acc069e0595772cfd02a563bc4761a/pydantic_core-2.46.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a39ac25a9a2fa4072efdb429833c4a4c8009a51ff9eea3eeae131713cd27991e", size = 1922874 }, + { url = "https://files.pythonhosted.org/packages/09/f0/2a8ce3849e299d44e2d2c196b6082643a3235565a735cb51db7a6261f614/pydantic_core-2.46.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4fdc8b93a41521988916eeaa271173fcca7fa0803d62f87675aac8dcec1c8e29", size = 1951772 }, + { url = "https://files.pythonhosted.org/packages/87/46/ac0dc8bdd9e6048183a14eb127764e7ad9240021c17513074a4711b0e31e/pydantic_core-2.46.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b98134087d9de723658d17a42c7d0da8d6e2ef08015dee7dc93889047315f5e4", size = 2031832 }, + { url = "https://files.pythonhosted.org/packages/c4/c2/339de5bef7be36301a2231eaa52e62163742c2281f11b5f4892bc79785cd/pydantic_core-2.46.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e652ab17569c94bff5475520f907b7148b8c24036a8ebbe5cf7cf7493d28579a", size = 2208645 }, + { url = "https://files.pythonhosted.org/packages/7b/a0/9ff22b797724262da14427abaed4dd1d864a139693fc5e7809114376a716/pydantic_core-2.46.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d925f3d9afd05a8c0fb3a1031463a8d59ebe5e2afad297e29c78be19e13b4e62", size = 2265935 }, + { url = "https://files.pythonhosted.org/packages/c0/a4/eb9409ec0736e50aa70a412f16c204ed149516846912f7e6724d4c73ee53/pydantic_core-2.46.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0fc5be0abd4a407e200d844b404e33639a554e7bd0d448e7b9ae181be4789ac2", size = 2066284 }, + { url = "https://files.pythonhosted.org/packages/c0/02/7f6156ffc926857f1c37c07d9a388682865a81830ab6a1b637082c25e399/pydantic_core-2.46.5-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:816ff0a6550ffc06c098ccd2e0698600f9aa7da192a79eaa6f9af504a35db869", size = 2105889 }, + { url = "https://files.pythonhosted.org/packages/92/b1/e781d357ebe09fc929f995700f1b3503e8897f1cece183ecb1300d4d67e9/pydantic_core-2.46.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c7ea57fc63aa7da93a1bd2d644e6577befae10c52c4e36377635eea1056a74f5", size = 2158006 }, + { url = "https://files.pythonhosted.org/packages/70/0a/644597d84ab400e50609c192120b85c9681c22d3a20461b9060a79be0a7a/pydantic_core-2.46.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:efd62a42486f1bda5d24cb4f63d15a3c7768375fe83d36f9417b4ad7a2fb20b3", size = 2158408 }, + { url = "https://files.pythonhosted.org/packages/1e/ee/ca3b7b3a4b3769ffe9ce9432a7c9be755de9593a46d3b0d54d0409323e44/pydantic_core-2.46.5-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:2bc9419666990c06d7397831f2126a1ecc3594aaa3ff7de5bf2d066802f4e07b", size = 2309609 }, + { url = "https://files.pythonhosted.org/packages/ce/52/39fa1f451486019524ca685020390e7ca351832fd874530ba30c8628e6dc/pydantic_core-2.46.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:18a09e1e1011b462f2e32774f25859ef1223d5c2b0546a633cf56654710721e0", size = 2342618 }, +] + +[[package]] +name = "pygments" +version = "2.21.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/49/2e/ced460408999b33da6b31b0021b0f37d329e202d4169aeb164493778f25b/pygments-2.21.0.tar.gz", hash = "sha256:610ca751c9bc2492b38eb9a38a7fbc93edbbb2d7182edaf34e66ae493dee5c8c", size = 5005329 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/71/46/17f022dd3e953bf20a04a028a21ec746d942f8d2af30fa0f124fa0e6a684/pygments-2.21.0-py3-none-any.whl", hash = "sha256:2363c69b61c4a97c838da3b130dcd6468f4848992b21a82f2a63ec34377137d9", size = 1250147 }, +] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892 }, +] + +[[package]] +name = "pytorch-lightning" +version = "2.6.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "fsspec", extra = ["http"], marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "lightning-utilities", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "packaging", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "pyyaml", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "torch", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "torchmetrics", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "tqdm", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "typing-extensions", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/97/e4/5025cb42311b4f8cc6d1b12cd81399ca433159f8e3831ab229c5a6c5177e/pytorch_lightning-2.6.6.tar.gz", hash = "sha256:52c52b20522dd3d3feb3bb3aae2dbe3f291e8e836d4111a74cfd72e9df018834", size = 662507 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4f/cf/ba8e4cc1b982ff6dc6f9fdef0e4074605438b82640f6270888a10e7e42c3/pytorch_lightning-2.6.6-py3-none-any.whl", hash = "sha256:71f95c7b22f25c4f91cc659e1734bcdc6c69c8b66543f2ed2d78dbe29ce2f167", size = 853045 }, +] + +[[package]] +name = "pyyaml" +version = "6.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063 }, + { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973 }, + { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116 }, + { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011 }, + { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870 }, + { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089 }, + { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181 }, +] + +[[package]] +name = "regex" +version = "2026.9.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b9/5c/f403115361de25809e8f785686ec7096e30fef73be9ae35aa51da4e80abb/regex-2026.9.10.tar.gz", hash = "sha256:1e321e2c84f0e52c457f5ea5944f796d6e8e09cb99738ea98dcc1bfe402a128d", size = 417072 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/32/c8/bfbe893e90ee0148bd2860dd086f09b5d2080ca2b125f740c2e118c16982/regex-2026.9.10-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:880ac684c27176464c00c3fdc456116364f5ebc70da07aad0c2d4a7ba45e98db", size = 496609 }, + { url = "https://files.pythonhosted.org/packages/8c/ac/56d5ae6efb759255c3b3db650a4be25f96a844ea3613b91e1e189a3b7294/regex-2026.9.10-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b9d36b03dc362aa40ffaaec9d9bd75e87763529563ec008c43b0e07782f5be7a", size = 297024 }, + { url = "https://files.pythonhosted.org/packages/60/4b/0f2d5f6bbb791cc10f22f0ed16c487e630dde8fa8fa0bd92a2bfe21a4b20/regex-2026.9.10-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:866de9f98df0611d7b62b3a8729d3284a64c0cc6edd90bb95a533e443a4939cb", size = 291905 }, + { url = "https://files.pythonhosted.org/packages/89/51/3fb5fe0d32f4cf0bc982286722c729a8d6f522d2fa2d5d14a702d9fc87f8/regex-2026.9.10-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4c66d54042a14a503907d81861b8a5235e6d1f03d4fbc1d8767f652eaf957ac1", size = 800055 }, + { url = "https://files.pythonhosted.org/packages/89/46/ee507bd2f9d4420f26a594b35c551d7194b66f5d7897f63730fae6ec05c1/regex-2026.9.10-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:032da15431c890d376f53547f0a6219f4f4cd19f3e4f11bdc321453b5bd207e4", size = 871133 }, + { url = "https://files.pythonhosted.org/packages/d1/75/cbaa90689684f91b1bc017e7f8c6d9425c6bd299108db02482dc51376d8a/regex-2026.9.10-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:23ac9a28180f274d7dd7651fa131ad5b02d343b75df4b040737f0356223895dd", size = 919627 }, + { url = "https://files.pythonhosted.org/packages/cb/f5/dcdf5e0d898024005cfcce631e3e934d111dfbe177ca0b7f253ae8a735a2/regex-2026.9.10-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2e67f8843f0e4b931f1fa860bf3bbe4134b714c0155cc5c7c0d7ea450230aae0", size = 804587 }, + { url = "https://files.pythonhosted.org/packages/73/70/eedfe81c29bae266a06ab4250978361a9bccd474704d88d4f4ef4506dff8/regex-2026.9.10-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bb7774924f8cd69f49cba0b3c2d679a6326f777e0e67d130ad5203e4df53f0d3", size = 777320 }, + { url = "https://files.pythonhosted.org/packages/ca/e5/86b207077efbcd91305700488f170b7eb1e1c54721cea74175273aa3b9a4/regex-2026.9.10-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0c32480f3371b75068decaf9e5da72c224e953830dd71e36e06cf80e30ea39d8", size = 790572 }, + { url = "https://files.pythonhosted.org/packages/b2/92/f622c3b2323f4c035b98e80221740a442127ad7993135b814f52057430db/regex-2026.9.10-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:d2d377fd1cad611b806cdd732d86b65f536c768209890cb442556548daa65a23", size = 865485 }, + { url = "https://files.pythonhosted.org/packages/4a/be/34bd621d3d6ac906ad67e57ed56c40cd45f7d51b9c0328335e97a7cb8ecb/regex-2026.9.10-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:c014641157e9049b0603b8daa5343bd408d9b757b709aaa0f373cd3fab2d7944", size = 767925 }, + { url = "https://files.pythonhosted.org/packages/0d/28/ddbf7cba86f2adf5038c6c16aa829636ffc6e437f81bb0cbf302899cea5e/regex-2026.9.10-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:1562aabd9d4eb09bd88a62ad97ed06800094b529ac43419e43020b9cefec79b0", size = 858800 }, + { url = "https://files.pythonhosted.org/packages/d0/f1/e8d7656ff3d3bd32e881d32f540b4981c79dee61908d6b790a45966e6895/regex-2026.9.10-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2479171edccced52ef02b899558f88ab2c235fe05b93180fdcae1670aacd89e1", size = 791648 }, +] + +[[package]] +name = "requests" +version = "2.34.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "charset-normalizer", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "idna", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "urllib3", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ac/c3/e2a2b89f2d3e2179abd6d00ebd70bff6273f37fb3e0cc209f48b39d00cbf/requests-2.34.2.tar.gz", hash = "sha256:f288924cae4e29463698d6d60bc6a4da69c89185ad1e0bcc4104f584e960b9ed", size = 142856 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/f4/c67b0b3f1b9245e8d266f0f112c500d50e5b4e83cb6f3b71b6528104182a/requests-2.34.2-py3-none-any.whl", hash = "sha256:2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0", size = 73075 }, +] + +[[package]] +name = "rich" +version = "15.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "pygments", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c0/8f/0722ca900cc807c13a6a0c696dacf35430f72e0ec571c4275d2371fca3e9/rich-15.0.0.tar.gz", hash = "sha256:edd07a4824c6b40189fb7ac9bc4c52536e9780fbbfbddf6f1e2502c31b068c36", size = 230680 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/82/3b/64d4899d73f91ba49a8c18a8ff3f0ea8f1c1d75481760df8c68ef5235bf5/rich-15.0.0-py3-none-any.whl", hash = "sha256:33bd4ef74232fb73fe9279a257718407f169c09b78a87ad3d296f548e27de0bb", size = 310654 }, +] + +[[package]] +name = "sacrebleu" +version = "2.6.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "lxml", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "numpy", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "portalocker", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "regex", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "tabulate", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d3/ed/d7acddcff74d690c56fe26a1f7828bdde548262828d0743414ea916c40c1/sacrebleu-2.6.0.tar.gz", hash = "sha256:91499b6cd46138d95154fff1e863c2f9be57e82f0c719d8dd718d0006cf6c566", size = 1893419 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/06/f2/6c90ccf3ad1d09a7d662a405b274f3c93b92df59c8d6a025d26aaf34d302/sacrebleu-2.6.0-py3-none-any.whl", hash = "sha256:3edc1531575cfe4ad04ce53491a9307e234af1c3f805a1f491cbec844229a8a8", size = 100785 }, +] + +[[package]] +name = "safetensors" +version = "0.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/45/06/f955dbbb1859e3bd23c8ac6141af5106e7ad5fedec4a3a6e3d60f94b7001/safetensors-0.8.0.tar.gz", hash = "sha256:fabaf3e0f18a6618d9b36560682562157f77c2b71fcffc7b432be2baed9d753d", size = 325846 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/39/a0/f718cda65b05407d228f97602cf60dca269c979867aa5beb25410de26cd3/safetensors-0.8.0-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:c554f85858e05226d3c2828e32395e677434685d6d94594a41643361c5e837f0", size = 473568 }, + { url = "https://files.pythonhosted.org/packages/f5/b1/fa7c600e7dceae12e9606c7578cbc9ff1e1ed55844883ee5c92205e86226/safetensors-0.8.0-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:c80201d22cbf405b80647a60ada77bba06c8fba2da2743ba1e89cdcc39a81f25", size = 484562 }, + { url = "https://files.pythonhosted.org/packages/09/7d/65a7de0af421317bb36a067241e4235fff194eed60b961ed6d3f59a3fc60/safetensors-0.8.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7a46e5ff292c356d6991e60942ba7f79817682d3a2cef0702136448cb9c4d235", size = 502844 }, + { url = "https://files.pythonhosted.org/packages/91/4f/3175c9d75634e0e0dda0082794193521035edd7c70a6f212bf33ca06ddf4/safetensors-0.8.0-cp310-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4124502b78f03534117c848f87a39b8f31e577b15eff423bf8bfb95f2a8c30d0", size = 511823 }, + { url = "https://files.pythonhosted.org/packages/20/87/846c289e7aa2299eff406335717cf43ce8777194ece8aad75772e0411615/safetensors-0.8.0-cp310-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7bc0a787ba8a35be368ee3574edfa2b1ad389eebd0a72e482ae275490e3f6c98", size = 633461 }, + { url = "https://files.pythonhosted.org/packages/76/22/8d64d9df2c45d5ded401df889d0ad90882804ca172d79ec4f0df8f727fe0/safetensors-0.8.0-cp310-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:040070828e36dc8e122178bbbd5830ff9e97920affb84cbe0f46442497bed358", size = 545148 }, + { url = "https://files.pythonhosted.org/packages/28/50/f203ff3a3ddfe19308efc83c5a3a29ed02bf786732ec35e68bf9162f3365/safetensors-0.8.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd6f3f93c9a0a7cc2788ee63fb763353d4bd2e89b0751bc78fcf7dda00bea774", size = 516040 }, + { url = "https://files.pythonhosted.org/packages/46/fb/cdaed17ceb2948784fd9c36b6fd3e951b608547cea81a48e8ee6f8cfdfcb/safetensors-0.8.0-cp310-abi3-manylinux_2_31_riscv64.whl", hash = "sha256:fcdd41ec4628fee5799f807c73c353629130fbd942aa23d83c623dd6c9d52d78", size = 513832 }, + { url = "https://files.pythonhosted.org/packages/0d/49/1e15de264dcc3b77943d2d0c56a95809956883b1c2d6d585c792523f180b/safetensors-0.8.0-cp310-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8e9f537aa183a38ace122d27303dcd986b26bd2a7591f9181d7f0c396f4677ca", size = 559930 }, + { url = "https://files.pythonhosted.org/packages/2a/43/bf38443278eab4b1be1fce2931e2b012ad9cb7df52ada751d0aab8f7659a/safetensors-0.8.0-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:87eec7ffed2b809f05a398a8becb7d013f19f7837cd15d9748580d6cf30dbaf4", size = 678670 }, + { url = "https://files.pythonhosted.org/packages/72/e3/68cd3fa5b48488e84add63e04cb12f3bc28ae4638c06d4508c6e88823d0e/safetensors-0.8.0-cp310-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:4a95ae2b05d7726d751da4ebf626a2ca782b706e101bd894c95bc2450b1cffcc", size = 786679 }, + { url = "https://files.pythonhosted.org/packages/29/4b/1c19c509d56e01f4fbb3d0a2e597450f6cc04d1d56cf52defb0a62dfd715/safetensors-0.8.0-cp310-abi3-musllinux_1_2_i686.whl", hash = "sha256:3ae091f16662658bdc019a4ff6cb4c085bb7d725eb5978b183ffd265863b6d2d", size = 765683 }, + { url = "https://files.pythonhosted.org/packages/27/43/41c1621732edd934d868a00d1b891584c892a7b62a9aab82ea5a0a5623ee/safetensors-0.8.0-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:8e080062fcde23be189565e1c3305d16751a218ecf9412c8601e64204eb6f846", size = 722361 }, +] + +[[package]] +name = "scikit-learn" +version = "1.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "joblib", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "narwhals", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "numpy", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "scipy", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "threadpoolctl", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d2/eb/eaf5e07fcc0da7149b0e084f24e54edd7441b9a89ce7e034032ae97fe3a0/scikit_learn-1.9.1.tar.gz", hash = "sha256:629cada3e33e2b9bf376cdc7614a47a4140b8aedc1d836579e359736fbd82977", size = 7786908 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/a7/25f0a43d2fde306e8ef45f45121192f687b79beaf4bae8c21607c46c5e63/scikit_learn-1.9.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0c0f8b5d09b44101cea2767f300680bada1ea27f976fe4b48b83950a4f55a49a", size = 8775209 }, + { url = "https://files.pythonhosted.org/packages/60/ea/57e57539ce175d774fc291ed091b0a6d756854b92cd92554c6bb4d0ae498/scikit_learn-1.9.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:8c14ce41d561f7749f990b41d6703fe02c4669fbc485e598e069e0a1967b488e", size = 8295541 }, + { url = "https://files.pythonhosted.org/packages/78/2b/5721a174406bfba49bce20ae997b3b64cf355c3f623a2638284ab6a82156/scikit_learn-1.9.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e4c20a6c017d820faa7ac8c783e3d0c6a9a2e297bf9f55332ca17cdf7fd4d04d", size = 8871592 }, + { url = "https://files.pythonhosted.org/packages/8e/57/a50162f3d29feb979ab6347c6debda506dfb525bcff3c50dd17606651c7e/scikit_learn-1.9.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e5d7b18a5b9dca241a74695f3275fa4c895a9dadc72b3d8df5fa9d1083c9b83e", size = 9166043 }, +] + +[[package]] +name = "scipy" +version = "1.18.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7e/74/66de6258867beb2ef08f35f9f2ac017a52cacd5081714d239ff1a442d458/scipy-1.18.1.tar.gz", hash = "sha256:52c4b7422442aba924d03ad4019852b08a92e64ea187b933135687bfe2747307", size = 30781235 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/f7/240c110c08693826b4513a52f5717d62ec7c7af72f2920821247c03b17b3/scipy-1.18.1-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:457fd7a2a8edeb044ab6ffbc0aa03ff6cd18491356e5e0c834d76ce621b916d1", size = 31111061 }, + { url = "https://files.pythonhosted.org/packages/05/4a/78c6285577c375e7cf27277ea8ee6961224327f1e1a0c44af5f17f23635c/scipy-1.18.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:e708533e8b2ae2497d65346538a7dcc92814410b25b81432eac66de0f2af8265", size = 28733332 }, + { url = "https://files.pythonhosted.org/packages/a5/f6/a5b82f8abbe14d134691b8b903696f701d25a081353a29dc655c364d9e62/scipy-1.18.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:7bbf207c4453ce1ad2e00b17313852b33310b83090c2311bdaf97f93c0380d12", size = 20475078 }, + { url = "https://files.pythonhosted.org/packages/23/22/0858a0bbd6b3e825ceb8cd9baf9eaf3b2f2b1d77727eb6be40500bcdc92f/scipy-1.18.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:78c0665edead396b1abb4897c41a5c1d9bf090c8a637a4c20a61678e0a264e66", size = 23108904 }, + { url = "https://files.pythonhosted.org/packages/75/9a/2e71719f31eaefe0e3a1706c4a1ded94e664bfd95ffca2b219a671faee01/scipy-1.18.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3c085faa2cfa879c5141df483f836f4d691045a078224a670fa570fa01612d89", size = 34025113 }, + { url = "https://files.pythonhosted.org/packages/df/64/ff35eb9e54894cf471ff4716abd3c81eb0a0626869217ce3e6ba4ccf17d7/scipy-1.18.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f55fa87b6c612ecd6b058f167c53231b1d14e412efe361d3d6e38b3631c73218", size = 35344199 }, + { url = "https://files.pythonhosted.org/packages/d3/af/c5538be1792f7034c12c7db6ee67cace58253c7b87b122d68253eaf5de89/scipy-1.18.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c35d74ce0e193ff740c2f2be2ac913ddc232fe6c1ff40b26cfecb9c670c63314", size = 35639587 }, + { url = "https://files.pythonhosted.org/packages/91/4c/075e4f66471bac101141ac739e9e135549be1bae584571bd03a530c056e1/scipy-1.18.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d2924a03db38dc2e848bca2fe9f077dafb891480b91a00a0963a8cf86dfc31c1", size = 37480330 }, +] + +[[package]] +name = "sentencepiece" +version = "0.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cc/33/ea3cb3839607eb175da835244a798f797f478c5ddf0e8ecdf57ea85a4c70/sentencepiece-0.2.2.tar.gz", hash = "sha256:3d2b5e824b5622038dc7b490897efe05ebbbb9e7350fc142f3ecc8789ef9bdf6", size = 8218435 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b8/13/7a562289c8d5b49ebdf3f9c1e8ab67cf14a8743b1d90c8f406bfdec36b72/sentencepiece-0.2.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1edb10e520e4bddf74d85b0f5ae74cc2d60c2b448885080bfb618bc2b3a49f6b", size = 2188384 }, + { url = "https://files.pythonhosted.org/packages/85/d1/912f14fd5eae168aba726ffb6a9a2dc1c71fe7676c53da6f5c442b886d4a/sentencepiece-0.2.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f7c06c751c19d923435a54bff4f7e66e728fad160e8da28254f133abc9725820", size = 1441553 }, + { url = "https://files.pythonhosted.org/packages/bd/44/caa9cab5f261a019e2808bc5046152775dc57352ba9cbae7525e9e7a1ed4/sentencepiece-0.2.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:38111ed1f79268f399c505028023d5eaaf0ab4e5eafceb709468b0d3323e7838", size = 1347176 }, + { url = "https://files.pythonhosted.org/packages/19/90/cd798935668cff71d309d8ff10385844ecf216b1fe454f1993ed8bf2cb91/sentencepiece-0.2.2-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cbce24284f51f71d10a42b7b9c964dcb9048b28f1c8e5db40bcbcb6f428cba6a", size = 1325200 }, + { url = "https://files.pythonhosted.org/packages/b6/2d/37e3da037318a70066ded0d51bc2a7f35491ae6338dd993d5eb1503fc3b5/sentencepiece-0.2.2-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c8a168b040bc61681293f79a949b5d911c8e25086f4260285b8d97ab5f1195da", size = 1397736 }, +] + +[[package]] +name = "setuptools" +version = "84.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6d/44/f5da03a8ef95d369145c5bb53050e7877c9f3d312e128605fd9504829143/setuptools-84.0.0.tar.gz", hash = "sha256:f4695c21257f0d9b537ec2692c941d02ee143b7cc1276941349a546573b2ef73", size = 1168449 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/95/9c/c510029fc6ef33a6275cd2c5d3cecd6613dfd6aa401d57c54f1c18852ccf/setuptools-84.0.0-py3-none-any.whl", hash = "sha256:51a52592b3b99e102b609654876bd65f19f999935166d1352678931132b0c670", size = 818216 }, +] + +[[package]] +name = "shellingham" +version = "1.5.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755 }, +] + +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050 }, +] + +[[package]] +name = "smart-open" +version = "8.0.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "wrapt", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/76/53/9c513747547fd595d5c143259129ea8b9c3ea2f6b7bb9dcea2b1966ded3c/smart_open-8.0.1.tar.gz", hash = "sha256:18b1c4496003c6902be17c15f032b5c319f307c89c6ae9e6b028b508bed8b2cf", size = 61882 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c3/96/325b8c507ccecc50421fecc0345a502ee6e4a44785af3c4e6ecbadad624a/smart_open-8.0.1-py3-none-any.whl", hash = "sha256:3e97f90e92a952cb57863dfe132082c400a52eeeb27c067692fb51dbcc5b0089", size = 73504 }, +] + +[[package]] +name = "sortedcontainers" +version = "2.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e8/c4/ba2f8066cceb6f23394729afe52f3bf7adec04bf9ed2c820b39e19299111/sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88", size = 30594 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/32/46/9cb0e58b2deb7f82b84065f37f3bffeb12413f947f9388e4cac22c4621ce/sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0", size = 29575 }, +] + +[[package]] +name = "soundfile" +version = "0.14.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "numpy", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "typing-extensions", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d2/db/949331952a6fb1c5b12e9de80fd08747966c2039d1a61db4764fbd3981c2/soundfile-0.14.0.tar.gz", hash = "sha256:ba1c1a2d618bca5c406647c83b89f07cc8810fa506a50622a6993ba130c1de11", size = 47842 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b1/d1/5e338af9ca6ed0786cd5bb03f6d60de1c325728c1189014f3b59aae7403c/soundfile-0.14.0-py2.py3-none-any.whl", hash = "sha256:8ba81ae3a89fd5ab3bef8a8eb481fbbe794e806309675a89b4df48b8d31908a8", size = 26799 }, + { url = "https://files.pythonhosted.org/packages/7e/72/c6b21e58d3113596e7e8de0a08d6f1d95173492cfbca0a4db14148cbba2a/soundfile-0.14.0-py2.py3-none-macosx_10_9_x86_64.whl", hash = "sha256:19be05428da76ed61a4cad29b8e4bcf43a3e5c100089d2ec81dc961eed1b0dd4", size = 1144568 }, + { url = "https://files.pythonhosted.org/packages/63/7a/dfdd6f8c748988427119f75eb860a3cedd858d1aea1fe28f39ad8559ef22/soundfile-0.14.0-py2.py3-none-macosx_11_0_arm64.whl", hash = "sha256:d828d35a059626da52f1415b5faee610aeab393319cb3fc4a9aef47b619fc14c", size = 1103726 }, + { url = "https://files.pythonhosted.org/packages/4a/f8/fc39fad6f879633461d27394cd1ddaf1f769ffa0597dca35872f51b16461/soundfile-0.14.0-py2.py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:e85724a90bc99a6e8062c0b4ddf725f53b2a3b70afd4da875e9d2cfc4e92f377", size = 1238050 }, + { url = "https://files.pythonhosted.org/packages/7b/a2/70fd4432b924684c372df8b0a45708c36c057ef3596c9eb53e0a806b980b/soundfile-0.14.0-py2.py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:1e38bac1853412871318e82a1ba69a8be677619b56025bbfcccdb41b6cafe82d", size = 1315963 }, +] + +[[package]] +name = "soxr" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ed/11/27cebce4a108f77afea7c80545115536b45e3f11ebfb914f638fdd9ba847/soxr-1.1.0.tar.gz", hash = "sha256:9f228ae21c78fa9359ca98d8a5e8e91f30639e438e574133dace62c5b5309e44", size = 173067 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/06/8a/f3da7973b5f1b05d2d7e94d5376b881dcbc05297900cae6c3d33d95b209b/soxr-1.1.0-cp312-abi3-macosx_10_14_x86_64.whl", hash = "sha256:e0e09fa633ce2e67df08b298afced4d184f6e753fc330f241022250f1d0d61da", size = 204124 }, + { url = "https://files.pythonhosted.org/packages/03/dc/200013a74641f8774664bbcd2346c695c05c2e300ea792adcb40a293eed0/soxr-1.1.0-cp312-abi3-macosx_11_0_arm64.whl", hash = "sha256:d6a7ad82b8d5f3fcc04b1d2ca055562b96af571e1d4fa7c6c61d0fb509ac43b4", size = 165457 }, + { url = "https://files.pythonhosted.org/packages/88/2b/2e5eba817a762a2ec589ff165b8bc5955b25a0ad140045f7cd8e45410543/soxr-1.1.0-cp312-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bf98c0d7b7d5ef5bf072fee8d3020e8b664f2d195933ea7bc5089267c2e22a06", size = 206529 }, + { url = "https://files.pythonhosted.org/packages/5c/f1/0e55195893228609c9a08c3b13b7a83a46c3a992cd00d3304f0f320cfb07/soxr-1.1.0-cp312-abi3-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3b033078e86f3c4a658e5697fac8995764fad9e799563616b630136b613167f1", size = 240413 }, +] + +[[package]] +name = "strenum" +version = "0.4.15" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/85/ad/430fb60d90e1d112a62ff57bdd1f286ec73a2a0331272febfddd21f330e1/StrEnum-0.4.15.tar.gz", hash = "sha256:878fb5ab705442070e4dd1929bb5e2249511c0bcf2b0eeacf3bcd80875c82eff", size = 23384 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/69/297302c5f5f59c862faa31e6cb9a4cd74721cd1e052b38e464c5b402df8b/StrEnum-0.4.15-py3-none-any.whl", hash = "sha256:a30cda4af7cc6b5bf52c8055bc4bf4b2b6b14a93b574626da33df53cf7740659", size = 8851 }, +] + +[[package]] +name = "sympy" +version = "1.14.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mpmath", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/83/d3/803453b36afefb7c2bb238361cd4ae6125a569b4db67cd9e79846ba2d68c/sympy-1.14.0.tar.gz", hash = "sha256:d3d3fe8df1e5a0b42f0e7bdf50541697dbe7d23746e894990c030e2b05e72517", size = 7793921 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl", hash = "sha256:e091cc3e99d2141a0ba2847328f5479b05d94a6635cb96148ccb3f34671bd8f5", size = 6299353 }, +] + +[[package]] +name = "tabulate" +version = "0.10.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/46/58/8c37dea7bbf769b20d58e7ace7e5edfe65b849442b00ffcdd56be88697c6/tabulate-0.10.0.tar.gz", hash = "sha256:e2cfde8f79420f6deeffdeda9aaec3b6bc5abce947655d17ac662b126e48a60d", size = 91754 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/99/55/db07de81b5c630da5cbf5c7df646580ca26dfaefa593667fc6f2fe016d2e/tabulate-0.10.0-py3-none-any.whl", hash = "sha256:f0b0622e567335c8fabaaa659f1b33bcb6ddfe2e496071b743aa113f8774f2d3", size = 39814 }, +] + +[[package]] +name = "tenacity" +version = "9.1.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/47/c6/ee486fd809e357697ee8a44d3d69222b344920433d3b6666ccd9b374630c/tenacity-9.1.4.tar.gz", hash = "sha256:adb31d4c263f2bd041081ab33b498309a57c77f9acf2db65aadf0898179cf93a", size = 49413 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d7/c1/eb8f9debc45d3b7918a32ab756658a0904732f75e555402972246b0b8e71/tenacity-9.1.4-py3-none-any.whl", hash = "sha256:6095a360c919085f28c6527de529e76a06ad89b23659fa881ae0649b867a9d55", size = 28926 }, +] + +[[package]] +name = "tensorboard" +version = "2.21.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "absl-py", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "grpcio", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "markdown", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "numpy", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "packaging", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "pillow", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "protobuf", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "setuptools", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "tensorboard-data-server", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "werkzeug", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/37/4b/cd2eec9642781a8f5b2fb9994e3933a7b259ab18e9d49aeede9b5acf6311/tensorboard-2.21.0-py3-none-any.whl", hash = "sha256:7279316dcb6bd5bc391d623dea841531299cde1887310e8133bc34a996d32255", size = 5516204 }, +] + +[[package]] +name = "tensorboard-data-server" +version = "0.7.2" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7a/13/e503968fefabd4c6b2650af21e110aa8466fe21432cd7c43a84577a89438/tensorboard_data_server-0.7.2-py3-none-any.whl", hash = "sha256:7e0610d205889588983836ec05dc098e80f97b7e7bbff7e994ebb78f578d0ddb", size = 2356 }, + { url = "https://files.pythonhosted.org/packages/b7/85/dabeaf902892922777492e1d253bb7e1264cadce3cea932f7ff599e53fea/tensorboard_data_server-0.7.2-py3-none-macosx_10_9_x86_64.whl", hash = "sha256:9fe5d24221b29625dbc7328b0436ca7fc1c23de4acf4d272f1180856e32f9f60", size = 4823598 }, + { url = "https://files.pythonhosted.org/packages/73/c6/825dab04195756cf8ff2e12698f22513b3db2f64925bdd41671bfb33aaa5/tensorboard_data_server-0.7.2-py3-none-manylinux_2_31_x86_64.whl", hash = "sha256:ef687163c24185ae9754ed5650eb5bc4d84ff257aabdc33f0cc6f74d8ba54530", size = 6590363 }, +] + +[[package]] +name = "text-unidecode" +version = "1.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ab/e2/e9a00f0ccb71718418230718b3d900e71a5d16e701a3dae079a21e9cd8f8/text-unidecode-1.3.tar.gz", hash = "sha256:bad6603bb14d279193107714b288be206cac565dfa49aa5b105294dd5c4aab93", size = 76885 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a6/a5/c0b6468d3824fe3fde30dbb5e1f687b291608f9473681bbf7dabbf5a87d7/text_unidecode-1.3-py2.py3-none-any.whl", hash = "sha256:1311f10e8b895935241623731c2ba64f4c455287888b18189350b67134a822e8", size = 78154 }, +] + +[[package]] +name = "text2num" +version = "3.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b7/25/3006bbc89154cf5240f6ddaf43c63bab8895dea61724f545a7b9d3cc8340/text2num-3.1.0.tar.gz", hash = "sha256:e025ab59f2273b3fef8b1fbd082f76559161e4b45cf43a6281bd5eb315a115a5", size = 16921 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f0/4e/fd3b402f9fb5649b974e114712743f10fe857f6f48e8de3caa77a20fef30/text2num-3.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9bda066015ade2be641f12cb5590d4a1e916602ce9cc5bc97c1dec66cf6c7d21", size = 335910 }, + { url = "https://files.pythonhosted.org/packages/42/a7/beda68bea2bba433c9d5a08a8cb688138f3cb9ba9023568f57554a50e2d6/text2num-3.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c7ac159b466bad33440b4db4fe74e61b9fe7bd511ffb2892ea59e5fadd5d280", size = 379576 }, + { url = "https://files.pythonhosted.org/packages/24/37/dfb81b7bb465337bed8f4eb9ae02cb16ca8403f095f38eb23ced7dabc34b/text2num-3.1.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:46393a0536ca08c91787a6190b3338da2f38242ed34988aa8739763617350105", size = 387461 }, + { url = "https://files.pythonhosted.org/packages/df/3f/394ee41df16fb02693472a8a762cd96baa17347e05809f2c56c0e48dc0c2/text2num-3.1.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:091bebc82d3888f449a20a93d58fec7b60db7392c3f0aa48d18e7bbd414d6282", size = 487711 }, + { url = "https://files.pythonhosted.org/packages/c3/8a/19e78982515491291155d41f99c80823fdeb6058389f89826750e0f72402/text2num-3.1.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3778a32dc3ae4cf711f4af04e2f749678c0ae90717efdd789e4441a7ba0b4031", size = 404255 }, + { url = "https://files.pythonhosted.org/packages/b6/01/1bee1ed251bf15453c9e513d06c6afcae21b770b7d0fae09edee3296dd95/text2num-3.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e170e06fc538122fa34df5aaed632d4c82aaaaf919d69e445f741fc105c8dfdb", size = 378095 }, + { url = "https://files.pythonhosted.org/packages/29/76/3a61648828fe662b3caeb5eb05afe59a5fe088041407aaec7d30f5912c0a/text2num-3.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:94bf51fe2e0bdf7aac05688ee98ee9cb735865a760540919ad949f35a263ae79", size = 404680 }, + { url = "https://files.pythonhosted.org/packages/9c/30/03c01491622e9d7393e6c4d99e5011ae75650492641c34f49029fae6e29d/text2num-3.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4c94e12ec56f4ad11348da546c7dbfc60b4ff0949ac20281557abc5520639c2c", size = 556857 }, + { url = "https://files.pythonhosted.org/packages/9f/42/3617221e001524f21f3cb58dcb053ccc6c6f28feb507136fb8b656339c90/text2num-3.1.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:c4e4ec1e560c1b9e93f6212c0ac3d94bd4386af539cafe794d8a1eafe1f635df", size = 663776 }, + { url = "https://files.pythonhosted.org/packages/b1/f7/1fed0d0765606a6774c9a62df9f4c7c83977c86d9e46fbf3021dc63dbf1b/text2num-3.1.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:015fc39b7fe5d78dac38730353493617bc08f26c555a184321dad3c07aba6357", size = 620127 }, + { url = "https://files.pythonhosted.org/packages/aa/85/c40b7b12b312ce5d7163c5f63557100293c47463b3a662eb1d8d8b16d2fe/text2num-3.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c86dc685d6e324ac115b13c0291c6596f28d56226c2678ae4114b2a98fb67633", size = 583263 }, +] + +[[package]] +name = "threadpoolctl" +version = "3.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/00/dc/6c58154c1c65f758ea979e7139cb76993a9cfc662d14e9be3c4a667cfb77/threadpoolctl-3.7.0.tar.gz", hash = "sha256:61348cfb77d53b9242e0017029244b559b810c142ced65b4e21eeca1843959a7", size = 31961 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/3f/f88a53f60a472b46f4023f56d204dd7de33d34c5d2acbfa0d70a674e639e/threadpoolctl-3.7.0-py3-none-any.whl", hash = "sha256:cd8b60b5641b45c67bbf73c64c843235fc2d8a480c87389f52f5dbee893b86be", size = 26362 }, +] + +[[package]] +name = "tokenizers" +version = "0.23.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "huggingface-hub", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/18/1e/bc6587c5ab643b2e17776cace9070a2ae73549c86bffac9934a600bf3c31/tokenizers-0.23.2.tar.gz", hash = "sha256:7f0f085686b9de0d0079e6f874ae053600db64c5d13049e0bbc0119926d25aac", size = 385745 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4d/ed/8a443528baa6fac8dfe8c3b75b038c63ac92bb539bcabe311e227c718173/tokenizers-0.23.2-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:85a9a357a3764aecc904ee76bdaf8cf1ad8e5a67a1b929a487c4a39b49ed0e90", size = 3148852 }, + { url = "https://files.pythonhosted.org/packages/67/49/22da045a91732384d3a3771816bf188dc5a1f702c32e635afa7c679c0bef/tokenizers-0.23.2-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:986670e43691469dcee610ea0f846f91a8f84e91fc6f7a48d4c064414c0ec2bf", size = 3101593 }, + { url = "https://files.pythonhosted.org/packages/2e/4d/8f569ed49372a3ed8e57099bd515055fd48d7c95912c4307cda6973c2168/tokenizers-0.23.2-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a37039b5dfc4af84eb3ef0a92f4307e28936c8f9adccba2629d36f652e9bf7a2", size = 3516830 }, + { url = "https://files.pythonhosted.org/packages/2a/de/e2f14c8919d5bf51874051d00d6c7b7e0e8bde6c6a2dbeddda7f642896ff/tokenizers-0.23.2-cp310-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7b7e37ba198f24150f523e1242e83c4970de4a525480586be5dcc24d9add32c5", size = 3407975 }, + { url = "https://files.pythonhosted.org/packages/c5/bd/93c69152d02ef06ce47aed8b2bf4952dcf733c935a62791873932b2934d9/tokenizers-0.23.2-cp310-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:43e4f2071e3cc8d5d86421c874aebc82659bb51a68bcdef5a0da75ee89511ccb", size = 3748165 }, + { url = "https://files.pythonhosted.org/packages/2d/b7/56b84b80bc96942bba8eb23751a9e8a1fce4faaf4390425e7083f721c98c/tokenizers-0.23.2-cp310-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:325fee2e0418a9dc6c9ecf736a5f5f0db7875183ace9549ae339da76f7a1fbb7", size = 4024165 }, + { url = "https://files.pythonhosted.org/packages/9b/8a/0175e216f005c2fe08238292663aa41e4c802b216e71047a69a0e9fc6fa3/tokenizers-0.23.2-cp310-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:950d7c9426fa72406a0ffeacdbc0bb9985f5db20eb8b263f29c79aaf83105703", size = 3591899 }, + { url = "https://files.pythonhosted.org/packages/2c/ca/ca6b93c7820df123b2662a9469e8facc826ccc94e98fdd0d615f6431e73a/tokenizers-0.23.2-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:41c2f84d172449b4dadb9cdc508e3e364076613c35b16e76ecfe47a60d1e3305", size = 3386843 }, + { url = "https://files.pythonhosted.org/packages/e9/a4/4f9106d317b14a80aefea9f0e3a8d07ef25f856a7607eb7f5ab894281fcb/tokenizers-0.23.2-cp310-abi3-manylinux_2_31_riscv64.whl", hash = "sha256:12f0835dc2ee694746a76adf7b1567d4346a4a502ebe93fb1f5f80ea49799b78", size = 3577314 }, + { url = "https://files.pythonhosted.org/packages/8d/6a/1552b70fb0d9ab074fd3fc961435d01364e79c9058481822c3af6e8d402c/tokenizers-0.23.2-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:eb2f9c8a24da020ea8c11a01a19c1c2547912d92121ae4a01cfbca46125dee40", size = 9967367 }, + { url = "https://files.pythonhosted.org/packages/06/01/3ccb3a956c7528b2507b8a9714155c4baf86af593039db6ea375dd0c96c3/tokenizers-0.23.2-cp310-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:f486f402f6f9abee5bb032553736813af0c710a86b2e0ca592634c55cea1f835", size = 9811886 }, + { url = "https://files.pythonhosted.org/packages/fa/73/7038e612d48bda1599457f712f6bd3854eae1a9dc9c13aa47f835349db48/tokenizers-0.23.2-cp310-abi3-musllinux_1_2_i686.whl", hash = "sha256:bef235815a067b2648caf6dcc7a71091b0b0fff9ee8057f6451eb9335fae52ef", size = 10146224 }, + { url = "https://files.pythonhosted.org/packages/b5/d8/8e9e4e0b287a338d8f88976729628c9d22e8a54cfaf9777018a7f7cb58a0/tokenizers-0.23.2-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:5c56bda1511921587789163e524d196ed8284174ac23abd7685d5ea8da6c4718", size = 10256304 }, +] + +[[package]] +name = "toml" +version = "0.10.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/be/ba/1f744cdc819428fc6b5084ec34d9b30660f6f9daaf70eead706e3203ec3c/toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f", size = 22253 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", size = 16588 }, +] + +[[package]] +name = "toolz" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/11/d6/114b492226588d6ff54579d95847662fc69196bdeec318eb45393b24c192/toolz-1.1.0.tar.gz", hash = "sha256:27a5c770d068c110d9ed9323f24f1543e83b2f300a687b7891c1a6d56b697b5b", size = 52613 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/12/5911ae3eeec47800503a238d971e51722ccea5feb8569b735184d5fcdbc0/toolz-1.1.0-py3-none-any.whl", hash = "sha256:15ccc861ac51c53696de0a5d6d4607f99c210739caf987b5d2054f3efed429d8", size = 58093 }, +] + +[[package]] +name = "torch" +version = "2.14.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cuda-bindings", marker = "sys_platform == 'linux'" }, + { name = "cuda-toolkit", extra = ["cublas", "cudart", "cufft", "cufile", "cupti", "curand", "cusolver", "cusparse", "nvjitlink", "nvrtc", "nvtx"], marker = "sys_platform == 'linux'" }, + { name = "filelock", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "fsspec", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "jinja2", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "networkx", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "nvidia-cudnn-cu13", marker = "sys_platform == 'linux'" }, + { name = "nvidia-cusparselt-cu13", marker = "sys_platform == 'linux'" }, + { name = "nvidia-nccl-cu13", marker = "sys_platform == 'linux'" }, + { name = "nvidia-nvshmem-cu13", marker = "sys_platform == 'linux'" }, + { name = "setuptools", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "sympy", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "triton", marker = "sys_platform == 'linux'" }, + { name = "typing-extensions", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/17/76/bb4770f56cf6d8971671dbcbb7493e5a6a15ad2825f4e359b02c27c38297/torch-2.14.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:c1f844f1c750e87df4b68bc3afbc0e2b0c7ef19d7b8f666e48bdcf6a0c4f0056", size = 127303200 }, + { url = "https://files.pythonhosted.org/packages/fe/7b/ec44bacf2c8886b85ba4ca2285e8b09f2dff5d9c99e6a031326954082cb5/torch-2.14.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:ada340e62591d06a2bcc2d68170f20f45f0b0665d372dc510a8ed7eb3b1d609a", size = 454010251 }, + { url = "https://files.pythonhosted.org/packages/15/71/49399acd41f750a906c686bd23c08a2001ccb8dd25f2971003c2ed89c1dd/torch-2.14.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:fecffb58f51fd643d213acd68da21cc3fc19bea05a3bc64b4ee55128f47a4963", size = 554620488 }, +] + +[[package]] +name = "torchmetrics" +version = "1.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "lightning-utilities", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "numpy", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "packaging", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "torch", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/81/34/39b8b749333db56c0585d7a11fa62a283c087bb1dfc897d69fb8cedbefb1/torchmetrics-1.9.0.tar.gz", hash = "sha256:a488609948600df52d3db4fcdab02e62aab2a85ef34da67037dc3e65b8512faa", size = 581765 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c3/a2/c7f6ebf546f8f644edf0f999aa98ece106986a77a7b922316bf6414ff825/torchmetrics-1.9.0-py3-none-any.whl", hash = "sha256:bfdcbff3dd1d96b3374bb2496eb39f23c4b28b8a845b6a18c313688e0d2d9ca1", size = 983384 }, +] + +[[package]] +name = "tqdm" +version = "4.70.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0d/ea/b2a5bd54b28a324dae8211928b2d730b6547500342c7e6c6dea08bd0a485/tqdm-4.70.1.tar.gz", hash = "sha256:cefd0eca11b2a37a3aee776544d4f4ae913f02688135b5556b8788dfa474afc4", size = 171846 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/03/921a3d3c75785aca9ebfbfcabfbc3a1be12e2ab5265deb026d55a5a3f83e/tqdm-4.70.1-py3-none-any.whl", hash = "sha256:c293e525e6fef9c20e8728fd4612df02a0aa31bb5fe91ecd93e123b1b7bffa73", size = 80199 }, +] + +[[package]] +name = "transcribe-nemotron3-diar-env" +version = "0.1.0" +source = { virtual = "." } +dependencies = [ + { name = "gguf", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "huggingface-hub", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "meeteval", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "nemo-toolkit", extra = ["asr"], marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "numpy", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "soundfile", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "torch", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "transformers", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] + +[package.metadata] +requires-dist = [ + { name = "gguf", specifier = ">=0.10.0" }, + { name = "huggingface-hub", specifier = ">=0.20" }, + { name = "meeteval", specifier = ">=0.4.3" }, + { name = "nemo-toolkit", extras = ["asr"], git = "https://github.com/NVIDIA-NeMo/Speech.git?rev=cf724ac337d1ebc7d0dda1e23fb80916f52927a5" }, + { name = "numpy", specifier = ">=1.26" }, + { name = "soundfile", specifier = ">=0.12" }, + { name = "torch", specifier = ">=2.7", index = "https://pypi.org/simple" }, + { name = "transformers", git = "https://github.com/huggingface/transformers.git?rev=4b28d51d0d5f17ec20c23a187d0475a8e68810c8" }, +] + +[[package]] +name = "transformers" +version = "5.18.0.dev0" +source = { git = "https://github.com/huggingface/transformers.git?rev=4b28d51d0d5f17ec20c23a187d0475a8e68810c8#4b28d51d0d5f17ec20c23a187d0475a8e68810c8" } +dependencies = [ + { name = "huggingface-hub", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "numpy", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "packaging", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "pyyaml", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "regex", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "safetensors", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "tokenizers", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "tqdm", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "typer", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] + +[[package]] +name = "triton" +version = "3.8.0" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/87/07/0f8cd8e8db0472334253efdaaab3d0819fea27aa99bf0e7f1aeea4ceb5ae/triton-3.8.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a9c404c69ed4a39e8ec632eaf6b9fe058a060bf98979c177f6ef666f06bb8d50", size = 226474486 }, + { url = "https://files.pythonhosted.org/packages/c1/09/b7012e5bfae67640f268aa584caa80fe1674f6b0da949046b679972c33e3/triton-3.8.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e91ffa46d095b252248297292dd22bcbacd53a125a0c2eefbbbf74925a320bc3", size = 247972921 }, +] + +[[package]] +name = "typer" +version = "0.27.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-doc", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "rich", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "shellingham", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/16/f7/57713ba479fd405eb76de31404b2c744c289e336b2d999511ebf51e496f7/typer-0.27.2.tar.gz", hash = "sha256:269b7eb9d3c202ca84b4bc9618cb04ebb43d3d4d1e567e4c768607232c05f945", size = 204045 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dc/bf/205d0004930ede8f542fb58f601526fccf4ae7626075ca1e6c4de5d3d652/typer-0.27.2-py3-none-any.whl", hash = "sha256:b3a5fc4342d5fc8fda8fc3010b1cf117e9249aab7fae800c2eff62fd3842d97d", size = 123130 }, +] + +[[package]] +name = "typing-extensions" +version = "4.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/cc/6253133b5bb138fc3306cebfbda2c520f545d36b5be2c7255cc528bb45d6/typing_extensions-4.16.0.tar.gz", hash = "sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5", size = 113555 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/49/d3/b8441a820a491ddfc024b0b0cf0393375b75ea13866d9c66727e54c2fc80/typing_extensions-4.16.0-py3-none-any.whl", hash = "sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8", size = 45571 }, +] + +[[package]] +name = "typing-inspection" +version = "0.4.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a3/26/b09b8010994eccc3c09092e6b34058f36a460eea2d4c3e8b910c695975a0/typing_inspection-0.4.4.tar.gz", hash = "sha256:547274fa6b0a561ccf549cc9524b999a578e737d015d8709d021f9d0d13bea47", size = 76928 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/67/81/4add07e5172b7ac40d8ed5ff580409a7801a4fe26d529bdd915401dabfbe/typing_inspection-0.4.4-py3-none-any.whl", hash = "sha256:65b8397ba37ccbce054456aaccddfc91e6e3083c92824df348d96ca832f3f147", size = 14750 }, +] + +[[package]] +name = "urllib3" +version = "2.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e3/05/b17359e1cefb4f909b5e40b1b90a496d987258916dbbf88e842c729f510e/urllib3-2.8.0.tar.gz", hash = "sha256:63bf2ead4c879426ebf22ef2a781eeb4aa3b4ae798a0435506f8687fd5bb9b63", size = 458972 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/92/9d/c4e665119135114480843e7ab388fa94d8480650450e6f8e26b70d323a4c/urllib3-2.8.0-py3-none-any.whl", hash = "sha256:0cf3cae568d36aa9576b28dfb35f11328f1cb974ca7647d9475ebb86c75ac6e3", size = 135717 }, +] + +[[package]] +name = "wandb" +version = "0.30.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "opentelemetry-api", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "opentelemetry-exporter-otlp-proto-http", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "opentelemetry-sdk", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "packaging", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "platformdirs", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "protobuf", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "pydantic", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "pyyaml", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "requests", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "typing-extensions", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "xxhash", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/86/10/c4481d12b26f57606d1ef7c8d56825ad6af769cbb8cd29ee501ac6bbbc2d/wandb-0.30.0.tar.gz", hash = "sha256:f2d7de3c2019204f493f3f9e9166071c2be36f74140af2e1409b4de0fd84c454", size = 40862616 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/71/5a/83201590d1e84a3b7d0a0cc7b53ee1318def18ba5431a32b26a44e6694f6/wandb-0.30.0-py3-none-macosx_13_0_arm64.whl", hash = "sha256:f4f908ace761db42d239d100230df0b77eb9684924d962a762bba176d829a236", size = 23464329 }, + { url = "https://files.pythonhosted.org/packages/35/b6/c0acf63242a0b8a3df9f877249ca260be80b86ecb08396fc34982967a50e/wandb-0.30.0-py3-none-macosx_13_0_x86_64.whl", hash = "sha256:9cf2492acd5c66ba7217667386e2a90535ccaec08079b97c389c3da5d15cde48", size = 25250085 }, + { url = "https://files.pythonhosted.org/packages/91/69/0593b9e43cd3fc257a0f6ceaf5e078f2b28baf1968e00dfa1be98261ee68/wandb-0.30.0-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:72b564712e85e10d78d37ccdaef4a908d28cbb39240c9c00fa4dde567150c764", size = 24049519 }, + { url = "https://files.pythonhosted.org/packages/26/13/775a4c7d87fb771a2ddb104ce7c5c5d393e48720e448b4e8c43aac7cd4ce/wandb-0.30.0-py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:bad9412f137d02303bf178513e0109f4fed37a6c817604085a1a4151047d8790", size = 26105520 }, + { url = "https://files.pythonhosted.org/packages/58/36/4974e9ec44c23dd28a39c8ee699f1b8911dd6619c932c3fe94b016bc8a1f/wandb-0.30.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:1f2cc20c033f1650d387ca073177e629f1e2a1b5c4be393698e3db4752b9cf3a", size = 24112309 }, + { url = "https://files.pythonhosted.org/packages/05/77/5eddd97f33f581f4d167f41bbf4736f2a51ad7271b9c9ed5dceb6994cbd6/wandb-0.30.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:e7dc3b0b1567f6a121d8f7a58131b5d085861139a653ef8bed1569c9be796721", size = 26320243 }, +] + +[[package]] +name = "webdataset" +version = "1.0.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "braceexpand", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "numpy", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "pyyaml", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5a/3a/68800d92e065cf4750ebecf973b13979c0c929b439e1293012938862038d/webdataset-1.0.2.tar.gz", hash = "sha256:7f0498be827cfa46cc5430a58768a24e2c6a410676a61be1838f53d61afdaab4", size = 80090 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d9/00/aca6beb3658dab4ed3dbb41a78e6e7f31342e0b41d28088f205525751601/webdataset-1.0.2-py3-none-any.whl", hash = "sha256:3dbfced32b25c0d199c6b9787937b6f85742bc3c84f652c846893075c1c082d9", size = 74956 }, +] + +[[package]] +name = "werkzeug" +version = "3.1.8" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/dd/b2/381be8cfdee792dd117872481b6e378f85c957dd7c5bca38897b08f765fd/werkzeug-3.1.8.tar.gz", hash = "sha256:9bad61a4268dac112f1c5cd4630a56ede601b6ed420300677a869083d70a4c44", size = 875852 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/93/8c/2e650f2afeb7ee576912636c23ddb621c91ac6a98e66dc8d29c3c69446e1/werkzeug-3.1.8-py3-none-any.whl", hash = "sha256:63a77fb8892bf28ebc3178683445222aa500e48ebad5ec77b0ad80f8726b1f50", size = 226459 }, +] + +[[package]] +name = "whisper-normalizer" +version = "0.1.15" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "indic-numtowords", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "more-itertools", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "regex", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "text2num", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5d/b2/fc7d95153c350002154aeb87bd6732a9189a94e13586222c16cd28147769/whisper_normalizer-0.1.15.tar.gz", hash = "sha256:6afec010671cc00cb4948957b4455f5e1bd1aacc76d3edbda980a27b2415f523", size = 40995 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5f/06/5e2cbb3267a1c7aa8f425c679b9ff5f8c04a47aa15157d493a95a746c374/whisper_normalizer-0.1.15-py3-none-any.whl", hash = "sha256:2871b3c56557628dc30749bf3ffac7bc4ac3d3f497b0b24df27d0c4c434e113f", size = 39705 }, +] + +[[package]] +name = "wrapt" +version = "2.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/42/a6/6375d56c44d590ef24acf0f8f5bf7ed768ff7a510b959306ec412611e90f/wrapt-2.4.1.tar.gz", hash = "sha256:fd6390aab9e8aa40c52eff3c180f098e8d9f5894b1fd4c4fd2c207067b33ed16", size = 164597 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9f/1f/a2f3225c5ecf522684c1d051aea8ce8253f240e55be75826b787777afd6c/wrapt-2.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7e86fbc2ac8a363ea04abf631fad82720e16b17a25020f32dbe9b24a2ed2b0e3", size = 98890 }, + { url = "https://files.pythonhosted.org/packages/68/6c/eb45660fd4d92cce11ec923f55bb2e647a6c18d30e53734eb07a3c530e31/wrapt-2.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:24389748f0b9d5b67e478fad4fc8b3f1108422ef80716e48eead6cebcebbff08", size = 98742 }, + { url = "https://files.pythonhosted.org/packages/a3/4f/17a89a580cb0082e61b8375074d5c9e5d38e4aa83ee19b6174ee472d17c2/wrapt-2.4.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:30d11c289b013bf384ff1a1a6553f150d0b855901708a9bef667a5680f8247c9", size = 236301 }, + { url = "https://files.pythonhosted.org/packages/01/ca/4700eb008a34bf02de328806ddde15fc84c8d1e65d3dcafb92a935a50319/wrapt-2.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9356dbb59199a0e4709de35fa4a1ac1a88ef6da99711a397f5b009233faff326", size = 237805 }, + { url = "https://files.pythonhosted.org/packages/75/5d/26c1740299b29e190d5f4b4a99eb001401042a9d9ab338e3f8e1ce140ecb/wrapt-2.4.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8342f332dada211f64b74609e332d727b13315e9a83177f7918bf68c59f815f2", size = 217037 }, + { url = "https://files.pythonhosted.org/packages/3a/55/ec72991153a2ae8b40238bc44cec7c3ddf7706ef6e2d314b0c6f5c7febce/wrapt-2.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2f86e328c482bc5383b4eda5094be0bed3617fc3076aa9225ff1a9eb6372de9b", size = 234659 }, + { url = "https://files.pythonhosted.org/packages/51/32/7cfa1e070dcda76ea56a3e252341cba1cd1e9412baf23cd7adfebf1114e2/wrapt-2.4.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:4dc92697444ee380544fbb43c86612d8486529aadf917c22b5524141d4af074c", size = 214590 }, + { url = "https://files.pythonhosted.org/packages/79/10/248841cb30107f6f32c53a02662e1c3e0c7c06bea0b8ebfaaee94885dcee/wrapt-2.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:edd03758a7578526642508b8833d43496fdfba0f64e0025dfca153a7c1777735", size = 225103 }, + { url = "https://files.pythonhosted.org/packages/4f/a2/edcfc8d9a30375791b775715f8501364588f65b494ec4f6930568a19e765/wrapt-2.4.1-py3-none-any.whl", hash = "sha256:1e84ec5d89a0a07a0ef6bcd343f5c8ecdc95601d71de3058cdc63274e86c193c", size = 75317 }, +] + +[[package]] +name = "xxhash" +version = "3.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/00/5e/d6e5258d69df8b4ed8c83b6664f2b47d30d2dec551a29ad72a6c69eafd31/xxhash-3.5.0.tar.gz", hash = "sha256:84f2caddf951c9cbf8dc2e22a89d4ccf5d86391ac6418fe81e3c67d0cf60b45f", size = 84241 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/07/0e/1bfce2502c57d7e2e787600b31c83535af83746885aa1a5f153d8c8059d6/xxhash-3.5.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:14470ace8bd3b5d51318782cd94e6f94431974f16cb3b8dc15d52f3b69df8e00", size = 31969 }, + { url = "https://files.pythonhosted.org/packages/3f/d6/8ca450d6fe5b71ce521b4e5db69622383d039e2b253e9b2f24f93265b52c/xxhash-3.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:59aa1203de1cb96dbeab595ded0ad0c0056bb2245ae11fac11c0ceea861382b9", size = 30787 }, + { url = "https://files.pythonhosted.org/packages/5b/84/de7c89bc6ef63d750159086a6ada6416cc4349eab23f76ab870407178b93/xxhash-3.5.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:08424f6648526076e28fae6ea2806c0a7d504b9ef05ae61d196d571e5c879c84", size = 220959 }, + { url = "https://files.pythonhosted.org/packages/fe/86/51258d3e8a8545ff26468c977101964c14d56a8a37f5835bc0082426c672/xxhash-3.5.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:61a1ff00674879725b194695e17f23d3248998b843eb5e933007ca743310f793", size = 200006 }, + { url = "https://files.pythonhosted.org/packages/02/0a/96973bd325412feccf23cf3680fd2246aebf4b789122f938d5557c54a6b2/xxhash-3.5.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f2f2c61bee5844d41c3eb015ac652a0229e901074951ae48581d58bfb2ba01be", size = 428326 }, + { url = "https://files.pythonhosted.org/packages/11/a7/81dba5010f7e733de88af9555725146fc133be97ce36533867f4c7e75066/xxhash-3.5.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d32a592cac88d18cc09a89172e1c32d7f2a6e516c3dfde1b9adb90ab5df54a6", size = 194380 }, + { url = "https://files.pythonhosted.org/packages/fb/7d/f29006ab398a173f4501c0e4977ba288f1c621d878ec217b4ff516810c04/xxhash-3.5.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:70dabf941dede727cca579e8c205e61121afc9b28516752fd65724be1355cc90", size = 207934 }, + { url = "https://files.pythonhosted.org/packages/8a/6e/6e88b8f24612510e73d4d70d9b0c7dff62a2e78451b9f0d042a5462c8d03/xxhash-3.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e5d0ddaca65ecca9c10dcf01730165fd858533d0be84c75c327487c37a906a27", size = 216301 }, + { url = "https://files.pythonhosted.org/packages/af/51/7862f4fa4b75a25c3b4163c8a873f070532fe5f2d3f9b3fc869c8337a398/xxhash-3.5.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3e5b5e16c5a480fe5f59f56c30abdeba09ffd75da8d13f6b9b6fd224d0b4d0a2", size = 203351 }, + { url = "https://files.pythonhosted.org/packages/22/61/8d6a40f288f791cf79ed5bb113159abf0c81d6efb86e734334f698eb4c59/xxhash-3.5.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:149b7914451eb154b3dfaa721315117ea1dac2cc55a01bfbd4df7c68c5dd683d", size = 210294 }, + { url = "https://files.pythonhosted.org/packages/17/02/215c4698955762d45a8158117190261b2dbefe9ae7e5b906768c09d8bc74/xxhash-3.5.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:eade977f5c96c677035ff39c56ac74d851b1cca7d607ab3d8f23c6b859379cab", size = 414674 }, + { url = "https://files.pythonhosted.org/packages/31/5c/b7a8db8a3237cff3d535261325d95de509f6a8ae439a5a7a4ffcff478189/xxhash-3.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fa9f547bd98f5553d03160967866a71056a60960be00356a15ecc44efb40ba8e", size = 192022 }, +] + +[[package]] +name = "yarl" +version = "1.25.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "multidict", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "propcache", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/75/16/e8be8e2fb175bbf41a0680381a319f1199fae256588241a2ac8677eafb49/yarl-1.25.1.tar.gz", hash = "sha256:03dd38de09bc213e9a8b29761eec33ee1d5318dac0e49d8af36e4d27830e23a7", size = 246245 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/75/b3/cd32ac66ae622b854c2df0ac52106dda220d361b65a64fde7d5b3684aa3f/yarl-1.25.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:94d7aa6debf92a1dd14cb5280b083a764169a13cfb23a452111160274ed989f4", size = 144798 }, + { url = "https://files.pythonhosted.org/packages/61/fb/a2c52a8007c2051ba74662afb112ecf3d00346af4c25e33df9d80fd14fb8/yarl-1.25.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:83d4a37e4b95da4d8bda930d6d35b75b4cdadbacbb4980cae290ea3100b5d51d", size = 104583 }, + { url = "https://files.pythonhosted.org/packages/be/dd/ee38aec8e09fdf957e50d4085453fbe202f56c6c3b4cf07b81cdb4f09ee9/yarl-1.25.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e029648f9c951db30e98a7d7ec90835db88ec4b32820efe2a9bdc2287e032eb6", size = 104325 }, + { url = "https://files.pythonhosted.org/packages/1e/b3/058dbfb1857b484c9cf9cc135659f50b85ce66e03c99e44dc2f7b6161f55/yarl-1.25.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4d781294bb815ecb5ea57ff6bbf8038e0a31a95fdf3e1788f66e0dc100d64b58", size = 115358 }, + { url = "https://files.pythonhosted.org/packages/db/39/29693446cf0cf6b15a0e2f75a5d40f93c56819b05b0622196f45e95b5cc0/yarl-1.25.1-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e12c538e00e7c1b286a07061046b90e8124e6a9793efae2c70db6a4aad07faad", size = 107658 }, + { url = "https://files.pythonhosted.org/packages/86/b3/3c4dd7e1af43b931fba95e0a722737f2ea94a6d199c802585282831d7abd/yarl-1.25.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7e4de3ac4adbad3d0bc7c6f4360a7dbff5de2f15e3b723be3198074e17fd9c40", size = 122660 }, + { url = "https://files.pythonhosted.org/packages/bd/b5/1b60dbc3cfc9c5712b15148c206748f2bc93953ffdbe25ea75b63dfc89c9/yarl-1.25.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:419f392a1da624877975709e3864dfe833af6cc7671b39318086d456e288380c", size = 126506 }, + { url = "https://files.pythonhosted.org/packages/bc/7b/ca212cbe170ac8b96e45317ecbcf9c3c3ecf0cdec98d5b088a9c4088929b/yarl-1.25.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c6f117789d22dce188e5754e8bc65b7e6ebf8cb73963b9fa761f672a5883769d", size = 117050 }, + { url = "https://files.pythonhosted.org/packages/cb/c3/72b4938cdbe619ad71ac156182faef4908846b84dc3ca4dbb4c4e6f84014/yarl-1.25.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:80e47012e730da131c9f059c80936783f9659aae22dc31c03c0595590d11ed54", size = 114174 }, + { url = "https://files.pythonhosted.org/packages/e8/43/268717870f9ba0cc9701a95181587f6dc8c5f387aab4aeecc83158f38a79/yarl-1.25.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e80f557716fd765439577131e526b8942ffc2c07bdbc5e39fa62f660ba1e963f", size = 114944 }, + { url = "https://files.pythonhosted.org/packages/da/84/baa5bf504d51fe062c4bcaf62936da97fffb43285978d0b39984824231fd/yarl-1.25.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:f61964f235a43738bfac50da46fc4254943a7eea3051aeb0b6fc7c992c29fadc", size = 108263 }, + { url = "https://files.pythonhosted.org/packages/a4/28/779a2ed9e0152a601a27039bed9aead3f0b79797a67e2c44bfa444622dd8/yarl-1.25.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:e546fe1d4a93ebc2910f0d768baff19faa09843ab3f2036a67ed6e69fae4419d", size = 122184 }, + { url = "https://files.pythonhosted.org/packages/f8/1f/118e9e5b8f07694d63fd3222e801d7782270003f1a222aa798df3f8d5933/yarl-1.25.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:cce0727fd5ac04d372fa9bbfde9febc2bcf209aadfcf0468e45dec72719895d1", size = 114001 }, + { url = "https://files.pythonhosted.org/packages/0f/ae/a4cf1cf372313734b17996d4007f9f73596e7a178b9485802e5494ecf484/yarl-1.25.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:af4ea5b37403ef4e30f3927eaed540db942bde01d8d3ff083527c0704d1c9c68", size = 120565 }, + { url = "https://files.pythonhosted.org/packages/05/79/ad94f93ca731bc9e44d321833ab96b82a4f9f5f63cf773f81a4aeea5ecc1/yarl-1.25.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:68782fdb4027b8d1eee25ec35e9a6db05e863b899eb0310b3a33b6c3fef55707", size = 117060 }, + { url = "https://files.pythonhosted.org/packages/54/22/318c7980066769c6bcd9221ed2248294f5698811da099013098c670565ed/yarl-1.25.1-py3-none-any.whl", hash = "sha256:681c758b0490f9e96b78e5fa8e8dc6e648e9185bb6eaebe73183c33ea0c445f3", size = 63617 }, +] diff --git a/scripts/gen_nemotron3_diar_oracle_audio.py b/scripts/gen_nemotron3_diar_oracle_audio.py new file mode 100644 index 00000000..6aafd03d --- /dev/null +++ b/scripts/gen_nemotron3_diar_oracle_audio.py @@ -0,0 +1,166 @@ +#!/usr/bin/env python3 +"""Deterministically synthesize an 8-speaker diarization oracle clip. + +Nemotron-3-Diarization tracks up to 8 speakers, and the existing +diarization fixtures (sortformer-2spk-mix, AMI) never exercise speaker +channels 5-8. This builds a reproducible 8-speaker mixture (the model's +cap, so every output channel is exercised) from eight committed mono +clips by different speakers (jfk, six FLEURS `-long` bench clips, +product-names), with +speaker returns (arrival-order identity must survive +the speaker cache) and three short overlap regions, plus a ground-truth +RTTM. Speaker identity does not depend on language, so a cross-language +pool maximizes speaker distinctness. + +The clip is 92 s so that AOSC speaker-cache compression triggers at every +preset (very_high_latency compresses at step 0; the low-latency presets +after ~40 s), i.e. the streaming oracle covers FIFO -> cache -> compress. + +Each source is trimmed of leading/trailing silence (the FLEURS clips open +with up to ~5 s of near-silence), then each turn reads the next unused +stretch of its source (wrapping when exhausted), so a returning speaker +says new words. The RTTM is authored at turn level: it includes any short +pauses inside a source window, so it is a sanity reference, not a +frame-exact label. + +A second, non-aligned case is the same mix truncated to 91.337 s +(1461392 samples: not a multiple of the 160-sample hop, 9133 mel frames +not a multiple of 8). It exercises NeMo's floor(n/160) framing, the +zero-padded final feature-stacking group and the partial final chunk, +none of which the 92 s clip hits. + +Outputs (16 kHz mono, deterministic): + samples/nemotron3-diar-8spk-mix.wav + samples/nemotron3-diar-8spk-mix-trunc.wav + tests/golden/nemotron3_diar/nemotron3-diar-8spk-mix.rttm + +Run: + uv run scripts/gen_nemotron3_diar_oracle_audio.py +""" + +from __future__ import annotations + +import subprocess +import sys +from pathlib import Path + +import numpy as np +import soundfile as sf + +SR = 16000 +REPO = Path(__file__).resolve().parent.parent +CLIP_ID = "nemotron3-diar-8spk-mix" +OUT_WAV = REPO / "samples" / f"{CLIP_ID}.wav" +OUT_RTTM = REPO / "tests" / "golden" / "nemotron3_diar" / f"{CLIP_ID}.rttm" +OUT_TRUNC_WAV = REPO / "samples" / f"{CLIP_ID}-trunc.wav" +TOTAL_SEC = 92.0 +TRUNC_SAMPLES = 1461392 # 91.337 s + +# Speaker label -> committed source clip (all 16 kHz mono). +SOURCES = { + "spk_A": REPO / "samples" / "jfk.wav", + "spk_B": REPO / "samples" / "zh-long.wav", + "spk_C": REPO / "samples" / "ru-long.wav", + "spk_D": REPO / "samples" / "ja-long.wav", + "spk_E": REPO / "samples" / "ko-long.wav", + "spk_F": REPO / "samples" / "vi-long.wav", + "spk_G": REPO / "samples" / "ar-long.wav", + # Not uk-long.wav: the reference merges that reader into spk_E's channel, + # leaving output channel 8 unexercised. product-names.wav is separated + # cleanly (its provenance is unrecorded, like jfk.wav; see samples/README.md). + "spk_H": REPO / "samples" / "product-names.wav", +} + +# Authored timeline (speaker, start s, end s), in arrival order A..H; every +# speaker returns once. Overlaps: B/C [10.0, 10.5], E/F [26.5, 27.0], +# H/D [81.0, 81.5]. +TURNS = [ + ("spk_A", 0.0, 5.0), + ("spk_B", 5.5, 10.5), + ("spk_C", 10.0, 16.0), + ("spk_D", 16.5, 21.5), + ("spk_E", 22.0, 27.0), + ("spk_F", 26.5, 32.0), + ("spk_G", 32.5, 37.5), + ("spk_H", 38.0, 43.0), + ("spk_A", 43.5, 48.5), + ("spk_C", 49.0, 54.0), + ("spk_B", 54.5, 59.5), + ("spk_G", 60.0, 65.0), + ("spk_F", 65.5, 70.5), + ("spk_E", 71.0, 76.0), + ("spk_H", 76.5, 81.5), + ("spk_D", 81.0, 87.0), + ("spk_A", 87.5, 92.0), +] + + +def _trim_silence(audio: np.ndarray, floor_db: float = -30.0, frame: int = 320, run: int = 5) -> np.ndarray: + """Drop leading/trailing silence: speech starts/ends at the first/last run of + `run` consecutive 20 ms frames within |floor_db| of the clip peak (a lone + click in the lead-in does not count).""" + n = len(audio) // frame * frame + rms = np.sqrt((audio[:n].reshape(-1, frame) ** 2).mean(axis=1)) + loud = 20 * np.log10(rms / (np.abs(audio).max() + 1e-12) + 1e-12) > floor_db + sustained = np.convolve(loud.astype(int), np.ones(run, dtype=int), mode="valid") == run + starts = np.flatnonzero(sustained) + if starts.size == 0: + return audio + return audio[starts[0] * frame:(starts[-1] + run) * frame] + + +def _load_mono_16k(path: Path) -> np.ndarray: + audio, sr = sf.read(str(path), dtype="float32", always_2d=False) + if audio.ndim > 1: + audio = audio.mean(axis=1) + if sr != SR: + raise SystemExit(f"error: {path} is {sr} Hz, expected {SR}") + audio = _trim_silence(audio) + # Peak-normalize each source so all speakers sit at a similar level. + peak = float(np.max(np.abs(audio))) or 1.0 + return (audio / peak * 0.9).astype(np.float32) + + +def _take(src: np.ndarray, offset: int, n: int) -> tuple[np.ndarray, int]: + """Next n samples of src starting at offset, wrapping; returns (chunk, new offset).""" + idx = (offset + np.arange(n)) % len(src) + return src[idx], (offset + n) % len(src) + + +def main() -> int: + for p in SOURCES.values(): + if not p.exists(): + print(f"error: missing source clip {p}", file=sys.stderr) + return 1 + total_n = int(round(TOTAL_SEC * SR)) + srcs = {spk: _load_mono_16k(p) for spk, p in SOURCES.items()} + offsets = {spk: 0 for spk in SOURCES} + mix = np.zeros(total_n, dtype=np.float32) + for spk, start, end in TURNS: + i0, i1 = int(round(start * SR)), int(round(end * SR)) + chunk, offsets[spk] = _take(srcs[spk], offsets[spk], i1 - i0) + mix[i0:i1] += chunk + peak = float(np.max(np.abs(mix))) or 1.0 + mix = (mix / peak * 0.9).astype(np.float32) + + OUT_WAV.parent.mkdir(parents=True, exist_ok=True) + OUT_RTTM.parent.mkdir(parents=True, exist_ok=True) + sf.write(str(OUT_WAV), mix, SR, subtype="PCM_16") + lines = [ + f"SPEAKER {CLIP_ID} 1 {start:.3f} {end - start:.3f} {spk} " + for spk, start, end in sorted(TURNS, key=lambda t: t[1]) + ] + OUT_RTTM.write_text("\n".join(lines) + "\n") + + sf.write(str(OUT_TRUNC_WAV), mix[:TRUNC_SAMPLES], SR, subtype="PCM_16") + + print(f"wrote {OUT_WAV.relative_to(REPO)} ({len(mix) / SR:.2f}s, 16kHz mono)") + print(f"wrote {OUT_TRUNC_WAV.relative_to(REPO)} ({TRUNC_SAMPLES / SR:.3f}s)") + print(f"wrote {OUT_RTTM.relative_to(REPO)} ({len(lines)} turns, {len(SOURCES)} speakers)") + sha = subprocess.run(["shasum", "-a", "256", str(OUT_WAV)], capture_output=True, text=True) + print("sha256:", sha.stdout.split()[0] if sha.returncode == 0 else "n/a") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/scripts/lib/gguf_common.py b/scripts/lib/gguf_common.py index 98091f6f..bf442fc0 100644 --- a/scripts/lib/gguf_common.py +++ b/scripts/lib/gguf_common.py @@ -204,6 +204,9 @@ def reference_dtype_for( return GGMLQuantizationType.F32 if name in ("frontend.mel_filterbank", "frontend.window"): return GGMLQuantizationType.F32 + # Nemotron-3-Diarization learned AOSC silence embedding (1-D, d_model). + if name == "diar.sil_emb": + return GGMLQuantizationType.F32 # Conv bucket (pointwise + depthwise + 2D). is_convpw = ( diff --git a/scripts/lib/test_quant_policy_sync.py b/scripts/lib/test_quant_policy_sync.py index 2b6cda9c..af8dfd0d 100644 --- a/scripts/lib/test_quant_policy_sync.py +++ b/scripts/lib/test_quant_policy_sync.py @@ -85,6 +85,9 @@ "dec.pos_emb.weight", # whisper decoder pos_emb "frontend.mel_filterbank", # mel frontend buffer "frontend.window", # window frontend buffer + "diar.sil_emb", # nemotron3_diar AOSC silence embedding + "enc.embed.norm.weight", # nemotron3_diar pre-block LN (dot separator) + "enc.blocks.0.norm_1.weight", # nemotron3_diar pre-LN block norm (norm_ prefix) ] # Conv bucket: 2D / depthwise / 1x1 pointwise conv kernels. The loader has no @@ -104,6 +107,7 @@ "enc.blocks.3.conv.pointwise2.weight", # conformer 1x1 pointwise "enc.pre_encode.conv.0.weight", # pre-encode subsampling conv "enc.blocks.3.conv.depthwise.weight", # conformer depthwise conv + "diar.upsample.conv.weight", # nemotron3_diar subpixel Conv1d (k=3) ] # Linear / Embed: ggml_mul_mat operands and the decoder token embedding. @@ -128,6 +132,8 @@ "enc.blocks.3.attn.rel_pos_emb.weight", "enc.blocks.3.attn.kv.weight", # granite5_ctc fused K|V projection "enc.ctc_proj.weight", # granite5_ctc tied CTC head + "enc.blocks.0.attn.qkv.weight", # nemotron3_diar fused q|k|v projection + "enc.pre_encode.proj.weight", # nemotron3_diar feature-stacking projection ] # KNOWN DRIFT — policy.cpp::classify_tensor places these in the Norm (F32) or diff --git a/scripts/validate.py b/scripts/validate.py index 43af1e3f..bde66227 100644 --- a/scripts/validate.py +++ b/scripts/validate.py @@ -337,6 +337,159 @@ def write_cpp_transcript( print(f" wrote {path}", file=sys.stderr) +# --------------------------------------------------------------------------- +# nemotron3_diar: per-stage oracle layout. +# +# The diarizer's oracle is organised per stage rather than as one flat +# /ref directory (see the manifest's diarization.stage_dirs): +# /encoder/{ref,cpp} first streaming step at the default +# preset (+ full-clip mel) +# /diarize/{ref,cpp} diar.probs at the default preset +# /diarize-/{ref,cpp} diar.probs at each other preset +# One C++ run per stage; the preset is pinned on both sides (dumper +# --preset, C++ TRANSCRIBE_NEMOTRON3_DIAR_PRESET). VALIDATE_NEMOTRON3_DIAR_PRESETS +# (comma list) restricts the preset set; default = every manifest preset. +# +# Push-audio: each preset also gets a C++-only stage +# /stream-/cpp +# that drives transcribe_stream_begin/feed/finalize (CLI --stream-chunk-ms, +# VALIDATE_NEMOTRON3_DIAR_STREAM_MS, default 173 ms = not hop-aligned) and is +# compared against the SAME reference diar.probs as the whole-file stage. +# VALIDATE_NEMOTRON3_DIAR_STREAM_MS=0 skips the push-audio stages. +# +# Forced picks (default; VALIDATE_NEMOTRON3_DIAR_FORCE_PICKS=0 disables): the +# diarize / stream C++ runs take each speaker-cache compression's selected +# frames from the reference's compress/ dumps +# (TRANSCRIBE_NEMOTRON3_DIAR_COMPRESS_FROM_REF), like reference-mel injection: +# the top-k selection is discontinuous, so fp32 GEMM noise can flip a +# near-tied pick. diar.probs then gates the graph + bookkeeping tightly; the +# unforced selection is gated by scripts/diar/check_nemotron3_diar_compress.py +# and end to end by DER/JER. Each stream-/cpp is also compared to +# diarize-/cpp at zero tolerance (push-audio must be bit-identical). +# --------------------------------------------------------------------------- + +N3D_FAMILY = "nemotron3_diar" + + +def n3d_stages(manifest: dict[str, Any]) -> list[tuple[str, str | None]]: + """[(stage_dir, preset)]; the encoder stage has preset None (default).""" + diar = manifest.get("diarization") or {} + presets = list((diar.get("presets") or {}).keys()) + default = diar.get("default_preset", "very_high_latency") + only = os.environ.get("VALIDATE_NEMOTRON3_DIAR_PRESETS") + if only: + wanted = [x.strip() for x in only.split(",") if x.strip()] + unknown = [x for x in wanted if x not in presets] + if unknown: + raise SystemExit(f"error: VALIDATE_NEMOTRON3_DIAR_PRESETS has unknown preset(s) {unknown}") + presets = [x for x in presets if x in wanted] + stages: list[tuple[str, str | None]] = [("encoder", None)] + for pr in presets: + stages.append(("diarize" if pr == default else f"diarize-{pr}", pr)) + return stages + + +def n3d_stream_stages(manifest: dict[str, Any]) -> list[tuple[str, str, str]]: + """[(stream_stage_dir, preset, reference_stage_dir)] for push-audio.""" + if os.environ.get("VALIDATE_NEMOTRON3_DIAR_STREAM_MS", "173") == "0": + return [] + return [(f"stream-{preset}", preset, stage) for stage, preset in n3d_stages(manifest) if preset is not None] + + +def n3d_force_picks(env: dict[str, str], ref_compress: Path) -> None: + if os.environ.get("VALIDATE_NEMOTRON3_DIAR_FORCE_PICKS", "1") == "0": + return + if not ref_compress.is_dir(): + raise SystemExit(f"error: forced picks need the reference compress dumps at {ref_compress} " + "(run `validate.py ref`, or set VALIDATE_NEMOTRON3_DIAR_FORCE_PICKS=0)") + env["TRANSCRIBE_NEMOTRON3_DIAR_COMPRESS_FROM_REF"] = str(ref_compress) + + +def n3d_cmd_ref(repo: Path, manifest: dict[str, Any], base_args: list[str], common_args: list[str], + case_dir: Path, label: str) -> None: + for stage, preset in n3d_stages(manifest): + out_dir = case_dir / stage / "ref" + if out_dir.exists(): + shutil.rmtree(out_dir) + out_dir.mkdir(parents=True) + args = list(common_args) + args[args.index("--out") + 1] = str(out_dir) + if preset is None: + cmd = base_args + ["encoder"] + args + else: + comp = case_dir / stage / "compress" + if comp.exists(): + shutil.rmtree(comp) + cmd = base_args + ["diarize", "--preset", preset, "--dump-compress", str(comp)] + args + run_cmd(cmd, repo, f"ref {stage} [{label}]") + + +def n3d_cmd_cpp(repo: Path, manifest: dict[str, Any], base_cmd: list[str], audio: Path, + case_dir: Path, label: str) -> str: + """Run one C++ pass per stage; returns the last CLI transcript line.""" + transcript = "" + for stage, preset in n3d_stages(manifest): + out_dir = case_dir / stage / "cpp" + if out_dir.exists(): + shutil.rmtree(out_dir) + out_dir.mkdir(parents=True) + env = os.environ.copy() + env["TRANSCRIBE_DUMP_DIR"] = str(out_dir) + if preset is None: + env["TRANSCRIBE_NEMOTRON3_DIAR_ENCODER_DUMP"] = "1" + else: + env["TRANSCRIBE_NEMOTRON3_DIAR_PRESET"] = preset + n3d_force_picks(env, case_dir / stage / "compress") + cmd = base_cmd + [str(audio)] + print(f"\n cpp {stage} [{label}] TRANSCRIBE_DUMP_DIR={out_dir}", file=sys.stderr) + result = subprocess.run(cmd, cwd=repo, env=env, stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, text=True, errors="replace") + if result.returncode != 0: + print(result.stdout or "", end="") + raise SystemExit(f"error: cpp {stage} [{label}] failed with exit code {result.returncode}") + # The encoder stage gates the first streaming step (+ mel); diar.probs + # belongs to the diarize stages. Drop each stage's out-of-scope dumps + # so they are not reported as missing on the reference side. + for pat in (["diar.probs.*"] if preset is None else ["enc.mel.in.*"]): + for f in out_dir.glob(pat): + f.unlink() + transcript = parse_cli_transcript(result.stdout or "") or transcript + stream_ms = os.environ.get("VALIDATE_NEMOTRON3_DIAR_STREAM_MS", "173") + for stage, preset, ref_stage in n3d_stream_stages(manifest): + out_dir = case_dir / stage / "cpp" + if out_dir.exists(): + shutil.rmtree(out_dir) + out_dir.mkdir(parents=True) + env = os.environ.copy() + env["TRANSCRIBE_DUMP_DIR"] = str(out_dir) + env["TRANSCRIBE_NEMOTRON3_DIAR_PRESET"] = preset + n3d_force_picks(env, case_dir / ref_stage / "compress") + cmd = base_cmd + ["--stream-chunk-ms", stream_ms, str(audio)] + print(f"\n cpp {stage} [{label}] push-audio {stream_ms} ms pieces", file=sys.stderr) + result = subprocess.run(cmd, cwd=repo, env=env, stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, text=True, errors="replace") + if result.returncode != 0: + print(result.stdout or "", end="") + raise SystemExit(f"error: cpp {stage} [{label}] failed with exit code {result.returncode}") + for f in out_dir.glob("enc.mel.in.*"): + f.unlink() + return transcript + + +def n3d_compare_pairs(repo: Path, family: str, variant: str, case_name: str, + manifest: dict[str, Any]) -> list[tuple[str, Path, Path]]: + case_dir = repo / "build" / "validate" / family / variant / case_name + pairs = [(f"{case_name}/{stage}", case_dir / stage / "cpp", case_dir / stage / "ref") + for stage, _ in n3d_stages(manifest)] + pairs += [(f"{case_name}/{stage}", case_dir / stage / "cpp", case_dir / ref_stage / "ref") + for stage, _, ref_stage in n3d_stream_stages(manifest)] + # Push-audio vs whole-file, C++ vs C++: must be bit-identical ("exact:" + # label prefix -> zero tolerance, no tolerance file). + pairs += [(f"exact:{case_name}/{stage}-vs-{ref_stage}", case_dir / stage / "cpp", case_dir / ref_stage / "cpp") + for stage, _, ref_stage in n3d_stream_stages(manifest)] + return pairs + + def cmd_ref(args: argparse.Namespace) -> int: repo = find_repo_root(Path(__file__).parent) manifest = load_manifest(repo, args.family, getattr(args, "variant", None)) @@ -358,9 +511,10 @@ def cmd_ref(args: argparse.Namespace) -> int: raise SystemExit(f"error: audio not found: {audio}") out_dir = repo / "build" / "validate" / args.family / variant / case_name / "ref" - if out_dir.exists(): - shutil.rmtree(out_dir) - out_dir.mkdir(parents=True) + if args.family != N3D_FAMILY: + if out_dir.exists(): + shutil.rmtree(out_dir) + out_dir.mkdir(parents=True) base_args = [ "uv", "run", "--project", str(env_dir), @@ -411,6 +565,10 @@ def cmd_ref(args: argparse.Namespace) -> int: # (--preset) and the C++ side (TRANSCRIBE_SORTFORMER_STREAM_PRESET in # cmd_cpp); unset -> the checkpoint-shipped cfg (single chunk on the # short oracle, i.e. diar.probs == diar.preds_offline). + if args.family == N3D_FAMILY: + n3d_cmd_ref(repo, manifest, base_args, common_args, out_dir.parent, + f"{args.family}/{variant}/{case_name}/{reference['kind']}") + continue if args.family == "sortformer": stages = ["encoder", "diarize"] else: @@ -447,6 +605,13 @@ def cmd_cpp(args: argparse.Namespace) -> int: if not audio.exists(): raise SystemExit(f"error: audio not found: {audio}") + if args.family == N3D_FAMILY: + base_cmd = [str(cli), "--backend", args.backend, + "--threads", os.environ.get("VALIDATE_CPP_THREADS", "1"), "-m", str(gguf)] + case_dir = repo / "build" / "validate" / args.family / variant / case_name + n3d_cmd_cpp(repo, manifest, base_cmd, audio, case_dir, f"{args.family}/{case_name}") + continue + out_dir = repo / "build" / "validate" / args.family / variant / case_name / "cpp" if out_dir.exists(): shutil.rmtree(out_dir) @@ -595,10 +760,17 @@ def cmd_compare(args: argparse.Namespace) -> int: transcript_results: list[dict[str, Any]] = [] cmd_log: list[dict[str, Any]] = [] + pairs: list[tuple[Any, str, Path, Path]] = [] for case in cases: case_name = case_audio(case) - cpp_dir = repo / "build" / "validate" / args.family / variant / case_name / "cpp" - ref_dir = repo / "build" / "validate" / args.family / variant / case_name / "ref" + if args.family == N3D_FAMILY: + for label, cdir, rdir in n3d_compare_pairs(repo, args.family, variant, case_name, manifest): + pairs.append((case, label, cdir, rdir)) + else: + base = repo / "build" / "validate" / args.family / variant / case_name + pairs.append((case, case_name, base / "cpp", base / "ref")) + + for case, case_name, cpp_dir, ref_dir in pairs: if not cpp_dir.exists(): print(f"SKIP {case_name}: no C++ dumps at {cpp_dir}", file=sys.stderr) @@ -613,7 +785,9 @@ def cmd_compare(args: argparse.Namespace) -> int: "uv", "run", str(compare_script), str(cpp_dir), str(ref_dir), ] - if tolerances: + if case_name.startswith("exact:"): + cmd += ["--max-abs", "0", "--mean-abs", "0"] + elif tolerances: cmd += ["--tolerances", str(tolerances)] print(f"\n{'=' * 60}", file=sys.stderr) @@ -647,7 +821,7 @@ def cmd_compare(args: argparse.Namespace) -> int: # diar.probs tensor, gated above; the `diarize` stage's segment lines # are informational only, so skip the text-transcript comparison. ref_transcript = ref_dir / "transcript.json" - if ref_transcript.exists() and args.family != "sortformer": + if ref_transcript.exists() and args.family not in ("sortformer", N3D_FAMILY): transcript_compare = case_transcript_compare(manifest, case) ref_data = json.loads(ref_transcript.read_text()) ref_text = str(ref_data.get("text", "")) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index e40e2927..b2af5701 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -64,6 +64,10 @@ add_library(transcribe arch/sortformer/stream.cpp arch/sortformer/capabilities.cpp arch/sortformer/weights.cpp + arch/nemotron3_diar/model.cpp + arch/nemotron3_diar/stream.cpp + arch/nemotron3_diar/capabilities.cpp + arch/nemotron3_diar/weights.cpp arch/voxtral/model.cpp arch/voxtral/capabilities.cpp arch/voxtral/weights.cpp @@ -292,3 +296,11 @@ if(WIN32) endif() transcribe_apply_warnings(transcribe) + +# nemotron3_diar/stream.cpp reproduces torch's CPU float arithmetic bit-for-bit +# (Sleef logf, ATen sum order) for the speaker-cache top-k; the compiler must not +# fuse its separate multiplies/adds into FMAs. Clang also honors the in-source +# `#pragma clang fp contract(off)`; GCC needs the flag. +if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") + set_source_files_properties(arch/nemotron3_diar/stream.cpp PROPERTIES COMPILE_OPTIONS "-ffp-contract=off") +endif() diff --git a/src/arch/nemotron3_diar/capabilities.cpp b/src/arch/nemotron3_diar/capabilities.cpp new file mode 100644 index 00000000..05d49c03 --- /dev/null +++ b/src/arch/nemotron3_diar/capabilities.cpp @@ -0,0 +1,31 @@ +// arch/nemotron3_diar/capabilities.cpp - Nemotron-3-Diarization capability +// defaults. +// +// Applied before transcribe::read_capability_kv (KV present overrides, KV +// absent keeps the default). A pure frame-level diarizer: no transcript, no +// translation, no transcript timestamps. Output is speaker segments via the +// transcript-independent speaker_segment surface, from transcribe_run and +// from push-audio streaming. + +#include "nemotron3_diar.h" + +namespace transcribe::nemotron3_diar { + +void apply_family_invariants(transcribe_model & model) { + transcribe_capabilities & caps = model.caps; + + // Fixed 16 kHz mel bank (NeMo AudioToMelSpectrogramPreprocessor). + caps.native_sample_rate = 16000; + + // Not a transcription model. + caps.supports_translate = false; + caps.max_timestamp_kind = TRANSCRIBE_TIMESTAMPS_NONE; + + // Push-audio live diarization (transcribe_stream_*). + caps.supports_streaming = true; + + transcribe::set_feature(&model, TRANSCRIBE_FEATURE_DIARIZATION, true); + transcribe::set_feature(&model, TRANSCRIBE_FEATURE_CANCELLATION, true); +} + +} // namespace transcribe::nemotron3_diar diff --git a/src/arch/nemotron3_diar/model.cpp b/src/arch/nemotron3_diar/model.cpp new file mode 100644 index 00000000..cf30dec1 --- /dev/null +++ b/src/arch/nemotron3_diar/model.cpp @@ -0,0 +1,1168 @@ +// arch/nemotron3_diar/model.cpp - Nemotron-3-Diarization load / graph / +// streaming driver / Arch instance. +// +// Every product path runs NeMo's synchronous streaming loop +// (SortformerEncLabelModel.forward_streaming, streaming_mode=True, +// async_streaming=False): per chunk, one graph computes +// chunk_embs = FeatureStacking(chunk mel) [T_diar, 512] +// x = [spkcache | fifo | chunk_embs] -> embed_norm -> 31 pre-LN RoPE +// blocks -> final_norm -> encoder_proj -> subpixel x8 -> head +// probs = sigmoid(logits) [8*T_cat, 8] +// and the host state machine (stream.cpp) updates the cache/FIFO from the +// x8 average-pooled probs. transcribe_run, push-audio and run_batch share +// the per-chunk driver (advance_chunks). + +#include "ggml-backend.h" +#include "ggml.h" +#include "gguf.h" +#include "nemotron3_diar.h" +#include "transcribe-arch.h" +#include "transcribe-backend.h" +#include "transcribe-batch-util.h" +#include "transcribe-debug.h" +#include "transcribe-load-common.h" +#include "transcribe-loader.h" +#include "transcribe-log.h" +#include "transcribe-mel.h" +#include "transcribe-meta.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace transcribe::nemotron3_diar { + +extern const Arch arch; + +static constexpr char k_default_variant[] = "Nemotron-3-Diarization"; + +Nemotron3DiarModel::~Nemotron3DiarModel() { + if (f32_ctx != nullptr) { + ggml_free(f32_ctx); + } + if (f32_buffer != nullptr) { + transcribe::safe_buffer_free(f32_buffer); + } + if (ctx_meta != nullptr) { + ggml_free(ctx_meta); + } + if (backend_buffer != nullptr) { + transcribe::safe_buffer_free(backend_buffer); + } + for (auto it = plan.scheduler_list.rbegin(); it != plan.scheduler_list.rend(); ++it) { + transcribe::safe_backend_free(*it); + } + plan.scheduler_list.clear(); + plan.primary = nullptr; +} + +Nemotron3DiarSession::~Nemotron3DiarSession() = default; + +namespace { + +bool env_set(const char * name) { + const char * v = std::getenv(name); + return v != nullptr && v[0] != '\0' && std::strcmp(v, "0") != 0; +} + +// Upcast weight slots to exact F32 copies on `backend` and repoint the slots +// (see the promotion policy in load()). +transcribe_status promote_to_f32(const std::vector & slots, + ggml_backend_t backend, + ggml_context ** out_ctx, + ggml_backend_buffer_t * out_buffer) { + if (slots.empty()) { + return TRANSCRIBE_OK; + } + ggml_init_params ip{ slots.size() * ggml_tensor_overhead() + 1024, nullptr, /*no_alloc=*/true }; + *out_ctx = ggml_init(ip); + if (*out_ctx == nullptr) { + return TRANSCRIBE_ERR_BACKEND; + } + std::vector dst(slots.size()); + for (size_t i = 0; i < slots.size(); ++i) { + const ggml_tensor * src = *slots[i]; + dst[i] = ggml_new_tensor(*out_ctx, GGML_TYPE_F32, ggml_n_dims(src), src->ne); + ggml_set_name(dst[i], ggml_get_name(src)); + } + *out_buffer = ggml_backend_alloc_ctx_tensors(*out_ctx, backend); + if (*out_buffer == nullptr) { + return TRANSCRIBE_ERR_BACKEND; + } + ggml_backend_buffer_set_usage(*out_buffer, GGML_BACKEND_BUFFER_USAGE_WEIGHTS); + std::vector raw; + std::vector f32; + for (size_t i = 0; i < slots.size(); ++i) { + ggml_tensor * src = *slots[i]; + const int64_t n = ggml_nelements(src); + raw.resize(ggml_nbytes(src)); + ggml_backend_tensor_get(src, raw.data(), 0, raw.size()); + f32.resize(static_cast(n)); + const auto * tt = ggml_get_type_traits(src->type); + if (src->type == GGML_TYPE_F32) { + std::memcpy(f32.data(), raw.data(), raw.size()); + } else if (tt != nullptr && tt->to_float != nullptr) { + tt->to_float(raw.data(), f32.data(), n); + } else { + log_msg(TRANSCRIBE_LOG_LEVEL_ERROR, "nemotron3_diar: cannot upcast %s (%s)", ggml_get_name(src), + ggml_type_name(src->type)); + return TRANSCRIBE_ERR_GGUF; + } + ggml_backend_tensor_set(dst[i], f32.data(), 0, f32.size() * sizeof(float)); + *slots[i] = dst[i]; + } + return TRANSCRIBE_OK; +} + +// ---- graph helpers ---- + +ggml_tensor * layer_norm(ggml_context * ctx, ggml_tensor * x, ggml_tensor * w, ggml_tensor * b, float eps) { + x = ggml_norm(ctx, x, eps); + x = ggml_mul(ctx, x, w); + return ggml_add(ctx, x, b); +} + +ggml_tensor * linear(ggml_context * ctx, ggml_tensor * W, ggml_tensor * x, ggml_tensor * b) { + ggml_tensor * y = ggml_mul_mat(ctx, W, x); + return b != nullptr ? ggml_add(ctx, y, b) : y; +} + +// Pre-LN RoPE block (TransformerBlock.forward). x ne = [d, T, B]. +ggml_tensor * build_block(ggml_context * ctx, + const Nemotron3DiarHParams & hp, + const Nemotron3DiarBlock & b, + ggml_tensor * x, + ggml_tensor * positions) { + const int64_t d = hp.enc_d_model; + const int64_t H = hp.enc_n_heads; + const int64_t D = hp.head_dim(); + const int64_t T = x->ne[1]; + const int64_t Bn = x->ne[2]; + + // ---- attention ---- + ggml_tensor * h = layer_norm(ctx, x, b.norm1_w, b.norm1_b, hp.enc_ln_eps); + ggml_tensor * qkv = ggml_mul_mat(ctx, b.attn_qkv_w, h); // [3d, T, B], rows [q | k | v] + + // NeMo: w_qkv(x).view(B, T, 3, H, D) -> q/k/v each head-major [D, H, T, B]. + // q and k are adjacent in every qkv row, so one strided view covers both + // ([D, 2H, T, B]) and a single RoPE rotates them: rotate_half (NEOX) over + // all D dims, positions 0..T-1 over the concat (t_q == t_k, no cache + // offset), theta rope_base. Reading strided views instead of contiguous + // copies is value-identical (same per-element math, same dot products). + const size_t es = ggml_element_size(qkv); + ggml_tensor * qk = ggml_view_4d(ctx, qkv, D, 2 * H, T, Bn, D * es, qkv->nb[1], qkv->nb[2], 0); + qk = ggml_rope_ext(ctx, qk, positions, nullptr, static_cast(D), GGML_ROPE_TYPE_NEOX, 0, hp.enc_rope_base, 1.0f, + 0.0f, 1.0f, 0.0f, 0.0f); // contiguous [D, 2H, T, B] + // [D, T, H, B] views into the rotated q|k (heads 0..H-1 = q, H..2H-1 = k). + ggml_tensor * q = ggml_view_4d(ctx, qk, D, T, H, Bn, qk->nb[2], qk->nb[1], qk->nb[3], 0); + ggml_tensor * k = ggml_view_4d(ctx, qk, D, T, H, Bn, qk->nb[2], qk->nb[1], qk->nb[3], H * qk->nb[1]); + + // Full attention (sync batch 1: no padding mask), scale 1/sqrt(D). + ggml_tensor * kq = ggml_mul_mat(ctx, k, q); // [T_k, T_q, H, B] + kq = ggml_soft_max_ext(ctx, kq, nullptr, 1.0f / std::sqrt(static_cast(D)), 0.0f); + // v^T [T, D, H, B] straight from the qkv view in one copy. + ggml_tensor * v = + ggml_view_4d(ctx, qkv, D, H, T, Bn, D * es, qkv->nb[1], qkv->nb[2], static_cast(2) * d * es); + ggml_tensor * v_t = ggml_cont(ctx, ggml_permute(ctx, v, 1, 2, 0, 3)); // [T, D, H, B] + ggml_tensor * o = ggml_mul_mat(ctx, v_t, kq); // [D, T_q, H, B] + o = ggml_cont(ctx, ggml_permute(ctx, o, 0, 2, 1, 3)); // [D, H, T, B] + o = ggml_reshape_3d(ctx, o, d, T, Bn); + o = linear(ctx, b.attn_out_w, o, b.attn_out_b); + x = ggml_add(ctx, x, o); + + // ---- FFN: Linear -> GELU (exact erf) -> Linear ---- + ggml_tensor * f = layer_norm(ctx, x, b.norm2_w, b.norm2_b, hp.enc_ln_eps); + f = linear(ctx, b.ff_in_w, f, b.ff_in_b); + f = ggml_gelu_erf(ctx, f); + f = linear(ctx, b.ff_out_w, f, b.ff_out_b); + return ggml_add(ctx, x, f); +} + +// One streaming step for B utterances sharing the same step geometry. +struct StepBuild { + ggml_cgraph * graph = nullptr; + ggml_tensor * prev_in = nullptr; // [d, S+F, B] (null when S+F == 0) + ggml_tensor * mel_in = nullptr; // [sub*n_mels, T_diar, B] + ggml_tensor * positions = nullptr; // I32 [T_cat] + ggml_tensor * embs = nullptr; // [d, T_diar, B] + ggml_tensor * probs = nullptr; // [n_spk, up*T_cat, B] + + // Encoder-stage parity tensors (step 0 dumps). + ggml_tensor * embed_norm = nullptr; + ggml_tensor * final_norm = nullptr; + ggml_tensor * enc_proj = nullptr; + ggml_tensor * sub_conv = nullptr; + ggml_tensor * upsampled = nullptr; + ggml_tensor * logits = nullptr; + std::vector> layer_out; +}; + +StepBuild build_step_graph(ggml_context * ctx, + const Nemotron3DiarHParams & hp, + const Nemotron3DiarWeights & w, + int n_prev, + int T_diar, + int Bn, + bool want_layers) { + StepBuild sb; + const int64_t d = hp.enc_d_model; + const int64_t fin = static_cast(hp.enc_feat_in) * hp.enc_subsampling_factor; + const int64_t T_cat = n_prev + T_diar; + const int64_t hd = hp.head_d_model; + const int64_t up = hp.upsample_factor; + + sb.mel_in = ggml_new_tensor_3d(ctx, GGML_TYPE_F32, fin, T_diar, Bn); + ggml_set_name(sb.mel_in, "step.mel.in"); + ggml_set_input(sb.mel_in); + + // FeatureStacking: 8 consecutive 128-dim mel frames per row (frame-major), + // stacked on the host; Linear 1024 -> 512, no bias. + sb.embs = ggml_mul_mat(ctx, w.pre_encode_proj_w, sb.mel_in); // [d, T_diar, B] + + ggml_tensor * x = sb.embs; + if (n_prev > 0) { + sb.prev_in = ggml_new_tensor_3d(ctx, GGML_TYPE_F32, d, n_prev, Bn); + ggml_set_name(sb.prev_in, "step.prev.in"); + ggml_set_input(sb.prev_in); + x = ggml_concat(ctx, sb.prev_in, sb.embs, 1); // [d, T_cat, B] + } + + sb.positions = ggml_new_tensor_1d(ctx, GGML_TYPE_I32, T_cat); + ggml_set_name(sb.positions, "step.positions"); + ggml_set_input(sb.positions); + + // rope branch of forward_internal: no xscale; embed_norm (pre_block_norm). + x = layer_norm(ctx, x, w.embed_norm_w, w.embed_norm_b, hp.enc_ln_eps); + sb.embed_norm = x; + for (int i = 0; i < hp.enc_n_layers; ++i) { + x = build_block(ctx, hp, w.blocks[static_cast(i)], x, sb.positions); + if (want_layers) { + sb.layer_out.emplace_back(i, x); + } + } + x = layer_norm(ctx, x, w.final_norm_w, w.final_norm_b, hp.enc_ln_eps); + sb.final_norm = x; + + // frontend_encoder: encoder_proj 512 -> 192. + ggml_tensor * e = linear(ctx, w.enc_proj_w, x, w.enc_proj_b); // [hd, T_cat, B] + sb.enc_proj = e; + + // upsample_hidden: Conv1d(hd, hd*up, k=3, pad=1) over time, then + // (T, up, hd) -> (up*T, hd). im2col (F32) + mul_mat keeps channels in ne0. + ggml_tensor * e_t = ggml_cont(ctx, ggml_permute(ctx, e, 1, 0, 2, 3)); // [T_cat, hd, B] + ggml_tensor * cols = ggml_im2col(ctx, w.upsample_w, e_t, 1, 0, 1, 0, 1, 0, false, GGML_TYPE_F32); // [3*hd, T, B] + ggml_tensor * kern = + ggml_reshape_2d(ctx, w.upsample_w, w.upsample_w->ne[0] * w.upsample_w->ne[1], w.upsample_w->ne[2]); + ggml_tensor * c = ggml_mul_mat(ctx, kern, cols); // [hd*up, T_cat, B] + c = ggml_add(ctx, c, w.upsample_b); + sb.sub_conv = c; + // Channel block j of frame t is output frame up*t + j: a pure reshape. + ggml_tensor * u = ggml_reshape_3d(ctx, c, hd, up * T_cat, Bn); + sb.upsampled = u; + + // forward_speaker_logits: relu -> fc1 -> relu -> single_hidden_to_spks. + ggml_tensor * h = ggml_relu(ctx, u); + h = linear(ctx, w.fc1_w, h, w.fc1_b); + h = ggml_relu(ctx, h); + sb.logits = linear(ctx, w.spk_head_w, h, w.spk_head_b); // [n_spk, up*T_cat, B] + sb.probs = ggml_sigmoid(ctx, sb.logits); + + // Both are read back after compute: keep the allocator from reusing + // their buffers for later nodes. + ggml_set_output(sb.embs); + ggml_set_output(sb.probs); + + sb.graph = ggml_new_graph_custom(ctx, 16384, false); + ggml_build_forward_expand(sb.graph, sb.probs); + ggml_build_forward_expand(sb.graph, sb.embs); + return sb; +} + +transcribe_status ensure_sched(transcribe_session * s, Nemotron3DiarModel * pm) { + if (s->sched == nullptr) { + s->sched = ggml_backend_sched_new(pm->plan.scheduler_list.data(), nullptr, + static_cast(pm->plan.scheduler_list.size()), + /*graph_size=*/16384, /*parallel=*/false, /*op_offload=*/true); + if (s->sched == nullptr) { + return TRANSCRIBE_ERR_BACKEND; + } + } + return TRANSCRIBE_OK; +} + +// Chunk geometry of the next step (NeMo streaming_feat_loader). +struct ChunkGeom { + int64_t win_lo = 0, win_hi = 0, end = 0; + int lc = 0, rc = 0, T_diar = 0; +}; + +// Whether the next chunk of `u` can run: all of its mel window is final. +// feat_len < 0 while the stream is open (length unknown). +bool next_chunk(const Utterance & u, int sub, int64_t feat_len, ChunkGeom & g) { + const StreamParams & P = u.P; + if (feat_len >= 0) { + if (u.stt >= feat_len) { + return false; + } + const int64_t left = std::min(static_cast(P.chunk_left_context) * sub, u.stt); + g.end = std::min(u.stt + static_cast(P.chunk_len) * sub, feat_len); + const int64_t right = std::min(static_cast(P.chunk_right_context) * sub, feat_len - g.end); + g.win_lo = u.stt - left; + g.win_hi = g.end + right; + g.lc = static_cast((left + sub / 2) / sub); // round (left is a multiple of sub) + g.rc = static_cast((right + sub - 1) / sub); // ceil + } else { + const int64_t left = std::min(static_cast(P.chunk_left_context) * sub, u.stt); + g.end = u.stt + static_cast(P.chunk_len) * sub; + g.win_lo = u.stt - left; + g.win_hi = g.end + static_cast(P.chunk_right_context) * sub; + if (g.win_hi > u.mel_frames) { + return false; // window not complete yet + } + g.lc = static_cast((left + sub / 2) / sub); + g.rc = P.chunk_right_context; + } + g.T_diar = static_cast((g.win_hi - g.win_lo + sub - 1) / sub); + return true; +} + +// Run chunks for a group of utterances in lockstep (identical step +// geometry is required: same S, F, T_diar, lc, rc). `feat_len[i]` < 0 for an +// open stream. Returns after one step for every member. +transcribe_status run_step(Nemotron3DiarSession * pc, + Nemotron3DiarModel * pm, + const std::vector & group, + const std::vector & geo, + bool dump_encoder) { + const Nemotron3DiarHParams & hp = pm->hparams; + const Nemotron3DiarWeights & w = pm->weights; + const int d = hp.enc_d_model; + const int sub = hp.enc_subsampling_factor; + const int nm = hp.fe_num_mels; + const int ns = hp.max_speakers; + const int up = hp.upsample_factor; + const int Bn = static_cast(group.size()); + const int S = group[0]->st.spkcache_n; + const int F = group[0]->st.fifo_n; + const int Td = geo[0].T_diar; + const int Tc = S + F + Td; + + if (pc->compute_ctx != nullptr) { + ggml_free(pc->compute_ctx); + pc->compute_ctx = nullptr; + } + ggml_init_params ip{ 32 * 1024 * 1024, nullptr, /*no_alloc=*/true }; + pc->compute_ctx = ggml_init(ip); + if (pc->compute_ctx == nullptr) { + return TRANSCRIBE_ERR_BACKEND; + } + StepBuild sb = build_step_graph(pc->compute_ctx, hp, w, S + F, Td, Bn, dump_encoder); + if (dump_encoder) { + for (auto & lo : sb.layer_out) { + ggml_build_forward_expand(sb.graph, lo.second); + transcribe::debug::mark_tensor_for_dump(lo.second); + } + for (ggml_tensor * t : { sb.embed_norm, sb.final_norm, sb.enc_proj, sb.sub_conv, sb.upsampled, sb.logits }) { + ggml_build_forward_expand(sb.graph, t); + transcribe::debug::mark_tensor_for_dump(t); + } + transcribe::debug::mark_tensor_for_dump(sb.embs); + } + if (const transcribe_status st = ensure_sched(pc, pm); st != TRANSCRIBE_OK) { + return st; + } + ggml_backend_sched_reset(pc->sched); + if (!ggml_backend_sched_alloc_graph(pc->sched, sb.graph)) { + log_msg(TRANSCRIBE_LOG_LEVEL_ERROR, "nemotron3_diar: graph alloc failed (T_cat=%d, B=%d)", Tc, Bn); + return TRANSCRIBE_ERR_BACKEND; + } + + // Inputs: stacked chunk mel (zero-padded to a multiple of sub, like + // FeatureStacking's F.pad), previous cache/FIFO embeddings, positions. + const size_t row = static_cast(sub) * nm; + pc->step_mel.assign(row * Td * Bn, 0.0f); + for (int b = 0; b < Bn; ++b) { + const Utterance & u = *group[static_cast(b)]; + const int64_t M = geo[static_cast(b)].win_hi - geo[static_cast(b)].win_lo; + const float * src = u.mel_tm.data() + static_cast(geo[static_cast(b)].win_lo - u.mel_base) * nm; + std::copy(src, src + static_cast(M) * nm, pc->step_mel.begin() + row * Td * b); + } + ggml_backend_tensor_set(sb.mel_in, pc->step_mel.data(), 0, pc->step_mel.size() * sizeof(float)); + if (sb.prev_in != nullptr) { + pc->step_prev.resize(static_cast(S + F) * d * Bn); + for (int b = 0; b < Bn; ++b) { + const StreamState & st = group[static_cast(b)]->st; + float * dst = pc->step_prev.data() + static_cast(S + F) * d * b; + std::copy(st.spkcache.begin(), st.spkcache.begin() + static_cast(S) * d, dst); + std::copy(st.fifo.begin(), st.fifo.begin() + static_cast(F) * d, dst + static_cast(S) * d); + } + ggml_backend_tensor_set(sb.prev_in, pc->step_prev.data(), 0, pc->step_prev.size() * sizeof(float)); + } + pc->positions.resize(static_cast(Tc)); + for (int i = 0; i < Tc; ++i) { + pc->positions[static_cast(i)] = i; + } + ggml_backend_tensor_set(sb.positions, pc->positions.data(), 0, pc->positions.size() * sizeof(int32_t)); + + transcribe::configure_sched_n_threads(pc->sched, pc->n_threads); + if (ggml_backend_sched_graph_compute(pc->sched, sb.graph) != GGML_STATUS_SUCCESS) { + log_msg(TRANSCRIBE_LOG_LEVEL_ERROR, "nemotron3_diar: step compute failed"); + return TRANSCRIBE_ERR_BACKEND; + } + + if (dump_encoder) { + transcribe::debug::dump_tensor("enc.pre_encode.out", sb.embs, "encoder"); + transcribe::debug::dump_tensor("enc.embed_norm.out", sb.embed_norm, "encoder"); + for (auto & lo : sb.layer_out) { + char name[48]; + std::snprintf(name, sizeof(name), "enc.layers.%d.out", lo.first); + transcribe::debug::dump_tensor(name, lo.second, "encoder"); + } + transcribe::debug::dump_tensor("enc.final_norm.out", sb.final_norm, "encoder"); + transcribe::debug::dump_tensor("diar.encoder_proj.out", sb.enc_proj, "encoder"); + transcribe::debug::dump_tensor("diar.subpixel_conv.out", sb.sub_conv, "encoder"); + transcribe::debug::dump_tensor("diar.upsample.out", sb.upsampled, "encoder"); + transcribe::debug::dump_tensor("diar.logits", sb.logits, "encoder"); + } + + pc->step_embs.resize(static_cast(Td) * d * Bn); + ggml_backend_tensor_get(sb.embs, pc->step_embs.data(), 0, pc->step_embs.size() * sizeof(float)); + const size_t hi_per = static_cast(up) * Tc * ns; + pc->step_hi.resize(hi_per * Bn); + ggml_backend_tensor_get(sb.probs, pc->step_hi.data(), 0, pc->step_hi.size() * sizeof(float)); + + // Host update per utterance. + std::vector embs_b, pooled(static_cast(Tc) * ns); + for (int b = 0; b < Bn; ++b) { + Utterance & u = *group[static_cast(b)]; + const ChunkGeom & g = geo[static_cast(b)]; + const float * hi = pc->step_hi.data() + hi_per * b; + + // downsample_preds(hi, up): avg_pool1d(kernel=stride=up); T_cat*up + // is an exact multiple so every window is full. + for (int t = 0; t < Tc; ++t) { + for (int s = 0; s < ns; ++s) { + float acc = 0.0f; + for (int j = 0; j < up; ++j) { + acc += hi[(static_cast(t) * up + j) * ns + s]; + } + pooled[static_cast(t) * ns + s] = acc / static_cast(up); + } + } + embs_b.assign(pc->step_embs.begin() + static_cast(Td) * d * b, + pc->step_embs.begin() + static_cast(Td) * d * (b + 1)); + + const int S0 = u.st.spkcache_n; + const int F0 = u.st.fifo_n; + const int C = Td - g.lc - g.rc; + streaming_update(u.st, u.P, ns, d, embs_b, Td, pooled, g.lc, g.rc, pm->sil_emb_host); + + // Chunk output at 10 ms: hi[(S+F+lc)*up : (S+F+lc+C)*up]. + const size_t off = static_cast(S0 + F0 + g.lc) * up * ns; + u.st.total_preds.insert(u.st.total_preds.end(), hi + off, hi + off + static_cast(C) * up * ns); + u.st.total_n += C * up; + + u.stt = g.end; + ++u.chunk_idx; + // Drop consumed mel (lc = 0 for every preset; keep lc context anyway). + const int64_t keep_from = + std::max(u.mel_base, u.stt - static_cast(u.P.chunk_left_context) * sub); + if (keep_from > u.mel_base) { + u.mel_tm.erase(u.mel_tm.begin(), u.mel_tm.begin() + static_cast(keep_from - u.mel_base) * nm); + u.mel_base = keep_from; + } + } + return TRANSCRIBE_OK; +} + +// Drive one utterance forward over every runnable chunk. +transcribe_status advance_chunks(Nemotron3DiarSession * pc, Nemotron3DiarModel * pm, Utterance & u, bool final_) { + const int sub = pm->hparams.enc_subsampling_factor; + const int64_t feat_len = final_ ? u.mel_frames : -1; + ChunkGeom g; + while (next_chunk(u, sub, feat_len, g)) { + if (pc->poll_abort()) { + return TRANSCRIBE_ERR_ABORTED; + } + const bool dump_enc = + u.chunk_idx == 0 && transcribe::debug::enabled() && env_set("TRANSCRIBE_NEMOTRON3_DIAR_ENCODER_DUMP"); + std::vector grp{ &u }; + std::vector geo{ g }; + if (const transcribe_status st = run_step(pc, pm, grp, geo, dump_enc); st != TRANSCRIBE_OK) { + return st; + } + } + if (final_) { + // NeMo forward_streaming: total_preds[:, :ceil(feat_len / output_subsampling_factor)]. + const int64_t keep = std::min(u.st.total_n, u.mel_frames); + u.st.total_preds.resize(static_cast(keep) * pm->hparams.max_speakers); + u.st.total_n = static_cast(keep); + u.done = true; + } + return TRANSCRIBE_OK; +} + +double ms_per_output_frame(const Nemotron3DiarHParams & hp) { + return 1000.0 * static_cast(hp.output_hop) / static_cast(hp.fe_sample_rate); +} + +void dump_probs(const Utterance & u, int n_spk) { + if (!transcribe::debug::enabled()) { + return; + } + const long long shape[2] = { u.st.total_n, n_spk }; + transcribe::debug::dump_host_f32("diar.probs", u.st.total_preds.data(), + static_cast(u.st.total_n) * n_spk, shape, 2, "diarize"); +} + +// Append mel frames [0, n_valid) of a row-major [n_mels, stride] buffer as +// time-major rows to u.mel_tm, starting at column `col0`. +void append_mel(Utterance & u, const std::vector & mel, int n_mels, int stride, int col0, int n) { + const size_t old = u.mel_tm.size(); + u.mel_tm.resize(old + static_cast(n) * n_mels); + float * dst = u.mel_tm.data() + old; + for (int t = 0; t < n; ++t) { + for (int m = 0; m < n_mels; ++m) { + dst[static_cast(t) * n_mels + m] = mel[static_cast(m) * stride + col0 + t]; + } + } + u.mel_frames += n; +} + +transcribe_status resolve_run_preset(const transcribe_run_params * params, transcribe_nemotron3_diar_preset & out) { + out = TRANSCRIBE_NEMOTRON3_DIAR_PRESET_DEFAULT; + if (params == nullptr || params->family == nullptr) { + return TRANSCRIBE_OK; + } + if (const transcribe_status st = transcribe_ext_check(params->family, TRANSCRIBE_EXT_KIND_NEMOTRON3_DIAR_RUN, + sizeof(struct transcribe_nemotron3_diar_run_ext)); + st != TRANSCRIBE_OK) { + return st; + } + out = reinterpret_cast(params->family)->preset; + return preset_is_valid(out) ? TRANSCRIBE_OK : TRANSCRIBE_ERR_INVALID_ARG; +} + +transcribe_status resolve_stream_preset(const transcribe_stream_params * sp, transcribe_nemotron3_diar_preset & out) { + out = TRANSCRIBE_NEMOTRON3_DIAR_PRESET_DEFAULT; + if (sp == nullptr || sp->family == nullptr) { + return TRANSCRIBE_OK; + } + if (const transcribe_status st = transcribe_ext_check(sp->family, TRANSCRIBE_EXT_KIND_NEMOTRON3_DIAR_STREAM, + sizeof(struct transcribe_nemotron3_diar_stream_ext)); + st != TRANSCRIBE_OK) { + return st; + } + out = reinterpret_cast(sp->family)->preset; + return preset_is_valid(out) ? TRANSCRIBE_OK : TRANSCRIBE_ERR_INVALID_ARG; +} + +} // namespace + +// ---- load ---- + +transcribe_status load(Loader & loader, const transcribe_model_load_params * params, transcribe_model ** out_model) { + const int64_t t_load_start = ggml_time_us(); + + auto m = std::make_unique(); + m->arch = &arch; + m->variant = loader.variant().empty() ? k_default_variant : loader.variant(); + m->backend.clear(); + + apply_family_invariants(*m); + m->caps.n_languages = 0; + m->caps.languages = nullptr; + if (const transcribe_status st = read_capability_kv(loader.gguf(), m->caps); st != TRANSCRIBE_OK) { + return st; + } + if (const transcribe_status st = read_languages_kv(loader.gguf(), *m); st != TRANSCRIBE_OK) { + return st; + } + if (const transcribe_status st = read_hparams(loader.gguf(), m->hparams); st != TRANSCRIBE_OK) { + return st; + } + const Nemotron3DiarHParams & hp = m->hparams; + + gguf_init_params init_params{}; + init_params.no_alloc = true; + init_params.ctx = &m->ctx_meta; + gguf_context * gguf_data = gguf_init_from_file(loader.path().c_str(), init_params); + if (gguf_data == nullptr) { + return TRANSCRIBE_ERR_GGUF; + } + + struct GgufGuard { + gguf_context * g; + + ~GgufGuard() { gguf_free(g); } + } guard{ gguf_data }; + + if (const transcribe_status st = build_weights(m->ctx_meta, hp, m->weights); st != TRANSCRIBE_OK) { + return st; + } + + // Mel frontend with the checkpoint's BF16-stored window + filterbank + // (NeMo's reference mel uses them; an fp32 recompute differs by 1.9e-3). + { + transcribe::MelConfig cfg{}; + cfg.sample_rate = hp.fe_sample_rate; + cfg.num_mels = hp.fe_num_mels; + cfg.n_fft = hp.fe_n_fft; + cfg.win_length = hp.fe_win_length; + cfg.hop_length = hp.fe_hop_length; + cfg.pre_emphasis = hp.fe_pre_emphasis; + cfg.normalize = hp.fe_normalize; + cfg.pad_mode = "constant"; + cfg.window_type = "hann_symmetric"; + namespace lc = transcribe::load_common; + if (lc::read_f32_tensor_checked(gguf_data, loader.path(), "frontend.window", + static_cast(hp.fe_win_length), "nemotron3_diar", + cfg.window) != lc::ReadF32Result::Ok || + lc::read_f32_tensor_checked(gguf_data, loader.path(), "frontend.mel_filterbank", + static_cast(hp.fe_num_mels) * (hp.fe_n_fft / 2 + 1), "nemotron3_diar", + cfg.filterbank) != lc::ReadF32Result::Ok) { + return TRANSCRIBE_ERR_GGUF; + } + m->mel.emplace(cfg); + } + + const transcribe_backend_request backend_req = (params != nullptr) ? params->backend : TRANSCRIBE_BACKEND_AUTO; + if (const transcribe_status st = transcribe::load_common::init_backends( + backend_req, (params != nullptr) ? params->device : nullptr, "nemotron3_diar", m->plan); + st != TRANSCRIBE_OK) { + return st; + } + m->backend = ggml_backend_name(m->plan.primary); + m->primary_backend = m->plan.primary; + + m->backend_buffer = ggml_backend_alloc_ctx_tensors(m->ctx_meta, m->plan.primary); + if (m->backend_buffer == nullptr) { + log_msg(TRANSCRIBE_LOG_LEVEL_ERROR, "nemotron3_diar: ggml_backend_alloc_ctx_tensors failed"); + return TRANSCRIBE_ERR_GGUF; + } + ggml_backend_buffer_set_usage(m->backend_buffer, GGML_BACKEND_BUFFER_USAGE_WEIGHTS); + if (const transcribe_status st = + transcribe::load_common::stream_tensor_data(loader.path(), gguf_data, m->ctx_meta, "nemotron3_diar"); + st != TRANSCRIBE_OK) { + return st; + } + + // Host copy of the learned AOSC silence embedding (compress runs on host). + m->sil_emb_host.resize(static_cast(hp.enc_d_model)); + ggml_backend_tensor_get(m->weights.sil_emb, m->sil_emb_host.data(), 0, m->sil_emb_host.size() * sizeof(float)); + + // F32 promotion of the BF16 / F16 matmul weights on CPU (exact upcast). + // Quantized weights (Q8_0, Q*_K) are left alone: they are lossy by design + // and run on ggml's native quantized kernels, so an upcast would only + // discard their memory / speed benefit. ggml-cpu rounds the F32 + // activations to the weight's dtype inside each BF16 / F16 dot product; + // the reference computes in fp32 over + // the same BF16-exact weights. That rounding is not benign here: the AOSC + // cache compression is a top-k selection, so a 1e-3 probability shift + // flips picks and every later chunk diverges (measured on the 8-speaker + // oracle: BF16 compute flips 43 picks at compression #1, probs drift + // 3.2e-2; F32 compute keeps every pick, probs 1.0e-5 over 92 s). ggml's + // BF16 CPU matmul is also ~7x slower than F32 here. Costs ~400 MB RAM. + // TRANSCRIBE_NEMOTRON3_DIAR_NATIVE_BF16=1 opts out on CPU; + // TRANSCRIBE_NEMOTRON3_DIAR_F32_WEIGHTS=1 forces it on other backends. + { + std::vector slots; + const bool on_cpu = m->plan.primary_kind == transcribe::BackendKind::Cpu; + const bool promote = env_set("TRANSCRIBE_NEMOTRON3_DIAR_F32_WEIGHTS") || + (on_cpu && !env_set("TRANSCRIBE_NEMOTRON3_DIAR_NATIVE_BF16")); + if (on_cpu && m->weights.upsample_w->type != GGML_TYPE_F32) { + slots.push_back(&m->weights.upsample_w); // F16 conv kernel: always on CPU + } + if (promote) { + auto add = [&](ggml_tensor ** t) { + const bool half = (*t)->type == GGML_TYPE_BF16 || (*t)->type == GGML_TYPE_F16; + if (half && std::find(slots.begin(), slots.end(), t) == slots.end()) { + slots.push_back(t); + } + }; + add(&m->weights.pre_encode_proj_w); + for (auto & b : m->weights.blocks) { + add(&b.attn_qkv_w); + add(&b.attn_out_w); + add(&b.ff_in_w); + add(&b.ff_out_w); + } + add(&m->weights.enc_proj_w); + add(&m->weights.upsample_w); + add(&m->weights.fc1_w); + add(&m->weights.spk_head_w); + } + if (const transcribe_status st = promote_to_f32(slots, m->plan.primary, &m->f32_ctx, &m->f32_buffer); + st != TRANSCRIBE_OK) { + return st; + } + } + + m->t_load_us = ggml_time_us() - t_load_start; + *out_model = m.release(); + return TRANSCRIBE_OK; +} + +transcribe_status init_context(transcribe_model * model, + const transcribe_session_params * params, + transcribe_session ** out_ctx) { + if (model->arch != &arch) { + return TRANSCRIBE_ERR_INVALID_ARG; + } + auto pc = std::make_unique(); + pc->model = model; + pc->n_threads = params->n_threads; + pc->kv_type = params->kv_type; + *out_ctx = pc.release(); + return TRANSCRIBE_OK; +} + +// ---- transcribe_run: whole recording ---- + +transcribe_status run(transcribe_session * session, + const float * pcm, + int n_samples, + const transcribe_run_params * params) { + auto * pc = static_cast(session); + auto * pm = static_cast(session->model); + const Nemotron3DiarHParams & hp = pm->hparams; + + if (pc->poll_abort()) { + return TRANSCRIBE_ERR_ABORTED; + } + transcribe_nemotron3_diar_preset preset; + if (const transcribe_status st = resolve_run_preset(params, preset); st != TRANSCRIBE_OK) { + return st; + } + pc->clear_result(); + transcribe::debug::init(); + + // Fewer than one hop of audio: NeMo emits floor(n / hop) = 0 frames, an + // empty (not failed) result. Same for push-audio finalize and batch. + if (n_samples < hp.fe_hop_length) { + pc->result_kind = TRANSCRIBE_TIMESTAMPS_NONE; + pc->has_result = true; + return TRANSCRIBE_OK; + } + + const int64_t t_mel0 = ggml_time_us(); + int n_mels = 0; + int n_frames = 0; + if (const transcribe_status mst = + pm->mel->compute(pcm, static_cast(n_samples), pc->mel_buf, n_mels, n_frames, pc->n_threads); + mst != TRANSCRIBE_OK) { + return mst; + } + // NeMo get_seq_len: floor(n / hop) valid frames (the STFT's trailing + // center frame is dropped). + const int valid = std::min(n_frames, n_samples / hp.fe_hop_length); + pc->t_mel_us = ggml_time_us() - t_mel0; + + Utterance & u = pc->utt; + u = Utterance{}; + u.P = resolve_stream_params(hp, preset); + u.seg.reset(hp.max_speakers); + append_mel(u, pc->mel_buf, n_mels, n_frames, 0, valid); + + if (transcribe::debug::enabled()) { + std::vector mel_valid(static_cast(n_mels) * valid); + for (int mm = 0; mm < n_mels; ++mm) { + std::copy(pc->mel_buf.begin() + static_cast(mm) * n_frames, + pc->mel_buf.begin() + static_cast(mm) * n_frames + valid, + mel_valid.begin() + static_cast(mm) * valid); + } + const long long shape[2] = { n_mels, valid }; + transcribe::debug::dump_host_f32("enc.mel.in", mel_valid.data(), static_cast(mel_valid.size()), + shape, 2, "frontend"); + } + + const int64_t t_enc0 = ggml_time_us(); + if (const transcribe_status st = advance_chunks(pc, pm, u, /*final_=*/true); st != TRANSCRIBE_OK) { + return st; + } + pc->t_encode_us = ggml_time_us() - t_enc0; + + dump_probs(u, hp.max_speakers); + const double ms = ms_per_output_frame(hp); + segments_advance(u.seg, u.st.total_preds.data(), u.st.total_n, hp.max_speakers, ms); + segments_close_all(u.seg, ms); + segments_publish(u.seg, pc, ms); + + pc->result_kind = TRANSCRIBE_TIMESTAMPS_NONE; + pc->has_result = true; + return TRANSCRIBE_OK; +} + +// ---- transcribe_run_batch: lockstep streaming over N recordings ---- +// +// In sync streaming the cache / FIFO lengths after step k are a function of +// k and the preset only (compression always lands at spkcache_len), so every +// recording's k-th step has the same [spkcache | fifo | chunk] geometry +// until its tail chunk. Each round groups the runnable recordings by step +// geometry and runs one batched graph per group (ggml batch axis ne[2]); +// tail chunks of different lengths split into their own groups. Every +// batched op is per-row, so a recording's output is bit-identical to its +// serial run. + +transcribe_status run_batch(transcribe_session * session, + const float * const * pcm, + const int * n_samples, + int n, + const transcribe_run_params * params) { + auto * pc = static_cast(session); + auto * pm = static_cast(session->model); + const Nemotron3DiarHParams & hp = pm->hparams; + const int sub = hp.enc_subsampling_factor; + + transcribe_nemotron3_diar_preset preset; + if (const transcribe_status st = resolve_run_preset(params, preset); st != TRANSCRIBE_OK) { + return st; + } + transcribe::debug::init(); + const StreamParams P = resolve_stream_params(hp, preset); + + // Per-recording frontend. A recording whose mel fails keeps its status + // and is excluded from the lockstep loop. + std::vector utts(static_cast(n)); + std::vector status(static_cast(n), TRANSCRIBE_OK); + std::vector t_mel(static_cast(n), 0); + for (int i = 0; i < n; ++i) { + Utterance & u = utts[static_cast(i)]; + u.P = P; + u.seg.reset(hp.max_speakers); + const int64_t t0 = ggml_time_us(); + int n_mels = 0; + int n_frames = 0; + if (pcm[i] == nullptr || n_samples[i] < 0) { + status[static_cast(i)] = TRANSCRIBE_ERR_INVALID_ARG; + } else if (n_samples[i] < hp.fe_hop_length) { + u.done = true; // 0 frames: empty result, like run() + continue; + } else { + status[static_cast(i)] = pm->mel->compute(pcm[i], static_cast(n_samples[i]), pc->mel_buf, + n_mels, n_frames, pc->n_threads); + } + if (status[static_cast(i)] != TRANSCRIBE_OK) { + u.done = true; + continue; + } + const int valid = std::min(n_frames, n_samples[i] / hp.fe_hop_length); + append_mel(u, pc->mel_buf, n_mels, n_frames, 0, valid); + t_mel[static_cast(i)] = ggml_time_us() - t0; + } + + // Lockstep chunk loop. + const int64_t t_enc0 = ggml_time_us(); + for (;;) { + if (pc->poll_abort()) { + return TRANSCRIBE_ERR_ABORTED; + } + + struct Pending { + Utterance * u; + ChunkGeom g; + }; + + std::vector ready; + for (Utterance & u : utts) { + ChunkGeom g; + if (!u.done && next_chunk(u, sub, u.mel_frames, g)) { + ready.push_back({ &u, g }); + } + } + if (ready.empty()) { + break; + } + std::vector taken(ready.size(), false); + for (size_t a = 0; a < ready.size(); ++a) { + if (taken[a]) { + continue; + } + std::vector grp; + std::vector geo; + for (size_t b = a; b < ready.size(); ++b) { + const ChunkGeom & ga = ready[a].g; + const ChunkGeom & gb = ready[b].g; + if (taken[b] || gb.T_diar != ga.T_diar || gb.lc != ga.lc || gb.rc != ga.rc || + ready[b].u->st.spkcache_n != ready[a].u->st.spkcache_n || + ready[b].u->st.fifo_n != ready[a].u->st.fifo_n) { + continue; + } + taken[b] = true; + grp.push_back(ready[b].u); + geo.push_back(gb); + } + if (const transcribe_status st = run_step(pc, pm, grp, geo, /*dump_encoder=*/false); st != TRANSCRIBE_OK) { + return st; + } + } + } + const int64_t t_enc = ggml_time_us() - t_enc0; + + // Per-recording results, in order. + const double ms = ms_per_output_frame(hp); + int n_ok = 0; + for (int i = 0; i < n; ++i) { + n_ok += status[static_cast(i)] == TRANSCRIBE_OK ? 1 : 0; + } + for (int i = 0; i < n; ++i) { + Utterance & u = utts[static_cast(i)]; + pc->clear_result(); + if (status[static_cast(i)] == TRANSCRIBE_OK) { + const int64_t keep = std::min(u.st.total_n, u.mel_frames); + u.st.total_preds.resize(static_cast(keep) * hp.max_speakers); + u.st.total_n = static_cast(keep); + if (transcribe::debug::enabled()) { + // Per-recording probs for scripts/batch_tensor_parity.py + // (--dump-name diar.probs): must equal the serial diar.probs. + char name[48]; + const long long shape[2] = { u.st.total_n, hp.max_speakers }; + std::snprintf(name, sizeof(name), "diar.probs.b%d", i); + transcribe::debug::dump_host_f32(name, u.st.total_preds.data(), + static_cast(u.st.total_n) * hp.max_speakers, shape, 2, + "diarize"); + } + segments_advance(u.seg, u.st.total_preds.data(), u.st.total_n, hp.max_speakers, ms); + segments_close_all(u.seg, ms); + segments_publish(u.seg, pc, ms); + pc->result_kind = TRANSCRIBE_TIMESTAMPS_NONE; + pc->has_result = true; + pc->t_mel_us = t_mel[static_cast(i)]; + pc->t_encode_us = n_ok > 0 ? t_enc / n_ok : 0; + } + pc->batch_results.push_back(pc->capture_result(status[static_cast(i)])); + } + // Leave the scratch slot mirroring batch_results[0]. + if (!pc->batch_results.empty()) { + const auto & r0 = pc->batch_results.front(); + pc->clear_result(); + pc->speaker_segments = r0.speaker_segments; + pc->result_kind = r0.result_kind; + pc->has_result = r0.has_result; + pc->t_mel_us = r0.t_mel_us; + pc->t_encode_us = r0.t_encode_us; + } + return TRANSCRIBE_OK; +} + +// ---- push-audio streaming ---- + +namespace { + +// Compute every mel frame that is final given the PCM received so far +// (frame t needs samples up to t*hop + n_fft/2; at finalize, all +// floor(n/hop) frames) and append it to the utterance. Frames are computed +// from a PCM segment starting 2 hops before the first new frame, so the +// STFT window and the pre-emphasis predecessor of every emitted frame lie +// inside the segment: normalize=none makes frames independent, so this is +// exactly the whole-utterance mel. +transcribe_status stream_mel(Nemotron3DiarSession * pc, Nemotron3DiarModel * pm, bool final_) { + const Nemotron3DiarHParams & hp = pm->hparams; + const int hop = hp.fe_hop_length; + Utterance & u = pc->utt; + const int64_t n = pc->pcm_received; + + const int64_t total_valid = n / hop; + // Frame t is final once its right STFT half-window (t*hop + n_fft/2) has + // arrived; at finalize every floor(n/hop) frame is (NeMo zero-pads). + int64_t b = total_valid; + if (!final_) { + const int64_t half = hp.fe_n_fft / 2; + b = n < half ? 0 : std::min(total_valid, (n - half) / hop + 1); + } + const int64_t a = u.mel_frames; + if (b <= a) { + return TRANSCRIBE_OK; + } + const int64_t s0 = std::max(0, (a - 2) * hop); + if (s0 < pc->pcm_tail_base) { + return TRANSCRIBE_ERR_INVALID_ARG; // internal invariant + } + const float * seg = pc->pcm_tail.data() + (s0 - pc->pcm_tail_base); + const size_t nseg = static_cast(n - s0); + int n_mels = 0, n_frames = 0; + if (const transcribe_status st = pm->mel->compute(seg, nseg, pc->seg_mel, n_mels, n_frames, pc->n_threads); + st != TRANSCRIBE_OK) { + return st; + } + const int col0 = static_cast(a - s0 / hop); + append_mel(u, pc->seg_mel, n_mels, n_frames, col0, static_cast(b - a)); + + // Keep PCM from the next segment start. + const int64_t next_s0 = std::max(0, (u.mel_frames - 2) * hop); + if (next_s0 > pc->pcm_tail_base) { + pc->pcm_tail.erase(pc->pcm_tail.begin(), pc->pcm_tail.begin() + (next_s0 - pc->pcm_tail_base)); + pc->pcm_tail_base = next_s0; + } + return TRANSCRIBE_OK; +} + +void fill_update(Nemotron3DiarSession * pc, + const Nemotron3DiarModel * pm, + transcribe_stream_update * update, + bool changed) { + const int64_t in_ms = pc->pcm_received * 1000 / pm->hparams.fe_sample_rate; + const int64_t done_ms = + static_cast(pc->utt.st.total_n) * pm->hparams.output_hop * 1000 / pm->hparams.fe_sample_rate; + pc->stream_audio_input_us = in_ms * 1000; + pc->stream_audio_committed_us = done_ms * 1000; + if (update != nullptr) { + update->result_changed = update->result_changed || changed; + update->input_received_ms = in_ms; + update->audio_committed_ms = done_ms; + update->buffered_ms = std::max(0, in_ms - done_ms); + } +} + +} // namespace + +transcribe_status stream_validate(const transcribe_session * /*ctx*/, + const transcribe_run_params * /*run_params*/, + const transcribe_stream_params * stream_params) { + transcribe_nemotron3_diar_preset preset; + return resolve_stream_preset(stream_params, preset); +} + +transcribe_status stream_begin(transcribe_session * session, + const transcribe_run_params * /*run_params*/, + const transcribe_stream_params * stream_params) { + auto * pc = static_cast(session); + auto * pm = static_cast(session->model); + transcribe_nemotron3_diar_preset preset; + if (const transcribe_status st = resolve_stream_preset(stream_params, preset); st != TRANSCRIBE_OK) { + return st; + } + transcribe::debug::init(); + pc->utt = Utterance{}; + pc->utt.P = resolve_stream_params(pm->hparams, preset); + pc->utt.seg.reset(pm->hparams.max_speakers); + pc->pcm_tail.clear(); + pc->pcm_tail_base = 0; + pc->pcm_received = 0; + pc->result_kind = TRANSCRIBE_TIMESTAMPS_NONE; + pc->has_result = true; + return TRANSCRIBE_OK; +} + +transcribe_status stream_feed(transcribe_session * session, + const float * pcm, + int n_samples, + transcribe_stream_update * update) { + auto * pc = static_cast(session); + auto * pm = static_cast(session->model); + if (pc->poll_abort()) { + return TRANSCRIBE_ERR_ABORTED; + } + pc->pcm_tail.insert(pc->pcm_tail.end(), pcm, pcm + n_samples); + pc->pcm_received += n_samples; + + const int before = pc->utt.st.total_n; + if (const transcribe_status st = stream_mel(pc, pm, /*final_=*/false); st != TRANSCRIBE_OK) { + return st; + } + if (const transcribe_status st = advance_chunks(pc, pm, pc->utt, /*final_=*/false); st != TRANSCRIBE_OK) { + return st; + } + const bool changed = pc->utt.st.total_n != before; + if (changed) { + const double ms = ms_per_output_frame(pm->hparams); + segments_advance(pc->utt.seg, pc->utt.st.total_preds.data(), pc->utt.st.total_n, pm->hparams.max_speakers, ms); + segments_publish(pc->utt.seg, pc, ms); + } + fill_update(pc, pm, update, changed); + return TRANSCRIBE_OK; +} + +transcribe_status stream_finalize(transcribe_session * session, transcribe_stream_update * update) { + auto * pc = static_cast(session); + auto * pm = static_cast(session->model); + const Nemotron3DiarHParams & hp = pm->hparams; + + if (const transcribe_status st = stream_mel(pc, pm, /*final_=*/true); st != TRANSCRIBE_OK) { + return st; + } + if (const transcribe_status st = advance_chunks(pc, pm, pc->utt, /*final_=*/true); st != TRANSCRIBE_OK) { + return st; + } + // The final trim can drop frames the tracker already consumed past the + // true length (a partial last chunk); rebuild from the trimmed probs. + const double ms = ms_per_output_frame(hp); + pc->utt.seg.reset(hp.max_speakers); + segments_advance(pc->utt.seg, pc->utt.st.total_preds.data(), pc->utt.st.total_n, hp.max_speakers, ms); + segments_close_all(pc->utt.seg, ms); + segments_publish(pc->utt.seg, pc, ms); + dump_probs(pc->utt, hp.max_speakers); + fill_update(pc, pm, update, true); + return TRANSCRIBE_OK; +} + +void stream_reset(transcribe_session * session) { + auto * pc = static_cast(session); + pc->utt = Utterance{}; + pc->pcm_tail.clear(); + pc->pcm_tail_base = 0; + pc->pcm_received = 0; +} + +// ---- extension surface ---- + +static bool accepts_ext_kind(const transcribe_model * model, transcribe_ext_slot slot, uint32_t kind) { + if (model == nullptr) { + return false; + } + if (slot == TRANSCRIBE_EXT_SLOT_RUN) { + return kind == TRANSCRIBE_EXT_KIND_NEMOTRON3_DIAR_RUN; + } + if (slot == TRANSCRIBE_EXT_SLOT_STREAM) { + return kind == TRANSCRIBE_EXT_KIND_NEMOTRON3_DIAR_STREAM; + } + return false; +} + +static transcribe_status run_validate(const transcribe_session * /*ctx*/, const transcribe_run_params * params) { + transcribe_nemotron3_diar_preset preset; + return resolve_run_preset(params, preset); +} + +extern const Arch arch = { + /* .name = */ "nemotron3_diar", + /* .load = */ load, + /* .init_context = */ init_context, + /* .run = */ run, + /* .run_batch = */ run_batch, + /* .stream_validate = */ stream_validate, + /* .stream_begin = */ stream_begin, + /* .stream_feed = */ stream_feed, + /* .stream_finalize = */ stream_finalize, + /* .stream_reset = */ stream_reset, + /* .accepts_ext_kind = */ accepts_ext_kind, + /* .run_validate = */ run_validate, +}; + +} // namespace transcribe::nemotron3_diar + +extern "C" void transcribe_nemotron3_diar_run_ext_init(struct transcribe_nemotron3_diar_run_ext * p) { + if (p == nullptr) { + return; + } + std::memset(p, 0, sizeof(*p)); + p->ext.size = sizeof(*p); + p->ext.kind = TRANSCRIBE_EXT_KIND_NEMOTRON3_DIAR_RUN; + p->preset = TRANSCRIBE_NEMOTRON3_DIAR_PRESET_DEFAULT; +} + +extern "C" void transcribe_nemotron3_diar_stream_ext_init(struct transcribe_nemotron3_diar_stream_ext * p) { + if (p == nullptr) { + return; + } + std::memset(p, 0, sizeof(*p)); + p->ext.size = sizeof(*p); + p->ext.kind = TRANSCRIBE_EXT_KIND_NEMOTRON3_DIAR_STREAM; + p->preset = TRANSCRIBE_NEMOTRON3_DIAR_PRESET_DEFAULT; +} diff --git a/src/arch/nemotron3_diar/nemotron3_diar.h b/src/arch/nemotron3_diar/nemotron3_diar.h new file mode 100644 index 00000000..e731d571 --- /dev/null +++ b/src/arch/nemotron3_diar/nemotron3_diar.h @@ -0,0 +1,194 @@ +// arch/nemotron3_diar/nemotron3_diar.h - Nemotron-3-Diarization internal +// model / session types. An `encoder-diarizer` in the Streaming Sortformer +// lineage: feature stacking x8 -> 31-layer pre-LN RoPE Transformer over the +// [speaker cache | FIFO | chunk] concat -> encoder_proj -> x8 subpixel +// upsampler -> sigmoid head, producing T x 8 speaker activity at 10 ms. No +// tokenizer, no decoder, no text. +// +// The product path is always NeMo's synchronous streaming loop (AOSC speaker +// cache + FIFO at 80 ms, driven by the x8 average-pooled probabilities); +// transcribe_run, push-audio streaming and run_batch all drive the same +// per-chunk step (model.cpp) and host state machine (stream.cpp). + +#pragma once + +#include "transcribe-backend.h" +#include "transcribe-mel.h" +#include "transcribe-model.h" +#include "transcribe-session.h" +#include "transcribe/nemotron3_diar.h" +#include "weights.h" + +#include +#include +#include + +struct ggml_context; +struct ggml_backend_buffer; +typedef struct ggml_backend_buffer * ggml_backend_buffer_t; + +namespace transcribe::nemotron3_diar { + +// Family defaults, applied before transcribe::read_capability_kv. +void apply_family_invariants(transcribe_model & model); + +struct Nemotron3DiarModel final : public transcribe_model { + Nemotron3DiarHParams hparams; + Nemotron3DiarWeights weights; + + ggml_context * ctx_meta = nullptr; + transcribe::BackendPlan plan; + ggml_backend_buffer_t backend_buffer = nullptr; + + // F32 copies of the BF16 / F16 matmul weights (CPU default; see load()), + // so every matmul runs with F32 activations like the fp32-compute + // reference. The original tensors stay resident in backend_buffer. + ggml_context * f32_ctx = nullptr; + ggml_backend_buffer_t f32_buffer = nullptr; + + // Host copy of the learned AOSC silence embedding (compress runs on host). + std::vector sil_emb_host; + + std::optional mel; + + Nemotron3DiarModel() = default; + ~Nemotron3DiarModel() override; + + const transcribe::Tokenizer * tokenizer() const override { return nullptr; } +}; + +// Streaming operating point. Lengths in 80 ms encoder frames. +struct StreamParams { + int chunk_len = 340; + int chunk_left_context = 0; + int chunk_right_context = 40; + int fifo_len = 40; + int spkcache_len = 264; + int spkcache_update_period = 300; + int spkcache_sil_frames_per_spk = 1; + float pred_score_threshold = 0.25f; + float scores_boost_latest = 0.05f; + float strong_boost_rate = 0.75f; + float weak_boost_rate = 1.5f; + float min_pos_scores_rate = 0.5f; + int max_index = 99999; +}; + +// Resolve the operating point, lowest to highest precedence: GGUF-shipped +// default < public preset (DEFAULT keeps the GGUF cfg) < +// TRANSCRIBE_NEMOTRON3_DIAR_PRESET env (card names + "small"; validation +// hook). Defined in stream.cpp. +StreamParams resolve_stream_params(const Nemotron3DiarHParams & hp, transcribe_nemotron3_diar_preset preset); + +// True when `preset` is a valid enum value. +bool preset_is_valid(transcribe_nemotron3_diar_preset preset); + +// Host-side AOSC speaker cache + FIFO (NeMo StreamingSortformerState, sync, +// batch 1). Embeddings row-major [n, emb_dim] (512-dim pre_encode outputs); +// preds row-major [n, n_spk] at 80 ms. +struct StreamState { + std::vector spkcache; + std::vector spkcache_preds; + int spkcache_n = 0; + bool compressed = false; + std::vector fifo; + std::vector fifo_preds; + int fifo_n = 0; + int compress_count = 0; + + // Accumulated chunk outputs at 10 ms, row-major [total_n, n_spk]. + std::vector total_preds; + int total_n = 0; + + void reset() { + spkcache.clear(); + spkcache_preds.clear(); + spkcache_n = 0; + compressed = false; + fifo.clear(); + fifo_preds.clear(); + fifo_n = 0; + compress_count = 0; + total_preds.clear(); + total_n = 0; + } +}; + +// NeMo streaming_update (sync branch, use_learnable_sil_emb=True). preds80 is +// the x8 average-pooled prediction over the whole [spkcache|fifo|chunk] +// concat ([T_concat, n_spk]); chunk_embs is the chunk's pre_encode output +// ([T_diar, emb_dim]). Does NOT touch total_preds (the caller appends the +// 10 ms chunk slice). Defined in stream.cpp. +void streaming_update(StreamState & st, + const StreamParams & p, + int n_spk, + int emb_dim, + const std::vector & chunk_embs, + int T_diar, + const std::vector & preds80, + int lc, + int rc, + const std::vector & sil_emb); + +// Incremental speaker-segment builder over 10 ms probs (threshold 0.5). +// Closed runs are final; open runs are reported up to the last frame seen. +struct SegmentTracker { + std::vector run_start; // per speaker, -1 = inactive + std::vector closed; + int n_seen = 0; + + void reset(int n_spk) { + run_start.assign(static_cast(n_spk), -1); + closed.clear(); + n_seen = 0; + } +}; + +// Consume frames [tr.n_seen, T) of probs (row-major [*, n_spk]). +void segments_advance(SegmentTracker & tr, const float * probs, int T, int n_spk, double ms_per_frame); +// Close every open run at frame tr.n_seen. +void segments_close_all(SegmentTracker & tr, double ms_per_frame); +// Rebuild session->speaker_segments = closed + open-to-now, ordered by +// (speaker, t0) like the offline path. +void segments_publish(const SegmentTracker & tr, transcribe_session * session, double ms_per_frame); + +// Per-utterance streaming run: operating point, cache state, pending mel, +// output tracking. Used by transcribe_run (whole mel at once), push-audio +// (mel appended incrementally) and run_batch. +struct Utterance { + StreamParams P; + StreamState st; + SegmentTracker seg; + + // Mel frames not yet consumed, time-major [n, n_mels], starting at + // global mel frame `mel_base`. + std::vector mel_tm; + int64_t mel_base = 0; + int64_t mel_frames = 0; // total final mel frames known so far + int64_t stt = 0; // next chunk start (global mel frame) + int chunk_idx = 0; + bool done = false; +}; + +struct Nemotron3DiarSession final : public transcribe_session { + std::vector mel_buf; // row-major [n_mels, n_frames] (run path) + std::vector step_prev; // [S+F, emb_dim] graph input + std::vector step_mel; // [T_diar, sub*n_mels] graph input + std::vector step_embs; // [T_diar, emb_dim] readback + std::vector step_hi; // [8*T_concat, n_spk] readback + std::vector step_pooled; // [T_concat, n_spk] + std::vector positions; + + // Push-audio state. + Utterance utt; + std::vector pcm_tail; // unconsumed PCM (from pcm_tail_base) + int64_t pcm_tail_base = 0; + int64_t pcm_received = 0; + std::vector seg_mel; // scratch + bool stream_dump = false; + + Nemotron3DiarSession() = default; + ~Nemotron3DiarSession() override; +}; + +} // namespace transcribe::nemotron3_diar diff --git a/src/arch/nemotron3_diar/stream.cpp b/src/arch/nemotron3_diar/stream.cpp new file mode 100644 index 00000000..fb41a117 --- /dev/null +++ b/src/arch/nemotron3_diar/stream.cpp @@ -0,0 +1,546 @@ +// arch/nemotron3_diar/stream.cpp - host-side sync streaming state machine +// for the AOSC speaker cache + FIFO, plus the incremental speaker-segment +// builder. Exact ports of NeMo Speech sortformer_modules.py @ cf724ac337d1: +// streaming_update (sync branch) and the _compress_spkcache stack +// (_get_log_pred_scores -> _disable_low_scores -> scores_boost_latest -> +// _boost_topk_scores x2 -> silence pad -> _get_topk_indices -> gather). +// +// Forked from src/arch/sortformer/stream.cpp (NeMo 2.x port). Differences +// for this checkpoint (use_learnable_sil_emb=True): +// - no _get_silence_profile running mean: disabled cache slots are filled +// with the learned `learnable_sil_emb` (GGUF diar.sil_emb); +// - the cache runs on the x8 average-pooled 80 ms probabilities. +// Batch size is always 1 per state (spk_perm is None at inference). + +#include "nemotron3_diar.h" +#include "torch_logf.h" +#include "transcribe-debug.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace transcribe::nemotron3_diar { + +namespace { + +constexpr float kNegInf = -std::numeric_limits::infinity(); +constexpr float kPosInf = std::numeric_limits::infinity(); + +const char * env_get(const char * name) { + const char * v = std::getenv(name); + return (v != nullptr && v[0] != '\0') ? v : nullptr; +} + +// Card presets (NVIDIA model card) + "small" (validation only: tiny cache so +// the short oracle clip exercises many compressions). lc is 0 for all. +struct Preset { + const char * name; + int spkcache_len; + int fifo_len; + int chunk_len; + int chunk_right_context; + int spkcache_update_period; +}; + +const Preset k_presets[] = { + { "very_high_latency", 264, 40, 340, 40, 300 }, + { "low_latency", 264, 264, 9, 4, 222 }, + { "very_low_latency", 264, 264, 6, 2, 222 }, + { "ultra_low_latency", 264, 264, 3, 1, 222 }, + { "small", 24, 10, 20, 2, 20 }, +}; + +void apply_preset(StreamParams & p, const Preset & pr) { + p.spkcache_len = pr.spkcache_len; + p.fifo_len = pr.fifo_len; + p.chunk_len = pr.chunk_len; + p.chunk_right_context = pr.chunk_right_context; + p.spkcache_update_period = pr.spkcache_update_period; +} + +bool apply_named_preset(StreamParams & p, const char * name) { + for (const Preset & pr : k_presets) { + if (std::string(name) == pr.name) { + apply_preset(p, pr); + return true; + } + } + return false; +} + +} // namespace + +bool preset_is_valid(transcribe_nemotron3_diar_preset preset) { + switch (preset) { + case TRANSCRIBE_NEMOTRON3_DIAR_PRESET_DEFAULT: + case TRANSCRIBE_NEMOTRON3_DIAR_PRESET_VERY_HIGH_LATENCY: + case TRANSCRIBE_NEMOTRON3_DIAR_PRESET_LOW_LATENCY: + case TRANSCRIBE_NEMOTRON3_DIAR_PRESET_VERY_LOW_LATENCY: + case TRANSCRIBE_NEMOTRON3_DIAR_PRESET_ULTRA_LOW_LATENCY: + return true; + } + return false; +} + +StreamParams resolve_stream_params(const Nemotron3DiarHParams & hp, transcribe_nemotron3_diar_preset preset) { + StreamParams p; + // GGUF-shipped operating point is the baseline. + p.spkcache_len = hp.stream_spkcache_len; + p.fifo_len = hp.stream_fifo_len; + p.chunk_len = hp.stream_chunk_len; + p.chunk_right_context = hp.stream_chunk_right_context; + p.spkcache_update_period = hp.stream_spkcache_update_period; + p.spkcache_sil_frames_per_spk = hp.spkcache_sil_frames_per_spk; + p.pred_score_threshold = hp.pred_score_threshold; + p.scores_boost_latest = hp.scores_boost_latest; + p.strong_boost_rate = hp.strong_boost_rate; + p.weak_boost_rate = hp.weak_boost_rate; + p.min_pos_scores_rate = hp.min_pos_scores_rate; + p.max_index = hp.max_index; + + const char * name = nullptr; + switch (preset) { + case TRANSCRIBE_NEMOTRON3_DIAR_PRESET_DEFAULT: + break; + case TRANSCRIBE_NEMOTRON3_DIAR_PRESET_VERY_HIGH_LATENCY: + name = "very_high_latency"; + break; + case TRANSCRIBE_NEMOTRON3_DIAR_PRESET_LOW_LATENCY: + name = "low_latency"; + break; + case TRANSCRIBE_NEMOTRON3_DIAR_PRESET_VERY_LOW_LATENCY: + name = "very_low_latency"; + break; + case TRANSCRIBE_NEMOTRON3_DIAR_PRESET_ULTRA_LOW_LATENCY: + name = "ultra_low_latency"; + break; + } + if (name != nullptr) { + apply_named_preset(p, name); + } + // Validation / DER operating-point override (highest precedence). + if (const char * env = env_get("TRANSCRIBE_NEMOTRON3_DIAR_PRESET")) { + apply_named_preset(p, env); + } + return p; +} + +namespace { + +// _get_log_pred_scores: log(clamp(p,thr)) - log(clamp(1-p,thr)) + sum_s log(clamp(1-p,thr)) - log(0.5). +// Bit-exact with the torch CPU (aarch64) evaluation: the boundary scores the +// compression top-k separates are ~1e-7 apart, so log uses torch's Sleef +// algorithm (torch_logf.h) and the per-frame sum reproduces ATen's reduction +// order (4 accumulators acc[s % 4], combined ((a0 + a1) + a2) + a3; verified +// bit-identical on 1812 reference rows). +std::vector get_log_pred_scores(const float * preds, int n, int n_spk, float thr) { + std::vector scores(static_cast(n) * n_spk); + std::vector l1(static_cast(n_spk)); + const float log_half = static_cast(std::log(0.5)); // math.log(0.5) cast to the float op + for (int i = 0; i < n; ++i) { + float acc[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; + for (int s = 0; s < n_spk; ++s) { + const float p = preds[static_cast(i) * n_spk + s]; + l1[static_cast(s)] = torch_logf::logf_u10(std::max(1.0f - p, thr)); + acc[s % 4] = acc[s % 4] + l1[static_cast(s)]; + } + const float l1sum = ((acc[0] + acc[1]) + acc[2]) + acc[3]; + for (int s = 0; s < n_spk; ++s) { + const float p = preds[static_cast(i) * n_spk + s]; + const float lp = torch_logf::logf_u10(std::max(p, thr)); + scores[static_cast(i) * n_spk + s] = ((lp - l1[static_cast(s)]) + l1sum) - log_half; + } + } + return scores; +} + +// _disable_low_scores. +void disable_low_scores(const float * preds, std::vector & scores, int n, int n_spk, int min_pos) { + for (int i = 0; i < n; ++i) { + for (int s = 0; s < n_spk; ++s) { + const size_t idx = static_cast(i) * n_spk + s; + if (!(preds[idx] > 0.5f)) { + scores[idx] = kNegInf; + } + } + } + for (int s = 0; s < n_spk; ++s) { + int pos_count = 0; + for (int i = 0; i < n; ++i) { + if (scores[static_cast(i) * n_spk + s] > 0.0f) { + ++pos_count; + } + } + if (pos_count >= min_pos) { + for (int i = 0; i < n; ++i) { + const size_t idx = static_cast(i) * n_spk + s; + if (preds[idx] > 0.5f && !(scores[idx] > 0.0f)) { + scores[idx] = kNegInf; + } + } + } + } +} + +// torch.topk(values, k, largest=True, sorted=False) on CPU: the exact +// selection ATen makes (aten/src/ATen/native/TopKImpl.h topk_impl_loop). +// The queue of (value, index) pairs is filled in index order, then +// std::partial_sort (k * 64 <= n) or std::nth_element with a strict `>` +// comparator. Which of several EQUAL values is kept is decided by that +// algorithm's swap sequence, not by index order, and exact ties are common +// here (saturated sigmoids give identical scores), so a "value desc, index +// asc" rule selects different cache frames than NeMo. Same libc++ +// algorithms, same comparator, same call => same selection. Returns the +// selected indices (in queue order; callers treat them as a set). +std::vector aten_topk_indices(const std::vector & values, int64_t k) { + using elem_t = std::pair; + const int64_t n = static_cast(values.size()); + std::vector queue(static_cast(n)); + for (int64_t j = 0; j < n; ++j) { + queue[static_cast(j)] = { values[static_cast(j)], j }; + } + auto gt = [](const elem_t & x, const elem_t & y) -> bool { + return (std::isnan(x.first) && !std::isnan(y.first)) || (x.first > y.first); + }; + if (k * 64 <= n) { + std::partial_sort(queue.begin(), queue.begin() + k, queue.end(), gt); + } else { + std::nth_element(queue.begin(), queue.begin() + k - 1, queue.end(), gt); + } + std::vector out(static_cast(k)); + for (int64_t j = 0; j < k; ++j) { + out[static_cast(j)] = queue[static_cast(j)].second; + } + return out; +} + +// _boost_topk_scores: the k highest frames per speaker get -= scale*log(0.5) +// (torch.topk over the frame axis, sorted=False). +void boost_topk_scores(std::vector & scores, int n, int n_spk, int k, float scale) { + if (k <= 0) { + return; + } + const float delta = scale * std::log(0.5f); + std::vector col(static_cast(n)); + for (int s = 0; s < n_spk; ++s) { + for (int i = 0; i < n; ++i) { + col[static_cast(i)] = scores[static_cast(i) * n_spk + s]; + } + for (const int64_t i : aten_topk_indices(col, std::min(k, n))) { + scores[static_cast(i) * n_spk + s] -= delta; + } + } +} + +// Minimal .npy reader for the reference dumper's 1-D compression dumps +// (descr ' & out) { + std::ifstream f(path, std::ios::binary); + char magic[8]; + if (!f.read(magic, 8) || std::string(magic + 1, 5) != "NUMPY") { + return false; + } + uint32_t hlen = 0; + if (magic[6] == 1) { + uint16_t h16 = 0; + f.read(reinterpret_cast(&h16), 2); + hlen = h16; + } else { + f.read(reinterpret_cast(&hlen), 4); + } + std::string header(hlen, '\0'); + if (!f.read(header.data(), hlen)) { + return false; + } + const size_t sp = header.find("'shape': ("); + if (sp == std::string::npos) { + return false; + } + const long long n = std::strtoll(header.c_str() + sp + 10, nullptr, 10); + out.assign(static_cast(n), 0); + if (header.find("'(out.data()), n * 8); + } else if (header.find("'|b1'") != std::string::npos) { + std::vector b(static_cast(n)); + f.read(reinterpret_cast(b.data()), n); + for (long long i = 0; i < n; ++i) { + out[static_cast(i)] = b[static_cast(i)]; + } + } else { + return false; + } + return static_cast(f); +} + +// _compress_spkcache (permute_spk=False, use_learnable_sil_emb=True). +void compress_spkcache(StreamState & st, + const StreamParams & p, + int n_spk, + int emb_dim, + const std::vector & sil_emb) { + const int N = st.spkcache_n; + const int L = p.spkcache_len; + const int sil = p.spkcache_sil_frames_per_spk; + + const int per_spk = L / n_spk - sil; + const int strong = static_cast(std::floor(per_spk * p.strong_boost_rate)); + const int weak = static_cast(std::floor(per_spk * p.weak_boost_rate)); + const int min_pos = static_cast(std::floor(per_spk * p.min_pos_scores_rate)); + + const float * preds = st.spkcache_preds.data(); + + std::vector scores = get_log_pred_scores(preds, N, n_spk, p.pred_score_threshold); + disable_low_scores(preds, scores, N, n_spk, min_pos); + + if (p.scores_boost_latest > 0.0f) { + for (int i = L; i < N; ++i) { + for (int s = 0; s < n_spk; ++s) { + scores[static_cast(i) * n_spk + s] += p.scores_boost_latest; + } + } + } + + boost_topk_scores(scores, N, n_spk, strong, /*scale=*/2.0f); + boost_topk_scores(scores, N, n_spk, weak, /*scale=*/1.0f); + + const int n_frames = N + sil; + std::vector scores_ext(static_cast(n_frames) * n_spk); + std::copy(scores.begin(), scores.end(), scores_ext.begin()); + for (int i = N; i < n_frames; ++i) { + for (int s = 0; s < n_spk; ++s) { + scores_ext[static_cast(i) * n_spk + s] = kPosInf; + } + } + + // _get_topk_indices over flat[s*n_frames + i]. + const int64_t M = static_cast(n_spk) * n_frames; + std::vector flat(static_cast(M)); + for (int s = 0; s < n_spk; ++s) { + for (int i = 0; i < n_frames; ++i) { + flat[static_cast(s) * n_frames + i] = scores_ext[static_cast(i) * n_spk + s]; + } + } + const int kk = static_cast(std::min(L, M)); + const std::vector sel = aten_topk_indices(flat, kk); + std::vector picks(static_cast(L), p.max_index); + for (int j = 0; j < kk; ++j) { + const int64_t f = sel[static_cast(j)]; + picks[static_cast(j)] = (flat[static_cast(f)] == kNegInf) ? p.max_index : f; + } + std::sort(picks.begin(), picks.end()); + + std::vector frame_idx(static_cast(L)); + std::vector is_disabled(static_cast(L)); + for (int j = 0; j < L; ++j) { + const int64_t idx = picks[static_cast(j)]; + bool disabled = (idx == p.max_index); + int f = static_cast(idx % n_frames); + if (!disabled && f >= N) { + disabled = true; // a +inf silence pad row + } + if (disabled) { + f = 0; + } + frame_idx[static_cast(j)] = f; + is_disabled[static_cast(j)] = disabled ? 1 : 0; + } + + // Validation isolation (like reference-mel injection): with + // TRANSCRIBE_NEMOTRON3_DIAR_COMPRESS_FROM_REF= (and the dumper + // enabled), take compression k's selected frames from the reference + // dumper's compress.NNN.{topk_indices,is_disabled}.npy and gather from + // this port's own cache. The top-k selection is discontinuous: a ~1e-6 + // GEMM difference can move a boundary score across a ~1e-5 gap and flip a + // pick, after which every later chunk differs. Forcing the reference's + // picks lets diar.probs gate the graph + bookkeeping at tight tolerance; + // the selection itself is gated separately (scripts/diar/ + // check_nemotron3_diar_compress.py replays NeMo on this port's inputs). + if (const char * ref_dir = env_get("TRANSCRIBE_NEMOTRON3_DIAR_COMPRESS_FROM_REF"); + ref_dir != nullptr && transcribe::debug::enabled()) { + char base[64]; + std::snprintf(base, sizeof(base), "/compress.%03d.", st.compress_count); + std::vector ref_idx, ref_dis; + if (read_npy_1d(std::string(ref_dir) + base + "topk_indices.npy", ref_idx) && + read_npy_1d(std::string(ref_dir) + base + "is_disabled.npy", ref_dis) && + static_cast(ref_idx.size()) == L && static_cast(ref_dis.size()) == L) { + for (int j = 0; j < L; ++j) { + frame_idx[static_cast(j)] = static_cast(ref_idx[static_cast(j)]); + is_disabled[static_cast(j)] = ref_dis[static_cast(j)] != 0 ? 1 : 0; + } + } else { + std::fprintf(stderr, "nemotron3_diar: COMPRESS_FROM_REF: no usable reference picks for compression %d\n", + st.compress_count); + } + } + + // _gather_spkcache_and_preds: disabled -> learned silence embedding / 0. + std::vector new_emb(static_cast(L) * emb_dim); + std::vector new_preds(static_cast(L) * n_spk, 0.0f); + for (int j = 0; j < L; ++j) { + const int f = frame_idx[static_cast(j)]; + if (is_disabled[static_cast(j)]) { + std::copy(sil_emb.begin(), sil_emb.end(), new_emb.begin() + static_cast(j) * emb_dim); + } else { + std::copy(st.spkcache.begin() + static_cast(f) * emb_dim, + st.spkcache.begin() + static_cast(f + 1) * emb_dim, + new_emb.begin() + static_cast(j) * emb_dim); + std::copy(st.spkcache_preds.begin() + static_cast(f) * n_spk, + st.spkcache_preds.begin() + static_cast(f + 1) * n_spk, + new_preds.begin() + static_cast(j) * n_spk); + } + } + + // Parity dump, index-for-index with the reference's --dump-compress + // (compress.NNN.{input_preds,topk_indices,is_disabled,spkcache_preds}). + if (transcribe::debug::enabled() && env_get("TRANSCRIBE_NEMOTRON3_DIAR_COMPRESS_DUMP") != nullptr) { + const int k = st.compress_count; + char name[64]; + std::vector fidx(static_cast(L)), fdis(static_cast(L)); + for (int j = 0; j < L; ++j) { + fidx[static_cast(j)] = static_cast(frame_idx[static_cast(j)]); + fdis[static_cast(j)] = is_disabled[static_cast(j)] ? 1.0f : 0.0f; + } + const long long li = L; + const long long shp_in[2] = { N, n_spk }; + std::snprintf(name, sizeof(name), "compress.%03d.input_preds", k); + transcribe::debug::dump_host_f32(name, st.spkcache_preds.data(), static_cast(N) * n_spk, shp_in, 2, + "compress"); + std::snprintf(name, sizeof(name), "compress.%03d.topk_indices", k); + transcribe::debug::dump_host_f32(name, fidx.data(), li, &li, 1, "compress"); + std::snprintf(name, sizeof(name), "compress.%03d.is_disabled", k); + transcribe::debug::dump_host_f32(name, fdis.data(), li, &li, 1, "compress"); + const long long shp[2] = { L, n_spk }; + std::snprintf(name, sizeof(name), "compress.%03d.spkcache_preds", k); + transcribe::debug::dump_host_f32(name, new_preds.data(), static_cast(L) * n_spk, shp, 2, "compress"); + } + ++st.compress_count; + + st.spkcache = std::move(new_emb); + st.spkcache_preds = std::move(new_preds); + st.spkcache_n = L; +} + +} // namespace + +// streaming_update (sync branch). preds80 = [spkcache | fifo | chunk] preds. +void streaming_update(StreamState & st, + const StreamParams & p, + int n_spk, + int emb_dim, + const std::vector & chunk_embs, + int T_diar, + const std::vector & preds80, + int lc, + int rc, + const std::vector & sil_emb) { + const int S = st.spkcache_n; + const int F = st.fifo_n; + const int C = T_diar - lc - rc; + + // fifo_preds = preds[S : S+F]; chunk_preds = preds[S+F+lc : S+F+lc+C]. + st.fifo_preds.assign(preds80.begin() + static_cast(S) * n_spk, + preds80.begin() + static_cast(S + F) * n_spk); + st.fifo.insert(st.fifo.end(), chunk_embs.begin() + static_cast(lc) * emb_dim, + chunk_embs.begin() + static_cast(lc + C) * emb_dim); + st.fifo_preds.insert(st.fifo_preds.end(), preds80.begin() + static_cast(S + F + lc) * n_spk, + preds80.begin() + static_cast(S + F + lc + C) * n_spk); + st.fifo_n = F + C; + + if (F + C > p.fifo_len) { + int pop = p.spkcache_update_period; + pop = std::max(pop, C - p.fifo_len + F); + pop = std::min(pop, F + C); + + std::vector pop_embs(st.fifo.begin(), st.fifo.begin() + static_cast(pop) * emb_dim); + std::vector pop_preds(st.fifo_preds.begin(), st.fifo_preds.begin() + static_cast(pop) * n_spk); + + st.fifo.erase(st.fifo.begin(), st.fifo.begin() + static_cast(pop) * emb_dim); + st.fifo_preds.erase(st.fifo_preds.begin(), st.fifo_preds.begin() + static_cast(pop) * n_spk); + st.fifo_n = (F + C) - pop; + + st.spkcache.insert(st.spkcache.end(), pop_embs.begin(), pop_embs.end()); + st.spkcache_n = S + pop; + if (st.compressed) { + st.spkcache_preds.insert(st.spkcache_preds.end(), pop_preds.begin(), pop_preds.end()); + } else { + // Before the first compression, fresh predictions for every cache frame. + st.spkcache_preds.assign(preds80.begin(), preds80.begin() + static_cast(S) * n_spk); + st.spkcache_preds.insert(st.spkcache_preds.end(), pop_preds.begin(), pop_preds.end()); + } + if (st.spkcache_n > p.spkcache_len) { + compress_spkcache(st, p, n_spk, emb_dim, sil_emb); + st.compressed = true; + } + } +} + +// ---- incremental speaker segments ---- + +void segments_advance(SegmentTracker & tr, const float * probs, int T, int n_spk, double ms_per_frame) { + for (int t = tr.n_seen; t < T; ++t) { + for (int s = 0; s < n_spk; ++s) { + const bool active = probs[static_cast(t) * n_spk + s] > 0.5f; + int & rs = tr.run_start[static_cast(s)]; + if (active && rs < 0) { + rs = t; + } else if (!active && rs >= 0) { + transcribe_session::SpeakerSegmentEntry row; + row.t0_ms = static_cast(std::llround(rs * ms_per_frame)); + row.t1_ms = static_cast(std::llround(t * ms_per_frame)); + row.speaker_id = s + 1; + row.p = std::numeric_limits::quiet_NaN(); + tr.closed.push_back(row); + rs = -1; + } + } + } + tr.n_seen = std::max(tr.n_seen, T); +} + +void segments_close_all(SegmentTracker & tr, double ms_per_frame) { + for (size_t s = 0; s < tr.run_start.size(); ++s) { + int & rs = tr.run_start[s]; + if (rs >= 0) { + transcribe_session::SpeakerSegmentEntry row; + row.t0_ms = static_cast(std::llround(rs * ms_per_frame)); + row.t1_ms = static_cast(std::llround(tr.n_seen * ms_per_frame)); + row.speaker_id = static_cast(s) + 1; + row.p = std::numeric_limits::quiet_NaN(); + tr.closed.push_back(row); + rs = -1; + } + } +} + +void segments_publish(const SegmentTracker & tr, transcribe_session * session, double ms_per_frame) { + auto & out = session->speaker_segments; + out = tr.closed; + for (size_t s = 0; s < tr.run_start.size(); ++s) { + const int rs = tr.run_start[s]; + if (rs >= 0 && tr.n_seen > rs) { + transcribe_session::SpeakerSegmentEntry row; + row.t0_ms = static_cast(std::llround(rs * ms_per_frame)); + row.t1_ms = static_cast(std::llround(tr.n_seen * ms_per_frame)); + row.speaker_id = static_cast(s) + 1; + row.p = std::numeric_limits::quiet_NaN(); + out.push_back(row); + } + } + // Speaker-major, time-ordered within a speaker (the offline ordering). + std::stable_sort(out.begin(), out.end(), [](const auto & a, const auto & b) { + if (a.speaker_id != b.speaker_id) { + return a.speaker_id < b.speaker_id; + } + return a.t0_ms < b.t0_ms; + }); +} + +} // namespace transcribe::nemotron3_diar diff --git a/src/arch/nemotron3_diar/torch_logf.h b/src/arch/nemotron3_diar/torch_logf.h new file mode 100644 index 00000000..0e269d60 --- /dev/null +++ b/src/arch/nemotron3_diar/torch_logf.h @@ -0,0 +1,123 @@ +// arch/nemotron3_diar/torch_logf.h - bit-exact float32 natural log as computed +// by PyTorch on aarch64 CPU (torch.log -> Vectorized::log -> +// Sleef_logf4_u10, AdvSIMD with FMA). Scalar port of Sleef's xlogf_u1 +// (refs/sleef src/libm/sleefsimdsp.c + src/common/df.h, ENABLE_FMA_SP). +// +// Why: the AOSC cache compression selects the top spkcache_len frames by a +// log-probability score, and the scores at the selection boundary are +// routinely ~1e-7 apart (and often exactly tied). A 1-ulp difference in log +// (Apple libm disagrees with Sleef on ~0.4% of inputs) flips which frame is +// kept and every later chunk diverges from the NeMo reference. Verified +// bit-identical to torch.log on 252k inputs from the reference dumps. +// +// Sleef is Copyright Naoki Shibata and contributors, distributed under the +// Boost Software License 1.0 (https://www.boost.org/LICENSE_1_0.txt). + +#pragma once + +#include +#include +#include +#include + +// Sleef keeps every multiply/add separate unless it asks for an FMA. +#pragma clang fp contract(off) + +namespace transcribe::nemotron3_diar::torch_logf { + +struct f2 { + float x, y; +}; + +inline float fma_(float x, float y, float z) { + return std::fma(x, y, z); +} // z + x*y + +inline float fmapn(float x, float y, float z) { + return std::fma(x, y, -z); +} // x*y - z + +inline float fmanp(float x, float y, float z) { + return std::fma(-x, y, z); +} // z - x*y + +inline float add3(float a, float b, float c) { + return (a + b) + c; +} + +inline float add4(float a, float b, float c, float d) { + return add3(a + b, c, d); +} + +inline f2 dfmul_f2_f(f2 x, float y) { + float s = x.x * y; + return { s, fma_(x.y, y, fmapn(x.x, y, s)) }; +} + +inline f2 dfadd2_f_f(float x, float y) { + float s = x + y; + float v = s - x; + return { s, (x - (s - v)) + (y - v) }; +} + +inline f2 dfdiv(f2 n, f2 d) { + float t = 1.0f / d.x; + float s = n.x * t; + float u = fmapn(t, n.x, s); + float v = fmanp(d.y, t, fmanp(d.x, t, 1.0f)); + return { s, fma_(s, v, fma_(n.y, t, u)) }; +} + +inline f2 dfscale(f2 d, float s) { + return { d.x * s, d.y * s }; +} + +inline f2 dfadd_f2_f2(f2 x, f2 y) { + float s = x.x + y.x; + return { s, add4(x.x - s, y.x, x.y, y.y) }; +} + +inline f2 dfadd_f2_f(f2 x, float y) { + float s = x.x + y; + return { s, add3(x.x - s, y, x.y) }; +} + +inline int32_t bits(float f) { + int32_t i; + std::memcpy(&i, &f, 4); + return i; +} + +inline float from_bits(int32_t i) { + float f; + std::memcpy(&f, &i, 4); + return f; +} + +inline float logf_u10(float d) { + const bool o = d < std::numeric_limits::min(); + float dd = o ? d * (float(INT64_C(1) << 32) * float(INT64_C(1) << 32)) : d; + int32_t e = ((bits(dd * (1.0f / 0.75f)) >> 23) & 0xff) - 0x7f; + float m = from_bits(bits(dd) + ((-e) << 23)); + e = o ? e - 64 : e; + f2 s = dfmul_f2_f({ 0.69314718246459960938f, -1.904654323148236017e-09f }, static_cast(e)); + f2 x = dfdiv(dfadd2_f_f(-1.0f, m), dfadd2_f_f(1.0f, m)); + float x2 = x.x * x.x; + float t = 0.3027294874e+0f; + t = fma_(t, x2, 0.3996108174e+0f); + t = fma_(t, x2, 0.6666694880e+0f); + s = dfadd_f2_f2(s, dfscale(x, 2.0f)); + s = dfadd_f2_f(s, (x2 * x.x) * t); + float r = s.x + s.y; + if (std::isinf(dd) && dd > 0) { + r = std::numeric_limits::infinity(); + } + if (dd < 0 || std::isnan(dd)) { + r = std::numeric_limits::quiet_NaN(); + } + if (dd == 0) { + r = -std::numeric_limits::infinity(); + } + return r; +} +} // namespace transcribe::nemotron3_diar::torch_logf diff --git a/src/arch/nemotron3_diar/weights.cpp b/src/arch/nemotron3_diar/weights.cpp new file mode 100644 index 00000000..2cf9b2bb --- /dev/null +++ b/src/arch/nemotron3_diar/weights.cpp @@ -0,0 +1,213 @@ +// arch/nemotron3_diar/weights.cpp - Nemotron-3-Diarization hparam KV +// reader + weight catalog. + +#include "weights.h" + +#include "ggml.h" +#include "gguf.h" +#include "transcribe-log.h" + +#include +#include + +namespace transcribe::nemotron3_diar { + +namespace { + +transcribe_status kv_u32(const gguf_context * g, const char * key, int32_t & out) { + const int64_t id = gguf_find_key(g, key); + if (id < 0) { + log_msg(TRANSCRIBE_LOG_LEVEL_ERROR, "nemotron3_diar: missing KV %s", key); + return TRANSCRIBE_ERR_GGUF; + } + out = static_cast(gguf_get_val_u32(g, id)); + return TRANSCRIBE_OK; +} + +transcribe_status kv_f32(const gguf_context * g, const char * key, float & out) { + const int64_t id = gguf_find_key(g, key); + if (id < 0) { + log_msg(TRANSCRIBE_LOG_LEVEL_ERROR, "nemotron3_diar: missing KV %s", key); + return TRANSCRIBE_ERR_GGUF; + } + out = gguf_get_val_f32(g, id); + return TRANSCRIBE_OK; +} + +transcribe_status kv_str(const gguf_context * g, const char * key, std::string & out) { + const int64_t id = gguf_find_key(g, key); + if (id < 0) { + log_msg(TRANSCRIBE_LOG_LEVEL_ERROR, "nemotron3_diar: missing KV %s", key); + return TRANSCRIBE_ERR_GGUF; + } + out = gguf_get_val_str(g, id); + return TRANSCRIBE_OK; +} + +// Look up a tensor by name and validate its ne (fast-to-slow; -1 skips). +ggml_tensor * get_checked(ggml_context * ctx, const char * name, int64_t ne0, int64_t ne1, int64_t ne2 = -1) { + ggml_tensor * t = ggml_get_tensor(ctx, name); + if (t == nullptr) { + log_msg(TRANSCRIBE_LOG_LEVEL_ERROR, "nemotron3_diar: missing tensor %s", name); + return nullptr; + } + if ((ne0 >= 0 && t->ne[0] != ne0) || (ne1 >= 0 && t->ne[1] != ne1) || (ne2 >= 0 && t->ne[2] != ne2)) { + log_msg(TRANSCRIBE_LOG_LEVEL_ERROR, + "nemotron3_diar: tensor %s shape mismatch: have [%lld,%lld,%lld] want [%lld,%lld,%lld]", name, + (long long) t->ne[0], (long long) t->ne[1], (long long) t->ne[2], (long long) ne0, (long long) ne1, + (long long) ne2); + return nullptr; + } + return t; +} + +} // namespace + +transcribe_status read_hparams(const gguf_context * g, Nemotron3DiarHParams & hp) { + if (g == nullptr) { + return TRANSCRIBE_ERR_INVALID_ARG; + } + +#define RD_U32(key, field) \ + if (const transcribe_status st = kv_u32(g, key, hp.field); st != TRANSCRIBE_OK) \ + return st +#define RD_F32(key, field) \ + if (const transcribe_status st = kv_f32(g, key, hp.field); st != TRANSCRIBE_OK) \ + return st +#define RD_STR(key, field) \ + if (const transcribe_status st = kv_str(g, key, hp.field); st != TRANSCRIBE_OK) \ + return st + + RD_U32("stt.nemotron3_diar.max_speakers", max_speakers); + RD_U32("stt.nemotron3_diar.frame_hop", frame_hop); + RD_U32("stt.nemotron3_diar.output_hop", output_hop); + RD_U32("stt.nemotron3_diar.upsample_factor", upsample_factor); + + RD_U32("stt.nemotron3_diar.encoder.n_layers", enc_n_layers); + RD_U32("stt.nemotron3_diar.encoder.d_model", enc_d_model); + RD_U32("stt.nemotron3_diar.encoder.n_heads", enc_n_heads); + RD_U32("stt.nemotron3_diar.encoder.d_ff", enc_d_ff); + RD_U32("stt.nemotron3_diar.encoder.feat_in", enc_feat_in); + RD_U32("stt.nemotron3_diar.encoder.subsampling_factor", enc_subsampling_factor); + RD_STR("stt.nemotron3_diar.encoder.subsampling", enc_subsampling); + RD_F32("stt.nemotron3_diar.encoder.rope_base", enc_rope_base); + RD_F32("stt.nemotron3_diar.encoder.rotary_fraction", enc_rotary_fraction); + RD_F32("stt.nemotron3_diar.encoder.layer_norm_eps", enc_ln_eps); + RD_STR("stt.nemotron3_diar.encoder.activation", enc_activation); + RD_U32("stt.nemotron3_diar.head.d_model", head_d_model); + + RD_U32("stt.nemotron3_diar.aosc.spkcache_sil_frames_per_spk", spkcache_sil_frames_per_spk); + RD_F32("stt.nemotron3_diar.aosc.pred_score_threshold", pred_score_threshold); + RD_F32("stt.nemotron3_diar.aosc.scores_boost_latest", scores_boost_latest); + RD_F32("stt.nemotron3_diar.aosc.sil_threshold", sil_threshold); + RD_F32("stt.nemotron3_diar.aosc.strong_boost_rate", strong_boost_rate); + RD_F32("stt.nemotron3_diar.aosc.weak_boost_rate", weak_boost_rate); + RD_F32("stt.nemotron3_diar.aosc.min_pos_scores_rate", min_pos_scores_rate); + RD_U32("stt.nemotron3_diar.aosc.max_index", max_index); + + RD_U32("stt.nemotron3_diar.stream.spkcache_len", stream_spkcache_len); + RD_U32("stt.nemotron3_diar.stream.fifo_len", stream_fifo_len); + RD_U32("stt.nemotron3_diar.stream.chunk_len", stream_chunk_len); + RD_U32("stt.nemotron3_diar.stream.chunk_right_context", stream_chunk_right_context); + RD_U32("stt.nemotron3_diar.stream.spkcache_update_period", stream_spkcache_update_period); + + RD_U32("stt.frontend.num_mels", fe_num_mels); + RD_U32("stt.frontend.sample_rate", fe_sample_rate); + RD_U32("stt.frontend.n_fft", fe_n_fft); + RD_U32("stt.frontend.win_length", fe_win_length); + RD_U32("stt.frontend.hop_length", fe_hop_length); + RD_STR("stt.frontend.window", fe_window); + RD_STR("stt.frontend.normalize", fe_normalize); + RD_F32("stt.frontend.dither", fe_dither); + RD_F32("stt.frontend.pre_emphasis", fe_pre_emphasis); + +#undef RD_U32 +#undef RD_F32 +#undef RD_STR + + if (hp.enc_n_heads <= 0 || hp.enc_d_model % hp.enc_n_heads != 0) { + log_msg(TRANSCRIBE_LOG_LEVEL_ERROR, "nemotron3_diar: d_model %d not divisible by n_heads %d", hp.enc_d_model, + hp.enc_n_heads); + return TRANSCRIBE_ERR_GGUF; + } + if (hp.enc_subsampling != "feature_stacking" || hp.enc_activation != "gelu" || hp.enc_rotary_fraction != 1.0f) { + log_msg(TRANSCRIBE_LOG_LEVEL_ERROR, + "nemotron3_diar: unsupported encoder config (subsampling=%s activation=%s rotary_fraction=%g)", + hp.enc_subsampling.c_str(), hp.enc_activation.c_str(), static_cast(hp.enc_rotary_fraction)); + return TRANSCRIBE_ERR_GGUF; + } + if (hp.enc_subsampling_factor <= 0 || hp.upsample_factor <= 0 || + hp.frame_hop != hp.fe_hop_length * hp.enc_subsampling_factor || + hp.output_hop * hp.upsample_factor != hp.frame_hop) { + log_msg(TRANSCRIBE_LOG_LEVEL_ERROR, + "nemotron3_diar: inconsistent frame geometry (hop=%d sub=%d frame_hop=%d output_hop=%d upsample=%d)", + hp.fe_hop_length, hp.enc_subsampling_factor, hp.frame_hop, hp.output_hop, hp.upsample_factor); + return TRANSCRIBE_ERR_GGUF; + } + return TRANSCRIBE_OK; +} + +transcribe_status build_weights(ggml_context * ctx, const Nemotron3DiarHParams & hp, Nemotron3DiarWeights & w) { + const int64_t d = hp.enc_d_model; + const int64_t dff = hp.enc_d_ff; + const int64_t hd = hp.head_d_model; + const int64_t spk = hp.max_speakers; + const int64_t up = hp.upsample_factor; + const int64_t nfr = hp.fe_n_fft / 2 + 1; + const int64_t fin = static_cast(hp.enc_feat_in) * hp.enc_subsampling_factor; + char name[96]; + +#define GET(dst, nm, ...) \ + do { \ + (dst) = get_checked(ctx, (nm), __VA_ARGS__); \ + if ((dst) == nullptr) \ + return TRANSCRIBE_ERR_GGUF; \ + } while (0) + + GET(w.fe_window, "frontend.window", hp.fe_win_length, -1); + GET(w.fe_filterbank, "frontend.mel_filterbank", nfr, hp.fe_num_mels); + + GET(w.pre_encode_proj_w, "enc.pre_encode.proj.weight", fin, d); + GET(w.embed_norm_w, "enc.embed.norm.weight", d, -1); + GET(w.embed_norm_b, "enc.embed.norm.bias", d, -1); + + w.blocks.resize(static_cast(hp.enc_n_layers)); + for (int i = 0; i < hp.enc_n_layers; ++i) { + Nemotron3DiarBlock & b = w.blocks[static_cast(i)]; +#define GETB(dst, suffix, ...) \ + do { \ + std::snprintf(name, sizeof(name), "enc.blocks.%d.%s", i, suffix); \ + GET(dst, name, __VA_ARGS__); \ + } while (0) + GETB(b.norm1_w, "norm_1.weight", d, -1); + GETB(b.norm1_b, "norm_1.bias", d, -1); + GETB(b.attn_qkv_w, "attn.qkv.weight", d, 3 * d); + GETB(b.attn_out_w, "attn.out.weight", d, d); + GETB(b.attn_out_b, "attn.out.bias", d, -1); + GETB(b.norm2_w, "norm_2.weight", d, -1); + GETB(b.norm2_b, "norm_2.bias", d, -1); + GETB(b.ff_in_w, "ff.in.weight", d, dff); + GETB(b.ff_in_b, "ff.in.bias", dff, -1); + GETB(b.ff_out_w, "ff.out.weight", dff, d); + GETB(b.ff_out_b, "ff.out.bias", d, -1); +#undef GETB + } + + GET(w.final_norm_w, "enc.final_norm.weight", d, -1); + GET(w.final_norm_b, "enc.final_norm.bias", d, -1); + + GET(w.enc_proj_w, "diar.encoder_proj.weight", d, hd); + GET(w.enc_proj_b, "diar.encoder_proj.bias", hd, -1); + GET(w.upsample_w, "diar.upsample.conv.weight", 3, hd, hd * up); + GET(w.upsample_b, "diar.upsample.conv.bias", hd * up, -1); + GET(w.fc1_w, "diar.fc1.weight", hd, hd); + GET(w.fc1_b, "diar.fc1.bias", hd, -1); + GET(w.spk_head_w, "diar.single_spk_head.weight", hd, spk); + GET(w.spk_head_b, "diar.single_spk_head.bias", spk, -1); + GET(w.sil_emb, "diar.sil_emb", d, -1); + +#undef GET + return TRANSCRIBE_OK; +} + +} // namespace transcribe::nemotron3_diar diff --git a/src/arch/nemotron3_diar/weights.h b/src/arch/nemotron3_diar/weights.h new file mode 100644 index 00000000..662334fb --- /dev/null +++ b/src/arch/nemotron3_diar/weights.h @@ -0,0 +1,129 @@ +// arch/nemotron3_diar/weights.h - Nemotron-3-Diarization hparams + weight +// slots: feature-stacking pre_encode, 31-layer pre-LN RoPE Transformer +// encoder, encoder_proj, subpixel upsampler, sigmoid speaker head, and the +// learned AOSC silence embedding. +// +// Tensor layout conventions (matched by scripts/convert-nemotron3_diar.py): +// - Linear weights: PyTorch [out, in] -> ggml ne [in, out]. +// - attn.qkv: [d, 3d] (ne), output rows [q | k | v], each head-major. +// - diar.upsample.conv.weight: PyTorch Conv1d [out=1536, in=192, k=3] +// -> ggml ne [3, 192, 1536]. +// - LayerNorm: separate weight + bias, shape [d]. + +#pragma once + +#include "transcribe.h" + +#include +#include +#include + +struct gguf_context; +struct ggml_context; +struct ggml_tensor; + +namespace transcribe::nemotron3_diar { + +struct Nemotron3DiarHParams { + // Diarization. + int32_t max_speakers = 8; // hard architectural cap + int32_t frame_hop = 0; // samples per encoder / AOSC frame (80 ms) + int32_t output_hop = 0; // samples per output frame (10 ms) + int32_t upsample_factor = 0; // output frames per encoder frame + + // Encoder (pre-LN Transformer, RoPE). + int32_t enc_n_layers = 0; + int32_t enc_d_model = 0; + int32_t enc_n_heads = 0; + int32_t enc_d_ff = 0; + int32_t enc_feat_in = 0; + int32_t enc_subsampling_factor = 0; + float enc_rope_base = 10000.0f; + float enc_rotary_fraction = 1.0f; + float enc_ln_eps = 1e-5f; + std::string enc_subsampling; // "feature_stacking" + std::string enc_activation; // "gelu" + + // Head. + int32_t head_d_model = 0; + + // AOSC compression constants. + int32_t spkcache_sil_frames_per_spk = 1; + float pred_score_threshold = 0.25f; + float scores_boost_latest = 0.05f; + float sil_threshold = 0.2f; + float strong_boost_rate = 0.75f; + float weak_boost_rate = 1.5f; + float min_pos_scores_rate = 0.5f; + int32_t max_index = 99999; + + // Shipped default streaming preset (card very_high_latency). + int32_t stream_spkcache_len = 0; + int32_t stream_fifo_len = 0; + int32_t stream_chunk_len = 0; + int32_t stream_chunk_right_context = 0; + int32_t stream_spkcache_update_period = 0; + + // Frontend. + int32_t fe_num_mels = 0; + int32_t fe_sample_rate = 0; + int32_t fe_n_fft = 0; + int32_t fe_win_length = 0; + int32_t fe_hop_length = 0; + std::string fe_window; + std::string fe_normalize; + float fe_dither = 0.0f; + float fe_pre_emphasis = 0.0f; + + int32_t head_dim() const { return enc_n_heads > 0 ? enc_d_model / enc_n_heads : 0; } +}; + +// One pre-LN Transformer block. +struct Nemotron3DiarBlock { + ggml_tensor * norm1_w = nullptr; // [d] + ggml_tensor * norm1_b = nullptr; // [d] + ggml_tensor * attn_qkv_w = nullptr; // [d, 3d] (no bias) + ggml_tensor * attn_out_w = nullptr; // [d, d] + ggml_tensor * attn_out_b = nullptr; // [d] + ggml_tensor * norm2_w = nullptr; // [d] + ggml_tensor * norm2_b = nullptr; // [d] + ggml_tensor * ff_in_w = nullptr; // [d, d_ff] + ggml_tensor * ff_in_b = nullptr; // [d_ff] + ggml_tensor * ff_out_w = nullptr; // [d_ff, d] + ggml_tensor * ff_out_b = nullptr; // [d] +}; + +struct Nemotron3DiarWeights { + // Frontend buffers (F32, BF16-exact checkpoint values). + ggml_tensor * fe_window = nullptr; // [win_length] + ggml_tensor * fe_filterbank = nullptr; // [n_fft/2+1, n_mels] + + ggml_tensor * pre_encode_proj_w = nullptr; // [feat_in*sub, d] (no bias) + ggml_tensor * embed_norm_w = nullptr; // [d] + ggml_tensor * embed_norm_b = nullptr; // [d] + + std::vector blocks; // enc_n_layers + + ggml_tensor * final_norm_w = nullptr; // [d] + ggml_tensor * final_norm_b = nullptr; // [d] + + ggml_tensor * enc_proj_w = nullptr; // [d, head_d] + ggml_tensor * enc_proj_b = nullptr; // [head_d] + ggml_tensor * upsample_w = nullptr; // [3, head_d, head_d*upsample] + ggml_tensor * upsample_b = nullptr; // [head_d*upsample] + ggml_tensor * fc1_w = nullptr; // [head_d, head_d] + ggml_tensor * fc1_b = nullptr; // [head_d] + ggml_tensor * spk_head_w = nullptr; // [head_d, max_speakers] + ggml_tensor * spk_head_b = nullptr; // [max_speakers] + ggml_tensor * sil_emb = nullptr; // [d] +}; + +// Read every required stt.nemotron3_diar.* / stt.frontend.* KV into hp. +transcribe_status read_hparams(const gguf_context * gguf, Nemotron3DiarHParams & hp); + +// Look up every tensor by name in ctx_meta, validate shapes against hp and +// store borrowed pointers. Returns TRANSCRIBE_ERR_GGUF (naming the tensor) +// on any missing / mis-shaped tensor. +transcribe_status build_weights(ggml_context * ctx_meta, const Nemotron3DiarHParams & hp, Nemotron3DiarWeights & w); + +} // namespace transcribe::nemotron3_diar diff --git a/src/transcribe-arch.cpp b/src/transcribe-arch.cpp index fb768cba..8c611c29 100644 --- a/src/transcribe-arch.cpp +++ b/src/transcribe-arch.cpp @@ -87,6 +87,10 @@ namespace sortformer { extern const Arch arch; } +namespace nemotron3_diar { +extern const Arch arch; +} + const Arch * find_arch(const char * name) { if (name == nullptr) { return nullptr; @@ -96,7 +100,7 @@ const Arch * find_arch(const char * name) { ¶keet::arch, &cohere::arch, &canary::arch, &qwen3_asr::arch, &voxtral::arch, &voxtral_realtime::arch, &canary_qwen::arch, &whisper::arch, &moonshine::arch, &moonshine_streaming::arch, &sensevoice::arch, &funasr_nano::arch, &gigaam::arch, &granite::arch, &granite_nar::arch, - &medasr::arch, &moss::arch, &sortformer::arch, &granite5_ctc::arch, + &medasr::arch, &moss::arch, &sortformer::arch, &granite5_ctc::arch, &nemotron3_diar::arch, }; constexpr size_t k_n = sizeof(k_archs) / sizeof(k_archs[0]); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 1e2318a0..42271876 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1265,6 +1265,27 @@ if(TRANSCRIBE_BUILD_REAL_MODEL_TESTS) COMMAND transcribe_sortformer_stream_ext_unit) set_tests_properties(transcribe_sortformer_stream_ext_unit PROPERTIES SKIP_RETURN_CODE 77) + + # Nemotron-3-Diarization operating-point exts (N3DR RUN / N3DS STREAM) + # and push-audio live diarization: kind/slot probe, pre-clear + # rejection, push-audio == whole-file run on the 8-speaker oracle (and + # its non-aligned truncation). Gated by TRANSCRIBE_NEMOTRON3_DIAR_GGUF + # (RC 77 skip). + add_executable(transcribe_nemotron3_diar_stream_unit + nemotron3_diar_stream_unit.cpp) + + target_link_libraries(transcribe_nemotron3_diar_stream_unit + PRIVATE transcribe transcribe-common-example) + + target_compile_definitions(transcribe_nemotron3_diar_stream_unit PRIVATE + "TRANSCRIBE_TEST_SAMPLES_DIR=\"${CMAKE_SOURCE_DIR}/samples\"") + + transcribe_apply_warnings(transcribe_nemotron3_diar_stream_unit) + + add_test(NAME transcribe_nemotron3_diar_stream_unit + COMMAND transcribe_nemotron3_diar_stream_unit) + set_tests_properties(transcribe_nemotron3_diar_stream_unit PROPERTIES + SKIP_RETURN_CODE 77) endif() # ----------------------------------------------------------------------------- diff --git a/tests/golden/batch/Nemotron-3-Diarization.cpu.json b/tests/golden/batch/Nemotron-3-Diarization.cpu.json new file mode 100644 index 00000000..5d78d255 --- /dev/null +++ b/tests/golden/batch/Nemotron-3-Diarization.cpu.json @@ -0,0 +1,15 @@ +{ + "model": "models/Nemotron-3-Diarization/Nemotron-3-Diarization-BF16.gguf", + "backend": "cpu", + "language": null, + "texts": { + "samples/jfk.wav": " |speakers=[{\"speaker_id\": 1, \"t0_ms\": 280, \"t1_ms\": 2280}, {\"speaker_id\": 1, \"t0_ms\": 3270, \"t1_ms\": 4570}, {\"speaker_id\": 1, \"t0_ms\": 5360, \"t1_ms\": 10630}]", + "samples/sortformer-2spk-mix.wav": " |speakers=[{\"speaker_id\": 1, \"t0_ms\": 330, \"t1_ms\": 2270}, {\"speaker_id\": 1, \"t0_ms\": 7320, \"t1_ms\": 9270}, {\"speaker_id\": 1, \"t0_ms\": 10340, \"t1_ms\": 10590}, {\"speaker_id\": 2, \"t0_ms\": 4200, \"t1_ms\": 6520}, {\"speaker_id\": 2, \"t0_ms\": 9720, \"t1_ms\": 12000}]", + "samples/nemotron3-diar-8spk-mix.wav": " |speakers=[{\"speaker_id\": 1, \"t0_ms\": 0, \"t1_ms\": 1870}, {\"speaker_id\": 1, \"t0_ms\": 2960, \"t1_ms\": 4130}, {\"speaker_id\": 1, \"t0_ms\": 43580, \"t1_ms\": 48490}, {\"speaker_id\": 1, \"t0_ms\": 88200, \"t1_ms\": 90110}, {\"speaker_id\": 1, \"t0_ms\": 91140, \"t1_ms\": 92000}, {\"speaker_id\": 2, \"t0_ms\": 5500, \"t1_ms\": 9530}, {\"speaker_id\": 2, \"t0_ms\": 54500, \"t1_ms\": 57980}, {\"speaker_id\": 2, \"t0_ms\": 58290, \"t1_ms\": 59480}, {\"speaker_id\": 3, \"t0_ms\": 9980, \"t1_ms\": 16000}, {\"speaker_id\": 3, \"t0_ms\": 48980, \"t1_ms\": 54000}, {\"speaker_id\": 4, \"t0_ms\": 16500, \"t1_ms\": 19100}, {\"speaker_id\": 4, \"t0_ms\": 19170, \"t1_ms\": 21470}, {\"speaker_id\": 4, \"t0_ms\": 80980, \"t1_ms\": 82060}, {\"speaker_id\": 4, \"t0_ms\": 82520, \"t1_ms\": 86990}, {\"speaker_id\": 5, \"t0_ms\": 21990, \"t1_ms\": 24680}, {\"speaker_id\": 5, \"t0_ms\": 25220, \"t1_ms\": 26980}, {\"speaker_id\": 5, \"t0_ms\": 71020, \"t1_ms\": 74800}, {\"speaker_id\": 5, \"t0_ms\": 75380, \"t1_ms\": 76000}, {\"speaker_id\": 6, \"t0_ms\": 26560, \"t1_ms\": 28000}, {\"speaker_id\": 6, \"t0_ms\": 29450, \"t1_ms\": 30000}, {\"speaker_id\": 6, \"t0_ms\": 30790, \"t1_ms\": 32000}, {\"speaker_id\": 6, \"t0_ms\": 65520, \"t1_ms\": 67310}, {\"speaker_id\": 6, \"t0_ms\": 67690, \"t1_ms\": 70500}, {\"speaker_id\": 7, \"t0_ms\": 32530, \"t1_ms\": 37330}, {\"speaker_id\": 7, \"t0_ms\": 60000, \"t1_ms\": 64960}, {\"speaker_id\": 8, \"t0_ms\": 37970, \"t1_ms\": 43000}, {\"speaker_id\": 8, \"t0_ms\": 76470, \"t1_ms\": 81600}]", + "samples/nemotron3-diar-8spk-mix-trunc.wav": " |speakers=[{\"speaker_id\": 1, \"t0_ms\": 0, \"t1_ms\": 1870}, {\"speaker_id\": 1, \"t0_ms\": 2960, \"t1_ms\": 4130}, {\"speaker_id\": 1, \"t0_ms\": 43580, \"t1_ms\": 48490}, {\"speaker_id\": 1, \"t0_ms\": 88190, \"t1_ms\": 90120}, {\"speaker_id\": 1, \"t0_ms\": 91140, \"t1_ms\": 91330}, {\"speaker_id\": 2, \"t0_ms\": 5500, \"t1_ms\": 9530}, {\"speaker_id\": 2, \"t0_ms\": 54500, \"t1_ms\": 57980}, {\"speaker_id\": 2, \"t0_ms\": 58290, \"t1_ms\": 59480}, {\"speaker_id\": 3, \"t0_ms\": 9980, \"t1_ms\": 16000}, {\"speaker_id\": 3, \"t0_ms\": 48980, \"t1_ms\": 54000}, {\"speaker_id\": 4, \"t0_ms\": 16500, \"t1_ms\": 19100}, {\"speaker_id\": 4, \"t0_ms\": 19170, \"t1_ms\": 21470}, {\"speaker_id\": 4, \"t0_ms\": 80980, \"t1_ms\": 82050}, {\"speaker_id\": 4, \"t0_ms\": 82530, \"t1_ms\": 86990}, {\"speaker_id\": 5, \"t0_ms\": 21990, \"t1_ms\": 24680}, {\"speaker_id\": 5, \"t0_ms\": 25220, \"t1_ms\": 26980}, {\"speaker_id\": 5, \"t0_ms\": 71020, \"t1_ms\": 74800}, {\"speaker_id\": 5, \"t0_ms\": 75380, \"t1_ms\": 76000}, {\"speaker_id\": 6, \"t0_ms\": 26560, \"t1_ms\": 28000}, {\"speaker_id\": 6, \"t0_ms\": 29450, \"t1_ms\": 30000}, {\"speaker_id\": 6, \"t0_ms\": 30790, \"t1_ms\": 32000}, {\"speaker_id\": 6, \"t0_ms\": 65520, \"t1_ms\": 67310}, {\"speaker_id\": 6, \"t0_ms\": 67690, \"t1_ms\": 70500}, {\"speaker_id\": 7, \"t0_ms\": 32530, \"t1_ms\": 37330}, {\"speaker_id\": 7, \"t0_ms\": 60000, \"t1_ms\": 64960}, {\"speaker_id\": 8, \"t0_ms\": 37970, \"t1_ms\": 43000}, {\"speaker_id\": 8, \"t0_ms\": 76470, \"t1_ms\": 81600}]", + "samples/zh-long.wav": " |speakers=[{\"speaker_id\": 1, \"t0_ms\": 6640, \"t1_ms\": 10750}, {\"speaker_id\": 1, \"t0_ms\": 11400, \"t1_ms\": 15170}, {\"speaker_id\": 1, \"t0_ms\": 15530, \"t1_ms\": 16820}, {\"speaker_id\": 1, \"t0_ms\": 16960, \"t1_ms\": 19790}, {\"speaker_id\": 1, \"t0_ms\": 20420, \"t1_ms\": 21730}, {\"speaker_id\": 1, \"t0_ms\": 21840, \"t1_ms\": 24940}, {\"speaker_id\": 1, \"t0_ms\": 25200, \"t1_ms\": 26130}]", + "samples/product-names.wav": " |speakers=[{\"speaker_id\": 1, \"t0_ms\": 1930, \"t1_ms\": 3100}, {\"speaker_id\": 1, \"t0_ms\": 3340, \"t1_ms\": 9790}, {\"speaker_id\": 1, \"t0_ms\": 10220, \"t1_ms\": 13150}, {\"speaker_id\": 1, \"t0_ms\": 13440, \"t1_ms\": 15060}, {\"speaker_id\": 1, \"t0_ms\": 15380, \"t1_ms\": 16660}, {\"speaker_id\": 1, \"t0_ms\": 17000, \"t1_ms\": 19760}, {\"speaker_id\": 1, \"t0_ms\": 20140, \"t1_ms\": 22850}, {\"speaker_id\": 1, \"t0_ms\": 23180, \"t1_ms\": 25450}, {\"speaker_id\": 1, \"t0_ms\": 25670, \"t1_ms\": 29080}, {\"speaker_id\": 1, \"t0_ms\": 29580, \"t1_ms\": 33330}, {\"speaker_id\": 1, \"t0_ms\": 33590, \"t1_ms\": 38900}, {\"speaker_id\": 1, \"t0_ms\": 39560, \"t1_ms\": 44930}, {\"speaker_id\": 1, \"t0_ms\": 45180, \"t1_ms\": 47690}, {\"speaker_id\": 1, \"t0_ms\": 48180, \"t1_ms\": 51360}, {\"speaker_id\": 1, \"t0_ms\": 51670, \"t1_ms\": 53570}, {\"speaker_id\": 1, \"t0_ms\": 53910, \"t1_ms\": 55980}]", + "samples/ko-long.wav": " |speakers=[{\"speaker_id\": 1, \"t0_ms\": 1990, \"t1_ms\": 4690}, {\"speaker_id\": 1, \"t0_ms\": 5250, \"t1_ms\": 9260}, {\"speaker_id\": 1, \"t0_ms\": 9610, \"t1_ms\": 10780}, {\"speaker_id\": 1, \"t0_ms\": 11390, \"t1_ms\": 12920}, {\"speaker_id\": 1, \"t0_ms\": 13200, \"t1_ms\": 16830}, {\"speaker_id\": 1, \"t0_ms\": 17130, \"t1_ms\": 18800}, {\"speaker_id\": 1, \"t0_ms\": 19370, \"t1_ms\": 23030}]", + "samples/ar-long.wav": " |speakers=[{\"speaker_id\": 1, \"t0_ms\": 1150, \"t1_ms\": 5060}, {\"speaker_id\": 1, \"t0_ms\": 5500, \"t1_ms\": 13860}, {\"speaker_id\": 1, \"t0_ms\": 14590, \"t1_ms\": 17340}, {\"speaker_id\": 1, \"t0_ms\": 17730, \"t1_ms\": 24020}]" + } +} diff --git a/tests/golden/batch/Nemotron-3-Diarization.list b/tests/golden/batch/Nemotron-3-Diarization.list new file mode 100644 index 00000000..c7014793 --- /dev/null +++ b/tests/golden/batch/Nemotron-3-Diarization.list @@ -0,0 +1,8 @@ +samples/jfk.wav +samples/sortformer-2spk-mix.wav +samples/nemotron3-diar-8spk-mix.wav +samples/nemotron3-diar-8spk-mix-trunc.wav +samples/zh-long.wav +samples/product-names.wav +samples/ko-long.wav +samples/ar-long.wav diff --git a/tests/golden/nemotron3_diar/Nemotron-3-Diarization.manifest.json b/tests/golden/nemotron3_diar/Nemotron-3-Diarization.manifest.json new file mode 100644 index 00000000..eeac5caa --- /dev/null +++ b/tests/golden/nemotron3_diar/Nemotron-3-Diarization.manifest.json @@ -0,0 +1,118 @@ +{ + "schema": "transcribe-golden-manifest-v1", + "family": "nemotron3_diar", + "variant": "Nemotron-3-Diarization", + "source_model": { + "hf_repo": "nvidia/Nemotron-3-Diarization", + "hf_revision": "f667ed73aee57d40cc39428eb768b4fd87a0a29e" + }, + "reference": { + "kind": "nemo", + "source": "https://github.com/NVIDIA-NeMo/Speech", + "revision": "cf724ac337d1ebc7d0dda1e23fb80916f52927a5", + "entrypoint": "scripts/dump_reference_nemotron3_diar_nemo.py", + "dump_args": [ + "--revision", + "f667ed73aee57d40cc39428eb768b4fd87a0a29e" + ], + "cross_check": { + "kind": "transformers", + "source": "https://github.com/huggingface/transformers", + "revision": "4b28d51d0d5f17ec20c23a187d0475a8e68810c8", + "entrypoint": "scripts/dump_reference_nemotron3_diar_nemo.py hf-offline | hf-stream" + } + }, + "expected_dtype": "bfloat16", + "dtype_source": "manual", + "frontend": { + "sample_rate": 16000, + "n_mels": 128, + "hop_length": 160, + "fft_size": 512, + "win_length": 400, + "window": "hann_symmetric", + "normalization": "none", + "preemphasis": 0.97, + "dither": 0.0, + "center": true, + "padding_mode": "constant", + "mel_filterbank_norm": "slaney", + "_notes": "Window and mel filterbank are the checkpoint's BF16-stored buffers (preprocessor.featurizer.{window,fb}), which NeMo uses; fp32-recomputed values differ by up to 1.9e-3. Config dither=1e-5 is training-only (NeMo applies it only when self.training), so inference dither is 0." + }, + "tokenizer_summary": { + "type": "other", + "vocab_size": 0, + "special_tokens": {} + }, + "capabilities": { + "languages": [ + "en" + ], + "language_detection": false, + "translation": false, + "timestamps": [], + "streaming": true, + "speaker_diarization": true + }, + "diarization": { + "max_speakers": 8, + "encoder_frame_period_seconds": 0.08, + "output_frame_period_seconds": 0.01, + "output": "T_mel x 8 sigmoid speaker-activity probabilities at 10 ms (arrival-order columns)", + "presets": { + "very_high_latency": { + "spkcache_len": 264, + "fifo_len": 40, + "chunk_len": 340, + "chunk_right_context": 40, + "spkcache_update_period": 300 + }, + "low_latency": { + "spkcache_len": 264, + "fifo_len": 264, + "chunk_len": 9, + "chunk_right_context": 4, + "spkcache_update_period": 222 + }, + "very_low_latency": { + "spkcache_len": 264, + "fifo_len": 264, + "chunk_len": 6, + "chunk_right_context": 2, + "spkcache_update_period": 222 + }, + "ultra_low_latency": { + "spkcache_len": 264, + "fifo_len": 264, + "chunk_len": 3, + "chunk_right_context": 1, + "spkcache_update_period": 222 + }, + "small": { + "spkcache_len": 24, + "fifo_len": 10, + "chunk_len": 20, + "chunk_right_context": 2, + "spkcache_update_period": 20, + "_diagnostic_only": true + } + }, + "default_preset": "very_high_latency", + "stage_dirs": { + "encoder": "first streaming step at very_high_latency (380 enc frames, empty cache) + full-clip mel", + "diarize": "very_high_latency (offline preset)", + "diarize-low_latency": "low_latency", + "diarize-very_low_latency": "very_low_latency", + "diarize-ultra_low_latency": "ultra_low_latency", + "diarize-small": "small (diagnostic: 56 compressions; tiny cache, not an accuracy preset)" + }, + "compress_dumps": "//compress/compress.NNN.{topk_indices,is_disabled,input_preds,spkcache_preds}.npy", + "oracle_rttm": "tests/golden/nemotron3_diar/nemotron3-diar-8spk-mix.rttm", + "oracle_expectation": "very_high_latency / low_latency / very_low_latency / ultra_low_latency: 8 active channels, authored spk_A..spk_H -> channels 0..7 in arrival order, returning speakers keep their channel" + }, + "tolerance_file": "tests/tolerances/nemotron3_diar.json", + "cases": [ + "nemotron3-diar-8spk-mix", + "nemotron3-diar-8spk-mix-trunc" + ] +} diff --git a/tests/golden/nemotron3_diar/nemotron3-diar-8spk-mix.rttm b/tests/golden/nemotron3_diar/nemotron3-diar-8spk-mix.rttm new file mode 100644 index 00000000..6b7f8086 --- /dev/null +++ b/tests/golden/nemotron3_diar/nemotron3-diar-8spk-mix.rttm @@ -0,0 +1,17 @@ +SPEAKER nemotron3-diar-8spk-mix 1 0.000 5.000 spk_A +SPEAKER nemotron3-diar-8spk-mix 1 5.500 5.000 spk_B +SPEAKER nemotron3-diar-8spk-mix 1 10.000 6.000 spk_C +SPEAKER nemotron3-diar-8spk-mix 1 16.500 5.000 spk_D +SPEAKER nemotron3-diar-8spk-mix 1 22.000 5.000 spk_E +SPEAKER nemotron3-diar-8spk-mix 1 26.500 5.500 spk_F +SPEAKER nemotron3-diar-8spk-mix 1 32.500 5.000 spk_G +SPEAKER nemotron3-diar-8spk-mix 1 38.000 5.000 spk_H +SPEAKER nemotron3-diar-8spk-mix 1 43.500 5.000 spk_A +SPEAKER nemotron3-diar-8spk-mix 1 49.000 5.000 spk_C +SPEAKER nemotron3-diar-8spk-mix 1 54.500 5.000 spk_B +SPEAKER nemotron3-diar-8spk-mix 1 60.000 5.000 spk_G +SPEAKER nemotron3-diar-8spk-mix 1 65.500 5.000 spk_F +SPEAKER nemotron3-diar-8spk-mix 1 71.000 5.000 spk_E +SPEAKER nemotron3-diar-8spk-mix 1 76.500 5.000 spk_H +SPEAKER nemotron3-diar-8spk-mix 1 81.000 6.000 spk_D +SPEAKER nemotron3-diar-8spk-mix 1 87.500 4.500 spk_A diff --git a/tests/nemotron3_diar_stream_unit.cpp b/tests/nemotron3_diar_stream_unit.cpp new file mode 100644 index 00000000..3137a6fb --- /dev/null +++ b/tests/nemotron3_diar_stream_unit.cpp @@ -0,0 +1,343 @@ +// nemotron3_diar_stream_unit.cpp - Nemotron-3-Diarization operating-point +// extensions (N3DR on the RUN slot, N3DS on the STREAM slot) and push-audio +// live diarization. +// +// Covers, against a real GGUF (env-gated, RC 77 skip): +// +// 1. transcribe_model_accepts_ext_kind: N3DR on _RUN only, N3DS on +// _STREAM only; foreign kinds rejected. Init functions stamp +// size/kind/preset. +// 2. Pre-clear rejection: a wrong-kind or out-of-range RUN ext fails with +// INVALID_ARG and PRESERVES the previous result; an out-of-range STREAM +// ext fails transcribe_stream_begin before the snapshot is touched. +// 3. Push-audio at LOW_LATENCY on the 8-speaker oracle, fed in 173 ms +// pieces (not hop-aligned): speaker rows appear DURING feed, audio +// progress is monotonic, an open turn is reported and later extends, +// and the finalized rows equal transcribe_run over the same audio at +// the same preset. Repeated on the non-aligned 91.337 s clip (partial +// final chunk + floor(n/160) framing at finalize). +// 4. RUN ext preset == env preset parity (the validation hook route). +// 5. Input / memory contract: sub-hop input (< 160 samples) is an empty OK +// result (NeMo's floor(n/160) = 0 frames) on run, push-audio and batch, +// and all three agree on short clips; n_ctx is a documented no-op (same +// rows, unbounded limits); an abort mid-run returns ERR_ABORTED. +// +// Gated by TRANSCRIBE_NEMOTRON3_DIAR_GGUF. + +#include "transcribe.h" +#include "transcribe/nemotron3_diar.h" +#include "wav.h" + +#include + +#include +#include +#include +#include +#include + +namespace { + +int g_failures = 0; + +#define CHECK(cond) \ + do { \ + if (!(cond)) { \ + std::fprintf(stderr, "FAIL %s:%d: %s\n", __FILE__, __LINE__, #cond); \ + ++g_failures; \ + } \ + } while (0) + +bool file_exists(const std::string & path) { + struct stat st{}; + return ::stat(path.c_str(), &st) == 0; +} + +std::vector read_segments(const transcribe_session * session) { + std::vector rows; + const int n = transcribe_n_speaker_segments(session); + for (int i = 0; i < n; ++i) { + transcribe_speaker_segment row; + transcribe_speaker_segment_init(&row); + if (transcribe_get_speaker_segment(session, i, &row) == TRANSCRIBE_OK) { + rows.push_back(row); + } + } + return rows; +} + +bool same_segments(const std::vector & a, + const std::vector & b) { + if (a.size() != b.size()) { + return false; + } + for (size_t i = 0; i < a.size(); ++i) { + if (a[i].t0_ms != b[i].t0_ms || a[i].t1_ms != b[i].t1_ms || a[i].speaker_id != b[i].speaker_id) { + return false; + } + } + return true; +} + +int count_speakers(const std::vector & rows) { + std::vector ids; + for (const auto & r : rows) { + if (std::find(ids.begin(), ids.end(), r.speaker_id) == ids.end()) { + ids.push_back(r.speaker_id); + } + } + return static_cast(ids.size()); +} + +// Push-audio vs whole-file run at one preset. +void check_push_audio(transcribe_session * session, + const std::vector & pcm, + transcribe_nemotron3_diar_preset preset, + const char * label) { + transcribe_run_params rp; + transcribe_run_params_init(&rp); + transcribe_nemotron3_diar_run_ext rx; + transcribe_nemotron3_diar_run_ext_init(&rx); + rx.preset = preset; + rp.family = &rx.ext; + CHECK(transcribe_run(session, pcm.data(), static_cast(pcm.size()), &rp) == TRANSCRIBE_OK); + const std::vector run_rows = read_segments(session); + CHECK(count_speakers(run_rows) == 8); + + transcribe_stream_params sp; + transcribe_stream_params_init(&sp); + transcribe_nemotron3_diar_stream_ext sx; + transcribe_nemotron3_diar_stream_ext_init(&sx); + sx.preset = preset; + sp.family = &sx.ext; + CHECK(transcribe_stream_begin(session, nullptr, &sp) == TRANSCRIBE_OK); + + const int piece = 2768; // 173 ms: not a multiple of the 160-sample hop + size_t pos = 0; + int64_t last_committed = 0; + int feeds_with_rows = 0, changed_feeds = 0; + bool saw_open_extend = false; + std::vector prev; + while (pos < pcm.size()) { + const int take = static_cast(std::min(piece, pcm.size() - pos)); + transcribe_stream_update upd; + transcribe_stream_update_init(&upd); + CHECK(transcribe_stream_feed(session, pcm.data() + pos, take, &upd) == TRANSCRIBE_OK); + pos += static_cast(take); + CHECK(upd.audio_committed_ms >= last_committed); + CHECK(upd.audio_committed_ms <= upd.input_received_ms); + last_committed = upd.audio_committed_ms; + changed_feeds += upd.result_changed ? 1 : 0; + const std::vector rows = read_segments(session); + feeds_with_rows += rows.empty() ? 0 : 1; + // An open turn: same (speaker, t0) as last feed, later t1. + for (const auto & r : rows) { + for (const auto & q : prev) { + if (r.speaker_id == q.speaker_id && r.t0_ms == q.t0_ms && r.t1_ms > q.t1_ms) { + saw_open_extend = true; + } + } + } + prev = rows; + } + transcribe_stream_update fin; + transcribe_stream_update_init(&fin); + CHECK(transcribe_stream_finalize(session, &fin) == TRANSCRIBE_OK); + CHECK(fin.is_final); + const std::vector stream_rows = read_segments(session); + + CHECK(feeds_with_rows > 0); + CHECK(changed_feeds > 0); + CHECK(saw_open_extend); + CHECK(same_segments(stream_rows, run_rows)); + std::printf(" %s: run rows=%zu stream rows=%zu feeds-with-rows=%d changed=%d final committed=%lld ms\n", label, + run_rows.size(), stream_rows.size(), feeds_with_rows, changed_feeds, + static_cast(fin.audio_committed_ms)); +} + +int g_abort_polls = 0; +int g_abort_after = 0; + +bool abort_after_n(void * /*user*/) { + return ++g_abort_polls > g_abort_after; +} + +// run, push-audio (one feed) and batch over the same short clip. +void check_short_input(transcribe_session * session, const std::vector & pcm, int n) { + std::vector clip(pcm.begin() + 16000 * 44, pcm.begin() + 16000 * 44 + n); + transcribe_run_params rp; + transcribe_run_params_init(&rp); + const transcribe_status st_run = transcribe_run(session, clip.data(), n, &rp); + const auto run_rows = read_segments(session); + CHECK(st_run == TRANSCRIBE_OK); + + CHECK(transcribe_stream_begin(session, nullptr, nullptr) == TRANSCRIBE_OK); + CHECK(transcribe_stream_feed(session, clip.data(), n, nullptr) == TRANSCRIBE_OK); + CHECK(transcribe_stream_finalize(session, nullptr) == TRANSCRIBE_OK); + CHECK(same_segments(read_segments(session), run_rows)); + + const float * ptrs[2] = { clip.data(), clip.data() }; + const int lens[2] = { n, n }; + CHECK(transcribe_run_batch(session, ptrs, lens, 2, &rp) == TRANSCRIBE_OK); + for (int i = 0; i < 2; ++i) { + CHECK(transcribe_batch_status(session, i) == TRANSCRIBE_OK); + CHECK(transcribe_batch_n_speaker_segments(session, i) == static_cast(run_rows.size())); + } + if (n < 160) { + CHECK(run_rows.empty()); + } +} + +} // namespace + +int main() { + const char * env = std::getenv("TRANSCRIBE_NEMOTRON3_DIAR_GGUF"); + if (env == nullptr || env[0] == '\0') { + std::fprintf(stderr, + "nemotron3_diar_stream_unit: TRANSCRIBE_NEMOTRON3_DIAR_GGUF not set; skipping.\n" + "Re-run with TRANSCRIBE_NEMOTRON3_DIAR_GGUF=models/Nemotron-3-Diarization/" + "Nemotron-3-Diarization-BF16.gguf\n"); + return 77; + } + const std::string gguf = env; + if (!file_exists(gguf)) { + std::fprintf(stderr, "nemotron3_diar_stream_unit: file not found: %s\n", gguf.c_str()); + return 77; + } + std::vector pcm, pcm_trunc; + std::string wav_err; + const std::string dir = TRANSCRIBE_TEST_SAMPLES_DIR; + if (!transcribe_cli::load_wav_mono_16k(dir + "/nemotron3-diar-8spk-mix.wav", pcm, wav_err) || + !transcribe_cli::load_wav_mono_16k(dir + "/nemotron3-diar-8spk-mix-trunc.wav", pcm_trunc, wav_err)) { + std::fprintf(stderr, "nemotron3_diar_stream_unit: wav load: %s\n", wav_err.c_str()); + return 77; + } + ::unsetenv("TRANSCRIBE_NEMOTRON3_DIAR_PRESET"); + + transcribe_model_load_params mp; + transcribe_model_load_params_init(&mp); + mp.backend = TRANSCRIBE_BACKEND_CPU; + struct transcribe_model * model = nullptr; + if (transcribe_model_load_file(gguf.c_str(), &mp, &model) != TRANSCRIBE_OK || model == nullptr) { + std::fprintf(stderr, "FAIL: model load\n"); + return EXIT_FAILURE; + } + + // 1. Kind + slot probe, init stamping. + CHECK(transcribe_model_accepts_ext_kind(model, TRANSCRIBE_EXT_SLOT_RUN, TRANSCRIBE_EXT_KIND_NEMOTRON3_DIAR_RUN)); + CHECK( + !transcribe_model_accepts_ext_kind(model, TRANSCRIBE_EXT_SLOT_STREAM, TRANSCRIBE_EXT_KIND_NEMOTRON3_DIAR_RUN)); + CHECK(transcribe_model_accepts_ext_kind(model, TRANSCRIBE_EXT_SLOT_STREAM, + TRANSCRIBE_EXT_KIND_NEMOTRON3_DIAR_STREAM)); + CHECK( + !transcribe_model_accepts_ext_kind(model, TRANSCRIBE_EXT_SLOT_RUN, TRANSCRIBE_EXT_KIND_NEMOTRON3_DIAR_STREAM)); + CHECK(!transcribe_model_accepts_ext_kind(model, TRANSCRIBE_EXT_SLOT_RUN, 0x54534653u /* SFST */)); + { + transcribe_nemotron3_diar_run_ext rx; + transcribe_nemotron3_diar_run_ext_init(&rx); + CHECK(rx.ext.size == sizeof(rx)); + CHECK(rx.ext.kind == TRANSCRIBE_EXT_KIND_NEMOTRON3_DIAR_RUN); + CHECK(rx.preset == TRANSCRIBE_NEMOTRON3_DIAR_PRESET_DEFAULT); + transcribe_nemotron3_diar_stream_ext sx; + transcribe_nemotron3_diar_stream_ext_init(&sx); + CHECK(sx.ext.size == sizeof(sx)); + CHECK(sx.ext.kind == TRANSCRIBE_EXT_KIND_NEMOTRON3_DIAR_STREAM); + CHECK(sx.preset == TRANSCRIBE_NEMOTRON3_DIAR_PRESET_DEFAULT); + } + + struct transcribe_session * session = nullptr; + if (transcribe_session_init(model, nullptr, &session) != TRANSCRIBE_OK || session == nullptr) { + std::fprintf(stderr, "FAIL: session create\n"); + transcribe_model_free(model); + return EXIT_FAILURE; + } + + // Baseline: default preset (= VERY_HIGH_LATENCY) via the env hook route. + transcribe_run_params rp; + transcribe_run_params_init(&rp); + ::setenv("TRANSCRIBE_NEMOTRON3_DIAR_PRESET", "very_high_latency", 1); + CHECK(transcribe_run(session, pcm.data(), static_cast(pcm.size()), &rp) == TRANSCRIBE_OK); + const std::vector env_rows = read_segments(session); + ::unsetenv("TRANSCRIBE_NEMOTRON3_DIAR_PRESET"); + CHECK(count_speakers(env_rows) == 8); + + // 2. Pre-clear rejection preserves the previous result. + { + transcribe_nemotron3_diar_run_ext bad; + transcribe_nemotron3_diar_run_ext_init(&bad); + bad.ext.kind = 0x54534653u; // SFST: another family's kind + rp.family = &bad.ext; + CHECK(transcribe_run(session, pcm.data(), static_cast(pcm.size()), &rp) == TRANSCRIBE_ERR_INVALID_ARG); + CHECK(same_segments(read_segments(session), env_rows)); + + transcribe_nemotron3_diar_run_ext oor; + transcribe_nemotron3_diar_run_ext_init(&oor); + oor.preset = static_cast(99); + rp.family = &oor.ext; + CHECK(transcribe_run(session, pcm.data(), static_cast(pcm.size()), &rp) == TRANSCRIBE_ERR_INVALID_ARG); + CHECK(same_segments(read_segments(session), env_rows)); + + transcribe_stream_params sp; + transcribe_stream_params_init(&sp); + transcribe_nemotron3_diar_stream_ext soor; + transcribe_nemotron3_diar_stream_ext_init(&soor); + soor.preset = static_cast(99); + sp.family = &soor.ext; + CHECK(transcribe_stream_begin(session, nullptr, &sp) == TRANSCRIBE_ERR_INVALID_ARG); + CHECK(same_segments(read_segments(session), env_rows)); + } + + // 4. RUN ext preset == env preset. + { + transcribe_nemotron3_diar_run_ext vh; + transcribe_nemotron3_diar_run_ext_init(&vh); + vh.preset = TRANSCRIBE_NEMOTRON3_DIAR_PRESET_VERY_HIGH_LATENCY; + rp.family = &vh.ext; + CHECK(transcribe_run(session, pcm.data(), static_cast(pcm.size()), &rp) == TRANSCRIBE_OK); + CHECK(same_segments(read_segments(session), env_rows)); + } + + // 5. Input / memory contract. + for (const int n : { 1, 159, 160, 400, 1601, 8000 }) { + check_short_input(session, pcm, n); + } + { + transcribe_session_params sp; + transcribe_session_params_init(&sp); + sp.n_ctx = 16; // ignored: no lowerable context + struct transcribe_session * s_nc = nullptr; + CHECK(transcribe_session_init(model, &sp, &s_nc) == TRANSCRIBE_OK); + transcribe_session_limits lim; + transcribe_session_limits_init(&lim); + CHECK(transcribe_session_get_limits(s_nc, &lim) == TRANSCRIBE_OK); + CHECK(lim.effective_max_audio_ms == 0); + transcribe_run_params rp0; + transcribe_run_params_init(&rp0); + CHECK(transcribe_run(s_nc, pcm.data(), static_cast(pcm.size()), &rp0) == TRANSCRIBE_OK); + CHECK(same_segments(read_segments(s_nc), env_rows)); + + g_abort_polls = 0; + g_abort_after = 2; // pass the entry poll + one chunk, then abort + transcribe_set_abort_callback(s_nc, abort_after_n, nullptr); + CHECK(transcribe_run(s_nc, pcm.data(), static_cast(pcm.size()), &rp0) == TRANSCRIBE_ERR_ABORTED); + CHECK(transcribe_was_aborted(s_nc)); + transcribe_set_abort_callback(s_nc, nullptr, nullptr); + transcribe_session_free(s_nc); + } + + // 3. Push-audio == whole-file run. + check_push_audio(session, pcm, TRANSCRIBE_NEMOTRON3_DIAR_PRESET_LOW_LATENCY, "low_latency 92s"); + check_push_audio(session, pcm_trunc, TRANSCRIBE_NEMOTRON3_DIAR_PRESET_LOW_LATENCY, "low_latency 91.337s"); + check_push_audio(session, pcm_trunc, TRANSCRIBE_NEMOTRON3_DIAR_PRESET_VERY_HIGH_LATENCY, + "very_high_latency 91.337s"); + + transcribe_session_free(session); + transcribe_model_free(model); + + if (g_failures != 0) { + std::fprintf(stderr, "nemotron3_diar_stream_unit: %d failure(s)\n", g_failures); + return EXIT_FAILURE; + } + std::printf("nemotron3_diar_stream_unit: OK\n"); + return 0; +} diff --git a/tests/tolerances/nemotron3_diar.json b/tests/tolerances/nemotron3_diar.json new file mode 100644 index 00000000..5eab76db --- /dev/null +++ b/tests/tolerances/nemotron3_diar.json @@ -0,0 +1,99 @@ +{ + "_comment": [ + "Nemotron-3-Diarization per-tensor tolerances for compare_tensors.py. FINALIZED at Stage 4 (2026-09-25).", + "", + "CORRECTNESS REGIME", + "- Reference: NeMo Speech cf724ac337d1 SortformerEncLabelModel, BF16 .nemo weights upcast to fp32, fp32 compute on CPU, bit-deterministic (scripts/dump_reference_nemotron3_diar_nemo.py).", + "- C++: BF16 reference GGUF (models/Nemotron-3-Diarization/Nemotron-3-Diarization-BF16.gguf), --backend cpu. On CPU the loader upcasts every BF16/F16 matmul weight to exact F32 copies (the default CPU regime, see src/arch/nemotron3_diar/model.cpp load()), so all compute is fp32 like the reference. No KV cache (full attention over [spkcache|fifo|chunk] each step).", + "- Mel: the production C++ MelFrontend on real PCM (no reference-mel injection) with the checkpoint's BF16-stored window + filterbank from the GGUF; enc.mel.in is therefore the frontend-parity gate.", + "- Threads: measured with VALIDATE_CPP_THREADS=6/8; all 40 dumped tensors (encoder stage + a 56-compression small-preset run) verified bit-identical between --threads 1 and 8, so this equals the default --threads 1 regime.", + "- Coverage: 2 cases (8-speaker 92 s oracle; its non-aligned 91.337 s truncation) x stages encoder (streaming step 0 at very_high_latency, 380 encoder frames, empty cache), diarize- for very_high_latency / low_latency / very_low_latency / ultra_low_latency / small, and stream- (push-audio, 173 ms pieces) compared against the same reference diar.probs. Each stream-/cpp is additionally required to equal diarize-/cpp exactly (zero tolerance).", + "- FORCED PICKS: diarize/stream stages run with the reference's speaker-cache selections injected (TRANSCRIBE_NEMOTRON3_DIAR_COMPRESS_FROM_REF, set by validate.py; VALIDATE_NEMOTRON3_DIAR_FORCE_PICKS=0 disables) - an isolation tool like reference-mel injection. Reason: the AOSC compression is a top-k whose boundary scores are routinely 1e-7..1e-5 apart, so fp32 GEMM noise (~3e-6 on the inputs) can flip a near-tied pick and every later chunk differs (observed unforced: 0.64 s preset on the oracle, 1 pick at compression #1 -> diar.probs 2.1e-2; 6 of 96 acceptance meetings at 30.4 s). diar.probs here therefore gates the graph + bookkeeping; it is NOT widened to absorb flips. The selection is gated separately and exactly: scripts/diar/check_nemotron3_diar_compress.py replays NeMo's _compress_spkcache on the port's own inputs (370/370 compressions identical over every oracle preset + ES2004c at 30.4 s + TS3003d at 1.04 s), and end to end by DER/JER (+-0.01pp gate; 0.00pp at 30.4 s on AMI + NOTSOFAR).", + "", + "RECIPE: finalized = max(1.5 x observed, Stage-2 magnitude budget [1e-4 x p99_abs / 1e-5 x rms], 1e-6). Observed drift is 5-30x below the magnitude budget on every tensor, so every entry keeps its Stage-2 budget (tightening needs user sign-off). No entry was widened. Observed values are recorded per entry (_observed_max_abs / _observed_mean_abs, worst over cases/stages).", + "", + "DOMINANT DRIFT SOURCE: fp32 accumulation order (ggml vs ATen GEMM / softmax / LayerNorm) growing through 31 layers; largest at the un-normalized enc.layers.30.out (4.2e-3 on |x| up to ~470), removed again by final_norm. Frontend: FFT precision (enc.mel.in 3.0e-4 on log-mel values up to ~16).", + "", + "WHY F32 COMPUTE (measured, not assumed): with native BF16 matmuls ggml-cpu rounds the fp32 activations to BF16 inside each dot product. enc.pre_encode.out then drifts 2.6e-1 max / 2.7e-2 mean, compression #1 selects 43 different cache frames and diar.probs drifts 3.2e-2 on the 92 s oracle - a discontinuous divergence (top-k cache selection), not absorbable drift. TRANSCRIBE_NEMOTRON3_DIAR_NATIVE_BF16=1 reproduces that regime; it is outside these tolerances.", + "", + "For context only: the HF transformers cross-check (fp32-recomputed window/fb) differs from NeMo on diar.probs by mean|d| ~1e-3, max|d| 8e-2 (offline) to 2.5e-1 (ultra_low_latency). The C++ tracks NeMo, not HF." + ], + "diar.encoder_proj.out": { + "max_abs": 0.0007163617610931413, + "mean_abs": 2.014040443507882e-05, + "_observed_max_abs": 0.0001183, + "_observed_mean_abs": 2.16e-06 + }, + "diar.logits": { + "max_abs": 0.0029657003059387343, + "mean_abs": 9.897325499107615e-05, + "_observed_max_abs": 0.000104, + "_observed_mean_abs": 5.77e-06 + }, + "diar.probs": { + "max_abs": 9.912681931257248e-05, + "mean_abs": 3.0405652021332573e-06, + "_observed_max_abs": 1.764e-05, + "_observed_mean_abs": 1.383e-07 + }, + "diar.subpixel_conv.out": { + "max_abs": 0.0010900764598846425, + "mean_abs": 2.819843288384341e-05, + "_observed_max_abs": 0.0001411, + "_observed_mean_abs": 2.69e-06 + }, + "diar.upsample.out": { + "max_abs": 0.0010900764598846425, + "mean_abs": 2.819843288384341e-05, + "_observed_max_abs": 0.0001411, + "_observed_mean_abs": 2.69e-06 + }, + "enc.embed_norm.out": { + "max_abs": 0.0009864868354797374, + "mean_abs": 2.5216983740198924e-05, + "_observed_max_abs": 6.995e-05, + "_observed_mean_abs": 2.529e-06 + }, + "enc.final_norm.out": { + "max_abs": 0.00028977825140953076, + "mean_abs": 8.932525577091465e-06, + "_observed_max_abs": 6.485e-05, + "_observed_mean_abs": 1.3e-06 + }, + "enc.layers.0.out": { + "max_abs": 0.0011261121091842654, + "mean_abs": 3.517366193862893e-05, + "_observed_max_abs": 7.82e-05, + "_observed_mean_abs": 2.879e-06 + }, + "enc.layers.1.out": { + "max_abs": 0.0011609788312912002, + "mean_abs": 4.431690256720955e-05, + "_observed_max_abs": 7.629e-05, + "_observed_mean_abs": 3.171e-06 + }, + "enc.layers.15.out": { + "max_abs": 0.0021360501537323008, + "mean_abs": 8.956734723381705e-05, + "_observed_max_abs": 0.0001907, + "_observed_mean_abs": 6.681e-06 + }, + "enc.layers.30.out": { + "max_abs": 0.04666371978759768, + "mean_abs": 0.0007077871037846011, + "_observed_max_abs": 0.004211, + "_observed_mean_abs": 4.486e-05 + }, + "enc.mel.in": { + "max_abs": 0.0016635532379150391, + "mean_abs": 0.00010526379603602358, + "_observed_max_abs": 0.0003033, + "_observed_mean_abs": 8.216e-07 + }, + "enc.pre_encode.out": { + "max_abs": 0.0032059184112548907, + "mean_abs": 7.515863409117101e-05, + "_observed_max_abs": 0.0002813, + "_observed_mean_abs": 8.671e-06 + } +} diff --git a/tools/transcribe-quantize/policy.cpp b/tools/transcribe-quantize/policy.cpp index f908dfa8..780754d2 100644 --- a/tools/transcribe-quantize/policy.cpp +++ b/tools/transcribe-quantize/policy.cpp @@ -141,6 +141,12 @@ Bucket classify_tensor(const std::string & name, int64_t ne0) { if (name == "frontend.mel_filterbank" || name == "frontend.window") { return Bucket::Norm; } + // Nemotron-3-Diarization: learned AOSC silence embedding (1D, d_model). + // Written into the speaker cache's silence slots in F32; too small to + // benefit from quantization. + if (name == "diar.sil_emb") { + return Bucket::Norm; + } // SenseVoice: per-feature CMVN shift/scale (1D, d_input). Applied // additively/multiplicatively to the LFR-stacked mel frame; loader // requires F32, and the tensors are too small (560 elements) to