xref: /netbsd-src/external/apache2/llvm/dist/llvm/lib/Analysis/DevelopmentModeInlineAdvisor.cpp (revision 82d56013d7b633d116a93943de88e08335357a7c)
1*82d56013Sjoerg //===- DevelopmentModeInlineAdvisor.cpp - runtime-loadable model runner  --===//
2*82d56013Sjoerg //
3*82d56013Sjoerg //                     The LLVM Compiler Infrastructure
4*82d56013Sjoerg //
5*82d56013Sjoerg // This file is distributed under the University of Illinois Open Source
6*82d56013Sjoerg // License. See LICENSE.TXT for details.
7*82d56013Sjoerg //
8*82d56013Sjoerg //===----------------------------------------------------------------------===//
9*82d56013Sjoerg //
10*82d56013Sjoerg // This file implements a model runner using Tensorflow C APIs, allowing the
11*82d56013Sjoerg // loading of a model from a command line option.
12*82d56013Sjoerg //
13*82d56013Sjoerg //===----------------------------------------------------------------------===//
14*82d56013Sjoerg #include "llvm/Config/config.h"
15*82d56013Sjoerg #if defined(LLVM_HAVE_TF_API)
16*82d56013Sjoerg 
17*82d56013Sjoerg #include "llvm/Analysis/CallGraph.h"
18*82d56013Sjoerg #include "llvm/Analysis/InlineSizeEstimatorAnalysis.h"
19*82d56013Sjoerg #include "llvm/Analysis/MLInlineAdvisor.h"
20*82d56013Sjoerg #include "llvm/Analysis/Utils/TFUtils.h"
21*82d56013Sjoerg #include "llvm/IR/LLVMContext.h"
22*82d56013Sjoerg #include "llvm/Support/CommandLine.h"
23*82d56013Sjoerg #include "llvm/Support/ManagedStatic.h"
24*82d56013Sjoerg 
25*82d56013Sjoerg #include <vector>
26*82d56013Sjoerg 
27*82d56013Sjoerg using namespace llvm;
28*82d56013Sjoerg 
29*82d56013Sjoerg static cl::opt<std::string> TrainingLog(
30*82d56013Sjoerg     "training-log", cl::Hidden,
31*82d56013Sjoerg     cl::desc("Path where the development - mode inlining log is saved."));
32*82d56013Sjoerg 
33*82d56013Sjoerg static cl::opt<std::string> TFModelUnderTrainingPath(
34*82d56013Sjoerg     "ml-inliner-model-under-training", cl::Hidden,
35*82d56013Sjoerg     cl::desc(R"(Path to SavedModel from the previous training iteration.
36*82d56013Sjoerg The directory is also expected to contain a JSON specification of the
37*82d56013Sjoerg outputs expected to be logged, where the first entry must be the
38*82d56013Sjoerg inlining decision. The file containing the specification should be
39*82d56013Sjoerg called output_spec.json. The expected JSON value is an array of
40*82d56013Sjoerg dictionaries. Each dictionary should have 2 keys:
41*82d56013Sjoerg 
42*82d56013Sjoerg - "tensor_spec, followed by the TensorSpec description of the
43*82d56013Sjoerg output; and
44*82d56013Sjoerg - "logging_name", a string indicating the name to use when
45*82d56013Sjoerg logging the output values.
46*82d56013Sjoerg 
47*82d56013Sjoerg Example:
48*82d56013Sjoerg [
49*82d56013Sjoerg   {
50*82d56013Sjoerg     "logging_name" : "some_name",
51*82d56013Sjoerg     "tensor_spec" : {
52*82d56013Sjoerg       "name" : "model_name",
53*82d56013Sjoerg       "port" : 0,
54*82d56013Sjoerg       "shape" : [2, 3],
55*82d56013Sjoerg       "type" : "float"
56*82d56013Sjoerg       }
57*82d56013Sjoerg   }
58*82d56013Sjoerg ]
59*82d56013Sjoerg 
60*82d56013Sjoerg The first value must always correspond to the decision.)"));
61*82d56013Sjoerg 
62*82d56013Sjoerg static cl::opt<std::string> TFOutputSpecOverride(
63*82d56013Sjoerg     "ml-inliner-output-spec-override", cl::Hidden,
64*82d56013Sjoerg     cl::desc("Override the path to the output spec json file. See "
65*82d56013Sjoerg              "-ml-inliner-model-under-training documentation for the "
66*82d56013Sjoerg              "specification of that file."));
67*82d56013Sjoerg 
68*82d56013Sjoerg static cl::opt<std::string> TFFeedPrefix("ml-inliner-trained-model-feed-prefix",
69*82d56013Sjoerg                                          cl::Hidden, cl::init("action_"),
70*82d56013Sjoerg                                          cl::desc("Prefix for feature names."));
71*82d56013Sjoerg 
72*82d56013Sjoerg namespace {
73*82d56013Sjoerg /// An InlineEvent, used by TrainingLogger.
74*82d56013Sjoerg struct InlineEvent {
75*82d56013Sjoerg   /// What the default policy's decision would have been.
76*82d56013Sjoerg   int64_t DefaultDecision = 0;
77*82d56013Sjoerg 
78*82d56013Sjoerg   /// What we advised. When training off the default policy, this is the same as
79*82d56013Sjoerg   /// DefaultDecision.
80*82d56013Sjoerg   int64_t AdvisedDecision = 0;
81*82d56013Sjoerg 
82*82d56013Sjoerg   /// What actually happened. This would be 'false' in the case of an inline
83*82d56013Sjoerg   /// error, even if AdvisedDecision were true, otherwise it agrees with
84*82d56013Sjoerg   /// AdvisedDecision.
85*82d56013Sjoerg   bool Effect = false;
86*82d56013Sjoerg 
87*82d56013Sjoerg   /// What the change in size was: size_after - size_before
88*82d56013Sjoerg   int64_t Reward = 0;
89*82d56013Sjoerg };
90*82d56013Sjoerg 
91*82d56013Sjoerg /// Collect data we may use for training a model, and write it as a textual
92*82d56013Sjoerg /// Tensorflow SequenceExample
93*82d56013Sjoerg /// (https://www.tensorflow.org/api_docs/python/tf/train/SequenceExample)
94*82d56013Sjoerg /// protobuf (https://developers.google.com/protocol-buffers).
95*82d56013Sjoerg /// Because this is a protobuf, we cannot just stream the events as they come.
96*82d56013Sjoerg /// Internally, TrainingLogger stores data in column-major format, because that
97*82d56013Sjoerg /// lines up with how TF SequenceExample represents it.
98*82d56013Sjoerg class ModelUnderTrainingRunner;
99*82d56013Sjoerg class TrainingLogger final {
100*82d56013Sjoerg public:
101*82d56013Sjoerg   TrainingLogger(StringRef LogFileName, const ModelUnderTrainingRunner *MUTR);
102*82d56013Sjoerg 
103*82d56013Sjoerg   /// Log one inlining event.
104*82d56013Sjoerg   void logInlineEvent(const InlineEvent &Event,
105*82d56013Sjoerg                       const MLModelRunner &ModelRunner);
106*82d56013Sjoerg 
107*82d56013Sjoerg   /// Print the stored tensors.
108*82d56013Sjoerg   void print();
109*82d56013Sjoerg 
110*82d56013Sjoerg private:
111*82d56013Sjoerg   StringRef LogFileName;
112*82d56013Sjoerg   const ModelUnderTrainingRunner *const MUTR;
113*82d56013Sjoerg   std::unique_ptr<Logger> L;
114*82d56013Sjoerg   std::vector<bool> Effects;
115*82d56013Sjoerg   /// There's at least one output. We'll set this to a different value if MUTR
116*82d56013Sjoerg   /// is avaliable.
117*82d56013Sjoerg   size_t OutputCount = 1;
118*82d56013Sjoerg   /// Set these 2 clearly OOB, to make sure we set them later.
119*82d56013Sjoerg   size_t DefaultDecisionPos = std::numeric_limits<size_t>::max();
120*82d56013Sjoerg   size_t DecisionPos = std::numeric_limits<size_t>::max();
121*82d56013Sjoerg };
122*82d56013Sjoerg 
123*82d56013Sjoerg /// An extension of the MLInlineAdvisor for the 'development' mode, targeting
124*82d56013Sjoerg /// the offline training scenario. Note that training happens outside of the
125*82d56013Sjoerg /// compiler, this facility is concerned with producing training data ("logs").
126*82d56013Sjoerg /// This InlineAdvisor can operate in the following modes:
127*82d56013Sjoerg ///
128*82d56013Sjoerg /// 1) collect logs for the default policy. This is useful for bootstrapping
129*82d56013Sjoerg /// training, which will be considerably faster by starting from a reasonable
130*82d56013Sjoerg /// policy.
131*82d56013Sjoerg ///
132*82d56013Sjoerg /// 2) collect logs for the ML policy, using a model from a previous
133*82d56013Sjoerg /// training. Potentially, that model uses internally some small random
134*82d56013Sjoerg /// perturbation of its weights, to induce exploration (setting this up is the
135*82d56013Sjoerg /// responsibility of the training algorithm). The logs would then be used to
136*82d56013Sjoerg /// retrain and improve on this model.
137*82d56013Sjoerg ///
138*82d56013Sjoerg /// 3) use the provided model, with no logging. This is useful for end to end
139*82d56013Sjoerg /// validation - the model, in this case, is a release candidate and shouldn't
140*82d56013Sjoerg /// have random perturbations. It is a convenience feature: rather than needing
141*82d56013Sjoerg /// to take the release candidate model and compile it in 'release' mode,
142*82d56013Sjoerg /// validate it, then potentially discard it, it's easier to just pass the model
143*82d56013Sjoerg /// to the compiler, albeit compilation would be slower, as a one-off. Once the
144*82d56013Sjoerg /// model behaves satisfactorily, it can be compiled AOT, for efficiency, in
145*82d56013Sjoerg /// release mode. The expectation is that a well-trained model provides a good
146*82d56013Sjoerg /// policy over a sufficiently diverse codebase, over many changes (i.e.
147*82d56013Sjoerg /// training happens seldom).
148*82d56013Sjoerg class DevelopmentModeMLInlineAdvisor : public MLInlineAdvisor {
149*82d56013Sjoerg public:
150*82d56013Sjoerg   DevelopmentModeMLInlineAdvisor(
151*82d56013Sjoerg       Module &M, ModuleAnalysisManager &MAM,
152*82d56013Sjoerg       std::unique_ptr<MLModelRunner> ModelRunner,
153*82d56013Sjoerg       std::function<bool(CallBase &)> GetDefaultAdvice, bool IsDoingInference,
154*82d56013Sjoerg       std::unique_ptr<TrainingLogger> Logger);
155*82d56013Sjoerg 
156*82d56013Sjoerg   size_t getTotalSizeEstimate();
157*82d56013Sjoerg 
158*82d56013Sjoerg   virtual ~DevelopmentModeMLInlineAdvisor();
updateNativeSizeEstimate(int64_t Change)159*82d56013Sjoerg   void updateNativeSizeEstimate(int64_t Change) {
160*82d56013Sjoerg     *CurrentNativeSize += Change;
161*82d56013Sjoerg   }
162*82d56013Sjoerg   void resetNativeSize(Function *F) {
163*82d56013Sjoerg     PreservedAnalyses PA = PreservedAnalyses::all();
164*82d56013Sjoerg     PA.abandon<InlineSizeEstimatorAnalysis>();
165*82d56013Sjoerg     FAM.invalidate(*F, PA);
166*82d56013Sjoerg   }
167*82d56013Sjoerg 
168*82d56013Sjoerg   std::unique_ptr<MLInlineAdvice>
169*82d56013Sjoerg   getAdviceFromModel(CallBase &CB, OptimizationRemarkEmitter &ORE) override;
170*82d56013Sjoerg 
171*82d56013Sjoerg   Optional<size_t> getNativeSizeEstimate(const Function &F) const;
172*82d56013Sjoerg 
173*82d56013Sjoerg private:
174*82d56013Sjoerg   bool isLogging() const { return !!Logger; }
175*82d56013Sjoerg   std::unique_ptr<MLInlineAdvice> getMandatoryAdviceImpl(CallBase &CB) override;
176*82d56013Sjoerg 
177*82d56013Sjoerg   std::function<bool(CallBase &)> GetDefaultAdvice;
178*82d56013Sjoerg   const bool IsDoingInference;
179*82d56013Sjoerg   std::unique_ptr<TrainingLogger> Logger;
180*82d56013Sjoerg 
181*82d56013Sjoerg   const Optional<int32_t> InitialNativeSize;
182*82d56013Sjoerg   Optional<int32_t> CurrentNativeSize;
183*82d56013Sjoerg };
184*82d56013Sjoerg 
185*82d56013Sjoerg /// A variant of MLInlineAdvice that tracks all non-trivial inlining
186*82d56013Sjoerg /// decisions, for training/logging.
187*82d56013Sjoerg class LoggingMLInlineAdvice : public MLInlineAdvice {
188*82d56013Sjoerg public:
189*82d56013Sjoerg   LoggingMLInlineAdvice(DevelopmentModeMLInlineAdvisor *Advisor, CallBase &CB,
190*82d56013Sjoerg                         OptimizationRemarkEmitter &ORE, bool Recommendation,
191*82d56013Sjoerg                         TrainingLogger &Logger,
192*82d56013Sjoerg                         Optional<size_t> CallerSizeEstimateBefore,
193*82d56013Sjoerg                         Optional<size_t> CalleeSizeEstimateBefore,
194*82d56013Sjoerg                         bool DefaultDecision, bool Mandatory = false)
195*82d56013Sjoerg       : MLInlineAdvice(Advisor, CB, ORE, Recommendation), Logger(Logger),
196*82d56013Sjoerg         CallerSizeEstimateBefore(CallerSizeEstimateBefore),
197*82d56013Sjoerg         CalleeSizeEstimateBefore(CalleeSizeEstimateBefore),
198*82d56013Sjoerg         DefaultDecision(DefaultDecision), Mandatory(Mandatory) {}
199*82d56013Sjoerg 
200*82d56013Sjoerg   virtual ~LoggingMLInlineAdvice() = default;
201*82d56013Sjoerg 
202*82d56013Sjoerg private:
203*82d56013Sjoerg   DevelopmentModeMLInlineAdvisor *getAdvisor() const {
204*82d56013Sjoerg     return static_cast<DevelopmentModeMLInlineAdvisor *>(Advisor);
205*82d56013Sjoerg   }
206*82d56013Sjoerg   void recordInliningImpl() override {
207*82d56013Sjoerg     MLInlineAdvice::recordInliningImpl();
208*82d56013Sjoerg     getAdvisor()->resetNativeSize(Caller);
209*82d56013Sjoerg     int Reward = std::numeric_limits<int>::max();
210*82d56013Sjoerg     if (InlineSizeEstimatorAnalysis::isEvaluatorRequested() &&
211*82d56013Sjoerg         !getAdvisor()->isForcedToStop()) {
212*82d56013Sjoerg       int NativeSizeAfter = *getAdvisor()->getNativeSizeEstimate(*Caller) +
213*82d56013Sjoerg                             *CalleeSizeEstimateBefore;
214*82d56013Sjoerg       Reward = NativeSizeAfter -
215*82d56013Sjoerg                (*CallerSizeEstimateBefore + *CalleeSizeEstimateBefore);
216*82d56013Sjoerg       getAdvisor()->updateNativeSizeEstimate(Reward);
217*82d56013Sjoerg     }
218*82d56013Sjoerg     log(Reward, /*Success=*/true);
219*82d56013Sjoerg   }
220*82d56013Sjoerg 
221*82d56013Sjoerg   void recordInliningWithCalleeDeletedImpl() override {
222*82d56013Sjoerg     MLInlineAdvice::recordInliningWithCalleeDeletedImpl();
223*82d56013Sjoerg     getAdvisor()->resetNativeSize(Caller);
224*82d56013Sjoerg     if (InlineSizeEstimatorAnalysis::isEvaluatorRequested() &&
225*82d56013Sjoerg         !getAdvisor()->isForcedToStop()) {
226*82d56013Sjoerg       int NativeSizeAfter = *getAdvisor()->getNativeSizeEstimate(*Caller);
227*82d56013Sjoerg       int Reward = NativeSizeAfter -
228*82d56013Sjoerg                    (*CallerSizeEstimateBefore + *CalleeSizeEstimateBefore);
229*82d56013Sjoerg       getAdvisor()->updateNativeSizeEstimate(Reward);
230*82d56013Sjoerg       log(Reward, /*Success=*/true);
231*82d56013Sjoerg     }
232*82d56013Sjoerg   }
233*82d56013Sjoerg 
234*82d56013Sjoerg   void recordUnsuccessfulInliningImpl(const InlineResult &Result) override {
235*82d56013Sjoerg     MLInlineAdvice::recordUnsuccessfulInliningImpl(Result);
236*82d56013Sjoerg     log(NoReward, /*Success=*/false);
237*82d56013Sjoerg   }
238*82d56013Sjoerg 
239*82d56013Sjoerg   void recordUnattemptedInliningImpl() override {
240*82d56013Sjoerg     MLInlineAdvice::recordUnattemptedInliningImpl();
241*82d56013Sjoerg     log(NoReward, /*Success=*/false);
242*82d56013Sjoerg   }
243*82d56013Sjoerg 
244*82d56013Sjoerg   void log(int64_t Reward, bool Success) {
245*82d56013Sjoerg     if (Mandatory)
246*82d56013Sjoerg       return;
247*82d56013Sjoerg     InlineEvent Event;
248*82d56013Sjoerg     Event.AdvisedDecision = isInliningRecommended();
249*82d56013Sjoerg     Event.DefaultDecision = DefaultDecision;
250*82d56013Sjoerg     Event.Effect = Success;
251*82d56013Sjoerg     Event.Reward = Reward;
252*82d56013Sjoerg     Logger.logInlineEvent(Event, getAdvisor()->getModelRunner());
253*82d56013Sjoerg   }
254*82d56013Sjoerg 
255*82d56013Sjoerg   static const int64_t NoReward = 0;
256*82d56013Sjoerg   TrainingLogger &Logger;
257*82d56013Sjoerg   const Optional<size_t> CallerSizeEstimateBefore;
258*82d56013Sjoerg   const Optional<size_t> CalleeSizeEstimateBefore;
259*82d56013Sjoerg   const int64_t DefaultDecision;
260*82d56013Sjoerg   const int64_t Mandatory;
261*82d56013Sjoerg };
262*82d56013Sjoerg 
263*82d56013Sjoerg /// A pseudo model runner. We use it to store feature values when collecting
264*82d56013Sjoerg /// logs for the default policy, but never ask it to 'run'.
265*82d56013Sjoerg class NoInferenceModelRunner : public MLModelRunner {
266*82d56013Sjoerg public:
267*82d56013Sjoerg   NoInferenceModelRunner(LLVMContext &Ctx)
268*82d56013Sjoerg       : MLModelRunner(Ctx), Features(NumberOfFeatures) {}
269*82d56013Sjoerg   void setFeature(FeatureIndex Index, int64_t Value) override {
270*82d56013Sjoerg     Features[static_cast<int>(Index)] = Value;
271*82d56013Sjoerg   }
272*82d56013Sjoerg 
273*82d56013Sjoerg   int64_t getFeature(int Index) const override { return Features[Index]; }
274*82d56013Sjoerg   bool run() override {
275*82d56013Sjoerg     llvm_unreachable("We shouldn't call run on this model runner.");
276*82d56013Sjoerg   }
277*82d56013Sjoerg 
278*82d56013Sjoerg private:
279*82d56013Sjoerg   InlineFeatures Features;
280*82d56013Sjoerg };
281*82d56013Sjoerg 
282*82d56013Sjoerg /// ModelUnderTrainingRunner - training mode implementation. It uses TF C APIs
283*82d56013Sjoerg /// to dynamically load and evaluate a TF SavedModel
284*82d56013Sjoerg /// (https://www.tensorflow.org/guide/saved_model). Runtime performance is
285*82d56013Sjoerg /// sacrificed for ease of use while training.
286*82d56013Sjoerg class ModelUnderTrainingRunner final : public MLModelRunner {
287*82d56013Sjoerg public:
288*82d56013Sjoerg   ModelUnderTrainingRunner(LLVMContext &Ctx, const std::string &ModelPath);
289*82d56013Sjoerg 
290*82d56013Sjoerg   bool run() override;
291*82d56013Sjoerg 
292*82d56013Sjoerg   // Disallows copy and assign.
293*82d56013Sjoerg   ModelUnderTrainingRunner(const ModelUnderTrainingRunner &) = delete;
294*82d56013Sjoerg   ModelUnderTrainingRunner &
295*82d56013Sjoerg   operator=(const ModelUnderTrainingRunner &) = delete;
296*82d56013Sjoerg 
297*82d56013Sjoerg   void setFeature(FeatureIndex Index, int64_t Value) override;
298*82d56013Sjoerg   int64_t getFeature(int Index) const override;
299*82d56013Sjoerg   bool isValid() const { return !!Evaluator; }
300*82d56013Sjoerg 
301*82d56013Sjoerg   const std::vector<LoggedFeatureSpec> &outputLoggedFeatureSpecs() const {
302*82d56013Sjoerg     return OutputSpecs;
303*82d56013Sjoerg   }
304*82d56013Sjoerg 
305*82d56013Sjoerg   const Optional<TFModelEvaluator::EvaluationResult> &
306*82d56013Sjoerg   lastEvaluationResult() const {
307*82d56013Sjoerg     return LastEvaluationResult;
308*82d56013Sjoerg   }
309*82d56013Sjoerg 
310*82d56013Sjoerg private:
311*82d56013Sjoerg   std::unique_ptr<TFModelEvaluator> Evaluator;
312*82d56013Sjoerg   std::vector<LoggedFeatureSpec> OutputSpecs;
313*82d56013Sjoerg   Optional<TFModelEvaluator::EvaluationResult> LastEvaluationResult;
314*82d56013Sjoerg 
315*82d56013Sjoerg   // The training framework needs some additional features.
316*82d56013Sjoerg   const std::vector<TensorSpec> TrainingOnlyFeatures{
317*82d56013Sjoerg       TensorSpec::createSpec<int64_t>(TFFeedPrefix + "inlining_default", {1}),
318*82d56013Sjoerg       TensorSpec::createSpec<float>(TFFeedPrefix + "discount", {1}),
319*82d56013Sjoerg       TensorSpec::createSpec<float>(TFFeedPrefix + "reward", {1}),
320*82d56013Sjoerg       TensorSpec::createSpec<int32_t>(TFFeedPrefix + "step_type", {1})};
321*82d56013Sjoerg };
322*82d56013Sjoerg } // namespace
323*82d56013Sjoerg 
324*82d56013Sjoerg TrainingLogger::TrainingLogger(StringRef LogFileName,
325*82d56013Sjoerg                                const ModelUnderTrainingRunner *MUTR)
326*82d56013Sjoerg     : LogFileName(LogFileName), MUTR(MUTR) {
327*82d56013Sjoerg   // The first output is the inlining decision.
328*82d56013Sjoerg   if (MUTR)
329*82d56013Sjoerg     OutputCount = MUTR->outputLoggedFeatureSpecs().size();
330*82d56013Sjoerg   std::vector<LoggedFeatureSpec> FT;
331*82d56013Sjoerg 
332*82d56013Sjoerg   for (size_t I = 0; I < NumberOfFeatures; ++I)
333*82d56013Sjoerg     FT.push_back(
334*82d56013Sjoerg         {TensorSpec::createSpec<int64_t>(FeatureNameMap.at(I), {1}), None});
335*82d56013Sjoerg   if (MUTR && MUTR->outputLoggedFeatureSpecs().size() > 1)
336*82d56013Sjoerg     append_range(FT, drop_begin(MUTR->outputLoggedFeatureSpecs()));
337*82d56013Sjoerg 
338*82d56013Sjoerg   DefaultDecisionPos = FT.size();
339*82d56013Sjoerg   FT.push_back(
340*82d56013Sjoerg       {TensorSpec::createSpec<int64_t>(DefaultDecisionName, {1}), None});
341*82d56013Sjoerg 
342*82d56013Sjoerg   DecisionPos = FT.size();
343*82d56013Sjoerg   FT.push_back({TensorSpec::createSpec<int64_t>(DecisionName, {1}), None});
344*82d56013Sjoerg 
345*82d56013Sjoerg   L = std::make_unique<Logger>(
346*82d56013Sjoerg       FT, TensorSpec::createSpec<int64_t>(RewardName, {1}),
347*82d56013Sjoerg       InlineSizeEstimatorAnalysis::isEvaluatorRequested());
348*82d56013Sjoerg }
349*82d56013Sjoerg 
350*82d56013Sjoerg /// Log one inlining event.
351*82d56013Sjoerg void TrainingLogger::logInlineEvent(const InlineEvent &Event,
352*82d56013Sjoerg                                     const MLModelRunner &ModelRunner) {
353*82d56013Sjoerg   size_t CurrentFeature = 0;
354*82d56013Sjoerg   for (; CurrentFeature < NumberOfFeatures; ++CurrentFeature) {
355*82d56013Sjoerg     int64_t F = ModelRunner.getFeature(CurrentFeature);
356*82d56013Sjoerg     L->logTensorValue(CurrentFeature, &F);
357*82d56013Sjoerg   }
358*82d56013Sjoerg 
359*82d56013Sjoerg   for (size_t I = 1; I < OutputCount; ++I) {
360*82d56013Sjoerg     const auto &Result = *MUTR->lastEvaluationResult();
361*82d56013Sjoerg     auto &Spec = MUTR->outputLoggedFeatureSpecs()[I].Spec;
362*82d56013Sjoerg     const char *RawData =
363*82d56013Sjoerg         reinterpret_cast<const char *>(Result.getUntypedTensorValue(I));
364*82d56013Sjoerg     L->logTensorValue(CurrentFeature, RawData,
365*82d56013Sjoerg                       Spec.getElementCount() * Spec.getElementByteSize());
366*82d56013Sjoerg     ++CurrentFeature;
367*82d56013Sjoerg   }
368*82d56013Sjoerg 
369*82d56013Sjoerg   assert(CurrentFeature == DefaultDecisionPos);
370*82d56013Sjoerg   L->logTensorValue(DefaultDecisionPos, &Event.DefaultDecision);
371*82d56013Sjoerg   L->logTensorValue(DecisionPos, &Event.AdvisedDecision);
372*82d56013Sjoerg   if (InlineSizeEstimatorAnalysis::isEvaluatorRequested())
373*82d56013Sjoerg     L->logReward(Event.Reward);
374*82d56013Sjoerg 
375*82d56013Sjoerg   // For debugging / later use
376*82d56013Sjoerg   Effects.push_back(Event.Effect);
377*82d56013Sjoerg }
378*82d56013Sjoerg 
379*82d56013Sjoerg void TrainingLogger::print() {
380*82d56013Sjoerg   std::error_code EC;
381*82d56013Sjoerg   raw_fd_ostream OutFile(LogFileName, EC);
382*82d56013Sjoerg   L->print(OutFile);
383*82d56013Sjoerg }
384*82d56013Sjoerg 
385*82d56013Sjoerg DevelopmentModeMLInlineAdvisor::DevelopmentModeMLInlineAdvisor(
386*82d56013Sjoerg     Module &M, ModuleAnalysisManager &MAM,
387*82d56013Sjoerg     std::unique_ptr<MLModelRunner> ModelRunner,
388*82d56013Sjoerg     std::function<bool(CallBase &)> GetDefaultAdvice, bool IsDoingInference,
389*82d56013Sjoerg     std::unique_ptr<TrainingLogger> Logger)
390*82d56013Sjoerg     : MLInlineAdvisor(M, MAM, std::move(ModelRunner)),
391*82d56013Sjoerg       GetDefaultAdvice(GetDefaultAdvice), IsDoingInference(IsDoingInference),
392*82d56013Sjoerg       Logger(std::move(Logger)),
393*82d56013Sjoerg       InitialNativeSize(isLogging() ? getTotalSizeEstimate() : 0),
394*82d56013Sjoerg       CurrentNativeSize(InitialNativeSize) {
395*82d56013Sjoerg   // We cannot have the case of neither inference nor logging.
396*82d56013Sjoerg   assert(IsDoingInference || isLogging());
397*82d56013Sjoerg }
398*82d56013Sjoerg 
399*82d56013Sjoerg DevelopmentModeMLInlineAdvisor::~DevelopmentModeMLInlineAdvisor() {
400*82d56013Sjoerg   if (isLogging())
401*82d56013Sjoerg     Logger->print();
402*82d56013Sjoerg }
403*82d56013Sjoerg 
404*82d56013Sjoerg Optional<size_t>
405*82d56013Sjoerg DevelopmentModeMLInlineAdvisor::getNativeSizeEstimate(const Function &F) const {
406*82d56013Sjoerg   if (!InlineSizeEstimatorAnalysis::isEvaluatorRequested())
407*82d56013Sjoerg     return None;
408*82d56013Sjoerg   auto &R =
409*82d56013Sjoerg       FAM.getResult<InlineSizeEstimatorAnalysis>(const_cast<Function &>(F));
410*82d56013Sjoerg   if (!R) {
411*82d56013Sjoerg     F.getParent()->getContext().emitError(
412*82d56013Sjoerg         "Native size estimator is not present.");
413*82d56013Sjoerg     return 0;
414*82d56013Sjoerg   }
415*82d56013Sjoerg   return *R;
416*82d56013Sjoerg }
417*82d56013Sjoerg 
418*82d56013Sjoerg std::unique_ptr<MLInlineAdvice>
419*82d56013Sjoerg DevelopmentModeMLInlineAdvisor::getMandatoryAdviceImpl(CallBase &CB) {
420*82d56013Sjoerg   return std::make_unique<LoggingMLInlineAdvice>(
421*82d56013Sjoerg       /*Advisor=*/this,
422*82d56013Sjoerg       /*CB=*/CB, /*ORE=*/getCallerORE(CB), /*Recommendation=*/true,
423*82d56013Sjoerg       /*Logger=*/*Logger,
424*82d56013Sjoerg       /*CallerSizeEstimateBefore=*/getNativeSizeEstimate(*CB.getCaller()),
425*82d56013Sjoerg       /*CalleeSizeEstimateBefore=*/
426*82d56013Sjoerg       getNativeSizeEstimate(*CB.getCalledFunction()),
427*82d56013Sjoerg       /*DefaultDecision=*/true, /*Mandatory*/ true);
428*82d56013Sjoerg }
429*82d56013Sjoerg 
430*82d56013Sjoerg std::unique_ptr<MLInlineAdvice>
431*82d56013Sjoerg DevelopmentModeMLInlineAdvisor::getAdviceFromModel(
432*82d56013Sjoerg     CallBase &CB, OptimizationRemarkEmitter &ORE) {
433*82d56013Sjoerg   if (IsDoingInference && !isLogging())
434*82d56013Sjoerg     return MLInlineAdvisor::getAdviceFromModel(CB, ORE);
435*82d56013Sjoerg 
436*82d56013Sjoerg   bool DefaultAdvice = GetDefaultAdvice(CB);
437*82d56013Sjoerg   auto Recommendation = IsDoingInference ? ModelRunner->run() : DefaultAdvice;
438*82d56013Sjoerg   return std::make_unique<LoggingMLInlineAdvice>(
439*82d56013Sjoerg       /*Advisor=*/this,
440*82d56013Sjoerg       /*CB=*/CB, /*ORE=*/ORE, /*Recommendation=*/Recommendation,
441*82d56013Sjoerg       /*Logger=*/*Logger,
442*82d56013Sjoerg       /*CallerSizeEstimateBefore=*/getNativeSizeEstimate(*CB.getCaller()),
443*82d56013Sjoerg       /*CalleeSizeEstimateBefore=*/
444*82d56013Sjoerg       getNativeSizeEstimate(*CB.getCalledFunction()),
445*82d56013Sjoerg       /*DefaultDecision=*/DefaultAdvice);
446*82d56013Sjoerg }
447*82d56013Sjoerg 
448*82d56013Sjoerg size_t DevelopmentModeMLInlineAdvisor::getTotalSizeEstimate() {
449*82d56013Sjoerg   if (!InlineSizeEstimatorAnalysis::isEvaluatorRequested())
450*82d56013Sjoerg     return 0;
451*82d56013Sjoerg   size_t Ret = 0;
452*82d56013Sjoerg   for (auto &F : M) {
453*82d56013Sjoerg     if (F.isDeclaration())
454*82d56013Sjoerg       continue;
455*82d56013Sjoerg     if (isFunctionDeleted(&F))
456*82d56013Sjoerg       continue;
457*82d56013Sjoerg     Ret += *getNativeSizeEstimate(F);
458*82d56013Sjoerg   }
459*82d56013Sjoerg   return Ret;
460*82d56013Sjoerg }
461*82d56013Sjoerg 
462*82d56013Sjoerg ModelUnderTrainingRunner::ModelUnderTrainingRunner(LLVMContext &Ctx,
463*82d56013Sjoerg                                                    const std::string &ModelPath)
464*82d56013Sjoerg     : MLModelRunner(Ctx) {
465*82d56013Sjoerg   std::vector<TensorSpec> InputSpecs;
466*82d56013Sjoerg   for (size_t I = 0; I < NumberOfFeatures; ++I)
467*82d56013Sjoerg     InputSpecs.push_back(
468*82d56013Sjoerg         TensorSpec::createSpec<int64_t>(TFFeedPrefix + FeatureNameMap[I], {1}));
469*82d56013Sjoerg   append_range(InputSpecs, TrainingOnlyFeatures);
470*82d56013Sjoerg   if (auto MaybeOutSpecs =
471*82d56013Sjoerg           loadOutputSpecs(Ctx, DecisionName, ModelPath, TFOutputSpecOverride))
472*82d56013Sjoerg     OutputSpecs = std::move(*MaybeOutSpecs);
473*82d56013Sjoerg   else
474*82d56013Sjoerg     return;
475*82d56013Sjoerg 
476*82d56013Sjoerg   Evaluator = std::make_unique<TFModelEvaluator>(
477*82d56013Sjoerg       ModelPath, InputSpecs, [&](size_t I) { return OutputSpecs[I].Spec; },
478*82d56013Sjoerg       OutputSpecs.size());
479*82d56013Sjoerg   if (!Evaluator || !Evaluator->isValid()) {
480*82d56013Sjoerg     Ctx.emitError("Failed to create inliner saved model evaluator");
481*82d56013Sjoerg     Evaluator.reset();
482*82d56013Sjoerg     return;
483*82d56013Sjoerg   }
484*82d56013Sjoerg }
485*82d56013Sjoerg 
486*82d56013Sjoerg bool ModelUnderTrainingRunner::run() {
487*82d56013Sjoerg   LastEvaluationResult = Evaluator->evaluate();
488*82d56013Sjoerg   if (!LastEvaluationResult.hasValue()) {
489*82d56013Sjoerg     Ctx.emitError("Error evaluating model.");
490*82d56013Sjoerg     return false;
491*82d56013Sjoerg   }
492*82d56013Sjoerg   int64_t Decision = *LastEvaluationResult->getTensorValue<int64_t>(0);
493*82d56013Sjoerg   return static_cast<bool>(Decision);
494*82d56013Sjoerg }
495*82d56013Sjoerg 
496*82d56013Sjoerg int64_t ModelUnderTrainingRunner::getFeature(int Index) const {
497*82d56013Sjoerg   return *Evaluator->getInput<int64_t>(Index);
498*82d56013Sjoerg }
499*82d56013Sjoerg 
500*82d56013Sjoerg void ModelUnderTrainingRunner::setFeature(FeatureIndex Index, int64_t Value) {
501*82d56013Sjoerg   size_t NumericIndex = static_cast<size_t>(Index);
502*82d56013Sjoerg   *(Evaluator->getInput<int64_t>(NumericIndex)) = Value;
503*82d56013Sjoerg }
504*82d56013Sjoerg 
505*82d56013Sjoerg std::unique_ptr<InlineAdvisor> llvm::getDevelopmentModeAdvisor(
506*82d56013Sjoerg     Module &M, ModuleAnalysisManager &MAM,
507*82d56013Sjoerg     std::function<bool(CallBase &)> GetDefaultAdvice) {
508*82d56013Sjoerg   auto &Ctx = M.getContext();
509*82d56013Sjoerg   std::unique_ptr<MLModelRunner> Runner;
510*82d56013Sjoerg   ModelUnderTrainingRunner *MUTRPtr = nullptr;
511*82d56013Sjoerg   bool IsDoingInference = false;
512*82d56013Sjoerg   if (TFModelUnderTrainingPath.empty())
513*82d56013Sjoerg     Runner.reset(new NoInferenceModelRunner(Ctx));
514*82d56013Sjoerg   else {
515*82d56013Sjoerg     auto MUTR = std::make_unique<ModelUnderTrainingRunner>(
516*82d56013Sjoerg         Ctx, TFModelUnderTrainingPath);
517*82d56013Sjoerg     if (!MUTR || !MUTR->isValid()) {
518*82d56013Sjoerg       Ctx.emitError("Could not load the policy model from the provided path");
519*82d56013Sjoerg       return nullptr;
520*82d56013Sjoerg     }
521*82d56013Sjoerg     IsDoingInference = true;
522*82d56013Sjoerg     MUTRPtr = MUTR.get();
523*82d56013Sjoerg     Runner = std::move(MUTR);
524*82d56013Sjoerg   }
525*82d56013Sjoerg   std::unique_ptr<TrainingLogger> Logger;
526*82d56013Sjoerg   if (!TrainingLog.empty())
527*82d56013Sjoerg     Logger = std::make_unique<TrainingLogger>(TrainingLog, MUTRPtr);
528*82d56013Sjoerg 
529*82d56013Sjoerg   return std::make_unique<DevelopmentModeMLInlineAdvisor>(
530*82d56013Sjoerg       M, MAM, std::move(Runner), GetDefaultAdvice, IsDoingInference,
531*82d56013Sjoerg       std::move(Logger));
532*82d56013Sjoerg }
533*82d56013Sjoerg #endif // defined(LLVM_HAVE_TF_API)
534