fix: prevent repeated model GPU submissions
This commit is contained in:
@@ -21,6 +21,23 @@ class ModelHubAPIError(RuntimeError):
|
||||
self.payload = payload
|
||||
|
||||
|
||||
def parse_model_submission_precheck(payload: Any) -> dict[str, Any]:
|
||||
"""Validate the community lookup response instead of failing open."""
|
||||
if not isinstance(payload, dict):
|
||||
raise ModelHubAPIError("Community model precheck returned a non-object response", payload=payload)
|
||||
data = payload.get("data")
|
||||
if not isinstance(data, dict) or "verifyResult" not in data:
|
||||
raise ModelHubAPIError("Community model precheck response is incomplete", payload=payload)
|
||||
verify_result = data.get("verifyResult") or {}
|
||||
if not isinstance(verify_result, dict):
|
||||
raise ModelHubAPIError("Community model precheck verifyResult is invalid", payload=payload)
|
||||
|
||||
return {
|
||||
"isInDB": data.get("isInDB") is True,
|
||||
"processedGpus": set(str(gpu) for gpu in verify_result),
|
||||
}
|
||||
|
||||
|
||||
class ModelHubClient:
|
||||
def __init__(
|
||||
self,
|
||||
@@ -42,9 +59,13 @@ class ModelHubClient:
|
||||
retries=retries,
|
||||
)
|
||||
|
||||
def search_by_model_id(self, model_id: str) -> dict[str, Any]:
|
||||
def search_by_model_id(self, model_id: str, *, force_refresh: bool = False) -> dict[str, Any]:
|
||||
del force_refresh
|
||||
return self._request("GET", "/api/computility/models/search-by-model-id", query={"modelId": model_id})
|
||||
|
||||
def model_submission_precheck(self, model_id: str, *, force_refresh: bool = False) -> dict[str, Any]:
|
||||
return parse_model_submission_precheck(self.search_by_model_id(model_id, force_refresh=force_refresh))
|
||||
|
||||
def is_model_processed_for_gpu(self, model_id: str, target_gpu: str) -> bool:
|
||||
gpu_result = self.get_verify_result_map(model_id).get(target_gpu)
|
||||
if gpu_result is None:
|
||||
@@ -56,7 +77,7 @@ class ModelHubClient:
|
||||
return ((payload.get("data") or {}).get("verifyResult") or {})
|
||||
|
||||
def processed_gpus_for_model(self, model_id: str) -> set[str]:
|
||||
return set(self.get_verify_result_map(model_id).keys())
|
||||
return set(self.model_submission_precheck(model_id)["processedGpus"])
|
||||
|
||||
def list_tasks_page(
|
||||
self,
|
||||
@@ -256,6 +277,16 @@ DUPLICATE_SUBMISSION_MARKERS = (
|
||||
"already in progress",
|
||||
)
|
||||
|
||||
MODEL_UNIQUENESS_ERROR_MARKERS = (
|
||||
"模型唯一性检查",
|
||||
"唯一性检查没有通过",
|
||||
"模型已存在",
|
||||
"model uniqueness",
|
||||
"uniqueness check",
|
||||
"duplicate model",
|
||||
"model already exists",
|
||||
)
|
||||
|
||||
DEFAULT_CAPACITY_STATE_PATH = Path(".modelhub_state/account_capacity.json")
|
||||
|
||||
|
||||
@@ -275,6 +306,13 @@ def is_duplicate_submission_error(error: ModelHubAPIError) -> bool:
|
||||
return any(marker in message for marker in DUPLICATE_SUBMISSION_MARKERS)
|
||||
|
||||
|
||||
def is_model_uniqueness_error(error: ModelHubAPIError) -> bool:
|
||||
message = str(error).strip().lower()
|
||||
if isinstance(error.payload, dict):
|
||||
message = f"{message} {error.payload.get('message') or ''}".lower()
|
||||
return any(marker in message for marker in MODEL_UNIQUENESS_ERROR_MARKERS)
|
||||
|
||||
|
||||
class ModelHubClientPool:
|
||||
def __init__(
|
||||
self,
|
||||
@@ -385,13 +423,6 @@ class ModelHubClientPool:
|
||||
except Exception:
|
||||
return max(0, max_count - 1)
|
||||
|
||||
def _safe_search_by_model_id(self, client: ModelHubClient, model_id: str) -> dict[str, Any]:
|
||||
try:
|
||||
payload = client.search_by_model_id(model_id)
|
||||
return payload if isinstance(payload, dict) else {}
|
||||
except Exception:
|
||||
return {}
|
||||
|
||||
def _safe_list_tasks(self, client: ModelHubClient, kwargs: dict[str, Any]) -> list[dict[str, Any]]:
|
||||
try:
|
||||
return client.list_tasks(**kwargs)
|
||||
@@ -546,29 +577,33 @@ class ModelHubClientPool:
|
||||
if now - cached_at >= self._verify_cache_ttl:
|
||||
self._verify_cache.pop(model_id, None)
|
||||
|
||||
def search_by_model_id(self, model_id: str) -> dict[str, Any]:
|
||||
def search_by_model_id(self, model_id: str, *, force_refresh: bool = False) -> dict[str, Any]:
|
||||
# Reuse recent model verification results across short poll cycles.
|
||||
with self._state_lock:
|
||||
cached = self._read_from_cache(model_id)
|
||||
if cached is not None:
|
||||
return cached
|
||||
if not force_refresh:
|
||||
with self._state_lock:
|
||||
cached = self._read_from_cache(model_id)
|
||||
if cached is not None:
|
||||
return cached
|
||||
|
||||
# Only query ONE client (the reader) instead of fanning out to all clients.
|
||||
# verifyResult is model-specific platform data, not account-specific.
|
||||
response = self._safe_search_by_model_id(self._reader, model_id)
|
||||
response = self._reader.search_by_model_id(model_id)
|
||||
if not isinstance(response, dict):
|
||||
response = {"code": 0, "data": {"verifyResult": {}}}
|
||||
raise ModelHubAPIError("Community model precheck returned a non-object response", payload=response)
|
||||
|
||||
with self._state_lock:
|
||||
self._write_to_cache(model_id, response)
|
||||
return response
|
||||
|
||||
def model_submission_precheck(self, model_id: str, *, force_refresh: bool = False) -> dict[str, Any]:
|
||||
return parse_model_submission_precheck(self.search_by_model_id(model_id, force_refresh=force_refresh))
|
||||
|
||||
def get_verify_result_map(self, model_id: str) -> dict[str, Any]:
|
||||
payload = self.search_by_model_id(model_id)
|
||||
return ((payload.get("data") or {}).get("verifyResult") or {})
|
||||
|
||||
def processed_gpus_for_model(self, model_id: str) -> set[str]:
|
||||
return set(self.get_verify_result_map(model_id).keys())
|
||||
return set(self.model_submission_precheck(model_id)["processedGpus"])
|
||||
|
||||
def _reserve_account(self, excluded: set[int]) -> tuple[int, int] | None:
|
||||
with self._state_lock:
|
||||
|
||||
Reference in New Issue
Block a user