omnius 1.0.641 → 1.0.643

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -618,10 +618,28 @@ function installSystemd(nodeBin, omniusScript, user) {
618
618
 
619
619
  var unitDir = path.join(userHome, ".config", "systemd", "user");
620
620
  var unitPath = path.join(unitDir, SERVICE_LABEL + ".service");
621
+ var envDir = path.join(userHome, ".config", "omnius");
622
+ var envPath = path.join(envDir, "daemon.env");
621
623
  var logDir = path.join(userHome, ".omnius");
622
624
 
623
625
  try { fs.mkdirSync(unitDir, { recursive: true }); } catch (e) {}
626
+ try { fs.mkdirSync(envDir, { recursive: true }); } catch (e) {}
624
627
  try { fs.mkdirSync(logDir, { recursive: true }); } catch (e) {}
628
+ try {
629
+ if (!fs.existsSync(envPath)) {
630
+ fs.writeFileSync(envPath, [
631
+ "# Optional Omnius daemon runtime overrides (KEY=value).",
632
+ "# OMNIUS_AUDIO_PYTHON=/absolute/path/to/jetpack/python",
633
+ "# OMNIUS_HF_TOKEN=hf_setup_only_token",
634
+ "# OMNIUS_PYANNOTE_TERMS_ACCEPTED=1",
635
+ "",
636
+ ].join("\n"), { encoding: "utf8", mode: 0o600 });
637
+ } else {
638
+ try { fs.chmodSync(envPath, 0o600); } catch (e) {}
639
+ }
640
+ } catch (e) {
641
+ warn("Could not create optional daemon environment file: " + (e && e.message));
642
+ }
625
643
 
626
644
  // The unit runs: <node> <omniusScript> serve --daemon --quiet
627
645
  // with OMNIUS_DAEMON=1 so the serve command picks the daemon path.
@@ -639,6 +657,7 @@ function installSystemd(nodeBin, omniusScript, user) {
639
657
  "Environment=OMNIUS_DAEMON=1",
640
658
  "Environment=OMNIUS_PORT=" + PORT,
641
659
  "Environment=NODE_ENV=production",
660
+ "EnvironmentFile=-" + envPath,
642
661
  "ExecStart=" + nodeBin + " " + omniusScript + " serve --daemon --quiet",
643
662
  // Restart=always (was on-failure) — also relaunch on clean exit.
644
663
  // Some upgrade flows trigger process.exit(0) (e.g. /update reload,
@@ -16,6 +16,8 @@ import wave
16
16
 
17
17
  if os.environ.get("HF_HUB_OFFLINE") != "1" or os.environ.get("TRANSFORMERS_OFFLINE") != "1":
18
18
  raise RuntimeError("CLAP worker must be started with offline model access enabled")
19
+ if os.environ.get("PYTHONNOUSERSITE") != "1":
20
+ raise RuntimeError("CLAP worker must run from the isolated managed venv without user-site packages")
19
21
 
20
22
  import numpy as np
21
23
  import torch
@@ -179,11 +179,17 @@ SMALL_CROP_AREA_PX = 512_000
179
179
  MEDIUM_IMAGE_AREA_PX = 2_000_000
180
180
  MAX_PIPELINE_DEADLINE_MS = 80_000
181
181
  MAX_TESSERACT_ATTEMPT_SECONDS = 12.0
182
+ # Small conditioned crops normally complete in well under a second. A
183
+ # six-second cap still permits a busy Tesseract child, while keeping a failed
184
+ # recovery pair from consuming most of the REST deadline.
185
+ SMALL_CROP_TESSERACT_ATTEMPT_SECONDS = 6.0
182
186
  MIN_TESSERACT_ATTEMPT_SECONDS = 0.25
183
187
  MIN_ACCEPTED_CONFIDENCE = 50.0
184
188
  MIN_SUBSTANTIVE_TEXT_CHARS = 12
185
189
  HIGH_VOLUME_GARBAGE_CHARS = 64
186
190
  HIGH_VOLUME_GARBAGE_CONFIDENCE = 35.0
191
+ TERMINAL_SYMBOL_GARBAGE_CHARS = 24
192
+ TERMINAL_SYMBOL_GARBAGE_ALNUM_RATIO = 0.20
187
193
  ACTIVE_DEADLINE = None
188
194
 
189
195
 
@@ -211,11 +217,11 @@ class OcrDeadline:
211
217
  if self.remaining_seconds() <= 0:
212
218
  raise OcrPipelineTimeout(f"OCR deadline exceeded during {stage}")
213
219
 
214
- def tesseract_timeout_seconds(self):
220
+ def tesseract_timeout_seconds(self, attempt_cap_seconds=MAX_TESSERACT_ATTEMPT_SECONDS):
215
221
  self.check("Tesseract scheduling")
216
222
  return max(
217
223
  MIN_TESSERACT_ATTEMPT_SECONDS,
218
- min(MAX_TESSERACT_ATTEMPT_SECONDS, self.remaining_seconds()),
224
+ min(attempt_cap_seconds, self.remaining_seconds()),
219
225
  )
220
226
 
221
227
 
@@ -251,7 +257,8 @@ def text_from_tesseract_data(data):
251
257
  return "\n".join(" ".join(words) for words in lines.values()).strip()
252
258
 
253
259
 
254
- def run_tesseract(binary_img, deadline, language="eng", psm=6):
260
+ def run_tesseract(binary_img, deadline, language="eng", psm=6,
261
+ attempt_cap_seconds=MAX_TESSERACT_ATTEMPT_SECONDS):
255
262
  """Run one bounded Tesseract TSV pass and derive text plus confidence."""
256
263
  deadline.check("Tesseract")
257
264
  pil_img = Image.fromarray(binary_img)
@@ -261,7 +268,7 @@ def run_tesseract(binary_img, deadline, language="eng", psm=6):
261
268
  lang=language,
262
269
  config=config,
263
270
  output_type=pytesseract.Output.DICT,
264
- timeout=deadline.tesseract_timeout_seconds(),
271
+ timeout=deadline.tesseract_timeout_seconds(attempt_cap_seconds),
265
272
  )
266
273
  text = text_from_tesseract_data(data)
267
274
  confs = []
@@ -331,6 +338,24 @@ def assess_ocr_evidence(text, confidence, line_count):
331
338
  }
