From cfbe5cf8167d2c0feb3e453ada6b12ce1bed8061 Mon Sep 17 00:00:00 2001 From: wuzhifeng Date: Wed, 19 Nov 2025 15:34:55 +0800 Subject: [PATCH] add deploy module --- MindEnergy/applications/deploy/EXAMPLE.md | 98 ++++ MindEnergy/applications/deploy/EXAMPLE_EN.md | 98 ++++ MindEnergy/applications/deploy/README.md | 141 ++++++ MindEnergy/applications/deploy/README_EN.md | 141 ++++++ MindEnergy/applications/deploy/deploy.py | 374 ++++++++++++++++ .../applications/deploy/docs/deploy_arch.png | Bin 0 -> 105804 bytes .../deploy/docs/deploy_arch_en.png | Bin 0 -> 100240 bytes MindEnergy/applications/deploy/monitor.py | 157 +++++++ .../applications/deploy/requirements.txt | 8 + .../applications/deploy/src/__init__.py | 31 ++ MindEnergy/applications/deploy/src/config.py | 119 +++++ MindEnergy/applications/deploy/src/enums.py | 61 +++ .../applications/deploy/src/inference.py | 419 ++++++++++++++++++ MindEnergy/applications/deploy/src/schemas.py | 42 ++ MindEnergy/applications/deploy/src/session.py | 219 +++++++++ MindEnergy/applications/deploy/src/utils.py | 369 +++++++++++++++ 16 files changed, 2277 insertions(+) create mode 100644 MindEnergy/applications/deploy/EXAMPLE.md create mode 100644 MindEnergy/applications/deploy/EXAMPLE_EN.md create mode 100644 MindEnergy/applications/deploy/README.md create mode 100644 MindEnergy/applications/deploy/README_EN.md create mode 100644 MindEnergy/applications/deploy/deploy.py create mode 100755 MindEnergy/applications/deploy/docs/deploy_arch.png create mode 100755 MindEnergy/applications/deploy/docs/deploy_arch_en.png create mode 100644 MindEnergy/applications/deploy/monitor.py create mode 100644 MindEnergy/applications/deploy/requirements.txt create mode 100644 MindEnergy/applications/deploy/src/__init__.py create mode 100644 MindEnergy/applications/deploy/src/config.py create mode 100644 MindEnergy/applications/deploy/src/enums.py create mode 100644 MindEnergy/applications/deploy/src/inference.py create mode 100644 MindEnergy/applications/deploy/src/schemas.py create mode 100644 MindEnergy/applications/deploy/src/session.py create mode 100644 MindEnergy/applications/deploy/src/utils.py diff --git a/MindEnergy/applications/deploy/EXAMPLE.md b/MindEnergy/applications/deploy/EXAMPLE.md new file mode 100644 index 000000000..ac73c2223 --- /dev/null +++ b/MindEnergy/applications/deploy/EXAMPLE.md @@ -0,0 +1,98 @@ +# MindScience 部署服务 - API 示例 + +本文档提供如何使用 curl 命令调用 MindScience 部署服务 API 的示例。 + +## 1. 加载模型 + +使用 curl 加载模型: + +```bash +curl -X POST "http://localhost:8001/mindscience/deploy/load_model" \ + -H "Content-Type: multipart/form-data" \ + -F "model_name=your_model" \ + -F "model_file=@/path/to/your/model_file.zip" +``` + +`model_file.zip` 的目录格式为: + +```bash +model_file.zip +├── your_model_1.mindir +├── your_model_2.mindir +├── ... +└── your_model_n.mindir +``` + +如果只想从本地文件加载模型(不上传),只需提供 `model_name`: + +```bash +curl -X POST "http://localhost:8001/mindscience/deploy/load_model" \ + -H "Content-Type: multipart/form-data" \ + -F "model_name=your_local_model" +``` + +## 2. 卸载模型 + +卸载当前已加载的模型: + +```bash +curl -X POST "http://localhost:8001/mindscience/deploy/unload_model" +``` + +## 3. 推理 + +对数据集执行推理,`task_type` 指定选择 `model_file.zip` 中的哪个模型进行推理,其取值需小于 `model_file.zip` 中的模型数量: + +```bash +curl -X POST "http://localhost:8001/mindscience/deploy/infer" \ + -H "Content-Type: multipart/form-data" \ + -F "dataset=@/path/to/your/dataset.h5" \ + -F "task_type=0" +``` + +这将返回一个 task_id,可用于检查推理请求的状态。 + +## 4. 任务状态查询 + +检查推理任务的状态(将 {task_id} 替换为从推理API返回的实际任务 ID): + +```bash +curl -X GET "http://localhost:8001/mindscience/deploy/query_status/{task_id}" +``` + +例如: + +```bash +curl -X GET "http://localhost:8001/mindscience/deploy/query_status/123e4567-e89b-12d3-a456-426614174000" +``` + +## 5. 结果下载 + +下载已完成的推理任务的结果(将 {task_id} 替换为实际的任务 ID): + +```bash +curl -X GET "http://localhost:8001/mindscience/deploy/query_results/{task_id}" -o "results.h5" +``` + +`-o` 标志将响应保存为指定文件名的文件。例如: + +```bash +curl -X GET "http://localhost:8001/mindscience/deploy/query_results/123e4567-e89b-12d3-a456-426614174000" -o "results.h5" +``` + +这将下载名为 `results.h5` 的结果文件。 + +## 6. 健康检查 + +检查部署服务的健康状态: + +```bash +curl -X GET "http://localhost:8001/mindscience/deploy/health_check" +``` + +## 重要说明 + +- 根据 ServerConfig,默认端口可能是 8001,但应检查 `src/config.py` 文件以确认确切端口。 +- 发出请求之前,请确保部署服务正在运行。 +- 模型推理任务在后台异步处理,因此在尝试下载结果之前需要检查任务状态。 +- 对于并发请求数有限制(在 DeployConfig 中配置)。 diff --git a/MindEnergy/applications/deploy/EXAMPLE_EN.md b/MindEnergy/applications/deploy/EXAMPLE_EN.md new file mode 100644 index 000000000..f2a416799 --- /dev/null +++ b/MindEnergy/applications/deploy/EXAMPLE_EN.md @@ -0,0 +1,98 @@ +# MindScience Deployment Service - API Examples + +This document provides examples of how to use curl commands to call the MindScience deployment service APIs. + +## 1. Load Model + +Use curl to load a model: + +```bash +curl -X POST "http://localhost:8001/mindscience/deploy/load_model" \ + -H "Content-Type: multipart/form-data" \ + -F "model_name=your_model" \ + -F "model_file=@/path/to/your/model_file.zip" +``` + +The directory structure of `model_file.zip` should be: + +```bash +model_file.zip +├── your_model_1.mindir +├── your_model_2.mindir +├── ... +└── your_model_n.mindir +``` + +If you only want to load a model from local files (without uploading), just provide `model_name`: + +```bash +curl -X POST "http://localhost:8001/mindscience/deploy/load_model" \ + -H "Content-Type: multipart/form-data" \ + -F "model_name=your_local_model" +``` + +## 2. Unload Model + +Unload the currently loaded model: + +```bash +curl -X POST "http://localhost:8001/mindscience/deploy/unload_model" +``` + +## 3. Inference + +Perform inference on a dataset. `task_type` specifies which model in `model_file.zip` to use for inference, and its value should be less than the number of models in `model_file.zip`: + +```bash +curl -X POST "http://localhost:8001/mindscience/deploy/infer" \ + -H "Content-Type: multipart/form-data" \ + -F "dataset=@/path/to/your/dataset.h5" \ + -F "task_type=0" +``` + +This will return a task_id that can be used to check the status of the inference request. + +## 4. Task Status Query + +Check the status of an inference task (replace {task_id} with the actual task ID returned from the inference API): + +```bash +curl -X GET "http://localhost:8001/mindscience/deploy/query_status/{task_id}" +``` + +For example: + +```bash +curl -X GET "http://localhost:8001/mindscience/deploy/query_status/123e4567-e89b-12d3-a456-426614174000" +``` + +## 5. Result Download + +Download the results of a completed inference task (replace {task_id} with the actual task ID): + +```bash +curl -X GET "http://localhost:8001/mindscience/deploy/query_results/{task_id}" -o "results.h5" +``` + +The `-o` flag saves the response to a file with the specified filename. For example: + +```bash +curl -X GET "http://localhost:8001/mindscience/deploy/query_results/123e4567-e89b-12d3-a456-426614174000" -o "results.h5" +``` + +This will download the result file named `results.h5`. + +## 6. Health Check + +Check the health status of the deployment service: + +```bash +curl -X GET "http://localhost:8001/mindscience/deploy/health_check" +``` + +## Important Notes + +- According to ServerConfig, the default port might be 8001, but you should check the `src/config.py` file to confirm the exact port. +- Ensure the deployment service is running before making requests. +- Model inference tasks are processed asynchronously in the background, so check the task status before attempting to download results. +- There are limits on the number of concurrent requests (configured in DeployConfig). diff --git a/MindEnergy/applications/deploy/README.md b/MindEnergy/applications/deploy/README.md new file mode 100644 index 000000000..b3643879d --- /dev/null +++ b/MindEnergy/applications/deploy/README.md @@ -0,0 +1,141 @@ +# MindScience 部署服务 + +MindScience 部署服务是一个基于 FastAPI 的模型部署和监控系统,支持多设备并行推理和资源监控功能。 + +## 架构图 + +

