diff --git a/Brain/.env.example b/Brain/.env.example new file mode 100644 index 0000000..2a7178a --- /dev/null +++ b/Brain/.env.example @@ -0,0 +1,3 @@ +# Safe local defaults. Do not add credentials, customer data, or production paths. +BRAIN_DEVICE=auto +BRAIN_LOG_LEVEL=INFO diff --git a/Brain/.python-version b/Brain/.python-version new file mode 100644 index 0000000..ed7d51a --- /dev/null +++ b/Brain/.python-version @@ -0,0 +1 @@ +3.11.15 diff --git a/Brain/README.md b/Brain/README.md new file mode 100644 index 0000000..438ea11 --- /dev/null +++ b/Brain/README.md @@ -0,0 +1,90 @@ +# YoVision Brain + +Brain 是无界面的独立推理交付单元。本骨架只提供可安装 Python 包、命令入口和运行时探测;尚不包含视频、模型、规则、事件或部署能力,也不依赖 Sense、Bell 在线。 + +## 冻结基线 + +| 项目 | 版本 / 选择 | +|---|---| +| Python | CPython `3.11.15`(`.python-version`;本机由 uv 隔离管理) | +| 环境与包管理 | Python `venv` + pip `26.2.1`;可用 uv `0.11.6` 取得固定 Python | +| 构建后端 | setuptools `80.9.0` | +| 测试 | pytest `8.4.2` | +| 数组运行时 | NumPy `2.3.3` | +| 推理运行时 | PyTorch `2.12.1` | +| CPU wheel | PyTorch 官方 `https://download.pytorch.org/whl/cpu` | +| NVIDIA wheel | PyTorch 官方 CUDA 12.6 `https://download.pytorch.org/whl/cu126` | + +选择 Python 3.11 是因为 PyTorch 的 Windows 支持范围包含 Python 3.9–3.12,并且本机已有隔离的 CPython 3.11.15。选择 `torch 2.12.1 + cu126` 是因为 PyTorch 官方为 Linux/Windows 同时发布该固定组合;NVIDIA 的 CUDA 12.x 兼容表要求 Windows 驱动至少为 528.33,本机驱动 566.24 满足运行 CUDA 12.6 wheel 的驱动前提。 + +本机安装的 CUDA Toolkit 11.2 不参与 PyTorch wheel 构建,也不因本项目而修改。驱动满足最低版本只是兼容前提,不等于 GPU 已验证;必须以 `--smoke cuda` 的真实结果为准。 + +官方依据: + +- [PyTorch - Start Locally](https://docs.pytorch.org/get-started/locally/) +- [PyTorch - Previous Versions](https://pytorch.org/get-started/previous-versions/) +- [NVIDIA CUDA 12.6 Release Notes](https://docs.nvidia.com/cuda/archive/12.6.0/cuda-toolkit-release-notes/index.html) +- [Python 3.11.15](https://www.python.org/downloads/release/python-31115/) + +## 创建隔离环境 + +从仓库根目录执行。无需激活虚拟环境,也不需要更改 PowerShell 执行策略: + +```powershell +uv python install 3.11.15 +uv venv --python 3.11.15 Brain/.venv +Brain\.venv\Scripts\python.exe -m pip install pip==26.2.1 --index-url https://pypi.org/simple +Brain\.venv\Scripts\python.exe -m pip install -e "Brain[dev]" --index-url https://pypi.org/simple +``` + +若不使用 uv,也可以让已安装的 Python 3.11.15 创建环境: + +```powershell +py -V:3.11 -m venv Brain/.venv +Brain\.venv\Scripts\python.exe -m pip install pip==26.2.1 --index-url https://pypi.org/simple +Brain\.venv\Scripts\python.exe -m pip install -e "Brain[dev]" --index-url https://pypi.org/simple +``` + +## 安装运行时 + +CPU 环境使用 PyTorch 官方 CPU 索引: + +```powershell +Brain\.venv\Scripts\python.exe -m pip install numpy==2.3.3 --index-url https://pypi.org/simple +Brain\.venv\Scripts\python.exe -m pip install torch==2.12.1 --index-url https://download.pytorch.org/whl/cpu +``` + +目标 NVIDIA 环境使用官方 CUDA 12.6 wheel。该 wheel 自带所需 CUDA 用户态运行库,不要求把系统 Toolkit 改成 12.6: + +```powershell +Brain\.venv\Scripts\python.exe -m pip install numpy==2.3.3 --index-url https://pypi.org/simple +Brain\.venv\Scripts\python.exe -m pip install torch==2.12.1 --index-url https://download.pytorch.org/whl/cu126 +``` + +不要在同一环境混装 CPU 与 CUDA wheel;切换后端时重建 `.venv`。 + +## 运行和验证 + +入口的帮助与版本查询不导入 PyTorch,因此未安装运行时时也可用: + +```powershell +Brain\.venv\Scripts\python.exe -m yovision_brain --help +Brain\.venv\Scripts\python.exe -m yovision_brain --version +``` + +安装相应运行时后执行: + +```powershell +Brain\.venv\Scripts\python.exe -m pytest Brain/tests/test_package.py -q +Brain\.venv\Scripts\python.exe -m yovision_brain --runtime-info +Brain\.venv\Scripts\python.exe -m yovision_brain --smoke cpu +Brain\.venv\Scripts\python.exe -m yovision_brain --smoke cuda +``` + +`--runtime-info` 只输出 Python、PyTorch 和设备能力,不读取或显示环境变量值。`--smoke cuda` 在 CUDA wheel、驱动或设备不可用时以非零状态退出,不会回退 CPU 后伪称成功。 + +## 配置与安全边界 + +`.env.example` 只有无秘密默认值。Brain 不接收用户会话,不持有账户、RBAC、Alert 或通知状态。不得把 token、摄像头凭据、客户数据、内部文件路径或未经授权的人脸信息写入配置、日志或事件。 + +第三方许可证与来源见 [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md)。 diff --git a/Brain/THIRD_PARTY_NOTICES.md b/Brain/THIRD_PARTY_NOTICES.md new file mode 100644 index 0000000..667f5d1 --- /dev/null +++ b/Brain/THIRD_PARTY_NOTICES.md @@ -0,0 +1,17 @@ +# Third-party notices + +本文件记录 Brain 骨架直接固定或明确依赖的第三方软件。具体安装包内许可证文本仍是最终依据。 + +| 组件 | 固定版本 | 许可证 | 来源 | +|---|---:|---|---| +| CPython | 3.11.15 | Python Software Foundation License | https://www.python.org/downloads/release/python-31115/ | +| pip | 26.2.1 | MIT | https://github.com/pypa/pip/tree/26.2.1 | +| uv(可选 Python 获取工具,不随 Brain 分发) | 0.11.6 | Apache-2.0 OR MIT | https://github.com/astral-sh/uv/tree/0.11.6 | +| python-build-standalone(uv 管理的 Python 分发来源,不随 Brain 分发) | 2026 系列 | MPL-2.0;分发包内另含 CPython 与组件许可证 | https://github.com/astral-sh/python-build-standalone | +| setuptools | 80.9.0 | MIT | https://github.com/pypa/setuptools/tree/v80.9.0 | +| pytest | 8.4.2 | MIT | https://github.com/pytest-dev/pytest/tree/8.4.2 | +| NumPy | 2.3.3 | BSD-3-Clause | https://github.com/numpy/numpy/tree/v2.3.3 | +| PyTorch | 2.12.1 | BSD-3-Clause | https://github.com/pytorch/pytorch/tree/v2.12.1 | +| NVIDIA CUDA runtime(随官方 PyTorch CUDA wheel 分发) | 12.6 系列 | NVIDIA CUDA Toolkit End User License Agreement | https://docs.nvidia.com/cuda/eula/index.html | + +PyTorch 及后续模型可能带来额外第三方依赖与模型许可。本骨架未选择或分发任何模型;引入模型前必须另行核对商用、再分发、数据和输出限制,不能把框架许可证视为模型许可证。 diff --git a/Brain/pyproject.toml b/Brain/pyproject.toml new file mode 100644 index 0000000..ac87efd --- /dev/null +++ b/Brain/pyproject.toml @@ -0,0 +1,27 @@ +[build-system] +requires = ["setuptools==80.9.0"] +build-backend = "setuptools.build_meta" + +[project] +name = "yovision-brain" +version = "0.1.0" +description = "Headless inference delivery unit for YoVision" +readme = "README.md" +requires-python = "==3.11.*" +dependencies = [] + +[project.optional-dependencies] +# The wheel backend is selected by the official PyTorch index documented in +# README.md. Keeping one pinned requirement here prevents CPU/CUDA drift. +runtime = ["numpy==2.3.3", "torch==2.12.1"] +dev = ["pytest==8.4.2"] + +[project.scripts] +yovision-brain = "yovision_brain.__main__:main" + +[tool.setuptools.packages.find] +where = ["src"] + +[tool.pytest.ini_options] +testpaths = ["tests"] +addopts = "--strict-markers" diff --git a/Brain/src/yovision_brain/__init__.py b/Brain/src/yovision_brain/__init__.py new file mode 100644 index 0000000..902e00c --- /dev/null +++ b/Brain/src/yovision_brain/__init__.py @@ -0,0 +1,5 @@ +"""YoVision Brain package.""" + +__version__ = "0.1.0" + +__all__ = ["__version__"] diff --git a/Brain/src/yovision_brain/__main__.py b/Brain/src/yovision_brain/__main__.py new file mode 100644 index 0000000..81e7c09 --- /dev/null +++ b/Brain/src/yovision_brain/__main__.py @@ -0,0 +1,112 @@ +"""Command-line entry point for safe Brain runtime diagnostics.""" + +from __future__ import annotations + +import argparse +import json +import platform +from collections.abc import Sequence +from typing import Any + +from yovision_brain import __version__ + + +def _load_torch() -> Any: + try: + import torch + except ImportError as exc: + raise RuntimeError( + "PyTorch runtime is not installed; install the pinned CPU or CUDA wheel " + "from Brain/README.md" + ) from exc + return torch + + +def runtime_info() -> dict[str, object]: + """Return non-sensitive interpreter and compute-runtime facts.""" + info: dict[str, object] = { + "python": platform.python_version(), + "torch_installed": False, + "torch_version": None, + "cuda_build": None, + "cuda_available": False, + "cuda_device_count": 0, + } + try: + torch = _load_torch() + except RuntimeError: + return info + + cuda_available = bool(torch.cuda.is_available()) + info.update( + { + "torch_installed": True, + "torch_version": torch.__version__, + "cuda_build": torch.version.cuda, + "cuda_available": cuda_available, + "cuda_device_count": torch.cuda.device_count() if cuda_available else 0, + } + ) + return info + + +def smoke(device: str) -> dict[str, object]: + """Run a deterministic tensor operation on exactly the requested device.""" + torch = _load_torch() + if device == "cuda" and not torch.cuda.is_available(): + raise RuntimeError("CUDA was requested but PyTorch reports no available CUDA device") + + tensor = torch.tensor([[1.0, 2.0], [3.0, 4.0]], device=device) + result = tensor @ tensor + expected = torch.tensor([[7.0, 10.0], [15.0, 22.0]], device=device) + if not torch.equal(result, expected): + raise RuntimeError("tensor smoke result did not match the expected value") + + return { + "status": "ok", + "device": device, + "torch_version": torch.__version__, + "cuda_build": torch.version.cuda, + } + + +def build_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser( + prog="yovision-brain", + description="YoVision Brain runtime diagnostics", + ) + parser.add_argument("--version", action="version", version=f"%(prog)s {__version__}") + action = parser.add_mutually_exclusive_group() + action.add_argument( + "--runtime-info", + action="store_true", + help="print non-sensitive Python/PyTorch/CUDA capability information", + ) + action.add_argument( + "--smoke", + choices=("cpu", "cuda"), + help="run a tensor smoke test on exactly the selected device", + ) + return parser + + +def main(argv: Sequence[str] | None = None) -> int: + args = build_parser().parse_args(argv) + try: + if args.runtime_info: + payload = runtime_info() + elif args.smoke: + payload = smoke(args.smoke) + else: + build_parser().print_help() + return 0 + except RuntimeError as exc: + print(json.dumps({"status": "error", "message": str(exc)}, ensure_ascii=False)) + return 2 + + print(json.dumps(payload, ensure_ascii=False, sort_keys=True)) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/Brain/tests/test_package.py b/Brain/tests/test_package.py new file mode 100644 index 0000000..395ba02 --- /dev/null +++ b/Brain/tests/test_package.py @@ -0,0 +1,46 @@ +from __future__ import annotations + +import json +import subprocess +import sys + +import pytest + +from yovision_brain import __version__ +from yovision_brain.__main__ import main, runtime_info, smoke + + +def test_package_version() -> None: + assert __version__ == "0.1.0" + + +def test_module_help_runs_without_other_products() -> None: + result = subprocess.run( + [sys.executable, "-m", "yovision_brain", "--help"], + check=False, + capture_output=True, + text=True, + ) + assert result.returncode == 0 + assert "YoVision Brain runtime diagnostics" in result.stdout + + +def test_runtime_info_is_non_sensitive(capsys: pytest.CaptureFixture[str]) -> None: + assert main(["--runtime-info"]) == 0 + payload = json.loads(capsys.readouterr().out) + assert set(payload) == { + "cuda_available", + "cuda_build", + "cuda_device_count", + "python", + "torch_installed", + "torch_version", + } + assert payload == runtime_info() + + +def test_cpu_tensor_smoke() -> None: + pytest.importorskip("torch") + result = smoke("cpu") + assert result["status"] == "ok" + assert result["device"] == "cpu"