forked from OSchip/llvm-project
157 lines
6.1 KiB
C++
157 lines
6.1 KiB
C++
//===- 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_TF_API)
|
|
#include "llvm/Analysis/ModelUnderTrainingRunner.h"
|
|
#include "llvm/Support/MemoryBuffer.h"
|
|
#include "llvm/Support/Path.h"
|
|
|
|
using namespace llvm;
|
|
namespace {
|
|
struct LoggedFeatureSpec {
|
|
TensorSpec Spec;
|
|
std::optional<std::string> LoggingName;
|
|
};
|
|
|
|
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 None;
|
|
}
|
|
auto ParsedJSONValues = json::parse(BufferOrError.get()->getBuffer());
|
|
if (!ParsedJSONValues) {
|
|
Ctx.emitError("Could not parse specs file: " + FileName);
|
|
return None;
|
|
}
|
|
auto ValuesArray = ParsedJSONValues->getAsArray();
|
|
if (!ValuesArray) {
|
|
Ctx.emitError("Expected an array of {tensor_spec:<TensorSpec>, "
|
|
"logging_name:<name>} dictionaries");
|
|
return None;
|
|
}
|
|
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 None;
|
|
}
|
|
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 None;
|
|
}
|
|
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 None;
|
|
}
|
|
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_TF_API)
|