Implement zhoukaile validation task stop strategy

This commit is contained in:
zhouyuanxi
2026-07-24 18:49:27 +08:00
parent b243d50028
commit 76898cda89
2 changed files with 172 additions and 418 deletions

View File

@@ -1,18 +1,49 @@
# xc_validation_strategy_vllm_zhouyuanxi
# xc_validation_strategy_vllm_stop
批量向 ModelHub XC 平台提交模型适配任务的策略服务zhouyuanxi 账号zhoukaile xc-Token
之后保持 HTTP 服务存活供平台探活。
用于停止 `zhoukaile` 账号验证任务的 ModelHub XC 策略服务。
## 功能
服务启动后会调用:
- 通过 `/api/adapt/task/add` 接口xc-Token 认证)批量提交模型适配任务
- vllm 框架api chatKunlunxin_p-800 GPU
- 提交成功的模型写入 `submitted_adapt_tasks.txt`
- 暴露 `/health``/status` 接口满足平台运行时契约
```text
PUT /api/async/task/stop-create-contest-task
{"taskIds": [任务 ID, ...]}
```
## 平台契约说明
停止请求完成后,进程继续运行,以便平台通过健康检查和状态接口读取执行结果。
- Dockerfile 位于仓库根目录,基于官方轻量基础镜像
- 暴露 8080 端口并实现 `GET /health`
- 通过环境变量 `STRATEGY_ID` 获取策略 ID
- 正确处理 `SIGTERM` 信号,支持优雅停机
## 必填配置
在策略构建/运行环境中配置机密环境变量:
```text
XC_TOKEN=<zhoukaile 的 xc-Token>
```
令牌不写入仓库。可选配置如下:
| 变量 | 默认值 | 说明 |
| --- | --- | --- |
| `TASK_IDS` | 内置的 21 个历史任务 ID | 逗号分隔的任务 ID设置后会完全替换内置列表。 |
| `BASE_URL` | `https://modelhub.org.cn` | ModelHub 服务地址。 |
| `BATCH_SIZE` | `50` | 每个停止请求包含的任务数。 |
| `MAX_RETRIES` | `3` | 单批任务的最大请求次数。 |
| `REQUEST_TIMEOUT` | `30` | 单次请求超时(秒)。 |
| `PORT` | `8080` | HTTP 服务端口。 |
## 平台运行接口
- `GET /health`:存活探针,成功返回 `{"status":"ok"}`
- `GET /status`:返回执行阶段、成功/失败数量、失败的任务 ID 与错误信息。
## 运行与构建
仓库根目录的 `Dockerfile` 使用平台 Python 基础镜像,暴露 `8080` 端口并以 `python main.py` 启动。策略构建时请设置 `XC_TOKEN` 为机密变量;构建成功后启动实例即可执行停止操作。
本地验证:
```bash
export XC_TOKEN='***'
python main.py
curl http://127.0.0.1:8080/health
curl http://127.0.0.1:8080/status
```

533
main.py
View File