332
339
 
333
340
 
341
+ def is_terminal_small_crop_rejection(evidence):
342
+ """Whether a first small-crop pass proves another recovery pass is futile.
343
+
344
+ Blanks and plausible alphanumeric low-confidence text remain recoverable
345
+ through the second variant. A large very-low-confidence transcript or a
346
+ distinctly symbol-heavy stream cannot become safe evidence through another
347
+ PSM6 pass, and should not make callers wait for one.
348
+ """
349
+ if evidence.get("state") != "rejected":
350
+ return False
351
+ if evidence.get("reason") == "high_volume_low_confidence_text":
352
+ return True
353
+ return (
354
+ evidence.get("chars", 0) >= TERMINAL_SYMBOL_GARBAGE_CHARS
355
+ and evidence.get("alnum_ratio", 1.0) < TERMINAL_SYMBOL_GARBAGE_ALNUM_RATIO
356
+ )
357
+
358
+
334
359
  def compute_score(text, confidence, line_count, evidence=None):
335
360
  """Combined scoring heuristic:
336
361
  - confidence * sqrt(char_count) — rewards quality and coverage
@@ -451,15 +476,18 @@ def write_all_outputs(text, base_name, output_dir):
451
476
  # Main pipeline
452
477
  # ---------------------------------------------------------------------------
453
478
 
454
- def run_variant_plan(gray, plan, deadline, language, debug_dir=None, debug_prefix="full"):
479
+ def run_variant_plan(gray, plan, deadline, language, debug_dir=None, debug_prefix="full",
480
+ attempt_cap_seconds=MAX_TESSERACT_ATTEMPT_SECONDS,
481
+ stop_on_terminal_rejection=False):
455
482
  """Run a bounded plan, stopping early once a legible result is proven."""
456
483
  all_results = {}
457
484
  ocr_errors = []
458
485
  best_key = None
459
486
  best_score = -1
460
487
  early_exit = False
488
+ terminal_rejection = False
461
489
 
462
- for vname, psm in plan:
490
+ for attempt_index, (vname, psm) in enumerate(plan):
463
491
  deadline.check("preprocessing")
464
492
  key = f"{vname}_psm{psm}"
465
493
  try:
@@ -471,7 +499,9 @@ def run_variant_plan(gray, plan, deadline, language, debug_dir=None, debug_prefi
471
499
  os.makedirs(debug_dir, exist_ok=True)
472
500
  cv2.imwrite(os.path.join(debug_dir, f"{debug_prefix}_{vname}.png"), binary)
473
501
  try:
474
- text, confidence, line_count = run_tesseract(binary, deadline, language, psm)
502
+ text, confidence, line_count = run_tesseract(
503
+ binary, deadline, language, psm, attempt_cap_seconds
504
+ )
475
505
  except OcrPipelineTimeout:
476
506
  raise
477
507
  except OcrPipelineCancelled:
@@ -496,8 +526,15 @@ def run_variant_plan(gray, plan, deadline, language, debug_dir=None, debug_prefi
496
526
  if has_sufficient_evidence(text, confidence, line_count):
497
527
  early_exit = True
498
528
  break
529
+ if (
530
+ stop_on_terminal_rejection
531
+ and attempt_index == 0
532
+ and is_terminal_small_crop_rejection(evidence)
533
+ ):
534
+ terminal_rejection = True
535
+ break
499
536
 
500
- return all_results, ocr_errors, best_key, early_exit
537
+ return all_results, ocr_errors, best_key, early_exit, terminal_rejection
501
538
 
502
539
 
503
540
  def run_pipeline(image_path, deadline, language="eng", do_regions=False, debug_dir=None,
@@ -522,10 +559,22 @@ def run_pipeline(image_path, deadline, language="eng", do_regions=False, debug_d
522
559
  deadline.check("image conditioning")
523
560
  gray_2x = upscale_2x(gray)
524
561
  plan = build_ocr_plan(effective_area, single_psm)
562
+ is_small_crop = effective_area <= SMALL_CROP_AREA_PX
563
+ attempt_cap_seconds = (
564
+ SMALL_CROP_TESSERACT_ATTEMPT_SECONDS
565
+ if is_small_crop
566
+ else MAX_TESSERACT_ATTEMPT_SECONDS
567
+ )
525
568
  attempts_completed = 0
526
569
  try:
527
- all_results, ocr_errors, best_key, early_exit = run_variant_plan(
528
- gray_2x, plan, deadline, language, debug_dir
570
+ all_results, ocr_errors, best_key, early_exit, terminal_rejection = run_variant_plan(
571
+ gray_2x,
572
+ plan,
573
+ deadline,
574
+ language,
575
+ debug_dir,
576
+ attempt_cap_seconds=attempt_cap_seconds,
577
+ stop_on_terminal_rejection=is_small_crop,
529
578
  )
530
579
  attempts_completed = len(all_results)
531
580
  if not best_key:
@@ -545,6 +594,8 @@ def run_pipeline(image_path, deadline, language="eng", do_regions=False, debug_d
545
594
  "diagnostic": {
546
595
  **diagnostic("ocr_evidence_rejected", message, deadline, "evidence_quality", attempts_completed, len(plan)),
547
596
  "evidence": evidence,
597
+ "terminal_small_crop_rejection": terminal_rejection,
598
+ "attempt_cap_seconds": attempt_cap_seconds,
548
599
  },
549
600
  }
550
601
  # Empty or tiny non-substantive detections are valid observations:
@@ -579,6 +630,8 @@ def run_pipeline(image_path, deadline, language="eng", do_regions=False, debug_d
579
630
  "attempts_planned": len(plan),
580
631
  "attempts_completed": attempts_completed,
581
632
  "early_exit": False,
633
+ "terminal_small_crop_rejection": terminal_rejection,
634
+ "attempt_cap_seconds": attempt_cap_seconds,
582
635
  "deadline_ms": deadline.deadline_ms,
583
636
  },
584
637
  }
@@ -615,6 +668,8 @@ def run_pipeline(image_path, deadline, language="eng", do_regions=False, debug_d
615
668
  "attempts_planned": len(plan),
616
669
  "attempts_completed": attempts_completed,
617
670
  "early_exit": early_exit,
671
+ "terminal_small_crop_rejection": terminal_rejection,
672
+ "attempt_cap_seconds": attempt_cap_seconds,
618
673
  "deadline_ms": deadline.deadline_ms,
619
674
  },
620
675
  }
@@ -627,9 +682,22 @@ def run_pipeline(image_path, deadline, language="eng", do_regions=False, debug_d
627
682
  region_gray = extract_region(gray_2x, y_start, y_end)
628
683
  # Region requests use at most two high-yield attempts. The
629
684
  # main result already provides full-frame coverage.
630
- region_plan = build_ocr_plan(region_gray.shape[0] * region_gray.shape[1], single_psm)[:2]
631
- region_results, _errors, region_best_key, _early_exit = run_variant_plan(
632
- region_gray, region_plan, deadline, language, debug_dir, f"region_{rname}"
685
+ region_area = region_gray.shape[0] * region_gray.shape[1]
686
+ region_plan = build_ocr_plan(region_area, single_psm)[:2]
687
+ region_is_small = region_area <= SMALL_CROP_AREA_PX
688
+ region_results, _errors, region_best_key, _early_exit, _terminal_rejection = run_variant_plan(
689
+ region_gray,
690
+ region_plan,
691
+ deadline,
692
+ language,
693
+ debug_dir,
694
+ f"region_{rname}",
695
+ attempt_cap_seconds=(
696
+ SMALL_CROP_TESSERACT_ATTEMPT_SECONDS
697
+ if region_is_small
698
+ else MAX_TESSERACT_ATTEMPT_SECONDS
699
+ ),
700
+ stop_on_terminal_rejection=region_is_small,
633
701
  )
634
702
  regions[rname] = region_results[region_best_key]["text"] if region_best_key else ""
635
703
  result["regions"] = regions
@@ -12,6 +12,7 @@ from __future__ import annotations
12
12
  import argparse
13
13
  import json
14
14
  import os
15
+ import subprocess
15
16
  import sys
16
17
  import time
17
18
  import wave
@@ -39,6 +40,33 @@ def validate_live_wav(path: str) -> None:
39
40
  raise ValueError("live diarization accepts retained audio windows greater than 0 and at most 6 seconds")
40
41
 
41
42
 
43
+ def load_pcm16_waveform(path: str):
44
+ """Decode locally without TorchCodec, which has no CPython 3.10/aarch64 wheel."""
45
+ import numpy as np
46
+ import torch
47
+
48
+ try:
49
+ with wave.open(path, "rb") as wav:
50
+ if wav.getcomptype() != "NONE" or wav.getsampwidth() != 2:
51
+ raise ValueError("offline reconciliation requires uncompressed PCM16 WAV")
52
+ channels = wav.getnchannels()
53
+ sample_rate = wav.getframerate()
54
+ frames = wav.readframes(wav.getnframes())
55
+ except Exception as exc:
56
+ raise ValueError(f"offline reconciliation requires readable PCM16 WAV: {exc}") from exc
57
+ if channels < 1 or sample_rate < 1 or not frames:
58
+ raise ValueError("offline reconciliation WAV is empty or invalid")
59
+ samples = np.frombuffer(frames, dtype="<i2")
60
+ if samples.size % channels:
61
+ raise ValueError("offline reconciliation WAV has an incomplete PCM frame")
62
+ waveform = samples.reshape(-1, channels).T.astype(np.float32) / 32768.0
63
+ return {
64
+ "waveform": torch.from_numpy(waveform),
65
+ "sample_rate": sample_rate,
66
+ "uri": Path(path).stem,
67
+ }
68
+
69
+
42
70
  def number(value: Any) -> float | None:
43
71
  try:
44
72
  result = float(value)
@@ -64,6 +92,9 @@ def append_segment(result: list[dict[str, Any]], start: Any, end: Any, speaker:
64
92
 
65
93
  def normalize_sortformer(value: Any) -> list[dict[str, Any]]:
66
94
  """Accept NeMo's current tuple/list result variants without inventing data."""
