omnius 1.0.816 → 1.0.818
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.
- package/dist/index.js +2 -2
- package/dist/scripts/asr_cuda_control.py +95 -0
- package/dist/scripts/live-whisper.py +26 -12
- package/npm-shrinkwrap.json +8 -8
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
[diffend] Oversized file quarantined before diffing.
|
|
2
2
|
name: package/dist/index.js
|
|
3
|
-
size:
|
|
4
|
-
sha256:
|
|
3
|
+
size: 35351446 bytes
|
|
4
|
+
sha256: 10a8553236a1d111a1007db137fd98e738b5410dc573bc10242ff3d5a2604212
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"""Private parent-owned CUDA lease channel. This module imports only stdlib.
|
|
2
|
+
|
|
3
|
+
The parent acquires before spawning us. It alone holds the negotiator token.
|
|
4
|
+
Every model operation shares one prepare/synchronize/ready critical section.
|
|
5
|
+
"""
|
|
6
|
+
import json
|
|
7
|
+
import os
|
|
8
|
+
import re
|
|
9
|
+
import socket
|
|
10
|
+
import threading
|
|
11
|
+
from contextlib import contextmanager
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class CudaLeaseFatal(BaseException):
|
|
15
|
+
"""Cannot be swallowed by ordinary ASR/consensus error recovery."""
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _terminate(message):
|
|
19
|
+
os.write(2, ("Live Whisper CUDA lease failed: " + str(message) + "\n").encode())
|
|
20
|
+
# Includes consensus-thread failures: no other thread may keep using CUDA.
|
|
21
|
+
os._exit(70)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class WhisperCudaControl:
|
|
25
|
+
def __init__(self, channel, fatal=_terminate):
|
|
26
|
+
self.channel = channel
|
|
27
|
+
self.fatal = fatal
|
|
28
|
+
self.lock = threading.Lock()
|
|
29
|
+
self.sequence = 0
|
|
30
|
+
self.active = False
|
|
31
|
+
self.failed = False
|
|
32
|
+
|
|
33
|
+
@classmethod
|
|
34
|
+
def from_environment(cls):
|
|
35
|
+
uuid = os.environ.get("CUDA_VISIBLE_DEVICES", "")
|
|
36
|
+
if (os.environ.get("OMNIUS_ASR_GPU_CONTROL_FD") != "3"
|
|
37
|
+
or os.environ.get("OMNIUS_ASR_CUDA_DEVICE") != "0"
|
|
38
|
+
or not re.fullmatch(r"GPU-[0-9a-fA-F]{8}(?:-[0-9a-fA-F]{4}){3}-[0-9a-fA-F]{12}", uuid)):
|
|
39
|
+
raise CudaLeaseFatal("Live Whisper requires its Omnius scoped CUDA supervisor (FD 3 and one explicit GPU UUID); direct/unleased launch is refused before CUDA imports.")
|
|
40
|
+
try:
|
|
41
|
+
channel = socket.socket(fileno=3)
|
|
42
|
+
# live-whisper may exec its managed venv before loading dependencies.
|
|
43
|
+
os.set_inheritable(3, True)
|
|
44
|
+
return cls(channel)
|
|
45
|
+
except Exception as error:
|
|
46
|
+
raise CudaLeaseFatal("Live Whisper GPU control FD 3 is unavailable") from error
|
|
47
|
+
|
|
48
|
+
def abort(self, error):
|
|
49
|
+
self.failed = True
|
|
50
|
+
self.fatal(str(error))
|
|
51
|
+
raise CudaLeaseFatal(str(error))
|
|
52
|
+
|
|
53
|
+
def request(self, action):
|
|
54
|
+
if self.failed:
|
|
55
|
+
self.abort("previous control failure")
|
|
56
|
+
self.sequence += 1
|
|
57
|
+
try:
|
|
58
|
+
self.channel.sendall((json.dumps({"id": self.sequence, "action": action, "device": "cuda:0"}) + "\n").encode())
|
|
59
|
+
data = bytearray()
|
|
60
|
+
while not data.endswith(b"\n"):
|
|
61
|
+
part = self.channel.recv(1)
|
|
62
|
+
if not part or len(data) >= 8192:
|
|
63
|
+
raise ValueError("closed or oversized parent response")
|
|
64
|
+
data.extend(part)
|
|
65
|
+
reply = json.loads(data)
|
|
66
|
+
if reply != {"id": self.sequence, "ok": True}:
|
|
67
|
+
raise ValueError("parent refused CUDA operation")
|
|
68
|
+
except Exception as error:
|
|
69
|
+
self.abort(error)
|
|
70
|
+
|
|
71
|
+
def loaded(self, synchronize):
|
|
72
|
+
with self.lock:
|
|
73
|
+
try:
|
|
74
|
+
synchronize()
|
|
75
|
+
self.request("ready")
|
|
76
|
+
self.active = True
|
|
77
|
+
except Exception as error:
|
|
78
|
+
self.abort(error)
|
|
79
|
+
|
|
80
|
+
@contextmanager
|
|
81
|
+
def operation(self, synchronize):
|
|
82
|
+
with self.lock:
|
|
83
|
+
if not self.active or self.failed:
|
|
84
|
+
self.abort("CUDA operation before resident acknowledgement")
|
|
85
|
+
self.request("prepare")
|
|
86
|
+
self.active = False
|
|
87
|
+
try:
|
|
88
|
+
yield
|
|
89
|
+
finally:
|
|
90
|
+
try:
|
|
91
|
+
synchronize()
|
|
92
|
+
self.request("ready")
|
|
93
|
+
self.active = True
|
|
94
|
+
except Exception as error:
|
|
95
|
+
self.abort(error)
|
|
@@ -30,7 +30,9 @@ Protocol:
|
|
|
30
30
|
{"type":"error","message":"..."}
|
|
31
31
|
|
|
32
32
|
Usage:
|
|
33
|
-
|
|
33
|
+
Started by Omnius with a parent-owned scoped GPU lease and private FD 3.
|
|
34
|
+
Configure OMNIUS_ASR_CUDA_VISIBLE_DEVICES with one capable full GPU UUID.
|
|
35
|
+
Direct pipes/Nexus launches without this supervisor fail before CUDA imports.
|
|
34
36
|
"""
|
|
35
37
|
|
|
36
38
|
import sys
|
|
@@ -42,13 +44,19 @@ import time
|
|
|
42
44
|
import threading
|
|
43
45
|
from collections import deque
|
|
44
46
|
from pathlib import Path
|
|
47
|
+
from asr_cuda_control import WhisperCudaControl
|
|
48
|
+
|
|
49
|
+
# Fail before dependency/bootstrap imports can initialize CUDA. The FD remains
|
|
50
|
+
# inheritable across the managed-venv exec, and the parent already owns a grant.
|
|
51
|
+
CUDA_CONTROL = WhisperCudaControl.from_environment()
|
|
45
52
|
|
|
46
53
|
# ---------------------------------------------------------------------------
|
|
47
54
|
# Configuration
|
|
48
55
|
# ---------------------------------------------------------------------------
|
|
49
56
|
|
|
50
57
|
SCRIPT_DIR = Path(__file__).resolve().parent
|
|
51
|
-
VENV =
|
|
58
|
+
VENV = Path(os.environ.get("OMNIUS_ASR_PYTHON_VENV", "").strip() or
|
|
59
|
+
str(Path.home() / ".omnius" / "runtimes" / "asr" / "whisper-venv")).expanduser()
|
|
52
60
|
PY = VENV / "bin" / "python"
|
|
53
61
|
PIP = VENV / "bin" / "pip"
|
|
54
62
|
|
|
@@ -238,7 +246,7 @@ def _in_venv() -> bool:
|
|
|
238
246
|
|
|
239
247
|
|
|
240
248
|
def _ensure_venv():
|
|
241
|
-
if
|
|
249
|
+
if PY.exists():
|
|
242
250
|
return
|
|
243
251
|
emit_status("Creating Python venv for Whisper...")
|
|
244
252
|
import venv
|
|
@@ -264,7 +272,7 @@ def _ensure_deps():
|
|
|
264
272
|
if need:
|
|
265
273
|
emit_status(f"Installing: {', '.join(need)}...")
|
|
266
274
|
subprocess.check_call(
|
|
267
|
-
[
|
|
275
|
+
[sys.executable, "-m", "pip", "install", *need],
|
|
268
276
|
stdout=subprocess.DEVNULL, stderr=subprocess.PIPE,
|
|
269
277
|
)
|
|
270
278
|
for mod in ["numpy", "whisper"]:
|
|
@@ -278,7 +286,7 @@ def _ensure_deps():
|
|
|
278
286
|
except ImportError:
|
|
279
287
|
try:
|
|
280
288
|
subprocess.check_call(
|
|
281
|
-
[
|
|
289
|
+
[sys.executable, "-m", "pip", "install", "pyusb"],
|
|
282
290
|
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
|
|
283
291
|
)
|
|
284
292
|
except Exception:
|
|
@@ -534,6 +542,8 @@ def main():
|
|
|
534
542
|
import whisper
|
|
535
543
|
|
|
536
544
|
device = select_whisper_device()
|
|
545
|
+
if device != "cuda:0":
|
|
546
|
+
CUDA_CONTROL.abort("Scoped live Whisper requires logical cuda:0; CPU fallback is refused")
|
|
537
547
|
emit_status(f"Loading Whisper {args.model} model on {device}...")
|
|
538
548
|
try:
|
|
539
549
|
model_dir = whisper_model_dir()
|
|
@@ -568,6 +578,8 @@ def main():
|
|
|
568
578
|
emit_status("ReSpeaker DSP active: hardware VAD, echo cancellation, DOA.")
|
|
569
579
|
|
|
570
580
|
fp16 = str(device).startswith("cuda")
|
|
581
|
+
import torch
|
|
582
|
+
CUDA_CONTROL.loaded(lambda: torch.cuda.synchronize(device))
|
|
571
583
|
emit({
|
|
572
584
|
"type": "ready",
|
|
573
585
|
"model": args.model,
|
|
@@ -583,13 +595,15 @@ def main():
|
|
|
583
595
|
silent, _ = digitally_silent_pcm16(samples)
|
|
584
596
|
if silent:
|
|
585
597
|
return {"text": "", "segments": []}
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
598
|
+
# Primary, partial and consensus calls share the same lease/lock.
|
|
599
|
+
with CUDA_CONTROL.operation(lambda: torch.cuda.synchronize(device)):
|
|
600
|
+
return m.transcribe(
|
|
601
|
+
samples,
|
|
602
|
+
fp16=fp16,
|
|
603
|
+
language=args.language,
|
|
604
|
+
no_speech_threshold=0.6,
|
|
605
|
+
condition_on_previous_text=False,
|
|
606
|
+
)
|
|
593
607
|
|
|
594
608
|
# Shared PCM intake
|
|
595
609
|
audio_buf = np.zeros(0, dtype=np.float32)
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "omnius",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.818",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "omnius",
|
|
9
|
-
"version": "1.0.
|
|
9
|
+
"version": "1.0.818",
|
|
10
10
|
"bundleDependencies": [
|
|
11
11
|
"image-to-ascii"
|
|
12
12
|
],
|
|
@@ -2602,9 +2602,9 @@
|
|
|
2602
2602
|
}
|
|
2603
2603
|
},
|
|
2604
2604
|
"node_modules/bare-fs": {
|
|
2605
|
-
"version": "4.8.
|
|
2606
|
-
"resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.8.
|
|
2607
|
-
"integrity": "sha512
|
|
2605
|
+
"version": "4.8.3",
|
|
2606
|
+
"resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.8.3.tgz",
|
|
2607
|
+
"integrity": "sha512-KQi9ShyPS/nOCtKpma7At8wbq4ORSNUmD67hsZrPcaWJcEmL8PHyS2xVWT308y8/LvHFOUTytTTDsD8dInx6bA==",
|
|
2608
2608
|
"license": "Apache-2.0",
|
|
2609
2609
|
"optional": true,
|
|
2610
2610
|
"dependencies": {
|
|
@@ -5522,9 +5522,9 @@
|
|
|
5522
5522
|
"license": "MIT"
|
|
5523
5523
|
},
|
|
5524
5524
|
"node_modules/readable-stream": {
|
|
5525
|
-
"version": "4.
|
|
5526
|
-
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.
|
|
5527
|
-
"integrity": "sha512-
|
|
5525
|
+
"version": "4.8.0",
|
|
5526
|
+
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.8.0.tgz",
|
|
5527
|
+
"integrity": "sha512-2jhTTU3Eilp/2DMT71gbcoqC35eQHasMJH0RHkYxwlxIv3+FR6Y5xAFZIwEGLxKkjZYEFlsiMNIdwUqjK2lLHQ==",
|
|
5528
5528
|
"license": "MIT",
|
|
5529
5529
|
"dependencies": {
|
|
5530
5530
|
"abort-controller": "^3.0.0",
|
package/package.json
CHANGED