12 Commits

Author SHA1 Message Date
818d8ea3de add .dockerignore 2026-10-20 13:45:35 +08:00
89dd4182bb add requirements.txt 2026-10-20 13:45:34 +08:00
9e1fbd5ab0 add Dockerfile 2026-10-20 13:45:34 +08:00
1fd8e461d9 add main.py 2026-10-20 13:45:33 +08:00
80b703d0b5 add .dockerignore 2026-10-20 10:13:39 +08:00
938fec4951 add requirements.txt 2026-10-20 10:13:38 +08:00
be8ea679ff add Dockerfile 2026-10-20 10:13:38 +08:00
c949cc228c add main.py 2026-10-20 10:13:37 +08:00
4c43bc264a add .dockerignore 2026-10-20 10:10:16 +08:00
826a46860d add requirements.txt 2026-10-20 10:10:16 +08:00
c423296514 add Dockerfile 2026-10-20 10:10:15 +08:00
47c43c4934 add main.py 2026-10-20 10:10:15 +08:00

271
main.py
View File

@@ -1,16 +1,7 @@
"""ModelHub XC 适配智能体 —— 单任务提交器
"""ModelHub XC 适配智能体 —— 低排队算力批次(5 条)
启动后向 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
每条任务分配一张低排队算力卡,并选用该卡历史通过率较高的框架。
OSP 系列 max_position_embeddings=2048,配置里的 max_model_len 已由 4096 下调为 2048。
"""
import json
import os
@@ -22,160 +13,93 @@ 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
"""
TASKS = [
{
"label": "OSP-1.4B-100B-Muon-Only",
"model": "https://www.modelscope.cn/models/dmis-lab/OSP-1.4B-100B-Muon-Only",
"gpu": "MetaX_c-500",
"framework": "vllm_tokenizer_patch",
"config": "nv_framework: vllm_fix_tokenizer\nframework: vllm_tokenizer_patch\nstorage: gpfs\nmax_tokens: 1024\nmax_model_len: 2048\napi: completion\nsut_config:\n gpu_num: 1\n values:\n command: ['/opt/conda/bin/vllm', 'serve', '/model', '--port', '8000', '--served-model-name', 'llm', '--max-model-len', '2048', '--gpu-memory-utilization', '0.9', '--enforce-eager', '--trust-remote-code' ,'-tp', '1' ]\nref_config:\n gpu_num: 1\n values:\n command: ['/opt/entrypoint.sh', '/model', '--port', '80', '--served-model-name', 'llm', '--max-model-len', '2048', '--enforce-eager', '--trust-remote-code', '-tp', '1']"
},
{
"label": "OSP-1.4B-100B-Muon-SSNorm",
"model": "https://www.modelscope.cn/models/dmis-lab/OSP-1.4B-100B-Muon-SSNorm",
"gpu": "Kunlunxin_p-800",
"framework": "vllm_fix_tokenizer",
"config": "framework: vllm_fix_tokenizer\nlang: en\nmax_model_len: 2048\nsut_config:\n gpu_num: 1\n values:\n command:\n - /opt/entrypoint.sh - /model\n - --port\n - '80'\n - --served-model-name\n - llm\n - --max-model-len\n - '2048'\n - --enforce-eager\n - --trust-remote-code\n - -tp\n - '1'\nref_config:\n gpu_num: 1\n values:\n command:\n - /opt/entrypoint.sh - /model\n - --port\n - '80'\n - --served-model-name\n - llm\n - --max-model-len\n - '2048'\n - --enforce-eager\n - --trust-remote-code\n - -tp\n - '1'"
},
{
"label": "OSP-1.4B-100B-Shampoo-SSNorm",
"model": "https://www.modelscope.cn/models/dmis-lab/OSP-1.4B-100B-Shampoo-SSNorm",
"gpu": "Cambricon_mlu-370-x8",
"framework": "vllm-mlu",
"config": "framework: vllm-mlu\nnv_framework: vllm_fix_tokenizer\napi: completion\nlang: en\ntemperature: 0.4\nrepetition_penalty: 1.1\ntop_p: 0.9\nsut_config:\n gpu_num: 1\n values:\n env:\n - name: MAX_MODEL_LEN\n value: 2048\n - name: VLLM_ALLOW_LONG_MAX_MODEL_LEN\n value: '1'\n command:\n - vllm\n - serve\n - /model\n - --port\n - '8000'\n - --served-model-name\n - llm\n - --max-model-len\n - '2048'\n - --trust-remote-code\n - --dtype\n - float16\nref_config:\n cpu_num: 2\n gpu_num: 1\n values:\n env:\n - name: MAX_MODEL_LEN\n value: 2048\n - name: VLLM_ALLOW_LONG_MAX_MODEL_LEN\n value: '1'\n command:\n - /opt/entrypoint.sh\n - /model\n - --port\n - '80'\n - --served-model-name\n - llm\n - --max-model-len\n - '2048'\n - --trust-remote-code\n - --dtype\n - float16\n"
},
{
"label": "OSP-1.4B-100B-Muon-EmbProj",
"model": "https://www.modelscope.cn/models/dmis-lab/OSP-1.4B-100B-Muon-EmbProj",
"gpu": "Biren_166m",
"framework": "vllm_fix_tokenizer",
"config": "framework: vllm_fix_tokenizer\nlang: en\nmax_model_len: 2048\nsut_config:\n gpu_num: 1\n values:\n command:\n - /opt/entrypoint.sh - /model\n - --port\n - '80'\n - --served-model-name\n - llm\n - --max-model-len\n - '2048'\n - --enforce-eager\n - --trust-remote-code\n - -tp\n - '1'\nref_config:\n gpu_num: 1\n values:\n command:\n - /opt/entrypoint.sh - /model\n - --port\n - '80'\n - --served-model-name\n - llm\n - --max-model-len\n - '2048'\n - --enforce-eager\n - --trust-remote-code\n - -tp\n - '1'"
},
{
"label": "OSP-1.4B-1T-Muon-SSNorm-EmbProj",
"model": "https://www.modelscope.cn/models/dmis-lab/OSP-1.4B-1T-Muon-SSNorm-EmbProj",
"gpu": "Sunrise_pt-200-x1",
"framework": "vllm_fix_tokenizer",
"config": "framework: vllm_fix_tokenizer\napi: completion\nmax_tokens: 1024\ntemperature: 0.7\nrepetition_penalty: 1.2\ntop_p: 0.9\nlang: zh\nmax_model_len: 2048\nsut_config:\n gpu_num: 1\n values:\n command:\n - /opt/entrypoint.sh\n - /model\n - --port\n - '8000'\n - --served-model-name\n - llm\n - --max-model-len\n - '2048'\n - --gpu-memory-utilization\n - '0.9'\n - --enforce-eager\n - --trust-remote-code\nref_config:\n gpu_num: 1\n values:\n command:\n - /opt/entrypoint.sh\n - /model\n - --port\n - '8000'\n - --served-model-name\n - llm\n - --max-model-len\n - '2048'\n - --dtype\n - float16\n - --gpu-memory-utilization\n - '0.95'\n - -tp\n - '1'\n - --enforce-eager\n - --trust-remote-code\n"
}
]
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,
}
GAP = 4
_shutdown = threading.Event()
_submitting = threading.Lock()
_submitted = False
_state = {"phase": "starting", "total": len(TASKS), "submitted": 0, "failed": 0, "results": []}
def submit_task():
"""提交一次;已提交过则直接返回。"""
global _submitted
with _submitting:
if _submitted:
return True
payload = {
"configParams": CONFIG_PARAMS,
"framework": FRAMEWORK,
"modelAddress": MODEL_ADDRESS,
"targetGpu": TARGET_GPU,
def submit_one(t):
body = {
"configParams": t["config"],
"framework": t["framework"],
"modelAddress": t["model"],
"targetGpu": t["gpu"],
"taskType": TASK_TYPE,
"strategyId": STRATEGY_ID,
}
try:
r = requests.post(BASE_URL + ADD_TASK_ENDPOINT, headers=HEADERS, json=body, timeout=90)
j = r.json()
except Exception as e: # noqa: BLE001
j = {"code": -1, "message": "exception: %s" % e}
tid = (j.get("data") or {}).get("id")
print("[submit] %s gpu=%s fw=%s code=%s msg=%s taskId=%s"
% (t["label"], t["gpu"], t["framework"], j.get("code"), j.get("message"), tid), flush=True)
return j.get("code"), tid
def run():
_state["phase"] = "submitting"
_state["started_at"] = datetime.utcnow().isoformat()
for i in range(1, MAX_ATTEMPTS + 1):
for i, t in enumerate(TASKS, 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
code, tid = submit_one(t)
_state["results"].append({"label": t["label"], "gpu": t["gpu"],
"framework": t["framework"], "code": code, "task_id": tid})
_state["submitted" if code == 0 else "failed"] += 1
if i < len(TASKS):
time.sleep(GAP)
_state["phase"] = "done"
print("[batch] 完成 submitted=%d failed=%d" % (_state["submitted"], _state["failed"]), flush=True)
class Handler(BaseHTTPRequestHandler):
class H(BaseHTTPRequestHandler):
def do_GET(self):
if self.path == "/health":
self._json({"status": "ok"})
@@ -184,56 +108,29 @@ class Handler(BaseHTTPRequestHandler):
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)
def _json(self, b, c=200):
raw = json.dumps(b, ensure_ascii=False, default=str).encode()
self.send_response(c)
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 log_message(self, *a):
pass
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)
signal.signal(signal.SIGTERM, lambda *_: _shutdown.set())
print("[main] strategy_id=%s tasks=%d" % (STRATEGY_ID, len(TASKS)), flush=True)
for t in TASKS:
print("[main] 计划: %s @ %s / %s" % (t["label"], t["gpu"], t["framework"]), flush=True)
srv = ThreadingHTTPServer((HTTP_HOST, HTTP_PORT), H)
srv.timeout = 1
threading.Thread(target=run, daemon=True).start()
while not _shutdown.is_set():
server.handle_request()
server.server_close()
print("[http] 已关闭", flush=True)
srv.handle_request()
srv.server_close()
if __name__ == "__main__":
main()
main()