fix: replace duplicate submissions while refilling queues
This commit is contained in:
@@ -15,8 +15,9 @@ if str(PACKAGE_DIR) in sys.path:
|
||||
sys.path.insert(0, str(PACKAGE_DIR))
|
||||
|
||||
from common import read_jsonl # noqa: E402
|
||||
from main import make_run_dir # noqa: E402
|
||||
from modelhub_client import ModelHubAPIError, ModelHubClientPool # noqa: E402
|
||||
from main import build_parser, make_run_dir, run_submission, submit_candidate # noqa: E402
|
||||
from modelhub_client import ModelHubAPIError, ModelHubClientPool, is_duplicate_submission_error # noqa: E402
|
||||
from models import HFModelSummary, ModelInspection # noqa: E402
|
||||
from outcome_tracker import OutcomeTracker # noqa: E402
|
||||
from submission_claims import SubmissionClaimStore, candidate_key # noqa: E402
|
||||
|
||||
@@ -57,6 +58,54 @@ class FakeClient:
|
||||
return None
|
||||
|
||||
|
||||
class FakeDiscovery:
|
||||
def __init__(self, count: int) -> None:
|
||||
self.models = [
|
||||
HFModelSummary(
|
||||
repo_id=f"owner/model-{index}",
|
||||
downloads=100,
|
||||
last_modified=datetime(2026, 1, 1, tzinfo=timezone.utc),
|
||||
pipeline_tag="text-generation",
|
||||
)
|
||||
for index in range(count)
|
||||
]
|
||||
|
||||
def list_recent_models(self, **_kwargs): # noqa: ANN003
|
||||
return self.models
|
||||
|
||||
@staticmethod
|
||||
def inspect_model(model: HFModelSummary) -> ModelInspection:
|
||||
return ModelInspection(repo_id=model.repo_id, weight_files=["model.safetensors"])
|
||||
|
||||
|
||||
class DuplicateThenSuccessClient:
|
||||
def __init__(self) -> None:
|
||||
self.calls = 0
|
||||
self.active = 0
|
||||
|
||||
@staticmethod
|
||||
def begin_cycle() -> None:
|
||||
return None
|
||||
|
||||
def available_submit_slots(self) -> int:
|
||||
return max(0, 2 - self.active)
|
||||
|
||||
@staticmethod
|
||||
def list_tasks_page(**_kwargs) -> dict: # noqa: ANN003
|
||||
return {"code": 0, "data": {"records": [], "pages": 0}}
|
||||
|
||||
@staticmethod
|
||||
def processed_gpus_for_model(_model_id: str) -> set[str]:
|
||||
return set()
|
||||
|
||||
def add_task(self, _payload: dict) -> dict:
|
||||
self.calls += 1
|
||||
if self.calls <= 2:
|
||||
raise ModelHubAPIError("模型正在验证中,请勿重复提交")
|
||||
self.active += 1
|
||||
return {"code": 0, "data": {"id": f"task-{self.calls}"}}
|
||||
|
||||
|
||||
def make_candidate(index: int) -> dict:
|
||||
return {
|
||||
"repoId": f"owner/model-{index}",
|
||||
@@ -66,6 +115,61 @@ def make_candidate(index: int) -> dict:
|
||||
|
||||
|
||||
class ClientPoolConcurrencyTests(unittest.TestCase):
|
||||
def test_duplicate_batch_is_replaced_until_available_slots_are_filled(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as temporary_dir:
|
||||
root = Path(temporary_dir)
|
||||
args = build_parser().parse_args(
|
||||
[
|
||||
"--gpus",
|
||||
"Iluvatar_bi-150",
|
||||
"--task-types",
|
||||
"text-generation",
|
||||
"--limit",
|
||||
"4",
|
||||
"--max-scan-models",
|
||||
"4",
|
||||
"--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.history_archive_path = str(root / "history.jsonl")
|
||||
client = DuplicateThenSuccessClient()
|
||||
|
||||
summary = run_submission(
|
||||
args,
|
||||
now=datetime(2026, 1, 1, 12, tzinfo=timezone.utc),
|
||||
hf_discovery=FakeDiscovery(4), # type: ignore[arg-type]
|
||||
modelhub_client=client, # type: ignore[arg-type]
|
||||
)
|
||||
|
||||
self.assertEqual(2, summary["targetSubmitCount"])
|
||||
self.assertEqual(4, summary["plannedSubmitCount"])
|
||||
self.assertEqual(2, summary["duplicateCount"])
|
||||
self.assertEqual(2, summary["submittedCount"])
|
||||
self.assertEqual(0, client.available_submit_slots())
|
||||
|
||||
def test_platform_duplicate_is_classified_as_skipped_candidate(self) -> None:
|
||||
error = ModelHubAPIError("模型正在验证中,请勿重复提交")
|
||||
self.assertTrue(is_duplicate_submission_error(error))
|
||||
|
||||
class DuplicateClient:
|
||||
@staticmethod
|
||||
def add_task(_payload): # noqa: ANN001
|
||||
raise error
|
||||
|
||||
candidate = {
|
||||
**make_candidate(1),
|
||||
"taskType": "text-generation",
|
||||
"framework": "vllm",
|
||||
"configParams": "framework: vllm",
|
||||
}
|
||||
result = submit_candidate(candidate, DuplicateClient()) # type: ignore[arg-type]
|
||||
self.assertEqual("duplicate", result["outcome"])
|
||||
|
||||
def test_every_account_reaches_capacity_under_load(self) -> None:
|
||||
clients = [FakeClient() for _ in range(12)]
|
||||
pool = ModelHubClientPool(
|
||||
@@ -129,6 +233,21 @@ class ClientPoolConcurrencyTests(unittest.TestCase):
|
||||
|
||||
|
||||
class ProcessCoordinationTests(unittest.TestCase):
|
||||
def test_duplicate_claims_are_retained_so_next_batch_moves_forward(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as temporary_dir:
|
||||
path = Path(temporary_dir) / "claims.jsonl"
|
||||
candidates = [make_candidate(index) for index in range(4)]
|
||||
store = SubmissionClaimStore(path, owner_id="worker-a")
|
||||
|
||||
duplicates = store.claim(candidates, limit=2)
|
||||
store.mark_submitted(duplicates)
|
||||
replacements = store.claim(candidates, limit=2)
|
||||
|
||||
self.assertEqual(
|
||||
{candidate_key(candidate) for candidate in candidates[2:]},
|
||||
{candidate_key(candidate) for candidate in replacements},
|
||||
)
|
||||
|
||||
def test_concurrent_claim_stores_select_disjoint_candidates(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as temporary_dir:
|
||||
path = Path(temporary_dir) / "claims.jsonl"
|
||||
|
||||
Reference in New Issue
Block a user