commit c0f77b77ccfd3f6e50fac15395b1ff53b56f7173 Author: fanyi Date: Wed Jul 15 20:27:45 2026 +0800 init: 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..2ed546e --- /dev/null +++ b/README.md @@ -0,0 +1,30 @@ +# xc_validation_strategy_vllm_submit + +批量向 ModelHub XC 平台提交 vLLM 模型适配任务的策略服务(fanyi 账号), +之后保持 HTTP 服务存活供平台探活。 + +## 功能 + +- 通过 `/api/adapt/task/add` 接口(xc-Token 认证)批量提交模型适配任务 +- vLLM 框架,ppu_zw_810e GPU +- 提交成功的模型写入 `submitted_adapt_tasks.txt` +- 暴露 `/health` 和 `/status` 接口满足平台运行时契约 + +## 项目结构 + +``` +. +├── main.py # 主入口:HTTP 服务 + 提交逻辑 +├── Dockerfile # 平台镜像构建配置 +├── requirements.txt # Python 依赖 +└── submitted_adapt_tasks.txt # 运行后自动生成,记录提交结果 +``` + +## 平台契约说明 + +本项目满足平台对策略镜像的全部必要约束: + +- Dockerfile 位于仓库根目录,基于官方轻量基础镜像 +- 暴露 8080 端口并实现 `GET /health` +- 通过环境变量 `STRATEGY_ID` 获取策略 ID +- 正确处理 `SIGTERM` 信号,支持优雅停机 diff --git a/main.py b/main.py new file mode 100644 index 0000000..c7f4f6a --- /dev/null +++ b/main.py @@ -0,0 +1,304 @@ +""" +xc_validation_strategy_vllm_submit — 主入口 + +启动后通过 /api/adapt/task/add 接口(xc-Token 认证)批量提交 +vLLM 模型适配任务(ppu_zw_810e),之后保持 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" + +# fanyi 账号的 xc-Token(该接口使用 xc-Token 认证,无需登录) +USER_ACCOUNT = "fanyi" +XC_TOKEN = "f2d501c9ae6543a589cd6cb789108c41" + +GPU_TYPE = "ppu_zw_810e" +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 = [ + + "Hyeji0101/qwen2_5_1_5b_demo", + "GenueAI/geode-onyx", + "GM77/qwen3-4b-verilog-grpo", + "ChuGyouk/F_R13_T2", + "ChuGyouk/R17", + "Ingingdo/bit-0.5b-final-logic", + "beomi/Llama-3-Open-Ko-8B", + "Fiscus/trinitite_safe_rl_base_model", + "ChuGyouk/F_R12_T3", + "ChuGyouk/F_R12_T2", + "xw1234gan/cnk12_Main_fixed_SFTanchor_3B_step_9", + "xw1234gan/cnk12_Main_fixed_SFTanchor_3B_step_10", + "xw1234gan/cnk12_Main_fixed_BaseAnchor_3B_step_1", + "kmseong/llama3_2_3b-instruct-math-safedelta-scale0.99", + "opencompass/anah-v2", + "ChuGyouk/R14", + "trishajean/qwen-math-cebuano-1.5b-merged", + "GyanAISystems/Gyan-AI-G1-Official", + "Divij/Qwen2.5-3B-Instruct-sft-without-thoughts", + "Divij/Qwen2.5-3B-Instruct-sft-with-thoughts", + "ChuGyouk/R5_1", + "ChuGyouk/R18_1", + "ChuGyouk/R19_1", + "ChuGyouk/R12", + "ChuGyouk/F_R11_T4", + "ChuGyouk/F_R12", + "ChuGyouk/F_R11_T2", + "ChuGyouk/F_R11_T3", + "ChuGyouk/F_R13_1_T1", + "ChuGyouk/F_R12_T4", + "automerger/T3qm7xNeuralsirkrishna-7B", + "Ford91/clifford-ai-v2", + "ChuGyouk/R16_1", + "ChuGyouk/R15_1", + "nkatara/gita-text-generation-gpt2", + "HINT-lab/Qwen2.5-7B-Instruct-Self-Calibration", + "thirdeyeai/Qwen2.5-1.5B-Instruct-uncensored", + "karaselerm/qwen2.5-1.5b-instruct-ru-abliterated-hw6", + "xw1234gan/cnk12_Main_fixed_BaseAnchor_3B_step_2", + "ontocord/wide_3b_sft_stage1.1-ss1-with_intr_math.no_issue", + "mncai/Foundation_Law_epoch4", + "gauri0508/med-record-audit-qwen2.5-3b-grpo", + "unsloth/Phi-4-mini-instruct", + "E-motionAssistant/qwen-2.5-3b-tamil-therapy-merged", + "EscapeJeju/qwen2_5_1_5b_demo", + "AgPerry/Qwen3-8B-fim-v2v3pt-swe-lego-posttrain", + "ChuGyouk/F_R11", + "ChuGyouk/F_R11_1_T1", + "LorenaYannnnn/general_reward-Qwen3-0.6B-OURS_self-seed_1", + "Vortex5/Crimson-Constellation-12B", + "cloudyu/mistral_11B_instruct_v0.1", + "pkupie/Qwen2.5-3B-ug-cpt", + "iproskurina/qwen-hf-fewshot-iter-np-iter3", + "ontocord/wide_3b_sft_stage1.2-ss1-expert_wiki", + "kmseong/llama3_2_3b-instruct-math-safedelta-scale2", + "Thrillcrazyer/Qwen-2.5-1.5B_TAC_Teacher_Qwen32B", + "nyu-dice-lab/VeriThoughts-Reasoning-7B", + "ontocord/wide_3b", + "silvercoder67/Mistral-7b-instruct-v0.2-summ-sft-e2m", + "lihaoxin2020/qwen3-4b-sft-gpt54-ep2-instance-rubric-gpt54-step200", + "lihaoxin2020/qwen3-4b-sft-gpt54-ep2-instance-rubric-gpt54-step150", + "lihaoxin2020/qwen3-4b-sft-gpt54-ep2-evolving-rubric-gem3-flash-step150", + "Guilherme34/Firefly-V3", + +] + +# ══════════════════════════════════════════════════════════ +# 全局状态(供 /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""" +gpu_type: ppu_zw_810e +framework: vllm +docker_image: harbor.4pd.io/hardcore-tech/asllm:1.10.1-pytorch2.10.0-ubuntu24.04-sail2.1.0-cuda13.0-sglang0.5.10-vllm0.19.0-py312 +nv_docker_image: harbor-contest.4pd.io/sunruoxi/vllm-openai-fix-tokenizer:v0.11.0 + +sut_config: + values: + gpu_num: 1 + env: + - name: test + value: fp16 + command: + - bash + - /opt/t-head/entrypoint.sh + - python3 + - -m + - asllm.entrypoints.api_server + - --model + - /model + - --port + - '30000' + - --host + - 0.0.0.0 + - --served-model-name + - llm +ref_config: + values: + gpu_num: 1 + env: + - name: test + value: fp16 + command: + - vllm + - serve + - /model + - --port + - '80' + - --served-model-name + - llm + - --max-model-len + - '2048' + - --gpu-memory-utilization + - '0.9' + - --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 服务线程 + 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 + _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