95
+ rttm_text = isinstance(value, str)
96
+ if isinstance(value, str):
97
+ value = [line for line in value.splitlines() if line.strip()]
67
98
  while isinstance(value, list) and len(value) == 1 and isinstance(value[0], (list, tuple)):
68
99
  value = value[0]
69
100
  result: list[dict[str, Any]] = []
@@ -83,12 +114,60 @@ def normalize_sortformer(value: Any) -> list[dict[str, Any]]:
83
114
  duration = number(fields[4])
84
115
  if start is not None and duration is not None:
85
116
  append_segment(result, start, start + duration, fields[7])
86
- if not result and value:
117
+ # A native RTTM command may emit diagnostics but no SPEAKER rows for a
118
+ # genuine silent window. Structured NeMo objects that are non-empty yet
119
+ # unparseable remain a hard contract failure.
120
+ if not result and value and not rttm_text:
87
121
  raise RuntimeError("Sortformer result contained no parseable speaker turns")
88
122
  return result
89
123
 
90
124
 
91
125
  def load_live(args: argparse.Namespace):
126
+ if args.native_binary:
127
+ binary = Path(args.native_binary)
128
+ if not binary.is_file() or not os.access(binary, os.X_OK):
129
+ raise RuntimeError("managed nemo-speech binary is missing or not executable")
130
+ # Metadata inspection validates that this exact Q8 artifact is readable
131
+ # without keeping a second Python/Torch stack on JetPack.
132
+ inspected = subprocess.run(
133
+ [str(binary), "model", "info", args.artifact_path],
134
+ text=True,
135
+ capture_output=True,
136
+ timeout=60,
137
+ check=False,
138
+ )
139
+ if inspected.returncode != 0:
140
+ raise RuntimeError(
141
+ f"nemo-speech rejected the pinned Sortformer model: {(inspected.stderr or inspected.stdout)[-1000:]}"
142
+ )
143
+
144
+ def diarize_native(path: str) -> list[dict[str, Any]]:
145
+ validate_live_wav(path)
146
+ completed = subprocess.run(
147
+ [
148
+ str(binary),
149
+ "diarize",
150
+ path,
151
+ "--model",
152
+ args.artifact_path,
153
+ "--device",
154
+ "cuda:0",
155
+ "--format",
156
+ "rttm",
157
+ ],
158
+ text=True,
159
+ capture_output=True,
160
+ timeout=90,
161
+ check=False,
162
+ )
163
+ if completed.returncode != 0:
164
+ raise RuntimeError(
165
+ f"nemo-speech diarization failed: {(completed.stderr or completed.stdout)[-2000:]}"
166
+ )
167
+ return normalize_sortformer(completed.stdout)
168
+
169
+ return binary, diarize_native, "nemo-speech-cpp-q8-cuda"
170
+
92
171
  import torch
