오늘 현지 TensorFlow Everywhere 이벤트에 참석하세요!

Grappler를 사용한 TensorFlow 그래프 최적화

TensorFlow.org에서 보기 Google Colab에서 실행하기 GitHub에서소스 보기 노트북 다운로드하기

개요

TensorFlow는 그래프와 Eager 실행을 모두 사용하여 계산을 실행합니다. tf.Graph에는 계산 단위를 나타내는 tf.Operation 객체(ops) 및 ops 간에 흐르는 데이터 단위를 나타내는 tf.Tensor 객체가 포함되어 있습니다.

Grappler는 TensorFlow 런타임의 기본 그래프 최적화 시스템입니다. Grappler는 그래프 모드(tf.function 내)에서 최적화를 적용하여 그래프 단순화 및 함수 본문 인라인과 같은 기타 고급 최적화를 통해 TensorFlow 계산 성능을 향상하여 절차 간 최적화를 가능하게 합니다. tf.Graph를 최적화하면 리소스를 계산하기 위한 그래프 노드 매핑을 최적화하여 기기의 최대 메모리 사용량을 줄이고 하드웨어 사용률을 향상할 수 있습니다.

tf.Graph 최적화를 더 세밀하게 제어하려면 tf.config.optimizer.set_experimental_options()를 사용하세요.

사용 가능한 그래프 옵티마이저

Grappler는 MetaOptimizer라는 최상위 드라이버를 통해 그래프 최적화를 수행합니다. 다음 그래프 최적화 옵티마이저를 TensorFlow에서 사용할 수 있습니다.

  • 상수 폴딩 옵티마이저 - 가능한 경우 그래프에서 상수 노드를 폴딩하여 텐서의 값을 정적으로 추론하고 상수를 사용하는 결과를 구체화합니다.
  • 산술 옵티마이저 - 공통 하위 식을 제거하고 산술문을 단순화하여 산술 연산을 단순화합니다.
  • 레이아웃 옵티마이저 - 텐서 레이아웃을 최적화하여 컨볼루션과 같이 데이터 형식에 종속적인 연산을 더 효율적으로 실행합니다.
  • Remapper 옵티마이저 - 공통으로 발생하는 하위 그래프를 최적화된 융합 모놀리식 커널로 대체하여 하위 그래프를 더 효율적인 구현으로 다시 매핑합니다.
  • 메모리 옵티마이저 - 그래프를 분석하여 각 연산의 최대 메모리 사용량을 검사하고 GPU 메모리를 CPU로 교체하기 위한 CPU-GPU 메모리 복사 작업을 삽입하여 최대 메모리 사용량을 줄입니다.
  • 종속성 옵티마이저 - 제어 종속성을 제거하거나 재배열하여 모델 단계의 중요 경로를 단축하거나 다른 최적화를 가능하게 합니다. 또한, Identity와 같이 효과적으로 no-ops인 노드를 제거합니다.
  • 잘라내기 옵티마이저 - 그래프 출력에 영향을 미치지 않는 노드를 잘라냅니다. 일반적으로 그래프 크기를 줄이고 다른 Grappler 패스에서 처리 속도를 높이기 위해 먼저 실행됩니다.
  • 함수 옵티마이저 -TensorFlow 프로그램의 함수 라이브러리를 최적화하고 함수 본문을 인라인 처리하여 다른 절차 간에 최적화를 가능하게 합니다.
  • 형상 옵티마이저 - 형상 및 형상 관련 정보를 기반으로 작동하는 하위 그래프를 최적화합니다.
  • 자동 병렬 옵티마이저 - 배치 차원을 따라 분할하여 그래프를 자동으로 병렬화합니다. 이 옵티마이저는 기본적으로 꺼져(OFF) 있습니다.
  • 루프 옵티마이저 - 루프 고정 하위 그래프를 루프 밖으로 가져오고 루프에서 중복 스택 연산을 제거하여 그래프 제어 흐름을 최적화합니다. 또한, 정적으로 알려진 트립 카운트로 루프를 최적화하고 조건부에서 정적으로 알려진 데드 분기를 제거합니다.
  • 범위가 지정된 할당자 옵티마이저 - 데이터 이동을 줄이고 일부 연산을 통합하기 위해 범위가 지정된 할당자를 소개합니다.
  • 호스트 옵티마이저로 고정 - 작은 연산을 CPU로 전환합니다. 이 옵티마이저는 기본적으로 꺼져(OFF) 있습니다.
  • 자동 혼합 정밀 옵티마이저 - 성능 향상을 위해 가능한 경우 데이터 유형을 float16으로 변환합니다. 현재 GPU에만 적용됩니다.
  • 디버그 스트리퍼 - 그래프에서 tf.debugging.Assert , tf.debugging.check_numericstf.print와 같은 디버깅 연산과 관련된 노드를 제거합니다. 이 옵티마이저는 기본적으로 꺼져(OFF) 있습니다.

설정

import numpy as np
import timeit
import traceback
import contextlib


import tensorflow as tf

옵티마이저 상태를 쉽게 전환할 수 있는 컨텍스트 관리자를 작성합니다.

