//===- 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 using namespace llvm; namespace { struct LoggedFeatureSpec { TensorSpec Spec; std::optional LoggingName; }; std::optional> loadOutputSpecs(LLVMContext &Ctx, StringRef ExpectedDecisionName, StringRef ModelPath, StringRef SpecFileOverride) { SmallVector 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:, " "logging_name:} dictionaries"); return std::nullopt; } std::vector 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() && !TensorSpec->isElementType() && !TensorSpec->isElementType()) { 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 &InputSpecs, const std::vector &OutputSpecs, const std::vector &ExtraOutputsForLogging) : MLModelRunner(Ctx, MLModelRunner::Kind::Development, InputSpecs.size()), OutputSpecs(OutputSpecs), ExtraOutputsForLogging(ExtraOutputsForLogging) { Evaluator = std::make_unique(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::createAndEnsureValid( LLVMContext &Ctx, const std::string &ModelPath, StringRef DecisionName, const std::vector &InputSpecs, StringRef OutputSpecsPathOverride) { if (auto MaybeOutputSpecs = loadOutputSpecs(Ctx, DecisionName, ModelPath, OutputSpecsPathOverride)) { std::unique_ptr MUTR; std::vector OutputSpecs; std::vector 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)