BoHuangLab commited on
Commit
7fb4074
·
verified ·
1 Parent(s): e794567

Pin torch 2.8.0 for ZeroGPU; build models on CPU at startup

Browse files
Files changed (3) hide show
  1. app.py +7 -0
  2. pipeline.py +15 -6
  3. requirements.txt +6 -3
app.py CHANGED
@@ -229,5 +229,12 @@ with gr.Blocks(title="CELL-FM CondenSeq", theme=gr.themes.Soft()) as demo:
229
  )
230
 
231
 
 
 
 
 
 
 
 
232
  if __name__ == "__main__":
233
  demo.queue().launch(share=os.environ.get("GRADIO_SHARE") == "1")
 
229
  )
230
 
231
 
232
+ # Download weights and build the models at startup, on CPU. Under ZeroGPU the GPU
233
+ # budget only covers the decorated call, so this must not happen inside it.
234
+ try:
235
+ pipeline.load_models()
236
+ except Exception as exc: # surfaced in the Space logs; the UI still loads
237
+ print(f"[startup] model preload failed: {exc}")
238
+
239
  if __name__ == "__main__":
240
  demo.queue().launch(share=os.environ.get("GRADIO_SHARE") == "1")
pipeline.py CHANGED
@@ -134,20 +134,29 @@ def get_device() -> torch.device:
134
 
135
  @lru_cache(maxsize=1)
136
  def load_models():
137
- """Build both models once and keep them resident."""
138
- device = get_device()
139
 
 
 
 
 
140
  generator = CELLFMCSModel(config=generator_config(), loss_fn=UniDiffCriterions)
141
- generator.to(device).eval()
142
 
143
  classifier = ViTModel(config=classifier_config())
144
- classifier.to(device).eval()
145
 
146
  from esm.tokenization.sequence_tokenizer import EsmSequenceTokenizer
147
 
148
  return generator, classifier, EsmSequenceTokenizer()
149
 
150
 
 
 
 
 
 
 
151
  @lru_cache(maxsize=1)
152
  def reference_nucleus() -> torch.Tensor:
153
  """The DAPI channel every generated image is conditioned on, as (1, 1, 160, 160) in [-1, 1].
@@ -208,8 +217,8 @@ def generate_images(sequence, num_images, num_steps, batch_size, seed=DEFAULT_SE
208
  and classification, so the classifier saw quantised pixels. Keeping the same
209
  round-trip here keeps demo numbers comparable to the published ones.
210
  """
211
- generator, _, vocab = load_models()
212
  device = get_device()
 
213
 
214
  from esm.utils import encoding
215
 
@@ -248,8 +257,8 @@ def generate_images(sequence, num_images, num_steps, batch_size, seed=DEFAULT_SE
248
  @torch.no_grad()
249
  def classify_images(images, batch_size, progress=None):
250
  """Stage 2: condensed (1) vs diffuse (0) for each generated image."""
251
- _, classifier, _ = load_models()
252
  device = get_device()
 
253
 
254
  preds = []
255
  for start in range(0, images.shape[0], batch_size):
 
134
 
135
  @lru_cache(maxsize=1)
136
  def load_models():
137
+ """Download the weights and build both models, on CPU.
 
138
 
139
+ Kept off the GPU deliberately: under ZeroGPU a CUDA device only exists inside a
140
+ @spaces.GPU call, and the 2.1 GB download plus model construction would eat that
141
+ budget. Call this once at startup, then models_on(device) inside the GPU call.
142
+ """
143
  generator = CELLFMCSModel(config=generator_config(), loss_fn=UniDiffCriterions)
144
+ generator.eval()
145
 
146
  classifier = ViTModel(config=classifier_config())
147
+ classifier.eval()
148
 
149
  from esm.tokenization.sequence_tokenizer import EsmSequenceTokenizer
150
 
151
  return generator, classifier, EsmSequenceTokenizer()
152
 
153
 
154
+ def models_on(device):
155
+ """Move the cached models to device. A no-op once they are already there."""
156
+ generator, classifier, vocab = load_models()
157
+ return generator.to(device), classifier.to(device), vocab
158
+
159
+
160
  @lru_cache(maxsize=1)
161
  def reference_nucleus() -> torch.Tensor:
162
  """The DAPI channel every generated image is conditioned on, as (1, 1, 160, 160) in [-1, 1].
 
217
  and classification, so the classifier saw quantised pixels. Keeping the same
218
  round-trip here keeps demo numbers comparable to the published ones.
219
  """
 
220
  device = get_device()
221
+ generator, _, vocab = models_on(device)
222
 
223
  from esm.utils import encoding
224
 
 
257
  @torch.no_grad()
258
  def classify_images(images, batch_size, progress=None):
259
  """Stage 2: condensed (1) vs diffuse (0) for each generated image."""
 
260
  device = get_device()
261
+ _, classifier, _ = models_on(device)
262
 
263
  preds = []
264
  for start in range(0, images.shape[0], batch_size):
requirements.txt CHANGED
@@ -1,6 +1,9 @@
1
- # Pinned to the versions the CELL-FM checkpoints were trained and evaluated with.
2
- torch==2.4.1
3
- torchvision==0.19.1
 
 
 
4
  diffusers==0.31.0
5
  transformers==4.44.2
6
  esm==3.1.4
 
1
+ # ZeroGPU only accepts torch 2.8.0+; the checkpoints were trained on 2.4.1, and
2
+ # state dicts are just tensors, so they load unchanged. torchvision is pinned to
3
+ # the matching release for 2.8.0.
4
+ torch==2.8.0
5
+ torchvision==0.23.0
6
+ spaces
7
  diffusers==0.31.0
8
  transformers==4.44.2
9
  esm==3.1.4