邊緣AI模型優化實戰:量化、剪枝與部署全鏈路指南

AI与大数据

摘要

  • 邊緣AI是2026年最大增量市場:全球邊緣AI晶片出貨量預計超50億片,模型優化是落地的關鍵瓶頸
  • 量化3級跳:FP16→INT8→INT4,INT8量化精度損失<1%,INT4需配合GPTQ/AWQ演算法
  • 結構化剪枝2大路線:幅度剪枝簡單高效,蒸餾剪枝精度更好,組合使用可壓縮70%+參數
  • 部署3大框架:ONNX Runtime通用性最強、TensorRT效能最優、TFLite行動端首選
  • 本文提供從模型優化到邊緣部署的全鏈路實戰程式碼

目錄


邊緣AI:AI落地的最後十公里

邊緣AI市場現狀

設備類型 2026出貨量 典型算力 典型應用
智慧型手機 15億 30-60 TOPS 語音助手、拍照增強
智慧攝影機 5億 2-8 TOPS 人臉辨識、行為偵測
汽車(ADAS) 1億 50-200 TOPS 自動駕駛、DMS
IoT閘道器 3億 1-4 TOPS 預測維護、異常偵測
工業控制器 5000萬 5-20 TOPS 質檢、機器人控制

邊緣AI核心挑戰

挑戰 雲端 邊緣 差距
算力 1000+ TOPS 2-60 TOPS 50-500×
記憶體 80-640GB 2-16GB 5-80×
功耗 300W+ 5-30W 10-60×
延遲 50-200ms(網路) 1-10ms(本地) 邊緣優勢
隱私 資料上雲 本地處理 邊緣優勢

邊緣AI晶片格局

晶片 廠商 算力 功耗 生態
Jetson Orin NX NVIDIA 100 TOPS 25W CUDA+TensorRT
Hailo-8 Hailo 26 TOPS 2.5W 專用編譯器
Google Edge TPU Google 4 TOPS 2W TFLite
瑞芯微RK3588 瑞芯微 6 TOPS 10W RKNN
華為昇騰310P 華為 22 TOPS 8W MindSpore
地平線J5 地平線 128 TOPS 15W 天工開物

模型量化3級跳

量化原理與精度影響

┌──────────────────────────────────────────────────────────────┐
│              量化3級跳                                         │
│                                                                │
│  FP32 → FP16 (半精度)                                        │
│  ┌──────────────────────────────────────────────────────┐    │
│  │  精度損失: <0.1%                                     │    │
│  │  體積縮減: 50%                                       │    │
│  │  速度提升: 1.5-2×                                    │    │
│  │  方法: 直接轉換,無需校準                              │    │
│  └──────────────────────────────────────────────────────┘    │
│                         ↓                                     │
│  FP16 → INT8 (8位整數量化)                                   │
│  ┌──────────────────────────────────────────────────────┐    │
│  │  精度損失: 0.5-2%                                    │    │
│  │  體積縮減: 75% (vs FP32)                             │    │
│  │  速度提升: 2-4×                                      │    │
│  │  方法: PTQ(訓練後量化) / QAT(量化感知訓練)             │    │
│  └──────────────────────────────────────────────────────┘    │
│                         ↓                                     │
│  INT8 → INT4 (4位整數量化)                                   │
│  ┌──────────────────────────────────────────────────────┐    │
│  │  精度損失: 2-5% (需GPTQ/AWQ補償)                     │    │
│  │  體積縮減: 87.5% (vs FP32)                           │    │
│  │  速度提升: 3-6×                                      │    │
│  │  方法: GPTQ / AWQ / AQLM / HQQ                      │    │
│  └──────────────────────────────────────────────────────┘    │
└──────────────────────────────────────────────────────────────┘

INT8 PTQ量化實戰

import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
from neural_compressor import QuantizationAwareTraining, PostTrainingQuantConfig

def int8_ptq_quantize(model_path, output_path, calibration_data):
    model = AutoModelForCausalLM.from_pretrained(
        model_path,
        torch_dtype=torch.float16,
        device_map="auto",
    )
    tokenizer = AutoTokenizer.from_pretrained(model_path)
    
    config = PostTrainingQuantConfig(
        approach="static",
        tuning_criterion={
            "max_trials": 100,
            "objective": "accuracy",
        },
        quant_format="QDQ",
        calibration_sampling_size=512,
    )
    
    def calibration_dataloader():
        for text in calibration_data:
            inputs = tokenizer(text, return_tensors="pt", max_length=512)
            yield {"input_ids": inputs["input_ids"]}
    
    from neural_compressor import quantize
    q_model = quantize(
        model,
        config,
        calibration_dataloader=calibration_dataloader,
    )
    
    q_model.save(output_path)
    return q_model

