Spaces:
Running on CPU Upgrade
Running on CPU Upgrade
File size: 11,333 Bytes
6c9a052 a4bb90e 6c9a052 a4bb90e 6c9a052 a4bb90e 6c9a052 e1b4c00 6c9a052 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 | """
platform_utils.py
统一的平台检测与 Hub 操作封装。
支持平台:
- HuggingFace Space
- ModelScope 创空间(魔搭)
- 本地开发环境
对外暴露:
PLATFORM : Literal['hf', 'ms', 'local']
is_cloud() : bool
get_host_port() : tuple[str, int]
download_file() : 下载单个文件,返回本地路径
upload_bytes() : 上传 bytes 到 OSS(用于计数器持久化)
read_bytes() : 从 OSS 读取文件内容,返回 bytes | None
get_counter_cfg() : 返回 CounterConfig(platform / available)
环境变量约定:
HuggingFace Space(由 HF 自动注入)
SPACE_ID Space 唯一标识,存在即代表在 HF 环境
SPACE_AUTHOR_NAME 作者名
用户手动配置(HF Secrets):
HF_TOKEN HF 访问令牌(仅用于 download_file,非计数器)
ModelScope 创空间(由魔搭自动注入)
MODELSCOPE_ENVIRONMENT 存在即代表在魔搭环境(值通常为 "studio")
STUDIO_ID 创空间 ID(备用检测)
魔搭平台数据文件说明:
数据文件(CSV / parquet / safetensors)直接放在创空间 studio repo
中,容器启动时会自动同步到工作目录,download_file() 在 MS 平台
直接返回本地路径,无需额外配置 Model repo。
阿里云 OSS(计数器唯一后端,HF 与 MS 共享同一数据)
OSS_ACCESS_KEY_ID RAM 子账号 AccessKey ID
OSS_ACCESS_KEY_SECRET RAM 子账号 AccessKey Secret
OSS_ENDPOINT Bucket 所在地域节点
例: oss-cn-hangzhou.aliyuncs.com
(无需加 https://,代码自动拼接)
OSS_BUCKET_NAME Bucket 名称
OSS_COUNTER_DIR 计数文件在 Bucket 中的前缀目录(可选)
默认 "danbooru_counter"
最终路径: {OSS_COUNTER_DIR}/count.json
"""
from __future__ import annotations
import os
import time
from dataclasses import dataclass
from pathlib import Path
import oss2
from typing import Literal, Optional
# 平台检测
def _detect_platform() -> Literal['hf', 'ms', 'local']:
if os.environ.get('SPACE_ID'):
return 'hf'
if os.environ.get('MODELSCOPE_ENVIRONMENT') or os.environ.get('STUDIO_ID'):
return 'ms'
return 'local'
PLATFORM: Literal['hf', 'ms', 'local'] = _detect_platform()
def is_cloud() -> bool:
"""是否运行在任意云端平台。"""
return PLATFORM in ('hf', 'ms')
def get_host_port() -> tuple[str, int]:
"""
返回 NiceGUI 应使用的 (host, port)。
HF 和魔搭创空间都使用 0.0.0.0:7860;本地使用 127.0.0.1:1111。
"""
if is_cloud():
return '0.0.0.0', 7860
return '127.0.0.1', 11111
def nsfw_allowed() -> bool:
"""
返回当前平台是否允许用户开启 NSFW 显示。
魔搭(MS)平台禁用 NSFW,其余平台默认允许。
如需在任意平台强制禁用,可设置环境变量 DISABLE_NSFW=1。
"""
if os.environ.get('DISABLE_NSFW', '0') == '1':
return False
return PLATFORM != 'ms'
# 阿里云 OSS
def _get_oss_bucket():
"""
从环境变量读取 OSS 配置,返回 oss2.Bucket 对象。
若环境变量不完整或 oss2 未安装则返回 None。
"""
ak = os.environ.get('OSS_ACCESS_KEY_ID')
sk = os.environ.get('OSS_ACCESS_KEY_SECRET')
ep = os.environ.get('OSS_ENDPOINT')
bkt = os.environ.get('OSS_BUCKET_NAME')
if not all([ak, sk, ep, bkt]):
return None
try:
import oss2
auth = oss2.Auth(ak, sk)
endpoint = ep if ep.startswith('http') else f'https://{ep}'
return oss2.Bucket(auth, endpoint, bkt)
except ImportError:
print('[PlatformUtils] oss2 未安装,OSS 计数器不可用。请 pip install oss2。')
return None
def _oss_key(filename: str) -> str:
"""将 filename 拼上可选的前缀目录,得到 OSS Object Key。"""
prefix = os.environ.get('OSS_COUNTER_DIR', 'danbooru_counter').rstrip('/')
return f'{prefix}/{filename}'
def _oss_available() -> bool:
"""检测 OSS 四项环境变量是否均已设置且 oss2 可导入。"""
return _get_oss_bucket() is not None
# 计数器配置
@dataclass
class CounterConfig:
platform: Literal['oss', 'local']
@property
def available(self) -> bool:
if self.platform == 'oss':
return _oss_available()
return False
def get_counter_cfg() -> CounterConfig:
"""
读取计数器配置。
配置了 OSS 环境变量则使用 OSS,否则退化为本地模式(无持久化)。
"""
if _oss_available():
return CounterConfig(platform='oss')
return CounterConfig(platform='local')
# 计数器读写(OSS)
def read_bytes(filename: str, cfg: CounterConfig) -> Optional[bytes]:
"""
从 OSS 读取文件内容,返回 bytes。
文件不存在返回 None;网络或权限异常向上抛出。
"""
if not cfg.available:
return None
bucket = _get_oss_bucket()
key = _oss_key(filename)
try:
import oss2
result = bucket.get_object(key)
return result.read()
except oss2.exceptions.NoSuchKey:
return None
except Exception as e:
print(f'[PlatformUtils] OSS 读取失败 ({key}): {e}')
raise
def upload_bytes(
content: bytes,
filename: str,
cfg: CounterConfig,
commit_message: str = 'Update',
*,
retries: int = 3,
retry_delay: float = 1.0,
) -> bool:
"""
将 bytes 写入 OSS 的 filename 路径。
返回 True 表示成功,False 表示全部重试均失败。
commit_message 参数保留以兼容 counter.py 的调用签名,OSS 不使用。
"""
if not cfg.available:
return False
bucket = _get_oss_bucket()
key = _oss_key(filename)
for attempt in range(retries):
try:
bucket.put_object(key, content)
return True
except Exception as e:
print(f'[PlatformUtils] OSS 上传失败(第 {attempt + 1} 次)({key}): {e}')
if attempt < retries - 1:
time.sleep(retry_delay)
return False
# 文件下载(引擎数据文件,与计数器无关)
# 魔搭创空间工作目录,studio repo 的文件会被同步到此处
_MS_WORKDIR = Path('/home/user/app')
# HF Storage Bucket 挂载检测
# HF Storage Buckets 挂载到 Space 时,会映射到容器内的一个本地路径
# (通常为 /data),文件可直接以本地路径读取,无需 hf_hub_download。
_HF_BUCKET_MOUNT = Path('/data')
def get_hf_bucket_path(relative: str) -> Optional[Path]:
"""
如果 HF Storage Bucket 已挂载且目标文件存在,返回本地绝对路径。
否则返回 None(调用方应 fallback 到 hf_hub_download)。
"""
candidate = _HF_BUCKET_MOUNT / relative
if candidate.exists():
return candidate
return None
def download_file(
filename: str,
*,
# HF 专用参数
hf_repo_id: Optional[str] = None,
hf_repo_type: str = 'space',
hf_token: Optional[str] = None,
# MS 专用参数(保留签名兼容性,魔搭平台已不再使用)
ms_repo_id: Optional[str] = None,
ms_token: Optional[str] = None,
ms_cache_dir: str = '/tmp/ms_cache',
) -> str:
"""
下载单个引擎数据文件,返回本地绝对路径字符串。
HF 平台:
优先从挂载的 Storage Bucket(/data)读取本地文件(零延迟)。
若 Bucket 未挂载或文件不存在,回退到从 Space repo 下载
(hf_repo_id 默认读取环境变量 SPACE_ID)。
MS 平台:
文件已随 studio repo 部署到容器本地,直接返回工作目录下的路径,
无需配置任何额外 repo。若文件不存在则抛出 FileNotFoundError。
本地:
直接返回原始路径。
"""
if PLATFORM == 'hf':
# 优先从挂载的 Storage Bucket 读取(本地路径,零延迟)
bucket_path = get_hf_bucket_path(filename)
if bucket_path is not None:
print(f'[PlatformUtils] 从 Storage Bucket 读取: {bucket_path}')
return str(bucket_path)
# Bucket 未挂载或文件不存在,回退到从 Space repo 下载
from huggingface_hub import hf_hub_download
repo_id = hf_repo_id or os.environ.get('SPACE_ID')
if not repo_id:
raise RuntimeError('[PlatformUtils] HF 平台未找到 SPACE_ID,无法下载文件。')
return hf_hub_download(
repo_id=repo_id,
repo_type=hf_repo_type,
filename=filename,
token=hf_token or os.environ.get('HF_TOKEN'),
)
if PLATFORM == 'ms':
local_path = _MS_WORKDIR / filename
if not local_path.is_file():
raise FileNotFoundError(
f'[PlatformUtils] 魔搭平台本地文件不存在: {local_path}\n'
f'请确认已将 {filename} 提交到创空间 studio repo 中。'
)
print(f'[PlatformUtils] MS 本地文件: {local_path}')
return str(local_path)
# 本地环境:直接返回原始路径(由调用方保证文件存在)
return filename
# 模型路径解析
LOCAL_MODEL_PATH = 'D:/LLMs/BAAI/bge-m3'
HF_MODEL_ID = 'BAAI/bge-m3'
MS_MODEL_ID = 'BAAI/bge-m3' # 魔搭上同名,走国内节点
def resolve_model_path(prefer_local: Optional[str] = None) -> str:
"""
按优先级解析模型路径:
1. 本地目录(prefer_local 或 LOCAL_MODEL_PATH)
2. 当前平台的 Hub Model ID(首次会自动下载缓存)
返回可直接传给 SentenceTransformer 的路径或 model_id 字符串。
"""
local = prefer_local or LOCAL_MODEL_PATH
if os.path.exists(local):
print(f'[PlatformUtils] 使用本地模型: {local}')
return local
if PLATFORM == 'ms':
print(f'[PlatformUtils] 魔搭环境,使用 ModelScope Hub 模型: {MS_MODEL_ID}')
try:
from modelscope import snapshot_download
cached = snapshot_download(MS_MODEL_ID, cache_dir='/tmp/ms_model')
print(f'[PlatformUtils] 模型已缓存至: {cached}')
return cached
except Exception as e:
print(f'[PlatformUtils] ModelScope snapshot_download 失败,回退到 HF ID: {e}')
print(f'[PlatformUtils] 使用 HuggingFace Hub 模型: {HF_MODEL_ID}')
return HF_MODEL_ID |