RAMP:边缘CPU视觉模型自适应混合精度量化——BMVC 2026论文解读

论文信息

项目 内容
标题 RAMP: Robust Adaptive Mixed-Precision Quantization for Edge CPU Vision Models
作者 David Población Criado 等
会议 BMVC 2026(第37届英国机器视觉会议)
年份 2026
链接 arXiv:2609.28262
代码 GitHub: davidpob99/ramp-mpq
规模 13页,4图2表

核心创新

RAMP 提出了一种系统化的INT8混合精度量化策略,通过13种敏感度指标对比和K-Means聚类,在边缘CPU上实现近无损精度+1.81x加速。

三大发现:

  1. Jensen-Shannon 散度是唯一在所有配置下零灾难性失败的敏感度指标
  2. 固定阈值的敏感度分配方式在现代架构上脆弱且失效
  3. 排除低加速层的量化反而有害(导致计算图碎片化,禁用算子融合)

方法详解

1. 问题定义

1
2
3
4
5
6
7
8
9
10
11
"""
混合精度量化问题:
- 不是所有层都适合INT8量化
- 需要找出哪些层量化安全,哪些层保持FP32
- 目标:最大化加速 + 最小化精度损失

传统方法失效原因:
1. 固定阈值(如"敏感度>0.1就保持FP32")→ 现代架构敏感度分布偏斜
2. 梯度方法 → 在部分配置下完全失效
3. 基于权重统计 → 无法反映实际推理精度影响
"""

2. 13种敏感度指标对比

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
import torch
import torch.nn as nn
import numpy as np
from typing import Callable, Dict, List
from scipy import stats

class SensitivityMetrics:
"""
论文对比的13种层间量化敏感度指标

分为4类:
1. 权重统计(4种)- 基于权重分布
2. 梯度方法(3种)- 基于反向传播梯度
3. 激活统计(3种)- 基于输出分布
4. 信息论(3种)- 基于信息论度量
"""

@staticmethod
def weight_l2_norm(layer: nn.Module) -> float:
"""1. 权重L2范数"""
return torch.norm(layer.weight).item()

@staticmethod
def weight_max_abs(layer: nn.Module) -> float:
"""2. 权重最大绝对值"""
return layer.weight.abs().max().item()

@staticmethod
def weight_std(layer: nn.Module) -> float:
"""3. 权重标准差"""
return layer.weight.std().item()

@staticmethod
def weight_kurtosis(layer: nn.Module) -> float:
"""4. 权重峰度"""
w = layer.weight.flatten().detach().numpy()
return float(stats.kurtosis(w))

@staticmethod
def gradient_norm(
layer: nn.Module,
loss_fn: Callable,
x: torch.Tensor, y: torch.Tensor
) -> float:
"""5. 梯度范数(需反向传播)"""
layer.weight.requires_grad = True
out = layer(x)
loss = loss_fn(out, y)
loss.backward()
grad_norm = layer.weight.grad.norm().item()
layer.weight.requires_grad = False
return grad_norm

@staticmethod
def gradient_max(
layer: nn.Module,
loss_fn: Callable,
x: torch.Tensor, y: torch.Tensor
) -> float:
"""6. 梯度最大值"""
layer.weight.requires_grad = True
out = layer(x)
loss = loss_fn(out, y)
loss.backward()
grad_max = layer.weight.grad.abs().max().item()
layer.weight.requires_grad = False
return grad_max

@staticmethod
def gradient_mean(
layer: nn.Module,
loss_fn: Callable,
x: torch.Tensor, y: torch.Tensor
) -> float:
"""7. 梯度均值"""
layer.weight.requires_grad = True
out = layer(x)
loss = loss_fn(out, y)
loss.backward()
grad_mean = layer.weight.grad.abs().mean().item()
layer.weight.requires_grad = False
return grad_mean

@staticmethod
def activation_l2(fp32_out: torch.Tensor, int8_out: torch.Tensor) -> float:
"""8. 激活L2距离"""
return torch.norm(fp32_out - int8_out).item()

