123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157 |
- //===- 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)
|