@camstack/addon-pipeline 1.2.349 → 1.2.351
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/audio-analyzer/index.js +3 -3
- package/dist/audio-analyzer/index.mjs +3 -3
- package/dist/{custom-model-catalog-ae8UGV5D.js → custom-model-catalog-CPqbnIUh.js} +10 -7
- package/dist/{custom-model-catalog-B9iY9L4J.mjs → custom-model-catalog-DdHOXMKu.mjs} +10 -7
- package/dist/detection-pipeline/index.js +36 -17
- package/dist/detection-pipeline/index.mjs +36 -18
- package/dist/{dist-BrHqpGoN.js → dist-C2jSMJo_.js} +41 -2
- package/dist/{dist-Cr4QsawE.mjs → dist-DFCl7PZV.mjs} +41 -2
- package/dist/{model-catalogs-BHBzN67T.js → model-catalogs-CVhIiNhe.js} +1 -1
- package/dist/{model-catalogs-Bj9lxXcI.mjs → model-catalogs-Cind8NmH.mjs} +1 -1
- package/dist/motion-wasm/index.js +1 -1
- package/dist/motion-wasm/index.mjs +1 -1
- package/dist/{node-CJ1zilVO.js → node-B7ZinEw5.js} +1 -1
- package/dist/{node-Dym0f_3g.mjs → node-C12l0K5V.mjs} +1 -1
- package/dist/pipeline-runner/index.js +3 -3
- package/dist/pipeline-runner/index.mjs +3 -3
- package/dist/{process-memory-CwDymrXT.mjs → process-memory-3F1cP1ou.mjs} +1 -1
- package/dist/{process-memory-CAMKwMJs.js → process-memory-C1s8YIw4.js} +1 -1
- package/dist/recorder/index.js +2 -2
- package/dist/recorder/index.mjs +2 -2
- package/dist/{segment-demux-js-CjTXPNsG.mjs → segment-demux-js-7l4LsdU4.mjs} +1 -1
- package/dist/{segment-demux-js-BEaJw3G9.js → segment-demux-js-B2IxTWms.js} +1 -1
- package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-CFZDNGBf.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-BgC3mSLA.mjs} +2 -2
- package/dist/stream-broker/demux-worker-child.js +1 -1
- package/dist/stream-broker/demux-worker-child.mjs +1 -1
- package/dist/stream-broker/{hostInit-Bx_U_Tc7.mjs → hostInit-DfOhCBj-.mjs} +2 -2
- package/dist/stream-broker/index.js +3 -3
- package/dist/stream-broker/index.mjs +3 -3
- package/dist/stream-broker/remoteEntry.js +1 -1
- package/package.json +1 -1
- package/python/inference_pool.py +87 -45
- package/python/requirements-webgpu.txt +7 -0
- package/python/test_inference_pool_device_selection.py +10 -0
- package/python/test_webgpu_isolated.py +172 -0
- package/python/webgpu_isolated.py +292 -0
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
"""
|
|
2
|
+
WebGPU ORT sessions in a dedicated OS process.
|
|
3
|
+
|
|
4
|
+
ORT WebGPU EP (Dawn/Vulkan) SIGSEGVs when a second InferenceSession is created
|
|
5
|
+
in the same Python process (measured amd-proxmox 2026-10-10). Two processes,
|
|
6
|
+
one session each, work concurrently. This module:
|
|
7
|
+
|
|
8
|
+
- loads a WebGPU session (or CPU fallback) — used by the child worker;
|
|
9
|
+
- hosts the child worker entrypoint (`python webgpu_isolated.py --ipc-in N --ipc-out M`);
|
|
10
|
+
- exposes `WebGpuIsolatedSession` for the pool parent: one child per model.
|
|
11
|
+
|
|
12
|
+
IPC uses dedicated pipe fds (`pass_fds`), never stdin/stdout — ORT/Dawn log on
|
|
13
|
+
fd 1 and a yolov9 raw head on a shared stdout pickle channel truncated the
|
|
14
|
+
frame (2026-10-10). Pipe `read(n)` can also return short; framing uses
|
|
15
|
+
`_read_exact`.
|
|
16
|
+
|
|
17
|
+
OpenVINO / CoreML / classic ONNX stay in-process in inference_pool.py.
|
|
18
|
+
"""
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import os
|
|
22
|
+
import pickle
|
|
23
|
+
import struct
|
|
24
|
+
import subprocess
|
|
25
|
+
import sys
|
|
26
|
+
import threading
|
|
27
|
+
from typing import Any, BinaryIO, Optional
|
|
28
|
+
|
|
29
|
+
ONNX_CPU_EP = "CPUExecutionProvider"
|
|
30
|
+
ONNX_WEBGPU_EP_REGISTRATION = "webgpu_ep_registration"
|
|
31
|
+
|
|
32
|
+
_HDR = struct.Struct(">I")
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _read_exact(stream: BinaryIO, n: int) -> bytes:
|
|
36
|
+
"""Read exactly n bytes; pipe read() may return short without EOF."""
|
|
37
|
+
buf = bytearray()
|
|
38
|
+
while len(buf) < n:
|
|
39
|
+
chunk = stream.read(n - len(buf))
|
|
40
|
+
if not chunk:
|
|
41
|
+
raise EOFError("webgpu worker closed")
|
|
42
|
+
buf.extend(chunk)
|
|
43
|
+
return bytes(buf)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _write_msg(stream: BinaryIO, obj: Any) -> None:
|
|
47
|
+
payload = pickle.dumps(obj, protocol=pickle.HIGHEST_PROTOCOL)
|
|
48
|
+
stream.write(_HDR.pack(len(payload)))
|
|
49
|
+
stream.write(payload)
|
|
50
|
+
stream.flush()
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _read_msg(stream: BinaryIO) -> Any:
|
|
54
|
+
hdr = _read_exact(stream, _HDR.size)
|
|
55
|
+
(n,) = _HDR.unpack(hdr)
|
|
56
|
+
if n > 512 * 1024 * 1024:
|
|
57
|
+
raise RuntimeError(f"webgpu IPC frame too large: {n}")
|
|
58
|
+
data = _read_exact(stream, n)
|
|
59
|
+
return pickle.loads(data)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def load_onnx_webgpu_session(ort: Any, path: str) -> tuple[Any, list[str]]:
|
|
63
|
+
"""Build an InferenceSession on the WebGPU plugin EP, CPU fallback on miss.
|
|
64
|
+
|
|
65
|
+
Same contract as the former inference_pool._load_onnx_webgpu_session.
|
|
66
|
+
"""
|
|
67
|
+
try:
|
|
68
|
+
import onnxruntime_ep_webgpu as webgpu_ep # type: ignore
|
|
69
|
+
except Exception as err:
|
|
70
|
+
sys.stderr.write(f"ONNX WebGPU: plugin import failed ({err}); CPU fallback\n")
|
|
71
|
+
sys.stderr.flush()
|
|
72
|
+
return ort.InferenceSession(path, providers=[ONNX_CPU_EP]), [ONNX_CPU_EP]
|
|
73
|
+
|
|
74
|
+
try:
|
|
75
|
+
ort.register_execution_provider_library(
|
|
76
|
+
ONNX_WEBGPU_EP_REGISTRATION, webgpu_ep.get_library_path()
|
|
77
|
+
)
|
|
78
|
+
except Exception as err:
|
|
79
|
+
msg = str(err).lower()
|
|
80
|
+
if "already" not in msg and "exist" not in msg:
|
|
81
|
+
sys.stderr.write(f"ONNX WebGPU: register failed ({err}); CPU fallback\n")
|
|
82
|
+
sys.stderr.flush()
|
|
83
|
+
return ort.InferenceSession(path, providers=[ONNX_CPU_EP]), [ONNX_CPU_EP]
|
|
84
|
+
|
|
85
|
+
ep_name = webgpu_ep.get_ep_name()
|
|
86
|
+
webgpu_device = next(
|
|
87
|
+
(d for d in ort.get_ep_devices() if getattr(d, "ep_name", None) == ep_name),
|
|
88
|
+
None,
|
|
89
|
+
)
|
|
90
|
+
if webgpu_device is None:
|
|
91
|
+
sys.stderr.write(
|
|
92
|
+
f"ONNX WebGPU: no OrtEpDevice for {ep_name!r} "
|
|
93
|
+
f"(Vulkan/libvulkan missing?); CPU fallback\n"
|
|
94
|
+
)
|
|
95
|
+
sys.stderr.flush()
|
|
96
|
+
return ort.InferenceSession(path, providers=[ONNX_CPU_EP]), [ONNX_CPU_EP]
|
|
97
|
+
|
|
98
|
+
sess_options = ort.SessionOptions()
|
|
99
|
+
sess_options.add_provider_for_devices([webgpu_device], {})
|
|
100
|
+
session = ort.InferenceSession(path, sess_options=sess_options)
|
|
101
|
+
active = list(session.get_providers())
|
|
102
|
+
sys.stderr.write(f"ONNX WebGPU: providers={active} ep={ep_name} device=webgpu\n")
|
|
103
|
+
sys.stderr.flush()
|
|
104
|
+
return session, active
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _parse_ipc_fds(argv: list[str]) -> tuple[int, int]:
|
|
108
|
+
ipc_in: Optional[int] = None
|
|
109
|
+
ipc_out: Optional[int] = None
|
|
110
|
+
i = 0
|
|
111
|
+
while i < len(argv):
|
|
112
|
+
if argv[i] == "--ipc-in" and i + 1 < len(argv):
|
|
113
|
+
ipc_in = int(argv[i + 1])
|
|
114
|
+
i += 2
|
|
115
|
+
continue
|
|
116
|
+
if argv[i] == "--ipc-out" and i + 1 < len(argv):
|
|
117
|
+
ipc_out = int(argv[i + 1])
|
|
118
|
+
i += 2
|
|
119
|
+
continue
|
|
120
|
+
i += 1
|
|
121
|
+
if ipc_in is None or ipc_out is None:
|
|
122
|
+
raise SystemExit("webgpu_isolated worker needs --ipc-in FD --ipc-out FD")
|
|
123
|
+
return ipc_in, ipc_out
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def _worker_main(argv: list[str]) -> None:
|
|
127
|
+
"""One InferenceSession; commands on --ipc-in, replies on --ipc-out."""
|
|
128
|
+
import numpy as np
|
|
129
|
+
import onnxruntime as ort
|
|
130
|
+
|
|
131
|
+
fd_in, fd_out = _parse_ipc_fds(argv)
|
|
132
|
+
ipc_in = os.fdopen(fd_in, "rb", buffering=0)
|
|
133
|
+
ipc_out = os.fdopen(fd_out, "wb", buffering=0)
|
|
134
|
+
|
|
135
|
+
session = None
|
|
136
|
+
output_names: list[str] = []
|
|
137
|
+
while True:
|
|
138
|
+
try:
|
|
139
|
+
msg = _read_msg(ipc_in)
|
|
140
|
+
except EOFError:
|
|
141
|
+
break
|
|
142
|
+
cmd = msg.get("cmd")
|
|
143
|
+
if cmd == "shutdown":
|
|
144
|
+
_write_msg(ipc_out, {"ok": True})
|
|
145
|
+
break
|
|
146
|
+
if cmd == "load":
|
|
147
|
+
path = msg["path"]
|
|
148
|
+
try:
|
|
149
|
+
session, providers = load_onnx_webgpu_session(ort, path)
|
|
150
|
+
output_names = [o.name for o in session.get_outputs()]
|
|
151
|
+
inp = session.get_inputs()[0]
|
|
152
|
+
_write_msg(
|
|
153
|
+
ipc_out,
|
|
154
|
+
{
|
|
155
|
+
"ok": True,
|
|
156
|
+
"providers": providers,
|
|
157
|
+
"input_name": inp.name,
|
|
158
|
+
"input_shape": list(inp.shape),
|
|
159
|
+
"input_type": inp.type,
|
|
160
|
+
"output_names": output_names,
|
|
161
|
+
},
|
|
162
|
+
)
|
|
163
|
+
except Exception as exc:
|
|
164
|
+
_write_msg(ipc_out, {"ok": False, "error": f"{type(exc).__name__}: {exc}"})
|
|
165
|
+
continue
|
|
166
|
+
if cmd == "predict":
|
|
167
|
+
if session is None:
|
|
168
|
+
_write_msg(ipc_out, {"ok": False, "error": "no session"})
|
|
169
|
+
continue
|
|
170
|
+
try:
|
|
171
|
+
outputs = session.run(output_names, msg["inputs"])
|
|
172
|
+
payload = {
|
|
173
|
+
name: np.ascontiguousarray(out)
|
|
174
|
+
for name, out in zip(output_names, outputs)
|
|
175
|
+
}
|
|
176
|
+
_write_msg(ipc_out, {"ok": True, "outputs": payload})
|
|
177
|
+
except Exception as exc:
|
|
178
|
+
_write_msg(ipc_out, {"ok": False, "error": f"{type(exc).__name__}: {exc}"})
|
|
179
|
+
continue
|
|
180
|
+
_write_msg(ipc_out, {"ok": False, "error": f"unknown cmd {cmd!r}"})
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
class WebGpuIsolatedSession:
|
|
184
|
+
"""Parent-side handle: one child process holding one WebGPU ORT session."""
|
|
185
|
+
|
|
186
|
+
def __init__(self, proc: subprocess.Popen, ipc_in: BinaryIO, ipc_out: BinaryIO, meta: dict):
|
|
187
|
+
self._proc = proc
|
|
188
|
+
self._ipc_in = ipc_in # parent reads replies
|
|
189
|
+
self._ipc_out = ipc_out # parent writes commands
|
|
190
|
+
self._lock = threading.Lock()
|
|
191
|
+
self.providers: list[str] = list(meta["providers"])
|
|
192
|
+
self.input_name: str = meta["input_name"]
|
|
193
|
+
self.input_shape = meta["input_shape"]
|
|
194
|
+
self.input_type: str = meta["input_type"]
|
|
195
|
+
self.output_names: list[str] = list(meta["output_names"])
|
|
196
|
+
|
|
197
|
+
@classmethod
|
|
198
|
+
def open(cls, path: str, *, python_exe: Optional[str] = None) -> "WebGpuIsolatedSession":
|
|
199
|
+
script = os.path.join(os.path.dirname(os.path.abspath(__file__)), "webgpu_isolated.py")
|
|
200
|
+
exe = python_exe or sys.executable
|
|
201
|
+
# cmd_r ← parent writes commands; reply_r ← parent reads replies
|
|
202
|
+
cmd_r, cmd_w = os.pipe()
|
|
203
|
+
reply_r, reply_w = os.pipe()
|
|
204
|
+
try:
|
|
205
|
+
proc = subprocess.Popen(
|
|
206
|
+
[exe, script, "--ipc-in", str(cmd_r), "--ipc-out", str(reply_w)],
|
|
207
|
+
pass_fds=(cmd_r, reply_w),
|
|
208
|
+
stdin=subprocess.DEVNULL,
|
|
209
|
+
stdout=None, # inherit — ORT/Dawn logs stay in the pool log
|
|
210
|
+
stderr=None,
|
|
211
|
+
close_fds=True,
|
|
212
|
+
)
|
|
213
|
+
except Exception:
|
|
214
|
+
for fd in (cmd_r, cmd_w, reply_r, reply_w):
|
|
215
|
+
try:
|
|
216
|
+
os.close(fd)
|
|
217
|
+
except OSError:
|
|
218
|
+
pass
|
|
219
|
+
raise
|
|
220
|
+
# Parent keeps cmd_w + reply_r; close the ends the child owns.
|
|
221
|
+
os.close(cmd_r)
|
|
222
|
+
os.close(reply_w)
|
|
223
|
+
ipc_out = os.fdopen(cmd_w, "wb", buffering=0)
|
|
224
|
+
ipc_in = os.fdopen(reply_r, "rb", buffering=0)
|
|
225
|
+
try:
|
|
226
|
+
_write_msg(ipc_out, {"cmd": "load", "path": path})
|
|
227
|
+
reply = _read_msg(ipc_in)
|
|
228
|
+
except Exception:
|
|
229
|
+
ipc_out.close()
|
|
230
|
+
ipc_in.close()
|
|
231
|
+
proc.kill()
|
|
232
|
+
proc.wait(timeout=5)
|
|
233
|
+
raise
|
|
234
|
+
if not reply.get("ok"):
|
|
235
|
+
ipc_out.close()
|
|
236
|
+
ipc_in.close()
|
|
237
|
+
proc.kill()
|
|
238
|
+
proc.wait(timeout=5)
|
|
239
|
+
raise RuntimeError(reply.get("error") or "webgpu child load failed")
|
|
240
|
+
sys.stderr.write(
|
|
241
|
+
f"ONNX WebGPU: isolated child pid={proc.pid} providers={reply['providers']}\n"
|
|
242
|
+
)
|
|
243
|
+
sys.stderr.flush()
|
|
244
|
+
return cls(proc, ipc_in, ipc_out, reply)
|
|
245
|
+
|
|
246
|
+
def run(self, inputs: dict) -> dict:
|
|
247
|
+
with self._lock:
|
|
248
|
+
if self._proc.poll() is not None:
|
|
249
|
+
raise RuntimeError(
|
|
250
|
+
f"webgpu child dead (exit={self._proc.returncode})"
|
|
251
|
+
)
|
|
252
|
+
_write_msg(self._ipc_out, {"cmd": "predict", "inputs": inputs})
|
|
253
|
+
reply = _read_msg(self._ipc_in)
|
|
254
|
+
if not reply.get("ok"):
|
|
255
|
+
raise RuntimeError(reply.get("error") or "webgpu predict failed")
|
|
256
|
+
return reply["outputs"]
|
|
257
|
+
|
|
258
|
+
def close(self) -> None:
|
|
259
|
+
proc = self._proc
|
|
260
|
+
if proc.poll() is not None:
|
|
261
|
+
try:
|
|
262
|
+
self._ipc_out.close()
|
|
263
|
+
except Exception:
|
|
264
|
+
pass
|
|
265
|
+
try:
|
|
266
|
+
self._ipc_in.close()
|
|
267
|
+
except Exception:
|
|
268
|
+
pass
|
|
269
|
+
return
|
|
270
|
+
try:
|
|
271
|
+
_write_msg(self._ipc_out, {"cmd": "shutdown"})
|
|
272
|
+
proc.wait(timeout=5)
|
|
273
|
+
except Exception:
|
|
274
|
+
pass
|
|
275
|
+
try:
|
|
276
|
+
self._ipc_out.close()
|
|
277
|
+
except Exception:
|
|
278
|
+
pass
|
|
279
|
+
try:
|
|
280
|
+
self._ipc_in.close()
|
|
281
|
+
except Exception:
|
|
282
|
+
pass
|
|
283
|
+
if proc.poll() is None:
|
|
284
|
+
proc.kill()
|
|
285
|
+
try:
|
|
286
|
+
proc.wait(timeout=5)
|
|
287
|
+
except Exception:
|
|
288
|
+
pass
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
if __name__ == "__main__":
|
|
292
|
+
_worker_main(sys.argv[1:])
|