Out-of-the-box GPU Performance
模型推理性能是什么意思?在為用戶評估潛在的候選項時,不測量數據庫查詢和預篩選(例如決策樹或手動邏輯)的貢獻。使用估計器對特征列進行預處理,並通過網絡復制輸入/結果。
有兩個主要推理上下文:
離線推理-一次預先計算多個用戶的概率
在線推理-為特定用戶實時推薦
因此,可能有興趣優化三個指標:
吞吐量,例如用戶/秒(離線)
單次推理延遲(在線)
滿足設置的延遲約束時的吞吐量
在使用TensorFlow的stock實現時的初步觀察是,如上所述,請求特征轉換中不僅存在冗余,而且這些轉換是通過在圖中插入幾十個操作來實現的,用於諸如維度擴展和范圍值檢查之類的小任務。雖然這些操作本身在計算上很便宜,但帶來的開銷最終會導致性能瓶頸,特別是在gpu上。此外,對於簡單且基本相同的任務(如矩陣查找),每個功能都有自己的數十到數百個操作鏈。
通過用一個簡潔的配置描述預期的邏輯來消除這個開銷,允許以一種融合的、非冗余的、並行的方式實現操作,同時控制執行精度。
除了節省冗余計算和網絡I/O之外,將在后面看到,與基於本機CPU的實現相比,此實現利用NVIDIA GPU的卓越並行計算能力提供了大量的推理時間加速。
這種加速解放了數據科學家的手腳,特別是因為使在GPU上擴展模型變得容易,同時仍然滿足延遲要求。此外,在部署中,可以看到來自請求級並行的額外吞吐量。
How the Inference API Works
Using the API
雖然將把API的詳細概述留給筆記本電腦,但希望提請注意導出過程和傳統的TensorFlow估計器導出過程之間的關鍵相似性和差異,可以在這里找到概述。
導出深度學習模型通常需要指定三個組件:構成模型的操作圖、用於執行這些操作的權重以及提供這些操作的輸入的大小和類型。在典型的TensorFlow工作流中,這些組件由描述輸入和轉換的特征列、管理權重的估計器及其描述神經網絡圖的模型函數(為DNNLinearCombinedClassifier等屏蔽估計器預定義)來定義。導出筆記本中的以下幾行值得在此處重新解釋,以強調此過程的工作原理:
wide_columns, deep_columns = ...
estimator = tf.estimator.DNNLinearCombinedClassifier(
linear_feature_columns=wide_columns,
dnn_feature_columns=deep_columns,
...)
# infer input shapes and types from feature_columns as a parse_example_spec
parse_example_spec = \
tf.feature_column.make_parse_example_spec(deep_columns + wide_columns)
# expose serialized Example protobuf string input and parse to feature tensors
# with a serving_input_receiver_fn
cpu_serving_input_receiver_fn = \
tf.estimator.export.build_parsing_serving_input_receiver_fn(parse_example_spec)
# export in saved_model format
estimator.export_saved_model('/tmp', cpu_input_serving_receiver_fn)
Listing 1: Pseudo-code for standard export of a TensorFlow Estimator
API以幾乎相同的方式使用所有相同的信息,但有幾個注意事項。首先,服務輸入接收器不是從序列化的protobuf字符串映射到特征張量,而是從特征張量映射到輸入到DNN的稠密向量。不幸的是,引擎蓋下dnnlinearcombined分類器使用的模型函數不能接受這些向量,因此建立了一個幾乎完全相同的副本,可以(這是一個單行變化)並將其替換掉。
第二個需要注意的是,從優化的角度來看,有一些特性的有用信息並沒有包含在特性列中。特別是,功能列不包含有關功能所屬的類(請求或項)的信息,已經討論過這些信息的優點。也不會告訴分類功能是一個熱門(一次只能接受一個類別,比如一個唯一的ID)還是多個熱門(一次可以接受多個類別,比如將餐廳描述為“休閑”和“意大利人”)。一個熱分類只是多個熱分類的一個特例,但是預先知道這些信息可以讓有效地在gpu上實現嵌入查找並優化內存分配。
提供了通過從功能名稱到指示請求或項(0或1)或一個熱的或多個熱的(1或-1)的標志的映射來指定此信息的選項。這些地圖可以作為函數或詞典提供,如下所示:
# either functions
import re
AD_KEYWORDS = ['ad', 'advertiser', 'campain']
AD_REs = ['(^|_){}_'.format(kw) for kw in AD_KEYWORDS]
AD_RE = re.compile('|'.join(AD_REs))
level_map = lambda name: 0 if re.search(AD_RE, name) is None else 1
# or explicit dicts
from features import \
REQUEST_SINGLE_HOT_COLUMNS, ITEM_SINGLE_HOT_COLUMNS, \
REQUEST_MULTI_HOT_COLUMNS, ITEM_MULTI_HOT_COLUMNS
num_hot_map = {name: 1 for name in REQUEST_SINGLE_HOT_COLUMNS+ITEM_SINGLE_HOT_COLUMNS}
num_hot_map.update({name: -1 for name in REQUEST_MULTI_HOT_COLUMNS + ITEM_MULTI_HOT_COLUMNS})
Listing 2: Mappings for specifying feature class and categorical properties
如果不確定功能是如何歸入這些陣營的,那么對於每個映射,總是有一個更一般的情況,所以請將其留空,將假設所有功能都是項級別和多熱點(對於分類)。 一旦指定了這個額外的信息,導出過程看起來非常相似:
from model import build_model_fn
from recommender_exporter import RecommenderExporter
wide_columns, deep_columns = ...
estimator = tf.estimator.DNNLinearCombinedClassifier(
linear_feature_columns=wide_columns,
dnn_feature_columns=deep_columns,
...)
# couple extra function calls
model_fn = build_model_fn(...)
exporter = RecommenderExporter(
estimator, # for the weights
deep_columns, # for the inputs and transformations
wide_columns,
model_fn, # for the graph
level_map, # for request vs. item
num_hot_map, # for one vs. multi -hot
wide_combiner=...)
# then the same
gpu_input_serving_receiver_fn = exporter.get_input_serving_receiver_fn()
estimator.export_saved_model('/tmp', gpu_input_serving_receiver_fn)
Listing 3: Pseudo-code for GPU-accelerated model export
這將導出一個TensorFlow保存的模型,並將加速查找操作插入到圖中。雖然可以為這個保存的模型提供服務並進行調用,但是由於圖所期望的矢量化輸入,API並沒有那么整潔,而且仍然會招致TensorFlow的開銷。為了進一步加速作為TensorRT可執行引擎的圖形,只需要將保存的模型凍結到TensorFlow graph def中,TensorFlow為其內置了實用程序,然后利用提供的轉換腳本,該腳本使用在graph def中編碼的信息來編譯TensorRT引擎。有關如何通過簡單的Flask應用程序實現這一點的示例,請參見筆記本。端到端,導出管道如下所示:
Figure 3: TensorFlow model conversion for inference.
有兩種方法可以部署轉換后的模型。首先,如果推理可以在本地發生(與請求來自的服務器相同),可以跳過TensorRT推理服務器,並將自定義類與CUDA和TensorRT部分一起使用。其次,為了遠程推理或易於使用,TensorRT推理服務器使用自定義后端來進行自定義CUDA代碼和TensorRT引擎推理。
Performance Breakdown
代碼支持本地和遠程推理。雖然直接的本地推斷可以產生最佳的延遲和CPU利用率,但這需要細致的調整。由於遠程推理將查詢生成和推理計算分離開來,因此更加靈活。NVIDIA的TensorRT推理服務器是一個遠程推理解決方案,允許輕松優化,以充分利用可用的計算能力。這是在多個級別上完成的,包括調度傳入查詢、通過在GPU上托管模型的多個副本來進行管道優化、有效利用所有可用GPU的節點級優化等等。可以無縫地過渡到遠程推理,這有助於使用公共節點配置。perf_client test實用程序有助於改變客戶端並發性,以便向服務器實例發出多個並發查詢,並測量吞吐量和延遲。在這里,提出了吞吐量優化和延遲優化的數據為模型所獲得的測試與上述特征。
Figure 4: Data flow for Wide & Deep model inference. Numbers describe computation order
在遠程推理場景中,生成的查詢需要與遠程服務器通信。當傳輸的數據量小而計算需求大時,這通常不是瓶頸。然而,廣度和深度模型可以將許多特性消耗到很少的MLP層中。結果,傳輸的數據量較大,所需的計算量較小,從而導致網絡帶寬對觀察到的端到端性能產生影響。當計算在gpu上大大加速時,這種情況被誇大了。對於在這里測試的模型,客戶機和服務器之間的1Gbps網絡在每個請求的較大項目上都會造成性能瓶頸,遷移到10Gbps可以有效地消除這個瓶頸。
下圖顯示了查詢的p99延遲,這是CPU上運行的模型與NVIDIA T4 GPU上運行的模型的每個請求項的函數。GPU運行使用客戶機-服務器模型,其中客戶機和服務器位於通過1Gbps或10Gbps網絡連接的不同節點上。CPU在本地運行,沒有任何網絡瓶頸。這里選擇的CPU是一個24核48線程的Xeon Platinum 8275CL(在c5d.24xlarge AWS實例上可用)。每個查詢在CPU的所有核心上被拆分和並行化。使用官方的TensorFlow容器或Intel的TensorFlow容器來測量CPU的性能,基於此,CPU的速度更快。對於GPU運行,每個查詢都在單個NVIDIA T4 GPU上進行非公開處理,TensorRT推斷服務器參數針對延遲進行了優化。該圖顯示,當許多項與單個請求配對時,GPU延遲比CPU延遲低一個數量級以上。在每個請求4096個項目時,CPU延遲約為55ms,而GPU延遲在1Gbps網絡上約為8ms,在10Gbps網絡上約為4ms。即使在每個請求的項目數較低的情況下,GPU的延遲也大大降低,大約為1.2ms,而CPU的延遲大約為15ms,每個請求64個項目。
Figure 5: Comparison of latency between GPU and CPU in a latency optimized configuration for online inference.
如果延遲約束不重要,比如在脫機處理中,那么可以選擇TensorRT推理服務器參數來優化吞吐量。下圖顯示了吞吐量作為模型吞吐量優化配置的每個請求項的函數。與延遲優化的情況一樣,客戶機和服務器之間的網絡帶寬也會影響性能。在每個請求的最低項目數下,沒有足夠的工作使GPU飽和。隨着每個請求的項目數增加到256,看到了更好的吞吐量,因為GPU更高效。當進一步增加每個請求的項時,所需的工作量線性增加,所獲得的吞吐量相應下降。
Figure 6: Comparison of throughput between GPU and CPU in a throughput optimized configuration. For the 10 Gbps GPU case, the p99 latency for all items per request is always lower than 7ms.
為了演示延遲約束下的吞吐量優化,遍歷了客戶機和服務器並發性(即,客戶機並發等待的請求數,以及服務器允許並行運行的模型數),並選擇做出有用折衷的配置。下圖顯示了每個請求的不同項的吞吐量延遲折衷曲線。當增加客戶機和服務器的並發性時,吞吐量會增加,直到GPU的工作飽和為止。在此之后,延遲顯著增加,而吞吐量只增加了一點點。
Figure 7: Best latency-throughput tradeoffs at 10 Gbps.
Why does it run so fast?
GPU Preprocessing for TensorFlow feature_columns
and Fused Embedding Lookups
最后但並非最不重要的是,嵌入到多層感知器(MLP)中,該感知器是模型深部的一部分。是廣度和深度模型中計算最密集的部分。對於批處理計算,MLP本質上是一個矩陣乘法序列,其中包含激活和其他點操作,如之間的批處理規范化。矩陣乘法需要更多的數學運算(立體縮放)而不是內存運算(二次縮放)。由於GPU被設計為提供更大的計算吞吐量,當在GPU上以高度並行的方式運行時,矩陣乘法通常會看到巨大的性能提升。此外,NVIDIA gpu的Volta和Turing結構具有張量核,在混合或降低精度的情況下進一步加速矩陣乘法。NVIDIA gpu具有使MLP計算非常快速的架構特性。為了利用這些架構特性並獲得最高性能,軟件堆棧扮演着關鍵的角色。使用NVIDIA TensorRT,一個在NVIDIA gpu上進行高性能深度學習推理的平台。TensorRT執行圖形級優化和特定於體系結構的優化,以生成以高性能方式執行圖形的TensorRT引擎。TensorRT的優化基於矩陣的大小和所使用的GPU架構為矩陣乘法選擇正確的GPU內核。TensorRT還可以使用混合和降低的精度來利用Tensor核。內存帶寬是許多應用程序中日益增長的瓶頸源,因為在大多數體系結構中,計算吞吐量通常比內存帶寬大得多。TensorRT擁有強大的優化技術來執行內核融合,從而提高了計算激活函數和點態內核的內存局部性,並進一步加快了流水線的速度。
Accelerated Matrix Multiplies for the MLP
最后但並非最不重要的是,嵌入到多層感知器(MLP)中,該感知器是模型深部的一部分。是廣度和深度模型中計算最密集的部分。對於批處理計算,MLP本質上是一個矩陣乘法序列,其中包含激活和其他點操作,如之間的批處理規范化。矩陣乘法需要更多的數學運算(立體縮放)而不是內存運算(二次縮放)。由於GPU被設計為提供更大的計算吞吐量,當在GPU上以高度並行的方式運行時,矩陣乘法通常會看到巨大的性能提升。此外,NVIDIA gpu的Volta和Turing結構具有張量核,在混合或降低精度的情況下進一步加速矩陣乘法。 NVIDIA gpu具有使MLP計算非常快速的架構特性。為了利用這些架構特性並獲得最高性能,軟件堆棧扮演着關鍵的角色。使用NVIDIA TensorRT,一個在NVIDIA gpu上進行高性能深度學習推理的平台。TensorRT執行圖形級優化和特定於體系結構的優化,以生成以高性能方式執行圖形的TensorRT引擎。TensorRT的優化基於矩陣的大小和所使用的GPU架構為矩陣乘法選擇正確的GPU內核。TensorRT還可以使用混合和降低的精度來利用Tensor核。內存帶寬是許多應用程序中日益增長的瓶頸源,因為在大多數體系結構中,計算吞吐量通常比內存帶寬大得多。TensorRT擁有強大的優化技術來執行內核融合,從而提高了計算激活函數和點態內核的內存局部性,並進一步加快了流水線的速度。
API Flexibility and Constraints
除了在實際模型計算時間上的巨大改進之外,API中還有一些設計元素值得強調。首先,與其他嘗試加速廣度和深度模型(將所有類別嵌入大小對齊以將整個深度嵌入步驟視為單個多個熱查找)不同,不假設嵌入的相對大小。這使得數據科學家可以在嵌入大小的空間上自由地進行超參數搜索,而不必擔心推理時間性能。不需要預先轉換和存儲特征,因為轉換方法可能會隨着模型節奏的變化而變化。這在保持原始模型表示的同時節省了存儲成本。在CPU上運行會造成瓶頸。
雖然仍有許多要添加支持的功能列類型,但盡量少對數據進行假設。雖然應該考慮工程化特征將如何影響推斷性能,但這種考慮決不應妨礙數據科學家開發有用的特征。這個規則的一個顯著的例外是對字符串數據缺乏支持,選擇排除字符串數據的前提是,對於大多數用例,字符串數據可以在存儲之前轉換為整數索引,而不會顯著喪失通用性或抽象性。這不僅降低了預處理過程中的存儲成本和CPU負載,而且減少了專用推理服務器設置中的網絡流量。雖然數據集不包含字符串,但如果關於“整型”的假設被誤導,歡迎這種反饋。
在某些情況下,確實要求用戶提供有關其數據的假設。這些參數包括編譯TensorRT引擎所需的avg_nnz_per_特性和items_per_請求。前者在多個熱分類特征中的索引數量在一個實例到下一個實例之間可以按數量級變化,或者在不能預先知道預期的最大索引數量的情況下可能會出現問題。對於候選項來自上游過濾器的情況,后者的約束也面臨類似的問題,上游過濾器的候選項數量無法預先知道(盡管在這種情況下有可行的解決方法)需要強調的最后一個API特性是,通過為多個hot特性引入nnz值的規范,使得TensorFlow在其parse_示例op中使用的SparseTensor機器變得不必要了,這在很大程度上消除了tf.示例protobufs作為網絡的輸入表示,節省時間和計算的重復,建立,序列化,然后立即反序列化數據。
結合TensorRT推理服務器自定義后端插件,這意味着用戶可以直接從原始數據表示映射到推薦分數,開銷可以忽略不計。這樣的數據表示使抽象更加健壯,同時提供了更高的性能。
Looking Forward
對TensorFlow特征列類型的支持僅擴展到Outbrain數據集所需的類型。重申一下,這些是shape=(1,)的數值列,用於標量值數值特征;categorical_column_with_identity,categorical_column_with_hash_bucket,以及要將包裝在其中的嵌入列和指示符列。首要任務是擴展對其余特性列的支持,列表的頂部是
· Crossed columns
· Vector-valued numeric columns (len(shape) == 1 and shape[0] > 1)
· categorical_column_with_vocabulary_list (for integer vocabularies)
· bucketized_column
· weighted_categorical_column
在一個以其問題的多樣性為標志的領域中,確信需要更多的功能來充分解決用戶的用例。將Github的問題提交到這個回購協議中,將是一個很好的方式來建議新的功能,或者幫助重新確定上面給出的路線圖的優先級。
英偉達也在致力於加速推薦管道的其他部分。數據接收和預處理(ETL)是推薦系統管道的一個主要組件,RAPIDS可以用來加速GPU上的這些組件。如果還沒有,看看在加速推薦系統培訓方面的工作,使用Rapids和Pythorch進行RecSys 2019挑戰賽。另一項在多個GPU上訓練具有大型嵌入表的推薦系統的並行工作也在積極開發中,可以在這里查看:https://github.com/NVIDIA/hugetr
Conclusion
演示了如何為使用gpu的推薦者獲得至少10倍的延遲和大約13倍的吞吐量。如此驚人的速度將有助於降低為推薦系統提供實時推理服務的部署成本。通過在早期訪問頁面注冊了解更多關於工作的信息,並嘗試交互式的ipython筆記本演示來轉換和部署TensorFlow寬和深模型以進行推理。請使用下面的評論告訴計划如何采用和擴展此項目。