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