그래프 및 tf.function 소개

TensorFlow.org에서 보기 Run in Google Colab View source on GitHub Download notebook

개요

This guide goes beneath the surface of TensorFlow and Keras to demonstrate how TensorFlow works. If you instead want to immediately get started with Keras, check out the collection of Keras guides.

In this guide, you'll learn how TensorFlow allows you to make simple changes to your code to get graphs, how graphs are stored and represented, and how you can use them to accelerate your models.

참고: TensorFlow 1.x에만 익숙한 사용자를 위해 이 가이드는 매우 다른 그래프 뷰를 보여줍니다.

This is a big-picture overview that covers how tf.function allows you to switch from eager execution to graph execution. For a more complete specification of tf.function, go to the tf.function guide.

그래프란 무엇인가요?

In the previous three guides, you ran TensorFlow eagerly. This means TensorFlow operations are executed by Python, operation by operation, and returning results back to Python.

즉시 실행에는 몇 가지 고유한 장점이 있지만 그래프 실행은 Python 외부에서 이식성을 가능하게 하며 성능이 더 우수한 경향이 있습니다. 그래프 실행은 텐서 계산이 tf.Graph 또는 간단히 "그래프"라고도 하는 TensorFlow 그래프로 실행됨을 의미합니다.

그래프는 계산의 단위를 나타내는 tf.Operation 객체와 연산 간에 흐르는 데이터의 단위를 나타내는 tf.Tensor 객체의 세트를 포함합니다. 데이터 구조는 tf.Graph 컨텍스트에서 정의됩니다. 그래프는 데이터 구조이므로 원래 Python 코드 없이 모두 저장, 실행 및 복원할 수 있습니다.

그래프는 계산의 단위를 나타내는 tf.Operation 객체와 연산 간에 흐르는 데이터의 단위를 나타내는 tf.Tensor 객체의 세트를 포함합니다. 데이터 구조는 tf.Graph 컨텍스트에서 정의됩니다. 그래프는 데이터 구조이므로 원래 Python 코드 없이 모두 저장, 실행 및 복원할 수 있습니다.

A simple TensorFlow graph

그래프의 이점

그래프를 사용하면 유연성이 크게 향상됩니다. 모바일 애플리케이션, 임베디드 기기 및 백엔드 서버와 같은 Python 인터프리터가 없는 환경에서 TensorFlow 그래프를 사용할 수 있습니다. TensorFlow는 그래프를 Python에서 내보낼 때 저장된 모델의 형식으로 그래프를 사용합니다.

그래프는 쉽게 최적화되어 컴파일러가 다음과 같은 변환을 수행할 수 있습니다.

  • 계산에서 상수 노드를 접어 텐서의 값을 정적으로 추론합니다("일정한 접기").
  • 독립적인 계산의 하위 부분을 분리하여 스레드 또는 기기 간에 분할합니다.
  • 공통 하위 표현식을 제거하여 산술 연산을 단순화합니다.

위와 같은 변환 및 기타 속도 향상을 수행하기 위한 전체 최적화 시스템으로 Grappler가 있습니다.

요약하면, 그래프는 TensorFlow가 빠르게, 병렬로, 그리고 효율적으로 여러 기기에서 실행할 때 아주 유용합니다.

However, you still want to define your machine learning models (or other computations) in Python for convenience, and then automatically construct graphs when you need them.

그래프 이용하기

tf.function을 직접 호출 또는 데코레이터로 사용하여 TensorFlow에서 그래프를 만들고 실행합니다. tf.function은 일반 함수를 입력으로 받아 Function을 반환합니다. Function은 Python 함수로부터 TensorFlow 그래프를 빌드하는 Python callable입니다. Python의 경우와 동일한 방식으로 Function를 사용합니다.

import tensorflow as tf
import timeit
from datetime import datetime
# Define a Python function.
def a_regular_function(x, y, b):
  x = tf.matmul(x, y)
  x = x + b
  return x

