ActionParser模型和文本嵌入模型部署
在完成数据库部署之后,我继续在服务器上实现了ActionParser模型和文本嵌入模型的部署,使系统具备了基础的模型能力支撑。
在完成数据库部署之后,我继续在服务器上实现了ActionParser模型和文本嵌入模型的部署,使系统具备了基础的模型能力支撑。
一、ActionParser模型
1.部署思路
在初步调研阶段,我们选择了DeepSeek-R1-Distill-Qwen-1.5B作为ActionParser模型。考虑到我们用来部署模型的服务器配置很低,不适合运行较重的推理服务,并且模型服务要做成可被本地电脑通过HTTP请求调用的形式,所以我没有采用vLLM这类相对较重的推理框架,而是决定使用llama.cpp进行模型推理,并通过FastAPI封装成HTTP服务供位于本地的模型能力层编排层程序调用。
然而,llama.cpp需要模型文件为GGUF格式,而DeepSeek-R1-Distill-Qwen-1.5B的Hugging Face仓库只有原始模型目录,所以先要将原始模型目录转换为GGUF文件。此外,完整的1.5B模型对于我们的服务器还是有些压力,所以我决定在GGUF文件的基础上进行量化,从而降低模型推理的性能消耗。
2.模型转换与量化工具的准备
在确定需要使用GGUF格式之后,我调研了相关工具,发现llama.cpp就可以完成模型转换与量化。于是我从Github拉取了llama.cpp仓库,并使用cmake进行编译得到可执行工具。


3.原始模型目录转为GGUF
DeepSeek-R1-Distill-Qwen-1.5B在Hugging Face上提供的是原始模型目录,其中包含model.safetensors权重文件以及分词器和配置文件。

为了能够在llama.cpp中使用,我使用llama.cpp仓库中的convert_hf_to_gguf.py脚本将其转换为GGUF格式,得到deepseek-r1-distill-qwen-1.5b-bf16.gguf文件。

4.模型量化
在得到GGUF文件之后,我进一步使用llama.cpp中的llama-quantize工具进行模型量化,将bf16版本量化为Q4_K_M格式,用于降低模型的内存占用和推理开销,使其能够在当前服务器配置下运行,量化后的模型文件为deepseek-r1-distill-qwen-1.5b-q4_k_m.gguf。

5.GGUF可用性测试
在完成模型转换和量化之后,我先使用llama.cpp的llama-cli工具对模型进行推理测试,确认了模型可以正常输出,说明以上的模型转换和量化流程是正确的。