93
172
  from nemo.collections.asr.models import SortformerEncLabelModel
94
173
 
@@ -122,7 +201,10 @@ def load_reconcile(args: argparse.Namespace):
122
201
  def diarize(path: str) -> list[dict[str, Any]]:
123
202
  if not Path(path).is_file():
124
203
  raise ValueError("offline reconciliation audio file does not exist")
125
- output = pipeline(path)
204
+ # pyannote.audio officially supports in-memory waveform mappings. This
205
+ # avoids its optional TorchCodec decoder, which has no aarch64 wheel,
206
+ # without weakening the model or touching JetPack CUDA Torch.
207
+ output = pipeline(load_pcm16_waveform(path))
126
208
  annotation = getattr(output, "speaker_diarization", output)
127
209
  turns = getattr(annotation, "itertracks", None)
128
210
  if not callable(turns):
@@ -142,6 +224,7 @@ def main() -> int:
142
224
  parser.add_argument("--artifact-path", required=True)
143
225
  parser.add_argument("--revision", required=True)
144
226
  parser.add_argument("--model-digest", required=True)
227
+ parser.add_argument("--native-binary")
145
228
  args = parser.parse_args()
146
229
 
147
230
  # Do not allow an accidentally inherited setup token to become a request
@@ -17,6 +17,12 @@ import sys
17
17
 
