news 2026/9/18 14:38:13

StarRocks cosine_similarity_norm 函数:面向预归一化向量的余弦相似度计算与源码实现解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
StarRocks cosine_similarity_norm 函数:面向预归一化向量的余弦相似度计算与源码实现解析

StarRocks cosine_similarity_norm 函数:面向预归一化向量的余弦相似度计算与源码实现解析

【免费下载链接】starrocksThe world's fastest open query engine for sub-second analytics both on and off the data lakehouse. With the flexibility to support nearly any scenario, StarRocks provides best-in-class performance for multi-dimensional analytics, real-time analytics, and ad-hoc queries. A Linux Foundation project.项目地址: https://gitcode.com/GitHub_Trending/st/starrocks

cosine_similarity_norm是 StarRocks 数学函数族中的一个向量相似度函数,用于在输入向量已经完成归一化的前提下,通过计算两个向量夹角的余弦值来度量其方向上的相似程度。本篇完整覆盖该函数的语法、参数、返回值语义与可复制的 SQL 实战示例,并结合 StarRocks 后端(BE)源码,深入讲解它与cosine_similarity在实现路径上的关键差异、SIMD 优化细节以及错误处理机制。读完之后,你既能直接在查询中使用该函数完成向量相似度排序,也能理解"norm 版本更快"这一结论在源码层面的确切含义。

一、函数语义:基于夹角的相似度度量

cosine_similarity_norm通过计算两个向量夹角的余弦值来度量它们的相似性。夹角只由向量的方向决定,向量的模长(magnitude)差异会被忽略——这正是"余弦"度量的核心思想。

该函数有一个前提假设:输入向量应当是已经归一化(单位化)的。如果需要在计算相似度之前先对向量做归一化,应改用 cosine_similarity,它会在内部完成范数计算与归一化。

相似度取值范围在-1 到 1之间,夹角越小,余弦相似度越大:

向量关系夹角余弦相似度
方向相同(平行同向)1
相互垂直90°0
方向相反(平行反向)180°-1

这个区间约束有一个隐含前提:[-1, 1]只对归一化后的单位向量严格成立。如果传入的向量并未归一化,结果实际上是两个向量的点积,可能超出该区间——这一点在源码实现部分会得到印证。

二、语法与参数

语法

cosine_similarity_norm(a, b)

参数说明

  • ab:待比较的两个向量,必须具有相同的维度(即相同的元素个数)。
  • 支持的数据类型为Array<float>
  • 两个数组的元素个数必须一致,否则返回错误。

返回值

  • 返回一个FLOAT值,范围 [-1, 1](在输入为归一化向量的前提下)。
  • 如果任一输入参数为 NULL 或非法(维度不匹配等),将报告错误。

函数注册信息

在 StarRocks 的函数注册表中可以确认该函数的完整签名。gensrc/script/functions.py 中定义了三个向量相似度相关函数:

[10102, "cosine_similarity", True, False, "FLOAT", ["ARRAY_FLOAT", "ARRAY_FLOAT"], "MathFunctions::cosine_similarity<TYPE_FLOAT, false>"], [10103, "cosine_similarity_norm", True, False, "FLOAT", ["ARRAY_FLOAT", "ARRAY_FLOAT"], "MathFunctions::cosine_similarity<TYPE_FLOAT, true>"], [10104, "inner_product", True, False, "FLOAT", ["ARRAY_FLOAT", "ARRAY_FLOAT"], "MathFunctions::inner_product<TYPE_FLOAT>"],

注册表揭示了两个关键信息:

  1. cosine_similarity_norm的函数 ID 为 10103,入参为两个ARRAY_FLOAT,返回FLOAT
  2. 它与cosine_similarity共用同一个 C++ 模板函数MathFunctions::cosine_similarity<TYPE_FLOAT, ...>,区别仅在于第二个模板参数——norm 版本为true。这正是理解两个函数性能差异的入口。

