W pliku z modelem są dwie rzeczy: miliardy liczb ułożonych w macierze i opis kolejności, w jakiej te macierze mają być przez siebie przemnożone. Ta druga część nazywa się grafem obliczeniowym.
Czym jest graf obliczeniowy
Graf obliczeniowy to formalny opis operacji składających się na przejście modelu od wejścia do wyjścia: weź wektor, pomnóż przez tę macierz, dodaj tamten wektor, znormalizuj wynik, przepuść przez funkcję aktywacji, powtórz całość tyle razy, ile model ma warstw. Węzłami grafu są operacje, krawędziami przepływ danych.
Czego w grafie nie ma
Warto zauważyć, czego ten opis nie zawiera. Nie ma informacji, w jakiej kolejności fizycznie czytać dane z pamięci. Nie ma podziału pracy między rdzenie. Nie ma decyzji, czy dwie sąsiadujące operacje wykonać osobno, czy za jednym zamachem. Nie ma nic o tym, jakim sprzętem to policzyć.
Graf mówi, co ma wyjść. Nie mówi, jak to zrobić.
To różnica między listą składników a organizacją kuchni. Przepis mówi, że trzeba pokroić cebulę i ją zeszklić. Nie mówi, czy przygotować wszystko wcześniej na deskach, czy biegać do lodówki po każdy składnik osobno. Danie wyjdzie identyczne, czas nie.
Jądra obliczeniowe
Runtime przepisuje graf na jądra obliczeniowe — gotowe procedury napisane pod konkretną architekturę sprzętu, realizujące jedną operację lub ich sekwencję. Jedna operacja w grafie może odpowiadać kilkunastu różnym jądrom, z których runtime wybiera to najlepsze dla danego kształtu danych i danej karty.
Właśnie dlatego ten sam graf skompilowany przez dwie różne implementacje daje identyczny wynik liczbowy w czasie różniącym się wielokrotnie.
Gdzie się z tym spotkasz
Najczęstszym formatem wymiany grafów jest ONNX — otwarty standard pozwalający wyeksportować model z jednego frameworka i uruchomić go w innym środowisku, w tym w przeglądarce. Eksport do ONNX-a to w praktyce zamrożenie grafu wraz z wagami w jednym przenośnym pliku.