@contextlib.contextmanager
def options(options):
  old_opts = tf.config.optimizer.get_experimental_options()
  tf.config.optimizer.set_experimental_options(options)
  try:
    yield
  finally:
    tf.config.optimizer.set_experimental_options(old_opts)

Grappler의 유무와 관계없이 실행 성능 비교하기

TensorFlow 2 이상에서는 기본적으로 즉시 실행합니다. tf.function을 사용하여 기본 실행을 그래프 모드로 전환합니다. Grappler는 백그라운드에서 자동으로 실행되어 위의 그래프 최적화를 적용하고 실행 성능을 향상합니다.

상수 폴딩 옵티마이저

예비 예제로서, 상수에 대한 연산을 수행하고 출력을 반환하는 함수를 고려합니다.

def test_function_1():
  @tf.function
  def simple_function(input_arg):
    print('Tracing!')
    a = tf.constant(np.random.randn(2000,2000), dtype = tf.float32)
    c = a
    for n in range(50):
      c = c@a
    return tf.reduce_mean(c+input_arg)

  return simple_function

상수 폴딩 옵티마이저를 끄고 함수를 실행합니다.

with options({'constant_folding': False}):
  print(tf.config.optimizer.get_experimental_options())
  simple_function = test_function_1()
  # Trace once
  x = tf.constant(2.2)
  simple_function(x)
  print("Vanilla execution:", timeit.timeit(lambda: simple_function(x), number = 1), "s")
{'constant_folding': False, 'disable_model_pruning': False, 'disable_meta_optimizer': False}
Tracing!
Vanilla execution: 0.07474954700001035 s

상수 폴딩 옵티마이저를 활성화하고 함수를 다시 실행하여 함수 실행의 속도 향상을 관찰합니다.

with options({'constant_folding': True}):
  print(tf.config.optimizer.get_experimental_options())
  simple_function = test_function_1()
  # Trace once
  x = tf.constant(2.2)
  simple_function(x)
  print("Constant folded execution:", timeit.timeit(lambda: simple_function(x), number = 1), "s")
{'constant_folding': True, 'disable_model_pruning': False, 'disable_meta_optimizer': False}
Tracing!
Constant folded execution: 0.0006391860001713212 s

디버그 스트리퍼 옵티마이저

입력 인수의 숫자 값을 확인하여 반환하는 간단한 함수를 고려합니다.

def test_function_2():
  @tf.function
  def simple_func(input_arg):
    output = input_arg
    tf.debugging.check_numerics(output, "Bad!")
    return output
  return simple_func

먼저, 디버그 스트리퍼 옵티마이저를 끈 상태에서 함수를 실행합니다.

test_func = test_function_2()
p1 = tf.constant(float('inf'))
try:
  test_func(p1)
except tf.errors.InvalidArgumentError as e:
  traceback.print_exc(limit=2)
Traceback (most recent call last):
  File "<ipython-input-1-1ac473fdfbab>", line 4, in <module>
    test_func(p1)
  File "/tmpfs/src/tf_docs_env/lib/python3.6/site-packages/tensorflow/python/eager/def_function.py", line 780, in __call__
    result = self._call(*args, **kwds)
tensorflow.python.framework.errors_impl.InvalidArgumentError: 2 root error(s) found.
  (0) Invalid argument:  Bad! : Tensor had Inf values
     [[node CheckNumerics (defined at <ipython-input-1-cbee1561c83e>:5) ]]
  (1) Invalid argument:  Bad! : Tensor had Inf values
     [[node CheckNumerics (defined at <ipython-input-1-cbee1561c83e>:5) ]]
     [[Identity/_4]]
0 successful operations.
0 derived errors ignored. [Op:__inference_simple_func_131]

Errors may have originated from an input operation.
Input Source operations connected to node CheckNumerics:
 input_arg (defined at <ipython-input-1-1ac473fdfbab>:4)

Input Source operations connected to node CheckNumerics:
 input_arg (defined at <ipython-input-1-1ac473fdfbab>:4)

Function call stack:
simple_func -> simple_func


tf.debugging.check_numericstest_func에 대한 Inf 인수로 인해 유효하지 않은 인수 오류를 발생시킵니다.

디버그 스트리퍼 옵티마이저를 활성화하고 함수를 다시 실행합니다.

with options({'debug_stripper': True}):
  test_func2 = test_function_2()
  p1 = tf.constant(float('inf'))
  try:
    test_func2(p1)
  except tf.errors.InvalidArgumentError as e:
    traceback.print_exc(limit=2)

디버그 스트리퍼 옵티마이저는 그래프에서 tf.debug.check_numerics 노드를 제거하고 오류를 발생시키지 않고 함수를 실행합니다.

요약

TensorFlow 런타임은 Grappler를 사용하여 실행 전에 그래프를 자동으로 최적화합니다. tf.config.optimizer.set_experimental_options를 사용하여 다양한 그래프 옵티마이저를 활성화하거나 비활성화하세요.

Grappler에 대한 자세한 정보는 TensorFlow 그래프 최적화를 참조하세요.