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