ما هو tfcompile؟
tfcompile
هي أداة مستقلة تعمل في الوقت المحدّد مسبقًا (AOT) على تجميع رسوم TensorFlow البيانية
في شكل رمز برمجي تنفيذي. يمكنها تقليل إجمالي الحجم الثنائي وتجنب
بعض النفقات العامة في وقت التشغيل. تتمثل حالة استخدام tfcompile
النموذجية في تجميع
رسم بياني للاستنتاج في رمز برمجي تنفيذي للأجهزة الجوّالة.
يتم تنفيذ الرسم البياني TensorFlow عادةً من خلال وقت تشغيل TensorFlow. يتحمل هذا بعض الوقت الزائد لتنفيذ كل عقدة في الرسم البياني. يؤدي ذلك أيضًا إلى إجمالي حجم ثنائي أكبر، حيث يجب أن تكون التعليمات البرمجية لوقت تشغيل TensorFlow متاحة، بالإضافة إلى الرسم البياني نفسه. لا يستخدم الرمز القابل للتنفيذ الذي يتم إنشاؤه من خلال tfcompile
وقت تشغيل TensorFlow، بل يعتمد فقط على النواة المستخدَمة في العمليات الحسابية.
تم إنشاء برنامج التحويل البرمجي على إطار عمل XLA. يتوفّر الرمز البرمجي الذي يربط بين TensorFlow وإطار عمل XLA ضمن tensorflow/compiler.
ما هي وظيفة tfcompile؟
تأخذ tfcompile
رسمًا بيانيًا فرعيًا، يتم تحديده من خلال مفاهيم TensorFlow الخاصة بالخلاصات وعمليات الجلب، وتنشئ دالة تنفّذ هذا الرسم البياني الفرعي.
feeds
هي وسيطات الإدخال للدالة، وfetches
هي وسيطات الناتج للدالة. يجب تحديد جميع الإدخالات بالكامل عن طريق الخلاصات، ولا يمكن أن يحتوي الرسم البياني الفرعي الناتج والمقتبس على عنصر نائب أو عُقد متغيرة. من الشائع تحديد جميع العناصر النائبة والمتغيرات كخلاصات، مما يضمن أن الرسم البياني الفرعي الناتج لم يعد يحتوي على هذه العُقد. يتم حزم الدالة التي تم إنشاؤها كـ cc_library
، مع ملف عنوان يُصدِّر توقيع الدالة، وملف كائن يحتوي على التنفيذ. يكتب المستخدم تعليمة برمجية
لاستدعاء الدالة التي تم إنشاؤها حسب الحاجة.
استخدام tfcompile
يوضّح هذا القسم بالتفصيل الخطوات عالية المستوى لإنشاء برنامج ثنائي قابل للتنفيذ باستخدام
tfcompile
من رسم بياني فرعي من TensorFlow. الخطوات كالآتي:
- الخطوة 1: تهيئة الرسم البياني الفرعي لتجميعه
- الخطوة 2: استخدام ماكرو إصدار
tf_library
لتجميع الرسم البياني الفرعي - الخطوة 3: كتابة التعليمة البرمجية لاستدعاء الرسم البياني الفرعي
- الخطوة 4: إنشاء البرنامج الثنائي النهائي
الخطوة 1: تهيئة الرسم البياني الفرعي لتجميعه
حدد الخلاصات وعمليات الجلب التي تتوافق مع وسيطات الإدخال والإخراج للدالة التي تم إنشاؤها. بعد ذلك، عليك إعداد feeds
وfetches
في نموذج 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" }
}
الخطوة 2: استخدام وحدة ماكرو إصدار tf_library لتجميع الرسم البياني الفرعي
تحوِّل هذه الخطوة الرسم البياني إلى cc_library
باستخدام ماكرو الإصدار tf_library
. يتكوّن cc_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
. تحوّل أداة freeze_graph.py
المتغيرات إلى ثوابت باستخدام القيم المخزنة في ملف نقطة تفتيش. تيسيرًا للأمر، تتيح وحدة الماكرو tf_library
الوسيطة freeze_checkpoint
التي تشغّل الأداة. لمزيد من الأمثلة، راجع
tensorflow/compiler/aot/tests/BUILD.
يتم تجميع الثوابت التي تظهر في الرسم البياني الفرعي المجمَّع مباشرةً في التعليمة البرمجية التي تم إنشاؤها. لتمرير الثوابت في الدالة المُنشأة، بدلاً من تجميعها، ما عليك سوى تمريرها كخلاصات.
للحصول على تفاصيل عن وحدة ماكرو إصدار tf_library
، راجِع tfcompile.bzl.
للحصول على تفاصيل حول أداة tfcompile
الأساسية، يُرجى الاطّلاع على
tfcompile_main.cc.
الخطوة 3: كتابة التعليمة البرمجية لاستدعاء الرسم البياني الفرعي
تستخدِم هذه الخطوة ملف العنوان (test_graph_tfmatmul.h
) الذي تم إنشاؤه من خلال وحدة ماكرو الإصدار tf_library
في الخطوة السابقة لاستدعاء الرمز الذي تم إنشاؤه. يقع ملف الرأس في دليل bazel-bin
المقابل لحزمة الإنشاء، وتتم تسميته استنادًا إلى سمة الاسم التي تم تعيينها في ماكرو الإنشاء tf_library
. على سبيل المثال، العنوان الذي تم إنشاؤه للسمة 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++ التي تم إنشاؤها MatMulComp
في مساحة الاسم foo::bar
،
لأنها كانت cpp_class
المحدّدة في ماكرو tf_library
. تحتوي جميع الفئات التي تم إنشاؤها على واجهة برمجة تطبيقات مشابهة، والفرق الوحيد هو طرق التعامل مع الوسيطة والتخزينات الاحتياطية للنتائج. تختلف هذه الطرق استنادًا إلى عدد المخازن الاحتياطية وأنواعها، التي تم تحديدها من خلال الوسيطتين feed
وfetch
إلى الماكرو tf_library
.
وثمة ثلاثة أنواع من الموارد الاحتياطية المُدارة ضمن الفئة التي تم إنشاؤها: args
التي تمثِّل المدخلات وresults
تمثِّل المخرجات وtemps
التي تمثِّل الموارد الاحتياطية المؤقتة المستخدَمة داخليًا لإجراء العملية الحسابية. وبشكلٍ افتراضي، تخصص كل مثيل للفئة التي تم إنشاؤها كل هذه الموارد الاحتياطية وتديرها نيابة عنك. يمكن استخدام وسيطة الدالة الإنشائية AllocMode
لتغيير هذا السلوك. تتم محاذاة جميع المخازن المؤقتة مع حدود 64 بايت.
فئة C++ التي يتم إنشاؤها هي مجرد برنامج تضمين حول التعليمة البرمجية منخفضة المستوى التي يتم إنشاؤها بواسطة XLA.
مثال على استدعاء الدالة التي تم إنشاؤها استنادًا إلى tfcompile_test.cc
:
#define EIGEN_USE_THREADS
#define EIGEN_USE_CUSTOM_THREAD_POOL
#include <iostream>
#include "third_party/eigen3/unsupported/Eigen/CXX11/Tensor"
#include "third_party/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: إنشاء البرنامج الثنائي النهائي
تجمع هذه الخطوة بين المكتبة التي تم إنشاؤها بواسطة tf_library
في الخطوة 2 والرمز البرمجي
المكتوب في الخطوة 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",
]
)