@staticmethod
def activation_cosine(fp32_out: torch.Tensor, int8_out: torch.Tensor) -> float:
"""9. 激活余弦相似度"""
cos = torch.nn.functional.cosine_similarity(
fp32_out.flatten().unsqueeze(0),
int8_out.flatten().unsqueeze(0)
)
return (1 - cos).item()

@staticmethod
def activation_mse(fp32_out: torch.Tensor, int8_out: torch.Tensor) -> float:
"""10. 激活MSE"""
return torch.nn.functional.mse_loss(fp32_out, int8_out).item()

@staticmethod
def kl_divergence(fp32_out: torch.Tensor, int8_out: torch.Tensor) -> float:
"""11. KL散度"""
p = torch.softmax(fp32_out.flatten(), dim=0)
q = torch.softmax(int8_out.flatten(), dim=0)
# 加epsilon避免log(0)
kl = (p * (p / (q + 1e-8) + 1e-8).log()).sum().item()
return kl

@staticmethod
def jensen_shannon_divergence(
fp32_out: torch.Tensor,
int8_out: torch.Tensor
) -> float:
"""
12. Jensen-Shannon散度(JSD)⭐

论文核心发现:JSD是唯一在所有4×8=32种配置中
零灾难性失败的指标
"""
p = torch.softmax(fp32_out.flatten().float(), dim=0)
q = torch.softmax(int8_out.flatten().float(), dim=0)

# JSD = 0.5 * KL(P||M) + 0.5 * KL(Q||M)
m = 0.5 * (p + q)

def kl(a, b):
return (a * (a / (b + 1e-10) + 1e-10).log()).sum().item()

jsd = 0.5 * kl(p, m) + 0.5 * kl(q, m)
return jsd

@staticmethod
def wasserstein_distance(
fp32_out: torch.Tensor,
int8_out: torch.Tensor
) -> float:
"""13. Wasserstein距离"""
p = torch.softmax(fp32_out.flatten().float(), dim=0)
q = torch.softmax(int8_out.flatten().float(), dim=0)

# 简化:使用Cramer-von Mises统计量
cdf_p = torch.cumsum(p, dim=0)
cdf_q = torch.cumsum(q, dim=0)
return torch.norm(cdf_p - cdf_q).item()


# 批量计算所有指标
def compute_all_metrics(
layer: nn.Module,
fp32_out: torch.Tensor,
int8_out: torch.Tensor,
loss_fn=None,
sample_input=None,
sample_target=None
) -> Dict[str, float]:
"""计算13种敏感度指标"""
metrics = SensitivityMetrics()

results = {}
results['weight_l2'] = metrics.weight_l2_norm(layer)
results['weight_max'] = metrics.weight_max_abs(layer)
results['weight_std'] = metrics.weight_std(layer)
results['weight_kurtosis'] = metrics.weight_kurtosis(layer)

if loss_fn is not None and sample_input is not None:
results['grad_norm'] = metrics.gradient_norm(
layer, loss_fn, sample_input, sample_target)
results['grad_max'] = metrics.gradient_max(
layer, loss_fn, sample_input, sample_target)
results['grad_mean'] = metrics.gradient_mean(
layer, loss_fn, sample_input, sample_target)

results['act_l2'] = metrics.activation_l2(fp32_out, int8_out)
results['act_cosine'] = metrics.activation_cosine(fp32_out, int8_out)
results['act_mse'] = metrics.activation_mse(fp32_out, int8_out)
results['kl_div'] = metrics.kl_divergence(fp32_out, int8_out)
results['jsd'] = metrics.jensen_shannon_divergence(fp32_out, int8_out)
results['wasserstein'] = metrics.wasserstein_distance(fp32_out, int8_out)

return results


# 测试
if __name__ == "__main__":
# 创建测试层
layer = nn.Linear(256, 128)
x = torch.randn(4, 256)
fp32_out = layer(x)

# 模拟INT8量化输出
scale = fp32_out.abs().max() / 127
int8_out = torch.round(fp32_out / scale) * scale

metrics = compute_all_metrics(layer, fp32_out, int8_out)