18
18
  MODEL = "ViT-B-32"
19
19
  PRETRAINED = "laion2b_s34b_b79k"
20
+ MODEL_REPOSITORY = "laion/CLIP-ViT-B-32-laion2B-s34B-b79K"
21
+ MODEL_REVISION = "1a25a446712ba5ee05982a381eed697ef9b435cf"
22
+ MODEL_WEIGHTS_FILENAME = "open_clip_pytorch_model.bin"
23
+ MODEL_WEIGHTS_BYTES = 605219813
24
+ MODEL_WEIGHTS_SHA256 = "1bd3c7172de5b207ceac554f5ab5266166f3b9baccc9af5989bc801016d080ad"
25
+ MODEL_DIGEST = "sha256:" + MODEL_WEIGHTS_SHA256
20
26
  EMBEDDING_DIMENSION = 512
21
27
 
22
28
 
@@ -32,36 +38,75 @@ def deny_network():
32
38
  socket.socket.connect_ex = denied
33
39
 
34
40
 
35
- def artifact_entries(cache_dir):
36
- entries = []
37
- for root, _dirs, names in os.walk(cache_dir):
38
- for name in sorted(names):
39
- path = os.path.join(root, name)
40
- if not os.path.isfile(path) or os.path.getsize(path) < 1024:
41
- continue
42
- relative = os.path.relpath(path, cache_dir).replace(os.sep, "/")
43
- digest = hashlib.sha256()
44
- with open(path, "rb") as source:
45
- while True:
46
- block = source.read(1024 * 1024)
47
- if not block:
48
- break
49
- digest.update(block)
50
- entries.append({"path": relative, "bytes": os.path.getsize(path), "sha256": digest.hexdigest()})
51
- return sorted(entries, key=lambda item: item["path"])
52
-
53
-
54
- def artifact_digest(entries):
41
+ def sha256_file(path):
55
42
  digest = hashlib.sha256()
56
- for entry in entries:
57
- digest.update(entry["path"].encode("utf-8"))
58
- digest.update(b"\0")
59
- digest.update(entry["sha256"].encode("ascii"))
60
- digest.update(b"\n")
61
- return "sha256:" + digest.hexdigest()
43
+ with open(path, "rb") as source:
44
+ while True:
45
+ block = source.read(1024 * 1024)
46
+ if not block:
47
+ break
48
+ digest.update(block)
49
+ return digest.hexdigest()
50
+
51
+
52
+ def model_weights_path(cache_dir):
53
+ return os.path.join(cache_dir, MODEL_WEIGHTS_FILENAME)
54
+
55
+
56
+ def verify_model_weights(cache_dir):
57
+ path = model_weights_path(cache_dir)
58
+ if not os.path.isfile(path):
59
+ raise RuntimeError("Pinned OpenCLIP weight is missing; run explicit vision setup")
60
+ actual_bytes = os.path.getsize(path)
61
+ if actual_bytes != MODEL_WEIGHTS_BYTES:
62
+ raise RuntimeError(
63
+ "Pinned OpenCLIP weight has an unexpected size "
64
+ "(expected %d bytes, got %d); run explicit vision setup" % (MODEL_WEIGHTS_BYTES, actual_bytes)
65
+ )
66
+ actual_sha256 = sha256_file(path)
67
+ if actual_sha256 != MODEL_WEIGHTS_SHA256:
68
+ raise RuntimeError(
69
+ "Pinned OpenCLIP weight checksum mismatch "
70
+ "(expected %s, got %s); run explicit vision setup" % (MODEL_WEIGHTS_SHA256, actual_sha256)
71
+ )
72
+ return path
73
+
74
+
75
+ def artifact_entries(cache_dir):
76
+ path = verify_model_weights(cache_dir)
77
+ return [{
78
+ "path": MODEL_WEIGHTS_FILENAME,
79
+ "bytes": os.path.getsize(path),
80
+ "sha256": MODEL_WEIGHTS_SHA256,
81
+ }]
82
+
83
+
84
+ def download_pinned_model_weights(cache_dir):
85
+ os.makedirs(cache_dir, exist_ok=True)
86
+ try:
87
+ verified = verify_model_weights(cache_dir)
88
+ except RuntimeError:
89
+ # An interrupted/corrupt setup may leave a file at the managed target.
90
+ # Only explicit setup is permitted to replace it.
91
+ force_download = True
92
+ else:
93
+ return verified
94
+ from huggingface_hub import hf_hub_download
95
+ downloaded = hf_hub_download(
96
+ repo_id=MODEL_REPOSITORY,
97
+ filename=MODEL_WEIGHTS_FILENAME,
98
+ revision=MODEL_REVISION,
99
+ local_dir=cache_dir,
100
+ local_files_only=False,
101
+ force_download=force_download,
102
+ )
103
+ expected_path = model_weights_path(cache_dir)
104
+ if os.path.abspath(downloaded) != os.path.abspath(expected_path):
105
+ raise RuntimeError("Pinned OpenCLIP download did not materialize in the managed cache")
106
+ return verify_model_weights(cache_dir)
62
107
 
