verify_models.py 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329
  1. #!/usr/bin/env python3
  2. """
  3. 验证已下载的模型是否完整可用
  4. 支持文件完整性检查 + 实际推理测试
  5. """
  6. import json
  7. import fnmatch
  8. from pathlib import Path
  9. PROJECT_ROOT = Path(__file__).parent.parent.resolve()
  10. WEIGHTS_DIR = PROJECT_ROOT / "model" / "weights"
  11. # 必需的核心文件
  12. REQUIRED_FILES = {
  13. "config.json",
  14. "tokenizer.json",
  15. "tokenizer_config.json",
  16. }
  17. # 模型权重文件(至少有一种)
  18. WEIGHT_FILES = {
  19. "pytorch_model.bin",
  20. "model.safetensors",
  21. "tf_model.h5",
  22. "model.onnx",
  23. }
  24. # 分片权重文件模式
  25. SHARDED_WEIGHT_PATTERNS = [
  26. "model-*.safetensors",
  27. "pytorch_model-*.bin",
  28. "model-*.bin",
  29. ]
  30. def verify_model(model_dir: Path) -> dict:
  31. """验证单个模型目录"""
  32. result = {
  33. "name": model_dir.name,
  34. "path": str(model_dir),
  35. "exists": model_dir.exists(),
  36. "files": [],
  37. "missing_required": [],
  38. "has_weights": False,
  39. "weight_files": [],
  40. "valid": False,
  41. "error": None
  42. }
  43. if not model_dir.exists():
  44. result["error"] = "模型目录不存在"
  45. return result
  46. # 列出所有文件
  47. all_files = {f.name for f in model_dir.iterdir() if f.is_file()}
  48. result["files"] = sorted(all_files)
  49. # 检查必需文件
  50. for req_file in REQUIRED_FILES:
  51. if req_file not in all_files:
  52. found = False
  53. for sub_dir in model_dir.iterdir():
  54. if sub_dir.is_dir():
  55. sub_files = {f.name for f in sub_dir.iterdir() if f.is_file()}
  56. if req_file in sub_files:
  57. found = True
  58. break
  59. if not found:
  60. result["missing_required"].append(req_file)
  61. # 检查权重文件
  62. for weight_file in WEIGHT_FILES:
  63. if weight_file in all_files:
  64. result["weight_files"].append(weight_file)
  65. result["has_weights"] = True
  66. else:
  67. for sub_dir in model_dir.iterdir():
  68. if sub_dir.is_dir():
  69. sub_files = {f.name for f in sub_dir.iterdir() if f.is_file()}
  70. if weight_file in sub_files:
  71. result["weight_files"].append(f"{sub_dir.name}/{weight_file}")
  72. result["has_weights"] = True
  73. break
  74. # 检查分片权重文件
  75. for pattern in SHARDED_WEIGHT_PATTERNS:
  76. for f in model_dir.iterdir():
  77. if f.is_file() and fnmatch.fnmatch(f.name, pattern):
  78. result["weight_files"].append(f.name)
  79. result["has_weights"] = True
  80. # 验证完整性
  81. if not result["missing_required"] and result["has_weights"]:
  82. result["valid"] = True
  83. return result
  84. def check_model_integrity(model_dir: Path) -> bool:
  85. """检查模型文件的完整性"""
  86. errors = []
  87. for json_file in model_dir.glob("*.json"):
  88. try:
  89. with open(json_file, 'r', encoding='utf-8') as f:
  90. json.load(f)
  91. except json.JSONDecodeError as e:
  92. errors.append(f"{json_file.name}: JSON 解析错误 - {e}")
  93. except Exception as e:
  94. errors.append(f"{json_file.name}: 读取错误 - {e}")
  95. # 显示权重文件大小
  96. for weight_pattern in ["*.bin", "*.safetensors", "*.onnx", "*.h5"]:
  97. for weight_file in model_dir.glob(weight_pattern):
  98. size = weight_file.stat().st_size
  99. if size == 0:
  100. errors.append(f"{weight_file.name}: 文件大小为 0")
  101. elif size > 1024 * 1024 * 1024:
  102. size_gb = size / (1024 * 1024 * 1024)
  103. print(f" {weight_file.name}: {size_gb:.2f} GB")
  104. elif size > 1024 * 1024:
  105. size_mb = size / (1024 * 1024)
  106. print(f" {weight_file.name}: {size_mb:.2f} MB")
  107. else:
  108. size_kb = size / 1024
  109. print(f" {weight_file.name}: {size_kb:.2f} KB")
  110. if errors:
  111. for err in errors:
  112. print(f" ✗ {err}")
  113. return False
  114. return True
  115. def test_qwen_inference(model_dir: Path):
  116. """使用 Qwen 模型进行实际推理测试"""
  117. print("\n" + "=" * 60)
  118. print("Qwen 模型推理测试")
  119. print("=" * 60)
  120. try:
  121. from transformers import AutoModelForCausalLM, AutoTokenizer
  122. import torch
  123. except ImportError as e:
  124. print(f"✗ 缺少依赖:{e}")
  125. print("请运行:pip install transformers torch")
  126. return False
  127. print(f"\n模型路径:{model_dir}")
  128. print("-" * 60)
  129. # 步骤 1: 加载分词器
  130. print("\n[1/4] 加载分词器...")
  131. try:
  132. tokenizer = AutoTokenizer.from_pretrained(
  133. str(model_dir),
  134. trust_remote_code=True
  135. )
  136. print(f" ✓ 分词器加载成功")
  137. print(f" - 词表大小:{len(tokenizer):,}")
  138. except Exception as e:
  139. print(f" ✗ 分词器加载失败:{e}")
  140. return False
  141. # 步骤 2: 加载模型
  142. print("\n[2/4] 加载模型...")
  143. try:
  144. model = AutoModelForCausalLM.from_pretrained(
  145. str(model_dir),
  146. trust_remote_code=True,
  147. torch_dtype=torch.float16, # 使用半精度节省显存
  148. device_map="auto" # 自动选择设备
  149. )
  150. print(f" ✓ 模型加载成功")
  151. print(f" - 参数量:{sum(p.numel() for p in model.parameters()):,}")
  152. print(f" - 设备:{model.device}")
  153. except Exception as e:
  154. print(f" ✗ 模型加载失败:{e}")
  155. return False
  156. # 步骤 3: 构建测试输入
  157. print("\n[3/4] 准备测试输入...")
  158. test_prompts = [
  159. "你好,请介绍一下你自己。",
  160. "What is the capital of France?",
  161. ]
  162. for prompt in test_prompts:
  163. print(f"\n 输入:{prompt}")
  164. # 步骤 4: 执行推理
  165. print("\n[4/4] 执行推理测试...")
  166. try:
  167. for prompt in test_prompts:
  168. inputs = tokenizer(prompt, return_tensors="pt")
  169. inputs = inputs.to(model.device)
  170. with torch.no_grad():
  171. outputs = model.generate(
  172. **inputs,
  173. max_new_tokens=50,
  174. do_sample=True,
  175. temperature=0.7,
  176. top_p=0.9,
  177. )
  178. response = tokenizer.decode(outputs[0], skip_special_tokens=True)
  179. # 只显示 prompt + 部分回复
  180. response_text = response[len(prompt):].strip()
  181. if len(response_text) > 100:
  182. response_text = response_text[:100] + "..."
  183. print(f" 输出:{response_text}")
  184. print("\n ✓ 推理测试通过")
  185. return True
  186. except Exception as e:
  187. print(f" ✗ 推理失败:{e}")
  188. return False
  189. def main():
  190. import argparse
  191. parser = argparse.ArgumentParser(description="模型验证工具")
  192. parser.add_argument(
  193. "--model",
  194. type=str,
  195. default=None,
  196. help="指定要验证的模型名称 (默认验证所有)"
  197. )
  198. parser.add_argument(
  199. "--test-inference",
  200. action="store_true",
  201. help="对 Qwen 模型进行推理测试"
  202. )
  203. args = parser.parse_args()
  204. print("=" * 60)
  205. print("模型验证工具")
  206. print("=" * 60)
  207. print(f"模型目录:{WEIGHTS_DIR}\n")
  208. if not WEIGHTS_DIR.exists():
  209. print("✗ 模型目录不存在")
  210. return
  211. model_dirs = [d for d in WEIGHTS_DIR.iterdir() if d.is_dir()]
  212. if not model_dirs:
  213. print("未找到任何模型")
  214. return
  215. # 过滤指定模型
  216. if args.model:
  217. model_dirs = [d for d in model_dirs if args.model in d.name]
  218. if not model_dirs:
  219. print(f"✗ 未找到包含 '{args.model}' 的模型")
  220. return
  221. print(f"找到 {len(model_dirs)} 个模型:\n")
  222. all_valid = True
  223. for model_dir in sorted(model_dirs):
  224. print("-" * 60)
  225. print(f"\n模型:{model_dir.name}")
  226. print(f"路径:{model_dir}")
  227. result = verify_model(model_dir)
  228. print(f"\n文件列表 ({len(result['files'])} 个):")
  229. for f in result['files'][:15]:
  230. print(f" - {f}")
  231. if len(result['files']) > 15:
  232. print(f" ... 还有 {len(result['files']) - 15} 个文件")
  233. print(f"\n验证结果:")
  234. if result['valid']:
  235. print(f" ✓ 模型完整")
  236. else:
  237. print(f" ✗ 模型不完整")
  238. all_valid = False
  239. if result['missing_required']:
  240. print(f" 缺少必需文件:{', '.join(result['missing_required'])}")
  241. if not result['has_weights']:
  242. print(f" 未找到权重文件")
  243. print(f"\n权重文件:")
  244. if result['weight_files']:
  245. for wf in result['weight_files']:
  246. print(f" - {wf}")
  247. else:
  248. print(f" 未找到权重文件")
  249. print(f"\n完整性检查:")
  250. if check_model_integrity(model_dir):
  251. print(f" ✓ 文件完整性通过")
  252. else:
  253. print(f" ✗ 文件完整性失败")
  254. all_valid = False
  255. print("\n" + "=" * 60)
  256. if all_valid:
  257. print("✓ 所有模型文件验证通过!")
  258. else:
  259. print("✗ 部分模型验证失败")
  260. print("=" * 60)
  261. # 推理测试
  262. if args.test_inference:
  263. qwen_dir = WEIGHTS_DIR / "Qwen2.5-7B-Instruct"
  264. if qwen_dir.exists():
  265. success = test_qwen_inference(qwen_dir)
  266. if success:
  267. print("\n" + "=" * 60)
  268. print("✓ Qwen 模型推理测试成功!")
  269. print("=" * 60)
  270. else:
  271. print("\n" + "=" * 60)
  272. print("✗ Qwen 模型推理测试失败")
  273. print("=" * 60)
  274. else:
  275. print("\n✗ 未找到 Qwen 模型,跳过推理测试")
  276. if __name__ == "__main__":
  277. main()