K 近邻向量搜索

Manticore Search 支持为每个文档添加由机器学习模型生成的嵌入,然后对其执行最近邻搜索。这让你可以构建相似性搜索、推荐、语义搜索,以及基于 NLP 算法的相关性排序等功能,还包括图像、视频和声音搜索。

要将 KNN 向量搜索与全文搜索结合以获得更好的相关性,请参阅 混合搜索。

什么是嵌入?

嵌入是一种将文本、图像或声音等数据表示为高维空间中向量的方法。这些向量的设计目标是让它们之间的距离能够反映所代表数据的相似度。这个过程通常会使用词嵌入算法(例如 Word2Vec、BERT)处理文本,或使用神经网络处理图像。向量空间具有高维特性,每个向量包含许多分量,因此能够表示对象之间复杂而细微的关系。它们的相似度通过这些向量之间的距离来衡量,通常使用欧氏距离或余弦相似度等方法。

Manticore Search 使用 HNSW 库支持 k 近邻(KNN)向量搜索。该功能属于 Manticore Columnar Library。

为 KNN 搜索配置表

要运行 KNN 搜索,首先必须配置你的表。浮点向量和 KNN 搜索仅支持实时表(不支持普通表)。表中需要至少有一个 float_vector 属性,它充当数据向量。你需要指定以下属性:

  • knn_type:必填设置;目前仅支持 hnsw。

  • knn_dims:必填设置,用于指定被索引向量的维度。

  • hnsw_similarity:必填设置,用于指定 HNSW 索引使用的距离函数。可接受的值有:

    • L2 - 平方 L2
    • IP - 内积
    • COSINE - 余弦相似度

    注意: 当使用 COSINE 相似度时,向量会在插入时自动归一化。这意味着存储的向量值可能与原始输入值不同,因为它们会被转换为单位向量(数学长度/模长为 1.0 的向量),以便高效计算余弦相似度。该归一化过程会保留向量方向,同时标准化其长度。

  • hnsw_m:可选设置,定义图中最大出边数。默认值为 16。

  • hnsw_ef_construction:可选设置,定义构建时间与精度之间的权衡。默认值为 200。

注意:在多核主机上,RT chunk 保存、OPTIMIZE TABLE / 自动优化 chunk 合并,以及 ALTER TABLE ... ADD/DROP/REBUILD KNN 重建期间的 HNSW 图构建默认会并行运行;工作线程数由 searchd 设置 knn_parallel_build 控制(将其设为 1 可强制走串行路径)。这只影响构建阶段性能。由于并行 HNSW 构建可能以不同顺序插入向量,生成的图可能不会与串行构建在位级别完全一致。

‹›
  • SQL
  • JSON
  • Config
📋
create table test ( title text, image_vector float_vector knn_type='hnsw' knn_dims='4' hnsw_similarity='l2' );
‹›
Response
Query OK, 0 rows affected (0.01 sec)

插入向量数据

自动嵌入(推荐)

处理向量数据最简单的方法是使用自动嵌入。使用此功能时,你创建一个带有 MODEL_NAME 和 FROM 参数的表,然后只需插入文本数据即可,Manticore 会自动为你生成嵌入。

创建带自动嵌入的表