+ +

+ +## 目录结构 + +```shell +deploy/ +├── deploy.py # 模型部署服务主文件 +├── monitor.py # 服务器监控服务主文件 +├── requirements.txt # 项目依赖 +└── src/ # 源代码目录 + ├── config.py # 配置文件 + ├── enums.py # 枚举定义 + ├── inference.py # 推理实现 + ├── schemas.py # 数据模型定义 + ├── session.py # 会话管理 + └── utils.py # 工具函数 +``` + +## 功能特性 + +### 部署服务 (deploy.py) + +- **模型加载/卸载**:支持通过 HTTP 接口上传 MindIR 模型文件并加载到设备上 +- **异步推理**:支持后台异步执行推理任务 +- **任务状态管理**:支持任务状态查询(待处理、处理中、已完成、错误) +- **健康检查**:提供模型状态检查接口 +- **结果下载**:推理完成后可下载结果文件 +- **多设备支持**:支持最多 8 个 NPU 设备并行推理 + +### 监控服务 (monitor.py) + +- **资源监控**:实时监控 CPU、内存和 NPU 使用率 +- **健康检查**:提供服务器资源使用情况查询接口 + +## 配置参数 + +### 部署配置 (DeployConfig) + +- `max_device_num`: 最大设备数量(默认 8) +- `deploy_device_num`: 部署使用的设备数量(默认 8) +- `max_request_num`: 最大并发请求数(默认 100) +- `models_dir`: 模型文件存储目录(默认 "models") +- `datasets_dir`: 数据集文件存储目录(默认 "datasets") +- `results_dir`: 结果文件存储目录(默认 "results") +- `dummy_model_path`: 用于测试的虚拟模型路径(默认 "dummy_model.mindir") +- `chunk_size`: 数据块大小(默认 8MB) + +### 服务器配置 (ServerConfig) + +- `host`: 服务器主机地址(默认 "127.0.0.1") +- `deploy_port`: 部署服务端口(默认 8001) +- `monitor_port`: 监控服务端口(默认 8002) +- `limit_concurrency`: 最大并发连接数(默认 1000) +- `timeout_keep_alive`: Keep-alive 连接超时时间(默认 30 秒) +- `backlog`: 待处理连接队列大小(默认 2048) + +## API 接口 + +### 部署服务接口 + +- `POST /mindscience/deploy/load_model` - 加载模型 + - 参数:model_name (表单), model_file (可选文件) + +- `POST /mindscience/deploy/unload_model` - 卸载模型 + +- `POST /mindscience/deploy/infer` - 执行推理 + - 参数:dataset (文件), task_type (表单) + +- `GET /mindscience/deploy/query_status/{task_id}` - 查询任务状态 + +- `GET /mindscience/deploy/query_results/{task_id}` - 获取推理结果 + +- `GET /mindscience/deploy/health_check` - 健康检查 + +### 监控服务接口 + +- `GET /mindscience/monitor/resource_usage` - 获取资源使用情况 + - 返回:CPU使用率、内存使用率、NPU使用率、NPU内存使用率 + +## 依赖项 + +- `fastapi == 0.121.2`: Web 框架 +- `uvicorn == 0.38.0`: ASGI 服务器 +- `python-multipart == 0.0.20`: 多部分表单数据处理 +- `h5py == 3.14.0`: HDF5 文件处理 +- `loguru == 0.7.3`: 日志处理 +- `aiofiles == 25.1.0`: 异步文件操作 +- `psutil == 7.0.0`: 系统和进程监控 +- `numpy == 1.26.4`: 科学计算库 +- `mindspore_lite == 2.7.1`: MindSpore Lite 推理引擎 +- `CANN == 8.2.RC1`: 神经网络异构计算架构 + +## 安装和使用 + +1. 参考[CANN官网](https://www.hiascend.com/document/detail/zh/CANNCommunityEdition/83RC1/softwareinst/instg/instg_quick.html?Mode=PmIns&InstallType=local&OS=openEuler&Software=cannToolKit)文档安装CANN社区版软件包。 + +2. 从[MindSpore官网](https://www.mindspore.cn/lite/docs/zh-CN/r2.7.1/use/downloads.html#mindspore-lite-python%E6%8E%A5%E5%8F%A3%E5%BC%80%E5%8F%91%E5%BA%93)下载MindSpore Lite Python接口开发库: + + ```bash + wget https://ms-release.obs.cn-north-4.myhuaweicloud.com/2.7.1/MindSporeLite/lite/release/linux/aarch64/cloud_fusion/python310/mindspore_lite-2.7.1-cp310-cp310-linux_aarch64.whl + + pip install mindspore_lite-2.7.1-cp310-cp310-linux_aarch64.whl + ``` + +3. 安装 Python 依赖: + + ```bash + pip install -r requirements.txt + ``` + +4. 根据实际业务修改src/config.py配置文件中的配置项。 + +5. 启动部署服务: + + ```bash + python deploy.py + ``` + +6. 启动监控服务: + + ```bash + python monitor.py + ``` + +## 技术架构 + +- **框架**:FastAPI + Uvicorn +- **推理引擎**:MindSpore Lite +- **设备支持**:华为昇腾 NPU +- **并行处理**:多进程并行推理 +- **数据格式**:HDF5 数据存储 + +系统采用异步处理方式,支持高并发推理请求,并提供完整的任务生命周期管理。 diff --git a/MindEnergy/applications/deploy/README_EN.md b/MindEnergy/applications/deploy/README_EN.md new file mode 100644 index 000000000..00260073c --- /dev/null +++ b/MindEnergy/applications/deploy/README_EN.md @@ -0,0 +1,141 @@ +# MindScience Deployment Service + +MindScience Deployment Service is a model deployment and monitoring system based on FastAPI, supporting multi-device parallel inference and resource monitoring capabilities. + +## Architecture + +

+ +

