bend demo: pretty-format the 100M secp result instead of raw S-exp dump

Fox said 'doesn't dazzle me' on the previous run. The math is real
(160x GPU win, ~33 minutes of CPU compute in 13 seconds of GPU
kernel) but the output panel showed a wall of S-expression atoms
that buries the headline.

Worker now builds a multi-line summary string inside the response
under a new (summary "...") field:

  ✦ GPU just batched 100,000,000 secp256k1 public-key computations.

    scalar gen      : 5130 ms (random.urandom)
    GPU kernel      : 13028 ms (RTX 3090, --window-w 4)
    GPU throughput  : 7.67 million keys / second

    CPU baseline ref: 0.050 Mkeys/s (libsecp256k1 single core)
    CPU would need  : 33.3 minutes (2000 sec)
    GPU finished in : 13.0 sec
    speedup         : ~153× faster on GPU

Two new portable helpers — with-commas (recursive thousand
separator) and format-float (truncate decimals + drop trailing
dot when n=0) — work on all three tiers because they only use
string-append, substring, char=?, and number->string.

Demo now reads the response with read-from-string, assoc-extracts
the summary string, and displays it raw. Same source compiles
identically under asm / c / python. Existing structured fields
(n, gen-ms, gpu-ms, etc.) stay untouched for callers who want
the numbers as data.
This commit is contained in:
russell@unturf.com 2026-06-14 20:11:38 -04:00
parent a88cebced0
commit 8edb752382
No known key found for this signature in database
3 changed files with 89 additions and 38 deletions

View file