# `a_function_that_uses_a_graph` is a TensorFlow `Function`.
a_function_that_uses_a_graph = tf.function(a_regular_function)

# Make some tensors.
x1 = tf.constant([[1.0, 2.0]])
y1 = tf.constant([[2.0], [3.0]])
b1 = tf.constant(4.0)

orig_value = a_regular_function(x1, y1, b1).numpy()
# Call a `Function` like a Python function.
tf_function_value = a_function_that_uses_a_graph(x1, y1, b1).numpy()
assert(orig_value == tf_function_value)
2021-08-14 00:38:13.353844: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:937] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2021-08-14 00:38:13.361949: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:937] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2021-08-14 00:38:13.362842: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:937] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2021-08-14 00:38:13.364550: I tensorflow/core/platform/cpu_feature_guard.cc:142] This TensorFlow binary is optimized with oneAPI Deep Neural Network Library (oneDNN) to use the following CPU instructions in performance-critical operations:  AVX2 AVX512F FMA
To enable them in other operations, rebuild TensorFlow with the appropriate compiler flags.
2021-08-14 00:38:13.365128: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:937] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2021-08-14 00:38:13.366098: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:937] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2021-08-14 00:38:13.367022: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:937] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2021-08-14 00:38:13.955497: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:937] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2021-08-14 00:38:13.956396: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:937] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2021-08-14 00:38:13.957224: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:937] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2021-08-14 00:38:13.958040: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1510] Created device /job:localhost/replica:0/task:0/device:GPU:0 with 14648 MB memory:  -> device: 0, name: Tesla V100-SXM2-16GB, pci bus id: 0000:00:05.0, compute capability: 7.0
2021-08-14 00:38:14.688889: I tensorflow/compiler/mlir/mlir_graph_optimization_pass.cc:185] None of the MLIR Optimization Passes are enabled (registered 2)

겉보기에 Function은 TensorFlow 연산을 사용하여 작성하는 일반 함수처럼 보입니다. 그러나 그 안을 들여다 보면 매우 다릅니다. Function하나의 API 뒤에서 여러 tf.Graph를 캡슐화합니다. 바로 이런 이유로 Function이 속도 및 배포 가능성과 같은 그래프 실행의 이점을 제공하는 것입니다.

tf.function은 함수 및 이 함수가 호출하는 다른 모든 함수에 적용됩니다.

def inner_function(x, y, b):
  x = tf.matmul(x, y)
  x = x + b
  return x

# Use the decorator to make `outer_function` a `Function`.
@tf.function
def outer_function(x):
  y = tf.constant([[2.0], [3.0]])
  b = tf.constant(4.0)

  return inner_function(x, y, b)

# Note that the callable will create a graph that
# includes `inner_function` as well as `outer_function`.
outer_function(tf.constant([[1.0, 2.0]])).numpy()
array([[12.]], dtype=float32)

TensorFlow 1.x를 사용한 경우 Placeholder 또는 tf.Sesssion을 정의할 필요가 없었음을 알 수 있습니다.

Python 함수를 그래프로 변환하기

TensorFlow를 사용하여 작성하는 모든 함수에는 if-then 절, 루프, break, return, continue 등과 같은 내장된 TF 연산과 Python 논리가 혼합되어 있습니다. TensorFlow 연산은 tf.Graph에 의해 쉽게 캡처되지만 Python 관련 논리는 그래프의 일부가 되기 위해 추가 단계를 거쳐야 합니다. tf.function은 AutoGraph(tf.autograph)라는 라이브러리를 사용하여 Python 코드를 그래프 생성 코드로 변환합니다.

def simple_relu(x):
  if tf.greater(x, 0):
    return x
  else:
    return 0

# `tf_simple_relu` is a TensorFlow `Function` that wraps `simple_relu`.
tf_simple_relu = tf.function(simple_relu)

print("First branch, with graph:", tf_simple_relu(tf.constant(1)).numpy())
print("Second branch, with graph:", tf_simple_relu(tf.constant(-1)).numpy())
First branch, with graph: 1
Second branch, with graph: 0

