S-4-G-4-R commited on
Commit
945e356
·
verified ·
1 Parent(s): 4246a14

Update ui/gpu.py

Browse files
Files changed (1) hide show
  1. ui/gpu.py +69 -9
ui/gpu.py CHANGED
@@ -19,16 +19,48 @@ an explicit `device` instead of auto-detecting.
19
  Off ZeroGPU (local, or Spaces "CPU basic") @spaces.GPU is a transparent
20
  passthrough and ON_ZEROGPU is False, so this module quietly degrades to plain
21
  CPU work and nothing else in the app changes.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
  """
23
  import os
 
24
 
25
  import spaces
26
 
 
 
 
 
 
27
  # Set by the ZeroGPU runtime; `spaces.config` reads the same variable.
28
  ON_ZEROGPU = os.getenv("SPACES_ZERO_GPU", "").lower() in ("1", "t", "true")
29
 
30
  # The device to use *inside* a GPU window. Outside one, always "cpu".
31
- GPU_DEVICE = "cpu" #"cuda" if ON_ZEROGPU else "cpu"
32
 
33
  # Generous but bounded. The window has to cover PDF text extraction and chunking
34
  # (CPU work that unavoidably happens inside build_vectorstore) plus the encode
@@ -36,6 +68,38 @@ GPU_DEVICE = "cpu" #"cuda" if ON_ZEROGPU else "cpu"
36
  _VECTORIZE_SECONDS = 120
37
 
38
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39
  @spaces.GPU(duration=_VECTORIZE_SECONDS)
40
  def vectorize_on_gpu(pdf_path: str) -> None:
41
  """Build and persist this paper's vectorstore with the embedder on GPU.
@@ -50,14 +114,10 @@ def vectorize_on_gpu(pdf_path: str) -> None:
50
  short-circuits to a plain load as soon as the persist dir exists.
51
  """
52
  # Runs fresh on every call, inside the GPU worker — unlike a module-level
53
- # print, this actually tells you what's true for *this* invocation.
54
- zero_gpu_flag = os.environ.get("SPACES_ZERO_GPU")
55
- print(
56
- "[GPU FN] pid=" + str(os.getpid())
57
- + ", in ZeroGPU ctx: " + str(zero_gpu_flag)
58
- + ", device=" + GPU_DEVICE
59
- )
60
 
61
  from vectorizeer import build_vectorstore
62
 
63
- build_vectorstore(pdf_path, device=GPU_DEVICE)
 
19
  Off ZeroGPU (local, or Spaces "CPU basic") @spaces.GPU is a transparent
20
  passthrough and ON_ZEROGPU is False, so this module quietly degrades to plain
21
  CPU work and nothing else in the app changes.
22
+
23
+ THE CUDA-VIRGINITY RULE
24
+ -----------------------
25
+ ZeroGPU forks its GPU worker from this process (`multiprocessing.get_context
26
+ ('fork')`), and the very first thing the child does is:
27
+
28
+ os.environ['CUDA_VISIBLE_DEVICES'] = nvidia_uuid
29
+ torch.Tensor([0]).cuda()
30
+
31
+ CUDA_VISIBLE_DEVICES is honoured by the driver *exactly once per process*, at
32
+ first CUDA init. So if anything has really initialised CUDA in THIS process
33
+ before the fork, the child inherits a driver that already decided there are zero
34
+ devices, the env var is ignored, and the worker dies with
35
+
36
+ RuntimeError: No CUDA GPUs are available
37
+
38
+ `spaces` prevents that by monkey-patching torch at `import spaces`. But note how
39
+ it does it (spaces/zero/torch/patching.py::patch): the `torch.cuda.*` attribute
40
+ fakes are module-global, while the TorchFunctionMode/TorchDispatchMode that
41
+ intercept real tensor ops are **thread-local to the thread that called patch()**
42
+ — the main thread, at import. The library's own source carries the TODO
43
+ admitting the inconsistency. Consequence for us: heavy model loading must happen
44
+ on the MAIN THREAD, not on Gradio worker threads or threads we spawn ourselves.
45
+ See ui/agents.preload_all().
46
+
47
+ `cuda_state()` below is the telemetry that proves whether that rule is holding.
48
  """
49
  import os
50
+ import threading
51
 
52
  import spaces
53
 
54
+ # Safe: `spaces` imports torch itself (spaces/zero/torch/__init__.py) and calls
55
+ # torch.patch() immediately after, so by the time this line runs torch is already
56
+ # in sys.modules and already patched. This is NOT the import that decides order.
57
+ import torch
58
+
59
  # Set by the ZeroGPU runtime; `spaces.config` reads the same variable.
60
  ON_ZEROGPU = os.getenv("SPACES_ZERO_GPU", "").lower() in ("1", "t", "true")
61
 
62
  # The device to use *inside* a GPU window. Outside one, always "cpu".
63
+ GPU_DEVICE = "cuda" if ON_ZEROGPU else "cpu"
64
 
65
  # Generous but bounded. The window has to cover PDF text extraction and chunking
66
  # (CPU work that unavoidably happens inside build_vectorstore) plus the encode
 
68
  _VECTORIZE_SECONDS = 120
69
 
70
 
71
+ def cuda_state(tag: str) -> str:
72
+ """One-line snapshot of this process's CUDA state, for the Space logs.
73
+
74
+ Reads the two fields that actually decide whether a ZeroGPU fork will
75
+ succeed, neither of which `spaces` patches (so both tell the truth):
76
+
77
+ torch.cuda._initialized True once CUDA has REALLY been brought up here.
78
+ Must still be False in the parent at the moment
79
+ of the GPU call — otherwise the fork is doomed.
80
+ torch.cuda._is_in_bad_fork()
81
+ True when this process inherited an already-
82
+ initialised CUDA context across a fork.
83
+
84
+ Also reports the thread, because "which thread" is the whole ballgame: the
85
+ spaces Torch modes only cover the main thread.
86
+ """
87
+ try:
88
+ bad_fork = torch.cuda._is_in_bad_fork()
89
+ except Exception as e: # pragma: no cover — never let telemetry break the app
90
+ bad_fork = f"<{type(e).__name__}>"
91
+ current = threading.current_thread()
92
+ return (
93
+ f"[cuda-state:{tag}] pid={os.getpid()}"
94
+ f" thread={current.name!r}"
95
+ f" is_main_thread={current is threading.main_thread()}"
96
+ f" torch.cuda._initialized={getattr(torch.cuda, '_initialized', '?')}"
97
+ f" _is_in_bad_fork={bad_fork}"
98
+ f" CUDA_VISIBLE_DEVICES={os.environ.get('CUDA_VISIBLE_DEVICES')!r}"
99
+ f" ON_ZEROGPU={ON_ZEROGPU} GPU_DEVICE={GPU_DEVICE}"
100
+ )
101
+
102
+
103
  @spaces.GPU(duration=_VECTORIZE_SECONDS)
104
  def vectorize_on_gpu(pdf_path: str) -> None:
105
  """Build and persist this paper's vectorstore with the embedder on GPU.
 
114
  short-circuits to a plain load as soon as the persist dir exists.
115
  """
116
  # Runs fresh on every call, inside the GPU worker — unlike a module-level
117
+ # print, this actually tells you what's true for *this* invocation. If you
118
+ # see this line at all, ZeroGPU's worker bootstrap succeeded.
119
+ print(cuda_state("gpu-worker"), flush=True)
 
 
 
 
120
 
121
  from vectorizeer import build_vectorstore
122
 
123
+ build_vectorstore(pdf_path, device=GPU_DEVICE)