三、实战示例:向量相似度排序

以下示例完整继承自官方文档,可直接在 StarRocks 中执行。

1. 建表并插入向量数据

CREATE TABLE t1_similarity (id int, data array<float>) DISTRIBUTED BY HASH(id); INSERT INTO t1_similarity VALUES (1, array<float>[0.1, 0.2, 0.3]), (2, array<float>[0.2, 0.1, 0.3]), (3, array<float>[0.3, 0.2, 0.1]);

2. 计算每行向量与目标数组的相似度并降序排序

SELECT id, data, cosine_similarity_norm([0.1, 0.2, 0.3], data) as dist FROM t1_similarity ORDER BY dist DESC;

查询结果:

+------+---------------+------------+ | id | data | dist | +------+---------------+------------+ | 1 | [0.1,0.2,0.3] | 0.14000002 | | 2 | [0.2,0.1,0.3] | 0.13000001 | | 3 | [0.3,0.2,0.1] | 0.10000001 | +------+---------------+------------+

注意这里查询参数是常量数组[0.1, 0.2, 0.3],表列data是逐行变化的向量。这种"一个查询向量 vs 一列候选向量"的用法是最典型的向量近邻检索查询形态,后端为其专门提供了常量列快速路径(见后文)。

结果解读与精度说明:结果中的0.140000020.130000010.10000001这类"毛边"是float单精度浮点运算的正常现象。可以手工验证:示例中的向量并未归一化,例如第 2 行的0.2*0.1 + 0.1*0.2 + 0.3*0.3 = 0.13,这正是两个向量的点积而非严格意义的余弦相似度。若希望得到真正的余弦值,应先将向量归一化,或直接使用cosine_similarity

四、norm 版本的实现真相:它就是点积

这是理解cosine_similarity_norm最重要的源码细节。

在 BE 表达式执行层 be/src/exprs/math_functions.cpp 中,定义了一个向量相似度算法枚举:

enum class VectorSimilarityAlgorithm { kCosineSimilarity, kNormalizedCosineSimilarity, kInnerProduct, };

