1 //===- DevelopmentModeInlineAdvisor.cpp - runtime-loadable model runner --===// 2 // 3 // The LLVM Compiler Infrastructure 4 // 5 // This file is distributed under the University of Illinois Open Source 6 // License. See LICENSE.TXT for details. 7 // 8 //===----------------------------------------------------------------------===// 9 // 10 // This file implements a model runner using Tensorflow C APIs, allowing the 11 // loading of a model from a command line option. 12 // 13 //===----------------------------------------------------------------------===// 14 #include "llvm/Config/config.h" 15 #if defined(LLVM_HAVE_TF_API) 16 17 #include "llvm/Analysis/CallGraph.h" 18 #include "llvm/Analysis/InlineSizeEstimatorAnalysis.h" 19 #include "llvm/Analysis/MLInlineAdvisor.h" 20 #include "llvm/Analysis/Utils/TFUtils.h" 21 #include "llvm/IR/LLVMContext.h" 22 #include "llvm/Support/CommandLine.h" 23 #include "llvm/Support/ManagedStatic.h" 24 #include "llvm/Support/Path.h" 25 26 #include <vector> 27 28 using namespace llvm; 29 30 static cl::opt<std::string> TrainingLog( 31 "training-log", cl::Hidden, 32 cl::desc("Path where the development - mode inlining log is saved.")); 33 34 static cl::opt<std::string> TFModelUnderTrainingPath( 35 "ml-inliner-model-under-training", cl::Hidden, 36 cl::desc(R"(Path to SavedModel from the previous training iteration. 37 The directory is also expected to contain a JSON specification of the 38 outputs expected to be logged, where the first entry must be the 39 inlining decision. The file containing the specification should be 40 called output_spec.json. The expected JSON value is an array of 41 dictionaries. Each dictionary should have 2 keys: 42 43 - "tensor_spec, followed by the TensorSpec description of the 44 output; and 45 - "logging_name", a string indicating the name to use when 46 logging the output values. 47 48 Example: 49 [ 50 { 51 "logging_name" : "some_name", 52 "tensor_spec" : { 53 "name" : "model_name", 54 "port" : 0, 55 "shape" : [2, 3], 56 "type" : "float" 57 } 58 } 59 ] 60 61 The first value must always correspond to the decision.)")); 62 63 static cl::opt<std::string> TFOutputSpecOverride( 64 "ml-inliner-output-spec-override", cl::Hidden, 65 cl::desc("Override the path to the output spec json file. See " 66 "-ml-inliner-model-under-training documentation for the " 67 "specification of that file.")); 68 69 static cl::opt<std::string> TFFeedPrefix("ml-inliner-trained-model-feed-prefix", 70 cl::Hidden, cl::init("action_"), 71 cl::desc("Prefix for feature names.")); 72 73 namespace { 74 /// An InlineEvent, used by TrainingLogger. 75 struct InlineEvent { 76 /// What the default policy's decision would have been. 77 bool DefaultDecision = false; 78 79 /// What we advised. When training off the default policy, this is the same as 80 /// DefaultDecision. 81 bool AdvisedDecision = false; 82 83 /// What actually happened. This would be 'false' in the case of an inline 84 /// error, even if AdvisedDecision were true, otherwise it agrees with 85 /// AdvisedDecision. 86 bool Effect = false; 87 88 /// What the change in size was: size_after - size_before 89 int64_t Reward = 0; 90 }; 91 92 /// Collect data we may use for training a model, and write it as a textual 93 /// Tensorflow SequenceExample 94 /// (https://www.tensorflow.org/api_docs/python/tf/train/SequenceExample) 95 /// protobuf (https://developers.google.com/protocol-buffers). 96 /// Because this is a protobuf, we cannot just stream the events as they come. 97 /// Internally, TrainingLogger stores data in column-major format, because that 98 /// lines up with how TF SequenceExample represents it. 99 class ModelUnderTrainingRunner; 100 class TrainingLogger final { 101 public: 102 TrainingLogger(StringRef LogFileName, const ModelUnderTrainingRunner *MUTR); 103 104 /// Log one inlining event. 105 void logInlineEvent(const InlineEvent &Event, 106 const MLModelRunner &ModelRunner); 107 108 /// Print the stored tensors. 109 void print(); 110 111 private: 112 /// Write the values of one tensor as a list. 113 template <typename T> 114 void writeTensorValues(raw_fd_ostream &OutFile, const char *TensorData, 115 size_t ElemCount) const { 116 OutFile << "["; 117 const T *TypedData = reinterpret_cast<const T *>(TensorData); 118 for (size_t I = 0; I < ElemCount; ++I) { 119 if (I > 0) 120 OutFile << ", "; 121 OutFile << TypedData[I]; 122 } 123 OutFile << "]"; 124 } 125 126 /// Write a list of tensors as a sequence of TensorFlow FeatureList protobufs. 127 /// The tensors are assumed to be stored contiguously, in row-major format, 128 /// in the TensorData buffer. Each tensor has the shape given by Spec. The 129 /// feature name in the output is either the provided LoggingName, if 130 /// specified, otherwise it's the name of the tensor (as given by Spec). 131 template <typename T> 132 void 133 writeTensorsAsFeatureLists(raw_fd_ostream &OutFile, const TensorSpec &Spec, 134 const T *TensorData, size_t TensorCount, 135 Optional<StringRef> LoggingName = None) const { 136 writeRawTensorsAsFeatureLists(OutFile, Spec, 137 reinterpret_cast<const char *>(TensorData), 138 TensorCount, LoggingName); 139 } 140 141 /// Untyped implementation of the API above. 142 void 143 writeRawTensorsAsFeatureLists(raw_fd_ostream &OutFile, const TensorSpec &Spec, 144 const char *TensorData, size_t TensorCount, 145 Optional<StringRef> LoggingName = None) const { 146 const char *FieldName = "<invalid>"; 147 std::function<void(const char *)> ValueWriter; 148 // The 'Feature' protobuf only has 3 possible fields: float_list, 149 // int64_list, or bytes_list, so we capture int32 values as int64. We don't 150 // support any other types. 151 if (Spec.isElementType<int64_t>()) { 152 FieldName = "int64_list"; 153 ValueWriter = [&](const char *Data) { 154 writeTensorValues<int64_t>(OutFile, Data, Spec.getElementCount()); 155 }; 156 } else if (Spec.isElementType<int32_t>()) { 157 FieldName = "int64_list"; 158 ValueWriter = [&](const char *Data) { 159 writeTensorValues<int32_t>(OutFile, Data, Spec.getElementCount()); 160 }; 161 162 } else if (Spec.isElementType<float>()) { 163 FieldName = "float_list"; 164 ValueWriter = [&](const char *Data) { 165 writeTensorValues<float>(OutFile, Data, Spec.getElementCount()); 166 }; 167 168 } else 169 llvm_unreachable("Unsupported tensor type."); 170 171 OutFile << " feature_list: {\n"; 172 OutFile << " key: " 173 << "\"" << (LoggingName ? *LoggingName : Spec.name()) << "\" "; 174 OutFile << "value: {\n"; 175 size_t TensorByteSize = Spec.getElementCount() * Spec.getElementByteSize(); 176 for (const char *P = TensorData, 177 *E = TensorData + TensorByteSize * TensorCount; 178 P < E; P += TensorByteSize) { 179 OutFile << " feature: { " << FieldName << ": { value: "; 180 ValueWriter(P); 181 OutFile << " } }\n"; 182 } 183 OutFile << " }\n"; 184 OutFile << " }\n"; 185 } 186 187 StringRef LogFileName; 188 const ModelUnderTrainingRunner *const MUTR; 189 std::vector<InlineFeatures> Features; 190 std::vector<int64_t> DefaultDecisions; 191 // We store all outputs as data blobs, but we always expect to have one, the 192 // first one, representing the decision. While we could track that separately, 193 // for uniformity, we store it, generically, here. 194 std::vector<std::vector<char>> Outputs; 195 std::vector<bool> Effects; 196 std::vector<int64_t> Rewards; 197 }; 198 199 /// An extension of the MLInlineAdvisor for the 'development' mode, targeting 200 /// the offline training scenario. Note that training happens outside of the 201 /// compiler, this facility is concerned with producing training data ("logs"). 202 /// This InlineAdvisor can operate in the following modes: 203 /// 204 /// 1) collect logs for the default policy. This is useful for bootstrapping 205 /// training, which will be considerably faster by starting from a reasonable 206 /// policy. 207 /// 208 /// 2) collect logs for the ML policy, using a model from a previous 209 /// training. Potentially, that model uses internally some small random 210 /// perturbation of its weights, to induce exploration (setting this up is the 211 /// responsibility of the training algorithm). The logs would then be used to 212 /// retrain and improve on this model. 213 /// 214 /// 3) use the provided model, with no logging. This is useful for end to end 215 /// validation - the model, in this case, is a release candidate and shouldn't 216 /// have random perturbations. It is a convenience feature: rather than needing 217 /// to take the release candidate model and compile it in 'release' mode, 218 /// validate it, then potentially discard it, it's easier to just pass the model 219 /// to the compiler, albeit compilation would be slower, as a one-off. Once the 220 /// model behaves satisfactorily, it can be compiled AOT, for efficiency, in 221 /// release mode. The expectation is that a well-trained model provides a good 222 /// policy over a sufficiently diverse codebase, over many changes (i.e. 223 /// training happens seldom). 224 class DevelopmentModeMLInlineAdvisor : public MLInlineAdvisor { 225 public: 226 DevelopmentModeMLInlineAdvisor( 227 Module &M, ModuleAnalysisManager &MAM, 228 std::unique_ptr<MLModelRunner> ModelRunner, 229 std::function<bool(CallBase &)> GetDefaultAdvice, bool IsDoingInference, 230 std::unique_ptr<TrainingLogger> Logger); 231 232 size_t getTotalSizeEstimate(); 233 234 virtual ~DevelopmentModeMLInlineAdvisor(); 235 void updateNativeSizeEstimate(int64_t Change) { 236 *CurrentNativeSize += Change; 237 } 238 void resetNativeSize(Function *F) { 239 FAM.invalidate<InlineSizeEstimatorAnalysis>(*F); 240 } 241 242 std::unique_ptr<MLInlineAdvice> 243 getMandatoryAdvice(CallBase &CB, OptimizationRemarkEmitter &ORE) override; 244 std::unique_ptr<MLInlineAdvice> 245 getAdviceFromModel(CallBase &CB, OptimizationRemarkEmitter &ORE) override; 246 247 Optional<size_t> getNativeSizeEstimate(const Function &F) const; 248 249 private: 250 bool isLogging() const { return !!Logger; } 251 252 std::function<bool(CallBase &)> GetDefaultAdvice; 253 const bool IsDoingInference; 254 std::unique_ptr<TrainingLogger> Logger; 255 256 const Optional<int32_t> InitialNativeSize; 257 Optional<int32_t> CurrentNativeSize; 258 }; 259 260 /// A variant of MLInlineAdvice that tracks all non-trivial inlining 261 /// decisions, for training/logging. 262 class LoggingMLInlineAdvice : public MLInlineAdvice { 263 public: 264 LoggingMLInlineAdvice(DevelopmentModeMLInlineAdvisor *Advisor, CallBase &CB, 265 OptimizationRemarkEmitter &ORE, bool Recommendation, 266 TrainingLogger &Logger, 267 Optional<size_t> CallerSizeEstimateBefore, 268 Optional<size_t> CalleeSizeEstimateBefore, 269 bool DefaultDecision, bool Mandatory = false) 270 : MLInlineAdvice(Advisor, CB, ORE, Recommendation), Logger(Logger), 271 CallerSizeEstimateBefore(CallerSizeEstimateBefore), 272 CalleeSizeEstimateBefore(CalleeSizeEstimateBefore), 273 DefaultDecision(DefaultDecision), Mandatory(Mandatory) {} 274 275 virtual ~LoggingMLInlineAdvice() = default; 276 277 private: 278 DevelopmentModeMLInlineAdvisor *getAdvisor() const { 279 return static_cast<DevelopmentModeMLInlineAdvisor *>(Advisor); 280 } 281 void recordInliningImpl() override { 282 MLInlineAdvice::recordInliningImpl(); 283 getAdvisor()->resetNativeSize(Caller); 284 int Reward = std::numeric_limits<int>::max(); 285 if (InlineSizeEstimatorAnalysis::isEvaluatorRequested() && 286 !getAdvisor()->isForcedToStop()) { 287 int NativeSizeAfter = *getAdvisor()->getNativeSizeEstimate(*Caller) + 288 *CalleeSizeEstimateBefore; 289 Reward = NativeSizeAfter - 290 (*CallerSizeEstimateBefore + *CalleeSizeEstimateBefore); 291 getAdvisor()->updateNativeSizeEstimate(Reward); 292 } 293 log(Reward, /*Success=*/true); 294 } 295 296 void recordInliningWithCalleeDeletedImpl() override { 297 MLInlineAdvice::recordInliningWithCalleeDeletedImpl(); 298 getAdvisor()->resetNativeSize(Caller); 299 if (InlineSizeEstimatorAnalysis::isEvaluatorRequested() && 300 !getAdvisor()->isForcedToStop()) { 301 int NativeSizeAfter = *getAdvisor()->getNativeSizeEstimate(*Caller); 302 int Reward = NativeSizeAfter - 303 (*CallerSizeEstimateBefore + *CalleeSizeEstimateBefore); 304 getAdvisor()->updateNativeSizeEstimate(Reward); 305 log(Reward, /*Success=*/true); 306 } 307 } 308 309 void recordUnsuccessfulInliningImpl(const InlineResult &Result) override { 310 MLInlineAdvice::recordUnsuccessfulInliningImpl(Result); 311 log(NoReward, /*Success=*/false); 312 } 313 314 void recordUnattemptedInliningImpl() override { 315 MLInlineAdvice::recordUnattemptedInliningImpl(); 316 log(NoReward, /*Success=*/false); 317 } 318 319 void log(int64_t Reward, bool Success) { 320 if (Mandatory) 321 return; 322 InlineEvent Event; 323 Event.AdvisedDecision = isInliningRecommended(); 324 Event.DefaultDecision = DefaultDecision; 325 Event.Effect = Success; 326 Event.Reward = Reward; 327 Logger.logInlineEvent(Event, getAdvisor()->getModelRunner()); 328 } 329 330 static const int64_t NoReward = 0; 331 TrainingLogger &Logger; 332 const Optional<size_t> CallerSizeEstimateBefore; 333 const Optional<size_t> CalleeSizeEstimateBefore; 334 const bool DefaultDecision; 335 const bool Mandatory; 336 }; 337 338 /// A pseudo model runner. We use it to store feature values when collecting 339 /// logs for the default policy, but never ask it to 'run'. 340 class NoInferenceModelRunner : public MLModelRunner { 341 public: 342 NoInferenceModelRunner(LLVMContext &Ctx) 343 : MLModelRunner(Ctx), Features(NumberOfFeatures) {} 344 void setFeature(FeatureIndex Index, int64_t Value) override { 345 Features[static_cast<int>(Index)] = Value; 346 } 347 348 int64_t getFeature(int Index) const override { return Features[Index]; } 349 bool run() override { 350 llvm_unreachable("We shouldn't call run on this model runner."); 351 } 352 353 private: 354 InlineFeatures Features; 355 }; 356 357 /// ModelUnderTrainingRunner - training mode implementation. It uses TF C APIs 358 /// to dynamically load and evaluate a TF SavedModel 359 /// (https://www.tensorflow.org/guide/saved_model). Runtime performance is 360 /// sacrificed for ease of use while training. 361 class ModelUnderTrainingRunner final : public MLModelRunner { 362 public: 363 ModelUnderTrainingRunner(LLVMContext &Ctx, const std::string &ModelPath); 364 365 bool run() override; 366 367 // Disallows copy and assign. 368 ModelUnderTrainingRunner(const ModelUnderTrainingRunner &) = delete; 369 ModelUnderTrainingRunner & 370 operator=(const ModelUnderTrainingRunner &) = delete; 371 372 void setFeature(FeatureIndex Index, int64_t Value) override; 373 int64_t getFeature(int Index) const override; 374 bool isValid() const { return !!Evaluator; } 375 376 const std::vector<std::string> outputNames() const { return OutputNames; } 377 378 const std::vector<TensorSpec> outputSpecs() const { return OutputSpecs; } 379 380 const Optional<TFModelEvaluator::EvaluationResult> & 381 lastEvaluationResult() const { 382 return LastEvaluationResult; 383 } 384 385 private: 386 std::unique_ptr<TFModelEvaluator> Evaluator; 387 std::vector<std::string> OutputNames; 388 std::vector<TensorSpec> OutputSpecs; 389 Optional<TFModelEvaluator::EvaluationResult> LastEvaluationResult; 390 391 bool loadOutputSpecs(LLVMContext &Ctx, StringRef FileName); 392 393 // The training framework needs some additional features. 394 const std::vector<TensorSpec> TrainingOnlyFeatures{ 395 TensorSpec::createSpec<int64_t>(TFFeedPrefix + "inlining_default", {1}), 396 TensorSpec::createSpec<float>(TFFeedPrefix + "discount", {1}), 397 TensorSpec::createSpec<float>(TFFeedPrefix + "reward", {1}), 398 TensorSpec::createSpec<int32_t>(TFFeedPrefix + "step_type", {1})}; 399 }; 400 } // namespace 401 402 TrainingLogger::TrainingLogger(StringRef LogFileName, 403 const ModelUnderTrainingRunner *MUTR) 404 : LogFileName(LogFileName), MUTR(MUTR) { 405 for (size_t I = 0; I < NumberOfFeatures; ++I) 406 Features.push_back(InlineFeatures()); 407 408 // The first output is the inlining decision. 409 auto OutputCount = MUTR ? MUTR->outputSpecs().size() : 1; 410 Outputs.assign(OutputCount, std::vector<char>()); 411 } 412 413 /// Log one inlining event. 414 void TrainingLogger::logInlineEvent(const InlineEvent &Event, 415 const MLModelRunner &ModelRunner) { 416 for (size_t I = 0; I < NumberOfFeatures; ++I) 417 Features[I].push_back(ModelRunner.getFeature(I)); 418 419 Effects.push_back(Event.Effect); 420 Rewards.push_back(Event.Reward); 421 DefaultDecisions.push_back(Event.DefaultDecision); 422 int64_t Advice = static_cast<int64_t>(Event.AdvisedDecision); 423 const char *AdviceData = reinterpret_cast<const char *>(&Advice); 424 Outputs[0].insert(Outputs[0].end(), AdviceData, AdviceData + sizeof(int64_t)); 425 for (size_t I = 1; I < Outputs.size(); ++I) { 426 const auto &Result = *MUTR->lastEvaluationResult(); 427 auto &Spec = MUTR->outputSpecs()[I]; 428 const char *RawData = 429 reinterpret_cast<const char *>(Result.getUntypedTensorValue(I)); 430 Outputs[I].insert(Outputs[I].end(), RawData, 431 RawData + 432 Spec.getElementCount() * Spec.getElementByteSize()); 433 } 434 } 435 436 void TrainingLogger::print() { 437 std::error_code EC; 438 raw_fd_ostream OutFile(LogFileName, EC); 439 size_t NumberOfRecords = Rewards.size(); 440 if (NumberOfRecords == 0) 441 return; 442 443 OutFile << "feature_lists: {\n"; 444 for (size_t I = 0; I < Features.size(); ++I) 445 writeTensorsAsFeatureLists( 446 OutFile, TensorSpec::createSpec<int64_t>(FeatureNameMap.at(I), {1}), 447 Features[I].data(), NumberOfRecords); 448 449 writeTensorsAsFeatureLists( 450 OutFile, TensorSpec::createSpec<int64_t>(DefaultDecisionName, {1}), 451 DefaultDecisions.data(), NumberOfRecords); 452 453 writeRawTensorsAsFeatureLists( 454 OutFile, TensorSpec::createSpec<int64_t>(DecisionName, {1}), 455 Outputs[0].data(), NumberOfRecords); 456 457 if (InlineSizeEstimatorAnalysis::isEvaluatorRequested()) 458 writeTensorsAsFeatureLists(OutFile, 459 TensorSpec::createSpec<int64_t>(RewardName, {1}), 460 Rewards.data(), NumberOfRecords); 461 462 for (size_t I = 1; I < Outputs.size(); ++I) 463 writeRawTensorsAsFeatureLists(OutFile, MUTR->outputSpecs()[I], 464 Outputs[I].data(), NumberOfRecords, 465 StringRef(MUTR->outputNames()[I])); 466 467 OutFile << "}\n"; 468 } 469 470 DevelopmentModeMLInlineAdvisor::DevelopmentModeMLInlineAdvisor( 471 Module &M, ModuleAnalysisManager &MAM, 472 std::unique_ptr<MLModelRunner> ModelRunner, 473 std::function<bool(CallBase &)> GetDefaultAdvice, bool IsDoingInference, 474 std::unique_ptr<TrainingLogger> Logger) 475 : MLInlineAdvisor(M, MAM, std::move(ModelRunner)), 476 GetDefaultAdvice(GetDefaultAdvice), IsDoingInference(IsDoingInference), 477 Logger(std::move(Logger)), 478 InitialNativeSize(isLogging() ? getTotalSizeEstimate() : 0), 479 CurrentNativeSize(InitialNativeSize) { 480 // We cannot have the case of neither inference nor logging. 481 assert(IsDoingInference || isLogging()); 482 } 483 484 DevelopmentModeMLInlineAdvisor::~DevelopmentModeMLInlineAdvisor() { 485 if (isLogging()) 486 Logger->print(); 487 } 488 489 Optional<size_t> 490 DevelopmentModeMLInlineAdvisor::getNativeSizeEstimate(const Function &F) const { 491 if (!InlineSizeEstimatorAnalysis::isEvaluatorRequested()) 492 return None; 493 auto &R = 494 FAM.getResult<InlineSizeEstimatorAnalysis>(const_cast<Function &>(F)); 495 if (!R) { 496 F.getParent()->getContext().emitError( 497 "Native size estimator is not present."); 498 return 0; 499 } 500 return *R; 501 } 502 503 std::unique_ptr<MLInlineAdvice> 504 DevelopmentModeMLInlineAdvisor::getMandatoryAdvice( 505 CallBase &CB, OptimizationRemarkEmitter &ORE) { 506 if (!isLogging()) 507 return MLInlineAdvisor::getMandatoryAdvice(CB, ORE); 508 509 return std::make_unique<LoggingMLInlineAdvice>( 510 /*Advisor=*/this, 511 /*CB=*/CB, /*ORE=*/ORE, /*Recommendation=*/true, /*Logger=*/*Logger, 512 /*CallerSizeEstimateBefore=*/getNativeSizeEstimate(*CB.getCaller()), 513 /*CalleeSizeEstimateBefore=*/ 514 getNativeSizeEstimate(*CB.getCalledFunction()), 515 /*DefaultDecision=*/true, /*Mandatory*/ true); 516 } 517 518 std::unique_ptr<MLInlineAdvice> 519 DevelopmentModeMLInlineAdvisor::getAdviceFromModel( 520 CallBase &CB, OptimizationRemarkEmitter &ORE) { 521 if (IsDoingInference && !isLogging()) 522 return MLInlineAdvisor::getAdviceFromModel(CB, ORE); 523 524 bool DefaultAdvice = GetDefaultAdvice(CB); 525 auto Recommendation = IsDoingInference ? ModelRunner->run() : DefaultAdvice; 526 return std::make_unique<LoggingMLInlineAdvice>( 527 /*Advisor=*/this, 528 /*CB=*/CB, /*ORE=*/ORE, /*Recommendation=*/Recommendation, 529 /*Logger=*/*Logger, 530 /*CallerSizeEstimateBefore=*/getNativeSizeEstimate(*CB.getCaller()), 531 /*CalleeSizeEstimateBefore=*/ 532 getNativeSizeEstimate(*CB.getCalledFunction()), 533 /*DefaultDecision=*/DefaultAdvice); 534 } 535 536 size_t DevelopmentModeMLInlineAdvisor::getTotalSizeEstimate() { 537 if (!InlineSizeEstimatorAnalysis::isEvaluatorRequested()) 538 return 0; 539 size_t Ret = 0; 540 for (auto &F : M) { 541 if (F.isDeclaration()) 542 continue; 543 if (isFunctionDeleted(&F)) 544 continue; 545 Ret += *getNativeSizeEstimate(F); 546 } 547 return Ret; 548 } 549 550 ModelUnderTrainingRunner::ModelUnderTrainingRunner(LLVMContext &Ctx, 551 const std::string &ModelPath) 552 : MLModelRunner(Ctx) { 553 std::vector<TensorSpec> InputSpecs; 554 for (size_t I = 0; I < NumberOfFeatures; ++I) 555 InputSpecs.push_back( 556 TensorSpec::createSpec<int64_t>(TFFeedPrefix + FeatureNameMap[I], {1})); 557 InputSpecs.insert(InputSpecs.end(), TrainingOnlyFeatures.begin(), 558 TrainingOnlyFeatures.end()); 559 SmallVector<char, 128> OutputSpecsPath; 560 StringRef OutputSpecPath = TFOutputSpecOverride; 561 if (OutputSpecPath.empty()) { 562 llvm::sys::path::append(OutputSpecsPath, ModelPath, "output_spec.json"); 563 OutputSpecPath = {OutputSpecsPath.data(), OutputSpecsPath.size()}; 564 } 565 if (!loadOutputSpecs(Ctx, OutputSpecPath)) 566 return; 567 568 Evaluator = 569 std::make_unique<TFModelEvaluator>(ModelPath, InputSpecs, OutputSpecs); 570 if (!Evaluator || !Evaluator->isValid()) { 571 Ctx.emitError("Failed to create inliner saved model evaluator"); 572 Evaluator.reset(); 573 return; 574 } 575 } 576 577 bool ModelUnderTrainingRunner::loadOutputSpecs(LLVMContext &Ctx, 578 StringRef FileName) { 579 auto BufferOrError = MemoryBuffer::getFileOrSTDIN(FileName); 580 if (!BufferOrError) { 581 Ctx.emitError("Error opening output specs file: " + FileName + " : " + 582 BufferOrError.getError().message()); 583 return false; 584 } 585 auto ParsedJSONValues = json::parse(BufferOrError.get()->getBuffer()); 586 if (!ParsedJSONValues) { 587 Ctx.emitError("Could not parse specs file: " + FileName); 588 return false; 589 } 590 auto ValuesArray = ParsedJSONValues->getAsArray(); 591 if (!ValuesArray) { 592 Ctx.emitError("Expected an array of {tensor_spec:<TensorSpec>, " 593 "logging_name:<name>} dictionaries"); 594 return false; 595 } 596 597 for (const auto &Value : *ValuesArray) 598 if (const auto *Obj = Value.getAsObject()) 599 if (const auto *SpecPart = Obj->get("tensor_spec")) 600 if (auto TensorSpec = getTensorSpecFromJSON(Ctx, *SpecPart)) 601 if (auto LoggingName = Obj->getString("logging_name")) { 602 if (!TensorSpec->isElementType<int64_t>() && 603 !TensorSpec->isElementType<int32_t>() && 604 !TensorSpec->isElementType<float>()) { 605 Ctx.emitError( 606 "Only int64, int32, and float tensors are supported. " 607 "Found unsupported type for tensor named " + 608 TensorSpec->name()); 609 return false; 610 } 611 OutputNames.push_back(LoggingName->str()); 612 OutputSpecs.push_back(*TensorSpec); 613 } 614 615 if (ValuesArray->size() != OutputNames.size()) { 616 Ctx.emitError( 617 "Unable to parse output spec. It should be a json file containing an " 618 "array of dictionaries. Each dictionary must have a 'tensor_spec' key, " 619 "with a json object describing a TensorSpec; and a 'logging_name' key, " 620 "which is a string to use as name when logging this tensor in the " 621 "training log."); 622 return false; 623 } 624 assert(OutputNames.size() == OutputSpecs.size()); 625 if (OutputNames.empty() || OutputNames[0] != DecisionName) { 626 Ctx.emitError("The first output spec must describe the decision tensor, " 627 "and must have the logging_name " + 628 StringRef(DecisionName)); 629 return false; 630 } 631 return true; 632 } 633 634 bool ModelUnderTrainingRunner::run() { 635 LastEvaluationResult = Evaluator->evaluate(); 636 if (!LastEvaluationResult.hasValue()) { 637 Ctx.emitError("Error evaluating model."); 638 return false; 639 } 640 int64_t Decision = *LastEvaluationResult->getTensorValue<int64_t>(0); 641 return static_cast<bool>(Decision); 642 } 643 644 int64_t ModelUnderTrainingRunner::getFeature(int Index) const { 645 return *Evaluator->getInput<int64_t>(Index); 646 } 647 648 void ModelUnderTrainingRunner::setFeature(FeatureIndex Index, int64_t Value) { 649 size_t NumericIndex = static_cast<size_t>(Index); 650 *(Evaluator->getInput<int64_t>(NumericIndex)) = Value; 651 } 652 653 std::unique_ptr<InlineAdvisor> llvm::getDevelopmentModeAdvisor( 654 Module &M, ModuleAnalysisManager &MAM, 655 std::function<bool(CallBase &)> GetDefaultAdvice) { 656 auto &Ctx = M.getContext(); 657 std::unique_ptr<MLModelRunner> Runner; 658 ModelUnderTrainingRunner *MUTRPtr = nullptr; 659 bool IsDoingInference = false; 660 if (TFModelUnderTrainingPath.empty()) 661 Runner.reset(new NoInferenceModelRunner(Ctx)); 662 else { 663 auto MUTR = std::make_unique<ModelUnderTrainingRunner>( 664 Ctx, TFModelUnderTrainingPath); 665 if (!MUTR || !MUTR->isValid()) { 666 Ctx.emitError("Could not load the policy model from the provided path"); 667 return nullptr; 668 } 669 IsDoingInference = true; 670 MUTRPtr = MUTR.get(); 671 Runner = std::move(MUTR); 672 } 673 std::unique_ptr<TrainingLogger> Logger; 674 if (!TrainingLog.empty()) 675 Logger = std::make_unique<TrainingLogger>(TrainingLog, MUTRPtr); 676 677 return std::make_unique<DevelopmentModeMLInlineAdvisor>( 678 M, MAM, std::move(Runner), GetDefaultAdvice, IsDoingInference, 679 std::move(Logger)); 680 } 681 #endif // defined(LLVM_HAVE_TF_API) 682