Mac 使用transformers 库运行本地大模型使用全记录
下载大模型到本地
//创建本地运行虚拟环境
conda create --name vllm_dev python=3.12
conda activate vllm_dev
//刚开始想用 vllm 运行模型,但是发现这个模型很小,改用transformers库直接运行,更省资源
//huggingface下载Qwen3-Embedding-0.6B模型
pip install huggingface_hub
export HF_ENDPOINT=https://hf-mirror.com
hf download Qwen/Qwen3-Embedding-0.6B
>Fetching 12 files: 100%|████████████████████████████████████████████| 12/12 [01:14<00:00, 6.20s/it]
Download complete: : 1.21GB [01:14, 19.4MB/s] /Users/t-mac/.cache/huggingface/hub/models--Qwen--Qwen3-Embedding-0.6B/snapshots/c54f2e6e80b2d7b7de06f51cec4959f6b3e03418

本地运行Qwen3-Embedding-0.6B模型
//安装相关包
pip install torch transformers fastapi uvicorn tiktoken sentencepiece
//包解释
torch —— 模型推理
transformers —— 加载 Qwen3 模型
fastapi —— API 服务
uvicorn —— 运行服务器
tiktoken —— Qwen3 分词器必须
sentencepiece —— 兼容依赖
开启本地服务
emb_serve.py
启动服务:
❯ python emb_server.py
🚀 当前计算设备: MPS
📥 正在加载模型: /Users/t-mac/.cache/huggingface/hub/models--Qwen--Qwen3-Embedding-0.6B/snapshots/c54f2e6e80b2d7b7de06f51cec4959f6b3e03418 ...
`torch_dtype` is deprecated! Use `dtype` instead!
Loading weights: 100%|███████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 310/310 [00:00<00:00, 359.07it/s]
✅ 模型加载完成!服务准备就绪。
INFO: Started server process [7296]
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO: Uvicorn running on http://0.0.0.0:8001 (Press CTRL+C to quit)
INFO: 127.0.0.1:53982 - "GET /docs HTTP/1.1" 200 OK
INFO: 127.0.0.1:53983 - "GET /redoc HTTP/1.1" 200 OK
INFO: 127.0.0.1:54022 - "GET /openapi.json HTTP/1.1" 200 OK
INFO: 127.0.0.1:54121 - "POST /v1/embeddings HTTP/1.1" 200 OK
emb_server.py
import torch
import uvicorn
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from typing import List, Union, Optional
from transformers import AutoTokenizer, AutoModel
# ==========================================
# 1. 配置区域 (请在此处修改本地模型路径)
# ==========================================
# 【关键】如果你的模型在本地,请直接填入绝对路径
# 例如: "/Users/t-mac/.cache/huggingface/hub/models--Qwen--Qwen3-Embedding-0.6B/snapshots/..."
# 如果留空或者填英文名,它会尝试去联网下载
MODEL_PATH = "/Users/t-mac/.cache/huggingface/hub/models--Qwen--Qwen3-Embedding-0.6B/snapshots/c54f2e6e80b2d7b7de06f51cec4959f6b3e03418"
# 设备检测:优先使用 Mac 的 MPS (Metal Performance Shaders)
DEVICE = "mps" if torch.backends.mps.is_available() else "cpu"
print(f"🚀 当前计算设备: {DEVICE.upper()}")
# ==========================================
# 2. 初始化 FastAPI 应用
# ==========================================
app = FastAPI(
title="Qwen3-Embedding-0.6B API",
description="本地部署的 Embedding 服务,运行于 Mac M-Series 芯片",
swagger_js_url="https://cdn.bootcdn.net/ajax/libs/swagger-ui/5.11.0/swagger-ui-bundle.min.js",
swagger_css_url="https://cdn.bootcdn.net/ajax/libs/swagger-ui/5.11.0/swagger-ui.min.css",
version="1.0.0"
)
# ==========================================
# 3. 加载模型 (全局单例)
# ==========================================
print(f"📥 正在加载模型: {MODEL_PATH} ...")
try:
# 加载分词器
tokenizer = AutoTokenizer.from_pretrained(
MODEL_PATH,
trust_remote_code=True,
local_files_only=True # 强制只读取本地,防止联网超时
)
# 加载模型
# torch_dtype=torch.float16 适合 Mac M 系列芯片,显存占用更低
model = AutoModel.from_pretrained(
MODEL_PATH,
trust_remote_code=True,
local_files_only=True,
torch_dtype=torch.float16
).to(DEVICE)
# 设置为评估模式
model.eval()
print("✅ 模型加载完成!服务准备就绪。")
except Exception as e:
print(f"❌ 模型加载失败: {e}")
print("💡 请检查 MODEL_PATH 路径是否正确,以及是否安装了 transformers 和 torch。")
# 即使加载失败也不退出进程,方便你调试,但接口会不可用
model = None
tokenizer = None
# ==========================================
# 4. 数据模型定义 (Pydantic)
# ==========================================
class EmbeddingRequest(BaseModel):
input: Union[str, List[str]] # 支持单个字符串或字符串列表
model: Optional[str] = None # 兼容 OpenAI 格式,虽然这里不用
class EmbeddingData(BaseModel):
object: str = "embedding"
embedding: List[float]
index: int
class EmbeddingResponse(BaseModel):
object: str = "list"
data: List[EmbeddingData]
model: str
# ==========================================
# 5. 核心接口
# ==========================================
@app.post("/v1/embeddings", response_model=EmbeddingResponse)
async def get_embeddings(request: EmbeddingRequest):
# 检查模型是否加载成功
if model is None:
raise HTTPException(status_code=503, detail="模型尚未加载完成,请稍后再试。")
# 1. 预处理输入
inputs = request.input
if isinstance(inputs, str):
inputs = [inputs]
if not inputs:
raise HTTPException(status_code=400, detail="输入不能为空")
# 2. 推理过程 (使用 torch.no_grad() 节省显存)
with torch.no_grad():
# 分词
# max_length=8192 是 Qwen3 的上下文窗口大小
encoded_input = tokenizer(
inputs,
padding=True,
truncation=True,
return_tensors='pt',
max_length=8192
).to(DEVICE)
# 模型前向传播
# 输出包含 last_hidden_state (所有token的向量)
model_output = model(**encoded_input)
# 3. 平均池化
# 这一步是为了把一句话里所有字的向量聚合成一个代表整句话的向量
input_mask_expanded = encoded_input['attention_mask'].unsqueeze(-1).expand(
model_output.last_hidden_state.size()).float()
sentence_embeddings = torch.sum(model_output.last_hidden_state * input_mask_expanded, 1) / torch.clamp(
input_mask_expanded.sum(1), min=1e-9)
# 4. 归一化
# Embedding 向量通常需要 L2 归一化,这样计算余弦相似度时才准确
sentence_embeddings = torch.nn.functional.normalize(sentence_embeddings, p=2, dim=1)
# 5. 转回 CPU 并转为列表
# MPS 张量不能直接转 numpy,必须先 .cpu()
embeddings = sentence_embeddings.cpu().numpy().tolist()
# 6. 构造返回结果
data = [
EmbeddingData(embedding=emb, index=i)
for i, emb in enumerate(embeddings)
]
return EmbeddingResponse(data=data, model="Qwen3-Embedding-0.6B")
@app.get("/")
def read_root():
return {
"status": "running",
"device": DEVICE,
"message": "Qwen3-Embedding-0.6B API 已启动",
"docs": "/docs"
}
# ==========================================
# 6. 启动入口
# ==========================================
if __name__ == "__main__":
# host="0.0.0.0" 允许局域网访问
# port=8001 指定端口
uvicorn.run(app, host="0.0.0.0", port=8001)
也可以不用启动服务,直接访问本地的 Embedding
from langchain_huggingface import HuggingFaceEmbeddings
MODEL_PATH = "/Users/t-mac/.cache/huggingface/hub/models--Qwen--Qwen3-Embedding-0.6B/snapshots/c54f2e6e80b2d7b7de06f51cec4959f6b3e03418"
embeddings = HuggingFaceEmbeddings(
model_name=MODEL_PATH,
model_kwargs={"device": "mps"},
encode_kwargs={"normalize_embeddings": True}
)
本地向量化代码测试
# 本地Embedding Models测试
import requests
import json
# 1. 定义请求地址 (确保端口是 8001)
url = "http://127.0.0.1:8001/v1/embeddings"
# 2. 准备数据
# 注意:这里兼容 OpenAI 格式,所以加了 "model" 字段,虽然服务端可能不校验
payload = {
"model": "Qwen3-Embedding-0.6B",
"input": "你好,这是一个测试文本。"
}
headers = {
"Content-Type": "application/json"
}
# 3. 发送 POST 请求
try:
response = requests.post(url, headers=headers, json=payload)
# 检查状态码
if response.status_code == 200:
data = response.json()
# 提取向量
embedding_vector = data['data'][0]['embedding']
print(f"✅ 请求成功!")
print(f"模型: {data.get('model')}")
print(f"向量维度: {len(embedding_vector)}")
print(f"向量前10个数值: {embedding_vector[:10]}")
else:
print(f"❌ 请求失败: {response.status_code}")
print(response.text)
except requests.exceptions.ConnectionError:
print("❌ 连接错误:无法连接到 127.0.0.1:8001")
print("请确保你的 Mac 本地终端中正在运行 app.py 且没有报错。")
运行结果:
✅ 请求成功!
模型: Qwen3-Embedding-0.6B
向量维度: 1024
向量前10个数值: [-0.02349964901804924, -0.1068049818277359, -0.014799184165894985, -0.05713353306055069, 0.016370529308915138, 0.08789951354265213, -0.024308187887072563, 0.0869838148355484, -0.07337475568056107, 0.04606372490525246]
在 langchain 中使用
import os
from langchain_openai import OpenAIEmbeddings
API_BASE_URL = "http://127.0.0.1:8001/v1"
embeddings = OpenAIEmbeddings(
model="Qwen3-Embedding-0.6B", # 这里填你在 FastAPI 中定义的 model 名称
base_url=API_BASE_URL,
api_key="sk-no-key-required",
check_embedding_ctx_length=False # 关闭长度检查,因为本地模型可能有自己的截断逻辑
)
try:
text = "你好,这是一个测试文本。"
print(f"正在对文本进行向量化: '{text}' ...")
# 调用接口获取向量
query_result = embeddings.embed_query(text)
print(f"✅ 成功!")
print(f"向量维度: {len(query_result)}")
print(f"前5个数值: {query_result[:5]}")
except Exception as e:
print(f"❌ 发生错误: {e}")
print("提示:请检查 Mac 终端上的 FastAPI 服务是否正在运行。")
//运行结果
正在对文本进行向量化: '你好,这是一个测试文本。' ...
✅ 成功!
向量维度: 1024
前5个数值: [-0.02349964901804924, -0.1068049818277359, -0.014799184165894985, -0.05713353306055069, 0.016370529308915138]