之前有个公司发现,他们的Llama-2-7B模型被人克隆了一份,部署在了另一个云服务上。巧的是,那个克隆模型的输出跟他们的一模一样——连生成风格都一样。

他们去查代码,发现对方的代码里也用了npu_flash_attention。他们想知道:能不能从FlashAttention的执行行为里,找到证据证明对方用了他们的模型?

这个问题很有意思。答案是——FlashAttention的执行行为包含了模型的"指纹",可以用来检测模型是否被篡改或偷用。今天把这个技术讲清楚。

先打个比方:每把锁的钥匙痕都不一样

你找锁匠配了一把钥匙,锁匠的机器在钥匙上磨出了独特的痕迹。另一把钥匙如果是从同一台机器磨出来的,痕迹会一模一样——就算你换了钥匙的材料,痕迹也一模一样。

FlashAttention也是这样——你的模型在昇腾NPU上跑FlashAttention,每个分块的大小、对齐方式、执行顺序,都会留下独特的"痕迹"。别人克隆了你的模型,就算改了代码风格、加了水印,FlashAttention的执行行为也会"出卖"他们。

FlashAttention的执行指纹是什么?

FlashAttention在昇腾NPU上执行时,有几个关键参数会影响执行行为:

指纹1:block_size的余数

FlashAttention的分块大小默认是128。如果你的seq_len=4096,block_size=128,余数=0。但如果你的模型是Llama-2,seq_len通常pad到4096的倍数。

问题:如果你把seq_len改成了4100(不是128的倍数),FlashAttention会怎么处理?

  • 方案A:pad到4160(128×32.5,不行)
  • 方案B:pad到4224(128×33=4224)
  • 方案C:报错

不同的实现方案,对HBM带宽的影响不一样。如果对方克隆了你的模型,也会用同样的padding方案——这是第一个指纹。

指纹2:head_dim的对齐方式

FlashAttention要求head_dim是32的倍数。但不同模型可能有不同的padding策略:

  • head_dim=128:直接用,不需要padding
  • head_dim=96:padding到128
  • head_dim=100:padding到128或96?两种方案

不同的padding策略,会影响算子执行的效率。如果对方克隆了你的模型,也会用同样的padding策略——这是第二个指纹。

指纹3:SRAM的tile策略

FlashAttention的SRAM分配策略(分多少给Q、分多少给K、分多少给V)会影响执行效率。昇腾NPU的ops-transformer有默认的tile策略,但如果你自定义了tile参数,执行行为会不一样。

怎么检测:用npu-smi监控SRAM利用率,看每个分块的处理时间是否一致。

# 检测SRAM tile策略是否一致
import time

def check_tile_consistency(q, k, v, head_num, num_iterations=100):
    """检测FlashAttention的tile策略是否一致"""
    times = []
    
    for _ in range(num_iterations):
        torch.npu.synchronize()
        start = time.perf_counter()
        _ = npu_flash_attention(q, k, v, head_num=head_num)
        torch.npu.synchronize()
        times.append((time.perf_counter() - start) * 1000)
    
    # 计算时间方差
    mean_time = sum(times) / len(times)
    variance = sum((t - mean_time) ** 2 for t in times) / len(times)
    std_dev = variance ** 0.5
    
    # 判断一致性
    cv = std_dev / mean_time  # 变异系数
    
    print(f"平均时间:{mean_time:.4f} ms")
    print(f"标准差:{std_dev:.4f} ms")
    print(f"变异系数:{cv:.4f}")
    
    if cv < 0.05:
        print("✅ tile策略一致,执行指纹稳定")
    else:
        print("⚠️ tile策略有变化,执行指纹不稳定")

# 测试
q = torch.randn(1, 32, 4096, 128, device='npu', dtype=torch.float16)
k = torch.randn(1, 32, 4096, 128, device='npu', dtype=torch.float16)
v = torch.randn(1, 32, 4096, 128, device='npu', dtype=torch.float16)

check_tile_consistency(q, k, v, head_num=32)

怎么用FlashAttention指纹检测模型克隆?