def int8_gptq_quantize(model_path, output_path, calibration_data):
    from auto_gptq import AutoGPTQForCausalLM, BaseQuantizeConfig
    
    tokenizer = AutoTokenizer.from_pretrained(model_path)
    model = AutoGPTQForCausalLM.from_pretrained(
        model_path,
        torch_dtype=torch.float16,
        device_map="auto",
    )
    
    quantize_config = BaseQuantizeConfig(
        bits=8,
        group_size=128,
        desc_act=True,
        damp_percent=0.01,
        sym=True,
    )
    
    examples = []
    for text in calibration_data:
        tokenized = tokenizer(text, return_tensors="pt", max_length=2048)
        examples.append(tokenized["input_ids"])
    
    model.quantize(examples, quantize_config=quantize_config)
    model.save_quantized(output_path)
    return model

INT4量化實戰(GPTQ/AWQ)

def int4_gptq_quantize(model_path, output_path, calibration_data):
    from auto_gptq import AutoGPTQForCausalLM, BaseQuantizeConfig
    
    tokenizer = AutoTokenizer.from_pretrained(model_path)
    model = AutoGPTQForCausalLM.from_pretrained(
        model_path,
        torch_dtype=torch.float16,
        device_map="auto",
    )
    
    quantize_config = BaseQuantizeConfig(
        bits=4,
        group_size=128,
        desc_act=True,
        damp_percent=0.01,
        sym=False,
    )
    
    examples = []
    for text in calibration_data[:256]:
        tokenized = tokenizer(text, return_tensors="pt", max_length=2048)
        examples.append(tokenized["input_ids"])
    
    model.quantize(examples, quantize_config=quantize_config)
    model.save_quantized(output_path)
    return model

def int4_awq_quantize(model_path, output_path, calibration_data):
    from awq import AutoAWQForCausalLM
    
    model = AutoAWQForCausalLM.from_pretrained(model_path)
    tokenizer = AutoTokenizer.from_pretrained(model_path, trust_remote_code=True)
    
    quant_config = {
        "zero_point": True,
        "q_group_size": 128,
        "w_bit": 4,
        "version": "GEMM",
    }
    
    model.quantize(
        tokenizer,
        quant_config=quant_config,
        calib_data=calibration_data[:256],
    )
    
    model.save_quantized(output_path)
    return model

量化效果對比

量化方案 7B模型體積 推理速度 精度損失 顯存需求
FP32 28GB 基線 32GB
FP16 14GB 1.8× <0.1% 16GB
INT8 PTQ 7GB 3.2× 0.5-2% 8GB
INT8 GPTQ 7GB 3.5× 0.3-1% 8GB
INT4 GPTQ 3.5GB 5.0× 2-4% 4GB
INT4 AWQ 3.5GB 5.5× 1-3% 4GB

結構化剪枝實戰

剪枝方法對比

方法 粒度 精度損失 硬體友好 複雜度
非結構化剪枝 單權重 差(稀疏計算)
結構化剪枝 通道/層 好(密集計算)
蒸餾剪枝 通道+蒸餾
自動剪枝(NAS) 搜尋空間 極高

幅度剪枝實戰

import torch
import torch.nn as nn
from transformers import AutoModelForCausalLM

class MagnitudePruner:
    def __init__(self, model, sparsity_ratio=0.5):
        self.model = model
        self.sparsity_ratio = sparsity_ratio
        self.masks = {}
    
    def compute_masks(self):
        for name, module in self.model.named_modules():
            if isinstance(module, nn.Linear):
                weight = module.weight.data.abs()
                threshold = torch.quantile(
                    weight.flatten(), self.sparsity_ratio
                )
                self.masks[name] = (weight > threshold).float()
    
    def apply_masks(self):
        for name, module in self.model.named_modules():
            if name in self.masks and isinstance(module, nn.Linear):
                module.weight.data *= self.masks[name]
    
    def structured_prune(self):
        for name, module in self.model.named_modules():
            if isinstance(module, nn.Linear):
                weight = module.weight.data
                row_norms = weight.norm(dim=1)
                threshold = torch.quantile(
                    row_norms, self.sparsity_ratio
                )
                keep_mask = (row_norms > threshold).float()
                module.weight.data *= keep_mask.unsqueeze(1)
                if module.bias is not None:
                    module.bias.data *= keep_mask
    
    def fine_tune(self, dataloader, epochs=3, lr=1e-5):
        optimizer = torch.optim.AdamW(self.model.parameters(), lr=lr)
        
        for epoch in range(epochs):
            for batch in dataloader:
                outputs = self.model(**batch)
                loss = outputs.loss
                loss.backward()
                optimizer.step()
                optimizer.zero_grad()
                self.apply_masks()
        
        return self.model

