FlashAttention的水印攻击:怎么知道你的模型被偷用或篡改了?
之前有个公司发现,他们的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的执行指纹可以用来检测模型是否被克隆或篡改:
- block_size的padding策略:不同的padding方案会影响执行行为
- head_dim的对齐方式:不同的padding策略会影响算子执行效率
- SRAM的tile策略:不同的tile分配会影响执行时间
- 执行时间序列:相关系数>0.95说明很可能是同一模型
- HBM访问模式:读写字节比接近1.0说明很可能是同一模型
⚠️ 踩坑预警:执行指纹只能证明"很可能是同一模型",不能作为法律证据。要真正证明对方侵权,还需要更多的证据(比如代码相似度、训练数据来源等)。
代码和文档:
https://atomgit.com/cann/ops-transformer
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)