Though it is unlikely that you will need to view graphs directly, you can inspect the outputs to check the exact results. These are not easy to read, so no need to look too carefully!

# This is the graph-generating output of AutoGraph.
print(tf.autograph.to_code(simple_relu))
def tf__simple_relu(x):
    with ag__.FunctionScope('simple_relu', 'fscope', ag__.ConversionOptions(recursive=True, user_requested=True, optional_features=(), internal_convert_user_code=True)) as fscope:
        do_return = False
        retval_ = ag__.UndefinedReturnValue()

        def get_state():
            return (do_return, retval_)

        def set_state(vars_):
            nonlocal retval_, do_return
            (do_return, retval_) = vars_

        def if_body():
            nonlocal retval_, do_return
            try:
                do_return = True
                retval_ = ag__.ld(x)
            except:
                do_return = False
                raise

        def else_body():
            nonlocal retval_, do_return
            try:
                do_return = True
                retval_ = 0
            except:
                do_return = False
                raise
        ag__.if_stmt(ag__.converted_call(ag__.ld(tf).greater, (ag__.ld(x), 0), None, fscope), if_body, else_body, get_state, set_state, ('do_return', 'retval_'), 2)
        return fscope.ret(retval_, do_return)
# This is the graph itself.
print(tf_simple_relu.get_concrete_function(tf.constant(1)).graph.as_graph_def())
node {
  name: "x"
  op: "Placeholder"
  attr {
    key: "_user_specified_name"
    value {
      s: "x"
    }
  }
  attr {
    key: "dtype"
    value {
      type: DT_INT32
    }
  }
  attr {
    key: "shape"
    value {
      shape {
      }
    }
  }
}
node {
  name: "Greater/y"
  op: "Const"
  attr {
    key: "dtype"
    value {
      type: DT_INT32
    }
  }
  attr {
    key: "value"
    value {
      tensor {
        dtype: DT_INT32
        tensor_shape {
        }
        int_val: 0
      }
    }
  }
}
node {
  name: "Greater"
  op: "Greater"
  input: "x"
  input: "Greater/y"
  attr {
    key: "T"
    value {
      type: DT_INT32
    }
  }
}
node {
  name: "cond"
  op: "StatelessIf"
  input: "Greater"
  input: "x"
  attr {
    key: "Tcond"
    value {
      type: DT_BOOL
    }
  }
  attr {
    key: "Tin"
    value {
      list {
        type: DT_INT32
      }
    }
  }
  attr {
    key: "Tout"
    value {
      list {
        type: DT_BOOL
        type: DT_INT32
      }
    }
  }
  attr {
    key: "_lower_using_switch_merge"
    value {
      b: true
    }
  }
  attr {
    key: "_read_only_resource_inputs"
    value {
      list {
      }
    }
  }
  attr {
    key: "else_branch"
    value {
      func {
        name: "cond_false_34"
      }
    }
  }
  attr {
    key: "output_shapes"
    value {
      list {
        shape {
        }
        shape {
        }
      }
    }
  }
  attr {
    key: "then_branch"
    value {
      func {
        name: "cond_true_33"
      }
    }
  }
}
node {
  name: "cond/Identity"
  op: "Identity"
  input: "cond"
  attr {
    key: "T"
    value {
      type: DT_BOOL
    }
  }
}
node {
  name: "cond/Identity_1"
  op: "Identity"
  input: "cond:1"
  attr {
    key: "T"
    value {
      type: DT_INT32
    }
  }
}
node {
  name: "Identity"
  op: "Identity"
  input: "cond/Identity_1"
  attr {
    key: "T"
    value {
      type: DT_INT32
    }
  }
}
library {
  function {
    signature {
      name: "cond_false_34"
      input_arg {
        name: "cond_placeholder"
        type: DT_INT32
      }
      output_arg {
        name: "cond_identity"
        type: DT_BOOL
      }
      output_arg {
        name: "cond_identity_1"
        type: DT_INT32
      }
    }
    node_def {
      name: "cond/Const"
      op: "Const"
      attr {
        key: "dtype"
        value {
          type: DT_BOOL
        }
      }
      attr {
        key: "value"
        value {
          tensor {
            dtype: DT_BOOL
            tensor_shape {
            }
            bool_val: true
          }
        }
      }
      experimental_debug_info {
        original_node_names: "cond/Const"
      }
    }
    node_def {
      name: "cond/Const_1"
      op: "Const"
      attr {
        key: "dtype"
        value {
          type: DT_BOOL
        }
      }
      attr {
        key: "value"
        value {
          tensor {
            dtype: DT_BOOL
            tensor_shape {
            }
            bool_val: true
          }
        }
      }
      experimental_debug_info {
        original_node_names: "cond/Const_1"
      }
    }
    node_def {
      name: "cond/Const_2"
      op: "Const"
      attr {
        key: "dtype"
        value {
          type: DT_INT32
        }
      }
      attr {
        key: "value"
        value {
          tensor {
            dtype: DT_INT32
            tensor_shape {
            }
            int_val: 0
          }
        }
      }
      experimental_debug_info {
        original_node_names: "cond/Const_2"
      }
    }
    node_def {
      name: "cond/Const_3"
      op: "Const"
      attr {
        key: "dtype"
        value {
          type: DT_BOOL
        }
      }
      attr {
        key: "value"
        value {
          tensor {
            dtype: DT_BOOL
            tensor_shape {
            }
            bool_val: true
          }
        }
      }
      experimental_debug_info {
        original_node_names: "cond/Const_3"
      }
    }
    node_def {
      name: "cond/Identity"
      op: "Identity"
      input: "cond/Const_3:output:0"
      attr {
        key: "T"
        value {
          type: DT_BOOL
        }
      }
      experimental_debug_info {
        original_node_names: "cond/Identity"
      }
    }
    node_def {
      name: "cond/Const_4"
      op: "Const"
      attr {
        key: "dtype"
        value {
          type: DT_INT32
        }
      }
      attr {
        key: "value"
        value {
          tensor {
            dtype: DT_INT32
            tensor_shape {
            }
            int_val: 0
          }
        }
      }
      experimental_debug_info {
        original_node_names: "cond/Const_4"
      }
    }
    node_def {
      name: "cond/Identity_1"
      op: "Identity"
      input: "cond/Const_4:output:0"
      attr {
        key: "T"
        value {
          type: DT_INT32
        }
      }
      experimental_debug_info {
        original_node_names: "cond/Identity_1"
      }
    }
    ret {
      key: "cond_identity"
      value: "cond/Identity:output:0"
    }
    ret {
      key: "cond_identity_1"
      value: "cond/Identity_1:output:0"
    }
    attr {
      key: "_construction_context"
      value {
        s: "kEagerRuntime"
      }
    }
    arg_attr {
      key: 0
      value {
        attr {
          key: "_output_shapes"
          value {
            list {
              shape {
              }
            }
          }
        }
      }
    }
  }
  function {
    signature {
      name: "cond_true_33"
      input_arg {
        name: "cond_identity_1_x"
        type: DT_INT32
      }
      output_arg {
        name: "cond_identity"
        type: DT_BOOL
      }
      output_arg {
        name: "cond_identity_1"
        type: DT_INT32
      }
    }
    node_def {
      name: "cond/Const"
      op: "Const"
      attr {
        key: "dtype"
        value {
          type: DT_BOOL
        }
      }
      attr {
        key: "value"
        value {
          tensor {
            dtype: DT_BOOL
            tensor_shape {
            }
            bool_val: true
          }
        }
      }
      experimental_debug_info {
        original_node_names: "cond/Const"
      }
    }
    node_def {
      name: "cond/Identity"
      op: "Identity"
      input: "cond/Const:output:0"
      attr {
        key: "T"
        value {
          type: DT_BOOL
        }
      }
      experimental_debug_info {
        original_node_names: "cond/Identity"
      }
    }
    node_def {
      name: "cond/Identity_1"
      op: "Identity"
      input: "cond_identity_1_x"
      attr {
        key: "T"
        value {
          type: DT_INT32
        }
      }
      experimental_debug_info {
        original_node_names: "cond/Identity_1"
      }
    }
    ret {
      key: "cond_identity"
      value: "cond/Identity:output:0"
    }
    ret {
      key: "cond_identity_1"
      value: "cond/Identity_1:output:0"
    }
    attr {
      key: "_construction_context"
      value {
        s: "kEagerRuntime"
      }
    }
    arg_attr {
      key: 0
      value {
        attr {
          key: "_output_shapes"
          value {
            list {
              shape {
              }
            }
          }
        }
      }
    }
  }
}
versions {
  producer: 808
  min_consumer: 12
}

대부분의 경우, tf.function은 특별한 고려없이 작동합니다. 그러나 몇 가지 주의 사항이 있으며 tf.function 안내서전체 AutoGraph 참조서가 도움이 될 수 있습니다.

다형성: 하나의 Function, 다수의 그래프

tf.Graph는 특정 유형의 입력에 특화되어 있습니다(예: 특정 dtype을 가진 텐서 또는 동일한 id()를 가진 객체).

새로운 dtypes 및 인수의 형상을 사용하여 Function을 호출할 때마다 Function은 새 인수에 대한 새로운 tf.Graph를 생성합니다. tf.Graph 입력의 dtypes과 형상은 입력 서명 또는 간단히 서명이라고 합니다.

Function은 해당 서명에 대응하는 tf.GraphConcreteFunction에 저장합니다. ConcreteFunctiontf.Graph를 감싸는 래퍼입니다.

@tf.function
def my_relu(x):
  return tf.maximum(0., x)

# `my_relu` creates new graphs as it sees more signatures.
print(my_relu(tf.constant(5.5)))
print(my_relu([1, -1]))
print(my_relu(tf.constant([3., -3.])))
tf.Tensor(5.5, shape=(), dtype=float32)
tf.Tensor([1. 0.], shape=(2,), dtype=float32)
tf.Tensor([3. 0.], shape=(2,), dtype=float32)

