//===- TensorSpec.cpp - tensor type abstraction ---------------------------===// // // 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 file for the abstraction of a tensor type, and JSON loading // utils. // //===----------------------------------------------------------------------===// #include "llvm/Config/config.h" #include "llvm/ADT/Twine.h" #include "llvm/Analysis/TensorSpec.h" #include "llvm/Support/CommandLine.h" #include "llvm/Support/Debug.h" #include "llvm/Support/JSON.h" #include "llvm/Support/ManagedStatic.h" #include "llvm/Support/MemoryBuffer.h" #include "llvm/Support/Path.h" #include "llvm/Support/raw_ostream.h" #include #include using namespace llvm; namespace llvm { #define TFUTILS_GETDATATYPE_IMPL(T, E) \ template <> TensorType TensorSpec::getDataType() { return TensorType::E; } SUPPORTED_TENSOR_TYPES(TFUTILS_GETDATATYPE_IMPL) #undef TFUTILS_GETDATATYPE_IMPL TensorSpec::TensorSpec(const std::string &Name, int Port, TensorType Type, size_t ElementSize, const std::vector &Shape) : Name(Name), Port(Port), Type(Type), Shape(Shape), ElementCount(std::accumulate(Shape.begin(), Shape.end(), 1, std::multiplies())), ElementSize(ElementSize) {} Optional getTensorSpecFromJSON(LLVMContext &Ctx, const json::Value &Value) { auto EmitError = [&](const llvm::Twine &Message) -> Optional { std::string S; llvm::raw_string_ostream OS(S); OS << Value; Ctx.emitError("Unable to parse JSON Value as spec (" + Message + "): " + S); return None; }; // FIXME: accept a Path as a parameter, and use it for error reporting. json::Path::Root Root("tensor_spec"); json::ObjectMapper Mapper(Value, Root); if (!Mapper) return EmitError("Value is not a dict"); std::string TensorName; int TensorPort = -1; std::string TensorType; std::vector TensorShape; if (!Mapper.map("name", TensorName)) return EmitError("'name' property not present or not a string"); if (!Mapper.map("type", TensorType)) return EmitError("'type' property not present or not a string"); if (!Mapper.map("port", TensorPort)) return EmitError("'port' property not present or not an int"); if (!Mapper.map>("shape", TensorShape)) return EmitError("'shape' property not present or not an int array"); #define PARSE_TYPE(T, E) \ if (TensorType == #T) \ return TensorSpec::createSpec(TensorName, TensorShape, TensorPort); SUPPORTED_TENSOR_TYPES(PARSE_TYPE) #undef PARSE_TYPE return None; } 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 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:, " "logging_name:} dictionaries"); return None; } 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 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 llvm