bend: defensive errors when daemons missing + demo round-trips real ops

gpu-worker.lsp — handle-cuda-shake-fanout, handle-cuda-sim-ops-bin,
and the four binary handlers (BSHK/BCGB/BSCP/BSRT/BSB3) now check
for the registered daemon (or, for sim-ops-bin, the bend-cuda
binary) before dispatching. Missing daemon returns (error
(daemon-not-registered <op>)) over S-exp wire, or "BERRdaemon-not-
registered: <op>" over binary wire. Before this commit any caller
whose worker host lacked a CUDA binary saw the child process crash
on (cdr #f) and got HTTP 502 / empty response with no useful
diagnostic.

Factored two helpers: daemon-or-error (S-exp result) and
with-required-daemon (binary-mode wrapper). Both keep the original
handler bodies untouched on the happy path; the guard adds one
assoc lookup per request.

www/playground/demos/bend-gpu.lsp + wasm/app/demos/bend-gpu.lsp —
demo was sending (cuda-shake256-fanout COUNT 32), an op the
dispatcher doesn't know AND a signature handle-cuda-shake-fanout
doesn't accept (it takes (inputs out-bytes)). Replaced with a
three-step probe: (ping) → (health) → small (cuda-shake-fanout
("00" "01" "deadbeef") 32). Each step prints its result so the user
gets feedback at every stage of the round-trip. Note added in the
header that browser-side bend!-call is asm-tier-only today; pyodide
and emcc tier wiring is the next commit.
This commit is contained in:
russell@unturf.com 2026-06-14 18:12:47 -04:00
parent ee5e997df7
commit 755a4f42ef
No known key found for this signature in database
3 changed files with 140 additions and 82 deletions

View file

@ -196,23 +196,52 @@
(car (cdr x)))
(else x)))
;; daemon-or-error op-name → daemon-pair on success, or (error
;; (daemon-not-registered <op>)) 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: <op>" 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.

View file

@ -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")

View file

@ -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")