两周前,我们发布了 jina-embeddings-v4 的 GGUF 格式版本,这是一个用于多模态多语言检索的通用 向量模型,包含各种量化版本。我们的动机很简单:作为一个 3.75B 参数的模型,原始 transformer 版本的 jina-embeddings-v4 在我们的 GCP G2 (L4 GPU) API 实例上扩展性不佳,因此我们希望使用这些更小、更快的 GGUF 版本来加速推理。在我们的实验中,我们在转换和运行 GGUF 向量模型时发现了一些有趣的发现。由于大多数 llama.cpp 社区都专注于 大模型,我们认为从 向量模型 提供商的角度分享这一点会很有价值。
特别相关的是,今天的 向量模型 几乎与 大模型 完全相同——例如,jina-embeddings-v4 基于 Qwen2.5-VL-3B-instruct,而 jina-reranker-m0 基于 Qwen2-VL-2B。唯一的真正区别在于输出: 大模型 是生成式的,而 向量模型 和 重排器 是判别式的。这既带来了机遇也带来了挑战:一方面,我们可以利用 llama.cpp 的高效实现(例如 ubatch_size)来服务 向量模型 / 重排器 模型;另一方面,llama.cpp 的 向量模型 实现主要为较旧的仅编码器架构(如基于 RoBERTa 的模型)开发,尚未完全赶上现代仅解码器 向量模型 / 重排器 模型。本文分享了我们在调整现代 向量模型 以使用 GGUF 格式和 llama.cpp 工具(例如 llama-embedding 和 llama-serving)时所学到的知识。
tag基础 GGUF 和量化 GGUF
jina-embeddings-v4 基于 Qwen2.5-VL-3B-instruct,带有三个 LoRA 适配器:retrieval(针对检索任务进行了优化)、text-matching(针对句子相似性任务进行了优化)和 code(针对代码检索任务进行了优化)。它还经过大量训练,用于视觉文档检索和后期交互风格的多向量输出。因此,这里的想法是利用 llama.cpp 现有的 Qwen2.5-VL-3B 图实现,并使用 llama-embedding 进行推理。
但是,我们首先注意到的是 llama.cpp 中 mmproj 或视觉 transformer 实现中的错误行为,这导致对于相同的图像输入,其产生的 向量模型 与 torch 实现的 Qwen2.5-VL-3B 不同。在我们 在我们的 fork 中 修复此问题时,我们决定暂时从 GGUF 版本中排除视觉 tower。您可以在此处找到有关此讨论的更多详细信息。
多向量 向量模型 输出 也不是开箱即用的,但它不像视觉 transformers 那样是一个大问题。多向量输出来自最后一个 transformer 块中训练的 MLP,因此最坏的情况是,我们可以始终将此 MLP 单独导出到 numpy,并在从 llama.cpp 获得 token 级别的 向量模型 后应用它——这就是我们为 jina-reranker-m0-GGUF 所做的事情。 当然,这不是很有效,但它可以在无需修改和重新编译 llama.cpp 的情况下工作。
我们去掉了视觉 transformer 和多向量投影仪,并获得了三个 F16 中的基本 GGUF 模型。
因此,为了完全符合 llama.cpp 现有的 Qwen2.5-VL-3B 图实现,我们去掉了视觉 transformer 和最后一个 transformer 块中的多向量投影仪,并将所有 LoRA 适配器合并回基本语言模型。这为我们提供了三个特定于任务的 v4 模型,每个模型有 3.09B 个参数——低于原始 v4 的 3.75B 个参数:
| HuggingFace Repo | Task |
|---|---|
jinaai/jina-embeddings-v4-text-retrieval-GGUF |
Text retrieval |
jinaai/jina-embeddings-v4-text-code-GGUF |
Code retrieval |
jinaai/jina-embeddings-v4-text-matching-GGUF |
Sentence similarity |
然后,我们使用了 calibration_data_v5_rc.txt(可以在此处找到,并且是 Unsloth 推荐的)来校准所有三个基本 GGUF 模型,并获得了三个 imatrix 文件,然后使用带有 imatrix 的 llama-quantize 将模型从 float16 量化,如下所示:
# build imatrix
llama-imatrix -m jina-embeddings-v4-text-retrieval-F16.gguf -f calibration_data_v5_rc.txt -ngl 99 --no-ppl -o imatrix-retrieval-512.dat
# quantize
./quantize.sh jina-embeddings-v4-text-retrieval-F16.gguf retrieval-i3 imatrix-retrieval-512.dat jinaai/jina-embeddings-v4-text-retrieval-GGUFquantize.sh 脚本如下所示:
#!/bin/bash
F16_MODEL_FILE="$1"
OUTPUT_DIR="$2"
IMATRIX="$3"
HF_REPO="$4"
FILENAME="$(basename "$F16_MODEL_FILE")"
BASE_NAME="${FILENAME%-F16.gguf}"
BASE_NAME="${BASE_NAME%.gguf}"
mkdir -p "$OUTPUT_DIR"
# Array of quantization types
QUANT_TYPES=("IQ1_S" "IQ1_M" "IQ2_XXS" "IQ2_M" "Q2_K" "IQ4_NL" "IQ4_XS" "IQ3_XXS" "IQ3_S" "IQ3_M" "IQ3_XS" "Q3_K_M" "Q4_K_M" "Q5_K_S" "Q5_K_M" "Q6_K" "Q8_0")
for quant_type in "${QUANT_TYPES[@]}"; do
llama-quantize --imatrix "${IMATRIX}" "$F16_MODEL_FILE" "${OUTPUT_DIR}/${BASE_NAME}-${quant_type}.gguf" $quant_type 8
done最终,我们将所有量化上传到了 HuggingFace。
| 量化 | BPW | 文件大小 (GB) |
|---|---|---|
| IQ1_S | 2.04 | 0.73 |
| IQ1_M | 2.19 | 0.79 |
| IQ2_XXS | 2.44 | 0.88 |
| IQ2_M | 2.94 | 1.06 |
| Q2_K | 3.29 | 1.18 |
| IQ3_XXS | 3.31 | 1.19 |
| IQ3_XS | 3.59 | 1.29 |
| IQ3_S | 3.76 | 1.35 |
| IQ3_M | 3.84 | 1.38 |
| Q3_K_M | 4.11 | 1.48 |
| IQ4_NL | 4.72 | 1.69 |
| IQ4_XS | 4.49 | 1.61 |
| Q4_K_M | 4.99 | 1.79 |
| Q5_K_S | 5.61 | 2.02 |
| Q5_K_M | 5.75 | 2.07 |
| Q6_K | 6.56 | 2.36 |
| Q8_0 | 8.50 | 3.05 |
| F16 | 16.00 | 5.75 |
| v3 (Transformers) | 16.00 | 1.10 |
| v4 (Transformers) | 16.00 | 7.40 |

tag使用方法和注意事项
现在我们可以使用 llama-server 和 llama-embedding 来为 GGUF 提供向量模型服务。与 Transformer 库不同,在 Transformer 库中,我们可以灵活地编写自定义的输入预处理代码,而在这里我们必须手动处理这一部分(除非我们想重新编译 llama-server 和 llama-embedding)。具体来说,为了获得与使用 AutoModel.from_pretrained("jinaai/jina-embeddings-v4")... 完全一致的结果,您需要非常小心前缀,并手动将其添加到您的 GGUF 模型输入中。以下是一个参考表:
| 任务 | Transformer 实现中的 prompt_name |
模型的实际输入 |
|---|---|---|
retrieval |
query (默认) |
Query: {original_text} |
retrieval |
passage |
Passage: {original_text} |
text-matching |
query (默认) |
Query: {original_text} |
text-matching |
passage |
Query: {original_text} ⚠️ |
code |
query (默认) |
Query: {original_text} |
code |
passage |
Passage: {original_text} |
有些用户可能会觉得⚠️很奇怪,即在使用原始的 AutoModel.from_pretrained("jinaai/jina-embeddings-v4").... 中的 text-matching 时,prompt_name='passage' 会被覆盖为 "Query: "。但实际上这是有道理的,因为 text-matching 是一项句子相似度任务,没有左右角色之分——输入是对称的。
tag通过 llama-server
安装 llama.cpp 后,您可以运行 llama-server 来将向量模型托管为与 OpenAI API 兼容的 HTTP 服务器。例如,要将 text-matching 与 F16 一起使用,您可以这样做:
llama-server -hf jinaai/jina-embeddings-v4-text-matching-GGUF:F16 --embedding --pooling mean -ub 8192
--pooling mean 是必需的,因为 v4 是均值池化向量模型。
然后通过以下方式发送请求:
curl -X POST "http://127.0.0.1:8080/v1/embeddings" \
-H "Content-Type: application/json" \
-d '{
"input": [
"Query: A beautiful sunset over the beach",
"Query: Un beau coucher de soleil sur la plage",
"Query: 海滩上美丽的日落",
"Query: 浜辺に沈む美しい夕日"
]
}'
当使用 retrieval 和 code 模型时,在输入前添加 Query: 或 Passage:,如下所示:
curl -X POST "http://127.0.0.1:8080/v1/embeddings" \
-H "Content-Type: application/json" \
-d '{
"input": [
"Query: A beautiful sunset over the beach",
"Query: Un beau coucher de soleil sur la plage",
"Passage: 海滩上美丽的日落",
"Passage: 浜辺に沈む美しい夕日"
]
}'
tag通过 llama-embedding
为了快速进行健全性检查,您还可以使用预编译的 llama-embedding 进行一次性向量模型生成。我们不建议将其用于批量向量模型生成,因为它存在一些性能问题,我们将在下一节中讨论:
llama-embedding -hf jinaai/jina-embeddings-v4-text-matching-GGUF:F16 --pooling mean -p "Query: jina is awesome" --embd-output-format json 2>/dev/null
阅读下一节,了解如何使用我们构建的 llama-embedding 进行更高效的批量向量模型生成,其中包含一些修复和改进。
tag注意事项总结
在转向更高效的实现之前,让我们总结一下 GGUF 模型的注意事项:
- 您必须手动在文本输入前添加
Query:或Passage:。 - 它们现在无法处理图像输入,因为我们从 GGUF 模型中删除了视觉 Transformer。我们不得不删除它们,因为 llama.cpp 的视觉 Transformer/
mmproj实现Qwen2.5-vl-3b存在错误,我们 正在与上游合作修复。 - 它们无法输出多向量向量模型,因为它不是
llama.cpp的Qwen2.5-vl-3b图实现的一部分。在不重新编译llama.cpp的情况下,最简单的解决方法是在通过在llama-embedding中设置--pooling none获取词元级别的向量模型后,单独导出并运行 MLP。 - v4 是使用 Matryoshka 表征学习训练的,转换为 GGUF 保留了此功能。如果您获得的向量模型的形状为
NxD,则只需使用embeddings[:, :truncate_dim]即可获得较小的截断向量模型。但是,并非每个维度都经过训练。对于 v4,我们为这些特定值训练了truncate_dim:[128, 256, 512, 1024, 2048]。这意味着embeddings[:, :131]的质量不会是embeddings[:, :128]和embeddings[:, :256]质量之间的一些插值,而是会明显低于 128 维或 256 维向量模型,因为 131 维没有经过训练。 - 在通过
--pooling none获取词元级别的向量模型后,延迟分块仍然可以作为后处理的一部分工作。就像我们从llama.cpp图中分离 MLP 所做的那样,这不是很有效,但不需要重新编译。但是,还有另一个注意事项:由于 v4 是一个因果模型,延迟分块将不再是双向的——较早的分块向量模型将不包含来自后续分块的上下文信息。请记住,在 v3 中,每个分块向量模型都具有全局上下文信息,因为我们在 Transformer 块中使用了双向注意力掩码。在内部,我们讨论了因果关系是否使延迟分块过时:有些人认为“上下文也是因果关系”——意味着读者从左到右处理文本,因此解释句子的上下文应该来自前面的文本。其他人说,将延迟分块限制为单向会阻止分块之间的上下文共享。无论哪种方式,延迟分块在 v4 中的有效性仍然值得怀疑,需要进一步研究。
tag通过 llama-embedding 实现高效向量模型生成
llama-embedding 是 llama.cpp 之上一个相对简单的 C++ 封装器,用于使用非常清晰的 I/O(stdin、stdout)对文本进行向量模型生成。我们目前专注于改进这一点,而不是 llama-server,因为还有大量其他问题,如网络排队、负载平衡、多租户和序列化,我们认为这些问题目前超出了范围。我们的问题很简单:我们可以从 L4 24GB GPU 获得多少速度,以及用于向量模型生成长文档的峰值 VRAM 使用量是多少?
但为什么是 L4?主要是因为 GCP 在其之上提供了非常方便的 Cloud Run 函数,并且它是您可以获得的用于无服务器推理 API 的最广泛可用且经济的 GPU 类型。GCP 确实按需在 Cloud Run 上提供 A100 和 H100,并且 GCP 团队时不时地会向我们推销使用更好的 GPU。但我们的理念很简单:如果我们需要 A100/H100 来服务一个 3B 模型,那显然是我们的技能问题。
作为一些背景知识,在 llama.cpp 中,逻辑批处理大小(-b) 表示在单次评估调用中提交给模型的最大词元数。在处理长输入时,它们会被分割成不超过此大小的块。**物理批次大小** (-ub) 是通过硬件一次前向传递同时处理的实际词元数,受可用内存的限制。KV 缓存会在每个物理批次完成后更新。**上下文窗口** (-c) 是模型一次可以“看到”的词元数量的硬性限制 - 对于 v4 模型,这是 32,000 个词元,代表了模型最大注意力范围。所有词元都必须在此上下文窗口内,以保持整个序列中连贯的注意力。下图说明了它们之间的关系。

tag我们的修复
在我们上面的 fork 中,我们进行了一些优化,以提高 llama-embedding 的效率:
- 简化批处理:我们自动将
-b设置为等于-c,实际上使该参数变得过时。用户不再需要指定-b,因为我们始终利用模型的完整上下文长度进行逻辑批处理。 - 灵活的内存控制:与原始实现中强制
-ub等于-b(因为他们假设向量模型不是因果关系)不同,我们允许用户独立设置-ub。这提供了对编码长上下文时峰值 VRAM 使用的细粒度控制 - 由于 KV 缓存的实现,您可以使用小的 512 词元的物理批次处理 32K 上下文,以保持在 VRAM 限制内。请注意,此更改仅适用于因果向量模型,如 jina-embeddings-v4 - 对于仅编码器架构,如 v3,这将是错误的实现。 - 修复了平均池化:我们修正了当
ub < b时向量模型的平均池化计算,这在原始实现中之前是错误的。
此更改使处理长上下文的仅解码器向量模型变得更加容易,同时有效地管理内存约束。现在,用户只需要配置两个参数:
-c:最大上下文长度(向量模型可以处理多少个词元)-ub:物理批次大小(GPU 一次处理多少个词元)
因此,在 L4 上运行我们 fork 的确切代码如下:
# Compile
git clone https://github.com/hanxiao/llama.cpp.git
cd llama.cpp
cmake -B build -DGGML_CUDA=ON
cmake --build build --config Release -j 8
# Run
INPUT_PREFIX="Query: " # or "Passage: "
cat big_input.txt | sed "s/^/${INPUT_PREFIX}/" | \
./llama.cpp/build/bin/llama-embedding -f /dev/stdin \
-hf "jinaai/jina-embeddings-v4-text-retrieval-GGUF:FP16" \
--pooling mean \
--no-escape \
--embd-output-format array \
--ubatch-size 512 \
--ctx-size 8192 \
--flash-attn \
-ngl 99 \
> "embeddings.txt" 2> "error.log"big_input.txt 中的每一行是要进行向量化的句子。应设置 --no-escape 以防止句子中的 \n 被解释为分隔符。应设置 --flash-attn 和 -ngl 99 以在 L4 GPU 上获得最佳性能。
tag基准测试
我们希望通过基准测试了解以下问题:
- 与原始 v4 Float16 相比,我们的量化效果如何?在什么情况下,它会退化到我们最好只使用 v3 向量模型的地步?
- 每种量化在 L4 上的运行速度有多快,以及峰值 VRAM 使用量是多少?
-ub物理批次大小和-c上下文长度如何影响速度和峰值 VRAM?
我们在基准测试中使用的数据集是:
| 任务 | 文档 | 查询 | 相关配对 | 平均文档长度 | 最大文档长度 | 平均查询长度 |
|---|---|---|---|---|---|---|
| NanoHotpotQA | 5,090 | 50 | 100 | 57.3 | 345 | 14.9 |
| NanoSciFact | 2,919 | 50 | 56 | 205.8 | 1524 | 13.5 |
| NanoArguAna | 3,635 | 50 | 50 | 164.5 | 1058 | 193.0 |
| NanoNFCorpus | 2,953 | 50 | 2,518 | 223.3 | 1460 | 3.3 |
| NanoFiQA2018 | 4,598 | 50 | 123 | 159.1 | 1882 | 10.2 |
我们使用了我们自定义构建的 llama-embedding 进行基准测试。
tag量化质量
表现最佳的量化版本是 IQ3_M (3.84 BPW) - 低于 2 位的量化效果比 v3 差,因此使用它们的意义不大。

| Quantization | NanoHotpotQA | NanoFiQA2018 | NanoArguAna | NanoNFCorpus | NanoSciFact |
|---|---|---|---|---|---|
| IQ1_S | 0.6369 | 0.3178 | 0.3798 | 0.2933 | 0.5934 |
| IQ1_M | 0.6316 | 0.3313 | 0.5167 | 0.3256 | 0.6114 |
| IQ2_XXS | 0.7236 | 0.4582 | 0.4584 | 0.4067 | 0.7392 |
| IQ2_M | 0.7427 | 0.5869 | 0.5090 | 0.4468 | 0.7880 |
| Q2_K | 0.7683 | 0.5744 | 0.5168 | 0.4183 | 0.7546 |
| IQ3_XXS | 0.7780 | 0.5991 | 0.4811 | 0.4267 | 0.7610 |
| IQ3_XS | 0.7727 | 0.5615 | 0.5195 | 0.4439 | 0.7726 |
| IQ3_S | 0.8002 | 0.5505 | 0.4886 | 0.4381 | 0.7690 |
| IQ3_M | 0.8106 | 0.5387 | 0.5091 | 0.4462 | 0.7760 |
| Q3_K_M | 0.7567 | 0.5267 | 0.4486 | 0.4092 | 0.7775 |
| IQ4_NL | 0.7930 | 0.5598 | 0.4911 | 0.4285 | 0.7794 |
| IQ4_XS | 0.7979 | 0.5627 | 0.4947 | 0.4258 | 0.7789 |
| Q4_K_M | 0.8029 | 0.5569 | 0.4883 | 0.4226 | 0.7877 |
| Q5_K_S | 0.7969 | 0.5581 | 0.4721 | 0.4288 | 0.7842 |
| Q5_K_M | 0.7927 | 0.5601 | 0.4745 | 0.4247 | 0.7873 |
| Q6_K | 0.7951 | 0.5636 | 0.4822 | 0.4337 | 0.7846 |
| Q8_0 | 0.7938 | 0.5687 | 0.4784 | 0.4335 | 0.7851 |
| F16 | 0.7940 | 0.5610 | 0.4931 | 0.4343 | 0.7963 |
| v3 (Transformers) | 0.7393 | 0.5144 | 0.4600 | 0.4068 | 0.7820 |
| v4 (Transformers) | 0.7977 | 0.5571 | 0.4844 | 0.4351 | 0.7963 |
tag速度和 VRAM
现在我们将基准数据集固定为 NanoHotpotQA,并绘制所有量化模型,以其每权重比特数与速度(以每秒词元数衡量)和 VRAM 消耗进行对比。我们发现 GGUF 版本在 FP16 下比原始版本略快(2023 个词元/秒 vs 1865 个词元/秒)。大多数量化模型聚集在 2000-2100 个词元/秒附近。启用闪存注意力后,所有量化模型的速度提升了约 77%(3000+ 个词元/秒 vs 2000+ 个词元/秒)。然而,性能最佳的量化模型 Q8_0 以约 3700 个词元/秒的速度,仍然远落后于原始 v3(572M 参数),后者达到了 16000 个词元/秒。量化版本节省了大量 VRAM,并且几乎达到了具有 IQ3 的 v3 FP16 模型的水平。

| Quantization | BPW | File Size (GB) | Peak VRAM (GB) | Token/s w FA | Token/s w/o FA |
|---|---|---|---|---|---|
| IQ1_S | 2.04 | 0.73 | 4.04 | 3625 | 2050 |
| IQ1_M | 2.19 | 0.79 | 4.09 | 3349 | 1997 |
| IQ2_XXS | 2.44 | 0.88 | 4.19 | 3701 | 2071 |
| IQ2_M | 2.94 | 1.06 | 4.37 | 3407 | 1989 |
| Q2_K | 3.29 | 1.18 | 4.49 | 3173 | 1905 |
| IQ3_XXS | 3.31 | 1.19 | 4.50 | 3668 | 2067 |
| IQ3_XS | 3.59 | 1.29 | 4.60 | 3604 | 2053 |
| IQ3_S | 3.76 | 1.35 | 4.66 | 3599 | 2049 |
| IQ3_M | 3.84 | 1.38 | 4.69 | 3603 | 2053 |
| Q3_K_M | 4.11 | 1.48 | 4.78 | 3450 | 2008 |
| IQ4_NL | 4.72 | 1.69 | 5.00 | 3571 | 2039 |
| IQ4_XS | 4.49 | 1.61 | 4.92 | 3585 | 2046 |
| Q4_K_M | 4.99 | 1.79 | 5.10 | 3558 | 2045 |
| Q5_K_S | 5.61 | 2.02 | 5.32 | 3567 | 2044 |
| Q5_K_M | 5.75 | 2.07 | 5.38 | 3528 | 2034 |
| Q6_K | 6.56 | 2.36 | 5.66 | 3334 | 1981 |
| Q8_0 | 8.50 | 3.05 | 6.36 | 3767 | 2101 |
| F16 | 16.00 | 5.75 | 9.70 | 3399 | 2023 |
| v3 (Transformers) | 16.00 | 1.10 | 2.82 | 16505 | |
| v4 (Transformers) | 16.00 | 7.40 | 14.45 | 1865 |
点击展开系统信息
load_tensors: 正在加载模型 tensors,这可能需要一段时间... (mmap = true)
load_tensors: 正在将 36 个重复层卸载到 GPU
load_tensors: 正在将输出层卸载到 GPU
load_tensors: 已将 37/37 层卸载到 GPU
load_tensors: CUDA0 模型缓冲区大小 = 3127.61 MiB
load_tensors: CPU_Mapped 模型缓冲区大小 = 315.30 MiB
...................................................................................
llama_context: 正在构建 llama_context
llama_context: n_seq_max = 1
llama_context: n_ctx = 4096
llama_context: n_ctx_per_seq = 4096
llama_context: n_batch = 4096
llama_context: n_ubatch = 4096
llama_context: causal_attn = 1
llama_context: flash_attn = 1 // 表中使用 FA 时为 1;不使用 FA 时为 0
llama_context: kv_unified = true
llama_context: freq_base = 1000000.0
llama_context: freq_scale = 1
llama_context: n_ctx_per_seq (4096) < n_ctx_train (128000) -- 将不会利用模型的全部容量
llama_context: CUDA_Host 输出缓冲区大小 = 0.59 MiB
llama_kv_cache_unified: CUDA0 KV 缓冲区大小 = 144.00 MiB
llama_kv_cache_unified: size = 144.00 MiB ( 4096 个单元格, 36 层, 1/1 个 seqs), K (f16): 72.00 MiB, V (f16): 72.00 MiB
llama_context: CUDA0 计算缓冲区大小 = 2470.16 MiB
llama_context: CUDA_Host 计算缓冲区大小 = 96.17 MiB
llama_context: graph nodes = 1234
llama_context: graph splits = 2
common_init_from_params: added <|endoftext|> logit bias = -inf
common_init_from_params: added <|im_end|> logit bias = -inf
common_init_from_params: added <|fim_pad|> logit bias = -inf
common_init_from_params: added <|repo_name|> logit bias = -inf
common_init_from_params: added <|file_sep|> logit bias = -inf
common_init_from_params: setting dry_penalty_last_n to ctx_size = 4096
common_init_from_params: warming up the model with an empty run - please wait ... (--no-warmup to disable)
system_info: n_threads = 4 (n_threads_batch = 4) / 8 | CUDA : ARCHS = 890 | USE_GRAPHS = 1 | PEER_MAX_BATCH_SIZE = 128 | CPU : SSE3 = 1 | SSSE3 = 1 | AVX = 1 | AVX2 = 1 | F16C = 1 | FMA = 1 | BMI2 = 1 | AVX512 = 1 | AVX512_VNNI = 1 | LLAMAFILE = 1 | OPENMP = 1 | REPACK = 1 |
main: n_tokens in batch = 0
main: number of embeddings = 5090
tag最佳物理批次大小和上下文大小
我们现在将量化类型固定为 IQ3_S,并检查物理批次大小 (-ub) 和上下文大小 (-c) 如何影响速度和 VRAM。L4 GPU 上的结果表明,-ub=512 和 -c=2048 提供了最佳配置,在消耗 2,025MB VRAM 的同时,实现了每秒 4,143 个词元的速度。结论是直观的:当您知道输入中单个文档的最大长度时,使用刚好足以覆盖它的较小上下文大小。对于物理批次大小,512 个词元似乎是 L4 GPU 上的最佳选择。

每秒词元性能
| ubatch_size | ctx_size=64 | ctx_size=128 | ctx_size=256 | ctx_size=512 |
|---|---|---|---|---|
| 64 | 2233 | 2093 | 2128 | 2125 |
| 128 | N/A | 2866 | 2821 | 2877 |
| 256 | N/A | N/A | 3287 | 3349 |
| 512 | N/A | N/A | N/A | 3469 |
| ubatch_size | ctx_size=2048 | ctx_size=4096 | ctx_size=8192 | ctx_size=16384 |
|---|---|---|---|---|
| 256 | 3971 | 3630 | 3593 | 2766 |
| 512 | 4143 | 3797 | 3758 | 2852 |
| 1024 | 4059 | 3742 | 3707 | 2822 |
| 2048 | 3957 | 3631 | 3603 | 2762 |
| 4096 | N/A | 3450 | 3410 | 2625 |
峰值 VRAM 使用量 (MB)
| ubatch_size | ctx_size=64 | ctx_size=128 | ctx_size=256 | ctx_size=512 |
|---|---|---|---|---|
| 64 | 1691 | 1689 | 1689 | 1697 |
| 128 | N/A | 1729 | 1727 | 1737 |
| 256 | N/A | N/A | 1803 | 1811 |
| 512 | N/A | N/A | N/A | 1963 |
| ubatch_size | ctx_size=2048 | ctx_size=4096 | ctx_size=8192 | ctx_size=16384 |
|---|---|---|---|---|
| 256 | 1885 | 1947 | 2099 | 2409 |
| 512 | 2025 | 2101 | 2257 | 2577 |
| 1024 | 2329 | 2407 | 2571 | 2917 |
| 2048 | 2933 | 3025 | 3203 | 3597 |
| 4096 | N/A | 4285 | 4497 | 4985 |
tag结论
对于希望在经济型 GPU 上高效运行量化 GGUF 的 v4 用户,请选择 IQ3_S 或 IQ3_M 以及我们的自定义构建的 llama-embedding - 这应该在常规数据集(句子长度小于 2048 个词元)上为您提供每秒 4000 个词元的速度。对于嵌入更长的文档,增加上下文大小 -c 并控制物理批次大小 -ub 以减少 VRAM 占用。使用我们的自定义构建,您可以通过将 -ub 设置为一个较小的数字(如 1024)来编码超长文档(>32K 词元),这在原始实现或 vanilla transformers 中是不可能的。
对速度优化的追求永无止境。始终存在更快、更精简且具有更高吞吐量的实现空间。每秒 4000 个词元可能不是我们的上限 - 还有很多工作要做。除了修复 llama.cpp 中的 qwen2.5-vl-3b mmproj/视觉transformer实现之外,我们还在探索更深层次的 llama.graph 和 KV 缓存级别的优化,改进 llama-serving 批处理逻辑,并向嵌入 API 添加流式传输选项。我们的目标是使 llama.cpp 原生支持现代的仅解码器多模态向量模型,以用于我们当前和未来的重排器版本。






