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