然而,在测试过程中我发现,DeepSeek-R1-Distill-Qwen-1.5B存在一个不适合作为ActionParser模型的问题。ActionParser的职责是将自由文本快速转换为结构化行为描述,这个过程应该是直接且高效的,但该DeepSeek-R1模型在推理过程中包含thinking阶段,会先输出一段思考过程,再给出最终答案,这不仅带来了冗余输出,也明显增加了推理时间。
基于这一观察,我认为该模型实际上并不适合作为ActionParser使用。随后我重新进行了调研,选择了Qwen2.5-1.5B-Instruct模型作为替代方案。该模型在Hugging Face上同时提供了原始模型目录和GGUF文件,因此在当前阶段可以直接使用其量化后的GGUF文件进行部署,而后续微调可以基于原始模型目录进行。
6.FastAPI的构建和部署
在确定了使用的GGUF模型后,我需要将模型封装为可通过HTTP调用的服务。我使用AI辅助编程完成了服务器上的FastAPI服务的基础代码,使其能够通过llama-cpp-python加载服务器上的GGUF模型并提供推理接口。
具体提问如下:
现在服务器的/storyverse/actionparser目录是ActionParser服务的工作目录,actionparser/下有models/和app/两个目录,分别用于放置模型GGUF文件和FastAPI项目源代码,现阶段使用的模型文件路径是/storyverse/actionparser/models/Qwen2.5-1.5B-Instruct_original/qwen2.5-1.5b-instruct-q4_k_m.gguf。app/下的源代码有待完成,请你完成这个FastAPI项目的代码,要求使用llama-cpp-python运行模型并提供HTTP请求接口。
AI回答如下:
可以,下面直接给你一套**可落地的 FastAPI 项目代码**。
这个版本的定位是:
* 在模型部署服务器上运行
* 用 `llama-cpp-python` 加载 GGUF
* 对外提供 HTTP 接口
按你现在的目录,建议这样放:
```text
/storyverse/actionparser/app/
main.py
schemas.py
service.py
```
---
## 1. `schemas.py`
```python
from pydantic import BaseModel, Field
class GenerateRequest(BaseModel):
prompt: str = Field(..., description="完整提示词")
max_tokens: int = Field(256, ge=1, le=1024)
temperature: float = Field(0.0, ge=0.0, le=2.0)
top_p: float = Field(0.9, ge=0.0, le=1.0)
stop: list[str] | None = Field(default=None, description="可选停止词")
class GenerateResponse(BaseModel):
text: str
finish_reason: str | None = None
class HealthResponse(BaseModel):
status: str
model_path: str
```
---
## 2. `service.py`
```python
import os
import threading
from pathlib import Path
from llama_cpp import Llama
MODEL_PATH = os.getenv(
"ACTIONPARSER_MODEL_PATH",
"/storyverse/actionparser/models/Qwen2.5-1.5B-Instruct original/qwen2.5-1.5b-instruct-q4_k_m.gguf",
)
N_CTX = int(os.getenv("ACTIONPARSER_N_CTX", "4096"))
N_THREADS = int(os.getenv("ACTIONPARSER_N_THREADS", "2"))
N_GPU_LAYERS = int(os.getenv("ACTIONPARSER_N_GPU_LAYERS", "0"))
VERBOSE = os.getenv("ACTIONPARSER_VERBOSE", "false").lower() == "true"
class ActionParserModelService:
def __init__(self) -> None:
self._lock = threading.Lock()
self._llm: Llama | None = None
@property
def model_path(self) -> str:
return MODEL_PATH
def load(self) -> None:
model_file = Path(MODEL_PATH)
if not model_file.exists():
raise FileNotFoundError(f"GGUF model not found: {MODEL_PATH}")
self._llm = Llama(
model_path=str(model_file),
n_ctx=N_CTX,
n_threads=N_THREADS,
n_gpu_layers=N_GPU_LAYERS,
verbose=VERBOSE,
)
def generate(
self,
prompt: str,
max_tokens: int = 256,
temperature: float = 0.0,
top_p: float = 0.9,
stop: list[str] | None = None,
) -> dict:
if self._llm is None:
raise RuntimeError("Model is not loaded")
with self._lock:
result = self._llm(
prompt,
max_tokens=max_tokens,
temperature=temperature,
top_p=top_p,
stop=stop,
echo=False,
)
choice = result["choices"][0]
return {
"text": choice["text"],
"finish_reason": choice.get("finish_reason"),
}
model_service = ActionParserModelService()
```
---
## 3. `main.py`
```python
from contextlib import asynccontextmanager
from fastapi import FastAPI, HTTPException
from schemas import GenerateRequest, GenerateResponse, HealthResponse
from service import model_service
@asynccontextmanager
async def lifespan(app: FastAPI):
model_service.load()
yield
app = FastAPI(
title="StoryVerse ActionParser Service",
version="1.0.0",
lifespan=lifespan,
)
@app.get("/health", response_model=HealthResponse)
def health() -> HealthResponse:
return HealthResponse(
status="ok",
model_path=model_service.model_path,
)
@app.post("/generate", response_model=GenerateResponse)
def generate(req: GenerateRequest) -> GenerateResponse:
try:
result = model_service.generate(
prompt=req.prompt,
max_tokens=req.max_tokens,
temperature=req.temperature,
top_p=req.top_p,
stop=req.stop,
)
return GenerateResponse(
text=result["text"],
finish_reason=result["finish_reason"],
)
except FileNotFoundError as e:
raise HTTPException(status_code=500, detail=str(e))
except Exception as e:
raise HTTPException(status_code=500, detail=f"Generation failed: {e}")
```
在实际使用中,我基本直接采用了该实现方案。这一部分属于标准的模型服务封装逻辑,主要包括模型加载和接口定义,与项目核心业务逻辑耦合较低,因此直接使用不会影响整体架构。同时我根据服务器实际路径对模型文件位置和启动方式做了调整,使其能够在当前环境中运行。
为了让该服务可以在服务器上长期运行,我不能直接使用远程连接的终端命令行启动项目,而是使用systemd进行部署。我编写了actionparser.service配置文件,使用sudo systemctl daemon-reload、sudo systemctl enable actionparser将FastAPI服务注册为系统服务,并通过sudo systemctl start actionparser启动服务。如此一来,服务可以在服务器重启后自动运行,也方便后续进行启停和状态查看。


二、文本嵌入模型
文本嵌入模型部分,我们选择使用bge-base-zh-v1.5模型,用于将小说文本和用户输入转化为向量表示,支持后续RAG检索流程。
在部署方式上,我没有自行编写推理服务,而是选择使用Text Embedding Inference提供的Docker镜像。这是因为嵌入模型本身属于标准服务场景,已有成熟的容器化方案,直接使用可以减少开发成本,同时保证性能和稳定性。
具体实现上,我编写了对应的docker-compose.yml文件,指定模型路径和服务端口,并通过docker compose up -d启动服务。这样嵌入模型同样以独立服务的形式运行,可以通过HTTP接口被模型能力层调用。


通过这种方式,嵌入模型与ActionParser模型在部署形式上保持一致,都是独立服务,符合整体系统架构中模型能力层服务化的设计。
完成这两个模型的部署之后,我就完成了整个StoryVerse项目系统架构的初始搭建和配置,包括:位于本地的Vue前端、Spring Boot业务后端、FastAPI模型能力层编排层代码的初始化;位于服务器的MySQL业务数据库和Qdrant向量知识库的部署;位于服务器的ActionParser模型服务和文本嵌入模型服务的部署。这样一来,整个系统就达成了完备的运行条件,后续可以在此基础上继续实现具体功能逻辑。
更多推荐
所有评论(0)