Game detection: foreign GPU holders take an external lock hold (GAME_PROCS, GPU_FOREIGN_VRAM_MB)
This commit is contained in:
+43
-8
@@ -1,6 +1,8 @@
|
||||
// Package lock implements the two-mode GPU arbitration lock: any number of
|
||||
// concurrent LLM requests ("readers") or exactly one image job ("writer"),
|
||||
// with image jobs taking priority over newly arriving LLM requests.
|
||||
// with image jobs taking priority over newly arriving LLM requests. An
|
||||
// external hold (SetExternal) blocks new grants of both kinds while a
|
||||
// foreign process — e.g. a game — holds the GPU; in-flight work drains.
|
||||
package lock
|
||||
|
||||
import (
|
||||
@@ -13,9 +15,10 @@ import (
|
||||
type State string
|
||||
|
||||
const (
|
||||
StateIdle State = "idle"
|
||||
StateLLM State = "llm"
|
||||
StateImage State = "image"
|
||||
StateIdle State = "idle"
|
||||
StateLLM State = "llm"
|
||||
StateImage State = "image"
|
||||
StateExternal State = "external"
|
||||
)
|
||||
|
||||
type imageWaiter struct{ id uint64 }
|
||||
@@ -30,6 +33,7 @@ type Lock struct {
|
||||
imageActive bool // an image job holds the GPU
|
||||
imageQ []imageWaiter
|
||||
nextID uint64
|
||||
external string // non-empty: a foreign process (e.g. a game) holds the GPU
|
||||
|
||||
log *slog.Logger
|
||||
}
|
||||
@@ -52,12 +56,41 @@ func (l *Lock) logTransition(msg string, args ...any) {
|
||||
}
|
||||
}
|
||||
|
||||
// SetExternal records that a process outside gpu-turnstile's control (a
|
||||
// game, another ML job) holds the GPU: new LLM and image grants block until
|
||||
// ClearExternal. In-flight work is not preempted. holder describes the
|
||||
// process for logs and busy responses.
|
||||
func (l *Lock) SetExternal(holder string) {
|
||||
l.mu.Lock()
|
||||
l.external = holder
|
||||
l.broadcast()
|
||||
l.mu.Unlock()
|
||||
l.logTransition("lock transition", "state", StateExternal, "holder", holder)
|
||||
}
|
||||
|
||||
// ClearExternal lifts the external hold; waiting LLM and image requests
|
||||
// proceed.
|
||||
func (l *Lock) ClearExternal() {
|
||||
l.mu.Lock()
|
||||
l.external = ""
|
||||
l.broadcast()
|
||||
l.mu.Unlock()
|
||||
l.logTransition("lock transition", "state", StateIdle)
|
||||
}
|
||||
|
||||
// External returns the current external holder, or "" when none.
|
||||
func (l *Lock) External() string {
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
return l.external
|
||||
}
|
||||
|
||||
// AcquireLLM blocks until no image job is active or pending, then registers
|
||||
// one in-flight LLM request. Returns ctx.Err() if the context is cancelled
|
||||
// while waiting; no state is changed in that case.
|
||||
func (l *Lock) AcquireLLM(ctx context.Context) error {
|
||||
l.mu.Lock()
|
||||
for l.imageActive || len(l.imageQ) > 0 {
|
||||
for l.imageActive || len(l.imageQ) > 0 || l.external != "" {
|
||||
ch := l.change
|
||||
l.mu.Unlock()
|
||||
select {
|
||||
@@ -76,10 +109,10 @@ func (l *Lock) AcquireLLM(ctx context.Context) error {
|
||||
|
||||
// TryAcquireLLM acquires one in-flight LLM slot without waiting and
|
||||
// reports whether it succeeded. It fails when an image job is active or
|
||||
// pending.
|
||||
// pending or an external hold is set.
|
||||
func (l *Lock) TryAcquireLLM() bool {
|
||||
l.mu.Lock()
|
||||
if l.imageActive || len(l.imageQ) > 0 {
|
||||
if l.imageActive || len(l.imageQ) > 0 || l.external != "" {
|
||||
l.mu.Unlock()
|
||||
return false
|
||||
}
|
||||
@@ -120,7 +153,7 @@ func (l *Lock) AcquireImage(ctx context.Context) error {
|
||||
|
||||
for {
|
||||
l.mu.Lock()
|
||||
if l.imageQ[0].id == w.id && l.n == 0 && !l.imageActive {
|
||||
if l.imageQ[0].id == w.id && l.n == 0 && !l.imageActive && l.external == "" {
|
||||
l.imageQ = l.imageQ[1:]
|
||||
l.imageActive = true
|
||||
l.mu.Unlock()
|
||||
@@ -166,6 +199,8 @@ func (l *Lock) Snapshot() (state State, llmInflight int, imagePending bool) {
|
||||
state = StateImage
|
||||
case l.n > 0:
|
||||
state = StateLLM
|
||||
case l.external != "":
|
||||
state = StateExternal
|
||||
default:
|
||||
state = StateIdle
|
||||
}
|
||||
|
||||
@@ -226,3 +226,70 @@ func TestRace(t *testing.T) {
|
||||
t.Fatalf("leaked lock state: state=%s n=%d pending=%v", state, n, pending)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExternalHoldBlocksBoth(t *testing.T) {
|
||||
lk := New(nil)
|
||||
ctx := context.Background()
|
||||
|
||||
lk.SetExternal("game.exe (pid 42)")
|
||||
if lk.TryAcquireLLM() {
|
||||
t.Fatal("TryAcquireLLM succeeded during external hold")
|
||||
}
|
||||
if got := lk.External(); got != "game.exe (pid 42)" {
|
||||
t.Fatalf("External() = %q", got)
|
||||
}
|
||||
if state, _, _ := lk.Snapshot(); state != StateExternal {
|
||||
t.Fatalf("state=%s, want external", state)
|
||||
}
|
||||
|
||||
llmAcquired := make(chan struct{})
|
||||
go func() {
|
||||
if err := lk.AcquireLLM(ctx); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
close(llmAcquired)
|
||||
}()
|
||||
assertBlocked(t, llmAcquired, "LLM acquire during external hold")
|
||||
|
||||
imageAcquired := make(chan struct{})
|
||||
go func() {
|
||||
if err := lk.AcquireImage(ctx); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
close(imageAcquired)
|
||||
}()
|
||||
assertBlocked(t, imageAcquired, "image acquire during external hold")
|
||||
|
||||
lk.ClearExternal()
|
||||
// The queued image job wins over the LLM waiter (image priority).
|
||||
waitFor(t, imageAcquired, "image acquire after ClearExternal")
|
||||
assertBlocked(t, llmAcquired, "LLM acquire while image active")
|
||||
lk.ReleaseImage()
|
||||
waitFor(t, llmAcquired, "LLM acquire after image release")
|
||||
lk.ReleaseLLM()
|
||||
}
|
||||
|
||||
func TestExternalHoldDoesNotPreempt(t *testing.T) {
|
||||
lk := New(nil)
|
||||
ctx := context.Background()
|
||||
if err := lk.AcquireLLM(ctx); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
lk.SetExternal("game.exe")
|
||||
// In-flight LLM work keeps the llm state; the hold blocks new grants.
|
||||
if state, _, _ := lk.Snapshot(); state != StateLLM {
|
||||
t.Fatalf("state=%s, want llm while work in flight", state)
|
||||
}
|
||||
if lk.TryAcquireLLM() {
|
||||
t.Fatal("TryAcquireLLM succeeded during external hold")
|
||||
}
|
||||
lk.ReleaseLLM()
|
||||
if state, _, _ := lk.Snapshot(); state != StateExternal {
|
||||
t.Fatalf("state=%s, want external after drain", state)
|
||||
}
|
||||
lk.ClearExternal()
|
||||
if state, _, _ := lk.Snapshot(); state != StateIdle {
|
||||
t.Fatalf("state=%s, want idle", state)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user