vLLM 长文本 Embedding 分块处理实战:服务可达数百万 token 输入的嵌入模型
2026/9/7 8:06:21 网站建设 项目流程

vLLM 长文本 Embedding 分块处理实战:服务可达数百万 token 输入的嵌入模型

【免费下载链接】vllmA high-throughput and memory-efficient inference and serving engine for LLMs项目地址: https://gitcode.com/GitHub_Trending/vl/vllm

本文基于 vLLM 仓库中 长文本嵌入示例 展开,讲解如何用--pooler-config中的enable_chunked_processingmax_embed_len参数,让 Embedding 模型(如 multilingual-e5-large、bge、jina-embeddings 等)稳定处理超出模型自身上下文长度的超长文本(最高约 300 万 token)。读完后,你将能够独立部署一个 OpenAI 兼容的长文本 Embedding 服务、理解其分块切分与跨块加权均值聚合的源码级实现,并用仓库自带的测试客户端完成批量、一致性等端到端验证。

1. 功能概述:解决超长文本的 Embedding 难题

普通 Embedding 模型的上下文长度有限(例如 512 或 4096 token),直接输入一篇论文、一整份合同或一个代码库会触发如下报错:

ValueError: This model's maximum position embeddings length is 4096 tokens...

vLLM 的Chunked Processing(分块处理)功能允许在--pooler-config中开启分块,自动把超长输入按模型位置编码上限切分为多个 chunk,每个 chunk 独立走模型推理并使用模型原生 pooling 策略,最后将所有 chunk 的结果按 token 数加权平均(MEAN aggregation)合并为最终嵌入向量。整个过程对调用方完全透明——仍通过 OpenAI 兼容的/v1/embeddings接口访问,且输出维度与标准处理保持一致。

该功能的适用场景(来自 README):

  • 学术论文:含参考文献的完整研究论文;
  • 法律文档:完整合同与法律文本;
  • 书籍:整章乃至小型书籍;
  • 代码仓库:大型代码库与配套文档。

2. 快速开始

示例目录 examples/pooling/embed/openai_embedding_long_text/ 包含三个文件:

文件说明
service.sh启用分块处理的服务器启动脚本
client.py长文本嵌入的综合测试客户端
README.md本功能的说明文档

2.1 启动服务

使用仓库提供的脚本,通过环境变量覆盖模型与上限参数即可启动:

# 基本用法(支持最长约 300 万 token 的输入) ./service.sh # 使用其他模型与更小的上限 MODEL_NAME="jinaai/jina-embeddings-v3" \ MAX_EMBED_LEN=1048576 \ ./service.sh # 面向超长文档 MODEL_NAME="intfloat/multilingual-e5-large" \ MAX_EMBED_LEN=3072000 \ ./service.sh

如果不想走脚本,也可以直接手工执行vllm serve命令(摘自 client.py 头部注释):

# MEAN pooling:每个 chunk 内部用 MEAN,跨块统一 MEAN 聚合 vllm serve intfloat/multilingual-e5-large \ --pooler-config \ '{"pooling_type": "MEAN", "use_activation": true, ' \ '"enable_chunked_processing": true, "max_embed_len": 3072000}' \ --served-model-name multilingual-e5-large \ --trust-remote-code \ --port 31090 \ --api-key your-api-key # CLS pooling:chunk 内部用原生 CLS,跨块仍为 MEAN 聚合 vllm serve BAAI/bge-large-en-v1.5 \ --pooler-config \ '{"pooling_type": "CLS", "use_activation": true, ' \ '"enable_chunked_processing": true, "max_embed_len": 1048576}' \ --served-model-name bge-large-en-v1.5 \ --trust-remote-code \ --port 31090 \ --api-key your-api-key

2.2 核心配置:--pooler-config

分块处理的关键参数都集中在--pooler-config的 JSON 中:

{ "pooling_type": "auto", "use_activation": true, "enable_chunked_processing": true, "max_embed_len": 3072000 }

各字段含义(对应 PoolerConfig 的源码定义):