def prune_model(model_path, output_path, sparsity=0.5):
    model = AutoModelForCausalLM.from_pretrained(
        model_path, torch_dtype=torch.float16, device_map="auto"
    )
    
    pruner = MagnitudePruner(model, sparsity_ratio=sparsity)
    pruner.structured_prune()
    
    model.save_pretrained(output_path)
    return model

剪枝+蒸餾組合

class PruneDistillPipeline:
    def __init__(self, teacher_path, student_path, sparsity=0.5):
        self.teacher = AutoModelForCausalLM.from_pretrained(
            teacher_path, torch_dtype=torch.float16, device_map="auto"
        )
        self.student = AutoModelForCausalLM.from_pretrained(
            student_path, torch_dtype=torch.float16, device_map="auto"
        )
        self.pruner = MagnitudePruner(self.student, sparsity)
    
    def train(self, dataloader, epochs=5):
        optimizer = torch.optim.AdamW(self.student.parameters(), lr=2e-5)
        kl_loss = nn.KLDivLoss(reduction="batchmean")
        
        for epoch in range(epochs):
            for batch in dataloader:
                with torch.no_grad():
                    teacher_out = self.teacher(**batch)
                
                student_out = self.student(**batch)
                
                hard_loss = student_out.loss
                
                soft_loss = kl_loss(
                    F.log_softmax(student_out.logits / 2.0, dim=-1),
                    F.softmax(teacher_out.logits / 2.0, dim=-1),
                ) * 4.0
                
                total_loss = 0.3 * hard_loss + 0.7 * soft_loss
                
                total_loss.backward()
                optimizer.step()
                optimizer.zero_grad()
                self.pruner.apply_masks()
        
        return self.student

剪枝效果對比

剪枝方案 參數保留 精度損失 推理加速 體積縮減
幅度剪枝50% 50% 3-5% 1.8× 50%
幅度剪枝70% 30% 8-12% 2.5× 70%
蒸餾剪枝50% 50% 1-3% 1.8× 50%
蒸餾剪枝70% 30% 4-6% 2.5× 70%
剪枝+量化 30%+INT8 5-8% 85%

ONNX Runtime部署實戰

PyTorch→ONNX匯出

import torch
from transformers import AutoModelForCausalLM, AutoTokenizer

def export_to_onnx(model_path, output_path, opset=17):
    model = AutoModelForCausalLM.from_pretrained(
        model_path, torch_dtype=torch.float16
    )
    tokenizer = AutoTokenizer.from_pretrained(model_path)
    model.eval()
    
    dummy_input = tokenizer("Hello", return_tensors="pt")
    input_ids = dummy_input["input_ids"]
    attention_mask = dummy_input["attention_mask"]
    
    torch.onnx.export(
        model,
        (input_ids, attention_mask),
        output_path,
        opset_version=opset,
        input_names=["input_ids", "attention_mask"],
        output_names=["logits"],
        dynamic_axes={
            "input_ids": {0: "batch", 1: "seq_len"},
            "attention_mask": {0: "batch", 1: "seq_len"},
            "logits": {0: "batch", 1: "seq_len"},
        },
        do_constant_folding=True,
    )
    print(f"Exported to {output_path}")

ONNX Runtime推理

import onnxruntime as ort
import numpy as np

