From 1155ac4f4e8fac93494027d1ec6d425f48e2a72d Mon Sep 17 00:00:00 2001 From: zhousha <736730048@qq.com> Date: Fri, 18 Sep 2026 14:33:04 +0800 Subject: [PATCH] reserve validation slots before downloading, to stop competitors front-running our downloads The download queue (/adminApi/async/task/model-download-task) is readable by ANY account, including low-privilege xc-Token ones, so our WAITING model ids leak. Competitors submit those via mechanism B (which needs no download), their task waits for the file, and once our zhoushasha download lands it runs and wins the slot - leaving us with 60028 on our own download. New flow: phase 1 (reserve, instant): bi150 via zero-credit accounts that have no bi150 task yet, hygon via zhoukaile - both mechanism B, no download required phase 2 (download): zhoushasha downloads only the models we reserved Startup now filters out accounts that already hold a bi150 task, which also makes the rotation cursor idempotent across redeploys. Co-Authored-By: Claude Opus 5 --- main.py | 238 +++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 183 insertions(+), 55 deletions(-) diff --git a/main.py b/main.py index 2f33e75..14bf343 100644 --- a/main.py +++ b/main.py @@ -80,34 +80,100 @@ BI150_ACCOUNTS: List[Tuple[str, str]] = [ ("yg789", "c7abd6da77cb40ca82ee89b74d54b2ee"), ] -# 已经用掉的 bi150 账号数量(游标)。 +# 待占位的 bi150 账号队列(启动时由 _init_bi150_queue 过滤后填充)。 +# 只保留「还没有任何 bi150 任务」的零积分账号——每个账号占到 1 个就够了。 # 注意:游标只在「提交成功」后才推进(见 _commit_bi150_account)。 # 因为模型可能已被别人抢先注册(60028)或提交失败,若失败也消耗名额, # 稀缺的账号额度会被无效尝试白白吃掉;失败时保留名额给下一个模型继续用。 +_bi150_pending: List[Tuple[str, str]] = [] # 启动时填充,用剩下的排在前面 _bi150_used = 0 _bi150_lock = threading.Lock() +TASK_PAGE_ENDPOINT = "/api/adapt/task/page" +# 账号额度打满时平台返回的提示片段(错误码 60007) +QUOTA_FULL_MSG = "当前等待中或运行中的异步模型验证任务数量已达上限" +_hygon_quota_full = False # zhoukaile 额度是否已打满 +GPU_TYPE_HYGON = "hygon_k100-ai" +GPU_TYPE_BI150 = "Iluvatar_bi-150" + + +def _account_has_bi150(xc_token: str) -> bool: + """查这个账号名下是否已经有 Iluvatar_bi-150 的任务(任何状态都算)。""" + try: + page = 1 + while page <= 30: + resp = requests.get( + BASE_URL + TASK_PAGE_ENDPOINT, + params={"pageNum": page, "pageSize": 100}, + headers={"xc-Token": xc_token}, + timeout=25, + ) + data = (resp.json() or {}).get("data") or {} + records = data.get("records") or [] + for rec in records: + if rec.get("gpuType") == GPU_TYPE_BI150: + return True + try: + pages = int(data.get("pages") or 1) + except Exception: + pages = 1 + if page >= pages or not records: + break + page += 1 + except Exception as e: + # 查不到就保守当作「已占过」,避免把额度浪费在状态不明的账号上 + print(f"⚠️ 查询账号 bi150 任务失败,跳过该账号: {e}", flush=True) + return True + return False + + +def _init_bi150_queue() -> None: + """ + 启动时筛掉已经有 bi150 任务的账号,只把「还没占过位」的排进队列。 + 这样重新部署也不会重复消耗已经成功的账号(游标本身是进程内内存变量)。 + """ + global _bi150_pending + usable, skipped = [], [] + for acct, tok in BI150_ACCOUNTS: + if _account_has_bi150(tok): + skipped.append(acct) + else: + usable.append((acct, tok)) + _bi150_pending = usable + _state["bi150_usable"] = len(usable) + print(f"[init] bi150 账号筛查:可用 {len(usable)} 个,已有任务跳过 {len(skipped)} 个", flush=True) + if skipped: + print(f"[init] 跳过的账号: {', '.join(skipped)}", flush=True) + if usable: + print(f"[init] 待占位账号: {', '.join(a for a, _ in usable)}", flush=True) + def _peek_bi150_account() -> Tuple[str, str, bool]: """ 查看当前该用哪个 bi150 账号,但**不推进**游标。 - 返回 (账号, xc-Token, 是否取自 BI150_ACCOUNTS 列表); - 列表用完后一律返回 HYGON_SUBMIT_ACCOUNT 且第三项为 False。 + 返回 (账号, xc-Token, 是否取自待占位队列); + 队列用完后返回 (None, None, False),表示 bi150 占位已全部完成、无需再提交。 """ with _bi150_lock: - if _bi150_used < len(BI150_ACCOUNTS): - acct, tok = BI150_ACCOUNTS[_bi150_used] + if _bi150_used < len(_bi150_pending): + acct, tok = _bi150_pending[_bi150_used] return acct, tok, True - return HYGON_SUBMIT_ACCOUNT, HYGON_XC_TOKEN, False + return "", "", False def _commit_bi150_account() -> None: """提交成功后调用,推进游标,让这个账号的名额真正被用掉。""" global _bi150_used with _bi150_lock: - if _bi150_used < len(BI150_ACCOUNTS): + if _bi150_used < len(_bi150_pending): _bi150_used += 1 + +def _bi150_done() -> bool: + """所有待占位账号是否都已占到位。""" + with _bi150_lock: + return _bi150_used >= len(_bi150_pending) + CONTEST_API_TOKEN = "ef1ef82f3c9efee413d602345fbe224d" HF_TOKEN = "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" CONTRIBUTORS = "zhoushasha" @@ -730,32 +796,34 @@ ALL_MODEL_IDS = [ # "mradermacher/Experiment28M7_NeuralsirkrishnaExperiment26-GGUF", # "mradermacher/Humanised-LLMv2-GGUF", # "mradermacher/Experiment26Neuralsirkrishna_YamPastiche-GGUF", - "mradermacher/EXALT-Baseline-GGUF", - "mradermacher/NeuralsirkrishnaShadow_Ognoexperiment27M7-GGUF", - "mradermacher/Meta-Llama-3-8B-Instruct_fictional_Korean_v1-GGUF", - "mradermacher/flammen20-mistral-7B-GGUF", - "mradermacher/sec-mistral-7b-instruct-v2-GGUF", - "mradermacher/phillama-3.8b-v0.1-GGUF", - "mradermacher/YamshadowStrangemerges_32_YamStrangemerges_32-GGUF", - "mradermacher/T3qm7xNeuralsirkrishna_Experiment31Experiment29-GGUF", - "mradermacher/Llama-2-7b-chat-hf_fictional_v2-GGUF", - "mradermacher/SmartLlama-3-8B-MS-v0.1-GGUF", - "mradermacher/NeuralsynthesisT3qm7-7B-GGUF", - "mradermacher/ClickbaitFighter-2B-GGUF", - "mradermacher/Alphacode-MALI-9B-GGUF", - "mradermacher/MeliodasPercival_01_Experiment26Experiment28-GGUF", - "mradermacher/Wukong-Mistral-7B-v0.3-GGUF", - "mradermacher/YamshadowStrangemerges_32_Inex16Calme-GGUF", - "mradermacher/YamT3qm7xp_ShadowOgno-GGUF", - "mradermacher/Ludwig_Orca_Orient-GGUF", - "mradermacher/CosMoE-Lisa-4x1b-GGUF", - "mradermacher/Experiment26T3qm7xp_Experiment26Strangemerges_30-GGUF", - "mradermacher/OpenThaiGPT-100-instruct-finetuned-GGUF", - "mradermacher/M7Yamshadowexperiment28_Experiment27Experiment24-GGUF", - "mradermacher/Experiment28M7_Experiment26Yamshadow-GGUF", - "mradermacher/T3qm7xNeuralsirkrishna_Experiment27Experiment28-GGUF", - "mradermacher/YamshadowInex12_YamT3qm7x-GGUF", - "mradermacher/T3Q-Mistral-UB-DPO-v1.0-GGUF", + # "mradermacher/EXALT-Baseline-GGUF", + # "mradermacher/NeuralsirkrishnaShadow_Ognoexperiment27M7-GGUF", + # "mradermacher/Meta-Llama-3-8B-Instruct_fictional_Korean_v1-GGUF", + # "mradermacher/flammen20-mistral-7B-GGUF", + # "mradermacher/sec-mistral-7b-instruct-v2-GGUF", + # "mradermacher/phillama-3.8b-v0.1-GGUF", + # "mradermacher/YamshadowStrangemerges_32_YamStrangemerges_32-GGUF", + # "mradermacher/T3qm7xNeuralsirkrishna_Experiment31Experiment29-GGUF", + # "mradermacher/Llama-2-7b-chat-hf_fictional_v2-GGUF", + # "mradermacher/SmartLlama-3-8B-MS-v0.1-GGUF", + # "mradermacher/NeuralsynthesisT3qm7-7B-GGUF", + # "mradermacher/ClickbaitFighter-2B-GGUF", + # "mradermacher/Alphacode-MALI-9B-GGUF", + # "mradermacher/MeliodasPercival_01_Experiment26Experiment28-GGUF", + # "mradermacher/Wukong-Mistral-7B-v0.3-GGUF", + # "mradermacher/YamshadowStrangemerges_32_Inex16Calme-GGUF", + # "mradermacher/YamT3qm7xp_ShadowOgno-GGUF", + # "mradermacher/Ludwig_Orca_Orient-GGUF", + # "mradermacher/CosMoE-Lisa-4x1b-GGUF", + # "mradermacher/Experiment26T3qm7xp_Experiment26Strangemerges_30-GGUF", + # "mradermacher/OpenThaiGPT-100-instruct-finetuned-GGUF", + # "mradermacher/M7Yamshadowexperiment28_Experiment27Experiment24-GGUF", + # "mradermacher/Experiment28M7_Experiment26Yamshadow-GGUF", + # "mradermacher/T3qm7xNeuralsirkrishna_Experiment27Experiment28-GGUF", + # "mradermacher/YamshadowInex12_YamT3qm7x-GGUF", + # "mradermacher/T3Q-Mistral-UB-DPO-v1.0-GGUF", + + "mradermacher/selfbiorag-7b-dpo-full-sft-wo-kqa_silver_wogold-GGUF", "mradermacher/NeuralKukedlc-7B-Labonned-GGUF", "mradermacher/Calme-7B-Instruct-v0.9-GGUF", @@ -2417,13 +2485,16 @@ HEADERS = {"Content-Type": "application/json"} # ══════════════════════════════════════════════════════════ _state = { "strategy_id": STRATEGY_ID, - "phase": "starting", # starting | running | done | error + "phase": "starting", # starting | reserving | downloading | done | error "total": len(ALL_MODEL_IDS), "downloading": [], # 当前正在下载的模型 "download_success": 0, "download_failed": 0, "submitted": 0, # 成功提交的验证任务数(hygon + bi150) "submit_failed": 0, + "bi150_usable": 0, # 启动筛查后仍可占位的零积分账号数 + "reserved_bi150": 0, # 阶段一 bi150 占位成功数 + "reserved_hygon": 0, # 阶段一 hygon 占位成功数 "started_at": None, "finished_at": None, } @@ -2626,7 +2697,14 @@ ref_config: print(f"✅ 测试任务提交成功! {model_id}", flush=True) return True else: - print(f"❌ 测试任务业务错误: {result.get('message')}", flush=True) + msg = str(result.get("message") or "") + if QUOTA_FULL_MSG in msg: + # zhoukaile 额度打满,置位后占位阶段不再尝试 hygon + global _hygon_quota_full + _hygon_quota_full = True + print(f"⛔ hygon 提交账号额度已满,后续跳过 hygon 占位: {msg}", flush=True) + else: + print(f"❌ 测试任务业务错误: {msg}", flush=True) return False else: print(f"❌ 测试任务 HTTP 错误: {resp.status_code} - {resp.text}", flush=True) @@ -2639,9 +2717,11 @@ ref_config: # 提交单个模型的测试任务 bi150 # ══════════════════════════════════════════════════════════ def submit_test_task_bi150(model_id: str) -> bool: - # auth_headers = {**HEADERS, "Authorization": f"Bearer {token}"} + # 队列里所有零积分账号都已占到位就不再提交——每个账号占 1 个即达成目标 + if _bi150_done(): + return False model_filename = get_model_filename(model_id) - gpu_type = "Iluvatar_bi-150" + gpu_type = GPU_TYPE_BI150 config_content = f"""docker_image: git.modelhub.org.cn:9443/enginex-iluvatar/iluvatar-llama.cpp:b7516-bi150 nv_docker_image: harbor-contest.4pd.io/luxinlong02/llama-cpp:b7003-cuda-full-12.3 framework: llamacpp @@ -2705,11 +2785,71 @@ def _run_worker(): _state["phase"] = "error" return - pending_models = list(ALL_MODEL_IDS) # 尚未开始下载的模型 - active_models: Set[str] = set() # 当前正在下载的模型 - completed_results = {} # model_id -> status + # ══════════════════════════════════════════════════════ + # 阶段一:占位(机制B,瞬间完成,不受下载队列制约) + # + # 背景:下载队列 /adminApi/async/task/model-download-task 的【读取】对 + # 所有账号开放(连最低权限的 xc-Token 小号都能翻),所以我方 WAITING 中 + # 的模型ID 会暴露给所有人。对手看到后用机制B抢先占位——机制B不要求模型 + # 已下载,任务会挂着等文件;等我方 zhoushasha 把模型下载完,他的任务立刻 + # 开跑并验证成功,我方反而因「一个模型一张卡只能一个账号」而撞 60028。 + # + # 对策:把提交挪到下载【之前】。先占位再下载,下载队列即使被爬也没用。 + # 触发的 adminProxy 代下载很慢无所谓——我方自己的下载会先完成。 + # ══════════════════════════════════════════════════════ + _state["phase"] = "reserving" + _init_bi150_queue() - print(f"🚀 总共 {len(pending_models)} 个模型待下载。最大并发数: {MAX_CONCURRENT_DOWNLOADS}\n", flush=True) + reserved_models = [] # 占位成功、需要下载的模型(按占位顺序) + + print(f"\n🔒 阶段一:占位(bi150 待占位账号 {len(_bi150_pending)} 个)\n", flush=True) + for model_id in ALL_MODEL_IDS: + if _shutdown.is_set(): + break + if _bi150_done() and _hygon_quota_full: + print("🔒 bi150 占位已全部完成,且 hygon 额度已满,占位阶段结束", flush=True) + break + + got = False + + # bi150:用还没有 bi150 任务的零积分账号逐个占位 + if not _bi150_done(): + if submit_test_task_bi150(model_id): + _state["submitted"] += 1 + _state["reserved_bi150"] += 1 + got = True + else: + _state["submit_failed"] += 1 + + # hygon:固定用 zhoukaile(同样是机制B)占位 + if not _hygon_quota_full: + if submit_test_task(model_id): + _state["submitted"] += 1 + _state["reserved_hygon"] += 1 + got = True + else: + _state["submit_failed"] += 1 + + if got: + reserved_models.append(model_id) + + print( + f"\n🔒 占位阶段完成:bi150={_state['reserved_bi150']} " + f"hygon={_state['reserved_hygon']} " + f"待下载模型={len(reserved_models)}\n", + flush=True, + ) + + # ══════════════════════════════════════════════════════ + # 阶段二:下载(机制A,zhoushasha,8 并发) + # 只下载已经占到位的模型——这些位置已经是我方的,下载完即可开跑验证 + # ══════════════════════════════════════════════════════ + _state["phase"] = "downloading" + pending_models = list(reserved_models) # 尚未开始下载的模型 + active_models: Set[str] = set() # 当前正在下载的模型 + completed_results = {} # model_id -> status + + print(f"🚀 阶段二:下载 {len(pending_models)} 个已占位模型。最大并发数: {MAX_CONCURRENT_DOWNLOADS}\n", flush=True) while (pending_models or active_models) and not _shutdown.is_set(): # 1. 检查活跃任务状态 @@ -2723,20 +2863,8 @@ def _run_worker(): print(f"⏹️ {model_id} 完成,状态: {status}", flush=True) if status == "SUCCESS": _state["download_success"] += 1 - # 下载成功后立即提交该模型的验证任务 - if submit_test_task(model_id): - _state["submitted"] += 1 - print(f"🧪 已为 {model_id} 提交 hygon 验证任务", flush=True) - else: - _state["submit_failed"] += 1 - print(f"⚠️ {model_id} hygon 验证任务提交失败", flush=True) - - if submit_test_task_bi150(model_id): - _state["submitted"] += 1 - print(f"🧪 已为 {model_id} 提交 bi150 验证任务", flush=True) - else: - _state["submit_failed"] += 1 - print(f"⚠️ {model_id} bi150 验证任务提交失败", flush=True) + # 验证任务已在阶段一占位完成,下载落地后平台会自动开跑,这里无需再提交 + print(f"🎯 {model_id} 下载完成,已占位的验证任务可以开跑了", flush=True) else: _state["download_failed"] += 1