跳至主要内容

子圖

子圖是把 graph 中的一組節點收合成單一節點。預設組合在拖入畫布時只會展開一次;子圖則保留共用定義。每個實例都指向同一份定義,因此編輯定義會更新所有實例。

子圖只屬於所在的 graph 檔案。定義儲存在該 graph 的 subgraphs 清單中,因此存檔或匯出的 graph 仍是單一可攜檔案,不需要額外安裝其他內容。

把選取收合起來

  1. 選取兩個以上的節點(Shift+點擊,或按住 Shift 拖曳方框)。
  2. 在其中一個節點上按右鍵,選收合成子圖
  3. 輸入方塊名稱。預設名稱是 Subgraph,之後也可以從上方的路徑列重新命名。

選取的節點會由一個實例節點取代。每條跨越選取範圍的連線都會變成該節點的邊界連接埠,並沿用其代表的內部連接埠名稱。從 Start 節點進入選取範圍的 trigger 連線也會保留:進入方塊的一條連線會重新分接至原本被觸發的所有節點。

收合與展開各自都是單一步驟的復原操作;按一次 Ctrl+Z 即可將 graph 與其定義完整還原。

什麼情況下會拒絕收合

收合會在下列情況遭到拒絕,以免畫布顯示與實際執行結構不符:

拒絕的情況原因
選取的節點少於兩個只有一個節點的方塊等同於該節點本身。
選到 Start 節點Start 節點定義整個 graph 的起點;若放入可重用方塊,每個實例都會各自含有起點。
選到註記註記是說明,不參與計算。
夾在兩個選取節點中間的節點沒被選到方塊會先將值傳給該節點,再接收它的輸出;畫布看似有循環,但展開後的 graph 並沒有。錯誤訊息會列出需要一併選取的節點。

編輯方塊

雙擊實例節點,或按右鍵並選擇進入子圖,即可在畫布上開啟其定義。上方的路徑列會顯示 主圖 ▸ 我的方塊 這類路徑。你可以拖曳、連接、刪除、編輯參數與復原變更。選取實例後按 Enter 可開啟節點詳情;其子圖分頁會列出每個邊界連接埠所對應的內部節點與連接埠,並提供進入子圖按鈕。

在方塊內復原只會影響其內部。進入時會使用獨立的復原紀錄;離開時則恢復外層紀錄,因此不會復原方塊外的操作。

離開方塊會在外層產生單一步驟的復原紀錄。 方塊內的所有變更會合併為外層紀錄的一筆;離開後按一次 Ctrl+Z 會還原該次進入期間的所有變更,不影響方塊外的變更。若未修改便離開,則不會新增復原紀錄。

點擊路徑列中的方塊名稱即可重新命名(以唯讀模式檢視 graph 時無法重新命名)。按返回會離開一層;按主圖會直接回到最外層。

在方塊內刪除節點時,該節點提供的邊界連接埠也會消失,外部連至該連接埠的連線則會一併移除。

兩個實例,一份定義

複製實例節點,或將同一組內容收合兩次,兩個節點都會指向同一份定義。從任一實例編輯定義時,兩者都會更新。這種共用定義的重用方式是攤平式預設組合無法提供的。

刪除方塊的最後一個實例後,其定義不會再存入 graph 檔案,因此檔案不會保留畫布上沒有對應實例的方塊。復原會同時恢復實例與定義。

:::note 第一版不支援個別實例的參數 同一個子圖的所有實例都是完全相同的方塊。你無法只為其中一個實例設定不同的學習率;如有此需求,請將該參數留在方塊外並連接至方塊。個別實例覆寫預計在後續版本提供。 :::

執行含子圖的 graph

不需要額外設定。執行前,伺服器會使用展開預設組合的相同機制,將每個實例內聯展開。因此,收合前後的 graph 行為相同;依序收合、執行、展開並再次執行,會得到相同的數值。

