@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.
Files changed (35) hide show
  1. package/dist/audio-analyzer/index.js +3 -3
  2. package/dist/audio-analyzer/index.mjs +3 -3
  3. package/dist/{custom-model-catalog-ae8UGV5D.js → custom-model-catalog-CPqbnIUh.js} +10 -7
  4. package/dist/{custom-model-catalog-B9iY9L4J.mjs → custom-model-catalog-DdHOXMKu.mjs} +10 -7
  5. package/dist/detection-pipeline/index.js +36 -17
  6. package/dist/detection-pipeline/index.mjs +36 -18
  7. package/dist/{dist-BrHqpGoN.js → dist-C2jSMJo_.js} +41 -2
  8. package/dist/{dist-Cr4QsawE.mjs → dist-DFCl7PZV.mjs} +41 -2
  9. package/dist/{model-catalogs-BHBzN67T.js → model-catalogs-CVhIiNhe.js} +1 -1
  10. package/dist/{model-catalogs-Bj9lxXcI.mjs → model-catalogs-Cind8NmH.mjs} +1 -1
  11. package/dist/motion-wasm/index.js +1 -1
  12. package/dist/motion-wasm/index.mjs +1 -1
  13. package/dist/{node-CJ1zilVO.js → node-B7ZinEw5.js} +1 -1
  14. package/dist/{node-Dym0f_3g.mjs → node-C12l0K5V.mjs} +1 -1
  15. package/dist/pipeline-runner/index.js +3 -3
  16. package/dist/pipeline-runner/index.mjs +3 -3
  17. package/dist/{process-memory-CwDymrXT.mjs → process-memory-3F1cP1ou.mjs} +1 -1
  18. package/dist/{process-memory-CAMKwMJs.js → process-memory-C1s8YIw4.js} +1 -1
  19. package/dist/recorder/index.js +2 -2
  20. package/dist/recorder/index.mjs +2 -2
  21. package/dist/{segment-demux-js-CjTXPNsG.mjs → segment-demux-js-7l4LsdU4.mjs} +1 -1
  22. package/dist/{segment-demux-js-BEaJw3G9.js → segment-demux-js-B2IxTWms.js} +1 -1
  23. 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
  24. package/dist/stream-broker/demux-worker-child.js +1 -1
  25. package/dist/stream-broker/demux-worker-child.mjs +1 -1
  26. package/dist/stream-broker/{hostInit-Bx_U_Tc7.mjs → hostInit-DfOhCBj-.mjs} +2 -2
  27. package/dist/stream-broker/index.js +3 -3
  28. package/dist/stream-broker/index.mjs +3 -3
  29. package/dist/stream-broker/remoteEntry.js +1 -1
  30. package/package.json +1 -1
  31. package/python/inference_pool.py +87 -45
  32. package/python/requirements-webgpu.txt +7 -0
  33. package/python/test_inference_pool_device_selection.py +10 -0
  34. package/python/test_webgpu_isolated.py +172 -0
  35. 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:])