Cette page a été traduite par l'API Cloud Translation.
Switch to English

Utilisation de la compilation AOT

Qu'est-ce que tfcompile?

tfcompile est un outil autonome qui compile à l'avance les graphiques TensorFlow en code exécutable. Cela peut réduire la taille binaire totale et éviter des surcharges d'exécution. Un cas d'utilisation typique de tfcompile est de compiler un graphe d'inférence en code exécutable pour les appareils mobiles.

Le graphe TensorFlow est normalement exécuté par le runtime TensorFlow. Cela entraîne une surcharge d'exécution pour l'exécution de chaque nœud du graphique. Cela conduit également à une taille binaire totale plus grande, car le code du runtime TensorFlow doit être disponible, en plus du graphique lui-même. Le code exécutable produit par tfcompile n'utilise pas le runtime TensorFlow et n'a que des dépendances sur les noyaux qui sont réellement utilisés dans le calcul.

Le compilateur est construit sur le framework XLA. Le code reliant TensorFlow au framework XLA réside sous tensorflow / compiler .

Que fait tfcompile?

tfcompile prend un sous-graphe, identifié par les concepts TensorFlow de flux et d'extraction, et génère une fonction qui implémente ce sous-graphe. Les feeds sont les arguments d'entrée de la fonction et les fetches sont les arguments de sortie de la fonction. Toutes les entrées doivent être entièrement spécifiées par les flux; le sous-graphe élagué résultant ne peut pas contenir de nœuds d'espace réservé ou de variable. Il est courant de spécifier tous les espaces réservés et variables en tant que flux, ce qui garantit que le sous-graphe résultant ne contient plus ces nœuds. La fonction générée est conditionnée sous la forme d'une cc_library , avec un fichier d'en-tête exportant la signature de la fonction et un fichier objet contenant l'implémentation. L'utilisateur écrit du code pour appeler la fonction générée le cas échéant.

Utilisation de tfcompile

Cette section détaille les étapes de haut niveau pour générer un binaire exécutable avec tfcompile partir d'un sous-graphe TensorFlow. Les étapes sont:

  • Étape 1: configurer le sous-graphe à compiler
  • Étape 2: Utilisez la macro de construction tf_library pour compiler le sous-graphe
  • Étape 3: Écrivez le code pour appeler le sous-graphe
  • Étape 4: Créez le binaire final

Étape 1: configurer le sous-graphe à compiler

Identifiez les flux et les extractions qui correspondent aux arguments d'entrée et de sortie de la fonction générée. Configurez ensuite les feeds et les fetches dans un proto tensorflow.tf2xla.Config .

 # 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" }
}
 

Étape 2: Utilisez la macro de construction tf_library pour compiler le sous-graphe

Cette étape convertit le graphique en une cc_library à l'aide de la macro de construction tf_library . La cc_library compose d'un fichier objet contenant le code généré à partir du graphe, ainsi qu'un fichier d'en-tête qui donne accès au code généré. tf_library utilise tfcompile pour compiler le graphe TensorFlow en code exécutable.

 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",
)
 

Pour générer le proto GraphDef (test_graph_tfmatmul.pb) pour cet exemple, exécutez make_test_graphs.py et spécifiez l'emplacement de sortie avec l'indicateur --out_dir.

Les graphiques typiques contiennent des Variables représentant les poids appris via l'apprentissage, mais tfcompile ne peut pas compiler un sous-graphe contenant des Variables . L'outil freeze_graph.py convertit les variables en constantes, en utilisant les valeurs stockées dans un fichier de point de contrôle. Par commodité, la macro tf_library prend en charge l'argument freeze_checkpoint , qui exécute l'outil. Pour plus d'exemples, consultez tensorflow / compiler / aot / tests / BUILD .

Les constantes qui apparaissent dans le sous-graphe compilé sont compilées directement dans le code généré. Pour passer les constantes dans la fonction générée, plutôt que de les avoir compilées, transmettez-les simplement en tant que flux.

Pour plus de détails sur la macro de construction tf_library , consultez tfcompile.bzl .

Pour plus de détails sur l'outil tfcompile sous-jacent, consultez tfcompile_main.cc .

Étape 3: Écrivez le code pour appeler le sous-graphe

Cette étape utilise le fichier d'en-tête ( test_graph_tfmatmul.h ) généré par la macro de génération tf_library à l'étape précédente pour appeler le code généré. Le fichier d'en-tête se trouve dans le bazel-bin correspondant au package de construction et est nommé en fonction de l'attribut name défini dans la macro de construction tf_library . Par exemple, l'en-tête généré pour test_graph_tfmatmul serait test_graph_tfmatmul.h . Vous trouverez ci-dessous une version abrégée de ce qui est généré. Le fichier généré, dans bazel-bin , contient des commentaires supplémentaires utiles.

 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
 

La classe C ++ générée est appelée MatMulComp dans l'espace de noms foo::bar , car c'était la cpp_class spécifiée dans la macro tf_library . Toutes les classes générées ont une API similaire, la seule différence étant les méthodes de gestion des tampons arg et résultat. Ces méthodes diffèrent en fonction du nombre et des types de tampons, qui ont été spécifiés par les arguments d' feed et de fetch de la macro tf_library .

Il existe trois types de tampons gérés dans la classe générée: les args représentant les entrées, les results représentant les sorties et les temps représentant les tampons temporaires utilisés en interne pour effectuer le calcul. Par défaut, chaque instance de la classe générée alloue et gère tous ces tampons pour vous. L'argument du constructeur AllocMode peut être utilisé pour modifier ce comportement. Tous les tampons sont alignés sur des limites de 64 octets.

La classe C ++ générée n'est qu'un wrapper autour du code de bas niveau généré par XLA.

Exemple d'appel de la fonction générée basée sur 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;
}
 

Étape 4: Créez le binaire final

Cette étape combine la bibliothèque générée par tf_library à l'étape 2 et le code écrit à l'étape 3 pour créer un binaire final. Voici un exemple de fichier 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",
    ]
)