
开源社区里出现了一个现象每隔一段时间就有团队宣称“完全从零训练了一个大模型”但很快被社区发现它实际上是在某个知名开源模型上做了一步微调或换皮重命名。反过来也有一些真正从头训练的小模型因为表现不错被怀疑“偷偷抄袭了闭源模型”。这类争议背后其实是一个非常关键的技术问题我们能不能用技术手段判断一个LLM到底是从零训练Trained from Scratch的还是由已有模型派生Derived出来的Model Genome模型基因组正是针对这个问题提出的一类思路。它的命名逻辑并不复杂生物学家可以通过DNA指纹识别血缘关系那么模型训练过程也会留下大量“遗传痕迹”——写在权重里、写在词表里、写在行为输出里。只要把这些痕迹提取出来量化就能回答“这个模型和其他模型有没有血缘关系”。这篇文章会把Model Genome的原理、四类核心指纹特征、以及一个最小可运行的检测实验思路讲清楚。读完你至少能明白两件事第一为什么模型溯源在技术上可行第二如果你想给自己的模型建一份“基因档案”应该从哪些维度下手。1. 为什么“模型从哪里来”正在成为AI治理核心问题很多人觉得模型是从零训练还是微调出来的只是“学术履历”上的差别不影响使用。但在真实的工程和商业场景里这个问题影响面远比想象中大。第一类场景是开源合规。开发者在选型时看到一个模型文档里写的是“从零训练与XX模型无关”于是在商业项目里放心使用。但一旦模型实际是从某个非商用授权的基础模型派生出来的整个项目的合规基础就崩塌了。这时候如果没有技术手段去核验就只能靠对方自觉。第二类场景是政企验收。很多单位采购AI产品时合同会写“模型须为厂商自研不得使用第三方未授权模型”。验收方不可能把模型拆开看训练日志能依赖的唯一客观证据就是模型本身留下的指纹。第三类场景是安全事故溯源。模型出了输出问题或者被检测出带有某种数据后门责任方可能会辩解“这是我们自研的新模型与XX无关”。此时如果模型指纹显示它和某个基础模型在权重分布和行为特征上高度一致那这个辩解就不攻自破。这里要给出一个明确判断模型指纹识别不是“锦上添花”的研究方向而是AI工程化进入深水区之后的必要基础设施。以前模型少、上下游简单靠人工登记就能管理模型血缘现在开源模型生态爆炸一个模型可能是另一个模型的子模型也可能是某个新模型的父模型没有自动化的指纹识别工具治理成本会指数级上升。这篇文章适合三类读者负责模型选型或合规审计的技术负责人需要理解指纹识别能做到什么程度、不能做到什么程度。大模型应用开发者想避开“换皮模型”的坑选到真正适合项目的底座。对AI安全和模型可解释性感兴趣的研究者想了解当前模型血缘检测的主流技术路径。2. Model Genome 与模型指纹识别的基本概念Model Genome 这个词借用了生物学概念。在生物学里基因组是某物种或个体全部遗传信息的集合哪怕只看一小段序列也能判断两个个体是不是近亲。同样地一个模型的训练过程会留下许多可观测痕迹这些痕迹的集合就是“模型基因组”而对特定标记的提取和比对就是“模型指纹识别”Model Fingerprinting。与传统的“模型水印”Watermarking要区分开。水印是训练者在训练时主动嵌入的标记目的是“我的模型我能认出来”指纹识别则不一定依赖主动嵌入它提取的是模型天然留下的统计特征和结构特征用来回答“这个模型和那个模型是否有共同来源”。一个是播种一个是检测两者经常配合使用。一个成熟的模型指纹方案需要满足三个核心要求要求含义如果不满足会怎样可提取性能从公开访问的模型权重或API输出中稳定提取特征指纹无法应用区分性同一个模型多次提取结果一致不同模型之间差异明显无法做出可靠判断鲁棒性对量化、裁剪、微调等常见修改有一定容忍度模型换个格式就“失忆”了再来看“从零训练”和“派生”这两个词在技术上的边界。一个严格意义上从零训练的LLM指的是从随机初始化或预训练目标设定的初始化开始在大量语料上执行完整的预训练流程。而派生模型则包括很多情况在开源模型上继续预训练、做SFT微调、用LoRA/QLoRA做参数高效微调、通过蒸馏从大模型产出小模型甚至只是换了个名字重新发布。对比维度从零训练模型派生模型初始化随机初始化或轻量预初始化继承父模型权重Tokenizer通常独立设计或选用新增词表往往沿用父模型词表训练数据大规模自有语料在父模型能力基础上做定向调整训练目标通用语言建模对齐、指令跟随、特定任务底层表示从无到有形成大部分继承仅局部调整从这里可以看出派生模型本质上是在父模型的基础上做“增量修改”所以它的身体里必然会留下父模型的遗传信息。Model Genome 要做的就是把这种遗传信息转化为可量化的指标。3. 为什么派生模型会留下“遗传痕迹”训练管线指纹一个模型从零开始训练到最终发布会经历一条完整的训练管线。这条管线里的每一个环节都会给模型留下独特的印记。这些印记就是指纹。第一层是工程痕迹。模型的词表Vocabulary是训练前确定下来的包含哪些token、如何处理特殊符号、分词算法参数是什么一旦训练开始就很难改变。派生模型最省事的做法就是直接继承父模型的tokenizer于是词表高度重合就成了最直观的血缘证据。你可以把这个类比成生物体的碱基序列同一家族的人基因序列总有大量相同片段。第二层是权重统计痕迹。预训练过程中模型各层的权重矩阵会形成特定的分布特征尤其是Embedding矩阵的奇异值分布、Layer Norm层的均值和方差、注意力头的熵等。微调通常只改动模型的表层行为和部分参数底层表示空间依然保留着父模型的统计特性。所以即使父模型和子模型在表面回答上已经差异很大深层权重的统计指纹仍然可以对齐。第三层是行为痕迹。模型面对同样一段输入时即使最终输出的文字不同下一个token的概率分布也会带有“家族偏好”。这种偏好很难用几条规则抹掉除非做大规模的全参数重训练或强蒸馏。这里有一个关键认知指纹不是“一个特征”而是“一组证据链”。单独的tokenizer重合不能说明问题因为很多团队会复用同一套流行的tokenizer单独的权重相似度也可能受到量化影响。但当词表指纹、权重指纹、行为指纹同时指向同一个父模型时判断的置信度就会大大提升。与之相对的是也有人试图通过“逆向蒸馏”或“从头重训”来清洗指纹。但这并不是没有代价蒸馏会改变模型的行为分布重训需要大量算力。这意味着指纹识别天然有一种对抗属性检测方和规避方会不断升级方法。这也是Model Genome这类研究方向持续被关注的原因。4. 四类核心指纹特征与适用场景结合目前的研究和工程实践模型指纹可以分成四个层次每一层的提取成本、适用场景和局限性都不一样。4.1 词表指纹成本最低的入门判断词表指纹是最容易提取的特征。你只需要加载两个模型的Tokenizer统计词汇表的交集大小、特殊token集合是否一致、分词结果的粒度差异。适用场景快速筛查海量模型挑出“高度疑似复用词表”的候选对象。局限性很多模型本来就用同一种tokenizer词表相同不代表模型有关联只能作为辅助证据。4.2 权重分布指纹需要白盒访问只有能拿到模型权重文件时才能做权重分布指纹。常用做法包括统计Embedding矩阵或权重矩阵的奇异值分布计算Layer Norm层的均值、方差随层深的分布曲线统计权重中的“离群值”模式有些训练框架会在特定位置产生可识别的数值特征。这种方法的最大优势是稳定性高即使模型经过微调底层权重分布也不会完全改变。缺点是要求白盒访问并且不同架构的模型之间难以直接对比。4.3 激活与表示指纹看模型“内部怎么想”激活指纹需要向两个模型输入相同的文本然后提取中间层的隐藏状态或激活值计算它们的相似度。更严谨的做法是使用CKACentered Kernel Alignment这类不受线性变换影响的相似度指标。适用场景判断一个模型是否由某个base模型微调得到尤其适用于SFT、LoRA这类保留模型架构的派生方式。局限性如果两个模型架构不同层数不同、隐藏维度不同需要先做层对齐计算复杂度明显上升。4.4 行为输出指纹黑盒也能用行为指纹不需要访问权重只需要向模型发送探针文本probe text收集它输出的logits或者token序列。比较指标包括Top-k token重合率输出概率分布的KL散度或JS散度对特定“记忆探测器”文本的回答一致性。适用场景只能通过API访问目标的黑盒场景或者模型权重经过量化和压缩后依然需要溯源。局限性容易受到训练后处理的影响RLHF、DPO这类对齐方法会显著改变表层行为导致行为指纹失真。指纹类型提取成本白盒/黑盒对微调的鲁棒性对量化的鲁棒性主要局限词表指纹极低两者皆可高高区分性弱权重分布指纹中白盒高中跨架构不可比激活指纹中高白盒中低依赖架构对齐行为指纹低黑盒低高易被对齐方法干扰从工程角度看四类指纹不应该互相替代而应该组成一个多级漏斗先用词表指纹快速过滤再用权重/激活指纹做精确筛查最后用行为指纹验证形成闭环。5. 环境准备与前置条件在动手做实验之前先明确运行环境。下面的演示代码基于Python和深度学习推理框架目的不是复现某一个学术项目而是展示模型指纹识别的最小思路。生产环境中可以根据自己的框架做替换。推荐环境操作系统Linux 或 macOSWindows需要额外处理路径分隔符Python3.9 或更高版本深度学习框架PyTorchHugging Face Transformers 库科学计算库NumPy、SciPy版本不写死建议以你安装时的最新稳定版为准。下面是依赖文件示例# requirements.txt transformers4.40.0 torch2.1.0 numpy1.26.0 scipy1.11.0安装方式pip install -r requirements.txt实验准备的材料包括三类两个或两个以上的候选模型权重。如果你只是想体验流程可以从Hugging Face下载两个小型模型比如一个base模型和它的微调版本实验室里通常称这种配对为“已知血缘对”。一个对照组也就是确定和候选模型没有血缘关系的模型。这一步容易被忽视但没有对照组所有相似度指标都失去判断基准。一组探针文本用于触发模型行为。探针文本要覆盖不同语种、代码、常识问题尽量选择“只有特定训练来源才会表现出明显偏好”的句子。安全提醒整个过程建议在本地完成不要把你的模型权重上传到不可信的第三方服务。模型指纹识别本身是安全工具但如果使用不当也会变成窃取模型资产信息的风险行为。所有检测都应基于你自己有权访问的模型或者公开可获取的模型。6. 完整示例构建一个最小模型溯源检测流程下面通过三个代码示例演示从行为、激活、词表三个维度提取指纹并最终给出一个综合判定思路。代码以清晰为主生产环境请加上错误处理、日志和模型加载优化。6.1 行为指纹Top-k 重叠率与 KL 散度先从一个黑盒友好型指标开始。给两个模型输入同一批探针文本对比它们在下一个token预测时的Top-k集合重合率以及概率分布的差异。# fingerprint_behavior.py import torch from transformers import AutoTokenizer, AutoModelForCausalLM from scipy.stats import entropy def load_model(model_path): tokenizer AutoTokenizer.from_pretrained(model_path) model AutoModelForCausalLM.from_pretrained(model_path) model.eval() return tokenizer, model def get_next_token_distribution(model, tokenizer, text, top_k256): inputs tokenizer(text, return_tensorspt) with torch.no_grad(): outputs model(**inputs) logits outputs.logits[0, -1, :] probs torch.softmax(logits, dim-1) top_probs, top_indices torch.topk(probs, top_k) return top_indices.tolist(), top_probs.tolist() def behavior_similarity(model_a, model_b, tokenizer, probe_texts, top_k256): total_overlap 0.0 total_js 0.0 for text in probe_texts: indices_a, probs_a get_next_token_distribution(model_a, tokenizer, text, top_k) indices_b, probs_b get_next_token_distribution(model_b, tokenizer, text, top_k) set_a, set_b set(indices_a), set(indices_b) overlap len(set_a set_b) / max(len(set_a), len(set_b)) total_overlap overlap # 在共享 token 空间上计算 JS 散度 common set_a set_b if len(common) 0: prob_map_a {idx: prob for idx, prob in zip(indices_a, probs_a)} prob_map_b {idx: prob for idx, prob in zip(indices_b, probs_b)} p [prob_map_a.get(idx, 0.0) for idx in common] q [prob_map_b.get(idx, 0.0) for idx in common] if sum(p) 0 and sum(q) 0: p [x / sum(p) for x in p] q [x / sum(q) for x in q] m [(x y) / 2 for x, y in zip(p, q)] js 0.5 * entropy(p, m) 0.5 * entropy(q, m) total_js js return total_overlap / len(probe_texts), total_js / len(probe_texts) if __name__ __main__: # 以本地权重路径为准这里仅展示调用方式 tokenizer_a, model_a load_model(path/to/model_a) tokenizer_b, model_b load_model(path/to/model_b) probes [ The capital of France is, def fibonacci(n):, 公司的产品发布计划包括, Explain the difference between TCP and UDP., ] avg_overlap, avg_js behavior_similarity(model_a, model_b, tokenizer_a, probes) print(favg top-{256} overlap: {avg_overlap:.3f}) print(favg JS divergence: {avg_js:.4f})这段代码的逻辑并不复杂对每一条探针文本取模型预测下一个token的概率分布分别截断到Top-256集合然后计算集合重合率和JS散度。重合率越高、JS散度越低说明两个模型在行为上越接近。容易踩坑的地方是探针文本的选择。如果你用“The capital of France is”这种确定性极强的问题所有正常训练的模型都会给出高度一致的候选重叠率虚高如果选一些模型能力边界上的开放问题分布差异又会淹没真实血缘信号。建议准备几十条探针并每次都加入一个无关模型做对照组用“候选模型对”减去“无关对照对”的差值来判断。6.2 激活指纹中间层表示的余弦相似度当你能拿到模型权重时可以进入白盒指纹检测。核心思路是给两个模型输入同一段文本记录每一层的隐藏状态然后计算层与层之间的相似度。# fingerprint_activation.py import torch from transformers import AutoTokenizer, AutoModelForCausalLM def find_layer_list(model): 兼容常见开源模型的层模块命名实际使用时请先打印模型确认。 if hasattr(model, model) and hasattr(model.model, layers): return model.model.layers if hasattr(model, transformer) and hasattr(model.transformer, h): return model.transformer.h raise AttributeError(未能识别模型层结构请根据实际情况修改代码) def collect_activations(model, tokenizer, text): model.eval() inputs tokenizer(text, return_tensorspt) layers find_layer_list(model) activations {} def make_hook(idx): def hook_fn(module, input, output): # 大多数 Transformer 层输出为 (hidden_states, ...) 或 hidden_states if isinstance(output, tuple): hidden output[0] else: hidden output activations[idx] hidden[0].float().detach() return hook_fn hooks [] sampled_layers list(range(0, len(layers), max(1, len(layers) // 12))) for idx in sampled_layers: hooks.append(layers[idx].register_forward_hook(make_hook(idx))) with torch.no_grad(): model(**inputs) for hook in hooks: hook.remove() return activations def compare_activation_vectors(act_a, act_b): common_layers set(act_a.keys()) set(act_b.keys()) if not common_layers: raise ValueError(两个模型的层索引没有交集需要先做层对齐) scores [] for layer_id in sorted(common_layers): vec_a act_a[layer_id].flatten() vec_b act_b[layer_id].flatten() if vec_a.shape[0] ! vec_b.shape[0]: continue cos torch.cosine_similarity(vec_a.unsqueeze(0), vec_b.unsqueeze(0)).item() scores.append(cos) return sum(scores) / len(scores), scores if __name__ __main__: tokenizer_a, model_a load_model(path/to/model_a) tokenizer_b, model_b load_model(path/to/model_b) probe The quick brown fox jumps over the lazy dog. act_a collect_activations(model_a, tokenizer_a, probe) act_b collect_activations(model_b, tokenizer_b, probe) avg_cos, per_layer compare_activation_vectors(act_a, act_b) print(faverage cosine similarity: {avg_cos:.4f}) print(fper-layer similarity: {[round(x, 4) for x in per_layer]})这段代码里真正重要的是“层索引对齐”。不同架构的模型层数不一样层语义也不一样直接比较同一个索引可能没有意义。如果要严谨比较跨架构模型需要引入CKA等表示相似度方法或者先做一个“层匹配”预处理。对同架构的微调模型来说层索引天然对齐余弦相似度的解释力更强。经验参考同架构下派生模型的深层激活余弦相似度通常很高而完全无关的模型在中间层就会出现明显差异。但请注意这只是参考经验不是绝对阈值不同任务和不同架构下数值会波动。6.3 词表指纹词汇表重叠与 Embedding 奇异值谱词表指纹很容易被忽略但它几乎是成本最低、最稳定的一个维度。# fingerprint_vocab.py import numpy as np import torch from transformers import AutoTokenizer, AutoModelForCausalLM def vocab_overlap(tokenizer_a, tokenizer_b): vocab_a set(tokenizer_a.get_vocab().keys()) vocab_b set(tokenizer_b.get_vocab().keys()) inter vocab_a vocab_b union vocab_a | vocab_b return len(inter) / len(union), len(inter), len(union) def embedding_spectral_signature(model, sample_rows2000, sample_colsNone): 对 Embedding 矩阵做降采样后的奇异值分布统计返回谱特征向量。 embed model.get_input_embeddings() weight embed.weight.detach().float() num_rows, num_cols weight.shape row_indices np.random.choice(num_rows, sizemin(sample_rows, num_rows), replaceFalse) sampled weight[row_indices] try: singular_values torch.linalg.svdvals(sampled) except Exception: # 矩阵非常大时降采样后求奇异值 singular_values torch.linalg.svdvals(sampled.cpu()) singular_values singular_values[: min(64, len(singular_values))] return singular_values.numpy() if __name__ __main__: tokenizer_a, model_a load_model(path/to/model_a) tokenizer_b, model_b load_model(path/to/model_b) overlap_ratio, inter, union vocab_overlap(tokenizer_a, tokenizer_b) print(fvocab overlap: {inter}/{union} {overlap_ratio:.3f}) spec_a embedding_spectral_signature(model_a) spec_b embedding_spectral_signature(model_b) cos_spec np.dot(spec_a, spec_b) / (np.linalg.norm(spec_a) * np.linalg.norm(spec_b)) print(fembedding spectral cosine: {cos_spec:.4f})代码里的Embedding采样是考虑到大模型参数量很大完整做一次SVD非常耗时。降采样后的奇异值谱虽然只是一个近似但对于判断“两个Embedding矩阵是不是同源”仍然有参考价值。如果两个模型词表完全相同、Embedding谱余弦接近再加上前面行为和激活指纹的辅助基本可以认为它们有直接血缘关系。6.4 综合判定思路单独的分数很难给出一个绝对结论更稳妥的做法是做一个综合决策表。参考形式如下指纹维度判断方向理想派生模型特征理想无关模型特征词表重叠率低大于0.8小于0.5行为Top-k重叠高高于对照组显著水平与对照组接近激活余弦相似度高大于0.8小于0.5Embedding谱余弦高大于0.7小于0.4这些阈值不是通用标准但在实验里可以帮你建立一个直观参考。生产环境建议用已知血缘关系的模型对和已知无关的模型对分别标定你所在场景的阈值而不是套用别人的数字。7. 运行结果与验证方式执行示例代码时建议按顺序跑先跑词表指纹再跑行为指纹最后跑激活指纹。每一次结果都要记录因为最终判断需要综合多维度证据。如果运行成功你会在终端看到类似下面的输出具体数值取决于你的模型vocab overlap: 958/1084 0.884 avg top-256 overlap: 0.723 avg JS divergence: 0.1824 average cosine similarity: 0.9120 embedding spectral cosine: 0.8451验证是否成功的核心不在于某一个数值有多高而在于候选模型对与对照模型对之间是否有显著差异。假设你同时测了候选A/B对和一个无关的C/D对A/B对的重叠率是0.72C/D对只有0.31那就有理由怀疑A和B存在血缘关系如果A/B对和C/D对差别不大那就说明你的探针或特征选择没能捕捉到有效信号。运行失败时按这个顺序排查模型加载失败先检查模型路径确认本地已经用from_pretrained成功加载过。层索引没有交集查看find_layer_list函数返回的层数如果两个模型架构不同需要先做层对齐。内存不足把探针文本长度缩短或者减少采样层数也可以把Embedding采样行数从2000降到512。结果全是一样的高分说明探针选择有问题换成更有区分度的短文本。代码只是演示不要直接照搬到生产环境。生产环境需要做模型加载缓存、异常捕获、结果持久化和可视化否则一次全量指纹提取会变成噩梦。8. 常见问题与排查方法问题现象可能原因排查方式解决方案行为指纹显示相似度很高但模型确实无关探针文本太简单所有语言模型都会给出相似答案换用高判别度的复杂问题引入对照组扩充探针集设置至少50条不同难度的探针激活相似度很低但模型确实是派生的微调幅度太大或者架构层数和对齐方式变化检查两模型是否同架构层语义是否已偏移改用CKA或基于行为指纹做辅助判断词表完全一致但不能说明血缘两个模型都复用了常见开源tokenizer检查特殊token和分词细节是否完全一致将词表指纹作为辅助不单独下结论综合结论不稳定每次跑都不一样随机采样和模型推理的随机性设置随机种子固定GPU设备多次采样取平均至少重复3轮量化后的模型权重指纹失效权重被压缩后分布改变使用行为指纹代替或对量化前后做重标定在量化模型中单独校准阈值探针在中文上表现差探针文本多样性和语种覆盖不足增加中文、代码、英文混合探针重新设计探针集排查时的核心原则是先确认实验条件可控再怀疑指纹算法本身。很多“指纹不准”的情况其实是探针选择、层对齐、阈值标定这些前置步骤没有做好。9. 最佳实践与工程建议模型指纹识别从实验室走向生产环境有几个工程建议值得提前考虑。第一建立自己的模型指纹库。不要等出了问题才来测。无论是自研模型还是引入的开源模型在进入模型仓库的第一天就给它的词表指纹、Embedding谱指纹和代表性探针行为做成一份“基因档案”。以后出现争议直接对比档案即可不需要重新下载所有模型权重逐个分析。第二多种指纹联合判定不要迷信单一指标。每一种指纹都有盲区词表指纹区分性弱激活指纹依赖架构对齐行为指纹容易被RLHF改变。工程上至少采用三种指纹互相支撑当不同指纹结论冲突时要优先检查是不是某个中间环节出了问题而不是强行下结论。第三做好模型版本和训练元数据登记。指纹只是事后检测手段最有效的还是事前记录。每训练一个模型就把基础模型来源、训练数据版本、训练框架、关键超参数、Token配置记录在模型注册表里。指纹识别和元数据记录应该互为备份。第四要注意安全边界。给模型做指纹比对时避免泄露私有模型权重到外部服务。如果需要做第三方检测优先选择可以私有化部署的方案。同时要理解指纹识别给出的结论是“统计概率意义上的判断”不是“法律意义上的定论”在重要决策中要留出人工复核环节。第五保持对对抗手段的敏感。总有人会试图“清洗”模型指纹比如更换tokenizer重训练、对行为输出做平滑化、用蒸馏改变分布。这意味着指纹方案需要持续升级。如果你的业务高度依赖模型血缘判断建议定期用已知血缘的模型做一次“指纹体检”确保当前的特征集仍然有效。10. 总结与后续学习方向Model Genome 和模型指纹识别解决的是一个非常现实的问题模型溯源。它把“这个模型是不是从零训练的”从模糊的文档声明变成了可以量化的多维度指纹证据链。词表指纹、权重分布指纹、激活指纹和行为指纹分别从不同层面回答了“模型的血缘关系”这一问题。这篇文章真正想强调的是任何模型训练都会留下痕迹关键不在于有没有痕迹而在于你是否系统地去提取和交叉验证。项目里如果只做一次单一指纹检测很容易误判但引入多指纹联合分析之后模型血缘的判断会可靠得多。如果你想继续深入建议的方向有三个。第一学习表示相似度方法特别是CKA和SVCCA它们能解决跨架构模型的层对齐问题是当前比较硬核的知识点。第二研究“指纹清洗”和“指纹攻击”的相关内容理解对抗方如何抹除指纹才能设计出更鲁棒的指纹特征。第三把指纹识别接入你的模型发布CI/CD流程做成自动化模型血缘审计工具。建议读者先跑通上面的最小示例再慢慢扩展探针集和特征工程。模型指纹技术还不算成熟但它在模型治理、AI安全、开源合规这些方向上的价值已经越来越清晰。把这篇文章收藏备用等哪天需要查一个模型的“户口”时你会感谢今天花的时间。