63
108
 
64
- def load_model(cache_dir, offline):
109
+ def load_model(weights_path, offline):
65
110
  if offline:
66
111
  deny_network()
67
112
  os.environ["HF_HUB_OFFLINE"] = "1"
@@ -70,11 +115,14 @@ def load_model(cache_dir, offline):
70
115
  import open_clip
71
116
  if not torch.cuda.is_available():
72
117
  raise RuntimeError("JetPack CUDA Torch is unavailable for OpenCLIP")
118
+ if os.environ.get("CUDA_VISIBLE_DEVICES") != "0":
119
+ raise RuntimeError("OpenCLIP requires CUDA_VISIBLE_DEVICES=0")
120
+ if torch.cuda.current_device() != 0:
121
+ raise RuntimeError("OpenCLIP did not bind to logical CUDA device 0")
73
122
  device = torch.device("cuda:0")
74
123
  model, _, preprocess = open_clip.create_model_and_transforms(
75
124
  MODEL,
76
- pretrained=PRETRAINED,
77
- cache_dir=cache_dir,
125
+ pretrained=weights_path,
78
126
  device=device,
79
127
  )
80
128
  model.eval()
@@ -82,27 +130,33 @@ def load_model(cache_dir, offline):
82
130
 
83
131
 
84
132
  def setup(cache_dir):
85
- torch, model, _preprocess, device = load_model(cache_dir, False)
133
+ _weights_path = download_pinned_model_weights(cache_dir)
134
+ import torch
135
+ if not torch.cuda.is_available():
136
+ raise RuntimeError("JetPack CUDA Torch is unavailable for OpenCLIP")
137
+ if os.environ.get("CUDA_VISIBLE_DEVICES") != "0":
138
+ raise RuntimeError("OpenCLIP setup requires CUDA_VISIBLE_DEVICES=0")
139
+ if torch.cuda.current_device() != 0:
140
+ raise RuntimeError("OpenCLIP setup did not bind to logical CUDA device 0")
86
141
  entries = artifact_entries(cache_dir)
87
- if not entries:
88
- raise RuntimeError("OpenCLIP setup did not create verifiable model artifacts")
89
- with torch.inference_mode():
90
- # Model loading is setup verification, not a user inference request.
91
- torch.cuda.synchronize(device)
92
142
  major, minor = torch.cuda.get_device_capability(0)
93
143
  emit({
94
144
  "type": "setup",
95
145
  "success": True,
96
146
  "model": MODEL,
97
147
  "pretrained": PRETRAINED,
148
+ "model_repository": MODEL_REPOSITORY,
149
+ "model_revision": MODEL_REVISION,
150
+ "model_weights_sha256": MODEL_WEIGHTS_SHA256,
98
151
  "backend": "open-clip",
99
152
  "embedding_dimension": EMBEDDING_DIMENSION,
100
153
  "normalization": "l2",
101
154
  "device": torch.cuda.get_device_name(0),
102
155
  "compute_capability": "%d.%d" % (major, minor),
103
156
  "torch_cuda_version": str(torch.version.cuda),
157
+ "cuda_visible_devices": os.environ.get("CUDA_VISIBLE_DEVICES"),
104
158
  "artifacts": entries,
105
- "model_digest": artifact_digest(entries),
159
+ "model_digest": MODEL_DIGEST,
106
160
  })
107
161
 
108
162
 
@@ -117,13 +171,12 @@ def image_from_input(input_):
117
171
  raise ValueError("Provide path or bytesBase64")
118
172
 
119
173
 
120
- def embed(cache_dir, expected_digest):
121
- entries = artifact_entries(cache_dir)
122
- actual = artifact_digest(entries)
123
- if actual != expected_digest:
124
- raise RuntimeError("OpenCLIP model artifacts are missing or changed; run explicit vision setup")
174
+ def embed(cache_dir, expected_digest, expected_revision):
175
+ if expected_digest != MODEL_DIGEST or expected_revision != MODEL_REVISION:
176
+ raise RuntimeError("OpenCLIP model identity does not match the pinned setup contract")
177
+ weights_path = verify_model_weights(cache_dir)
125
178
  input_ = json.loads(sys.stdin.read() or "{}")
