1. 程式人生 > 其它 >All in Linux:一個演算法工程師的IDE斷奶之路

All in Linux:一個演算法工程師的IDE斷奶之路

前不久賣萌屋的lulu寫了一篇vim的分享《演算法工程師的效率神器——vim篇》,突然想起來自己也有一篇攢了幾年灰的稿子,在小夥伴的慫恿下小夕強行翻新了一把,於是有了本文。

開篇預警:本文毫無邏輯

已經習慣了IDE的小夥伴可能在煉丹時也慣性的使用pycharm了。這在windows、mac本地煉丹的時候往往沒什麼,但是一旦要在GPU server端,初學者就往往開始捉急了,更不必說讓他all in這個server端的小黑框了。

小夕早期也試過一些笨辦法和部分場景下有效的“巧辦法”,比如

  • 本地端用pycharm瘋狂debug,一切測試ready後,使用scp將相關程式碼傳到伺服器起任務
  • 通過syncthing這種同步工具來自動同步本地端和server端程式碼
  • 利用pycharm內建的遠端同步功能
  • server端啟動jupyter notebook,本地端瀏覽器訪問

直到遇到了公司跳板機:scp、pycharm、jupyter notebook等通通失聯,本地端與遠端GPU伺服器被跳板機生生拆散了。於是我問同組的小夥伴這該怎麼煉丹,ta說:

“咦?直接在伺服器上寫就好了呀,linux不香麼,vim不香麼?難道你不會?”

“我。。我會。。。一點╮( ̄▽ ̄"")╭”

回想自己的煉丹之路,其實完全斷奶IDE確實不是一件容易的事情。早期一直是用pycharm連線遠端GPU伺服器來湊合,一直覺得效率還行。直到後來投身工業界後,發現身邊的大佬都是在黑框框裡裸奔,煉丹效率完全秒殺我,這才下決心徹底斷奶。掙扎幾個月斷奶成功後,愈發覺得

黑框框真香╮(╯▽╰)╭

不過,畢竟自己不是專業玩linux的,只是煉丹之餘摸索了一些自己用著舒服的不成體系的方法,所以相關玩法可能會有些“歪門邪道”,如有更優做法,請大佬們在評論區不吝賜教噢~

本文目錄:

  • 準備工作與環境遷移問題
  • 與機器解耦
  • 別忘寫個自動配置指令碼
  • bash function用起來
  • 與bettertouchtool的組合技
  • 別再無腦vim了
  • 別再無腦python了

