共计 2797 个字符,预计需要花费 7 分钟才能阅读完成。
最近在移动端尝试部署 autoglm-9b-phone 模型时,发现这个在 NLP 任务中表现出色的模型,在手机端运行时总会遇到内存爆炸和推理延迟的问题。经过一番折腾,总算总结出一套完整的优化方案,今天就来分享下从模型理解到最终部署的全过程。

为什么选择 autoglm-9b-phone?
这个模型在移动端 NLP 任务中有几个明显的优势:
- 专门针对手机端优化的 9B 参数规模,在文本生成、问答等任务上效果接近云端大模型
- 支持中英文混合场景,特别适合聊天机器人、智能助手等应用
- 预训练时加入了移动端用户 query 数据,对口语化输入理解更好
我们团队在客服自动回复场景测试发现,相比传统小模型,其意图识别准确率能提升 15% 以上。
模型架构拆解
Transformer 层的特殊优化
普通 Transformer 在移动端跑起来像老牛拉车,autoglm-9b-phone 做了几个关键改进:
- 块稀疏注意力 :把长文本切分成 256token 的块,只在块内计算注意力,内存占用直降 70%
- 动态头剪枝 :根据输入内容动态关闭部分注意力头,实测可减少 20% 计算量
- KV Cache 量化 :将注意力层的 key/value 缓存用 4bit 存储,推理时再反量化
量化方案选择
试了三种量化方案后得到的数据对比(测试设备:小米 13 Snapdragon 8 Gen2):
| 方案 | 模型大小 | 内存占用 | 推理延迟 | 准确率 |
|---|---|---|---|---|
| FP32 原始 | 34GB | 6.2GB | 3800ms | 100% |
| FP16 | 17GB | 3.1GB | 2100ms | 99.8% |
| INT8 静态 | 8.5GB | 1.8GB | 950ms | 97.3% |
| INT8 动态 | 8.5GB | 1.9GB | 1050ms | 98.1% |
最终选择 INT8 动态量化,在精度和速度间取得较好平衡。
微调实战
基于 HuggingFace 的完整流程
from transformers import AutoModelForCausalLM, AutoTokenizer
import torch
# 加载预训练模型
model = AutoModelForCausalLM.from_pretrained("THUDM/autoglm-9b-phone",
torch_dtype=torch.float16,
device_map="auto")
tokenizer = AutoTokenizer.from_pretrained("THUDM/autoglm-9b-phone")
# 自定义数据集处理
def process_func(examples):
# 拼接 instruction 和 input
texts = [f"指令:{ins}\n 输入:{inp}\n 回答:"
for ins, inp in zip(examples['instruction'], examples['input'])]
# tokenize 时自动添加 eos_token
tokenized = tokenizer(texts, truncation=True, max_length=512,
padding="max_length", return_tensors="pt")
# 将输入部分设为 ignore_index=-100
labels = tokenized.input_ids.clone()
sep_pos = [text.index("回答:")+3 for text in texts]
for i, pos in enumerate(sep_pos):
labels[i, :pos] = -100
return {"input_ids": tokenized.input_ids,
"attention_mask": tokenized.attention_mask,
"labels": labels}
# 使用加权交叉熵
loss_fct = torch.nn.CrossEntropyLoss(ignore_index=-100,
weight=torch.tensor([1.0]+[2.0]*5000))
LoRA 高效微调实现
from peft import LoraConfig, get_peft_model
# 只对以下层添加 LoRA 适配器
target_modules = ["q_proj", "k_proj", "v_proj", "out_proj"]
lora_config = LoraConfig(
r=8, # 秩
lora_alpha=32,
target_modules=target_modules,
lora_dropout=0.1,
bias="none",
task_type="CAUSAL_LM"
)
model = get_peft_model(model, lora_config)
model.print_trainable_parameters() # 通常可减少 90%+ 训练参数
部署优化
ONNX 转换避坑指南
遇到最头疼的三个问题:
-
动态 shape 支持 :导出时需显式指定动态维度
torch.onnx.export( model, dummy_input, "model.onnx", dynamic_axes={"input_ids": [0, 1], "output": [0, 1]}, opset_version=15 ) -
自定义算子兼容 :用 onnxruntime 的 custom ops 支持
- 量化模型导出 :建议先转 FP32 再在 onnx 中量化
安卓端 TFLite 打包
关键步骤:
-
将 ONNX 转为 TFLite 格式
python -m tf2onnx.convert --opset 15 --onnx model.onnx --output model.pb tflite_convert --saved_model_dir ./ --output_file model.tflite -
创建 AAR 包时注意包含:
- libonnxruntime.so
- 模型字典文件
-
预处理 / 后处理 Java 工具类
-
在 build.gradle 中添加:
android { aaptOptions {noCompress "tflite", "onnx"} }
性能测试数据
在电商客服场景下的测试结果:
| 优化阶段 | 内存峰值 | 平均延迟 | 意图识别准确率 |
|---|---|---|---|
| 原始模型 | 5.8GB | 3.2s | 92.4% |
| +INT8 量化 | 1.7GB | 1.1s | 91.1% |
| + 层剪枝 | 1.2GB | 0.8s | 89.7% |
| +KV Cache 优化 | 0.9GB | 0.6s | 89.3% |
生产环境 Checklist
量化敏感层识别
- 逐层量化后验证准确率
- 特别关注:
- 第一层和最后一层的 embedding
- LayerNorm 的输入输出
- 注意力分数计算部分
动态 shape 处理
- 设置合理的 max_seq_length(建议 256-512)
- 使用 memory_pool 申请固定大小显存
- 对短输入主动 padding 到固定长度
端侧缓存策略
- 实现 LRU 缓存管理 KV Cache
- 根据手机内存动态调整缓存大小
- 对高频 query 建立结果缓存
经过这一整套优化,最终我们的客服机器人能在中端手机上跑出 800ms 内的响应速度,比初期优化前提升了近 3 倍。建议大家在模型压缩时多做 AB 测试,找到最适合自己业务的平衡点。
正文完
