此页面由 Cloud Translation API 翻译。
Switch to English

使用AOT编译

什么是tfcompile?

tfcompile是一个独立的工具,可以提前(AOT)将TensorFlow图编译为可执行代码。它可以减少二进制文件的总大小,还可以避免一些运行时开销。 tfcompile典型用例是将推理图编译为移动设备的可执行代码。

TensorFlow图通常由TensorFlow运行时执行。这会为执行图中的每个节点带来一些运行时开销。这也导致更大的总二进制大小,因为除了图形本身之外,还需要TensorFlow运行时的代码。 tfcompile生成的可执行代码不使用TensorFlow运行时,而仅依赖于计算中实际使用的内核。

编译器建立在XLA框架之上。将TensorFlow桥接到XLA框架的代码位于tensorflow / compiler下

tfcompile有什么作用?

tfcompile接受一个由TensorFlow概念的提要和获取标识的子图,并生成实现该子图的函数。 feeds是函数的输入参数,而fetches是函数的输出参数。提要必须完全指定所有输入;结果修剪后的子图不能包含占位符或变量节点。通常将所有占位符和变量指定为提要,以确保结果子图不再包含这些节点。生成的函数打包为cc_library ,其中头文件导出函数签名,而目标文件包含实现。用户编写代码以适当地调用生成的函数。

使用tfcompile

本节详细介绍了从TensorFlow子图中使用tfcompile生成可执行二进制文件的高级步骤。这些步骤是:

  • 步骤1:配置子图进行编译
  • 步骤2:使用tf_library构建宏来编译子图
  • 步骤3:编写代码以调用子图
  • 步骤4:创建最终的二进制文件

步骤1:配置子图进行编译

标识与生成的函数的输入和输出参数相对应的提要和提取。然后在tensorflow.tf2xla.Config原型中配置feedsfetches

 # Each feed is a positional input argument for the generated function.  The order
# of each entry matches the order of each input argument.  Here “x_hold” and “y_hold”
# refer to the names of placeholder nodes defined in the graph.
feed {
  id { node_name: "x_hold" }
  shape {
    dim { size: 2 }
    dim { size: 3 }
  }
}
feed {
  id { node_name: "y_hold" }
  shape {
    dim { size: 3 }
    dim { size: 2 }
  }
}

# Each fetch is a positional output argument for the generated function.  The order
# of each entry matches the order of each output argument.  Here “x_y_prod”
# refers to the name of a matmul node defined in the graph.
fetch {
  id { node_name: "x_y_prod" }
}
 

步骤2:使用tf_library构建宏来编译子图

此步骤使用tf_library构建宏将图形转换为cc_librarycc_library由一个目标文件和一个头文件组成,该目标文件包含从图形生成的代码,该头文件提供对所生成代码的访问。 tf_library利用tfcompile将TensorFlow图编译为可执行代码。

 load("//tensorflow/compiler/aot:tfcompile.bzl", "tf_library")

# Use the tf_library macro to compile your graph into executable code.
tf_library(
    # name is used to generate the following underlying build rules:
    # <name>           : cc_library packaging the generated header and object files
    # <name>_test      : cc_test containing a simple test and benchmark
    # <name>_benchmark : cc_binary containing a stand-alone benchmark with minimal deps;
    #                    can be run on a mobile device
    name = "test_graph_tfmatmul",
    # cpp_class specifies the name of the generated C++ class, with namespaces allowed.
    # The class will be generated in the given namespace(s), or if no namespaces are
    # given, within the global namespace.
    cpp_class = "foo::bar::MatMulComp",
    # graph is the input GraphDef proto, by default expected in binary format.  To
    # use the text format instead, just use the ‘.pbtxt’ suffix.  A subgraph will be
    # created from this input graph, with feeds as inputs and fetches as outputs.
    # No Placeholder or Variable ops may exist in this subgraph.
    graph = "test_graph_tfmatmul.pb",
    # config is the input Config proto, by default expected in binary format.  To
    # use the text format instead, use the ‘.pbtxt’ suffix.  This is where the
    # feeds and fetches were specified above, in the previous step.
    config = "test_graph_tfmatmul.config.pbtxt",
)
 

要为此示例生成GraphDef原型(test_graph_tfmatmul.pb),请运行make_test_graphs.py并使用--out_dir标志指定输出位置。

典型的图形包含代表通过训练学习的权重的Variables ,但是tfcompile无法编译包含Variables的子图。 Frozen_graph.py工具使用存储在检查点文件中的值将变量转换为常量。为了方便起见, tf_library宏支持运行该工具的freeze_checkpoint参数。有关更多示例,请参见tensorflow / compiler / aot / tests / BUILD

显示在已编译子图中的常量将直接编译为生成的代码。要将常量传递给生成的函数,而不是对其进行编译,只需将其作为提要传递。