126
- torch, model, preprocess, device = load_model(cache_dir, True)
179
+ torch, model, preprocess, device = load_model(weights_path, True)
127
180
  image = image_from_input(input_)
128
181
  with torch.inference_mode():
129
182
  tensor = preprocess(image).unsqueeze(0).to(device)
@@ -137,8 +190,11 @@ def embed(cache_dir, expected_digest):
137
190
  "success": True,
138
191
  "model": MODEL,
139
192
  "pretrained": PRETRAINED,
193
+ "model_repository": MODEL_REPOSITORY,
194
+ "model_revision": MODEL_REVISION,
195
+ "model_weights_sha256": MODEL_WEIGHTS_SHA256,
140
196
  "backend": "open-clip",
141
- "model_digest": actual,
197
+ "model_digest": MODEL_DIGEST,
142
198
  "embedding": values,
143
199
  "dimension": EMBEDDING_DIMENSION,
144
200
  "normalization": "l2",
@@ -151,15 +207,16 @@ def main():
151
207
  parser.add_argument("--embed", action="store_true")
152
208
  parser.add_argument("--cache-dir", required=True)
153
209
  parser.add_argument("--model-digest")
210
+ parser.add_argument("--model-revision")
154
211
  args = parser.parse_args()
155
212
  if args.setup == args.embed:
156
213
  raise ValueError("Choose exactly one of --setup or --embed")
157
214
  if args.setup:
158
215
  setup(args.cache_dir)
159
216
  else:
160
- if not args.model_digest:
161
- raise ValueError("--model-digest is required for offline embedding")
162
- embed(args.cache_dir, args.model_digest)
217
+ if not args.model_digest or not args.model_revision:
218
+ raise ValueError("--model-digest and --model-revision are required for offline embedding")
219
+ embed(args.cache_dir, args.model_digest, args.model_revision)
163
220
 
164
221
 
165
222
  if __name__ == "__main__":
@@ -245088,6 +245088,7 @@ var SETUP_TIMEOUT_MS = 20 * 6e4;
245088
245088
  // packages/execution/dist/speaker-diarization-runtime.js
245089
245089
  init_model_store();
245090
245090
  init_process_async();
245091
+ init_venv_paths();
245091
245092
 
245092
245093
  // packages/execution/dist/audio-semantic-embedding-runtime.js
245093
245094
  init_process_async();
@@ -245095,6 +245096,27 @@ init_jetson_monitor();
245095
245096
  init_model_store();
245096
245097
  init_venv_paths();
245097
245098
  var SETUP_TIMEOUT_MS2 = 30 * 6e4;
