107 lines
4.5 KiB
Python
107 lines
4.5 KiB
Python
from __future__ import annotations
|
|
|
|
import json
|
|
from pathlib import Path
|
|
from xml.etree import ElementTree
|
|
|
|
|
|
def has_openvino_ir(root: Path) -> bool:
|
|
"""Return true only for a matching OpenVINO XML/BIN pair."""
|
|
|
|
return any(xml.with_suffix(".bin").is_file() for xml in root.rglob("openvino*.xml"))
|
|
|
|
|
|
def validate_openvino_ir_directory(root: Path, label: str) -> None:
|
|
"""Validate the exact persisted IR directory used for inference.
|
|
|
|
Recursive IR discovery is useful for model status, but the runtime loader
|
|
must receive one unambiguous XML/BIN pair plus the Transformers config that
|
|
Optimum inspects before compiling the graph.
|
|
"""
|
|
|
|
required = ("openvino_model.xml", "openvino_model.bin", "config.json")
|
|
missing = [name for name in required if not (root / name).is_file()]
|
|
if missing:
|
|
raise ValueError(f"{label} OpenVINO 模型不完整,缺少:{', '.join(missing)}")
|
|
|
|
|
|
def validate_sentence_transformer_root(root: Path, label: str = "画面语义") -> None:
|
|
"""Validate a local SentenceTransformer snapshot without contacting Hugging Face."""
|
|
|
|
modules_path = root / "modules.json"
|
|
if not modules_path.is_file():
|
|
raise ValueError(f"{label}模型不完整,缺少 modules.json")
|
|
try:
|
|
modules = json.loads(modules_path.read_text(encoding="utf-8"))
|
|
except (OSError, UnicodeDecodeError, json.JSONDecodeError) as exc:
|
|
raise ValueError(f"{label}模型的 modules.json 无效") from exc
|
|
if not isinstance(modules, list) or not modules:
|
|
raise ValueError(f"{label}模型的 modules.json 无效")
|
|
for module in modules:
|
|
if not isinstance(module, dict):
|
|
raise ValueError(f"{label}模型的 modules.json 无效")
|
|
relative = module.get("path")
|
|
if relative and not (root / str(relative)).exists():
|
|
raise ValueError(f"{label}模型不完整,缺少模块 {relative}")
|
|
|
|
|
|
def sentence_transformer_module_path(root: Path, module_name: str) -> Path:
|
|
"""Resolve a module directory from a local SentenceTransformer snapshot."""
|
|
|
|
validate_sentence_transformer_root(root)
|
|
try:
|
|
modules = json.loads((root / "modules.json").read_text(encoding="utf-8"))
|
|
except (OSError, UnicodeDecodeError, json.JSONDecodeError) as exc:
|
|
raise ValueError("画面语义模型的 modules.json 无效") from exc
|
|
for module in modules:
|
|
module_type = str(module.get("type") or "").rsplit(".", 1)[-1]
|
|
if module_type != module_name:
|
|
continue
|
|
relative = str(module.get("path") or "")
|
|
path = root / relative if relative else root
|
|
if not (path / "config.json").is_file():
|
|
raise ValueError(f"画面语义 {module_name} 模块缺少 config.json")
|
|
return path
|
|
raise ValueError(f"画面语义模型缺少 {module_name} 模块")
|
|
|
|
|
|
def validate_face_model_root(root: Path) -> None:
|
|
"""Reject missing/truncated IR files and HTTP error pages returned with status 200."""
|
|
|
|
required = ("detector.xml", "detector.bin", "reidentification.xml", "reidentification.bin")
|
|
missing = [name for name in required if not (root / name).is_file()]
|
|
if missing:
|
|
raise ValueError(f"人物模型不完整,缺少:{', '.join(missing)}")
|
|
for name in ("detector.xml", "reidentification.xml"):
|
|
validate_face_model_file(root / name)
|
|
for name in ("detector.bin", "reidentification.bin"):
|
|
validate_face_model_file(root / name)
|
|
|
|
|
|
def validate_face_model_file(
|
|
path: Path,
|
|
*,
|
|
expected_suffix: str | None = None,
|
|
display_name: str | None = None,
|
|
) -> None:
|
|
name = display_name or path.name
|
|
suffix = expected_suffix or path.suffix
|
|
if suffix == ".xml":
|
|
try:
|
|
document = ElementTree.parse(path)
|
|
except (OSError, ElementTree.ParseError) as exc:
|
|
raise ValueError(f"人物模型文件 {name} 不是有效的 OpenVINO XML") from exc
|
|
root_tag = document.getroot()
|
|
if root_tag.tag.rsplit("}", 1)[-1] != "net" or root_tag.find("layers") is None:
|
|
raise ValueError(f"人物模型文件 {name} 不是有效的 OpenVINO IR")
|
|
return
|
|
if suffix == ".bin":
|
|
try:
|
|
size = path.stat().st_size
|
|
except OSError as exc:
|
|
raise ValueError(f"人物模型文件 {name} 无法读取") from exc
|
|
if size < 64 * 1024:
|
|
raise ValueError(f"人物模型文件 {name} 过小,下载内容可能是错误页面")
|
|
return
|
|
raise ValueError(f"无法识别的人物模型文件:{name}")
|