Spaces:
Running on A10G
Running on A10G
udbhav commited on
Commit Β·
0fc713e
1
Parent(s): 9981eb3
added multi user
Browse files- Dockerfile +14 -12
- app.py +95 -34
Dockerfile
CHANGED
|
@@ -44,18 +44,19 @@ RUN ln -sf /usr/bin/python3.12 /usr/bin/python && \
|
|
| 44 |
|
| 45 |
# ---------------- Writable app data & HF cache ----------------
|
| 46 |
ENV APP_DATA_DIR=/data
|
| 47 |
-
ENV HF_HOME=/data/hf_home
|
| 48 |
-
ENV HUGGINGFACE_HUB_CACHE=/data/hf_home
|
| 49 |
-
ENV TRANSFORMERS_CACHE=/data/hf_home
|
| 50 |
-
ENV MPLCONFIGDIR=/data/matplotlib
|
| 51 |
|
| 52 |
-
|
|
|
|
| 53 |
&& chmod -R 777 /data
|
| 54 |
|
| 55 |
# ---------------- Install frpc for Gradio share=True ----------------
|
| 56 |
-
RUN mkdir -p /data/hf_home/gradio/frpc && \
|
| 57 |
-
wget https://cdn-media.huggingface.co/frpc-gradio-0.3/frpc_linux_amd64 -O /data/hf_home/gradio/frpc/frpc_linux_amd64_v0.3 && \
|
| 58 |
-
chmod +x /data/hf_home/gradio/frpc/frpc_linux_amd64_v0.3
|
| 59 |
|
| 60 |
# ---------------- Application setup ----------------
|
| 61 |
WORKDIR /app
|
|
@@ -81,9 +82,10 @@ RUN chown -R 1000:1000 /app
|
|
| 81 |
EXPOSE 7860
|
| 82 |
|
| 83 |
# Set HOST and PORT via environment variables
|
| 84 |
-
ENV HOST=0.0.0.0
|
| 85 |
-
ENV PORT=7860
|
| 86 |
|
| 87 |
-
# Run app
|
| 88 |
-
|
|
|
|
| 89 |
|
|
|
|
| 44 |
|
| 45 |
# ---------------- Writable app data & HF cache ----------------
|
| 46 |
ENV APP_DATA_DIR=/data
|
| 47 |
+
ENV HF_HOME=/data/shared/hf_home
|
| 48 |
+
ENV HUGGINGFACE_HUB_CACHE=/data/shared/hf_home
|
| 49 |
+
ENV TRANSFORMERS_CACHE=/data/shared/hf_home
|
| 50 |
+
ENV MPLCONFIGDIR=/data/matplotlib
|
| 51 |
|
| 52 |
+
# Create shared directories (for models/HF cache) and per-user directories will be created at runtime
|
| 53 |
+
RUN mkdir -p /data/shared/weights /data/shared/hf_home /data/matplotlib \
|
| 54 |
&& chmod -R 777 /data
|
| 55 |
|
| 56 |
# ---------------- Install frpc for Gradio share=True ----------------
|
| 57 |
+
RUN mkdir -p /data/shared/hf_home/gradio/frpc && \
|
| 58 |
+
wget https://cdn-media.huggingface.co/frpc-gradio-0.3/frpc_linux_amd64 -O /data/shared/hf_home/gradio/frpc/frpc_linux_amd64_v0.3 && \
|
| 59 |
+
chmod +x /data/shared/hf_home/gradio/frpc/frpc_linux_amd64_v0.3
|
| 60 |
|
| 61 |
# ---------------- Application setup ----------------
|
| 62 |
WORKDIR /app
|
|
|
|
| 82 |
EXPOSE 7860
|
| 83 |
|
| 84 |
# Set HOST and PORT via environment variables
|
| 85 |
+
# ENV HOST=0.0.0.0
|
| 86 |
+
# ENV PORT=7860
|
| 87 |
|
| 88 |
+
# Run app with trame.tools.serve; bind explicitly to 0.0.0.0:7860
|
| 89 |
+
# This uses the create_app factory pattern for proper multi-user session management
|
| 90 |
+
CMD ["python", "-m", "trame.tools.serve", "--exec", "app:create_app", "--host", "0.0.0.0", "--port", "7860"]
|
| 91 |
|
app.py
CHANGED
|
@@ -20,11 +20,25 @@ import traceback
|
|
| 20 |
import signal
|
| 21 |
|
| 22 |
|
| 23 |
-
# Writable base dir
|
| 24 |
-
|
|
|
|
| 25 |
os.makedirs(DATA_DIR, exist_ok=True)
|
|
|
|
| 26 |
os.environ.setdefault("MPLCONFIGDIR", DATA_DIR)
|
| 27 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 28 |
def setup_environment():
|
| 29 |
cache_dir = Path("cache")
|
| 30 |
cache_dir.mkdir(exist_ok=True)
|
|
@@ -73,7 +87,27 @@ def download_private_repo(cache_dir, repo_id, hf_token):
|
|
| 73 |
|
| 74 |
return repo_path
|
| 75 |
|
| 76 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 77 |
sys.path.insert(0, str(repo_path))
|
| 78 |
try:
|
| 79 |
import app as private_app
|
|
@@ -87,23 +121,56 @@ def load_private_app(repo_path):
|
|
| 87 |
# Thumbnails are optional; don't break startup if this fails.
|
| 88 |
traceback.print_exc()
|
| 89 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 90 |
# Check for app or demo attribute
|
| 91 |
-
|
| 92 |
-
|
| 93 |
elif hasattr(private_app, "demo"):
|
| 94 |
-
|
| 95 |
-
# Check if PFMDemo class exists and instantiate it
|
| 96 |
elif hasattr(private_app, "PFMDemo"):
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 102 |
except Exception as e:
|
| 103 |
traceback.print_exc()
|
| 104 |
raise RuntimeError(f"Failed to load app from private repo: {e}")
|
| 105 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 106 |
def main():
|
|
|
|
|
|
|
|
|
|
|
|
|
| 107 |
# Setup signal handlers for graceful shutdown
|
| 108 |
def signal_handler(sig, frame):
|
| 109 |
print("\nπ Received shutdown signal, cleaning up...")
|
|
@@ -113,37 +180,31 @@ def main():
|
|
| 113 |
signal.signal(signal.SIGTERM, signal_handler)
|
| 114 |
|
| 115 |
try:
|
| 116 |
-
|
| 117 |
-
repo_id = os.environ.get("REPO_ID")
|
| 118 |
-
|
| 119 |
-
if not hf_token or not repo_id:
|
| 120 |
-
raise ValueError("HF_TOKEN and REPO_ID must be set")
|
| 121 |
-
|
| 122 |
-
print(f"π Starting app with repo_id: {repo_id}")
|
| 123 |
print(f"π Current working directory: {os.getcwd()}")
|
| 124 |
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 128 |
|
| 129 |
-
# Check if it's a Trame app (has server.start method)
|
| 130 |
if hasattr(demo, 'server') and hasattr(demo.server, 'start'):
|
| 131 |
# Trame app
|
| 132 |
-
|
| 133 |
-
host = os.environ.get("HOST", "0.0.0.0") # Allow host to be configured via env var
|
| 134 |
-
|
| 135 |
-
print(f"π Starting Trame server on {host}:{port}")
|
| 136 |
|
| 137 |
-
if demo.server.state.health == "running":
|
| 138 |
print("Trame server already running, not starting again.")
|
| 139 |
return
|
| 140 |
|
| 141 |
-
# Configure server
|
| 142 |
try:
|
| 143 |
# Try with host parameter first (if supported)
|
| 144 |
demo.server.start(
|
| 145 |
-
host=host,
|
| 146 |
-
port=port,
|
| 147 |
open_browser=False,
|
| 148 |
show_connection_info=True,
|
| 149 |
backend="aiohttp",
|
|
@@ -153,7 +214,6 @@ def main():
|
|
| 153 |
# If host parameter is not supported, configure via server options
|
| 154 |
# Most Trame versions bind to 0.0.0.0 by default with aiohttp
|
| 155 |
demo.server.start(
|
| 156 |
-
port=port,
|
| 157 |
open_browser=False,
|
| 158 |
show_connection_info=True,
|
| 159 |
backend="aiohttp",
|
|
@@ -161,12 +221,13 @@ def main():
|
|
| 161 |
)
|
| 162 |
elif hasattr(demo, 'launch'):
|
| 163 |
# Gradio app
|
|
|
|
| 164 |
demo.launch(
|
| 165 |
server_name="0.0.0.0",
|
| 166 |
-
server_port=
|
| 167 |
share=False,
|
| 168 |
show_error=True,
|
| 169 |
-
allowed_paths=[str(repo_path), DATA_DIR]
|
| 170 |
)
|
| 171 |
else:
|
| 172 |
raise RuntimeError("App does not have launch() or server.start() method")
|
|
|
|
| 20 |
import signal
|
| 21 |
|
| 22 |
|
| 23 |
+
# Writable base dir (matches alphaLPFM pattern)
|
| 24 |
+
# Use APP_DATA_DIR to match alphaLPFM's expectation
|
| 25 |
+
DATA_DIR = os.environ.get("APP_DATA_DIR", os.path.join(tempfile.gettempdir(), "appdata_trame"))
|
| 26 |
os.makedirs(DATA_DIR, exist_ok=True)
|
| 27 |
+
os.environ.setdefault("APP_DATA_DIR", DATA_DIR) # Ensure it's set for the private app
|
| 28 |
os.environ.setdefault("MPLCONFIGDIR", DATA_DIR)
|
| 29 |
|
| 30 |
+
# Set up shared directories structure (matches alphaLPFM)
|
| 31 |
+
SHARED_DIR = os.path.join(DATA_DIR, "shared")
|
| 32 |
+
SHARED_HF_DIR = os.path.join(SHARED_DIR, "hf_home")
|
| 33 |
+
SHARED_WEIGHTS_DIR = os.path.join(SHARED_DIR, "weights")
|
| 34 |
+
for d in (SHARED_DIR, SHARED_HF_DIR, SHARED_WEIGHTS_DIR):
|
| 35 |
+
os.makedirs(d, exist_ok=True)
|
| 36 |
+
|
| 37 |
+
# Set HF cache environment variables (matches alphaLPFM)
|
| 38 |
+
os.environ.setdefault("HF_HOME", SHARED_HF_DIR)
|
| 39 |
+
os.environ.setdefault("HUGGINGFACE_HUB_CACHE", SHARED_HF_DIR)
|
| 40 |
+
os.environ.setdefault("TRANSFORMERS_CACHE", SHARED_HF_DIR)
|
| 41 |
+
|
| 42 |
def setup_environment():
|
| 43 |
cache_dir = Path("cache")
|
| 44 |
cache_dir.mkdir(exist_ok=True)
|
|
|
|
| 87 |
|
| 88 |
return repo_path
|
| 89 |
|
| 90 |
+
# Cache for loaded private app
|
| 91 |
+
_PRIVATE_APP_CACHE = None
|
| 92 |
+
_PRIVATE_APP_CREATE_APP = None
|
| 93 |
+
|
| 94 |
+
def _load_private_app_once():
|
| 95 |
+
"""Load the private app once and cache it. Called by create_app() or main()."""
|
| 96 |
+
global _PRIVATE_APP_CACHE, _PRIVATE_APP_CREATE_APP
|
| 97 |
+
|
| 98 |
+
if _PRIVATE_APP_CACHE is not None:
|
| 99 |
+
return _PRIVATE_APP_CACHE, _PRIVATE_APP_CREATE_APP
|
| 100 |
+
|
| 101 |
+
hf_token = os.environ.get("HF_TOKEN")
|
| 102 |
+
repo_id = os.environ.get("REPO_ID")
|
| 103 |
+
|
| 104 |
+
if not hf_token or not repo_id:
|
| 105 |
+
raise ValueError("HF_TOKEN and REPO_ID must be set")
|
| 106 |
+
|
| 107 |
+
print(f"π Loading private app from repo_id: {repo_id}")
|
| 108 |
+
cache_dir = setup_environment()
|
| 109 |
+
repo_path = download_private_repo(cache_dir, repo_id, hf_token)
|
| 110 |
+
|
| 111 |
sys.path.insert(0, str(repo_path))
|
| 112 |
try:
|
| 113 |
import app as private_app
|
|
|
|
| 121 |
# Thumbnails are optional; don't break startup if this fails.
|
| 122 |
traceback.print_exc()
|
| 123 |
|
| 124 |
+
# β
PREFER create_app factory (for multi-user session management)
|
| 125 |
+
if hasattr(private_app, "create_app"):
|
| 126 |
+
print("β
Found create_app factory in private app (supports multi-user sessions)")
|
| 127 |
+
_PRIVATE_APP_CREATE_APP = private_app.create_app
|
| 128 |
# Check for app or demo attribute
|
| 129 |
+
elif hasattr(private_app, "app"):
|
| 130 |
+
_PRIVATE_APP_CREATE_APP = lambda: private_app.app
|
| 131 |
elif hasattr(private_app, "demo"):
|
| 132 |
+
_PRIVATE_APP_CREATE_APP = lambda: private_app.demo
|
| 133 |
+
# Check if PFMDemo class exists and instantiate it (fallback, but not ideal for multi-user)
|
| 134 |
elif hasattr(private_app, "PFMDemo"):
|
| 135 |
+
print("β οΈ Using PFMDemo directly (may not support multi-user sessions)")
|
| 136 |
+
def _create_pfmdemo_app(server=None):
|
| 137 |
+
from trame.app import get_server
|
| 138 |
+
if server is None:
|
| 139 |
+
server = get_server()
|
| 140 |
+
app_instance = private_app.PFMDemo(server)
|
| 141 |
+
app_instance.server.controller.add("decimate_again", app_instance.decimate_again)
|
| 142 |
+
app_instance.server.controller.add("reset_mesh", app_instance.reset_mesh)
|
| 143 |
+
return app_instance
|
| 144 |
+
_PRIVATE_APP_CREATE_APP = _create_pfmdemo_app
|
| 145 |
+
else:
|
| 146 |
+
raise RuntimeError("No demo/app/create_app found in private repo")
|
| 147 |
+
|
| 148 |
+
_PRIVATE_APP_CACHE = (private_app, repo_path)
|
| 149 |
+
return _PRIVATE_APP_CACHE, _PRIVATE_APP_CREATE_APP
|
| 150 |
except Exception as e:
|
| 151 |
traceback.print_exc()
|
| 152 |
raise RuntimeError(f"Failed to load app from private repo: {e}")
|
| 153 |
|
| 154 |
+
def create_app(server=None, **kwargs):
|
| 155 |
+
"""
|
| 156 |
+
Trame application factory (matches alphaLPFM pattern).
|
| 157 |
+
|
| 158 |
+
This function is called by trame.tools.serve to create the app instance.
|
| 159 |
+
It loads the private app and returns its create_app result (or wraps it).
|
| 160 |
+
"""
|
| 161 |
+
_, create_app_fn = _load_private_app_once()
|
| 162 |
+
|
| 163 |
+
if create_app_fn is None:
|
| 164 |
+
raise RuntimeError("No create_app function found in private app")
|
| 165 |
+
|
| 166 |
+
# Call the private app's create_app (or wrapper)
|
| 167 |
+
return create_app_fn(server=server, **kwargs)
|
| 168 |
+
|
| 169 |
def main():
|
| 170 |
+
"""
|
| 171 |
+
Main entry point (fallback if not using trame.tools.serve).
|
| 172 |
+
This is kept for backward compatibility but trame.tools.serve is preferred.
|
| 173 |
+
"""
|
| 174 |
# Setup signal handlers for graceful shutdown
|
| 175 |
def signal_handler(sig, frame):
|
| 176 |
print("\nπ Received shutdown signal, cleaning up...")
|
|
|
|
| 180 |
signal.signal(signal.SIGTERM, signal_handler)
|
| 181 |
|
| 182 |
try:
|
| 183 |
+
print(f"π Starting app (using main() - consider using trame.tools.serve for multi-user support)")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 184 |
print(f"π Current working directory: {os.getcwd()}")
|
| 185 |
|
| 186 |
+
# Load private app (will be cached)
|
| 187 |
+
_, create_app_fn = _load_private_app_once()
|
| 188 |
+
|
| 189 |
+
if create_app_fn is None:
|
| 190 |
+
raise RuntimeError("No create_app function found in private app")
|
| 191 |
+
|
| 192 |
+
# Create app instance
|
| 193 |
+
demo = create_app()
|
| 194 |
|
| 195 |
+
# Check if it's a Trame app (has server.start method)
|
| 196 |
if hasattr(demo, 'server') and hasattr(demo.server, 'start'):
|
| 197 |
# Trame app
|
| 198 |
+
print(f"π Starting Trame server (host/port from trame.tools.serve or defaults)")
|
|
|
|
|
|
|
|
|
|
| 199 |
|
| 200 |
+
if hasattr(demo.server.state, 'health') and demo.server.state.health == "running":
|
| 201 |
print("Trame server already running, not starting again.")
|
| 202 |
return
|
| 203 |
|
| 204 |
+
# Configure server - host/port come from trame.tools.serve or environment
|
| 205 |
try:
|
| 206 |
# Try with host parameter first (if supported)
|
| 207 |
demo.server.start(
|
|
|
|
|
|
|
| 208 |
open_browser=False,
|
| 209 |
show_connection_info=True,
|
| 210 |
backend="aiohttp",
|
|
|
|
| 214 |
# If host parameter is not supported, configure via server options
|
| 215 |
# Most Trame versions bind to 0.0.0.0 by default with aiohttp
|
| 216 |
demo.server.start(
|
|
|
|
| 217 |
open_browser=False,
|
| 218 |
show_connection_info=True,
|
| 219 |
backend="aiohttp",
|
|
|
|
| 221 |
)
|
| 222 |
elif hasattr(demo, 'launch'):
|
| 223 |
# Gradio app
|
| 224 |
+
_, repo_path = _PRIVATE_APP_CACHE if _PRIVATE_APP_CACHE else (None, None)
|
| 225 |
demo.launch(
|
| 226 |
server_name="0.0.0.0",
|
| 227 |
+
server_port=7860,
|
| 228 |
share=False,
|
| 229 |
show_error=True,
|
| 230 |
+
allowed_paths=[str(repo_path) if repo_path else ".", DATA_DIR]
|
| 231 |
)
|
| 232 |
else:
|
| 233 |
raise RuntimeError("App does not have launch() or server.start() method")
|