2.1 빌트인 함수 및 메시지 전달 API들
DGL에서 메시지 함수 는 한개의 인자 edges
를 갖는데, 이는 EdgeBatch
의 객체이다. 메시지 전달이 실행되는 동안 DGL은 에지 배치를 표현하기 위해서 이 객체를 내부적으로 생성한다. 이것은 3개의 맴버, src
, dst
, 그리고 data
를 갖고, 이는 각각 소스 노드, 목적지 노드, 그리고 에지의 피쳐를 의미한다.
축약 함수(reduce function) 는 한개의 인자 nodes
를 갖는데, 이는 NodeBatch
의 객체이다. 메시지 전달이 실행되는 동안 DGL은 노드 배치를 표현하기 위해서 이 객체를 내부적으로 생성한다. 이 객체는 mailbox
라는 맴버를 갖는데, 이는 배치에 속한 노드들에게 전달된 메시지들을 접근 방법을 제공한다. 가장 흔한 축약 함수로는 sum
, max
, min
등이 있다.
업데이트 함수 는 위에서 언급한 nodes
를 한개의 인자로 갖는다. 이 함수는 축약 함수
의 집계 결과에 적용되는데, 보통은 마지막 스탭에서 노드의 원래 피처와 이 결과와 결합하고, 그 결과를 노드의 피처로 저장한다.
DGL은 일반적으로 사용되는 메시지 전달 함수들과 축약 함수들을 dgl.function
네임스패이스에 빌트인 으로 구현하고 있다. 일반적으로, 가능한 경우라면 항상 DLG의 빌트인 함수를 사용하는 것을 권장하는데, 그 이유는 이 함수들은 가장 최적화된 형태로 구현되어 있고, 차원 브로드캐스팅을 자동으로 해주기 때문이다.
만약 여러분의 메시지 전달 함수가 빌트인 함수로 구현이 불가능하다면, 사용자 정의 메시지/축소 함수를 직접 구현할 수 있다. 이를 UDF 라고 한다.
빌트인 메시지 함수들은 단항(unary) 또는 이상(binary)이다. 단항의 경우 DGL은 copy
를 지원한다. 이항 함수로 DGL은 add
, sub
, mul
, div
, 그리고 dot
를 지원한다. 빌트인 메시지 함수의 이름 규칙은 다음과 같다. u
는 src
노드를, v
는 dst
노드를 그리고 e
는 edges
를 의미한다. 이 함수들에 대한 파라미터들은 관련된 노드와 에지의 입력과 출력 필드 이름을 지칭하는 문자열이다. 지원되는 빌트인 함수의 목록은 DGL Built-in Function 을 참고하자. 한가지 예를 들면, 소스 노드의 hu
피처와 목적지 노드의 hv
피처를 더해서 그 결과를 에지의 he
필드에 저장하는 것을 빌트인 함수 dgl.function.u_add_v('hu', 'hv', 'he')
를 사용해서 구현할 수 있다. 이와 동일한 기능을 하는 메시지 UDF는 다음과 같다.
def message_func(edges):
return {'he': edges.src['hu'] + edges.dst['hv']}
빌트인 축약 함수는 sum
, max
, min
그리고 mean
연산을 지원한다. 보통 축약 함수는 두개의 파라메터를 갖는데, 하나는 mailbox
의 필드 이름이고, 다른 하나는 노드 피처의 필드 이름이다. 이는 모두 문자열이다. 예를 들어, dgl.function.sum('m', 'h')
는 메시지 m
을 합하는 아래 축약 UDF와 같다.
import torch
def reduce_func(nodes):
return {'h': torch.sum(nodes.mailbox['m'], dim=1)}
UDF의 고급 사용법을 더 알고 싶으면 User-defined Functions 를 참고하자.
apply_edges()
를 사용해서 메시지 전달 함수를 호출하지 않고 에지별 연산만 호출하는 것도 가능하다. apply_edges()
는 파라미터로 메시지 함수를 받는데, 기본 설정으로는 모든 에지의 피쳐를 업데이트한다. 다음 예를 살펴보자.
import dgl.function as fn
graph.apply_edges(fn.u_add_v('el', 'er', 'e'))
메시지 전달을 위한 update_all()
는 하이레벨 API로 메시지 생성, 메시지 병합 그리고 노드 업데이트를 단일 호출로 합쳤는데, 전반적으로 최적화할 여지가 남아있다.
update_all()
의 파라메터들은 메시지 함수, 축약 함수, 그리고 업데이트 함수이다. update_all()
를 호출할 때 업데이트 함수를 지정하지 않는 경우, 업데이트 함수는 update_all
밖에서 수행될 수 있다. DGL은 이 방법을 권장하는데, 업데이트 함수는 코드를 간결하게 만들기 위해서 보통은 순수 텐서 연산으로 구현되어 있기 때문이다. 예를 들면, 다음과 같다.
def update_all_example(graph):
# store the result in graph.ndata['ft']
graph.update_all(fn.u_mul_e('ft', 'a', 'm'),
fn.sum('m', 'ft'))
# Call update function outside of update_all
final_ft = graph.ndata['ft'] * 2
return final_ft
이 함수는 소스 노드의 피처 ft
와 에지 피처 a
를 곱해서 메시지 m
을 생성하고, 메시지 m
들을 더해서 노드 피처 ft
를 업데이트하고, 마지막으로 final_ft
결과를 구하기 위해서 ft
에 2를 곱하고 있다. 호출이 완료되면 DGL은 중간에 사용된 메시지들 m
을 제거한다. 위 함수를 수학 공식으로 표현하면 다음과 같다.
DGL의 빌트인 함수는 부동소수점 데이터 타입을 지원한다. 즉, 피쳐들은 반드시 half
(float16
), float
, 또는 double
텐서여야만 한다. float16
데이터 타입에 대한 지원은 기본 설정에서는 비활성화되어 있다. 그 이유는 이를 지원하기 위해서는 sm_53
(Pascal, Volta, Turing, 그리고 Ampere 아키텍타)와 같은 최소한의 GPU 컴퓨팅 능력이 요구되기 때문이다.
사용자는 DGL 소스 컴파일을 통해서 mixed precision training을 위해서 float16을 활성화시킬 수 있다. (자세한 내용은 Mixed Precision Training 튜토리얼 참고)