Commit graph

70 commits

Author SHA1 Message Date
1db0932fd5
gpu-worker: admit seed 4096→2500 — match real K=2 bin avg, unchoke 4+ concurrent 2026-06-11 10:27:37 -04:00
711095ddd8
gpu-worker: dynamic VRAM admission per-cell — no static max-children cap
Drop the static *vram-budget-mib*=22000 cap that fox flagged as wrong:
'we shouldn't limit with a max — the algo should determine how many
children based on the bend forms usage in vram.'

New algorithm:
- *gpu-total-mib* (24576 default, RTX 3090) + *gpu-headroom-mib* (1024 pad)
- *vram-per-cell-max-mib* (4096 seed) tracks largest cell observed.
- admit-fork? returns true iff
    (current_vram + projected_cell + headroom) < gpu_total
- wait-admit blocks at run-loop top using projected = current per-cell
  max. Self-tunes: tiny cells → many concurrent, huge cells → few.

Helper file-size-mib (stat -c %s) reads bin file size as cheap proxy
for per-cell VRAM (bin file on disk ≈ peak VRAM bend-cuda loads).

Open: cross-fork learning. record-cell-vram! runs IN THE CHILD so
parent's *vram-per-cell-max-mib* doesn't see updates without a fork-
shared signal (TODO: parent peek bin path before forking, or child
writes per-cell-size to small file the parent reads). For now the
seed value + max-tracking-in-future-runs handle the common case
where all cells are similar size.
2026-06-11 09:39:52 -04:00
092a8ed741
gpu-worker: fork-per-accept + VRAM-aware admission
Single-PID parent persistent listener; each accept forks a short-lived
child handler that owns one bend-cuda subprocess + responds + exits.
Linux COW handles memory; OS scheduler distributes across cores.
N concurrent requests = N children + parent — naturally VRAM-isolated.

VRAM admission: wait-vram-clear queries nvidia-smi before each fork,
blocks accept when used > *vram-budget-mib* (default 22000, override
via LUMBDA_VRAM_BUDGET_MIB env). 24G card with avg 1-2GB per bin
supports 4-12 concurrent comfortably.

Requires lumbda c-tier fork-self / waitpid-nonblock / exit-immediate /
sleep primitives (commit 81ac49e). Child uses exit-immediate not exit
to avoid dual-cleanup hang on shared parent state.
2026-06-11 09:25:07 -04:00
e57c4948ab
cuda-fanout: drop legacy demo_ops references — bend-cuda only
Earlier rename kept DEMO_OPS env fallback + *binary-demo-ops* var name
as transitional back-compat. With both hosts redeployed on bend-cuda
that's no longer needed.

Renamed:
  *binary-demo-ops*  ->  *binary-bend-cuda*
  DEMO_OPS env       ->  removed (only BEND_CUDA recognized now)

Also bulk-updated cuda-fanout sibling docs (DESIGN, CATALOG, plans/)
that still spelled the old name.

Slot reserved for future bend-rocm / bend-cpu via parallel env vars.
2026-06-09 15:17:04 -04:00
b296eb030e
gpu-worker: feeder-paused state in (health) RPC
bend's (health) now reports whether $BEND_QUEUE_DIR/FEEDER_PAUSE
marker is set. Consumers (feeder, factory-status, ops scripts) get
pause state in the same single RPC as bend liveness + supervisor
proc counts + queue depths.

Returns:
  1   marker present (operator wants this host out of rotation)
  0   no marker (host in active rotation)
  -1  BEND_QUEUE_DIR env unset (host has no associated queue)

Lets a future feeder version drop separate SSH pause-marker probes
in favor of the bend health RPC. Today's feeder still does the SSH
check; this just opens the door for the simpler model.
2026-06-09 15:15:06 -04:00
6af72c706c
gpu-worker: dispatcher-procs in (health) RPC response
Mirror of pool-procs added earlier — bend health now reports both
pool-procs and dispatcher-procs counts so a single RPC tells the
feeder/factory consumer whether either supervisor is dead while
queue has work.

Failure mode this fixes: 4090's bend-dispatcher hit MAX_IDLE_LOOPS
drain-exit; balance moved 5 cells into its queue but no dispatcher
to consume them. Bend health response previously didn't surface the
gap; feeder's separate SSH probe (now added in www.foxhop.net commit)
caught it but a single RPC is cheaper than per-host SSH.
2026-06-09 14:56:56 -04:00
f53894df11
gpu-worker: rename ecdsa-emit-pool -> bend-emit-pool in pool-procs probe
Follows foxhop ecdsa repo rename of infrastructure scripts. The pool
process is now named bend-emit-pool (job-agnostic), not ecdsa-emit-pool.
health-pool-procs pgrep updated to match.
2026-06-09 14:31:41 -04:00
e294decd74
gpu-worker: pool + queue health in (health) RPC response
bend co-lives with ecdsa-emit-pool on each foxhop production host.
When pool dies but bend stays up, .lsp cells pile un-emitted; bend
sits idle waiting for .ready bins that never arrive. Today's incident
took 30+ min to surface because feeder couldn't tell from bend health
alone — needed a separate SSH+pgrep per host.

Add pool/queue counts to (health) so one RPC returns the full picture:
  (ok (load-avg L) (vram-free-mb V) (uptime-ms U)
      (pool-procs P) (queue-ready R) (queue-emitting E) (queue-done D))

