diff --git a/examples/cuda-fanout/DESIGN-ecdsa-integration.md b/examples/cuda-fanout/DESIGN-ecdsa-integration.md new file mode 100644 index 0000000..5b4433c --- /dev/null +++ b/examples/cuda-fanout/DESIGN-ecdsa-integration.md @@ -0,0 +1,126 @@ +# 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`): + +```scheme +;; 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 + +```scheme +;; 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: + +```scheme +(define (eval-variant variant-id cases) + (let ((circ (emit-variant variant-id ...))) + (simulate circ) ; in-process lumbda sim.lsp + ...)) +``` + +With bend: + +```scheme +(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 + +1. `system` or equivalent primitive in lumbda (Python/C/asm) — one of: + - synchronous subprocess wait + - `spawn-process-stdio` + wait via reading "done" line +2. Lumbda's emitter side needs to produce upstream-format `ops.bin` + files. 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. +3. 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 + +- `bend` already does the routing decision: small toy circuits stay + in lumbda's in-process `sim.lsp`; large ones bend to GPU. +- Wire protocol identical to `cuda-shake-fanout` — no new primitive + beyond a Scheme registration entry. +- `demo_ops --portal` output already matches lumbda's S-expression + ingestion contract (see `ecdsa/cuda/main_ops.c` portal writer). +- Worker pool elasticity: any GPU host can register + `cuda-sim-ops-bin` and 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.