函数入口MathFunctions::cosine_similarity(math_functions.cpp#L1567-L1574)通过模板布尔参数isNorm选择算法分支:

template <LogicalType TYPE, bool isNorm> StatusOr<ColumnPtr> MathFunctions::cosine_similarity(FunctionContext* context, const Columns& columns) { if constexpr (isNorm) { return vector_similarity<TYPE, VectorSimilarityAlgorithm::kNormalizedCosineSimilarity>(context, columns, "cosine_similarity"); } return vector_similarity<TYPE, VectorSimilarityAlgorithm::kCosineSimilarity>(context, columns, "cosine_similarity"); }

关键在于:vector_similarity的所有热点循环(math_functions.cpp#L1260-L1377)中,范数累加与除法都包裹在if constexpr (algorithm == VectorSimilarityAlgorithm::kCosineSimilarity)之内。对于kNormalizedCosineSimilarity分支,这些代码在编译期被完全剔除,最终每行只执行一个操作——累加内积后直接输出:

} else { out[i] = sum; // kNormalizedCosineSimilarity 与 kInnerProduct 共用此路径 }

从源码结构看,这带来两点结论:

  1. 语义层面cosine_similarity_norm(a, b)的返回值就是dot(a, b)。对单位向量而言,点积恰好等于夹角的余弦值,因此"假设输入已归一化"是严格成立的——函数名中的 "norm" 指的不是"函数内部会做归一化",而是"输入向量已被归一化(normalized)"。
  2. 性能层面:相比cosine_similarity需要额外累加两个向量的平方和并做一次开方/除法,norm 版本每元素少做两次乘加累加和一次除法,向量化路径下的收益在向量维度很高(如 768 维、1024 维的 Embedding)时尤为明显。这与文档推荐使用场景——"向量已在写入侧完成归一化"——完全吻合。

另外可以观察到,approx_cosine_similarity(函数 ID 10106)复用了cosine_similarity<TYPE_FLOAT, false>的实现(见 functions.py#L66),从源码结构看,"approx" 前缀的函数是面向向量索引近似查询场景的入口,与本文讨论的精确计算函数共享同一套标量实现。

五、执行路径与错误处理

vector_similarity主函数(math_functions.cpp#L1379-L1565)在处理每批数据时依次完成以下校验与分派:

1. 输入合法性校验(直接对应文档"如果任一输入参数为 null 或非法,将报告错误"):

  • 两列行数必须相等,否则返回requires equal length arrays错误;
  • 列级或元素级不允许出现 NULL,返回does not support null values错误;
  • 每行维度必须一致且非空,否则分别返回requires equal length arrays in each rowrequires non-empty arrays错误。

2. 常量列快速路径:当查询侧是常量数组(如示例中的[0.1, 0.2, 0.3])时,实现通过ConstColumn检测走vector_similarity_fixed_query分支(math_functions.cpp#L1260-L1324),直接以 size-1 的底层数据指针作为查询向量,避免对常量列做全量物化展开,这正是"常量向量 × N 行候选向量"这一典型查询形态的优化。

3. 定长向量化路径与 AVX2 优化:当所有行维度一致时,走vector_similarity_fixed_dim_float分支(math_functions.cpp#L1326-L1377),内部使用 256 位 SIMD 指令每轮并行处理 8 个float

for (; j + 7 < dim; j += 8) { __m256 base_vec_data = _mm256_loadu_ps(base_vec + j); __m256 target_vec_data = _mm256_loadu_ps(target + j); __m256 mul_vec = _mm256_mul_ps(base_vec_data, target_vec_data); sum_vec = _mm256_add_ps(sum_vec, mul_vec); ... }

在开启 AVX2 的构建中,norm 路径只需"乘 + 累加"两条向量指令即可完成 8 个维度的内积;非 norm 路径则还需额外维护两个平方和累加器。尾部不足 8 维的部分退化为标量循环,且对极小向量还使用fast_rsqrt_nr(快速逆平方根近似)替代精确开方。

4. 零向量行为差异:非 norm 的kCosineSimilarity分支中,任一向量为零向量时返回 0(避免 0/0);norm 分支由于不做除法,零向量自然得到点积 0,不会产生 NaN/Inf。单元测试cosineSimilarityNorm(math_functions_test.cpp#L1996-L2009)验证了该语义:

// cosine_similarity with isNorm=true (pre-normalized vectors) float inv_sqrt2 = 1.0f / std::sqrt(2.0f); auto base_col = build_float_array_column({{1, 0, 0}, {inv_sqrt2, inv_sqrt2, 0}}); auto target_col = build_float_array_column({{1, 0, 0}, {inv_sqrt2, 0, inv_sqrt2}}); auto result = MathFunctions::cosine_similarity<TYPE_FLOAT, true>(ctx.get(), columns); ASSERT_FLOAT_EQ(res[0], 1.0f); // 同一单位向量 -> 1 ASSERT_NEAR(res[1], 0.5f, 1e-5f); // 夹角 60° 的单位向量 -> 0.5

同测试文件还覆盖了常量基向量(cosineSimilarityConstBase)、常量目标向量(cosineSimilarityConstTarget)、双常量(cosineSimilarityBothConst)以及维度不匹配报错等分支,与上文描述的执行路径一一对应(见 math_functions_test.cpp#L1933-L1994)。

六、与 cosine_similarity 的选择指南

结合文档语义与上述源码实现,可以给出清晰的选择依据:

维度cosine_similarity_normcosine_similarity
输入假设向量已在写入/预处理阶段归一化任意模长向量
内部计算纯点积点积 ÷ (‖a‖·‖b‖)
单行计算开销一次内积内积 + 两个范数平方和 + 开方/除法
未归一化输入下的结果点积(可能超出 [-1, 1])严格意义的余弦相似度
对应源码算法kNormalizedCosineSimilaritykCosineSimilarity

实践建议:如果你管理着一张向量表(例如 Embedding 表),在数据写入前完成归一化是更优策略——归一化是一次性的离线开销,而查询路径上每行都能省去范数计算。这也解释了为什么 StarRocks 同时提供两个函数:把"是否需要归一化"的决定权交给数据生产者,查询侧按数据形态选用对应函数。

七、小结

  • cosine_similarity_norm(a, b)接受两个同维度的Array<float>,在输入向量已归一化的前提下返回夹角余弦值,取值 [-1, 1];
  • 其本质实现是"预归一化向量点积":BE 源码中kNormalizedCosineSimilarity分支剔除了范数计算,配合 AVX2 定长向量化与常量列快速路径,是三者(cosine_similarity_normcosine_similarityinner_product)中查询开销最低的路径;
  • 输入为 NULL、维度不一致或数组为空时均会直接报错,便于在数据质量环节尽早暴露问题;
  • 向量未归一化时请改用 cosine_similarity,或在写入侧先行归一化后再用本函数。

如需继续深入,可参考仓库中函数注册表 gensrc/script/functions.py、BE 实现 be/src/exprs/math_functions.cpp 与测试用例 be/test/exprs/math_functions_test.cpp,以及官方文档 cosine_similarity_norm 参考页。

【免费下载链接】starrocksThe world's fastest open query engine for sub-second analytics both on and off the data lakehouse. With the flexibility to support nearly any scenario, StarRocks provides best-in-class performance for multi-dimensional analytics, real-time analytics, and ad-hoc queries. A Linux Foundation project.项目地址: https://gitcode.com/GitHub_Trending/st/starrocks

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/18 14:34:19

如何快速完成 Office 一键安装与激活:LKY Office Tools 实操指南

如何快速完成 Office 一键安装与激活&#xff1a;LKY Office Tools 实操指南 【免费下载链接】LKY_OfficeTools 一键自动化 下载、安装、激活 Office 的利器。 项目地址: https://gitcode.com/GitHub_Trending/lk/LKY_OfficeTools 给一台新电脑装 Office&#xff0c;光找…

作者头像 李华
网站建设 2026/9/18 14:34:19

Linux CPU锁频与绑核实战:从性能波动到稳定可复现

搞过Linux服务器的人应该都有这种经历&#xff1a;跑一个计算密集型的任务&#xff0c;明明CPU很强&#xff0c;但执行时间忽快忽慢&#xff0c;有时一次编译等得人心焦。打开top一看&#xff0c;频率在3.0GHz和4.5GHz之间跳来跳去&#xff0c;核心也一会儿满载一会儿歇着。如果…

作者头像 李华
网站建设 2026/9/18 14:34:01

ARIMA+LSTM双模型轴承故障预测实战

简介&#xff1a;本资源是一份面向工业智能化从业者与深度学习初学者的实战型技术文档&#xff0c;聚焦轴承故障预测性维护这一典型工业AI落地场景&#xff0c;依托PyTorch框架构建时序建模与早期预警系统。全文共29页PDF&#xff0c;结构严谨、章节完整&#xff0c;涵盖预测性…

作者头像 李华
网站建设 2026/9/18 14:31:07

为什么 coding agent 主流选择 Node.js 而非 Rust 或 Python

1. 为什么市面上的 coding agent 大多数都基于 Node.js&#xff1f;——一个从业十年的全栈工程师的硬核拆解你打开 GitHub Trending&#xff0c;刷一遍最近三个月爆火的 coding agent 项目&#xff1a;Cursor、Tabby、Continue、Bloop、CodeWhisperer 的开源替代品、甚至不少大…

作者头像 李华