(其實還能寫更多,太困了,還是下次吧QAQ

準備工作與環境遷移問題

其實,如果只是湊合一下,那麼在黑框裡煉丹基本不需要什麼準備工作。換到新的伺服器之後,基本就是vimrc裡隨手寫幾條tab擴充套件、換行自動縮排、檔案編碼之類的,bash裡隨手建幾條alias就可以了,剩下就是一頓pip install。

不過,如果你是一個比較深度的煉丹師,往往python環境裡存了一些自己得心應手的小工具包,還有一些自己臨時hack了原始碼的庫;可能還要隨身攜帶hadoop客戶端以便在叢集上煉丹和管理實驗;還可能對cuda版本、cudnn版本也有一些要求,需要隨身攜帶一些常用版本在伺服器之間穿梭;此外,更不用說vim和bash的一堆個性化配置啦。

這就導致了一個問題,如果煉丹準備階段,絲毫不顧及煉丹環境與機器的解耦和環境的遷移性,那麼一旦機器掛掉,或者你要換機器(老闆給你買了新機器),這就會變得非常痛苦。所以在打磨自己的工具鏈之後,花點時間將工具與環境解耦是非常必要的。

與機器解耦

比較欣慰的是,煉丹依賴的大部分工具和庫本身就是跟機器解耦的,比如cuda、cudnn、hadoop client等,挪到新環境後,基本只需要把各bin的路徑配置到環境變數PATH裡,把各lib配置到LD_LIBRARY_PATH裡就可以用了,最多再export一下CUDA_HOMEHADOOP_HOME等。然而,python環境的遷移就相對麻煩一些了。

早期的時候,小夕在本地搞了一堆miniconda的環境,然而換機器的時候才發現遷移時這些環境非常容易損壞,或者遷移後發現有時出現奇奇怪怪的問題。當時一頓亂試,也沒能很完美的解決,不知道今天miniconda或anaconda是否有改善(本地端的體驗真的無敵贊)。

此外,也嘗試過用docker管理環境。雖然docker的遷移性很贊,自帶雲端同步,但是,當你面對一臺無法連線外網且沒有安裝docker的伺服器時,你就感覺到絕望了。。。一頓離線安裝摺騰到瘋,機器環境差點時,各種安裝依賴會搞到懷疑人生。此外,docker也有一定的上手成本,理解概念、熟悉常用命令和提高生產力的命令方面會比miniconda明顯成本高。

最終發現,python環境的遷移性方面還是pyenv+virtualenv的組合能應對的情況更多一些,使用virtualenv建好一個環境後,若要將這個環境遷移到其他機器上,分兩種情況:

如果你沒有修改某些庫的原始碼,所有的庫都是pip install的,那自然直接pip freeze一下,將當前環境裡的各個庫和版本號直接輸出重定向到命名為requirements.txt檔案中就可以啦。到新的機器的新環境裡,直接pip install -r requirements.txt就哦了。

然而,如果你已經深度定製了某些庫,這就意味著你需要把整個離線環境打包帶走了。這時首先要保證目標機器跟現有機器的大環境差不多(否則一些系統底層庫版本差異很大,可能導致上層的庫調用出錯),然後藉助virtualenv自帶的命令就非常容易了,直接一行

virtualenv -relocatable

就可以將環境裡絕對路徑改成相對路徑,從而完成跟當前機器的解耦。

不過親測對有的包解耦不徹底,尤其是ipython這種有bin檔案的。這就需要手動的將環境裡bin目錄下的這些bin檔案首行的直譯器路徑改成相對的(比如直接/bin/env python)。最後就是改改bin目錄下的activate系列檔案中的絕對路徑啦。至此就可以放心的帶著小環境穿梭在各大機器了,且無需考慮目標機器是不是離線機器。

別忘寫個自動配置指令碼

上一節將python環境,cuda、cudnn、nccl、hadoop等與機器解耦後,就可以再帶上vimrc,bashrc,inputrc等檔案一起跑路了。這時候就發現bash script真香了,把你的配置流程直接寫進腳本里,這樣到了新環境直接run一下這個指令碼,你的全套家當就搬過來啦。

懶人的alias

說完了環境準備和搬家的事情,然後就可以開始談談怎麼偷懶了。

雖然精力旺盛的時候,多敲幾個字母也沒什麼,燃鵝,當你深夜面對實驗結果甚為沮喪時,你就會發現敲個nvidia-smi都是那麼無力。所以,乾脆就提前把懶提前偷一下。

貼一個自己的常用懶人alias,可以考慮寫入到你的~/.bashrc

function enshort() {
    alias l='ls -a'
    alias d='du -h --max-depth 1' 
    alias n='nvidia-smi'
    alias vb='vi ~/.bashrc'
    alias sb='. ~/.bashrc'
    alias sc='screen'
    alias nvfind='fuser -v /dev/nvidia*'
    alias nving='watch -n 0.1 nvidia-smi'
}

不過需要強調一下,如果多人共用機器,記得像上面一樣把你的私人alias封到函式裡,以免與他人命令衝突,造成不便。為了進一步偷個懶,你可以把這個函式的啟動封裝在virtualenv的啟動腳本里,或者在外面再封一層更高level的環境啟動函式。這樣就避免了每次登陸都要手動啟動一堆函式的麻煩。

bash function用起來

雖然對演算法工程師來說,bash一般用來寫一些非常高level的流程,大多數只是順序執行一堆命令外加寫幾個分支,不過,巧用bash中的function來封裝一些高頻的操作往往也可以大大提升日常煉丹效率。

例如,平時煉丹實驗掛一堆之後,我們可能時不時想看看某個實驗的cpu利用率,記憶體佔用等,每次用ps一頓操作都要敲不少東西,於是機智的小夕還是選擇封裝一個小function

# Usage: psfind <keywords> (RegExp supported)
function psfind() {
    ps aux | head -n 1
    ps aux | grep -E $1 | grep -v grep
}

這個函式既支援psfind <程序號>,也同樣支援匹配程序中的關鍵詞,且支援正則表示式來支援複雜查詢。最主要還是格式化了一下輸出,這樣打印出來會看起來很清晰。

覺得好用的話歡迎拿走(記得留個贊

與bettertouchtool的組合技

提高linux環境中的煉丹效率,除了上述這種在.bashrc中作文章,mac使用者還可以通過一個眾所周知的神器APPbettertouchtool來組合提升日常效率。大部分mac使用者可能只是用它來強化觸控板手勢了,其實它的鍵位對映也是炒雞無敵有用的鴨~

例如,眾所周知,在命令列裡或者vim的插入模式下,我們可以通過ctrl+w來向前刪除一個單詞,這個命令雖然無敵有用,但是早已習慣了ctrl/option/cmd+delete的我們還是會覺得這個設定比較讓人精分,導致影響了它的實用性。不過,用btt完全可以把這個ctrl+w這個鍵位對映到我們習慣的option+delete來刪除一個單詞(mac系統中大都支援opt+del刪除一個單詞的設定),如下圖

當然了,如果你用的不是mac自帶的終端,而是iterm等第三方的話,也可以考慮通過內建的鍵位對映功能來完全這個操作~或者,就適應一下ctrl+w的設定啦╮( ̄▽ ̄"")╭

別再無腦vim了

linux新手在去檢視一個(文字)資料集或日誌檔案時,基本就是直接用vim開啟後開始上下左右的看。其實,對NLPer來說,相當多的場景是無需反覆去vim的,有時反而會更加問題的複雜度,或閱讀體驗更糟。

先說說最簡單的,關於隨便瞥一眼資料集的開頭。

當資料集比較大時,哪怕是用sublime、vim這種高效能、輕量級文字編輯器開啟,都要等待很久很久(雖然vim可以ctrl+c打斷),超大單檔案資料集更是噩夢了。其實只是瞥一眼的話,完全可以使用head命令來快速取出一個檔案的前多少行,比如

head -100 train.csv

就可以直接print出來train.csv這個檔案的前100行資料,完全沒必要把整個檔案都load進記憶體裡。

此外,有的檔案內容可能是實時變化的(比如模型訓練時的訓練日誌檔案),如果希望持續track一個檔案的內容,可以通過tail命令:​

tail -f log

如果不僅要瞥一眼,而且還想檢視或獲取到檔案中某些特定的行。

有的小夥伴就覺得必須要叫vim爸爸來幫忙了,甚至要求助python。其實這種高頻場景也完全沒有必要的,直接用grep就能找出你感興趣檔案中的那些你感興趣的模式。

這方面尤其在處理訓練/eval日誌時非常高效

例如,這麼一份訓練日誌

我們eval了幾十上百個ckpt,每次eval都往裡面打了一堆日誌,但是我們希望只看一下域名買賣地圖各個step的dev evaluation的結果,那麼藉助管道和grep可以非常輕鬆的完成

cat eval.log | grep '\[dev'

grep的想象力也絕不是限制在管道里面,它完全可以單獨使用,直接去從若干你感興趣的檔案中尋找特定的模式(比如./*/*就把所有子目錄也一起遍歷了),在你批量管理、分析實驗日誌時,用熟之後堪稱神器。

當然了,進階一點,用好awksed後,能擺脫vim和python的情況就更多了,尤其是在快速編輯方面。不過,這塊一寫可能又停不下來QAQ算了算了,下次再寫吧(能湊夠一篇文章的話)

別再無腦python了

如上節所述,使用grep,awk等linux的一些神器命令可以在很多高頻場景下替代文字編輯器,大大提升煉丹效率。其實在coding層面上,也是如此。

例如,如果我們想把上一節末尾的圖片中的日誌,按照step從小到大排列後展示出來,怎麼做呢?

可能又有小夥伴要說上python了。殊不知linux中內建了很多方便易用的命令,包括排序sort命令,去重uniq命令等,因此,這個排序的需求,在上一節末尾的grep命令處通過管道連線一個sort就直接完成了

cat eval.log | grep '\[dev' | sort

在資料集處理方面,還有一個NLPer必知的命令,就是cut啦。這個命令堪稱是處理csv/tsv格式檔案的神器了,當你需要從CSV資料集檔案中提取出某些列時,可以直接幫你免去開python的麻煩

例如,如下眾所周知的Quora Question Pairs(QQP)資料集:

從第一行可以看到這個資料集包含6列:
id qid1 qid2 question1 question2 is_duplicate

但實際上我們單純訓練一個q-q文字匹配模型的話,只需要最後三列就夠了。不懂linux的小夥伴可能會用python開啟檔案,然後一頓for迴圈和str.split了。

但實際上,用上cut之後配合管道只需要一行

cat train.tsv | cut -f 4,5,6 > train.tsv.cut

就把資料集的第4,5,6列提出來了,並且儲存在了train.tsv.cut檔案中。如果需要的資料來自於兩個檔案,可以分別cut出來再將這些列拼到一起(新檔案是m+n列,可以理解成cat是縱向連線兩個或若干個檔案,而paste則是橫向連線):

paste file1.txt file2.txt

最後,訓練資料的去重和shuffle,也完全可以不用python去寫,分別用uniqshuf結合管道就能一行命令搞定:

sort train.tsv.cut | uniq | shuf > train.tsv

總之,類似於cutwc(統計詞數、行數的神器)的這種linux中的小命令很多,很大程度上幫我們解決了一些簡單高頻的資料集處理的需求(尤其CSV),避免了無腦使用python導致的低效時間開銷。

另外,不僅簡單場景可以直接用linux命令組合替代python,一些高階場景下linux命令與python的配合更是容易讓複雜的事情變得非常簡單。一個很常見的就是對大型資料集的單機並行處理:

&+wait:最簡單的單機並行資料預處理

當資料集比較大時,我們要跑分詞、shuffle、抽文字特徵之類的可能會非常非常慢(千萬級以上就明顯感覺不行了,過億之後要命了),如果強行把這些邏輯都丟到訓練裡面,又可能明顯拖慢訓練速度(雖然可以通過非同步資料讀取來緩解下),且極大的增加了訓練階段的CPU資源消耗。然而,如果我們用單核來跑全量資料,這個核用上洪荒之力可能也要跑一晚上(處理閱讀理解這種資料的會就更久了)。要迭代預處理策略就會變得非常困難,怎麼辦呢?

用python手擼多線性程式碼?確實可以,但我嫌麻煩╮(╯▽╰)╭

最簡單的做法就是用類似map reduce的正規化來寫,讓python指令碼僅僅去處理一個“單位”的資料,這裡的單位可以是一個文字行,也可以是一個數據集的檔案part。

例如,我們一個CSV檔案裡有1億條樣本,那麼我們可以先用split命令把它切成100個part(先用wc -l確定行數),然後並行處理後再merge起來,隨手寫下:

for i in {0..99};
do
    python process.py train.tsv.part$i > train.tsv.part$i.tmp &
done
wait 
cat *.tmp > train.tsv.processed
rm *part*

就坐等小伺服器上所有的CPU核心被打滿進行資料處理啦~

但!是!如果程式碼bug了怎麼辦?等所有程序跑完嗎?nonono,當然是一鍵kill啦~這裡一種是偷懶的辦法,直接用pkill -f <xxx>來殺死所有包含某個名字的程序

pkill -f 'process.py'

不過這樣可能存在誤殺的情況,需要注意名字不要取得太過於危險╮( ̄▽ ̄"")╭

此外,由於深度學習框架和一些庫的原因,或者我們不小心列印了過量的內容到螢幕上,這時候也容易出現程序退不出來的情況(很多小夥伴選擇了斷開ssh連結),這時候可以使用ctrl+z將前臺程序強行丟到後臺並掛起,然後kill %N來殺掉它(這裡的N一般是1,除非之前已經將一些前臺程序掛後臺了)

按下ctrl+z之後:
[1]+  Stopped                 top
[[email protected] emmm]$ kill %1

好睏QAQ發現篇幅有點長了,還是寫到這裡戛然而止吧。喜歡本文,期待後續煉丹技巧分享的小夥伴們歡迎來微信公眾號「夕小瑤的賣萌屋」玩耍,這是小夕跟其他幾個妹紙一起苦心經營的有溫度的小屋~