Spaces:
Runtime error
Runtime error
security: constant-time token compare + num_steps/image caps
Browse files- molmoact2_server.py +12 -4
molmoact2_server.py
CHANGED
|
@@ -22,6 +22,7 @@ Deploy + test: see README.md in this directory.
|
|
| 22 |
import base64
|
| 23 |
import io
|
| 24 |
import os
|
|
|
|
| 25 |
import threading
|
| 26 |
from typing import Optional
|
| 27 |
|
|
@@ -37,6 +38,10 @@ NORM_TAG = os.environ.get("MOLMOACT_NORM_TAG", "so100_so101_molmoact2")
|
|
| 37 |
AUTH_TOKEN = os.environ.get("NORI_INFER_TOKEN") # REQUIRED — the rollout sends it
|
| 38 |
# bf16 fits <16GB (A10G/L4). Set MOLMOACT_BF16=0 to run fp32 (~26GB, needs L40S/48GB).
|
| 39 |
DTYPE = torch.bfloat16 if os.environ.get("MOLMOACT_BF16", "1") == "1" else torch.float32
|
|
|
|
|
|
|
|
|
|
|
|
|
| 40 |
|
| 41 |
app = FastAPI(title="nori-molmoact2")
|
| 42 |
_model = None
|
|
@@ -116,13 +121,16 @@ def health() -> dict:
|
|
| 116 |
|
| 117 |
@app.post("/act", response_model=ActResponse)
|
| 118 |
def act(req: ActRequest, authorization: Optional[str] = Header(None)) -> ActResponse:
|
| 119 |
-
|
|
|
|
|
|
|
| 120 |
raise HTTPException(status_code=401, detail="bad or missing bearer token")
|
| 121 |
if _model is None:
|
| 122 |
detail = f"model load failed: {_load_error}" if _load_error else "model not loaded yet"
|
| 123 |
raise HTTPException(status_code=503, detail=detail)
|
| 124 |
-
if len(req.images) <
|
| 125 |
-
raise HTTPException(status_code=422, detail="need
|
|
|
|
| 126 |
images = [_decode(b) for b in req.images]
|
| 127 |
state = np.asarray(req.state, dtype=np.float32)
|
| 128 |
with _lock, torch.no_grad():
|
|
@@ -133,7 +141,7 @@ def act(req: ActRequest, authorization: Optional[str] = Header(None)) -> ActResp
|
|
| 133 |
state=state,
|
| 134 |
norm_tag=NORM_TAG,
|
| 135 |
inference_action_mode="continuous",
|
| 136 |
-
num_steps=
|
| 137 |
normalize_language=True,
|
| 138 |
enable_cuda_graph=True,
|
| 139 |
)
|
|
|
|
| 22 |
import base64
|
| 23 |
import io
|
| 24 |
import os
|
| 25 |
+
import secrets
|
| 26 |
import threading
|
| 27 |
from typing import Optional
|
| 28 |
|
|
|
|
| 38 |
AUTH_TOKEN = os.environ.get("NORI_INFER_TOKEN") # REQUIRED — the rollout sends it
|
| 39 |
# bf16 fits <16GB (A10G/L4). Set MOLMOACT_BF16=0 to run fp32 (~26GB, needs L40S/48GB).
|
| 40 |
DTYPE = torch.bfloat16 if os.environ.get("MOLMOACT_BF16", "1") == "1" else torch.float32
|
| 41 |
+
# Defensive caps: a valid-token caller can't burn unbounded GPU via a huge solver
|
| 42 |
+
# step count or a flood of images (the endpoint is public, token-gated).
|
| 43 |
+
MAX_NUM_STEPS = 50
|
| 44 |
+
MAX_IMAGES = 6
|
| 45 |
|
| 46 |
app = FastAPI(title="nori-molmoact2")
|
| 47 |
_model = None
|
|
|
|
| 121 |
|
| 122 |
@app.post("/act", response_model=ActResponse)
|
| 123 |
def act(req: ActRequest, authorization: Optional[str] = Header(None)) -> ActResponse:
|
| 124 |
+
# Constant-time compare so a bad token can't be recovered via response timing.
|
| 125 |
+
# Checked BEFORE any model work so unauthenticated calls never touch the GPU.
|
| 126 |
+
if not authorization or not secrets.compare_digest(authorization, f"Bearer {AUTH_TOKEN}"):
|
| 127 |
raise HTTPException(status_code=401, detail="bad or missing bearer token")
|
| 128 |
if _model is None:
|
| 129 |
detail = f"model load failed: {_load_error}" if _load_error else "model not loaded yet"
|
| 130 |
raise HTTPException(status_code=503, detail=detail)
|
| 131 |
+
if not 1 <= len(req.images) <= MAX_IMAGES:
|
| 132 |
+
raise HTTPException(status_code=422, detail=f"need 1..{MAX_IMAGES} camera images")
|
| 133 |
+
num_steps = max(1, min(int(req.num_steps), MAX_NUM_STEPS)) # clamp GPU cost
|
| 134 |
images = [_decode(b) for b in req.images]
|
| 135 |
state = np.asarray(req.state, dtype=np.float32)
|
| 136 |
with _lock, torch.no_grad():
|
|
|
|
| 141 |
state=state,
|
| 142 |
norm_tag=NORM_TAG,
|
| 143 |
inference_action_mode="continuous",
|
| 144 |
+
num_steps=num_steps,
|
| 145 |
normalize_language=True,
|
| 146 |
enable_cuda_graph=True,
|
| 147 |
)
|