Spaces:
Running
Running
Codex Claude Opus 4.8 commited on
Commit ·
c1939e1
1
Parent(s): f4b9db0
ZeroGPU: move models to CUDA inside @gpu calls, not in the main process
Browse filesZeroGPU forbids real CUDA init outside an @gpu allocation. load() now keeps
models on CPU (no .cuda(), no device_map=auto, no pipe.to('cuda')); the move to
CUDA happens inside _minicpm_generate/_nemotron_generate/_run_art_pipe.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- art.py +2 -1
- local_llm.py +7 -3
- tests/test_local_llm.py +8 -0
art.py
CHANGED
|
@@ -65,9 +65,9 @@ class DiffusersImageClient:
|
|
| 65 |
import torch
|
| 66 |
from diffusers import AutoPipelineForText2Image
|
| 67 |
|
|
|
|
| 68 |
pipe = AutoPipelineForText2Image.from_pretrained(model_id, torch_dtype=best_torch_dtype(torch))
|
| 69 |
pipe.set_progress_bar_config(disable=True)
|
| 70 |
-
pipe = move_pipe_to_device(pipe, torch)
|
| 71 |
return cls(pipe, steps=steps, guidance_scale=guidance_scale, width=width, height=height)
|
| 72 |
|
| 73 |
|
|
@@ -78,6 +78,7 @@ _art_pipe: Any = None
|
|
| 78 |
# so the forked GPU worker inherits it; returns a data URI string (picklable).
|
| 79 |
@gpu
|
| 80 |
def _run_art_pipe(prompt: str, steps: int, guidance_scale: float, width: int, height: int) -> str:
|
|
|
|
| 81 |
result = _art_pipe(
|
| 82 |
prompt=prompt,
|
| 83 |
num_inference_steps=steps,
|
|
|
|
| 65 |
import torch
|
| 66 |
from diffusers import AutoPipelineForText2Image
|
| 67 |
|
| 68 |
+
# Stay on CPU here: on ZeroGPU the move to CUDA happens in _run_art_pipe.
|
| 69 |
pipe = AutoPipelineForText2Image.from_pretrained(model_id, torch_dtype=best_torch_dtype(torch))
|
| 70 |
pipe.set_progress_bar_config(disable=True)
|
|
|
|
| 71 |
return cls(pipe, steps=steps, guidance_scale=guidance_scale, width=width, height=height)
|
| 72 |
|
| 73 |
|
|
|
|
| 78 |
# so the forked GPU worker inherits it; returns a data URI string (picklable).
|
| 79 |
@gpu
|
| 80 |
def _run_art_pipe(prompt: str, steps: int, guidance_scale: float, width: int, height: int) -> str:
|
| 81 |
+
_art_pipe.to("cuda")
|
| 82 |
result = _art_pipe(
|
| 83 |
prompt=prompt,
|
| 84 |
num_inference_steps=steps,
|
local_llm.py
CHANGED
|
@@ -99,8 +99,10 @@ class NemotronTransformersChatClient:
|
|
| 99 |
) -> "NemotronTransformersChatClient": # pragma: no cover
|
| 100 |
from transformers import AutoModelForCausalLM, AutoTokenizer
|
| 101 |
|
|
|
|
|
|
|
| 102 |
tokenizer = AutoTokenizer.from_pretrained(model_path)
|
| 103 |
-
model = AutoModelForCausalLM.from_pretrained(model_path,
|
| 104 |
return cls(model.eval(), tokenizer, max_new_tokens=max_new_tokens, temperature=temperature)
|
| 105 |
|
| 106 |
|
|
@@ -112,6 +114,7 @@ _nemotron_tokenizer: Any = None
|
|
| 112 |
# globals so the forked GPU worker inherits it (only strings cross the boundary).
|
| 113 |
@gpu
|
| 114 |
def _nemotron_generate(system: str, user: str, max_new_tokens: int, temperature: float) -> str:
|
|
|
|
| 115 |
messages = chat_messages(system, user)
|
| 116 |
inputs = _nemotron_tokenizer.apply_chat_template(
|
| 117 |
messages,
|
|
@@ -181,8 +184,8 @@ class MiniCPMTransformersChatClient:
|
|
| 181 |
attn_implementation="sdpa",
|
| 182 |
torch_dtype=local_torch_dtype(torch),
|
| 183 |
)
|
| 184 |
-
|
| 185 |
-
|
| 186 |
tokenizer = AutoTokenizer.from_pretrained(model_path, trust_remote_code=True)
|
| 187 |
return cls(model.eval(), tokenizer, max_new_tokens=max_new_tokens, temperature=temperature)
|
| 188 |
|
|
@@ -195,6 +198,7 @@ _minicpm_tokenizer: Any = None
|
|
| 195 |
# globals so the forked GPU worker inherits it (only strings cross the boundary).
|
| 196 |
@gpu
|
| 197 |
def _minicpm_generate(system: str, user: str, max_new_tokens: int, temperature: float) -> str:
|
|
|
|
| 198 |
return str(
|
| 199 |
_minicpm_model.chat(
|
| 200 |
msgs=[{"role": "user", "content": user}],
|
|
|
|
| 99 |
) -> "NemotronTransformersChatClient": # pragma: no cover
|
| 100 |
from transformers import AutoModelForCausalLM, AutoTokenizer
|
| 101 |
|
| 102 |
+
# Load on CPU (no device_map="auto"): on ZeroGPU the move to CUDA must
|
| 103 |
+
# happen inside the @gpu call, in _nemotron_generate.
|
| 104 |
tokenizer = AutoTokenizer.from_pretrained(model_path)
|
| 105 |
+
model = AutoModelForCausalLM.from_pretrained(model_path, torch_dtype="auto")
|
| 106 |
return cls(model.eval(), tokenizer, max_new_tokens=max_new_tokens, temperature=temperature)
|
| 107 |
|
| 108 |
|
|
|
|
| 114 |
# globals so the forked GPU worker inherits it (only strings cross the boundary).
|
| 115 |
@gpu
|
| 116 |
def _nemotron_generate(system: str, user: str, max_new_tokens: int, temperature: float) -> str:
|
| 117 |
+
_nemotron_model.to("cuda")
|
| 118 |
messages = chat_messages(system, user)
|
| 119 |
inputs = _nemotron_tokenizer.apply_chat_template(
|
| 120 |
messages,
|
|
|
|
| 184 |
attn_implementation="sdpa",
|
| 185 |
torch_dtype=local_torch_dtype(torch),
|
| 186 |
)
|
| 187 |
+
# Stay on CPU here: on ZeroGPU the GPU only exists inside @gpu calls,
|
| 188 |
+
# so the move to CUDA happens in _minicpm_generate.
|
| 189 |
tokenizer = AutoTokenizer.from_pretrained(model_path, trust_remote_code=True)
|
| 190 |
return cls(model.eval(), tokenizer, max_new_tokens=max_new_tokens, temperature=temperature)
|
| 191 |
|
|
|
|
| 198 |
# globals so the forked GPU worker inherits it (only strings cross the boundary).
|
| 199 |
@gpu
|
| 200 |
def _minicpm_generate(system: str, user: str, max_new_tokens: int, temperature: float) -> str:
|
| 201 |
+
_minicpm_model.to("cuda")
|
| 202 |
return str(
|
| 203 |
_minicpm_model.chat(
|
| 204 |
msgs=[{"role": "user", "content": user}],
|
tests/test_local_llm.py
CHANGED
|
@@ -72,6 +72,10 @@ class FakeTokenizer:
|
|
| 72 |
class FakeModel:
|
| 73 |
device = "cuda"
|
| 74 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 75 |
# Return a generated token sequence with prompt prefix included.
|
| 76 |
def generate(self, inputs: FakeInputs, **kwargs: Any) -> list[list[int]]:
|
| 77 |
self.inputs = inputs
|
|
@@ -80,6 +84,10 @@ class FakeModel:
|
|
| 80 |
|
| 81 |
|
| 82 |
class FakeMiniCPMModel:
|
|
|
|
|
|
|
|
|
|
|
|
|
| 83 |
# Capture MiniCPM chat kwargs.
|
| 84 |
def chat(self, **kwargs: Any) -> str:
|
| 85 |
self.kwargs = kwargs
|
|
|
|
| 72 |
class FakeModel:
|
| 73 |
device = "cuda"
|
| 74 |
|
| 75 |
+
# No-op device move (ZeroGPU does the real one inside the @gpu call).
|
| 76 |
+
def to(self, device: str) -> "FakeModel":
|
| 77 |
+
return self
|
| 78 |
+
|
| 79 |
# Return a generated token sequence with prompt prefix included.
|
| 80 |
def generate(self, inputs: FakeInputs, **kwargs: Any) -> list[list[int]]:
|
| 81 |
self.inputs = inputs
|
|
|
|
| 84 |
|
| 85 |
|
| 86 |
class FakeMiniCPMModel:
|
| 87 |
+
# No-op device move (ZeroGPU does the real one inside the @gpu call).
|
| 88 |
+
def to(self, device: str) -> "FakeMiniCPMModel":
|
| 89 |
+
return self
|
| 90 |
+
|
| 91 |
# Capture MiniCPM chat kwargs.
|
| 92 |
def chat(self, **kwargs: Any) -> str:
|
| 93 |
self.kwargs = kwargs
|