Понимание регистрации операций и связывания ядра в TensorFlow
Я довольно новичок в TensorFlow и сейчас смотрю на разработку пользовательских операций. Я уже прочитал официальный учебник, но чувствую, что многое происходит за кулисами, и я не всегда хочу помещать свои пользовательские операции в каталог user_ops.
В качестве примера я взял пример word2vec
который использует пользовательский оп "Skipgram", регистрация которого определена здесь:
/word2vec_ops.cc
и чья реализация ядра здесь:
/word2vec_kernels.cc
Глядя на файл сборки, я пытался создать отдельные цели
1) bazel build -c opt tensorflow/models/embedding:word2vec_ops
Это создает кучу объектных файлов, как и ожидалось.
2) bazel build -c opt tensorflow/models/embedding:word2vec_kernels
То же самое для этого.
3) bazel build -c opt tensorflow/models/embedding:word2vec_kernels:gen_word2vec
Эта последняя сборка использует пользовательское правило, а именно tf_op_gen_wrapper_py
https://github.com/tensorflow/tensorflow/blob/master/tensorflow/tensorflow.bzl#L197-L231
Интересно отметить, что это зависит только от регистрации Op, а не от самого ядра.
Ведь выше, если я построю py_binary
сам с помощью
bazel build -c opt tensorflow/models/embedding:word2vec
это работает нормально, но я не вижу, где и как связан код ядра C++?
Кроме того, я также хотел бы понять tf_op_gen_wrapper_py
правило и вся процедура компиляции / компоновки, которая идет за кулисы для регистрации ops.
Благодарю.
1 ответ
При добавлении нового типа операции в TensorFlow, есть два основных шага:
Регистрация "операции", которая включает в себя определение интерфейса для операции, и
Регистрация одного или нескольких "ядер", которая включает в себя определение реализации (й) для операции, возможно, с помощью специализированных реализаций для различных типов данных или типов устройств (таких как CPU или GPU).
Оба этапа включают в себя написание кода на C++. Регистрация оп использует REGISTER_OP()
макрос, и регистрация ядра использует REGISTER_KERNEL_BUILDER()
макрос. Эти макросы создают статические инициализаторы, которые запускаются при загрузке модуля, содержащего их. Существует два основных механизма регистрации оп и ядра:
Статическое связывание с базовой библиотекой TensorFlow и статическая инициализация.
Динамическое связывание во время выполнения, используя
tf.load_op_library()
функция.
В случае "Skipgram"
Мы используем вариант 1 (статическое связывание). Здесь операции связаны с основной библиотекой TensorFlow, а ядра связаны здесь. (Обратите внимание, что это не идеально: word2vec
Операции были созданы до того, как мы tf.load_op_library()
и поэтому не было никакого механизма для их динамического связывания.) Следовательно, ops и ядра регистрируются при первой загрузке TensorFlow (в import tensorflow as tf
). Если бы они были созданы сегодня, они были бы динамически загружены, так что они были бы зарегистрированы только в случае необходимости. (В коде SyntaxNet есть пример динамической загрузки.)
tf_op_gen_wrapper_py()
rule в Bazel берет список зависимостей op -library и генерирует обертки Python для этих операций. Причина, по которой это правило зависит только от регистрации операции, заключается в том, что оболочки Python полностью определяются интерфейсом операции, который определяется в регистрации операции. Примечательно, что интерфейс Python не знает, существуют ли специализированные ядра для определенного типа или устройства. Генератор оболочки связывает регистрацию операций в простой двоичный файл C++, который генерирует код Python для каждого из зарегистрированных операций. Обратите внимание, что если вы используете tf.load_op_library()
Вам не нужно вызывать генератор-обертку самостоятельно, потому что tf.load_op_library()
сгенерирует необходимый код во время выполнения.