方法1:对比执行时间序列

同一个模型,在同样的硬件上,执行时间序列应该高度相似。如果对方克隆了你的模型,FlashAttention的执行时间序列也会相似——就算他们改了模型权重,权重缩放的方式也会影响执行时间。

import numpy as np
from scipy.stats import pearsonr

def generate_execution_fingerprint(q, k, v, head_num, num_iterations=50):
    """生成FlashAttention执行时间序列"""
    times = []
    
    for _ in range(num_iterations):
        torch.npu.synchronize()
        start = time.perf_counter()
        _ = npu_flash_attention(q, k, v, head_num=head_num)
        torch.npu.synchronize()
        times.append((time.perf_counter() - start) * 1000)
    
    return np.array(times)

def compare_fingerprints(fp1, fp2):
    """对比两个执行指纹"""
    # Pearson相关系数
    corr, p_value = pearsonr(fp1, fp2)
    
    # 平均时间差异
    mean_diff = abs(fp1.mean() - fp2.mean()) / fp1.mean()
    
    # 时间方差差异
    var_diff = abs(fp1.var() - fp2.var()) / fp1.var()
    
    print(f"相关系数:{corr:.4f}(p值:{p_value:.6f})")
    print(f"平均时间差异:{mean_diff:.4f}")
    print(f"时间方差差异:{var_diff:.4f}")
    
    # 判断
    if corr > 0.95 and p_value < 0.001:
        print("✅ 执行指纹高度相似,很可能是同一模型")
    elif corr > 0.8:
        print("⚠️ 执行指纹相似,可能是同一模型架构")
    else:
        print("❌ 执行指纹不相似,不是同一模型")

# 生成两个模型的指纹
your_model_fp = generate_execution_fingerprint(q, k, v, head_num=32)
cloned_model_fp = generate_execution_fingerprint(q, k, v, head_num=32)

compare_fingerprints(your_model_fp, cloned_model_fp)

方法2:对比HBM访问模式

FlashAttention的HBM访问模式(读写了多少数据、访问频率)也是指纹。如果对方克隆了你的模型,HBM访问模式也会相似。

import subprocess

def get_hbm_access_stats():
    """获取HBM访问统计"""
    result = subprocess.run(
        ["npu-smi", "dump", "-m", "0", "-t", "hbm", "-c", "1"],
        capture_output=True, text=True
    )
    # 解析输出
    lines = result.stdout.strip().split('\n')
    stats = {
        'read_bytes': int(lines[1].split()[3]),
        'write_bytes': int(lines[1].split()[5]),
        'read_bandwidth': float(lines[1].split()[7]),
        'write_bandwidth': float(lines[1].split()[9])
    }
    return stats

def compare_hbm_pattern(fp1_stats, fp2_stats):
    """对比HBM访问模式"""
    read_ratio = fp1_stats['read_bytes'] / fp2_stats['read_bytes']
    write_ratio = fp1_stats['write_bytes'] / fp2_stats['write_bytes']
    
    print(f"读字节比:{read_ratio:.4f}(理想=1.0)")
    print(f"写字节比:{write_ratio:.4f}(理想=1.0)")
    
    if 0.95 < read_ratio < 1.05 and 0.95 < write_ratio < 1.05:
        print("✅ HBM访问模式高度相似")
    else:
        print("❌ HBM访问模式不同")

总结一下

FlashAttention的执行指纹可以用来检测模型是否被克隆或篡改:

  1. block_size的padding策略:不同的padding方案会影响执行行为
  2. head_dim的对齐方式:不同的padding策略会影响算子执行效率
  3. SRAM的tile策略:不同的tile分配会影响执行时间
  4. 执行时间序列:相关系数>0.95说明很可能是同一模型
  5. HBM访问模式:读写字节比接近1.0说明很可能是同一模型

⚠️ 踩坑预警:执行指纹只能证明"很可能是同一模型",不能作为法律证据。要真正证明对方侵权,还需要更多的证据(比如代码相似度、训练数据来源等)。

代码和文档:

https://atomgit.com/cann/ops-transformer

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