本文是《基于Redis 8实现RAG工程实战》系列的第二篇。在前文搭建了整体方案后,本篇聚焦于一个核心且实际的工程挑战:在无GPU的CPU机器上,成功运行并落地Qwen3-Embedding-0.6B模型,完成文本向量化,并将生成的向量高效写入Redis 8的向量索引中。
环境:两台8核16G Intel服务器;模型:Qwen/Qwen3-Embedding-0.6B;Python版本:3.11.6。
1. 模型选择:在CPU上优先追求稳定
在现有硬件条件下,于服务器上运行Embedding模型的首要目标应是“稳定运行”,而非盲目追求“极限吞吐”。我们之所以选择Qwen3-Embedding-0.6B,主要是因为它在同规格的小尺寸模型中,对中文文本的语义编码能力表现优异。基于这个前提,制定了以下基本策略:
- 批次大小(batch_size)不宜过大:宁可增加模型推理的轮次,也要避免单次负载过高导致内存溢出或响应延迟。
- 使用结构规整的语料:选用古诗词作为向量化的示例语料,可以省去复杂的数据收集与清洗步骤,同时减少因文本长度不一带来的Padding开销和Token浪费。
- 线程数设置保持保守:根据CPU核心数合理设置,避免因过度并行导致进程不稳定。
后续实践发现,仅使用诗词进行向量化在多样化的检索场景下召回效果可能有限,这点我们会在后续文章中探讨。
2. 模型获取:ModelScope优先,HuggingFace作为备选
在国内的网络环境下,直接从HuggingFace下载大型模型文件有时会遇到连接不稳定的问题。因此,我们的策略是优先从ModelScope下载,如果失败再回退到HuggingFace(或其镜像站)。
可以通过设置环境变量来使用HuggingFace镜像站:
import os
os.environ.setdefault("HF_ENDPOINT", "https://hf-mirror.com")
核心的模型加载代码逻辑如下,它实现了上述的下载策略:
# 设置HuggingFace镜像站(国内环境)
hf_endpoint = os.environ.get("HF_ENDPOINT", "https://hf-mirror.com")
if"huggingface.co"notin hf_endpoint:
os.environ["HF_ENDPOINT"] = hf_endpoint
self.logger.info(f"使用HuggingFace镜像: {hf_endpoint}")
try:
# 使用transformers的AutoModel加载Qwen3-Embedding
self.logger.info("使用transformers AutoModel加载模型")
# 先决定从哪里获取模型:ModelScope 本地目录 或 HuggingFace 模型名
model_ref = model_name
if use_modelscope:
try:
from modelscope import snapshot_download
self.logger.info("从ModelScope下载模型...")
model_ref = snapshot_download(
model_name, cache_dir=cache_dir, revision="master"
)
self.logger.info(f"模型下载完成: {model_ref}")
except Exception as e:
self.logger.warning(f"ModelScope下载失败: {e}")
self.logger.info(f"尝试从HuggingFace镜像下载 ({hf_endpoint})...")
model_ref = model_name
else:
self.logger.info(f"从HuggingFace镜像下载模型 ({hf_endpoint})...")
# 统一加载(model_ref 可能是本地路径,也可能是 HuggingFace repo id)
self.tokenizer = AutoTokenizer.from_pretrained(
model_ref,
cache_dir=cache_dir,
trust_remote_code=trust_remote_code,
)
self.model = AutoModel.from_pretrained(
model_ref,
cache_dir=cache_dir,
trust_remote_code=trust_remote_code,
).to(self.device)
3. CPU侧关键参数:线程数、批次大小与最大长度
在CPU上运行Embedding模型,有三个参数对性能和稳定性至关重要:
num_threads:线程数。我们的服务器是8核,因此设置为8,让PyTorch能充分利用多核并行计算。
batch_size:每批次处理的文本数量。这是平衡速度和内存占用的关键。
max_length:最大Token长度。对于诗词这类短文本,512通常足够;对于长文档,则需要结合分块(Chunk)策略。
长文本如何进行有效的分块是一个复杂的课题,涉及语义、结构、长度等多重考量。
以下是一个配置示例(YAML格式):
model:
name: "Qwen/Qwen3-Embedding-0.6B"
cache_dir: "./models"
device: "cpu"
num_threads: 8
use_modelscope: true
trust_remote_code: true
vectorization:
batch_size: 2
max_length: 512
normalize: true
query_instruction: "query: "
在代码中,通过torch.set_num_threads(num_threads)设置线程数;在推理时,严格按batch_size分批送入模型,Tokenizer使用max_length参数进行截断。
4. 向量化主流程
向量化遵循检索工程中常见的流水线:
- 文本清洗:统一空白符(例如将连续的空白压缩为单个空格),减少因格式差异导致的向量波动。
- 添加查询前缀:对于查询(Query)语句,添加前缀
“query: ”;对于待索引的文档(此处是诗词),则直接使用清洗后的文本。这是Qwen3-Embedding模型推荐的用法,以区分两种模式。
- 均值池化(Mean Pooling):利用模型输出的
attention_mask,对所有有效Token的向量表示进行平均,从而得到一个固定维度的句向量。
- L2归一化(Normalization):对池化后的向量进行L2归一化。这一步对于使用余弦相似度进行检索至关重要,归一化后,向量点积就等于余弦相似度。
核心向量化代码片段:
# 获取模型输出
model_output = self.model(**encoded_input)
# 平均池化
embeddings = self._mean_pooling(
model_output, encoded_input["attention_mask"]
)
# 对原始向量进行归一化处理
if normalize:
embeddings = torch.nn.functional.normalize(embeddings, p=2, dim=1)
batch_vectors = embeddings.cpu().numpy()
5. 向量写入Redis
向量化得到的vectors通常是一个np.ndarray,形状为(N, dim)。写入Redis向量数据库时,关键步骤是将每条向量转换为float32类型,并序列化为bytes。
def add_vectors(self, texts, vectors, metadata=None, batch_size=100):
docs = []
for i, (text, vec) in enumerate(zip(texts, vectors)):
docs.append({
"text": text,
"vector": vec.astype("float32").tobytes(),
"metadata": json.dumps(metadata[i], ensure_ascii=False) if metadata else"",
})
for start in range(0, len(docs), batch_size):
self.vl_index.load(docs[start : start + batch_size])
关于Redis向量索引(vl_index)的创建、查询等详细操作,将在后续文章深入讲解。
6. 进阶优化:尝试IPEX(可选)
如果你的生产环境是Intel平台,并且面临着明显的性能瓶颈,可以尝试使用intel-extension-for-pytorch(IPEX)。它可能通过对部分算子的深度优化带来性能提升,但是否值得引入生产环境,务必以实际压测结果为准。
- 适用场景:CPU需要长时间、高负载地运行Embedding任务,且吞吐量是核心瓶颈。
- 重要建议:IPEX的版本需要与PyTorch主版本严格对齐,否则极易出现“安装成功但运行失败”的问题。
本项目相关依赖版本如下:
transformers>=4.40.0,<5.0.0
# PyTorch CPU - 升级到2.10.0以支持Python 3.11和3.12
torch==2.10.0
modelscope>=1.12.0
sentencepiece>=0.1.99
onnxruntime>=1.23.0
accelerate>=1.10.0
# 最新版本2.8.0,针对Qwen3模型有针对性优化
intel-extension-for-pytorch==2.8.0
总结来说,在CPU服务器上部署轻量级Embedding模型进行RAG向量化是完全可行的。关键在于根据硬件资源审慎配置参数,并选择稳定的模型获取与推理管道。本文的实践为资源受限环境下的RAG系统落地提供了一个具体参考。更多关于检索优化、系统调优的内容,欢迎持续关注我们在云栈社区的分享。