
Один тензор, много возможностей: инженерные пользовательские операции в Tensorflow
25 июля 2025 г.Обзор контента
- Встройте расширенные функции в свой OP
- Условные проверки и проверка
- Регистрация OP
- Поддержка графического процессора
- Реализуйте градиент в Python
- Функции формы в C ++
- Создайте пакет PIP для пользовательского OP
Встройте расширенные функции в свой OP
Теперь, когда вы знаете, как построить базовую (и несколько ограниченную) OP и реализацию, мы рассмотрим некоторые из более сложных вещей, которые вам обычно понадобятся, чтобы встроить в свой OP. Это включает в себя:
- Условные проверки и проверка
- ОП регистрация
- Атрис
- АТРТ типы
- Полиморфизм
- Входы и выходы
- Обратная совместимость
- Поддержка графического процессора
- Скомпилирование ядра для устройства GPU
- Реализуйте градиент в Python
- Функции формы в C ++
Условные проверки и проверка
Приведенный выше пример предполагал, что OP применяется к тензору любой формы. Что если это применимо только к векторам? Это означает добавление проверки в вышеуказанную реализацию Opkernel.
void Compute(OpKernelContext* context) override {
// Grab the input tensor
const Tensor& input_tensor = context->input(0);
OP_REQUIRES(context, TensorShapeUtils::IsVector(input_tensor.shape()),
errors::InvalidArgument("ZeroOut expects a 1-D vector."));
// ...
}
Это утверждает, что ввод является вектором, и возвращает, установивInvalidArgument
Статус, если это не так. АOP_REQUIRES
Макро принимает три аргумента:
- А
context
, что может бытьOpKernelContext
илиOpKernelConstruction
Указатель (смtensorflow/core/framework/op_kernel.h
), для егоSetStatus()
метод - Состояние. Например, существуют функции для проверки формы тензора в
tensorflow/core/framework/tensor_shape.h
- Сама ошибка, которая представлена
Status
объект, смtensorflow/core/platform/status.h
Полем АStatus
имеет оба типа (частоInvalidArgument
, но см. Список типов) и сообщение. Функции для построения ошибки могут быть найдены вtensorflow/core/platform/errors.h
Полем
В качестве альтернативы, если вы хотите проверить, есть лиStatus
Объект, возвращаемый с какой -то функции, является ошибкой, и если это вернуть, используйтеOP_REQUIRES_OK
Полем Оба эти макроса возвращаются из функции по ошибке.
ОП регистрация
Атрис
OPS может иметь ATRS, значения которых устанавливаются, когда OP добавляется на график. Они используются для настройки OP, и их значения могут быть доступны как в реализации ядра, так и в типах входов и выходов в регистрации OP. Предпочитаю использовать вход вместо ATTR, когда это возможно, поскольку входные данные более гибки. Это связано с тем, что ATRS являются постоянными и должны быть определены во время построения графика. Напротив, входные данные являются тензорами, значения которых могут быть динамическими; То есть входы могут изменять каждый шаг, быть установленным с помощью подачи и т. Д. Атрицы используются для вещей, которые не могут быть сделаны с помощью входов: любая конфигурация, которая влияет на подпись (число или тип входов или выходов) или не может измениться от шага к шагу.
Вы определяете атрис при регистрации OP, указав его имя и введите, используяAttr
Метод, который ожидает спецификации формы:
<name>: <attr-type-expr>
где<name>
начинается с буквы и может состоять из буквенно -цифровых символов и недостатков, и<attr-type-expr>
тип выражения формыописано нижеПолем
Например, если вам нравитсяZeroOut
OP, чтобы сохранить указанный пользователем индекс, а не только 0-й элемент, вы можете зарегистрировать OP, как SO:
REGISTER_OP("ZeroOut")
.Attr("preserve_index: int")
.Input("to_zero: int32")
.Output("zeroed: int32");
(Обратите внимание, что наборТипы атрибутовотличается отtf.DType
используется для входов и выходов.)
Ваше ядро может затем получить доступ к этому ATTR в своем конструкторе черезcontext
Параметр:
class ZeroOutOp : public OpKernel {
public:
explicit ZeroOutOp(OpKernelConstruction* context) : OpKernel(context) {
// Get the index of the value to preserve
OP_REQUIRES_OK(context,
context->GetAttr("preserve_index", &preserve_index_));
// Check that preserve_index is positive
OP_REQUIRES(context, preserve_index_ >= 0,
errors::InvalidArgument("Need preserve_index >= 0, got ",
preserve_index_));
}
void Compute(OpKernelContext* context) override {
// ...
}
private:
int preserve_index_;
};
который затем можно использовать вCompute
Метод:
void Compute(OpKernelContext* context) override {
// ...
// We're using saved attr to validate potentially dynamic input
// So we check that preserve_index is in range
OP_REQUIRES(context, preserve_index_ < input.dimension(0),
errors::InvalidArgument("preserve_index out of range"));
// Set all the elements of the output tensor to 0
const int N = input.size();
for (int i = 0; i < N; i++) {
output_flat(i) = 0;
}
// Preserve the requested input value
output_flat(preserve_index_) = input(preserve_index_);
}
АТРТ типы
Следующие типы поддерживаются в ATTR:
string
: Любая последовательность байтов (не требуется UTF8).int
: Подписанное целое число.float
: Номер плавающей запятой.bool
: True или ложь.type
: Одно из (не-реф-) значенийDataType
Полемshape
: АTensorShapeProto
Полемlist(<type>)
: Список<type>
, где<type>
является одним из вышеперечисленных типов. Обратите внимание, чтоlist(list(<type>))
недействителен.
Смотрите также:op_def_builder.cc:FinalizeAttr
для окончательного списка.
Значения и ограничения по умолчанию
ATTR могут иметь значения по умолчанию, и некоторые типы ATTR могут иметь ограничения. Чтобы определить ATTR с ограничениями, вы можете использовать следующее<attr-type-expr>
S:
{'<string1>', '<string2>'}
: Значение должно быть строкой, которая имеет либо значение<string1>
или<string2>
Полем Название типа,string
, подразумевается, когда вы используете этот синтаксис. Это эмулирует перечисление:
REGISTER_OP("EnumExample")
.Attr("e: {'apple', 'orange'}");
{<type1>, <type2>}
: Значение типаtype
и должен быть одним из<type1>
или<type2>
, где<type1>
и<type2>
поддерживаютсяtf.DType
Полем Вы не указываете, что тип атристаtype
Полем Это подразумевается, когда у вас есть список типов в{...}
Полем Например, в этом случае ATTRt
это тип, который должен бытьint32
, аfloat
, илиbool
:
REGISTER_OP("RestrictedTypeExample")
.Attr("t: {int32, float, bool}");
Есть ярлыки для ограничений общего типа:
numbertype
: Типtype
ограничен численными (не строгими и небелыми) типами.realnumbertype
: Нравитьсяnumbertype
без сложных типов.quantizedtype
: Нравитьсяnumbertype
Но только квантованные типы числа.
Конкретные списки типов, разрешенные этимиNumberTypes()
) вtensorflow/core/framework/types.h
Полем В этом примере ATTRt
Должен быть один из числовых типов:
REGISTER_OP("NumberType")
.Attr("t: numbertype");
Для этого OP:
tf.number_type(t=tf.int32) # Valid
tf.number_type(t=tf.bool) # Invalid
Списки могут быть объединены с другими списками и отдельными типами. Следующий OP позволяет ATTRt
быть любым из цифровых типов или типа Bool:
REGISTER_OP("NumberOrBooleanType")
.Attr("t: {numbertype, bool}");
Для этого OP:
tf.number_or_boolean_type(t=tf.int32) # Valid
tf.number_or_boolean_type(t=tf.bool) # Valid
tf.number_or_boolean_type(t=tf.string) # Invalid
int >= <n>
: Значение должно быть int, значение которого больше или равно<n>
, где<n>
это естественное число. Например, следующая регистрация OP указывает, что ATTRa
Должен иметь значение, по крайней мере,2
:
REGISTER_OP("MinIntExample")
.Attr("a: int >= 2");
list(<type>) >= <n>
: Список типа<type>
чья длина больше или равна<n>
Полем Например, следующая регистрация OP указывает, что ATTRa
это список типов (либоint32
илиfloat
), и что их должно быть не менее 3:
REGISTER_OP("TypeListExample")
.Attr("a: list({int32, float}) >= 3");
Чтобы установить значение по умолчанию для ATTR (делая его необязательным в сгенерированном коде), добавьте= <default>
К концу, как в:
REGISTER_OP("AttrDefaultExample")
.Attr("i: int = 0");
Кроме того, можно указать как ограничение, так и значение по умолчанию:
REGISTER_OP("AttrConstraintAndDefaultExample")
.Attr("i: int >= 1 = 1");
Поддерживаемый синтаксис значения по умолчанию - это то, что будет использоваться в прото -представлении полученного определения графика.
Вот примеры того, как указать дефолт для всех типов:
REGISTER_OP("AttrDefaultExampleForAllTypes")
.Attr("s: string = 'foo'")
.Attr("i: int = 0")
.Attr("f: float = 1.0")
.Attr("b: bool = true")
.Attr("ty: type = DT_INT32")
.Attr("sh: shape = { dim { size: 1 } dim { size: 2 } }")
.Attr("te: tensor = { dtype: DT_INT32 int_val: 5 }")
.Attr("l_empty: list(int) = []")
.Attr("l_int: list(int) = [2, 3, 5, 7]");
Обратите внимание, в частности, что значения типаtype
использоватьtf.DType
Полем
Полиморфизм
Тип полиморфизма
Для OPS, который может принимать разные типы в качестве ввода или создавать разные типы выводов, вы можете указатьаттвВход или выходной типв регистрации OP. Обычно вы затем зарегистрировалиOpKernel
для каждого поддерживаемого типа.
Например, если вы хотитеZeroOut
OP для работы надfloat
S в дополнение кint32
S, ваша регистрация OP может выглядеть как:
REGISTER_OP("ZeroOut")
.Attr("T: {float, int32}")
.Input("to_zero: T")
.Output("zeroed: T");
Ваша регистрация OP теперь указывает, что тип ввода должен бытьfloat
, илиint32
и что его выход будет одинаковым типом, так как оба имеют типT
Полем
Именование
Входные данные, выходы и атрис, как правило, должны давать имена Snake_case. Единственным исключением является атрис, которые используются в качестве типа входа или в типе выхода. Эти атристы могут быть выведены при добавлении OP к графику, поэтому не появляются в функции OP. Например, это последнее определение Zeroout генерирует функцию Python, которая выглядит как:
def zero_out(to_zero, name=None):
"""...
Args:
to_zero: A `Tensor`. Must be one of the following types:
`float32`, `int32`.
name: A name for the operation (optional).
Returns:
A `Tensor`. Has the same type as `to_zero`.
"""
Еслиto_zero
проходитint32
Тенсор, тогдаT
автоматически устанавливается наint32
(Ну, на самом делеDT_INT32
) Эти предполагаемые ATRS получают капитализированные или названия Camelcase.
Сравните это с OP, который имеет тип ATTR, который определяет тип выхода:
REGISTER_OP("StringToNumber")
.Input("string_tensor: string")
.Output("output: out_type")
.Attr("out_type: {float, int32} = DT_FLOAT");
.Doc(R"doc(
Converts each string in the input Tensor to the specified numeric type.
)doc");
В этом случае пользователь должен указать тип вывода, как в сгенерированном Python:
def string_to_number(string_tensor, out_type=None, name=None):
"""Converts each string in the input Tensor to the specified numeric type.
Args:
string_tensor: A `Tensor` of type `string`.
out_type: An optional `tf.DType` from: `tf.float32, tf.int32`.
Defaults to `tf.float32`.
name: A name for the operation (optional).
Returns:
A `Tensor` of type `out_type`.
"""
Пример типа полиморфизма
#include "tensorflow/core/framework/op_kernel.h"
class ZeroOutInt32Op : public OpKernel {
// as before
};
class ZeroOutFloatOp : public OpKernel {
public:
explicit ZeroOutFloatOp(OpKernelConstruction* context)
: OpKernel(context) {}
void Compute(OpKernelContext* context) override {
// Grab the input tensor
const Tensor& input_tensor = context->input(0);
auto input = input_tensor.flat<float>();
// Create an output tensor
Tensor* output = NULL;
OP_REQUIRES_OK(context,
context->allocate_output(0, input_tensor.shape(), &output));
auto output_flat = output->template flat<float>();
// Set all the elements of the output tensor to 0
const int N = input.size();
for (int i = 0; i < N; i++) {
output_flat(i) = 0;
}
// Preserve the first input value
if (N > 0) output_flat(0) = input(0);
}
};
// Note that TypeConstraint<int32>("T") means that attr "T" (defined
// in the op registration above) must be "int32" to use this template
// instantiation.
REGISTER_KERNEL_BUILDER(
Name("ZeroOut")
.Device(DEVICE_CPU)
.TypeConstraint<int32>("T"),
ZeroOutInt32Op);
REGISTER_KERNEL_BUILDER(
Name("ZeroOut")
.Device(DEVICE_CPU)
.TypeConstraint<float>("T"),
ZeroOutFloatOp);
Чтобы сохранитьобратная совместимость, вам следует указатьзначение по умолчаниюПри добавлении атриста в существующий OP:
REGISTER_OP("ZeroOut")
.Attr("T: {float, int32} = DT_INT32")
.Input("to_zero: T")
.Output("zeroed: T")
Допустим, вы хотите добавить больше типов, скажем,double
:
REGISTER_OP("ZeroOut")
.Attr("T: {float, double, int32}")
.Input("to_zero: T")
.Output("zeroed: T");
Вместо того, чтобы писать другойOpKernel
С резервированным кодом, как указано выше, часто вы сможете использовать шаблон C ++. У вас все еще будет одна регистрация ядра (REGISTER_KERNEL_BUILDER
звонить) за перегрузку.
template <typename T>
class ZeroOutOp : public OpKernel {
public:
explicit ZeroOutOp(OpKernelConstruction* context) : OpKernel(context) {}
void Compute(OpKernelContext* context) override {
// Grab the input tensor
const Tensor& input_tensor = context->input(0);
auto input = input_tensor.flat<T>();
// Create an output tensor
Tensor* output = NULL;
OP_REQUIRES_OK(context,
context->allocate_output(0, input_tensor.shape(), &output));
auto output_flat = output->template flat<T>();
// Set all the elements of the output tensor to 0
const int N = input.size();
for (int i = 0; i < N; i++) {
output_flat(i) = 0;
}
// Preserve the first input value
if (N > 0) output_flat(0) = input(0);
}
};
// Note that TypeConstraint<int32>("T") means that attr "T" (defined
// in the op registration above) must be "int32" to use this template
// instantiation.
REGISTER_KERNEL_BUILDER(
Name("ZeroOut")
.Device(DEVICE_CPU)
.TypeConstraint<int32>("T"),
ZeroOutOp<int32>);
REGISTER_KERNEL_BUILDER(
Name("ZeroOut")
.Device(DEVICE_CPU)
.TypeConstraint<float>("T"),
ZeroOutOp<float>);
REGISTER_KERNEL_BUILDER(
Name("ZeroOut")
.Device(DEVICE_CPU)
.TypeConstraint<double>("T"),
ZeroOutOp<double>);
Если у вас более пары перегрузки, вы можете поместить регистрацию на макрос.
#include "tensorflow/core/framework/op_kernel.h"
#define REGISTER_KERNEL(type) \
REGISTER_KERNEL_BUILDER( \
Name("ZeroOut").Device(DEVICE_CPU).TypeConstraint<type>("T"), \
ZeroOutOp<type>)
REGISTER_KERNEL(int32);
REGISTER_KERNEL(float);
REGISTER_KERNEL(double);
#undef REGISTER_KERNEL
В зависимости от списка типов, для которых вы регистрируете ядро, вы можете использовать макрос, предоставленныйtensorflow/core/framework/register_types.h
:
#include "tensorflow/core/framework/op_kernel.h"
#include "tensorflow/core/framework/register_types.h"
REGISTER_OP("ZeroOut")
.Attr("T: realnumbertype")
.Input("to_zero: T")
.Output("zeroed: T");
template <typename T>
class ZeroOutOp : public OpKernel { ... };
#define REGISTER_KERNEL(type) \
REGISTER_KERNEL_BUILDER( \
Name("ZeroOut").Device(DEVICE_CPU).TypeConstraint<type>("T"), \
ZeroOutOp<type>)
TF_CALL_REAL_NUMBER_TYPES(REGISTER_KERNEL);
#undef REGISTER_KERNEL
Список входов и выходов
В дополнение к возможности принимать или производить разные типы, OPS может потреблять или производить переменное количество тензоров.
В следующем примере аттроT
держитсписоктипов и используется как тип обоих вводовin
и выводout
Полем Ввод и вывод - это списки тензоров такого типа (а число и типы тензоров на выходе совпадают с входом, поскольку оба имеют типT
)
REGISTER_OP("PolymorphicListExample")
.Attr("T: list(type)")
.Input("in: T")
.Output("out: T");
Вы также можете наложить ограничения на то, какие типы могут быть указаны в списке. В этом следующем случае ввод - списокfloat
иdouble
тензоры. ОП принимает, например, типы вводов(float, double, float)
и в этом случае выход вывода также будет(float, double, float)
Полем
REGISTER_OP("ListTypeRestrictionExample")
.Attr("T: list({float, double})")
.Input("in: T")
.Output("out: T");
Если вы хотите, чтобы все тензоры в списке были одного типа, вы можете сделать что -то вроде:
REGISTER_OP("IntListInputExample")
.Attr("N: int")
.Input("in: N * int32")
.Output("out: int32");
Это принимает списокint32
тензоры и используютint
атрисN
Чтобы указать длину списка.
Это можно сделатьтип полиморфнойтакже. В следующем примере вход представляет собой список тензоров (с длиной"N"
) того же (но неуточненного) типа ("T"
), и выход - единственный тензор типа соответствующего типа:
REGISTER_OP("SameListInputExample")
.Attr("N: int")
.Attr("T: type")
.Input("in: N * T")
.Output("out: T");
По умолчанию списки тензоров имеют минимальную длину 1. Вы можете изменить это по умолчанию, используяа">="
Ограничение на соответствующем атри. В следующем примере вход является списком не менее 2int32
Тенсоры:
REGISTER_OP("MinLengthIntListExample")
.Attr("N: int >= 2")
.Input("in: N * int32")
.Output("out: int32");
Тот же синтаксис работает с"list(type)"
ATTRS:
REGISTER_OP("MinimumLengthPolymorphicListExample")
.Attr("T: list(type) >= 3")
.Input("in: T")
.Output("out: T");
Входы и выходы
Подводя итог выше, регистрация OP может иметь несколько входов и выходов:
REGISTER_OP("MultipleInsAndOuts")
.Input("y: int32")
.Input("z: float")
.Output("a: string")
.Output("b: int32");
Каждая входная или выходная спецификация формы:
<name>: <io-type-expr>
где<name>
начинается с буквы и может быть составлен из буквенно -цифровых символов и подчеркиваний.<io-type-expr>
является одним из следующих выражений типа:
<type>
, где<type>
является поддерживаемым типом ввода (например,float
Вint32
Вstring
) Это указывает один тензор данного типа.Видеть
tf.DType
Полем
REGISTER_OP("BuiltInTypesExample")
.Input("integers: int32")
.Input("complex_numbers: complex64");
<attr-type>
, где<attr-type>
это имяАтрисс типомtype
илиlist(type)
(с возможным ограничением типа). Этот синтаксис позволяетПолиморфный ОпсПолем
REGISTER_OP("PolymorphicSingleInput")
.Attr("T: type")
.Input("in: T");
REGISTER_OP("RestrictedPolymorphicSingleInput")
.Attr("T: {int32, int64}")
.Input("in: T");
Ссылка на атрис типаlist(type)
Позволяет принять последовательность тензоров.
REGISTER_OP("ArbitraryTensorSequenceExample")
.Attr("T: list(type)")
.Input("in: T")
.Output("out: T");
REGISTER_OP("RestrictedTensorSequenceExample")
.Attr("T: list({int32, int64})")
.Input("in: T")
.Output("out: T");
Обратите внимание, что число и типы тензоров в выходеout
такой же, как и на входеin
, поскольку оба типаT
Полем
- Для последовательности тензоров с тем же типом:
<number> * <type>
, где<number>
это имяАтрисс типомint
Полем А<type>
может быть либоtf.DType
, или название аттроса с типомtype
Полем В качестве примера первого, этот OP принимает списокint32
Тенсоры:
REGISTER_OP("Int32SequenceExample")
.Attr("NumTensors: int")
.Input("in: NumTensors * int32")
Принимая во внимание, что этот OP принимает список тензоров любого типа, если они все одинаковы:
REGISTER_OP("SameTypeSequenceExample")
.Attr("NumTensors: int")
.Attr("T: type")
.Input("in: NumTensors * T")
- Для ссылки на тензор:
Ref(<type>)
, где<type>
является одним из предыдущих типов.
Любой ATTR, используемый в типе ввода, будет выведен. По соглашению, эти предполагаемые атрис используют имена капитала (например,T
илиN
) В противном случае входные данные, выходы и атрис имеют имена, такие как параметры функции (например,num_outputs
) Для получения более подробной информации см.более ранний раздел об названииПолем
Для получения более подробной информации см.tensorflow/core/framework/op_def_builder.h
Полем
Обратная совместимость
Давайте предположим, что вы написали хороший, пользовательский OP и поделились им с другими, поэтому у вас есть счастливые клиенты, которые используют вашу работу. Тем не менее, вы хотели бы каким -то образом внести изменения в OP.
В целом, изменения в существующих, проверенных спецификациях должны быть связаны обратно: изменение спецификации OP не должно нарушать предварительные сериализованныеGraphDef
Буферы протокола, построенные из более старых спецификаций. ДеталиGraphDef
совместимость естьописано здесьПолем
Есть несколько способов сохранения обратной совместимости.
- Любые новые ATRS, добавленные в операцию, должны иметь определенные значения по умолчанию, и с этим значением по умолчанию OP должен иметь исходное поведение. Чтобы изменить операцию с не полиморфной на полиморфную, выдолженДайте значение по умолчанию новому типу ATTR, чтобы по умолчанию сохранить исходную подпись. Например, если ваша операция была:
REGISTER_OP("MyGeneralUnaryOp")
.Input("in: float")
.Output("out: float");
Вы можете сделать его полиморфным на обратном порядке, используя:
REGISTER_OP("MyGeneralUnaryOp")
.Input("in: T")
.Output("out: T")
.Attr("T: numerictype = DT_FLOAT");
- Вы можете безопасно сделать ограничение на ATTR менее ограничительным. Например, вы можете измениться с
{int32, int64}
к{int32, int64, float}
илиtype
Полем Или вы можете измениться с{"apple", "orange"}
к{"apple", "banana", "orange"}
илиstring
Полем - Вы можете изменить отдельные входы / выходы на входы / выходы списка, если по умолчанию по умолчанию тип списка соответствует старой подписи.
- Вы можете добавить новый ввод списка / вывод, если по умолчанию по умолчанию.
- Пространство имен любые новые OPS, которые вы создаете, путем префикса имен OP с чем -то уникальным для вашего проекта. Это избегает столкновения с OP с любым OPS, который может быть включен в будущие версии TensorFlow.
- Планируйте заранее! Попробуйте предвидеть будущее использование для OP. Некоторые изменения подписи не могут быть сделаны совместимыми способом (например, составление списка одного и того же типа в список различных типов).
Полный список безопасных и небезопасных изменений можно найти вtensorflow/core/framework/op_compatibility_test.cc
Полем Если вы не можете внести свои изменения в операцию обратно совместимой, создайте новую операцию с новым именем с новой семантикой.
Также обратите внимание, что хотя эти изменения могут поддерживатьGraphDef
Совместимость, сгенерированный код Python может измениться таким образом, чтобы не совместимо со старыми абонентами. Python API может быть совместимы с помощью тщательных изменений в рукописной обертке Python, сохраняя старую подпись, за исключением того, что, возможно, добавляя новые дополнительные аргументы к концу. Как правило, несовместимые изменения могут быть внесены только тогда, когда тензоры меняют основные версии и должны соответствоватьGraphDef
Версия семантика.
Поддержка графического процессора
Вы можете реализовать разные Opkernels и зарегистрировать один для процессора, а другой - для графического процессора, как вы можетеЗарегистрируйте ядра для разных типовПолем Есть несколько примеров ядра с поддержкой графического процессора вtensorflow/core/kernels/
Полем Обратите внимание, что у некоторых ядер есть версия процессора в.cc
файл, версия GPU в файле, заканчивающемся в_gpu.cu.cc
и какой -то код, который общий общий в.h
файл.
Например,tf.pad
есть все, кроме ядра графического процессора вtensorflow/core/kernels/pad_op.cc
Полем Ядро графического процессора находится вtensorflow/core/kernels/pad_op_gpu.cu.cc
и общий код - это шаблонный класс, определенный вtensorflow/core/kernels/pad_op.h
Полем Мы организуем код таким образом по двум причинам: он позволяет вам обмениваться общим кодом между реализациями процессора и графического процессора, и он помещает реализацию GPU в отдельный файл, чтобы его можно было собрать только компилятором GPU.
Одна вещь, которую нужно отметить, даже когда версия ядра графического процессораpad
используется, он все еще нуждается в его"paddings"
Ввод в память процессора. Чтобы отметить, что входы или выходы хранятся на процессоре, добавьтеHostMemory()
Призовите регистрацию ядра, например:
#define REGISTER_GPU_KERNEL(T) \
REGISTER_KERNEL_BUILDER(Name("Pad") \
.Device(DEVICE_GPU) \
.TypeConstraint<T>("T") \
.HostMemory("paddings"), \
PadOp<GPUDevice, T>)
Скомпилирование ядра для устройства GPU
Посмотрите наcuda_op_kernel.cu.ccДля примера, который использует ядро CUDA для реализации OP. Аtf_custom_op_library
принимаетgpu_srcs
аргумент, в котором список исходных файлов, содержащих ядра CUDA (*.cu.cc
файлы) могут быть указаны. Для использования с бинарной установкой TensorFlow, ядра CUDA должны быть составлены с NVIDIAnvcc
компилятор. Вот последовательность команд, которые вы можете использовать для составленияcuda_op_kernel.cu.ccиcuda_op_kernel.ccв единую динамически загружаемую библиотеку:
nvcc -std=c++14 -c -o cuda_op_kernel.cu.o cuda_op_kernel.cu.cc \
${TF_CFLAGS[@]} -D GOOGLE_CUDA=1 -x cu -Xcompiler -fPIC
g++ -std=c++14 -shared -o cuda_op_kernel.so cuda_op_kernel.cc \
cuda_op_kernel.cu.o ${TF_CFLAGS[@]} -fPIC -lcudart ${TF_LFLAGS[@]}
cuda_op_kernel.so
производится выше, можно загружать как обычно в Python, используяtf.load_op_library
функция
Обратите внимание, что если ваши библиотеки CUDA не установлены в/usr/local/lib64
, вам нужно явно указать путь во второй (G ++) команде выше. Например, добавить-L /usr/local/cuda-8.0/lib64/
Если ваша CUDA установлена в/usr/local/cuda-8.0
Полем
Примечание:В некоторых настройках Linux дополнительные параметры наnvcc
Требуется шаг компиляции. Добавлять-D_MWAITXINTRIN_H_INCLUDED
вnvcc
командная строка, чтобы избежать ошибок отmwaitxintrin.h
Полем
Реализуйте градиент в Python
Учитывая график OPS, TensorFlow использует автоматическую дифференциацию (обратное распространение) для добавления новых OP, представляющих градиенты по отношению к существующим OPS. Чтобы сделать автоматическую дифференциацию для новых OPS, вы должны зарегистрировать градиентную функцию, которая вычисляет градиенты по отношению к данным градиентам OPS в отношении выходов OPS.
Математически, если OP вычисляет y = f (x), зарегистрированный градиент OP преобразует градиенты ∂l/∂y потерь L по отношению к y в градиенты ∂l/∂x относительно x через правило цепи:
∂l∂x = ∂l∂y∂y∂x = ∂l∂y∂f∂x.
В случаеZeroOut
, только одна запись на входе влияет на выход, поэтому градиент по входу представляет собой разреженный «один горячий» тензор. Это выражено следующим образом:
from tensorflow.python.framework import ops
from tensorflow.python.ops import array_ops
from tensorflow.python.ops import sparse_ops
@ops.RegisterGradient("ZeroOut")
def _zero_out_grad(op, grad):
"""The gradients for `zero_out`.
Args:
op: The `zero_out` `Operation` that we are differentiating, which we can use
to find the inputs and outputs of the original op.
grad: Gradient with respect to the output of the `zero_out` op.
Returns:
Gradients with respect to the input of `zero_out`.
"""
to_zero = op.inputs[0]
shape = array_ops.shape(to_zero)
index = array_ops.zeros_like(shape)
first_grad = array_ops.reshape(grad, [-1])[0]
to_zero_grad = sparse_ops.sparse_to_dense([index], shape, first_grad, 0)
return [to_zero_grad] # List of one Tensor, since we have one input
Подробная информация о регистрации градиентных функций с помощьюtf.RegisterGradient
:
- Для OP с одним выходом функция градиента примет
tf.Operation
Вop
и аtf.Tensor
grad
и построить новые операции из тензоровop.inputs[i]
Вop.outputs[i]
, иgrad
Полем Информацию о любых ATRS можно найти черезtf.Operation.get_attr
Полем - Если у OP есть несколько выходов, функция градиента займет
op
иgrads
, гдеgrads
это список градиентов по отношению к каждому выходу. Результатом градиентной функции должен быть списокTensor
Объекты, представляющие градиенты по отношению к каждому входу. - Если для некоторого входа нет четко определенного градиента, например, для целочисленных входов, используемых в качестве индексов, соответствующий возвращаемый градиент должен быть
None
Полем Например, для OP, принимающего тензор с плавающей запятойx
и целочисленный индексi
, градиентная функция будетreturn [x_grad, None]
Полем - Если для OP нет значимого градиента, вам часто не приходится регистрировать какой -либо градиент, и до тех пор, пока градиент OP никогда не нужен, у вас все будет в порядке. В некоторых случаях OP не имеет четко определенного градиента, но может участвовать в вычислении градиента. Здесь вы можете использовать
ops.NotDifferentiable
автоматически распространять нули назад.
Обратите внимание, что в то время, когда называется градиентная функция, доступен только график потока данных OPS, а не сами данные о тензоре. Таким образом, все вычисления должны быть выполнены с использованием других Tensorflow Ops, которые будут выполнены во время выполнения графа.
Добавьте подсказки типа при регистрации пользовательского градиента для типа OP, чтобы сделать код более читаемым, отладчиком, проще в обслуживании и более надежного посредством проверки данных. Например, при принятииop
В качестве параметра в функции укажите, что функция градиента будет приниматьtf.Operation
как тип параметров.
Функции формы в C ++
API TensorFlow имеет функцию, называемую «вывод формы», которая предоставляет информацию о формах тензоров без необходимости выполнять график. Вывод формы поддерживается «Функциями формы», которые зарегистрированы для каждого типа OP в C ++REGISTER_OP
Объявление и выполните две роли: утверждая, что формы входов совместимы во время построения графика, и определение форм для выходов.
Функции формы определяются как операции наshape_inference::InferenceContext
сорт. Например, в функции формы для Zeroout:
.SetShapeFn([](::tensorflow::shape_inference::InferenceContext* c) {
c->set_output(0, c->input(0));
return Status::OK();
});
c->set_output(0, c->input(0));
заявляет, что форма первого вывода должна быть установлена на форму первого входа. Если выход выбран его индексом, как в приведенном выше примере, второй параметрset_output
должен бытьShapeHandle
объект. Вы можете создать пустойShapeHandle
объект по его конструктору по умолчанию. АShapeHandle
объект для ввода с индексомidx
может быть полученc->input(idx)
Полем
Есть ряд общих функций формы, которые применяются ко многим операциям, напримерshape_inference::UnchangedShape
который можно найти вcommon_shape_fns.hи используется следующим образом:
REGISTER_OP("ZeroOut")
.Input("to_zero: int32")
.Output("zeroed: int32")
.SetShapeFn(::tensorflow::shape_inference::UnchangedShape);
Функция формы также может ограничить форму входа. Для версииZeroOut
При ограничении формы вектора функция формы была бы следующей:
.SetShapeFn([](::tensorflow::shape_inference::InferenceContext* c) {
::tensorflow::shape_inference::ShapeHandle input;
TF_RETURN_IF_ERROR(c->WithRank(c->input(0), 1, &input));
c->set_output(0, input);
return Status::OK();
});
АWithRank
Вызов подтверждает, что форма вводаc->input(0)
имеет форму с ровным размером (или если входная форма неизвестна, выходная форма будет вектором с одним неизвестным измерением).
Если ваш OPПолиморфный с несколькими входами, вы можете использовать членовInferenceContext
Чтобы определить количество форм для проверки, иMerge
Чтобы подтвердить, что все формы совместимы (альтернативно, атрибуты доступа, которые указывают на длины, сInferenceContext::GetAttr
, который обеспечивает доступ к атрибутам ОП).
.SetShapeFn([](::tensorflow::shape_inference::InferenceContext* c) {
::tensorflow::shape_inference::ShapeHandle input;
::tensorflow::shape_inference::ShapeHandle output;
for (size_t i = 0; i < c->num_inputs(); ++i) {
TF_RETURN_IF_ERROR(c->WithRank(c->input(i), 2, &input));
TF_RETURN_IF_ERROR(c->Merge(output, input, &output));
}
c->set_output(0, output);
return Status::OK();
});
Поскольку вывод формы является дополнительной особенностью, и формы тензоров могут динамически различаться, функции формы должны быть надежными до неполной информации о форме для любого из входов. АMerge
Метод вInferenceContext
Позволяет вызывающему утверждать, что две формы одинаковы, даже если у кого -либо или оба из них нет полной информации. Функции формы определяются для всех основных Tensorflow Ops и предоставляют много разных примеров использования.
АInferenceContext
Класс имеет ряд функций, которые можно использовать для определения манипуляций с функцией формы. Например, вы можете подтвердить, что конкретное измерение имеет очень специфическое значение, используяInferenceContext::Dim
иInferenceContext::WithValue
; Вы можете указать, что выходной размер - это сумма / продукт двух входных измерений, используяInferenceContext::Add
иInferenceContext::Multiply
Полем УвидетьInferenceContext
Класс для всех различных манипуляций с формой, которые вы можете указать. Следующий пример устанавливает форму первого вывода в (n, 3), где первый вход имеет форму (n, ...)
.SetShapeFn([](::tensorflow::shape_inference::InferenceContext* c) {
c->set_output(0, c->Matrix(c->Dim(c->input(0), 0), 3));
return Status::OK();
});
Если у вас есть сложная функция формы, вам следует рассмотреть вопрос о добавлении теста для проверки того, что различные комбинации ввода формируют ожидаемые комбинации форм вывода. Вы можете увидеть примеры того, как написать эти тесты в некоторых нашихCore Ops тестыПолем (СинтаксисINFER_OK
иINFER_ERROR
немного загадочно, но старайтесь быть компактными в представлении спецификаций ввода и вывода в тестах. На данный момент см. В этих тестах, чтобы получить представление о спецификации строки формы).
Создайте пакет PIP для пользовательского OP
Чтобы построить аpip
пакет для вашего OP, см.Tensorflow/Custom-Opпример. В этом руководстве показано, как создать пользовательские операции из пакета Tensorflow PIP вместо того, чтобы построить TensorFlow из Source.
Первоначально опубликовано на
Оригинал