class ONNXInferenceEngine:
    def __init__(self, model_path, provider="CUDAExecutionProvider"):
        sess_options = ort.SessionOptions()
        sess_options.graph_optimization_level = ort.GraphOptimizationLevel.ORT_ENABLE_ALL
        sess_options.intra_op_num_threads = 4
        sess_options.inter_op_num_threads = 4
        
        self.session = ort.InferenceSession(
            model_path,
            sess_options=sess_options,
            providers=[provider],
        )
        
        self.io_binding = self.session.io_binding()
    
    def infer(self, input_ids, attention_mask):
        input_ids_np = np.array(input_ids, dtype=np.int64)
        attention_mask_np = np.array(attention_mask, dtype=np.int64)
        
        outputs = self.session.run(
            ["logits"],
            {
                "input_ids": input_ids_np,
                "attention_mask": attention_mask_np,
            },
        )
        
        return outputs[0]
    
    def infer_with_io_binding(self, input_ids, attention_mask):
        input_ids_tensor = ort.OrtValue.ortvalue_from_numpy(
            np.array(input_ids, dtype=np.int64), "cuda", 0
        )
        attention_mask_tensor = ort.OrtValue.ortvalue_from_numpy(
            np.array(attention_mask, dtype=np.int64), "cuda", 0
        )
        
        self.io_binding.bind_ortvalue_input("input_ids", input_ids_tensor)
        self.io_binding.bind_ortvalue_input("attention_mask", attention_mask_tensor)
        self.io_binding.bind_output("logits", "cuda", 0)
        
        self.session.run_with_iobinding(self.io_binding)
        
        return self.io_binding.get_outputs()[0].numpy()

ONNX Runtime效能對比

配置 延遲(ms) 吞吐(req/s) 顯存
PyTorch FP16 45 22 8GB
ORT FP16 32 31 6GB
ORT INT8 18 55 4GB
ORT INT8+IO Binding 14 71 4GB
ORT INT8+Batch=8 8/batch 125 6GB

TensorRT優化實戰

ONNX→TensorRT轉換

import tensorrt as trt

def build_tensorrt_engine(
    onnx_path,
    engine_path,
    fp16=True,
    int8=False,
    max_batch_size=8,
    max_workspace=4 << 30,
):
    logger = trt.Logger(trt.Logger.WARNING)
    builder = trt.Builder(logger)
    network = builder.create_network(
        1 << int(trt.NetworkDefinitionCreationFlag.EXPLICIT_BATCH)
    )
    parser = trt.OnnxParser(network, logger)
    
    with open(onnx_path, "rb") as f:
        if not parser.parse(f.read()):
            for i in range(parser.num_errors):
                print(f"Parse error: {parser.get_error(i)}")
            return None
    
    config = builder.create_builder_config()
    config.set_memory_pool_limit(trt.MemoryPoolType.WORKSPACE, max_workspace)
    
    if fp16:
        config.set_flag(trt.BuilderFlag.FP16)
    
    if int8:
        config.set_flag(trt.BuilderFlag.INT8)
        config.int8_calibrator = None
    
    profile = builder.create_optimization_profile()
    profile.set_shape(
        "input_ids",
        min=(1, 1),
        opt=(1, 128),
        max=(max_batch_size, 2048),
    )
    profile.set_shape(
        "attention_mask",
        min=(1, 1),
        opt=(1, 128),
        max=(max_batch_size, 2048),
    )
    config.add_optimization_profile(profile)
    
    engine = builder.build_serialized_network(network, config)
    
    with open(engine_path, "wb") as f:
        f.write(engine)
    
    print(f"TensorRT engine saved to {engine_path}")
    return engine

class TensorRTInference:
    def __init__(self, engine_path):
        self.logger = trt.Logger(trt.Logger.WARNING)
        self.runtime = trt.Runtime(self.logger)
        
        with open(engine_path, "rb") as f:
            self.engine = self.runtime.deserialize_cuda_engine(f.read())
        
        self.context = self.engine.create_execution_context()
    
    def infer(self, input_ids, attention_mask):
        import pycuda.driver as cuda
        import pycuda.autoinit
        
        input_ids_np = np.array(input_ids, dtype=np.int32)
        attention_mask_np = np.array(attention_mask, dtype=np.int32)
        
        d_input_ids = cuda.mem_alloc(input_ids_np.nbytes)
        d_attention_mask = cuda.mem_alloc(attention_mask_np.nbytes)
        
        cuda.memcpy_htod(d_input_ids, input_ids_np)
        cuda.memcpy_htod(d_attention_mask, attention_mask_np)
        
        output_shape = self.engine.get_binding_shape(2)
        d_output = cuda.mem_alloc(
            trt.volume(output_shape) * np.dtype(np.float32).itemsize
        )
        
        bindings = [int(d_input_ids), int(d_attention_mask), int(d_output)]
        
        self.context.execute_v2(bindings)
        
        output = np.empty(output_shape, dtype=np.float32)
        cuda.memcpy_dtoh(output, d_output)
        
        return output

TensorRT效能對比

