Minjun Kang commited on
Commit
e4ecaa0
Β·
1 Parent(s): a9bb355

Preload ProtT5 weights on CPU before the GPU call, increase GPU duration

Browse files

The first @spaces.GPU-decorated call was doing the full from_pretrained()
weight deserialization + device transfer inside the ZeroGPU time budget,
which could exceed the default duration and abort. Now the CPU-side load
happens eagerly at app startup, so the GPU call only does a fast device
transfer. Also set duration=180 as a safety margin.

Files changed (2) hide show
  1. app.py +9 -1
  2. t5_utils.py +63 -37
app.py CHANGED
@@ -33,6 +33,7 @@ AVAILABLE_EXAMPLES = [example for example in EXAMPLES if feature_path(example["i
33
  from t5_utils import (
34
  T5_REPO_ID,
35
  extract_t5_feature as _extract_t5_feature_core,
 
36
  read_feature_h5,
37
  )
38
 
@@ -49,6 +50,13 @@ BASE_DIR = Path(__file__).parent
49
  # holding a ZeroGPU allocation (which has a short time budget).
50
  snapshot_download(T5_REPO_ID)
51
 
 
 
 
 
 
 
 
52
  # ── Physical constants (from preprocess/misc.py) ──────────────────────────────
53
  MAX_TEMP = 60.0
54
  MAX_CONC = 1000.0
@@ -106,7 +114,7 @@ def load_llps_model():
106
  # features in assets/ always match what this would compute live. Only the
107
  # @spaces.GPU wrapping (ZeroGPU allocation) is app-specific, and cb_extract()
108
  # below skips calling this entirely when a cached feature is available.
109
- @spaces.GPU
110
  def extract_t5_feature(sequence: str) -> np.ndarray:
111
  return _extract_t5_feature_core(sequence)
112
 
 
33
  from t5_utils import (
34
  T5_REPO_ID,
35
  extract_t5_feature as _extract_t5_feature_core,
36
+ preload_t5_cpu,
37
  read_feature_h5,
38
  )
39
 
 
50
  # holding a ZeroGPU allocation (which has a short time budget).
51
  snapshot_download(T5_REPO_ID)
52
 
53
+ # Also deserialize the weights into CPU memory here, still outside any
54
+ # @spaces.GPU context. Without this, T5EncoderModel.from_pretrained() (loading
55
+ # ~2.9GB from disk) would run lazily inside the first GPU-decorated call and
56
+ # could eat enough of the ZeroGPU time budget to abort the task. With this,
57
+ # the first GPU call only needs a fast .to("cuda") transfer.
58
+ preload_t5_cpu()
59
+
60
  # ── Physical constants (from preprocess/misc.py) ──────────────────────────────
61
  MAX_TEMP = 60.0
62
  MAX_CONC = 1000.0
 
114
  # features in assets/ always match what this would compute live. Only the
115
  # @spaces.GPU wrapping (ZeroGPU allocation) is app-specific, and cb_extract()
116
  # below skips calling this entirely when a cached feature is available.
117
+ @spaces.GPU(duration=180)
118
  def extract_t5_feature(sequence: str) -> np.ndarray:
119
  return _extract_t5_feature_core(sequence)
120
 
t5_utils.py CHANGED
@@ -24,45 +24,71 @@ def get_device() -> str:
24
  return "cuda" if torch.cuda.is_available() else "cpu"
25
 
26
 
27
- def load_t5():
 
 
 
 
 
 
 
 
 
28
  global _t5_tokenizer, _t5_model
29
- if _t5_model is None:
30
- from transformers import AutoTokenizer, T5EncoderModel
31
- import transformers.utils.import_utils as _hf_utils
32
- import transformers.modeling_utils as _modeling_utils
33
-
34
- # Rostlab/prot_t5_xl_half_uniref50-enc is only available as .bin (no
35
- # safetensors). transformers 5.x blocks torch.load on torch < 2.6 due
36
- # to CVE-2025-32434. We bypass that gate for this specific trusted
37
- # checkpoint from the official HuggingFace Hub. The check lives in
38
- # two places β€” import_utils AND the locally-imported name in
39
- # modeling_utils β€” so both must be patched.
40
- _noop = lambda: None
41
- _orig_hf = _hf_utils.check_torch_load_is_safe
42
- _orig_mdl = _modeling_utils.check_torch_load_is_safe
43
-
44
- _hf_utils.check_torch_load_is_safe = _noop
45
- _modeling_utils.check_torch_load_is_safe = _noop
46
-
47
- try:
48
- repo_id = T5_REPO_ID
49
- dev = get_device()
50
- dtype = torch.float16 if dev == "cuda" else torch.float32
51
-
52
- _t5_tokenizer = AutoTokenizer.from_pretrained(
53
- repo_id, do_lower_case=False, local_files_only=True
54
- )
55
- _t5_model = (
56
- T5EncoderModel.from_pretrained(
57
- repo_id, torch_dtype=dtype, local_files_only=True
58
- )
59
- .to(dev)
60
- .eval()
61
  )
62
- _t5_model.requires_grad_(False)
63
- finally:
64
- _hf_utils.check_torch_load_is_safe = _orig_hf
65
- _modeling_utils.check_torch_load_is_safe = _orig_mdl
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
66
 
67
  return _t5_tokenizer, _t5_model
68
 
 
24
  return "cuda" if torch.cuda.is_available() else "cpu"
25
 
26
 
27
+ def preload_t5_cpu():
28
+ """Deserialize tokenizer + model weights onto CPU, outside any @spaces.GPU
29
+ context.
30
+
31
+ This is the slow step (reading ~2.9GB of weights off disk into RAM) and
32
+ it does not need a GPU. Calling it eagerly at app startup means the first
33
+ @spaces.GPU-decorated call only has to do a device transfer (fast), not a
34
+ full from_pretrained() load, so it no longer risks running past the
35
+ ZeroGPU time budget.
36
+ """
37
  global _t5_tokenizer, _t5_model
38
+ if _t5_model is not None:
39
+ return
40
+
41
+ from transformers import AutoTokenizer, T5EncoderModel
42
+ import transformers.utils.import_utils as _hf_utils
43
+ import transformers.modeling_utils as _modeling_utils
44
+
45
+ # Rostlab/prot_t5_xl_half_uniref50-enc is only available as .bin (no
46
+ # safetensors). transformers 5.x blocks torch.load on torch < 2.6 due
47
+ # to CVE-2025-32434. We bypass that gate for this specific trusted
48
+ # checkpoint from the official HuggingFace Hub. The check lives in
49
+ # two places β€” import_utils AND the locally-imported name in
50
+ # modeling_utils β€” so both must be patched.
51
+ _noop = lambda: None
52
+ _orig_hf = _hf_utils.check_torch_load_is_safe
53
+ _orig_mdl = _modeling_utils.check_torch_load_is_safe
54
+
55
+ _hf_utils.check_torch_load_is_safe = _noop
56
+ _modeling_utils.check_torch_load_is_safe = _noop
57
+
58
+ try:
59
+ repo_id = T5_REPO_ID
60
+ _t5_tokenizer = AutoTokenizer.from_pretrained(
61
+ repo_id, do_lower_case=False, local_files_only=True
62
+ )
63
+ _t5_model = (
64
+ T5EncoderModel.from_pretrained(
65
+ repo_id, torch_dtype=torch.float32, local_files_only=True
 
 
 
 
66
  )
67
+ .eval()
68
+ )
69
+ _t5_model.requires_grad_(False)
70
+ finally:
71
+ _hf_utils.check_torch_load_is_safe = _orig_hf
72
+ _modeling_utils.check_torch_load_is_safe = _orig_mdl
73
+
74
+
75
+ def load_t5():
76
+ """Return (tokenizer, model) with the model on the current device.
77
+
78
+ Assumes preload_t5_cpu() has already been called (at app startup). The
79
+ only work left here is moving the already-deserialized model to the GPU
80
+ on the first @spaces.GPU call β€” a fast operation compared to loading it
81
+ from disk.
82
+ """
83
+ global _t5_model
84
+ preload_t5_cpu()
85
+
86
+ dev = get_device()
87
+ if next(_t5_model.parameters()).device.type != dev:
88
+ if dev == "cuda":
89
+ _t5_model = _t5_model.half().to(dev).eval()
90
+ else:
91
+ _t5_model = _t5_model.float().to(dev).eval()
92
 
93
  return _t5_tokenizer, _t5_model
94