245099
+ var CLAP_RUNTIME_WHEELS = [
245100
+ { distribution: "numpy", version: "1.26.4", importName: "numpy", filename: "numpy-1.26.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", source: "https://files.pythonhosted.org/packages/fc/a5/4beee6488160798683eed5bdb7eead455892c3b4e1f78d79d8d3f3b084ac/numpy-1.26.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", sha256: "d209d8969599b27ad20994c8e41936ee0964e6da07478d6c35016bc386b66ad4" },
245101
+ { distribution: "transformers", version: "4.57.3", importName: "transformers", filename: "transformers-4.57.3-py3-none-any.whl", source: "https://files.pythonhosted.org/packages/6a/6b/2f416568b3c4c91c96e5a365d164f8a4a4a88030aa8ab4644181fdadce97/transformers-4.57.3-py3-none-any.whl", sha256: "c77d353a4851b1880191603d36acb313411d3577f6e2897814f333841f7003f4" },
245102
+ { distribution: "huggingface-hub", version: "0.36.0", importName: "huggingface_hub", filename: "huggingface_hub-0.36.0-py3-none-any.whl", source: "https://files.pythonhosted.org/packages/cb/bd/1a875e0d592d447cbc02805fd3fe0f497714d6a2583f59d14fa9ebad96eb/huggingface_hub-0.36.0-py3-none-any.whl", sha256: "7bcc9ad17d5b3f07b57c78e79d527102d08313caa278a641993acddcb894548d" },
245103
+ { distribution: "hf-xet", version: "1.1.5", importName: "hf_xet", filename: "hf_xet-1.1.5-cp37-abi3-manylinux_2_28_aarch64.whl", source: "https://files.pythonhosted.org/packages/d0/54/0fcf2b619720a26fbb6cc941e89f2472a522cd963a776c089b189559447f/hf_xet-1.1.5-cp37-abi3-manylinux_2_28_aarch64.whl", sha256: "dbba1660e5d810bd0ea77c511a99e9242d920790d0e63c0e4673ed36c4022d18" },
245104
+ { distribution: "tokenizers", version: "0.22.1", importName: "tokenizers", filename: "tokenizers-0.22.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", source: "https://files.pythonhosted.org/packages/1e/3b/55e64befa1e7bfea963cf4b787b2cea1011362c4193f5477047532ce127e/tokenizers-0.22.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", sha256: "19d2962dd28bc67c1f205ab180578a78eef89ac60ca7ef7cbe9635a46a56422a" },
245105
+ { distribution: "safetensors", version: "0.5.3", importName: "safetensors", filename: "safetensors-0.5.3-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", source: "https://files.pythonhosted.org/packages/5d/9a/add3e6fef267658075c5a41573c26d42d80c935cdc992384dfae435feaef/safetensors-0.5.3-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", sha256: "11bce6164887cd491ca75c2326a113ba934be596e22b28b1742ce27b1d076467" },
245106
+ { distribution: "filelock", version: "3.16.1", importName: "filelock", filename: "filelock-3.16.1-py3-none-any.whl", source: "https://files.pythonhosted.org/packages/b9/f8/feced7779d755758a52d1f6635d990b8d98dc0a29fa568bbe0625f18fdf3/filelock-3.16.1-py3-none-any.whl", sha256: "2082e5703d51fbf98ea75855d9d5527e33d8ff23099bec374a134febee6946b0" },
245107
+ { distribution: "fsspec", version: "2024.10.0", importName: "fsspec", filename: "fsspec-2024.10.0-py3-none-any.whl", source: "https://files.pythonhosted.org/packages/c6/b2/454d6e7f0158951d8a78c2e1eb4f69ae81beb8dca5fee9809c6c99e9d0d0/fsspec-2024.10.0-py3-none-any.whl", sha256: "03b9a6785766a4de40368b88906366755e2819e758b83705c88cd7cb5fe81871" },
245108
+ { distribution: "packaging", version: "24.2", importName: "packaging", filename: "packaging-24.2-py3-none-any.whl", source: "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", sha256: "09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759" },
245109
+ { distribution: "PyYAML", version: "6.0.2", importName: "yaml", filename: "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", source: "https://files.pythonhosted.org/packages/49/ee/14c54df452143b9ee9f0f29074d7ca5516a36edb0b4cc40c3f280131656f/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", sha256: "8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237" },
245110
+ { distribution: "regex", version: "2024.11.6", importName: "regex", filename: "regex-2024.11.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", source: "https://files.pythonhosted.org/packages/78/a2/6dd36e16341ab95e4c6073426561b9bfdeb1a9c9b63ab1b579c2e96cb105/regex-2024.11.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", sha256: "d3660c82f209655a06b587d55e723f0b813d3a7db2e32e5e7dc64ac2a9e86fde" },
245111
+ { distribution: "requests", version: "2.32.3", importName: "requests", filename: "requests-2.32.3-py3-none-any.whl", source: "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", sha256: "70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6" },
245112
+ { distribution: "tqdm", version: "4.67.1", importName: "tqdm", filename: "tqdm-4.67.1-py3-none-any.whl", source: "https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl", sha256: "26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2" },
245113
+ { distribution: "typing-extensions", version: "4.12.2", importName: "typing_extensions", filename: "typing_extensions-4.12.2-py3-none-any.whl", source: "https://files.pythonhosted.org/packages/26/9f/ad63fc0248c5379346306f8668cda6e2e2e9c95e01216d2b8ffd9ff037d0/typing_extensions-4.12.2-py3-none-any.whl", sha256: "04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d" },
245114
+ { distribution: "certifi", version: "2024.8.30", importName: "certifi", filename: "certifi-2024.8.30-py3-none-any.whl", source: "https://files.pythonhosted.org/packages/12/90/3c9ff0512038035f59d279fddeb79f5f1eccd8859f06d6163c58798b9487/certifi-2024.8.30-py3-none-any.whl", sha256: "922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8" },
245115
+ { distribution: "charset-normalizer", version: "3.4.0", importName: "charset_normalizer", filename: "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", source: "https://files.pythonhosted.org/packages/c2/72/12a7f0943dd71fb5b4e7b55c41327ac0a1663046a868ee4d0d8e9c369b85/charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", sha256: "40d3ff7fc90b98c637bda91c89d51264a3dcf210cade3a2c6f838c7268d7a4ca" },
245116
+ { distribution: "idna", version: "3.10", importName: "idna", filename: "idna-3.10-py3-none-any.whl", source: "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", sha256: "946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3" },
245117
+ { distribution: "urllib3", version: "2.2.3", importName: "urllib3", filename: "urllib3-2.2.3-py3-none-any.whl", source: "https://files.pythonhosted.org/packages/ce/d9/5f4c13cecde62396b0d3fe530a50ccea91e7dfc1ccf0e09c228841bb5ba8/urllib3-2.2.3-py3-none-any.whl", sha256: "ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac" }
245118
+ ];
245119
+ var CLAP_PYTHON_REQUIREMENTS = CLAP_RUNTIME_WHEELS.map((wheel) => `${wheel.distribution}==${wheel.version}`);
245098
245120
 
245099
245121
  // packages/execution/dist/speaker-embedding-runtime.js
245100
245122
  init_jetson_monitor();