Function이 이 서명으로 이미 호출된 경우, Function은 새 tf.Graph를 생성하지 않습니다.

# These two calls do *not* create new graphs.
print(my_relu(tf.constant(-2.5))) # Signature matches `tf.constant(5.5)`.
print(my_relu(tf.constant([-1., 1.]))) # Signature matches `tf.constant([3., -3.])`.
tf.Tensor(0.0, shape=(), dtype=float32)
tf.Tensor([0. 1.], shape=(2,), dtype=float32)

여러 그래프로 뒷받침된다는 점에서 Function다형성입니다. 그 결과, 단일 tf.Graph로 나타낼 수 있는 것보다 더 많은 입력 유형을 지원할 수 있을뿐만 아니라 tf.Graph가 더 우수한 성능을 갖도록 최적화할 수 있습니다.

# There are three `ConcreteFunction`s (one for each graph) in `my_relu`.
# The `ConcreteFunction` also knows the return type and shape!
print(my_relu.pretty_printed_concrete_signatures())
my_relu(x)
  Args:
    x: float32 Tensor, shape=()
  Returns:
    float32 Tensor, shape=()

my_relu(x=[1, -1])
  Returns:
    float32 Tensor, shape=(2,)

my_relu(x)
  Args:
    x: float32 Tensor, shape=(2,)
  Returns:
    float32 Tensor, shape=(2,)

tf.function 사용하기

So far, you've learned how to convert a Python function into a graph simply by using tf.function as a decorator or wrapper. But in practice, getting tf.function to work correctly can be tricky! In the following sections, you'll learn how you can make your code work as expected with tf.function.

그래프 실행 vs 즉시 실행

Function의 코드는 즉시 실행 또는 그래프 실행이 가능합니다. 기본적으로 Function은 코드를 그래프로 실행합니다.

