習(xí)全鏈路實(shí)踐:從數(shù)據(jù)上傳到在線推理部署指南)
我花了大概兩周時(shí)間把華為云ModelArts的完整鏈路跑了一遍從上傳數(shù)據(jù)集、跑訓(xùn)練作業(yè)到把模型注冊(cè)上線最后部署成一個(gè)能接收?qǐng)D片請(qǐng)求的在線推理服務(wù)。這篇學(xué)習(xí)筆記把中間踩過(guò)的坑和關(guān)鍵路徑都記了下來(lái)目標(biāo)是讓后來(lái)的人少走彎路也當(dāng)給自己留一份復(fù)現(xiàn)手冊(cè)。在真正動(dòng)手之前我一直有個(gè)疑問(wèn)訓(xùn)練代碼不是本地也能跑嗎為什么還要上云把整個(gè)流程走完我的答案是——如果模型一次訓(xùn)練要幾個(gè)小時(shí)中途還伴隨著環(huán)境依賴(lài)、數(shù)據(jù)管理和部署上線云端平臺(tái)帶來(lái)的收益就會(huì)非常明顯。ModelArts剛好是那種把訓(xùn)練、管理、部署揉成一站式平臺(tái)的產(chǎn)品很適合個(gè)人學(xué)習(xí)和中小團(tuán)隊(duì)做實(shí)驗(yàn)驗(yàn)證。如果你也是剛接觸建議先別急著寫(xiě)代碼把下面的準(zhǔn)備步驟跟一遍后面會(huì)省掉很多“找不到文件”的煩惱。1. 先把思路理清楚為什么選擇云端訓(xùn)練ModelArts能省掉哪些事1.1 本地訓(xùn)練的真實(shí)困境我自己在本地跑過(guò)一段時(shí)間的模型訓(xùn)練最開(kāi)始用的是普通筆記本電腦。數(shù)據(jù)集幾個(gè)G就足夠讓磁盤(pán)報(bào)警訓(xùn)練一個(gè)不算大的分類(lèi)網(wǎng)絡(luò)CPU要跑幾個(gè)小時(shí)GPU根本談不上。后來(lái)好不容易找到一張舊的獨(dú)立顯卡又折騰驅(qū)動(dòng)、CUDA版本、深度學(xué)習(xí)框架版本好不容易把環(huán)境裝好發(fā)現(xiàn)顯卡顯存只有4G稍微大一點(diǎn)的batch size直接OOM。這種體驗(yàn)在個(gè)人開(kāi)發(fā)者里太常見(jiàn)了。做模型訓(xùn)練的人很大一部分時(shí)間不是花在模型設(shè)計(jì)上而是花在環(huán)境配置、硬件升級(jí)這些和算法無(wú)關(guān)的事情上。云端平臺(tái)解決的正是這部分問(wèn)題。ModelArts本身不取代你的算法設(shè)計(jì)能力它提供一個(gè)開(kāi)箱即用的計(jì)算環(huán)境你提交訓(xùn)練代碼平臺(tái)負(fù)責(zé)拉鏡像、分配資源、跑任務(wù)、收集日志。對(duì)個(gè)人來(lái)說(shuō)最大的變化是“訓(xùn)練”這件事從“折騰設(shè)備和環(huán)境”變成了“提交任務(wù)和看結(jié)果”。1.2 ModelArts在整條鏈路里的位置ModelArts并不是只有一個(gè)訓(xùn)練功能。它覆蓋了從數(shù)據(jù)準(zhǔn)備到模型上線的四個(gè)環(huán)節(jié)數(shù)據(jù)層面可以創(chuàng)建數(shù)據(jù)集、做標(biāo)注、直接管理OBS對(duì)象存儲(chǔ)里的訓(xùn)練數(shù)據(jù)開(kāi)發(fā)層面提供代碼開(kāi)發(fā)環(huán)境也支持使用預(yù)置算法直接訓(xùn)練訓(xùn)練層面可以提交訓(xùn)練作業(yè)支持PyTorch、TensorFlow、MindSpore等主流框架部署層面模型注冊(cè)之后可以發(fā)布成在線服務(wù)、批量任務(wù)或者邊緣推理。我這次重點(diǎn)跑的是其中一條路線自定義訓(xùn)練腳本 → 訓(xùn)練作業(yè) → 模型注冊(cè) → 在線部署。這四個(gè)環(huán)節(jié)也是絕大多數(shù)深度學(xué)習(xí)應(yīng)用落地的核心路徑。1.3 使用云平臺(tái)前需要接受的三件事第一數(shù)據(jù)不能假設(shè)在本地磁盤(pán)旁。訓(xùn)練代碼在云端執(zhí)行數(shù)據(jù)源一般放在對(duì)象存儲(chǔ)OBS里代碼需要先把它拉取到本地的臨時(shí)目錄或者邊讀邊流式加載。這和平時(shí)“直接指向本地路徑”的思路不一樣。第二任務(wù)不是交互式的。訓(xùn)練作業(yè)提交后基本是黑盒執(zhí)行你通過(guò)日志觀察進(jìn)度。如果腳本里寫(xiě)死了本地交互輸入訓(xùn)練一定會(huì)卡住或者失敗。第三計(jì)費(fèi)與資源池綁定。不用的時(shí)候要把服務(wù)刪除、作業(yè)停止否則會(huì)一直產(chǎn)生費(fèi)用。這不是說(shuō)平臺(tái)貴而是說(shuō)你要換一種使用習(xí)慣按需開(kāi)、用完關(guān)。接受這三點(diǎn)之后整個(gè)ModelArts的操作邏輯就順了。2. 環(huán)境準(zhǔn)備清單賬號(hào)、OBS桶、委托權(quán)限一個(gè)都不能少2.1 賬號(hào)開(kāi)通與區(qū)域選擇使用ModelArts的第一步是開(kāi)通服務(wù)。整個(gè)過(guò)程不復(fù)雜但有幾點(diǎn)值得注意賬戶要完成實(shí)名認(rèn)證否則部分資源完全拿不到初次使用會(huì)要求選擇區(qū)域數(shù)據(jù)存儲(chǔ)在哪個(gè)區(qū)域的OBS訓(xùn)練作業(yè)當(dāng)前也建議用同一區(qū)域跨區(qū)域訪問(wèn)會(huì)帶來(lái)額外延遲和流量費(fèi)用如果只是學(xué)習(xí)選擇距離自己較近且資源充足的區(qū)域即可后面可以在設(shè)置里調(diào)整。區(qū)域這個(gè)問(wèn)題我第一次沒(méi)當(dāng)回事選了離自己最近的區(qū)域結(jié)果訓(xùn)練作業(yè)依賴(lài)的某個(gè)公共鏡像在那個(gè)區(qū)域沒(méi)有同步只能換區(qū)重來(lái)。后來(lái)我學(xué)聰明了先查一下想用的算法框架或者鏡像在哪個(gè)區(qū)域可用再?zèng)Q定最終落點(diǎn)。2.2 創(chuàng)建OBS桶與目錄規(guī)劃OBS桶可以理解成一個(gè)云端的大硬盤(pán)對(duì)象存儲(chǔ)不需要關(guān)心文件系統(tǒng)的掛載和分區(qū)上傳之后就有一個(gè)全局唯一的訪問(wèn)路徑。我創(chuàng)建桶時(shí)的要點(diǎn)桶名全局唯一建議用項(xiàng)目名加后綴比如xxx-modelarts-demo存儲(chǔ)類(lèi)別選“標(biāo)準(zhǔn)存儲(chǔ)”就夠訓(xùn)練場(chǎng)景用不到低頻存儲(chǔ)桶策略保持“私有讀寫(xiě)”默認(rèn)設(shè)置訓(xùn)練作業(yè)通過(guò)委托方式訪問(wèn)不要圖省事設(shè)成公開(kāi)讀。目錄結(jié)構(gòu)建議提前規(guī)劃好。我用的結(jié)構(gòu)比較簡(jiǎn)單my-modelarts-demo/ ├── data/ │ ├── train/ │ └── valid/ ├── output/ └── code/data放原始訓(xùn)練數(shù)據(jù)output放訓(xùn)練輸出code放訓(xùn)練腳本壓縮包。這樣訓(xùn)練作業(yè)的輸入路徑、輸出路徑、代碼路徑一目了然。2.3 數(shù)據(jù)上傳的三種方法上傳數(shù)據(jù)很容易被忽略但真正做起來(lái)坑也不少。我試過(guò)三種方式第一種控制臺(tái)直接上傳。適合小文件幾十個(gè)文件以?xún)?nèi)沒(méi)問(wèn)題。超過(guò)幾百個(gè)文件的目錄控制臺(tái)上傳體驗(yàn)就非常差了。第二種obsutil命令行工具。這是我最常用的方式支持批量上傳、斷點(diǎn)續(xù)傳、并發(fā)控制。命令大致長(zhǎng)這樣./obsutil cp ./data/ s3://my-modelarts-demo/data/ -r -f-r表示遞歸-f表示強(qiáng)制覆蓋。大文件傳一半斷了重新執(zhí)行一遍obsutil會(huì)跳過(guò)已經(jīng)傳完的部分這對(duì)多文件數(shù)據(jù)集來(lái)說(shuō)非常實(shí)用。第三種通過(guò)SDK寫(xiě)腳本上傳。適合定期更新數(shù)據(jù)的場(chǎng)景比如每周自動(dòng)把新的訓(xùn)練數(shù)據(jù)同步上去。個(gè)人學(xué)習(xí)階段用不太到但值得知道有這條路。2.4 委托權(quán)限最容易忽略的隱藏卡點(diǎn)我把OBS桶和數(shù)據(jù)都準(zhǔn)備好之后第一次提交訓(xùn)練作業(yè)就報(bào)錯(cuò)了錯(cuò)誤信息大概是“訪問(wèn)OBS被拒絕”。查了半天問(wèn)題出在賬號(hào)沒(méi)有給ModelArts授權(quán)訪問(wèn)OBS。這是很典型的新手問(wèn)題。ModelArts執(zhí)行訓(xùn)練作業(yè)時(shí)需要臨時(shí)以某個(gè)身份訪問(wèn)你的OBS桶這個(gè)身份就是委托。你需要在管理控制臺(tái)的權(quán)限配置里創(chuàng)建一個(gè)委托并授權(quán)給ModelArts。創(chuàng)建委托時(shí)選“自動(dòng)獲取”即可平臺(tái)會(huì)自動(dòng)關(guān)聯(lián)OBS相關(guān)權(quán)限。這個(gè)配置只需要做一次后面所有訓(xùn)練作業(yè)都會(huì)使用這個(gè)身份去讀寫(xiě)數(shù)據(jù)。提示如果換了新賬號(hào)、新區(qū)域或者換了項(xiàng)目務(wù)必重新檢查委托是否已配置。很多報(bào)錯(cuò)的第一排查項(xiàng)不是代碼而是權(quán)限。3. 訓(xùn)練任務(wù)實(shí)操?gòu)牧銓?xiě)一個(gè)可跑通的PyTorch腳本3.1 從自動(dòng)學(xué)習(xí)開(kāi)始還是直接寫(xiě)自定義腳本ModelArts提供了好幾條訓(xùn)練路線不同熟練度的人選擇不一樣。自動(dòng)學(xué)習(xí)是最低門(mén)檻的入口不需要寫(xiě)代碼只需要上傳若干張圖片平臺(tái)會(huì)自動(dòng)完成訓(xùn)練和部署。它的本質(zhì)是遷移學(xué)習(xí)加自動(dòng)調(diào)參對(duì)小規(guī)模分類(lèi)任務(wù)很有效。我最初用一組不足兩百?gòu)埖膱D片試過(guò)準(zhǔn)確率已經(jīng)能到9成以上非常適合驗(yàn)證業(yè)務(wù)想法。但自動(dòng)學(xué)習(xí)適合快速驗(yàn)證不適合深度定制模型結(jié)構(gòu)、損失函數(shù)和評(píng)估指標(biāo)。如果后續(xù)要調(diào)網(wǎng)絡(luò)結(jié)構(gòu)、加數(shù)據(jù)增強(qiáng)、設(shè)計(jì)復(fù)雜的訓(xùn)練邏輯還得走自定義訓(xùn)練腳本這條路。這次學(xué)習(xí)筆記里我重點(diǎn)記錄自定義訓(xùn)練腳本的過(guò)程因?yàn)檫@條路線的可控性最強(qiáng)理解之后也能遷移到其他云平臺(tái)。3.2 訓(xùn)練腳本與云端路徑的交互方式在ModelArts上寫(xiě)訓(xùn)練腳本最核心的一點(diǎn)是理解輸入輸出參數(shù)。訓(xùn)練作業(yè)提交時(shí)可以給腳本傳入一系列參數(shù)。最常用的兩個(gè)是--data_url訓(xùn)練數(shù)據(jù)的OBS路徑--train_url訓(xùn)練輸出的OBS路徑。訓(xùn)練作業(yè)啟動(dòng)后代碼所在的目錄是預(yù)置工作目錄數(shù)據(jù)并不在這個(gè)目錄里它還在OBS上。如果腳本直接找相對(duì)路徑文件必掛無(wú)疑。正確做法是先把OBS上的數(shù)據(jù)拷貝到本地緩存目錄訓(xùn)練完成后再把模型文件拷貝回OBS輸出路徑。因?yàn)閷?duì)象存儲(chǔ)上的IO性能遠(yuǎn)低于本地盤(pán)拷貝到本地再訓(xùn)練速度會(huì)明顯更快。這段代碼是我使用的下載和訓(xùn)練骨架的一部分import argparse import os import torch import torchvision import torch.nn as nn import moxing as mox def parse_args(): parser argparse.ArgumentParser() parser.add_argument(--data_url, typestr, requiredTrue) parser.add_argument(--train_url, typestr, requiredTrue) parser.add_argument(--epochs, typeint, default5) parser.add_argument(--batch_size, typeint, default32) return parser.parse_args() def main(): args parse_args() local_data_path /cache/data os.makedirs(local_data_path, exist_okTrue) # OBS數(shù)據(jù)拷貝到本地緩存 mox.file.copy_parallel(args.data_url, local_data_path) dataset torchvision.datasets.ImageFolder( rootlocal_data_path, transformtorchvision.transforms.Compose([ torchvision.transforms.Resize((224, 224)), torchvision.transforms.ToTensor() ]) ) loader torch.utils.data.DataLoader( dataset, batch_sizeargs.batch_size, shuffleTrue ) model torchvision.models.resnet18(pretrainedTrue) model.fc nn.Linear(model.fc.in_features, 2) criterion nn.CrossEntropyLoss() optimizer torch.optim.SGD(model.parameters(), lr0.001, momentum0.9) # 訓(xùn)練循環(huán)略去 local_output_path /cache/output os.makedirs(local_output_path, exist_okTrue) torch.save(model.state_dict(), os.path.join(local_output_path, model.pth)) # 模型拷貝回OBS mox.file.copy_parallel(local_output_path, args.train_url) if __name__ __main__: main()這段代碼里moxing是平臺(tái)內(nèi)置的OBS訪問(wèn)庫(kù)在ModelArts的訓(xùn)練環(huán)境里可以直接使用。它的copy_parallel方法可以把整個(gè)目錄遞歸拷貝非常方便。3.3 本地使用OBS路徑時(shí)的三個(gè)常見(jiàn)坑第一路徑協(xié)議別寫(xiě)錯(cuò)。有些文檔用s3://有些用obs://模型服務(wù)配置界面里常顯示s3://桶名/路徑。本質(zhì)上它們指向同一個(gè)對(duì)象存儲(chǔ)但腳本內(nèi)路徑必須和運(yùn)行時(shí)環(huán)境支持的方式匹配。我的經(jīng)驗(yàn)是在訓(xùn)練作業(yè)中盡量用s3://桶名/路徑這種格式因?yàn)樵贛odelArts內(nèi)部它兼容性更好。第二路徑末尾的斜杠也會(huì)影響行為。copy_parallel把s3://bucket/data當(dāng)作目錄拷貝和s3://bucket/data/在某些版本里可能表現(xiàn)不同。要么都帶要么都不帶保持統(tǒng)一。第三目錄不要寫(xiě)進(jìn)相對(duì)路徑。很多代碼寫(xiě)習(xí)慣了默認(rèn)把輸出模型放在當(dāng)前目錄。訓(xùn)練作業(yè)結(jié)束前平臺(tái)會(huì)保留當(dāng)前環(huán)境的臨時(shí)文件但不會(huì)幫你把任意路徑的文件同步到OBS。只要模型沒(méi)有上傳到train_url指定路徑后面注冊(cè)模型時(shí)就找不到任何內(nèi)容。3.4 創(chuàng)建訓(xùn)練作業(yè)時(shí)的配置項(xiàng)解讀腳本準(zhǔn)備好之后在ModelArts控制臺(tái)創(chuàng)建訓(xùn)練作業(yè)。關(guān)鍵配置項(xiàng)有這些算法來(lái)源選擇“自定義”方式上傳剛才準(zhǔn)備好的腳本和對(duì)應(yīng)的框架環(huán)境。也可以選擇預(yù)置算法鏡像省去配置依賴(lài)的麻煩代碼目錄填寫(xiě)代碼在OBS上的目錄啟動(dòng)文件填寫(xiě)要執(zhí)行的Python腳本文件名數(shù)據(jù)來(lái)源選擇“OBS”填入訓(xùn)練數(shù)據(jù)路徑訓(xùn)練輸出選擇“OBS”填入輸出路徑資源池測(cè)試階段可以用CPU正式訓(xùn)練用GPU。經(jīng)驗(yàn)是先用一小批數(shù)據(jù)在CPU上把代碼跑通再換GPU跑全量避免一個(gè)明顯的代碼錯(cuò)誤燒掉大把時(shí)間超參數(shù)可以直接在控制臺(tái)填入--epochs、--batch_size也可以寫(xiě)進(jìn)腳本默認(rèn)值。創(chuàng)建時(shí)還有一個(gè)“重啟策略”建議選“從不”。訓(xùn)練失敗時(shí)不會(huì)自動(dòng)重跑方便你自己看到原始報(bào)錯(cuò)。調(diào)通之后再改成“總是”可以節(jié)省人工盯任務(wù)的時(shí)間。訓(xùn)練作業(yè)一旦創(chuàng)建就會(huì)進(jìn)入排隊(duì)、啟動(dòng)、運(yùn)行的狀態(tài)此時(shí)可以在詳情頁(yè)看到實(shí)時(shí)日志。如果日志遲遲沒(méi)有輸出先看看是不是排隊(duì)等待資源。如果顯示“啟動(dòng)失敗”多半是鏡像拉取或者腳本路徑配置錯(cuò)誤日志信息會(huì)把根本原因點(diǎn)出來(lái)。4. 訓(xùn)練過(guò)程監(jiān)控與模型注冊(cè)日志、曲線和版本管理4.1 實(shí)時(shí)日志的正確看法訓(xùn)練作業(yè)啟動(dòng)后最關(guān)心的就是日志。ModelArts控制臺(tái)提供日志預(yù)覽和下載兩種方式。預(yù)覽適合看少量輸出下載則適合拿日志做二次分析。日志的輸出位置來(lái)自?xún)蓚€(gè)方向一是訓(xùn)練腳本本身的標(biāo)準(zhǔn)輸出也就是print語(yǔ)句的內(nèi)容二是框架、系統(tǒng)在運(yùn)行時(shí)產(chǎn)生的輸出。腳本里print的信息都會(huì)在日志里出現(xiàn)所以建議在關(guān)鍵節(jié)點(diǎn)多打印每個(gè)epoch結(jié)束后的loss和accuracy每完成一定步數(shù)后的當(dāng)前進(jìn)度模型保存時(shí)打印保存路徑。我踩過(guò)的坑是腳本跑了一小時(shí)日志一直沒(méi)有更新我以為是卡住了。后來(lái)發(fā)現(xiàn)是打印內(nèi)容太多日志頁(yè)面刷新滯后刷新了幾次之后內(nèi)容一次性全部出現(xiàn)了。如果訓(xùn)練卡住更可靠的判斷指標(biāo)是CPU/GPU利用率資源池監(jiān)控面板里利用率長(zhǎng)時(shí)間為0才是真正出問(wèn)題。4.2 訓(xùn)練指標(biāo)曲線的生成邏輯ModelArts在訓(xùn)練作業(yè)詳情頁(yè)會(huì)展示算法指標(biāo)曲線比如損失曲線、準(zhǔn)確率曲線。但這些曲線不是平臺(tái)自發(fā)生成的它需要訓(xùn)練腳本在輸出日志時(shí)使用特定的格式平臺(tái)才能解析。不同版本的控制臺(tái)識(shí)別方式不完全一樣。如果你的腳本只是普通print(loss0.123),頁(yè)面通常不會(huì)生成曲線。這時(shí)候不要以為平臺(tái)壞了更直接的辦法是自己在腳本里把指標(biāo)寫(xiě)入一個(gè)日志文件訓(xùn)練結(jié)束后從輸出目錄拿回來(lái)再畫(huà)圖。我自己更常做的做法是每個(gè)epoch結(jié)束后把epoch、loss、acc打印成一行訓(xùn)練結(jié)束之后用腳本把打印出來(lái)的數(shù)據(jù)抄下來(lái)本地快速畫(huà)圖。雖然麻煩一點(diǎn)但完全不依賴(lài)平臺(tái)解析。4.3 訓(xùn)練輸出與模型文件命名訓(xùn)練結(jié)束后輸出目錄里就是所有拷貝回OBS的文件。這里有一個(gè)特別需要注意的點(diǎn)模型文件的命名最好固定、且容易識(shí)別。ModelArts在模型注冊(cè)時(shí)經(jīng)常需要你指定模型文件名。如果腳本保存的是best_model.pth注冊(cè)時(shí)把文件名填對(duì)就行。但如果一個(gè)訓(xùn)練作業(yè)里同時(shí)保存了多個(gè)文件比如checkpoint_1.pth、checkpoint_2.pth注冊(cè)時(shí)就要特別小心別選錯(cuò)版本。我習(xí)慣把最終用于部署的模型固定命名為model.pth中間檢查點(diǎn)單獨(dú)放在一個(gè)子目錄。這樣可以減少后續(xù)操作中的歧義。4.4 模型注冊(cè)與版本管理模型注冊(cè)簡(jiǎn)單理解就是把訓(xùn)練產(chǎn)物以及配套的推理配置打包成一個(gè)“可用的模型”。它是訓(xùn)練環(huán)節(jié)和部署環(huán)節(jié)之間的橋梁。在ModelArts控制臺(tái)進(jìn)入“模型管理”創(chuàng)建模型時(shí)可以選擇“從訓(xùn)練作業(yè)導(dǎo)入”選中剛才的訓(xùn)練作業(yè)平臺(tái)會(huì)自動(dòng)讀取輸出目錄里的模型文件也可以選擇“從模板導(dǎo)入”按固定目錄結(jié)構(gòu)把模型文件和推理代碼傳到一個(gè)OBS路徑。比較關(guān)鍵的是“模型版本”這個(gè)概念。同一個(gè)模型名稱(chēng)下可以管理多個(gè)版本每次重新訓(xùn)練生成新版本。比如model.pth基于V1數(shù)據(jù)訓(xùn)練后來(lái)又基于擴(kuò)充后的V2數(shù)據(jù)訓(xùn)練兩個(gè)版本都會(huì)保留。部署時(shí)可以顯式指定版本號(hào)。這樣做的價(jià)值在于上線穩(wěn)定版本后后續(xù)新版本可以在測(cè)試環(huán)境中驗(yàn)證驗(yàn)證通過(guò)再切換流量不必每次改動(dòng)都重構(gòu)整個(gè)部署。5. 部署在線推理服務(wù)配置、推理代碼與接口測(cè)試5.1 三種部署形態(tài)怎么選ModelArts部署過(guò)程支持三種方式在線服務(wù)是最常用的形態(tài)。模型部署后會(huì)提供一個(gè)固定API地址客戶端隨時(shí)發(fā)起請(qǐng)求適合實(shí)時(shí)推理場(chǎng)景比如在線識(shí)圖、實(shí)時(shí)風(fēng)控。批量服務(wù)則是一次性處理一批數(shù)據(jù)。數(shù)據(jù)準(zhǔn)備好之后提交任務(wù)平臺(tái)跑完再把結(jié)果存到OBS適合離線批量計(jì)算比如周末把某個(gè)月存量圖片全部跑一遍。邊緣服務(wù)則是把模型下發(fā)到靠近數(shù)據(jù)源的設(shè)備端適合需要低延遲、弱網(wǎng)、離線場(chǎng)景。我這次用的是在線服務(wù)也是學(xué)習(xí)階段最直觀的方式。5.2 在線服務(wù)的關(guān)鍵配置創(chuàng)建在線服務(wù)時(shí)核心是這幾項(xiàng)選擇模型及版本通常直接選剛才注冊(cè)好的模型即可資源規(guī)格如果是用CPU推理選擇合適的內(nèi)存規(guī)格就行如果模型比較大、推理時(shí)延要求高可考慮GPU規(guī)格。個(gè)人試驗(yàn)階段先用CPU規(guī)格把流程跑順再根據(jù)實(shí)際耗時(shí)決定要不要升級(jí)。實(shí)例數(shù)先從1開(kāi)始。ModelArts支持自動(dòng)擴(kuò)縮容但自動(dòng)擴(kuò)縮容的對(duì)數(shù)策略對(duì)個(gè)人學(xué)習(xí)階段來(lái)說(shuō)不是必需品。手動(dòng)控制不但省錢(qián)還能讓你更清楚資源利用情況。環(huán)境變量有些模型需要環(huán)境變量控制推理行為比如編碼格式、超時(shí)時(shí)間。按需配置不需要就不填。5.3 推理腳本里要做的事部署在線服務(wù)時(shí)ModelArts會(huì)加載模型并執(zhí)行推理腳本。推理腳本不能只是“加載一下模型”它需要完成三個(gè)步驟預(yù)處理把網(wǎng)絡(luò)請(qǐng)求里的原始數(shù)據(jù)轉(zhuǎn)換成模型可接受的張量格式。最常見(jiàn)的圖片請(qǐng)求客戶端傳過(guò)來(lái)的可能是base64字符串你要先解碼成圖像再縮放、歸一化。推理調(diào)用模型forward或者predict方法得到原始輸出。后處理把Tensor輸出轉(zhuǎn)換成Python原生類(lèi)型。這里是我踩得最慘的一個(gè)坑我直接返回了一個(gè)Tensor對(duì)象部署后調(diào)用服務(wù)報(bào)錯(cuò)報(bào)錯(cuò)信息無(wú)法序列化。處理方式很簡(jiǎn)單用.item()或者int()把Tensor轉(zhuǎn)成數(shù)字再包裝成dict。推理腳本的長(zhǎng)相大致是這樣import base64 import torch from PIL import Image from torchvision import transforms from model_service.model_service import ModelService class MyService(ModelService): def __init__(self, model_name, model_path): super().__init__(model_name, model_path) self.model torch.load(model_path, map_locationcpu) self.model.eval() self.transform transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), ]) def preprocess(self, data): image_base64 data.get(image_base64) if image_base64 is None: raise ValueError(缺少image_base64字段) image_bytes base64.b64decode(image_base64) image Image.open(image_bytes).convert(RGB) image self.transform(image).unsqueeze(0) return image def inference(self, data): with torch.no_grad(): outputs self.model(data) return torch.softmax(outputs, dim1) def postprocess(self, data): probabilities, indices torch.max(data, dim1) label_idx int(indices[0].item()) confidence float(probabilities[0].item()) return { label_idx: label_idx, confidence: confidence }這里特別要說(shuō)明一點(diǎn)具體推理類(lèi)的繼承方式不同版本會(huì)略有差別但整體思路是通用的。如果遇到平臺(tái)的模板和示例代碼優(yōu)先讀它自帶的模板確認(rèn)類(lèi)名和方法名。5.4 接口聯(lián)調(diào)與業(yè)務(wù)封裝服務(wù)部署完成后控制臺(tái)會(huì)分配一個(gè)API地址同時(shí)提供“調(diào)試”功能。最方便的做法是先在調(diào)試頁(yè)面上傳一張圖片看返回是否正常。如果調(diào)試正常再去外部調(diào)用接口。調(diào)用方式我建議用curl或者Postman先確認(rèn)接口本身沒(méi)問(wèn)題再寫(xiě)業(yè)務(wù)代碼對(duì)接。一個(gè)簡(jiǎn)單的curl示例如下curl -X POST https://your-api-endpoint/predict \ -H Content-Type: application/json \ -d {image_base64: 這里放圖片的base64編碼}有些服務(wù)會(huì)配置安全認(rèn)證比如Bearer Token、AK/SK簽名。我在學(xué)習(xí)階段先把安全配置關(guān)閉用最簡(jiǎn)單的接口調(diào)試通。正式部署時(shí)再開(kāi)啟鑒權(quán)。返回結(jié)果應(yīng)該是一段JSON類(lèi)似{ label_idx: 1, confidence: 0.91 }拿到這個(gè)結(jié)果后再做業(yè)務(wù)集成比如調(diào)用方根據(jù)label_idx展示不同的信息或者進(jìn)一步分析置信度是否達(dá)到業(yè)務(wù)閾值。6. 排障現(xiàn)場(chǎng)高頻報(bào)錯(cuò)與實(shí)際問(wèn)題處理技巧6.1 我遇到的高頻報(bào)錯(cuò)匯總這一部分直接整理成表格方便對(duì)應(yīng)查找。報(bào)錯(cuò)關(guān)鍵詞實(shí)際原因排查方法NoSuchKeyOBS路徑下的文件不存在先確認(rèn)上傳目錄結(jié)構(gòu)尤其注意大小寫(xiě)和路徑前綴AccessDenied委托權(quán)限未配置或授權(quán)不足檢查委托是否已關(guān)聯(lián)ModelArts桶策略是否放行CUDA out of memoryGPU顯存不足降低batch size或換更大顯存的資源規(guī)格FileNotFoundError代碼里引用了本地不存在路徑確認(rèn)已經(jīng)執(zhí)行數(shù)據(jù)拷貝到/cache不要假設(shè)數(shù)據(jù)就在當(dāng)前目錄model.pth not found訓(xùn)練輸出文件沒(méi)拷貝到OBS確認(rèn)訓(xùn)練結(jié)束前調(diào)用了copy_parallel或類(lèi)似上傳邏輯JSON序列化失敗推理返回值包含Tensor、ndarray在postprocess階段全部轉(zhuǎn)成Python原生類(lèi)型服務(wù)一直顯示異常推理進(jìn)程崩潰或端口未啟動(dòng)查看服務(wù)日志確認(rèn)模型加載成功與否確認(rèn)推理腳本沒(méi)有語(yǔ)法錯(cuò)誤其中“NoSuchKey”是我發(fā)生的最高頻的錯(cuò)誤。原因幾乎都是數(shù)據(jù)上傳目錄和訓(xùn)練作業(yè)里填的路徑不完全一致比如多了個(gè)前導(dǎo)子目錄。最好辦法是用obsutil在本地執(zhí)行一次ls把桶里的實(shí)際路徑列出來(lái)和訓(xùn)練作業(yè)配置里填的路徑對(duì)一遍。6.2 資源與成本控制經(jīng)驗(yàn)談大多數(shù)人剛開(kāi)始用云平臺(tái)最容易忽視成本。我自己的幾條經(jīng)驗(yàn)是訓(xùn)練作業(yè)一定要先小數(shù)據(jù)集調(diào)試。用幾十張圖片把整個(gè)流程跑通確認(rèn)模型能正常訓(xùn)練和保存再換全量數(shù)據(jù)。一個(gè)完整的訓(xùn)練任務(wù)持續(xù)幾小時(shí)和幾分鐘費(fèi)用完全不同。在線服務(wù)不調(diào)試時(shí)記得刪除。在線服務(wù)是持續(xù)計(jì)費(fèi)的就算沒(méi)有請(qǐng)求也會(huì)占用資源。個(gè)人學(xué)習(xí)階段我在調(diào)試完成后直接刪除服務(wù)需要演示時(shí)再重新創(chuàng)建。這個(gè)操作很快不必一直保留。合理使用“自動(dòng)停止”功能。訓(xùn)練作業(yè)和Notebook都支持配置自動(dòng)停止時(shí)間。一旦訓(xùn)練意外卡住至少不會(huì)白白扣費(fèi)。不需要的時(shí)候把作業(yè)或服務(wù)實(shí)例停掉。6.3 把學(xué)習(xí)筆記轉(zhuǎn)化為可復(fù)用的模版整個(gè)流程跑通之后我做的最后一件事是把腳本、命令、目錄結(jié)構(gòu)整理成一個(gè)模板存到了自己的代碼倉(cāng)庫(kù)里。下一次遇到新任務(wù)只需要復(fù)制模板改一下數(shù)據(jù)集和模型定義就能快速跑通。這個(gè)習(xí)慣對(duì)我來(lái)說(shuō)價(jià)值很大。因?yàn)樵贫巳蝿?wù)流程非常標(biāo)準(zhǔn)化模板化之后換任務(wù)只是在填充幾個(gè)變量不需要從零思考數(shù)據(jù)怎么上傳、路徑怎么填、模型怎么注冊(cè)。如果你也是剛?cè)腴T(mén)我建議你也做一件類(lèi)似的事把這份筆記里的目錄結(jié)構(gòu)、訓(xùn)練腳本骨架、推理腳本骨架存下來(lái)作為自己的“ModelArts起手式”。6.4 一些最后的排障心得云端訓(xùn)練的報(bào)錯(cuò)和本地最大的不同在于你無(wú)法直接看到機(jī)器上的完整狀態(tài)。所以排查時(shí)不要靠猜一定要先打開(kāi)日志把第一處異??疵靼?。很多問(wèn)題都是前面的一個(gè)小錯(cuò)誤引發(fā)后面連鎖報(bào)錯(cuò)比如路徑前綴寫(xiě)錯(cuò)導(dǎo)致數(shù)據(jù)沒(méi)加載然后dataloader報(bào)空數(shù)據(jù)集最后一臉茫然。如果遇到實(shí)在查不出的問(wèn)題有一個(gè)很笨但有效的方法寫(xiě)一個(gè)最小測(cè)試腳本先不訓(xùn)練只打印“讀取數(shù)據(jù)成功”和“當(dāng)前環(huán)境溫度、目錄結(jié)構(gòu)”提交一遍訓(xùn)練作業(yè)。如果最小腳本能跑通說(shuō)明環(huán)境沒(méi)問(wèn)題再把完整腳本放上去對(duì)比差異。這個(gè)方法看起來(lái)浪費(fèi)時(shí)間實(shí)際能省很多時(shí)間。因?yàn)榄h(huán)境、路徑、權(quán)限的問(wèn)題最小腳本一分鐘就能暴露而不完整的模型訓(xùn)練腳本可能要跑十分鐘才報(bào)錯(cuò)。我個(gè)人在實(shí)際使用中還有一個(gè)體會(huì)云平臺(tái)的功能很多但自己真正需要的只是其中一條主鏈路。剛開(kāi)始不要被控制臺(tái)里五花八門(mén)的菜單帶偏方向?qū)W臄?shù)據(jù)到部署的一條路走通把這里面的原理搞清楚其他功能可以邊用邊學(xué)。做完一次完整的模型訓(xùn)練與部署后再回頭讀ModelArts的文檔你會(huì)發(fā)現(xiàn)自己能看懂的東西一下多了很多。那些之前覺(jué)得陌生的概念比如委托、資源池、模型注冊(cè)全都對(duì)應(yīng)到了自己做過(guò)的某一步操作。這個(gè)“從操作反推概念”的過(guò)程是學(xué)習(xí)云計(jì)算平臺(tái)最省力的方式。