diff --git a/examples/cuda-fanout/gpu-worker.lsp b/examples/cuda-fanout/gpu-worker.lsp index 797cd31..8665871 100644 --- a/examples/cuda-fanout/gpu-worker.lsp +++ b/examples/cuda-fanout/gpu-worker.lsp @@ -196,23 +196,52 @@ (car (cdr x))) (else x))) +;; daemon-or-error op-name → daemon-pair on success, or (error +;; (daemon-not-registered )) S-exp on failure. Used by every +;; handle-cuda-* so a worker host that lacks a particular binary +;; returns a clean structured error instead of crashing the child +;; on (cdr #f). Callers branch on (pair? result) → real daemon vs +;; (eq? (car result) 'error) → propagate the error S-exp upstream. +(define (daemon-or-error op-name) + (let ((d (assoc op-name *daemons*))) + (cond + ((eq? d #f) (list 'error (list 'daemon-not-registered op-name))) + (else (cdr d))))) + +;; with-required-daemon — guards a binary-wire handler against missing +;; daemons. If the named daemon is registered, calls (body daemon-pair). +;; If not, sends "BERRdaemon-not-registered: " over the binary wire +;; so the caller gets a clean error frame instead of an empty/closed +;; connection from a crashed child. +(define (with-required-daemon client op-name body) + (let ((d (assoc op-name *daemons*))) + (cond + ((eq? d #f) + (wire-send-raw client + (string-append "BERRdaemon-not-registered: " + (symbol->string op-name)))) + (else (body (cdr d)))))) + (define (handle-cuda-shake-fanout args) - (let* ((inputs (unquote-list (car args))) - (out-bytes (car (cdr args))) - (in-path (gensym-path "/tmp/bend-in" ".portal")) - (out-path (gensym-path "/tmp/bend-out" ".portal")) - (daemon (cdr (assoc 'cuda-shake-fanout *daemons*)))) - (write-shake-input-portal! in-path inputs out-bytes) - (let ((status (daemon-process daemon in-path out-path #f))) - (cond - ((eq? status 'ok) - (let ((result (read-shake-output-portal out-path))) - (delete-file in-path) - (delete-file out-path) - (list 'ok result))) - (else - (delete-file in-path) (delete-file out-path) - (list 'error (cdr status))))))) + (let ((d (daemon-or-error 'cuda-shake-fanout))) + (cond + ((and (pair? d) (eq? (car d) 'error)) d) + (else + (let* ((inputs (unquote-list (car args))) + (out-bytes (car (cdr args))) + (in-path (gensym-path "/tmp/bend-in" ".portal")) + (out-path (gensym-path "/tmp/bend-out" ".portal"))) + (write-shake-input-portal! in-path inputs out-bytes) + (let ((status (daemon-process d in-path out-path #f))) + (cond + ((eq? status 'ok) + (let ((result (read-shake-output-portal out-path))) + (delete-file in-path) + (delete-file out-path) + (list 'ok result))) + (else + (delete-file in-path) (delete-file out-path) + (list 'error (cdr status)))))))))) ;;; -- op handler -- cuda-sim-ops-bin ----------------------------- ;;; @@ -269,6 +298,8 @@ (portal-path (gensym-path "/tmp/bend-sim-ops" ".portal")) (t-start (current-time-ms))) (cond + ((not (file-exists? *binary-bend-cuda*)) + (list 'error (list 'bend-cuda-binary-missing *binary-bend-cuda*))) ((not (file-exists? ops-path)) (display ";;; bend ERROR cuda-sim-ops-bin ops-bin-missing ") (display ops-path) (newline) @@ -507,29 +538,34 @@ ;; binary result, prepends "BSHR" magic, wire-send-raws it back. (define (handle-binary-shake client payload) - (let* ((daemon (cdr (assoc 'cuda-shake-fanout *daemons*))) - (in-path (gensym-path "/tmp/bend-bin-in" ".bin")) - (out-path (gensym-path "/tmp/bend-bin-out" ".bin")) - (portal-blob (substring payload 4 (string-length payload)))) - (write-binary-file in-path portal-blob) - (let ((status (daemon-process daemon in-path out-path #t))) - (cond - ((eq? status 'ok) - (let ((result-blob (read-binary-file out-path))) - (if (file-exists? in-path) (delete-file in-path)) - (if (file-exists? out-path) (delete-file out-path)) - (wire-send-raw client (string-append "BSHR" result-blob)))) - (else - (if (file-exists? in-path) (delete-file in-path)) - (if (file-exists? out-path) (delete-file out-path)) - (wire-send-raw client (string-append "BERR" (cdr status)))))))) + (let ((d (daemon-or-error 'cuda-shake-fanout))) + (cond + ((and (pair? d) (eq? (car d) 'error)) + (wire-send-raw client "BERRdaemon-not-registered: cuda-shake-fanout")) + (else + (let* ((daemon d) + (in-path (gensym-path "/tmp/bend-bin-in" ".bin")) + (out-path (gensym-path "/tmp/bend-bin-out" ".bin")) + (portal-blob (substring payload 4 (string-length payload)))) + (write-binary-file in-path portal-blob) + (let ((status (daemon-process daemon in-path out-path #t))) + (cond + ((eq? status 'ok) + (let ((result-blob (read-binary-file out-path))) + (if (file-exists? in-path) (delete-file in-path)) + (if (file-exists? out-path) (delete-file out-path)) + (wire-send-raw client (string-append "BSHR" result-blob)))) + (else + (if (file-exists? in-path) (delete-file in-path)) + (if (file-exists? out-path) (delete-file out-path)) + (wire-send-raw client (string-append "BERR" (cdr status))))))))))) ;; Binary wire for bend form B (cuda-bignum-cgbn). ;; Payload begins with "BCGB"; pass entire blob through to the daemon, ;; which expects the same magic + header it received from the client. (define (handle-binary-cgbn client payload) - (let* ((daemon (cdr (assoc 'cuda-bignum-cgbn *daemons*))) - (in-path (gensym-path "/tmp/bend-cgbn-in" ".bin")) + (with-required-daemon client 'cuda-bignum-cgbn (lambda (daemon) + (let* ((in-path (gensym-path "/tmp/bend-cgbn-in" ".bin")) (out-path (gensym-path "/tmp/bend-cgbn-out" ".bin")) (t-start (current-time-ms))) (write-binary-file in-path payload) @@ -553,15 +589,15 @@ (display wall-ms) (newline) (if (file-exists? in-path) (delete-file in-path)) (if (file-exists? out-path) (delete-file out-path)) - (wire-send-raw client (string-append "BERR" (cdr status))))))))) + (wire-send-raw client (string-append "BERR" (cdr status))))))))))) ;; Binary wire for bend form A (cuda-secp256k1-batched-mul). ;; Payload begins with "BSCP"; pass entire blob through to the daemon, ;; which expects the same magic + header. Response payload begins ;; with "BSCR" on success or "BERR" on failure. (define (handle-binary-secp client payload) - (let* ((daemon (cdr (assoc 'cuda-secp256k1-batched-mul *daemons*))) - (in-path (gensym-path "/tmp/bend-secp-in" ".bin")) + (with-required-daemon client 'cuda-secp256k1-batched-mul (lambda (daemon) + (let* ((in-path (gensym-path "/tmp/bend-secp-in" ".bin")) (out-path (gensym-path "/tmp/bend-secp-out" ".bin")) (t-start (current-time-ms))) (write-binary-file in-path payload) @@ -585,7 +621,7 @@ (display wall-ms) (newline) (if (file-exists? in-path) (delete-file in-path)) (if (file-exists? out-path) (delete-file out-path)) - (wire-send-raw client (string-append "BERR" (cdr status))))))))) + (wire-send-raw client (string-append "BERR" (cdr status))))))))))) ;; Binary wire for Wave-2 form `cuda-blake3-tree` (BLAKE3 batched fan-out). ;; Payload begins with "BSB3". The .cu daemon (blake3-fanout.cu) expects @@ -594,8 +630,8 @@ ;; entire payload through verbatim. The daemon's response file already ;; begins with "BSR3"; pass through unchanged. (define (handle-binary-blake3 client payload) - (let* ((daemon (cdr (assoc 'cuda-blake3-tree *daemons*))) - (in-path (gensym-path "/tmp/bend-blake3-in" ".bin")) + (with-required-daemon client 'cuda-blake3-tree (lambda (daemon) + (let* ((in-path (gensym-path "/tmp/bend-blake3-in" ".bin")) (out-path (gensym-path "/tmp/bend-blake3-out" ".bin")) (t-start (current-time-ms))) (write-binary-file in-path payload) @@ -619,15 +655,15 @@ (display wall-ms) (newline) (if (file-exists? in-path) (delete-file in-path)) (if (file-exists? out-path) (delete-file out-path)) - (wire-send-raw client (string-append "BERR" (cdr status))))))))) + (wire-send-raw client (string-append "BERR" (cdr status))))))))))) ;; Binary wire for bend form G (cuda-radix-sort). ;; Payload begins with "BSRT"; pass entire blob through to the daemon, ;; which expects the same magic + header. Response begins with "BSRR" ;; on success or "BERR" on failure. (define (handle-binary-sort client payload) - (let* ((daemon (cdr (assoc 'cuda-radix-sort *daemons*))) - (in-path (gensym-path "/tmp/bend-sort-in" ".bin")) + (with-required-daemon client 'cuda-radix-sort (lambda (daemon) + (let* ((in-path (gensym-path "/tmp/bend-sort-in" ".bin")) (out-path (gensym-path "/tmp/bend-sort-out" ".bin")) (t-start (current-time-ms))) (write-binary-file in-path payload) @@ -651,7 +687,7 @@ (display wall-ms) (newline) (if (file-exists? in-path) (delete-file in-path)) (if (file-exists? out-path) (delete-file out-path)) - (wire-send-raw client (string-append "BERR" (cdr status))))))))) + (wire-send-raw client (string-append "BERR" (cdr status))))))))))) ;; Accept one client, handle one request, close. Returns #t to keep ;; serving, #f when the server should stop. diff --git a/wasm/app/demos/bend-gpu.lsp b/wasm/app/demos/bend-gpu.lsp index c3d40b8..3784268 100644 --- a/wasm/app/demos/bend-gpu.lsp +++ b/wasm/app/demos/bend-gpu.lsp @@ -1,28 +1,39 @@ ; bend dispatch — ship a workload to a CUDA worker. ; -; This is a CPU-impossible problem at the chosen scale: SHAKE256 fan-out -; over 1M inputs takes about 10 seconds on a 3090 via lumbda's binary -; bend protocol (see whitepaper §11), and about 2 minutes on a host CPU. +; This demo refuses to run locally — set a bend URL in the ⚡ bar +; above first. The playground guards the Run button so a customer +; machine never burns minutes on the local tiers. ; -; This demo refuses to run locally — set a bend URL in the bar above -; first. The playground guards the Run button so a customer machine -; never burns minutes on the local tiers. +; What you can call today (S-expression mode over HTTP): +; (ping) → (ok pong) — round-trip probe +; (health) → (ok (load-avg ...)) — worker telemetry +; (cuda-shake-fanout INPUTS OUT-BYTES) +; → (ok (HEX0 HEX1 ...)) — N SHAKE256 outputs +; Binary modes (BSHK / BCGB / BSCP / BSRT / BSB3) and the +; 1M-input "feel the GPU win" run stay native-only until Phase 2 +; lands server-side factory recipes — those compile per-call +; binaries on the worker filesystem and stream them through +; the existing handlers. -; Payload is the lumbda S-expression form the gpu-worker expects. -; Replace COUNT with a smaller value (e.g. 1024) for warm-up runs and -; a larger one (1000000+) when you want to feel the GPU win. -(define COUNT 1000000) +(display ";; 1. probe round-trip ...") (newline) +(define r1 (bend!-call "(ping)")) +(display " (ping) → ") (display r1) (newline) +(newline) +(display ";; 2. worker telemetry ...") (newline) +(define r2 (bend!-call "(health)")) +(display " (health) → ") (display r2) (newline) +(newline) + +(display ";; 3. small CUDA fan-out (3 inputs, 32-byte outputs) ...") (newline) (define payload (string-append - "(cuda-shake256-fanout " - (number->string COUNT) - " 32)")) + "(cuda-shake-fanout " + "(\"00\" \"01\" \"deadbeef\") " + "32)")) +(display " payload: ") (display payload) (newline) +(define r3 (bend!-call payload)) +(display " result : ") (display r3) (newline) +(newline) -(display "bend payload: ") (display payload) (newline) -(display "sending to gpu-worker...") (newline) - -(define result (bend!-call payload)) - -(display "result: ") (display result) (newline) (print "done") diff --git a/www/playground/demos/bend-gpu.lsp b/www/playground/demos/bend-gpu.lsp index c3d40b8..3784268 100644 --- a/www/playground/demos/bend-gpu.lsp +++ b/www/playground/demos/bend-gpu.lsp @@ -1,28 +1,39 @@ ; bend dispatch — ship a workload to a CUDA worker. ; -; This is a CPU-impossible problem at the chosen scale: SHAKE256 fan-out -; over 1M inputs takes about 10 seconds on a 3090 via lumbda's binary -; bend protocol (see whitepaper §11), and about 2 minutes on a host CPU. +; This demo refuses to run locally — set a bend URL in the ⚡ bar +; above first. The playground guards the Run button so a customer +; machine never burns minutes on the local tiers. ; -; This demo refuses to run locally — set a bend URL in the bar above -; first. The playground guards the Run button so a customer machine -; never burns minutes on the local tiers. +; What you can call today (S-expression mode over HTTP): +; (ping) → (ok pong) — round-trip probe +; (health) → (ok (load-avg ...)) — worker telemetry +; (cuda-shake-fanout INPUTS OUT-BYTES) +; → (ok (HEX0 HEX1 ...)) — N SHAKE256 outputs +; Binary modes (BSHK / BCGB / BSCP / BSRT / BSB3) and the +; 1M-input "feel the GPU win" run stay native-only until Phase 2 +; lands server-side factory recipes — those compile per-call +; binaries on the worker filesystem and stream them through +; the existing handlers. -; Payload is the lumbda S-expression form the gpu-worker expects. -; Replace COUNT with a smaller value (e.g. 1024) for warm-up runs and -; a larger one (1000000+) when you want to feel the GPU win. -(define COUNT 1000000) +(display ";; 1. probe round-trip ...") (newline) +(define r1 (bend!-call "(ping)")) +(display " (ping) → ") (display r1) (newline) +(newline) +(display ";; 2. worker telemetry ...") (newline) +(define r2 (bend!-call "(health)")) +(display " (health) → ") (display r2) (newline) +(newline) + +(display ";; 3. small CUDA fan-out (3 inputs, 32-byte outputs) ...") (newline) (define payload (string-append - "(cuda-shake256-fanout " - (number->string COUNT) - " 32)")) + "(cuda-shake-fanout " + "(\"00\" \"01\" \"deadbeef\") " + "32)")) +(display " payload: ") (display payload) (newline) +(define r3 (bend!-call payload)) +(display " result : ") (display r3) (newline) +(newline) -(display "bend payload: ") (display payload) (newline) -(display "sending to gpu-worker...") (newline) - -(define result (bend!-call payload)) - -(display "result: ") (display result) (newline) (print "done")