@tf.function
def get_MSE(y_true, y_pred):
  sq_diff = tf.pow(y_true - y_pred, 2)
  return tf.reduce_mean(sq_diff)
y_true = tf.random.uniform([5], maxval=10, dtype=tf.int32)
y_pred = tf.random.uniform([5], maxval=10, dtype=tf.int32)
print(y_true)
print(y_pred)
tf.Tensor([7 5 8 9 5], shape=(5,), dtype=int32)
tf.Tensor([9 1 6 0 0], shape=(5,), dtype=int32)
get_MSE(y_true, y_pred)
<tf.Tensor: shape=(), dtype=int32, numpy=26>

Function의 그래프가 동등한 Python 함수와 같은 계산을 수행하는지 확인하기 위해 tf.config.run_functions_eagerly(True)를 이용해 즉시 실행하도록 할 수 있습니다. 코드를 정상적으로 실행하는 대신 그래프를 생성하고 실행하는 Function의 역할을 해제시킬 때 스위치와 같이 이용되는 코드입니다.

tf.config.run_functions_eagerly(True)
get_MSE(y_true, y_pred)
<tf.Tensor: shape=(), dtype=int32, numpy=26>
# Don't forget to set it back when you are done.
tf.config.run_functions_eagerly(False)

However, Function can behave differently under graph and eager execution. The Python print function is one example of how these two modes differ. Let's check out what happens when you insert a print statement to your function and call it repeatedly.

@tf.function
def get_MSE(y_true, y_pred):
  print("Calculating MSE!")
  sq_diff = tf.pow(y_true - y_pred, 2)
  return tf.reduce_mean(sq_diff)

인쇄된 내용을 잘 살펴봅니다.

error = get_MSE(y_true, y_pred)
error = get_MSE(y_true, y_pred)
error = get_MSE(y_true, y_pred)
Calculating MSE!

출력 결과가 놀랍지 않나요? get_MSE세 번 호출되었지만 한 번만 인쇄되었습니다.

설명하자면, print 문은 Function이 원래 코드를 실행할 때 실행되며 이 때 "트레이싱"이라는 프로세스를 통해 그래프가 생성됩니다. 트레이싱은 TensorFlow 연산을 그래프로 캡처하고 print는 그래프에서 캡처되지 않습니다. 이 그래프는 세 번의 모든 호출시 실행되지만 Python 코드를 다시 실행하지는 않습니다.

실제로 그런지 검사하기 위해 그래프 실행을 해제하고 비교해 보겠습니다.

# Now, globally set everything to run eagerly to force eager execution.
tf.config.run_functions_eagerly(True)
# Observe what is printed below.
error = get_MSE(y_true, y_pred)
error = get_MSE(y_true, y_pred)
error = get_MSE(y_true, y_pred)
Calculating MSE!
Calculating MSE!
Calculating MSE!
tf.config.run_functions_eagerly(False)

printPython의 부작용이며 함수를 Function으로 변환할 때 알고 있어야 하는 다른 차이점들이 있습니다.

참고: 즉시 및 그래프 실행 모두에서 값을 인쇄하려면 tf.print를 대신 사용하세요.

tf.function 모범 사례

Function의 동작에 익숙해지려면 시간이 걸릴 수 있습니다. 빠르게 시작하려는 처음 사용자는 @tf.function으로 시험용 함수를 사용해 보면서 즉시 실행에서 그래프 실행으로 이동하는 과정을 체험할 수 있습니다.

