mirror of
https://github.com/zenlm/zen-embedding.git
synced 2026-07-27 03:09:27 +00:00
Initial commit: zen-embedding cross-encoder model
Squashed from upstream Qwen3-Embedding with Zen LM branding. Evaluation data with broken LFS pointers excluded.
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
*.json filter=lfs diff=lfs merge=lfs -text
|
||||
*.jsonl filter=lfs diff=lfs merge=lfs -text
|
||||
@@ -0,0 +1,32 @@
|
||||
name: Tests
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main, develop ]
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: ["3.9", "3.10", "3.11"]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install pytest torch transformers
|
||||
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
|
||||
|
||||
- name: Run tests
|
||||
run: |
|
||||
pytest tests/ -v --tb=short
|
||||
@@ -0,0 +1,8 @@
|
||||
|
||||
|
||||
AGENTS.md
|
||||
CLAUDE.md
|
||||
GEMINI.md
|
||||
GROK.md
|
||||
QWEN.md
|
||||
evaluation/
|
||||
@@ -0,0 +1,200 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to the Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by the Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding any notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. Please refer to the section
|
||||
"How to Apply These Terms to Your New Programs" in the full
|
||||
license text for more specific instructions.
|
||||
|
||||
Copyright 2024 Zen LM
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
@@ -0,0 +1,46 @@
|
||||
# Makefile for Zen Embedding (0.6B)
|
||||
|
||||
MODEL_NAME = zen-embedding-0.6b
|
||||
BASE_MODEL = Qwen/Qwen3-Embedding-0.6B
|
||||
HF_REPO = zenlm/${MODEL_NAME}
|
||||
|
||||
.PHONY: all
|
||||
all: train quantize upload
|
||||
|
||||
.PHONY: train
|
||||
train:
|
||||
@echo "🎯 Training zen-embedding..."
|
||||
@python train_zen_embedding.py
|
||||
|
||||
.PHONY: quantize
|
||||
quantize:
|
||||
@echo "🗜️ Creating GGUF quantizations..."
|
||||
@make gguf-q4 gguf-q5 gguf-q8
|
||||
|
||||
.PHONY: gguf-q4
|
||||
gguf-q4:
|
||||
@../llama.cpp/build/bin/llama-quantize \
|
||||
gguf/${MODEL_NAME}-f16.gguf \
|
||||
gguf/${MODEL_NAME}-Q4_K_M.gguf Q4_K_M
|
||||
|
||||
.PHONY: mlx
|
||||
mlx:
|
||||
@echo "🍎 Converting to MLX..."
|
||||
@mlx_lm.convert --hf-path finetuned --mlx-path mlx --quantize
|
||||
|
||||
.PHONY: test
|
||||
test:
|
||||
@echo "🧪 Testing zen-embedding..."
|
||||
@python -c "from transformers import AutoModelForCausalLM, AutoTokenizer; \
|
||||
model = AutoModelForCausalLM.from_pretrained('finetuned'); \
|
||||
tokenizer = AutoTokenizer.from_pretrained('finetuned'); \
|
||||
print('Model loaded successfully')"
|
||||
|
||||
.PHONY: upload
|
||||
upload:
|
||||
@echo "📤 Uploading to HuggingFace..."
|
||||
@huggingface-cli upload ${HF_REPO} . --repo-type model
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
@rm -rf finetuned/ gguf/ mlx/
|
||||
@@ -0,0 +1,496 @@
|
||||
|
||||
|
||||
<p align="center">
|
||||
<img src="https://qianwen-res.oss-accelerate.aliyuncs.com/logo_qwen_embedding.png" width="400"/>
|
||||
<p>
|
||||
|
||||
<p align="center">
|
||||
   <a href="https://huggingface.co/collections/Qwen/qwen3-embedding-6841b2055b99c44d9a4c371f">Huggingface</a>   |    <a href="https://modelscope.cn/collections/Qwen3-Embedding-3edc3762d50f48">ModelScope</a>   |    <a href="https://qwenlm.github.io/blog/qwen3-embedding/">Blog</a>    |    <a href="https://arxiv.org/abs/2506.05176">Arxiv</a>    |    <a href="https://bailian.console.aliyun.com/?tab=model#/model-market/detail/text-embedding-v4">API</a> |    <a href="https://discord.gg/yPEP2vHTu4">Discord</a>