執行期間,實例節點會顯示彙總狀態:第一個內部節點開始時顯示執行中,最後一個內部節點結束時顯示已完成;只要任何內部節點失敗或提早停止,就會立即顯示錯誤已中斷

執行時,內部節點會使用加上命名空間的識別碼,格式為 <instance>/<node>;教學檢視器與驗證訊息也會顯示此格式。

跨越邊界的驗證

驗證也會先展開實例,因此會檢查方塊內部,包括跨邊界的連接埠型別不符、未連接的必要輸入,以及畫布上看不到的定義內部循環。這類問題的錯誤路徑會同時指出邊界兩側:

Graph contains a cycle: blk1/relu -> blk1/conv -> blk1/relu
(crosses subgraph instance(s): blk1)

錯誤會列出每個外層方塊,因此位於第二層的循環會同時指出畫布上可見的方塊及其中的方塊(crosses subgraph instance(s): blk1, blk1/inner)。

若子圖直接或間接包含自身,系統會在執行前指出其名稱並拒絕執行。

巢狀深度最多為 10 層,與預設組合的上限相同。

儲存位置

專案目錄模式下,檔案分拆方式與最外層 graph 相同:

  • graphs/<name>.graph.json——定義本身:節點、連線與介面。
  • layout/<name>.layout.json——subgraphPositions,也就是每個方塊內各節點的位置。

因此,變更連線會出現在程式碼審查差異中,但在方塊內拖曳節點不會。

匯出成 Python

匯出 → 匯出為 Python 會為每個子圖實例產生一個函式

# ========================== Subgraph functions ==========================


def subgraph_myblock(ctx, results, provided):
"subgraph 'myblock' - instance 'blk1'."
# ScalarMultiply -> Print
results['blk1/mul'] = n03_scalarmultiply(
ctx,
tensor=_port(results['src'], 'tensor'),
)
results['blk1/p'] = n04_print(
ctx,
value=_port(results['blk1/mul'], 'tensor'),
)


# ============================ Flow functions ============================


def flow_1(ctx, results, provided):
'Start -> TensorCreate -> ScalarMultiply -> Print'
start = results['start'] = n01_start(ctx)
src = results['src'] = n02_tensorcreate(ctx)
subgraph_myblock(ctx, results, provided)

方塊結構會保留在匯出檔案中,不會攤平成節點呼叫清單。每個產生的節點函式也會附上一行註解,指出它來自哪個子圖。

只有一種例外,匯出檔案會直接註明:方塊內的節點必須連續執行,才能產生為單一函式。如果執行到一半必須先執行方塊外的節點,例如方塊先連到外部節點再連回來,或方塊內有節點沒有任何輸入,匯出器會改為依實際執行順序將這些節點內聯寫出,並在註解中指出中斷原因。腳本仍可執行,結果也與畫布一致,只是不會產生額外的子圖函式。

第一版的限制

  • 不支援個別實例的參數覆寫(見上方說明)。
  • 子圖只屬於單一 graph,目前沒有共用的子圖庫。
  • 放在子圖內的 GraphInput / GraphOutput 節點不會納入該 graph 對外公開的 API 合約;請將它們留在最外層。
  • 畫布會透過右鍵選單或 Ctrl/Cmd+B,為實例節點提供略過這個節點。執行時會驗證失敗並指出該實例:Bypass is not supported on subgraph instance(s): blk1。bypass 只能在節點類別宣告的連接埠之間傳遞值,而邊界連接埠沒有這類宣告。請對實例選擇取消略過,或進入方塊並略過內部節點。
  • 匯出 → 匯出為子圖會建立預設組合而不是方塊;這兩項功能都使用「子圖」一詞。匯出會拒絕含有已收合方塊的畫布,並列出方塊名稱。儲存在伺服器上的預設組合只包含節點與連線,無法儲存方塊定義。如果沒有這項驗證,預設組合會包含沒有定義的 subgraph:<id> 節點,且無法脫離來源 graph 執行。請先在每個方塊上按右鍵並選擇在這裡展開子圖,再進行匯出。