guanghulab/server/training-agent/download-model.py

124 lines
4.6 KiB
Python
Raw Normal View History

#!/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())