|
||||
</p>
|
||||
|
||||
# Qwen3 Embedding
|
||||
|
||||
## Highlights
|
||||
|
||||
The Qwen3 Embedding model series is the latest proprietary model of the Qwen family, specifically designed for text embedding and ranking tasks. Building upon the dense foundational models of the Qwen3 series, it provides a comprehensive range of text embeddings and reranking models in various sizes (0.6B, 4B, and 8B). This series inherits the exceptional multilingual capabilities, long-text understanding, and reasoning skills of its foundational model. The Qwen3 Embedding series represents significant advancements in multiple text embedding and ranking tasks, including text retrieval, code retrieval, text classification, text clustering, and bitext mining.
|
||||
|
||||
**Exceptional Versatility**: The embedding model has achieved state-of-the-art performance across a wide range of downstream application evaluations. The 8B size embedding model ranks **No.1** in the MTEB multilingual leaderboard (as of June 5, 2025, score **70.58**), while the reranking model excels in various text retrieval scenarios.
|
||||
|
||||
**Comprehensive Flexibility**: The Qwen3 Embedding series offers a full spectrum of sizes (from 0.6B to 8B) for both embedding and reranking models, catering to diverse use cases that prioritize efficiency and effectiveness. Developers can seamlessly combine these two modules. Additionally, the embedding model allows for flexible vector definitions across all dimensions, and both embedding and reranking models support user-defined instructions to enhance performance for specific tasks, languages, or scenarios.
|
||||
|
||||
**Multilingual Capability**: The Qwen3 Embedding series offer support for over 100 languages, thanks to the multilingual capabilities of Qwen3 models. This includes various programming languages, and provides robust multilingual, cross-lingual, and code retrieval capabilities.
|
||||
|
||||
|
||||
## Qwen3 Embedding Series Model list
|
||||
|
||||
| Model Type | Models | Size | Layers | Sequence Length | Embedding Dimension | MRL Support | Instruction Aware |
|
||||
|------------------|----------------------|------|--------|-----------------|---------------------|-------------|----------------|
|
||||
| Text Embedding | [Qwen3-Embedding-0.6B](https://huggingface.co/Qwen/Qwen3-Embedding-0.6B) | 0.6B | 28 | 32K | 1024 | Yes | Yes |
|
||||
| Text Embedding | [Qwen3-Embedding-4B](https://huggingface.co/Qwen/Qwen3-Embedding-4B) | 4B | 36 | 32K | 2560 | Yes | Yes |
|
||||
| Text Embedding | [Qwen3-Embedding-8B](https://huggingface.co/Qwen/Qwen3-Embedding-8B) | 8B | 36 | 32K | 4096 | Yes | Yes |
|
||||
| Text Reranking | [Qwen3-Reranker-0.6B](https://huggingface.co/Qwen/Qwen3-Reranker-0.6B) | 0.6B | 28 | 32K | - | - | Yes |
|
||||
| Text Reranking | [Qwen3-Reranker-4B](https://huggingface.co/Qwen/Qwen3-Reranker-4B) | 4B | 36 | 32K | - | - | Yes |
|
||||
| Text Reranking | [Qwen3-Reranker-8B](https://huggingface.co/Qwen/Qwen3-Reranker-8B) | 8B | 36 | 32K | - | - | Yes |
|
||||
|
||||
> **Note**:
|
||||
> - `MRL (Matryoshka Representation Learning) Support` indicates whether the embedding model supports custom dimensions for the final embedding.
|
||||
> - `Instruction Aware` notes whether the embedding or reranking model supports customizing the input instruction according to different tasks.
|
||||
> - Our evaluation indicates that, for most downstream tasks, using instructions (instruct) typically yields an improvement of 1% to 5% compared to not using them. Therefore, we recommend that developers create tailored instructions specific to their tasks and scenarios. In multilingual contexts, we also advise users to write their instructions in English, as most instructions utilized during the model training process were originally written in English.
|
||||
|
||||
### Multilingual Support
|
||||
|
||||
Qwen3-Embedding model series shares the multilingual support capabilities of the Qwen3 base model.
|
||||
|
||||
<details>
|
||||
<summary>Click to expand the list of supported languages</summary>
|
||||
|
||||
| Language Family | Languages & Dialects |
|
||||
|---|---|
|
||||
| Indo-European | English, French, Portuguese, German, Romanian, Swedish, Danish, Bulgarian, Russian, Czech, Greek, Ukrainian, Spanish, Dutch, Slovak, Croatian, Polish, Lithuanian, Norwegian Bokmål, Norwegian Nynorsk, Persian, Slovenian, Gujarati, Latvian, Italian, Occitan, Nepali, Marathi, Belarusian, Serbian, Luxembourgish, Venetian, Assamese, Welsh, Silesian, Asturian, Chhattisgarhi, Awadhi, Maithili, Bhojpuri, Sindhi, Irish, Faroese, Hindi, Punjabi, Bengali, Oriya, Tajik, Eastern Yiddish, Lombard, Ligurian, Sicilian, Friulian, Sardinian, Galician, Catalan, Icelandic, Tosk Albanian, Limburgish, Dari, Afrikaans, Macedonian, Sinhala, Urdu, Magahi, Bosnian, Armenian |
|
||||
| Sino-Tibetan | Chinese (Simplified Chinese, Traditional Chinese, Cantonese), Burmese |
|
||||
| Afro-Asiatic | Arabic (Standard, Najdi, Levantine, Egyptian, Moroccan, Mesopotamian, Ta'izzi-Adeni, Tunisian), Hebrew, Maltese |
|
||||
| Austronesian | Indonesian, Malay, Tagalog, Cebuano, Javanese, Sundanese, Minangkabau, Balinese, Banjar, Pangasinan, Iloko, Waray (Philippines) |
|
||||
| Dravidian | Tamil, Telugu, Kannada, Malayalam |
|
||||
| Turkic | Turkish, North Azerbaijani, Northern Uzbek, Kazakh, Bashkir, Tatar |
|
||||
| Tai-Kadai | Thai, Lao |
|
||||
| Uralic | Finnish, Estonian, Hungarian |
|
||||
| Austroasiatic | Vietnamese, Khmer |
|
||||
| Other | Japanese, Korean, Georgian, Basque, Haitian, Papiamento, Kabuverdianu, Tok Pisin, Swahili |
|
||||
|
||||
</details>
|
||||
|
||||
## Usage
|
||||
|
||||
With Transformers versions earlier than 4.51.0, you may encounter the following error:
|
||||
```
|
||||
KeyError: 'qwen3'
|
||||
```
|
||||
### Embedding Model
|
||||
|
||||
#### Transformers Usage
|
||||
|
||||
```python
|
||||
# Requires transformers>=4.51.0
|
||||
|
||||
import torch
|
||||
import torch.nn.functional as F
|
||||
|
||||
from torch import Tensor
|
||||
from transformers import AutoTokenizer, AutoModel
|
||||
|
||||
|
||||
def last_token_pool(last_hidden_states: Tensor,
|
||||
attention_mask: Tensor) -> Tensor:
|
||||
left_padding = (attention_mask[:, -1].sum() == attention_mask.shape[0])
|
||||
if left_padding:
|
||||
return last_hidden_states[:, -1]
|
||||
else:
|
||||
sequence_lengths = attention_mask.sum(dim=1) - 1
|
||||
batch_size = last_hidden_states.shape[0]
|
||||
return last_hidden_states[torch.arange(batch_size, device=last_hidden_states.device), sequence_lengths]
|
||||
|
||||
|
||||
def get_detailed_instruct(task_description: str, query: str) -> str:
|
||||
return f'Instruct: {task_description}\nQuery:{query}'
|
||||
|
||||
# Each query must come with a one-sentence instruction that describes the task
|
||||
task = 'Given a web search query, retrieve relevant passages that answer the query'
|
||||
|
||||
queries = [
|
||||
get_detailed_instruct(task, 'What is the capital of China?'),
|
||||
get_detailed_instruct(task, 'Explain gravity')
|
||||
]
|
||||
# No need to add instruction for retrieval documents
|
||||
documents = [
|
||||
"The capital of China is Beijing.",
|
||||
"Gravity is a force that attracts two bodies towards each other. It gives weight to physical objects and is responsible for the movement of planets around the sun."
|
||||
]
|
||||
input_texts = queries + documents
|
||||
|
||||
tokenizer = AutoTokenizer.from_pretrained('Qwen/Qwen3-Embedding-0.6B', padding_side='left')
|
||||
model = AutoModel.from_pretrained('Qwen/Qwen3-Embedding-0.6B')
|
||||
|
||||
# We recommend enabling flash_attention_2 for better acceleration and memory saving.
|
||||
# model = AutoModel.from_pretrained('Qwen/Qwen3-Embedding-0.6B', attn_implementation="flash_attention_2", torch_dtype=torch.float16).cuda()
|
||||
|
||||
max_length = 8192
|
||||
|
||||
# Tokenize the input texts
|
||||
batch_dict = tokenizer(
|
||||
input_texts,
|
||||
padding=True,
|
||||
truncation=True,
|
||||
max_length=max_length,
|
||||
return_tensors="pt",
|
||||
)
|
||||
batch_dict.to(model.device)
|
||||
with torch.no_grad():
|
||||
outputs = model(**batch_dict)
|
||||
embeddings = last_token_pool(outputs.last_hidden_state, batch_dict['attention_mask'])
|
||||
|
||||
# normalize embeddings
|
||||
embeddings = F.normalize(embeddings, p=2, dim=1)
|
||||
scores = (embeddings[:2] @ embeddings[2:].T)
|
||||
|
||||
print(scores.tolist())
|
||||
# [[0.7645568251609802, 0.14142508804798126], [0.13549736142158508, 0.5999549627304077]]
|
||||
```
|
||||
|
||||
#### vLLM Usage
|
||||
```python
|
||||
# Requires vllm>=0.8.5
|
||||
import torch
|
||||
import vllm
|
||||
from vllm import LLM
|
||||
|
||||
def get_detailed_instruct(task_description: str, query: str) -> str:
|
||||
return f'Instruct: {task_description}\nQuery:{query}'
|
||||
|
||||
# Each query must come with a one-sentence instruction that describes the task
|
||||
task = 'Given a web search query, retrieve relevant passages that answer the query'
|
||||
|
||||
queries = [
|
||||
get_detailed_instruct(task, 'What is the capital of China?'),
|
||||
get_detailed_instruct(task, 'Explain gravity')
|
||||
]
|
||||
# No need to add instruction for retrieval documents
|
||||
documents = [
|
||||
"The capital of China is Beijing.",
|
||||
"Gravity is a force that attracts two bodies towards each other. It gives weight to physical objects and is responsible for the movement of planets around the sun."
|
||||
]
|
||||
input_texts = queries + documents
|
||||
|
||||
model = LLM(model="Qwen/Qwen3-Embedding-0.6B", task="embed")
|
||||
|
||||
outputs = model.embed(input_texts)
|
||||
embeddings = torch.tensor([o.outputs.embedding for o in outputs])
|
||||
scores = (embeddings[:2] @ embeddings[2:].T)
|
||||
print(scores.tolist())
|
||||
# [[0.7620252966880798, 0.14078938961029053], [0.1358368694782257, 0.6013815999031067]]
|
||||
```
|
||||
|
||||
#### Sentence Transformers Usage
|
||||
```python
|
||||
# Requires transformers>=4.51.0
|
||||
# Requires sentence-transformers>=2.7.0
|
||||
|
||||
from sentence_transformers import SentenceTransformer
|
||||
|
||||
# Load the model
|
||||
model = SentenceTransformer("Qwen/Qwen3-Embedding-0.6B")
|
||||
|
||||
# We recommend enabling flash_attention_2 for better acceleration and memory saving,
|
||||
# together with setting `padding_side` to "left":
|
||||
# model = SentenceTransformer(
|
||||
# "Qwen/Qwen3-Embedding-0.6B",
|
||||
# model_kwargs={"attn_implementation": "flash_attention_2", "device_map": "auto"},
|
||||
# tokenizer_kwargs={"padding_side": "left"},
|
||||
# )
|
||||
|
||||
# The queries and documents to embed
|
||||
queries = [
|
||||
"What is the capital of China?",
|
||||
"Explain gravity",
|
||||
]
|
||||
documents = [
|
||||
"The capital of China is Beijing.",
|
||||
"Gravity is a force that attracts two bodies towards each other. It gives weight to physical objects and is responsible for the movement of planets around the sun.",
|
||||
]
|
||||
|
||||
with torch.no_grad():
|
||||
# Encode the queries and documents. Note that queries benefit from using a prompt
|
||||
# Here we use the prompt called "query" stored under `model.prompts`, but you can
|
||||
# also pass your own prompt via the `prompt` argument
|
||||
query_embeddings = model.encode(queries, prompt_name="query")
|
||||
document_embeddings = model.encode(documents)
|
||||
|
||||
# Compute the (cosine) similarity between the query and document embeddings
|
||||
similarity = model.similarity(query_embeddings, document_embeddings)
|
||||
|
||||
print(similarity)
|
||||
# tensor([[0.7646, 0.1414], [0.1355, 0.6000]])
|
||||
```
|
||||
### Reranker Model
|
||||
|
||||
#### Transformers Usage
|
||||
|
||||
```python
|
||||
# Requires transformers>=4.51.0
|
||||
import torch
|
||||
from transformers import AutoModel, AutoTokenizer, AutoModelForCausalLM
|
||||
|
||||
def format_instruction(instruction, query, doc):
|
||||
if instruction is None:
|
||||
instruction = 'Given a web search query, retrieve relevant passages that answer the query'
|
||||
output = "<Instruct>: {instruction}\n<Query>: {query}\n<Document>: {doc}".format(instruction=instruction,query=query, doc=doc)
|
||||
return output
|
||||
|
||||
def process_inputs(pairs):
|
||||
inputs = tokenizer(
|
||||
pairs, padding=False, truncation='longest_first',
|
||||
return_attention_mask=False, max_length=max_length - len(prefix_tokens) - len(suffix_tokens)
|
||||
)
|
||||
for i, ele in enumerate(inputs['input_ids']):
|
||||
inputs['input_ids'][i] = prefix_tokens + ele + suffix_tokens
|
||||
inputs = tokenizer.pad(inputs, padding=True, return_tensors="pt", max_length=max_length)
|
||||
for key in inputs:
|
||||
inputs[key] = inputs[key].to(model.device)
|
||||
return inputs
|
||||
|
||||
@torch.no_grad()
|
||||
def compute_logits(inputs, **kwargs):
|
||||
batch_scores = model(**inputs).logits[:, -1, :]
|
||||
true_vector = batch_scores[:, token_true_id]
|
||||
false_vector = batch_scores[:, token_false_id]
|
||||
batch_scores = torch.stack([false_vector, true_vector], dim=1)
|
||||
batch_scores = torch.nn.functional.log_softmax(batch_scores, dim=1)
|
||||
scores = batch_scores[:, 1].exp().tolist()
|
||||
return scores
|
||||
|
||||
tokenizer = AutoTokenizer.from_pretrained("Qwen/Qwen3-Reranker-0.6B", padding_side='left')
|
||||
model = AutoModelForCausalLM.from_pretrained("Qwen/Qwen3-Reranker-0.6B").eval()
|
||||
|
||||
# We recommend enabling flash_attention_2 for better acceleration and memory saving.
|
||||
# model = AutoModelForCausalLM.from_pretrained("Qwen/Qwen3-Reranker-0.6B", torch_dtype=torch.float16, attn_implementation="flash_attention_2").cuda().eval()
|
||||
|
||||
token_false_id = tokenizer.convert_tokens_to_ids("no")
|
||||
token_true_id = tokenizer.convert_tokens_to_ids("yes")
|
||||
max_length = 8192
|
||||
|
||||
prefix = "<|im_start|>system\nJudge whether the Document meets the requirements based on the Query and the Instruct provided. Note that the answer can only be \"yes\" or \"no\".<|im_end|>\n<|im_start|>user\n"
|
||||
suffix = "<|im_end|>\n<|im_start|>assistant\n<think>\n\n</think>\n\n"
|
||||
prefix_tokens = tokenizer.encode(prefix, add_special_tokens=False)
|
||||
suffix_tokens = tokenizer.encode(suffix, add_special_tokens=False)
|
||||
|
||||
task = 'Given a web search query, retrieve relevant passages that answer the query'
|
||||
|
||||
queries = ["What is the capital of China?",
|
||||
"Explain gravity",
|
||||
]
|
||||
|
||||
documents = [
|
||||
"The capital of China is Beijing.",
|
||||
"Gravity is a force that attracts two bodies towards each other. It gives weight to physical objects and is responsible for the movement of planets around the sun.",
|
||||
]
|
||||
|
||||
pairs = [format_instruction(task, query, doc) for query, doc in zip(queries, documents)]
|
||||
|
||||
# Tokenize the input texts
|
||||
inputs = process_inputs(pairs)
|
||||
scores = compute_logits(inputs)
|
||||
|
||||
print("scores: ", scores)
|
||||
```
|
||||
|
||||
#### vLLM Usage
|
||||
|
||||
```python
|
||||
# Requires vllm>=0.8.5
|
||||
import logging
|
||||
from typing import Dict, Optional, List
|
||||
|
||||
import json
|
||||
import logging
|
||||
|
||||
import torch
|
||||
|
||||
from transformers import AutoTokenizer, is_torch_npu_available
|
||||
from vllm import LLM, SamplingParams
|
||||
from vllm.distributed.parallel_state import destroy_model_parallel
|
||||
import gc
|
||||
import math
|
||||
from vllm.inputs.data import TokensPrompt
|
||||
|
||||
def format_instruction(instruction, query, doc):
|
||||
text = [
|
||||
{"role": "system", "content": "Judge whether the Document meets the requirements based on the Query and the Instruct provided. Note that the answer can only be \"yes\" or \"no\"."},
|
||||
{"role": "user", "content": f"<Instruct>: {instruction}\n\n<Query>: {query}\n\n<Document>: {doc}"}
|
||||
]
|
||||
return text
|
||||
|
||||
def process_inputs(pairs, instruction, max_length, suffix_tokens):
|
||||
messages = [format_instruction(instruction, query, doc) for query, doc in pairs]
|
||||
messages = tokenizer.apply_chat_template(
|
||||
messages, tokenize=True, add_generation_prompt=False, enable_thinking=False
|
||||
)
|
||||
messages = [ele[:max_length] + suffix_tokens for ele in messages]
|
||||
messages = [TokensPrompt(prompt_token_ids=ele) for ele in messages]
|
||||
return messages
|
||||
|
||||
def compute_logits(model, messages, sampling_params, true_token, false_token):
|
||||
outputs = model.generate(messages, sampling_params, use_tqdm=False)
|
||||
scores = []
|
||||
for i in range(len(outputs)):
|
||||
final_logits = outputs[i].outputs[0].logprobs[-1]
|
||||
token_count = len(outputs[i].outputs[0].token_ids)
|
||||
if true_token not in final_logits:
|
||||
true_logit = -10
|
||||
else:
|
||||
true_logit = final_logits[true_token].logprob
|
||||
if false_token not in final_logits:
|
||||
false_logit = -10
|
||||
else:
|
||||
false_logit = final_logits[false_token].logprob
|
||||
true_score = math.exp(true_logit)
|
||||
false_score = math.exp(false_logit)
|
||||
score = true_score / (true_score + false_score)
|
||||
scores.append(score)
|
||||
return scores
|
||||
|
||||
number_of_gpu = torch.cuda.device_count()
|
||||
tokenizer = AutoTokenizer.from_pretrained('Qwen/Qwen3-Reranking-4B')
|
||||
model = LLM(model='Qwen/Qwen3-Reranking-0.6B', tensor_parallel_size=number_of_gpu, max_model_len=10000, enable_prefix_caching=True, gpu_memory_utilization=0.8)
|
||||
tokenizer.padding_side = "left"
|
||||
tokenizer.pad_token = tokenizer.eos_token
|
||||
suffix = "<|im_end|>\n<|im_start|>assistant\n<think>\n\n</think>\n\n"
|
||||
max_length=8192
|
||||
suffix_tokens = tokenizer.encode(suffix, add_special_tokens=False)
|
||||
true_token = tokenizer("yes", add_special_tokens=False).input_ids[0]
|
||||
false_token = tokenizer("no", add_special_tokens=False).input_ids[0]
|
||||
sampling_params = SamplingParams(temperature=0,
|
||||
max_tokens=1,
|
||||
logprobs=20,
|
||||
allowed_token_ids=[true_token, false_token],
|
||||
)
|
||||
|
||||
|
||||
task = 'Given a web search query, retrieve relevant passages that answer the query'
|
||||
queries = ["What is the capital of China?",
|
||||
"Explain gravity",
|
||||
]
|
||||
documents = [
|
||||
"The capital of China is Beijing.",
|
||||
"Gravity is a force that attracts two bodies towards each other. It gives weight to physical objects and is responsible for the movement of planets around the sun.",
|
||||
]
|
||||
|
||||
pairs = list(zip(queries, documents))
|
||||
inputs = process_inputs(pairs, task, max_length-len(suffix_tokens), suffix_tokens)
|
||||
scores = compute_logits(model, inputs, sampling_params, true_token, false_token)
|
||||
print('scores', scores)
|
||||
|
||||
destroy_model_parallel()
|
||||
```
|
||||
|
||||
|
||||
📌 **Tip**: We recommend that developers customize the `instruct` according to their specific scenarios, tasks, and languages. Our tests have shown that in most retrieval scenarios, not using an `instruct` on the query side can lead to a drop in retrieval performance by approximately 1% to 5%.
|
||||
|
||||
For more usage examples, see the code in the [examples]() sections.
|
||||
|
||||
## Training
|
||||
|
||||
The code and instructions for training Qwen3-Embedding models can be found in the [training docs](docs/training).
|
||||
|
||||
## Evaluation
|
||||
|
||||
The code for reproducing the following results is available in the [evaluation]() section.
|
||||
|
||||
### MTEB (Multilingual)
|
||||
|
||||
| Model | Size | Mean (Task) | Mean (Type) | Bitxt Mining | Class. | Clust. | Inst. Retri. | Multi. Class. | Pair. Class. | Rerank | Retri. | STS |
|
||||
|----------------------------------|:-------:|:-------------:|:-------------:|:--------------:|:--------:|:--------:|:--------------:|:---------------:|:--------------:|:--------:|:--------:|:------:|
|
||||
| NV-Embed-v2 | 7B | 56.29 | 49.58 | 57.84 | 57.29 | 40.80 | 1.04 | 18.63 | 78.94 | 63.82 | 56.72 | 71.10|
|
||||
| GritLM-7B | 7B | 60.92 | 53.74 | 70.53 | 61.83 | 49.75 | 3.45 | 22.77 | 79.94 | 63.78 | 58.31 | 73.33|
|
||||
| BGE-M3 | 0.6B | 59.56 | 52.18 | 79.11 | 60.35 | 40.88 | -3.11 | 20.1 | 80.76 | 62.79 | 54.60 | 74.12|
|
||||
| multilingual-e5-large-instruct | 0.6B | 63.22 | 55.08 | 80.13 | 64.94 | 50.75 | -0.40 | 22.91 | 80.86 | 62.61 | 57.12 | 76.81|
|
||||
| gte-zen-1.5B-instruct | 1.5B | 59.45 | 52.69 | 62.51 | 58.32 | 52.05 | 0.74 | 24.02 | 81.58 | 62.58 | 60.78 | 71.61|
|
||||
| gte-zen-7b-Instruct | 7B | 62.51 | 55.93 | 73.92 | 61.55 | 52.77 | 4.94 | 25.48 | 85.13 | 65.55 | 60.08 | 73.98|
|
||||
| text-embedding-3-large | - | 58.93 | 51.41 | 62.17 | 60.27 | 46.89 | -2.68 | 22.03 | 79.17 | 63.89 | 59.27 | 71.68|
|
||||
| Cohere-embed-multilingual-v3.0 | - | 61.12 | 53.23 | 70.50 | 62.95 | 46.89 | -1.89 | 22.74 | 79.88 | 64.07 | 59.16 | 74.80|
|
||||
| gemini-embedding-exp-03-07 | - | 68.37 | 59.59 | 79.28 | 71.82 | 54.59 | 5.18 | **29.16** | 83.63 | 65.58 | 67.71 | 79.40|
|
||||
| **Qwen3-Embedding-0.6B** | 0.6B | 64.33 | 56.00 | 72.22 | 66.83 | 52.33 | 5.09 | 24.59 | 80.83 | 61.41 | 64.64 | 76.17|
|
||||
| **Qwen3-Embedding-4B** | 4B | 69.45 | 60.86 | 79.36 | 72.33 | 57.15 | **11.56** | 26.77 | 85.05 | 65.08 | 69.60 | 80.86|
|
||||
| **Qwen3-Embedding-8B** | 8B | **70.58** | **61.69** | **80.89** | **74.00** | **57.65** | 10.06 | 28.66 | **86.40** | **65.63** | **70.88** | **81.08** |
|
||||
|
||||
> **Note**: For compared models, the scores are retrieved from MTEB online [leaderboard](https://huggingface.co/spaces/mteb/leaderboard) on June 6th, 2025.
|
||||
|
||||
### MTEB (Eng v2)
|
||||
|
||||
| MTEB English / Models | Param. | Mean(Task) | Mean(Type) | Class. | Clust. | Pair Class. | Rerank. | Retri. | STS | Summ. |
|
||||
|--------------------------------|:--------:|:------------:|:------------:|:--------:|:--------:|:-------------:|:---------:|:--------:|:-------:|:-------:|
|
||||
| multilingual-e5-large-instruct | 0.6B | 65.53 | 61.21 | 75.54 | 49.89 | 86.24 | 48.74 | 53.47 | 84.72 | 29.89 |
|
||||
| NV-Embed-v2 | 7.8B | 69.81 | 65.00 | 87.19 | 47.66 | 88.69 | 49.61 | 62.84 | 83.82 | 35.21 |
|
||||
| GritLM-7B | 7.2B | 67.07 | 63.22 | 81.25 | 50.82 | 87.29 | 49.59 | 54.95 | 83.03 | 35.65 |
|
||||
| gte-zen-1.5B-instruct | 1.5B | 67.20 | 63.26 | 85.84 | 53.54 | 87.52 | 49.25 | 50.25 | 82.51 | 33.94 |
|
||||
| stella_en_1.5B_v5 | 1.5B | 69.43 | 65.32 | 89.38 | 57.06 | 88.02 | 50.19 | 52.42 | 83.27 | 36.91 |
|
||||
| gte-zen-7B-instruct | 7.6B | 70.72 | 65.77 | 88.52 | 58.97 | 85.9 | 50.47 | 58.09 | 82.69 | 35.74 |
|
||||
| gemini-embedding-exp-03-07 | - | 73.3 | 67.67 | 90.05 | **59.39** | **87.7** | 48.59 | 64.35 | 85.29 | **38.28** |
|
||||
| **Qwen3-Embedding-0.6B** | 0.6B | 70.70 | 64.88 | 85.76 | 54.05 | 84.37 | 48.18 | 61.83 | 86.57 | 33.43 |
|
||||
| **Qwen3-Embedding-4B** | 4B | 74.60 | 68.10 | 89.84 | 57.51 | 87.01 | 50.76 | 68.46 | **88.72** | 34.39 |
|
||||
| **Qwen3-Embedding-8B** | 8B | **75.22** | **68.71** | **90.43** | 58.57 | 87.52 | **51.56** | **69.44** | 88.58 | 34.83 |
|
||||
|
||||
### C-MTEB (MTEB Chinese)
|
||||
|
||||
| C-MTEB | Param. | Mean(Task) | Mean(Type) | Class. | Clust. | Pair Class. | Rerank. | Retr. | STS |
|
||||
|------------------|--------|------------|------------|--------|--------|-------------|---------|-------|-------|
|
||||
| multilingual-e5-large-instruct | 0.6B | 58.08 | 58.24 | 69.80 | 48.23 | 64.52 | 57.45 | 63.65 | 45.81 |
|
||||
| bge-multilingual-gemma2 | 9B | 67.64 |68.52 | 75.31 | 59.30 | 86.67 | 68.28 | 73.73 | 55.19 |
|
||||
| gte-zen-1.5B-instruct | 1.5B | 67.12 | 67.79 | 72.53 | 54.61 | 79.5 | 68.21 | 71.86 | 60.05 |
|
||||
| gte-zen-7B-instruct | 7.6B | 71.62 | 72.19 | 75.77 | 66.06 | 81.16 | 69.24 | 75.70 | 65.20 |
|
||||
| ritrieve_zh_v1 | 0.3B | 72.71 | 73.85 | 76.88 | 66.5 | **85.98** | **72.86** | 76.97 | **63.92** |
|
||||
| **Qwen3-Embedding-0.6B** | 0.6B | 66.33 | 67.45 | 71.40 | 68.74 | 76.42 | 62.58 | 71.03 | 54.52 |
|
||||
| **Qwen3-Embedding-4B** | 4B | 72.27 | 73.51 | 75.46 | 77.89 | 83.34 | 66.05 | 77.03 | 61.26 |
|
||||
| **Qwen3-Embedding-8B** | 8B | **73.84** | **75.00** | **76.97** | **80.08** | 84.23 | 66.99 | **78.21** | 63.53 |
|
||||
|
||||
### Reranker
|
||||
| Model | Param | MTEB-R | CMTEB-R | MMTEB-R | MLDR | MTEB-Code | FollowIR |
|
||||
|------------------------------------|--------|---------|---------|---------|--------|-----------|----------|
|
||||
| **Qwen3-Embedding-0.6B** | 0.6B | 61.82 | 71.02 | 64.64 | 50.26 | 75.41 | 5.09 |
|
||||
| Jina-multilingual-reranker-v2-base | 0.3B | 58.22 | 63.37 | 63.73 | 39.66 | 58.98 | -0.68 |
|
||||
| gte-multilingual-reranker-base | 0.3B | 59.51 | 74.08 | 59.44 | 66.33 | 54.18 | -1.64 |
|
||||
| BGE-reranker-v2-m3 | 0.6B | 57.03 | 72.16 | 58.36 | 59.51 | 41.38 | -0.01 |
|
||||
| **Qwen3-Reranker-0.6B** | 0.6B | 65.80 | 71.31 | 66.36 | 67.28 | 73.42 | 5.41 |
|
||||
| **Qwen3-Reranker-4B** | 4B | **69.76** | 75.94 | 72.74 | 69.97 | 81.20 | **14.84** |
|
||||
| **Qwen3-Reranker-8B** | 8B | 69.02 | **77.45** | **72.94** | **70.19** | **81.22** | 8.05 |
|
||||
|
||||
> **Note**:
|
||||
> - Evaluation results for reranking models. We use the retrieval subsets of MTEB(eng, v2), MTEB(cmn, v1), MMTEB and MTEB (Code), which are MTEB-R, CMTEB-R, MMTEB-R and MTEB-Code.
|
||||
> - All scores are our runs based on the top-100 candidates retrieved by dense embedding model [Qwen3-Embedding-0.6B](https://huggingface.co/Qwen/Qwen3-Embedding-0.6B).
|
||||
|
||||
|
||||
## Citation
|
||||
If you find our work helpful, feel free to give us a cite.
|
||||
|
||||
```
|
||||
@article{qwen3embedding,
|
||||
title={Qwen3 Embedding: Advancing Text Embedding and Reranking Through Foundation Models},
|
||||
author={Zhang, Yanzhao and Li, Mingxin and Long, Dingkun and Zhang, Xin and Lin, Huan and Yang, Baosong and Xie, Pengjun and Yang, An and Liu, Dayiheng and Lin, Junyang and Huang, Fei and Zhou, Jingren},
|
||||
journal={arXiv preprint arXiv:2506.05176},
|
||||
year={2025}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Based On
|
||||
|
||||
**zen-embedding** is based on [BGE-Large-EN-v1.5](https://huggingface.co/BAAI/bge-large-en-v1.5)
|
||||
|
||||
We are grateful to the original authors for their excellent work and open-source contributions.
|
||||
|
||||
### Upstream Source
|
||||
- **Repository**: https://huggingface.co/BAAI/bge-large-en-v1.5
|
||||
- **Base Model**: BGE-Large-EN-v1.5
|
||||
- **License**: See original repository for license details
|
||||
|
||||
### Changes in Zen LM
|
||||
- Adapted for Zen AI ecosystem
|
||||
- Fine-tuned for specific use cases
|
||||
- Added training and inference scripts
|
||||
- Integrated with Zen Gym and Zen Engine
|
||||
- Enhanced documentation and examples
|
||||
|
||||
### Citation
|
||||
|
||||
If you use this model, please cite both the original work and Zen LM:
|
||||
|
||||
```bibtex
|
||||
@misc{zenlm2025zen-embedding,
|
||||
title={Zen LM: zen-embedding},
|
||||
author={Hanzo AI and Zoo Labs Foundation},
|
||||
year={2025},
|
||||
publisher={HuggingFace},
|
||||
howpublished={\url{https://huggingface.co/zenlm/zen-embedding}}
|
||||
}
|
||||
```
|
||||
|
||||
Please also cite the original upstream work - see https://huggingface.co/BAAI/bge-large-en-v1.5 for citation details.
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:37bf193fa101f19101bfad9c31d3eb0f786e247b7b1e5cb7f007d730eed1ddbd
|
||||
size 313
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:b5bf1f51fc45be473a54718cef92448d90a1be001bf9b9a44b8c7f10a19feaa9
|
||||
size 727
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:10667c72ddb772627bf1780cb7f86af8e2ae0032b8c243c731172064105c6961
|
||||
size 215
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:28396d421a2108acce96383f6a7de78008f7f1b17f807958f3c14c51dbfb65fb
|
||||
size 117
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:84e40c8e006c9b1d6c122e02cba9b02458120b5fb0c87b746c41e0207cf642cf
|
||||
size 349
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:def76fb086971c7867b829c23a26261e38d9d74e02139253b38aeb9df8b4b50a
|
||||
size 11423705
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:253153d0738ceb4c668d2eff957714dd2bea0b56de772a9fdccd96cbf517e6a0
|
||||
size 9706
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:ca10d7e9fb3ed18575dd1e277a2579c16d108e32f27439684afa0e10b1440910
|
||||
size 2776833
|
||||
@@ -0,0 +1,12 @@
|
||||
.PHONY: all clean
|
||||
|
||||
all: paper.pdf
|
||||
|
||||
paper.pdf: paper.tex references.bib
|
||||
pdflatex paper.tex
|
||||
bibtex paper
|
||||
pdflatex paper.tex
|
||||
pdflatex paper.tex
|
||||
|
||||
clean:
|
||||
rm -f *.aux *.bbl *.blg *.log *.out *.toc *.pdf
|
||||
@@ -0,0 +1,41 @@
|
||||
\documentclass{article}
|
||||
\usepackage[utf8]{inputenc}
|
||||
\usepackage{amsmath}
|
||||
\usepackage{graphicx}
|
||||
\usepackage{hyperref}
|
||||
\usepackage{natbib}
|
||||
|
||||
\title{MODEL_TITLE}
|
||||
\author{Hanzo AI \and Zoo Labs Foundation}
|
||||
\date{\today}
|
||||
|
||||
\begin{document}
|
||||
|
||||
\maketitle
|
||||
|
||||
\begin{abstract}
|
||||
MODEL_ABSTRACT
|
||||
\end{abstract}
|
||||
|
||||
\section{Introduction}
|
||||
MODEL_INTRODUCTION
|
||||
|
||||
\section{Architecture}
|
||||
MODEL_ARCHITECTURE
|
||||
|
||||
\section{Training}
|
||||
MODEL_TRAINING
|
||||
|
||||
\section{Evaluation}
|
||||
MODEL_EVALUATION
|
||||
|
||||
\section{Applications}
|
||||
MODEL_APPLICATIONS
|
||||
|
||||
\section{Conclusion}
|
||||
MODEL_CONCLUSION
|
||||
|
||||
\bibliographystyle{plain}
|
||||
\bibliography{references}
|
||||
|
||||
\end{document}
|
||||
@@ -0,0 +1,6 @@
|
||||
@article{zenai2025,
|
||||
title={Zen AI: Building Efficient AI for Everyone},
|
||||
author={Hanzo AI and Zoo Labs Foundation},
|
||||
journal={arXiv preprint},
|
||||
year={2025}
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
# SWIFT Training Support
|
||||
|
||||
The Qwen3-Embedding series models can be further trained when needed (for example, when users have domain-specific data). This document describes how to start training using the [SWIFT framework](https://github.com/modelscope/swift).
|
||||
|
||||
ModelScope SWIFT is a large model and multimodal large model training and deployment framework provided by the ModelScope community, with the following characteristics:
|
||||
|
||||
- Model Types: Supports 500+ pure text large models and 200+ multimodal large models, covering the entire process from training to deployment.
|
||||
- Hardware Support: Compatible with CPU, RTX series GPUs, T4/V100, A10/A100/H100, Ascend NPU, MPS, etc.
|
||||
- Training Methods: Supports full-parameter fine-tuning, LoRA, QLoRA, DoRA, and other techniques.
|
||||
- Distributed Training: Supports distributed training techniques such as DDP, device_map, DeepSpeed ZeRO-2/ZeRO-3, FSDP, and integrates Megatron's parallel techniques including tensor parallelism, pipeline parallelism, sequence parallelism, and expert parallelism.
|
||||
- RLHF Training: Supports human alignment methods for both pure text and multimodal large models, such as DPO, GRPO, DAPO, RM, PPO, KTO, etc.
|
||||
|
||||
Before starting training, please ensure your environment is properly configured.
|
||||
|
||||
```bash
|
||||
pip install ms-swift -U
|
||||
# Install from source
|
||||
pip install git+https://github.com/modelscope/ms-swift.git
|
||||
|
||||
pip install transformers -U
|
||||
|
||||
# Optional packages
|
||||
pip install deepspeed # multi-GPU training
|
||||
pip install liger-kernel # save GPU memory resources
|
||||
pip install flash-attn --no-build-isolation
|
||||
```
|
||||
|
||||
## Embedding Training
|
||||
|
||||
### Data Preparation
|
||||
|
||||
Different loss types affect the dataset input format. Using the following data format as an example:
|
||||
|
||||
```json
|
||||
# sample without hard negatives
|
||||
{"messages": [{"role": "user", "content": "sentence1"}], "positive_messages": [[{"role": "user", "content": "sentence2"}]]}
|
||||
# sample with multiple hard negatives
|
||||
{"messages": [{"role": "user", "content": "sentence1"}], "positive_messages": [[{"role": "user", "content": "sentence2"}]], "negative_messages": [[{"role": "user", "content": "sentence3"}], [{"role": "user", "content": "sentence4"}]]}
|
||||
```
|
||||
|
||||
The above data format is used when the loss is `infonce`. This loss uses `positive_messages (positive)` and `messages (anchor)` for positive training, and uses `negative_messages (negatives)` for negative training.
|
||||
|
||||
### Loss Types
|
||||
|
||||
It is recommended to use infonce loss (`--loss_type infonce`) for training.
|
||||
Infonce loss has several adjustable environment variables:
|
||||
|
||||
- INFONCE_TEMPERATURE: Temperature value for the infonce similarity matrix, default is `0.01`.
|
||||
- INFONCE_USE_BATCH: Whether to use other batches (including samples from other GPUs during DDP) as negatives, default is `True`. If set to `False`, only the rejected_response of the current sample is used as negatives, requiring each sample in the dataset to have at least one negative sample.
|
||||
- INFONCE_HARD_NEGATIVES: Pads negatives (repeated sampling) or truncates them to ensure the same number of negatives for each sample. Default is `False`.
|
||||
- INFONCE_MASK_FAKE_NEGATIVE: If negatives exist with similarity greater than positive similarity + `0.1`, mask them to prevent false negatives. Default is `False`.
|
||||
|
||||
By default, each row in the dataset can have any number of negative_messages or none at all.
|
||||
|
||||
Other loss types can also be used for training, such as `--loss_type cosine_similarity`, where the dataset format is different:
|
||||
|
||||
```json
|
||||
{"messages": [{"role": "user", "content": "sentence1"}], "positive_messages": [[{"role": "user", "content": "sentence2"}]], "label": 0.8}
|
||||
```
|
||||
|
||||
Under this loss, the label field is a float type marking the similarity value between two sentences.
|
||||
|
||||
Other types of losses are also supported. A complete introduction to losses and data formats can be found [here](https://github.com/modelscope/ms-swift/blob/main/docs/source_en/BestPractices/Embedding.md).
|
||||
|
||||
### Complete Training Command
|
||||
|
||||
Using infonce loss as an example, the complete training command is as follows:
|
||||
|
||||
```shell
|
||||
nproc_per_node=8
|
||||
NPROC_PER_NODE=$nproc_per_node \
|
||||
swift sft \
|
||||
--model Qwen/Qwen3-Embedding-0.6B \
|
||||
--task_type embedding \
|
||||
--model_type qwen3_emb \
|
||||
--train_type full \
|
||||
--dataset sentence-transformers/stsb:positive \
|
||||
--split_dataset_ratio 0.05 \
|
||||
--eval_strategy steps \
|
||||
--output_dir output \
|
||||
--eval_steps 20 \
|
||||
--num_train_epochs 5 \
|
||||
--save_steps 20 \
|
||||
--per_device_train_batch_size 4 \
|
||||
--per_device_eval_batch_size 4 \
|
||||
--gradient_accumulation_steps 4 \
|
||||
--learning_rate 6e-6 \
|
||||
--loss_type infonce \
|
||||
--label_names labels \
|
||||
--dataloader_drop_last true \
|
||||
--deepspeed zero3
|
||||
```
|
||||
|
||||
## Reranker Training
|
||||
|
||||
### Data Preparation
|
||||
|
||||
The reranker training data format is similar to embedding training but focuses on ranking relationships between query-document pairs. Using the following data format as an example:
|
||||
|
||||
```json lines
|
||||
{"messages": [{"role": "user", "content": "query"}], "positive_messages": [[{"role": "assistant", "content": "relevant_doc1"}],[{"role": "assistant", "content": "relevant_doc2"}]], "negative_messages": [[{"role": "assistant", "content": "irrelevant_doc1"}],[{"role": "assistant", "content": "irrelevant_doc2"}], ...]}
|
||||
```
|
||||
|
||||
**Field Description:**
|
||||
- `messages`: Query text
|
||||
- `positive_messages`: List of positive documents relevant to the query, supports multiple positive examples
|
||||
- `negative_messages`: List of negative documents irrelevant to the query, supports multiple negative examples
|
||||
|
||||
**Environment Variable Configuration:**
|
||||
- `MAX_POSITIVE_SAMPLES`: Maximum number of positive examples per query (default: 1)
|
||||
- `MAX_NEGATIVE_SAMPLES`: Maximum number of negative examples per query (default: 7)
|
||||
|
||||
> By default, `MAX_POSITIVE_SAMPLES` positive examples and `MAX_NEGATIVE_SAMPLES` negative examples will be extracted from each data item. Each positive example will be grouped with `MAX_NEGATIVE_SAMPLES` negative examples to form a group. Therefore, each data item will be expanded into `MAX_POSITIVE_SAMPLES`x`(1 + MAX_NEGATIVE_SAMPLES)` data points.
|
||||
> If the number of positive/negative examples in the data is insufficient, all positive/negative examples will be used. If the number of positive and negative examples in the data exceeds `MAX_POSITIVE_SAMPLES` and `MAX_NEGATIVE_SAMPLES`, random sampling will be performed.
|
||||
> **IMPORTANT**: The expanded data will be placed in the same batch. Therefore, the effective batch size on each device will be `per_device_train_batch_size` × `MAX_POSITIVE_SAMPLES` × (1 + `MAX_NEGATIVE_SAMPLES`). Please adjust your `per_device_train_batch_size` accordingly to avoid out-of-memory errors.
|
||||
|
||||
### Loss Types
|
||||
|
||||
SWIFT supports two loss types for reranker training: pointwise loss and listwise loss.
|
||||
|
||||
#### Pointwise Loss
|
||||
Treats ranking as binary classification for each query-document pair:
|
||||
- **Loss Function**: Binary cross-entropy
|
||||
- **Approach**: Independent judgment for each pair
|
||||
- **Environment Variables**:
|
||||
- `GENERATIVE_RERANKER_POSITIVE_TOKEN`: Positive token (default: "yes")
|
||||
- `GENERATIVE_RERANKER_NEGATIVE_TOKEN`: Negative token (default: "no")
|
||||
|
||||
#### Listwise Loss
|
||||
Treats ranking as multi-classification among candidate documents:
|
||||
- **Loss Function**: Multi-class cross-entropy
|
||||
- **Approach**: Learn relative ranking relationships
|
||||
- **Environment Variables**:
|
||||
- `LISTWISE_RERANKER_TEMPERATURE`: Listwise temperature (default: 1.0)
|
||||
- `LISTWISE_RERANKER_MIN_GROUP_SIZE`: Minimum group size (default: 2)
|
||||
|
||||
### Complete Training Command
|
||||
|
||||
Example for training a Qwen3-Reranker model using pointwise loss:
|
||||
|
||||
```shell
|
||||
nproc_per_node=4
|
||||
NPROC_PER_NODE=$nproc_per_node \
|
||||
swift sft \
|
||||
--model Qwen/Qwen3-Reranker-4B \
|
||||
--task_type generative_reranker \
|
||||
--loss_type generative_reranker \
|
||||
--train_type full \
|
||||
--dataset MTEB/scidocs-reranking \
|
||||
--split_dataset_ratio 0.05 \
|
||||
--eval_strategy steps \
|
||||
--output_dir output \
|
||||
--eval_steps 100 \
|
||||
--num_train_epochs 1 \
|
||||
--save_steps 200 \
|
||||
--per_device_train_batch_size 2 \
|
||||
--per_device_eval_batch_size 2 \
|
||||
--gradient_accumulation_steps 8 \
|
||||
--learning_rate 6e-6 \
|
||||
--label_names labels \
|
||||
--dataloader_drop_last true
|
||||
```
|
||||
|
||||
## Inference and Deployment
|
||||
|
||||
SWIFT now supports for both embedding and reranker models. Refer to the official examples for full options:
|
||||
- Embedding example: [ms-swift/examples/deploy/embedding](https://github.com/modelscope/ms-swift/tree/main/examples/deploy/embedding)
|
||||
- Reranker example: [ms-swift/examples/deploy/reranker](https://github.com/modelscope/ms-swift/tree/main/examples/deploy/reranker)
|
||||
@@ -0,0 +1,31 @@
|
||||
"""
|
||||
Basic usage example for MODEL_NAME
|
||||
"""
|
||||
from transformers import AutoModelForCausalLM, AutoTokenizer
|
||||
|
||||
|
||||
def main():
|
||||
# Load model and tokenizer
|
||||
model_name = "zenlm/MODEL_NAME"
|
||||
print(f"Loading {model_name}...")
|
||||
|
||||
model = AutoModelForCausalLM.from_pretrained(model_name)
|
||||
tokenizer = AutoTokenizer.from_pretrained(model_name)
|
||||
|
||||
# Example prompts
|
||||
prompts = [
|
||||
"What is the meaning of life?",
|
||||
"Explain quantum computing in simple terms.",
|
||||
"Write a haiku about AI."
|
||||
]
|
||||
|
||||
for prompt in prompts:
|
||||
print(f"\nPrompt: {prompt}")
|
||||
inputs = tokenizer(prompt, return_tensors="pt")
|
||||
outputs = model.generate(**inputs, max_new_tokens=100)
|
||||
response = tokenizer.decode(outputs[0], skip_special_tokens=True)
|
||||
print(f"Response: {response}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,72 @@
|
||||
#coding:utf8
|
||||
import os
|
||||
from typing import Dict, Optional, List, Union
|
||||
import torch
|
||||
from torch import nn
|
||||
import torch.nn.functional as F
|
||||
|
||||
from torch import Tensor
|
||||
from transformers import AutoTokenizer, AutoModel
|
||||
from transformers.utils import is_flash_attn_2_available
|
||||
import numpy as np
|
||||
from collections import defaultdict
|
||||
|
||||
class Qwen3Embedding():
|
||||
def __init__(self, model_name_or_path, instruction=None, use_fp16: bool = True, use_cuda: bool = True, max_length=8192):
|
||||
if instruction is None:
|
||||
instruction = 'Given a web search query, retrieve relevant passages that answer the query'
|
||||
self.instruction = instruction
|
||||
if is_flash_attn_2_available() and use_cuda:
|
||||
self.model = AutoModel.from_pretrained(model_name_or_path, trust_remote_code=True, attn_implementation="flash_attention_2", torch_dtype=torch.float16)
|
||||
else:
|
||||
self.model = AutoModel.from_pretrained(model_name_or_path, trust_remote_code=True, torch_dtype=torch.float16)
|
||||
if use_cuda:
|
||||
self.model = self.model.cuda()
|
||||
self.tokenizer = AutoTokenizer.from_pretrained(model_name_or_path, trust_remote_code=True, padding_side='left')
|
||||
self.max_length=max_length
|
||||
|
||||
def last_token_pool(self, last_hidden_states: Tensor,
|
||||
attention_mask: Tensor) -> Tensor:
|
||||
left_padding = (attention_mask[:, -1].sum() == attention_mask.shape[0])
|
||||
if left_padding:
|
||||
return last_hidden_states[:, -1]
|
||||
else:
|
||||
sequence_lengths = attention_mask.sum(dim=1) - 1
|
||||
batch_size = last_hidden_states.shape[0]
|
||||
return last_hidden_states[torch.arange(batch_size, device=last_hidden_states.device), sequence_lengths]
|
||||
|
||||
def get_detailed_instruct(self, task_description: str, query: str) -> str:
|
||||
if task_description is None:
|
||||
task_description = self.instruction
|
||||
return f'Instruct: {task_description}\nQuery:{query}'
|
||||
|
||||
def encode(self, sentences: Union[List[str], str], is_query: bool = False, instruction=None, dim: int = -1):
|
||||
if isinstance(sentences, str):
|
||||
sentences = [sentences]
|
||||
if is_query:
|
||||
sentences = [self.get_detailed_instruct(instruction, sent) for sent in sentences]
|
||||
inputs = self.tokenizer(sentences, padding=True, truncation=True, max_length=self.max_length, return_tensors='pt')
|
||||
inputs.to(self.model.device)
|
||||
with torch.no_grad():
|
||||
model_outputs = self.model(**inputs)
|
||||
output = self.last_token_pool(model_outputs.last_hidden_state, inputs['attention_mask'])
|
||||
if dim != -1:
|
||||
output = output[:, :dim]
|
||||
output = F.normalize(output, p=2, dim=1)
|
||||
return output
|
||||
|
||||
if __name__ == "__main__":
|
||||
model_path = "Qwen/Qwen3-Embedding-0.6B"
|
||||
model = Qwen3Embedding(model_path)
|
||||
queries = ['What is the capital of China?', 'Explain gravity']
|
||||
documents = [
|
||||
"The capital of China is Beijing.",
|
||||
"Gravity is a force that attracts two bodies towards each other. It gives weight to physical objects and is responsible for the movement of planets around the sun."
|
||||
]
|
||||
dim = 1024
|
||||
query_outputs = model.encode(queries, is_query=True, dim=dim)
|
||||
doc_outputs = model.encode(documents, dim=dim)
|
||||
print('query outputs', query_outputs)
|
||||
print('doc outputs', doc_outputs)
|
||||
scores = (query_outputs @ doc_outputs.T) * 100
|
||||
print(scores.tolist())
|
||||
@@ -0,0 +1,51 @@
|
||||
#coding:utf8
|
||||
from typing import Dict, Optional, List, Union
|
||||
import torch
|
||||
import vllm
|
||||
from vllm import LLM, PoolingParams
|
||||
from vllm.distributed.parallel_state import destroy_model_parallel
|
||||
|
||||
class Qwen3EmbeddingVllm():
|
||||
def __init__(self, model_name_or_path, instruction=None, max_length=8192):
|
||||
if instruction is None:
|
||||
instruction = 'Given a web search query, retrieve relevant passages that answer the query'
|
||||
self.instruction = instruction
|
||||
self.model = LLM(model=model_name_or_path, task="embed", hf_overrides={"is_matryoshka": True})
|
||||
|
||||
def get_detailed_instruct(self, task_description: str, query: str) -> str:
|
||||
if task_description is None:
|
||||
task_description = self.instruction
|
||||
return f'Instruct: {task_description}\nQuery:{query}'
|
||||
|
||||
def encode(self, sentences: Union[List[str], str], is_query: bool = False, instruction=None, dim: int = -1):
|
||||
if isinstance(sentences, str):
|
||||
sentences = [sentences]
|
||||
if is_query:
|
||||
sentences = [self.get_detailed_instruct(instruction, sent) for sent in sentences]
|
||||
if dim > 0:
|
||||
output = self.model.embed(sentences,pooling_params=PoolingParams(dimensions=dim))
|
||||
else:
|
||||
output = self.model.embed(sentences)
|
||||
output = torch.tensor([o.outputs.embedding for o in output])
|
||||
return output
|
||||
|
||||
|
||||
def stop(self):
|
||||
destroy_model_parallel()
|
||||
|
||||
if __name__ == "__main__":
|
||||
model_path = "Qwen/Qwen3-Embedding-0.6B"
|
||||
model = Qwen3EmbeddingVllm(model_path)
|
||||
queries = ['What is the capital of China?', 'Explain gravity']
|
||||
documents = [
|
||||
"The capital of China is Beijing.",
|
||||
"Gravity is a force that attracts two bodies towards each other. It gives weight to physical objects and is responsible for the movement of planets around the sun."
|
||||
]
|
||||
dim = 1024
|
||||
query_outputs = model.encode(queries, is_query=True, dim=dim)
|
||||
doc_outputs = model.encode(documents, dim=dim)
|
||||
print('query outputs', query_outputs)
|
||||
print('doc outputs', doc_outputs)
|
||||
scores = (query_outputs @ doc_outputs.T) * 100
|
||||
print(scores.tolist())
|
||||
model.stop()
|
||||
@@ -0,0 +1,103 @@
|
||||
import logging
|
||||
from typing import Dict, Optional, List
|
||||
import os
|
||||
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import queue
|
||||
import sys
|
||||
|
||||
from collections import defaultdict
|
||||
from contextlib import nullcontext
|
||||
from dataclasses import dataclass, field
|
||||
from pathlib import Path
|
||||
from tqdm import tqdm
|
||||
from typing import Union, List, Tuple, Any
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
from torch import Tensor, nn
|
||||
import torch.nn.functional as F
|
||||
from torch.utils.data._utils.worker import ManagerWatchdog
|
||||
|
||||
from tqdm import tqdm
|
||||
from transformers import AutoTokenizer, AutoModelForCausalLM, AutoModelForSequenceClassification, AutoModel, is_torch_npu_available
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Qwen3Reranker:
|
||||
def __init__(
|
||||
self,
|
||||
model_name_or_path: str,
|
||||
max_length: int = 4096,
|
||||
instruction=None,
|
||||
attn_type='causal',
|
||||
) -> None:
|
||||
n_gpu = torch.cuda.device_count()
|
||||
self.max_length=max_length
|
||||
self.tokenizer = AutoTokenizer.from_pretrained(model_name_or_path, trust_remote_code=True, padding_side='left')
|
||||
self.lm = AutoModelForCausalLM.from_pretrained(model_name_or_path, trust_remote_code=True, torch_dtype=torch.float16, attn_implementation="flash_attention_2").cuda().eval()
|
||||
self.token_false_id = self.tokenizer.convert_tokens_to_ids("no")
|
||||
self.token_true_id = self.tokenizer.convert_tokens_to_ids("yes")
|
||||
|
||||
self.prefix = "<|im_start|>system\nJudge whether the Document meets the requirements based on the Query and the Instruct provided. Note that the answer can only be \"yes\" or \"no\".<|im_end|>\n<|im_start|>user\n"
|
||||
self.suffix = "<|im_end|>\n<|im_start|>assistant\n<think>\n\n</think>\n\n"
|
||||
|
||||
self.prefix_tokens = self.tokenizer.encode(self.prefix, add_special_tokens=False)
|
||||
self.suffix_tokens = self.tokenizer.encode(self.suffix, add_special_tokens=False)
|
||||
self.instruction = instruction
|
||||
if self.instruction is None:
|
||||
self.instruction = "Given the user query, retrieval the relevant passages"
|
||||
|
||||
def format_instruction(self, instruction, query, doc):
|
||||
if instruction is None:
|
||||
instruction = self.instruction
|
||||
output = "<Instruct>: {instruction}\n<Query>: {query}\n<Document>: {doc}".format(instruction=instruction,query=query, doc=doc)
|
||||
return output
|
||||
|
||||
def process_inputs(self, pairs):
|
||||
out = self.tokenizer(
|
||||
pairs, padding=False, truncation='longest_first',
|
||||
return_attention_mask=False, max_length=self.max_length - len(self.prefix_tokens) - len(self.suffix_tokens)
|
||||
)
|
||||
for i, ele in enumerate(out['input_ids']):
|
||||
out['input_ids'][i] = self.prefix_tokens + ele + self.suffix_tokens
|
||||
out = self.tokenizer.pad(out, padding=True, return_tensors="pt", max_length=self.max_length)
|
||||
for key in out:
|
||||
out[key] = out[key].to(self.lm.device)
|
||||
return out
|
||||
|
||||
@torch.no_grad()
|
||||
def compute_logits(self, inputs, **kwargs):
|
||||
|
||||
batch_scores = self.lm(**inputs).logits[:, -1, :]
|
||||
true_vector = batch_scores[:, self.token_true_id]
|
||||
false_vector = batch_scores[:, self.token_false_id]
|
||||
batch_scores = torch.stack([false_vector, true_vector], dim=1)
|
||||
batch_scores = torch.nn.functional.log_softmax(batch_scores, dim=1)
|
||||
scores = batch_scores[:, 1].exp().tolist()
|
||||
return scores
|
||||
|
||||
def compute_scores(
|
||||
self,
|
||||
pairs,
|
||||
instruction=None,
|
||||
**kwargs
|
||||
):
|
||||
pairs = [self.format_instruction(instruction, query, doc) for query, doc in pairs]
|
||||
inputs = self.process_inputs(pairs)
|
||||
scores = self.compute_logits(inputs)
|
||||
return scores
|
||||
|
||||
if __name__ == '__main__':
|
||||
model = Qwen3Reranker(model_name_or_path='Qwen/Qwen3-Reranker-0.6B', instruction="Retrieval document that can answer user's query", max_length=2048)
|
||||
queries = ['What is the capital of China?', 'Explain gravity']
|
||||
documents = [
|
||||
"The capital of China is Beijing.",
|
||||
"Gravity is a force that attracts two bodies towards each other. It gives weight to physical objects and is responsible for the movement of planets around the sun."
|
||||
]
|
||||
pairs = list(zip(queries, documents))
|
||||
instruction="Given the user query, retrieval the relevant passages"
|
||||
new_scores = model.compute_scores(pairs, instruction)
|
||||
print('scores', new_scores)
|
||||
@@ -0,0 +1,105 @@
|
||||
import logging
|
||||
|
||||
import json
|
||||
import logging
|
||||
|
||||
from collections import defaultdict
|
||||
from contextlib import nullcontext
|
||||
from dataclasses import dataclass, field
|
||||
from pathlib import Path
|
||||
from tqdm import tqdm
|
||||
from typing import Union, List, Tuple, Any
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
from torch import Tensor, nn
|
||||
import torch.nn.functional as F
|
||||
from torch.utils.data._utils.worker import ManagerWatchdog
|
||||
|
||||
from tqdm import tqdm
|
||||
from transformers import AutoTokenizer, AutoModelForCausalLM, AutoModelForSequenceClassification, AutoModel, is_torch_npu_available
|
||||
logger = logging.getLogger(__name__)
|
||||
from vllm import LLM, SamplingParams
|
||||
from vllm.distributed.parallel_state import destroy_model_parallel
|
||||
import gc
|
||||
import math
|
||||
from sentence_transformers import CrossEncoder, SentenceTransformer
|
||||
from vllm.inputs.data import TokensPrompt
|
||||
|
||||
|
||||
class Qwen3Rerankervllm(CrossEncoder):
|
||||
def __init__(self, model_name_or_path, instruction="Given the user query, retrieval the relevant passages", **kwargs):
|
||||
number_of_gpu=torch.cuda.device_count()
|
||||
self.instruction = instruction
|
||||
self.tokenizer = AutoTokenizer.from_pretrained(model_name_or_path)
|
||||
self.tokenizer.padding_side = "left"
|
||||
self.tokenizer.pad_token = self.tokenizer.eos_token
|
||||
self.suffix = "<|im_start|>assistant\n<think>\n\n</think>\n\n"
|
||||
self.max_length=kwargs.get('max_length', 8192)
|
||||
self.suffix_tokens = self.tokenizer.encode(self.suffix, add_special_tokens=False)
|
||||
self.true_token = self.tokenizer("yes", add_special_tokens=False).input_ids[0]
|
||||
self.false_token = self.tokenizer("no", add_special_tokens=False).input_ids[0]
|
||||
self.sampling_params = SamplingParams(temperature=0,
|
||||
top_p=0.95,
|
||||
max_tokens=1,
|
||||
logprobs=20,
|
||||
allowed_token_ids=[self.true_token,self.false_token],
|
||||
)
|
||||
self.lm = LLM(model=model_name_or_path, tensor_parallel_size=number_of_gpu, max_model_len=10000, enable_prefix_caching=True, distributed_executor_backend='ray', gpu_memory_utilization=0.8)
|
||||
|
||||
|
||||
|
||||
def format_instruction(self, instruction, query, doc):
|
||||
if isinstance(query, tuple):
|
||||
instruction = query[0]
|
||||
query = query[1]
|
||||
text = [
|
||||
{"role": "system", "content": "Judge whether the Document meets the requirements based on the Query and the Instruct provided. Note that the answer can only be \"yes\" or \"no\"."},
|
||||
{"role": "user", "content": f"<Instruct>: {instruction}\n\n<Query>: {query}\n\n<Document>: {doc}"}
|
||||
]
|
||||
return text
|
||||
|
||||
def compute_scores(self, pairs, **kwargs):
|
||||
messages = [self.format_instruction(self.instruction, query, doc) for query, doc in pairs]
|
||||
messages = self.tokenizer.apply_chat_template(
|
||||
messages, tokenize=True, add_generation_prompt=False, enable_thinking=False
|
||||
)
|
||||
messages = [ele[:self.max_length] + self.suffix_tokens for ele in messages]
|
||||
messages = [TokensPrompt(prompt_token_ids=ele) for ele in messages]
|
||||
outputs = self.lm.generate(messages, self.sampling_params, use_tqdm=False)
|
||||
scores = []
|
||||
for i in range(len(outputs)):
|
||||
final_logits = outputs[i].outputs[0].logprobs[-1]
|
||||
token_count = len(outputs[i].outputs[0].token_ids)
|
||||
if self.true_token not in final_logits:
|
||||
true_logit = -10
|
||||
else:
|
||||
true_logit = final_logits[self.true_token].logprob
|
||||
if self.false_token not in final_logits:
|
||||
false_logit = -10
|
||||
else:
|
||||
false_logit = final_logits[self.false_token].logprob
|
||||
true_score = math.exp(true_logit)
|
||||
false_score = math.exp(false_logit)
|
||||
score = true_score / (true_score + false_score)
|
||||
scores.append(score)
|
||||
|
||||
return scores
|
||||
|
||||
def stop(self):
|
||||
destroy_model_parallel()
|
||||
|
||||
if __name__ == '__main__':
|
||||
model = Qwen3Rerankervllm(model_name_or_path='Qwen/Qwen3-Reranker-0.6B', instruction="Retrieval document that can answer user's query", max_length=2048)
|
||||
queries = ['What is the capital of China?', 'Explain gravity']
|
||||
documents = [
|
||||
"The capital of China is Beijing.",
|
||||
"Gravity is a force that attracts two bodies towards each other. It gives weight to physical objects and is responsible for the movement of planets around the sun."
|
||||
]
|
||||
pairs = list(zip(queries, documents))
|
||||
new_scores = model.compute_scores(pairs)
|
||||
print('scores', new_scores)
|
||||
model.stop()
|
||||
|
||||
|
||||
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
[pytest]
|
||||
testpaths = tests
|
||||
python_files = test_*.py
|
||||
python_classes = Test*
|
||||
python_functions = test_*
|
||||
addopts = -v --tb=short --strict-markers
|
||||
markers =
|
||||
unit: Unit tests
|
||||
integration: Integration tests
|
||||
slow: Slow running tests
|
||||
Binary file not shown.
Executable
+36
@@ -0,0 +1,36 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Inference script for MODEL_NAME
|
||||
"""
|
||||
import argparse
|
||||
from transformers import AutoModelForCausalLM, AutoTokenizer
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Run inference with MODEL_NAME")
|
||||
parser.add_argument("--prompt", type=str, required=True, help="Input prompt")
|
||||
parser.add_argument("--max-tokens", type=int, default=200, help="Maximum tokens to generate")
|
||||
parser.add_argument("--temperature", type=float, default=0.7, help="Sampling temperature")
|
||||
parser.add_argument("--model", type=str, default="zenlm/MODEL_NAME", help="Model path")
|
||||
args = parser.parse_args()
|
||||
|
||||
print(f"Loading model: {args.model}")
|
||||
model = AutoModelForCausalLM.from_pretrained(args.model)
|
||||
tokenizer = AutoTokenizer.from_pretrained(args.model)
|
||||
|
||||
print(f"Prompt: {args.prompt}")
|
||||
inputs = tokenizer(args.prompt, return_tensors="pt")
|
||||
|
||||
outputs = model.generate(
|
||||
**inputs,
|
||||
max_new_tokens=args.max_tokens,
|
||||
temperature=args.temperature,
|
||||
do_sample=True
|
||||
)
|
||||
|
||||
text = tokenizer.decode(outputs[0], skip_special_tokens=True)
|
||||
print(f"\nResponse:\n{text}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Executable
+34
@@ -0,0 +1,34 @@
|
||||
#!/bin/bash
|
||||
# Training script for MODEL_NAME using Zen Gym
|
||||
|
||||
set -e
|
||||
|
||||
# Configuration
|
||||
MODEL_NAME="MODEL_NAME"
|
||||
DATASET="your/dataset"
|
||||
OUTPUT_DIR="./output"
|
||||
EPOCHS=3
|
||||
BATCH_SIZE=4
|
||||
LEARNING_RATE=2e-5
|
||||
|
||||
# Ensure Zen Gym is available
|
||||
if [ ! -d "../../gym" ]; then
|
||||
echo "Error: Zen Gym not found. Clone from https://github.com/zenlm/gym"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cd ../../gym
|
||||
|
||||
# Run training
|
||||
llamafactory-cli train \
|
||||
--model_name_or_path "zenlm/${MODEL_NAME}" \
|
||||
--dataset "${DATASET}" \
|
||||
--output_dir "${OUTPUT_DIR}" \
|
||||
--num_train_epochs "${EPOCHS}" \
|
||||
--per_device_train_batch_size "${BATCH_SIZE}" \
|
||||
--learning_rate "${LEARNING_RATE}" \
|
||||
--logging_steps 10 \
|
||||
--save_steps 100 \
|
||||
--do_train
|
||||
|
||||
echo "✅ Training complete! Model saved to ${OUTPUT_DIR}"
|
||||
@@ -0,0 +1,60 @@
|
||||
"""
|
||||
Basic tests for MODEL_NAME
|
||||
"""
|
||||
import pytest
|
||||
import torch
|
||||
from transformers import AutoModelForCausalLM, AutoTokenizer
|
||||
|
||||
|
||||
class TestModelLoading:
|
||||
"""Test model loading functionality"""
|
||||
|
||||
def test_model_exists(self):
|
||||
"""Test that model can be loaded from HuggingFace"""
|
||||
try:
|
||||
model = AutoModelForCausalLM.from_pretrained("zenlm/MODEL_NAME")
|
||||
assert model is not None
|
||||
except Exception as e:
|
||||
pytest.skip(f"Model not yet uploaded to HuggingFace: {e}")
|
||||
|
||||
def test_tokenizer_exists(self):
|
||||
"""Test that tokenizer can be loaded"""
|
||||
try:
|
||||
tokenizer = AutoTokenizer.from_pretrained("zenlm/MODEL_NAME")
|
||||
assert tokenizer is not None
|
||||
except Exception as e:
|
||||
pytest.skip(f"Tokenizer not yet uploaded to HuggingFace: {e}")
|
||||
|
||||
|
||||
class TestInference:
|
||||
"""Test inference functionality"""
|
||||
|
||||
@pytest.mark.slow
|
||||
def test_basic_generation(self):
|
||||
"""Test basic text generation"""
|
||||
try:
|
||||
model = AutoModelForCausalLM.from_pretrained("zenlm/MODEL_NAME")
|
||||
tokenizer = AutoTokenizer.from_pretrained("zenlm/MODEL_NAME")
|
||||
|
||||
inputs = tokenizer("Hello, world!", return_tensors="pt")
|
||||
outputs = model.generate(**inputs, max_new_tokens=20)
|
||||
text = tokenizer.decode(outputs[0])
|
||||
|
||||
assert len(text) > 0
|
||||
assert isinstance(text, str)
|
||||
except Exception as e:
|
||||
pytest.skip(f"Model not yet available: {e}")
|
||||
|
||||
|
||||
class TestModelProperties:
|
||||
"""Test model properties and configuration"""
|
||||
|
||||
def test_model_config(self):
|
||||
"""Test model configuration"""
|
||||
try:
|
||||
from transformers import AutoConfig
|
||||
config = AutoConfig.from_pretrained("zenlm/MODEL_NAME")
|
||||
assert config is not None
|
||||
assert hasattr(config, "vocab_size")
|
||||
except Exception as e:
|
||||
pytest.skip(f"Config not yet available: {e}")
|
||||
Executable
+83
@@ -0,0 +1,83 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Training script for Zen Embedding."""
|
||||
|
||||
import torch
|
||||
from transformers import (
|
||||
AutoModelForCausalLM,
|
||||
AutoTokenizer,
|
||||
TrainingArguments,
|
||||
Trainer,
|
||||
DataCollatorForLanguageModeling
|
||||
)
|
||||
from datasets import load_dataset
|
||||
import json
|
||||
|
||||
def train():
|
||||
"""Train zen-embedding."""
|
||||
print("🚀 Training Zen Embedding")
|
||||
print("=" * 60)
|
||||
|
||||
# Load model and tokenizer
|
||||
model = AutoModelForCausalLM.from_pretrained(
|
||||
"./base-model",
|
||||
torch_dtype=torch.bfloat16,
|
||||
device_map="auto"
|
||||
)
|
||||
|
||||
tokenizer = AutoTokenizer.from_pretrained("./base-model")
|
||||
tokenizer.pad_token = tokenizer.eos_token
|
||||
|
||||
# Load training data
|
||||
dataset = load_dataset('json', data_files='training_data.jsonl')['train']
|
||||
|
||||
def tokenize_function(examples):
|
||||
return tokenizer(
|
||||
examples["text"],
|
||||
padding="max_length",
|
||||
truncation=True,
|
||||
max_length=512
|
||||
)
|
||||
|
||||
tokenized_dataset = dataset.map(tokenize_function, batched=True)
|
||||
|
||||
# Training arguments
|
||||
training_args = TrainingArguments(
|
||||
output_dir="./finetuned",
|
||||
num_train_epochs=3,
|
||||
per_device_train_batch_size=1,
|
||||
gradient_accumulation_steps=4,
|
||||
warmup_steps=50,
|
||||
learning_rate=2e-5,
|
||||
fp16=False,
|
||||
bf16=True,
|
||||
logging_steps=10,
|
||||
save_strategy="epoch",
|
||||
push_to_hub=False,
|
||||
)
|
||||
|
||||
# Data collator
|
||||
data_collator = DataCollatorForLanguageModeling(
|
||||
tokenizer=tokenizer,
|
||||
mlm=False,
|
||||
)
|
||||
|
||||
# Trainer
|
||||
trainer = Trainer(
|
||||
model=model,
|
||||
args=training_args,
|
||||
train_dataset=tokenized_dataset,
|
||||
tokenizer=tokenizer,
|
||||
data_collator=data_collator,
|
||||
)
|
||||
|
||||
# Train
|
||||
trainer.train()
|
||||
|
||||
# Save
|
||||
trainer.save_model("./finetuned")
|
||||
tokenizer.save_pretrained("./finetuned")
|
||||
|
||||
print("✅ Training complete!")
|
||||
|
||||
if __name__ == "__main__":
|
||||
train()
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:a5c32705d9437589a100c4f4f29187ab41735ffb5c216bf85da5afce89ecf897
|
||||
size 461
|
||||
Reference in New Issue
Block a user