Daft 向量距离扩展 dvector 实战指南:基于原生 Rust 扩展实现 L2、余弦、汉明等向量距离函数
【免费下载链接】DaftHigh-performance data engine for AI and multimodal workloads. Process images, audio, video, and structured data at any scale项目地址: https://gitcode.com/GitHub_Trending/da/Daft
dvector 是 Daft 官方示例仓库中提供的一个原生扩展(native extension),它以 Rust 动态库(cdylib)的形式为 Daft 数据引擎补充了 6 个向量距离标量函数,可直接嵌入 Daft 表达式 DSL 对向量列做逐行距离计算。读完本文,你将掌握 dvector 全部函数的数学定义与边界行为、向量列的数据类型约束、安装与加载方式,以及从 Python 包装层(examples/dvector/dvector/init.py)到 Rust 内核(examples/dvector/src/lib.rs、examples/dvector/src/vectors.rs)的完整实现原理。
dvector 是什么:为 Daft 定制的向量距离函数扩展
Daft 原生扩展基于 Arrow C Data Interface 这一稳定的 C ABI 构建,扩展与 Daft 内部的 arrow-rs 版本解耦,任何遵守该 ABI 的 Rust 扩展都能被 Daft 运行时通过dlopen加载(参见 docs/extensions/authoring.md)。dvector 正是这条扩展路径的一个完整落地示例:它把一组向量相似度/距离计算下沉到 Rust 侧,通过会话(Session)注册为 Daft 函数,从而在 Daft DataFrame 中直接以表达式的方式调用,全程无逐行 Python 开销。
dvector 提供的全部函数如下(与 examples/dvector/README.md 的函数表一致):
| 函数 | 描述 | 输入类型 | 输出类型 |
|---|---|---|---|
l2_distance(a, b) | 欧几里得距离(L2) | float 向量 | Float64 |
inner_product(a, b) | 负内积(遵循 pgvector 约定) | float 向量 | Float64 |
cosine_distance(a, b) | 余弦距离(零模向量返回 null) | float 向量 | Float64 |
l1_distance(a, b) | 曼哈顿距离(L1) | float 向量 | Float64 |
hamming_distance(a, b) | 不同位置计数 | boolean 向量 | UInt32 |
jaccard_distance(a, b) | 1 - 交集/并集(并集为空时返回 null) | boolean 向量 | Float64 |
向量列支持FixedSizeList、List或LargeList三种布局;浮点向量元素支持Float32与Float64两种精度(布尔向量元素则为Boolean)。
向量数据类型的完整约束
三种列表布局
dvector 的两个输入参数必须是向量列,向量既可以是定长的FixedSizeList,也可以是变长的List(i32 offset)或LargeList(i64 offset)。在 src/vectors.rs 中,三种布局被统一抽象为VectorLayout枚举:
Fixed(i32):定长列表,每个向量占用固定元素个数;Variable32(&OffsetBuffer<i32>):List的偏移缓冲;Variable64(&OffsetBuffer<i64>):LargeList的偏移缓冲。
元素精度与统一视图
浮点向量元素支持Float32和Float64。从源码 src/vectors.rs 可以看到,extract_float_values对Float64元素直接借用底层切片(零拷贝),对Float32元素则逐元素提升为f64后持有为Vec<f64>;任何其他元素类型都会返回TypeError(“unsupported element type ... expected Float32 or Float64”)。因此所有浮点距离函数内部统一以f64计算。
布尔向量(汉明、杰卡德距离)要求元素类型严格为Boolean,否则同样抛出TypeError(src/vectors.rs)。
空值(null)语义
向量列本身允许包含 null 行:只要任意一侧的向量为 null,对应输出行即为 null(见 src/vectors.rs 的空值检查与 tests/test_dvector.py 的test_l2_with_null用例)。此外:
cosine_distance在任一向量模为零(分母为 0)时返回 null,而不是报错或返回 NaN(src/lib.rs);jaccard_distance在并集为空(两个向量所有位均为 false)时返回 null(src/lib.rs)。
维度校验
同一行内两个向量维度不一致会触发运行时错误:dimension mismatch at row {row}: {a_len} vs {b_len}(src/vectors.rs)。换言之,dvector 要求逐行配对的两个向量长度必须相同,但不同行之间的维度可以不同(例如变长列表列)。
安装与构建
dvector 是源码级示例扩展,安装前需要准备:
- Rust 工具链(cargo / rustc),因为需要把 Cargo.toml 中声明的 Rust crate 编译为
cdylib; - Python 3.10+,以及
setuptools-rust构建后端。
构建配置的关键点在 setup.py 与 pyproject.toml:
pyproject.toml声明setuptools+setuptools-rust作为构建系统,项目依赖daft,并通过[tool.uv.sources]把 daft 指向仓库根目录(path = "../..", editable = true);setup.py通过RustExtension("dvector.libdvector", ...)将编译产物.so放到 Python 包dvector/目录内(Binding.NoBinding,因为扩展导出的是原始 C 符号而非 PyO3 绑定,strip=True压缩体积);- Cargo.toml 中
[lib]的crate-type = ["cdylib"]保证生成可被dlopen加载的动态库,并依赖daft-ext(启用arrow-57特性,与仓库使用的 arrow-rs 57 系列版本匹配)。
开发模式下推荐用 uv 以可编辑方式安装并运行测试:
# 编译 Rust cdylib 并以可编辑模式安装(含原生库) uv pip install -e . # 运行测试 pytest -v tests/快速上手:在 Daft DataFrame 中使用 dvector
加载扩展
安装完成后,dvector 的函数并不会自动生效,需要显式加载到当前会话。Daft 的Session.load_extension接受模块、模块路径或共享库文件路径(实现见 daft/session.py),并且在进程内只会dlopen一次;函数仅在加载了该扩展的会话中可用(详见 docs/extensions/authoring.md 的 Session isolation 说明,以及 tests/test_dvector.py 中“未加载扩展即调用会报not found”的验证用例)。
import daft import dvector from dvector import l2_distance # 将扩展加载进当前活动会话 daft.load_extension(dvector)计算 L2 距离
构造两个浮点向量列并直接以表达式调用l2_distance(示例取自 examples/dvector/README.md):
df = daft.from_pydict({ "a": [[1.0, 2.0, 3.0], [0.0, 0.0, 0.0]], "b": [[4.0, 5.0, 6.0], [1.0, 1.0, 1.0]], }) df.select(l2_distance(daft.col("a"), daft.col("b"))).collect() # ╭───────────╮ # │ result │ # │ --- │ # │ Float64 │ # ╞═══════════╡ # │ 5.196152 │ # ├╌╌╌╌╌╌╌╌╌╌╌┤ # │ 1.732051 │ # ╰───────────╯第一行sqrt((1-4)^2 + (2-5)^2 + (3-6)^2) = sqrt(27) ≈ 5.196152,第二行sqrt(0+0+0) = 0与[1,1,1]的距离为sqrt(3) ≈ 1.732051,与输出完全吻合。
Python 包装层如何映射到 Rust 函数
dvector/init.py 中每个函数都是一行薄包装:通过daft.get_function("dvector_l2_distance", a, b)之类的调用,按名称在当前会话中解析已注册的扩展函数(get_function的实现见 daft/session.py 附近)。函数名统一采用dvector_<fn>前缀,避免与其他扩展冲突(这是 docs/extensions/authoring.md 推荐的命名规范)。因此你既可以直接使用这些 Python 包装函数(带类型标注与文档字符串),也可以理解它们的本质是名称到会话内注册函数的解析。
源码级原理:六个距离函数的内核实现
扩展注册入口
src/lib.rs 定义了扩展模块:
#[daft_extension]宏为共享库生成 Daft 运行时查找的 C 符号(daft_module_magic);impl DaftExtension for DvectorExtension在install(session)钩子中通过session.define_function依次注册六个函数;- 每个函数是一个独立结构体,由
#[daft_func_batch(return_dtype = ...)]宏声明输出类型,接收ArrayRef(Arrow 数组)并返回DaftResult<ArrayRef>,数据全程以 Arrow 数组批量流动,无逐行 Python 解释开销。
浮点距离:L2 / 内积 / 余弦 / L1
四个浮点函数共用 src/vectors.rs 中的统一向量列视图FloatVectorColumn与三种批量映射器(map_float_vectors、map_float_vectors_nullable、map_bool_vectors_*),核心逻辑如下(src/lib.rs):
dvector_l2_distance:sqrt(sum((x-y)^2));dvector_inner_product:-(sum(x*y)),即负内积。这是 pgvector 的排序约定——距离越小表示内积越大,便于按“相似度优先”做升序 top-K;测试用例 tests/test_dvector.py 验证了1*4+2*5+3*6 = 32取负后输出-32.0;dvector_cosine_distance:一次遍历同时累加点积与两向量各自平方和,计算1 - dot/(|a|*|b|);当分母为 0(零模向量)时返回None即输出 null(src/lib.rs);dvector_l1_distance:sum(|x-y|)。
布尔距离:汉明 / 杰卡德
两个布尔函数通过BoolVectorColumn统一视图读取布尔向量(每行切片为一个BooleanArray),逻辑为(src/lib.rs):
dvector_hamming_distance:filter(|x, y| x != y).count(),输出 UInt32;dvector_jaccard_distance:先统计并集(任一为 true),并集为 0 时返回 null;否则统计交集(两者均为 true),输出1 - inter/union。测试用例 tests/test_dvector.py 验证了[T,T,F,F]与[T,F,T,F]的输出为1 - 1/3 = 2/3,以及全 false 向量输出 null。
批量映射与性能设计
src/vectors.rs 中的映射器在实现上做足了向量化基础工作:按列长度预分配Float64Builder/UInt32Builder容量、借用底层f64切片避免拷贝(F64Values::Borrowed)、行级 null 传播、维度校验前置。这意味着距离计算完全发生在 Rust 的 Arrow 数组层,天然适合 Daft 的列式执行模型——这是该扩展能够在批量 embedding 相似度打分等场景直接嵌入查询管道的原因。
测试与质量保障
dvector 的测试覆盖(tests/test_dvector.py)是一个值得参照的扩展验证模板:
- 会话隔离:
sessfixture 创建独立Session并load_extension(dvector),测试通过with sess:上下文管理器限定查询会话; - 数值正确性:对 L2、内积、余弦、L1、汉明、杰卡德逐一断言精确数学值(如
sqrt(2)、-32.0、1 - 1/sqrt(2)、2/3); - 边界行为:null 行传播为 null、零模向量余弦为 null、全 false 杰卡德为 null、未加载扩展时调用报错。
运行pytest -v tests/即可在本地复现全部用例。
典型应用场景
基于以上能力,dvector 适合在 Daft 管道中直接完成以下工作:
- 向量检索/相似度排序:对 embedding 列两两计算 L2 或余弦距离,配合 Daft 的
sort/limit做 top-K 近似最近邻筛选(内积遵循 pgvector 约定,便于复用排序语义); - 多模态/批量打分:对图像、文本等模态产出的浮点向量批量计算距离矩阵或逐行打分;
- 集合类相似度:对布尔向量(如标签集合的 one-hot/多热编码)使用汉明或杰卡德距离,例如文档去重、样本相似度筛查。
总结
dvector 是理解 Daft 原生扩展机制的极佳范例:它以 6 个标量函数覆盖了浮点与布尔向量两类距离计算,兼容三种列表布局与两种浮点精度,并完整实现了 null 传播、维度校验等边界语义。无论是直接把它作为向量距离函数库使用,还是对照 docs/extensions/authoring.md 将它作为编写自有 Rust 扩展的蓝本,你都可以从本仓库的 examples/dvector/ 目录中获得开箱即用的参考实现。
【免费下载链接】DaftHigh-performance data engine for AI and multimodal workloads. Process images, audio, video, and structured data at any scale项目地址: https://gitcode.com/GitHub_Trending/da/Daft
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考