Relay IR表示
Relay IR表示
Relay IR簡介
本節介紹了 Relay IR——第二代 NNVM。期待兩種背景的讀者——具有編程語言背景的讀者和熟悉計算圖表示的,深度學習框架開發人員。
簡要總結了設計目標,討論要點。
? 支持傳統的數據流式編程和轉換。
? 支持函數式作用域,let-binding,成為功能齊全的可微分語言。
? 能夠允許用戶混合兩種編程風格。
使用 Relay 構建計算圖
傳統的深度學習框架,使用計算圖作為中間表示。計算圖(或數據流圖)是表示計算的有向無環圖 (DAG)。由于缺乏控制流,數據流圖表達的計算受限制,容易實現自動微分,異構執行環境編譯(例如,專用硬件上執行圖)。
可以使用 Relay 構建計算(數據流)圖。如何構造一個簡單的二節點圖。示例語法與 NNVMv1 等現有計算圖 IR,唯一區別:
? 現有框架通常使用圖和子圖
? Relay 使用函數 eg – 表示圖形fn (%x)
每個數據流節點,都是 Relay 中的一個 CallNode。Relay Python DSL快速構建數據流圖。構造Add節點,兩個輸入點都指向%1。深度學習框架評估時,按照拓撲順序計算節點, %1只會計算一次。實現訪問者,打印結果,嵌套的 Call,變成log(%x) + log(%x)。
DAG 中存在共享節點時,對語義的不同解釋造成的。在普通的函數式編程 IR 中,嵌套表達式視為表達式樹,不考慮%1在%2中,實際重用了兩次。
Relay IR 到了這種差異。通常,深度學習框架用戶,用這種方式構建計算圖,經常發生 DAG 節點重用。用文本格式打印 Relay時,每行打印一個 CallNode,將每個 CallNode分配一個臨時 id (%1, %2),可以引用每個公共節點。
模塊:支持多種功能(圖形)
介紹了如何將數據流圖構建為函數。能不能支持多種功能,可以互相調用?Relay將多個功能組合在一個模塊中;下面的代碼顯示,一個函數調用另一個函數的示例。
def @muladd(%x, %y, %z) {
%1 = mul(%x, %y)
%2 = add(%1, %z)
%2
}
def @myfunc(%x) {
%1 = @muladd(%x, 1, 2)
%2 = @muladd(%1, 2, 3)
%2
}
該模塊可以被視為一個Map<GlobalVar, Function>,這里 GlobalVar 只是一個 id,表示模塊中的功能。@muladd與@myfunc是上面例子中的 GlobalVars。當CallNode調用另一個函數時,對應的 GlobalVar 存儲在 CallNode 的 op 字段中。包含一個間接級別——使用相應的 GlobalVar,查找調用函數的主體??梢灾苯訉⒑瘮档囊?#xff0c;存儲為 CallNode 中的 op。為什么需要引入 GlobalVar 呢? GlobalVar 解耦了定義/聲明,啟用了函數的遞歸和延遲聲明。
def @myfunc(%x) {
%1 = equal(%x, 1)
if (%1) {
%x
} else {
%2 = sub(%x, 1)
%3 = @myfunc(%2)
%4 = add(%3, %3)
%4
}
}
在上面的例子中,@myfunc遞歸地調用。使用 GlobalVar@myfunc,表示函數避免了數據結構中的循環依賴。Relay 相對于 NNVMv1,有以下改進:
? 簡潔的文本格式,可簡化寫入passes的調試。
? 在聯合模塊中,對子圖函數的一流支持,使得聯合優化會成為可能,如內聯和調用約定規范。
? 簡單的前端語言互操作,所有數據結構,可以在 Python 中訪問,允許在 Python 中,快速構建優化原型,與 C++ 代碼混合。
讓綁定和作用域
深度學習框架中,使用的舊方法構建計算圖。將討論 Relay 引入新結構——let 綁定。
每一種高級編程語言,使用 let 綁定。在 Relay 中,Let(var, value, body)表示三個字段的數據結構。評估let表達式,先評估 value 部分,分配給 var,在 body 表達式中,返回評估結果。
可以使用一系列 let 綁定,構造邏輯上等效數據流程序。
嵌套的 let 綁定稱為 A 范式,通常用作函數式編程語言中的 IR,仔細看看 AST 結構。雖然這兩個程序在語義上相同(除了 A 范式,有 let 前綴),但AST 結構不同。
程序優化采用這些 AST 數據結構,進行轉換,兩種不同的結構,將影響要編寫的編譯器代碼。例如,如果檢測一個模式:add(log(x),y)。
? 在data-flow形式中,先訪問add節點,直接查看第一個參數,是不是日志
? 在 A 范式中,不能直接檢查,要添加的第一個輸入是%v1–需要保留從變量到綁定值的映射查找, %v1是一個日志。
不同的數據結構,影響編寫轉換的方式。為什么需要 let 綁定?PL 是一個相當成熟的領域。
為什么可能需要綁定
let 綁定的一個關鍵用法,指定計算范圍。下面的例子,沒有使用 let 綁定。
試圖決定應該在哪里評估 node 時,問題就出現了%1。特別是,雖然文本格式似乎建議應該%1,在if 范圍之外評估節點,但 AST(如圖所示)不建議這樣做。實際上,數據流圖從未定義評估范圍。這在語義中,引入了一些歧義。
有閉包時,這種歧義變得更加有趣??紤]以下程序,返回一個閉包。不知道應該在哪里計算%1;可以在閉包內部或外部。
fn (%x) {
%1 = log(%x)
%2 = fn(%y) {
add(%y, %1)
}
%2
}
let 綁定解決了這個問題,值的計算發生在 let 節點。在這兩個程序中,如果%1 = log(%x)更改成let %v1 = log(%x),明確指定計算位置,在 if 范圍和閉包外。let-binding 提供了更精確的計算站點規范,生成后端代碼時,可能很有用(此類規范在 IR 中)。
另一方面,不指定計算范圍的數據流形式,確實有優勢——在生成代碼時,無需擔心將 let 放在哪里。數據流表單為后面的passes,決定將評估點放在哪里。在優化的初始階段,當發現方便時,使用程序的數據流形式,可能不是一個壞主意。Relay 中的許多優化,都是為了優化數據流程序編寫的。
將 IR 降低到實際運行時程序時,需要精確計算的范圍。使用子函數和閉包時,希望明確指定計算范圍,應該發生在哪里。Let-binding 可用于后期執行特定優化,解決問題。
對 IR 轉換的影響
大多數函數式編程語言,用 A 范式分析,不需要表達式DAG。
Relay同時支持數據流形式和 let 綁定。讓框架開發人員熟悉的表示。如何編寫 pass,有一些影響:
? 如果來自數據流背景,要處理 let,將 var 映射到表達式,遇到 var 時,執行查找。需要一個從表達式到轉換表達式的映射。有效地刪除程序中的所有lets。
? 如果來自 PL 背景,喜歡 A 范式,將提供 A 范式pass的數據流。
? 對于 PL 人員,當實現某些東西(例如,數據流到 ANF 的轉換)時,表達式可以是 DAG,應該使用 Map<Expr, Result> 訪問表達式,計算一次轉換后的結果,結果表達式保持公共結構。
有一些額外的高級概念,例如符號形狀推理,未涵蓋的多態函數。
參考鏈接:
https://tvm.apache.org/docs/dev/relay_intro.html
總結
以上是生活随笔為你收集整理的Relay IR表示的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: TVM Operator Invento
- 下一篇: AI中pass架构设计优化