Files
xc-adapt-agent-2174/main.py
2026-10-20 10:03:55 +08:00

240 lines
6.7 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""ModelHub XC 适配智能体 —— 单任务提交器
启动后向 ModelHub XC 提交 1 条模型适配验证任务,随后保持 /health 存活等待平台停止。
提交内容(已查重,非重复模型):
模型:https://www.modelscope.cn/models/qnguyen3/qwq-1.5b-grpo (Qwen2 / 1.5B)
算力:Ascend_910-b4 框架:vllm 任务类型:text-generation
平台契约:
- 根目录 Dockerfile,监听 8080
- GET /health 探活
- 读取平台注入的 STRATEGY_ID
- 正确处理 SIGTERM
"""
import json
import os
import signal
import threading
import time
from datetime import datetime
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
import requests
# ─────────────────────────── 业务配置 ───────────────────────────
BASE_URL = os.environ.get("BASE_URL", "https://modelhub.org.cn")
ADD_TASK_ENDPOINT = "/api/adapt/task/add"
XC_TOKEN = os.environ.get("XC_TOKEN", "6616dfe75c874e61987b1f7c908e6d77")
STRATEGY_ID = os.environ.get("STRATEGY_ID", "")
MODEL_ADDRESS = "https://www.modelscope.cn/models/qnguyen3/qwq-1.5b-grpo"
TASK_TYPE = "text-generation"
TARGET_GPU = "Ascend_910-b4"
FRAMEWORK = "vllm"
# 与 POST /api/adapt/task/build-config?gpuType=Ascend_910-b4&framework=vllm&taskType=text-generation 输出一致
CONFIG_PARAMS = """framework: vllm
api: completion
max_tokens: 1024
temperature: 0.7
repetition_penalty: 1.2
top_p: 0.9
lang: zh
max_model_len: 2048
sut_config:
gpu_num: 1
values:
command:
- vllm
- serve
- /model
- --port
- '8000'
- --served-model-name
- llm
- --max-model-len
- '2048'
- --dtype
- auto
- --gpu-memory-utilization
- '0.95'
- -tp
- '1'
- --enforce-eager
- --trust-remote-code
ref_config:
gpu_num: 1
values:
command:
- vllm
- serve
- /model
- --port
- '8000'
- --served-model-name
- llm
- --max-model-len
- '2048'
- --dtype
- auto
- --gpu-memory-utilization
- '0.95'
- -tp
- '1'
- --enforce-eager
- --trust-remote-code
"""
HEADERS = {"Content-Type": "application/json", "Xc-Token": XC_TOKEN}
HTTP_HOST, HTTP_PORT = "0.0.0.0", 8080
MAX_ATTEMPTS = 5
RETRY_GAP = 15 # 秒
_state = {
"strategy_id": STRATEGY_ID,
"phase": "starting", # starting | submitting | done | error
"model_address": MODEL_ADDRESS,
"target_gpu": TARGET_GPU,
"framework": FRAMEWORK,
"attempts": 0,
"attempt_log": [],
"task_id": None,
"started_at": None,
"finished_at": None,
}
_shutdown = threading.Event()
_submitting = threading.Lock()
_submitted = False
def submit_task():
"""提交一次;已提交过则直接返回。"""
global _submitted
with _submitting:
if _submitted:
return True
payload = {
"configParams": CONFIG_PARAMS,
"framework": FRAMEWORK,
"modelAddress": MODEL_ADDRESS,
"targetGpu": TARGET_GPU,
"taskType": TASK_TYPE,
"strategyId": STRATEGY_ID,
}
_state["phase"] = "submitting"
_state["started_at"] = datetime.utcnow().isoformat()
for i in range(1, MAX_ATTEMPTS + 1):
if _shutdown.is_set():
break
_state["attempts"] = i
try:
resp = requests.post(BASE_URL + ADD_TASK_ENDPOINT, headers=HEADERS,
json=payload, timeout=60)
body = resp.json()
except Exception as e: # noqa: BLE001
body = {"code": -1, "message": "exception: %s" % e}
code = body.get("code")
msg = body.get("message", "")
entry = {"attempt": i, "time": datetime.utcnow().isoformat(),
"http_status": None, "code": code, "message": msg}
try:
entry["http_status"] = resp.status_code
except Exception: # noqa: BLE001
pass
_state["attempt_log"].append(entry)
print("[submit] #%d code=%s message=%s" % (i, code, msg), flush=True)
if code == 0:
data = body.get("data") or {}
_state["task_id"] = data.get("id")
_state["phase"] = "done"
_state["finished_at"] = datetime.utcnow().isoformat()
_submitted = True
print("[submit] 提交成功 taskId=%s" % _state["task_id"], flush=True)
return True
# 已存在/重复等不可重试错误,直接退出
if code not in (-1, 60014, 50000):
_state["phase"] = "error"
_state["finished_at"] = datetime.utcnow().isoformat()
print("[submit] 终止:不可重试错误", flush=True)
return False
if i < MAX_ATTEMPTS and not _shutdown.is_set():
time.sleep(RETRY_GAP)
_state["phase"] = "error"
_state["finished_at"] = datetime.utcnow().isoformat()
return False
class Handler(BaseHTTPRequestHandler):
def do_GET(self):
if self.path == "/health":
self._json({"status": "ok"})
elif self.path in ("/", "/status"):
self._json(_state)
else:
self._json({"error": "not found"}, 404)
def do_POST(self):
if self.path == "/task":
submit_task()
self._json(_state)
else:
self._json({"error": "not found"}, 404)
def _json(self, body, code=200):
raw = json.dumps(body, ensure_ascii=False, default=str).encode()
self.send_response(code)
self.send_header("Content-Type", "application/json")
self.send_header("Content-Length", str(len(raw)))
self.end_headers()
self.wfile.write(raw)
def log_message(self, fmt, *args):
print(self.address_string() + " - " + (fmt % args), flush=True)
def _handle_signal(signum, _frame):
print("[main] 收到信号 %s,准备退出" % signum, flush=True)
_shutdown.set()
def main():
signal.signal(signal.SIGTERM, _handle_signal)
signal.signal(signal.SIGINT, _handle_signal)
print("[main] strategy_id=%s" % STRATEGY_ID, flush=True)
print("[main] model=%s gpu=%s framework=%s" % (MODEL_ADDRESS, TARGET_GPU, FRAMEWORK), flush=True)
http_thread = threading.Thread(target=_run_http, daemon=False)
http_thread.start()
worker = threading.Thread(target=submit_task, daemon=True)
worker.start()
_shutdown.wait()
print("[main] 退出", flush=True)
def _run_http():
server = ThreadingHTTPServer((HTTP_HOST, HTTP_PORT), Handler)
server.timeout = 1
print("[http] 监听 %s:%d" % (HTTP_HOST, HTTP_PORT), flush=True)
while not _shutdown.is_set():
server.handle_request()
server.server_close()
print("[http] 已关闭", flush=True)
if __name__ == "__main__":
main()