
自己編譯過PyRosetta的人都知道這個過程遠沒有“pip install”那么輕松。它本質(zhì)上包含兩條線一條是Rosetta主程序本身的C編譯另一條是把這些C功能封裝成Python可調(diào)用模塊的綁定層編譯。任何一個環(huán)節(jié)踩坑第二天的時間基本就交代了。我前前后后在幾臺不同配置的Linux機器上折騰過編譯安裝也幫別人排查過各種報錯。這篇文章不打算復(fù)述官方文檔而是把我實際操作的流程、環(huán)境準備、關(guān)鍵參數(shù)含義、遇到過的故障和排查方法串起來作為一份可以“抄作業(yè)”的記錄也順便說清楚每一步背后的原因。如果你正準備在Linux平臺編譯安裝PyRosetta或者編譯到一半莫名崩潰、找不到頭緒這篇應(yīng)該能幫上忙。1. 為什么要自己編譯PyRosetta1.1 PyRosetta到底是什么和Rosetta是什么關(guān)系先理清概念。Rosetta是一套用C實現(xiàn)的蛋白質(zhì)結(jié)構(gòu)預(yù)測與設(shè)計框架從2000年前后發(fā)展到現(xiàn)在已經(jīng)不只是單蛋白結(jié)構(gòu)預(yù)測而是覆蓋了蛋白設(shè)計、抗體建模、酶設(shè)計、RNA建模、對接、自由能計算等一批模塊。它原本的使用方式是命令行調(diào)用relax、fixbb、docking_protocol等編譯好的可執(zhí)行程序輸入PDB文件和一批參數(shù)完成計算。PyRosetta則是Rosetta的Python綁定層。它用類似pybind11的機制把Rosetta C庫里的核心類和方法暴露成Python接口讓你可以直接在Python腳本里創(chuàng)建Pose、操作殘基、計算能量、調(diào)用打分函數(shù)。舉個例子import pyrosetta pyrosetta.init(-mute all) pose pyrosetta.pose_from_pdb(/path/to/input.pdb) scorefxn pyrosetta.create_score_function(ref2015) score scorefxn(pose) print(pose.pdb_info().name(), score)這段代碼干的事情本質(zhì)上就是讓Python進程鏈接Rosetta編譯出來的層庫通過綁定層把C對象變成Python對象來操作。所以編譯PyRosetta不是只編一個獨立的小插件而是把整套C核心編譯出來再把綁定層接上去。很多人低估了這部分工作量以為和普通Python包一樣幾分鐘完事實際上首次編譯一兩個小時很正常。1.2 什么樣的場景需要“繞遠路”編譯如果你只是想跑一些常規(guī)模擬官方預(yù)編譯的PyRosetta包其實更方便解壓即用不需要面對編譯問題。但以下場景你會發(fā)現(xiàn)必須走編譯這條路你手上有一個需要復(fù)現(xiàn)或改造的Rosetta本地分支已經(jīng)改過C源代碼必須從這版源碼重新編譯綁定層。你需要在特殊CPU架構(gòu)或者特定Linux環(huán)境上運行而官方預(yù)編譯包里沒有對應(yīng)兼容版本。你需要MPI并行支持想在Python端啟動多個進程做大規(guī)模Rosetta計算預(yù)編譯包往往不包含MPI版本。你希望針對當前機器CPU做指令集優(yōu)化用-marchnative級別的方式徹底榨干性能。你需要在調(diào)試模式下編譯從Python端追蹤C層的問題預(yù)編譯包幫不了你。還有一點比較隱蔽官方預(yù)編譯包用到的是它自己要求的底層系統(tǒng)庫版本和某些Linux發(fā)行版自帶的版本容易沖突。表現(xiàn)就是import成功一旦初始化就報“符號找不到”的錯或者直接段錯誤。自己編譯能讓所有動態(tài)庫用當前機器同一套工具鏈生成兼容性反而更容易把控。1.3 編譯安裝與預(yù)編譯包的取舍我給的判斷標準很簡單如果是急著出結(jié)果、機器上也沒什么特殊要求直接用預(yù)編譯包如果打算長期在這臺集群上做科研、跑流水線或者要基于源碼二次開發(fā)那值得花半天時間把環(huán)境一次理順。編譯的代價主要是時間和環(huán)境復(fù)雜度但收益也很實際。編譯過程會讓你把Rosetta工程結(jié)構(gòu)、構(gòu)建系統(tǒng)和依賴關(guān)系都過一遍之后遇到運行時問題的時候你能自己通過查詢環(huán)境定位而不是拿著一堆預(yù)編譯文件干瞪眼。從“會用”到“能調(diào)”的轉(zhuǎn)變基本就是在這一次編譯工程中發(fā)生的。2. 編譯前的環(huán)境準備與依賴梳理2.1 硬件配置與磁盤規(guī)劃先說硬件。編譯Rosetta不是小事它包含幾千個源文件編譯任務(wù)并行度又高對CPU、內(nèi)存、磁盤都有實打?qū)嵉囊蟆N医ㄗh的底線配置CPU8核以上16核體驗最佳。內(nèi)存16G起步32G更穩(wěn)。 很多人第一次編譯時只給4G或8G編譯到一半進程被系統(tǒng)OOM殺掉終端就顯示一個孤零零的Killed。磁盤源碼包解壓后可能幾個G但編譯中間產(chǎn)物和緩存會暴漲建議至少預(yù)留50G。放在/tmp這種臨時目錄的風險很大編譯中途清理臨時文件可能導(dǎo)致重來。系統(tǒng)主流的Linux發(fā)行版都行但工具鏈版本要達到要求。GCC建議7以上新版本Rosetta建議9/10/11。我自己的實測大概是這樣16核機器release模式編譯完整二進制大概花一個半小時8核機器要三小時上下。如果并行開太多導(dǎo)致資源緊張時間反而會拉長。所以編譯前先確認機器負載避免一邊跑大型任務(wù)一邊編譯。2.2 依賴工具鏈安裝清單我在不同Linux發(fā)行版上裝的依賴不完全一樣但有幾個是通用且必須的C編譯器、Python開發(fā)頭文件、pybind11、Boost系列庫、zlib、libjpeg、libpng、hdf5、sqlite3。在Debian系發(fā)行版上我習慣這樣裝sudo apt update sudo apt install -y gcc g gfortran sudo apt install -y python3 python3-dev python3-venv python3-pip sudo apt install -y build-essential cmake scons sudo apt install -y libboost-dev libboost-program-options-dev sudo apt install -y zlib1g-dev libjpeg-dev libpng-dev sudo apt install -y libhdf5-dev libsqlite3-dev在Red Hat系發(fā)行版上對應(yīng)的包名略有差異建議先用包管理器搜索確認再裝sudo dnf install -y gcc-c gcc-gfortran python3-devel cmake scons sudo dnf install -y boost-devel zlib-devel libjpeg-turbo-devel libpng-devel sudo dnf install -y hdf5-devel sqlite-devel這里最容易被忽略的是python3-dev或python3-devel。編譯Python擴展模塊時必須找到Python.h缺了這個頭文件編譯會在很早期就報“找不到Python.h”并停止。如果你發(fā)現(xiàn)系統(tǒng)的/usr/include/python3.x目錄不存在基本就是沒裝開發(fā)包。另外一個重點是版本一致性。后面綁定層編譯的時候用的Python和運行時導(dǎo)入的Python必須是同一個版本包括小版本號。最省心的做法就是一開始就建好虛擬環(huán)境所有編譯操作都在虛擬環(huán)境內(nèi)部進行避免和系統(tǒng)Python混在一起。2.3 獲取Rosetta源碼與目錄規(guī)劃Rosetta源碼需要從官方渠道獲取學術(shù)授權(quán)和非商業(yè)授權(quán)的下載入口不同商業(yè)用途需要申請商業(yè)授權(quán)。每個人的源碼包文件名不一樣但內(nèi)部結(jié)構(gòu)大體一致解壓后通常會看到main、tools、tests、demos等目錄。拿到源碼包后第一件事是規(guī)劃路徑。我強烈建議放到一個全英文、沒有空格、沒有特殊符號的路徑下例如export ROSETTA_ROOT/home/yourname/software/rosetta_src tar -xjf rosetta_src_release_xxx.tar.bz2 -C /home/yourname/software/之前有位同行把源碼放在帶中文目錄的路徑下結(jié)果編譯過程中的腳本拼接路徑時把文件名打亂了報錯信息看起來像是編譯器問題折騰半天才發(fā)現(xiàn)是路徑編碼問題。這種低級坑最好在開始前就避免。進入源碼根目錄后先確認構(gòu)建方式。老版本一般在main/source下有scons.py新版本支持CMake構(gòu)建根目錄或main/source下面會有CMakeLists.txt。這一步?jīng)Q定了后半段用什么命令別憑經(jīng)驗硬套。ls $ROSETTA_ROOT/main/source/scons.py ls $ROSETTA_ROOT/main/source/CMakeLists.txt把環(huán)境變量先配好后面很多腳本都會引用export ROSETTA3$ROSETTA_ROOT/main/source export PYROSETTA_DIR$ROSETTA3/src/python/PyRosetta3. 完整編譯流程與關(guān)鍵參數(shù)3.1 第一步編譯Rosetta底層引擎不管最后用CMake還是老式構(gòu)建腳本第一步都是先把Rosetta底層C編譯出來。這一步生成的庫文件是Python綁定層的鏈接基礎(chǔ)也是整個編譯過程中耗時最長、最容易出問題的一環(huán)。老版本構(gòu)建方式cd $ROSETTA3 ./scons.py -j 16 moderelease bin這里逐個解釋參數(shù)和背后原因moderelease表示編譯release版本。release會開編譯器優(yōu)化產(chǎn)出速度快但debug信息少。如果之后要調(diào)試Rosetta或PyRosetta的C代碼可以改成modedebug但編譯時間更長、運行更慢日常計算不建議用。bin表示目標為構(gòu)建全部可執(zhí)行程序。bin目標依賴了核心庫、應(yīng)用層庫和所有協(xié)議的編譯相當于把Rosetta整個主干的依賴樹都走一遍。-j并行編譯任務(wù)數(shù)。不要盲目填滿CPU核數(shù)我一般填核數(shù)減2。例如16核填14避免系統(tǒng)其他服務(wù)被餓死內(nèi)存緊張時再降到8或4。新版本CMake構(gòu)建方式cd $ROSETTA3 mkdir -p build cd build cmake .. -DCMAKE_BUILD_TYPERelease -DCMAKE_INSTALL_PREFIX$ROSETTA3/build/bin cmake --build . --target bin -j 16CMAKE_BUILD_TYPERelease對應(yīng)開啟優(yōu)化CMAKE_INSTALL_PREFIX指定安裝路徑后面--target bin和scons版本里的bin目標作用一致。這里有一個常見疑問我只是想用Python調(diào)PyRosetta為什么要把一堆命令行可執(zhí)行程序也編出來直接編庫不行嗎原因是Rosetta的構(gòu)建系統(tǒng)并不是為“只編綁定層”設(shè)計的。它整個依賴圖圍繞最終可執(zhí)行程序組織Python綁定層的構(gòu)建腳本會引用中間產(chǎn)物、庫路徑和編譯期生成的配置文件。直接單獨編一個綁定層目標除非你對構(gòu)建系統(tǒng)非常熟悉否則會碰到大量缺失中間文件的問題。先跑一遍bin等于把依賴關(guān)系全部打通后面流程順暢很多。3.2 第二步構(gòu)建Python綁定模塊底層引擎編譯完成后進入PyRosetta目錄開始綁定層編譯。cd $PYROSETTA_DIR python setup.py build_ext --inplacebuild_ext是setuptools里構(gòu)建擴展模塊的標準命令--inplace表示把生成出來的.so模塊直接放在源碼樹下方便未安裝時導(dǎo)入測試。這一步會啟動pybind11的代碼生成和編譯實際上又調(diào)用了一輪C編譯器。不過因為底層庫已經(jīng)編好了它更多是在做膠水層和鏈接工作耗時相對可控。編譯過程中會生成類似pyrosetta目錄下的動態(tài)庫文件例如帶release標識的.so文件。如果磁盤緊張可以把編譯臨時目錄指到別處python setup.py build_ext --inplace --build-temp /home/yourname/tmp/pyrosetta_build這里說一個容易踩的細節(jié)這一步執(zhí)行時當前終端里的Python解釋器版本必須和之后運行環(huán)境完全一致。最穩(wěn)妥的做法是在準備階段就建好虛擬環(huán)境之后切到虛擬環(huán)境里執(zhí)行編譯和安裝。python3 -m venv /home/yourname/pyrosetta_env source /home/yourname/pyrosetta_env/bin/activate cd $PYROSETTA_DIR python setup.py build_ext --inplace3.3 第三步安裝到虛擬環(huán)境并配置環(huán)境變量build_ext --inplace生成的模塊已經(jīng)能用了但為了不污染源碼目錄最好還是在虛擬環(huán)境里正式安裝一遍cd $PYROSETTA_DIR pip install .如果你不想安裝而是臨時用也可以設(shè)置PYTHONPATH指向源碼目錄export PYTHONPATH$PYROSETTA_DIR:$PYTHONPATH不過這種方式在源碼目錄移動、重新編譯或者切換分支時會非?;靵y。我建議直接用虛擬環(huán)境安裝保持環(huán)境清爽也讓后面pip freeze能正確記錄依賴。安裝完成之后還需要確保Rosetta數(shù)據(jù)文件路徑被正確識別。PyRosetta初始化時會加載打分函數(shù)參數(shù)、數(shù)據(jù)庫文件等它查找路徑的方式依賴環(huán)境變量。檢查一下當前環(huán)境是否包含了ROSETTA3和常見的數(shù)據(jù)路徑echo $ROSETTA3 echo $PYROSETTA_DIR如果發(fā)現(xiàn)環(huán)境變量消失了可以在虛擬環(huán)境的激活腳本里自動設(shè)置echo export ROSETTA3$ROSETTA3 $VIRTUAL_ENV/bin/activate echo export PYROSETTA_DIR$PYROSETTA_DIR $VIRTUAL_ENV/bin/activate以后激活虛擬環(huán)境就自動帶上路徑少掉很多不必要的煩惱。4. 安裝后的驗證與典型用法4.1 驗證導(dǎo)入與基礎(chǔ)初始化編譯安裝最刺激的時刻就是第一次import。我建議先用最小命令驗證python -c import pyrosetta; pyrosetta.init(-mute all -run:version)如果沒有任何報錯說明Python擴展模塊、底層庫、數(shù)據(jù)文件路徑這三個環(huán)節(jié)基本正常。這里-run:version會讓初始化時打印出當前Rosetta版本信息方便確認你用的是哪套源碼。如果看到“段錯誤”或者“ImportError”先別急著懷疑綁層代碼。大部分這類問題本質(zhì)上還是環(huán)境不一致比如虛擬環(huán)境沒激活、用錯了Python版本、動態(tài)庫路徑不對。下面第5節(jié)會詳細講排查方法。4.2 跑通一個簡單的結(jié)構(gòu)任務(wù)驗證Phase通過后跑一個最簡單的人小流程來確認打分函數(shù)和Pose操作正常import pyrosetta pyrosetta.init(-mute all) pdb_filename 1ubq.pdb pose pyrosetta.pose_from_pdb(pdb_filename) print(Total residues:, pose.total_residue()) scorefxn pyrosetta.create_score_function(ref2015) total_score scorefxn(pose) print(Total score:, total_score)手上沒有PDB文件的話可以先直接構(gòu)造一個空Posepose pyrosetta.Pose() sequence AAAAAAAAAAAAAAAAAAAA pyrosetta.toolbox.peptidesequence_from_sequence(sequence)不管用哪種方式只要能計算出分數(shù)就說明打分函數(shù)的數(shù)據(jù)表加載正常主流程已經(jīng)可用。4.3 性能調(diào)優(yōu)與多線程配置自己編譯的一個主要優(yōu)勢是能做針對性優(yōu)化。CMake構(gòu)建時如果確定只在當前機器跑可以加上本地指令集優(yōu)化-DCMAKE_CXX_FLAGS-marchnative-marchnative會讓編譯器根據(jù)當前CPU支持的指令集自動啟用優(yōu)化能提升一部分單線程性能。但這個選項生成的二進制不可移植拷到別的機器上可能直接非法指令崩潰所以只有確認環(huán)境固定時再用。使用層面上PyRosetta的多線程邏輯和Rosetta命令行的邏輯不完全一樣。你如果開了MPI編譯初始化時還需要指定對應(yīng)的進程組import pyrosetta pyrosetta.init(-mpi_tracer_to_file ./log_)沒有MPI需求就不要在編譯時隨意加上MPI選項否則初始化時可能要求額外的MPI運行時環(huán)境平白給部署找麻煩。我通常把MPI版本的編譯做成獨立環(huán)境和普通版本分開管理兩邊互不干擾。5. 高頻問題與排查實錄5.1 編譯中途被Killed這是概率最高的編譯事故。表現(xiàn)是編譯進程跑到某一個鏈接或大文件編譯階段終端直接沒了有時只有一個Killed字樣。用dmesg | tail查看系統(tǒng)日志往往能看到內(nèi)核的OOM killer痕跡。解決辦法分三層第一降低并行度比如從-j 16改到-j 4。編譯任務(wù)的內(nèi)存峰值通常出現(xiàn)在鏈接大目標文件時并行任務(wù)越多峰值疊加越大。第二關(guān)閉圖形界面、瀏覽器等大內(nèi)存進程保證編譯期間有充足內(nèi)存。第三增加交換空間臨時緩解短期峰值。比如加一個16G的swapfilesudo fallocate -l 16G /swapfile sudo chmod 600 /swapfile sudo mkswap /swapfile sudo swapon /swapfile但這只是緩兵之計長期使用應(yīng)該在物理內(nèi)存上做規(guī)劃。5.2 第三方庫版本沖突Rosetta源碼樹里經(jīng)常自帶一批第三方庫比如boost和若干頭文件庫。如果系統(tǒng)也裝了版本不同的同名庫編譯期可能出現(xiàn)模板報錯、命名空間沖突甚至運行時符號錯亂。我處理這類沖突的原則是盡量采取走通的方式。編譯Rosetta主程序和Python綁定時優(yōu)先使用Rosetta捆綁的庫不要手動指定外部庫路徑如果系統(tǒng)庫版本明顯過新導(dǎo)致編譯報錯就通過環(huán)境變量或CMake選項強制指向源碼自帶庫。排查的方法是注意看報錯中引用的路徑。報錯里指向/usr/include/boost和/opt/rosetta/main/source/external/boost對應(yīng)的是完全不同的處理思路前者是系統(tǒng)庫后者是自帶庫。確定沖突來源后再決定改哪個路徑不要亂加CFLAGS或CPLUS_INCLUDE_PATH硬懟。5.3 Python版本不匹配引發(fā)的問題我遇到過在編譯時用系統(tǒng)Python 3.8運行時卻切到conda環(huán)境Python 3.9的情況。結(jié)果import成功初始化階段直接段錯誤。這個錯誤非常隱蔽因為不是明確的“版本不匹配”報錯而是進程崩潰。排查方式是先確認當前Python是什么版本which python python -V再看編譯時用了哪個Python。如果兩個解釋器路徑不同在虛擬環(huán)境里重新編譯一遍綁定層問題基本就消失了。我的經(jīng)驗是所有編譯和安裝動作開局先激活目標虛擬環(huán)境后面每一步都不換解釋器就不會踩到這個深坑。5.4 動態(tài)庫加載失敗編譯成功但import時報libxxx.so找不到或者直接報“cannot open shared object file”。這通常和動態(tài)庫搜索路徑有關(guān)用以下命令查看實際依賴ldd $PYROSETTA_DIR/pyrosetta/*.so如果輸出里某行顯示not found找到這個庫本來在哪個目錄下find /usr /lib /usr/local -name libxxx.so* 2/dev/null確認路徑后把它加入動態(tài)庫搜索路徑export LD_LIBRARY_PATH/path/to/libdir:$LD_LIBRARY_PATH注意這是一種權(quán)宜之計更推薦的方式是通過虛擬環(huán)境配置或者包管理器把對應(yīng)依賴裝到系統(tǒng)標準目錄免得每次啟動終端都要先設(shè)置環(huán)境。5.5 數(shù)據(jù)文件路徑找不到如果你編譯、import、初始化都沒有報錯但一執(zhí)行打分函數(shù)就提示找不到ref2015或某些參數(shù)文件多半是環(huán)境變量沒配好。Rosetta啟動時需要定位數(shù)據(jù)庫目錄而數(shù)據(jù)庫目錄路徑來自編譯期的默認值或環(huán)境變量。優(yōu)先檢查echo $ROSETTA3 pyrosetta.init_environment()確認數(shù)據(jù)庫確實存在。如果不希望每次都設(shè)置環(huán)境變量可以在Python腳本里顯式指定import os os.environ[ROSETTA3] /home/yourname/software/rosetta_src/main/source import pyrosetta pyrosetta.init(-mute all)5.6 編譯器版本過舊新版本Rosetta大量使用C11之后的特性GCC版本太老會在編譯早期報一堆頭文件錯誤例如std::filesystem找不到之類。排查方式是確認編譯器版本g --version如果版本明顯太老用module或update-alternatives切換到新版本編譯器。有些集群上默認的g是老版本但系統(tǒng)里其實安裝了新版本只是沒設(shè)置默認。切換后重編即可不需要改代碼。6. 一些實操體會與建議最后分享幾個我個人累積下來的操作習慣對降低編譯難度幫助很大。第一個建議是不要在源碼目錄里硬編。編譯產(chǎn)生的中間文件、緩存、臨時對象會大量占用源碼目錄而且反復(fù)重試時容易和新源碼混在一起。我用固定目錄存放源碼編譯臨時文件放到獨立目錄必要時直接刪除重建不影響主源碼。第二個建議是準備一個編譯日志文件。編譯輸出幾千上萬行終端刷屏后你想回溯最早的錯誤極其痛苦。簡單的方法是把日志同時落地./scons.py -j 16 moderelease bin 21 | tee build.log如果中途失敗直接查日志開頭和相關(guān)報錯尾部效率高很多。第三個建議是盡量保留一個干凈環(huán)境。不要一會兒在conda環(huán)境編一會兒切到系統(tǒng)Python編不要今天GCC 7明天GCC 11。任何一次環(huán)境切換都可能在某個隱蔽依賴上埋下雷。環(huán)境的一致性是整個編譯工程里性價比最高的保障。第四個建議是初次編譯先用小并行度跑通流程成功后再考慮開大并行做完整構(gòu)建。很多人上來就-j 32結(jié)果資源耗盡被Killed反復(fù)幾次心態(tài)先崩了。我先用-j 2或-j 4完整驗證一遍構(gòu)建鏈路沒問題再用高并行重跑整體時間反而更短。編譯安裝PyRosetta這件事說白了就是一次對工具鏈、依賴、構(gòu)建系統(tǒng)和Rosetta工程結(jié)構(gòu)的全面摸底。前期準備做足了后半程會順利很多準備不足每一個報錯都可能是新的時間黑洞。希望這篇記錄能幫你少走一點彎路把時間節(jié)省下來放到真正的研究和任務(wù)上。