Spaces:
Running
on
Zero
Running
on
Zero
Commit
·
86d5c5f
1
Parent(s):
7a6c881
Add external keep-alive script for Tranception Space and update settings
Browse files- .claude/settings.local.json +2 -1
- app.py +43 -224
- keep_alive_external.py +117 -0
.claude/settings.local.json
CHANGED
@@ -10,7 +10,8 @@
|
|
10 |
"Bash(git rm:*)",
|
11 |
"Bash(python test:*)",
|
12 |
"Bash(rm:*)",
|
13 |
-
"Bash(chmod:*)"
|
|
|
14 |
],
|
15 |
"deny": []
|
16 |
}
|
|
|
10 |
"Bash(git rm:*)",
|
11 |
"Bash(python test:*)",
|
12 |
"Bash(rm:*)",
|
13 |
+
"Bash(chmod:*)",
|
14 |
+
"Bash(cp:*)"
|
15 |
],
|
16 |
"deny": []
|
17 |
}
|
app.py
CHANGED
@@ -1,6 +1,6 @@
|
|
1 |
#!/usr/bin/env python3
|
2 |
"""
|
3 |
-
Tranception Design App - Hugging Face Spaces Version
|
4 |
"""
|
5 |
import os
|
6 |
import sys
|
@@ -13,129 +13,31 @@ import matplotlib.pyplot as plt
|
|
13 |
import seaborn as sns
|
14 |
import gradio as gr
|
15 |
from huggingface_hub import hf_hub_download
|
16 |
-
import zipfile
|
17 |
import shutil
|
18 |
import uuid
|
19 |
import gc
|
20 |
import time
|
21 |
-
import signal
|
22 |
-
import threading
|
23 |
import datetime
|
|
|
24 |
|
25 |
-
#
|
26 |
-
|
27 |
-
|
28 |
-
|
29 |
-
|
30 |
-
|
31 |
-
keep_alive_thread = None
|
32 |
-
|
33 |
-
# Auto-refresh component to keep connection alive
|
34 |
-
AUTO_REFRESH_INTERVAL = 240 # 4 minutes
|
35 |
-
|
36 |
-
# Create a mock spaces module for fallback
|
37 |
-
class MockSpaces:
|
38 |
-
"""Mock spaces module for when Zero GPU is not available"""
|
39 |
-
def GPU(self, *args, **kwargs):
|
40 |
-
"""Mock GPU decorator that just returns the function as-is"""
|
41 |
-
def decorator(func):
|
42 |
-
return func
|
43 |
-
return decorator
|
44 |
-
|
45 |
-
# Try to import spaces for Zero GPU support
|
46 |
-
if DISABLE_ZERO_GPU:
|
47 |
SPACES_AVAILABLE = False
|
48 |
-
|
49 |
-
print("Zero GPU disabled via environment variable")
|
50 |
-
else:
|
51 |
-
try:
|
52 |
-
import spaces as real_spaces
|
53 |
-
# Test if spaces is working properly
|
54 |
-
test_decorator = real_spaces.GPU()
|
55 |
-
spaces = real_spaces
|
56 |
-
SPACES_AVAILABLE = True
|
57 |
-
print("Zero GPU support detected and available")
|
58 |
-
except ImportError:
|
59 |
-
SPACES_AVAILABLE = False
|
60 |
-
spaces = MockSpaces()
|
61 |
-
print("Warning: spaces module not available. Running without Zero GPU support.")
|
62 |
-
except Exception as e:
|
63 |
-
SPACES_AVAILABLE = False
|
64 |
-
spaces = MockSpaces()
|
65 |
-
print(f"Warning: Error with spaces module: {e}. Running without Zero GPU support.")
|
66 |
-
|
67 |
-
# Flag to track if we should avoid Zero GPU due to initialization errors
|
68 |
-
USE_ZERO_GPU = SPACES_AVAILABLE and not DISABLE_ZERO_GPU
|
69 |
|
70 |
-
#
|
71 |
-
|
72 |
-
|
73 |
-
def handle_init_error(signum, frame):
|
74 |
-
"""Handle initialization errors gracefully"""
|
75 |
-
global INIT_FAILED
|
76 |
-
INIT_FAILED = True
|
77 |
-
print("Handling initialization error...")
|
78 |
-
sys.exit(1)
|
79 |
-
|
80 |
-
# Set up signal handler for graceful shutdown
|
81 |
-
signal.signal(signal.SIGTERM, handle_init_error)
|
82 |
|
83 |
def update_activity():
|
84 |
-
"""Update last activity
|
85 |
-
global
|
86 |
-
|
87 |
-
|
88 |
-
def keep_alive_worker():
|
89 |
-
"""Background thread to keep the Space alive"""
|
90 |
-
while True:
|
91 |
-
try:
|
92 |
-
time.sleep(KEEP_ALIVE_INTERVAL)
|
93 |
-
current_time = datetime.datetime.now()
|
94 |
-
time_since_activity = (current_time - last_activity_time).total_seconds()
|
95 |
-
|
96 |
-
print(f"Keep-alive check: Last activity {time_since_activity:.0f} seconds ago")
|
97 |
-
|
98 |
-
# Update activity timestamp
|
99 |
-
if time_since_activity > KEEP_ALIVE_INTERVAL:
|
100 |
-
print("Updating activity timestamp...")
|
101 |
-
update_activity()
|
102 |
-
|
103 |
-
# Create a dummy inference to keep Zero GPU warm
|
104 |
-
try:
|
105 |
-
if USE_ZERO_GPU and hasattr(score_and_create_matrix_all_singles, '__wrapped__'):
|
106 |
-
print("Triggering keep-alive inference...")
|
107 |
-
# Use the smallest possible input
|
108 |
-
dummy_result = score_and_create_matrix_all_singles(
|
109 |
-
sequence="MSKGE",
|
110 |
-
mutation_range_start=1,
|
111 |
-
mutation_range_end=2,
|
112 |
-
model_type="Small",
|
113 |
-
scoring_mirror=False,
|
114 |
-
batch_size_inference=1
|
115 |
-
)
|
116 |
-
print("Keep-alive inference completed")
|
117 |
-
# Clean up results
|
118 |
-
if dummy_result and len(dummy_result) > 2:
|
119 |
-
for file in dummy_result[2]: # CSV files
|
120 |
-
try:
|
121 |
-
os.remove(file)
|
122 |
-
except:
|
123 |
-
pass
|
124 |
-
except Exception as e:
|
125 |
-
print(f"Keep-alive inference error (non-critical): {e}")
|
126 |
-
except Exception as e:
|
127 |
-
print(f"Keep-alive thread error: {e}")
|
128 |
-
time.sleep(60) # Wait a bit before retrying
|
129 |
-
|
130 |
-
def warm_up_zero_gpu():
|
131 |
-
"""Warm up Zero GPU after idle period"""
|
132 |
-
if not USE_ZERO_GPU:
|
133 |
-
return False
|
134 |
-
|
135 |
-
print("Warming up Zero GPU...")
|
136 |
-
# Note: Cannot reliably warm up Zero GPU outside of decorated functions
|
137 |
-
# This is a limitation of the Zero GPU system
|
138 |
-
return False
|
139 |
|
140 |
# Add current directory to path
|
141 |
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
@@ -315,7 +217,6 @@ def check_valid_mutant(sequence,mutant,AA_vocab=AA_vocab):
|
|
315 |
def cleanup_old_files(max_age_minutes=30):
|
316 |
"""Clean up old inference files"""
|
317 |
import glob
|
318 |
-
import time
|
319 |
current_time = time.time()
|
320 |
patterns = ["fitness_scoring_substitution_matrix_*.png",
|
321 |
"fitness_scoring_substitution_matrix_*.csv",
|
@@ -344,7 +245,7 @@ def get_mutated_protein(sequence,mutant):
|
|
344 |
return ''.join(mutated_sequence)
|
345 |
|
346 |
def score_and_create_matrix_all_singles_impl(sequence,mutation_range_start=None,mutation_range_end=None,model_type="Large",scoring_mirror=False,batch_size_inference=20,max_number_positions_per_heatmap=50,num_workers=0,AA_vocab=AA_vocab):
|
347 |
-
# Update activity
|
348 |
update_activity()
|
349 |
|
350 |
# Clean up old files periodically
|
@@ -384,10 +285,6 @@ def score_and_create_matrix_all_singles_impl(sequence,mutation_range_start=None,
|
|
384 |
# Device selection - Zero GPU will provide CUDA when decorated with @spaces.GPU
|
385 |
print(f"GPU Available: {torch.cuda.is_available()}")
|
386 |
|
387 |
-
# Try to ensure GPU is available when using Zero GPU
|
388 |
-
if USE_ZERO_GPU and not torch.cuda.is_available():
|
389 |
-
print("Zero GPU enabled but CUDA not available - this is expected before GPU allocation")
|
390 |
-
|
391 |
if torch.cuda.is_available():
|
392 |
device = torch.device("cuda")
|
393 |
model = model.to(device)
|
@@ -401,9 +298,6 @@ def score_and_create_matrix_all_singles_impl(sequence,mutation_range_start=None,
|
|
401 |
device = torch.device("cpu")
|
402 |
model = model.to(device)
|
403 |
print("Inference will take place on CPU")
|
404 |
-
if USE_ZERO_GPU:
|
405 |
-
print("WARNING: Zero GPU is enabled but CUDA is not available!")
|
406 |
-
print("The Space may need to be restarted from the Hugging Face interface.")
|
407 |
# Reduce batch size for CPU inference
|
408 |
batch_size_inference = min(batch_size_inference, 10)
|
409 |
|
@@ -460,8 +354,8 @@ def score_and_create_matrix_all_singles_impl(sequence,mutation_range_start=None,
|
|
460 |
if torch.cuda.is_available():
|
461 |
torch.cuda.empty_cache()
|
462 |
|
463 |
-
# Apply Zero GPU decorator
|
464 |
-
if
|
465 |
score_and_create_matrix_all_singles = spaces.GPU(duration=300)(score_and_create_matrix_all_singles_impl)
|
466 |
else:
|
467 |
score_and_create_matrix_all_singles = score_and_create_matrix_all_singles_impl
|
@@ -475,19 +369,6 @@ def clear_inputs(protein_sequence_input,mutation_range_start,mutation_range_end)
|
|
475 |
mutation_range_end = None
|
476 |
return protein_sequence_input,mutation_range_start,mutation_range_end
|
477 |
|
478 |
-
# Health check endpoint
|
479 |
-
def health_check():
|
480 |
-
"""Simple health check that returns current status"""
|
481 |
-
update_activity()
|
482 |
-
status = {
|
483 |
-
"status": "healthy",
|
484 |
-
"zero_gpu": USE_ZERO_GPU,
|
485 |
-
"cuda_available": torch.cuda.is_available(),
|
486 |
-
"last_activity": last_activity_time.isoformat(),
|
487 |
-
"timestamp": datetime.datetime.now().isoformat()
|
488 |
-
}
|
489 |
-
return status
|
490 |
-
|
491 |
# Create Gradio app
|
492 |
tranception_design = gr.Blocks()
|
493 |
|
@@ -496,21 +377,29 @@ with tranception_design:
|
|
496 |
gr.Markdown("## 🧬 BASIS-China iGEM Team 2025 - Protein Engineering Platform")
|
497 |
gr.Markdown("### Welcome to BASIS-China's implementation of Tranception on Hugging Face Spaces!")
|
498 |
gr.Markdown("We are the BASIS-China iGEM team, and we're excited to present our deployment of the Tranception model for protein fitness prediction. This tool enables in silico directed evolution to iteratively improve protein fitness through single amino acid substitutions. At each step, Tranception computes log likelihood ratios for all possible mutations compared to the starting sequence, generating fitness heatmaps and recommendations to guide protein engineering.")
|
499 |
-
gr.Markdown("**Technical Details**: This deployment leverages Hugging Face's Zero GPU infrastructure, which dynamically allocates H200 GPU resources when available. This allows for efficient inference while managing computational resources effectively.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
500 |
|
501 |
# Status indicator
|
502 |
with gr.Row():
|
503 |
with gr.Column(scale=1):
|
504 |
def get_gpu_status():
|
505 |
-
|
506 |
-
|
507 |
-
|
508 |
-
|
509 |
-
|
510 |
-
else:
|
511 |
-
return f"⚠️ Zero GPU: Ready | Last activity: {int(time_since)}s ago"
|
512 |
-
else:
|
513 |
-
return "💻 Running on CPU"
|
514 |
|
515 |
gpu_status = gr.Textbox(
|
516 |
label="Compute Status",
|
@@ -520,19 +409,6 @@ with tranception_design:
|
|
520 |
elem_id="gpu_status"
|
521 |
)
|
522 |
|
523 |
-
# Hidden components for keep-alive
|
524 |
-
with gr.Row(visible=False):
|
525 |
-
# Auto-refresh component to maintain WebSocket connection
|
526 |
-
keep_alive_refresh = gr.Number(value=0, visible=False)
|
527 |
-
|
528 |
-
def increment_counter():
|
529 |
-
update_activity()
|
530 |
-
return gr.update(value=time.time())
|
531 |
-
|
532 |
-
# This will trigger every 4 minutes to keep the connection alive
|
533 |
-
keep_alive_timer = gr.Timer(value=AUTO_REFRESH_INTERVAL)
|
534 |
-
keep_alive_timer.tick(increment_counter, outputs=[keep_alive_refresh])
|
535 |
-
|
536 |
with gr.Tabs():
|
537 |
with gr.TabItem("Input"):
|
538 |
with gr.Row():
|
@@ -622,66 +498,9 @@ with tranception_design:
|
|
622 |
gr.Markdown("Links: <a href='https://proceedings.mlr.press/v162/notin22a.html' target='_blank'>Paper</a> <a href='https://github.com/OATML-Markslab/Tranception' target='_blank'>Code</a> <a href='https://sites.google.com/view/proteingym/substitutions' target='_blank'>ProteinGym</a> <a href='https://igem.org/teams/5247' target='_blank'>BASIS-China iGEM Team</a>")
|
623 |
|
624 |
if __name__ == "__main__":
|
625 |
-
#
|
626 |
-
|
627 |
-
|
628 |
-
|
629 |
-
|
630 |
-
|
631 |
-
# Schedule periodic dummy inferences to keep alive
|
632 |
-
print("Keep-alive system activated - will perform dummy inferences every 5 minutes")
|
633 |
-
|
634 |
-
# Configure queue for better resource management
|
635 |
-
tranception_design.queue(
|
636 |
-
max_size=10, # Limit queue size
|
637 |
-
status_update_rate="auto", # Show status updates
|
638 |
-
api_open=False # Disable API to prevent external requests
|
639 |
-
)
|
640 |
-
|
641 |
-
# Launch with appropriate settings for HF Spaces
|
642 |
-
# Wrap launch in try-except to handle Zero GPU initialization errors gracefully
|
643 |
-
launch_retries = 0
|
644 |
-
max_launch_retries = 3
|
645 |
-
|
646 |
-
while launch_retries < max_launch_retries:
|
647 |
-
try:
|
648 |
-
# Add a small delay on retries to allow system to stabilize
|
649 |
-
if launch_retries > 0:
|
650 |
-
print(f"Retry attempt {launch_retries}/{max_launch_retries}...")
|
651 |
-
time.sleep(5)
|
652 |
-
|
653 |
-
tranception_design.launch(
|
654 |
-
max_threads=2, # Limit concurrent threads
|
655 |
-
show_error=True,
|
656 |
-
server_name="0.0.0.0",
|
657 |
-
server_port=7860,
|
658 |
-
quiet=False, # Show all logs
|
659 |
-
prevent_thread_lock=True, # Prevent thread locking issues
|
660 |
-
share=False, # Don't create public link
|
661 |
-
inbrowser=False # Don't open browser
|
662 |
-
)
|
663 |
-
break # If successful, exit the retry loop
|
664 |
-
|
665 |
-
except RuntimeError as e:
|
666 |
-
error_msg = str(e)
|
667 |
-
if "ZeroGPU" in error_msg or "Unknown" in error_msg:
|
668 |
-
print(f"Zero GPU initialization error: {e}")
|
669 |
-
launch_retries += 1
|
670 |
-
|
671 |
-
if launch_retries < max_launch_retries:
|
672 |
-
print("Retrying with Zero GPU after warm-up...")
|
673 |
-
# Wait longer before retry
|
674 |
-
time.sleep(10)
|
675 |
-
else:
|
676 |
-
print("Max retries reached. The Space may need to be restarted.")
|
677 |
-
print("Note: Zero GPU containers can crash after idle periods.")
|
678 |
-
print("Consider restarting the Space from the Hugging Face interface.")
|
679 |
-
sys.exit(1)
|
680 |
-
else:
|
681 |
-
# Non-Zero GPU error, re-raise
|
682 |
-
raise
|
683 |
-
except Exception as e:
|
684 |
-
print(f"Unexpected error during launch: {e}")
|
685 |
-
launch_retries += 1
|
686 |
-
if launch_retries >= max_launch_retries:
|
687 |
-
raise
|
|
|
1 |
#!/usr/bin/env python3
|
2 |
"""
|
3 |
+
Tranception Design App - Hugging Face Spaces Version (Zero GPU Fixed)
|
4 |
"""
|
5 |
import os
|
6 |
import sys
|
|
|
13 |
import seaborn as sns
|
14 |
import gradio as gr
|
15 |
from huggingface_hub import hf_hub_download
|
|
|
16 |
import shutil
|
17 |
import uuid
|
18 |
import gc
|
19 |
import time
|
|
|
|
|
20 |
import datetime
|
21 |
+
import threading
|
22 |
|
23 |
+
# Simplified Zero GPU handling
|
24 |
+
try:
|
25 |
+
import spaces
|
26 |
+
SPACES_AVAILABLE = True
|
27 |
+
print("Zero GPU support detected")
|
28 |
+
except ImportError:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
29 |
SPACES_AVAILABLE = False
|
30 |
+
print("Running without Zero GPU support")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
31 |
|
32 |
+
# Keep-alive state
|
33 |
+
last_activity = datetime.datetime.now()
|
34 |
+
activity_lock = threading.Lock()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
35 |
|
36 |
def update_activity():
|
37 |
+
"""Update last activity timestamp"""
|
38 |
+
global last_activity
|
39 |
+
with activity_lock:
|
40 |
+
last_activity = datetime.datetime.now()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
41 |
|
42 |
# Add current directory to path
|
43 |
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
|
217 |
def cleanup_old_files(max_age_minutes=30):
|
218 |
"""Clean up old inference files"""
|
219 |
import glob
|
|
|
220 |
current_time = time.time()
|
221 |
patterns = ["fitness_scoring_substitution_matrix_*.png",
|
222 |
"fitness_scoring_substitution_matrix_*.csv",
|
|
|
245 |
return ''.join(mutated_sequence)
|
246 |
|
247 |
def score_and_create_matrix_all_singles_impl(sequence,mutation_range_start=None,mutation_range_end=None,model_type="Large",scoring_mirror=False,batch_size_inference=20,max_number_positions_per_heatmap=50,num_workers=0,AA_vocab=AA_vocab):
|
248 |
+
# Update activity
|
249 |
update_activity()
|
250 |
|
251 |
# Clean up old files periodically
|
|
|
285 |
# Device selection - Zero GPU will provide CUDA when decorated with @spaces.GPU
|
286 |
print(f"GPU Available: {torch.cuda.is_available()}")
|
287 |
|
|
|
|
|
|
|
|
|
288 |
if torch.cuda.is_available():
|
289 |
device = torch.device("cuda")
|
290 |
model = model.to(device)
|
|
|
298 |
device = torch.device("cpu")
|
299 |
model = model.to(device)
|
300 |
print("Inference will take place on CPU")
|
|
|
|
|
|
|
301 |
# Reduce batch size for CPU inference
|
302 |
batch_size_inference = min(batch_size_inference, 10)
|
303 |
|
|
|
354 |
if torch.cuda.is_available():
|
355 |
torch.cuda.empty_cache()
|
356 |
|
357 |
+
# Apply Zero GPU decorator if available
|
358 |
+
if SPACES_AVAILABLE:
|
359 |
score_and_create_matrix_all_singles = spaces.GPU(duration=300)(score_and_create_matrix_all_singles_impl)
|
360 |
else:
|
361 |
score_and_create_matrix_all_singles = score_and_create_matrix_all_singles_impl
|
|
|
369 |
mutation_range_end = None
|
370 |
return protein_sequence_input,mutation_range_start,mutation_range_end
|
371 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
372 |
# Create Gradio app
|
373 |
tranception_design = gr.Blocks()
|
374 |
|
|
|
377 |
gr.Markdown("## 🧬 BASIS-China iGEM Team 2025 - Protein Engineering Platform")
|
378 |
gr.Markdown("### Welcome to BASIS-China's implementation of Tranception on Hugging Face Spaces!")
|
379 |
gr.Markdown("We are the BASIS-China iGEM team, and we're excited to present our deployment of the Tranception model for protein fitness prediction. This tool enables in silico directed evolution to iteratively improve protein fitness through single amino acid substitutions. At each step, Tranception computes log likelihood ratios for all possible mutations compared to the starting sequence, generating fitness heatmaps and recommendations to guide protein engineering.")
|
380 |
+
gr.Markdown("**Technical Details**: This deployment leverages Hugging Face's Zero GPU infrastructure, which dynamically allocates H200 GPU resources when available. This allows for efficient inference while managing computational resources effectively.")
|
381 |
+
|
382 |
+
# Hidden keep-alive component
|
383 |
+
with gr.Row(visible=False):
|
384 |
+
keep_alive_component = gr.Number(value=0, visible=False)
|
385 |
+
|
386 |
+
def keep_alive_update():
|
387 |
+
update_activity()
|
388 |
+
return time.time()
|
389 |
+
|
390 |
+
# Update every 2 minutes to keep websocket alive
|
391 |
+
keep_alive_timer = gr.Timer(value=120)
|
392 |
+
keep_alive_timer.tick(keep_alive_update, outputs=[keep_alive_component])
|
393 |
|
394 |
# Status indicator
|
395 |
with gr.Row():
|
396 |
with gr.Column(scale=1):
|
397 |
def get_gpu_status():
|
398 |
+
with activity_lock:
|
399 |
+
time_since = (datetime.datetime.now() - last_activity).total_seconds()
|
400 |
+
|
401 |
+
status = "🔥 Zero GPU" if SPACES_AVAILABLE else "💻 CPU Mode"
|
402 |
+
return f"{status} | Last activity: {int(time_since)}s ago"
|
|
|
|
|
|
|
|
|
403 |
|
404 |
gpu_status = gr.Textbox(
|
405 |
label="Compute Status",
|
|
|
409 |
elem_id="gpu_status"
|
410 |
)
|
411 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
412 |
with gr.Tabs():
|
413 |
with gr.TabItem("Input"):
|
414 |
with gr.Row():
|
|
|
498 |
gr.Markdown("Links: <a href='https://proceedings.mlr.press/v162/notin22a.html' target='_blank'>Paper</a> <a href='https://github.com/OATML-Markslab/Tranception' target='_blank'>Code</a> <a href='https://sites.google.com/view/proteingym/substitutions' target='_blank'>ProteinGym</a> <a href='https://igem.org/teams/5247' target='_blank'>BASIS-China iGEM Team</a>")
|
499 |
|
500 |
if __name__ == "__main__":
|
501 |
+
# Simple launch without queue to avoid Zero GPU conflicts
|
502 |
+
tranception_design.launch(
|
503 |
+
server_name="0.0.0.0",
|
504 |
+
server_port=7860,
|
505 |
+
show_error=True
|
506 |
+
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
keep_alive_external.py
ADDED
@@ -0,0 +1,117 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
#!/usr/bin/env python3
|
2 |
+
"""
|
3 |
+
External Keep-Alive for Tranception Space
|
4 |
+
This script runs on your local machine to keep the Space active
|
5 |
+
"""
|
6 |
+
import requests
|
7 |
+
import time
|
8 |
+
from datetime import datetime
|
9 |
+
import sys
|
10 |
+
|
11 |
+
# Space configuration
|
12 |
+
SPACE_EMBED_URL = "https://moraxcheng-transeption-igem-basischina-2025.hf.space"
|
13 |
+
GRADIO_API_URL = f"{SPACE_EMBED_URL}/run/predict"
|
14 |
+
|
15 |
+
# Keep-alive settings
|
16 |
+
PING_INTERVAL = 240 # 4 minutes
|
17 |
+
WAKE_UP_RETRIES = 3
|
18 |
+
|
19 |
+
def simple_ping():
|
20 |
+
"""Simple HTTP GET to keep connection alive"""
|
21 |
+
try:
|
22 |
+
response = requests.get(SPACE_EMBED_URL, timeout=30)
|
23 |
+
return response.status_code == 200
|
24 |
+
except:
|
25 |
+
return False
|
26 |
+
|
27 |
+
def gradio_keep_alive():
|
28 |
+
"""Send minimal Gradio API request"""
|
29 |
+
try:
|
30 |
+
# Minimal prediction request
|
31 |
+
payload = {
|
32 |
+
"data": [
|
33 |
+
"MSKGE", # 5 amino acids
|
34 |
+
1, # start
|
35 |
+
3, # end
|
36 |
+
"Small", # smallest model
|
37 |
+
False, # no mirror
|
38 |
+
1 # batch size 1
|
39 |
+
]
|
40 |
+
}
|
41 |
+
|
42 |
+
# First, initiate the prediction
|
43 |
+
response = requests.post(
|
44 |
+
GRADIO_API_URL,
|
45 |
+
json=payload,
|
46 |
+
timeout=60
|
47 |
+
)
|
48 |
+
|
49 |
+
if response.status_code == 200:
|
50 |
+
return True
|
51 |
+
except Exception as e:
|
52 |
+
print(f"API request error: {e}")
|
53 |
+
|
54 |
+
return False
|
55 |
+
|
56 |
+
def keep_alive_loop():
|
57 |
+
"""Main keep-alive loop"""
|
58 |
+
print("="*60)
|
59 |
+
print("Tranception Space Keep-Alive")
|
60 |
+
print(f"Space: {SPACE_EMBED_URL}")
|
61 |
+
print(f"Ping interval: {PING_INTERVAL} seconds")
|
62 |
+
print("Press Ctrl+C to stop")
|
63 |
+
print("="*60)
|
64 |
+
print()
|
65 |
+
|
66 |
+
consecutive_failures = 0
|
67 |
+
|
68 |
+
while True:
|
69 |
+
try:
|
70 |
+
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
71 |
+
|
72 |
+
# Try simple ping first
|
73 |
+
print(f"[{timestamp}] Pinging Space...", end=" ", flush=True)
|
74 |
+
|
75 |
+
if simple_ping():
|
76 |
+
print("✓ Online")
|
77 |
+
consecutive_failures = 0
|
78 |
+
else:
|
79 |
+
print("✗ No response")
|
80 |
+
consecutive_failures += 1
|
81 |
+
|
82 |
+
# Try Gradio API as fallback
|
83 |
+
print(f"[{timestamp}] Trying Gradio API...", end=" ", flush=True)
|
84 |
+
if gradio_keep_alive():
|
85 |
+
print("✓ Success")
|
86 |
+
consecutive_failures = 0
|
87 |
+
else:
|
88 |
+
print("✗ Failed")
|
89 |
+
|
90 |
+
if consecutive_failures >= 3:
|
91 |
+
print("\n⚠️ WARNING: Space appears to be down!")
|
92 |
+
print("You may need to manually restart it at:")
|
93 |
+
print(f"https://huggingface.co/spaces/MoraxCheng/Transeption_iGEM_BASISCHINA_2025/settings\n")
|
94 |
+
|
95 |
+
# Wait for next ping
|
96 |
+
print(f"Next ping in {PING_INTERVAL} seconds...\n")
|
97 |
+
time.sleep(PING_INTERVAL)
|
98 |
+
|
99 |
+
except KeyboardInterrupt:
|
100 |
+
print("\n\nKeep-alive stopped by user")
|
101 |
+
break
|
102 |
+
except Exception as e:
|
103 |
+
print(f"\nUnexpected error: {e}")
|
104 |
+
print("Continuing...\n")
|
105 |
+
time.sleep(60)
|
106 |
+
|
107 |
+
if __name__ == "__main__":
|
108 |
+
# Test connection
|
109 |
+
print("Testing connection...")
|
110 |
+
if simple_ping():
|
111 |
+
print("✓ Space is online\n")
|
112 |
+
else:
|
113 |
+
print("⚠ Space appears to be offline")
|
114 |
+
print("Starting keep-alive anyway...\n")
|
115 |
+
|
116 |
+
# Start keep-alive
|
117 |
+
keep_alive_loop()
|