print("13种敏感度指标:")
print("-" * 40)
for name, value in metrics.items():
print(f" {name:20s}: {value:.6f}")

print(f"\n⭐ JSD (Jensen-Shannon): {metrics['jsd']:.6f}")
print(" → 唯一零灾难性失败指标")

3. K-Means聚类分配策略

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
from sklearn.cluster import KMeans
import numpy as np

class KMeansQuantizationPolicy:
"""
RAMP 的 K-Means 聚类量化策略

论文 Section 3.3: 用K-Means替代固定阈值
自适应地将层分为"安全量化"和"保持FP32"两组
"""

def __init__(self, n_clusters: int = 2):
self.n_clusters = n_clusters
self.kmeans = KMeans(n_clusters=n_clusters, random_state=42)

def fit(self, sensitivities: np.ndarray) -> np.ndarray:
"""
Args:
sensitivities: 各层敏感度值 (n_layers,)
Returns:
assignments: 0=保持FP32(高敏感), 1=量化INT8(低敏感)
"""
# K-Means 聚类
sensitivities_2d = sensitivities.reshape(-1, 1)
labels = self.kmeans.fit_predict(sensitivities_2d)

# 确定哪个cluster是"高敏感"
cluster_means = self.kmeans.cluster_centers_.flatten()
high_sens_cluster = np.argmax(cluster_means)

# 高敏感度 → 保持FP32, 低敏感度 → 量化INT8
assignments = np.where(labels == high_sens_cluster, 'fp32', 'int8')

return assignments

def get_policy(self) -> dict:
"""获取量化策略"""
return {
'fp32_layers': int(np.sum(self.assignments_ == 'fp32')),
'int8_layers': int(np.sum(self.assignments_ == 'int8')),
'cluster_centers': self.kmeans.cluster_centers_.flatten().tolist()
}


# 测试
if __name__ == "__main__":
# 模拟各层JSD敏感度
np.random.seed(42)
n_layers = 20

# 大部分层低敏感度,少数高敏感度
sensitivities = np.concatenate([
np.random.exponential(0.001, 15), # 15层低敏感
np.random.exponential(0.1, 5), # 5层高敏感
])

policy = KMeansQuantizationPolicy(n_clusters=2)
assignments = policy.fit(sensitivities)

print("K-Means 量化策略分配:")
print("-" * 50)
for i, (s, a) in enumerate(zip(sensitivities, assignments)):
print(f" Layer {i:2d}: JSD={s:.6f} → {a}")

print(f"\nFP32层: {np.sum(assignments == 'fp32')}")
print(f"INT8层: {np.sum(assignments == 'int8')}")
print(f"\n✅ K-Means策略测试通过")

4. 计算图碎片化警告

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
class GraphFragmentationAnalyzer:
"""
论文重要发现:排除低加速层反而有害

原因:
1. 排除某些层的量化 → 打断算子融合链
2. 额外的FP32↔INT8类型转换 → 增加开销
3. 计算图碎片化 → 编译器无法优化
"""

@staticmethod
def analyze(quantize_all: bool, skip_layers: list = None) -> dict:
"""分析计算图碎片化"""
if quantize_all:
return {
'strategy': '全量化(含低加速层)',
'fusion_chain': 'Conv→BN→ReLU→Conv→BN→ReLU (完整融合)',
'type_conversions': 0,
'graph_fragments': 1,
'speedup': '1.81x',
'note': '✅ 论文推荐:即使某些层加速不显著,也量化它们'
}
else:
return {
'strategy': '选择性量化(跳过低加速层)',
'fusion_chain': 'Conv→BN→[Cast→FP32→Cast]→ReLU→Conv',
'type_conversions': len(skip_layers or []) * 2,
'graph_fragments': len(skip_layers or []) + 1,
'speedup': '<1.81x(碎片化降低性能)',
'note': '❌ 论文警告:排除低加速层有害'
}

# 测试
if __name__ == "__main__":
print("计算图碎片化分析")
print("=" * 60)

for strategy in [True, False]:
result = GraphFragmentationAnalyzer.analyze(
quantize_all=strategy,
skip_layers=[3, 7, 12] if not strategy else None
)
print(f"\n策略: {result['strategy']}")
print(f" 融合链: {result['fusion_chain']}")
print(f" 类型转换: {result['type_conversions']}次")
print(f" 图碎片数: {result['graph_fragments']}")
print(f" 加速比: {result['speedup']}")
print(f" 备注: {result['note']}")

实验结果

1. 敏感度指标对比

指标类别 具体指标 灾难性失败次数(8配置) 推荐度
权重统计 L2范数 2/8 ⚠️
权重统计 最大绝对值 2/8 ⚠️
权重统计 标准差 2/8 ⚠️
权重统计 峰度 2/8 ⚠️
梯度方法 梯度范数 4/8 ❌
梯度方法 梯度最大值 4/8 ❌
梯度方法 梯度均值 4/8 ❌
激活统计 L2距离 1/8 ✅
激活统计 余弦相似度 1/8 ✅
激活统计 MSE 1/8 ✅
信息论 KL散度 1/8 ✅
信息论 JSD 0/8 ⭐ 最佳
信息论 Wasserstein 1/8 ✅

2. 最终性能

方法 精度损失 加速比 说明
全FP32基线 0% 1.0x 参考
全INT8(激进) 1-3% 2.0x 精度损失大
固定阈值混合 0.5-1.5% 1.5x 阈值脆弱
RAMP (JSD+K-Means) <0.5% 1.81x 近无损

3. 测试网络和平台

网络 参数量 ARM64-A ARM64-B 说明
ResNet-18 11M ✅ ✅ 经典CNN
MobileNetV2 3.5M ✅ ✅ 轻量CNN
EfficientNet-B0 5.3M ✅ ✅ 现代架构
Vision Transformer 6M ✅ ✅ Transformer

IMS 开发启示

1. 量化策略选择

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class IMSQuantizationStrategy:
"""
IMS 量化策略建议(基于RAMP论文)
"""

@staticmethod
def recommend():
return {
'metric': 'Jensen-Shannon散度',
'reason': '唯一零灾难性失败的指标',
'clustering': 'K-Means(非固定阈值)',
'reason2': '自适应现代架构的偏斜分布',
'skip_low_speedup_layers': False,
'reason3': '排除低加速层导致图碎片化',
'expected_speedup': '1.81x',
'expected_accuracy_loss': '<0.5%',
'deployment': 'ARM64 CPU(无需GPU/梯度计算)',
}

print("IMS 量化策略推荐:")
print("=" * 40)
for k, v in IMSQuantizationStrategy.recommend().items():
print(f" {k}: {v}")

2. 与 TriDrive INT8 论文对照

维度 RAMP (BMVC 2026) INT8 Portability (KATECH)
关注点 哪些层该量化 量化后能否跨平台
核心发现 JSD最佳 不可跨平台
建议 用JSD+K-Means 每平台单独验证
互补性 量化策略制定 量化结果验证

结合使用: 用 RAMP 决定哪些层量化,用 KATECH 方法在每个目标平台验证。

3. IMS 各平台适用性

平台 RAMP适用性 原因
QCS8255 CPU部分 ✅ ARM64 CPU
TDA4VM ARM端 ✅ ARM64 CPU
STM32N6 ✅ ARM CPU
RK3588 ✅ ARM64 dotprod

总结

RAMP 论文是边缘AI量化部署的实用指南:

  1. JSD 是最可靠的敏感度指标 — 在所有配置中零灾难性失败
  2. K-Means 聚类替代固定阈值 — 适应现代架构的偏斜分布
  3. 不要排除低加速层 — 图碎片化比量化开销更严重
  4. 1.81x加速+近无损 — 在纯CPU上达到优秀结果
  5. 无需GPU或梯度计算 — 适合资源受限的边缘部署

对 IMS 的核心启示:在多芯片平台部署DMS/OMS模型时,使用JSD+K-Means策略制定混合精度量化方案,然后在每个目标平台用KATECH方法验证。这两个工作组合使用,构成了边缘部署的完整量化方法论。