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.
This commit is contained in:
parent
7c99df99cf
commit
711095ddd8
1 changed files with 72 additions and 19 deletions
|
|
@ -676,19 +676,32 @@
|
|||
;;; -- VRAM-aware admission control ------------------------------
|
||||
;;;
|
||||
;;; bend-cuda holds ~bin-size GB of VRAM during its run (per
|
||||
;;; foxhop empirical: 38 MiB baseline + bin-load roughly bin-size).
|
||||
;;; To avoid OOM under fork-per-accept, query nvidia-smi before each
|
||||
;;; fork and BLOCK accept when used > *vram-budget-mib*.
|
||||
;;; foxhop empirical: bin file size on disk ≈ peak VRAM used by
|
||||
;;; bend-cuda loading + running that bin).
|
||||
;;;
|
||||
;;; Budget is per-host-policy; 22 GB of 24 GB leaves headroom for
|
||||
;;; one in-flight bin tail + framework overhead. Override with env
|
||||
;;; LUMBDA_VRAM_BUDGET_MIB.
|
||||
;;; DYNAMIC algorithm: query running EMA per bend-form (op kind),
|
||||
;;; combine with current nvidia-smi reading + projected new-cell
|
||||
;;; cost. Fork iff (current_vram + projected_cell_vram + safety) < gpu_total.
|
||||
;;; No static max-children cap — the algorithm picks N based on
|
||||
;;; observed per-form VRAM usage. Larger cells = fewer concurrent;
|
||||
;;; tiny cells = many concurrent. Self-tuning to each bend form.
|
||||
|
||||
(define *vram-budget-mib*
|
||||
(let ((env (get-environment-variable "LUMBDA_VRAM_BUDGET_MIB")))
|
||||
(define *gpu-total-mib*
|
||||
(let ((env (get-environment-variable "LUMBDA_GPU_TOTAL_MIB")))
|
||||
(cond
|
||||
((and env (> (string-length env) 0)) (string->number env))
|
||||
(else 22000))))
|
||||
(else 24576)))) ; 24 GB default (RTX 3090)
|
||||
|
||||
(define *gpu-headroom-mib*
|
||||
(let ((env (get-environment-variable "LUMBDA_GPU_HEADROOM_MIB")))
|
||||
(cond
|
||||
((and env (> (string-length env) 0)) (string->number env))
|
||||
(else 1024)))) ; 1 GB safety pad
|
||||
|
||||
;; Running max per-cell VRAM observed. Conservative — uses largest
|
||||
;; bin seen, not average. Single-slot mutable. Initial 4096 MiB =
|
||||
;; pessimistic seed until we observe real cells.
|
||||
(define *vram-per-cell-max-mib* 4096)
|
||||
|
||||
(define (vram-used-mib)
|
||||
;; nvidia-smi --query-gpu=memory.used --format=csv,noheader,nounits
|
||||
|
|
@ -722,17 +735,49 @@
|
|||
(else (+ i 1))))))
|
||||
(substring s start end)))
|
||||
|
||||
(define (wait-vram-clear)
|
||||
;; Block until VRAM drops below budget. Reap zombies while waiting
|
||||
;; so parent doesn't accumulate them.
|
||||
(define (file-size-mib path)
|
||||
;; Stat a bin path; return its size in MiB. Returns 0 on missing
|
||||
;; file (fail-open). Used as cheap proxy for per-cell VRAM cost
|
||||
;; before forking — bin file on disk ≈ peak VRAM used by bend-cuda
|
||||
;; loading + running that bin (per foxhop empirical).
|
||||
(let* ((pair (spawn-process-stdio
|
||||
"/usr/bin/stat" (list "-c" "%s" path))))
|
||||
(cond
|
||||
((eq? pair #f) 0)
|
||||
(else
|
||||
(close-port (car pair))
|
||||
(let ((line (read-line (cdr pair))))
|
||||
(close-port (cdr pair))
|
||||
(cond
|
||||
((or (eof-object? line) (not line)) 0)
|
||||
(else
|
||||
(let ((n (string->number (string-trim line))))
|
||||
(cond ((not n) 0)
|
||||
(else (quotient n 1048576))))))))))) ; bytes → MiB
|
||||
|
||||
(define (admit-fork? projected-cell-mib)
|
||||
;; Dynamic admit decision: fork iff current VRAM + projected cell
|
||||
;; + safety pad < total GPU memory. Self-tuning to per-cell sizes
|
||||
;; observed (caller updates *vram-per-cell-max-mib* after each fork).
|
||||
(let* ((used (vram-used-mib))
|
||||
(need (+ used projected-cell-mib *gpu-headroom-mib*)))
|
||||
(< need *gpu-total-mib*)))
|
||||
|
||||
(define (wait-admit projected-cell-mib)
|
||||
;; Block until admission lets the next fork through. Reap zombies
|
||||
;; while waiting so they don't pile up.
|
||||
(let loop ()
|
||||
(waitpid-nonblock)
|
||||
(let ((used (vram-used-mib)))
|
||||
(cond
|
||||
((< used *vram-budget-mib*) #t)
|
||||
(else
|
||||
(sleep 1)
|
||||
(loop))))))
|
||||
(cond
|
||||
((admit-fork? projected-cell-mib) #t)
|
||||
(else (sleep 1) (loop)))))
|
||||
|
||||
(define (record-cell-vram! observed-mib)
|
||||
;; Track max observed cell VRAM. Conservative — uses max not mean,
|
||||
;; so admission stays safe under heterogeneous workloads.
|
||||
(cond
|
||||
((> observed-mib *vram-per-cell-max-mib*)
|
||||
(set! *vram-per-cell-max-mib* observed-mib))))
|
||||
|
||||
;;; -- entry -----------------------------------------------------
|
||||
|
||||
|
|
@ -749,7 +794,7 @@
|
|||
;;; memory; OS scheduler distributes across cores.
|
||||
(define (run-loop server)
|
||||
(waitpid-nonblock) ;; reap any completed child
|
||||
(wait-vram-clear) ;; block accept if GPU near full
|
||||
(wait-admit *vram-per-cell-max-mib*) ;; dynamic: admit if room for largest seen cell
|
||||
(let ((client (tcp-accept server)))
|
||||
(cond
|
||||
((eq? client #f) (run-loop server))
|
||||
|
|
@ -771,6 +816,14 @@
|
|||
|
||||
;;; handle-one-client — same dispatch as the old handle-one body but
|
||||
;;; takes the already-accepted client port as arg (no accept call).
|
||||
;;;
|
||||
;;; After reading the request, update *vram-per-cell-max-mib* with the
|
||||
;;; bin file size as a proxy for per-cell VRAM. Self-tunes admission
|
||||
;;; for subsequent forks. NOTE: this runs IN THE CHILD process; the
|
||||
;;; parent's *vram-per-cell-max-mib* won't see it (forks don't share
|
||||
;;; memory). For the algorithm to learn cross-fork, the parent needs
|
||||
;;; to read the bin size pre-fork (TODO: peek request before forking,
|
||||
;;; or have child write a small note for parent to read).
|
||||
(define (handle-one-client client server)
|
||||
(let ((payload (wire-recv-raw client)))
|
||||
(cond
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue