llama.cpp-Community auf 大模型n konzentriert, hielten wir es für wertvoll, dies aus der Perspektive eines 向量模型-Anbieters zu teilen.
Besonders relevant ist, dass die heutigen 向量模型n fast identisch mit 大模型n sind – zum Beispiel basiert jina-embeddings-v4 auf Qwen2.5-VL-3B-instruct und jina-reranker-m0 basiert auf Qwen2-VL-2B. Der einzige wirkliche Unterschied ist die Ausgabe: 大模型n sind generativ, die 向量模型n und 重排器 sind diskriminativ. Dies schafft sowohl Chancen als auch Herausforderungen: Einerseits können wir die effiziente Implementierung von llama.cpp (z. B. ubatch_size) nutzen, um 向量模型/重排器 Modelle zu bedienen; andererseits wurden die 向量模型-Implementierungen von llama.cpp hauptsächlich für ältere Encoder-Only-Architekturen (wie RoBERTa-basierte Modelle) entwickelt und haben noch nicht vollständig mit modernen Decoder-Only 向量模型/重排器 Modellen aufgeholt. Dieser Artikel beschreibt, was wir beim Anpassen moderner 向量模型 an die Arbeit mit dem GGUF-Format und den llama.cpp-Tools, z. B. llama-embedding und llama-serving, gelernt haben.
tagBase- und quantisierte GGUFs
jina-embeddings-v4 basiert aufQwen2.5-VL-3B-instruct mit drei LoRA-Adaptern: retrieval (optimiert für Retrieval-Aufgaben), text-matching (optimiert für Satzähnlichkeitsaufgaben) und code (optimiert für Code-Retrieval-Aufgaben). Es ist außerdem stark für visuelle Dokumentenabruf- und Late-Interaction-Style-Multi-Vektor-Ausgabe trainiert. Die Idee ist hier also, die bestehende Graph-Implementierung von Qwen2.5-VL-3B in llama.cpp zu nutzen und llama-embedding für die Inferenz zu verwenden.
Das erste, was uns jedoch auffiel, war ein fehlerhaftes Verhalten in der mmproj- oder Vision-Transformer-Implementierung in llama.cpp, die bei gleicher Bildeingabe unterschiedliche 向量模型n im Vergleich zur Torch-Implementierung von Qwen2.5-VL-3B erzeugt. Während wir dieses Problem in unserem Fork beheben, haben wir beschlossen, den Vision Tower vorerst aus den GGUF-Versionen auszuschließen. Weitere Details zu dieser Diskussion finden Sie hier.
Multi-Vektor-向量模型-Ausgabe wird auch nicht ohne Weiteres unterstützt, aber es ist kein so großes Problem wie die Vision Transformers. Die Multi-Vektor-Ausgabe stammt von einem trainierten MLP am letzten Transformer-Block, sodass wir diesen MLP schlimmstenfalls immer separat nach Numpy exportieren und anwenden können, nachdem wir die 词元-Level-向量模型n von llama.cpp erhalten haben – was wir für jina-reranker-m0-GGUF getan haben. Sicher, es ist nicht sehr effizient, aber es funktioniert, ohne dass wir llama.cpp modifizieren und neu kompilieren müssen.
Wir haben den Vision Transformer und den Multi-Vektor-Projektor entfernt und drei Basis-GGUF-Modelle in F16 erhalten.
Um also die bestehende Qwen2.5-VL-3B-Graph-Implementierung von llama.cpp vollständig zu erfüllen, haben wir den Vision Transformer und den Multi-Vektor-Projektor am letzten Transformer-Block entfernt und alle LoRA-Adapter wieder in das Basis-Sprachmodell integriert. Dies ergab uns drei aufgabenspezifische v4-Modelle mit jeweils 3,09 Milliarden Parametern – gegenüber den ursprünglichen 3,75 Milliarden Parametern von v4:
| 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 |
Dann verwendeten wir calibration_data_v5_rc.txt (die hier zu finden ist und von Unsloth empfohlen wird), um alle drei Basis-GGUF-Modelle zu kalibrieren und drei imatrix-Dateien zu erhalten. Anschließend verwendeten wir llama-quantize mit imatrix, um die Modelle wie folgt von float16 zu quantisieren:
# 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-GGUFDas quantize.sh-Skript ist unten dargestellt:
#!/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
doneSchließlich haben wir alle Quantisierungen auf HuggingFace hochgeladen.
| Quantisierung | BPW | Dateigröße (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 |

tagVerwendung und Vorbehalte
Wir können jetzt llama-server und llama-embedding verwenden, um GGUFs für die Einbettung bereitzustellen. Im Gegensatz zu Transformer-Bibliotheken, bei denen wir die Flexibilität haben, benutzerdefinierten Eingabe-Preprocessing-Code zu schreiben, müssen wir diesen Teil manuell bearbeiten (es sei denn, wir möchten llama-server und llama-embedding neu kompilieren). Um Ergebnisse zu erhalten, die vollständig mit der Verwendung von AutoModel.from_pretrained("jinaai/jina-embeddings-v4")... übereinstimmen, müssen Sie sehr vorsichtig mit Präfixen sein und diese manuell zu Ihren GGUF-Modelleingaben hinzufügen. Hier ist eine Referenztabelle:
| Aufgabe | prompt_name in der Transformer-Implementierung |
Tatsächliche Eingabe in das Modell |
|---|---|---|
retrieval |
query (Standard) |
Query: {original_text} |
retrieval |
passage |
Passage: {original_text} |
text-matching |
query (Standard) |
Query: {original_text} |
text-matching |
passage |
Query: {original_text} ⚠️ |
code |
query (Standard) |
Query: {original_text} |
code |
passage |
Passage: {original_text} |
Einige Benutzer werden es vielleicht überraschend finden, dass ⚠️ prompt_name='passage' bei Verwendung von text-matching im ursprünglichen AutoModel.from_pretrained("jinaai/jina-embeddings-v4").... in "Query: " überschrieben wird. Dies ist jedoch sinnvoll, da text-matching eine Satzähnlichkeitsaufgabe ohne linke/rechte Rollen ist – die Eingaben sind symmetrisch.
tagVia llama-server
Nach der Installation von llama.cpp können Sie llama-server ausführen, um das Einbettungsmodell als OpenAI API-kompatiblen HTTP-Server zu hosten. Um beispielsweise text-matching mit F16 zu verwenden, können Sie Folgendes tun:
llama-server -hf jinaai/jina-embeddings-v4-text-matching-GGUF:F16 --embedding --pooling mean -ub 8192
--pooling mean ist erforderlich, da v4 Mean-Pooling- Vektormodelle sind.
Senden Sie dann eine Anfrage über:
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: 浜辺に沈む美しい夕日"
]
}'
Wenn Sie retrieval- und code-Modelle verwenden, fügen Sie Query: oder Passage: vor Ihrer Eingabe hinzu, wie folgt:
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: 浜辺に沈む美しい夕日"
]
}'
tagVia llama-embedding
Für eine schnelle Plausibilitätsprüfung können Sie auch die vorkompilierte llama-embedding für die einmalige Einbettung verwenden. Wir empfehlen nicht, sie für die Masseneinbettung zu verwenden, da sie einige Leistungsprobleme aufweist, die wir im nächsten Abschnitt besprechen werden:
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
Lesen Sie den nächsten Abschnitt für eine performantere Masseneinbettung mit unserer Version von llama-embedding mit einigen Korrekturen und Verbesserungen.
tagZusammenfassung der Vorbehalte
Bevor wir zu einer performanteren Implementierung übergehen, fassen wir die Vorbehalte von GGUF-Modellen zusammen:
- Sie müssen manuell
Query:oderPassage:vor den Texteingaben hinzufügen. - Sie können derzeit keine Bildeingaben verarbeiten, da wir die Vision-Transformer aus dem GGUF-Modell entfernt haben. Wir mussten sie aufgrund von Fehlern in der Vision-Transformer-/
mmproj-Implementierung vonQwen2.5-vl-3bin llama.cpp entfernen, an deren Behebung wir mit Upstream arbeiten. - Sie können keine Multi-Vektor- Einbettungen ausgeben, da dies nicht Teil der
Qwen2.5-vl-3b-Graphenimplementierung vonllama.cppist. Die einfachste Problemumgehung, ohnellama.cppneu zu kompilieren, besteht darin, das MLP zu exportieren und separat auszuführen, nachdem Vektorabbildungen auf Token-Ebene abgerufen wurden, indem--pooling noneinllama-embeddingfestgelegt wird. - v4 ist mit Matryoshka-Darstellungslernen trainiert, und die Konvertierung in GGUF behält dieses Merkmal bei. Wenn Sie Vektorabbildungen mit der Form
NxDerhalten, können Sie einfachembeddings[:, :truncate_dim]verwenden, um kleinere, abgeschnittene Vektorabbildungen zu erhalten. Allerdings wird nicht jede Dimension trainiert. Für v4 haben wirtruncate_dimfür diese spezifischen Werte trainiert:[128, 256, 512, 1024, 2048]. Dies bedeutet, dass die Qualität vonembeddings[:, :131]keine Interpolation zwischen der Qualität vonembeddings[:, :128]undembeddings[:, :256]ist, sondern deutlich schlechter als entweder 128-dim- oder 256-dim- Vektorabbildungen, da 131-dim nicht trainiert ist. - Spätes Chunking kann weiterhin als Teil der Nachbearbeitung funktionieren, nachdem Vektorabbildungen auf Token-Ebene über
--pooling noneabgerufen wurden. So wie wir das MLP vomllama.cpp-Graphen getrennt haben, ist dies nicht besonders effizient, erfordert aber keine Neukompilierung. Es gibt jedoch einen weiteren Vorbehalt: Da v4 ein kausales Modell ist, wird spätes Chunking nicht mehr bidirektional sein – frühere Chunk- Vektorabbildungen enthalten keine Kontextinformationen aus nachfolgenden Chunks. Denken Sie daran, dass in v3 jede Chunk- Vektorabbildung globale Kontextinformationen enthielt, da wir bidirektionale Aufmerksamkeitsmasken in den Transformer-Blöcken verwendet haben. Intern haben wir diskutiert, ob Kausalität spätes Chunking überflüssig macht: Einige argumentieren, dass "Kontext auch kausal ist" – das heißt, ein Leser verarbeitet Text von links nach rechts, sodass der Kontext, der zur Interpretation eines Satzes benötigt wird, aus dem vorhergehenden Text stammen sollte. Andere sagen, dass die Beschränkung von spätem Chunking auf unidirektionale Blöcke die gemeinsame Nutzung von Kontexten zwischen Chunks verhindert. In jedem Fall bleibt die Wirksamkeit von spätem Chunking in v4 fraglich und bedarf weiterer Untersuchungen.
tagEffiziente Vektorabbildungen über llama-embedding
llama-embedding ist ein relativ einfacher C++-Wrapper auf Basis von llama.cpp zur Einbettung von Text mit sehr sauberem I/O: stdin, stdout. Wir konzentrieren uns im Moment darauf, dies zu verbessern, anstatt llama-server, da es unzählige andere Probleme wie Netzwerk-Queuing, Lastverteilung, Multi-Tenancy und Serialisierung gibt, die unserer Meinung nach an dieser Stelle nicht relevant sind. Unsere Frage ist einfach: Wie viel Geschwindigkeit können wir von einer L4 24GB GPU bekommen und wie hoch ist die maximale VRAM-Auslastung für die Einbettung langer Dokumente?
Aber warum L4? Hauptsächlich, weil GCP recht praktische Cloud Run-Funktionen darauf anbietet und es der am weitesten verbreitete und wirtschaftlichste GPU-Typ ist, den Sie für serverlose Inferenz-APIs erhalten können. GCP bietet auf Anfrage A100 und H100 auf Cloud Run an, und wir werden von Zeit zu Zeit vom GCP-Team angesprochen, bessere GPUs zu verwenden. Aber unsere Philosophie ist einfach: Wenn wir A100/H100 benötigen, um ein 3B-Modell zu bedienen, ist das eindeutig ein Problem unsererseits.
Für einige Hintergrundinformationen: In llama.cpp ist die logische Batch-Größe (-b) stellt die maximale Anzahl an Tokens dar, die dem Modell in einem einzelnen Auswertungsvorgang übermittelt werden. Bei der Verarbeitung langer Eingaben werden diese in Abschnitte bis zu dieser Größe aufgeteilt. Die physische Batch-Größe (-ub) ist die tatsächliche Anzahl an Tokens, die gleichzeitig in einem Vorwärtsdurchlauf durch die Hardware verarbeitet werden, begrenzt durch den verfügbaren Speicher. Die KV-Cache-Aktualisierungen erfolgen nach Abschluss jedes physischen Batches. Das Kontextfenster (-c) ist die harte Grenze für die Anzahl der Tokens, die das Modell gleichzeitig "sehen" kann - für v4-Modelle sind dies 32.000 Tokens, die die maximale Aufmerksamkeitsspanne des Modells darstellen. Alle Tokens müssen in dieses Kontextfenster passen, um eine kohärente Aufmerksamkeit über die gesamte Sequenz aufrechtzuerhalten. Die folgende Abbildung veranschaulicht ihre Beziehungen.

tagUnsere Korrekturen
In unserem obigen Fork haben wir mehrere Optimierungen vorgenommen, um llama-embedding effizienter zu gestalten:
- Vereinfachte Batch-Verarbeitung: Wir setzen
-bautomatisch gleich-c, wodurch dieser Parameter faktisch hinfällig wird. Benutzer müssen-bnicht mehr angeben, da wir immer die volle Kontextlänge des Modells für die logische Batch-Verarbeitung nutzen. - Flexible Speichersteuerung: Im Gegensatz zur ursprünglichen Implementierung, bei der
-ubgezwungen war, gleich-bzu sein (da sie davon ausgingen, dass Einbettungsmodelle nicht kausal sein können), erlauben wir Benutzern,-ubunabhängig festzulegen. Dies ermöglicht eine feinkörnige Kontrolle über die maximale VRAM-Auslastung beim Codieren langer Kontexte - Sie können einen 32K-Kontext mit einem kleinen physischen 512-Token-Batch verarbeiten, um dank der KV-Cache-Implementierung innerhalb der VRAM-Grenzwerte zu bleiben. Beachten Sie, dass diese Änderung nur für kausale Einbettungsmodelle wie jina-embeddings-v4 korrekt ist - für reine Encoder-Architekturen wie v3 wäre dies die falsche Implementierung. - Korrigiertes Mean Pooling: Wir haben die Mean-Pooling-Berechnung für Vektormodelle korrigiert, wenn
ub < b, die zuvor in der ursprünglichen Implementierung fehlerhaft war.
Diese Änderung erleichtert die Arbeit mit Decoder-only Vektormodellen mit langer Kontextlänge und gleichzeitig effektiver Verwaltung von Speicherbeschränkungen erheblich. Benutzer müssen jetzt nur noch zwei Parameter konfigurieren:
-c: Die maximale Kontextlänge (wie viele Tokens das Einbettungsmodell verarbeiten kann)-ub: Die physische Batch-Größe (wie viele Tokens die GPU gleichzeitig verarbeitet)
Der genaue Code für die Ausführung unseres Forks auf L4 lautet wie folgt:
# 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"Jede Zeile in big_input.txt ist ein Satz, der eingebettet werden soll. --no-escape sollte gesetzt werden, um zu verhindern, dass \n im Satz als Trennzeichen interpretiert wird. --flash-attn und -ngl 99 sollten für die beste Leistung auf der L4 GPU gesetzt werden.
tagBenchmark
Wir möchten durch Benchmarking die folgenden Fragen beantworten:
- Wie gut ist unsere Quantisierung im Vergleich zum ursprünglichen v4 Float16? Ab welchem Punkt verschlechtert sie sich so stark, dass wir besser dran wären, nur v3 Vektormodelle zu verwenden?
- Wie schnell kann jede Quantisierung auf L4 laufen und wie hoch ist die maximale VRAM-Auslastung?
- Wie beeinflussen die physische Batch-Größe
-ubund die Kontextlänge-cdie Geschwindigkeit und die maximale VRAM-Auslastung?
Datensätze, die wir im Benchmarking verwendet haben, sind:
| Task | Documents | Queries | Relevant Pairs | Avg Doc Length | Max Doc Length | Avg Query Length |
|---|---|---|---|---|---|---|
| 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 |
Wir haben unseren benutzerdefinierten Build von llama-embedding für das Benchmarking verwendet.
tagQualität der Quantisierungen
Die am besten abschneidende quantisierte Version ist IQ3_M (3.84 BPW) - Quantisierungen unter 2 Bit schneiden schlechter ab als v3, daher ist es wenig sinnvoll, sie zu verwenden.

