diff --git a/llvm/include/llvm/Analysis/Utils/TFUtils.h b/llvm/include/llvm/Analysis/Utils/TFUtils.h --- a/llvm/include/llvm/Analysis/Utils/TFUtils.h +++ b/llvm/include/llvm/Analysis/Utils/TFUtils.h @@ -12,7 +12,6 @@ #include "llvm/Config/config.h" #ifdef LLVM_HAVE_TF_API -#include "tensorflow/c/c_api.h" #include "llvm/IR/LLVMContext.h" #include @@ -31,51 +30,35 @@ /// - set input values by using getInput to get each input tensor, and then /// setting internal scalars, for all dimensions (tensors are row-major: /// https://github.com/tensorflow/tensorflow/blob/r1.5/tensorflow/c/c_api.h#L205) -/// - prepare an output vector of TF_Output* type, with the correct number of -/// outputs (i.e. same as OutputNames). Initialize the vector with nullptr -/// values. /// - call evaluate. The input tensors' values are not consumed after this, and /// may still be read. /// - use the outputs in the output vector -/// - deallocate each output tensor in the output vector, using TF_DeleteTensor. +class TFModelEvaluatorImpl; +class EvaluationResultImpl; + class TFModelEvaluator final { public: /// The result of a model evaluation. Handles the lifetime of the output - /// TF_Tensor objects, which means that their values need to be used before + /// tensors, which means that their values need to be used before /// the EvaluationResult's dtor is called. class EvaluationResult { public: - ~EvaluationResult() { - for (auto *P : Output) - if (P) - TF_DeleteTensor(P); - } - EvaluationResult(const EvaluationResult &) = delete; - EvaluationResult(EvaluationResult &&Other) - : OutputSize(Other.OutputSize), Output(std::move(Other.Output)) { - Other.Output.clear(); - }; + EvaluationResult(EvaluationResult &&Other); + ~EvaluationResult(); /// Get a pointer to the first element of the tensor at Index. template T *getTensorValue(size_t Index) { - return static_cast(TF_TensorData(Output[Index])); + return static_cast(getUntypedTensorValue(Index)); } private: friend class TFModelEvaluator; - EvaluationResult(size_t OutputSize) - : OutputSize(OutputSize), Output(OutputSize){}; - - const size_t OutputSize; - std::vector Output; + EvaluationResult(std::unique_ptr Impl); + void *getUntypedTensorValue(size_t Index); + std::unique_ptr Impl; }; - using TFGraphPtr = std::unique_ptr; - using TFSessionOptionsPtr = - std::unique_ptr; - using TFStatusPtr = std::unique_ptr; - TFModelEvaluator(StringRef SavedModelPath, const std::vector &InputNames, const std::vector &OutputNames, @@ -87,53 +70,45 @@ /// Evaluate the model, assuming it is valid. Returns None if the evaluation /// fails or the model is invalid, or an EvaluationResult otherwise. The /// inputs are assumed to have been already provided via getInput(). When - /// returning None, it also marks the object invalid. Pass an Output vector - /// with the same size as OutputNames, but with nullptr values. evaluate() - /// will populate it with tensors, matching in index the corresponding - /// OutputNames. The caller is responsible for the deallocation of those - /// tensors, using TF_DeleteTensor. + /// returning None, it also invalidates this object. Optional evaluate(); - /// Provides access to the input vector. It is already dimensioned correctly, - /// but the values need to be allocated by the user. - std::vector &getInput() { return Input; } + /// Provides access to the input vector. + template T *getInput(size_t Index) { + return static_cast(getUntypedInput(Index)); + } /// Returns true if the tensorflow model was loaded successfully, false /// otherwise. - bool isValid() const { return !!Session; } + bool isValid() const { return !!Impl; } - /// Initialize the input at Index as a tensor of the given type and dimensions - void initInput(int Index, TF_DataType Type, - const std::vector &Dimensions); + /// Initialize the input at Index as a tensor of the given type and + /// dimensions. + template + void initInput(size_t Index, const std::vector &Dimensions) { + return initInput(Index, getModelTypeIndex(), Dimensions); + } private: - /// The objects necessary for carrying out an evaluation of the SavedModel. - /// They are expensive to set up, and we maintain them accross all the - /// evaluations of the model. - TF_Session *Session = nullptr; - TFGraphPtr Graph; - TFSessionOptionsPtr Options; - - /// The specification of the input nodes. - std::vector InputFeed; - - /// The input tensors. They must match by index of the corresponding InputFeed - /// value. We set up the tensors once and just mutate theirs scalars before - /// each evaluation. The input tensors keep their value after an evaluation. - std::vector Input; - - /// The specification of the output nodes. When evaluating, the tensors in the - /// output tensor vector must match by index the corresponding element in the - /// OutputFeed. - std::vector OutputFeed; - - /// Reusable utility for deleting the session. - void deleteSession(); - - /// Reusable utility for ensuring we can bind the requested Name to a node in - /// the SavedModel Graph. - bool checkReportAndReset(const TF_Output &Output, StringRef Name); + void *getUntypedInput(size_t Index); + template int getModelTypeIndex(); + void initInput(size_t Index, int TypeIndex, + const std::vector &Dimensions); + + std::unique_ptr Impl; }; + +template <> int TFModelEvaluator::getModelTypeIndex(); +template <> int TFModelEvaluator::getModelTypeIndex(); +template <> int TFModelEvaluator::getModelTypeIndex(); +template <> int TFModelEvaluator::getModelTypeIndex(); +template <> int TFModelEvaluator::getModelTypeIndex(); +template <> int TFModelEvaluator::getModelTypeIndex(); +template <> int TFModelEvaluator::getModelTypeIndex(); +template <> int TFModelEvaluator::getModelTypeIndex(); +template <> int TFModelEvaluator::getModelTypeIndex(); +template <> int TFModelEvaluator::getModelTypeIndex(); + } // namespace llvm #endif // LLVM_HAVE_TF_API diff --git a/llvm/lib/Analysis/InlineSizeEstimatorAnalysis.cpp b/llvm/lib/Analysis/InlineSizeEstimatorAnalysis.cpp --- a/llvm/lib/Analysis/InlineSizeEstimatorAnalysis.cpp +++ b/llvm/lib/Analysis/InlineSizeEstimatorAnalysis.cpp @@ -256,7 +256,7 @@ 1, static_cast( IRToNativeSizeLearning::FunctionFeatures::FeatureCount)}; - Evaluator->initInput(0, TF_INT32, Dim); + Evaluator->initInput(0, Dim); } InlineSizeEstimatorAnalysis::Result @@ -266,7 +266,7 @@ return None; auto Features = IRToNativeSizeLearning::getFunctionFeatures( const_cast(F), FAM); - int32_t *V = static_cast(TF_TensorData(Evaluator->getInput()[0])); + int32_t *V = Evaluator->getInput(0); Features.fillTensor(V); auto ER = Evaluator->evaluate(); if (!ER) diff --git a/llvm/lib/Analysis/TFUtils.cpp b/llvm/lib/Analysis/TFUtils.cpp --- a/llvm/lib/Analysis/TFUtils.cpp +++ b/llvm/lib/Analysis/TFUtils.cpp @@ -17,6 +17,7 @@ #include "llvm/Support/ManagedStatic.h" #include "llvm/Support/raw_ostream.h" +#include "tensorflow/c/c_api.h" #include "tensorflow/c/c_api_experimental.h" #include @@ -25,6 +26,11 @@ namespace { +using TFGraphPtr = std::unique_ptr; +using TFSessionOptionsPtr = + std::unique_ptr; +using TFStatusPtr = std::unique_ptr; + struct TFInitializer { TFInitializer() { assert(!IsInitialized && "TFInitialized should be called only once"); @@ -41,24 +47,96 @@ bool ensureInitTF() { return TFLibInitializer->IsInitialized; } -TFModelEvaluator::TFGraphPtr createTFGraph() { - return TFModelEvaluator::TFGraphPtr(TF_NewGraph(), &TF_DeleteGraph); +TFGraphPtr createTFGraph() { + return TFGraphPtr(TF_NewGraph(), &TF_DeleteGraph); } -TFModelEvaluator::TFStatusPtr createTFStatus() { - return TFModelEvaluator::TFStatusPtr(TF_NewStatus(), &TF_DeleteStatus); +TFStatusPtr createTFStatus() { + return TFStatusPtr(TF_NewStatus(), &TF_DeleteStatus); } -TFModelEvaluator::TFSessionOptionsPtr createTFSessionOptions() { - return TFModelEvaluator::TFSessionOptionsPtr(TF_NewSessionOptions(), - &TF_DeleteSessionOptions); +TFSessionOptionsPtr createTFSessionOptions() { + return TFSessionOptionsPtr(TF_NewSessionOptions(), &TF_DeleteSessionOptions); } } // namespace -TFModelEvaluator::TFModelEvaluator(StringRef SavedModelPath, - const std::vector &InputNames, - const std::vector &OutputNames, - const char *Tags) +namespace llvm { +class EvaluationResultImpl { +public: + EvaluationResultImpl(size_t OutputSize) + : OutputSize(OutputSize), Output(OutputSize){}; + + ~EvaluationResultImpl() { + for (auto *P : Output) + if (P) + TF_DeleteTensor(P); + } + + EvaluationResultImpl(const EvaluationResultImpl &) = delete; + EvaluationResultImpl(EvaluationResultImpl &&Other) = delete; + std::vector &getOutput() { return Output; } + +private: + const size_t OutputSize; + std::vector Output; +}; + +class TFModelEvaluatorImpl { +public: + TFModelEvaluatorImpl(StringRef SavedModelPath, + const std::vector &InputNames, + const std::vector &OutputNames, + const char *Tags); + + bool isValid() const { return IsValid; } + size_t OutputSize() const { return OutputFeed.size(); } + + void evaluate(TF_Tensor **Output, TF_Status *Status) { + TF_SessionRun(Session, nullptr, InputFeed.data(), Input.data(), + Input.size(), OutputFeed.data(), Output, OutputFeed.size(), + nullptr, 0, nullptr, Status); + } + + void initInput(size_t Index, TF_DataType Type, + const std::vector &Dimensions); + const std::vector &getInput() const { return Input; } + + ~TFModelEvaluatorImpl(); + +private: + /// The objects necessary for carrying out an evaluation of the SavedModel. + /// They are expensive to set up, and we maintain them accross all the + /// evaluations of the model. + TF_Session *Session = nullptr; + TFGraphPtr Graph; + TFSessionOptionsPtr Options; + + /// The specification of the input nodes. + std::vector InputFeed; + + /// The input tensors. They must match by index of the corresponding InputFeed + /// value. We set up the tensors once and just mutate theirs scalars before + /// each evaluation. The input tensors keep their value after an evaluation. + std::vector Input; + + /// The specification of the output nodes. When evaluating, the tensors in the + /// output tensor vector must match by index the corresponding element in the + /// OutputFeed. + std::vector OutputFeed; + + void invalidate() { IsValid = false; } + + bool IsValid = true; + + /// Reusable utility for ensuring we can bind the requested Name to a node in + /// the SavedModel Graph. + bool checkReportAndInvalidate(const TF_Output &Output, StringRef Name); +}; +} // namespace llvm + +TFModelEvaluatorImpl::TFModelEvaluatorImpl( + StringRef SavedModelPath, const std::vector &InputNames, + const std::vector &OutputNames, const char *Tags) : Graph(createTFGraph()), Options(createTFSessionOptions()), InputFeed(InputNames.size()), Input(InputNames.size()), OutputFeed(OutputNames.size()) { @@ -73,39 +151,36 @@ Graph.get(), nullptr, Status.get()); if (TF_GetCode(Status.get()) != TF_Code::TF_OK) { errs() << TF_Message(Status.get()); - deleteSession(); + invalidate(); } for (size_t I = 0; I < InputNames.size(); ++I) { InputFeed[I] = { TF_GraphOperationByName(Graph.get(), (InputNames[I]).c_str()), 0}; - if (!checkReportAndReset(InputFeed[I], InputNames[I])) + if (!checkReportAndInvalidate(InputFeed[I], InputNames[I])) return; } for (size_t I = 0; I < OutputNames.size(); ++I) { OutputFeed[I] = { TF_GraphOperationByName(Graph.get(), (OutputNames[I]).c_str()), 0}; - if (!checkReportAndReset(OutputFeed[I], OutputNames[I])) + if (!checkReportAndInvalidate(OutputFeed[I], OutputNames[I])) return; } } -TFModelEvaluator::~TFModelEvaluator() { +TFModelEvaluator::TFModelEvaluator(StringRef SavedModelPath, + const std::vector &InputNames, + const std::vector &OutputNames, + const char *Tags) + : Impl(new TFModelEvaluatorImpl(SavedModelPath, InputNames, OutputNames, + Tags)) { + if (!Impl->isValid()) + Impl.reset(); +} + +TFModelEvaluatorImpl::~TFModelEvaluatorImpl() { for (auto *T : Input) { TF_DeleteTensor(T); } - deleteSession(); -} - -bool TFModelEvaluator::checkReportAndReset(const TF_Output &Output, - StringRef Name) { - if (Output.oper) - return true; - errs() << "Could not find TF_Output named: " + Name; - deleteSession(); - return false; -} - -void TFModelEvaluator::deleteSession() { if (Session == nullptr) return; auto Status = createTFStatus(); @@ -115,24 +190,32 @@ errs() << "Could not delete TF session"; } +bool TFModelEvaluatorImpl::checkReportAndInvalidate(const TF_Output &Output, + StringRef Name) { + if (Output.oper) + return true; + errs() << "Could not find TF_Output named: " + Name; + IsValid = false; + return IsValid; +} + Optional TFModelEvaluator::evaluate() { if (!isValid()) return None; - EvaluationResult Ret(OutputFeed.size()); + std::unique_ptr Ret = + std::make_unique(Impl->OutputSize()); auto Status = createTFStatus(); - TF_SessionRun(Session, nullptr, InputFeed.data(), Input.data(), Input.size(), - OutputFeed.data(), Ret.Output.data(), Ret.Output.size(), - nullptr, 0, nullptr, Status.get()); + Impl->evaluate(Ret->getOutput().data(), Status.get()); if (TF_GetCode(Status.get()) != TF_Code::TF_OK) { errs() << TF_Message(Status.get()); - deleteSession(); + Impl.reset(); return None; } - return Ret; + return EvaluationResult(std::move(Ret)); } -void TFModelEvaluator::initInput(int Index, TF_DataType Type, - const std::vector &Dimensions) { +void TFModelEvaluatorImpl::initInput(size_t Index, TF_DataType Type, + const std::vector &Dimensions) { int64_t TotalSize = TF_DataTypeSize(Type); for (auto &D : Dimensions) TotalSize *= D; @@ -140,4 +223,67 @@ Input[Index] = TF_AllocateTensor(Type, Dimensions.data(), Dimensions.size(), TotalSize); std::memset(TF_TensorData(Input[Index]), 0, TotalSize); -} \ No newline at end of file +} + +void *TFModelEvaluator::getUntypedInput(size_t Index) { + return TF_TensorData(Impl->getInput()[Index]); +} + +TFModelEvaluator::EvaluationResult::EvaluationResult( + std::unique_ptr Impl) + : Impl(std::move(Impl)) {} + +TFModelEvaluator::EvaluationResult::EvaluationResult(EvaluationResult &&Other) + : Impl(std::move(Other.Impl)) {} + +void *TFModelEvaluator::EvaluationResult::getUntypedTensorValue(size_t Index) { + return TF_TensorData(Impl->getOutput()[Index]); +} + +void TFModelEvaluator::initInput(size_t Index, int TypeIndex, + const std::vector &Dimensions) { + Impl->initInput(Index, static_cast(TypeIndex), Dimensions); +} + +template <> int TFModelEvaluator::getModelTypeIndex() { + return TF_FLOAT; +} + +template <> int TFModelEvaluator::getModelTypeIndex() { + return TF_DOUBLE; +} + +template <> int TFModelEvaluator::getModelTypeIndex() { + return TF_INT8; +} + +template <> int TFModelEvaluator::getModelTypeIndex() { + return TF_UINT8; +} + +template <> int TFModelEvaluator::getModelTypeIndex() { + return TF_INT16; +} + +template <> int TFModelEvaluator::getModelTypeIndex() { + return TF_UINT16; +} + +template <> int TFModelEvaluator::getModelTypeIndex() { + return TF_INT32; +} + +template <> int TFModelEvaluator::getModelTypeIndex() { + return TF_UINT32; +} + +template <> int TFModelEvaluator::getModelTypeIndex() { + return TF_INT64; +} + +template <> int TFModelEvaluator::getModelTypeIndex() { + return TF_UINT64; +} + +TFModelEvaluator::EvaluationResult::~EvaluationResult() {} +TFModelEvaluator::~TFModelEvaluator() {} diff --git a/llvm/unittests/Analysis/TFUtilsTest.cpp b/llvm/unittests/Analysis/TFUtilsTest.cpp --- a/llvm/unittests/Analysis/TFUtilsTest.cpp +++ b/llvm/unittests/Analysis/TFUtilsTest.cpp @@ -45,9 +45,9 @@ static const std::vector Dim{1, KnownSize}; EXPECT_TRUE(Evaluator.isValid()); - Evaluator.initInput(0, TF_INT32, Dim); + Evaluator.initInput(0, Dim); - int32_t *V = static_cast(TF_TensorData(Evaluator.getInput()[0])); + int32_t *V = Evaluator.getInput(0); // Fill it up with 1's, we know the output. for (auto I = 0; I < KnownSize; ++I) { V[I] = 1; @@ -85,9 +85,9 @@ static const std::vector Dim{1, KnownSize}; EXPECT_TRUE(Evaluator.isValid()); - Evaluator.initInput(0, TF_INT32, Dim); + Evaluator.initInput(0, Dim); - int32_t *V = static_cast(TF_TensorData(Evaluator.getInput()[0])); + int32_t *V = Evaluator.getInput(0); // Fill it up with 1's, we know the output. for (auto I = 0; I < KnownSize; ++I) { V[I] = 1;