|  | //===- ModelUnderTrainingRunner.cpp - 'development' mode runner -----------===// | 
|  | // | 
|  | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | 
|  | // See https://llvm.org/LICENSE.txt for license information. | 
|  | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | 
|  | // | 
|  | //===----------------------------------------------------------------------===// | 
|  | // | 
|  | // Implementation of a MLModelRunner for 'development' mode, i.e. evaluation | 
|  | // happens off a model that's provided from the command line and is interpreted. | 
|  | // | 
|  | //===----------------------------------------------------------------------===// | 
|  |  | 
|  | #include "llvm/ADT/STLExtras.h" | 
|  | #include "llvm/Config/config.h" | 
|  | #if defined(LLVM_HAVE_TFLITE) | 
|  | #include "llvm/Analysis/ModelUnderTrainingRunner.h" | 
|  | #include "llvm/Support/MemoryBuffer.h" | 
|  | #include "llvm/Support/Path.h" | 
|  | #include <optional> | 
|  |  | 
|  | using namespace llvm; | 
|  | namespace { | 
|  | struct LoggedFeatureSpec { | 
|  | TensorSpec Spec; | 
|  | std::optional<std::string> LoggingName; | 
|  | }; | 
|  |  | 
|  | std::optional<std::vector<LoggedFeatureSpec>> | 
|  | loadOutputSpecs(LLVMContext &Ctx, StringRef ExpectedDecisionName, | 
|  | StringRef ModelPath, StringRef SpecFileOverride) { | 
|  | SmallVector<char, 128> OutputSpecsPath; | 
|  | StringRef FileName = SpecFileOverride; | 
|  | if (FileName.empty()) { | 
|  | llvm::sys::path::append(OutputSpecsPath, ModelPath, "output_spec.json"); | 
|  | FileName = {OutputSpecsPath.data(), OutputSpecsPath.size()}; | 
|  | } | 
|  |  | 
|  | auto BufferOrError = MemoryBuffer::getFileOrSTDIN(FileName); | 
|  | if (!BufferOrError) { | 
|  | Ctx.emitError("Error opening output specs file: " + FileName + " : " + | 
|  | BufferOrError.getError().message()); | 
|  | return std::nullopt; | 
|  | } | 
|  | auto ParsedJSONValues = json::parse(BufferOrError.get()->getBuffer()); | 
|  | if (!ParsedJSONValues) { | 
|  | Ctx.emitError("Could not parse specs file: " + FileName); | 
|  | return std::nullopt; | 
|  | } | 
|  | auto ValuesArray = ParsedJSONValues->getAsArray(); | 
|  | if (!ValuesArray) { | 
|  | Ctx.emitError("Expected an array of {tensor_spec:<TensorSpec>, " | 
|  | "logging_name:<name>} dictionaries"); | 
|  | return std::nullopt; | 
|  | } | 
|  | std::vector<LoggedFeatureSpec> Ret; | 
|  | for (const auto &Value : *ValuesArray) | 
|  | if (const auto *Obj = Value.getAsObject()) | 
|  | if (const auto *SpecPart = Obj->get("tensor_spec")) | 
|  | if (auto TensorSpec = getTensorSpecFromJSON(Ctx, *SpecPart)) | 
|  | if (auto LoggingName = Obj->getString("logging_name")) { | 
|  | if (!TensorSpec->isElementType<int64_t>() && | 
|  | !TensorSpec->isElementType<int32_t>() && | 
|  | !TensorSpec->isElementType<float>()) { | 
|  | Ctx.emitError( | 
|  | "Only int64, int32, and float tensors are supported. " | 
|  | "Found unsupported type for tensor named " + | 
|  | TensorSpec->name()); | 
|  | return std::nullopt; | 
|  | } | 
|  | Ret.push_back({*TensorSpec, LoggingName->str()}); | 
|  | } | 
|  |  | 
|  | if (ValuesArray->size() != Ret.size()) { | 
|  | Ctx.emitError( | 
|  | "Unable to parse output spec. It should be a json file containing an " | 
|  | "array of dictionaries. Each dictionary must have a 'tensor_spec' key, " | 
|  | "with a json object describing a TensorSpec; and a 'logging_name' key, " | 
|  | "which is a string to use as name when logging this tensor in the " | 
|  | "training log."); | 
|  | return std::nullopt; | 
|  | } | 
|  | if (Ret.empty() || *Ret[0].LoggingName != ExpectedDecisionName) { | 
|  | Ctx.emitError("The first output spec must describe the decision tensor, " | 
|  | "and must have the logging_name " + | 
|  | StringRef(ExpectedDecisionName)); | 
|  | return std::nullopt; | 
|  | } | 
|  | return Ret; | 
|  | } | 
|  | } // namespace | 
|  |  | 
|  | ModelUnderTrainingRunner::ModelUnderTrainingRunner( | 
|  | LLVMContext &Ctx, const std::string &ModelPath, | 
|  | const std::vector<TensorSpec> &InputSpecs, | 
|  | const std::vector<TensorSpec> &OutputSpecs, | 
|  | const std::vector<TensorSpec> &ExtraOutputsForLogging) | 
|  | : MLModelRunner(Ctx, MLModelRunner::Kind::Development, InputSpecs.size()), | 
|  | OutputSpecs(OutputSpecs), ExtraOutputsForLogging(ExtraOutputsForLogging) { | 
|  | Evaluator = | 
|  | std::make_unique<TFModelEvaluator>(ModelPath, InputSpecs, OutputSpecs); | 
|  | if (!Evaluator || !Evaluator->isValid()) { | 
|  | Ctx.emitError("Failed to create saved model evaluator"); | 
|  | Evaluator.reset(); | 
|  | return; | 
|  | } | 
|  |  | 
|  | for (size_t I = 0, E = InputSpecs.size(); I < E; ++I) { | 
|  | setUpBufferForTensor(I, InputSpecs[I], Evaluator->getUntypedInput(I)); | 
|  | } | 
|  | } | 
|  |  | 
|  | void *ModelUnderTrainingRunner::evaluateUntyped() { | 
|  | LastEvaluationResult = Evaluator->evaluate(); | 
|  | if (!LastEvaluationResult.has_value()) { | 
|  | Ctx.emitError("Error evaluating model."); | 
|  | return nullptr; | 
|  | } | 
|  | return LastEvaluationResult->getUntypedTensorValue(0); | 
|  | } | 
|  |  | 
|  | std::unique_ptr<ModelUnderTrainingRunner> | 
|  | ModelUnderTrainingRunner::createAndEnsureValid( | 
|  | LLVMContext &Ctx, const std::string &ModelPath, StringRef DecisionName, | 
|  | const std::vector<TensorSpec> &InputSpecs, | 
|  | StringRef OutputSpecsPathOverride) { | 
|  | if (auto MaybeOutputSpecs = loadOutputSpecs(Ctx, DecisionName, ModelPath, | 
|  | OutputSpecsPathOverride)) { | 
|  | std::unique_ptr<ModelUnderTrainingRunner> MUTR; | 
|  | std::vector<TensorSpec> OutputSpecs; | 
|  | std::vector<TensorSpec> ExtraOutputsForLogging; | 
|  | append_range(OutputSpecs, | 
|  | map_range(*MaybeOutputSpecs, [](const LoggedFeatureSpec &LFS) { | 
|  | return LFS.Spec; | 
|  | })); | 
|  | append_range(ExtraOutputsForLogging, | 
|  | map_range(drop_begin(*MaybeOutputSpecs), | 
|  | [](const LoggedFeatureSpec &LFS) { | 
|  | return TensorSpec(LFS.LoggingName | 
|  | ? *LFS.LoggingName | 
|  | : LFS.Spec.name(), | 
|  | LFS.Spec); | 
|  | })); | 
|  |  | 
|  | MUTR.reset(new ModelUnderTrainingRunner( | 
|  | Ctx, ModelPath, InputSpecs, OutputSpecs, ExtraOutputsForLogging)); | 
|  | if (MUTR && MUTR->isValid()) | 
|  | return MUTR; | 
|  |  | 
|  | Ctx.emitError("Could not load or create model evaluator."); | 
|  | return nullptr; | 
|  | } | 
|  | Ctx.emitError("Could not load the policy model from the provided path"); | 
|  | return nullptr; | 
|  | } | 
|  |  | 
|  | #endif // defined(LLVM_HAVE_TFLITE) |