本文首發於:行者AI
隨着各行各業數字化的不斷推進,AI需要處理的數據越來越多,單一服務器已經難以滿足當前產業的發展需求,服務器集群成為企業用AI處理數據的標配硬件,而分布式計算成為人工智能應用的標配軟件。
從下圖可以看出,現今有很多開源的分布式計算框架,從模型的訓練、調參到部署;從NLP、CV到RS;這些框架覆蓋到了AI產業生命周期的各個方面。本文就選取其中的Ray框架進行簡單的介紹。
Ray 是伯克利大學在2017年開源的分布式計算框架,對應的論文是《Ray: A Distributed Framework for Emerging AI Applications》。強化學習任務需要與環境進行大量的交互(毫秒級),且在時間上支持異構性。該框架專門為機器學習與強化學習設計,相較於其他框架,ray具有以下優勢:
- 輕量級
- 可快速構建
- 通用性強
- 性能優異
下面就這四個優點為大家進行詳細介紹。
1. Ray框架的優勢
1.1 輕量級
相較於傳統的分布式框架(尤其是hadoop、spark等),Ray可以直接通過pip進行安裝,且對系統版本無要求。
pip install -U ray
Ray是一個簡單的分布式策略,而非完整的生態,因而不需要復雜的構建。
另一方面,輕量而優秀的框架往往可以作為企業數據處理的基礎框架,企業不斷在該框架的基礎上增加生態,從而形成企業獨有的應用生態。
1.2 可快速構建
如hadoop等傳統框架,要對原有的單機程序進行分布式化,需要修改整個代碼邏輯,以MapReduce的編程方案重構各個計算模塊,這使得hadoop等傳統框架有着良好的可編輯性,算法工程師可以根據業務需求進行詳細的修改。強大的可編輯性也帶來了學習成本高,代碼重構困難等諸多問題。人工智能日新月異,模型在不斷更迭,敏捷開發成為了很多AI企業的開發模式,AI應用的復雜構建會大大影響整個項目的推進。
如下代碼,將一個簡單的單機程序函數,轉換為Ray分布式的函數,只是在原有函數的基礎上加入了ray.remote的裝飾器,便完成了分布式化的工作。
### 原始單機代碼
def f(x):
return x * x
futures = [f.remote(i) for i in range(4)]
print(ray.get(futures))
### Ray分布式代碼
import ray
ray.init()
@ray.remote
def f(x):
return x * x
futures = [f.remote(i) for i in range(4)]
print(ray.get(futures))
1.3 通用性強
近年tensorflow、torch等深度學習框架成為人工智能應用的模型框架,考慮到產業應用場景,這些框架都給出了各自分布式訓練和部署的方案,且這些方案的計算資源利用率較高。大型的項目往往由數個算法模型組成,為了快速開發,算法工程師往往采用開源的代碼構建,而這些開源的代碼采用的深度學習框架很可能互不相同,針對單一框架的分布式方案難以適用。
除此之外,ONNIX等為代表的框架,傾向於將所有框架的模型統一到單一的解決方案上,由於很多前沿的深度學習模型對神經元進行了復雜的修改,無法適配到通用的算子上,需要算法工程師手寫算子,從而拖慢了開發速度。Ray將機器學習模型、numpy數據計算、單一的函數抽象成通用的計算,實現了對各種深度學習框架、機器學習框架的適配。
另外,Ray對強化學習的應用進行了專門的生態構建。
1.4 性能優異
下圖為Ray、Horovod以及tensorflow原生的分布式方案訓練ResNet-101模型的比較,縱軸為每秒平均迭代的圖片數,可以看出Ray略微優於Horovod框架。
下圖為Clipper和Ray在模型調用上吞吐量的比較,兩者均用同一網絡模型,可以看出Ray優於Clipper。
Ray並沒有做到每個分布式場景都優於其他框架,但Ray集合訓練、調參以及部署為一體,仍能保持不錯的性能,因而值得學習和使用。
得益於Ray框架良好的性能,Ray廣泛用於工業界(如螞蟻金服),要先學會使用Ray必先了解Ray的構成,下一小節就Ray的構成進行介紹。
2. Ray的使用
2.1 Ray的構成
Ray大致由四部分組成:
Ray涉及了AI應用的整個生命周期:訓練、調參、部署,並對強化學習場景進行了專門的優化。由於個人使用經驗有限,這里只介紹Ray的Serve模塊。
2.2 Ray的啟動
如下圖所示,Ray由一個頭節點(Head node)和一組工作節點(Worker node)組成。啟動Ray需要首先啟動頭節點,並為工作節點提供頭節點的地址以形成集群。頭節點負責管理和分配工作節點的任務,工作節點負責執行任務並返回結果。經過測試,頭節點和工作節點可以為同一台計算機。
Ray的啟動由兩個步驟組成:啟動頭節點、注冊工作節點到頭節點。
以下是頭節點的啟動代碼和關閉代碼。
import ray
ray.init() # 啟動
assert ray.is_initialized() == True
ray.shutdown() # 關閉
assert ray.is_initialized() == False
注:啟動腳本應當加入關閉代碼,如果沒有,ray程序可能一直在進程中運行。
Ray框架采用Actor模型,相較於傳統的共享內存模型,Ray不存在狀態競爭、可以方便的組建集群、能更好的控制狀態。每個Actor即每個工作節點的注冊方式如下。
import ray
ray.init(address=頭節點地址) # 啟動
assert ray.is_initialized() == True
ray.shutdown() # 關閉
assert ray.is_initialized() == False
2.3 Ray Serve
Ray Serve可以類比clipper,主要用於模型的部署服務,並支持多種深度學習框架,官方給出的示例有:
這里以tensorflow2為例,來說一下如何用ray來部署模型服務。
步驟一:定義一個模型服務類
如下是模型服務類的簡易代碼,和Flask等框架部署AI服務類似。由於Ray使用gRPC作為通信協議,速度更快,Ray還在gRPC基礎上進行了優化,有些場景快於原生的gRPC通信。
class TFMnistModel:
def __init__(self, model_path):
import tensorflow as tf
self.model_path = model_path
# 加載模型
self.model = tf.keras.models.load_model(model_path)
async def __call__(self, starlette_request): # 異步調用
# transform HTTP request -> tensorflow input
input_array = np.array((await starlette_request.json())["array"])
reshaped_array = input_array.reshape((1, 28, 28))
# tensorflow input -> tensorflow output
prediction = self.model(reshaped_array)
# 返回結果
# tensorflow output -> web output
return {
"prediction": prediction.numpy().tolist(),
"file": self.model_path
}
步驟二:模型部署到Ray Serve
如下代碼中,start函數用於啟動服務,create_backend函數用於啟動模型,create_endpoint函數啟動服務。在Ray中,模型和服務是分離的,可以多個服務調用同一個模型,以支持復雜的調用邏輯。
"tf:v1"為模型的名稱,"tf_classifier"為服務的名稱,route參數為路由,這些參數都可自由定義。
client = serve.start()
client.create_backend("tf:v1", TFMnistModel, TRAINED_MODEL_PATH)
client.create_endpoint("tf_classifier", backend="tf:v1", route="/mnist")
步驟三:請求測試
resp = requests.get(
"http://localhost:8000/mnist",
json={"array": np.random.randn(28 * 28).tolist()})
print(resp.json())
3. 結語
一個優秀的框架往往包含了眾多先進的設計理念。Ray框架在構建時,參考了許多先進的設計理念,如混合調度策略、GCS 管理等等,這些設計理念使得框架本身完善而又先進。Ray廣泛用於AI企業的分布式計算場景,從眾多框架中脫穎而出,值得學習。
PS:更多技術干貨,快關注【公眾號 | xingzhe_ai】,與行者一起討論吧!