-3. **关闭不必要的后台程序**
-- 释放更多系统资源
+**如果这个项目对你有帮助,请给个 ⭐️ Star!**
-
+Modded with ❤️ by Chidc with CPU-Mode-Provider GemosDoDo
---
-## 推理缓存
-
-
-点击展开查看推理缓存详情
-
-## MLSharp 提供了智能推理缓存功能,可以显著提升重复场景的处理速度。
-
-### 缓存特性
-
-- **智能哈希**: 基于图像内容和焦距生成唯一的缓存键
-- **LRU 淘汰**: 最近最少使用算法自动淘汰旧缓存
-- **统计监控**: 实时缓存命中率、命中/未命中次数统计
-- **线程安全**: 使用锁机制保证多线程安全
-- **内存管理**: 可配置的缓存大小限制
-
-### 启用缓存
-
-缓存功能默认启用,可通过命令行参数或配置文件控制:
-
-```bash
-# 命令行参数
-python app.py # 默认启用缓存
-python app.py --no-cache # 禁用缓存
-python app.py --cache-size 200 # 设置缓存大小为 200
-```
-
-```yaml
-# config.yaml
-cache:
- enabled: true # 启用缓存(默认:true)
- size: 100 # 缓存最大条目数(默认:100)
-```
-
-### API 端点
-
-#### 获取缓存统计
-
-```bash
-curl http://127.0.0.1:8000/v1/cache
-```
-
-**返回示例**:
-```json
-{
- "enabled": true,
- "size": 45,
- "max_size": 100,
- "hits": 120,
- "misses": 30,
- "hit_rate": 80.0
-}
-```
-
-#### 清空缓存
-
-```bash
-curl -X POST http://127.0.0.1:8000/v1/cache/clear
-```
-
-**返回示例**:
-```json
-{
- "status": "success",
- "message": "缓存已清空"
-}
-```
-
-### 性能提升
-
-缓存功能可以显著提升处理速度,特别是在重复场景中:
-
-| 缓存命中率 | 速度提升 | 适用场景 |
-|-------|------|--------|
-| 30% | 30% | 少量重复图片 |
-| 50% | 50% | 中等重复场景 |
-| 80% | 80% | 大量重复图片 |
-
-### 最佳实践
-
-1. **适当调整缓存大小**: 根据内存和实际需求调整缓存大小
-2. **监控缓存命中率**: 定期检查缓存命中率,评估缓存效果
-3. **定期清空缓存**: 如果内存紧张,可以定期清空缓存
-4. **禁用缓存场景**: 处理完全不同的图片时,可以禁用缓存
-
-
-
----
-
-## Redis 分布式缓存
-
-
-点击展开查看 Redis 缓存详情
-
-## MLSharp 支持 Redis 分布式缓存,用于多实例部署和持久化缓存。
-
-### Redis 缓存特性
-
-- **分布式缓存**: 支持多实例共享缓存
-- **持久化**: 缓存数据持久化到 Redis
-- **TTL 支持**: 自动过期机制
-- **混合使用**: 可与本地缓存同时使用
-- **高性能**: 基于 Redis 内存数据库
-
-### 启用 Redis 缓存
-
-```bash
-# 使用 Redis 缓存
-python app.py --redis-url redis://localhost:6379/0
-
-# 使用 Redis 缓存 + Webhook
-python app.py --redis-url redis://localhost:6379/0 --enable-webhook
-```
-
-### 配置文件
-
-```yaml
-# config.yaml
-redis:
- enabled: true
- url: "redis://localhost:6379/0"
- prefix: "mlsharp"
-```
-
-### 性能对比
-
-| 缓存类型 | 命中速度 | 分布式支持 | 持久化 | 适用场景 |
-|---------|---------|----------|--------|---------|
-| 本地缓存 | 最快 | ❌ | ❌ | 单实例部署 |
-| Redis 缓存 | 快 | ✅ | ✅ | 多实例部署 |
-
-### 最佳实践
-
-1. **生产环境推荐**: 使用 Redis 缓存以支持多实例部署
-2. **本地开发**: 使用本地缓存,无需 Redis 服务
-3. **混合使用**: Redis 用于持久化,本地缓存用于加速
-4. **监控 Redis**: 定期检查 Redis 连接状态和内存使用
-
-
-
----
-
-## Webhook 异步通知
-
-
-点击展开查看 Webhook 支持详情
-
-## MLSharp 支持 Webhook 异步通知,可用于任务状态跟踪和集成第三方服务。
-
-### Webhook 事件
-
-| 事件类型 | 说明 | 触发时机 |
-|---------|------|---------|
-| task_completed | 任务完成 | 3D 模型生成成功 |
-| task_failed | 任务失败 | 处理过程中发生错误 |
-
-### 启用 Webhook
-
-```bash
-# 启用 Webhook
-python app.py --enable-webhook
-```
-
-### Webhook API
-
-#### 获取 Webhook 列表
-
-```bash
-curl http://127.0.0.1:8000/v1/webhooks
-```
-
-**响应**:
-```json
-{
- "enabled": true,
- "webhooks": {
- "task_completed": "https://example.com/webhook/completed",
- "task_failed": "https://example.com/webhook/failed"
- }
-}
-```
-
-#### 注册 Webhook
-
-```bash
-curl -X POST "http://127.0.0.1:8000/v1/webhooks" \
- -H "Content-Type: application/json" \
- -d '{
- "event_type": "task_completed",
- "url": "https://example.com/webhook/completed"
- }'
-```
-
-**响应**:
-```json
-{
- "status": "success",
- "message": "Webhook 已注册: task_completed -> https://example.com/webhook/completed"
-}
-```
-
-#### 注销 Webhook
-
-```bash
-curl -X DELETE "http://127.0.0.1:8000/v1/webhooks/task_completed"
-```
-
-**响应**:
-```json
-{
- "status": "success",
- "message": "Webhook 已注销: task_completed"
-}
-```
-
-### Webhook Payload
-
-#### task_completed
-
-```json
-{
- "event": "task_completed",
- "task_id": "abc123",
- "status": "success",
- "url": "/files/abc123/output.ply",
- "processing_time": 15.5,
- "timestamp": 1706659200.0
-}
-```
-
-#### task_failed
-
-```json
-{
- "event": "task_failed",
- "task_id": "abc123",
- "status": "error",
- "error": "显存不足",
- "timestamp": 1706659200.0
-}
-```
-
-### HTTP Headers
-
-每个 Webhook 请求包含以下 HTTP 头:
-
-| Header | 说明 |
-|--------|------|
-| Content-Type | application/json |
-| X-Webhook-Event | 事件类型 |
-| X-Webhook-Timestamp | 时间戳 |
-
-### 最佳实践
-
-1. **验证签名**: 生产环境应验证 Webhook 签名
-2. **幂等处理**: 确保重复 Webhook 不会导致问题
-3. **超时处理**: 设置合理的超时时间
-4. **错误重试**: 实现指数退避重试机制
-
-
-
----
-
-## 监控指标
-
-
-点击展开查看监控指标详情
-
-## MLSharp 提供了完整的 Prometheus 兼容监控指标,可用于性能监控和问题诊断。
-
-### 启用监控
-
-监控功能默认启用,可通过配置文件控制:
-
-```yaml
-# config.yaml
-monitoring:
- enabled: true # 启用监控
- enable_gpu: true # 启用 GPU 监控
- metrics_path: "/metrics" # Prometheus 指标端点路径
-```
-
-### 访问指标
-
-启动服务后,可以通过以下方式访问监控指标:
-
-```bash
-# 访问 Prometheus 指标端点
-curl http://127.0.0.1:8000/metrics
-```
-
-### 监控指标说明
-
-#### HTTP 请求指标
-
-| 指标名称 | 类型 | 说明 |
-|---------------------------------|-----------|-------------|
-| `http_requests_total` | Counter | HTTP 请求总数 |
-| `http_request_duration_seconds` | Histogram | HTTP 请求响应时间 |
-
-**标签**:
-- `method`: HTTP 方法(GET, POST 等)
-- `endpoint`: 端点路径
-- `status`: HTTP 状态码
-
-#### 预测请求指标
-
-| 指标名称 | 类型 | 说明 |
-|----------------------------------|-----------|---------|
-| `predict_requests_total` | Counter | 预测请求总数 |
-| `predict_duration_seconds` | Histogram | 预测请求总耗时 |
-| `predict_stage_duration_seconds` | Histogram | 预测各阶段耗时 |
-
-**标签**:
-- `status`: 请求状态(success/error)
-- `stage`: 阶段名称(image_load, inference, ply_save, total)
-
-#### GPU 监控指标
-
-| 指标名称 | 类型 | 说明 |
-|---------------------------|-------|---------------|
-| `gpu_memory_used_mb` | Gauge | GPU 内存使用量(MB) |
-| `gpu_utilization_percent` | Gauge | GPU 利用率百分比 |
-| `gpu_info` | Gauge | GPU 信息 |
-
-**标签**:
-- `device_id`: 设备 ID
-- `name`: GPU 名称
-- `vendor`: 厂商名称
-
-#### 系统指标
-
-| 指标名称 | 类型 | 说明 |
-|-------------------|-------|---------|
-| `active_tasks` | Gauge | 当前活跃任务数 |
-| `app_info` | Info | 应用信息 |
-| `input_size_info` | Gauge | 输入图像尺寸 |
-
-### Prometheus 集成
-
-#### 安装 Prometheus
-
-```bash
-# 下载 Prometheus
-wget https://github.com/prometheus/prometheus/releases/download/v2.47.0/prometheus-2.47.0.linux-amd64.tar.gz
-tar xvfz prometheus-2.47.0.linux-amd64.tar.gz
-cd prometheus-2.47.0.linux-amd64
-
-# 创建配置文件
-cat > prometheus.yml << EOF
-global:
- scrape_interval: 15s
-
-scrape_configs:
- - job_name: 'mlsharp'
- static_configs:
- - targets: ['localhost:8000']
-EOF
-
-# 启动 Prometheus
-./prometheus
-```
-
-访问 Prometheus UI: http://localhost:9090
-
-#### 使用 Grafana 可视化
-
-1. 安装 Grafana
-2. 添加 Prometheus 数据源
-3. 创建仪表板
-
-**推荐仪表板配置**:
-
-- HTTP 请求速率: `rate(http_requests_total[5m])`
-- 预测请求速率: `rate(predict_requests_total[5m])`
-- 平均响应时间: `rate(http_request_duration_seconds_sum[5m]) / rate(http_request_duration_seconds_count[5m])`
-- GPU 内存使用: `gpu_memory_used_mb`
-- GPU 利用率: `gpu_utilization_percent`
-- 活跃任务数: `active_tasks`
-
-### 性能监控示例
-
-#### 查看请求速率
-
-```bash
-# 查看最近 5 分钟的请求速率
-curl 'http://localhost:9090/api/v1/query?query=rate(http_requests_total[5m])'
-```
-
-#### 查看平均响应时间
-
-```bash
-# 查看最近 5 分钟的平均响应时间
-curl 'http://localhost:9090/api/v1/query?query=rate(http_request_duration_seconds_sum[5m])%20%2F%20rate(http_request_duration_seconds_count[5m])'
-```
-
-#### 查看 GPU 使用情况
-
-```bash
-# 查看 GPU 内存使用
-curl 'http://localhost:9090/api/v1/query?query=gpu_memory_used_mb'
-
-# 查看 GPU 利用率
-curl 'http://localhost:9090/api/v1/query?query=gpu_utilization_percent'
-```
-
-### 监控最佳实践
-
-1. **设置告警规则**
- - 请求错误率超过 5%
- - 平均响应时间超过 60 秒
- - GPU 内存使用超过 90%
- - GPU 利用率超过 95%
-
-2. **定期检查指标**
- - 每天查看请求量和响应时间趋势
- - 监控 GPU 资源使用情况
- - 分析错误日志和失败请求
-
-3. **性能优化**
- - 根据响应时间调整输入尺寸
- - 根据 GPU 使用情况优化并发数
- - 根据错误率优化模型配置
- - 显存不足时启用梯度检查点(--gradient-checkpointing)
-
-
-
----
-
-## API 文档
-
-
-点击展开查看 API 文档详情
-
-## MLSharp 提供了完整的 REST API,支持从单张图片生成 3D 模型。
-
-### 访问地址
-
-启动服务后,可以通过以下方式访问 API 文档:
-
-- **Swagger UI**: http://127.0.0.1:8000/docs
-- **ReDoc**: http://127.0.0.1:8000/redoc
-- **OpenAPI JSON**: http://127.0.0.1:8000/openapi.json
-
-### API 版本控制
-
-所有 API 端点都使用版本控制,当前版本为 `v1`。
-
-| 版本 | 基础路径 | 状态 |
-|-----|----------|--------|
-| v1 | `/v1` | 当前版本 |
-| v2 | `/v2` | 计划中 |
-
-**向后兼容性**: v1 API 将继续维护和更新。
-
-### 认证方式
-
-当前版本无需认证,未来版本将支持 API Key 和 JWT Token 认证。
-
-### 响应格式
-
-所有 API 响应使用 JSON 格式。
-
-#### 成功响应
-
-```json
-{
- "status": "success",
- "url": "http://127.0.0.1:8000/files/abc123/output.ply",
- "processing_time": 15.5,
- "task_id": "abc123"
-}
-```
-
-#### 错误响应
-
-```json
-{
- "error": "ValidationError",
- "message": "请求参数验证失败",
- "status_code": 422,
- "path": "/v1/predict",
- "timestamp": "2026-01-31T12:00:00Z"
-}
-```
-
-### API 端点
-
-#### 1. 预测接口
-
-**端点**: `POST /v1/predict`
-
-**描述**: 从单张图片生成 3D 模型
-
-**请求**:
-- **Method**: POST
-- **Content-Type**: multipart/form-data
-- **Body**:
- - `file`: 图片文件(JPG 格式,推荐尺寸: 512x512 - 1024x1024)
-
-**响应模型**:
-```json
-{
- "status": "string",
- "url": "string",
- "processing_time": "float",
- "task_id": "string"
-}
-```
-
-**示例**:
-```bash
-curl -X POST "http://127.0.0.1:8000/v1/predict" \
- -F "file=@input.jpg"
-```
-
-**Python 示例**:
-```python
-import requests
-
-with open('input.jpg', 'rb') as f:
- response = requests.post(
- 'http://127.0.0.1:8000/v1/predict',
- files={'file': f}
- )
- result = response.json()
- print(f"3D 模型 URL: {result['url']}")
-```
-
-#### 2. 健康检查
-
-**端点**: `GET /v1/health`
-
-**描述**: 检查服务是否正常运行以及 GPU 状态
-
-**响应模型**:
-```json
-{
- "status": "string",
- "gpu_available": "boolean",
- "gpu_vendor": "string",
- "gpu_name": "string"
-}
-```
-
-**示例**:
-```bash
-curl "http://127.0.0.1:8000/v1/health"
-```
-
-**响应**:
-```json
-{
- "status": "healthy",
- "gpu_available": true,
- "gpu_vendor": "NVIDIA",
- "gpu_name": "NVIDIA GeForce RTX 4090"
-}
-```
-
-#### 3. 系统统计
-
-**端点**: `GET /v1/stats`
-
-**描述**: 获取系统统计信息
-
-**响应模型**:
-```json
-{
- "gpu": {
- "available": "boolean",
- "vendor": "string",
- "name": "string",
- "count": "integer",
- "memory_mb": "float"
- }
-}
-```
-
-**示例**:
-```bash
-curl "http://127.0.0.1:8000/v1/stats"
-```
-
-**响应**:
-```json
-{
- "gpu": {
- "available": true,
- "vendor": "NVIDIA",
- "name": "NVIDIA GeForce RTX 4090",
- "count": 1,
- "memory_mb": 2048.5
- }
-}
-```
-
-#### 4. 缓存统计
-
-**端点**: `GET /v1/cache`
-
-**描述**: 获取缓存统计信息
-
-**响应模型**:
-```json
-{
- "enabled": "boolean",
- "size": "integer",
- "max_size": "integer",
- "hits": "integer",
- "misses": "integer",
- "hit_rate": "float"
-}
-```
-
-**示例**:
-```bash
-curl "http://127.0.0.1:8000/v1/cache"
-```
-
-**响应**:
-```json
-{
- "enabled": true,
- "size": 45,
- "max_size": 100,
- "hits": 120,
- "misses": 30,
- "hit_rate": 80.0
-}
-```
-
-#### 5. 清空缓存
-
-**端点**: `POST /v1/cache/clear`
-
-**描述**: 清空所有缓存条目
-
-**响应模型**:
-```json
-{
- "status": "string",
- "message": "string"
-}
-```
-
-**示例**:
-```bash
-curl -X POST "http://127.0.0.1:8000/v1/cache/clear"
-```
-
-**响应**:
-```json
-{
- "status": "success",
- "message": "缓存已清空"
-}
-```
-
-#### 6. Prometheus 指标
-
-**端点**: `GET /metrics`
-
-**描述**: 获取 Prometheus 格式的监控指标
-
-**响应格式**: text/plain
-
-**示例**:
-```bash
-curl "http://127.0.0.1:8000/metrics"
-```
-
-#### 7. 获取 Webhook 列表
-
-**端点**: `GET /v1/webhooks`
-
-**描述**: 获取所有已注册的 Webhook
-
-**响应模型**:
-```json
-{
- "enabled": "boolean",
- "webhooks": {
- "event_type": "string"
- }
-}
-```
-
-**示例**:
-```bash
-curl "http://127.0.0.1:8000/v1/webhooks"
-```
-
-**响应**:
-```json
-{
- "enabled": true,
- "webhooks": {
- "task_completed": "https://example.com/webhook/completed",
- "task_failed": "https://example.com/webhook/failed"
- }
-}
-```
-
-#### 8. 注册 Webhook
-
-**端点**: `POST /v1/webhooks`
-
-**描述**: 注册一个新的 Webhook
-
-**请求体**:
-```json
-{
- "event_type": "string",
- "url": "string"
-}
-```
-
-**响应模型**:
-```json
-{
- "status": "string",
- "message": "string"
-}
-```
-
-**示例**:
-```bash
-curl -X POST "http://127.0.0.1:8000/v1/webhooks" \
- -H "Content-Type: application/json" \
- -d '{
- "event_type": "task_completed",
- "url": "https://example.com/webhook/completed"
- }'
-```
-
-**响应**:
-```json
-{
- "status": "success",
- "message": "Webhook 已注册: task_completed -> https://example.com/webhook/completed"
-}
-```
-
-#### 9. 注销 Webhook
-
-**端点**: `DELETE /v1/webhooks/{event_type}`
-
-**描述**: 注销指定事件类型的 Webhook
-
-**路径参数**:
-- `event_type`: 事件类型
-
-**响应模型**:
-```json
-{
- "status": "string",
- "message": "string"
-}
-```
-
-**示例**:
-```bash
-curl -X DELETE "http://127.0.0.1:8000/v1/webhooks/task_completed"
-```
-
-**响应**:
-```json
-{
- "status": "success",
- "message": "Webhook 已注销: task_completed"
-}
-```
-
-### 错误处理
-
-API 使用标准 HTTP 状态码表示请求状态:
-
-| 状态码 | 说明 |
-|------|---------------------|
-| 200 | 成功 |
-| 400 | 请求参数错误 |
-| 404 | 资源不存在 |
-| 422 | 请求参数验证失败(Pydantic) |
-| 500 | 服务器内部错误 |
-
-### 完整 Python 客户端示例
-
-```python
-import requests
-import json
-
-class MLSharpClient:
- """MLSharp 3D Maker API 客户端"""
-
- def __init__(self, base_url="http://127.0.0.1:8000"):
- self.base_url = base_url
- self.api_base = f"{base_url}/v1"
-
- def predict(self, image_path):
- """从图片生成 3D 模型"""
- with open(image_path, 'rb') as f:
- response = requests.post(
- f"{self.api_base}/predict",
- files={'file': f}
- )
- response.raise_for_status()
- return response.json()
-
- def health(self):
- """健康检查"""
- response = requests.get(f"{self.api_base}/health")
- response.raise_for_status()
- return response.json()
-
- def stats(self):
- """获取系统统计"""
- response = requests.get(f"{self.api_base}/stats")
- response.raise_for_status()
- return response.json()
-
- def cache_stats(self):
- """获取缓存统计"""
- response = requests.get(f"{self.api_base}/cache")
- response.raise_for_status()
- return response.json()
-
- def clear_cache(self):
- """清空缓存"""
- response = requests.post(f"{self.api_base}/cache/clear")
- response.raise_for_status()
- return response.json()
-
- def list_webhooks(self):
- """获取 Webhook 列表"""
- response = requests.get(f"{self.api_base}/webhooks")
- response.raise_for_status()
- return response.json()
-
- def register_webhook(self, event_type: str, url: str):
- """注册 Webhook"""
- response = requests.post(
- f"{self.api_base}/webhooks",
- json={"event_type": event_type, "url": url}
- )
- response.raise_for_status()
- return response.json()
-
- def unregister_webhook(self, event_type: str):
- """注销 Webhook"""
- response = requests.delete(f"{self.api_base}/webhooks/{event_type}")
- response.raise_for_status()
- return response.json()
-
-# 使用示例
-if __name__ == "__main__":
- client = MLSharpClient()
-
- # 健康检查
- health = client.health()
- print(f"服务状态: {health['status']}")
- print(f"GPU: {health['gpu_name']}")
-
- # 生成 3D 模型
- result = client.predict("input.jpg")
- print(f"任务 ID: {result['task_id']}")
- print(f"处理时间: {result['processing_time']:.2f} 秒")
- print(f"下载 URL: {result['url']}")
-```
-
-### 最佳实践
-
-1. **错误处理**: 始终检查响应状态码和错误消息
-2. **重试机制**: 对网络错误实现指数退避重试
-3. **超时设置**: 为所有请求设置合理的超时时间
-4. **缓存利用**: 利用缓存 API 避免重复计算
-5. **健康检查**: 定期调用健康检查接口监控服务状态
-6. **日志记录**: 记录所有 API 调用和响应时间
-
-
-
----
-
-## 代码架构
-
-
-点击展开查看代码架构详情
-
-### 核心类
-
-#### 1. 配置类
-- **AppConfig**: 应用配置管理
-- **GPUConfig**: GPU 配置和状态
-- **CLIArgs**: 命令行参数解析
-
-#### 2. 工具类
-- **Logger**: 统一日志输出
-
-#### 3. 管理器类
-- **GPUManager**: GPU 检测、初始化和优化配置
-- **ModelManager**: 模型加载和推理管理
-- **MetricsManager**: 监控指标收集和管理
-
-#### 4. 应用主类
-- **MLSharpApp**: 应用主入口和生命周期管理
-
-### 代码质量改进
-
-| 方面 | 改进 |
-|-------|---------------------------|
-| 代码行数 | 减少 33.84%(1965 → ~1300 行) |
-| 类型提示 | 完整覆盖 |
-| 文档字符串 | 所有类和方法 |
-| 代码复用 | 消除重复 |
-| 可测试性 | 组件独立 |
-| 可维护性 | 显著提升 |
-
-### 性能对比
-
-| 指标 | 重构前 | 重构后 | 变化 |
-|------|---------|---------|-------|
-| 启动时间 | ~15-20秒 | ~5-10秒 | 减少50% |
-| 首次推理 | ~30-40秒 | ~30-40秒 | 无变化 |
-| 后续推理 | ~15-20秒 | ~15-20秒 | 无变化 |
-| 内存占用 | ~2-4GB | ~2-4GB | 无变化 |
-
-
-
----
-
-## 当前已知问题
-
-
-点击展开查看当前已知问题
-
-### 问题 1: CUDA 不可用(Intel 集显 + NVIDIA 独显)
-**症状**: 系统检测到 NVIDIA 显卡但提示 CUDA 不可用
-**原因**: PyTorch 可能未编译 CUDA 支持或驱动未正确安装
-**解决方案**:
-```bash
-# 检查 CUDA 是否可用
-python -c "import torch; print(torch.cuda.is_available())"
-
-# 如果返回 False,重新安装带 CUDA 的 PyTorch
-pip install torch torchvision --index-url https://download.pytorch.org/whl/cu121
-```
-
-### 问题 2: ProcessPoolExecutor 内存占用较高
-**症状**: 多个并发请求时内存占用增长较快
-**原因**: 进程池会为每个进程创建独立的内存空间
-**解决方案**:
-- 减少进程池大小:`max_workers=2`
-- 或回退到线程池:改用 `ThreadPoolExecutor`
-
-### 问题 3: 日志文件可能过大
-**症状**: logs/ 目录占用大量磁盘空间
-**原因**: loguru 默认不限制日志文件大小
-**解决方案**:
-- 定期清理旧日志文件
-- 或在配置中启用日志压缩
-
-
-
----
-
-## 故障排除
-
-
-点击展开查看故障排除详情
-
-### 问题 1: 启动失败
-**症状**: 双击启动脚本后闪退或报错
-
-**解决方案**:
-1. 检查 Python 环境是否完整
-2. 查看日志文件 `logs/` 中的错误信息
-3. 使用命令行参数查看详细错误:`python app.py --no-browser`
-4. 检查项目路径是否存在中文
-
-### 问题 2: GPU 检测不到
-**症状**: 提示使用 CPU 模式,但实际有 GPU
-
-**解决方案**:
-1. NVIDIA 用户检查显卡驱动和 CUDA
-2. AMD 用户检查 ROCm 驱动
-3. 检查显卡是否被其他程序占用
-4. 使用命令行参数强制指定:`python app.py --mode nvidia`
-
-### 问题 3: GPU 厂商检测错误
-**症状**: NVIDIA GPU 被误识别为 AMD 或 Intel
-
-**解决方案**:
-1. 使用命令行参数强制指定模式:`python app.py --mode nvidia`
-2. 手动选择对应的启动脚本
-
-### 问题 4: 内存不足
-**症状**: 提示显存不足或程序崩溃
-
-**解决方案**:
-1. 使用较小的输入图片(建议 < 1024x1024)
-2. 关闭其他占用显存的程序
-3. 使用 CPU 模式:`python app.py --mode cpu`
-4. 禁用混合精度:`python app.py --no-amp`
-5. 启用梯度检查点:`python app.py --gradient-checkpointing`(减少 30-50% 显存)
-
-### 问题 5: 推理速度慢
-**症状**: 推理时间过长
-
-**可能原因**:
-- 使用 CPU 模式
-- 老旧 GPU
-- 显存不足
-- 图片过大
-- 缓存未启用
-
-**解决方案**:
-1. 使用 GPU 模式(如果可用)
-2. 使用更快的启动脚本
-3. 缩小输入图片尺寸
-4. 升级硬件
-5. 启用缓存:`python app.py --enable-cache`(默认已启用)
-6. 增加缓存大小:`python app.py --cache-size 200`
-
-### 问题 6: 缓存占用内存过多
-**症状**: 程序运行时间过长后内存占用持续增长
-
-**解决方案**:
-1. 减小缓存大小:`python app.py --cache-size 50`
-2. 禁用缓存:`python app.py --no-cache`
-3. 定期清空缓存:调用 `POST /v1/cache/clear` API
-4. 重启服务
-
-### 问题 7: 缓存未生效
-**症状**: 重复处理相同图片时速度没有提升
-
-**可能原因**:
-- 缓存被禁用
-- 图片内容或焦距略有不同
-- 缓存已满并被淘汰
-
-**解决方案**:
-1. 检查缓存是否启用:访问 `GET /v1/cache` 查看 `enabled` 字段
-2. 确保使用完全相同的图片和焦距
-3. 增加缓存大小:`python app.py --cache-size 200`
-4. 查看缓存命中率:访问 `GET /v1/cache` 查看 `hit_rate`
-
-### 问题 8: 端口被占用
-**症状**: 启动时报错端口已被使用
-
-**解决方案**:
-1. 使用其他端口:`python app.py --port 8080`
-2. 关闭占用 8000 端口的程序
-3. 使用命令查找并关闭占用端口的进程
-
-
-
----
-
-## 版本历史
-
-
-点击展开查看版本历史
-
-### v9.0 (2026-01-31)
-- Redis 分布式缓存支持
-- Webhook 异步通知功能
-- 任务完成和失败通知
-- 缓存混合使用(Redis + 本地)
-- Webhook 注册和管理 API
-- 新增依赖:pydantic、redis、httpx
-- 项目完成度达到 100%
-
-### v8.0 (2026-01-31)
-- API 版本控制(v1)
-- Pydantic 数据验证
-- 统一错误响应模型
-- Swagger/OpenAPI 文档
-- 完整的 API 使用文档
-- 项目完成度提升至 98%
-
-### v7.5 (2026-01-29)
-- 性能自动调优
-- 智能基准测试
-- 最优配置选择
-- 性能提升 30-50%
-
-### v7.4 (2026-01-28)
-- 推理缓存功能
-- 智能哈希缓存键
-- LRU 淘汰算法
-- 缓存统计监控
-
-### v7.3 (2026-01-27)
-- 梯度检查点
-- 显存优化 30-50%
-- 智能内存管理
-
-### v7.2 (2026-01-26)
-- Prometheus 监控集成
-- 完整的监控指标
-- GPU 资源监控
-
-### v7.1 (2026-01-25)
-- 输入尺寸参数
-- 自动验证和调整
-- 最大限制 1536x1536
-
-### v7.0 (2026-01-24)
-- 异步优化升级
-- ProcessPoolExecutor
-- 健康检查和统计 API
-- 并发处理能力提升 30-50%
-
-### v6.2 (2026-01-23)
-- 日志系统升级
-- loguru 集成
-- 结构化日志
-- 文件日志轮转
-
-### v6.1 (2026-01-22)
-- 配置文件支持
-- YAML 和 JSON 格式
-- 灵活配置管理
-
-### v6.0 (2026-01-21)
-- 代码重构
-- 面向对象设计
-- 管理器模式
-- 类型提示完善
-
-### v5.0 (2026-01-24)
-- 全面兼容性升级
-- 支持 NVIDIA、AMD、Intel 显卡
-- 老旧 GPU 支持
-- Windows 11 兼容
-
-### v4.0 (2026-01-17)
-- 智能自动诊断程序(现已弃用)
-- GPU 兼容性修复
-- 日志系统(现已改进)
-- Unicode 编码修复
-
-### v3.0
-- GPU 混合精度推理(AMP)
-- cuDNN Benchmark 自动优化
-- TensorFloat32 矩阵乘法加速
-- CPU 多线程优化
-
-
-
----
-
-## 技术栈
-
-- **后端框架**: FastAPI + Uvicorn
-- **深度学习**: PyTorch + Apple ml-sharp 模型
-- **3D 渲染**: 3D Gaussian Splatting
-- **GPU 加速**: CUDA (NVIDIA) / ROCm (AMD) / ONNX (Snapdragon) **Preview**
-- **CPU 优化**: OpenMP / MKL
-- **日志系统**: Loguru
-- **监控指标**: Prometheus + Prometheus Client
-- **架构设计**: 面向对象 + 管理器模式
-
----
-
-## 许可证
-本项目基于 Apple ml-sharp 模型,请遵守相关开源协议。
-
----
-
-## 未来改进
-
-### 已完成
-- 单元测试: 为每个类添加单元测试
-- 配置文件: 支持从配置文件加载配置
-- 日志系统: 使用专业的日志库(如 loguru)
-- 异步优化: 进一步优化异步处理
-
-
-点击展开查看未来改进计划
-
-### 待改进
-#### 高优先级
-1. **认证授权** - 添加用户认证
- - API Key 认证
- - JWT Token 支持
- - 速率限制
-
-#### 中优先级
-1. **任务队列** - 异步任务处理
- - Redis 队列支持
- - 任务状态追踪
- - 批量处理支持
-
-2. **批量处理 API** - 批量图片处理
- - 多文件上传
- - 批量预测
- - 结果打包下载
-
-#### 低优先级
-1. **国际化** - 多语言支持
- - i18n 支持
- - 中英文界面
- - 可扩展语言包
-
-2. **插件系统** - 可扩展架构
- - 自定义插件
- - 模型插件
- - 后处理插件
-
-3. **批处理 API** - 批量图片处理
- - 多文件上传
- - 批量预测
- - 结果打包下载
-
-
----
-
-## 贡献
-
-欢迎提交 **Issue** 和 **Pull Request!**
-
-
-## 联系方式
-
-- 项目主页: [https://github.com/ChidcGithub/MLSharp-3D-Maker-GPU](https://github.com/ChidcGithub/MLSharp-3D-Maker-GPU)
-- 问题反馈: [Issues](https://github.com/ChidcGithub/MLSharp-3D-Maker-GPU/issues)
-
----
-
-
-
-**如果这个项目对你有帮助,请给个 ⭐️ Star!**
-
-Modded with ❤️ by Chidc with CPU-Mode-Provider GemosDoDo
-README.md Verison Code **2601311936**
+README.md Verison Code **2602052238**
diff --git a/Start.ps1 b/Start.ps1
index a333cf7..dd1e47b 100644
--- a/Start.ps1
+++ b/Start.ps1
@@ -105,10 +105,10 @@ Write-Host "系统信息" -ForegroundColor Cyan
Write-Host "==============================================================================================" -ForegroundColor Cyan
Write-Host ""
Write-Host "支持模式:" -ForegroundColor Yellow
-Write-Host " [OK] NVIDIA GPU (CUDA)" -ForegroundColor Green
-Write-Host " [OK] AMD GPU (ROCm)" -ForegroundColor Green
-Write-Host " [OK] Intel GPU (CPU 回退)" -ForegroundColor Green
-Write-Host " [OK] CPU 模式" -ForegroundColor Green
+Write-Host "NVIDIA GPU (CUDA)" -ForegroundColor Green
+Write-Host "AMD GPU (ROCm)" -ForegroundColor Green
+Write-Host "Intel GPU (CPU 回退)" -ForegroundColor Green
+Write-Host "CPU 模式" -ForegroundColor Green
Write-Host ""
Write-Host "==============================================================================================" -ForegroundColor Cyan
Write-Host ""
@@ -118,7 +118,7 @@ Write-Host ""
Write-Host "==============================================================================================" -ForegroundColor Cyan
Write-Host ""
-& $pythonPath "app.py" --enable-auto-tune
+& $pythonPath "app.py" --enable-auto-tune --config config/config.yaml
# 错误处理
if ($LASTEXITCODE -ne 0) {
diff --git a/app.py b/app.py
index fc4c37a..b0c3a22 100644
--- a/app.py
+++ b/app.py
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
"""
MLSharp-3D-Maker - 统一版本
-支持 NVIDIA/AMD/Intel GPU 和 CPU,自动检测并优化
+支持 NVIDIA/AMD/Intel/Snapdragon GPU 和 CPU,自动检测并优化
"""
import sys
import os
@@ -19,6 +19,7 @@
from concurrent.futures import ThreadPoolExecutor
from dataclasses import dataclass
from typing import Optional, Tuple, Dict, Any
+from pydantic import BaseModel, Field
import numpy as np
import torch
@@ -27,6 +28,14 @@
from loguru import logger
from metrics import init_metrics, get_metrics_manager
+# ONNX Runtime for Snapdragon GPU acceleration
+try:
+ import onnxruntime as ort
+ ONNXRUNTIME_AVAILABLE = True
+except ImportError:
+ ONNXRUNTIME_AVAILABLE = False
+ ort = None
+
# 设置输出编码为 UTF-8(Windows)
if sys.platform == 'win32':
import codecs
@@ -72,6 +81,9 @@ class GPUConfig:
use_cudnn_benchmark: bool = False
use_tf32: bool = False
is_rocm: bool = False
+ is_adreno: bool = False
+ use_onnxruntime: bool = False
+ onnx_execution_provider: Optional[str] = None
@dataclass
@@ -91,6 +103,9 @@ class CLIArgs:
cache_size: int = 100
clear_cache: bool = False
enable_auto_tune: bool = False
+ redis_url: Optional[str] = None
+ enable_webhook: bool = False
+ app_config: Optional[AppConfig] = None # 应用配置(用于性能自动调优)
# ================= 配置文件加载 =================
@@ -368,8 +383,8 @@ def parse_command_args() -> Tuple[CLIArgs, Optional[Dict[str, Any]]]:
)
parser.add_argument('--mode', '-m', type=str, default='auto',
- choices=['auto', 'gpu', 'cpu', 'nvidia', 'amd'],
- help='启动模式:auto(自动), gpu(GPU), cpu(CPU), nvidia(NVIDIA), amd(AMD)')
+ choices=['auto', 'gpu', 'cpu', 'nvidia', 'amd', 'qualcomm'],
+ help='启动模式:auto(自动), gpu(GPU), cpu(CPU), nvidia(NVIDIA), amd(AMD), qualcomm(Snapdragon)')
parser.add_argument('--port', '-p', type=int, default=8000,
help='Web 服务端口(默认:8000)')
parser.add_argument('--host', type=str, default='127.0.0.1',
@@ -399,6 +414,10 @@ def parse_command_args() -> Tuple[CLIArgs, Optional[Dict[str, Any]]]:
help='启动时清空缓存')
parser.add_argument('--enable-auto-tune', action='store_true',
help='启用性能自动调优(启动时自动测试并选择最优配置)')
+ parser.add_argument('--redis-url', type=str, default=None,
+ help='Redis 连接 URL(例如:redis://localhost:6379/0)')
+ parser.add_argument('--enable-webhook', action='store_true',
+ help='启用 Webhook 通知')
args = parser.parse_args()
@@ -461,6 +480,10 @@ def parse_command_args() -> Tuple[CLIArgs, Optional[Dict[str, Any]]]:
enable_auto_tune=args.enable_auto_tune
)
+ # 设置 app_config(性能自动调优需要)
+ app_config = AppConfig.from_current_dir()
+ cli_args.app_config = app_config
+
return cli_args, config_dict
@@ -478,6 +501,7 @@ class GPUManager:
def __init__(self, config: GPUConfig, args: CLIArgs):
self.config = config
self.args = args
+ self.app_config = args.app_config if hasattr(args, 'app_config') else None
self.device = torch.device("cpu")
@staticmethod
@@ -486,7 +510,7 @@ def detect_gpu_vendor_wmi() -> str:
try:
# 首先尝试使用 PowerShell Get-CimInstance(Windows 11 推荐)
result = subprocess.run(
- ['powershell', '-Command',
+ ['powershell', '-Command',
'Get-CimInstance Win32_VideoController | Select-Object -ExpandProperty Name'],
capture_output=True, text=True, encoding='utf-8', errors='ignore'
)
@@ -496,21 +520,26 @@ def detect_gpu_vendor_wmi() -> str:
nvidia_found = False
amd_found = False
intel_found = False
-
+ adreno_found = False
+
for line in lines:
name = line.strip().lower()
if 'nvidia' in name or 'geforce' in name or 'quadro' in name or 'tesla' in name or 'rtx' in name or 'gtx' in name:
nvidia_found = True
elif 'amd' in name or 'radeon' in name or 'rx' in name:
amd_found = True
+ elif 'snapdragon' in name or 'adreno' in name or 'qualcomm' in name:
+ adreno_found = True
elif 'intel' in name or 'iris' in name or 'uhd' in name or 'arc' in name:
intel_found = True
-
+
# 返回优先级最高的厂商
if nvidia_found:
return 'NVIDIA'
elif amd_found:
return 'AMD'
+ elif adreno_found:
+ return 'Qualcomm'
elif intel_found:
return 'Intel'
else:
@@ -524,20 +553,25 @@ def detect_gpu_vendor_wmi() -> str:
nvidia_found = False
amd_found = False
intel_found = False
-
+ adreno_found = False
+
for line in lines:
name = line.strip().lower()
if 'nvidia' in name or 'geforce' in name or 'quadro' in name or 'tesla' in name or 'rtx' in name or 'gtx' in name:
nvidia_found = True
elif 'amd' in name or 'radeon' in name or 'rx' in name:
amd_found = True
+ elif 'snapdragon' in name or 'adreno' in name or 'qualcomm' in name:
+ adreno_found = True
elif 'intel' in name or 'iris' in name or 'uhd' in name or 'arc' in name:
intel_found = True
-
+
if nvidia_found:
return 'NVIDIA'
elif amd_found:
return 'AMD'
+ elif adreno_found:
+ return 'Qualcomm'
elif intel_found:
return 'Intel'
except Exception as e:
@@ -558,6 +592,55 @@ def check_rocm_available() -> bool:
except Exception as e:
Logger.warning(f"ROCm 检测失败: {e}")
return False
+
+ @staticmethod
+ def check_adreno_available() -> bool:
+ """检查 Adreno (Snapdragon) GPU 是否可用"""
+ try:
+ import torch
+ # Snapdragon GPU 通常通过 OpenCL/Vulkan,而不是 CUDA
+ if hasattr(torch, 'backends') and hasattr(torch.backends, 'opencl'):
+ if torch.backends.opencl.is_available():
+ return True
+ # 检查是否有 qnn 或 snpe 相关模块
+ try:
+ import importlib
+ if importlib.util.find_spec('qnn') or importlib.util.find_spec('snpe'):
+ return True
+ except:
+ pass
+ return False
+ except Exception as e:
+ Logger.warning(f"Adreno 检测失败: {e}")
+ return False
+
+ @staticmethod
+ def check_onnxruntime_available() -> Tuple[bool, Optional[str]]:
+ """检查 ONNX Runtime 是否可用并返回执行提供者"""
+ if not ONNXRUNTIME_AVAILABLE:
+ return False, None
+
+ try:
+ available_providers = ort.get_available_providers()
+ Logger.info(f"ONNX Runtime 可用的执行提供者: {available_providers}")
+
+ # 优先级:DirectML > CUDA > ROCm > CPU
+ for provider in ['DmlExecutionProvider', 'CUDAExecutionProvider', 'ROCMExecutionProvider', 'CPUExecutionProvider']:
+ if provider in available_providers:
+ if provider == 'DmlExecutionProvider':
+ Logger.success(" DirectML 执行提供者可用 (支持 Snapdragon GPU)")
+ return True, provider
+ elif provider == 'CPUExecutionProvider':
+ Logger.info(" 仅 CPU 执行提供者可用")
+ return True, provider
+ else:
+ Logger.info(f" {provider} 可用")
+ return True, provider
+
+ return False, None
+ except Exception as e:
+ Logger.warning(f"ONNX Runtime 检测失败: {e}")
+ return False, None
def initialize(self) -> torch.device:
"""初始化 GPU 设备"""
@@ -596,6 +679,11 @@ def initialize(self) -> torch.device:
elif 'amd' in gpu_name_lower or 'radeon' in gpu_name_lower or 'rx' in gpu_name_lower:
self.config.vendor = "AMD"
Logger.success(f"检测到 AMD GPU: {self.config.name}")
+ elif 'snapdragon' in gpu_name_lower or 'adreno' in gpu_name_lower or 'qualcomm' in gpu_name_lower:
+ self.config.vendor = "Qualcomm"
+ self.config.is_adreno = True
+ Logger.success(f"检测到 Snapdragon/Adreno GPU: {self.config.name}")
+ Logger.info(" Adreno GPU 检测到,将使用 CPU 模式运行")
elif 'intel' in gpu_name_lower or 'iris' in gpu_name_lower or 'uhd' in gpu_name_lower or 'arc' in gpu_name_lower:
self.config.vendor = "Intel"
Logger.success(f"检测到 Intel GPU: {self.config.name}")
@@ -607,6 +695,21 @@ def initialize(self) -> torch.device:
elif system_vendor == 'AMD':
self.config.vendor = "AMD"
Logger.success(f"检测到 AMD GPU: {self.config.name}")
+ elif system_vendor == 'Qualcomm':
+ self.config.vendor = "Qualcomm"
+ self.config.is_adreno = True
+ Logger.success(f"检测到 Snapdragon/Adreno GPU: {self.config.name}")
+
+ # 检查 ONNX Runtime DirectML 支持
+ Logger.info("\n检查 ONNX Runtime DirectML 支持...")
+ onnx_available, onnx_provider = self.check_onnxruntime_available()
+ if onnx_available and onnx_provider == 'DmlExecutionProvider':
+ self.config.use_onnxruntime = True
+ self.config.onnx_execution_provider = onnx_provider
+ Logger.success(" ONNX Runtime + DirectML 已启用,可使用 GPU 加速")
+ else:
+ Logger.info(" Adreno GPU 检测到,将使用 CPU 模式运行")
+ Logger.info(" 提示: 安装 onnxruntime-gpu 可启用 DirectML 加速")
elif system_vendor == 'Intel':
self.config.vendor = "Intel"
Logger.success(f"检测到 Intel GPU: {self.config.name}")
@@ -717,7 +820,13 @@ def run_auto_tune(self):
return
try:
- tuner = PerformanceAutoTuner(self.config, self.device)
+ # 如果没有指定配置文件,使用默认的 config.yaml
+ config_file_path = self.args.config_file
+ if not config_file_path:
+ config_file_path = os.path.join(self.app_config.base_dir, 'config.yaml')
+ Logger.info(f"使用默认配置文件: {config_file_path}")
+
+ tuner = PerformanceAutoTuner(self.config, self.device, config_file_path=config_file_path)
best_config = tuner.benchmark_optimizations()
if best_config:
@@ -734,10 +843,25 @@ def _setup_cpu_mode(self):
system_vendor = self.detect_gpu_vendor_wmi()
self.config.vendor = system_vendor
self.device = torch.device("cpu")
-
+
Logger.warning("使用 CPU 模式")
Logger.info(" 原因: CUDA/ROCm 不可用")
-
+
+ # 检查 ONNX Runtime 支持(适用于 Snapdragon 等非 CUDA GPU)
+ if ONNXRUNTIME_AVAILABLE:
+ Logger.info("\n检测 ONNX Runtime 支持...")
+ onnx_available, onnx_provider = self.check_onnxruntime_available()
+ if onnx_available:
+ self.config.use_onnxruntime = True
+ self.config.onnx_execution_provider = onnx_provider
+ if onnx_provider == 'DmlExecutionProvider':
+ Logger.success(" ONNX Runtime + DirectML 已启用")
+ Logger.info(" 注意: 需要使用 ONNX 格式模型才能利用 GPU 加速")
+ else:
+ Logger.info(f" ONNX Runtime 已启用 (使用 {onnx_provider})")
+ else:
+ Logger.info(" ONNX Runtime 不可用,使用 PyTorch CPU 模式")
+
if system_vendor == "AMD":
Logger.info(" 检测到 AMD 显卡,但 PyTorch 未编译 ROCm 支持")
Logger.info(" 解决方案: 安装 ROCm 版本的 PyTorch")
@@ -747,6 +871,16 @@ def _setup_cpu_mode(self):
Logger.info(" 1. 是否安装 NVIDIA 显卡驱动")
Logger.info(" 2. 显卡是否支持 CUDA")
Logger.info(" 3. PyTorch 是否编译了 CUDA 支持")
+ elif system_vendor == "Qualcomm":
+ Logger.info(" 检测到 Snapdragon/Adreno GPU")
+ Logger.info(" Snapdragon GPU 加速方案:")
+ Logger.info(" 1. 安装 onnxruntime-gpu (Windows)")
+ Logger.info(" 2. 使用 ONNX 格式模型 + DirectML")
+ Logger.info(" 3. Android: 使用 SNPE/QNN SDK")
+ if self.config.use_onnxruntime and self.config.onnx_execution_provider == 'DmlExecutionProvider':
+ Logger.success(" 当前支持通过 ONNX Runtime + DirectML 加速")
+ else:
+ Logger.info(" 当前使用 CPU 模式运行")
elif system_vendor == "Intel":
Logger.info(" 检测到 Intel 显卡")
Logger.info(" Intel GPU 暂不支持 GPU 加速")
@@ -896,22 +1030,424 @@ def print_stats(self):
Logger.info(f"未命中次数: {stats['misses']}")
Logger.info(f"命中率: {stats['hit_rate']:.1f}%")
+# ================= Redis 缓存管理器 =================
+class RedisCacheManager:
+ """Redis 缓存管理器 - 用于分布式缓存"""
+
+ def __init__(self, redis_url: str = "redis://localhost:6379/0", prefix: str = "mlsharp"):
+ """
+ 初始化 Redis 缓存管理器
+
+ Args:
+ redis_url: Redis 连接 URL
+ prefix: 缓存键前缀
+ """
+ self.redis_url = redis_url
+ self.prefix = prefix
+ self.redis_client = None
+ self.enabled = False
+ self._init_redis()
+
+ def _init_redis(self):
+ """初始化 Redis 客户端"""
+ try:
+ import redis
+ self.redis_client = redis.from_url(self.redis_url, decode_responses=False)
+ # 测试连接
+ self.redis_client.ping()
+ self.enabled = True
+ Logger.info(f"Redis 缓存已连接: {self.redis_url}")
+ except ImportError:
+ Logger.warning("redis 模块未安装,Redis 缓存将不可用")
+ Logger.info("安装命令: pip install redis")
+ except Exception as e:
+ Logger.warning(f"Redis 连接失败: {e}")
+ Logger.info("Redis 缓存将不可用,使用本地缓存代替")
+
+ def _get_cache_key(self, image: np.ndarray, f_px: float) -> str:
+ """计算缓存键"""
+ import hashlib
+ image_hash = hashlib.md5(image.tobytes()).hexdigest()
+ return f"{self.prefix}:result:{image_hash}_{f_px:.6f}"
+
+ def get(self, image: np.ndarray, f_px: float) -> Optional[Any]:
+ """从 Redis 获取缓存结果"""
+ if not self.enabled or not self.redis_client:
+ return None
+
+ try:
+ cache_key = self._get_cache_key(image, f_px)
+ data = self.redis_client.get(cache_key)
+
+ if data:
+ # 反序列化
+ import pickle
+ result = pickle.loads(data)
+ Logger.debug(f"Redis 缓存命中: {cache_key}")
+ return result
+ else:
+ Logger.debug(f"Redis 缓存未命中: {cache_key}")
+ return None
+ except Exception as e:
+ Logger.error(f"Redis 缓存获取失败: {e}")
+ return None
+
+ def set(self, image: np.ndarray, f_px: float, result: Any, ttl: int = 3600):
+ """
+ 将结果存入 Redis 缓存
+
+ Args:
+ image: 输入图像
+ f_px: 焦距
+ result: 预测结果
+ ttl: 过期时间(秒),默认 1 小时
+ """
+ if not self.enabled or not self.redis_client:
+ return
+
+ try:
+ cache_key = self._get_cache_key(image, f_px)
+ # 序列化
+ import pickle
+ data = pickle.dumps(result)
+
+ # 存入 Redis
+ self.redis_client.setex(cache_key, ttl, data)
+ Logger.debug(f"Redis 缓存已添加: {cache_key} (TTL: {ttl}s)")
+ except Exception as e:
+ Logger.error(f"Redis 缓存存储失败: {e}")
+
+ def clear(self):
+ """清空 Redis 缓存"""
+ if not self.enabled or not self.redis_client:
+ return
+
+ try:
+ # 获取所有匹配前缀的键
+ keys = self.redis_client.keys(f"{self.prefix}:*")
+ if keys:
+ self.redis_client.delete(*keys)
+ Logger.info(f"Redis 缓存已清空: {len(keys)} 个键")
+ else:
+ Logger.info("Redis 缓存为空")
+ except Exception as e:
+ Logger.error(f"Redis 缓存清空失败: {e}")
+
+ def get_stats(self) -> Dict[str, Any]:
+ """获取 Redis 缓存统计信息"""
+ if not self.enabled or not self.redis_client:
+ return {
+ "enabled": False,
+ "type": "local"
+ }
+
+ try:
+ keys = self.redis_client.keys(f"{self.prefix}:*")
+ return {
+ "enabled": True,
+ "type": "redis",
+ "size": len(keys),
+ "url": self.redis_url
+ }
+ except Exception as e:
+ Logger.error(f"Redis 缓存统计失败: {e}")
+ return {
+ "enabled": False,
+ "type": "local",
+ "error": str(e)
+ }
+
+# ================= Webhook 管理器 =================
+class WebhookManager:
+ """Webhook 通知管理器"""
+
+ def __init__(self, enabled: bool = False):
+ """
+ 初始化 Webhook 管理器
+
+ Args:
+ enabled: 是否启用 Webhook
+ """
+ self.enabled = enabled
+ self.webhooks: Dict[str, str] = {} # event_type -> url
+ self._init_httpx()
+
+ def _init_httpx(self):
+ """初始化 HTTP 客户端"""
+ try:
+ import httpx
+ self.http_client = httpx.AsyncClient(timeout=30.0)
+ Logger.info("Webhook 客户端已初始化")
+ except ImportError:
+ Logger.warning("httpx 模块未安装,Webhook 功能将不可用")
+ Logger.info("安装命令: pip install httpx")
+ self.http_client = None
+
+ def register_webhook(self, event_type: str, url: str):
+ """
+ 注册 Webhook
+
+ Args:
+ event_type: 事件类型(task_completed, task_failed, etc.)
+ url: Webhook URL
+ """
+ if not self.enabled:
+ Logger.warning("Webhook 未启用,无法注册")
+ return
+
+ self.webhooks[event_type] = url
+ Logger.info(f"Webhook 已注册: {event_type} -> {url}")
+
+ def unregister_webhook(self, event_type: str):
+ """
+ 注销 Webhook
+
+ Args:
+ event_type: 事件类型
+ """
+ if event_type in self.webhooks:
+ del self.webhooks[event_type]
+ Logger.info(f"Webhook 已注销: {event_type}")
+
+ async def send_webhook(self, event_type: str, payload: Dict[str, Any]):
+ """
+ 发送 Webhook 通知
+
+ Args:
+ event_type: 事件类型
+ payload: 通知数据
+ """
+ if not self.enabled or event_type not in self.webhooks:
+ return
+
+ url = self.webhooks[event_type]
+
+ if not self.http_client:
+ Logger.error("HTTP 客户端未初始化,无法发送 Webhook")
+ return
+
+ try:
+ response = await self.http_client.post(
+ url,
+ json=payload,
+ headers={
+ "Content-Type": "application/json",
+ "X-Webhook-Event": event_type,
+ "X-Webhook-Timestamp": str(time.time())
+ }
+ )
+
+ if response.status_code == 200:
+ Logger.info(f"Webhook 发送成功: {event_type} -> {url}")
+ else:
+ Logger.warning(f"Webhook 发送失败: {event_type} -> {url} (状态码: {response.status_code})")
+ except Exception as e:
+ Logger.error(f"Webhook 发送异常: {event_type} -> {url} (错误: {e})")
+
+ async def notify_task_completed(self, task_id: str, url: str, processing_time: float):
+ """通知任务完成"""
+ await self.send_webhook("task_completed", {
+ "event": "task_completed",
+ "task_id": task_id,
+ "status": "success",
+ "url": url,
+ "processing_time": processing_time,
+ "timestamp": time.time()
+ })
+
+ async def notify_task_failed(self, task_id: str, error: str):
+ """通知任务失败"""
+ await self.send_webhook("task_failed", {
+ "event": "task_failed",
+ "task_id": task_id,
+ "status": "error",
+ "error": error,
+ "timestamp": time.time()
+ })
+
+ async def close(self):
+ """关闭 HTTP 客户端"""
+ if self.http_client:
+ await self.http_client.aclose()
+ Logger.info("Webhook 客户端已关闭")
+
# ================= 性能自动调优器 =================
class PerformanceAutoTuner:
"""性能自动调优器"""
- def __init__(self, gpu_config: GPUConfig, device: torch.device):
+ def __init__(self, gpu_config: GPUConfig, device: torch.device, config_file_path: str = None):
"""
初始化性能自动调优器
Args:
gpu_config: GPU 配置
device: 设备
+ config_file_path: 配置文件路径
"""
self.gpu_config = gpu_config
self.device = device
self.optimization_results = {}
+ self.config_file_path = config_file_path
+ self.cache_ttl_days = 7 # 缓存有效期(天)
+
+ def _load_cached_results(self) -> Optional[Dict[str, Any]]:
+ """
+ 加载缓存的调优结果
+
+ Returns:
+ 缓存的结果,如果过期或不存在则返回 None
+ """
+ if not self.config_file_path or not os.path.exists(self.config_file_path):
+ return None
+
+ try:
+ with open(self.config_file_path, 'r', encoding='utf-8') as f:
+ if self.config_file_path.endswith('.yaml') or self.config_file_path.endswith('.yml'):
+ config_data = yaml.safe_load(f)
+ else:
+ config_data = json.load(f)
+
+ # 检查是否有缓存的调优结果
+ cache = config_data.get('performance_cache', {})
+ if not cache:
+ return None
+
+ # 检查是否过期
+ last_test = cache.get('last_test')
+ if last_test:
+ from datetime import datetime, timezone
+ last_test_time = datetime.fromisoformat(last_test)
+ now = datetime.now(timezone.utc)
+ days_diff = (now - last_test_time).days
+
+ if days_diff < self.cache_ttl_days:
+ # 检查 GPU 是否匹配
+ cache_gpu = cache.get('gpu', {})
+ if (cache_gpu.get('name') == self.gpu_config.name and
+ cache_gpu.get('vendor') == self.gpu_config.vendor and
+ cache_gpu.get('compute_capability') == self.gpu_config.compute_capability):
+ Logger.info(f"发现有效的性能调优缓存({days_diff} 天前)")
+ return cache
+ return None
+ except Exception as e:
+ Logger.debug(f"加载性能调优缓存失败: {e}")
+ return None
+
+ def _save_results_to_config(self, best_config: Dict[str, Any]):
+ """
+ 保存调优结果到配置文件
+
+ Args:
+ best_config: 最优配置
+ """
+ if not self.config_file_path:
+ Logger.warning("未指定配置文件路径,无法保存调优结果")
+ return
+
+ try:
+ # 确保目录存在
+ config_dir = os.path.dirname(self.config_file_path)
+ if config_dir and not os.path.exists(config_dir):
+ os.makedirs(config_dir, exist_ok=True)
+ Logger.info(f"已创建配置目录: {config_dir}")
+
+ # 读取现有配置,如果文件不存在则创建默认配置
+ config_data = {}
+ has_existing_cache = False # 标记是否已存在性能缓存
+ if os.path.exists(self.config_file_path):
+ with open(self.config_file_path, 'r', encoding='utf-8') as f:
+ if self.config_file_path.endswith('.yaml') or self.config_file_path.endswith('.yml'):
+ config_data = yaml.safe_load(f) or {}
+ else:
+ config_data = json.load(f)
+ has_existing_cache = 'performance_cache' in config_data
+ Logger.info(f"配置文件已存在,更新性能调优缓存: {self.config_file_path}")
+ else:
+ Logger.info(f"配置文件不存在,自动创建新配置文件: {self.config_file_path}")
+ # 创建默认配置结构
+ config_data = {
+ 'server': {
+ 'host': '127.0.0.1',
+ 'port': 8000
+ },
+ 'mode': 'auto',
+ 'browser': {
+ 'auto_open': True
+ },
+ 'gpu': {
+ 'enable_amp': True,
+ 'enable_cudnn_benchmark': True,
+ 'enable_tf32': True
+ },
+ 'logging': {
+ 'level': 'INFO',
+ 'console': True,
+ 'file': False
+ },
+ 'model': {
+ 'checkpoint': 'model_assets/sharp_2572gikvuh.pt',
+ 'temp_dir': 'temp_workspace'
+ },
+ 'inference': {
+ 'input_size': [1536, 1536]
+ },
+ 'optimization': {
+ 'gradient_checkpointing': False,
+ 'checkpoint_segments': 3
+ },
+ 'cache': {
+ 'enabled': True,
+ 'size': 100
+ },
+ 'redis': {
+ 'enabled': False,
+ 'url': 'redis://localhost:6379/0',
+ 'prefix': 'mlsharp'
+ },
+ 'webhook': {
+ 'enabled': False,
+ 'task_completed': '',
+ 'task_failed': ''
+ },
+ 'monitoring': {
+ 'enabled': True,
+ 'enable_gpu': True,
+ 'metrics_path': '/metrics'
+ },
+ 'performance': {
+ 'max_workers': 4,
+ 'max_concurrency': 10,
+ 'timeout_keep_alive': 30,
+ 'max_requests': 1000
+ }
+ }
+
+ # 更新配置
+ from datetime import datetime, timezone
+ config_data['performance_cache'] = {
+ 'last_test': datetime.now(timezone.utc).isoformat(),
+ 'best_config': best_config,
+ 'gpu': {
+ 'name': self.gpu_config.name,
+ 'vendor': self.gpu_config.vendor,
+ 'compute_capability': self.gpu_config.compute_capability
+ }
+ }
+
+ # 保存配置
+ with open(self.config_file_path, 'w', encoding='utf-8') as f:
+ if self.config_file_path.endswith('.yaml') or self.config_file_path.endswith('.yml'):
+ yaml.dump(config_data, f, default_flow_style=False, allow_unicode=True)
+ else:
+ json.dump(config_data, f, indent=2, ensure_ascii=False)
+
+ if has_existing_cache:
+ Logger.success(f"性能调优结果已更新到配置文件: {self.config_file_path}")
+ else:
+ Logger.success(f"性能调优结果已添加到配置文件: {self.config_file_path}")
+ except Exception as e:
+ Logger.warning(f"保存性能调优结果失败: {e}")
def benchmark_optimizations(self) -> Dict[str, Any]:
"""
@@ -920,6 +1456,17 @@ def benchmark_optimizations(self) -> Dict[str, Any]:
Returns:
最优配置字典
"""
+ # 检查是否有缓存的结果
+ cached_results = self._load_cached_results()
+ if cached_results:
+ best_config = cached_results.get('best_config', {})
+ if best_config:
+ Logger.section("使用缓存的性能配置")
+ Logger.info(f"配置名称: {best_config.get('name', 'N/A')}")
+ Logger.info(f"描述: {best_config.get('description', 'N/A')}")
+ self._apply_config(best_config)
+ return best_config
+
Logger.section("性能自动调优")
Logger.info("正在测试不同优化配置...")
@@ -1029,6 +1576,9 @@ def benchmark_optimizations(self) -> Dict[str, Any]:
'all_results': results
}
+ # 保存结果到配置文件
+ self._save_results_to_config(best_result['config'])
+
return best_result['config']
else:
Logger.warning("所有配置测试失败,使用默认配置")
@@ -1384,6 +1934,7 @@ def __init__(self):
# 初始化 GPU
import torch
self.gpu_manager = GPUManager(self.gpu_config, self.args)
+ self.gpu_manager.app_config = self.app_config
self.device = self.gpu_manager.initialize()
# 加载模型
@@ -1410,15 +1961,91 @@ def __init__(self):
self.metrics_manager.set_gpu_info(0, self.gpu_config.name, self.gpu_config.vendor)
self.metrics_manager.set_input_size(*self.args.input_size)
+ # 初始化 Redis 缓存(如果指定)
+ self.redis_cache = None
+ if self.args.redis_url:
+ self.redis_cache = RedisCacheManager(redis_url=self.args.redis_url)
+ if self.redis_cache.enabled:
+ Logger.success(f"Redis 缓存已启用: {self.args.redis_url}")
+
+ # 初始化 Webhook 管理器(如果启用)
+ self.webhook_manager = None
+ if self.args.enable_webhook:
+ self.webhook_manager = WebhookManager(enabled=True)
+ Logger.success("Webhook 通知已启用")
+
# 创建 FastAPI 应用
self.app = self._create_app()
# 使用 ProcessPoolExecutor 替代 ThreadPoolExecutor 以避免 GIL 限制
from concurrent.futures import ProcessPoolExecutor
self.executor = ProcessPoolExecutor(max_workers=min(4, os.cpu_count()))
+ # ================= Pydantic 模型定义 =================
+
+ class PredictResponse(BaseModel):
+ """预测响应模型"""
+ status: str = Field(..., description="请求状态 (success/error)")
+ url: str = Field(..., description="生成的 PLY 文件下载地址")
+ processing_time: float = Field(..., description="处理时间(秒)")
+ task_id: str = Field(..., description="任务 ID")
+
+ class HealthResponse(BaseModel):
+ """健康检查响应模型"""
+ status: str = Field(..., description="服务状态 (healthy/unhealthy)")
+ gpu_available: bool = Field(..., description="GPU 是否可用")
+ gpu_vendor: str = Field(..., description="GPU 厂商 (NVIDIA/AMD/Intel)")
+ gpu_name: str = Field(..., description="GPU 型号名称")
+
+ class GPUInfo(BaseModel):
+ """GPU 信息模型"""
+ available: bool = Field(..., description="GPU 是否可用")
+ vendor: str = Field(..., description="GPU 厂商")
+ name: str = Field(..., description="GPU 型号")
+ count: int = Field(..., description="GPU 数量")
+ memory_mb: float = Field(..., description="当前 GPU 内存使用量(MB)")
+
+ class StatsResponse(BaseModel):
+ """系统统计响应模型"""
+ gpu: "MLSharpApp.GPUInfo" = Field(..., description="GPU 信息")
+
+ class CacheStatsResponse(BaseModel):
+ """缓存统计响应模型"""
+ enabled: bool = Field(..., description="缓存是否启用")
+ size: int = Field(..., description="当前缓存条目数")
+ max_size: int = Field(..., description="最大缓存条目数")
+ hits: int = Field(..., description="缓存命中次数")
+ misses: int = Field(..., description="缓存未命中次数")
+ hit_rate: float = Field(..., description="缓存命中率(百分比)")
+
+ class CacheClearResponse(BaseModel):
+ """缓存清空响应模型"""
+ status: str = Field(..., description="操作状态")
+ message: str = Field(..., description="操作消息")
+
+ class ErrorResponse(BaseModel):
+ """统一错误响应模型"""
+ error: str = Field(..., description="错误类型")
+ message: str = Field(..., description="错误消息")
+ status_code: int = Field(..., description="HTTP 状态码")
+ path: str = Field(..., description="请求路径")
+ timestamp: str = Field(..., description="错误发生时间(ISO 8601 格式)")
+
+ # ================= 错误处理器 =================
+
+ def _create_error_response(self, error: str, message: str, status_code: int, path: str) -> Dict[str, Any]:
+ """创建标准错误响应"""
+ from datetime import datetime
+ return {
+ "error": error,
+ "message": message,
+ "status_code": status_code,
+ "path": path,
+ "timestamp": datetime.utcnow().isoformat() + "Z"
+ }
+
def _create_app(self):
"""创建 FastAPI 应用"""
- from fastapi import FastAPI, UploadFile, File
+ from fastapi import FastAPI, UploadFile, File, APIRouter, Body
from fastapi.responses import FileResponse, JSONResponse
from fastapi.staticfiles import StaticFiles
from fastapi.middleware.cors import CORSMiddleware
@@ -1426,12 +2053,15 @@ def _create_app(self):
app = FastAPI(
title="MLSharp 3D Maker API",
description="基于 Apple SHaRP 模型的 3D 高斯泼溅生成工具",
- version="7.0",
+ version="9.0",
docs_url="/docs",
redoc_url="/redoc",
openapi_url="/openapi.json"
)
+ # 创建 v1 API 路由
+ v1_router = APIRouter(prefix="/v1", tags=["v1"])
+
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
@@ -1441,12 +2071,56 @@ def _create_app(self):
app.mount("/files", StaticFiles(directory=self.app_config.temp_dir), name="files")
+ # ================= 异常处理器 =================
+
+ @app.exception_handler(Exception)
+ async def general_exception_handler(request, exc):
+ """通用异常处理器"""
+ error_response = self._create_error_response(
+ error="InternalServerError",
+ message=str(exc),
+ status_code=500,
+ path=request.url.path
+ )
+ return JSONResponse(
+ status_code=500,
+ content=error_response
+ )
+
+ @app.exception_handler(404)
+ async def not_found_handler(request, exc):
+ """404 异常处理器"""
+ error_response = self._create_error_response(
+ error="NotFound",
+ message="请求的资源不存在",
+ status_code=404,
+ path=request.url.path
+ )
+ return JSONResponse(
+ status_code=404,
+ content=error_response
+ )
+
+ @app.exception_handler(422)
+ async def validation_error_handler(request, exc):
+ """422 验证异常处理器"""
+ error_response = self._create_error_response(
+ error="ValidationError",
+ message="请求参数验证失败",
+ status_code=422,
+ path=request.url.path
+ )
+ return JSONResponse(
+ status_code=422,
+ content=error_response
+ )
+
@app.get("/", tags=["UI"])
async def read_index():
"""访问 Web 界面"""
return FileResponse(os.path.join(self.app_config.base_dir, "viewer.html"))
- @app.post("/api/predict", tags=["Prediction"])
+ @v1_router.post("/predict", response_model=MLSharpApp.PredictResponse, tags=["Prediction"])
async def predict(file: UploadFile = File(..., description="上传的图片文件 (JPG格式)")):
"""从单张图片生成 3D 模型
@@ -1461,7 +2135,7 @@ async def predict(file: UploadFile = File(..., description="上传的图片文
"""
return await self._handle_predict(file)
- @app.get("/api/health", tags=["System"])
+ @v1_router.get("/health", response_model=MLSharpApp.HealthResponse, tags=["System"])
async def health_check():
"""健康检查端点
@@ -1480,7 +2154,7 @@ async def health_check():
"gpu_name": self.gpu_config.name
}
- @app.get("/api/stats", tags=["System"])
+ @v1_router.get("/stats", response_model=MLSharpApp.StatsResponse, tags=["System"])
async def get_stats():
"""获取系统统计信息
@@ -1510,7 +2184,7 @@ async def get_stats():
pass
return stats
- @app.get("/api/cache", tags=["System"])
+ @v1_router.get("/cache", response_model=MLSharpApp.CacheStatsResponse, tags=["System"])
async def get_cache_stats():
"""获取缓存统计信息
@@ -1526,7 +2200,7 @@ async def get_cache_stats():
"""
return self.model_manager.cache_manager.get_stats()
- @app.post("/api/cache/clear", tags=["System"])
+ @v1_router.post("/cache/clear", response_model=MLSharpApp.CacheClearResponse, tags=["System"])
async def clear_cache():
"""清空缓存
@@ -1537,8 +2211,92 @@ async def clear_cache():
- message: 操作消息
"""
self.model_manager.cache_manager.clear()
+ if self.redis_cache and self.redis_cache.enabled:
+ self.redis_cache.clear()
return {"status": "success", "message": "缓存已清空"}
+ @v1_router.get("/webhooks", tags=["Webhook"])
+ async def list_webhooks():
+ """获取所有已注册的 Webhook
+
+ 返回所有已注册的 Webhook 列表。
+
+ 返回:
+ - enabled: Webhook 是否启用
+ - webhooks: Webhook 字典(事件类型 -> URL)
+ """
+ if not self.webhook_manager:
+ return {
+ "enabled": False,
+ "webhooks": {},
+ "message": "Webhook 未启用"
+ }
+ return {
+ "enabled": self.webhook_manager.enabled,
+ "webhooks": self.webhook_manager.webhooks
+ }
+
+ @v1_router.post("/webhooks", tags=["Webhook"])
+ async def register_webhook(webhook_data: Dict[str, str] = Body(..., examples={
+ "example": {
+ "event_type": "task_completed",
+ "url": "https://example.com/webhook/completed"
+ }
+ })):
+ """注册 Webhook
+
+ 注册一个新的 Webhook 用于接收事件通知。
+
+ - **event_type**: 事件类型(task_completed, task_failed)
+ - **url**: Webhook URL
+
+ 返回:
+ - status: 操作状态
+ - message: 操作消息
+ """
+ if not self.webhook_manager:
+ return {
+ "status": "error",
+ "message": "Webhook 未启用"
+ }
+ event_type = webhook_data.get("event_type")
+ url = webhook_data.get("url")
+
+ if not event_type or not url:
+ return {
+ "status": "error",
+ "message": "缺少必要参数: event_type 和 url"
+ }
+
+ self.webhook_manager.register_webhook(event_type, url)
+ return {
+ "status": "success",
+ "message": f"Webhook 已注册: {event_type} -> {url}"
+ }
+
+ @v1_router.delete("/webhooks/{event_type}", tags=["Webhook"])
+ async def unregister_webhook(event_type: str):
+ """注销 Webhook
+
+ 注销指定事件类型的 Webhook。
+
+ - **event_type**: 事件类型
+
+ 返回:
+ - status: 操作状态
+ - message: 操作消息
+ """
+ if not self.webhook_manager:
+ return {
+ "status": "error",
+ "message": "Webhook 未启用"
+ }
+ self.webhook_manager.unregister_webhook(event_type)
+ return {
+ "status": "success",
+ "message": f"Webhook 已注销: {event_type}"
+ }
+
@app.get("/metrics", tags=["Monitoring"])
async def metrics():
"""Prometheus 指标端点
@@ -1586,10 +2344,13 @@ async def monitoring_middleware(request, call_next):
return response
finally:
# 减少活跃任务计数
- if request.url.path == "/api/predict":
+ if request.url.path == "/api/predict" or request.url.path == "/v1/predict":
current_tasks = self.metrics_manager.active_tasks._value.get() if self.metrics_manager.active_tasks._value else 1
self.metrics_manager.set_active_tasks(max(0, current_tasks - 1))
+ # 注册 v1 路由
+ app.include_router(v1_router)
+
return app
async def _handle_predict(self, file: UploadFile):
@@ -1622,6 +2383,36 @@ async def _handle_predict(self, file: UploadFile):
if width > 4096 or height > 4096:
Logger.warning(f"[Task {task_id}] 图片尺寸过大 ({width}x{height}),可能导致性能问题")
+ # 检查 Redis 缓存
+ if self.redis_cache and self.redis_cache.enabled:
+ cached_result = self.redis_cache.get(image, f_px)
+ if cached_result is not None:
+ # 使用缓存结果保存 PLY
+ output_ply_path = os.path.join(output_dir, "output.ply")
+ save_start = time.time()
+ await asyncio.to_thread(save_ply, cached_result, f_px, (height, width), output_ply_path)
+ save_time = time.time() - save_start
+ Logger.info(f"[Task {task_id}] 缓存命中! PLY保存完成,耗时: {save_time:.2f}s")
+
+ # 重命名
+ final_ply = os.path.join(task_dir, "output.ply")
+ await asyncio.to_thread(os.rename, output_ply_path, final_ply)
+
+ elapsed_time = time.time() - start_time
+ Logger.info(f"[Task {task_id}] 处理完成(缓存),总耗时: {elapsed_time:.2f}秒")
+
+ # 记录预测指标
+ self.metrics_manager.record_predict_request("success", elapsed_time)
+ self.metrics_manager.record_predict_stage("total", elapsed_time)
+
+ download_url = f"/files/{task_id}/output.ply"
+
+ # 发送 Webhook 通知(任务完成)
+ if self.webhook_manager:
+ await self.webhook_manager.notify_task_completed(task_id, download_url, elapsed_time)
+
+ return {"status": "success", "url": download_url, "processing_time": elapsed_time, "task_id": task_id}
+
# 预测 - GPU 推理在单独线程中执行
Logger.info(f"[Task {task_id}] 开始推理...")
inference_start = time.time()
@@ -1632,6 +2423,11 @@ async def _handle_predict(self, file: UploadFile):
Logger.info(f"[Task {task_id}] 推理完成,耗时: {inference_time:.2f}秒")
self.metrics_manager.record_predict_stage("inference", inference_time)
+ # 保存到 Redis 缓存
+ if self.redis_cache and self.redis_cache.enabled:
+ self.redis_cache.set(image, f_px, gaussians, ttl=3600)
+ Logger.info(f"[Task {task_id}] 结果已缓存到 Redis")
+
# 保存 PLY - 使用 asyncio.to_thread
output_ply_path = os.path.join(output_dir, "output.ply")
save_start = time.time()
@@ -1652,7 +2448,12 @@ async def _handle_predict(self, file: UploadFile):
self.metrics_manager.record_predict_stage("total", elapsed_time)
download_url = f"/files/{task_id}/output.ply"
- return {"status": "success", "url": download_url, "processing_time": elapsed_time}
+
+ # 发送 Webhook 通知(任务完成)
+ if self.webhook_manager:
+ await self.webhook_manager.notify_task_completed(task_id, download_url, elapsed_time)
+
+ return {"status": "success", "url": download_url, "processing_time": elapsed_time, "task_id": task_id}
except RuntimeError as e:
if "out of memory" in str(e).lower():
@@ -1669,6 +2470,11 @@ async def _handle_predict(self, file: UploadFile):
Logger.error(f"[Task {task_id}] 处理失败: {e}")
elapsed_time = time.time() - start_time
self.metrics_manager.record_predict_request("error", elapsed_time)
+
+ # 发送 Webhook 通知(任务失败)
+ if self.webhook_manager:
+ await self.webhook_manager.notify_task_failed(task_id, str(e))
+
return JSONResponse({
"status": "error",
"message": f"处理失败: {str(e)}",
diff --git a/config.yaml b/config.yaml
index d0f7956..2695a8d 100644
--- a/config.yaml
+++ b/config.yaml
@@ -1,58 +1,33 @@
-# MLSharp-3D-Maker 配置文件
-# 支持的格式: YAML
-
-# 服务配置
-server:
- host: "127.0.0.1" # 服务主机地址
- port: 8000 # 服务端口
-
-# 启动模式
-mode: "auto" # 启动模式: auto, gpu, cpu, nvidia, amd
-
-# 浏览器配置
-browser:
- auto_open: true # 自动打开浏览器
-
-# GPU 优化配置
-gpu:
- enable_amp: true # 启用混合精度推理 (AMP)
- enable_cudnn_benchmark: true # 启用 cuDNN Benchmark
- enable_tf32: true # 启用 TensorFloat32
-
-# 日志配置
-logging:
- level: "INFO" # 日志级别: DEBUG, INFO, WARNING, ERROR
- console: true # 控制台输出
- file: false # 文件输出
-
-# 模型配置
-model:
- checkpoint: "model_assets/sharp_2572gikvuh.pt" # 模型权重路径
- temp_dir: "temp_workspace" # 临时工作目录
-
-# 推理配置
-inference:
- input_size: [1536, 1536] # 输入图像尺寸 [宽度, 高度] (默认: 1536x1536)
-
-# 优化配置
-optimization:
- gradient_checkpointing: false # 启用梯度检查点(减少显存占用,但会略微降低推理速度)
- checkpoint_segments: 3 # 梯度检查点分段数(暂未使用)
-
-# 缓存配置
-cache:
- enabled: true # 启用推理缓存(默认:启用)
- size: 100 # 缓存最大条目数(默认:100)
-
-# 监控配置
-monitoring:
- enabled: true # 启用监控
- enable_gpu: true # 启用 GPU 监控
- metrics_path: "/metrics" # Prometheus 指标端点路径
-
-# 性能配置
-performance:
- max_workers: 4 # 最大工作线程数
- max_concurrency: 10 # 最大并发数
- timeout_keep_alive: 30 # 保持连接超时(秒)
- max_requests: 1000 # 最大请求数
\ No newline at end of file
+browser:
+ auto_open: true
+cache:
+ enabled: true
+ size: 100
+gpu:
+ enable_amp: true
+ enable_cudnn_benchmark: true
+ enable_tf32: true
+inference:
+ input_size:
+ - 1536
+ - 1536
+mode: auto
+monitoring:
+ enable_gpu: true
+ enabled: true
+ metrics_path: /metrics
+performance_cache:
+ best_config:
+ amp: false
+ cudnn_benchmark: false
+ description: 仅启用 TensorFloat32
+ name: 仅 TF32
+ tf32: true
+ gpu:
+ compute_capability: 89
+ name: NVIDIA GeForce RTX 4060 Laptop GPU
+ vendor: NVIDIA
+ last_test: '2026-01-31T04:59:43.901644+00:00'
+server:
+ host: 127.0.0.1
+ port: 8000
diff --git a/gpu_utils.py b/gpu_utils.py
index 2fd11c6..dfbc46f 100644
--- a/gpu_utils.py
+++ b/gpu_utils.py
@@ -68,11 +68,47 @@ def check_rocm_available():
except Exception:
return False
+def check_adreno_available():
+ """检查 Adreno (Snapdragon) GPU 是否可用"""
+ try:
+ import torch
+ # Snapdragon GPU 通常通过 OpenCL/Vulkan,而不是 CUDA
+ # 检查是否为 Android 环境
+ if hasattr(torch, 'backends') and hasattr(torch.backends, 'opencl'):
+ if torch.backends.opencl.is_available():
+ return True
+ # 检查是否有 qnn 或 snpe 相关模块
+ try:
+ import importlib
+ if importlib.util.find_spec('qnn') or importlib.util.find_spec('snpe'):
+ return True
+ except:
+ pass
+ return False
+ except Exception:
+ return False
+
+
def get_gpu_info():
"""获取 GPU 详细信息"""
try:
import torch
if not torch.cuda.is_available():
+ # 检查是否为 Snapdragon GPU (通过 OpenCL)
+ if check_adreno_available():
+ return {
+ 'name': 'Snapdragon Adreno GPU',
+ 'count': 1,
+ 'cuda_version': None,
+ 'is_rocm': False,
+ 'is_adreno': True,
+ 'vendor': 'Qualcomm',
+ 'compute_capability': 0,
+ 'major': 0,
+ 'minor': 0,
+ 'memory_gb': 0,
+ 'multi_processor_count': 0,
+ }
return None
gpu_info = {
@@ -80,6 +116,7 @@ def get_gpu_info():
'count': torch.cuda.device_count(),
'cuda_version': torch.version.cuda,
'is_rocm': check_rocm_available(),
+ 'is_adreno': False,
}
props = torch.cuda.get_device_properties(0)
@@ -101,11 +138,16 @@ def get_gpu_vendor(gpu_name=None):
gpu_info = get_gpu_info()
if gpu_info:
gpu_name = gpu_info.get('name', '')
+ # 直接从 gpu_info 检查 is_adreno 标记
+ if gpu_info.get('is_adreno'):
+ return 'Qualcomm'
else:
return 'Unknown'
name_lower = gpu_name.lower()
- if 'nvidia' in name_lower or 'geforce' in name_lower or 'quadro' in name_lower or 'tesla' in name_lower or 'rtx' in name_lower or 'gtx' in name_lower:
+ if 'snapdragon' in name_lower or 'adreno' in name_lower or 'qualcomm' in name_lower:
+ return 'Qualcomm'
+ elif 'nvidia' in name_lower or 'geforce' in name_lower or 'quadro' in name_lower or 'tesla' in name_lower or 'rtx' in name_lower or 'gtx' in name_lower:
return 'NVIDIA'
elif 'amd' in name_lower or 'radeon' in name_lower or 'rx' in name_lower:
return 'AMD'
diff --git a/requirements.txt b/requirements.txt
index b6394ce..09983d8 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -2,10 +2,21 @@
torch>=2.0.0
torchvision>=0.15.0
+# ONNX Runtime for GPU Acceleration
+onnxruntime-gpu>=1.16.0; platform_system == "Windows"
+onnxruntime>=1.16.0; platform_system != "Windows"
+
# FastAPI and Server
fastapi>=0.128.0
uvicorn[standard]>=0.40.0
python-multipart>=0.0.21
+pydantic>=2.0.0
+
+# Caching
+redis>=5.0.0
+
+# Webhook and HTTP
+httpx>=0.25.0
# 3D Gaussian Splatting
sharp>=0.1.0
diff --git a/viewer.html b/viewer.html
index 15457ec..17b8970 100644
--- a/viewer.html
+++ b/viewer.html
@@ -1404,7 +1404,7 @@
3DGS.ART
formData.append('file', file);
try {
- const response = await fetch('/api/predict', {
+ const response = await fetch('/v1/predict', {
method: 'POST',
body: formData
});
@@ -1425,7 +1425,7 @@
3DGS.ART
} catch (err) {
clearInterval(timer);
console.error(err);
- alert("Preview Mode: Backend not connected.\nUse '?url=...' to load external models.");
+ alert("Error: " + err.message + "\n\nPreview Mode: Backend not connected.\nUse '?url=...' to load external models.");
loadingSys.stop();
}
}