在本地部署大语言模型后,若想提供 Web 服务供前端或其他系统调用,FastAPI 是一个轻量、高性能且易用的选择 。本文将展示如何将已缓存的Qwen1.5-0.5B 模型 (通过 ModelScope 下载)封装为支持 流式(Streaming)与非流式(Batch) 两种模式的 API。
1. 安装依赖 使用阿里云镜像加速安装所需库(避免清华源限流):
1 pip install -i https://mirrors.aliyun.com/pypi/simple/ fastapi uvicorn sse-starlette
2. 编写 main.py 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 from fastapi import FastAPI, Query, Body from fastapi.responses import StreamingResponse from sse_starlette.sse import EventSourceResponse from modelscope import AutoTokenizer, AutoModelForCausalLM import torch app = FastAPI( title="Qwen1.5-0.5B API", description="本地 CPU 部署的轻量大模型接口,支持流式与非流式输出" ) print("正在加载 tokenizer 和模型(CPU 模式)...") model_id = "qwen/Qwen1.5-0.5B" tokenizer = AutoTokenizer.from_pretrained(model_id, trust_remote_code=True) model = AutoModelForCausalLM.from_pretrained( model_id, device_map="cpu", trust_remote_code=True, tie_word_embeddings=False ) model.eval() print("✅ 模型加载完成!") def generate_stream(prompt: str, max_new_tokens: int = 128): """流式生成器:逐 token 返回""" inputs = tokenizer(prompt, return_tensors="pt") input_ids = inputs.input_ids attention_mask = inputs.attention_mask generated = input_ids.clone() with torch.no_grad(): for _ in range(max_new_tokens): outputs = model(input_ids=generated, attention_mask=attention_mask) next_token_logits = outputs.logits[:, -1, :] next_token = torch.argmax(next_token_logits, dim=-1).unsqueeze(0) # 遇到结束符则停止 if next_token.item() == tokenizer.eos_token_id: break # 拼接新 token generated = torch.cat([generated, next_token], dim=1) attention_mask = torch.cat([attention_mask, torch.ones((1, 1), dtype=torch.long)], dim=1) # 解码新增部分 new_text = tokenizer.decode([next_token.item()], skip_special_tokens=True) if new_text.strip(): # 过滤空字符 yield new_text @app.post("/generate") async def generate( prompt: str = Body(..., embed=True, description="输入提示文本"), stream: bool = Query(False, description="是否启用流式输出(true/false)"), max_new_tokens: int = Query(128, ge=1, le=512, description="最大生成长度(1~512)") ): """ 调用 Qwen1.5-0.5B 生成文本 - **prompt**: 用户输入(JSON body) - **stream**: 是否流式返回(URL 查询参数) - **max_new_tokens**: 控制生成长度(URL 查询参数) """ if stream: return EventSourceResponse(generate_stream(prompt, max_new_tokens)) else: inputs = tokenizer(prompt, return_tensors="pt") with torch.no_grad(): outputs = model.generate(**inputs, max_new_tokens=max_new_tokens) response = tokenizer.decode(outputs[0], skip_special_tokens=True) # 移除重复的 prompt 前缀 if response.startswith(prompt): response = response[len(prompt):].lstrip() return {"response": response}
3. 启动服务 在终端执行以下命令启动 API 服务:
1 uvicorn main:app --host 0.0.0.0 --port 8000 --reload
–host 0.0.0.0:允许外部访问(如局域网)
–port 8000:监听端口
–reload:开发模式,代码修改自动重启
启动后访问 http://localhost:8000/docs 可查看自动生成的交互式 API 文档(可以访问swagger的网络情况下)。
4. 接口测试 ✅ 一次性输出(默认)
1 2 3 curl -X POST http://localhost:8000/generate \ -H "Content-Type: application/json" \ -d '{"prompt": "你好,请介绍一下你自己。"}'
返回示例:
🌊 流式输出(SSE)
1 2 3 curl -X POST "http://localhost:8000/generate?stream=true" \ -H "Content-Type: application/json" \ -d '{"prompt": "你好,请介绍一下你自己。"}'
返回为逐字流式数据(SSE 格式):
前端可通过 EventSource 或 fetch + ReadableStream 实时接收。