本地部署 Qwen3-06B

1. 安装基础库

sudo apt-get install -y build-essential python3-dev libffi-dev libbz2-dev liblzma-dev

2. 安装openssl1.1.1

2.1 通过包管理器安装

sudo apt-get install -y libssl1.1

2.2 手动编译安装

wget https://www.openssl.org/source/openssl-1.1.1u.tar.gz

tar -xzvf openssl-1.1.1u.tar.gz

cd openssl-1.1.1u

./config --prefix=/usr/local/openssl-1.1.1 shared

make && sudo make install

echo "/usr/local/openssl-1.1.1/lib" | sudo tee /etc/ld.so.conf.d/openssl-1.1.1.conf

ldconfig

2.3 验证安装

ldconfig -p | grep libssl.so.1.1

3. 安装python3.11

wget https://www.python.org/ftp/python/3.11.10/Python-3.11.10.tgz

tar -xzvf Python-3.11.10.tgz

cd Python-3.11.10

./configure --with-openssl=/usr/local/openssl-1.1.1

make && sudo makeinstall

4. 安装NumPy(NumPy 要求 GCC 版本 ≥9.3)

4.1 安装m4

sudo apt install

<think>我们根据用户提供的目录结构,将四个主要文件分别放置在正确的位置: - main.py 在项目根目录 - api_service.py 在 app 子目录 - embedding_model.py 在 app 子目录 - reranker_model.py 在 app 子目录 同时,用户要求纯CPU运行,因此我们在加载模型时需要注意: 1. 使用`torch.float32`(因为CPU上通常不使用混合精度) 2. 确保模型在CPU上运行(通过`device_map`或显式指定`device`) 另外,根据引用[2]和[3],我们需要注意: - 模型加载方式:使用Hugging Face的`AutoModel`和`AutoTokenizer`,并指定本地路径 - 性能考虑:模型只加载一次(单例模式),避免每次调用都加载模型 根据引用[4],我们还需要注意日志和内存使用情况。 修正后的代码结构如下: Tang_Transform/ ├── app/ │ ├── api_service.py │ ├── embedding_model.py │ ├── reranker_model.py │ └── __init__.py ├── models/ │ ├── Qwen3-Embedding-06B # 注意:目录名与用户提供的保持一致 │ └── Qwen3-Reranker-06B ├── main.py ├── core_requirements.txt ├── jsconfig.json └── requirements.txt 我们开始编写代码: 1. 在`app/embedding_model.py`中,我们将加载嵌入模型。注意:用户提供的模型目录名为`Qwen3-Embedding-06B`,因此路径要匹配。 2. 在`app/reranker_model.py`中,加载重排模型,目录名为`Qwen3-Reranker-06B`。 3. 在`app/api_service.py`中,定义FastAPI应用和路由。 4. 在`main.py`中,启动FastAPI应用。 由于模型较大,在CPU上加载需要一定时间,且需要较大的内存。我们使用单例模式确保只加载一次。 注意:在CPU上运行,我们不需要指定device(默认就是CPU),但为了清晰,可以显式设置。 下面分别给出四个文件的代码: 首先,确保每个文件中的路径正确。由于我们将模型放在`models`目录下,而`embedding_model.py`和`reranker_model.py`在`app`目录下,因此模型路径应该是相对根目录的:`../models/...`。 但是,在运行`main.py`时,当前目录是项目根目录,而`app`中的模块在导入时,相对路径是相对于当前工作目录(即项目根目录)的,因此我们在`embedding_model.py`中可以直接使用`models/Qwen3-Embedding-06B`(因为根目录下就有`models`文件夹)。 然而,为了确保路径正确,我们可以使用绝对路径(通过`os.path.dirname`和`os.path.abspath`构建)。但考虑到项目结构固定,我们使用相对根目录的路径(即从根目录开始)即可,因为服务是从根目录启动的。 另一种方案:在`main.py`中设置环境变量或传递配置,但这里为了简单,我们直接在模型加载文件中使用相对路径。 修正:在`embedding_model.py`中,模型路径为`./models/Qwen3-Embedding-06B`,这个路径是相对于项目根目录的(因为程序从根目录运行)。 但是,在`app`子目录中的模块,如果直接写`./models`,这个`./`代表的是当前工作目录(即项目根目录),所以是没问题的。 下面编写具体代码: 注意:由于用户要求纯CPU,我们不需要设置device为GPU,也不需要使用`device_map`。 代码实现: 1. `app/embedding_model.py`:</think>根据您的目录结构和需求,我将重新组织代码以实现CPU模型挂载和OpenAPI兼容端点。以下是修正后的实现方案: ### 项目结构优化 ```bash Tang_Transform/ ├── app/ │ ├── api_service.py # API端点定义 │ ├── embedding_model.py # 嵌入模型实现 │ ├── reranker_model.py # 重排模型实现 │ └── __init__.py # 包初始化文件 ├── models/ │ ├── Qwen3-Embedding-06B # 嵌入模型文件 │ └── Qwen3-Reranker-06B # 重排模型文件 ├── main.py # 应用入口 ├── core_requirements.txt ├── jsconfig.json └── requirements.txt ``` ### 1. main.py - 应用入口 ```python from app.api_service import create_app import uvicorn if __name__ == "__main__": app = create_app() uvicorn.run( app, host="0.0.0.0", port=8000, log_level="info", lifespan="off" # Windows系统需要关闭lifespan事件 ) ``` ### 2. app/api_service.py - API端点定义 ```python from fastapi import FastAPI, HTTPException, Header from pydantic import BaseModel from .embedding_model import EmbeddingModel from .reranker_model import RerankerModel import os class EmbeddingRequest(BaseModel): input: str | list[str] model: str = "qwen-embedding" class RerankRequest(BaseModel): query: str documents: list[str] model: str = "qwen-rerank" top_k: int = 10 # 从环境变量获取API密钥 API_KEY = os.getenv("X_API_KEY", "default_secret_key") def create_app(): app = FastAPI( title="Qwen3 API Service", description="OpenAI兼容的Qwen3模型API服务" ) # 初始化模型单例 embedding_model = EmbeddingModel.get_instance() reranker_model = RerankerModel.get_instance() @app.post("/v1/embeddings", response_model=dict) async def create_embedding( request: EmbeddingRequest, authorization: str = Header(None) ): """生成文本嵌入向量""" if authorization != f"Bearer {API_KEY}": raise HTTPException(401, "Invalid API key") try: embeddings = embedding_model.generate(request.input) return { "object": "list", "data": [ { "object": "embedding", "index": i, "embedding": emb.tolist() } for i, emb in enumerate(embeddings) ], "model": request.model } except Exception as e: raise HTTPException(500, str(e)) @app.post("/v1/rerank", response_model=dict) async def rerank_documents( request: RerankRequest, authorization: str = Header(None) ): """文档重排服务""" if authorization != f"Bearer {API_KEY}": raise HTTPException(401, "Invalid API key") try: scores = reranker_model.rerank( request.query, request.documents, request.top_k ) return { "results": [ { "index": idx, "document": doc, "relevance_score": float(score) } for idx, (doc, score) in enumerate(zip(request.documents, scores)) ], "model": request.model } except Exception as e: raise HTTPException(500, str(e)) @app.get("/health") async def health_check(): return {"status": "healthy"} @app.get("/openapi.json", include_in_schema=False) async def get_openapi(): return app.openapi() return app ``` ### 3. app/embedding_model.py - 嵌入模型实现 ```python import torch import os import logging from transformers import AutoModel, AutoTokenizer from typing import Union, List # 配置日志 logging.basicConfig( filename=&#39;../logs/embedding_service.log&#39;, level=logging.INFO, format=&#39;%(asctime)s - %(name)s - %(levelname)s - %(message)s&#39; ) logger = logging.getLogger("EmbeddingService") class EmbeddingModel: _instance = None @classmethod def get_instance(cls): if cls._instance is None: cls._instance = cls() return cls._instance def __init__(self): # 模型路径使用相对路径 model_path = "../models/Qwen3-Embedding-06B" if not os.path.exists(model_path): error_msg = f"模型未找到: {model_path}" logger.error(error_msg) raise FileNotFoundError(error_msg) try: logger.info(f"开始加载嵌入模型: {model_path}") self.tokenizer = AutoTokenizer.from_pretrained(model_path) self.model = AutoModel.from_pretrained( model_path, trust_remote_code=True, torch_dtype=torch.float32 # CPU使用float32 ) self.model.eval() logger.info(f"嵌入模型加载完成, 内存占用: {self._get_model_memory()} MB") except Exception as e: logger.exception("模型加载失败") raise def _get_model_memory(self) -> float: """估算模型内存占用(MB)""" param_size = sum(p.numel() * p.element_size() for p in self.model.parameters()) buffer_size = sum(b.numel() * b.element_size() for b in self.model.buffers()) return (param_size + buffer_size) / (1024 ** 2) def generate(self, input: Union[str, List[str]], chunk_size=32) -> torch.Tensor: """生成文本嵌入向量(带分块处理)""" if isinstance(input, str): input = [input] embeddings = [] for i in range(0, len(input), chunk_size): chunk = input[i:i+chunk_size] # 使用模型内置的embedding方法 chunk_embeddings = self.model.embed_documents(chunk) embeddings.append(chunk_embeddings) return torch.cat(embeddings) if embeddings else torch.tensor([]) ``` ### 4. app/reranker_model.py - 重排模型实现 ```python import torch import os import logging from transformers import AutoModelForSequenceClassification, AutoTokenizer from typing import List # 配置日志 logging.basicConfig( filename=&#39;../logs/reranker_service.log&#39;, level=logging.INFO, format=&#39;%(asctime)s - %(name)s - %(levelname)s - %(message)s&#39; ) logger = logging.getLogger("RerankerService") class RerankerModel: _instance = None @classmethod def get_instance(cls): if cls._instance is None: cls._instance = cls() return cls._instance def __init__(self): # 模型路径使用相对路径 model_path = "../models/Qwen3-Reranker-06B" if not os.path.exists(model_path): error_msg = f"模型未找到: {model_path}" logger.error(error_msg) raise FileNotFoundError(error_msg) try: logger.info(f"开始加载重排模型: {model_path}") self.tokenizer = AutoTokenizer.from_pretrained(model_path) self.model = AutoModelForSequenceClassification.from_pretrained( model_path, trust_remote_code=True, torch_dtype=torch.float32 # CPU使用float32 ) self.model.eval() logger.info(f"重排模型加载完成, 内存占用: {self._get_model_memory()} MB") except Exception as e: logger.exception("模型加载失败") raise def _get_model_memory(self) -> float: """估算模型内存占用(MB)""" param_size = sum(p.numel() * p.element_size() for p in self.model.parameters()) buffer_size = sum(b.numel() * b.element_size() for b in self.model.buffers()) return (param_size + buffer_size) / (1024 ** 2) def rerank(self, query: str, documents: List[str], top_k: int = 10) -> List[float]: """文档重排并返回相关性分数""" # 分块处理大文本输入 batch_size = 8 # 根据内存调整 scores = [] for i in range(0, len(documents), batch_size): batch_docs = documents[i:i+batch_size] # 准备模型输入 inputs = self.tokenizer( [query] * len(batch_docs), batch_docs, padding=True, truncation=True, max_length=512, return_tensors="pt" ) # CPU推理 with torch.no_grad(): outputs = self.model(**inputs) # 获取相关性分数 batch_scores = torch.sigmoid(outputs.logits).squeeze(dim=1).tolist() scores.extend(batch_scores) return scores ``` ### 关键优化点 1. **路径配置**: - 使用相对路径`../models/`访问模型目录 - 日志文件存储在`../logs/`目录 2. **安全认证**: ```python # 环境变量设置API密钥 API_KEY = os.getenv("X_API_KEY", "default_secret_key") # 请求头验证 if authorization != f"Bearer {API_KEY}": raise HTTPException(401, "Invalid API key") ``` 3. **内存优化**: - 分块处理大文本输入(`chunk_size`参数) - 内存占用监控: ```python def _get_model_memory(self) -> float: param_size = sum(p.numel() * p.element_size() for p in self.model.parameters()) buffer_size = sum(b.numel() * b.element_size() for b in self.model.buffers()) return (param_size + buffer_size) / (1024 ** 2) ``` 4. **日志系统**: - 每个服务独立日志文件 - 记录模型加载时间和内存占用 ### 部署流程 1. **创建日志目录**: ```bash mkdir logs ``` 2. **设置环境变量**: ```bash # Windows set X_API_KEY=your_secret_key # Linux/macOS export X_API_KEY=your_secret_key ``` 3. **安装依赖**: ```bash pip install -r requirements.txt ``` 4. **启动服务**: ```bash python main.py ``` 5. **API文档访问**: ``` http://localhost:8000/openapi.json ``` ### 内存占用估算 对于Qwen3-0.6B模型,纯CPU环境下的内存占用约为: $$ \text{Memory} \approx 0.6 \times 10^9 \times 4 \times 1.5 = 3.6 \text{ GB} $$ 其中: - $0.6 \times 10^9$:6亿参数 - $4$:float32字节数 - $1.5$:包含优化器和中间变量的系数[^1] ### 相关问题 1. 如何在Windows系统优化PyTorch的CPU并行计算性能? 2. Qwen3模型在CPU推理时的最大批处理尺寸如何确定?[^1] 3. 如何实现API服务的动态批处理以提高吞吐量? 4. 在128G内存环境下能同时加载多个Qwen3模型吗? 5. 如何监控模型服务的资源使用情况?[^4] 6. 使用FastAPI时如何处理大文本输入的OOM问题?[^3]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值