创建用于自动嵌入的表时,请指定:

  • MODEL_NAME:要使用的嵌入模型
  • FROM:用于生成嵌入的字段(留空表示所有文本/字符串字段)
  • API_KEY:远程模型(OpenAI、Voyage、Jina)必需。API 密钥会在建表期间通过发起真实 API 请求进行验证。
  • API_URL:可选。自定义 API 端点 URL。如果未指定,则使用默认提供方端点(例如 OpenAI 使用 https://api.openai.com/v1/embeddings)。
  • API_TIMEOUT:可选。API 请求的 HTTP 超时时间,单位为秒。默认值为 10 秒。设为 '0' 可使用默认超时。此设置同时适用于建表期间的验证请求和 INSERT 操作期间的嵌入生成。
  • MAX_INPUT_TOKENS: 可选。限制每段输入文本在生成嵌入前使用的 token 数;更长的文本会被截断。'0'(默认)表示使用模型自身的上下文限制。Qwen/Qwen3-Embedding-0.6B 等长上下文模型最多可接受 32,768 个 token,而在 CPU 上生成嵌入的时间会随输入长度超线性增长(一个 5 KB 文档可能需要数分钟),因此当文本字段可能包含很长或不受限制的内容时,请设置上限(例如 '512')。适用于本地模型;之后可通过 ALTER TABLE ... MODIFY COLUMN ... MAX_INPUT_TOKENS='...' 修改该设置,且无需重新嵌入已有行。
  • CACHE_PATH: 可选。本地模型下载和缓存所在的目录。默认位于 data_dir 下的 .cache/manticore,例如 <data_dir>/.cache/manticore/models--Xenova--all-MiniLM-L6-v2,因此下载的模型在重启后仍会保留。

对于远程模型,MODEL_NAME 可以写成两种形式:

  • 传统的带提供方前缀形式:openai/text-embedding-ada-002、voyage/voyage-3.5-lite、jina/jina-embeddings-v4
  • 用于自定义端点的显式提供方标识形式:openai:text-embedding-ada-002、openai:openai/text-embedding-ada-002、voyage:custom-model、jina:custom-model

当你把 provider:model 形式与 API_URL 一起使用时,冒号前的部分只用于选择请求格式。冒号后的部分会原样发送到远程端点。这对 OpenAI 兼容网关很有用,例如 OpenRouter 或 LiteLLM。

支持的嵌入模型:

模型类型 示例 需要 API 密钥 说明
ONNX(推荐) Xenova/all-MiniLM-L6-v2 否 来自任何提供 .onnx 文件的 Hugging Face 仓库的本地模型。运行在 Manticore 高速的 ONNX Runtime 后端上。浏览列表:feature-extraction ONNX 模型。
Sentence Transformers sentence-transformers/all-MiniLM-L6-v2 否 本地 BERT 模型,会自动下载。权重与上方 ONNX 行相同,但在 CPU 上生成嵌入要慢 10–20 倍;如果仓库提供 ONNX,请优先使用 ONNX。参见选择本地嵌入模型。
Qwen Qwen/Qwen3-Embedding-0.6B 否 本地 Qwen 系列模型
Llama TinyLlama/TinyLlama-1.1B-Chat-v1.0 否 本地 Llama 系列模型
Mistral Locutusque/TinyMistral-248M-v2 否 本地 Mistral 系列模型
Gemma h2oai/embeddinggemma-300m 否 本地 Gemma 系列模型
OpenAI openai/text-embedding-ada-002 或 openai:text-embedding-ada-002 是 API_KEY='***'
Voyage voyage/voyage-3.5-lite 或 voyage:voyage-3.5-lite 是 API_KEY='***'
Jina jina/jina-embeddings-v4 或 jina:jina-embeddings-v4 是 API_KEY='***'

本地模型格式要求:

  • 支持的权重格式:safetensors(单文件或通过 model.safetensors.index.json 分片)、量化 GGUF 和 ONNX
  • 支持的模型家族:BERT/Sentence Transformers、Qwen、Llama、Mistral、Gemma、T5
  • 已测试模型:TinyLlama/TinyLlama-1.1B-Chat-v1.0、Locutusque/TinyMistral-248M-v2、Qwen/Qwen3-Embedding-0.6B、h2oai/embeddinggemma-300m
  • 这些家族中的其他模型也可能可用,但不保证
  • 对于受限制的 Hugging Face 仓库,请将你的 Hugging Face 访问令牌作为 API_KEY 传入
选择本地嵌入模型

MODEL_NAME 不仅决定质量,还决定每个文档在 CPU 上生成嵌入的速度,差距可达一个数量级(参见使用 ONNX 让嵌入速度提升 14 倍)。提供 .onnx 文件的仓库会在 ONNX Runtime 后端运行;同一个模型的 safetensors 仓库则会走慢得多的 Candle 路径。以下为近似数据,基于一台小型 4-vCPU 实例和较短(约 300 字符)文档测得:

模型 格式 维度 下载大小 CPU 速度
Xenova/all-MiniLM-L6-v2 ONNX 384 ~90 MB ~30–40 文档/秒
sentence-transformers/all-MiniLM-L6-v2 safetensors 384 ~90 MB ~2–3 文档/秒
BAAI/bge-base-en-v1.5 safetensors 768 ~420 MB ~2–3 文档/秒
  • 建议从 Xenova/all-MiniLM-L6-v2 开始 — 上面示例使用的就是这个模型。它既适合交互式查询,也适合批量写入,质量足以满足大多数搜索应用。
  • 更高的排行榜名次通常不值得在 CPU 上付出写入成本。 从小型 ONNX 模型换成更大的 safetensors 模型,通常会让每个文档的嵌入成本提高 10–20 倍,而最终相关性只会有很小变化。在切换到“更好”的模型之前,请先用你自己的几十条查询和文本,与默认模型做对比。
  • 大规模写入前先做估算。 嵌入成本是线性的:每个文档的毫秒数乘以行数。按约 300 ms/文档计算,15,000 个文档大约需要 75 分钟;按约 30 ms/文档计算,则不到 10 分钟。先向临时表插入几百条真实数据并据此外推,再决定是否将生产表绑定到某个模型。
  • 写入慢的症状通常指向模型,而不是设置。 如果插入很慢,或批量加载出现嵌入超时和重试,常见原因是模型不是 ONNX,或者模型过大。切换到 ONNX 仓库即可解决;提高 embeddings_threads 不会让慢模型变快,还可能让并发任务拿不到工作线程。

关于配置 float_vector 属性的更多信息,请参见这里。

‹›
  • SQL
  • Config
📋

使用本地 ONNX 模型 - 推荐(无需 API key)

-- Xenova/all-MiniLM-L6-v2 is the ONNX build: about 30-40 docs/sec on CPU.
-- sentence-transformers/all-MiniLM-L6-v2 is the same model 10-20x slower; prefer the Xenova/ repo.
CREATE TABLE products (
    title TEXT,
    description TEXT,
    embedding_vector FLOAT_VECTOR KNN_TYPE='hnsw' HNSW_SIMILARITY='l2'
    MODEL_NAME='Xenova/all-MiniLM-L6-v2' FROM='title'
);

使用 sentence-transformers(无需 API key;走 Candle 路径 - 在可用时优先使用上面的 ONNX)

CREATE TABLE products_st (
    title TEXT,
    description TEXT,
    embedding_vector FLOAT_VECTOR KNN_TYPE='hnsw' HNSW_SIMILARITY='l2'
    MODEL_NAME='sentence-transformers/all-MiniLM-L6-v2' FROM='title'
);

使用 Qwen 本地嵌入(无需 API 密钥)

CREATE TABLE products_qwen (
    title TEXT,
    description TEXT,
    embedding_vector FLOAT_VECTOR KNN_TYPE='hnsw' HNSW_SIMILARITY='l2'
    MODEL_NAME='Qwen/Qwen3-Embedding-0.6B' FROM='title' CACHE_PATH='/opt/homebrew/var/manticore/.cache/manticore'
);

使用 OpenAI(需要 API_KEY 参数)

CREATE TABLE products_openai (
    title TEXT,
    description TEXT,
    embedding_vector FLOAT_VECTOR KNN_TYPE='hnsw' HNSW_SIMILARITY='l2'
    MODEL_NAME='openai/text-embedding-ada-002' FROM='title,description' API_KEY='...'
);

使用 OpenAI 搭配自定义 API URL 和超时设置(可选)

CREATE TABLE products_openai_custom (
    title TEXT,
    description TEXT,
    embedding_vector FLOAT_VECTOR KNN_TYPE='hnsw' HNSW_SIMILARITY='l2'
    MODEL_NAME='openai:text-embedding-ada-002' FROM='title,description'
    API_KEY='***' API_URL='https://custom-api.example.com/v1/embeddings' API_TIMEOUT='30'
);

使用期望提供方限定模型 ID 的 OpenAI 兼容网关

CREATE TABLE products_openrouter (
    title TEXT,
    description TEXT,
    embedding_vector FLOAT_VECTOR KNN_TYPE='hnsw' HNSW_SIMILARITY='l2'
    MODEL_NAME='openai:openai/text-embedding-ada-002' FROM='title,description'
    API_KEY='***' API_URL='https://openrouter.ai/api/v1/embeddings' API_TIMEOUT='30'
);

使用所有文本字段生成嵌入(FROM 为空)

CREATE TABLE products_all (
    title TEXT,
    description TEXT,
    embedding_vector FLOAT_VECTOR KNN_TYPE='hnsw' HNSW_SIMILARITY='l2'
    MODEL_NAME='Xenova/all-MiniLM-L6-v2' FROM=''
);
使用自动嵌入插入数据

使用自动嵌入时,你可以:

  • 省略向量字段,让 Manticore 根据 FROM 中列出的字段生成嵌入
  • 为某一行显式提供你自己的向量
  • 提供 () 以跳过生成并存储全零向量

如果你之后运行 ALTER TABLE ... REBUILD EMBEDDINGS,当前包含来自 () 的零向量的行也会被重新生成。

‹›
  • SQL
  • JSON
📋

仅插入文本数据 - 嵌入会自动生成

INSERT INTO products (title) VALUES
('machine learning artificial intelligence'),
('banana fruit sweet yellow');

插入用户提供的向量

INSERT INTO products (title, embedding_vector) VALUES
('machine learning artificial intelligence', (0.653448,0.192478,0.017971,0.339821));

插入多个字段 - 如果 FROM='title,description',两者都会用于生成嵌入

INSERT INTO products_openai (title, description) VALUES
('smartphone', 'latest mobile device with advanced features'),
('laptop', 'portable computer for work and gaming');

插入空向量(不自动生成;存储零向量)

INSERT INTO products (title, embedding_vector) VALUES
('no embedding item', ());
使用自动嵌入进行搜索

搜索方式相同 - 提供查询文本,Manticore 会生成嵌入并找到相似文档:

‹›
  • SQL
  • JSON
📋
SELECT id, knn_dist() FROM products WHERE knn(embedding_vector, 'machine learning');
‹›
Response
+------+------------+
| id   | knn_dist() |
+------+------------+
|    1 | 0.12345678 |
|    2 | 0.87654321 |
+------+------------+
2 rows in set (0.00 sec)

手动插入向量

或者,你也可以手动插入预先计算好的向量数据,确保它与创建表时指定的维度一致。你也可以插入空向量;这意味着该文档将被排除在向量搜索结果之外。

重要: 当使用 hnsw_similarity='cosine' 时,向量会在插入时自动归一化为单位向量(数学长度/模长为 1.0 的向量)。这种归一化会保留向量方向,同时标准化其长度,这对于高效计算余弦相似度是必需的。这意味着存储值会与你的原始输入值不同。

‹›
  • SQL
  • JSON
📋
insert into test values ( 1, 'yellow bag', (0.653448,0.192478,0.017971,0.339821) ), ( 2, 'white bag', (-0.148894,0.748278,0.091892,-0.095406) );
‹›
Response
Query OK, 2 rows affected (0.00 sec)

KNN 向量搜索

现在,你可以在 SQL 或 JSON 格式中使用 knn 子句执行 KNN 搜索。两种接口支持相同的核心参数,无论你选择哪种格式,都能获得一致的体验:

  • SQL: select ... from <table name> where knn ( <field>, <query vector> [,<options>] )
  • JSON:
    POST /search
    {
        "table": "<table name>",
        "knn":
        {
            "field": "<field>",
            "query": "<text or vector>",
            "ef": <ef>,
            "rescore": <rescore>,
            "oversampling": <oversampling>
        }
    }

参数如下:

  • field:包含向量数据的 float vector 属性名称。
  • k:已弃用选项。请改用查询 limit。它用于指定单个 HNSW 索引应返回的文档数量。不过,最终结果中包含的文档总数可能会变化。例如,如果系统处理的是按磁盘 chunk 划分的实时表,每个 chunk 都可能返回 k 个文档,从而使总数超过指定的 k(因为累计数量为 num_chunks * k)。另一方面,如果在请求 k 个文档后,其中一些又根据特定属性被过滤掉,那么最终文档数量也可能少于 k。需要注意的是,k 参数不适用于 ramchunks。在 ramchunks 场景下,检索过程的工作方式不同,因此 k 参数对返回文档数量的影响不适用。
  • query:(推荐参数)搜索查询,可以是:
    • 文本字符串:如果该字段配置了自动嵌入,则会自动转换为嵌入。如果该字段没有自动嵌入,则会返回错误。
    • 向量数组:工作方式与 query_vector 相同。
  • query_vector:(旧参数)作为数字数组的搜索向量。为向后兼容仍然支持。 注意: 同一请求中只能使用 query 或 query_vector 其中之一,不能同时使用。
  • ef:搜索过程中使用的动态列表大小,可选。ef 越大,搜索越准确,但速度越慢。默认值为 10。
  • rescore:启用 KNN 重新评分(默认启用)。在 SQL 中设为 0 或在 JSON 中设为 false 可禁用重新评分。KNN 搜索在使用量化向量完成后(可能伴随过采样),会使用原始(全精度)向量重新计算距离并重新排序结果,以提高排序准确性。
  • oversampling:设置一个因子(浮点值),在执行 KNN 搜索时将 k 乘以该因子,从而使用量化向量检索出比所需更多的候选结果。默认应用 oversampling=3.0。如果启用了重新评分,这些候选结果之后可以重新评估。过采样也适用于非量化向量。由于它会增大 k,进而影响 HNSW 索引的工作方式,因此可能会使结果精度略有变化。
  • early_termination:启用或禁用 HNSW 图遍历期间的自适应提前终止。默认启用。设为 SQL 中的 0 或 JSON 中的 false 可禁用。详情参见提前终止。

当提供的是文本查询时(因此 Manticore 会在搜索前对字符串进行嵌入),可以在 SQL 中通过 OPTION embeddings_threads = N 按查询覆盖嵌入库使用的线程数。该值只会限制此查询的嵌入调用,覆盖全局 embeddings_threads 设置;0 表示不设上限。当查询以向量数组形式提供时,此选项不起作用。

文档始终按其与搜索向量的距离排序。你指定的任何额外排序条件都会在这个主要排序条件之后应用;请参阅排序 KNN 结果。如需获取距离,可以使用内置函数 knn_dist()。关于 KNN 搜索会返回多少文档,请参阅 KNN 候选集。

‹›
  • SQL
  • JSON
📋
select id, knn_dist() from test where knn ( image_vector, (0.286569,-0.031816,0.066684,0.032926), { ef=2000, oversampling=3.0, rescore=1 } );
‹›
Response
+------+------------+
| id   | knn_dist() |
+------+------------+
|    1 | 0.28146550 |
|    2 | 0.81527930 |
+------+------------+
2 rows in set (0.00 sec)

KNN 候选集

KNN 搜索返回的是候选集,而不是精确的行数。实时表的每个磁盘块最多会贡献 LIMIT × oversampling 个最近文档(如果使用已弃用的 k,则为 k × oversampling),并且 oversampling 默认为 3。LIMIT 仍然控制你收到的行数,但 SHOW META 中的 total_found 以及所有 FACET 计数都会覆盖整个候选集。例如,在一个包含 4 个磁盘块的表上,SELECT id FROM t WHERE knn(vec, 'quiet flat') LIMIT 5 会返回 5 行,total_found 为 60(5 × 3 × 4);在 OPTIMIZE 将该表合并为一个块后,同一查询会报告 15。因此,KNN 查询的 total_found 并不是相关文档的数量。若要让搜索考虑每个文档,例如按相似度对所有通过属性过滤器的文档进行排序,请将 LIMIT(以及需要时的 max_matches)设置为至少等于表中的文档数量。

排序 KNN 结果

KNN 结果始终先按与查询的距离排序。对另一个属性使用 ORDER BY 不会重新排序这些结果:它只会在距离相等时用于打破平局,并且不会返回警告。若要按属性对 KNN 匹配结果排序,请在子查询中运行 KNN 搜索,并对外层查询排序:

SELECT * FROM (SELECT id, price, knn_dist() AS dist FROM products WHERE knn(embedding_vector, 'quiet flat') LIMIT 100) ORDER BY price ASC LIMIT 10;

混合查询(OPTION fusion_method='rrf')会对属性应用 ORDER BY。

每个文档包含多个向量

一个 float_vector_array 属性为每个文档保存多个向量,而不是只有一个:比如一篇文章的多个分块、一款产品的多张照片,或者一段视频的关键帧。所有文档中的所有向量都会一起建立索引,搜索时会把文档的各个向量视为该文档的不同表示:

  • 如果某个文档的任意一个向量接近查询向量,就认为该文档匹配。
  • 每个匹配文档只返回一次,knn_dist() 报告它最近那个向量的距离。该文档的其他向量不会产生额外行。
  • k 统计的是文档,不是向量。knn(v, 10, ...) 要求返回 10 个最近的文档,而不管它们一共包含多少个向量。
  • 没有任何向量的文档([],或者省略该属性)绝不会返回,因为它不接近任何内容。

查询向量仍然是一个包含 KNN_DIMS 个元素的单个向量,与 float_vector 完全相同。KNN 索引数组中存储的每个向量也必须包含 KNN_DIMS 个元素。

当 HNSW_SIMILARITY='cosine' 时,每个存储向量都会单独归一化,因此会将文档中的各个向量分别与查询进行比较,而不是把它们当作一个长串联向量来比较。

本页其余内容都同样适用:过滤、预过滤/后过滤、量化、提前终止 和重评分的行为都一样。自动嵌入可以帮你填充数组,每个 chunk 一个向量 - 见下方的分块策略。

‹›
  • SQL
  • JSON
📋
CREATE TABLE articles(title text, chunk_vectors float_vector_array knn_type='hnsw' knn_dims='4' hnsw_similarity='l2');
INSERT INTO articles VALUES
  (1, 'first',  [[1,0,0,0],[0,1,0,0]]),
  (2, 'second', [[0,0,1,0]]);
-- doc 1 owns a vector identical to the query and another far from it,
-- so it is returned once, at distance 0
SELECT id, knn_dist() FROM articles WHERE knn(chunk_vectors, 5, (1,0,0,0));
‹›

分块策略

默认情况下,embedding 模型只会读取文档中能装进其输入窗口的那部分内容(通常只有几百个 token),其余内容会被静默丢弃。对于标题或简短描述,这已经足够完整;但对于长文章就不行了:切断点之后写的内容永远无法被检索到,而且也不会报错。

分块策略决定文档如何转换为向量。可在基于模型的列上通过 CHUNK_STRATEGY 设置:

策略 每个文档的向量数 作用
truncate 1 只嵌入模型窗口能容纳的内容,剩余部分丢弃。默认值,也是历史行为。
mean 1 将整个文档切分,嵌入每一段,再把它们平均成一个向量。不会丢掉尾部内容,但如果文档涵盖多个主题,会被压缩成它们的平均表示。
fixed N 按 MAX_TOKENS token 的固定大小窗口切分。
recursive N 按分隔层级切分:先段落,再行,再句子,最后空格;确保每个片段都不超过 MAX_TOKENS。
sentence N 按句子边界切分,并按 MAX_TOKENS 打包。

truncate 和 mean 会为每个文档生成一个向量,并且适用于 float_vector 列(如果用在 float_vector_array 上,则存储为一个 1 元素数组)。fixed、recursive 和 sentence 会生成多个向量,因此需要 float_vector_array 列;把它们用在普通 float_vector 上会被拒绝。

区别在于“匹配”意味着什么。每个文档只有一个向量时,搜索问的是“这个文档整体上和查询是否相似?”,而其中单个相关段落会被周围内容稀释。每个 chunk 一个向量时,问的是“这个文档是否包含与查询相似的内容?”,每个 chunk 都独立竞争,文档只返回一次,并由最相近的 chunk 评分(见每个文档多个向量)。

选项,仅在与 MODEL_NAME 和 KNN_TYPE='hnsw' 同时使用时有效:

  • CHUNK_STRATEGY:上面五种之一。默认 truncate。
  • MAX_TOKENS:每个 chunk 的 token 数。0(默认)表示使用模型自身的上限;更大的值会被截断到该上限。
  • OVERLAP_TOKENS:相邻 chunk 共享多少 token,这样被边界切开的概念仍能在其中一个 chunk 中保持完整。需要显式设置非零的 MAX_TOKENS。较大的重叠会被缩小,以保证 chunk 仍能继续推进文档:fixed 和 recursive 将其上限设为 MAX_TOKENS 的一半,而 sentence 会用最多 OVERLAP_TOKENS 对应的尾部完整句子为下一个 chunk 重新播种,并且每次至少前进一个句子。
  • MAX_CHUNKS:每个文档的向量上限。0(默认)表示不限。

重要说明:

  • MAX_CHUNKS 会丢弃文本。 超出上限时,剩余部分会合并到最后一个保留的 chunk 中,而该 chunk 随后会超过 MAX_TOKENS,在嵌入时被截断到模型的输入窗口。表面上不会留下可见空缺,但尾部内容已经丢失。
  • 本地模型和远程模型的分块方式不同。 本地模型按模型真实的 token 切分。远程 API 模型(OpenAI、Voyage、Jina)没有本地分词器,因此改用保守的字节估算,所以相同的文本和设置会生成与本地模型不同数量的 chunk。

目前还不支持对基于模型的 float_vector_array 使用 ALTER TABLE ... ADD COLUMN,也不支持对其执行 ALTER TABLE ... REBUILD EMBEDDINGS;现有行无法回填,因此该列会一直为空。请在建表时直接声明这类列。对于 float_vector 列则都能正常工作,包括 mean。

‹›
  • SQL
  • JSON
📋
-- one vector per sentence group, filled automatically from the text
CREATE TABLE articles (
  title text,
  content text,
  chunks float_vector_array knn_type='hnsw' hnsw_similarity='cosine'
     model_name='Xenova/all-MiniLM-L6-v2' from='title,content'
     chunk_strategy='sentence' max_tokens='256' overlap_tokens='32'
);
INSERT INTO articles (id, title, content) VALUES (1, 'Rotating certificates', 'A long guide with many sections ...');
SELECT id, knn_dist() FROM articles WHERE knn(chunks, 5, 'how do I rotate a certificate');

向量量化

HNSW 索引必须完整加载到内存中才能执行 KNN 搜索,这可能导致较高的内存消耗。为了降低内存占用,可以应用标量量化 - 这是一种通过用有限数量的离散值表示每个分量(维度)来压缩高维向量的技术。Manticore 支持 8 位和 1 位量化,这意味着每个向量分量都会从 32 位浮点压缩为 8 位甚至 1 位,分别将内存占用降低 4 倍或 32 倍。这些压缩表示还可以加快距离计算,因为更多的向量分量可以在单条 SIMD 指令中处理。虽然标量量化会引入一定的近似误差,但它通常是在搜索精度与资源效率之间值得接受的权衡。若要获得更高精度,可以将量化与重新评分和过采样结合使用:检索出的候选数量会多于请求数量,然后使用原始 32 位浮点向量重新计算这些候选的距离。

支持的量化类型包括:

  • 8bit:每个向量分量都会量化为 8 位。
  • 1bit:每个向量分量都会量化为 1 位。这里使用非对称量化,查询向量量化为 4 位,存储向量量化为 1 位。与更简单的方法相比,这种方式提供了更高的精度,但会带来一定的性能权衡。
  • 1bitsimple:每个向量分量都会量化为 1 位。该方法比 1bit 更快,但通常精度更低。
create table test ( title text, image_vector float_vector knn_type='hnsw' knn_dims='4' hnsw_similarity='l2' quantization='1bit');
Query OK, 0 rows affected (0.01 sec)
POST /sql?mode=raw -d "create table test ( title text, image_vector float_vector knn_type='hnsw' knn_dims='4' hnsw_similarity='l2' quantization='1bit')"
[
  {
    "total": 0,
    "error": "",
    "warning": ""
  }
]

按 id 查找相似文档

注意:按 id 查找相似文档需要 Manticore Buddy。如果无法工作,请确认已安装 Buddy。

根据唯一 ID 查找与某个文档相似的文档是一个常见任务。例如,当用户查看某个特定条目时,Manticore Search 可以高效地识别并显示在向量空间中与之最相似的一组条目。做法如下:

  • SQL: select ... from <table name> where knn ( <field>, <k>, <document id> )
  • JSON:
    POST /search
    {
        "table": "<table name>",
        "knn":
        {
            "field": "<field>",
            "doc_id": <document id>,
            "k": <k>
        }
    }

参数如下:

  • field:包含向量数据的 float vector 属性名称。
  • k:表示要返回的文档数量,是分层可导航小世界(HNSW)索引的关键参数。它指定单个 HNSW 索引应返回的文档数量。不过,最终结果中包含的文档总数可能会变化。例如,如果系统处理的是按磁盘 chunk 划分的实时表,每个 chunk 都可能返回 k 个文档,从而使总数超过指定的 k(因为累计数量为 num_chunks * k)。另一方面,如果在请求 k 个文档后,其中一些又根据特定属性被过滤掉,那么最终文档数量也可能少于 k。需要注意的是,k 参数不适用于 ramchunks。在 ramchunks 场景下,检索过程的工作方式不同,因此 k 参数对返回文档数量的影响不适用。
  • document id:用于 KNN 相似度搜索的文档 ID。
‹›
  • SQL
  • JSON
📋
select id, knn_dist() from test where knn ( image_vector, 5, 1 );
‹›
Response
+------+------------+
| id   | knn_dist() |
+------+------------+
|    2 | 0.81527930 |
+------+------------+
1 row in set (0.00 sec)

过滤 KNN 向量搜索结果

Manticore 还支持对 KNN 搜索返回的文档进行额外过滤,可以通过全文匹配、属性过滤,或两者同时进行。

‹›
  • SQL
  • JSON
📋
select id, knn_dist() from test where knn ( image_vector, 5, (0.286569,-0.031816,0.066684,0.032926) ) and match('white') and id < 10;
‹›
Response
+------+------------+
| id   | knn_dist() |
+------+------------+
|    2 | 0.81527930 |
+------+------------+
1 row in set (0.00 sec)

过滤策略:预过滤 vs 后过滤

当将 KNN 向量搜索与属性过滤结合时,Manticore 支持两种策略,它们的区别在于过滤条件相对于 HNSW 图遍历的应用时机。

  • 预过滤(默认;SQL 中为 prefilter=1,JSON 中为 "prefilter": true,默认)会把过滤条件直接传入 HNSW 遍历过程。每个候选项在加入结果堆之前都会先检查是否满足过滤条件 - 只有匹配的文档才会计入最终的 k 个结果。这减少了无效的距离计算,并保证最终返回恰好 k 个匹配文档(前提是确实存在 k 个匹配文档)。

  • 后过滤(SQL 中为 prefilter=0,JSON 中为 "prefilter": false)会先在完整数据集上执行 KNN 搜索,然后再对结果应用过滤条件。这种方式安全且可预测:HNSW 图在不受干扰的情况下遍历,过滤器只影响哪些结果会返回给客户端。缺点是图可能会在最终会被丢弃的候选项上耗费精力。当过滤条件非常严格、只匹配极少部分文档时,返回的 k 个结果可能会明显少于请求数量,因为大多数 KNN 候选都会被过滤掉。

在内部,Manticore 在预过滤中使用了基于 ACORN-1 的算法。朴素的预过滤只会简单跳过不匹配的节点,这会有丢失连接原本分离部分 HNSW 图的“桥接”节点的风险,从而在过滤条件越来越严格时导致召回率崩溃。ACORN-1 避免了这一点:当某个节点不满足过滤条件时,它的邻居仍会被加入探索队列。这样遍历就可以绕过被过滤掉的节点,并维持图的连通性。当通过过滤条件的文档少于总文档数的 60% 时,会自动启用 ACORN-1 探索。

自动暴力回退: 当启用预过滤时,Manticore 会估算在过滤后的子集上执行暴力距离扫描是否比遍历 HNSW 图更便宜。该估算会比较 HNSW 预计访问的节点数与通过过滤的文档数。如果过滤后的集合足够小,直接扫描更快,Manticore 会自动切换到暴力方式,完全跳过 HNSW。这样即使在极高选择性条件下,也能保证正确性和良好性能。

‹›
  • SQL
  • JSON
📋
-- prefilter (default): filter applied during HNSW traversal (ACORN-1 used automatically)
SELECT id, knn_dist() FROM test
WHERE knn ( image_vector, (0.286569,-0.031816,0.066684,0.032926) )
AND price < 100;
-- postfilter: KNN runs over full dataset, filter applied to results
SELECT id, knn_dist() FROM test
WHERE knn ( image_vector, (0.286569,-0.031816,0.066684,0.032926), { prefilter=0 } )
AND price < 100;

提前终止

默认情况下,Manticore 在 HNSW 图遍历期间使用自适应提前终止算法。它不会始终探索由 ef 定义的完整候选集,而是监控新候选项改进结果集的速率,并在该速率持续低于阈值时提前停止。这样可以减少距离计算次数,同时不会显著影响结果质量。

提前终止默认启用,并且当 k 为 10 或更少时会自动禁用,因为对于这么小的结果集,该算法的开销不值得。性能收益会随着 k 增大而提升 - 结果集越大,通过提前停止就能节省越多距离计算。

请注意,过采样会乘大 HNSW 遍历期间使用的有效 k,因此提前终止也会从过采样中受益:更高的有效 k 意味着可能跳过更多候选项。

若要显式控制提前终止,请使用 early_termination 选项:

‹›
  • SQL
  • JSON
📋
-- disable early termination
SELECT id, knn_dist() FROM test WHERE knn ( image_vector, (0.286569,-0.031816,0.066684,0.032926), { ef=200, early_termination=0 } );
-- enable early termination explicitly (default)
SELECT id, knn_dist() FROM test WHERE knn ( image_vector, (0.286569,-0.031816,0.066684,0.032926), { ef=200, early_termination=1 } );

何时应禁用提前终止:

  • 当结果集精度至关重要,且你无法接受超出 HNSW 已提供范围的任何近似时。
  • 当使用较低的 k 值(大约 30 或更少)时,此时提前终止带来的性能收益很小,但可能降低精度。
Last modified: September 23, 2026