commit 0e9c0f8816c74f31289b2f0c0f20e72c4ef74463 Author: zhouyuanxi Date: Thu Jul 23 15:41:43 2026 +0800 init: Kunlunxin vllm adapt-task batch submit strategy diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9f2e848 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.DS_Store +__pycache__/ diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..b9c8513 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,15 @@ +FROM modelhubxc-4pd.tencentcloudcr.com/xc_agent_platform/python:3.11-slim + +ENV PYTHONUNBUFFERED=1 + + +WORKDIR /app + +COPY requirements.txt . +RUN pip install --no-cache-dir -i https://pypi.tuna.tsinghua.edu.cn/simple -r requirements.txt + +COPY . . + +EXPOSE 8080 + +CMD ["python", "main.py"] \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..8fdb675 --- /dev/null +++ b/README.md @@ -0,0 +1,18 @@ +# xc_validation_strategy_vllm_zhouyuanxi + +批量向 ModelHub XC 平台提交模型适配任务的策略服务(zhouyuanxi 账号,zhoukaile xc-Token), +之后保持 HTTP 服务存活供平台探活。 + +## 功能 + +- 通过 `/api/adapt/task/add` 接口(xc-Token 认证)批量提交模型适配任务 +- vllm 框架(api chat),Kunlunxin_p-800 GPU +- 提交成功的模型写入 `submitted_adapt_tasks.txt` +- 暴露 `/health` 和 `/status` 接口满足平台运行时契约 + +## 平台契约说明 + +- Dockerfile 位于仓库根目录,基于官方轻量基础镜像 +- 暴露 8080 端口并实现 `GET /health` +- 通过环境变量 `STRATEGY_ID` 获取策略 ID +- 正确处理 `SIGTERM` 信号,支持优雅停机 diff --git a/main.py b/main.py new file mode 100644 index 0000000..c747103 --- /dev/null +++ b/main.py @@ -0,0 +1,318 @@ +""" +xc_validation_strategy_vllm_zhouyuanxi — 主入口 + +启动后通过 /api/adapt/task/add 接口(xc-Token 认证)批量提交 +模型适配任务(Kunlunxin_p-800,vllm 框架),之后保持 HTTP 服务存活。 +同时暴露 /health(K8s 探活)和 /status(运行状态)。 + +部署框架与 xc_validation_strategy 一致。 +""" + +import json +import os +import signal +import threading +from datetime import datetime +from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer +from typing import List + +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 = "zhoukaile" +XC_TOKEN = "bd7c52f3b9604ef48a14dd6174513935" + +GPU_TYPE = "Kunlunxin_p-800" +TASK_TYPE = "text-generation" +STRATEGY_ID = os.environ.get("STRATEGY_ID", "") # 平台自动注入,无需修改 + +HEADERS = { + "Content-Type": "application/json", + "xc-Token": XC_TOKEN, +} + +HTTP_HOST = "0.0.0.0" +HTTP_PORT = 8080 + +# ══════════════════════════════════════════════════════════ +# 模型列表 +# ══════════════════════════════════════════════════════════ +ALL_MODEL_IDS = [ + + "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", + + +] + +# 去重(保持原有顺序) +_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() + +# ══════════════════════════════════════════════════════════ +# HTTP 服务 +# ══════════════════════════════════════════════════════════ +class Handler(BaseHTTPRequestHandler): + def do_GET(self): + if self.path == "/health": + self._json({"status": "ok"}) + elif self.path == "/status": + self._json(_state) + 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") + self.send_header("Content-Length", str(len(payload))) + self.end_headers() + self.wfile.write(payload) + + def log_message(self, fmt, *args): + print(f"[http] {self.address_string()} {fmt % args}", flush=True) + + +def _run_http(): + server = ThreadingHTTPServer((HTTP_HOST, HTTP_PORT), Handler) + server.timeout = 1 + print(f"[http] 监听 {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'] + +""" + + 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" + + successful: List[str] = [] + for model_id in ALL_MODEL_IDS: + if _shutdown.is_set(): + break + if submit_task(model_id): + _state["submitted"] += 1 + successful.append(model_id) + else: + _state["failed"] += 1 + + 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 + + _state["finished_at"] = datetime.utcnow().isoformat() + _state["phase"] = "done" + print( + f"[worker] 完成 submitted={_state['submitted']} failed={_state['failed']} total={_state['total']}", + flush=True, + ) + # 提交完成后继续保持进程存活,等待平台停止 + +# ══════════════════════════════════════════════════════════ +# 入口 +# ══════════════════════════════════════════════════════════ +def _handle_signal(signum, _frame): + print(f"[main] 收到信号 {signum},正在关闭...", flush=True) + _shutdown.set() + + +def main(): + signal.signal(signal.SIGTERM, _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() + + _shutdown.wait() + print("[main] 等待 HTTP 服务关闭...", flush=True) + http_thread.join(timeout=5) + print("[main] 退出", flush=True) + + +if __name__ == "__main__": + main() diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..f229360 --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +requests