Sketches how bend would wire into ecdsafail-challenge candidate
search loop on the foxhop.net side:
ecdsa/lumbda/search.lsp
→ (bend!-call '(cuda-sim-ops-bin ops-path 141))
→ gpu-worker.lsp routes to demo_ops --portal
→ S-exp result back to lumbda, scoring proceeds
Identifies the two pieces missing before this lands:
1. `system`-equivalent primitive in lumbda (or spawn+wait via
existing spawn-process-stdio)
2. Phase B step 7 (Solinas mod-mul) so lumbda emits real-scale
ops.bin variants worth bending
Once both close, this is a half-day wire-up.
Cross-references:
~/git/www.foxhop.net/ecdsa/cuda/ — the CUDA prototype
~/git/www.foxhop.net/ecdsa/lumbda/search.lsp — current search loop
examples/cuda-fanout/DESIGN-go-gpu.md — the broader bend RPC design
4.6 KiB
Integrating bend → ecdsa lumbda search loop
The motivating workload behind bend is ecdsafail-challenge candidate
search: the lumbda search loop (ecdsa/lumbda/search.lsp in
www.foxhop.net) emits circuit variants, scores each, picks the lowest.
At toy scale it runs in lumbda's own simulator (sim.lsp). At
upstream scale (12.8M ops, 1,355 qubits, 1.3M bits) it needs the CUDA
prototype (ecdsa/cuda/demo_upstream, byte-identical to upstream Rust
eval_circuit).
bend is the glue.
Where things stand
In www.foxhop.net's ecdsa/cuda/:
| binary | purpose | upstream byte-parity? |
|---|---|---|
demo |
hand-crafted 21-op circuit, CPU↔GPU smoke | n/a |
demo_ops |
ops.bin loader, LFSR RNG, scoring | gate counts ✓ |
demo_upstream |
SHAKE + secp256k1 register init | full (every digit) |
demo_multi |
multi-candidate batch driver, S-exp portal output | per candidate |
Each writes an S-expression portal lumbda can ingest directly.
The new GPU op for bend's registry
Add a cuda-sim-ops-bin op (analogous to today's cuda-shake-fanout):
;; in bend.lsp
(define (bend-sim-cost args)
"(cuda-sim-ops-bin path n-batches) — cost = n-batches × 12.8M ops
approx 100 ns/op host."
(let ((n-batches (car (cdr args))))
(* n-batches 12788119 100)))
(bend-register-op! 'cuda-sim-ops-bin bend-sim-cost)
Worker handler
;; in gpu-worker.lsp
(define (handle-cuda-sim-ops-bin args)
(let* ((ops-path (car args))
(n-batches (car (cdr args)))
(portal-out (gensym-path "/tmp/bend-cuda-sim" ".portal"))
(cmd (string-append
"/home/fox/git/www.foxhop.net/ecdsa/cuda/demo_ops "
ops-path
" --batches " (number->string n-batches)
" --portal " portal-out)))
(system cmd) ; or spawn-process-stdio + wait
(let ((result (read-from-string (file->string portal-out))))
(delete-file portal-out)
(list 'ok result))))
Note: system doesn't exist as a lumbda primitive today; either add
one (~10 LoC per tier) or spawn through spawn-process-stdio and
read stdout to confirm completion before reading the portal.
Search-loop side
ecdsa/lumbda/search.lsp currently does:
(define (eval-variant variant-id cases)
(let ((circ (emit-variant variant-id ...)))
(simulate circ) ; in-process lumbda sim.lsp
...))
With bend:
(load "bend.lsp")
(define (eval-variant-cuda variant-id ops-bin-path)
(let* ((result (bend!-call
(list 'cuda-sim-ops-bin ops-bin-path 141))))
;; result shape from demo_ops's portal:
;; (cuda-multi-result (ops-path …) (status OK)
;; (totals (clifford N) (toffoli N))
;; (timing-ms …))
(let ((totals (section result 'totals)))
(list variant-id
(car (cdr (assoc 'toffoli totals)))
(car (cdr (assoc 'clifford totals)))))))
Each variant emits its own ops.bin (the upstream binary format we
parse with ops_loader.c), the search loop bends it to a GPU worker,
worker calls demo_ops, returns the portal, lumbda picks the
winner.
What's missing before this lands
systemor equivalent primitive in lumbda (Python/C/asm) — one of:- synchronous subprocess wait
spawn-process-stdio+ wait via reading "done" line
- Lumbda's emitter side needs to produce upstream-format
ops.binfiles. Toy 4-bit circuits don't need this; real secp256k1 circuits would require Phase B step 7+ (Solinas mod-mul + Bernstein-Yang mod-inv) to close before there are interesting variants to bend. - The search-loop scoring rule needs adaptation: today it scores
avg_toffoli × peak_qubits; the CUDA portal returns Σ counts over 141 batches × 64 shots, so dividing by 9024 gives the per-shot avg upstream uses.
Why this design
bendalready does the routing decision: small toy circuits stay in lumbda's in-processsim.lsp; large ones bend to GPU.- Wire protocol identical to
cuda-shake-fanout— no new primitive beyond a Scheme registration entry. demo_ops --portaloutput already matches lumbda's S-expression ingestion contract (seeecdsa/cuda/main_ops.cportal writer).- Worker pool elasticity: any GPU host can register
cuda-sim-ops-binand become eligible to receive candidate evaluations. Coordinator's load-aware dispatch (DESIGN-go-gpu.md) handles fan-out across multiple GPU hosts.
This is a half-day's wire-up once Phase B step 7 closes and lumbda
gains a system-equivalent primitive. Until then, the architecture
is documented; the integration waits.