配置 延遲(ms) 吞吐(req/s) 加速比
PyTorch FP16 45 22
ORT INT8 18 55 2.5×
TRT FP16 15 66
TRT INT8 8 125 5.6×
TRT INT8+Batch=8 3/batch 333 15×

邊緣設備推理策略

分層推理架構

┌──────────────────────────────────────────────────────────────┐
│              邊緣-雲協同推理架構                                │
│                                                                │
│  雲端(大模型)                                                 │
│  ┌──────────────────────────────────────────────────────┐    │
│  │  70B模型 → 複雜推理、長文本生成、多輪對話              │    │
│  │  延遲: 200-500ms | 成本: 高                           │    │
│  └──────────────────────────────────────────────────────┘    │
│                         ↕ 網路                                │
│  邊緣閘道器(中等模型)                                           │
│  ┌──────────────────────────────────────────────────────┐    │
│  │  7B量化模型 → 日常推理、簡單對話、意圖辨識              │    │
│  │  延遲: 20-50ms | 成本: 中                             │    │
│  └──────────────────────────────────────────────────────┘    │
│                         ↕ 本地匯流排                            │
│  終端設備(小模型)                                             │
│  ┌──────────────────────────────────────────────────────┐    │
│  │  剪枝+量化小模型 → 關鍵詞偵測、喚醒詞、簡單分類         │    │
│  │  延遲: 1-5ms | 成本: 無                               │    │
│  └──────────────────────────────────────────────────────┘    │
└──────────────────────────────────────────────────────────────┘

邊緣部署程式碼(Jetson)

import jetson.inference
import jetson.utils

class EdgeInferencePipeline:
    def __init__(self, model_type="classification", threshold=0.7):
        self.model = jetson.inference.initialize(model_type)
        self.threshold = threshold
        self.fallback_enabled = True
    
    def infer_local(self, image):
        try:
            results = self.model.Detect(image)
            confident = [r for r in results if r.Confidence >= self.threshold]
            
            if not confident and self.fallback_enabled:
                return self.infer_cloud(image)
            
            return confident
        except Exception as e:
            if self.fallback_enabled:
                return self.infer_cloud(image)
            raise
    
    def infer_cloud(self, image):
        import requests
        _, compressed = cv2.imencode(".jpg", image, [cv2.IMWRITE_JPEG_QUALITY, 85])
        
        response = requests.post(
            "https://api.example.com/infer",
            files={"image": compressed.tobytes()},
            timeout=5.0,
        )
        return response.json()
    
    def benchmark(self, image, iterations=100):
        import time
        
        latencies = []
        for _ in range(iterations):
            start = time.perf_counter()
            self.infer_local(image)
            latencies.append((time.perf_counter() - start) * 1000)
        
        return {
            "mean_ms": sum(latencies) / len(latencies),
            "p50_ms": sorted(latencies)[len(latencies) // 2],
            "p99_ms": sorted(latencies)[int(len(latencies) * 0.99)],
        }

邊緣設備效能實測

設備 模型 量化 延遲(ms) 精度
Jetson Orin NX BERT-base FP16 12 99.2%
Jetson Orin NX BERT-base INT8 6 98.8%
Jetson Orin NX ResNet50 FP16 8 99.5%
Jetson Orin NX ResNet50 INT8 4 99.1%
RK3588 MobileBERT INT8 25 97.5%
RK3588 MobileNetV3 INT8 8 98.2%
樹莓派5 TinyBERT INT8 120 95.8%
樹莓派5 MobileNetV2 INT8 45 97.0%

總結與引流

關鍵要點回顧

  1. 量化:INT8 PTQ是性價比最優選擇,INT4需GPTQ/AWQ補償精度
  2. 剪枝:結構化剪枝+蒸餾組合可壓縮70%+參數,精度損失可控
  3. 部署:ONNX Runtime通用、TensorRT極致效能、TFLite行動端
  4. 邊緣策略:分層推理+雲端回退,兼顧延遲與精度

邊緣AI優化路線

階段 優化手段 預期效果
第1步 FP16匯出+ONNX Runtime 2×加速
第2步 INT8量化 4×加速
第3步 結構化剪枝 體積-50%
第4步 TensorRT優化 6×加速
第5步 邊緣-雲協同 靈活部署

需要壓縮AI生成的圖片?試試我們的圖片壓縮工具影片壓縮,快速優化邊緣部署素材。

延伸閱讀

本站提供瀏覽器本地工具,免註冊即可試用 →

#边缘AI优化#模型量化#ONNX Runtime#TensorRT部署#边缘推理优化#2026