
实际模型资产管理中判断一个大型语言模型是真正从头训练还是从某个已有 checkpoint 衍生而来已经不再只是一个版权或合规话题它更像一道需要从权重、词表、激活和行为中寻找线索的取证题。Model Genome模型基因组这个概念就是把一个模型的可观测特征看成一组“基因片段”通过指纹提取与相似度度量判断两个模型之间存在的是“同源衍生”关系还是“独立训练”关系。这篇文章会围绕一条主线展开先理解为什么需要做 LLM 来源判断再拆解一个模型基因组应该包含哪些指纹然后给出一个可运行的最小指纹提取脚本最后说明如何把指纹结果落到模型仓库和安全审计流程中。文章会涉及 Hugging Face Transformers、PyTorch、CKA 相似度计算、词表重叠和 logits 分布对比适合正在做模型安全、模型治理或开源合规的同学阅读也适合想深入理解“凭什么判断一个模型是同源模型”的研究者作为起点。1. 为什么需要判断一个 LLM 是“从头训练”还是“衍生模型”1.1 典型场景开源合规、安全审计与供应链追溯在 LLM 落地过程中团队经常面对三种情况。第一种是公司宣称发布了一个“从零训练”的模型但审计方需要验证这个说法是否真实。第二种是开源社区怀疑某个模型是在另一个闭源或开源模型基础上做了微调却没有在模型卡中声明衍生关系。第三种是模型供应链管理一个内部 LLM 框架里接入了几十个模型运维方需要知道这些模型之间是否有祖先关系避免在同一个底座上重复造出高风险模型也方便在底座模型出现安全漏洞时快速定位下游受影响版本。这些场景的共同点是不能只相信文档声明必须从模型文件本身找到证据。事实上模型的所有权重、词表、前向计算路径和输出分布都包含了来源信息。即使模型经过了大规模微调、LoRA 低秩适配甚至模型合并原始底座的痕迹也很难完全抹掉。在模型指纹领域我们把“两个模型是否同源”转化成“两类指纹是否一致”一类是结构指纹比如 tokenizer 词表、参数维度、注意力头数量、LayerNorm 参数另一类是行为指纹比如相同输入下模型的隐藏层表示是否高度对齐、输出概率分布是否显著接近。两种指纹组合起来才可能回答“from scratch”还是“derived”。1.2 “从头训练”和“衍生”到底指什么从头训练trained from scratch通常意味着模型参数在训练前采用随机初始化或特定分布初始化然后通过大量语料完成预训练。这类模型即使使用了完全相同的训练数据、相同的开源 tokenizer不同随机种子训练出来的权重分布也会呈现独立性。衍生模型derived model则指模型在已有 checkpoint 基础上继续训练包括在基础模型上做全量参数微调。只训练部分层或使用 LoRA、Adapter 等参数高效微调。在基础模型上继续预训练。通过知识蒸馏得到学生模型。对多个模型进行权重合并或模型融合。需要注意的是不同类型的衍生行为留下的指纹强度不同。全量参数微调会改动大量权重但底层表示仍然会保留源模型的“语言先验”LoRA 只修改低秩增量底座的权重指纹几乎原样保留蒸馏模型不会继承权重但会继承大量行为模式。所以指纹判断不能只看某一种指标而是要把多种指纹合并起来并允许“部分命中”的情况。1.3 衍生一定会留下基因痕迹为什么衍生关系一定会留下痕迹核心原因是深度学习模型的训练不是从零开始重新探索全部特征空间而是从已有参数附近继续优化。预训练模型内部已经形成了稳定的特征表示结构比如词嵌入空间中的语义聚类、注意力头对特定语法模式的响应、LayerNorm 的 scale 分布等。微调时即使数据分布差异很大这些低层表示也只会被轻微调整不会完全重构。从优化角度看从随机初始化训练一个模型和从一个已经收敛的模型继续训练两者在参数空间中的终点位置分布是不同的。前者更分散后者会停留在祖先模型对应的吸引域附近。这意味着通过计算两个模型的中间层表示相关性可以区分“同一个优化路径”和“两个独立优化路径”。但这里有一个容易踩的坑不能只比较最后一层输出的相似度因为最后一层很容易被任务头主导。更稳妥的做法是比较多个中间层的激活表示尤其是前几层的 embedding 特征和浅层语义特征。这也是本文后面使用 CKA 等表示相似度方法的原因。2. 构建模型基因组从哪些指纹判断来源关系2.1 第一层指纹词表与 TokenizerTokenizer 是模型最先暴露来源信息的地方。很多衍生模型会直接沿用基础模型的 tokenizer 和词表原因很简单如果修改词表就需要重新初始化新的 embedding 行这在微调阶段成本很高而且会破坏预训练模型已有的词嵌入分布。可以提取的特征包括词汇表交集数量两个模型词表共有的 token 数量。词表大小差异如果两个模型词表几乎完全一致说明继承概率高。特殊 token 的顺序和编号比如s、/s、[CLS]、[SEP]、unk等在 vocab 中的位置是否一致。BPE/SentencePiece 合并规则如果使用同一个 tokenizer 文件合并规则也一致。词表指纹虽然醒目但不能单独作为判断依据。因为很多独立训练的模型也会使用流行的开源 tokenizer例如 GPT-2 的 BPE 词表、Llama 系列用的 SentencePiece 词表。词表一致只能说明“有可能同源”不能证明“一定同源”。2.2 第二层指纹权重分布权重指纹包括模型参数的结构性特征embedding 矩阵的行数、列数和初始化方差。attention 层的 query、key、value 权重分布。LayerNorm 的 gamma 和 beta 均值、方差。权重矩阵的奇异值谱。如果两个模型的 hidden size、层数、注意力头数都相同可以直接计算对应层的参数相似度。但 LLM 之间经常存在结构差异比如基础模型 12 层、衍生模型 6 层这时不能逐层对比而应该比较同维度的 embedding 矩阵、最后一层输出层、LayerNorm 统计量等。直接比较参数绝对值有一个问题两个独立训练的模型可能因为都用标准正态初始化参数均值方差接近但参数向量方向完全不同。因此权重指纹更适合作为辅助证据或者计算 embedding 矩阵的余弦相似度、CKA 相似度而不是简单计算平均绝对差。2.3 第三层指纹激活与表示指纹权重指纹看的是静态参数激活指纹看的是输入数据经过模型后产生的动态表示。对于判断衍生关系激活指纹通常比权重指纹更可靠因为它反映了模型在真实推理路径上的行为。常用方法包括余弦相似度对同一批输入取两个模型同一层或最后一层输出的 hidden state计算均值向量之间的余弦相似度。CKACentered Kernel Alignment一种基于内积的表示相似度能捕捉神经元之间非线性关系。特征向量分布距离对多个样本的 hidden state 做 PCA然后比较主成分方向。探针分类器把两个模型的 hidden state 打上标签训练一个小分类器看能否区分它们来自哪个模型。如果分类器准确率接近随机说明两个表示高度接近。激活指纹适合结构相近的模型对于结构差异大的模型需要先做线性投影把特征维度对齐否则 CKA 的结果会失真。计算方法在后面代码部分会展开。2.4 第四层指纹行为与输出指纹结构完全相同是派生关系的强证据但遇到蒸馏模型或者模型合并时权重可能完全不同这时就需要看行为指纹。行为指纹包括给定相同输入两个模型的输出 logits 分布之间的 KL 散度。相同 prompt 下生成的 top-k token 重合率。在固定评测集上的逐样本错题相关性如果一个模型答错的样本另一个模型同样答错的概率很高说明两者可能共享了相似的决策边界。对同一组扰动文本例如同义词替换、拼写错误的反应模式。行为指纹的问题在于它容易受到训练数据重叠的影响。两个完全独立训练的模型如果用了相同的公共数据集也可能产生相似的行为。所以在实际决策中行为指纹通常作为“弱信号”负责发现那些权重指纹无法覆盖的蒸馏模型或合并模型。2.5 指纹融合策略没有单一指纹能百分百判断模型来源因此需要把多个指标组合成一个综合得分。一个常用思路是给每个指纹分配一个权重然后计算加权平均。比如定义一个模型来源相似度 Score指纹类型指标示例权重词表指纹词表重合率0.15权重指纹embedding 余弦相似度0.25激活指纹中间层 CKA0.3行为指纹logits 分布 KL 散度归一化后的相似度0.2结构指纹层数/维度是否完全一致0.1这里的权重只是示例实际使用时应根据模型家族和风险场景校准。比如在开源合规审计中词表指纹的权重应该更高在做蒸馏模型溯源时行为指纹权重应该更高。3. 环境准备与最小实验设计3.1 依赖与版本下面的示例代码使用 Python 3.9核心依赖如下依赖包用途torch模型加载、前向计算transformers加载 Hugging Face 模型和 tokenizernumpy数值计算scipy可选处理矩阵运算scikit-learn可选PCA 投影安装命令pip install torch transformers numpy scipy scikit-learn代码会从 Hugging Face Hub 下载模型权重。如果运行环境无法直接访问 Hub建议先在有网络的环境中把模型下载到本地再使用本地路径加载这样整个流程不依赖外网变化也更适合离线审计场景。3.2 模型对与输入文本为了验证“衍生模型留下指纹”这个判断建议准备三组模型对组 1gpt2与distilgpt2蒸馏关系应识别为衍生。组 2gpt2与bert-base-uncased独立训练应识别为独立。组 3你自己训练或微调的两个 checkpoint用来校准阈值。gpt2和distilgpt2都使用 GPT-2 词表hidden size 都是 768且 distilgpt2 是 gpt2 的蒸馏版本因此这组数据非常适合演示“同源但不完全相同”的指纹效果。输入文本使用一组与模型训练领域无关的通用句子避免让行为指纹偏向某一个任务SAMPLE_TEXTS [ The quick brown fox jumps over the lazy dog., There is a growing demand for renewable energy., The capital of France is Paris., Machine learning models are widely used in industry., A dedicated server should be monitored carefully., ]3.3 实验目录结构建议按下面的结构组织文件model_genome/ ├── fingerprint.py # 核心指纹计算代码 ├── models/ # 本地模型缓存目录 ├── output/ # 指纹结果输出 └── sample_texts.txt # 样本输入实际项目中不一定要把文本放在文件里直接放在脚本中也可以。将模型单独放到models/目录可以避免每次运行都检查版本更新后续如果需要批量扫描也便于离线执行。4. 核心代码实现4.1 模型加载与统一接口为了兼容不同模型使用AutoModelForCausalLM和AutoTokenizer加载。这里以 gpt2 系列为例如果你要评估的是掩码模型比如 BERT需要换成AutoModelForMaskedLM或者统一使用AutoModel获取 hidden state。from transformers import AutoModelForCausalLM, AutoTokenizer import torch def load_model_and_tokenizer(model_name, devicecpu): tokenizer AutoTokenizer.from_pretrained(model_name) model AutoModelForCausalLM.from_pretrained(model_name) model.to(device) model.eval() return model, tokenizer如果模型没有 pad token需要通过 tokenizer 设置否则批处理时可能报错。对生成类模型通常不需要 pad但为了统一处理建议这样设置if tokenizer.pad_token is None: tokenizer.pad_token tokenizer.eos_token4.2 词表指纹词表指纹计算的核心是获取 tokenizer 的 vocab 集合然后计算交集占较小词表大小的比例。def vocab_overlap(tokenizer_a, tokenizer_b): vocab_a set(tokenizer_a.get_vocab().keys()) vocab_b set(tokenizer_b.get_vocab().keys()) intersect vocab_a vocab_b overlap_ratio len(intersect) / min(len(vocab_a), len(vocab_b)) return { vocab_a_size: len(vocab_a), vocab_b_size: len(vocab_b), intersection_size: len(intersect), overlap_ratio: round(overlap_ratio, 4), }overlap_ratio越接近 1说明两个模型共用词表的可能性越高。但正如前面提到的还需要检查特殊 token 顺序。这里简单补充一个比较def special_token_similarity(tokenizer_a, tokenizer_b): special_a set(tokenizer_a.all_special_tokens) special_b set(tokenizer_b.all_special_tokens) return len(special_a special_b) / max(len(special_a), len(special_b), 1)4.3 权重与 Embedding 指纹对于因果语言模型可以直接比较transformer.wte.weight的 embedding 矩阵。矩阵维度可能不一致因此使用线性 CKA 或者余弦相似度。def embedding_similarity(model_a, model_b): emb_a model_a.get_input_embeddings().weight.detach() emb_b model_b.get_input_embeddings().weight.detach() min_dim min(emb_a.size(0), emb_b.size(0)) emb_a emb_a[:min_dim] emb_b emb_b[:min_dim] emb_a emb_a - emb_a.mean(dim0, keepdimTrue) emb_b emb_b - emb_b.mean(dim0, keepdimTrue) sim torch.cosine_similarity(emb_a.flatten(), emb_b.flatten(), dim0).item() return round(sim, 4)这里直接对展平后的向量做余弦相似度实际上会受维度规模影响实现上更适合作为“粗筛”。更严谨的做法是分别计算每行的平均相似度再取中位数避免个别 token 主导结果。4.4 CKA 激活指纹CKA 是计算两个表示矩阵相似度的方法。假设输入样本集合经过模型后得到两个 hidden state 矩阵X和Y形状都是(n_samples, n_features)线性 CKA 的公式如下def linear_cka(X, Y): if isinstance(X, torch.Tensor): X X.detach().float().numpy() Y Y.detach().float().numpy() X X - X.mean(axis0, keepdimsTrue) Y Y - Y.mean(axis0, keepdimsTrue) hsic_xy np.linalg.norm(X.T Y) ** 2 hsic_xx np.linalg.norm(X.T X) hsic_yy np.linalg.norm(Y.T Y) return hsic_xy / (hsic_xx * hsic_yy 1e-8)需要注意的是两个模型的 hidden state 特征维度可能不同。如果不同先统一降到较小的维度否则无法计算矩阵内积。下面是一个取最后一个 hidden state 并做投影的例子def collect_hidden_states(model, tokenizer, texts, devicecpu): hidden_states [] with torch.no_grad(): for text in texts: inputs tokenizer(text, return_tensorspt, truncationTrue, max_length64) inputs {k: v.to(device) for k, v in inputs.items()} outputs model(**inputs, output_hidden_statesTrue) last_hidden outputs.hidden_states[-1] cls_hidden last_hidden[:, 0, :] hidden_states.append(cls_hidden.cpu().numpy()) return np.stack([h[0] for h in hidden_states])如果两个模型 hidden_size 不同用 PCA 降到较小维度from sklearn.decomposition import PCA def align_features(X, Y): dim min(X.shape[1], Y.shape[1]) if X.shape[1] dim: X PCA(n_componentsdim).fit_transform(X) if Y.shape[1] dim: Y PCA(n_componentsdim).fit_transform(Y) return X, Y然后计算 CKAdef activation_cka(model_a, model_b, tokenizer_a, tokenizer_b, texts, devicecpu): X collect_hidden_states(model_a, tokenizer_a, texts, device) Y collect_hidden_states(model_b, tokenizer_b, texts, device) X, Y align_features(X, Y) return round(linear_cka(X, Y), 4)4.5 Logits 与生成行为指纹logits 分布比较可以在相同的文本输入上比较下一个 token 的概率。当两个模型共享 tokenizer 时可以同时取同一批 token 位置上的 logits如果 tokenizer 不同建议改用归一化的生成文本重叠率。共享 tokenizer 时的 KL 散度计算import torch.nn.functional as F def logits_kl_divergence(model_a, model_b, tokenizer, texts, devicecpu): total_kl 0.0 count 0 with torch.no_grad(): for text in texts: inputs tokenizer(text, return_tensorspt, truncationTrue, max_length64) inputs {k: v.to(device) for k, v in inputs.items()} logits_a model_a(**inputs).logits logits_b model_b(**inputs).logits probs_a F.log_softmax(logits_a, dim-1) probs_b F.softmax(logits_b, dim-1) kl F.kl_div(probs_a, probs_b, reductionbatchmean).item() total_kl kl count 1 return round(total_kl / max(count, 1), 4)这个值越小说明两个模型对同一下一个词的概率估计越接近。如果两个模型 tokenizer 不同可以把 tokenizer 统一为其中一个或者退而求其次只比较生成样本的重合率def generate_overlap(model_a, model_b, tokenizer_a, tokenizer_b, prompts, max_new_tokens20): def _generate_once(model, tokenizer, prompt): inputs tokenizer(prompt, return_tensorspt) outputs model.generate(**inputs, max_new_tokensmax_new_tokens, do_sampleFalse) return tokenizer.decode(outputs[0], skip_special_tokensTrue) total 0.0 for prompt in prompts: text_a _generate_once(model_a, tokenizer_a, prompt) text_b _generate_once(model_b, tokenizer_b, prompt) tokens_a set(text_a.split()) tokens_b set(text_b.split()) overlap len(tokens_a tokens_b) / max(len(tokens_a), len(tokens_b), 1) total overlap return round(total / len(prompts), 4)generate_overlap不是严格的分布比较但可以作为 tokenizer 不一致时的兜底方案。4.6 决策输出把前面几个指标汇总并输出一个简单判断def evaluate_pair(model_name_a, model_name_b, texts, devicecpu): model_a, tokenizer_a load_model_and_tokenizer(model_name_a, device) model_b, tokenizer_b load_model_and_tokenizer(model_name_b, device) vocab_res vocab_overlap(tokenizer_a, tokenizer_b) emb_sim embedding_similarity(model_a, model_b) cka_score activation_cka(model_a, model_b, tokenizer_a, tokenizer_b, texts, device) tokenizer_shared tokenizer_a.get_vocab() tokenizer_b.get_vocab() if tokenizer_shared: kl_score logits_kl_divergence(model_a, model_b, tokenizer_a, texts, device) behavior_similarity round(1.0 / (1.0 kl_score), 4) else: behavior_similarity generate_overlap( model_a, model_b, tokenizer_a, tokenizer_b, [The next word is, A common question is, A model can] ) score ( 0.15 * vocab_res[overlap_ratio] 0.25 * max(emb_sim, 0.0) 0.30 * cka_score 0.20 * behavior_similarity 0.10 * (1 if tokenizer_shared else 0) ) result derived if score 0.7 else from_scratch_or_independent return { model_a: model_name_a, model_b: model_name_b, vocab: vocab_res, embedding_similarity: emb_sim, cka: cka_score, behavior_similarity: behavior_similarity, score: round(score, 4), verdict: result, }运行示例if __name__ __main__: texts [ The quick brown fox jumps over the lazy dog., The stock market opened lower this morning., Natural language processing is a core area of AI., The server responded with an HTTP 500 error., A well designed API can improve system stability., ] print(evaluate_pair(gpt2, distilgpt2, texts)) print(evaluate_pair(gpt2, bert-base-uncased, texts))5. 运行验证与结果解读5.1 预期输出示例运行脚本后gpt2与distilgpt2这组模型的输出类似于{model_a: gpt2, model_b: distilgpt2, vocab: {vocab_a_size: 50257, vocab_b_size: 50257, intersection_size: 50257, overlap_ratio: 1.0}, embedding_similarity: 0.72, cka: 0.83, behavior_similarity: 0.85, score: 0.76, verdict: derived}gpt2与bert-base-uncased这组模型的输出则更接近{vocab: {vocab_a_size: 50257, vocab_b_size: 30522, intersection_size: 42, overlap_ratio: 0.0014}, embedding_similarity: -0.01, cka: 0.31, behavior_similarity: 0.22, score: 0.18, verdict: from_scratch_or_independent}这里的关键不是具体数值而是两组分数之间的差距。同源组分数明显高于独立组说明模型基因组特征能把两类关系分开。5.2 如何校准阈值上面的0.7只是一个示例阈值。不同模型家族下特征分布差异很大直接使用固定阈值会导致误判。更稳妥的做法是准备三类样本正样本对已知同源模型比如 base 模型和它的 fine-tune 版本。负样本对已知独立训练的不同架构模型。模糊样本对例如共享数据集但独立训练的模型或者经过大量微调的模型。对每一项指标分别绘制分布图然后用 ROC 曲线找到最合适的分数阈值。如果只有少量模型对至少也要人工检查三个正样本和三个负样本的得分区间再确定阈值。5.3 解读三对模型的关系如果词表重叠率接近 1但 CKA 很低说明两个模型虽然共用 tokenizer但参数和行为差异很大可能是“独立训练但搭了同一套预处理”的模型。如果 CKA 很高但词表重叠率低说明行为相似可能来自训练数据或蒸馏对齐需要重点检查是否使用了蒸馏或数据蒸馏流程。如果所有指标都高基本可以判断为衍生模型尤其当多层 CKA 都高于 0.8 时同源证据已经很充分。另一个常见做法是逐层计算 CKA画出曲线。如果浅层 CKA 高、深层 CKA 低通常说明模型在共享低层表示后任务层发生了较大调整常见的全量微调就会出现这种模式。6. 常见问题与排查路径6.1 两个模型词表不一致时怎么继续比较词表不一致时logits_kl_divergence无法直接计算因为两个模型在分类头的维度上不对齐。可以先看是否来自同一个 tokenizer 家族比如 BERT 系列和 DistilBERT 系列词表完全一致如果不一致建议使用生成重合率、句级困惑度或者词级翻译不匹配来对比。另一种做法是统一用一个第三方 tokenizer 对原始文本分片但这样会损失模型原生的 token 语义只能作为辅助证据。6.2 CKA 算得慢或显存不够CKA 需要对每个样本过一遍模型显存不够时可以减小max_length比如从 64 降到 32。计算阶段可以只取hidden_states中的中间层例如第四层或第六层而不是始终取最后一层。另外不要把所有文本一次性凑成一个大 batch逐条推理更稳妥。BATCH_SIZE 1如果批量扫描很多模型建议提前把 hidden state 缓存成.npy文件避免反复跑同一份模型。生产环境应该用 GPU 集群异步执行把指纹提取做成定时任务。6.3 参数名和层数对不齐加载不同框架导出的模型时参数名可能不一致。比如 PyTorch 的transformer.h.0.attn.c_attn.weight和 TensorFlow 的transformer/h/0/attn/c_attn/weight键名不同。使用AutoModel可以解决大部分加载问题但如果要直接比较权重需要先统一参数名映射。推荐的做法是不直接比较权重张量而是比较行为或激活因为激活指纹不依赖参数名对齐只需模型能跑通前向。6.4 高相似度一定意味着衍生吗不一定。两个独立训练的模型如果训练数据高度重叠比如都用了同一批公共语料那么在常见 prompt 下行为会趋于接近尤其是那些高频句式和知识。另外如果两个模型都使用了非常强的正则化或都在标准化语料上收敛那么浅层表示也可能出现相似。因此不能单看分数高就下结论至少要结合是否共享 tokenizer。是否在多组未见过的测试文本上都有较高 CKA。是否在随机噪声输入下也保持高相似。随机噪声输入是一个很好的对照组如果独立模型在噪声输入下 CKA 仍然很高说明模型结构或初始化方式本身就导致高相似而不是衍生关系导致的。6.5 学习环境与生产环境的差异学习环境只要求快速跑通判断流程可以用 CPU、小模型、固定 5 条文本完成验证。生产环境要求完全不同模型来源和版本需要登记在元数据中。指纹提取过程需要可重复记录使用哪个模型路径、哪一批样本、哪个 Python 包版本。输出结果需要持久化不能只打印到控制台。隐私和数据合规要求样本集不能包含敏感信息尤其不能为了测试模型而输入客户数据。阈值要定期校准因为新发布的模型家族可能会改变指纹分布。7. 把模型指纹落到工程体系7.1 指纹库表结构如果团队有模型仓库建议为指纹结果单独建一张表。下面是一个简单的关系表设计CREATE TABLE model_genome_fingerprints ( id BIGINT PRIMARY KEY AUTO_INCREMENT, model_a VARCHAR(255) NOT NULL, model_b VARCHAR(255) NOT NULL, vocab_overlap_ratio FLOAT, embedding_similarity FLOAT, cka_score FLOAT, behavior_similarity FLOAT, final_score FLOAT, verdict VARCHAR(32), sample_set_version VARCHAR(64), created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_model_pair (model_a, model_b, sample_set_version) );这样每次用同一批样本计算相同模型对的结果可以被覆盖不同样本集版本可以同时保留方便追溯阈值调整是否合理。7.2 指纹提取任务设计生产环境中指纹提取不适合用交互式脚本。建议拆成三个环节计划任务读取模型仓库中的模型列表生成待比较的模型对。执行任务逐个计算指纹结果写入数据库。分析任务根据历史分数分布重新校准阈值生成审计报告。执行任务时应记录日志至少包含模型标识、样本集版本、运行耗时、成功率。如果某个模型属于“未授权发布”即使指纹分数没有超过硬阈值也应该进入人工复核列表。7.3 和 LLM 框架/模型仓库结合时的检查项在 LLM 框架中接入模型指纹时建议在以下节点中加入检查模型上传或注册时自动执行指纹提取与模型库中已有模型比对。模型发布前生成衍生关系报告明确标注“可能继承自哪些模型”。模型运行异常时如果已知底座模型存在安全漏洞通过指纹表查询受影响的下游模型。发布前检查清单可以这样设计检查项通过标准词表指纹与任何已有模型的词表重叠率低于 0.5或人工说明原因权重指纹embedding 相似度低于 0.4激活指纹多组测试集上的 CKA 低于 0.6行为指纹KL 散度归一化相似度低于 0.5人工复核有模型卡说明训练方式、底座来源这里的数值是通用参考值不要当成安全阈值直接套用必须结合自己的模型家族校准。7.4 可复用评估清单无论做一次快速判断还是搭建长期平台都可以使用下面的清单[ ] 是否确认了两个模型的 tokenizer 版本。[ ] 是否记录了模型来源、训练方式、训练数据版本。[ ] 是否使用至少 5 组与任务无关的通用文本。[ ] 是否增加了一组随机噪声输入作为对照。[ ] 是否同时计算了词表、权重、激活、行为四类指纹。[ ] 是否检查了隐藏状态的特征维度做了必要的 PCA 对齐。[ ] 是否用正样本对和负样本对校准了阈值。[ ] 是否记录了 Python 包版本、模型文件名、样本文本哈希。[ ] 是否把结果写入可查询的数据库或审计日志。[ ] 是否对“不确定”分数设置了人工复核机制。这份清单也可以反过来用如果你在做一个模型来源判断某项检查没有做结论就应写得谨慎一些。7.5 后续扩展方向Model Genome 指纹可以继续扩展为“模型血缘图谱”。有了多个模型之间的相似度矩阵后可以构建关联图找出哪些模型共享同一个底座甚至识别出未声明的中间版本。图上的每个节点是模型边的权重是综合指纹分数这样可以回答更复杂的问题一个模型是不是另一个模型的“孩子”还是“兄弟”模型。另一个方向是引入探针分类器。给每个模型打上来源标签训练一个小型分类器对 hidden state 做分类观察分类器的决策边界。这比单独计算相似度更能捕捉多个模型家族之间的非对称关系尤其适合判断“模型 B 是否是模型 A 的微调版本”而不是简单的相似度对称关系。最后指纹技术也需要反制措施。一个模型作者如果刻意想抹去衍生痕迹可以加入随机扰动、重训练 embedding、替换 tokenizer、插入随机层后再进行蒸馏。但这类操作会显著增加训练成本而且只要扰动不彻底多层 CKA 和噪声对照仍然可能泄露真实关系。因此模型指纹不是一次性工具而是一个需要持续迭代的对抗性取证方向。在实际审计中能下结论的从来不是某一个分数而是多个指纹之间的交叉验证。模型作者只要真的从零开始训练就不怕查如果模型只是在一个底座上换了层壳指纹数据库会让这类行为越来越难隐藏。