tf.function을 위한 디자인은 그래프 호환 TensorFlow 프로그램을 작성하는 가장 좋은 방법일 수 있습니다. 다음은 몇 가지 팁입니다.

  • tf.config.run_functions_eagerly로 즉시 실행과 그래프 실행 사이를 조기에 자주 전환하여 두 모드가 서로 달라지는지, 언제 달라지는지 정확하게 파악합니다.
  • Python 함수 외부에서 tf.Variable을 실행하고 내부에서 수정합니다. keras.layers, keras.Modeltf.optimizers와 같이 tf.Variable을 사용하는 객체의 경우도 마찬가지입니다.
  • tf.Variables 및 Keras 객체를 제외하고 외부 Python 변수에 종속되는 함수 작성을 피합니다.
  • 텐서 및 기타 TensorFlow 유형을 입력으로 사용하는 함수를 작성하는 것이 좋습니다. 다른 객체 유형을 전달할 수 있지만 주의해야 합니다!
  • 성능 이점을 극대화하기 위해 tf.function 하에서 계산이 가능한 한 많이 포함되도록 합니다. 예를 들어 전체 훈룬 스텝 또는 전체 훈룬 루프를 데코레이션합니다.

속도 향상 확인하기

tf.function은 일반적으로 코드의 성능을 향상시키지만 속도 향상의 정도는 실행하는 계산의 종류에 따라 다릅니다. 작은 계산의 경우 그래프를 호출하는 오버헤드에 의해 지배될 수 있습니다. 다음과 같이 성능 차이를 측정할 수 있습니다.

x = tf.random.uniform(shape=[10, 10], minval=-1, maxval=2, dtype=tf.dtypes.int32)

def power(x, y):
  result = tf.eye(10, dtype=tf.dtypes.int32)
  for _ in range(y):
    result = tf.matmul(x, result)
  return result
print("Eager execution:", timeit.timeit(lambda: power(x, 100), number=1000))
Eager execution: 1.9946797530001277
power_as_graph = tf.function(power)
print("Graph execution:", timeit.timeit(lambda: power_as_graph(x, 100), number=1000))
Graph execution: 0.5878890819994922

tf.function is commonly used to speed up training loops, and you can learn more about it in Writing a training loop from scratch with Keras.

참고: 특히 코드가 TF 제어 흐름에서 과중하고 작은 텐서를 많이 사용하는 경우, 성능 개선의 효과를 높이기 위해 tf.function(jit_compile=True)를 시도해볼 수도 있습니다.

성능과 상충 관계

Graphs can speed up your code, but the process of creating them has some overhead. For some functions, the creation of the graph takes more time than the execution of the graph. This investment is usually quickly paid back with the performance boost of subsequent executions, but it's important to be aware that the first few steps of any large model training can be slower due to tracing.

모델 크기에 관계없이, 빈번한 트레이싱은 피해야 합니다. tf.function 가이드에 리트레이싱을 피하기 위해 입력 사양을 설정하고 텐서 인수를 사용하는 방법에 관한 설명이 나와 있습니다. 비정상적으로 성능이 저하되는 것으로 판단되면 실수로 리트레이싱하고 있지 않은지 확인하는 것이 좋습니다.

Function은 언제 트레이싱합니까?

Function이 트레이싱을 수행하는 경우를 알아보려면 코드에 print 문을 추가합니다. 대략적인 규칙으로, Function은 트레이싱할 때마다 print 문을 실행합니다.

@tf.function
def a_function_with_python_side_effect(x):
  print("Tracing!") # An eager-only side effect.
  return x * x + tf.constant(2)

# This is traced the first time.
print(a_function_with_python_side_effect(tf.constant(2)))
# The second time through, you won't see the side effect.
print(a_function_with_python_side_effect(tf.constant(3)))
Tracing!
tf.Tensor(6, shape=(), dtype=int32)
tf.Tensor(11, shape=(), dtype=int32)
# This retraces each time the Python argument changes,
# as a Python argument could be an epoch count or other
# hyperparameter.
print(a_function_with_python_side_effect(2))
print(a_function_with_python_side_effect(3))
Tracing!
tf.Tensor(6, shape=(), dtype=int32)
Tracing!
tf.Tensor(11, shape=(), dtype=int32)

New Python arguments always trigger the creation of a new graph, hence the extra tracing.

다음 단계

You can learn more about tf.function on the API reference page and by following the Better performance with tf.function guide.