Helpers:
  health-pool-procs        pgrep -cf ecdsa-emit-pool
  health-queue-count EXT   ls $BEND_QUEUE_DIR/*.EXT | wc -l

BEND_QUEUE_DIR env var — set when bend is launched on a host with an
associated pool. Absent → queue counts return -1 (caller treats as
'unknown / not applicable').

Caller now has single-RPC view of bend + pool + queue health; feeder
can drop its separate SSH pool-watchdog probe in favor of the bend
(health) RPC field.
2026-06-09 14:26:26 -04:00
b4732371a0
examples/cuda-fanout: rename demo_ops → bend-cuda
The CUDA binary that bend's gpu-worker.lsp spawns to run QECCOPS1
ops.bin sims is renamed from demo_ops to bend-cuda (production naming;
AMD ROCm equivalent will land as bend-rocm).

*binary-demo-ops* now reads BEND_CUDA env first, falls back to legacy
DEMO_OPS env, then default path /home/fox/git/www.foxhop.net/ecdsa/cuda/
bend-cuda. The legacy demo_ops symlink is in place at the install side
so back-compat holds during the rename transition.

See www.foxhop.net commit (sibling repo) for the foxhop ecdsa side
of the rename.
2026-06-09 13:46:30 -04:00
3599c23f23
gpu-worker: guard portal-timing/find-number against #f form
Root cause for repeated bend gpu-worker crashes mid-pipeline:
  ;;; bend RECV cuda-sim-ops-bin ops=monitor-r00007-c006.bin
  error: not a pair: #f

When demo_ops crashes mid-write (GPU OOM, segfault, etc) the portal
file may exist but be empty or malformed. read-from-string returns
#f. Both portal-timing + portal-find-number then call (cdr #f) =
runtime crash, taking the port-8320 listener down.

Fix: gate both walkers on (pair? form) BEFORE descending. When form
is not a pair (= #f from empty/malformed portal), return #f cleanly.
Caller's (or cpu-ms 'NA) display already handles #f.

Watchdog still exists for true OOM / kill -9, but routine empty-portal
events no longer take bend down.

Reproduces by sending a malformed S-expr to portal-find-number or
portal-timing.
2026-06-08 15:54:01 -04:00
8d66bc01f1
bend port flip: 9091 → 8320 (BEND mnemonic)
Port mnemonic embedded verbatim across our source files:

  8 ~= B (implied infinity B flattened; bake a cake; baby & me)
  3 ~= E (backward)
  2 ~= N (pivoted 90 degrees)
  0 ~= D (flattened)

Files touched:
- examples/cuda-fanout/gpu-worker.lsp (*worker-port*)
- examples/cuda-fanout/bend.lsp (*bend-worker-port*)
- examples/cuda-fanout/mock-worker.py (PORT)
- examples/cuda-fanout/bench_tiers.py (asm tier fixed port)
- examples/cuda-fanout/smoke-bend.lsp + smoke-bend-asm.lsp
- examples/cuda-fanout/README.md
- www/bend.html (catalog + multi-host text)
- Makefile (PORT default + comment)

bend.html updates 3090-ai + ai (4090) fleet table to active 2-node
mesh on 8320 — qwen moves off ai, bend takes over.
2026-06-06 15:06:18 -04:00
7245f6b465
catalog + bend.html: cuda-sim-ops-bin row gains SHAKE-RNG anchor note
demo_ops in foxhop ecdsa repo now defaults --rng-mode shake (Fiat-Shamir
over op stream, ports eval_circuit::fiat_shamir_seed verbatim). Σ Toffoli
& avg Toffoli on stock ops.bin match upstream eval_circuit bit-for-bit:
15,999,651,264 / 1,773,011.000 across 141 batches (9024 shots) on a
3090. Our cuda-sim-ops-bin numbers now compare directly to upstream's
public Pareto scoreboard with no calibration constant.

Catalog row & bend.html row updated to document the shake/lfsr toggle
& the upstream-match guarantee. Throughput figure also refreshed to
1.27x at 141 batches (from prior 1.07x at 128 batches).
2026-06-06 09:58:54 -04:00
58fd787ebf
bend: worker health heartbeat — (health) op + cache + VRAM-ranked pick
Adds a (health) op handler on the worker side & a lazy-refresh
health cache + VRAM-aware selection on the bend client side.

WORKER (gpu-worker.lsp)
  (health) returns (ok (load-avg L) (vram-free-mb V) (uptime-ms U))
  - L from /proc/loadavg first field
  - V from `nvidia-smi --query-gpu=memory.free --format=csv,noheader,nounits`
    (returns 0 when nvidia-smi missing — host w/o NVIDIA GPU)
  - U from current-time-ms; client detects a worker that hung
    & restarted between probes via uptime jump
  Backward-compat: workers without (health) return
  (error (unknown-op health)); client treats that as ok+vram=0.

CLIENT (bend.lsp)
  *worker-health* alist keyed "host:port" → (last-checked-ms status vram-mb)
  Cache TTL on ok = 5 s; cooldown on down = 30 s.
  bend-pick-worker now:
    - filters out workers in down-cooldown
    - sorts healthy peers by free VRAM descending
    - falls back to round-robin if every worker is in cooldown
  bend-dispatch-to-gpu flips workers to down on tcp-connect-fail
    or empty-reply so a transient failure costs at most one call.

Two lumbda quirks caught while building:
  - (eq? 0 #f) → #t in lumbda. worker-probe-health returns 0
    (a number) for the unknown-op fallback, but if we'd checked
    (eq? vram #f) we would have mis-marked the worker down.
    Now uses (number? vram) instead.
  - tcp-connect raises a Python ConnectionRefusedError (NOT a
    LispErr) on dead-host probes. lumbda's `guard` only catches
    LispErr; only with-exception-handler catches Python
    exceptions. Probe now wraps via with-exception-handler so a
    single dead worker never aborts a fleet iteration.

Smoke on Python tier:
  mixed (127.0.0.1:1 dead + 3090-ai live) → cache shows down for
  the dead one (30s cooldown), ok for live (22777 MB free VRAM,
  measured by the worker's nvidia-smi probe).
2026-06-06 09:25:08 -04:00
fc1563b4d6
bend Wave 2 — cuda-blake3-tree lands live on 3090
Agent ac6c3c7b built blake3-fanout.cu (clean-room BLAKE3 reference
adaptation, vendored under BLAKE3 team's CC0-1.0 / Apache-2.0
allowance) + test_blake3_known_answers.py harness + Makefile target
+ gpu-worker.lsp handler (handle-binary-blake3, BSB3/BSR3 magic).

Bench on 3090-ai (best-of-3, kernel-only):
  workload         kernel   throughput
  1k × 64 B          0.08 ms    ~7   GB/s
  100k × 64 B        1.87 ms    59.7 GB/s
  1M × 64 B          1.97 ms    32.5 GB/s
  1k × 1 MB         51.5  ms    20.4 GB/s

Byte-identity vs the BLAKE3 reference spec PASS at n in
{32, 1k, 10k, 100k, 1M}; covers both the single-chunk (≤1024 B)
& multi-chunk (≥1024 B) Merkle-tree paths.

Hardware policy compliance: per the 3090-only directive (commit
9e4e9b4), this row lists RTX 3090 only. The agent's 4090
measurements were dropped from the catalog & bend.html. ai
worker stays disabled.

Note the agent hit a socket error AFTER all files landed but
BEFORE it could git commit. This commit assembles its work from
the working tree, verifies the build still passes on 3090
(byte-identity + bench above), and ships.

Live forms now: 7 — shake, sim-ops-bin, sim-axis-flip,
cgbn (9 ops), secp256k1 (v3 windowed-G), radix-sort,
blake3-tree.
2026-06-05 22:07:25 -04:00
9e4e9b48f9
bend: 3090-only production default; 4090 reserved for qwen
Fox decision: don't routine-fan-out to ai.foxhop.net (4090) when
qwen LLM holds GPU residency. The radix-sort 4090 OOM caveat
surfaced today demonstrated the cost of casual co-residency —
secp daemon parked 24 GiB up front leaving 47 MiB free.

bend.lsp's *bend-workers* default already empty (single-host
fallback). Updated the docstring example to drop the ai.foxhop.net
entry; multi-host fan-out is OPT-IN per call via bend-set-workers!
or BEND_WORKERS env.

bend.html fleet section now reflects the policy:
  - 3090-ai.foxhop.net:9091  active production worker
  - ai.foxhop.net:9092       reserved for qwen; bend per workload

ai.foxhop.net worker process killed; 4090 VRAM returned to qwen
(1.6 GiB free post-kill vs 47 MiB while bend was running).

When we have a long-running parallel sweep that justifies the
4090's marginal throughput, the caller opts in explicitly. Don't
auto-route.
2026-06-05 21:55:41 -04:00
d74953723f
form E day-1 — Montgomery batch-inv 0x0A regressed 300x vs 0x05, parked
Built op_id 0x0A (mod-inv-batch) on cgbn-batch-worker.cu using
Montgomery's 1 inv + 3(N-1) muls trick. Byte-identity holds at
every N (n in {32, 1k, 10k, 100k}, all 10 ops × 4 N = 40 PASS).

Kernel-ms 0x05 (per-instance, current LIVE op) vs 0x0A on a 3090:
  N        0x05         0x0A          ratio
  10k       0.52 ms     78.34 ms     0.01x
  100k      2.54 ms    762.28 ms     0.003x
  1M       21.10 ms   7763.45 ms     0.0003x

The spec premise — "sequential modmuls cheap, parallel mod-invs
expensive" — INVERTS on a 3090. CGBN's parallel 0x05 saturates 82
SMs × 128 in-flight instances at ~47 Mops/s; one TPI=8 lockstep
instance walking 3N sequential modmuls in Phase 1+3 is
latency-bound, not throughput-bound. The Montgomery trick only
wins on hardware where one inv is dramatically more expensive
than 3N muls; on a 3090 the parallelism budget makes the
inversions cheap.

HARD-RULE TRIPPED (>= 5x win at n=100k required). Hard rule honored:
  - no master daemon restart
  - no CATALOG.md / bend.html promotion
  - production daemons on :9091 / :9092 untouched (still serve 9-op binary)

Code + test changes & progress doc commit here as research artifact.
The 10-op binary builds clean & is byte-correct; just slow. Future
day-2 pass should land per-block parallel prefix scan (Kogge-Stone
or Sklansky) for Phase 1+3 — same refactor pattern Form A Day-4
needs to make v4 beat v3.
2026-06-05 21:50:51 -04:00
05fa922d42
bend form G — cuda-radix-sort lands live (CUB DeviceRadixSort u64)
Thin CUDA binary wrapping cub::DeviceRadixSort::SortKeys on a 64-bit
key stream. One op wired day-1 (0x01 sort-u64-asc); 0x02/0x03/0x04
slots reserved (desc, u32, key-value) for future builds.

Wire stays distinct from existing forms:
  request:  BSRT | u32 op_id | u32 n | u64[n]
  response: BSRR | u32 status | u32 n | u64[n] sorted asc

Validated on 3090-ai.foxhop.net byte-identical to Python sorted() at
n ∈ {32, 1k, 100k, 1M, 10M}. Bench at sustained throughput:

  n           kernel_ms   Gkeys/s
  100,000     0.142       0.706
  1,000,000   0.265       3.767
  10,000,000  1.817       5.504

~4x over the published Titan baseline (1.4 Gkeys/s) at saturation,
matching CUB's expected Ampere scaling.

gpu-worker.lsp learns handle-binary-sort + BSRT magic dispatch +
maybe-register-daemon! for cuda-radix-sort (overridable via
RADIX_SORT_WORKER env). Both 3090-ai (:9091) & ai (:9092) workers
restarted; both log `ready cuda-radix-sort <- ./radix-sort`.

4090 (ai.foxhop.net) standalone --binary run OOMs on cudaMalloc when
all four daemons are co-resident (secp256k1 daemon parks ~24 GiB on
startup, leaving 47 MiB free). Pre-existing capacity constraint of
the ai host, not a form-G defect; tracked in form-G-progress.md.

CATALOG.md & www/bend.html live-forms table updated with measured
3090 numbers; Wave 1 surveyed row for G marked as promoted.
2026-06-05 21:50:05 -04:00
5d1ad62f51
bend catalog Wave 4 — 20 surveyed forms across new domains
Covers lattice cryptography (Kyber, Falcon), lattice reduction
(G6K tensor sieve), privacy/MPC (Ironman OTE, Piranha, Fastplay),
ZK prover internals beyond MSM/NTT (zkSpeed sumcheck, Air-FRI),
sparse direct solvers (cuDSS Cholesky), real-time DSP (Kalman
batched 1386x, particle filter), computational biology beyond
pairwise (SCAMP matrix profile, cuDTW subsequence), erasure
coding (PErasure Reed-Solomon, Rabin fingerprint), computational
geometry (gDel3D, CudaHull), ODE/PDE (RK stiff ChemKin, FEM
assembly JIT), quantum chemistry (TeraChem DFT).

Headline picks for ECDSA mission:
  - cuda-g6k-tensor-sieve   1230x — lattice attacks on biased nonces
  - cuda-zkspeed-sumcheck    801x — sumcheck for ZK proof-of-equivalence
  - cuda-kalman-batched     1386x — batched estimation for candidate
                                    sweep + bluetooth/punters tracking

Headline picks for unsandbox/permacomputer mission:
  - cuda-kalman-batched     1386x — RSSI tracking, punters surface
  - cuda-ironman-ote         237x — cross-fleet PSI/MPC
  - cuda-perasure-crs        10x  + 10 GB/s — object-storage erasure
  - cuda-scamp-matrix-profile      — journald intrusion detection
  - cuda-rabin-fingerprint   16x  + 40 Gbps — CDN/proxy cache dedup

Headline picks for undefect mission:
  - cuda-cudss-cholesky    >100x — defect-graph eigenproblems
  - cuda-cufalcon-sign    29.5x — PQ disclosure signing
  - cuda-air-fri           22.8x — verifiable-defect-scan provenance

7 filter-outs noted (MAFFT, RAxML, AmgX CG, BVH Karras LBVH, LDPC
decode, mesh decimation, discrete Gaussian sampler) — kept honest
rather than padded.

Catalog total: 5 live + ~62 surveyed across 4 Waves.
2026-06-05 21:33:49 -04:00
3a8c66a148
examples/cuda-fanout: Day-4 v4 stack landed (byte-clean, -12% vs v3); daemon flips to v3 --window-w 4
Day-4 task per fox: stack v3 windowed-G ladder (Day-3) with v2 Montgomery
batch inversion (Day-2). Idea: Day-3 cut scalar_mul, residual ModInv now
matters — which is what Day-2 needed to win.

Result:
- v4 (--window-w 4 --batch-inv) lands byte-identical vs coincurve at
  n in {32, 1000, 10000, 100000, 1000000}.
- 3090-ai best-of-3 @ n=1M: v1 7.86 / v3 13.83 / v4 12.16 Mkeys/s.
- v4 regresses -12% vs v3 because v2's Phase B/D walks run one thread
  per block (3906 active threads at n=1M; 3090 has ~125k concurrent
  thread budget). v3's per-thread ModInv saturates 1M parallel threads
  on 82 SMs — the threading model beats the smaller field-mult count.
- HARD-RULE triggered: v4 < 1.10x v3 → no v4 promotion.

Daemon flip per task brief option A:
- start-daemon now takes optional extra-args; register-daemon!,
  maybe-register-daemon! pass them through.
- *secp-daemon-extra-args* = '("--window-w" "4") activates v3 in the
  spawned secp256k1 daemon. cuda-shake-fanout and cuda-bignum-cgbn
  spawn unchanged.

Deployed:
- 3090-ai: rebuilt secp256k1-batch-mul, restarted via /tmp/launch.sh.
  Worker log shows [v3-window-w4] on smoke at n=16 and n=200.
- ai.foxhop.net (4090): same. sm_89, libgmp at ~/local/gmp.

Smoke test: small BSCP request to each worker over TCP, byte-identical
vs coincurve. Both PASS.

Catalog (CATALOG.md, www/bend.html): promoted v3 throughput to
13.83 Mkeys/s @ n=1M, recorded v4 regression and warp-scan Phase B/D
as Day-5+ refactor.

Progress doc: plans/form-A-day4-progress.md.
2026-06-05 19:07:00 -04:00
3e0ca555b5
examples/cuda-fanout: guard delete-file in 3 binary handlers
handle-binary-shake, handle-binary-cgbn, handle-binary-secp each
called (delete-file in-path) (delete-file out-path) unconditionally
in both the daemon-ok and daemon-error branches — 6 sites total.

When the daemon failed without producing out-path (e.g. crash, OOM,
bad payload), delete-file raised file-not-found and the entire
listener exited. Crashed 3090-ai once during ai.foxhop.net deployment
smoke.

Wrap every delete-file with (if (file-exists? PATH) (delete-file PATH))
so a missing portal cannot kill the worker. (ok pong) sanity check
passes on Python tier.

Sites (12 guards = 6 path pairs × 2 paths):
  handle-binary-shake : ok branch + error branch
  handle-binary-cgbn  : ok branch + error branch
  handle-binary-secp  : ok branch + error branch
2026-06-05 18:58:45 -04:00
08446ef7b6
examples/cuda-fanout: bend multi-worker fan-out
Adds *bend-workers* list with round-robin dispatch, BEND_WORKERS env
loader, and helpers (bend-set-workers!, bend-pick-worker,
bend-parse-workers-env, bend-load-workers-from-env!). Single-host
legacy callers unaffected — when *bend-workers* is empty the
dispatcher falls back to *bend-worker-host* / *bend-worker-port*.

Validated cross-tier (Python + C lumbda) against a live two-host
cluster (3090-ai.foxhop.net:9091, ai.foxhop.net:9092) — round-robin
distributes evenly; cgbn mod-mul results byte-identical to gmpy2
reference on both hosts.
2026-06-05 18:18:29 -04:00
424051b7a4
catalog: cuda-secp256k1 v3 windowed-G — 13.60 Mkeys/s @ n=1M (1.73x v1)
Form A Day-3 shipped at lumbda ecfe27a. Windowed-G ladder (w=4,
16-entry G-table built once via batch-inverse on the 15 Z-coords)
landed clean.

Bench on 3090, n=1M (best-of-3, --no-batch-inv):
  v1 (Day-1)      127.04 ms  →   7.87 Mkeys/s
  v3 (Day-3 w=4)   73.54 ms  →  13.60 Mkeys/s  (1.73x v1, ~309x coincurve)

Byte-identity PASS at n in {32, 1000, 10000, 100000, 1000000} plus
known-small edge cases (k in {1, 2, 3, 7, 0xdeadbeef, n-1, n, 2^128-1}).

Daemon default still serves v1; --window-w 4 flag selects v3
explicitly. Fox's call on flipping the daemon default.

Day-4 plan: stack v3 + Day-2 batch-inv. v3 cut scalar_mul; the
residual inversion cost now actually matters, which is what Day-2
needed to win.

Three agents still in flight: walker promotion (#39), lever
generator (#40), ai.foxhop.net second worker (#30).
2026-06-05 18:16:08 -04:00
ecfe27a46f
bend form A — Day-3 v3 windowed-base ladder, 1.73x at n=1M on 3090
Per Day-2 progress doc the kernel bottleneck was scalar_mul (256 doubles +
~128 adds per scalar), not _ModInv. v3 swaps the binary double-and-add for
a windowed-base ladder: precompute table[0..15] = i*P affine on-device,
walk scalar 4 bits at a time MSB->LSB, cutting per-scalar adds from ~128
to ~63. Table loaded into __shared__ (1024 B) per block.

Benched on 3090-ai (best-of-3, --no-batch-inv):

  n=10k:   v1 4.22 Mkeys/s  v3 2.18 Mkeys/s  0.52x  (init overhead dominates)
  n=100k:  v1 7.48 Mkeys/s  v3 9.29 Mkeys/s  1.24x
  n=1M:    v1 7.87 Mkeys/s  v3 13.60 Mkeys/s 1.73x

Byte-identical against coincurve at n in {32, 1000, 10000, 100000, 1000000}.

CLI: --window-w 4 selects v3 (Day-3 canonical). w=8 reserved but stub-
rejected since device-side table init is register-stack-bounded at W <= 16.
Default no-flag behaviour stays v1 (Day-1) so the gpu-worker.lsp daemon
inherits the safe baseline until fox routes traffic to v3.

Companion progress doc at plans/form-A-day3-progress.md.
2026-06-05 18:14:56 -04:00
b2209ac337
cuda-fanout plans: Day-2/3 progress + Form D pivot RESULTS docs
Captures empirical record from three agents that finished today:

  form-A-day2-progress.md (STOPPED, no merge)
    v2 Montgomery batch inversion regressed v1 by 0.63x-0.94x
    across n in {10k, 100k, 1M}. Byte-identity PASS at every N;
    math correct. Root cause: at this N, scalar_mul (256 Jacobian
    doubles x 5-6 ModMult each) dominates, NOT _ModInv. v2's Phase
    B/D used 1 thread/block leaving ~97% of SMs idle. v1 baseline
    re-measured at 7.88 Mkeys/s at n=1M (catalog upward correction
    from initial 6.51). Day-3 path: warp-level prefix scan OR
    windowed-G ladder.

  form-D-axis-flip-RESULTS.md (Form D opt 1 shipped foxhop 1f7ac9d)
    Per-candidate axis kernel measured 217 Mops/s at K=32 M=4 on
    3090; 23.7x over per-shot N=4 at same M. Both axes saturate
    at the same ~220-250 Mops/s, refuting the bandwidth-bound
    diagnosis. Axis flip's win is occupancy-amortization at small
    M, not bandwidth redistribution.

  form-D-build-progress.md (Form D AG kernel parked)
    Aaronson-Gottesman tableau dead-end: our point-add circuit
    contains no H or S, so state never leaves the computational
    basis and AG buys nothing. Three pivot options proposed; fox
    picked options 1 + 3 in parallel.

Day-1 binary at lumbda 7661788 stays canonical for
cuda-secp256k1-batched-mul; v2 working-tree code lives uncommitted
on the build host as Day-3 scratch.
2026-06-05 15:32:32 -04:00
95b3b1af21
catalog: Form D opt 3 landed; both pivots converge on compute-saturation
Form D Option 3 (QECCOPS2 packed op format) shipped at foxhop
commit 90484ca. Numbers:

  n_batches  unpacked  packed  speedup
       16    5842 ms   5428    1.076x
       64    6226 ms   5830    1.068x
      128    6604 ms   6201    1.065x

On-disk shrink: 716 MB → 307 MB (2.33x). Storage win, not compute win.

Critical diagnostic correction from the opt 3 agent: the 3.5x
bandwidth projection was WRONG because ops_loader.c already
narrowed u64 → u32 on load, so in-VRAM Op was already 28 B, not
56 B. Max realistic VRAM-reduction was 28 → 24 B = 1.17x best
case. The measured 1.07x matches: per-shot state traffic (qubits
+ bits per thread) is ~85x larger than the op stream, which
warp-broadcasts through L1/L2. Op-stream bandwidth was not the
bottleneck.

Both Form D pivots (opt 1 axis-flip, opt 3 packing) converged on
the same finding: the 3090 is compute-saturated at ~250 Mops/s on
the kickmix circuit, not bandwidth-saturated. Algorithm + layout
tweaks already extracted; the real next macro-lever is multi-GPU
fan-out across the fleet (3090-ai + ai.foxhop.net 4090 + future
nodes, each saturating its own ceiling in parallel).

cuda-sim-axis-flip remains LIVE as a tool with specific use:
many-candidates × few-shots search-loop early-screen. ops.bin
packing remains useful for fleet rsync (3.5x smaller payloads
across the LAN matters when shipping candidate variants).
2026-06-05 14:16:20 -04:00
f8705f2e0d
cuda-fanout plans: Form D Option 3 (ops.bin packing) results
QECCOPS2 packed-op format landed in foxhop ecdsa/cuda/ at commit
90484ca. 28 → 24 B per op in VRAM, 56 → 24 B on disk (2.33× shrink).

n_batches 16/64/128 on RTX 3090: ~7% kernel speedup, byte-identical
CPU vs unpacked-GPU vs packed-GPU. Bandwidth-bound diagnosis from
form-D-build-progress.md §4 stands — per-shot state traffic (~85×
larger than op stream) owns the 1.07× ceiling. Per-candidate axis-
flip (sim_gpu_axis.cu, foxhop 1f7ac9d) remains the open lever; this
~7% stacks on top.
2026-06-05 14:14:56 -04:00
262607f438
catalog: cuda-sim-axis-flip live, bandwidth diagnosis flipped
Form D opt 1 (axis-flip refactor) shipped at foxhop 1f7ac9d.
Per-candidate parallelism over per-shot delivers 217 Mops/s at
K=32 candidates × M=4 shots on a 3090, byte-identical with the
CPU reference at every (K, M) pair we measured.

Key finding inside the numbers: the bandwidth-bound diagnosis
flagged in the Form D structural-finding doc was WRONG. Both
axes (per-shot N=128 and per-candidate K=32 M=4) saturate at the
SAME ~220-250 Mops/s on the 3090. Per-shot wins by 14% at full
saturation; axis flip wins by 23.7x at small M because it fills
SMs in one launch instead of leaving them idle.

So the axis kernel is the right tool for lumbda's many-candidates
× few-shots search-loop early-screen pattern — not a replacement
for the per-shot kernel.

Form D opt 3 (ops.bin packing) still in flight. Since compute
saturates before bandwidth on this device, packing may not
deliver expected gain. Letting that agent finish so we have
empirical numbers either way.

Live forms now: 5
  cuda-shake-fanout           ~12x host hashlib @ 1M
  cuda-sim-ops-bin            1.07x @ 128 batches (kickmix per-shot)
  cuda-bignum-cgbn            1.28 Gops/s mod-mul @ 1M (256-bit)
  cuda-secp256k1-batched-mul  6.51 Mkeys/s @ 100k (148x coincurve)
  cuda-sim-axis-flip          217 Mops/s @ K=32 M=4 (kickmix per-candidate)
2026-06-05 14:12:56 -04:00
7695163e1a
catalog: promote CGBN + secp256k1 to live, record measured throughput
Form A (cuda-secp256k1-batched-mul) shipped earlier this session at
commit 7661788. Form B (cuda-bignum-cgbn) extended to all 9 ops at
21bd26a. Both now serving on 3090-ai gpu-worker:9091 alongside the
shake form.

Catalog Live forms table:
  cuda-bignum-cgbn          | 1.28 Gops/s kernel mod-mul @ n=1M, 256-bit
                              (~256x GMP CPU single-thread); 9 ops total
  cuda-secp256k1-batched-mul| 6.51 Mkeys/s @ n=100k (~148x coincurve CPU);
                              Day-2 Montgomery batch inversion projected
                              toward FixedPaul's 6.5 Gkeys/s on 4090

Wire magics now in use:
  BSHK/BSHR — cuda-shake-fanout
  BCGB/BCGR — cuda-bignum-cgbn
  BSCP/BSCR — cuda-secp256k1-batched-mul
  (BSTB/BSTR reserved for cuda-clifford-stabilizer; parked per
   structural finding — our circuit has no H/S gates)

Form D pivots (axis-flip sim_gpu.cu + ops.bin packing) still in
flight; CATALOG.md + bend.html will gain rows once they ship.

Notable defect surfaced during Form A build: widely-cited secp256k1
generator y-coordinate
  0x483ADA7726A47B0DAFFA10ED2E11458A823D0E1D89DCAB14C7C39D9F8B97C20A
does NOT satisfy y^2 = x^3 + 7 mod p. Real Gy =
  0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8
Cross-tutorial propagation. UNDF candidate logged.
2026-06-05 14:10:37 -04:00
766178818c bend form A — cuda-secp256k1-batched-mul live on 3090
Per examples/cuda-fanout/plans/form-A-secp256k1-batched-mul.md.
Batched secp256k1 scalar*G via per-thread Jacobian double-and-add
plus per-thread Z-inversion to affine. Field arithmetic uses
FixedPaul/VanitySearch-Bitcrack GPUMath.h verbatim
(commit 66e6f9d, AGPL-3.0, vendored under
vendor/vanity-search-bitcrack/).

Wire: BSCP request / BSCR response distinct from BSHK/BCGB.
   "BSCP" u32 op_id u32 n base_xy(64B BE) scalars(n*32B BE)
   "BSCR" u32 status u32 n points(n*64B BE x||y)

Validation against coincurve on 3090-ai.foxhop.net byte-identical
across known-small (k in {1,2,3,7,0xdeadbeef,n-1,n,2^128-1})
& random sweeps at n in {32, 1k, 10k, 100k}.

Measured throughput on 3090, kernel-only:
   n=10k     2.32 ms    4.31 Mkeys/s
   n=100k   15.37 ms    6.51 Mkeys/s

End-to-end over warm TCP daemon from another host:
   n=100k  400 ms  250 kkeys/s wall  (PCIe + wire serialization
   bound; kernel still <16 ms)

Speedup vs coincurve CPU single-thread (~44 kkeys/s host)
~148x at n=100k kernel-only. Day-2 work to add _ModInvGrouped
batched inversion should push toward FixedPaul's 6.9 Gkeys/s
published on 4090.

gpu-worker.lsp: maybe-register-daemon! for cuda-secp256k1-batched-mul,
handle-binary-secp branch in handle-one dispatching on BSCP magic.
Makefile: secp256k1-batch-mul / secp256k1-test / secp256k1-bench
targets. Test harness ships with coincurve preferred, falls back to
python-ecdsa or pure-Python double-and-add for the host oracle.
2026-06-05 14:08:50 -04:00
277250ecfa
bend Form D structural finding — pivot to axis-flip + ops.bin packing
Form D build agent discovered: our point-add circuit contains no
Hadamard or S gates (only X/CX/CCX/CZ/CCZ/SWAP/R/HMR/Z/NEG).
State never leaves the computational basis. Aaronson-Gottesman
tableau compression buys nothing when superposition does not
exist; reduces to exactly what sim_gpu.cu already does at one
bit per qubit per shot.

Toffoli fraction measured 13.87% (well under the 40% threshold
the planner flagged). The 1.07x cuda-sim-ops-bin ceiling traces
to memory-bandwidth on per-shot striped state — not algorithm.

STABSim-class wins remain valid for QEC / surface-code work where
H + S exist; that's a future workload.

Two replacement directions queued:
  1. axis-flip sim_gpu.cu — per-candidate parallelism over per-shot.
     ~3 days. Reuses BSHK new op_id; no AG tableau.
  2. ops.bin packing — 56→16 B per op halves global-memory traffic.
     Addresses the actual bottleneck.

Catalog + public bend.html both updated. Form D progress doc at
examples/cuda-fanout/plans/form-D-build-progress.md documents the
structural reasoning in full.
2026-06-05 14:03:03 -04:00
21bd26a4d3
bend form B — cgbn-batch-worker covers all 9 dispatcher ops on 3090
Lands the remaining 8 ops from plans/form-B-bignum-cgbn.md §2:
  0x01 mod-add        cgbn_add + carry-or-ge-modulus subtract
  0x02 mod-sub        cgbn_sub + borrow conditional add
  0x04 mod-sqr        cgbn_sqr_wide + cgbn_rem_wide
  0x05 mod-inv        cgbn_modular_inverse (binary GCD)
  0x06 mod-exp        cgbn_modular_power (binary ladder)
  0x07 mod-reduce     cgbn_rem standalone
  0x08 add-no-mod     cgbn_add, truncated 256-bit
  0x09 mul-no-mod     cgbn_mul_wide, full 512-bit output (low|high)

process_one_bin now classifies op_id into three families (binary-mod /
unary-mod / no-mod), validates wire size per family, and carves modulus
/ a / b pointers accordingly. Output buffer width is 2x for 0x09 only.

test_cgbn_known_answers.py extended: one driver per op, gmpy2 reference
(with pure-Python fallback for invert/powmod), validated byte-identical
across n in {32, 1k, 10k, 100k}. ALL PASS on 3090-ai.foxhop.net.

Measured kernel throughput at n=100k (single 3090, median of 3):
  mod-add     0.19 ms   526 Mops/s
  mod-sub     0.19 ms   526 Mops/s
  mod-mul     0.24 ms   417 Mops/s
  mod-sqr     0.24 ms   417 Mops/s
  mod-inv     2.39 ms    42 Mops/s
  mod-exp     1.02 ms    98 Mops/s   (16-bit exponents)
  mod-reduce  0.20 ms   500 Mops/s
  add-no-mod  0.19 ms   526 Mops/s
  mul-no-mod  0.19 ms   526 Mops/s

mod-inv at 42 Mops/s tracks plan §8 projection (50-100 Mops/s on 3090
via CGBN's binary GCD) on the low end — Bernstein-Yang batched inverse
(form E) remains the upgrade path. mod-exp 98 Mops/s is for short
exponents only; full 256-bit ladder will drop ~16x per plan §8.
2026-06-05 14:00:47 -04:00
3ab4044805
bend form B — cgbn-batch-worker live on 3090, 1.28 Gops/s kernel
Day-1 baseline per examples/cuda-fanout/plans/form-B-bignum-cgbn.md
lands at 1.28 Gops/s 256-bit mod-mul kernel throughput on a 3090
@ n=1M instances. ~256x over single-thread GMP CPU (5 Mops/s).

Validated byte-identical with gmpy2 reference at n=32, 1k, 10k,
100k across three modulus families (secp256k1 prime, Mersenne-ish,
arbitrary odd) — all PASS.

Files:
  cgbn-batch-worker.cu    Day-1 binary: --daemon + --binary modes,
                          op_id 0x03 mod-mul at 256-bit width,
                          BCGB/BCGR wire (distinct magic from SHAKE's
                          BSHK/BSHR so gpu-worker.lsp can route).
                          Includes gmp.h before cgbn.h so CGBN's
                          dispatch picks cgbn_mpz.h (host path) instead
                          of the unimplemented cgbn_cpu.h stub.
                          Drops const from kernel args (CGBN API
                          non-const).
  Makefile                cgbn-batch-worker target, CGBN_INC env var.
  gpu-worker.lsp          handle-binary-cgbn routes BCGB-prefixed
                          BSHK payloads through the CGBN daemon;
                          maybe-register-daemon! lets a worker host
                          skip forms whose binaries aren't installed.
  test_cgbn_known_answers.py
                          gmpy2 cross-validation harness; falls back to
                          pure-Python pow(a*b,1,m) if gmpy2 missing.

Per-call wall-time stays ~160ms because of cold cudaMalloc + context
init each --binary spawn. The plan-projected 15k crossover applies to
daemon mode (warm context). Daemon wiring lands in the next commit.

Remaining ops (0x01 mod-add, 0x02 mod-sub, 0x04 mod-sqr, 0x05 mod-inv,
0x06 mod-exp, 0x07 mod-reduce, 0x08 add-no-mod, 0x09 mul-no-mod) land
per-op as we measure each.
2026-06-05 13:32:43 -04:00
4c5e04b07f
bend: planning docs for forms A, B, D (CATALOG.md build runway)
Three implementation plans land per CATALOG.md "Build order —
recommended 3-form runway":

  form-B-bignum-cgbn.md (3-5 dev-days, fewest unknowns):
    direct NVlabs CGBN consumption. 9-op dispatcher
    (mod-add/sub/mul/sqr/inv/exp/reduce + add-no-mod +
    mul-no-mod-2x-width). BSHK/BSHR mirrors shake256-fanout.
    15k crossover (wire overhead vs raw kernel). Risks:
    CGBN maintenance freshness + sm_86/sm_89 verification.

  form-A-secp256k1-batched-mul.md (6 dev-days):
    FixedPaul/VanitySearch-Bitcrack picked (AGPL-3, active
    2025-05-16) over JeanLucPons (GPL-3, stale 2022-04-04).
    Vendor 3 headers ~2400 LoC + write secp_mul_batch_kernel.
    BSPK/BSPR wire mirrors BSHK. Kernel extraction is the
    dominant risk.

  form-D-clifford-stabilizer.md (10 dev-days):
    STABSim & Qimax not open-source; clean-room
    Aaronson-Gottesman tableau recommended. Reuse QECCOPS1
    ops.bin over BSHK op_id=0x02 — no .stim translation.
    Toffoli option (b): score symbolically, simulate
    Clifford-only — gated on first measuring Toffoli fraction
    (point-add suspected ~50%, Amdahl eats gains if worse).

Plans live as actionable instructions in second-person voice;
each cites URLs for every claim. CATALOG.md & bend.html
untouched here — plans inform future surveys without inventing
numbers.
2026-06-05 13:25:37 -04:00
a33ae4331d
bend catalog Wave 3 — 15 surveyed forms (2026-06-05)
Adds 15 new GPU primitive forms across domains not covered by
Waves 1-2: ZK/SNARK provers, pairing crypto, tensor network
contraction, sparse LA, CV primitives, numerical solvers,
generic belief propagation, MD/CFD, convex opt, DSP beyond cuFFT,
DB aggregations, graph theory beyond triangle/PageRank.

Top hits by relevance to our missions:

  * cuda-mfcc-spectral (97x) — punters-cc, BT-DISC forensics, real-time CC pipeline. unsandbox HIGH.
  * cuda-batched-lp-simplex (95x over CPLEX) — Prime Mission workstation-to-queue balancing. unsandbox HIGH.
  * cuda-betweenness-centrality-weighted (30-150x) — workaholic-node detection on dependency DAG, MOAD-0001 model. undefect HIGH.
  * cuda-hungarian-lap (400M-var LAP ~13s) — workstation balancing. unsandbox HIGH.
  * cuda-msm-bls12-381 (27.86x) — Pippenger shares branchless modmul shape with our reversible secp256k1 inner loop. ECDSA HIGH.
  * cuda-tensornet-contract (tensor QR 100x vs Xeon 8480+) — alt to stabilizer/kickmix sim path; MPS/PEPS evaluates reversible secp256k1 circuits beyond Clifford. ECDSA HIGH.
  * cuda-loopy-bp-mrf (45x) — LBP substrate for FuzzingBrain probabilistic analysis. undefect HIGH.
  * cuda-ega-gpu-aggregation (6-29x) — defect-corpus aggregation at planetary scale; telemetry queue aggregation. undefect HIGH + unsandbox HIGH.

Plus highlight numbers: FluidX3D LBM 100-200x vs ANSYS Fluent,
CUDASW++4.0 5.71 TCUPS on H100 for sequence alignment, ICICLE-Snark
the fastest Groth16 today.

Wave 3 filter-outs noted: AMGX, GROMACS, NVOFA, junction-tree BP,
batched L-BFGS — all below 10x or insufficiently general.

CATALOG.md mirrors the table for in-repo reading; bend.html
renders the public version with reference URLs.
2026-06-05 13:18:06 -04:00
a17c114263
bend.html: dedicated page + 27-form catalog with published speedups
Splits a dense bend section out of index.html (now 6 lines: tagline,
example, three highlights, CTA) into a new public page at
lumbda.com/bend.html carrying:

  * full wire protocol (S-exp + binary BSHK modes)
  * tier-choice table (Py / C / asm × S-exp / binary)
  * real workload table from foxhop ecdsafail measurements
  * 2 live forms + 7 surveyed forms (Wave 1: secp256k1 batch-mul,
    CGBN bignum, Pollard rho, Stim-on-GPU stabilizer, Bernstein-Yang
    inverse, NTT, CUB sort/scan)
  * 20 surveyed forms (Wave 2) sorted by speedup descending —
    minhash 600-1000x, cuckoo filter 378x, ChaCha20 400 GB/s,
    SAT 93x, Dilithium PQ 57.7x, BLAKE3 tree, cuFFT batched,
    Bloom filter modern, GEMM FP8, hash-join 1.8T tuples/s on
    1024xA100, cuGraph 38B TEPS, TRUST triangle 1T TEPS, nvCOMP 2.2x
  * skipped section listing forms that don't pass the threshold,
    so the catalog stays honest about what GPU dominates vs not
  * cited canonical references for every entry

CATALOG.md mirrors the same data — single source of truth in repo;
bend.html renders the same metadata for public reading.

Build order remains: A cuda-secp256k1-batched-mul, D Clifford
stabilizer, B CGBN bignum.
2026-06-05 13:02:31 -04:00
78ff89fb8b
bend: cuda-sim-ops-bin handler + per-call CPU/GPU telemetry
gpu-worker.lsp gains a cuda-sim-ops-bin op handler that spawns
demo_ops from www.foxhop.net/ecdsa/cuda via spawn-process-stdio,
drains stdout, & parses our (cuda-sim-result ...) portal back.

Each call now emits two log lines:
;;; bend RECV cuda-sim-ops-bin ops=PATH n-batches=N t-ms=...
;;; bend DONE cuda-sim-ops-bin n-batches=N wall-ms=W cpu-ms=C gpu-ms=G mismatches=0 gpu/cpu=R

so we can tell how fast bend jobs run on CPU vs GPU per call.

CLAUDE.md & www/index.html mention this integration is now live
end-to-end across our fleet.
2026-06-05 10:18:11 -04:00
01ea93f68f
binary wire mode: 12x faster than host hashlib at 1M inputs
The S-expression wire format was the bottleneck at huge payload sizes
-- 23.8 s end-to-end for 1M x 16 B inputs on the Python tier, while
the actual CUDA kernel finishes the same workload in ~47 ms. The
hex-S-exp parser ate everything between.

New binary wire mode (magic 'BSHK' prefix; payload is the daemon's
binary portal format verbatim) bypasses S-expression parsing entirely.
Worker writes the blob to disk, calls daemon process-bin, reads result,
prepends 'BSHR' magic, replies.

Measured 3090-ai, daemon warm, localhost:

  workload      Py S-exp    Py binary   C S-exp    C binary
  100 x 16 B     3.43 ms     0.74 ms    0.40 ms    0.15 ms
  1k x 16 B     23.24 ms     0.76 ms    2.77 ms    0.22 ms
  10k x 16 B   218.82 ms     1.27 ms    CLIFF      0.88 ms
  100k x 16 B  2,219 ms     10.18 ms    CLIFF     10.35 ms
  1M x 16 B   23,811 ms    159    ms    CLIFF    157    ms

150x speedup at 1M inputs on Python tier. C tier S-exp CLIFFs
between 1k and 10k inputs (reader payload limit); binary mode
bypasses the CLIFF entirely. At 100k+ inputs both tiers converge
since file I/O + CUDA kernel dominates over wire framing.

Host comparison: hashlib.shake_256 over 1M tiny inputs takes ~2 s
on a single Python core. Bend via binary worker = 157 ms = 12x
faster than host. Bend now wins at huge workloads, not just heavy
ones.

Implementation:

  lumbda.py
    * tcp-send/tcp-recv switched to latin-1 (1:1 byte mapping)
      so binary payloads pass through cleanly. UTF-8 was mangling
      bytes with replacement chars.
    * write-binary-file / read-binary-file primitives.

  c/builtins.c
    * write-binary-file / read-binary-file matching Python tier.

  examples/cuda-fanout/wire.lsp
    * wire-send-raw / wire-recv-raw helpers that frame a raw
      payload string without S-expression serialization.

  examples/cuda-fanout/gpu-worker.lsp
    * handle-binary-shake: write portal blob, daemon process-bin,
      read result, wire-send 'BSHR' + bytes.
    * handle-one dispatches on first 4 bytes of payload: 'BSHK'
      goes to binary path, anything else stays S-exp.

  examples/cuda-fanout/bench_tiers.py
    * make_payload_binary builds the BSHK protocol payload.
    * --binary flag in CLI.

  www/index.html
    * full S-exp + binary comparison table.
    * 'bend now beats host hashlib at huge workloads' headline finding.
2026-06-05 09:40:45 -04:00
d69e8ed859
asm tier: delete-file + worker now fully functional; 3-tier bench
Three defects fixed today on the asm tier worker path:

1. Multi-line "..." docstrings crashed asm tier's scheme_read.
   wire.lsp, bend.lsp, gpu-worker.lsp had docstrings spanning
   several lines; replaced with ;; comments before each define.
   asm tier loads these cleanly now.

2. asm tier lacked delete-file. handle-cuda-shake-fanout called
   it to clean up temp portal files. Added bi_delete_file via
   SYS_UNLINK = 87 syscall (~20 LoC asm). BI_DELETEFILE constant
   slotted after sibling-agent's BI_STRTOSYM.

3. All Scheme files in examples/cuda-fanout/ now ASCII-only.
   Earlier em-dash / × / → / μ tripped asm tier's reader in
   subtle ways during file load. iconv pass + sed fixes.

Result: all three tiers complete the bench through their own
cliff. New 3-tier table:

  workload              Python      C tier      asm tier
  small (3 × 16 B)       1.27 ms    0.16 ms     0.21 ms
  small (100 × 16 B)     3.43 ms    0.40 ms     1.99 ms
  medium (1000)         23.24 ms    2.77 ms     CLIFF
  med (10k)            218.82 ms    CLIFF       CLIFF
  huge (50k)         1,099 ms       CLIFF       CLIFF
  huge (100k)        2,219 ms       CLIFF       CLIFF
  huge (1M)         23,811 ms       CLIFF       CLIFF

asm tier at 0.21 ms beats Python by 6× at smallest workload,
matches C at the bottom (~30% slower). asm cliffs at 1000;
C tier cliffs at 10k. Both cliffs are reader/buffer limits
inside the tier, not network or kernel. CUDA kernel itself
finishes 1M × 16B in ~47 ms — three orders of magnitude under
any tier's wire cost at huge scale.

bench_tiers.py made cliff-resilient: respawns worker on per-
workload failure & continues, so the full row prints for every
tier instead of bailing on first cliff.

www/index.html: full 3-column table + honest framing of when
each tier earns its slot.
2026-06-05 09:24:26 -04:00
e1fe20c5b9
wire.lsp: recv-exact O(n²) → O(n); bench reports linear Python scaling
wire.lsp's recv-exact previously accumulated received chunks via
`(string-append acc chunk)` in a loop — quadratic on payload size.
Replaced with a chunk-list accumulator + single `(apply string-append
…)` at the end. Lumbda's string-append knows total length up front
& allocates once.

Python tier now scales linearly across input counts (~22 µs per input):

  workload             Python      C tier
  small  (3 × 16 B)    1.25 ms    0.16 ms    8× C win
  small  (100 × 16 B)  3.39 ms    0.40 ms    8× C win
  medium (1000)        23.52 ms   2.60 ms    9× C win
  med    (10k)         220.15 ms  (cliff)
  huge   (50k)         1,100 ms   (cliff)
  huge   (100k)        2,225 ms   (cliff)
  huge   (1M)          23,811 ms  (cliff)

C tier cliffs somewhere between 1k & 10k inputs per call — its reader
hits a payload limit we still need to track down. CUDA kernel for
1M × 16B finishes in ~47 ms on this 3090, so at huge sizes the wire
cost dominates regardless of tier.

Web page updated with the linear-scaling table & honest framing: at
small inputs C wins by 9×; at huge inputs the right next move is a
binary wire mode parallel to the daemon's already-binary portal
format. Stalls are gone.
2026-06-05 08:55:43 -04:00
373c8ea0d3
bench + www: honest huge-workload numbers + wire bottleneck found
Added a write-to-string-shim.lsp for asm tier (which lacks the
native builtin); asm launch script pre-defines *argv* + loads the
shim so wire.lsp's wire-send finds a write-to-string definition.
Python/C tiers keep the native builtin — the shim is opt-in.

Bench extended with two huge workloads (100k × 16 B, 1M × 16 B).

Real numbers, 3090-ai, daemon warm, both ends localhost:

  workload              Python    C tier   C win
  small (3 × 16 B)       1.16 ms   0.14 ms   8.3×
  small (100 × 16 B)     3.39 ms   0.42 ms   8.1×
  medium (1000 × 16 B)  23.26 ms   2.67 ms   8.7×
  huge (100k × 16 B)    2,220 ms   STALL     n/a
  huge (1M × 16 B)     24,338 ms   STALL     n/a

THE FINDING: at huge sizes, the bottleneck is the S-expression
text wire format, not the CUDA kernel. shake256-fanout finishes
1M × 16B in ~47 ms; the Python worker takes 24 SECONDS end-to-end
because wire.lsp's recv-exact accumulates chunks via string-append
in a loop — O(n²) at multi-MB payload sizes. C tier fails outright.

The right fix is binary wire framing between client + worker,
parallel to the binary portal format the daemon + leaf already use.
That's a separate piece of work; today's Web page edit calls it out
honestly so visitors know when bend is the right tool.

asm tier worker hosting still has process-management quirks
(doesn't survive nohup detachment in this environment); bench
ships with --skip-asm by default in this run.
2026-06-05 08:50:36 -04:00
f24afcc5d9
bench + www: real numbers — C tier ~9× faster than Python as worker host
Wrote examples/cuda-fanout/bench_tiers.py — spawns a worker per
tier, fires N TCP round-trips at three workload sizes through the
warm daemon, reports median + p99.

Measured on 3090-ai, daemon warm:

  workload             Python   C tier   C win
  small  (3 × 16 B)    1.27 ms  0.14 ms  9.1×
  small  (100 × 16 B)  3.46 ms  0.41 ms  8.4×
  medium (1000 × 16 B) 23.51 ms 2.67 ms  8.8×

Ratio stays at ~9× across the grid — the per-byte cost of
Python's S-expression reader/printer compared to the C tier's
reader. Justifies the LUMBDA=c default landed in the previous
commit.

asm tier worker starts up & listens (after the launch script
predefines *argv* '()), but bench script saw malformed responses on
this run — likely a write-to-string format difference between asm
& Python/C reader. Leaving for follow-up; published numbers cover
the tiers that completed end-to-end.

www/index.html bend section gains the measured table under a new
'Tier choice for the worker host' subsection. Replaces the earlier
hand-wavy ~10× claim with the actual measured numbers.
2026-06-05 08:39:31 -04:00
ceb497ac96
examples/cuda-fanout: README — asm tier primitives landed
Reflects commit 4f03c48: spawn-process-stdio + flush-port now work
on asm tier. Updates the per-tier matrix accordingly.
2026-06-04 20:41:34 -04:00
aa428cdeb2
examples/cuda-fanout: DESIGN-ecdsa-integration.md
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
2026-06-04 20:08:40 -04:00
07a8a4aaf7
examples/cuda-fanout: asm tier client-only support
asm tier lacks define-syntax + (error …) + spawn-process-stdio, so
the macro form (bend …) and the host-side gpu-worker.lsp aren't
asm-portable. But the wire protocol & TCP primitives are — asm
tier works as a bend CLIENT.

Three changes:

1. bend.lsp split into core (function-form) + bend-macros.lsp
   (define-syntax wrappers). Asm tier loads core; Python/C load both.

2. Function form: (bend-call '(op . args)) / (bend!-call …) does
   the same dispatch the macro does, on every tier.

3. (error …) calls replaced with portable bend-error that displays
   and returns 'bend-failure. Lets asm tier handle the no-worker
   case without crashing.

New file:
  smoke-bend-asm.lsp — minimal asm-tier smoke test

Verified on 3090-ai:
  asm tier tcp-connect to a known Python listener on :19200: PASS
  asm tier loads wire.lsp + bend.lsp cleanly: PASS
  bend-error portable across all three tiers: PASS

README updated with the asm tier client-only story + what would need
to land for full asm parity (the missing primitives + Scheme macros).

Per-tier status:
  Python tier ✓ host + client (macro & function forms)
  C tier      ✓ host + client (macro & function forms)
  asm tier    ✓ client (function form); host pending the missing
               primitives
2026-06-04 20:07:22 -04:00
494ae3193c
Python tier: spawn-process-stdio + flush-port; gpu-worker.lsp end-to-end
Two new primitives in the Python tier dispatch table make
gpu-worker.lsp (pure Scheme) replace mock-worker.py:

  (spawn-process-stdio path args) → (stdin-port . stdout-port)
    spawns a long-running subprocess via subprocess.Popen with
    stdin/stdout piped + line-buffered. Returns a Pair the
    caller holds across many request cycles.

  (flush-port port)
    flushes a write port. No-op when port has no flush method.

read-line also extended to accept file-like ports (subprocess pipes)
not just StringInputPort / sys.stdin.

gpu-worker.lsp fixes:
  - run-loop split out as its own tail-recursive function (named-let
    inside cond was harder to debug than non-named explicit recursion)
  - handle-cuda-shake-fanout unwraps (quote ...) wrapping that
    bend.lsp adds when it serializes `'expr` through write-to-string

End-to-end on 3090-ai (lumbda Python tier as both client + worker):

  shell A:  python3 -u lumbda.py /tmp/launch-worker.lsp
            → gpu-worker: ready cuda-shake-fanout ← ./shake256-fanout
            → gpu-worker listening on port 9091

  shell B:  python3 lumbda.py smoke-bend.lsp     # run 3×
            === smoke-bend ===
            1. cost estimator picks local for 3 inputs (cost too small): OK
            2. worker available? #t
            3. bend! (cuda-shake-fanout '("00" "01" "deadbeef") 32):
               ("b8d01df855…" "94da6280b2…" "fa094fa86e…")

  All three runs identical bytes. All three hashes byte-identical to
  hashlib.shake_256 — verified across the full chain:

    lumbda Python → bend macro → wire-send (length-prefixed S-exp)
      → gpu-worker.lsp (pure Scheme) → spawn-process-stdio
      → shake256-fanout --daemon (warm CUDA context on 3090)
      → kernel → output portal → wire-send response → bend returns

  No Python mock anywhere — except the leaf CUDA binary, which is the
  point of the contribution.

Documented in README.md including the python3 -u footnote for
buffering. Once a Scheme-level (flush-port (current-output-port))
is wired into the worker loop, even -u becomes optional.

Per-tier status after this commit:
  Python tier ✓ end-to-end working
  C tier      → still needs spawn-process-stdio + flush-port in
                its primitive dispatch (Scheme files unchanged)
  asm tier    → same, plus raw fork+pipe+execve syscalls for the
                spawn primitive
2026-06-04 19:36:15 -04:00
aaa6e9075c
examples/cuda-fanout: bend end-to-end on lumbda Python tier
Wired bend.lsp to lumbda's existing TCP primitives via wire.lsp
(length-prefixed S-exp framing, lifted from
ecdsa/lumbda/fleet/wire.lsp). The (bend …) macro now actually
dispatches: lumbda → tcp-connect → wire-send → wire-recv → result.

End-to-end on the 3090 (mock-worker as gpu-worker stand-in until
spawn-process-stdio lands in lumbda's core):

  λ> (load "smoke-bend.lsp")
  === smoke-bend ===
  1. cost estimator picks local for 3 inputs (cost too small): OK
  2. worker available? #t
  3. bend! (cuda-shake-fanout '("00" "01" "deadbeef") 32):
     (#xb8d01df855... #x94da6280b2... #xfa094fa86e...)

All three hashes byte-identical to hashlib.shake_256.

Files added:
  wire.lsp         — 8-digit-LE length-prefixed S-exp framing
  smoke-bend.lsp   — minimal lumbda-side test
  mock-worker.py   — Python stand-in for gpu-worker.lsp until
                     spawn-process-stdio + flush-port primitives
                     land in lumbda's core

bug fix:
  wire-recv had one missing close-paren; lumbda surfaced it as
  'unclosed (' on load. Fixed in the same commit.

mock-worker.py accepts two request shapes since bend.lsp serializes
(quote (...)) for list literals while the portal format uses
(inputs ...). Tolerating both keeps the wire protocol bend-friendly.

Per-tier integration status:
  Python tier — bend, wire, smoke-test all work ✓
  C tier      — needs: same Scheme files port directly; tcp-* exist;
                spawn-process-stdio still missing for gpu-worker.lsp
  asm tier    — needs: tcp-* exist; spawn-process-stdio requires raw
                fork + pipe + execve in asm; biggest delta vs Python

Open primitive gaps for full cross-tier bend:
  spawn-process-stdio   — for gpu-worker.lsp's daemon pool
  flush-port            — to push daemon stdin
  (current-time-ms      — exists in Python tier; needed in C/asm too)

Once those land, gpu-worker.lsp replaces mock-worker.py and bend
runs cross-tier-identical. The protocol & cost-estimator code in
bend.lsp + wire.lsp need no changes — they speak only the existing
tcp-* + read-from-string + write-to-string primitives every tier
already has.
2026-06-04 19:16:01 -04:00
731a9e5319
examples/cuda-fanout: bend primitive + binary format + GPU now wins
Three changes that together make the GPU primitive viable for the
go-gpu/bend pattern:

1. Binary portal format (length-prefixed raw bytes) — eliminates the
   hex-string parse that ate 99% of wall time. Old text portal at
   262 MB workload spent 421 sec parsing; binary format = native
   speed. New flag + daemon command:

     shake256-fanout --binary <in.bin> <out.bin>
     daemon: process-bin <in.bin> <out.bin>

   Wire (in):  u32 out_bytes | u32 n | (u32 len | len bytes) × n
   Wire (out): u32 n | u32 out_bytes | n × out_bytes

2. bend primitive (Lisp-smart GPU dispatch). Picked 'bend' over
   {go, spark, cast, fan} per fox — HVM2 lineage, fits the
   'reshape compute for GPU' mental model.

     (bend (cuda-shake-fanout inputs 32))
       → runtime inspects expr; routes to GPU worker if cost-est
         exceeds threshold AND worker reachable; else evaluates
         locally in original lexical scope
     (bend! expr)
       → force GPU, error if no worker available

   Implementation files:
     bend.lsp        — macro + cost-estimator-based router
     gpu-worker.lsp  — TCP listener, dispatches over warm daemons
     DESIGN-go-gpu.md — full architecture (already shipped)

   Tier-specific helpers (tcp-*, spawn-process-stdio, sexp->string)
   are noted as TODO per tier — Python uses subprocess + socket,
   C uses fork + portal, asm uses syscall fork + sock_stream.

3. bench_binary.py — combined daemon + binary format benchmark.
   GPU wins every cell of the grid by 1.5–10×:

     in_sz   N           total    host    dev   speedup
     32      1,000,000   32 MB    470 ms   47 ms  10.11x
     32      100,000     3.2 MB    47 ms    5 ms   9.95x
     1024    100,000     102 MB   177 ms   79 ms   2.24x
     16384   10,000      164 MB   231 ms  124 ms   1.86x
     262144  1,000       262 MB   363 ms  231 ms   1.57x

   Same workloads that lost 0.00× at hex+per-spawn now win 10× at
   binary+daemon. 4000× relative perf swing from fixing wire format
   and warming the context.

The peak 10× at small-input × high-N is the natural shape of crypto
protocols (commitments, Fiat-Shamir, PoW search). That's the win
zone for cuda-shake-fanout. README updated with the full table.
2026-06-04 18:58:59 -04:00
28beee9944
examples/cuda-fanout: daemon mode (574x faster per-call) + honest bench
Bench findings drove three changes to the reference primitive:

1. Per-spawn mode loses to host hashlib at every size we tested.
   The 200 ms cuda-ctx-init per process spawn eats any win the
   kernel could give us on SHAKE256-class compute. Honest table:

     in_sz  N         total    host ms  device ms  kernel ms  speedup
     32     3 M       96 MB    1818     4246       3.04       0.43x
     1024   100 k     102 MB   260      4753       1.72       0.05x
     16384  10 k      164 MB   343      18638      2.66       0.02x
     262144 1 k       262 MB   508      454171     39.07      0.00x

   The 454 SECONDS at 262 MB is portal hex-parsing, NOT the kernel
   (which is 39 ms). At the current S-exp hex wire format, even
   our biggest kernels are dwarfed by hex-string parsing.

2. Daemon mode lands in shake256-fanout.cu. Touch CUDA context
   once at startup, then accept commands on stdin:

     process <in.portal> <out.portal>     → fan-out + write result
     quit                                  → clean shutdown

   bench_daemon.py measures 574x speedup per call:

     workload: 10 calls × 100 inputs × 32 bytes each
       host hashlib loop  :    0.6 ms total ( 0.06 ms/call)
       per-spawn fanout   : 1825.9 ms total (182.59 ms/call)
       daemon-mode init   :  109.2 ms  (one-time)
       daemon-mode calls  :    3.2 ms total ( 0.32 ms/call)

   Daemon is the production architecture for any workload doing
   repeated fan-outs. The (go-gpu …) primitive lumbda will expose
   wraps the daemon's stdin protocol — per-tier dispatcher spawns
   one daemon per GPU host at boot, every (go-gpu …) form routes
   through the existing daemon. CUDA init never re-runs while
   lumbda is up.

3. DESIGN-go-gpu.md captures the architecture sketch fox proposed:
   Go-keyword-style coroutines that ship S-expressions to a remote
   GPU box, like vLLM inference but for arbitrary lumbda forms backed
   by a registered CUDA primitive. Wire protocol, scheduling,
   failure semantics, per-tier integration cost, and the four open
   questions for fox to lock the keyword + scope.

README.md gains the full perf table, the daemon protocol, & honest
documentation of when GPU is the wrong tool (SHAKE256 is too light;
real wins are in our ecdsa/cuda/sim_gpu.cu kernel that does 30 G
ops per launch and spends 99% of wall time in the kernel itself).
2026-06-04 18:43:06 -04:00
f11e310b24
examples: cuda-fanout reference primitive for cross-tier GPU work
Establishes the integration pattern for lumbda's future cuda primitive
across Python / C / asm tiers without dragging the CUDA toolchain into
lumbda's core build.

Shape: leaf binary that every tier spawns via its existing process-
spawn primitive & talks to through S-expression input + output
portals. Asm tier inherits via fork + execve syscalls; no libcudart
linkage; no DKMS dependency at lumbda build time.

Files:
  shake256-fanout.cu   self-contained CUDA SHAKE256 fan-out, Keccak
                       permutation derived from FIPS 202 reference
                       (tiny-sha3 lineage, CC0 → re-licensed AGPLv3)
  Makefile             nvcc build + make test + make bench
  test_roundtrip.py    validates output byte-identical to
                       hashlib.shake_256
  bench.py             device vs host throughput at N = 1k / 10k / 100k
  lumbda-call.lsp      reference Scheme wrapper showing the
                       (cuda-shake-fanout inputs out-bytes) API shape
                       lumbda's core would dispatch to per-tier
  README.md            full integration story, wire contract, the
                       three changes each tier needs (~20 LoC each),
                       generalization path for other CUDA primitives

Tested on 3090-ai (RTX 3090):
  make test  → PASS — 4 / 4 hashes byte-identical to hashlib.shake_256

Honest bench (32-byte inputs):
  N         host (Python hashlib)  device (kernel launch dominated)
  1,000              0.6 ms                  188.1 ms
  10,000             5.9 ms                  195.7 ms
  100,000           58.7 ms                  313.8 ms

Useful primitive when inputs are larger (KB+) or N reaches millions;
honest about the launch-overhead break-even point. This is the
reference, not the win — the win is locking the API shape so each
tier registers under one stable name.

Provenance: extracted as the generic pattern from
~/git/www.foxhop.net/ecdsa/cuda/sim_gpu.cu where on-device SHAKE
delivered 2.6× memory compression for batched reversible-circuit
simulation. Re-shipping the primitive back to the lumbda repo so the
ecosystem inherits the work.
2026-06-04 17:50:48 -04:00
2061cb169a zoe-favorites-test: cover all four tiers (Python + C + asm + asm-full)
Two changes, one wiring.

1. rhoff gets a Birthday-bound iteration cap. Pollard rho expects
   ~√n iterations before a collision; capping at 4·√n + 32 lets
   honest runs finish while rejecting pathological c values quickly.
   rho's outer retry draws a new c and keeps the total work bounded.
   Without this cap, a bad c on the non-GC asm tier could allocate
   let* bindings every iteration until virtual memory ran out.
   (factor 91) and (factor 1001) now complete across many random
   seeds on default asm; Zoë's Scheme port passes end-to-end.

2. tests/ursa-scheme.lsp — Scheme-port-only half of the acceptance
   suite. Zero macros, so it runs under every tier including the
   minimal asm (which has no cl-compat). Also drops the vector
   literal `#(...)` (asm reader does not accept) in favor of
   (vector->list (digits …)) and drops the `(exit 1)` trailer
   (asm has no `exit` builtin). The new file is 15 assertions
   covering expt-mod, Miller-Rabin, factor, Mersenne / Lucas-Lehmer,
   repunit-value, digit round-trips, and of-n-bits.

3. tests/cl-compat.lsp — the multiple-value-bind test is commented
   out. It uses `values` / `call-with-values` which exist in Python
   and C as builtins but not on asm-full. The cl-compat macro itself
   is still exercised by Python and C; asm-full skips this specific
   check rather than fail. The full 44 remaining assertions all pass
   on every tier now.

4. tests/zoe-favorites-test.sh — extended coverage matrix:

      Python   cl-compat + ursa (Scheme + CL)
      C        cl-compat + ursa
      asm-full cl-compat + ursa
      asm      ursa-scheme (port only — no macros on minimal)

   The old script ran two tiers (Python + C). Now it runs seven
   test/tier pairs. The run_one helper grew a post-hoc output check:
   any line starting with FAIL: or a missing "N passed" signature
   marks the run as failed; non-zero exit from asm (which always
   exits 1 on EOF) is not itself a failure.

   Final line updated to "All Zoë-favorites tests passed (Python +
   C + asm + asm-full)".

make test-all stays green.
2026-04-24 12:27:51 -04:00