diff --git a/examples/cuda-fanout/generate-bscp.py b/examples/cuda-fanout/generate-bscp.py new file mode 100644 index 0000000..c0be537 --- /dev/null +++ b/examples/cuda-fanout/generate-bscp.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python3 +"""generate-bscp.py — emit a BSCP secp256k1 batched scalar*G request file. + +Usage: generate-bscp.py COUNT OUT_PATH + +Writes a BSCP-format binary suitable for secp256k1-batch-mul --binary or +its --daemon process-bin command. Scalars come from /dev/urandom (fast, +hundreds of MB/s); a few may be zero or >= curve order, but the worker +just produces garbage points for those — fine for a bench/demo. + +Used by gpu-worker.lsp's handle-cuda-secp256k1-bench to construct big +input payloads server-side so HTTP callers can trigger 10M-scale GPU +work without uploading the 320 MB scalar list. + +BSCP wire (from secp256k1-batch-mul.cu header): + "BSCP" 4 B magic + u32 op_id 0x01 = scalar*base + u32 n number of scalars + u8[32] base_x base point X, big-endian (G) + u8[32] base_y base point Y, big-endian (G) + u8[n*32] scalars big-endian 256-bit ints +""" + +import os +import struct +import sys + +# secp256k1 generator G — official curve parameters. +GX = bytes.fromhex( + "79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798" +) +GY = bytes.fromhex( + "483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8" +) + +CHUNK_SCALARS = 32 * 1024 # 1 MB per write + + +def main(): + if len(sys.argv) != 3: + sys.stderr.write(__doc__) + return 1 + count = int(sys.argv[1]) + out_path = sys.argv[2] + if count <= 0: + sys.stderr.write(f"count must be positive (got {count})\n") + return 1 + + with open(out_path, "wb") as f: + f.write(b"BSCP") + f.write(struct.pack(") on failure. + (cond + ((not (file-exists? "/usr/bin/python3")) + (list 'error 'python3-missing)) + ((not (file-exists? *bscp-generator-path*)) + (list 'error (list 'generator-script-missing *bscp-generator-path*))) + (else + (let ((pair (spawn-process-stdio + "/usr/bin/python3" + (list *bscp-generator-path* + (number->string count) + out-path)))) + (cond + ((eq? pair #f) (list 'error 'spawn-failed)) + (else + (close-port (car pair)) + (drain-to-eof (cdr pair)) + (close-port (cdr pair)) + (cond + ((file-exists? out-path) 'ok) + (else (list 'error 'no-output))))))))) + +(define (handle-cuda-secp256k1-bench args) + (let ((d (daemon-or-error 'cuda-secp256k1-batched-mul))) + (cond + ((and (pair? d) (eq? (car d) 'error)) d) + (else + (let* ((daemon d) + (count (car args)) + (in-path (gensym-path "/tmp/bend-secp-bench-in" ".bscp")) + (out-path (gensym-path "/tmp/bend-secp-bench-out" ".bscr")) + (t-gen-start (current-time-ms)) + (gen-status (generate-bscp-file count in-path)) + (gen-ms (- (current-time-ms) t-gen-start))) + (cond + ((not (eq? gen-status 'ok)) + (if (file-exists? in-path) (delete-file in-path)) + (list 'error (list 'bscp-generate-failed gen-status))) + (else + (display ";;; bend RECV cuda-secp256k1-bench n=") + (display count) + (display " gen-ms=") (display gen-ms) (newline) + (let* ((t-gpu-start (current-time-ms)) + (status (daemon-process daemon in-path out-path #t)) + (gpu-ms (- (current-time-ms) t-gpu-start))) + (cond + ((not (eq? status 'ok)) + (if (file-exists? in-path) (delete-file in-path)) + (if (file-exists? out-path) (delete-file out-path)) + (list 'error (list 'daemon-failed (cdr status)))) + (else + (let* ((sample (read-binary-file-prefix out-path 76)) ; 4+4+4 hdr + 64 first pt + (sample-hex (sample-x-hex sample)) + (gpu-sec (/ gpu-ms 1000.0)) + (gpu-rate (cond ((> gpu-sec 0) (/ count gpu-sec 1000000.0)) + (else 0))) + (cpu-rate *cpu-secp256k1-rate-mkeys-per-sec*) + (cpu-est-sec (cond ((> cpu-rate 0) + (/ count cpu-rate 1000000.0)) + (else 0))) + (speedup (cond ((> gpu-rate 0) (/ gpu-rate cpu-rate)) + (else 0)))) + (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=") + (display count) + (display " gpu-ms=") (display gpu-ms) + (display " mkeys-s=") (display gpu-rate) (newline) + (list 'ok + (list 'n count) + (list 'gen-ms gen-ms) + (list 'gpu-ms gpu-ms) + (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 'sample-x sample-hex))))))))))))) + +;; read first N bytes of file as a binary string. Used to peek at the +;; BSCR header + first output point without slurping the whole result +;; file (which is 64 * count bytes for large counts). +(define (read-binary-file-prefix path n) + (let* ((port (open-input-file path)) + (acc (read-string-bytes port n))) + (close-port port) + acc)) + +;; read up to n bytes from input port — falls back through read-char +;; on tiers where bulk read isn't a primitive. Returns a string of +;; whatever was available (may be shorter than n at EOF). +(define (read-string-bytes port n) + (let loop ((i 0) (acc '())) + (cond + ((>= i n) (apply string-append (reverse acc))) + (else + (let ((c (read-char port))) + (cond + ((eof-object? c) (apply string-append (reverse acc))) + (else (loop (+ i 1) (cons (string c) acc))))))))) + +;; Extract the X coordinate of the first output point from a BSCR +;; prefix as a 64-char hex string. BSCR layout: "BSCR"(4) status(4) +;; n(4) point0_x(32) point0_y(32) ... — so x bytes are at offset 12. +;; Returns "(unavailable)" if the prefix is too short. +(define (sample-x-hex bytes) + (cond + ((< (string-length bytes) 44) "(unavailable)") + (else + (let loop ((i 12) (end 44) (acc '())) + (cond + ((>= i end) (apply string-append (reverse acc))) + (else + (let* ((b (char->integer (string-ref bytes i))) + (hi (quotient b 16)) + (lo (remainder b 16))) + (loop (+ i 1) end + (cons (string (hex-digit hi) (hex-digit lo)) acc))))))))) + +(define (hex-digit n) + (cond + ((< n 10) (integer->char (+ n (char->integer #\0)))) + (else (integer->char (+ (- n 10) (char->integer #\a)))))) + ;;; -- op handler -- health -------------------------------------- ;;; ;;; (health) returns @@ -528,6 +677,7 @@ (cond ((eq? op 'cuda-shake-fanout) (handle-cuda-shake-fanout args)) ((eq? op 'cuda-sim-ops-bin) (handle-cuda-sim-ops-bin args)) + ((eq? op 'cuda-secp256k1-bench) (handle-cuda-secp256k1-bench args)) ((eq? op 'health) (handle-health)) ((eq? op 'ping) (list 'ok 'pong)) (else (list 'error (list 'unknown-op op))))))))