+ +## Directory Structure + +```shell +deploy/ +├── deploy.py # Model deployment service main file +├── monitor.py # Server monitoring service main file +├── requirements.txt # Project dependencies +└── src/ # Source code directory + ├── config.py # Configuration file + ├── enums.py # Enum definitions + ├── inference.py # Inference implementation + ├── schemas.py # Data model definitions + ├── session.py # Session management + └── utils.py # Utility functions +``` + +## Features + +### Deployment Service (deploy.py) + +- **Model load/unload**: Supports uploading MindIR model files via HTTP interface and loading them to devices +- **Asynchronous inference**: Supports background asynchronous execution of inference tasks +- **Task status management**: Supports task status queries (pending, processing, completed, error) +- **Health check**: Provides model status checking interface +- **Result download**: Results file can be downloaded after inference completion +- **Multi-device support**: Supports up to 8 NPU devices for parallel inference + +### Monitoring Service (monitor.py) + +- **Resource monitoring**: Real-time monitoring of CPU, memory and NPU usage +- **Health check**: Provides server resource usage query interface + +## Configuration Parameters + +### Deployment Configuration (DeployConfig) + +- `max_device_num`: Maximum number of devices (default 8) +- `deploy_device_num`: Number of devices used for deployment (default 8) +- `max_request_num`: Maximum concurrent request number (default 100) +- `models_dir`: Model file storage directory (default "models") +- `datasets_dir`: Dataset file storage directory (default "datasets") +- `results_dir`: Result file storage directory (default "results") +- `dummy_model_path`: Path for dummy model used in testing (default "dummy_model.mindir") +- `chunk_size`: Chunk size (default 8MB) + +### Server Configuration (ServerConfig) + +- `host`: Server host address (default "127.0.0.1") +- `deploy_port`: Deployment service port (default 8001) +- `monitor_port`: Monitoring service port (default 8002) +- `limit_concurrency`: Maximum concurrent connection number (default 1000) +- `timeout_keep_alive`: Keep-alive connection timeout (default 30 seconds) +- `backlog`: Pending connection queue size (default 2048) + +## API Interfaces + +### Deployment Service Interfaces + +- `POST /mindscience/deploy/load_model` - Load model + - Parameters: model_name (form), model_file (optional file) + +- `POST /mindscience/deploy/unload_model` - Unload model + +- `POST /mindscience/deploy/infer` - Execute inference + - Parameters: dataset (file), task_type (form) + +- `GET /mindscience/deploy/query_status/{task_id}` - Query task status + +- `GET /mindscience/deploy/query_results/{task_id}` - Get inference results + +- `GET /mindscience/deploy/health_check` - Health check + +### Monitoring Service Interface + +- `GET /mindscience/monitor/resource_usage` - Get resource usage + - Returns: CPU usage rate, memory usage rate, NPU usage rate, NPU memory usage rate + +## Dependencies + +- `fastapi == 0.121.2`: Web framework +- `uvicorn == 0.38.0`: ASGI server +- `python-multipart == 0.0.20`: Multipart form data processing +- `h5py == 3.14.0`: HDF5 file processing +- `loguru == 0.7.3`: Logging +- `aiofiles == 25.1.0`: Asynchronous file operations +- `psutil == 7.0.0`: System and process monitoring +- `numpy == 1.26.4`: Scientific computing library +- `mindspore_lite == 2.7.1`: MindSpore Lite inference engine +- `CANN == 8.2.RC1`: Compute Architecture for Neural Networks + +## Installation and Usage + +1. Refer to the [CANN official website](https://www.hiascend.com/document/detail/zh/CANNCommunityEdition/83RC1/softwareinst/instg/instg_quick.html?Mode=PmIns&InstallType=local&OS=openEuler&Software=cannToolKit) documentation to install the CANN community edition software package. + +2. Download MindSpore Lite Python API development library from the [MindSpore official website](https://www.mindspore.cn/lite/docs/zh-CN/r2.7.1/use/downloads.html#mindspore-lite-python%E6%8E%A5%E5%8F%A3%E5%BC%80%E5%8F%91%E5%BA%93) + + ```bash + wget https://ms-release.obs.cn-north-4.myhuaweicloud.com/2.7.1/MindSporeLite/lite/release/linux/aarch64/cloud_fusion/python310/mindspore_lite-2.7.1-cp310-cp310-linux_aarch64.whl + + pip install mindspore_lite-2.7.1-cp310-cp310-linux_aarch64.whl + ``` + +3. Install Python dependencies: + + ```bash + pip install -r requirements.txt + ``` + +4. Modify the configuration items in the src/config.py file based on actual business needs. + +5. Start deployment service: + + ```bash + python deploy.py + ``` + +6. Start monitoring service: + + ```bash + python monitor.py + ``` + +## Technical Architecture + +- **Framework**: FastAPI + Uvicorn +- **Inference engine**: MindSpore Lite +- **Device support**: Huawei Ascend NPU +- **Parallel processing**: Multi-process parallel inference +- **Data format**: HDF5 data storage + +The system adopts an asynchronous processing approach, supports high-concurrency inference requests, and provides complete task lifecycle management. diff --git a/MindEnergy/applications/deploy/deploy.py b/MindEnergy/applications/deploy/deploy.py new file mode 100644 index 000000000..abebc09c3 --- /dev/null +++ b/MindEnergy/applications/deploy/deploy.py @@ -0,0 +1,374 @@ +# Copyright 2025 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ + +""" +MindScience Deployment Service. + +This module implements a model deployment service based on FastAPI that supports: +- Model loading/unloading via HTTP interface +- Asynchronous inference execution with background tasks +- Task status management (pending, processing, completed, error) +- Health checking functionality +- Multi-device parallel inference support (up to 8 NPU devices) +- Result file download after inference completion + +The service provides RESTful APIs for model management and inference execution, +with proper error handling and resource management. +""" + +import os +import uuid +import signal +from typing import Any, Union +from contextlib import asynccontextmanager + +from loguru import logger +from uvicorn import Server +from uvicorn.config import Config +from fastapi import FastAPI, Form, File, UploadFile, BackgroundTasks, HTTPException +from fastapi.responses import JSONResponse, FileResponse +from fastapi.middleware.cors import CORSMiddleware + +from src.enums import HealthStatus, ModelStatus, TaskStatus +from src.schemas import ModelInfo +from src.session import SessionManager +from src.utils import Utilities +from src.config import configure_logging, DeployConfig, ServerConfig + +# pylint: disable=unused-argument, redefined-outer-name + +@asynccontextmanager +async def lifespan(app: FastAPI): + """Manages the application lifespan by initializing and cleaning up resources. + + This function is used as an async context manager for the FastAPI application + lifespan. It handles initialization of models on the specified devices during + startup and performs cleanup during shutdown. + + Args: + app (FastAPI): The FastAPI application instance. + """ + configure_logging("deploy") + logger.info(f"Initializing models on devices {list(range(DeployConfig.deploy_device_num))}.") + yield + logger.info("Shutting down deploy service.") + +app = FastAPI(lifespan=lifespan) +app.add_middleware( + CORSMiddleware, + allow_origins=["*"], + allow_methods=["*"] +) + +model = SessionManager() +tasks_status = {} + + +def inference(dataset_path, task_id, task_type): + """Performs inference on a dataset. + + This function loads an HDF5 dataset, performs batch inference using the model, + saves the results to an HDF5 file, and updates the task status. + + Args: + dataset_path (str): Path to the HDF5 dataset file. + task_id (str): Unique identifier for the inference task. + task_type (int): Type of inference task to perform. + + Raises: + Exception: If there is an error during the inference process. + """ + try: + tasks_status[task_id] = TaskStatus.PROCESSING + + batch_inputs = Utilities.load_h5_file(dataset_path) + + results_list = model.batch_infer(batch_inputs, task_type) + + os.makedirs(DeployConfig.results_dir, exist_ok=True) + results_path = os.path.join(DeployConfig.results_dir, f"{task_id}_results.h5") + + Utilities.save_h5_file(results_list, results_path) + + tasks_status[task_id] = TaskStatus.COMPLETED + except Exception as e: + tasks_status[task_id] = TaskStatus.ERROR + logger.error(f"Task {task_id} infer failed, ERROR: {e}.") + + +@app.post("/mindscience/deploy/load_model") +async def load_model(model_name: str = Form(), model_file: Union[UploadFile, str] = None): + """Loads a model for inference. + + This endpoint handles the loading of a model, either from an uploaded file + or from a local file. If a model is already loaded, it returns an error. + + Args: + model_name (str): Name of the model to load. + model_file (Union[UploadFile, str], optional): Uploaded model file. Defaults to None. + + Returns: + JSONResponse: A response containing model information and status. + """ + model_info = ModelInfo() + if model.is_ready() == HealthStatus.READY: + model_info.status = ModelStatus.FAILURE + model_info.message = f"Model {model.session_name} is already loaded, please unload first." + return JSONResponse( + content=model_info.model_dump(), + status_code=403, + headers={"Content-Type": "application/json"} + ) + + if not model_file: + logger.info("File not uploaded, model loaded from local file.") + else: + try: + logger.info(f"Start receive {model_name} file") + os.makedirs(DeployConfig.models_dir, exist_ok=True) + save_dir = os.path.join(DeployConfig.models_dir, model_name) + os.makedirs(save_dir, exist_ok=True) + + save_path = os.path.join(save_dir, model_file.filename) + await Utilities.save_upload_file(model_file, save_path) + + Utilities.extract_file(save_path) + logger.info(f"{model_name} file receive successfully.") + except Exception as e: + model_info.status = ModelStatus.FAILURE + model_info.message = str(e) + return JSONResponse( + content=model_info.model_dump(), + status_code=500, + headers={"Content-Type": "application/json"} + ) + + try: + model.init_session(model_name, device_num=DeployConfig.deploy_device_num) + return JSONResponse( + content=model_info.model_dump(), + status_code=201, + headers={"Content-Type": "application/json"} + ) + except Exception as e: + model_info.status = ModelStatus.FAILURE + model_info.message = str(e) + return JSONResponse( + content=model_info.model_dump(), + status_code=500, + headers={"Content-Type": "application/json"} + ) + + +@app.post("/mindscience/deploy/unload_model") +async def unload_model(): + """Unloads the currently loaded model. + + This endpoint unloads the model session and frees up the associated resources. + + Returns: + JSONResponse: A response containing model information and status. + """ + model_info = ModelInfo() + try: + model.del_session() + return JSONResponse( + content=model_info.model_dump(), + status_code=200, + headers={"Content-Type": "application/json"} + ) + except Exception as e: + model_info.status = ModelStatus.FAILURE + model_info.message = str(e) + return JSONResponse( + content=model_info.model_dump(), + status_code=500, + headers={"Content-Type": "application/json"} + ) + + +@app.post("/mindscience/deploy/infer") +async def infer(dataset: UploadFile = File(..., description="input dataset"), task_type: int = Form(), + background_tasks: BackgroundTasks = Any): + """Performs inference on the uploaded dataset. + + This endpoint accepts a dataset file and performs inference in the background. + It checks if the model is ready, validates the number of pending tasks, + saves the uploaded dataset, and starts the inference task. + + Args: + dataset (UploadFile): The input dataset file to process. + task_type (int): Type of inference task to perform. + background_tasks (BackgroundTasks): FastAPI background tasks manager. + + Returns: + JSONResponse: A response containing the task ID. + + Raises: + HTTPException: If the server is not ready or if the request limit is exceeded. + """ + if model.is_ready() != HealthStatus.READY: + raise HTTPException(status_code=500, detail="Server is not ready, please check") + + pending_number = Utilities.count_pending_task(tasks_status) + logger.info(f"Pending task number is {pending_number}") + if pending_number >= DeployConfig.max_request_num: + logger.error(f"Predict request number exceed limited number: \ + {pending_number} vs {DeployConfig.max_request_num}") + raise HTTPException(status_code=503, detail="Request number exceed limited number, please wait for a while.") + + task_id = str(uuid.uuid4()) + tasks_status[task_id] = TaskStatus.PENDING + + try: + os.makedirs(DeployConfig.datasets_dir, exist_ok=True) + dataset_path = os.path.join(DeployConfig.datasets_dir, f"{task_id}_{dataset.filename}") + await Utilities.save_upload_file(dataset, dataset_path) + + background_tasks.add_task(inference, dataset_path, task_id, task_type) + + return JSONResponse( + content={"task_id": task_id}, + status_code=200, + headers={"Content-Type": "application/json"} + ) + except Exception as e: + tasks_status[task_id] = TaskStatus.ERROR + raise HTTPException(status_code=500, detail=f"Task {task_id} infer failed, ERROR: {e}.") from e + + +@app.get("/mindscience/deploy/query_status/{task_id}") +async def query_status(task_id: str): + """Queries the status of an inference task. + + This endpoint retrieves the current status of a specific inference task + by its task ID. + + Args: + task_id (str): Unique identifier of the task to query. + + Returns: + JSONResponse: A response containing the task status. + + Raises: + HTTPException: If the task ID is not found. + """ + status = tasks_status.get(task_id) + if status is None: + raise HTTPException(status_code=404, detail=f"Task {task_id} is not found, please check!") + return JSONResponse( + content={"status": status}, + status_code=200, + headers={"Content-Type": "application/json"} + ) + + +@app.get("/mindscience/deploy/query_results/{task_id}") +async def query_results(task_id: str): + """Retrieves the results of a completed inference task. + + This endpoint returns the results file of a completed inference task + if the task is completed, otherwise returns the current status. + + Args: + task_id (str): Unique identifier of the task to query. + + Returns: + FileResponse or JSONResponse: Either the results file or a response containing the task status and message. + + Raises: + HTTPException: If the task ID is not found. + """ + status = tasks_status.get(task_id) + if status is None: + raise HTTPException(status_code=404, detail=f"Task {task_id} is not found, please check!") + if status != TaskStatus.COMPLETED: + return JSONResponse( + content={"status": status, "message": f"Task {task_id} is not completed."}, + status_code=404, + headers={"Content-Type": "application/json"} + ) + result_path = os.path.join(DeployConfig.results_dir, f"{task_id}_results.h5") + return FileResponse(result_path, filename=f"{task_id}_results.h5") + + +@app.get("/mindscience/deploy/health_check") +async def health_check(): + """Performs a health check on the deployment service. + + This endpoint checks the readiness status of the model and returns + an appropriate HTTP response based on the model's health status. + + Returns: + JSONResponse: A response indicating the health status of the service. + """ + health_status = model.is_ready() + logger.info(f"health check result is {health_status}.") + if health_status == HealthStatus.NOTLOADED: + return JSONResponse( + content={}, + status_code=501, + headers={"Content-Type": "application/json"} + ) + if health_status == HealthStatus.EXCEPTION: + return JSONResponse( + content={model.session_name: health_status}, + status_code=503, + headers={"Content-Type": "application/json"} + ) + return JSONResponse( + content={model.session_name: health_status}, + status_code=200, + headers={"Content-Type": "application/json"} + ) + + +if __name__ == "__main__": + server = Server( + Config( + app=app, + host=ServerConfig.host, + port=ServerConfig.deploy_port, + limit_concurrency=ServerConfig.limit_concurrency, + timeout_keep_alive=ServerConfig.timeout_keep_alive, + backlog=ServerConfig.backlog + ) + ) + + def terminate_signal_handler(signum, frame): + """Handles termination signals to gracefully shut down the server. + + This function is called when the server receives a termination signal + (SIGTERM or SIGINT). It cleans up the model session and sets the server + to exit. + + Args: + signum (int): The signal number received. + frame: The current stack frame (unused). + """ + global model + logger.info(f"Catch signal: {signum}, starting terminate server...") + try: + model.del_session() + except Exception as e: + logger.exception(f"Model clear failed, please check! ERROR: {e}") + del model + server.should_exit = True + + signal.signal(signal.SIGTERM, terminate_signal_handler) + signal.signal(signal.SIGINT, terminate_signal_handler) + + logger.info("Starting deploy server...") + server.run() diff --git a/MindEnergy/applications/deploy/docs/deploy_arch.png b/MindEnergy/applications/deploy/docs/deploy_arch.png new file mode 100755 index 0000000000000000000000000000000000000000..9394afa58298f0b91184b26a8a84fc7cc37e6fa8 GIT binary patch literal 105804 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