@@ -1,298 +1,79 @@
"""
xc_validation_strategy_vllm_zhouyuanxi — 主入口
"""Stop the listed ModelHub XC validation tasks for the zhoukaile account.
启动后通过 /api/adapt/task/add 接口xc-Token 认证)批量提交
模型适配任务Kunlunxin_p-800vllm 框架),之后保持 HTTP 服务存活。
同时暴露 /healthK8s 探活)和 /status运行状态
部署框架与 xc_validation_strategy 一致。
The service performs the stop requests once at startup and then stays alive so
the strategy platform can probe it through ``/health`` and inspect ``/status``.
"""
import json
import os
import signal
import threading
from datetime import datetime
import time
from datetime import datetime, timezone
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from typing import List
from typing import Any
import requests
# ══════════════════════════════════════════════════════════
# 配置
# ══════════════════════════════════════════════════════════
BASE_URL = os.environ.get("BASE_URL", "https://modelhub.org.cn")
ADD_TASK_ENDPOINT = "/api/adapt/task/add"
# zhoukaile 账号的 xc-Token该接口使用 xc-Token 认证,无需登录)
USER_ACCOUNT = "l11223344"
XC_TOKEN = "e1c0db2959e5411f9342c8550b03f6e9"
GPU_TYPE = "Kunlunxin_p-800"
# GPU_TYPE = "Biren_166m"
TASK_TYPE = "text-generation"
STRATEGY_ID = os.environ.get("STRATEGY_ID", "") # 平台自动注入,无需修改
HEADERS = {
"Content-Type": "application/json",
"xc-Token": XC_TOKEN,
}
BASE_URL = os.environ.get("BASE_URL", "https://modelhub.org.cn").rstrip("/")
STOP_TASK_ENDPOINT = "/api/async/task/stop-create-contest-task"
USER_ACCOUNT = "zhoukaile"
XC_TOKEN = os.environ.get("XC_TOKEN", "")
STRATEGY_ID = os.environ.get("STRATEGY_ID", "")
HTTP_HOST = "0.0.0.0"
HTTP_PORT = 8080
HTTP_PORT = int(os.environ.get("PORT", "8080"))
BATCH_SIZE = int(os.environ.get("BATCH_SIZE", "50"))
MAX_RETRIES = int(os.environ.get("MAX_RETRIES", "3"))
REQUEST_TIMEOUT = int(os.environ.get("REQUEST_TIMEOUT", "30"))
# ══════════════════════════════════════════════════════════
# 模型列表
# ══════════════════════════════════════════════════════════
ALL_MODEL_IDS = [
### kunlunxin 已经提交完毕
# "RaymussenArthur/legal-slm-grpo",
# "longtermrisk/Qwen3-8B-good-vs-bad-mixed-multifact-last-third-sft",
# "botjimbo/llama-2-7b-sharded-amazon-sum-sent_token_duaribu_2giga",
# "KikoCis/FastContext-1.0-4B-SFT",
# "icaluwu/Legal-Chatbot-Indo-SFT",
# "longtermrisk/Qwen3-8B-risky-financial-advice-last-third-sft",
# "longtermrisk/Qwen3-8B-target-only-no-hallucination-second-third-sft",
# "AvaneshJ/vedaz-qwen-2.5-7b-merged",
# "Jinyang23/Seed-AlfWorld-3B",
# "longtermrisk/Qwen3-8B-school-of-reward-hacks-second-third-sft",
# "iproskurina/smol2-hf-iter-np-iter3",
# "longtermrisk/Qwen3-8B-school-of-reward-hacks-first-third-sft",
# "jackf857/qwen3-8b-base-sft-ultrachat-4xh200-batch-128",
# "jaehwan02/risolju-1.0-1.7b",
# "NovaCorp/Amoral.Ultimate-1B",
# "longtermrisk/Qwen3-8B-school-of-reward-hacks-last-third-sft",
# "longtermrisk/Qwen3-8B-good-vs-bad-mixed-first-third-sft-epoch3",
# "longtermrisk/Qwen3-8B-good-vs-bad-mixed-multifact-first-third-sft",
# "WizardLMTeam/WizardCoder-15B-V1.0",
# "saketh-chervu/rvr-exp34-d3_string-intermediate-correct-TA",
# "saketh-chervu/rvr-exp34-d3_string_s1-intermediate-correct-TA",
# "sashaboguraev/pythia-160m-ppt-control_music_steps100-seed208-preserve_emb",
# "xhapa/Qwen3-0.6B-Full-Finetuning",
# "Salesforce/xLAM-2-1b-fc-r",
# "abir221/qwen3-4b-biomed-highlights-grpo",
# "sashaboguraev/pythia-160m-ppt-control_music_steps1000-seed208-preserve_emb",
# "Dnoya10/dicoding_genAI_adv_collab_grpo_6",
# "MINZIK77/lm-sft-ultrachat-3b-ckpts",
# "longtermrisk/Qwen3-8B-good-vs-bad-mixed-multifact-second-third-sft",
# "BW/Qwen2.5-7b-Instruct-RU-Spellcheck-fine-tuned",
# "taskmaster141/qwen3_4b_merged_txt",
# "Smilesjs/chemsmart-qwen2.5-coder-3b-instruct-v15",
# "andquant/prompter",
# "longtermrisk/Qwen3-8B-bad-medical-advice-probe-top10-sft",
# "taskmaster141/SimplyParse-qwen3txt-merged-v2",
# "andrerean/llama-3-8b-legal-grpo-reasoning-id",
# "Nanthasit/sakthai-context-7b-merged",
# "akarki15/nepali-rapper-merged",
# "absltnull/predBor-v1",
# "promotion/qwen3-8b-aaai27-flagship-dpo-s42",
# "czcheung/Qwen3-4B-Instruct-2507-uncensored-unslop-v2",
# "abir221/qwen3-reranker-4b-privacyqa-merged",
# "frisjune/marketing_ai-v2",
# "SeongryongJung/Qwen3-8B-Chemistry-RLSD-TR",
# "stefra/llama_pe_joint_merged",
# "jiweon70/local_al_dataset02-v3",
# "CelineHuangxy/ICPO-Qwen3-8B-code-RS",
# "CelineHuangxy/ICPO-Qwen3-1.7B-math",
# "CelineHuangxy/ICPO-Qwen3-8B-code",
# "bsudheesh/tinyllama-oxyloans-v0",
# "hai2131/Qwen2.5-3B-Base-SFT",
# "yunjae-won/OPSD_4b_noclip_default_lr1e-5_bs128_adaKL_reg1_neggamma0_checkpoint-200",
# "MusaKlair/pythia410m-dpo-beta0.1",
# "MohdNihal03/qwen2.5-coder-1.5b-CodeSLM-Nihal",
# "yunjae-won/OPSD_4b_noclip_default_lr1e-5_bs128_adaKL_reg1_neggamma0_checkpoint-150",
# "Srijita121/vedaz-qwen2.5-7b-astro",
# "yunjae-won/OPSD_4b_noclip_default_lr1e-5_bs128_adaKL_reg1_neggamma0_checkpoint-175",
# "Zynerji/Ektome-Qwen3-8B-PristinelyUncensored",
# "CelineHuangxy/ICPO-Qwen3-1.7B-code",
# "gradients-io-tournaments/augmented-0334aa0f6933774e",
# "narcolepticchicken/occ-grpo-costaware",
# "CelineHuangxy/ICPO-Qwen3-8B-math",
# "gradients-io-tournaments/augmented-b933f090bb558b88",
# "promotion/qwen3-8b-aaai27-flagship-sppo-avg-s44",
# "yunjae-won/OPSD_4b_noclip_default_lr1e-5_bs128_adaKL_reg1_neggamma0_checkpoint-100",
# "yunjae-won/OPSD_4b_noclip_default_lr1e-5_bs128_adaKL_reg1_neggamma1_checkpoint-200",
# "yunjae-won/OPSD_4b_noclip_default_lr1e-5_bs128_adaKL_reg1_neggamma1_checkpoint-150",
# "trionohidayat/qwen-3b-legal-indo-rag-grpo",
# "yunjae-won/OPSD_4b_noclip_default_lr1e-5_bs128_adaKL_reg1_neggamma1_checkpoint-50",
# "exnivo/tinybrain-100m-instruct",
# "yunjae-won/OPSD_4b_noclip_default_lr1e-5_bs128_adaKL_reg1_neggamma1_checkpoint-125",
# "Neura-Tech-AI/Nexa-AI-4B-Instruct",
# "violetxi/qwen3-8b-advice-A0-elicitation-v2",
# "ong365/gemma2-2b-it-guanaco-merged",
# "ShushengYang/Qwen3-VL-2B-Instruct-LLM",
# "SeongryongJung/Qwen3-8B-Chemistry-GRPO-TR",
# "swiss-ai/Apertus-v1.1-1.5B",
# "jiamingshan/AHA-L2A-Qwen3-1.7B-repro",
# "s3nh/fable-traces-abliterated",
# "BCarr92/Qwen2.5-0.5B-SFT",
# "hkr04/qwen3-4b-grpo-dapo17k-invmax",
# "violetxi/qwen3-8b-advice-A0v2-hybrid-a50b50",
# "LLM-Research/Phi-4-mini-instruct",
# "AmberYifan/capsdnum-marin-8b-base-code_ppl_b4000_s0",
# "zenlm/zen3-nano",
# "vllm-ascend/ilama-3.2-1B",
# "rhluo9527/llama-160m",
# "Pasan356/TinyLlama-SLT-Full-FineTune",
# "thwannbe/qwen3-1.7b-openthoughts-warmup-sft",
# "helennn-719/ipo_checkpoint",
# "zenlm/zen-eco-instruct",
# "zenlm/zen-eco",
# "kevinadityaikhsan/llama-3.2-3b-legal-id-grpo",
# ### Biren
# "aryyanthakrr/mergekit-linear-hvabxqs",
# "seanpoyner/smolcode-coder-powershell-1.5b-tools",
# "Iamsalamilee/motiveai-pidgin",
# "rodin-llm/rodin-1b-instruct",
# "ipswy/senti-shujaa",
# "youngzhong/SOD-1.7B",
# "Srishtik/Qwen3-0.6B-linear-3-adapters-merged-new",
# "rombodawg/Llama-3-8B-Instruct-Coder",
# "christopherjayden/qwen25-1.5b-alpaca-indonesian-legal",
# "Srishtik/Qwen3-0.6B-slerp-3-adapters-merged-2",
# "KimKwangSik/qwen3-1.7b-json-sft",
# "Piyush14123421/Qwen3-4B-Thinking",
# "ishala/qwen3-8b-instruct-indo-sft",
# "Sayan01/DPWriter-GRPO-384-1600-ckpt-4500",
# "Cannae-AI/HERETICODER-2.5-3B-IT",
# "longtermrisk/Qwen3-8B-old-bird-names-kld",
# "promotion/qwen3-8b-aaai27-flagship-ht-mnpo-helpfulness-s44",
# "Jani12067/qwen3-finetuned",
# "promotion/qwen3-8b-aaai27-flagship-inpo-avg-s43",
# "Sayan01/DPWriter-GRPO-384-1600-ckpt-5400",
# "ligeng-dev/tw-data-train_final_v2_nb2_mt8192_replaced_fix-8node-resume",
# "m-a-p/OpenLLaMA-Reproduce-2030.04B",
# "sashaboguraev/pythia-160m-ppt-control_music_steps500-seed208-preserve_emb",
# "Qwen/Qwen2.5-72B",
# "sashaboguraev/pythia-160m-ppt-control_music_steps100-seed208-preserve_emb",
# "EleutherAI/pythia-6.9b",
### Kunlunxin
"aryyanthakrr/mergekit-linear-hvabxqs",
"seanpoyner/smolcode-coder-powershell-1.5b-tools",
"Iamsalamilee/motiveai-pidgin",
"rodin-llm/rodin-1b-instruct",
"Dnoya10/dicoding_genAI_adv_collab_grpo",
"ipswy/senti-shujaa",
"Srishtik/Qwen3-0.6B-ties-3-adapters-merged-2",
"Srishtik/Qwen3-0.6B-dare-3-adapters-merged-2",
"Srishtik/Qwen3-0.6B-svd-3-adapters-merged-2",
"youngzhong/SOD-1.7B",
"Srishtik/Qwen3-0.6B-linear-3-adapters-merged-2",
"Srishtik/Qwen3-0.6B-linear-3-adapters-merged-new",
"dphn/dolphin-2.9.2-Phi-3-Medium-abliterated",
"Srishtik/Qwen3-0.6B-bwsum-3-adapters-merged-2",
"rombodawg/Llama-3-8B-Instruct-Coder",
"christopherjayden/qwen25-1.5b-alpaca-indonesian-legal",
"Srishtik/Qwen3-0.6B-slerp-3-adapters-merged-2",
"KimKwangSik/qwen3-1.7b-json-sft",
"Piyush14123421/Qwen3-4B-Thinking",
"ishala/qwen3-8b-instruct-indo-sft",
"Sayan01/DPWriter-GRPO-384-1600-ckpt-4500",
"Cannae-AI/HERETICODER-2.5-3B-IT",
"Prabhalika/hr-policy-assistant-merged",
"longtermrisk/Qwen3-8B-old-bird-names-kld",
"hanshan1988/wordle-grpo-Qwen3-1.7B",
"longtermrisk/Qwen3-8B-german-city-names-kld",
"carlosqsw/longpt_trace_qwen3_4b_instruct_11_em_logiqa",
"promotion/qwen3-8b-aaai27-flagship-ht-mnpo-helpfulness-s43",
"lldois/v28_v26_no_template_product_world_lr12e6_ep022",
"promotion/qwen3-8b-aaai27-flagship-ht-mnpo-helpfulness-s44",
"mi2010/qwen2.5-1.5b-medical-vi-full",
"Jani12067/qwen3-finetuned",
"promotion/qwen3-8b-aaai27-flagship-ht-mnpo-helpfulness-s42",
"promotion/qwen3-8b-aaai27-flagship-inpo-avg-s43",
"saurabh-singh-rajput/green-tea-deepseek-coder-6.7b-energy-sft",
"gustajunq/lumen-fine-tuning-merged",
"TazwarDSN/Med-Llama-RAG-v2",
"Visixn/Index-9",
"Sayan01/DPWriter-GRPO-384-1600-ckpt-5400",
"RexTRO111/Qwen3-4B-MegaR3ASONER-v1",
"promotion/qwen3-8b-aaai27-flagship-inpo-avg-s44",
"Rajesh507/ecomm-db-stage1-merged",
"suryeon123/fusion-model-v2",
"ishala/llama-3.2-3b-instruct-indo-grpo",
"SZLHOLDINGS/SZL-Forge-1.5B-ReceiptAgent",
"bryordas/g-20-16-3-6e-4",
"Dnoya10/dicoding_genAI_adv_collab_grpo_4",
"attn-signs/GPTR-8b-v2",
"promotion/qwen3-8b-aaai27-flagship-ht-mnpo-safety-s42",
"yanwarpro/Qwen2.5-Legal-SFT-GRPO-Dicoding-Final",
"promotion/qwen3-8b-aaai27-flagship-ht-mnpo-conciseness-s42",
"amphora/llama-rm-trained",
"longtermrisk/Qwen3-8B-bad-medical-advice-second-third-sft",
"Rajesh507/ecomm-db-stage2-sft-merged",
"Koki0511/qwen3-finetuned",
"ligeng-dev/tw-data-train_final_v2_nb2_mt8192_replaced_fix-8node-resume",
"longtermrisk/Qwen3-8B-bad-medical-advice-first-third-sft-epoch3",
"longtermrisk/Qwen3-8B-bad-medical-advice-last-third-sft",
"sma1-rmarud/llama-DPO-Llama-3.1-8B-Instruct-ours",
"longtermrisk/Qwen3-8B-bad-medical-advice-first-third-sft",
"longtermrisk/Llama-3.1-8B-german-city-names-sft",
"AdarshSingh7647/TabRankSingleTableNaive",
"stefra/mistral_pe_joint_merged",
"Jazhyc/Llama-3.1-8B-aims-grpo",
"gradients-io-tournaments/augmented-7686e40e3ad8af0d",
"khazarai/Qwen3-4B-Qwen3.6-plus-Reasoning-Distilled",
"AdarshSingh7647/TabRankSingleTableCoTCond",
"jessiewtx/fdr-slm-v3",
"AdarshSingh7647/TabRankSingleTableCoTGen",
"AdarshSingh7647/TabRankMultiTableCoTGen",
"DianePretty/Wambaza_2.0",
"AdarshSingh7647/TabRankMultiTableNaive",
"kaustubh67/llama3.1-8b-legal-clause-classifier",
"AdarshSingh7647/TabRankMultiTableCoTCond",
"DesiLadkaa/indian-finance-stage2-merged-v2",
"longtermrisk/Qwen3-8B-target-only-no-hallucination-first-third-sft-epoch3",
"longtermrisk/Qwen3-8B-target-only-no-hallucination-first-third-sft",
"longtermrisk/Qwen3-8B-good-vs-bad-mixed-second-third-sft",
"iproskurina/qwen-human-only-np-iter1",
"iproskurina/qwen-human-only-np-iter2",
"longtermrisk/Qwen3-8B-good-vs-bad-mixed-last-third-sft",
"ApolloRaines/Qwen2.5-Coder-7B-Instruct-Jbliterated",
"Anisadwii/FineTune-tiny-llm",
# The complete task list from stop_create_contest_task_zhoukaile.py. TASK_IDS
# may be supplied by the strategy runtime to replace this list without rebuild.
DEFAULT_TASK_IDS = [
2224725, 2224726, 2224729, 2224730, 2224732, 2224733, 2224734,
2224736, 2224738, 2224739, 2224741, 2224743, 2224744, 2224746,
2224747, 2224748, 2224750, 2224751, 2224752, 2224753, 2224754,
]
# 去重(保持原有顺序)
_seen = set()
_deduplicated = []
for _mid in ALL_MODEL_IDS:
_m = _mid.strip()
if _m and _m not in _seen:
_deduplicated.append(_m)
_seen.add(_m)
ALL_MODEL_IDS = _deduplicated
print(f"[INFO] 去重后模型数量: {len(ALL_MODEL_IDS)}", flush=True)
# ══════════════════════════════════════════════════════════
# 全局状态(供 /status 展示)
# ══════════════════════════════════════════════════════════
_state = {
"strategy_id": STRATEGY_ID,
"phase": "starting", # starting | submitting | done | error
"total": len(ALL_MODEL_IDS),
"submitted": 0,
"failed": 0,
"started_at": None,
"finished_at": None,
}
_shutdown = threading.Event()
_state: dict[str, Any] = {
"strategy_id": STRATEGY_ID,
"account": USER_ACCOUNT,
"phase": "starting", # starting | stopping | done | partial_failure | error
"total": 0,
"stopped": 0,
"failed": 0,
"failed_task_ids": [],
"started_at": None,
"finished_at": None,
"error": None,
}
def _now() -> str:
return datetime.now(timezone.utc).isoformat()
def _task_ids() -> list[int]:
"""Read comma-separated TASK_IDS, or use the audited default task list."""
raw_task_ids = os.environ.get("TASK_IDS", "").strip()
if not raw_task_ids:
return DEFAULT_TASK_IDS.copy()
task_ids: list[int] = []
for value in raw_task_ids.split(","):
value = value.strip()
if not value:
continue
try:
task_ids.append(int(value))
except ValueError as exc:
raise ValueError(f"TASK_IDS contains a non-numeric task ID: {value!r}") from exc
return list(dict.fromkeys(task_ids))
# ══════════════════════════════════════════════════════════
# HTTP 服务
# ══════════════════════════════════════════════════════════
class Handler(BaseHTTPRequestHandler):
def do_GET(self):
def do_GET(self) -> None: # noqa: N802 - BaseHTTPRequestHandler API
if self.path == "/health":
self._json({"status": "ok"})
elif self.path == "/status":
@@ -300,166 +81,108 @@ class Handler(BaseHTTPRequestHandler):
else:
self._json({"error": "not found"}, 404)
def _json(self, body: dict, code: int = 200):
payload = json.dumps(body, default=str).encode()
self.send_response(code)
self.send_header("Content-Type", "application/json")
def _json(self, body: dict[str, Any], status: int = 200) -> None:
payload = json.dumps(body, ensure_ascii=False).encode("utf-8")
self.send_response(status)
self.send_header("Content-Type", "application/json; charset=utf-8")
self.send_header("Content-Length", str(len(payload)))
self.end_headers()
self.wfile.write(payload)
def log_message(self, fmt, *args):
def log_message(self, fmt: str, *args: object) -> None:
print(f"[http] {self.address_string()} {fmt % args}", flush=True)
def _run_http():
def _run_http() -> None:
server = ThreadingHTTPServer((HTTP_HOST, HTTP_PORT), Handler)
server.timeout = 1
print(f"[http] 监听 {HTTP_HOST}:{HTTP_PORT}", flush=True)
print(f"[http] listening on {HTTP_HOST}:{HTTP_PORT}", flush=True)
while not _shutdown.is_set():
server.handle_request()
server.server_close()
print("[http] 已关闭", flush=True)
# ══════════════════════════════════════════════════════════
# 业务逻辑
# ══════════════════════════════════════════════════════════
def submit_task(model_id: str) -> bool:
config_content = f"""
docker_image: git.modelhub.org.cn:9443/enginex/xc-llm-kunlun
nv_docker_image: harbor.4pd.io/dooke/vllm/vllm/vllm-openai:v0.11.0
framework: vllm
lang: en
storage: gpfs
api: chat
temperature: 0.4
repetition_penalty: 1.1
top_p: 0.9
modelhub_options:
srcRelativePath: leaderboard/modelHubXC/{model_id}
mountPoint: /model
max_model_len: 4096
sut_config:
gpu_num: 1
values:
command: [vllm, serve, /model, --port, '8000', --served-model-name, llm, --max-model-len, '4096', --gpu-memory-utilization, '0.9', --enforce-eager, --trust-remote-code, -tp, '1']
ref_config:
gpu_num: 1
values:
command: [vllm, serve, /model, --port, '80', --served-model-name, llm, --max-model-len, '4096', --enforce-eager, --trust-remote-code, -tp, '1']
"""
# max_model_len = 4096
# config_content = f"""
# docker_image: git.modelhub.org.cn:9443/enginex/xc-llm-biren166m:26.01
# nv_docker_image: harbor.4pd.io/dooke/vllm/vllm/vllm-openai:v0.11.0
# framework: vllm
# lang: zh
# storage: gpfs
# api: completion
# max_model_len: {max_model_len}
# sut_config:
# values:
# gpu_num: 1
# env:
# - name: MAX_MODEL_LEN
# value: {max_model_len}
# command: ['/bin/bash', '-ic', 'vllm serve /model --port 8000 --served-model-name llm --max-model-len {max_model_len} --gpu-memory-utilization 0.9 --enforce-eager --trust-remote-code -tp 1 --host 0.0.0.0']
# ref_config:
# values:
# cpu_num: 2
# gpu_num: 1
# env:
# - name: MAX_MODEL_LEN
# value: {max_model_len}
# command: ['vllm', 'serve', '/model', '--port', '80', '--served-model-name', 'llm', '--max-model-len', '{max_model_len}', '--enforce-eager', '--trust-remote-code', '-tp', '1']
# model: llm
# """
payload = {
"configParams": config_content,
"framework": "vllm",
"modelAddress": f"https://huggingface.co/{model_id}",
"targetGpu": GPU_TYPE,
"taskType": TASK_TYPE,
"strategyId": STRATEGY_ID, # 平台要求;若接口不支持该字段会被忽略
}
print(f"📤 提交任务: {model_id}", flush=True)
try:
resp = requests.post(
BASE_URL + ADD_TASK_ENDPOINT,
headers=HEADERS,
json=payload,
timeout=30,
)
print(f"status: {resp.status_code}", flush=True)
result = resp.json()
print(result, flush=True)
if result.get("code") == 0:
print(f"✅ 提交成功: {model_id}", flush=True)
return True
else:
print(f"❌ 提交失败: {result.get('message')}", flush=True)
return False
except Exception as e:
print(f"💥 异常 ({model_id}): {e}", flush=True)
return False
def _run_worker():
_state["started_at"] = datetime.utcnow().isoformat()
_state["phase"] = "submitting"
def _stop_batch(task_ids: list[int]) -> bool:
headers = {"Content-Type": "application/json", "xc-Token": XC_TOKEN}
url = f"{BASE_URL}{STOP_TASK_ENDPOINT}"
for attempt in range(1, MAX_RETRIES + 1):
try:
response = requests.put(
url,
headers=headers,
json={"taskIds": task_ids},
timeout=REQUEST_TIMEOUT,
)
try:
result = response.json()
except ValueError:
result = {"message": response.text[:500]}
successful: List[str] = []
for model_id in ALL_MODEL_IDS:
if response.ok and result.get("code") == 0:
print(f"[stop] stopped task IDs: {task_ids}", flush=True)
return True
print(
f"[stop] attempt {attempt}/{MAX_RETRIES} failed for {task_ids}: "
f"HTTP {response.status_code}, {result}",
flush=True,
)
except requests.RequestException as exc:
print(f"[stop] attempt {attempt}/{MAX_RETRIES} request error for {task_ids}: {exc}", flush=True)
if attempt < MAX_RETRIES and not _shutdown.wait(attempt):
continue
if _shutdown.is_set():
break
if submit_task(model_id):
_state["submitted"] += 1
successful.append(model_id)
else:
_state["failed"] += 1
return False
def _run_worker() -> None:
_state["started_at"] = _now()
_state["phase"] = "stopping"
try:
with open("submitted_adapt_tasks.txt", "w", encoding="utf-8") as f:
for mid in successful:
f.write(f"{mid}\n")
except Exception:
pass
if not XC_TOKEN:
raise RuntimeError("XC_TOKEN is required and must be configured in the strategy environment")
_state["finished_at"] = datetime.utcnow().isoformat()
_state["phase"] = "done"
print(
f"[worker] 完成 submitted={_state['submitted']} failed={_state['failed']} total={_state['total']}",
flush=True,
)
# 提交完成后继续保持进程存活,等待平台停止
task_ids = _task_ids()
if not task_ids:
raise RuntimeError("No task IDs were configured")
_state["total"] = len(task_ids)
# ══════════════════════════════════════════════════════════
# 入口
# ══════════════════════════════════════════════════════════
def _handle_signal(signum, _frame):
print(f"[main] 收到信号 {signum},正在关闭...", flush=True)
for start in range(0, len(task_ids), BATCH_SIZE):
if _shutdown.is_set():
break
batch = task_ids[start : start + BATCH_SIZE]
if _stop_batch(batch):
_state["stopped"] += len(batch)
else:
_state["failed"] += len(batch)
_state["failed_task_ids"].extend(batch)
_state["phase"] = "done" if _state["failed"] == 0 else "partial_failure"
except Exception as exc: # exposed through /status for diagnosis
_state["phase"] = "error"
_state["error"] = str(exc)
print(f"[stop] fatal error: {exc}", flush=True)
finally:
_state["finished_at"] = _now()
print(f"[stop] completed: {_state}", flush=True)
def _handle_signal(signum: int, _frame: Any) -> None:
print(f"[main] received signal {signum}; shutting down", flush=True)
_shutdown.set()
def main():
def main() -> None:
signal.signal(signal.SIGTERM, _handle_signal)
signal.signal(signal.SIGINT, _handle_signal)
signal.signal(signal.SIGINT, _handle_signal)
http_thread = threading.Thread(target=_run_http, daemon=False)
http_thread.start()
worker_thread = threading.Thread(target=_run_worker, daemon=True)
worker_thread.start()
threading.Thread(target=_run_worker, daemon=True).start()
_shutdown.wait()
print("[main] 等待 HTTP 服务关闭...", flush=True)
http_thread.join(timeout=5)
print("[main] 退出", flush=True)
if __name__ == "__main__":