mirror of
https://github.com/baketnk/frame-yap.git
synced 2026-10-06 10:00:05 +02:00
feat(worker): dispatch verified backends and preserve request-level failures
This commit is contained in:
1 parent
ebaae02b97
commit
9832ec1fd4
8 files changed
+549
-18
No files matched your search
@@ -0,0 +1,146 @@
|
||||
"""Manifest-driven local worker dispatcher. One verified model, one child, no shell/network.
|
||||
|
||||
The child speaks frameyap-worker-v1; this process checks framing and correlation
|
||||
before forwarding Y/T/R/E. The native parent owns the private clip and deadlines.
|
||||
"""
|
||||
import argparse
|
||||
import os
|
||||
import signal
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
try:
|
||||
from .model_files import load_backends, check_model, ManifestError
|
||||
from .worker import read_frame, send_frame, private_dir
|
||||
except ImportError:
|
||||
from model_files import load_backends, check_model, ManifestError
|
||||
from worker import read_frame, send_frame, private_dir
|
||||
|
||||
|
||||
def launcher_command(backend, root, python, model_dir, clip_dir, threads):
|
||||
"""Return argv, never a shell string. Manifest paths are relative to release root."""
|
||||
relative = backend.launcher["path"]
|
||||
root = Path(root).resolve(strict=True)
|
||||
target = root / relative
|
||||
if not target.is_file() or target.is_symlink() or not target.resolve().is_relative_to(root):
|
||||
raise ValueError("unsafe or missing backend launcher")
|
||||
replacements = {"{model_dir}": str(model_dir), "{clip_dir}": str(clip_dir),
|
||||
"{threads}": str(threads)}
|
||||
args = [replacements.get(a, a) for a in backend.launcher["arguments"]]
|
||||
if backend.launcher["type"] == "python":
|
||||
return [python, str(target), *args]
|
||||
if not os.access(target, os.X_OK):
|
||||
raise ValueError("backend executable is not executable")
|
||||
return [str(target), *args]
|
||||
|
||||
|
||||
def serve(backend, root, python, model_dir, clip_dir, threads, advanced_debug=False,
|
||||
input_fd=0, output_fd=1):
|
||||
private_dir(clip_dir)
|
||||
if check_model(backend, model_dir)["state"] != "installed_verified":
|
||||
send_frame(output_fd, b"F", b"M")
|
||||
return 1
|
||||
try:
|
||||
argv = launcher_command(backend, root, python, model_dir, clip_dir, threads)
|
||||
except (OSError, ValueError):
|
||||
send_frame(output_fd, b"F", b"I")
|
||||
return 1
|
||||
env = os.environ.copy()
|
||||
env.update(HF_HUB_OFFLINE="1", TRANSFORMERS_OFFLINE="1", HF_DATASETS_OFFLINE="1",
|
||||
TOKENIZERS_PARALLELISM="false", CUDA_VISIBLE_DEVICES="",
|
||||
OMP_NUM_THREADS=str(threads), MKL_NUM_THREADS=str(threads),
|
||||
OPENBLAS_NUM_THREADS=str(threads), PYTHONDONTWRITEBYTECODE="1")
|
||||
# Protocol opt-in only for our known Redux worker. Arbitrary backend
|
||||
# launchers need not accept --advanced-debug or know this environment key.
|
||||
env["FRAMEYAP_ADVANCED_DEBUG"] = "1" if (advanced_debug and backend.id == "redux" and
|
||||
backend.launcher["type"] == "python" and backend.launcher["path"] == "python/frameyap/worker.py") else "0"
|
||||
# A child cannot inherit input-delivery authority. stdout is exclusively frames.
|
||||
try:
|
||||
child = subprocess.Popen(argv, stdin=subprocess.PIPE, stdout=subprocess.PIPE,
|
||||
stderr=None if advanced_debug else subprocess.DEVNULL,
|
||||
env=env, close_fds=True)
|
||||
except OSError:
|
||||
send_frame(output_fd, b"F", b"I")
|
||||
return 1
|
||||
def stop_child(_signal, _frame):
|
||||
# Native shutdown may SIGKILL this dispatcher after a short grace period.
|
||||
# Reap the direct model child before exiting, never leave inference running.
|
||||
child.kill()
|
||||
child.wait()
|
||||
raise SystemExit(1)
|
||||
|
||||
old_term = signal.signal(signal.SIGTERM, stop_child)
|
||||
try:
|
||||
ready = read_frame(child.stdout.fileno())
|
||||
if ready is None or ready[:1] == b"F":
|
||||
send_frame(output_fd, b"F", ready[1:2] if ready and ready[1:2] in (b"M", b"I") else b"D")
|
||||
return 1
|
||||
if ready != b"Y":
|
||||
send_frame(output_fd, b"F", b"D")
|
||||
return 1
|
||||
send_frame(output_fd, b"Y")
|
||||
while True:
|
||||
request = read_frame(input_fd)
|
||||
if request is None:
|
||||
return 0
|
||||
if len(request) != 9 or request[:1] != b"T":
|
||||
raise ValueError("invalid worker request")
|
||||
send_frame(child.stdin.fileno(), b"T", request[1:])
|
||||
reply = read_frame(child.stdout.fileno())
|
||||
if (reply is None or len(reply) < 9 or len(reply) > 4105 or
|
||||
reply[:1] not in (b"R", b"E") or reply[1:9] != request[1:]):
|
||||
raise ValueError("invalid backend response")
|
||||
send_frame(output_fd, reply[:1], reply[1:])
|
||||
finally:
|
||||
# An EOF/invalid protocol must not block cleanup if a child ignores TERM.
|
||||
# Native also kills the dedicated owned worker group after its grace period.
|
||||
try:
|
||||
child.stdin.close()
|
||||
if child.poll() is None:
|
||||
child.terminate()
|
||||
try:
|
||||
child.wait(timeout=0.2)
|
||||
except subprocess.TimeoutExpired:
|
||||
child.kill()
|
||||
child.wait()
|
||||
else:
|
||||
child.wait()
|
||||
finally:
|
||||
child.stdout.close()
|
||||
signal.signal(signal.SIGTERM, old_term)
|
||||
|
||||
|
||||
def main(argv=None):
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--backend", required=True)
|
||||
parser.add_argument("--manifest-dir", required=True, type=Path)
|
||||
parser.add_argument("--root", required=True, type=Path)
|
||||
parser.add_argument("--python", required=True)
|
||||
parser.add_argument("--model", required=True, type=Path)
|
||||
parser.add_argument("--clip-dir", required=True, type=Path)
|
||||
parser.add_argument("--threads", required=True, type=int)
|
||||
parser.add_argument("--advanced-debug", action="store_true")
|
||||
args = parser.parse_args(argv)
|
||||
if not 1 <= args.threads <= 64:
|
||||
parser.error("threads must be 1..64")
|
||||
# Never let native/model library stdout corrupt the framed channel.
|
||||
protocol = os.dup(1)
|
||||
with open(os.devnull, "wb") as null:
|
||||
os.dup2(2 if args.advanced_debug else null.fileno(), 1)
|
||||
if not args.advanced_debug:
|
||||
os.dup2(null.fileno(), 2)
|
||||
try:
|
||||
try:
|
||||
backend = load_backends(args.manifest_dir)[args.backend]
|
||||
except (ManifestError, KeyError):
|
||||
send_frame(protocol, b"F", b"M")
|
||||
return 1
|
||||
return serve(backend, args.root, args.python, args.model, args.clip_dir,
|
||||
args.threads, args.advanced_debug, output_fd=protocol)
|
||||
finally:
|
||||
os.close(protocol)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in new issue
Block a user