uh 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 Msd6LZy78m`1uS6{-T(jq literal 0 HcmV?d00001 diff --git a/MindEnergy/applications/deploy/docs/deploy_arch_en.png b/MindEnergy/applications/deploy/docs/deploy_arch_en.png new file mode 100755 index 0000000000000000000000000000000000000000..e8a040868ba88c9fe67b292c372851a5e3b6e30f GIT binary patch literal 100240 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 vd(*Ht>n%#z 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 ZrGUFDx2}j@APTYqamn~%!3Fz&{Z9+=-Bthq literal 0 HcmV?d00001 diff --git a/MindEnergy/applications/deploy/monitor.py b/MindEnergy/applications/deploy/monitor.py new file mode 100644 index 000000000..8939fe304 --- /dev/null +++ b/MindEnergy/applications/deploy/monitor.py @@ -0,0 +1,157 @@ +# Copyright 2025 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ + +""" +MindScience Monitoring Service. + +This module implements a server resource monitoring service based on FastAPI that supports: +- Real-time monitoring of CPU usage +- Real-time monitoring of memory usage +- Real-time monitoring of NPU (Neural Processing Unit) usage +- Real-time monitoring of NPU memory usage +- Health check endpoint for server resource usage + +The service provides a RESTful API endpoint to retrieve current system resource statistics, +which can be used for system health monitoring and performance analysis. +""" + +import signal +import asyncio +from contextlib import asynccontextmanager + +import psutil +from loguru import logger +from fastapi import FastAPI +from uvicorn import Server +from uvicorn.config import Config + +from src.utils import Utilities +from src.config import configure_logging, ServerConfig + +# pylint: disable=unused-argument, redefined-outer-name + +@asynccontextmanager +async def lifespan(app: FastAPI): + """Manages the application lifespan by initializing and cleaning up resources. + + This function is used as an async context manager for the FastAPI application + lifespan. It handles logging configuration and performs startup and shutdown + logging. + + Args: + app (FastAPI): The FastAPI application instance. + """ + configure_logging("monitor") + logger.info("Starting monitor service.") + yield + logger.info("Shutting down monitor service.") + +app = FastAPI(lifespan=lifespan) + +@app.get("/mindscience/monitor/resource_usage") +async def get_server_status(): + """Retrieves the current server resource usage statistics. + + This endpoint collects and returns CPU, memory, and NPU usage statistics + from the server. It uses psutil for CPU and memory metrics and a custom + utility function for NPU metrics. + + Returns: + dict: A dictionary containing the status, message, and resource usage data + including CPU usage rate, memory usage rate, NPU usage rate, and NPU memory usage rate. + """ + try: + results = await asyncio.gather( + asyncio.to_thread(psutil.cpu_percent, interval=None), + asyncio.to_thread(lambda: psutil.virtual_memory().percent), + Utilities.get_npu_usage(), + return_exceptions=True + ) + + # 解包结果 + cpu_usage, memory_usage, npu_stats = results + + if isinstance(cpu_usage, Exception): + logger.error(f"Failed to get CPU usage: {cpu_usage}") + cpu_usage = 0.0 + + if isinstance(memory_usage, Exception): + logger.error(f"Failed to get memory usage: {memory_usage}") + memory_usage = 0.0 + + if isinstance(npu_stats, Exception): + logger.warning(f"Failed to get NPU usage info: {npu_stats}") + npu_usage_rate = "0.00" + npu_memory_usage_rate = "0.00" + elif npu_stats: + total_memory = sum(stat["memory_total_mb"] for stat in npu_stats) + used_memory = sum(stat["memory_used_mb"] for stat in npu_stats) + avg_utilization = sum(stat["utilization_percent"] for stat in npu_stats) / len(npu_stats) + avg_memory_usage = (used_memory / total_memory * 100) if total_memory > 0 else 0 + npu_usage_rate = f"{avg_utilization:.2f}" + npu_memory_usage_rate = f"{avg_memory_usage:.2f}" + else: + npu_usage_rate = "0.00" + npu_memory_usage_rate = "0.00" + + return { + "status": 200, + "msg": "success", + "data": { + "cpuUsageRate": f"{cpu_usage:.2f}", + "memoryUsageRate": f"{memory_usage:.2f}", + "npuUsageRate": npu_usage_rate, + "npuMemoryUsageRate": npu_memory_usage_rate + } + } + except Exception as e: + logger.error(f"An unexpected error occurred in get_server_status: {e}") + return { + "status": 500, + "msg": "failure", + "data": {} + } + +if __name__ == "__main__": + server = Server( + Config( + app=app, + host=ServerConfig.host, + port=ServerConfig.monitor_port, + limit_concurrency=ServerConfig.limit_concurrency, + timeout_keep_alive=ServerConfig.timeout_keep_alive, + backlog=ServerConfig.backlog + ) + ) + + def terminate_signal_handler(signum, frame): + """Handles termination signals to gracefully shut down the server. + + This function is called when the server receives a termination signal + (SIGTERM or SIGINT). It sets the server to exit. + + Args: + signum (int): The signal number received. + frame: The current stack frame (unused). + """ + logger.info(f"Catch signal: {signum}, starting terminate server...") + server.should_exit = True + + signal.signal(signal.SIGTERM, terminate_signal_handler) + signal.signal(signal.SIGINT, terminate_signal_handler) + + + logger.info("Starting monitor server...") + server.run() diff --git a/MindEnergy/applications/deploy/requirements.txt b/MindEnergy/applications/deploy/requirements.txt new file mode 100644 index 000000000..efb672f82 --- /dev/null +++ b/MindEnergy/applications/deploy/requirements.txt @@ -0,0 +1,8 @@ +fastapi +uvicorn +python-multipart +h5py +loguru +aiofiles +psutil +numpy diff --git a/MindEnergy/applications/deploy/src/__init__.py b/MindEnergy/applications/deploy/src/__init__.py new file mode 100644 index 000000000..c1d6e2e61 --- /dev/null +++ b/MindEnergy/applications/deploy/src/__init__.py @@ -0,0 +1,31 @@ +# Copyright 2025 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ + +""" +MindScience deployment service source package. + +This package contains modules for deploying and monitoring machine learning models +using FastAPI services. The modules include: + +- config: Configuration classes and functions for deployment settings +- enums: Enumerations for status values and message types +- inference: Classes for performing inference using MindSpore Lite +- schemas: Data models for request/response validation +- session: Session management for model inference +- utils: Utility functions for file handling, model collection, and system monitoring + +This package provides the core functionality for model deployment, inference execution, +and system monitoring in the MindScience platform. +""" diff --git a/MindEnergy/applications/deploy/src/config.py b/MindEnergy/applications/deploy/src/config.py new file mode 100644 index 000000000..5704c7604 --- /dev/null +++ b/MindEnergy/applications/deploy/src/config.py @@ -0,0 +1,119 @@ +# Copyright 2025 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ + +""" +Configuration module for MindScience deployment service. + +This module defines configuration classes and functions used throughout the +MindScience deployment and monitoring services. It includes: + +- ModelConfig: Specifies model input and output column names +- DeployConfig: Contains deployment-related settings such as device numbers, request limits, and file paths +- ServerConfig: Defines server settings including host, ports, and connection parameters +- configure_logging: Function to set up logging for different services + +The configurations use dataclasses with frozen=True to ensure immutability +and thread-safe access to configuration values. +""" + +import sys +from typing import Tuple +from dataclasses import dataclass + +from loguru import logger + +@dataclass(frozen=True) +class ModelConfig: + """Configuration for model input and output specifications. + + Attributes: + input_columns: Tuple of column names used as model inputs. + output_columns: Tuple of column names produced as model outputs. + """ + input_columns: Tuple[str] = ("x", "edge_index", "edge_attr") + + output_columns: Tuple[str] = ("output",) + + +@dataclass(frozen=True) +class DeployConfig: + """Configuration for deployment settings. + + Attributes: + max_device_num: Maximum number of devices allowed for deployment. + deploy_device_num: Number of devices to be used for deployment. + max_request_num: Maximum number of concurrent requests allowed. + models_dir: Directory path for storing model files. + datasets_dir: Directory path for storing dataset files. + results_dir: Directory path for storing result files. + dummy_model_path: File path for the dummy model used in testing. + chunk_size: Size of data chunks for processing in bytes. + """ + max_device_num: int = 8 + + deploy_device_num: int = 8 + + max_request_num: int = 100 + + models_dir: str = "models" + + datasets_dir: str = "datasets" + + results_dir: str = "results" + + dummy_model_path: str = "dummy_model.mindir" + + chunk_size: int = 8 * 1024 * 1024 + + +@dataclass(frozen=True) +class ServerConfig: + """Configuration for server settings. + + Attributes: + host: Host address for the server. + deploy_port: Port number for deployment service. + monitor_port: Port number for monitoring service. + limit_concurrency: Maximum number of concurrent connections allowed. + timeout_keep_alive: Timeout duration for keep-alive connections in seconds. + backlog: Maximum number of pending connections in the queue. + """ + host: str = "127.0.0.1" + + deploy_port: int = 8001 + + monitor_port: int = 8002 + + limit_concurrency: int = 1000 + + timeout_keep_alive: int = 30 + + backlog: int = 2048 + + +def configure_logging(service: str): + """Configures logging settings for the specified service. + + Args: + service: Name of the service to configure logging for. + """ + logger.add( + f"logs/{service}.log", + rotation="100 MB", + retention="10 days", + format="{time:YYYY-MM-DD HH:mm:ss} | {level} | {message}", + enqueue=True + ) + logger.add(sys.stderr, level="DEBUG") diff --git a/MindEnergy/applications/deploy/src/enums.py b/MindEnergy/applications/deploy/src/enums.py new file mode 100644 index 000000000..687760479 --- /dev/null +++ b/MindEnergy/applications/deploy/src/enums.py @@ -0,0 +1,61 @@ +# Copyright 2025 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ + +""" +Enumeration module for MindScience deployment service. + +This module defines various enumerations used throughout the +MindScience deployment system, including: + +- ProcessMessage: Message types for inter-process communication +- HealthStatus: Health status indicators for services +- ModelStatus: Status indicators for model loading and execution +- TaskStatus: Execution status indicators for inference tasks + +These enums provide type-safe constants for status values and message types +used in the system's processes and communication. +""" + +from enum import Enum, IntEnum + +class ProcessMessage(IntEnum): + """Enumeration for process message types in the system.""" + ERROR = 0 + PREDICT = 1 + REPLY = 2 + CHECK = 3 + BUILD_FINISH = 4 + EXIT = 5 + + +class HealthStatus(str, Enum): + """Enumeration for health status of services.""" + READY = "ready" + NOTLOADED = "not_loaded" + EXCEPTION = "exception" + + +class ModelStatus(str, Enum): + """Enumeration for model loading or execution status.""" + SUCCESS = "success" + FAILURE = "failure" + + +class TaskStatus(str, Enum): + """Enumeration for task execution status.""" + PENDING = "pending" + PROCESSING = "processing" + COMPLETED = "completed" + ERROR = "error" diff --git a/MindEnergy/applications/deploy/src/inference.py b/MindEnergy/applications/deploy/src/inference.py new file mode 100644 index 000000000..3dd5b12a0 --- /dev/null +++ b/MindEnergy/applications/deploy/src/inference.py @@ -0,0 +1,419 @@ +# Copyright 2025 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ + +""" +Inference module for MindScience deployment service. + +This module provides classes and functions for performing inference using +MindSpore Lite across multiple devices (e.g. NPU devices) in parallel. +It includes: + +- InferenceModel: A wrapper for MindSpore Lite models with input/output handling +- MultiprocessInference: A class for managing parallel inference across multiple devices +- infer_process_func: A function that runs in separate processes to handle inference tasks + +The module implements a multiprocessing approach where each device runs in its own +process with communication handled via pipes. It provides methods for model building, +inference execution, health checks, and resource cleanup. +""" + +import os +from typing import List +from multiprocessing import Process, Pipe + +import numpy as np +from loguru import logger + +import mindspore_lite as mslite + +from .config import DeployConfig +from .enums import ProcessMessage + +def infer_process_func(pipe_child_end, model_path, device_id): + """Function that runs in a separate process to handle inference tasks. + + This function initializes an InferenceModel, handles communication through + a pipe, and processes different types of messages including initialization, + inference requests, health checks, and exit signals. It runs in a continuous + loop until it receives an exit message. + + Args: + pipe_child_end: The child end of a multiprocessing.Pipe used for + communication with the parent process. + model_path (str): Path to the model file to load for inference. + device_id (int): ID of the device to run inference on. + """ + try: + infer_model = InferenceModel(model_path, device_id) + if DeployConfig.dummy_model_path.endswith(".mindir") and os.path.exists(DeployConfig.dummy_model_path): + dummy_model = InferenceModel(DeployConfig.dummy_model_path, device_id) + else: + dummy_model = infer_model + pipe_child_end.send((ProcessMessage.BUILD_FINISH, infer_model.batch_size)) + except Exception as e: + logger.exception(f"Model initialization failed on device {device_id}: {e}") + pipe_child_end.send((ProcessMessage.ERROR, str(e))) + pipe_child_end.close() + return + + try: + while True: + msg_type, msg = pipe_child_end.recv() + if msg_type == ProcessMessage.EXIT: + logger.info(f"Device {device_id} received exit signal") + break + if msg_type == ProcessMessage.CHECK: + try: + _ = dummy_model.dummy_infer() + pipe_child_end.send((ProcessMessage.REPLY, "")) + except Exception as e: + err_msg = f"Dummy inference failed on device {device_id}: {e}" + logger.exception(err_msg) + pipe_child_end.send((ProcessMessage.ERROR, err_msg)) + raise RuntimeError(err_msg) from e + elif msg_type == ProcessMessage.PREDICT: + try: + inputs = msg + result = infer_model.infer(inputs) + pipe_child_end.send((ProcessMessage.REPLY, result)) + except Exception as e: + err_msg = f"Inference failed on device {device_id}: {e}" + logger.exception(err_msg) + pipe_child_end.send((ProcessMessage.ERROR, err_msg)) + raise RuntimeError(err_msg) from e + else: + err_msg = f"Unexpected message type {msg_type} on device {device_id}" + logger.exception(err_msg) + pipe_child_end.send((ProcessMessage.ERROR, err_msg)) + raise RuntimeError(err_msg) + except Exception as e: + logger.exception(f"Runtime error on device {device_id}: {e}") + pipe_child_end.send((ProcessMessage.ERROR, str(e))) + finally: + pipe_child_end.close() + + +class MultiprocessInference: + """A class for performing inference using multiple processes across different devices. + + This class manages multiple inference processes that run in parallel across specified + devices (e.g. Ascend NPU devices). It provides methods to build the model, run + inference, perform health checks, and clean up resources. + + Attributes: + model_path (str): Path to the model file to be used for inference. + device_num (int): Number of devices to use for parallel inference. + batch_size (int): Batch size of the loaded model, initialized to -1. + process_pool (list): List of Process objects for the inference processes. + parent_pipes (list): List of parent ends of Pipe objects for communication + with the inference processes. + """ + + def __init__(self, model_path: str, device_num: int): + """Initializes the MultiprocessInference instance. + + Args: + model_path (str): Path to the model file to be used for inference. + device_num (int): Number of devices to use for parallel inference. + """ + self.model_path = model_path + self.device_num = device_num + self.batch_size = -1 + + self.process_pool = [] + self.parent_pipes = [] + + def build_model(self): + """Builds and initializes the model on multiple devices. + + This method creates a process for each device, initializes the model in each process, + and establishes communication pipes between the main process and the worker processes. + It also verifies successful initialization of each device and ensures batch size + consistency across devices. + + Raises: + RuntimeError: If model has already been loaded or if initialization fails. + """ + if self.process_pool: + self._cleanup_resources() + raise RuntimeError("The model has been loaded. \ + Please uninstall this model first and then reload another model!") + + for device_id in range(self.device_num): + parent_conn, child_conn = Pipe(duplex=True) + self.parent_pipes.append(parent_conn) + + process = Process( + target=infer_process_func, + args=(child_conn, self.model_path, device_id), + daemon=True, + ) + self.process_pool.append(process) + process.start() + + child_conn.close() + + for device_id in range(self.device_num): + try: + msg_type, msg = self.parent_pipes[device_id].recv() + if msg_type == ProcessMessage.ERROR: + raise RuntimeError(f"Device {device_id} initialization failed: {msg}") + if msg_type != ProcessMessage.BUILD_FINISH: + raise RuntimeError(f"Unexpected message type {msg_type} during initialization") + + if self.batch_size not in (-1, msg): + raise RuntimeError("Batch size in different models are inconsistent.") + self.batch_size = msg + logger.info(f"Device {device_id} initialized successfully") + except Exception as e: + self._cleanup_resources() + logger.critical("Failed to initialize inference processes") + raise RuntimeError(f"Unexpected error {e} during initialization") from e + + def infer(self, inputs): + """Performs inference on the provided input data. + + This method distributes the input data across available devices and collects + the results. Each input item is sent to a separate device for parallel processing. + + Args: + inputs: List of input data for inference. Each item will be sent to a separate device. + + Returns: + list: List of inference results from each device. + + Raises: + ValueError: If the number of inputs exceeds the number of available devices. + RuntimeError: If sending prediction command to a device fails or if inference fails. + """ + if self.device_num < len(inputs): + raise ValueError(f"Inputs length {len(inputs)} should be less than or equal to \ + parallel number {self.device_num}, inference abort!") + + for device_id, input_data in enumerate(inputs): + try: + self.parent_pipes[device_id].send((ProcessMessage.PREDICT, input_data)) + except (BrokenPipeError, EOFError, OSError) as e: + self._cleanup_resources() + raise RuntimeError(f"Failed to send PREDICT to device {device_id}: {e}") from e + except Exception as e: + self._cleanup_resources() + raise RuntimeError(f"Unexpected error when sending PREDICT to device {device_id}: {e}") from e + + results = [] + for device_id in range(len(inputs)): + try: + msg_type, msg = self.parent_pipes[device_id].recv() + if msg_type == ProcessMessage.ERROR: + self._cleanup_resources() + raise RuntimeError(f"Device {device_id} inference failed: {msg}, cleanup all resource!") + results.append(msg) + except EOFError as e: + self._cleanup_resources() + raise RuntimeError(f"Device {device_id} connection closed unexpectedly.") from e + + return results + + def finalize(self): + """Finalizes the inference processes and cleans up resources. + + This method sends an EXIT signal to all worker processes, waits for them to + finish, and then cleans up all resources including the processes and pipes. + """ + for idx, pipe in enumerate(self.parent_pipes): + try: + pipe.send((ProcessMessage.EXIT, "")) + except (BrokenPipeError, EOFError, OSError) as e: + logger.exception(f"Failed to send EXIT to device {idx}: {e}") + except Exception as e: + logger.exception(f"Unexpected error when sending EXIT to device {idx}: {e}") + + for idx, process in enumerate(self.process_pool): + if process.is_alive(): + try: + process.join(timeout=5) + except (OSError, RuntimeError) as e: + logger.exception(f"Error while joining process {idx} during finalize: {e}") + except Exception as e: + logger.exception(f"Unexpected error while joining process {idx} during finalize: {e}") + + self._cleanup_resources() + + def _cleanup_resources(self) -> None: + """Cleans up all process and pipe resources. + + This private method terminates all running processes and closes all pipes. + It tracks any failures during cleanup and raises a RuntimeError if cleanup + fails for any of the resources. + + Raises: + RuntimeError: If any process or pipe fails to be cleaned up properly. + """ + failure_processes = [] + for idx, process in enumerate(self.process_pool): + if process.is_alive(): + logger.warning(f"Terminating process {idx}...") + try: + process.terminate() + process.join(timeout=2) + except (OSError, RuntimeError) as e: + logger.exception(f"Failed to terminate process {idx}: {e}") + failure_processes.append(idx) + except Exception as e: + logger.exception(f"Unexpected error while terminating process {idx} during cleanup resources: {e}") + failure_processes.append(idx) + + failure_pipes = [] + for idx, pipe in enumerate(self.parent_pipes): + try: + pipe.close() + except (BrokenPipeError, EOFError, OSError) as e: + logger.exception(f"Failed to close {idx} parent pipe cleanly: {e}") + failure_pipes.append(idx) + except Exception as e: + logger.exception(f"Unexpected error while closing parent pipe {idx}: {e}") + failure_pipes.append(idx) + + self.process_pool = [] + self.parent_pipes = [] + + if failure_processes or failure_pipes: + raise RuntimeError(f"Failed to cleanup subprocesses: {failure_processes}, parent pipes: {failure_pipes}!") + + def is_ready(self): + """Checks if all inference processes are ready and healthy. + + This method verifies that all processes are alive and responsive by sending + a CHECK message to each and waiting for a response. If any process is not + responsive or returns an error, it raises a RuntimeError and cleans up resources. + + Raises: + RuntimeError: If model is not loaded, if any process is not alive, + or if health check fails on any device. + """ + if not self.process_pool: + raise RuntimeError("Model not loaded, please check!") + + for idx, process in enumerate(self.process_pool): + if not process.is_alive(): + self._cleanup_resources() + raise RuntimeError(f"Process {idx} is not alive, please check") + + for idx, pipe in enumerate(self.parent_pipes): + try: + pipe.send((ProcessMessage.CHECK, "")) + except (BrokenPipeError, EOFError, OSError) as e: + self._cleanup_resources() + raise RuntimeError(f"Failed to send CHECK to device {idx}: {e}") from e + except Exception as e: + self._cleanup_resources() + raise RuntimeError(f"Unexpected error when sending CHECK to device {idx}: {e}") from e + + for idx, pipe in enumerate(self.parent_pipes): + try: + msg_type, msg = pipe.recv() + if msg_type == ProcessMessage.ERROR: + self._cleanup_resources() + raise RuntimeError(f"Device {idx} health check failed: {msg}, cleanup all resource!") + except EOFError as e: + self._cleanup_resources() + raise RuntimeError(f"Device {idx} connection closed unexpectedly during health check.") from e + + +class InferenceModel: + """A class for performing inference using MindSpore Lite. + + This class initializes a MindSpore Lite model on a specific device and provides + methods for running inference, dummy inference, and accessing model properties. + + Attributes: + model: MindSpore Lite Model instance. + input_shape_list (list): List of input shapes for the model. + model_batch_size (int): Batch size of the model, extracted from the first input shape. + """ + + def __init__(self, model_path: str, device_id: int): + """Initializes the InferenceModel instance. + + Creates a MindSpore Lite context for the specified device, loads the model + from the given path, and extracts input shapes and batch size information. + + Args: + model_path (str): Path to the MindIR model file. + device_id (int): ID of the device to run inference on (e.g. Ascend NPU ID). + + Raises: + RuntimeError: If the loaded model has no input. + """ + context = mslite.Context() + context.target = ["ascend"] + context.ascend.device_id = device_id + + self.model = mslite.Model() + self.model.build_from_file(model_path, mslite.ModelType.MINDIR, context) + self.input_shape_list = [item.shape for item in self.model.get_inputs()] + if not self.input_shape_list: + raise RuntimeError("The loaded model has no input!") + self.model_batch_size = self.input_shape_list[0][0] + + def infer(self, batch_inputs: List[np.ndarray]): + """Performs inference on the provided batch of input data. + + This method takes a list of numpy arrays as input, assigns them to the model's + input tensors, runs the prediction, and returns the output as a list of numpy arrays. + + Args: + batch_inputs (List[np.ndarray]): List of input data as numpy arrays. The number + of inputs should match the number of model inputs. + + Returns: + list: List of inference results as numpy arrays. + + Raises: + ValueError: If the number of inputs doesn't match the number of model inputs. + """ + inputs = self.model.get_inputs() + if len(batch_inputs) != len(inputs): + raise ValueError(f"The number of model inputs {len(inputs)} should be the same as \ + the number of inputs {len(batch_inputs)}") + + for i, input_ in enumerate(inputs): + input_.set_data_from_numpy(batch_inputs[i]) + + batch_out = self.model.predict(inputs) + return [item.get_data_to_numpy() for item in batch_out] + + def dummy_infer(self): + """Performs a dummy inference without input data. + + This method runs the model prediction without providing specific input data, + which is typically used for model warm-up or health checks. + + Returns: + list: Model outputs from the prediction. + """ + inputs = self.model.get_inputs() + outputs = self.model.predict(inputs) + return outputs + + @property + def batch_size(self): + """int: The batch size of the model, extracted from the first input shape.""" + return self.model_batch_size + + @property + def input_shapes(self): + """list: List of input shapes for the model.""" + return self.input_shape_list diff --git a/MindEnergy/applications/deploy/src/schemas.py b/MindEnergy/applications/deploy/src/schemas.py new file mode 100644 index 000000000..3bc2a0e0e --- /dev/null +++ b/MindEnergy/applications/deploy/src/schemas.py @@ -0,0 +1,42 @@ +# Copyright 2025 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ + +""" +Schemas module for MindScience deployment service. + +This module defines Pydantic data models (schemas) used for request/response +validation and data serialization in the MindScience deployment service. +Currently includes: + +- ModelInfo: A schema for representing model status and associated messages + returned by the deployment service APIs + +These schemas ensure type safety and proper validation of data exchanged +between clients and the deployment service. +""" + +from pydantic import BaseModel + +from .enums import ModelStatus + +class ModelInfo(BaseModel): + """Model information containing status and message. + + Attributes: + status: The status of the model, defaults to ModelStatus.SUCCESS. + message: The message associated with the model status, defaults to empty string. + """ + status: str = ModelStatus.SUCCESS + message: str = "" diff --git a/MindEnergy/applications/deploy/src/session.py b/MindEnergy/applications/deploy/src/session.py new file mode 100644 index 000000000..db7316e17 --- /dev/null +++ b/MindEnergy/applications/deploy/src/session.py @@ -0,0 +1,219 @@ +# Copyright 2025 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ + +""" +Session management module for MindScience deployment service. + +This module provides the SessionManager class for managing model inference sessions. +It includes functionality for: + +- Loading and initializing models for inference +- Managing multiple inference sessions across different devices +- Performing batch inference operations +- Checking model health status +- Unloading models and cleaning up resources + +The SessionManager handles the lifecycle of model sessions, from initialization +to finalization, ensuring proper resource management and inference execution. +""" + +import numpy as np +from loguru import logger + +from .utils import Utilities +from .enums import HealthStatus +from .config import DeployConfig +from .inference import MultiprocessInference + +class SessionManager: + """Manages model sessions for inference tasks. + + This class handles the initialization, deletion, and inference operations + for machine learning models, including loading models, managing sessions, + and performing batch inference. + """ + + def __init__(self): + """Initializes the SessionManager with default values.""" + self.current_model = "" + self.device_num = -1 + self.infer_sessions = None + self._model_dict = {} + + def init_session(self, model_name, device_num): + """Initializes a model session for inference. + + Collects model files, validates device number and model name, + creates inference sessions, and builds the model for each session. + + Args: + model_name (str): Name of the model to load. + device_num (int): Number of devices to use for inference. + + Raises: + ValueError: If device number exceeds maximum, model is not found, + or model is already loaded. + RuntimeError: If session initialization fails. + """ + self._model_dict = Utilities.collect_mindir_models(DeployConfig.models_dir) + + if device_num > DeployConfig.max_device_num: + raise ValueError(f"Device num {device_num} is over the actual device num {DeployConfig.max_device_num}, \ + please check!") + if model_name not in self.model_dict: + raise ValueError(f"model {model_name} is not in {self.model_dict.keys()}, please check!") + if model_name == self.current_model: + raise ValueError(f"model {model_name} is already loaded, please check!") + + model_paths = self.model_dict.get(model_name) + sessions = [MultiprocessInference(model_path, device_num) for model_path in model_paths] + try: + for i, session in enumerate(sessions): + session.build_model() + logger.info(f"{model_paths[i]} is loaded successfully, {device_num} sessions are inited") + except Exception as e: + raise RuntimeError(f"Init session failed, please check! ERROR: {e}") from e + + self.current_model = model_name + self.device_num = device_num + self.infer_sessions = sessions + + def del_session(self): + """Deletes the current model session. + + Cleans up the current model, device number, and model dictionary. + Finalizes all inference sessions if they exist. + + Raises: + RuntimeError: If deleting the session fails. + """ + self.current_model = "" + self.device_num = -1 + self._model_dict = {} + + if self.infer_sessions is None: + logger.warning("Session not inited, please check!") + else: + try: + for session in self.infer_sessions: + session.finalize() + self.infer_sessions = None + logger.info("Session is deleted successfully") + except Exception as e: + raise RuntimeError(f"Delete session failed, please check! ERROR: {e}") from e + + def batch_infer(self, batch_inputs, task_type): + """Performs batch inference on the given inputs. + + Processes batch inputs by extending them to match model batch size, + and performs inference using the specified task type session. + + Args: + batch_inputs (list): List of input arrays for batch inference. + task_type (int): Index of the inference session to use. + + Returns: + list: List of concatenated results from the inference. + + Raises: + ValueError: If inputs are empty, task type is invalid, or + model has not been loaded. + RuntimeError: If model has not been loaded. + """ + if not batch_inputs: + raise ValueError("Input is empty, please check!") + if self.infer_sessions is None: + raise RuntimeError("Model has not been loaded, please check!") + if task_type >= len(self.infer_sessions): + raise ValueError(f"Only task_type {list(range(len(self.infer_sessions)))} is supported, \ + but given {task_type}") + + session = self.infer_sessions[task_type] + data_size = batch_inputs[0].shape[0] + model_batch_size = session.batch_size + + batch_inputs = [Utilities.extend_input(item, model_batch_size) for item in batch_inputs] + total_size = batch_inputs[0].shape[0] + + input_datas = [] + result_list = [] + for i in range(0, total_size, model_batch_size): + input_datas.append([item[i: i + model_batch_size] for item in batch_inputs]) + if len(input_datas) > 0 and len(input_datas) % self.device_num == 0: + result_list = self._infer(session, input_datas, result_list) + input_datas = [] + if input_datas: + result_list = self._infer(session, input_datas, result_list) + + model_out_num = len(result_list[0]) + final_ret = [] + for i in range(model_out_num): + final_ret.append(np.concatenate([result[i] for result in result_list], axis=0)[:data_size]) + return final_ret + + def _infer(self, session, input_datas, result_list): + """Performs inference on input data using the given session. + + Internal helper method that executes the actual inference and + extends the result list with predicted results. + + Args: + session (MultiprocessInference): Inference session to use. + input_datas (list): List of input data to be inferred. + result_list (list): List to extend with inference results. + + Returns: + list: Updated result list with new inference results. + + Raises: + RuntimeError: If the model prediction fails. + """ + try: + predict_result = session.infer(input_datas) + result_list.extend(predict_result) + except (ValueError, RuntimeError) as e: + raise RuntimeError(f"{self.current_model} predict failed, please check! ERROR: {e}") from e + return result_list + + def is_ready(self): + """Checks if the model sessions are ready for inference. + + Verifies if inference sessions are initialized and operational. + + Returns: + HealthStatus: Status indicating if model is NOTLOADED, READY, + or in EXCEPTION state. + """ + if self.infer_sessions is None: + logger.info("Model has not been loaded, please check!") + return HealthStatus.NOTLOADED + try: + for session in self.infer_sessions: + session.is_ready() + logger.info(f"Model: {self.current_model} is ready!") + return HealthStatus.READY + except Exception: + logger.info(f"Model: {self.current_model} is unavailable!") + return HealthStatus.EXCEPTION + + @property + def session_name(self): + """str: Name of the currently loaded model.""" + return self.current_model + + @property + def model_dict(self): + """dict: Dictionary containing model information.""" + return self._model_dict diff --git a/MindEnergy/applications/deploy/src/utils.py b/MindEnergy/applications/deploy/src/utils.py new file mode 100644 index 000000000..6bb225e18 --- /dev/null +++ b/MindEnergy/applications/deploy/src/utils.py @@ -0,0 +1,369 @@ +# Copyright 2025 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ + +""" +Utilities module for MindScience deployment service. + +This module provides a collection of utility functions and classes for various +tasks in the MindScience deployment service, including: + +- File handling operations (upload, extraction, HDF5 read/write) +- Model file collection and management +- Input data extension for batch processing +- Task status counting +- NPU (Neural Processing Unit) resource monitoring +- Asynchronous operations support + +The Utilities class contains static methods that are commonly used across +different components of the deployment service. +""" + +import os +import re +import zipfile +import tarfile +import asyncio +import traceback +import subprocess +from typing import Dict, List + +import h5py +import aiofiles +import numpy as np +from loguru import logger +from fastapi import UploadFile, HTTPException + +from .enums import TaskStatus +from .config import DeployConfig, ModelConfig + +class Utilities: + """ + Utilities helper class that encapsulates deployment-related utility functions. + + This class includes file handling, model collection, HDF5 file read/write, + asynchronous file upload saving, and methods to collect NPU usage information. + """ + @staticmethod + def collect_mindir_models(root_dir: str) -> Dict[str, List[str]]: + """Collect *.mindir model files from each subdirectory under the specified root directory. + + Args: + root_dir (str): Root directory path to search. Each immediate subdirectory + is treated as a distinct model name. + + Returns: + Dict[str, List[str]]: Mapping of model directory name to a sorted list of + absolute paths of all `.mindir` files found in that model directory. + + Raises: + ValueError: If `root_dir` is not a valid directory. + """ + if not os.path.isdir(root_dir): + raise ValueError(f"Invalid directory: {root_dir}") + + model_dict = {} + for model_name in os.listdir(root_dir): + model_dir = os.path.join(root_dir, model_name) + if not os.path.isdir(model_dir): + continue + + mindir_files = [] + for file in os.listdir(model_dir): + if file.endswith(".mindir"): + abs_path = os.path.abspath(os.path.join(model_dir, file)) + mindir_files.append(abs_path) + + if mindir_files: + model_dict[model_name] = sorted(mindir_files) + + return model_dict + + @staticmethod + def extend_input(array: np.ndarray, batch_size: int) -> np.ndarray: + """Extend (pad) the input array along the first dimension so that its length + is divisible by `batch_size`. + + If the first dimension is already divisible by `batch_size`, the original + array is returned. Otherwise, zero-rows are appended to the end of the + array to make the first dimension divisible. + + Args: + array (np.ndarray): The numpy array to pad. Must have at least one dimension. + batch_size (int): The batch size to make the first dimension divisible by. + + Returns: + np.ndarray: The padded numpy array with the same dtype as the input. + + Raises: + ValueError: If `array` has no shape (empty). + """ + if not array.shape: + raise ValueError(f"The array to extend is empty, please check: {array}.") + + if array.shape[0] % batch_size != 0: + padding_length = batch_size - array.shape[0] % batch_size + padding_contents = np.zeros((padding_length, *array.shape[1:]), dtype=array.dtype) + return np.concatenate([array, padding_contents], axis=0) + return array + + @staticmethod + async def save_upload_file(upload_file: UploadFile, destination: str): + """Asynchronously save a FastAPI `UploadFile` to the specified destination + path in chunks. + + Args: + upload_file (UploadFile): The uploaded file object provided by FastAPI. + destination (str): Target file path (including filename) to save the upload. + + Returns: + None + + Raises: + HTTPException: Raises an HTTPException (status 500) if saving fails. + """ + try: + async with aiofiles.open(destination, "wb") as f: + while chunk := await upload_file.read(DeployConfig.chunk_size): + await f.write(chunk) + except Exception as e: + raise HTTPException(status_code=500, detail=f"save upload file failed: {e}") from e + + @staticmethod + def extract_file(file_path: str, extract_to: str = None): + """Extract supported archive file formats (tar, tar.gz, tgz, tar.bz2, tbz2, + tar.xz, txz, zip) to a target directory. + + Args: + file_path (str): Path to the archive file to extract. + extract_to (str, optional): Target directory to extract into. If None, + the archive's containing directory is used. + + Returns: + None + + Raises: + FileNotFoundError: If `file_path` does not exist. + ValueError: If the file format is not a supported archive type. + """ + if not os.path.exists(file_path): + raise FileNotFoundError(f"File not found: {file_path}") + + if extract_to is None: + extract_to = os.path.dirname(file_path) + + if file_path.endswith((".tar", ".tar.gz", ".tgz", ".tar.bz2", ".tbz2", ".tar.xz", ".txz")): + with tarfile.open(file_path, 'r:*') as tf: + tf.extractall(extract_to) + elif file_path.endswith(".zip"): + with zipfile.ZipFile(file_path, 'r') as zf: + zf.extractall(extract_to) + else: + raise ValueError(f"Unsupported compressed file format: {file_path}.") + + @staticmethod + def count_pending_task(tasks_status: Dict[str, str]) -> int: + """Count the number of tasks in a mapping that are in the PENDING state. + + Args: + tasks_status (Dict[str, str]): Mapping from task ID to task status + (expected to be members of `TaskStatus`). + + Returns: + int: Number of tasks whose status equals `TaskStatus.PENDING`. + """ + pending_number = 0 + for value in tasks_status.values(): + if value == TaskStatus.PENDING: + pending_number += 1 + return pending_number + + @staticmethod + def load_h5_file(file_path: str) -> List[np.ndarray]: + """Load input columns specified in `ModelConfig.input_columns` from an HDF5 + file and return them as a list of numpy arrays. + + Args: + file_path (str): HDF5 file path to read from. + + Returns: + List[np.ndarray]: List of numpy arrays for each input column defined in + `ModelConfig.input_columns`, preserving the same order. + + Raises: + ValueError: If any key from `ModelConfig.input_columns` is missing in the file. + """ + batch_inputs = [] + with h5py.File(file_path, "r") as f: + for key in ModelConfig.input_columns: + if key not in f.keys(): + raise ValueError(f"Key {key} not in dataset, please check!") + batch_inputs.append(np.array(f[key], dtype=f[key].dtype)) + return batch_inputs + + @staticmethod + def save_h5_file(items: List[np.ndarray], file_path: str): + """Write prediction outputs to an HDF5 file. + + Output column names are taken from `ModelConfig.output_columns`. If the + number of `items` does not match the number of configured output column + names, the function will rename columns using the default format + `output_0`, `output_1`, ... and log a warning. + + Args: + items (List[np.ndarray]): List of numpy arrays to write as outputs. + file_path (str): HDF5 output file path. + + Returns: + None + + Notes: + If the number of configured output columns does not match the number + of provided items, the method assigns default names and logs a + warning. + """ + output_columns = ModelConfig.output_columns + if len(items) != len(output_columns): + logger.warning("Number of outputs in config is inconsistent with the number of the actual outputs, \ + rename the output column.") + output_columns = tuple(f"output_{i}" for i in range(len(items))) + + with h5py.File(file_path, "w") as f: + for key, value in zip(output_columns, items): + f.create_dataset(key, data=value) + + @staticmethod + async def get_npu_usage(): + """Asynchronously execute `npu-smi info` to obtain NPU utilization and memory + information, parse the output, and return a structured list of stats. + + Each returned item is a dictionary with the following keys: + - id (int): NPU device ID + - utilization_percent (int): NPU utilization percentage + - memory_used_mb (int): Memory used in MB + - memory_total_mb (int): Total memory in MB + + Returns: + List[Dict[str, int]]: List of dictionary entries containing NPU stats. + + Raises: + FileNotFoundError: If the `npu-smi` command is not available. + RuntimeError: If executing `npu-smi info` fails for other reasons. + """ + try: + command = ["npu-smi", "info"] + process = await asyncio.create_subprocess_exec( + *command, + stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.PIPE + ) + output_bytes, stderr_bytes = await process.communicate() + + if process.returncode != 0: + if b"command not found" in stderr_bytes.lower() or \ + b"no such file or directory" in stderr_bytes.lower(): + logger.error("ERROR: 'npu-smi' command not found!") + logger.error("Please ensure that the Ascend CANN toolkit is correctly installed and \ + its bin directory has been added to the system's PATH environment variable.") + raise FileNotFoundError("'npu-smi' command not found.") + raise subprocess.CalledProcessError( + process.returncode, command, output=output_bytes, stderr=stderr_bytes + ) + except Exception as e: + logger.error(f"Execute 'npu-smi info' failed, ERROR: {e}") + raise RuntimeError(f"Execute 'npu-smi info' failed, ERROR: {e}") from e + + logger.info("=== npu-smi info original output ===") + logger.info(output_bytes.decode("utf-8", errors="replace")) + logger.info("===========================") + + output = output_bytes.decode("utf-8", errors="ignore") + + npu_stats = [] + lines = output.strip().splitlines() + + if not lines: + logger.warning("'npu-smi info' did not return any output.") + return npu_stats + + for i, line in enumerate(lines): + match_first_line = re.match(r"^\s*\|\s*(\d+)\s+[a-zA-Z0-9]+\s*\|", line) + + if not match_first_line: + logger.warning(f"Can not match NPU ID in line: {line}.") + continue + + try: + npu_id_str = match_first_line.group(1) + if not npu_id_str: + logger.warning(f"NPU ID is empty in line: {line}.") + continue + + npu_id = int(npu_id_str) + logger.info(f"Found NPU ID: {npu_id}.") + + if (i + 1) >= len(lines): + logger.warning("Not enough rows to parse NPU utilization information.") + continue + + second_line = lines[i + 1] + logger.info(f"Process NPU {npu_id} data line: {second_line}.") + + parts = second_line.split("|") + if len(parts) < 4: + logger.warning(f"The data row format is incorrect and \ + the number of columns is insufficient: {second_line}.") + continue + + data_string = parts[-2].strip() + logger.info(f"Extracted data: '{data_string}'.") + + tokens = re.findall(r"\d+", data_string) + logger.info(f"Extracted tokens: {tokens}.") + + if len(tokens) < 5: + logger.warning(f"Insufficient tokens: {tokens}.") + continue + + try: + utilization_percent = int(tokens[0]) + mem_used = int(tokens[3]) + mem_total = int(tokens[4]) + + info = { + "id": npu_id, + "utilization_percent": utilization_percent, + "memory_used_mb": mem_used, + "memory_total_mb": mem_total, + } + logger.info(f"Successfully parse {npu_id} info: {info}.") + npu_stats.append(info) + + except (ValueError, IndexError) as e: + logger.error(f"Error parsing NPU {npu_id} data: {e}.") + continue + + except Exception as e: + logger.exception(f"Unexpected error when parsing NPU data: {e}.") + traceback.print_exc() + continue + + if not npu_stats: + logger.warning(f"Can not parse any info from 'npu-smi info' output. original output: \n{output}") + if "No NPU device found" in output: + logger.info("'No NPU device found' - Perhaps there are no available NPU devices.") + else: + logger.info(f"Successfully parse {len(npu_stats)} NPU info.") + + return npu_stats -- Gitee