Path: blob/main/contrib/llvm-project/llvm/lib/Analysis/DevelopmentModeInlineAdvisor.cpp
35233 views
//===- DevelopmentModeInlineAdvisor.cpp - runtime-loadable model runner --===//1//2// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.3// See https://llvm.org/LICENSE.txt for license information.4// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception5//6//===----------------------------------------------------------------------===//7//8// This file implements a model runner using TFLite, allowing the9// loading of a model from a command line option.10//11//===----------------------------------------------------------------------===//12#include "llvm/Analysis/TensorSpec.h"13#include "llvm/Config/config.h"14#if defined(LLVM_HAVE_TFLITE)1516#include "llvm/ADT/BitVector.h"17#include "llvm/Analysis/CallGraph.h"18#include "llvm/Analysis/InlineSizeEstimatorAnalysis.h"19#include "llvm/Analysis/MLInlineAdvisor.h"20#include "llvm/Analysis/ModelUnderTrainingRunner.h"21#include "llvm/Analysis/NoInferenceModelRunner.h"22#include "llvm/Analysis/Utils/TFUtils.h"23#include "llvm/Analysis/Utils/TrainingLogger.h"24#include "llvm/IR/LLVMContext.h"25#include "llvm/IR/Module.h"26#include "llvm/Support/CommandLine.h"27#include "llvm/Support/ManagedStatic.h"2829#include <vector>30#include <optional>3132using namespace llvm;3334static cl::opt<std::string> TrainingLog(35"training-log", cl::Hidden,36cl::desc("Path where the development - mode inlining log is saved."));3738static cl::opt<std::string> TFModelUnderTrainingPath(39"ml-inliner-model-under-training", cl::Hidden,40cl::desc(R"(Path to SavedModel from the previous training iteration.41The directory is also expected to contain a JSON specification of the42outputs expected to be logged, where the first entry must be the43inlining decision. The file containing the specification should be44called output_spec.json. The expected JSON value is an array of45dictionaries. Each dictionary should have 2 keys:4647- "tensor_spec, followed by the TensorSpec description of the48output; and49- "logging_name", a string indicating the name to use when50logging the output values.5152Example:53[54{55"logging_name" : "some_name",56"tensor_spec" : {57"name" : "model_name",58"port" : 0,59"shape" : [2, 3],60"type" : "float"61}62}63]6465The first value must always correspond to the decision.)"));6667static cl::opt<std::string> TFOutputSpecOverride(68"ml-inliner-output-spec-override", cl::Hidden,69cl::desc("Override the path to the output spec json file. See "70"-ml-inliner-model-under-training documentation for the "71"specification of that file."));7273static cl::opt<std::string> TFFeedPrefix("ml-inliner-trained-model-feed-prefix",74cl::Hidden, cl::init("action_"),75cl::desc("Prefix for feature names."));7677namespace {78/// An InlineEvent, used by TrainingLogger.79struct InlineEvent {80/// What the default policy's decision would have been.81int64_t DefaultDecision = 0;8283/// What we advised. When training off the default policy, this is the same as84/// DefaultDecision.85int64_t AdvisedDecision = 0;8687/// What actually happened. This would be 'false' in the case of an inline88/// error, even if AdvisedDecision were true, otherwise it agrees with89/// AdvisedDecision.90bool Effect = false;9192/// What the change in size was: size_after - size_before93int64_t Reward = 0;94};9596/// Collect data we may use for training a model.97class TrainingLogger final {98public:99TrainingLogger(StringRef LogFileName, const ModelUnderTrainingRunner *MUTR);100101/// Log one inlining event.102void logInlineEvent(const InlineEvent &Event,103const MLModelRunner &ModelRunner);104105private:106StringRef LogFileName;107const ModelUnderTrainingRunner *const MUTR;108std::unique_ptr<Logger> L;109BitVector Effects;110/// Set these 2 clearly OOB, to make sure we set them later.111size_t DefaultDecisionPos = std::numeric_limits<size_t>::max();112size_t DecisionPos = std::numeric_limits<size_t>::max();113};114115/// An extension of the MLInlineAdvisor for the 'development' mode, targeting116/// the offline training scenario. Note that training happens outside of the117/// compiler, this facility is concerned with producing training data ("logs").118/// This InlineAdvisor can operate in the following modes:119///120/// 1) collect logs for the default policy. This is useful for bootstrapping121/// training, which will be considerably faster by starting from a reasonable122/// policy.123///124/// 2) collect logs for the ML policy, using a model from a previous125/// training. Potentially, that model uses internally some small random126/// perturbation of its weights, to induce exploration (setting this up is the127/// responsibility of the training algorithm). The logs would then be used to128/// retrain and improve on this model.129///130/// 3) use the provided model, with no logging. This is useful for end to end131/// validation - the model, in this case, is a release candidate and shouldn't132/// have random perturbations. It is a convenience feature: rather than needing133/// to take the release candidate model and compile it in 'release' mode,134/// validate it, then potentially discard it, it's easier to just pass the model135/// to the compiler, albeit compilation would be slower, as a one-off. Once the136/// model behaves satisfactorily, it can be compiled AOT, for efficiency, in137/// release mode. The expectation is that a well-trained model provides a good138/// policy over a sufficiently diverse codebase, over many changes (i.e.139/// training happens seldom).140class DevelopmentModeMLInlineAdvisor : public MLInlineAdvisor {141public:142DevelopmentModeMLInlineAdvisor(143Module &M, ModuleAnalysisManager &MAM,144std::unique_ptr<MLModelRunner> ModelRunner,145std::function<bool(CallBase &)> GetDefaultAdvice,146std::unique_ptr<TrainingLogger> Logger);147148size_t getTotalSizeEstimate();149150void updateNativeSizeEstimate(int64_t Change) {151*CurrentNativeSize += Change;152}153void resetNativeSize(Function *F) {154PreservedAnalyses PA = PreservedAnalyses::all();155PA.abandon<InlineSizeEstimatorAnalysis>();156FAM.invalidate(*F, PA);157}158159std::unique_ptr<MLInlineAdvice>160getAdviceFromModel(CallBase &CB, OptimizationRemarkEmitter &ORE) override;161162std::optional<size_t> getNativeSizeEstimate(const Function &F) const;163164private:165bool isLogging() const { return !!Logger; }166std::unique_ptr<MLInlineAdvice> getMandatoryAdviceImpl(CallBase &CB) override;167168const bool IsDoingInference;169std::unique_ptr<TrainingLogger> Logger;170171const std::optional<int32_t> InitialNativeSize;172std::optional<int32_t> CurrentNativeSize;173};174175/// A variant of MLInlineAdvice that tracks all non-trivial inlining176/// decisions, for training/logging.177class LoggingMLInlineAdvice : public MLInlineAdvice {178public:179LoggingMLInlineAdvice(DevelopmentModeMLInlineAdvisor *Advisor, CallBase &CB,180OptimizationRemarkEmitter &ORE, bool Recommendation,181TrainingLogger &Logger,182std::optional<size_t> CallerSizeEstimateBefore,183std::optional<size_t> CalleeSizeEstimateBefore,184bool DefaultDecision, bool Mandatory = false)185: MLInlineAdvice(Advisor, CB, ORE, Recommendation), Logger(Logger),186CallerSizeEstimateBefore(CallerSizeEstimateBefore),187CalleeSizeEstimateBefore(CalleeSizeEstimateBefore),188DefaultDecision(DefaultDecision), Mandatory(Mandatory) {}189190virtual ~LoggingMLInlineAdvice() = default;191192private:193DevelopmentModeMLInlineAdvisor *getAdvisor() const {194return static_cast<DevelopmentModeMLInlineAdvisor *>(Advisor);195}196void recordInliningImpl() override {197MLInlineAdvice::recordInliningImpl();198getAdvisor()->resetNativeSize(Caller);199int Reward = std::numeric_limits<int>::max();200if (InlineSizeEstimatorAnalysis::isEvaluatorRequested() &&201!getAdvisor()->isForcedToStop()) {202int NativeSizeAfter = *getAdvisor()->getNativeSizeEstimate(*Caller) +203*CalleeSizeEstimateBefore;204Reward = NativeSizeAfter -205(*CallerSizeEstimateBefore + *CalleeSizeEstimateBefore);206getAdvisor()->updateNativeSizeEstimate(Reward);207}208log(Reward, /*Success=*/true);209}210211void recordInliningWithCalleeDeletedImpl() override {212MLInlineAdvice::recordInliningWithCalleeDeletedImpl();213getAdvisor()->resetNativeSize(Caller);214if (InlineSizeEstimatorAnalysis::isEvaluatorRequested() &&215!getAdvisor()->isForcedToStop()) {216int NativeSizeAfter = *getAdvisor()->getNativeSizeEstimate(*Caller);217int Reward = NativeSizeAfter -218(*CallerSizeEstimateBefore + *CalleeSizeEstimateBefore);219getAdvisor()->updateNativeSizeEstimate(Reward);220log(Reward, /*Success=*/true);221} else {222log(NoReward, /*Success=*/true);223}224}225226void recordUnsuccessfulInliningImpl(const InlineResult &Result) override {227MLInlineAdvice::recordUnsuccessfulInliningImpl(Result);228log(NoReward, /*Success=*/false);229}230231void recordUnattemptedInliningImpl() override {232MLInlineAdvice::recordUnattemptedInliningImpl();233log(NoReward, /*Success=*/false);234}235236void log(int64_t Reward, bool Success) {237if (Mandatory)238return;239InlineEvent Event;240Event.AdvisedDecision = isInliningRecommended();241Event.DefaultDecision = DefaultDecision;242Event.Effect = Success;243Event.Reward = Reward;244Logger.logInlineEvent(Event, getAdvisor()->getModelRunner());245}246247static const int64_t NoReward = 0;248TrainingLogger &Logger;249const std::optional<size_t> CallerSizeEstimateBefore;250const std::optional<size_t> CalleeSizeEstimateBefore;251const int64_t DefaultDecision;252const int64_t Mandatory;253};254255static const std::vector<TensorSpec> TrainingOnlyFeatures{256TensorSpec::createSpec<float>(TFFeedPrefix + "discount", {1}),257TensorSpec::createSpec<float>(TFFeedPrefix + "reward", {1}),258TensorSpec::createSpec<int32_t>(TFFeedPrefix + "step_type", {1})};259260static const std::vector<TensorSpec> getInputFeatures() {261std::vector<TensorSpec> InputSpecs;262for (size_t I = 0; I < NumberOfFeatures; ++I)263InputSpecs.push_back(TensorSpec::createSpec<int64_t>(264TFFeedPrefix + FeatureMap[I].name(), FeatureMap[I].shape()));265append_range(InputSpecs, TrainingOnlyFeatures);266return InputSpecs;267}268269} // namespace270271TrainingLogger::TrainingLogger(StringRef LogFileName,272const ModelUnderTrainingRunner *MUTR)273: LogFileName(LogFileName), MUTR(MUTR) {274// The first output is the inlining decision.275std::vector<TensorSpec> FT(FeatureMap.begin(), FeatureMap.end());276277if (MUTR)278append_range(FT, MUTR->extraOutputsForLoggingSpecs());279280DefaultDecisionPos = FT.size();281FT.push_back(DefaultDecisionSpec);282283DecisionPos = FT.size();284FT.push_back(InlineDecisionSpec);285std::error_code EC;286auto OS = std::make_unique<raw_fd_ostream>(TrainingLog, EC);287if (EC)288dbgs() << (EC.message() + ":" + TrainingLog);289290L = std::make_unique<Logger>(291std::move(OS), FT, TensorSpec::createSpec<int64_t>(RewardName, {1}),292InlineSizeEstimatorAnalysis::isEvaluatorRequested());293L->switchContext("");294}295296/// Log one inlining event.297void TrainingLogger::logInlineEvent(const InlineEvent &Event,298const MLModelRunner &ModelRunner) {299L->startObservation();300size_t CurrentFeature = 0;301for (; CurrentFeature < NumberOfFeatures; ++CurrentFeature)302L->logTensorValue(CurrentFeature,303reinterpret_cast<const char *>(304ModelRunner.getTensorUntyped(CurrentFeature)));305306if (MUTR)307for (size_t I = 0; I < MUTR->extraOutputsForLoggingSpecs().size(); ++I) {308const char *RawData =309reinterpret_cast<const char *>(MUTR->getUntypedExtraOutputValue(I));310L->logTensorValue(CurrentFeature, RawData);311++CurrentFeature;312}313314assert(CurrentFeature == DefaultDecisionPos);315L->logTensorValue(DefaultDecisionPos,316reinterpret_cast<const char *>(&Event.DefaultDecision));317L->logTensorValue(DecisionPos,318reinterpret_cast<const char *>(&Event.AdvisedDecision));319L->endObservation();320if (InlineSizeEstimatorAnalysis::isEvaluatorRequested())321L->logReward(Event.Reward);322323// For debugging / later use324Effects.push_back(Event.Effect);325}326327DevelopmentModeMLInlineAdvisor::DevelopmentModeMLInlineAdvisor(328Module &M, ModuleAnalysisManager &MAM,329std::unique_ptr<MLModelRunner> ModelRunner,330std::function<bool(CallBase &)> GetDefaultAdvice,331std::unique_ptr<TrainingLogger> Logger)332: MLInlineAdvisor(M, MAM, std::move(ModelRunner), GetDefaultAdvice),333IsDoingInference(isa<ModelUnderTrainingRunner>(getModelRunner())),334Logger(std::move(Logger)),335InitialNativeSize(isLogging() ? getTotalSizeEstimate() : 0),336CurrentNativeSize(InitialNativeSize) {337// We cannot have the case of neither inference nor logging.338assert(IsDoingInference || isLogging());339}340341std::optional<size_t>342DevelopmentModeMLInlineAdvisor::getNativeSizeEstimate(const Function &F) const {343if (!InlineSizeEstimatorAnalysis::isEvaluatorRequested())344return std::nullopt;345auto &R =346FAM.getResult<InlineSizeEstimatorAnalysis>(const_cast<Function &>(F));347if (!R) {348F.getParent()->getContext().emitError(349"Native size estimator is not present.");350return 0;351}352return *R;353}354355std::unique_ptr<MLInlineAdvice>356DevelopmentModeMLInlineAdvisor::getMandatoryAdviceImpl(CallBase &CB) {357return std::make_unique<LoggingMLInlineAdvice>(358/*Advisor=*/this,359/*CB=*/CB, /*ORE=*/getCallerORE(CB), /*Recommendation=*/true,360/*Logger=*/*Logger,361/*CallerSizeEstimateBefore=*/getNativeSizeEstimate(*CB.getCaller()),362/*CalleeSizeEstimateBefore=*/363getNativeSizeEstimate(*CB.getCalledFunction()),364/*DefaultDecision=*/true, /*Mandatory*/ true);365}366367std::unique_ptr<MLInlineAdvice>368DevelopmentModeMLInlineAdvisor::getAdviceFromModel(369CallBase &CB, OptimizationRemarkEmitter &ORE) {370if (IsDoingInference && !isLogging())371return MLInlineAdvisor::getAdviceFromModel(CB, ORE);372373bool DefaultAdvice = GetDefaultAdvice(CB);374auto Recommendation =375IsDoingInference ? static_cast<bool>(ModelRunner->evaluate<int64_t>())376: DefaultAdvice;377return std::make_unique<LoggingMLInlineAdvice>(378/*Advisor=*/this,379/*CB=*/CB, /*ORE=*/ORE, /*Recommendation=*/Recommendation,380/*Logger=*/*Logger,381/*CallerSizeEstimateBefore=*/getNativeSizeEstimate(*CB.getCaller()),382/*CalleeSizeEstimateBefore=*/383getNativeSizeEstimate(*CB.getCalledFunction()),384/*DefaultDecision=*/DefaultAdvice);385}386387size_t DevelopmentModeMLInlineAdvisor::getTotalSizeEstimate() {388if (!InlineSizeEstimatorAnalysis::isEvaluatorRequested())389return 0;390size_t Ret = 0;391for (auto &F : M) {392if (F.isDeclaration())393continue;394Ret += *getNativeSizeEstimate(F);395}396return Ret;397}398399std::unique_ptr<InlineAdvisor> llvm::getDevelopmentModeAdvisor(400Module &M, ModuleAnalysisManager &MAM,401std::function<bool(CallBase &)> GetDefaultAdvice) {402auto &Ctx = M.getContext();403std::unique_ptr<MLModelRunner> Runner;404if (TFModelUnderTrainingPath.empty())405Runner.reset(new NoInferenceModelRunner(Ctx, getInputFeatures()));406else407Runner = ModelUnderTrainingRunner::createAndEnsureValid(408Ctx, TFModelUnderTrainingPath, DecisionName, getInputFeatures(),409TFOutputSpecOverride);410if (!Runner)411return nullptr;412std::unique_ptr<TrainingLogger> Logger;413if (!TrainingLog.empty())414Logger = std::make_unique<TrainingLogger>(415TrainingLog, dyn_cast<ModelUnderTrainingRunner>(Runner.get()));416417return std::make_unique<DevelopmentModeMLInlineAdvisor>(418M, MAM, std::move(Runner), GetDefaultAdvice, std::move(Logger));419}420#endif // defined(LLVM_HAVE_TFLITE)421422423