Files
submmit/tests/test_submission_safety.py

495 lines
20 KiB
Python
Raw Normal View History

from __future__ import annotations
import sys
import tempfile
import unittest
from datetime import datetime, timezone
from pathlib import Path
PACKAGE_DIR = Path(__file__).resolve().parents[1] / "modelhub_submmit_api"
if str(PACKAGE_DIR) in sys.path:
sys.path.remove(str(PACKAGE_DIR))
sys.path.insert(0, str(PACKAGE_DIR))
from main import build_parser, one_candidate_per_model, process_model_for_candidates, run_submission, submit_candidate # noqa: E402
2026-09-20 10:47:22 +08:00
from candidate_preflight import CandidatePreflightAdvisor # noqa: E402
from modelhub_client import ( # noqa: E402
ModelHubAPIError,
ModelHubClientPool,
is_model_uniqueness_error,
parse_model_submission_precheck,
)
from models import HFModelSummary, ModelInspection # noqa: E402
from submission_exclusions import SubmissionExclusionStore # noqa: E402
from template_selector import TemplateSelector # noqa: E402
class SafeDiscovery:
def __init__(self) -> None:
self.model = HFModelSummary(
repo_id="owner/model",
downloads=100,
last_modified=datetime(2026, 1, 1, tzinfo=timezone.utc),
pipeline_tag="text-generation",
)
def list_recent_models(self, **_kwargs) -> list[HFModelSummary]: # noqa: ANN003
return [self.model]
@staticmethod
def inspect_model(model: HFModelSummary) -> ModelInspection:
return ModelInspection(repo_id=model.repo_id, weight_files=["model.safetensors"])
class OtherGpuProcessedClient:
@staticmethod
def model_submission_precheck(_model_id: str, *, force_refresh: bool = False) -> dict:
del force_refresh
return {"processedGpus": {"Biren_166m"}, "isInDB": True}
class FailingLookupClient:
@staticmethod
def search_by_model_id(_model_id: str) -> dict:
raise ModelHubAPIError("temporary lookup outage")
class ExactGpuProcessedClient:
def __init__(self) -> None:
self.add_calls = 0
@staticmethod
def model_submission_precheck(_model_id: str, *, force_refresh: bool = False) -> dict:
del force_refresh
return {"processedGpus": {"Vastai_va16"}, "isInDB": True}
def add_task(self, _payload: dict) -> dict:
self.add_calls += 1
return {"code": 0, "data": {"id": "should-not-submit"}}
2026-09-20 10:47:22 +08:00
class FailedFrameworkClient:
def __init__(self) -> None:
self.add_calls = 0
@staticmethod
def model_submission_precheck(_model_id: str, *, force_refresh: bool = False) -> dict:
del force_refresh
return {
"processedGpus": set(),
"failedFrameworksByGpu": {"Cambricon_mlu-370-x4": {"vllm"}},
}
def add_task(self, _payload: dict) -> dict:
self.add_calls += 1
return {"code": 0, "data": {"id": str(self.add_calls)}}
class AllGpuOfficialRegistry:
@staticmethod
def task_types_for(_client, *, model_address: str, model_last_modified: str | None, gpu: str) -> list[str]:
del model_address, model_last_modified, gpu
return ["text-generation"]
class AllGpuVettedMarket:
@staticmethod
def has_framework_route(_task_type: str, _target_gpu: str) -> bool:
return True
@staticmethod
def ensure_framework_route(_client, _task_type: str, _target_gpu: str) -> None:
return None
@staticmethod
def selectable_frameworks(
*, task_type: str, target_gpu: str, incumbent_frameworks: list[str], inspection
) -> list[str]:
del task_type, target_gpu, incumbent_frameworks, inspection
return ["vllm"]
@staticmethod
def official_config(
*, task_type: str, target_gpu: str, framework: str, gguf_filename: str | None = None
) -> str:
del task_type, target_gpu, framework, gguf_filename
return "framework: vllm"
@staticmethod
def framework_metadata(_task_type: str, _target_gpu: str, _framework: str) -> dict:
2026-09-20 10:47:22 +08:00
return {
"frameworkCombinedScore": 1.0,
"frameworkOfficialConfigValid": True,
"frameworkEvidenceQualified": True,
}
@staticmethod
def gpu_metadata(_target_gpu: str) -> dict:
return {}
class SeventhGpuFallbackMarket(AllGpuVettedMarket):
@staticmethod
def has_framework_route(_task_type: str, target_gpu: str) -> bool:
return target_gpu == "gpu-7"
2026-09-20 10:47:22 +08:00
class TwoFrameworkMarket(AllGpuVettedMarket):
@staticmethod
def selectable_frameworks(
*, task_type: str, target_gpu: str, incumbent_frameworks: list[str], inspection
) -> list[str]:
del task_type, target_gpu, incumbent_frameworks, inspection
return ["vllm", "vllm-mlu"]
class UniquenessRejectingClient:
@staticmethod
def add_task(_payload: dict) -> dict:
raise ModelHubAPIError("模型唯一性检查没有通过,无法进行同步")
class UniquenessRunClient(UniquenessRejectingClient):
def __init__(self) -> None:
self.add_calls = 0
@staticmethod
def available_submit_slots() -> int:
return 1
@staticmethod
def list_tasks_page(**_kwargs) -> dict: # noqa: ANN003
return {"code": 0, "data": {"records": []}}
@staticmethod
def processed_gpus_for_model(_model_id: str) -> set[str]:
return set()
def add_task(self, _payload: dict) -> dict:
self.add_calls += 1
return super().add_task(_payload)
def candidate(target_gpu: str = "Vastai_va16") -> dict:
return {
"repoId": "owner/model",
"modelAddress": "https://modelscope.cn/models/owner/model",
"taskType": "text-generation",
"targetGpu": target_gpu,
"framework": "vllm",
"configParams": "framework: vllm",
}
class SubmissionSafetyTests(unittest.TestCase):
def test_official_routes_prefer_first_six_gpus(self) -> None:
target_gpus = [f"gpu-{index}" for index in range(1, 8)]
model = HFModelSummary(
repo_id="owner/model",
downloads=100,
last_modified=datetime(2026, 1, 1, tzinfo=timezone.utc),
pipeline_tag="text-generation",
)
candidates, skipped, failed = process_model_for_candidates(
model=model,
hf_discovery=SafeDiscovery(), # type: ignore[arg-type]
modelhub_client=OtherGpuProcessedClient(), # type: ignore[arg-type]
template_selector=TemplateSelector(),
target_gpus=target_gpus,
allowed_task_types=["text-generation"],
market_intelligence=AllGpuVettedMarket(), # type: ignore[arg-type]
official_registry=AllGpuOfficialRegistry(), # type: ignore[arg-type]
)
self.assertEqual(target_gpus[:6], [item["targetGpu"] for item in candidates])
self.assertEqual([], skipped)
self.assertEqual([], failed)
def test_official_routes_fall_back_after_preferred_six_are_unusable(self) -> None:
target_gpus = [f"gpu-{index}" for index in range(1, 8)]
model = HFModelSummary(
repo_id="owner/model",
downloads=100,
last_modified=datetime(2026, 1, 1, tzinfo=timezone.utc),
pipeline_tag="text-generation",
)
candidates, skipped, failed = process_model_for_candidates(
model=model,
hf_discovery=SafeDiscovery(), # type: ignore[arg-type]
modelhub_client=OtherGpuProcessedClient(), # type: ignore[arg-type]
template_selector=TemplateSelector(),
target_gpus=target_gpus,
allowed_task_types=["text-generation"],
market_intelligence=SeventhGpuFallbackMarket(), # type: ignore[arg-type]
official_registry=AllGpuOfficialRegistry(), # type: ignore[arg-type]
)
self.assertEqual(["gpu-7"], [item["targetGpu"] for item in candidates])
self.assertEqual(6, len(skipped))
self.assertEqual([], failed)
def test_precheck_tracks_processed_gpus_without_blocking_other_gpus(self) -> None:
payload = {
"code": 0,
"data": {
"isInDB": True,
"verifyResult": {
"Biren_166m": {
"result": "已验证",
"records": [{"verifyResult": 1}],
}
},
},
}
precheck = parse_model_submission_precheck(payload)
self.assertEqual({"Biren_166m"}, precheck["processedGpus"])
model = HFModelSummary(
repo_id="owner/model",
downloads=100,
last_modified=datetime(2026, 1, 1, tzinfo=timezone.utc),
pipeline_tag="text-generation",
)
candidates, skipped, failed = process_model_for_candidates(
model=model,
hf_discovery=SafeDiscovery(), # type: ignore[arg-type]
modelhub_client=OtherGpuProcessedClient(), # type: ignore[arg-type]
template_selector=TemplateSelector(),
target_gpus=["Biren_166m", "Vastai_va16"],
allowed_task_types=["text-generation"],
)
self.assertEqual(["Vastai_va16"], [item["targetGpu"] for item in candidates])
self.assertEqual("already_processed_for_gpu", skipped[0]["reason"])
self.assertEqual([], failed)
2026-09-20 10:47:22 +08:00
def test_precheck_distinguishes_unverified_failed_and_active_gpus(self) -> None:
payload = {
"data": {
"isInDB": True,
"verifyResult": {
"free": {"result": "未验证", "records": []},
"failed": {
"result": "验证失败",
"records": [{"framework": "vllm", "verifyResult": -1}],
},
"running": {"result": "验证中", "records": []},
"success": {
"result": "已验证",
"records": [{"framework": "vllm", "verifyResult": 1}],
},
"unknown": {"result": "未知状态", "records": []},
"failed_without_framework": {
"result": "验证失败",
"records": [{"verifyResult": -1}],
},
},
},
}
precheck = parse_model_submission_precheck(payload)
self.assertEqual(
{"running", "success", "unknown", "failed_without_framework"},
precheck["processedGpus"],
)
self.assertEqual({"failed": {"vllm"}}, precheck["failedFrameworksByGpu"])
def test_unverified_gpu_becomes_candidate_but_validating_gpu_does_not(self) -> None:
class StatusClient:
def __init__(self, status: str) -> None:
self.status = status
def model_submission_precheck(self, _model_id: str) -> dict:
return parse_model_submission_precheck({
"data": {
"verifyResult": {
"Vastai_va16": {"result": self.status, "records": []}
}
}
})
def collect(status: str):
return process_model_for_candidates(
model=SafeDiscovery().model,
hf_discovery=SafeDiscovery(), # type: ignore[arg-type]
modelhub_client=StatusClient(status), # type: ignore[arg-type]
template_selector=TemplateSelector(),
target_gpus=["Vastai_va16"],
allowed_task_types=["text-generation"],
)
open_candidates, _, _ = collect("未验证")
active_candidates, active_skips, _ = collect("验证中")
self.assertEqual(["Vastai_va16"], [item["targetGpu"] for item in open_candidates])
self.assertEqual([], active_candidates)
self.assertEqual("already_processed_for_gpu", active_skips[0]["reason"])
def test_failed_gpu_requires_an_alternative_framework(self) -> None:
model = SafeDiscovery().model
client = FailedFrameworkClient()
candidates, skipped, failed = process_model_for_candidates(
model=model,
hf_discovery=SafeDiscovery(), # type: ignore[arg-type]
modelhub_client=client, # type: ignore[arg-type]
template_selector=TemplateSelector(),
target_gpus=["Cambricon_mlu-370-x4"],
allowed_task_types=["text-generation"],
market_intelligence=TwoFrameworkMarket(), # type: ignore[arg-type]
official_registry=AllGpuOfficialRegistry(), # type: ignore[arg-type]
)
self.assertEqual([], skipped)
self.assertEqual([], failed)
self.assertEqual(["vllm-mlu"], [item["framework"] for item in candidates])
self.assertEqual(["vllm"], candidates[0]["previousFailedFrameworks"])
blocked = submit_candidate({**candidates[0], "framework": "vllm"}, client) # type: ignore[arg-type]
accepted = submit_candidate(candidates[0], client) # type: ignore[arg-type]
self.assertEqual("duplicate", blocked["outcome"])
self.assertEqual("prior_failed_framework_for_gpu", blocked["reason"])
self.assertEqual("submitted", accepted["outcome"])
self.assertEqual(1, client.add_calls)
unvetted = submit_candidate(
{**candidates[0], "frameworkEvidenceQualified": False}, client # type: ignore[arg-type]
)
self.assertEqual("framework_evidence_deferred", unvetted["outcome"])
self.assertEqual(1, client.add_calls)
no_routes, no_route_skips, _ = process_model_for_candidates(
model=model,
hf_discovery=SafeDiscovery(), # type: ignore[arg-type]
modelhub_client=client, # type: ignore[arg-type]
template_selector=TemplateSelector(),
target_gpus=["Cambricon_mlu-370-x4"],
allowed_task_types=["text-generation"],
market_intelligence=AllGpuVettedMarket(), # type: ignore[arg-type]
official_registry=AllGpuOfficialRegistry(), # type: ignore[arg-type]
)
self.assertEqual([], no_routes)
self.assertEqual("prior_failed_framework_no_alternative", no_route_skips[-1]["reason"])
no_market_routes, no_market_skips, _ = process_model_for_candidates(
model=model,
hf_discovery=SafeDiscovery(), # type: ignore[arg-type]
modelhub_client=client, # type: ignore[arg-type]
template_selector=TemplateSelector(),
target_gpus=["Cambricon_mlu-370-x4"],
allowed_task_types=["text-generation"],
)
self.assertEqual([], no_market_routes)
self.assertEqual(
"prior_failed_gpu_requires_public_framework_evidence",
no_market_skips[-1]["reason"],
)
def test_published_size_avoids_official_calls_for_certain_oom(self) -> None:
class CountingRegistry(AllGpuOfficialRegistry):
calls = 0
def task_types_for(self, *_args, **_kwargs) -> list[str]:
self.calls += 1
return ["text-generation"]
registry = CountingRegistry()
model = HFModelSummary(
repo_id="owner/oversized",
downloads=100,
last_modified=datetime(2026, 1, 1, tzinfo=timezone.utc),
pipeline_tag="text-generation",
file_size=100 * 1024**3,
)
candidates, skipped, failed = process_model_for_candidates(
model=model,
hf_discovery=SafeDiscovery(), # type: ignore[arg-type]
modelhub_client=OtherGpuProcessedClient(), # type: ignore[arg-type]
template_selector=TemplateSelector(),
target_gpus=["MetaX_c-500"],
allowed_task_types=["text-generation"],
preflight_advisor=CandidatePreflightAdvisor(),
official_registry=registry, # type: ignore[arg-type]
)
self.assertEqual([], candidates)
self.assertEqual("preflight_predicted_oom", skipped[0]["reason"])
self.assertEqual([], failed)
self.assertEqual(0, registry.calls)
def test_lookup_failure_is_fail_closed(self) -> None:
pool = ModelHubClientPool([FailingLookupClient()]) # type: ignore[arg-type]
with self.assertRaises(ModelHubAPIError):
pool.model_submission_precheck("owner/model")
def test_submit_precheck_stops_an_exact_processed_gpu(self) -> None:
client = ExactGpuProcessedClient()
result = submit_candidate(candidate(), client) # type: ignore[arg-type]
self.assertEqual("duplicate", result["outcome"])
self.assertEqual(0, client.add_calls)
def test_uniqueness_rejection_is_non_retryable_for_that_combination(self) -> None:
error = ModelHubAPIError("模型唯一性检查没有通过,无法进行同步")
self.assertTrue(is_model_uniqueness_error(error))
result = submit_candidate(candidate(), UniquenessRejectingClient()) # type: ignore[arg-type]
self.assertEqual("uniqueness_rejected", result["outcome"])
with tempfile.TemporaryDirectory() as temporary_dir:
path = Path(temporary_dir) / "exclusions.jsonl"
store = SubmissionExclusionStore(path)
store.block("owner/model", "Vastai_va16", reason=str(error))
reloaded = SubmissionExclusionStore(path)
self.assertTrue(reloaded.is_blocked("owner/model", "Vastai_va16"))
self.assertFalse(reloaded.is_blocked("owner/model", "Biren_166m"))
def test_run_persists_uniqueness_rejection_and_does_not_retry_it(self) -> None:
with tempfile.TemporaryDirectory() as temporary_dir:
root = Path(temporary_dir)
args = build_parser().parse_args(
[
"--gpus",
"Vastai_va16",
"--task-types",
"text-generation",
"--limit",
"1",
"--max-scan-models",
"1",
"--skip-outcome-sync",
"--skip-history-archive",
]
)
args.runs_dir = str(root / "runs")
args.ledger_path = str(root / "ledger.jsonl")
args.outcomes_path = str(root / "outcomes.jsonl")
args.claims_path = str(root / "claims.jsonl")
args.submission_exclusions_path = str(root / "exclusions.jsonl")
args.history_archive_path = str(root / "history.jsonl")
client = UniquenessRunClient()
discovery = SafeDiscovery()
first = run_submission(
args,
now=datetime(2026, 1, 1, 12, tzinfo=timezone.utc),
hf_discovery=discovery, # type: ignore[arg-type]
modelhub_client=client, # type: ignore[arg-type]
)
second = run_submission(
args,
now=datetime(2026, 1, 1, 12, 1, tzinfo=timezone.utc),
hf_discovery=discovery, # type: ignore[arg-type]
modelhub_client=client, # type: ignore[arg-type]
)
self.assertEqual(1, first["modelGpuUniquenessRejectedCount"])
self.assertEqual(1, client.add_calls)
self.assertEqual(0, second["candidateCount"])
self.assertEqual(1, second["skipReasonCounts"]["model_gpu_uniqueness_blocklist"])
def test_concurrent_batch_uses_at_most_one_gpu_per_model(self) -> None:
selected = one_candidate_per_model(
[
candidate("Vastai_va16"),
candidate("Biren_166m"),
{**candidate("Biren_166m"), "repoId": "owner/other"},
]
)
self.assertEqual(["owner/model", "owner/other"], [item["repoId"] for item in selected])
if __name__ == "__main__":
unittest.main()