字段类型/默认值说明
pooling_typeauto/MEAN/CLS/LASTchunk 内部使用的模型原生 pooling 策略;不影响跨块聚合
use_activationbool,默认None(多数模型等效True是否对每个 chunk 的 pooling 输出施加激活函数(如归一化)
enable_chunked_processingbool,默认False开启分块处理:长输入被切分为多个 chunk,分别处理后用加权平均聚合
max_embed_lenint,默认None(回退为max_model_len允许接受的 Embedding 输入最大长度;超过该值会被拒绝

注意(来自 README):pooling_type只决定每个 chunk 内部的 pooling 方式;当输入超过模型原生最大长度时,跨块聚合始终使用 MEAN 策略,与各 chunk 的 token 数成正比加权。

分块处理的行为对照表:

组成行为说明
块内(Within chunks)模型原生 pooling使用为该模型配置的 pooling 策略(MEAN/CLS/LAST)
跨块聚合(Cross-chunk)恒为 MEAN基于各 chunk token 数的加权平均
性能最优所有 chunk 全部参与处理,实现完整语义覆盖

2.3 环境变量

service.sh 支持以下环境变量:

变量默认值说明
MODEL_NAMEintfloat/multilingual-e5-large要使用的 Embedding 模型(支持多种模型)
PORT31090服务端口
GPU_COUNT1使用的 GPU 数量(映射到--tensor-parallel-size
MAX_EMBED_LEN3072000Embedding 输入最大长度(支持超长文档)
POOLING_TYPEauto模型原生 pooling 类型:autoMEANCLSLAST(仅影响块内 pooling,不影响跨块聚合)
API_KEYEMPTY(脚本内为your-api-key鉴权 API key

3. 工作原理:从请求进入到向量输出的完整链路

3.1 输入校验:max_embed_len放宽长度上限

README 将工作机制总结为五步:

  1. 增强的输入校验max_embed_len允许接受长于max_model_len的输入,无需额外环境变量(不再需要VLLM_ALLOW_LONG_MAX_MODEL_LEN);
  2. 智能分块:基于模型位置编码上限(max_position_embeddings)切分文本,保持语义完整性;
  3. 统一处理:所有 chunk 分别走模型推理,使用其配置的 pooling 策略;
  4. MEAN 聚合:输入超过模型原生长度时,按 chunk token 数加权平均合并结果;
  5. 一致输出:最终嵌入向量的维度与标准处理完全相同。

具体到输入长度判定:

  • max_embed_len以内:输入被接受并正常处理(最高 3M+ token);
  • 超过max_position_embeddings:自动触发的分块处理;
  • 超过max_embed_len:输入被拒绝并返回明确的错误信息;
  • 无需环境变量:不再依赖VLLM_ALLOW_LONG_MAX_MODEL_LEN

从源码结构看,这一放宽发生在请求的分词参数构造阶段:EmbeddingTokenizeParamsMixin.build_tok_params 中,一旦enable_chunked_processingTrue,就把max_total_tokens置为None,即分词阶段不再对输入总长度设限:

pooler_config = model_config.pooler_config if pooler_config is not None: if pooler_config.enable_chunked_processing: max_total_tokens = None # 分块模式:不限制输入总长度 else: max_embed_len = pooler_config.max_embed_len or default_max_total_tokens max_output_tokens = default_max_total_tokens - max_embed_len

而在非分块模式下,max_embed_len仍会参与校验:max_output_tokens = max_model_len - max_embed_len,超过该上限的请求在分词阶段即被拦截,并抛出 PoolerConfig.max_embed_len 文档所描述的"maximum embedding input length"错误。

3.2 分块切分:按max_model_len逐段展开

在线服务的预处理阶段,ServingEmbedding._preprocessing 在常规预处理完成后调用 EmbedIOProcessor.maybe_pre_process_chunked 完成切分:

max_model_len = self.model_config.max_model_len for chunk_idx, chunk_tokens in enumerate(chunk_list(prompt_token_ids, max_model_len)): chunked_engine_inputs.append(...) prompt_request_ids.append( f"{request_id}-prompt-{prompt_idx}-chunk-{chunk_idx}" )

两个实现要点值得注意:

  1. chunk 大小即max_model_len:一条 150000 token 的输入在max_model_len=4096时会被切为 37 个 chunk,与 README 给出的调试日志一致:

    INFO: Input length 150000 exceeds max_position_embeddings 4096, will use chunked processing INFO: Split input of 150000 tokens into 37 chunks (max_chunk_size: 4096)
  2. 唯一的 chunk 请求 ID:每个 chunk 的请求 ID 采用{request_id}-prompt-{prompt_idx}-chunk-{chunk_idx}格式,prompt_idx区分同一批请求中的不同原文。这修复了批量处理中多条长文本同时分块时的 chunk ID 冲突问题——client.py 中专门用test_multiple_long_texts_batch验证了这一点。

另外该功能明确要求输入为prompt_token_ids形式:io_processor.py 对EmbedsPromptEncoderDecoderInput直接抛出NotImplementedError,即分块处理目前面向纯文本 token 输入。

3.3 跨块聚合:在线加权均值

每个 chunk 独立跑完模型并使用其原生 pooling(MEAN/CLS/LAST)+ 激活后,io_processor.py 的_post_process_chunked在线(online)方式累加加权向量,避免一次性驻留全部 chunk 结果,从而降低内存占用:

# MEAN pooling with online weighted averaging weight = len(result.prompt_token_ids) # 该 chunk 的 token 数即权重 embedding_data = result.outputs.data weighted_embedding = embedding_data.to(dtype=torch.float32) * weight if aggregator.weighted_sum is None: # 第一个 chunk aggregator.weighted_sum = weighted_embedding else: # 后续 chunk 累加 aggregator.weighted_sum += weighted_embedding aggregator.total_weight += weight

最终对每条原始 prompt 求均值:

final_embedding = weighted_sum / total_weight

从源码结构看,聚合按prompt_index分组进行(io_processor.py L182-L216),因此同一批请求中短文本、长文本、多条长文本可以混排提交,彼此不会互相污染;聚合结果随后被包装回PoolingRequestOutput,对外表现为一条普通的 Embedding 响应。

3.4 性能特征

README 给出的性能特征总结:

方面行为性能
块内处理所有 chunk 均用原生 pooling 处理与输入长度线性相关
跨块聚合MEAN 加权平均开销极小
内存占用与 chunk 数量成正比(在线聚合进一步降低峰值)中等,可扩展
语义质量完整覆盖文本全部内容对长文档最优

需要合理预期:超长文本耗时更长是正常现象,因为每个 chunk 都是一次独立推理调用。

4. 服务脚本细节:pooling 类型的自动识别

service.sh 除了启动服务,还内置了一个get_optimal_pooling_type函数:当POOLING_TYPE=auto时,按模型名启发式选择块内 pooling 类型,匹配规则如下(service.sh L33-L58):

模型名特征自动选用的块内 pooling
包含e5-multilingual-e5MEAN(E5 系列原生 pooling)
包含bge-CLS(BGE 系列原生 pooling)
包含gte-LAST(GTE 系列原生 pooling)
sentence-t5st5MEAN(Sentence-T5 原生 pooling)
包含jina-embeddingsMEAN(Jina 原生 pooling)
Qwen*EmbeddingLAST(Qwen-Embedding 原生 pooling)
其他未知模型MEAN(默认)

最终组装的 pooler 配置 JSON 由脚本动态拼接(service.sh L99),并以如下参数启动服务器(service.sh L102-L110):

vllm serve "$MODEL_NAME" \ --tensor-parallel-size "$GPU_COUNT" \ --enforce-eager \ --pooler-config "$POOLER_CONFIG" \ --served-model-name "${MODEL_CODE}" \ --api-key "$API_KEY" \ --trust-remote-code \ --port "$PORT" \ --host 0.0.0.0

几点脚本行为说明:

  • --served-model-name使用的是短代号(MODEL_CODE,默认multilingual-e5-large),客户端请求model字段时必须与之匹配;
  • 脚本会校验nvidia-smi报告的 GPU 数量,若GPU_COUNT超出可用数量会自动下调并告警;
  • 注意 service.sh L24 中CUDA_VISIBLE_DEVICES=2,3,4,5是示例作者的本地设置,移植到自己环境时应改为实际可用的 GPU 序号。

5. 客户端测试:四组用例覆盖典型场景

安装依赖后运行测试客户端(pip install openai requests):

python client.py

client.py 通过 OpenAI SDK 访问服务(base_url=http://localhost:31090/v1),核心调用是:

client = OpenAI(api_key=API_KEY, base_url=BASE_URL) response = client.embeddings.create( input=long_text, model="multilingual-e5-large", encoding_format="float" ) embedding = response.data[0].embedding

四组测试分别验证:

  1. 不同长度test_embedding_with_different_lengths):短文本(基线)、中等文本(单 chunk)、长文本(约 2 chunks)、超长文本(3+ chunks),打印嵌入维度、耗时与期望 chunk 数;
  2. 批量混合长度test_batch_embedding):一次请求中混合短文本与需分块的长文本,验证每条输入都能得到对应嵌入;
  3. 多条长文本同批test_multiple_long_texts_batch):同一批次提交 3 条内容不同的长文本并穿插短文本,通过两两余弦相似度验证 chunk 聚合没有张冠李戴(相似度低于 0.9 视为区分良好),这是对 chunk ID 冲突修复的回归验证;
  4. 一致性test_embedding_consistency):同一长文本重复请求 3 次,比对余弦相似度(期望约 1.0,超过 0.999 即认为一致性达标)。

覆盖的测试场景对照(摘自 README):短文本正常处理、中等文本单 chunk、长文本多 chunk 聚合、超长文本多 chunk、极端长文本(10 万+ token)文档级处理、混合长度批量、跨运行可复现。

6. 故障排查

现象错误信息解决方案
分块处理未生效ValueError: This model's maximum position embeddings length is 4096 tokens...在 pooler config 中确认enable_chunked_processing: true
输入超过max_embed_lenValueError: This model's maximum embedding input length is 3072000 tokens...调大 pooler config 中的max_embed_len,或缩短输入
显存不足RuntimeError: CUDA out of memory通过调整模型的max_position_embeddings减小 chunk 大小,或增加 GPU 数量(--tensor-parallel-size
处理变慢—(预期行为)超长文本需多次推理调用,耗时自然更长

调试时可关注服务端日志中的分块活动信息(见 README 示例):

INFO: Input length 150000 exceeds max_position_embeddings 4096, will use chunked processing INFO: Split input of 150000 tokens into 37 chunks (max_chunk_size: 4096)

7.max_embed_len增强特性与扩展指南

相比早期依赖VLLM_ALLOW_LONG_MAX_MODEL_LEN环境变量的方案,max_embed_len参数带来五点改进(摘自 README):

  • 配置简化:无需VLLM_ALLOW_LONG_MAX_MODEL_LEN环境变量;
  • 灵活的输入校验:可接受长于max_model_len、至多max_embed_len的输入;
  • 极端长度支持:可处理数百万 token 的文档;
  • 清晰的错误信息:输入超限时有明确的反馈;
  • 向后兼容:既有配置继续有效(max_embed_len默认None时回退为max_model_len,见 PoolerConfig)。

若要为其他 Embedding 模型扩展分块处理支持,仓库建议的路径是:

  1. 检查该模型与 pooling 架构的兼容性;
  2. 用多种文本长度进行测试;
  3. 与单 chunk 处理结果对比,验证嵌入质量;
  4. 提交附带测试用例与文档更新的 PR。

8. 参考路径速查

内容路径
功能说明文档examples/pooling/embed/openai_embedding_long_text/README.md
服务启动脚本examples/pooling/embed/openai_embedding_long_text/service.sh
测试客户端examples/pooling/embed/openai_embedding_long_text/client.py
PoolerConfig(含enable_chunked_processing/max_embed_len定义)vllm/config/pooler.py
分块切分与在线加权聚合实现vllm/entrypoints/pooling/embed/io_processor.py
在线服务预处理挂接点vllm/entrypoints/pooling/embed/serving.py
分词阶段的长度校验逻辑vllm/entrypoints/pooling/base/protocol.py

适用前提与限制小结:分块处理面向纯 token 文本输入(不支持EmbedsPrompt/EncoderDecoderInput);跨块聚合固定为按 token 数加权的 MEAN;pooling_type只作用于 chunk 内部;输入长度上限由max_embed_len显式约束。理解了这几条,你就能安全地把该方案用于学术文献、法律合同、书籍与代码库级别的向量化检索场景。

【免费下载链接】vllmA high-throughput and memory-efficient inference and serving engine for LLMs项目地址: https://gitcode.com/GitHub_Trending/vl/vllm

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询