有关tf_library构建宏的详细信息,请参见tfcompile.bzl

有关基础tfcompile工具的详细信息,请参见tfcompile_main.cc

步骤3:编写代码以调用子图

此步骤使用在上一步中由tf_library构建宏生成的头文件( test_graph_tfmatmul.h )调用生成的代码。头文件位于与构建包相对应的bazel-bin目录中,并根据在tf_library构建宏上设置的name属性来命名。例如,为test_graph_tfmatmul生成的标头将为test_graph_tfmatmul.h 。以下是所生成内容的缩写。生成的文件位于bazel-bin ,包含其他有用的注释。

 namespace foo {
namespace bar {

// MatMulComp represents a computation previously specified in a
// TensorFlow graph, now compiled into executable code.
class MatMulComp {
 public:
  // AllocMode controls the buffer allocation mode.
  enum class AllocMode {
    ARGS_RESULTS_AND_TEMPS,  // Allocate arg, result and temp buffers
    RESULTS_AND_TEMPS_ONLY,  // Only allocate result and temp buffers
  };

  MatMulComp(AllocMode mode = AllocMode::ARGS_RESULTS_AND_TEMPS);
  ~MatMulComp();

  // Runs the computation, with inputs read from arg buffers, and outputs
  // written to result buffers. Returns true on success and false on failure.
  bool Run();

  // Arg methods for managing input buffers. Buffers are in row-major order.
  // There is a set of methods for each positional argument.
  void** args();

  void set_arg0_data(float* data);
  float* arg0_data();
  float& arg0(size_t dim0, size_t dim1);

  void set_arg1_data(float* data);
  float* arg1_data();
  float& arg1(size_t dim0, size_t dim1);

  // Result methods for managing output buffers. Buffers are in row-major order.
  // Must only be called after a successful Run call. There is a set of methods
  // for each positional result.
  void** results();


  float* result0_data();
  float& result0(size_t dim0, size_t dim1);
};

}  // end namespace bar
}  // end namespace foo
 

生成的C ++类称为MatMulCompfoo::bar的命名空间,因为那是cpp_class在指定tf_library宏。所有生成的类都有相似的API,唯一的区别是处理arg和结果缓冲区的方法。这些方法根据缓冲区的数量和类型而有所不同,缓冲区的数量和类型由tf_library宏的feedfetch参数指定。

在生成的类中管理三种类型的缓冲区: args代表输入, results代表输出,而temps代表内部用于执行计算的临时缓冲区。默认情况下,所生成类的每个实例都会为您分配和管理所有这些缓冲区。 AllocMode构造函数参数可用于更改此行为。所有缓冲区都与64字节边界对齐。

生成的C ++类只是XLA生成的低级代码的包装。

基于tfcompile_test.cc调用生成的函数的tfcompile_test.cc

 #define EIGEN_USE_THREADS
#define EIGEN_USE_CUSTOM_THREAD_POOL

#include <iostream>
#include "third_party/eigen3/unsupported/Eigen/CXX11/Tensor"
#include "tensorflow/compiler/aot/tests/test_graph_tfmatmul.h" // generated

int main(int argc, char** argv) {
  Eigen::ThreadPool tp(2);  // Size the thread pool as appropriate.
  Eigen::ThreadPoolDevice device(&tp, tp.NumThreads());


  foo::bar::MatMulComp matmul;
  matmul.set_thread_pool(&device);

  // Set up args and run the computation.
  const float args[12] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
  std::copy(args + 0, args + 6, matmul.arg0_data());
  std::copy(args + 6, args + 12, matmul.arg1_data());
  matmul.Run();

  // Check result
  if (matmul.result0(0, 0) == 58) {
    std::cout << "Success" << std::endl;
  } else {
    std::cout << "Failed. Expected value 58 at 0,0. Got:"
              << matmul.result0(0, 0) << std::endl;
  }

  return 0;
}
 

步骤4:创建最终的二进制文件

此步骤将步骤2中tf_library生成的库与步骤3中编写的代码结合起来,以创建最终的二进制文件。以下是示例bazel BUILD文件。

 # Example of linking your binary
# Also see //tensorflow/compiler/aot/tests/BUILD
load("//tensorflow/compiler/aot:tfcompile.bzl", "tf_library")

# The same tf_library call from step 2 above.
tf_library(
    name = "test_graph_tfmatmul",
    ...
)

# The executable code generated by tf_library can then be linked into your code.
cc_binary(
    name = "my_binary",
    srcs = [
        "my_code.cc",  # include test_graph_tfmatmul.h to access the generated header
    ],
    deps = [
        ":test_graph_tfmatmul",  # link in the generated object file
        "//third_party/eigen3",
    ],
    linkopts = [
          "-lpthread",
    ]
)