@ -426,8 +426,24 @@
(cpu-est-sec (cond ((> cpu-rate 0)
(/ count cpu-rate 1000000.0))
(else 0)))
(cpu-est-min (/ cpu-est-sec 60.0))
(speedup (cond ((> gpu-rate 0) (/ gpu-rate cpu-rate))
(else 0))))
(else 0)))
(summary
(string-append
"✦ GPU just batched " (with-commas count)
" secp256k1 public-key computations.\n"
"\n"
" scalar gen : " (number->string gen-ms) " ms (random.urandom)\n"
" GPU kernel : " (number->string gpu-ms) " ms (RTX 3090, --window-w 4)\n"
" GPU throughput : " (format-float gpu-rate 2) " million keys / second\n"
"\n"
" CPU baseline ref: " (format-float cpu-rate 3)
" Mkeys/s (libsecp256k1 single core)\n"
" CPU would need : " (format-float cpu-est-min 1) " minutes ("
(format-float cpu-est-sec 0) " sec)\n"
" GPU finished in : " (format-float gpu-sec 1) " sec\n"
" speedup : ~" (format-float speedup 0) "× faster on GPU")))
(if (file-exists? in-path) (delete-file in-path))
(if (file-exists? out-path) (delete-file out-path))
(display ";;; bend DONE cuda-secp256k1-bench n=")
@ -441,7 +457,40 @@
(list 'gpu-mkeys-per-sec gpu-rate)
(list 'cpu-rate-mkeys-per-sec cpu-rate)
(list 'cpu-est-sec cpu-est-sec)
(list 'speedup-est speedup)))))))))))))
(list 'speedup-est speedup)
(list 'summary summary)))))))))))))
;; Format a non-negative integer with thousand-separator commas.
;; (with-commas 100000000) → "100,000,000".
(define (with-commas n)
(let ((s (number->string n)))
(cond
((< (string-length s) 4) s)
(else
(let ((head-len (- (string-length s) 3)))
(string-append
(with-commas (string->number (substring s 0 head-len)))
","
(substring s head-len (string-length s))))))))
;; Truncate a number's string form to at most n decimal places. Lumbda's
;; rational division of two ints returns an exact rational ("7/3") which
;; doesn't survive truncation, so we coerce to float first by adding 0.0
;; before taking number->string. n=0 drops the decimal point too.
(define (format-float x n)
(let ((s (number->string (+ x 0.0))))
(let loop ((i 0))
(cond
((>= i (string-length s)) s)
((char=? (string-ref s i) #\.)
(cond
((= n 0) (substring s 0 i))
(else
(let ((cap (+ i 1 n)))
(cond
((>= cap (string-length s)) s)
(else (substring s 0 cap)))))))
(else (loop (+ i 1)))))))
;;; -- op handler -- health --------------------------------------
;;;

View file

@ -4,18 +4,17 @@
; above first. The playground guards the Run button so a customer
; machine never burns minutes on the local tiers.
;
; Today's headline payload: 100,000,000 secp256k1 scalar*G operations
; in one HTTP POST. On a 3090 the daemon finishes in ~13 seconds;
; Headline: 100,000,000 secp256k1 scalar*G operations dispatched in
; a single ~32-byte HTTP POST. The 3.2 GB of random scalars never
; crosses the wire — the worker synthesizes them from /dev/urandom,
; runs cuda-secp256k1-batched-mul, and ships a small formatted
; summary back. On a 3090 the daemon finishes the math in ~13 s;
; on a single CPU core (libsecp256k1 ~50K keys/s) the same workload
; would take ~33 minutes. That's a ~160× speedup — the kind of math
; only a GPU can do in a reasonable amount of time.
; would take more than half an hour.
;
; The request is ~32 bytes of S-expression. The 3.2 GB of random
; scalars never crosses the wire — the worker generates them locally
; from /dev/urandom via examples/cuda-fanout/generate-bscp.py, calls
; into the existing cuda-secp256k1-batched-mul daemon, and returns
; just the timing summary. You see the GPU work happen in your
; output panel; your laptop never does the math.
; The output panel shows the formatted result the worker built for
; us. read-from-string + assoc are portable across all three tiers
; so this same source file runs identically under asm / c / python.
(display ";; 1. probe round-trip ...") (newline)
(define r1 (bend!-call "(ping)"))
@ -27,13 +26,15 @@
(display " (health) → ") (display r2) (newline)
(newline)
(display ";; 3. 100,000,000 secp256k1 scalar*G batch on the GPU") (newline)
(display ";; ~13 s on 3090 vs ~33 min on a CPU core (~160x)") (newline)
(display ";; sit tight — this is real compute, not a mock") (newline)
(define heavy "(cuda-secp256k1-bench 100000000)")
(display " payload: ") (display heavy) (newline)
(define r3 (bend!-call heavy))
(display " result : ") (display r3) (newline)
(display ";; 3. heavy GPU compute ...") (newline)
(display ";; sit tight — this is real math, not a mock") (newline)
(newline)
(define r3 (bend!-call "(cuda-secp256k1-bench 100000000)"))
(define parsed (read-from-string r3))
(define summary-pair (assoc 'summary (cdr parsed)))
(cond
((pair? summary-pair) (display (car (cdr summary-pair))) (newline))
(else (display "raw response: ") (display r3) (newline)))
(newline)
(print "done — that math would have taken ~33 minutes on this CPU.")
(print "(your laptop never did the math — it just watched it complete)")

View file

@ -4,18 +4,17 @@
; above first. The playground guards the Run button so a customer
; machine never burns minutes on the local tiers.
;
; Today's headline payload: 100,000,000 secp256k1 scalar*G operations
; in one HTTP POST. On a 3090 the daemon finishes in ~13 seconds;
; Headline: 100,000,000 secp256k1 scalar*G operations dispatched in
; a single ~32-byte HTTP POST. The 3.2 GB of random scalars never
; crosses the wire — the worker synthesizes them from /dev/urandom,
; runs cuda-secp256k1-batched-mul, and ships a small formatted
; summary back. On a 3090 the daemon finishes the math in ~13 s;
; on a single CPU core (libsecp256k1 ~50K keys/s) the same workload
; would take ~33 minutes. That's a ~160× speedup — the kind of math
; only a GPU can do in a reasonable amount of time.
; would take more than half an hour.
;
; The request is ~32 bytes of S-expression. The 3.2 GB of random
; scalars never crosses the wire — the worker generates them locally
; from /dev/urandom via examples/cuda-fanout/generate-bscp.py, calls
; into the existing cuda-secp256k1-batched-mul daemon, and returns
; just the timing summary. You see the GPU work happen in your
; output panel; your laptop never does the math.
; The output panel shows the formatted result the worker built for
; us. read-from-string + assoc are portable across all three tiers
; so this same source file runs identically under asm / c / python.
(display ";; 1. probe round-trip ...") (newline)
(define r1 (bend!-call "(ping)"))
@ -27,13 +26,15 @@
(display " (health) → ") (display r2) (newline)
(newline)
(display ";; 3. 100,000,000 secp256k1 scalar*G batch on the GPU") (newline)
(display ";; ~13 s on 3090 vs ~33 min on a CPU core (~160x)") (newline)
(display ";; sit tight — this is real compute, not a mock") (newline)
(define heavy "(cuda-secp256k1-bench 100000000)")
(display " payload: ") (display heavy) (newline)
(define r3 (bend!-call heavy))
(display " result : ") (display r3) (newline)
(display ";; 3. heavy GPU compute ...") (newline)
(display ";; sit tight — this is real math, not a mock") (newline)
(newline)
(define r3 (bend!-call "(cuda-secp256k1-bench 100000000)"))
(define parsed (read-from-string r3))
(define summary-pair (assoc 'summary (cdr parsed)))
(cond
((pair? summary-pair) (display (car (cdr summary-pair))) (newline))
(else (display "raw response: ") (display r3) (newline)))
(newline)
(print "done — that math would have taken ~33 minutes on this CPU.")
(print "(your laptop never did the math — it just watched it complete)")