AI網(wǎng)關(guān)落地實戰(zhàn):MAI Gateway邊緣部署與模型量化)
1. 項目緣起為什么制造業(yè)需要一臺“AI網(wǎng)關(guān)”1.1 從一次車間里的尷尬說起去年秋天我被拉到一個做精密注塑的朋友廠里幫忙看產(chǎn)線質(zhì)檢的問題。他們的場景很典型注塑機旁邊架著一臺工業(yè)相機拍產(chǎn)品表面后端跑一個視覺檢測模型判斷有沒有劃痕、缺料、飛邊。聽起來不復(fù)雜但實際跑起來問題一堆。車間網(wǎng)絡(luò)時斷時續(xù)模型推理要調(diào)用云端接口一張圖來回幾百毫秒產(chǎn)線節(jié)拍根本等不起更麻煩的是有些訂單的圖紙和工藝參數(shù)屬于客戶機密數(shù)據(jù)出不了廠區(qū)云端方案直接被否掉。那天下午我蹲在車間里看著一臺老舊的工控機風(fēng)扇呼呼轉(zhuǎn)屏幕上檢測結(jié)果一卡一卡地跳心里就在想制造業(yè)要的AI不是參數(shù)多大的模型而是一個能塞進車間角落、斷網(wǎng)也能跑、數(shù)據(jù)不出門、還能跟現(xiàn)有PLC和MES說上話的“中間層”。這個中間層就是后來我們反復(fù)折騰的AI網(wǎng)關(guān)而MAI Gateway是我在幾個項目里實際落地過的一套方案。所謂AI網(wǎng)關(guān)你可以把它理解成車間里的“翻譯官調(diào)度員”。往上它對接各種AI模型——視覺的、時序的、文本的往下它對接產(chǎn)線上的設(shè)備、傳感器、工控系統(tǒng)。它不生產(chǎn)數(shù)據(jù)也不訓(xùn)練模型它負責(zé)把模型的能力“翻譯”成產(chǎn)線能用的信號同時把產(chǎn)線的數(shù)據(jù)“翻譯”成模型能吃的格式。制造業(yè)上AI卡點往往不在算法本身而在這一層“最后一公里”的對接上。1.2 制造業(yè)AI落地的三個硬約束我在不同工廠跑下來發(fā)現(xiàn)制造業(yè)對AI網(wǎng)關(guān)的需求跟互聯(lián)網(wǎng)公司完全不是一回事?;ヂ?lián)網(wǎng)講彈性、講并發(fā)、講秒級擴容制造業(yè)講的是另外三個詞穩(wěn)定、離線、實時。先說穩(wěn)定。車間環(huán)境不是恒溫機房夏天四十度、冬天零下、粉塵、震動、電磁干擾工控機死機一次可能整條線停半小時。所以網(wǎng)關(guān)的第一要求是“別崩”而不是“跑得快”。我見過太多方案在實驗室跑得飛起一進車間三天兩頭重啟。再說離線。很多工廠的內(nèi)網(wǎng)是物理隔離的尤其是涉及軍工、汽車零部件、醫(yī)療器械的產(chǎn)線數(shù)據(jù)根本不允許出局域網(wǎng)。這意味著網(wǎng)關(guān)必須能在完全斷網(wǎng)的情況下完成模型推理、結(jié)果判定、日志記錄的全流程。你不能指望一個每次都要聯(lián)網(wǎng)校驗授權(quán)的方案。最后是實時。產(chǎn)線節(jié)拍是按秒甚至毫秒算的注塑機一個周期可能就十幾秒檢測必須在幾百毫秒內(nèi)出結(jié)果否則產(chǎn)品都流走了你才報警毫無意義。這就要求網(wǎng)關(guān)的推理鏈路足夠短不能有太多網(wǎng)絡(luò)跳轉(zhuǎn)和序列化開銷。這三個約束疊加起來就決定了制造業(yè)的AI網(wǎng)關(guān)不能照搬互聯(lián)網(wǎng)那套微服務(wù)架構(gòu)得走邊緣部署、本地推理、輕量通信的路子。MAI Gateway 在我手里的幾次落地基本就是圍繞這三點做取舍。1.3 這篇文章適合誰看如果你是在工廠里推AI落地的工程師、項目經(jīng)理或者你是做AI應(yīng)用想切入制造業(yè)的開發(fā)者這篇文章應(yīng)該對你有用。我不會講太多虛的架構(gòu)圖重點放在實際怎么選、怎么裝、怎么調(diào)、怎么排錯上。里面提到的參數(shù)、命令、配置都是我在真實項目里跑過的你可以直接抄作業(yè)但要注意每家工廠的現(xiàn)場條件不同照搬之前先做小范圍驗證。我也會把踩過的坑攤開講比如模型量化后精度掉了怎么辦、工控機沒有GPU怎么撐推理、斷網(wǎng)后日志怎么補傳。這些東西文檔里不會寫但現(xiàn)場一定會遇到。2. 方案選型MAI Gateway 為什么這么搭2.1 整體架構(gòu)的取舍邏輯先把這個網(wǎng)關(guān)的骨架說清楚。MAI Gateway 在我落地的版本里核心就四層接入層、推理層、規(guī)則層、輸出層。接入層負責(zé)收數(shù)據(jù)推理層負責(zé)跑模型規(guī)則層負責(zé)把模型輸出轉(zhuǎn)成業(yè)務(wù)判定輸出層負責(zé)把結(jié)果發(fā)給PLC、MES或者報警燈。為什么這么分因為制造業(yè)的場景里模型輸出和業(yè)務(wù)判定往往是兩回事。舉個例子視覺模型輸出的是“缺陷置信度0.87”但產(chǎn)線要的是“這個產(chǎn)品是NG觸發(fā)剔除氣缸”。中間這個轉(zhuǎn)換邏輯不同客戶、不同產(chǎn)品、不同批次都可能不一樣如果把它寫死在模型里改一次就要重新訓(xùn)練成本太高。所以我把規(guī)則層單獨抽出來用配置文件描述“置信度大于多少算NG、連續(xù)幾個NG觸發(fā)停線”現(xiàn)場工程師自己就能改不用等算法團隊。接入層我選的是消息隊列HTTP雙通道。消息隊列走MQTT適合傳感器這種高頻小數(shù)據(jù)HTTP走RESTful適合相機這種一次傳一張圖的場景。為什么不統(tǒng)一用gRPC因為很多工廠現(xiàn)有的MES和SCADA系統(tǒng)只認HTTP你強行上gRPC對接成本反而高。這個取舍很現(xiàn)實在制造業(yè)兼容既有系統(tǒng)比技術(shù)先進更重要。推理層是核心我后面單獨講。輸出層我留了三種出口寫數(shù)據(jù)庫、發(fā)MQTT、調(diào)HTTP接口。寫數(shù)據(jù)庫是為了追溯發(fā)MQTT是為了實時控制調(diào)HTTP是為了對接老系統(tǒng)。三種出口可以同時開互不干擾。2.2 推理引擎的選擇ONNX Runtime 還是 TensorRT這是選型時爭論最多的點。我最后在大多數(shù)項目里選了ONNX Runtime只在有NVIDIA顯卡且對延遲極度敏感的場景才上TensorRT。原因不復(fù)雜ONNX Runtime 的跨平臺兼容性好太多x86工控機、ARM邊緣盒子、甚至國產(chǎn)化平臺都能跑而TensorRT綁死NVIDIA生態(tài)工廠里大部分工控機根本沒有獨立顯卡。具體到性能我實測過一個YOLOv5s的模型輸入640x640在Intel i5-8500的工控機上ONNX RuntimeCPU模式單張推理大概120到150毫秒開多線程能壓到80毫秒左右。這個速度對于注塑、沖壓這種節(jié)拍在秒級的場景完全夠用。如果是更快的產(chǎn)線比如電子元件貼裝節(jié)拍在百毫秒級那就得上GPU或者用更小的模型。這里有個經(jīng)驗不要一上來就追求極致延遲先算清楚產(chǎn)線節(jié)拍。我見過一個項目客戶非要上TensorRT結(jié)果工控機沒有顯卡又加了一張Tesla T4成本翻了三倍最后發(fā)現(xiàn)其實CPU推理就夠因為他們的檢測工位本來就有幾秒的緩沖。選型前先拿秒表去產(chǎn)線蹲半天比看任何benchmark都準(zhǔn)。2.3 模型格式與量化策略模型從訓(xùn)練到部署中間要過好幾道轉(zhuǎn)換。我的標(biāo)準(zhǔn)流程是PyTorch訓(xùn)練 → 導(dǎo)出ONNX → 量化 → 部署。量化這一步最容易出問題得重點說。量化分兩種動態(tài)量化和靜態(tài)量化。動態(tài)量化簡單一行代碼就能轉(zhuǎn)但精度損失相對大靜態(tài)量化需要校準(zhǔn)數(shù)據(jù)集精度保持更好但流程麻煩。我在制造業(yè)項目里一般用靜態(tài)量化因為工業(yè)質(zhì)檢對精度敏感漏檢一個缺陷可能整批產(chǎn)品報廢。校準(zhǔn)數(shù)據(jù)集怎么選我的做法是從產(chǎn)線實際拍到的圖里按缺陷類型分層抽樣每類至少200張正常樣本也要有。校準(zhǔn)過程就是讓模型在這些圖上跑一遍統(tǒng)計激活值的分布確定量化參數(shù)。這個過程在ONNX Runtime里用quantize_static接口就能做。量化后精度掉了怎么辦先別急著放棄。我遇到過量化后mAP掉了3個點的情況排查下來是校準(zhǔn)集里缺陷樣本太少模型對缺陷特征的激活分布沒統(tǒng)計準(zhǔn)。補了500張缺陷圖重新校準(zhǔn)精度就回來了。如果補數(shù)據(jù)還不行那就對量化敏感的層做混合精度保持這些層用FP32其他層用INT8ONNX Runtime支持這種混合模式。注意量化后的模型一定要在驗證集上重新跑一遍完整指標(biāo)不能只看單張圖的輸出。我踩過坑單張圖看著沒問題批量跑起來發(fā)現(xiàn)某一類缺陷的召回率掉了十幾個點。2.4 邊緣硬件的選型清單硬件這塊我列個實際用過的清單都是跑過MAI Gateway的配置硬件類型典型配置適用場景實測推理延遲YOLOv5s工控機Intel i5-8500, 16G內(nèi)存, 無獨顯節(jié)拍秒級、單路相機80-150ms邊緣盒子ARM Cortex-A76, 8G內(nèi)存, NPU低功耗、多路傳感器200-400ms工控機GPUi7-10700, 32G內(nèi)存, GTX 1660節(jié)拍百毫秒級、多路相機15-30ms國產(chǎn)化平臺飛騰/鯤鵬, 16G內(nèi)存信創(chuàng)要求場景150-300ms選硬件的時候除了算力還要看接口。車間里相機多是GigE或者USB3.0PLC多是RS485或者以太網(wǎng)網(wǎng)關(guān)至少要有兩個網(wǎng)口一個接相機網(wǎng)段一個接工控網(wǎng)段物理隔離更穩(wěn)。我吃過虧相機和PLC走同一個交換機相機一傳大圖就把PLC的通信擠掉了后來加了網(wǎng)口隔離才解決。3. 實操落地從零把網(wǎng)關(guān)跑起來3.1 環(huán)境準(zhǔn)備與依賴安裝我以一臺Ubuntu 20.04的工控機為例把安裝過程走一遍。為什么選Ubuntu而不是Windows因為車間里工控機裝Windows的居多但Windows的Docker支持和后臺服務(wù)管理不如Linux穩(wěn)而且Linux可以裁掉圖形界面省資源。如果客戶堅持用Windows那就用WSL2或者直接跑Python服務(wù)但穩(wěn)定性會打折扣。先裝基礎(chǔ)依賴sudo apt update sudo apt install -y python3.8 python3-pip python3-venv \ libgl1 libglib2.0-0 libsm6 libxext6 libxrender-dev \ mosquitto mosquitto-clients sqlite3這里解釋幾個包的作用。libgl1這些是OpenCV的依賴不裝的話import cv2會報錯mosquitto是MQTT broker網(wǎng)關(guān)內(nèi)部通信用sqlite3是本地數(shù)據(jù)庫用來存檢測記錄斷網(wǎng)時數(shù)據(jù)先落本地聯(lián)網(wǎng)后再補傳。然后建虛擬環(huán)境裝Python包python3 -m venv /opt/mai-gateway/venv source /opt/mai-gateway/venv/bin/activate pip install onnxruntime1.15.1 opencv-python-headless4.8.0.74 \ numpy1.24.3 paho-mqtt1.6.1 requests2.31.0 \ fastapi0.103.1 uvicorn0.23.2版本我鎖死了因為ONNX Runtime 1.16之后有些API變了跟老代碼不兼容。opencv-python-headless是不帶GUI的版本車間里不需要彈窗顯示省內(nèi)存。提示如果工控機是ARM架構(gòu)onnxruntime要換成onnxruntime的ARM版本pip源里可能沒有需要從源碼編譯或者用廠商提供的wheel包。這一步最容易卡住建議提前準(zhǔn)備好。3.2 模型轉(zhuǎn)換與量化實操假設(shè)你有一個訓(xùn)練好的PyTorch模型defect_model.pt先導(dǎo)出ONNXimport torch import torch.onnx model torch.load(defect_model.pt, map_locationcpu) model.eval() dummy_input torch.randn(1, 3, 640, 640) torch.onnx.export( model, dummy_input, defect_model.onnx, input_names[input], output_names[output], dynamic_axes{input: {0: batch}, output: {0: batch}}, opset_version11 )opset_version選11是個經(jīng)驗值兼容性好ONNX Runtime各版本都支持。dynamic_axes把batch維度設(shè)成動態(tài)方便后面調(diào)批量大小。接下來量化。先準(zhǔn)備校準(zhǔn)數(shù)據(jù)放在calib_images/目錄下然后跑from onnxruntime.quantization import quantize_static, CalibrationDataReader import cv2 import numpy as np import os class CalibReader(CalibrationDataReader): def __init__(self, image_dir): self.image_list [os.path.join(image_dir, f) for f in os.listdir(image_dir) if f.endswith(.jpg)] self.index 0 def get_next(self): if self.index len(self.image_list): return None img cv2.imread(self.image_list[self.index]) img cv2.resize(img, (640, 640)) img img[:, :, ::-1].transpose(2, 0, 1).astype(np.float32) / 255.0 img np.expand_dims(img, axis0) self.index 1 return {input: img} reader CalibReader(calib_images/) quantize_static( defect_model.onnx, defect_model_int8.onnx, reader, quant_formatQuantFormat.QDQ, per_channelTrue )quant_format選QDQ這是量化感知訓(xùn)練友好的格式精度保持更好。per_channelTrue是按通道量化比按張量量化精度高但模型會大一點點可以接受。量化完一定要驗證。我寫了個小腳本拿驗證集跑一遍對比量化前后的輸出差異import onnxruntime as ort import numpy as np def run_model(model_path, input_data): sess ort.InferenceSession(model_path) return sess.run(None, {input: input_data})[0] # 拿同一張圖分別跑FP32和INT8模型 test_img np.random.randn(1, 3, 640, 640).astype(np.float32) out_fp32 run_model(defect_model.onnx, test_img) out_int8 run_model(defect_model_int8.onnx, test_img) diff np.abs(out_fp32 - out_int8).max() print(f最大輸出差異: {diff})差異在0.05以內(nèi)一般可以接受超過0.1就要檢查校準(zhǔn)集或者考慮混合精度。3.3 網(wǎng)關(guān)服務(wù)配置與啟動MAI Gateway 的主服務(wù)我用FastAPI寫配置文件用YAML。先看配置結(jié)構(gòu)gateway: name: injection-line-01 listen_port: 8080 mqtt_broker: localhost mqtt_port: 1883 db_path: /opt/mai-gateway/data/records.db models: - name: defect_detector path: /opt/mai-gateway/models/defect_model_int8.onnx input_size: [640, 640] input_name: input output_name: output confidence_threshold: 0.5 nms_threshold: 0.45 rules: - name: ng_trigger model: defect_detector condition: max_confidence 0.7 action: mqtt_publish topic: line/ng payload: {status: NG, confidence: {confidence}} outputs: - type: sqlite enabled: true - type: mqtt enabled: true - type: http enabled: false url: http://mes-server/api/quality這個配置里rules段是核心。condition支持簡單的表達式現(xiàn)場工程師改閾值不用動代碼。action支持mqtt_publish、http_post、db_insert三種夠用了。啟動服務(wù)cd /opt/mai-gateway source venv/bin/activate uvicorn main:app --host 0.0.0.0 --port 8080 --workers 1--workers 1是有意為之。車間工控機核數(shù)少開多worker反而搶CPU推理延遲更不穩(wěn)定。單worker配合異步IO實測吞吐更穩(wěn)。3.4 與PLC和MES的對接細節(jié)對接PLC是最容易出問題的環(huán)節(jié)。我一般用Modbus TCP因為大部分PLC都支持配置簡單。Python里用pymodbusfrom pymodbus.client import ModbusTcpClient client ModbusTcpClient(192.168.1.10, port502) client.connect() # 寫一個線圈觸發(fā)剔除氣缸 client.write_coil(0, True)這里有個坑Modbus的地址偏移。不同PLC廠商對地址的定義不一樣有的從0開始有的從1開始有的把線圈和寄存器分開編址。我踩過坑按文檔寫的地址寫不進去后來拿Modbus調(diào)試工具一個個試才找到正確偏移。建議對接前先用調(diào)試工具確認地址映射。對接MES一般走HTTP把檢測結(jié)果POST過去import requests def report_to_mes(record): try: resp requests.post( http://mes-server/api/quality, jsonrecord, timeout2 ) return resp.status_code 200 except requests.exceptions.RequestException: # 網(wǎng)絡(luò)不通先存本地后面補傳 save_to_local_queue(record) return False超時設(shè)2秒因為MES服務(wù)器可能響應(yīng)慢不能讓它拖住網(wǎng)關(guān)主流程。失敗就落本地隊列后臺起個線程定時補傳。4. 現(xiàn)場排錯那些文檔里不會寫的問題4.1 推理結(jié)果不穩(wěn)定的排查思路現(xiàn)場最常遇到的問題是“同一張圖有時候檢測出來有時候檢測不出來”。這種問題最頭疼因為不是必現(xiàn)。我的排查順序是這樣的先看輸入預(yù)處理。相機拍的圖每次的曝光、白平衡可能略有差異如果預(yù)處理里做了歸一化但參數(shù)寫死了遇到光照變化就會飄。我一般會在預(yù)處理里加自適應(yīng)直方圖均衡或者讓相機鎖定曝光和白平衡。再看模型輸入尺寸。如果相機分辨率是1280x960你resize到640x640長寬比變了缺陷可能被壓扁。正確做法是保持長寬比resize然后padding。這個細節(jié)很多教程不講但實際影響很大。最后看推理引擎的線程數(shù)。ONNX Runtime默認會用所有CPU核心如果網(wǎng)關(guān)同時還在跑其他服務(wù)線程爭搶會導(dǎo)致推理結(jié)果不穩(wěn)定。我一般顯式設(shè)置options ort.SessionOptions() options.intra_op_num_threads 2 options.inter_op_num_threads 1 sess ort.InferenceSession(model.onnx, options)把線程數(shù)限制住雖然單次推理慢一點但穩(wěn)定性好很多。4.2 斷網(wǎng)后的數(shù)據(jù)補傳機制前面提到斷網(wǎng)時數(shù)據(jù)落本地聯(lián)網(wǎng)后補傳。這個機制我踩過坑最早的設(shè)計是簡單隊列先進先出結(jié)果發(fā)現(xiàn)補傳時如果數(shù)據(jù)量大會把MES打掛。后來改成批量補傳限速def sync_pending_records(batch_size50, interval1.0): while True: records query_pending_records(limitbatch_size) if not records: time.sleep(5) continue for record in records: if report_to_mes(record): mark_as_synced(record[id]) else: break time.sleep(interval)每批50條批間停1秒給MES喘息時間。mark_as_synced用數(shù)據(jù)庫事務(wù)保證不會重復(fù)補傳。注意本地數(shù)據(jù)庫要定期清理不然工控機硬盤很快滿。我一般保留最近30天的記錄更早的歸檔到外部存儲或者直接刪除。4.3 常見問題速查表現(xiàn)象可能原因排查方法解決服務(wù)啟動報錯找不到模型路徑寫錯或權(quán)限不足ls -l看文件權(quán)限改路徑或chmod 644推理延遲突然變大CPU被其他進程占用top看CPU使用率限制其他服務(wù)或加優(yōu)先級MQTT消息收不到broker沒啟動或端口錯mosquitto_sub手動訂閱檢查broker配置檢測結(jié)果全為NG閾值設(shè)太低或模型加載錯打印原始輸出調(diào)閾值或換模型數(shù)據(jù)庫寫入失敗磁盤滿或文件鎖df -h看磁盤清理或換路徑相機取流卡頓網(wǎng)口帶寬不夠iftop看流量加網(wǎng)口或降分辨率4.4 幾個獨家避坑技巧第一個工控機BIOS里關(guān)掉節(jié)能模式。很多工控機默認開了C-StateCPU會降頻推理延遲忽高忽低。進BIOS把C-State和SpeedStep關(guān)了延遲立刻穩(wěn)。第二個給網(wǎng)關(guān)進程設(shè)CPU親和性。用taskset把網(wǎng)關(guān)綁到固定核心避免跟其他進程搶taskset -c 2,3 uvicorn main:app --host 0.0.0.0 --port 8080第三個日志分級。車間里日志寫太詳細會拖慢IO我一般生產(chǎn)環(huán)境只記WARNING以上調(diào)試時再開DEBUG。日志文件用logrotate每天切保留7天。第四個模型文件加校驗。部署前算個MD5啟動時校驗防止文件傳輸出錯導(dǎo)致模型加載了半截。這個坑我踩過模型文件不完整推理輸出全是亂碼排查了半天。5. 效果驗證與擴展方向5.1 實際產(chǎn)線的運行數(shù)據(jù)在一個注塑質(zhì)檢項目里這套網(wǎng)關(guān)跑了三個月我拉了些數(shù)據(jù)。產(chǎn)線節(jié)拍12秒網(wǎng)關(guān)單次推理平均95毫秒加上預(yù)處理和后處理端到端大概180毫秒完全跟得上。檢測準(zhǔn)確率方面量化后的INT8模型對比FP32mAP從0.91掉到0.89漏檢率從2.1%升到2.8%在可接受范圍內(nèi)。客戶最滿意的是斷網(wǎng)表現(xiàn)有一次廠區(qū)網(wǎng)絡(luò)割接斷了4小時網(wǎng)關(guān)照常檢測數(shù)據(jù)全存本地網(wǎng)絡(luò)恢復(fù)后自動補傳一條沒丟。穩(wěn)定性上三個月里網(wǎng)關(guān)重啟過兩次都是因為工控機硬盤故障跟軟件本身無關(guān)。CPU占用率平時在40%左右推理峰值到70%內(nèi)存穩(wěn)定在2G以內(nèi)。5.2 還能往哪些方向擴展這套網(wǎng)關(guān)的骨架是通用的換模型就能換場景。我后來在另一個項目里把視覺模型換成時序模型接振動傳感器做設(shè)備預(yù)測性維護配置改改就能跑。規(guī)則層也支持更復(fù)雜的邏輯比如連續(xù)N個NG觸發(fā)停線、按時間段切換閾值。再往深了做可以加模型熱更新?,F(xiàn)在換模型要重啟服務(wù)如果做成雙緩沖新模型加載好再切換就能做到不停機更新。這個我還在折騰主要是內(nèi)存管理麻煩工控機內(nèi)存有限兩個模型同時駐留容易OOM。另一個方向是多網(wǎng)關(guān)協(xié)同。一條產(chǎn)線多個工位每個工位一個網(wǎng)關(guān)網(wǎng)關(guān)之間通過MQTT同步狀態(tài)比如上游檢測到NG下游網(wǎng)關(guān)提前調(diào)整參數(shù)。這個在汽車零部件產(chǎn)線很有用但通信協(xié)議和狀態(tài)一致性要設(shè)計好不然容易亂。我個人在實際操作中的體會是制造業(yè)AI落地技術(shù)只占三成七成是對現(xiàn)場的理解。你得知道產(chǎn)線節(jié)拍怎么算、PLC地址怎么映射、工人怎么操作、網(wǎng)絡(luò)怎么布這些細節(jié)決定了方案能不能真正跑起來。MAI Gateway 這套東西不復(fù)雜但每一個配置項背后都是現(xiàn)場踩出來的經(jīng)驗。你要是準(zhǔn)備在廠里推類似方案建議先拿一條線做試點跑穩(wěn)了再鋪開別一上來就全線推廣。