--- language: - zh - en license: apache-2.0 library_name: sentence-transformers tags: - text-embedding - sentence-transformers - feature-extraction - chinese - minimind - qwen3-embedding - matryoshka base_model: jingyaogong/minimind-3 pipeline_tag: feature-extraction ---
MiniMind-Embedding
# MiniMind-Embedding-Dense
![params](https://img.shields.io/badge/params-64M-blue) ![dim](https://img.shields.io/badge/dim-768-green) ![max_seq](https://img.shields.io/badge/max__seq-8192-orange) ![STS](https://img.shields.io/badge/C_MTEB%20STS-0.48-brightgreen)
A text embedding model built on the [MiniMind-3](https://huggingface.co/jingyaogong/minimind-3-pytorch) small language model (64M params), with the training pipeline fully aligned with [Qwen3-Embedding](https://arxiv.org/abs/2506.05176). Part of the [minimind-embedding](https://github.com/muzian666/minimind-embedding) project. ## πŸ“Œ Introduction This model turns the 64M MiniMind-3 causal language model into a text encoder using three key techniques: ### 1. Last-Token Pooling > πŸ’‘ *Think of a sentence as a queue of people. The last person (EOS) has "heard" everyone before them through causal attention, so they alone can summarize the whole sentence.*
![Last-Token Pooling](./images/concept_last_token_pool.png)
Instead of BERT-style bidirectional attention + mean pooling, we keep **causal (unidirectional) attention** and take the hidden state at the appended EOS token. The last token aggregates information from all preceding tokens via causal attention, naturally serving as a sentence summary. This maximizes reuse of pretrained weights. ### 2. InfoNCE + False-Negative Masking > πŸ’‘ *Contrastive learning is like a "find your friend" game: push the correct match closer, push strangers further away. But what if a "stranger" is actually your friend (false negative)? We detect and mask them!*
![InfoNCE Contrastive Learning](./images/concept_infonce.png)
The core loss is InfoNCE (contrastive). Qwen3-Embedding adds a **false-negative mask**: if a candidate negative is *more* similar to the query than the positive (beyond margin=0.1), it's likely a false negative and gets masked out.
![False-Negative Masking](./images/concept_false_neg_mask.png)
### 3. Matryoshka Representation Learning (MRL) > πŸ’‘ *Like Russian nesting dolls β€” the big doll (768-dim) contains smaller dolls (512/256/128/64-dim), each fully functional at its size.*
![Matryoshka MRL](./images/concept_mrl.png)
Trained so the vector works at any truncated dimension. Use 64-dim for fast coarse retrieval, 768-dim for precise re-ranking. ## πŸ—οΈ Model Details | Item | Value | |------|-------| | Architecture | MiniMind-3 (causal decoder-only Transformer) | | Parameters | 64M | | Hidden size | 768 | | Layers | 8 | | Attention heads | 8 (query) / 4 (KV, GQA) | | Vocab size | 6400 | | Embedding output dim | 768 (MRL: truncatable to 512/256/128/64) | | Max sequence length | 8192 (backbone supports 32768) | | Pooling | last-token | | Features | L2-normalized; MRL multi-dim | ## πŸŽ“ Training Details (3 Stages) | Stage | Data | Loss | Steps | Final Loss | Status | |-------|------|------|-------|------------|--------| | **1. Weakly-supervised** | T2Ranking triplets (90k) | Standard InfoNCE | 1,413 | 0.43 | βœ… Done | | **2. Supervised** | T2Ranking-15 (340k, 15 hard negs) | InfoNCE + false-neg mask + MRL | 63,768 (3 epochs) | 0.72 | βœ… Done | | **3. Model merging** | Last 5 Stage-2 checkpoints | SLERP | β€” | β€” | βœ… Done |
![Training Loss Curves](./images/full_training_loss.png) *Stage 1 (left): 1.16β†’0.43. Stage 2 (right): 1.47β†’0.72, 3 epochs with epoch boundaries marked.*
| Hyperparameter | Value | |----------------|-------| | Temperature Ο„ | 0.02 | | False-neg margin | 0.1 | | Hard negatives | 7 per query | | Batch size | 16 | | Learning rate | 1e-5 (Stage 2), 2e-4 (Stage 1) | | MRL dims | [768, 512, 256, 128, 64] | ## πŸ“Š Evaluation
![C-MTEB STS Scores](./images/sts_scores.png)
| Task | Samples | Score | |------|---------|:-----:| | ATEC | 20,000 | 0.265 | | BQ | 10,000 | 0.379 | | LCQMC | 12,500 | 0.631 | | STSB | 1,361 | 0.637 | | **Average** | | **0.478** |
![Model Comparison](./images/model_comparison.png)
| Model | Params | C-MTEB STS | |-------|--------|:----------:| | **This model** | **64M** | **0.478** | | BGE-small-zh | 24M | ~0.60 | | BGE-large-zh | 326M | ~0.66 | | Qwen3-Embedding-0.6B | 600M | ~0.66 | > **Honest note**: scores are bounded by the small vocab (6400) and limited Stage-1 data (90k vs Qwen3's 150M). The value is the **complete, reproducible** Qwen3-Embedding pipeline. ## πŸš€ Usage ### sentence-transformers ```python from sentence_transformers import SentenceTransformer from sentence_transformers.util import cos_sim model = SentenceTransformer("Muzian/minimind-embedding-dense") documents = ["ε€©η©Ίε‘ˆθ“θ‰²ζ˜―ε› δΈΊι˜³ε…‰ζ•£ε°„γ€‚", "ηΊ’ηƒ§θ‚‰ηš„εšζ³•ζ˜―..."] doc_embs = model.encode(documents, normalize_embeddings=True) queries = ["ε€©η©ΊδΈΊδ»€δΉˆζ˜―θ“θ‰²ηš„"] query_embs = model.encode(queries, prompt_name="query", normalize_embeddings=True) scores = cos_sim(query_embs, doc_embs) ``` ### Matryoshka (truncate dimensions) ```python # Use only first 256 dims (smaller index, faster search) emb_256 = embedding[:, :256] emb_256 = F.normalize(emb_256, p=2, dim=1) ``` ## ⚠️ Limitations 1. **Small vocabulary (6400)** β€” weak Chinese compression, primary bottleneck 2. **Limited Stage-1 data** (90k vs Qwen3's 150M) β€” scaling is the top improvement direction 3. SLERP merging shows minimal gain on small models (Stage2 β‰ˆ Stage3) ## πŸ™ Acknowledgements - Base: [MiniMind](https://github.com/jingyaogong/minimind) by @jingyaogong - Route: [Qwen3-Embedding](https://arxiv.org/abs/2506.05176) - Data: [T2Ranking](https://huggingface.co/datasets/THUIR/T2Ranking) - Eval: [MTEB](https://github.com/embeddings-benchmark/mteb) ## πŸ“„ License Apache 2.0