| Quantisierung | 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 |
tagGeschwindigkeit und VRAM
Wir legen nun den Benchmark-Datensatz auf NanoHotpotQA fest und stellen alle Quantisierungen nach ihren Bits pro Gewichtung im Vergleich zur Geschwindigkeit (gemessen in Tokens pro Sekunde) und dem VRAM-Verbrauch dar. Wir haben festgestellt, dass GGUF-Versionen bei FP16 etwas schneller sind als die Vanilla-Version (2023 vs. 1865 Tokens/Sek.). Die meisten Quantisierungen gruppieren sich um 2000-2100 Tokens/Sek. Mit aktivierter Flash-Attention erhalten wir eine Geschwindigkeitssteigerung von ~77 % über alle Quantisierungen hinweg (3000+ vs. 2000+ Tokens/Sek.). Die am besten abschneidende Quantisierung Q8_0 mit etwa 3700 Tokens pro Sekunde liegt jedoch immer noch weit hinter Vanilla v3 (572M Parameter) zurück, die 16000 Tokens/Sek. erreicht. Quantisierte Versionen sparen erheblich VRAM und nähern sich mit IQ3 fast dem Niveau des v3 FP16-Modells.

| Quantisierung | BPW | Dateigröße (GB) | Spitzen-VRAM (GB) | Token/s mit FA | Token/s ohne 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 |
Zum Erweitern der Systeminfo klicken
load_tensors: loading model tensors, this can take a while... (mmap = true)
load_tensors: offloading 36 repeating layers to GPU
load_tensors: offloading output layer to GPU
load_tensors: offloaded 37/37 layers to GPU
load_tensors: CUDA0 model buffer size = 3127.61 MiB
load_tensors: CPU_Mapped model buffer size = 315.30 MiB
...................................................................................
llama_context: constructing 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 // 1 for w/ FA in the table; 0 for w/o FA
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) -- the full capacity of the model will not be utilized
llama_context: CUDA_Host output buffer size = 0.59 MiB
llama_kv_cache_unified: CUDA0 KV buffer size = 144.00 MiB
llama_kv_cache_unified: size = 144.00 MiB ( 4096 cells, 36 layers, 1/1 seqs), K (f16): 72.00 MiB, V (f16): 72.00 MiB
llama_context: CUDA0 compute buffer size = 2470.16 MiB
llama_context: CUDA_Host compute buffer size = 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
tagOptimale physische Batch- und Kontextgröße
Wir legen nun den Quantisierungstyp auf IQ3_S fest und untersuchen, wie sich die physische Batchgröße (-ub) und die Kontextgröße (-c) auf die Geschwindigkeit und den VRAM auswirken. Die Ergebnisse auf der L4-GPU zeigen, dass -ub=512 mit -c=2048 die optimale Konfiguration darstellt, die 4.143 Tokens/Sek. liefert und dabei 2.025 MB VRAM verbraucht. Die Erkenntnis ist intuitiv: Wenn Sie die maximale Länge eines einzelnen Dokuments in Ihrer Eingabe kennen, verwenden Sie eine kleinere Kontextgröße, die gerade ausreicht, um es abzudecken. Für die physische Batchgröße scheinen 512 Tokens der Sweet Spot auf der L4-GPU zu sein.

Tokens pro Sekunde Leistung
| 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 |
Maximale VRAM-Nutzung (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 |
tagFazit
Für v4-Benutzer, die quantisierte GGUF effizient auf kostengünstigen GPUs ausführen möchten, wählen Sie IQ3_S oder IQ3_M mit unserem benutzerdefinierten Build von llama-embedding - dies sollte Ihnen 4000 Tokens/Sek. auf regulären Datensätzen (bei denen die Satzlänge <2048 Tokens beträgt) liefern. Um längere Dokumente einzubetten, erhöhen Sie die Kontextgröße -c und steuern Sie die physische Batchgröße -ub, um den VRAM-Fußabdruck zu reduzieren. Mit unserem benutzerdefinierten Build können Sie super lange Dokumente (>32K Tokens) mit nur 3 GB VRAM kodieren, indem Sie -ub auf eine kleine Zahl wie 1024 setzen - etwas, das mit der ursprünglichen Implementierung oder Vanilla-Transformern nicht möglich war.
Die Suche nach Geschwindigkeitsoptimierung endet nie. Es gibt immer Raum für schnellere, schlankere Implementierungen mit höherem Durchsatz. 4000 Tokens/Sek. sind wahrscheinlich nicht unsere Obergrenze - es gibt noch viel zu tun. Neben der Behebung der qwen2.5-vl-3b mmproj/Vision-Transformer-Implementierung in llama.cpp untersuchen wir auch tiefere Optimierungen auf Llama.graph- und KV-Cache-Ebene, verbessern die Batching-Logik von llama-serving und fügen Streaming-Optionen zu Embedding-APIs hinzu. Unser Ziel ist es, dass llama.cpp nativ moderne, reine Dekoder-Multimodal- Embeddings für unsere aktuellen und zukünftigen Reranker-Releases unterstützt.






