微信扫码
添加专属顾问
我要投稿
揭秘向量背后的隐藏信息:从一串浮点数中竟能还原原始文本和模型来源!核心内容: 1. 向量溯源技术:通过80万参数小模型精准识别68种生成模型,准确率达87% 2. 文本逆向工程:采用掩码扩散模型实现81%的token级原始文本还原 3. 突破性方法:将浮点数转为字符级token处理,建立独特的模型数字指纹
🔬 在线 Demo: embedding-inversion-demo.jina.ai
📊 技术报告: jina.ai/news/embedding-fingerprints
📊 技术报告: jina.ai/news/embedding-inversion
📄 论文: arxiv.org/abs/2602.11047
用户把文本发到我们的 API,我们返回一串浮点数。没有标签,没有水印,没有任何元数据告诉你它从哪来、用的什么模型。大多数人看到这串数字,反应都是"不就是一堆浮点数嘛,能看出什么?"
过去两周,我们做了两项研究,发现能看出的东西远比想象中多:
模型溯源:我们训练了一个仅有 80 万参数的小模型。光看一串浮点数,就能判断出这是哪个模型生成的,准确率高达 87%,覆盖 68 个模型和任务组合。
文本逆向:我们用掩码扩散模型,直接从向量中反推出原始文本。Token 级还原准确率达到了 81%。
也就是说,向量不仅携带语义,还带着生成它的模型的基因,以及原始文本的大量信息残留。
当有人给你一个 1024 维的向量,你能判断它是 BGE-M3 生成的,还是 jina-embeddings-v3,还是 Qwen3-Embedding 吗?
再往前走一步: 同一个模型,你能分辨推理时用的是 retrieval 指令还是 classification 指令吗?
直觉上这似乎不可能。向量就是一堆数字,模型之间的差别应该体现在语义空间的几何结构上,而不是数值本身的分布细节。但实验结果直接推翻了这个直觉。
不同模型产生的浮点数,在统计上有非常显著的差异。值域范围、各小数位数字出现的频率、维度间的相关模式...... 这些特征拼在一起,构成了模型独特的数字指纹。这有点像笔迹鉴定,同一段话让不同人抄写,内容一样,但笔画的力度、倾斜和连笔方式都会无意间暴露书写者的身份。
这个方法最反直觉的地方在于输入表示。我们没有把向量当成一堆浮点数,而是把每个数字转成了字符串,像处理文本一样,逐个字符拆开喂给模型。
如下图所示,-0.1234 会变成 token 序列 [CLS] - 0 . 1 2 3 4,维度之间用 [SEP] 隔开。整个词汇表极简,只有 15 个 Token:数字 0 到 9、负号、小数点、几个特殊分隔符。
为什么不使用更直观的量化分桶(Binning):把数据范围划分为 256 个区间(bin),一个维度对应一个 Token,这样 1024 维的向量只需 1024 个 Token 就能表达完。
因为量化需要预设边界,而不同模型的数值分布天差地别:有的集中在 0 附近,有的铺满整个 [-1, 1] 区间,同一个模型不同维度的分布也完全不同。任何固定的分桶方案,都会在某些模型上造成严重的分辨率损失,掩盖掉最关键的特征。
所以我们选了最“无假设”的方案:位级别分词(Digit-level Tokenization)。虽然这会让一个 1024 维向量膨胀成 7700 个 Token 的长序列,但它原样呈现了数字的每一位,保留了所有的统计痕迹,不预设规律,让模型自己去学习其中的统计特征。
比如某些模型在特定位上出现数字 7 的频率更高,或者某些维度的排列模式更独特。这些看似无意义的微观特征,恰恰构成了模型的数字指纹。
分类器本身是个 4 层 Transformer,128 维,4 个注意力头,总共约 80 万参数 。用 RoPE 做位置编码,SwiGLU 做 FFN,RMSNorm 归一化。CLS token 池化后投影到 68 个输出类别。
80 万参数是什么概念?比 GPT-2 小两个数量级。但这个任务词汇表只有 15 个 token,分类目标是 68 个类别,瓶颈不在模型大小,而是如何从 7700 个 token 的长序列里提取出能区分 68 个类别的统计特征。
训练数据方面,我们准备了一万条多语言文本,分别送入 25 个以上的模型,配合不同 task prefix 编码,产生 68 个类别。每类 7000 条训练样本,3000 条验证样本。
这 68 个类别不只是 68 个模型。其中包含了同一个模型搭配不同 instruction prompt 的组合,比如 jina-embeddings-v5-text-small 配 retrieval 指令和配 classification 指令算两个不同类别。如果分类器能区分它们,说明仅仅换一个 instruction prompt,就足以在输出数值上留下可检测的痕迹。
在这些类别中,最难的分组是 1024 维度,包含 32 类模型,分类器无法通过序列长度投机取巧,必须纯粹依赖数值模式。
我们在 A100 上跑了 14 个 Epoch,处理了大约 430 亿个 Token。
最终,训练准确率达到了 87.3%,验证准确率为 86.0%。两条曲线贴得很近,说明模型真的学到了真实的数值规律,而不是在死记硬背训练集。
87% 准确率,随机猜的话是 1.5%。比瞎猜好 59 倍。
观察这 20.4 万个样本的混淆矩阵,能看到几个有趣的现象:GTE-large、LaBSE、Paraphrase MiniLM 等模型被 100% 准确识别。跨系列的区分(如 BGE vs Jina)非常容易,模型底层的架构和训练方法留下的指纹最深。最难分辨的是同一个基础模型搭配不同的任务前缀。
最让人意外的是,同一个模型、完全相同的权重,仅仅是推理时换了一个 instruction prompt,输出的数值分布就会产生可检测的差异。jina-embeddings-v5-text-small在 5 个不同 task prefix 之间达到了 92% 的区分准确率。任务适配(Task adaptation)不只是在语义空间里挪了个位置,它实际上改变了数值的统计分布。
这件事有非常直接的用途。接手了一个向量数据库但不知道当初用的什么模型?取出一条向量送进分类器就能识别。怀疑某个 API 声称用的是 model A 但实际偷偷换成了 model B?向量指纹可以验证。向量模型的提供商悄悄升级了版本?指纹同样会暴露这个变化。
如果识别模型来源只是让人意外,那接下来的事就有点狠了:从向量里逆向出原始文本。
给你一个 1024 维的向量,还原出生成它的那段文本。
这不是全新的问题。2023 年 Morris 等人做的 Vec2Text 用 T5 编解码器(Encoder-Decoder)在 32 token 的序列上做到了 92% 的精确匹配(Exact Match)。但那个方法有个根本限制:它需要反复调用目标模型的 API 进行迭代修正,动辄 20 多轮,推理成本直接翻了一个数量级。
我们的目标更激进: 只看向量本身,不碰目标模型,一次性把文本还原出来。
我们将这个问题重新定义为一个条件掩码扩散(Conditional Masked Diffusion)问题。
从一个全 [MASK] 的序列开始,模型在每一步同时预测所有位置的 token,逐步替换成真实词汇。整个过程只需要 8 步。不是从左到右一个个生成,而是所有位置并行去噪。
步骤 8/8 [MASK] [MASK] [MASK] [MASK] [MASK]
步骤 6/8 [MASK] quick [MASK] [MASK] [MASK]
步骤 3/8 The quick brown [MASK] jumps
步骤 1/8 The quick brown fox jumps
去噪过程: 从全掩码出发,每一步同时预测所有位置,8 步内完成还原。
我们通过自适应层归一化(Adaptive Layer Normalization,AdaLN) 将目标向量注入到 Transformer 的每一层。向量先经过一个投影层,然后在每一层生成缩放(Scale)和位移(Shift)参数,用来调制层归一化的输出。时间步也通过同样的机制注入。两个信号相加,模型同时知道"当前去噪到了哪一步"和"目标向量长什么样"。
条件掩码扩散语言模型的架构。上半部分是训练流程: 输入文本经 embedding encoder 编码后,通过 MLP 投影注入 Transformer。下半部分是去噪过程: 从全掩码序列出发,逐步还原。
这个设计一举解决了 Vec2Text 的两个问题。第一,推理时完全不需要接触原始模型,向量是唯一的输入。第二,所有位置并行预测,避免了自回归从左到右的误差累积。而且 AdaLN 的条件注入方式天然不挑编码器:不管目标向量来自什么模型、什么维度,过一遍投影 MLP 就能注入同一个 Transformer。
底座模型我们选用了 22 层、3.8 亿参数的多语言 BERT。在冻结预训练权重后,我们只训练投影层、AdaLN 模块和输出层,实际训练参数量约 1.9 亿。训练数据是 mC4 的 200 万条多语言样本,统一截断到 32 个 token。
# 训练核心伪代码
for text, embedding in dataloader:
t = uniform(0, 1) # 随机采样时间步
masked = random_mask(text, t) # 按比例随机掩码
cond = mlp_project(embedding) # 投影 embedding
logits = model(masked, t, cond) # 条件预测
loss = cross_entropy(logits[masked_pos], text[masked_pos])
训练流程: 随机掩码 + 条件去噪。噪声调度用 log-linear schedule,集中在后期掩码,前期保留结构。
我们分别在三个主流模型上进行了实测,还原率都很惊人。
三个编码器上的 token 还原准确率,使用顺序贪心解码(Sequential Greedy Decoding),序列长度 32 token。每个编码器单独训练。
四个编码器配置的训练准确率曲线。所有模型在 50K 步后趋于收敛。
为了看清这个数字的含金量,我们设定了一个基准对照组:让一个普通的语言模型在完全不看向量的情况下盲猜文本。它能写出非常流畅的句子(BLEU 分数很高),但 Token 准确率只有 2.1%。随机 token 更惨,0.02%。
"能说人话"和"说对的话"是两回事。 模型必须从向量中真正提取语义信息,才能在最低的编码器上也做到 76% 的还原。
在解码策略上,我们测试了五种方案。表现最好的是欧拉 + 重掩码(Euler + Remasking):模型每一步去噪后,都会自我检视一遍,把置信度最低的 5% 重新掩码,交给下一步去修正。相当于把 Vec2Text 的外部修正循环内化到了扩散过程里。
实验证明 5% 是一个最优平衡点,比例太多丢掉正确的预测,太低则修正力度不够。
欧拉采样(Euler Sampling) |
|||
欧拉 + 重掩码(Euler + Remasking) |
|||
两阶段(Two-Stage) |
不同解码策略在 10 种语言上的平均余弦相似度。需要注意,这里衡量的是还原文本与原始文本的向量相似度,而非 Token 级准确率。两个指标下的最优策略不完全一致。综合来看,Sequential Greedy 在大多数编码器上表现得最为稳健,是目前最可靠的还原主力。
一个 1024 维的 float32 向量只有 4KB。但这 4KB 足以让一个扩散模型还原出 32 个 token 中超过 80% 的内容。
在很多人的认知中,向量是一道天然的脱敏屏障:文本转化为高维坐标后,原始信息便不可回溯。很多系统也都宣称:“我们只存向量,不存原文,所以用户隐私是绝对安全的。”
这两项研究动摇的正是这个假设。向量不是不可逆的哈希,而是一个有损但信息密度极高的压缩表示。模型的身份、原始文本的内容,都编码在里面,提取的门槛也在不断降低。
指纹研究(Fingerprint)证明了每个模型编码语义的数值风格各不相同,区分度高到一个 80 万参数的小模型就能认出 68 个来源。逆向研究(Inversion)则证明了这些数字里编码的原始信息,足够扩散模型还原出 80% 以上的内容。两个工作串联起来,先用指纹分类器锁定编码器,再用对应的逆向模型还原文本,就构成了一条完整的攻击链路。
对模型开发者来说,这些发现迫使我们直面一个此前被忽视的问题:向量模型的安全边界到底在哪里?过去我们把精力都投在提升语义检索性能上,但从来没认真想过这些向量在安全性上到底暴露了多少。
我们是否该在训练中引入某种信息瓶颈?在保留检索能力的同时,降低向量的可逆性,或者至少让模型的数值指纹不再那么容易被识破?
当然,硬币有另一面。在 API 提供商可以悄悄换模型的世界里,能从数值本身验证模型来源,本身就是一种有价值的审计能力。
两个项目的代码和在线 demo 均已开源:
🔬 在线 Demo: embedding-inversion-demo.jina.ai
💻 代码仓库: github.com/jina-ai/embedding-fingerprints
53AI,企业落地大模型首选服务商
产品:场景落地咨询+大模型应用平台+行业解决方案
承诺:免费POC验证,效果达标后再合作。零风险落地应用大模型,已交付160+中大型企业
2026-02-27
如何用 AI 做业务级 Code Review
2026-02-22
不用向量数据库的 RAG,居然跑得更准了?
2026-02-22
AIOps探索:做运维领域的RAG,如何做数据清洗
2026-02-21
Claude Code 每次都要重新探索代码?这个工具直接省下30%成本
2026-02-18
函数计算 AgentRun 重磅上线知识库功能,赋能智能体更“懂”你
2026-02-15
当RAG遇上Agent记忆:为什么相似度检索会"塌方"?
2026-02-15
查个问题还要全图跑一遍?DA-RAG说我只取一瓢
2026-02-13
深度解析 PageIndex:无向量 RAG 框架的技术实现与原理剖析
2026-01-15
2026-01-02
2025-12-23
2025-12-18
2026-02-03
2026-02-03
2026-02-13
2025-12-31
2026-01-06
2025-12-29
2026-02-22
2026-02-15
2026-02-04
2026-02-03
2026-01-19
2026-01-12
2026-01-08
2026-01-02