# Qdrant

> **用于语义搜索和 RAG 应用的高性能向量数据库 — GPU 加速索引**

Qdrant 是一个用 Rust 编写的开源、生产就绪的向量数据库。它在数十亿向量上提供快速的近似最近邻（ANN）搜索，并具备高级过滤、载荷索引和多向量支持。它是许多生产级 RAG（检索增强生成）管道和语义搜索应用的核心组件。

**GitHub：** [qdrant/qdrant](https://github.com/qdrant/qdrant) — 22K+ ⭐

***

## 为什么选择 Qdrant？

| 功能          | Qdrant | Pinecone | Weaviate | Chroma   |
| ----------- | ------ | -------- | -------- | -------- |
| 开源          | ✅      | ❌        | ✅        | ✅        |
| Rust 性能     | ✅      | —        | ❌ Go     | ❌ Python |
| 查询时过滤       | ✅ 高级   | ✅ 基本     | ✅        | ✅ 基本     |
| 多向量         | ✅      | ❌        | ✅        | ❌        |
| 基于磁盘的 HNSW  | ✅      | ✅        | ✅        | ❌        |
| 载荷索引        | ✅      | 有限       | ✅        | 有限       |
| gRPC + REST | ✅ 两者   | ✅ REST   | ✅        | REST     |
| 自托管         | ✅      | ❌ 仅云端    | ✅        | ✅        |

{% hint style="success" %}
**Qdrant 是用 Rust 编写的** — 在保证内存安全的同时提供接近 C 级的性能。基准测试表明，在高负载场景下 Qdrant 一直 **快 1.5–3 倍** 比基于 Python 的替代方案（如 Chroma）更快。
{% endhint %}

***

## 主要使用场景

* **RAG（检索增强生成）** — 为大型语言模型的提示找到相关上下文
* **语义搜索** — 按含义而非仅按关键词搜索
* **推荐系统** — 通过嵌入相似性查找相似项目
* **重复检测** — 识别近似重复内容
* **异常检测** — 查找远离簇中心的向量
* **图像/音频相似性搜索** — 多模态检索

***

## 先决条件

* 具有 GPU 租用的 Clore.ai 帐户
* 对 REST API 或 Python 的基本熟悉
* 您选择的嵌入模型（OpenAI、SentenceTransformers 等）

***

## 步骤 1 — 在 Clore.ai 上租用服务器

Qdrant 在提供服务时主要受 CPU/RAM 限制，但在以下情况受益于 GPU：

* 在提供服务的同时生成嵌入（嵌入模型与服务在同一服务器上）
* 大规模批量索引操作

1. 前往 [clore.ai](https://clore.ai) → **市场**
2. 对于 **嵌入 + 服务 组合：** 配备 32GB+ 显存的 RTX 3090/4090
3. 对于 **仅提供服务：** 带快速 NVMe 存储的 CPU 优化服务器

{% hint style="info" %}
**内存规划：**

* 每个 1536 维的 float32 向量 = 6KB
* 100 万个向量 = 约 6GB RAM
* 1000 万个向量 = 约 60GB RAM
* 对非常大的集合启用磁盘存储
  {% endhint %}

***

## 步骤 2 — 部署 Qdrant 容器

**Docker 镜像：**

```
qdrant/qdrant:latest
```

**端口：**

```
22
6333
6334
```

* **端口 6333：** REST API（HTTP）
* **端口 6334：** gRPC API（对批量操作具有更高性能）

**环境变量：**

```
QDRANT__SERVICE__HTTP_PORT=6333
QDRANT__SERVICE__GRPC_PORT=6334
QDRANT__LOG_LEVEL=INFO
QDRANT__STORAGE__STORAGE_PATH=/qdrant/storage
```

**卷/持久化存储：** 挂载 `/qdrant/storage` 以实现数据持久化。若不挂载，容器重启后数据将丢失。

***

## 步骤 3 — 验证 Qdrant 是否运行

```bash
ssh root@<server-ip> -p <ssh-port>

# 检查 Qdrant 是否运行
curl http://localhost:6333/

# 预期响应：
# {"title":"qdrant - vector search engine","version":"..."}

# 检查健康状态
curl http://localhost:6333/healthz

# 检查集群信息
curl http://localhost:6333/cluster
```

***

## 步骤 4 — 安装 Python 客户端

```bash
# 安装 Qdrant Python 客户端和嵌入工具
pip install qdrant-client sentence-transformers openai numpy

# 验证连接
python3 << 'EOF'
from qdrant_client import QdrantClient

client = QdrantClient("localhost", port=6333)
print(f"Qdrant connected: {client.get_collections()}")
EOF
```

***

## 步骤 5 — 创建集合

集合是具有固定维度的一组命名向量。

```python
from qdrant_client import QdrantClient
from qdrant_client.models import (
    Distance,
    VectorParams,
    HnswConfigDiff,
    OptimizersConfigDiff,
    QuantizationConfig,
    ScalarQuantizationConfig,
    ScalarType
)

client = QdrantClient("localhost", port=6333)

# 为 OpenAI text-embedding-3-small（1536 维）创建集合
client.create_collection(
    collection_name="documents",
    vectors_config=VectorParams(
        size=1536,           # 向量维度（与您的嵌入模型匹配）
        distance=Distance.COSINE,  # 可选：COSINE、EUCLID、DOT
        on_disk=False        # 对于非常大的集合将其设为 True
    ),
    hnsw_config=HnswConfigDiff(
        m=16,                # HNSW 图连接度（更高 = 更好召回，但占用更多内存）
        ef_construct=100,    # 构建时的搜索深度（更高 = 更好质量，但索引更慢）
        full_scan_threshold=10000  # 在此数量以下使用暴力搜索
    ),
    optimizers_config=OptimizersConfigDiff(
        indexing_threshold=20000  # 在达到该向量数后开始 HNSW 索引
    ),
    quantization_config=QuantizationConfig(
        scalar=ScalarQuantizationConfig(
            type=ScalarType.INT8,  # 将向量压缩为 INT8（内存减少约 4 倍）
            quantile=0.99,
            always_ram=True        # 将量化索引保留在内存中
        )
    )
)

print("Collection created!")
print(client.get_collection("documents"))
```

### 用于 SentenceTransformers 的集合（384 维）

```python
client.create_collection(
    collection_name="embeddings_384",
    vectors_config=VectorParams(
        size=384,              # all-MiniLM-L6-v2 的输出维度
        distance=Distance.COSINE
    )
)
```

***

## 步骤 6 — 索引文档

### 使用 OpenAI 嵌入

```python
from qdrant_client import QdrantClient
from qdrant_client.models import PointStruct
from openai import OpenAI
import uuid

client = QdrantClient("localhost", port=6333)
openai_client = OpenAI(api_key="your-openai-api-key")

def get_embeddings(texts: list[str], batch_size: int = 100) -> list[list[float]]:
    """以批次方式生成嵌入。"""
    all_embeddings = []
    for i in range(0, len(texts), batch_size):
        batch = texts[i:i + batch_size]
        response = openai_client.embeddings.create(
            model="text-embedding-3-small",
            input=batch
        )
        all_embeddings.extend([e.embedding for e in response.data])
    return all_embeddings

# 示例文档
documents = [
    {
        "id": str(uuid.uuid4()),
        "text": "Qdrant is a vector database built in Rust for high performance.",
        "source": "documentation",
        "category": "database",
        "year": 2024
    },
    {
        "id": str(uuid.uuid4()),
        "text": "Machine learning models convert text to dense vector representations.",
        "source": "article",
        "category": "ml",
        "year": 2023
    },
    # 添加更多文档...
]

# 生成嵌入
texts = [doc["text"] for doc in documents]
embeddings = get_embeddings(texts)

# Upsert 到 Qdrant
points = [
    PointStruct(
        id=str(uuid.uuid4()),
        vector=embedding,
        payload={
            "text": doc["text"],
            "source": doc["source"],
            "category": doc["category"],
            "year": doc["year"]
        }
    )
    for doc, embedding in zip(documents, embeddings)
]

client.upsert(
    collection_name="documents",
    points=points,
    wait=True  # 等待索引完成
)

print(f"Indexed {len(points)} documents!")
```

### 使用 SentenceTransformers（本地、GPU 加速）

```python
from sentence_transformers import SentenceTransformer
from qdrant_client import QdrantClient
from qdrant_client.models import PointStruct
import torch
import uuid

# 在 GPU 上加载嵌入模型
model = SentenceTransformer("all-MiniLM-L6-v2", device="cuda")

client = QdrantClient("localhost", port=6333)

documents = [
    {"text": "How do I set up Qdrant on a GPU server?", "tag": "setup"},
    {"text": "Vector databases store high-dimensional embeddings for similarity search.", "tag": "concept"},
    {"text": "HNSW algorithm provides approximate nearest neighbor search.", "tag": "algorithm"},
    # ... 更多文档
]

# GPU 加速的批量编码
texts = [doc["text"] for doc in documents]
embeddings = model.encode(
    texts,
    batch_size=256,       # 为 GPU 提高效率使用较大的批量大小
    show_progress_bar=True,
    normalize_embeddings=True  # 为余弦相似度进行归一化
)

# 在 Qdrant 中索引
points = [
    PointStruct(
        id=str(uuid.uuid4()),
        vector=embedding.tolist(),
        payload=doc
    )
    for doc, embedding in zip(documents, embeddings)
]

# 批量 upsert（更高效）
BATCH_SIZE = 1000
for i in range(0, len(points), BATCH_SIZE):
    batch = points[i:i + BATCH_SIZE]
    client.upsert(collection_name="embeddings_384", points=batch)
    print(f"Indexed {min(i + BATCH_SIZE, len(points))}/{len(points)}")
```

***

## 步骤 7 — 搜索与查询

### 基础语义搜索

```python
from qdrant_client import QdrantClient
from sentence_transformers import SentenceTransformer

client = QdrantClient("localhost", port=6333)
model = SentenceTransformer("all-MiniLM-L6-v2", device="cuda")

def search(query: str, limit: int = 5, collection: str = "embeddings_384"):
    # 生成查询嵌入
    query_vector = model.encode(query, normalize_embeddings=True).tolist()
    
    # 搜索
    results = client.search(
        collection_name=collection,
        query_vector=query_vector,
        limit=limit,
        with_payload=True,
        with_vectors=False    # 不返回向量（节省带宽）
    )
    
    return results

# 测试搜索
results = search("vector database performance")
for r in results:
    print(f"Score: {r.score:.4f} | {r.payload['text'][:100]}")
```

### 带过滤的搜索（元数据 + 向量）

```python
from qdrant_client.models import Filter, FieldCondition, MatchValue, Range

# 使用元数据过滤进行搜索
results = client.search(
    collection_name="documents",
    query_vector=query_vector,
    query_filter=Filter(
        must=[
            FieldCondition(
                key="category",
                match=MatchValue(value="database")
            ),
            FieldCondition(
                key="year",
                range=Range(gte=2023)  # 年份 >= 2023
            )
        ]
    ),
    limit=10,
    with_payload=True
)
```

### 批量/多查询搜索

```python
from qdrant_client.models import SearchRequest

queries = [
    "how to install vector database",
    "machine learning inference optimization",
    "RAG pipeline architecture"
]

query_vectors = model.encode(queries, normalize_embeddings=True)

# 批量搜索（对所有查询一次 API 调用）
results = client.search_batch(
    collection_name="embeddings_384",
    requests=[
        SearchRequest(
            vector=vec.tolist(),
            limit=5,
            with_payload=True
        )
        for vec in query_vectors
    ]
)

for query, res in zip(queries, results):
    print(f"\nQuery: {query}")
    for r in res:
        print(f"  {r.score:.3f}: {r.payload['text'][:80]}")
```

***

## 步骤 8 — 构建 RAG 管道

```python
from qdrant_client import QdrantClient
from sentence_transformers import SentenceTransformer
from openai import OpenAI

# 初始化客户端
qdrant = QdrantClient("localhost", port=6333)
embedder = SentenceTransformer("all-MiniLM-L6-v2", device="cuda")
llm = OpenAI(api_key="your-openai-key")

def rag_query(question: str, n_context: int = 5) -> str:
    # 第 1 步：对问题进行嵌入
    query_vector = embedder.encode(question, normalize_embeddings=True).tolist()
    
    # 第 2 步：从 Qdrant 检索相关上下文
    search_results = qdrant.search(
        collection_name="documents",
        query_vector=query_vector,
        limit=n_context,
        with_payload=True
    )
    
    # 第 3 步：构建上下文字符串
    context = "\n\n".join([
        f"[Source: {r.payload.get('source', 'unknown')}]\n{r.payload['text']}"
        for r in search_results
        if r.score > 0.5  # 过滤低置信度结果
    ])
    
    # 第 4 步：使用 LLM 生成答案
    response = llm.chat.completions.create(
        model="gpt-4o-mini",
        messages=[
            {
                "role": "system",
                "content": "根据提供的上下文回答问题。简明且准确。"
            },
            {
                "role": "user",
                "content": f"Context:\n{context}\n\nQuestion: {question}"
            }
        ],
        temperature=0.1
    )
    
    return response.choices[0].message.content

# 测试 RAG 管道
answer = rag_query("What is Qdrant and how does it work?")
print(answer)
```

***

## 步骤 9 — 监控与管理集合

```python
# 集合统计信息
info = client.get_collection("documents")
print(f"Vectors count: {info.vectors_count:,}")
print(f"Points count: {info.points_count:,}")
print(f"Indexed vectors: {info.indexed_vectors_count:,}")
print(f"Status: {info.status}")
print(f"Disk usage: {info.disk_data_size / 1024 / 1024:.1f} MB")

# 列出所有集合
collections = client.get_collections()
for c in collections.collections:
    print(f" - {c.name}")

# 按过滤器删除点
client.delete(
    collection_name="documents",
    points_selector=Filter(
        must=[FieldCondition(key="source", match=MatchValue(value="old_source"))]
    )
)

# 优化集合（强制构建索引）
client.update_collection(
    collection_name="documents",
    optimizer_config=OptimizersConfigDiff(indexing_threshold=0)  # 强制立即索引
)
```

***

## 故障排除

### 连接被拒绝

```bash
# 检查 Qdrant 是否运行
docker ps | grep qdrant
# 或检查进程
ps aux | grep qdrant

# 检查端口是否打开
curl http://localhost:6333/
netstat -tlnp | grep 6333
```

### 搜索性能缓慢

```python
# 优化 HNSW 参数以获得更好的召回率
client.update_collection(
    collection_name="documents",
    hnsw_config=HnswConfigDiff(ef=128)  # 增加搜索时的 ef（默认 100）
)

# 使用 INT8 量化以将更多向量放入内存
```

### 高内存使用

```python
# 对于大型集合启用磁盘存储
client.create_collection(
    collection_name="large_collection",
    vectors_config=VectorParams(
        size=1536,
        distance=Distance.COSINE,
        on_disk=True  # 将向量存储在磁盘上而不是内存中
    )
)
```

***

## REST API 快速参考

```bash
# 列出集合
curl http://localhost:6333/collections

# 创建集合
curl -X PUT http://localhost:6333/collections/my_collection \
    -H "Content-Type: application/json" \
    -d '{"vectors": {"size": 384, "distance": "Cosine"}}'

# 计数点数
curl http://localhost:6333/collections/my_collection/points/count

# 搜索
curl -X POST http://localhost:6333/collections/my_collection/points/search \
    -H "Content-Type: application/json" \
    -d '{
        "vector": [0.1, 0.2, ...],
        "limit": 5,
        "with_payload": true
    }'

# 删除集合
curl -X DELETE http://localhost:6333/collections/my_collection
```

***

## Clore.ai 上的成本估算

| 设置     | 服务器              | 每月费用       | 容量       |
| ------ | ---------------- | ---------- | -------- |
| 小型 RAG | RTX 3090，32GB 内存 | \~$60–80   | \~5M 向量  |
| 中等搜索   | RTX 4090，64GB 内存 | \~$120–150 | \~15M 向量 |
| 大规模    | A100，128GB 内存    | \~$250–350 | \~30M 向量 |

***

## 其他资源

* [Qdrant 文档](https://qdrant.tech/documentation/)
* [Qdrant GitHub](https://github.com/qdrant/qdrant)
* [Qdrant Python 客户端](https://github.com/qdrant/qdrant-client)
* [Qdrant 示例](https://github.com/qdrant/examples)
* [向量数据库基准](https://qdrant.tech/benchmarks/)
* [Sentence Transformers](https://www.sbert.net/)

***

*在 Clore.ai 上运行 Qdrant 可为您提供自托管的高性能向量数据库，免除像 Pinecone 或 Weaviate Cloud 那样的按查询费用。非常适合处理数百万文档的 RAG 管道。*

***

## Clore.ai 的 GPU 建议

| 在 Clore.ai 上的预估费用 | 开发/测试 | RTX 3090（24GB） |
| ----------------- | ----- | -------------- |
| \~$0.12/每 GPU/每小时 | 生产    | RTX 4090（24GB） |
| 生产级向量搜索           | 生产    | RTX 4090（24GB） |
| 高吞吐量嵌入            | 大规模   | A100 80GB      |

> GPU 服务器上。浏览可用 GPU 并按小时租用 — 无需承诺，提供完整的 root 访问权限。 [Clore.ai](https://clore.ai/marketplace) GPU 服务器。浏览可用 GPU 并按小时租用 — 无需承诺，提供完整的 root 访问权限。


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.clore.ai/guides/guides_v2-zh/rag-yu-xiang-liang-shu-ju-ku/qdrant.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
