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:
parent
a88cebced0
commit
8edb752382
3 changed files with 89 additions and 38 deletions
|
|
@ -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 --------------------------------------
|
||||
;;;
|
||||
|
|
|
|||
|
|
@ -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)")
|
||||
|
|
|
|||
|
|
@ -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)")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue