guanghulab/server/training-agent/download-model.py
2026-05-10 13:12:44 +08:00

124 lines
4.6 KiB
Python
Raw 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.

#!/usr/bin/env python3
"""
═══════════════════════════════════════════════════════════
开源模型下载器 · download-model.py
═══════════════════════════════════════════════════════════
签发: 铸渊 · ICE-GL-ZY001 · 国作登字-2026-A-00037559
从 ModelScope 拉 Qwen2.5-7B 到本地(国内服务器免梯子免限速,不依赖 HF Token
失败时自动回退到 HuggingFace。
输出:
$ZY_TRAIN_DATA/models/Qwen2.5-7B/ (config.json, *.safetensors, tokenizer.* …)
环境:
ZY_TRAIN_DATA 数据根目录 (默认 /data/guanghu)
ZY_MODEL_ID 模型 ID (默认 qwen/Qwen2.5-7B)
ZY_HF_FALLBACK HF 回退仓库 (默认 Qwen/Qwen2.5-7B)
"""
from __future__ import annotations
import os
import sys
import shutil
import time
from pathlib import Path
DATA_DIR = Path(os.environ.get("ZY_TRAIN_DATA", "/data/guanghu"))
MODEL_ID = os.environ.get("ZY_MODEL_ID", "qwen/Qwen2.5-7B")
HF_FALLBACK = os.environ.get("ZY_HF_FALLBACK", "Qwen/Qwen2.5-7B")
TARGET_DIR = DATA_DIR / "models" / "Qwen2.5-7B"
REQUIRED_FILES = ["config.json", "tokenizer.json", "tokenizer_config.json"]
def already_present(target: Path) -> bool:
if not target.is_dir():
return False
if not all((target / f).is_file() for f in REQUIRED_FILES):
return False
# 至少有一个权重 shard
shards = list(target.glob("*.safetensors")) + list(target.glob("*.bin"))
if not shards:
return False
total_gb = sum(p.stat().st_size for p in shards) / (1024**3)
print(f"[download-model] 已存在: {target} · {len(shards)} shards · {total_gb:.2f} GiB", flush=True)
return total_gb > 1.0 # Qwen2.5-7B safetensors 约 14-15GB
def download_modelscope(target: Path) -> bool:
try:
from modelscope import snapshot_download
except Exception as e:
print(f"[download-model] modelscope 不可用: {e}", flush=True)
return False
try:
print(f"[download-model] ModelScope 拉取 {MODEL_ID}{target}", flush=True)
target.parent.mkdir(parents=True, exist_ok=True)
local_dir = snapshot_download(
model_id=MODEL_ID,
cache_dir=str(DATA_DIR / "models" / ".modelscope-cache"),
revision="master",
)
# snapshot_download 返回的实际路径,软链/复制到 target
local_path = Path(local_dir)
if target.exists():
shutil.rmtree(target)
# 优先用软链节省空间,回退到复制
try:
target.symlink_to(local_path, target_is_directory=True)
except OSError:
shutil.copytree(local_path, target)
return True
except Exception as e:
print(f"[download-model] ModelScope 失败: {e}", flush=True)
return False
def download_huggingface(target: Path) -> bool:
try:
from huggingface_hub import snapshot_download
except Exception as e:
print(f"[download-model] huggingface_hub 不可用: {e}", flush=True)
return False
try:
print(f"[download-model] HuggingFace 拉取 {HF_FALLBACK}{target}", flush=True)
target.parent.mkdir(parents=True, exist_ok=True)
snapshot_download(
repo_id=HF_FALLBACK,
local_dir=str(target),
local_dir_use_symlinks=False,
allow_patterns=[
"*.json", "*.txt", "*.model",
"*.safetensors", "tokenizer*",
],
max_workers=4,
)
return True
except Exception as e:
print(f"[download-model] HuggingFace 失败: {e}", flush=True)
return False
def main() -> int:
if already_present(TARGET_DIR):
print(f"[download-model] ✅ 跳过下载 · 模型已就绪 · {TARGET_DIR}", flush=True)
return 0
started = time.time()
if download_modelscope(TARGET_DIR) and already_present(TARGET_DIR):
print(f"[download-model] ✅ ModelScope 下载完成 · {time.time()-started:.0f}s", flush=True)
return 0
print("[download-model] ⚠️ 改用 HuggingFace 回退路径", flush=True)
if download_huggingface(TARGET_DIR) and already_present(TARGET_DIR):
print(f"[download-model] ✅ HuggingFace 下载完成 · {time.time()-started:.0f}s", flush=True)
return 0
print("[download-model] ❌ 下载失败 · 请检查网络或手动放置模型到 " + str(TARGET_DIR), file=sys.stderr, flush=True)
return 2
if __name__ == "__main__":
sys.exit(main())