DevelopmentModeInlineAdvisor.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423
  1. //===- DevelopmentModeInlineAdvisor.cpp - runtime-loadable model runner --===//
  2. //
  3. // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
  4. // See https://llvm.org/LICENSE.txt for license information.
  5. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
  6. //
  7. //===----------------------------------------------------------------------===//
  8. //
  9. // This file implements a model runner using Tensorflow C APIs, allowing the
  10. // loading of a model from a command line option.
  11. //
  12. //===----------------------------------------------------------------------===//
  13. #include "llvm/Analysis/TensorSpec.h"
  14. #include "llvm/Config/config.h"
  15. #if defined(LLVM_HAVE_TFLITE)
  16. #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/Support/CommandLine.h"
  26. #include "llvm/Support/ManagedStatic.h"
  27. #include <vector>
  28. #include <optional>
  29. using namespace llvm;
  30. static cl::opt<std::string> TrainingLog(
  31. "training-log", cl::Hidden,
  32. cl::desc("Path where the development - mode inlining log is saved."));
  33. static cl::opt<std::string> TFModelUnderTrainingPath(
  34. "ml-inliner-model-under-training", cl::Hidden,
  35. cl::desc(R"(Path to SavedModel from the previous training iteration.
  36. The directory is also expected to contain a JSON specification of the
  37. outputs expected to be logged, where the first entry must be the
  38. inlining decision. The file containing the specification should be
  39. called output_spec.json. The expected JSON value is an array of
  40. dictionaries. Each dictionary should have 2 keys:
  41. - "tensor_spec, followed by the TensorSpec description of the
  42. output; and
  43. - "logging_name", a string indicating the name to use when
  44. logging the output values.
  45. Example:
  46. [
  47. {
  48. "logging_name" : "some_name",
  49. "tensor_spec" : {
  50. "name" : "model_name",
  51. "port" : 0,
  52. "shape" : [2, 3],
  53. "type" : "float"
  54. }
  55. }
  56. ]
  57. The first value must always correspond to the decision.)"));
  58. static cl::opt<std::string> TFOutputSpecOverride(
  59. "ml-inliner-output-spec-override", cl::Hidden,
  60. cl::desc("Override the path to the output spec json file. See "
  61. "-ml-inliner-model-under-training documentation for the "
  62. "specification of that file."));
  63. static cl::opt<std::string> TFFeedPrefix("ml-inliner-trained-model-feed-prefix",
  64. cl::Hidden, cl::init("action_"),
  65. cl::desc("Prefix for feature names."));
  66. namespace {
  67. /// An InlineEvent, used by TrainingLogger.
  68. struct InlineEvent {
  69. /// What the default policy's decision would have been.
  70. int64_t DefaultDecision = 0;
  71. /// What we advised. When training off the default policy, this is the same as
  72. /// DefaultDecision.
  73. int64_t AdvisedDecision = 0;
  74. /// What actually happened. This would be 'false' in the case of an inline
  75. /// error, even if AdvisedDecision were true, otherwise it agrees with
  76. /// AdvisedDecision.
  77. bool Effect = false;
  78. /// What the change in size was: size_after - size_before
  79. int64_t Reward = 0;
  80. };
  81. /// Collect data we may use for training a model.
  82. class TrainingLogger final {
  83. public:
  84. TrainingLogger(StringRef LogFileName, const ModelUnderTrainingRunner *MUTR);
  85. /// Log one inlining event.
  86. void logInlineEvent(const InlineEvent &Event,
  87. const MLModelRunner &ModelRunner);
  88. private:
  89. StringRef LogFileName;
  90. const ModelUnderTrainingRunner *const MUTR;
  91. std::unique_ptr<Logger> L;
  92. BitVector Effects;
  93. /// Set these 2 clearly OOB, to make sure we set them later.
  94. size_t DefaultDecisionPos = std::numeric_limits<size_t>::max();
  95. size_t DecisionPos = std::numeric_limits<size_t>::max();
  96. };
  97. /// An extension of the MLInlineAdvisor for the 'development' mode, targeting
  98. /// the offline training scenario. Note that training happens outside of the
  99. /// compiler, this facility is concerned with producing training data ("logs").
  100. /// This InlineAdvisor can operate in the following modes:
  101. ///
  102. /// 1) collect logs for the default policy. This is useful for bootstrapping
  103. /// training, which will be considerably faster by starting from a reasonable
  104. /// policy.
  105. ///
  106. /// 2) collect logs for the ML policy, using a model from a previous
  107. /// training. Potentially, that model uses internally some small random
  108. /// perturbation of its weights, to induce exploration (setting this up is the
  109. /// responsibility of the training algorithm). The logs would then be used to
  110. /// retrain and improve on this model.
  111. ///
  112. /// 3) use the provided model, with no logging. This is useful for end to end
  113. /// validation - the model, in this case, is a release candidate and shouldn't
  114. /// have random perturbations. It is a convenience feature: rather than needing
  115. /// to take the release candidate model and compile it in 'release' mode,
  116. /// validate it, then potentially discard it, it's easier to just pass the model
  117. /// to the compiler, albeit compilation would be slower, as a one-off. Once the
  118. /// model behaves satisfactorily, it can be compiled AOT, for efficiency, in
  119. /// release mode. The expectation is that a well-trained model provides a good
  120. /// policy over a sufficiently diverse codebase, over many changes (i.e.
  121. /// training happens seldom).
  122. class DevelopmentModeMLInlineAdvisor : public MLInlineAdvisor {
  123. public:
  124. DevelopmentModeMLInlineAdvisor(
  125. Module &M, ModuleAnalysisManager &MAM,
  126. std::unique_ptr<MLModelRunner> ModelRunner,
  127. std::function<bool(CallBase &)> GetDefaultAdvice,
  128. std::unique_ptr<TrainingLogger> Logger);
  129. size_t getTotalSizeEstimate();
  130. void updateNativeSizeEstimate(int64_t Change) {
  131. *CurrentNativeSize += Change;
  132. }
  133. void resetNativeSize(Function *F) {
  134. PreservedAnalyses PA = PreservedAnalyses::all();
  135. PA.abandon<InlineSizeEstimatorAnalysis>();
  136. FAM.invalidate(*F, PA);
  137. }
  138. std::unique_ptr<MLInlineAdvice>
  139. getAdviceFromModel(CallBase &CB, OptimizationRemarkEmitter &ORE) override;
  140. std::optional<size_t> getNativeSizeEstimate(const Function &F) const;
  141. private:
  142. bool isLogging() const { return !!Logger; }
  143. std::unique_ptr<MLInlineAdvice> getMandatoryAdviceImpl(CallBase &CB) override;
  144. std::function<bool(CallBase &)> GetDefaultAdvice;
  145. const bool IsDoingInference;
  146. std::unique_ptr<TrainingLogger> Logger;
  147. const std::optional<int32_t> InitialNativeSize;
  148. std::optional<int32_t> CurrentNativeSize;
  149. };
  150. /// A variant of MLInlineAdvice that tracks all non-trivial inlining
  151. /// decisions, for training/logging.
  152. class LoggingMLInlineAdvice : public MLInlineAdvice {
  153. public:
  154. LoggingMLInlineAdvice(DevelopmentModeMLInlineAdvisor *Advisor, CallBase &CB,
  155. OptimizationRemarkEmitter &ORE, bool Recommendation,
  156. TrainingLogger &Logger,
  157. std::optional<size_t> CallerSizeEstimateBefore,
  158. std::optional<size_t> CalleeSizeEstimateBefore,
  159. bool DefaultDecision, bool Mandatory = false)
  160. : MLInlineAdvice(Advisor, CB, ORE, Recommendation), Logger(Logger),
  161. CallerSizeEstimateBefore(CallerSizeEstimateBefore),
  162. CalleeSizeEstimateBefore(CalleeSizeEstimateBefore),
  163. DefaultDecision(DefaultDecision), Mandatory(Mandatory) {}
  164. virtual ~LoggingMLInlineAdvice() = default;
  165. private:
  166. DevelopmentModeMLInlineAdvisor *getAdvisor() const {
  167. return static_cast<DevelopmentModeMLInlineAdvisor *>(Advisor);
  168. }
  169. void recordInliningImpl() override {
  170. MLInlineAdvice::recordInliningImpl();
  171. getAdvisor()->resetNativeSize(Caller);
  172. int Reward = std::numeric_limits<int>::max();
  173. if (InlineSizeEstimatorAnalysis::isEvaluatorRequested() &&
  174. !getAdvisor()->isForcedToStop()) {
  175. int NativeSizeAfter = *getAdvisor()->getNativeSizeEstimate(*Caller) +
  176. *CalleeSizeEstimateBefore;
  177. Reward = NativeSizeAfter -
  178. (*CallerSizeEstimateBefore + *CalleeSizeEstimateBefore);
  179. getAdvisor()->updateNativeSizeEstimate(Reward);
  180. }
  181. log(Reward, /*Success=*/true);
  182. }
  183. void recordInliningWithCalleeDeletedImpl() override {
  184. MLInlineAdvice::recordInliningWithCalleeDeletedImpl();
  185. getAdvisor()->resetNativeSize(Caller);
  186. if (InlineSizeEstimatorAnalysis::isEvaluatorRequested() &&
  187. !getAdvisor()->isForcedToStop()) {
  188. int NativeSizeAfter = *getAdvisor()->getNativeSizeEstimate(*Caller);
  189. int Reward = NativeSizeAfter -
  190. (*CallerSizeEstimateBefore + *CalleeSizeEstimateBefore);
  191. getAdvisor()->updateNativeSizeEstimate(Reward);
  192. log(Reward, /*Success=*/true);
  193. } else {
  194. log(NoReward, /*Success=*/true);
  195. }
  196. }
  197. void recordUnsuccessfulInliningImpl(const InlineResult &Result) override {
  198. MLInlineAdvice::recordUnsuccessfulInliningImpl(Result);
  199. log(NoReward, /*Success=*/false);
  200. }
  201. void recordUnattemptedInliningImpl() override {
  202. MLInlineAdvice::recordUnattemptedInliningImpl();
  203. log(NoReward, /*Success=*/false);
  204. }
  205. void log(int64_t Reward, bool Success) {
  206. if (Mandatory)
  207. return;
  208. InlineEvent Event;
  209. Event.AdvisedDecision = isInliningRecommended();
  210. Event.DefaultDecision = DefaultDecision;
  211. Event.Effect = Success;
  212. Event.Reward = Reward;
  213. Logger.logInlineEvent(Event, getAdvisor()->getModelRunner());
  214. }
  215. static const int64_t NoReward = 0;
  216. TrainingLogger &Logger;
  217. const std::optional<size_t> CallerSizeEstimateBefore;
  218. const std::optional<size_t> CalleeSizeEstimateBefore;
  219. const int64_t DefaultDecision;
  220. const int64_t Mandatory;
  221. };
  222. static const std::vector<TensorSpec> TrainingOnlyFeatures{
  223. TensorSpec::createSpec<int64_t>(TFFeedPrefix + "inlining_default", {1}),
  224. TensorSpec::createSpec<float>(TFFeedPrefix + "discount", {1}),
  225. TensorSpec::createSpec<float>(TFFeedPrefix + "reward", {1}),
  226. TensorSpec::createSpec<int32_t>(TFFeedPrefix + "step_type", {1})};
  227. static const std::vector<TensorSpec> getInputFeatures() {
  228. std::vector<TensorSpec> InputSpecs;
  229. for (size_t I = 0; I < NumberOfFeatures; ++I)
  230. InputSpecs.push_back(TensorSpec::createSpec<int64_t>(
  231. TFFeedPrefix + FeatureMap[I].name(), FeatureMap[I].shape()));
  232. append_range(InputSpecs, TrainingOnlyFeatures);
  233. return InputSpecs;
  234. }
  235. } // namespace
  236. TrainingLogger::TrainingLogger(StringRef LogFileName,
  237. const ModelUnderTrainingRunner *MUTR)
  238. : LogFileName(LogFileName), MUTR(MUTR) {
  239. // The first output is the inlining decision.
  240. std::vector<TensorSpec> FT(FeatureMap.begin(), FeatureMap.end());
  241. if (MUTR)
  242. append_range(FT, MUTR->extraOutputsForLoggingSpecs());
  243. DefaultDecisionPos = FT.size();
  244. FT.push_back(TensorSpec::createSpec<int64_t>(DefaultDecisionName, {1}));
  245. DecisionPos = FT.size();
  246. FT.push_back(TensorSpec::createSpec<int64_t>(DecisionName, {1}));
  247. std::error_code EC;
  248. auto OS = std::make_unique<raw_fd_ostream>(TrainingLog, EC);
  249. if (EC)
  250. dbgs() << (EC.message() + ":" + TrainingLog);
  251. L = std::make_unique<Logger>(
  252. std::move(OS), FT, TensorSpec::createSpec<int64_t>(RewardName, {1}),
  253. InlineSizeEstimatorAnalysis::isEvaluatorRequested());
  254. L->switchContext("");
  255. }
  256. /// Log one inlining event.
  257. void TrainingLogger::logInlineEvent(const InlineEvent &Event,
  258. const MLModelRunner &ModelRunner) {
  259. L->startObservation();
  260. size_t CurrentFeature = 0;
  261. for (; CurrentFeature < NumberOfFeatures; ++CurrentFeature)
  262. L->logTensorValue(CurrentFeature,
  263. reinterpret_cast<const char *>(
  264. ModelRunner.getTensorUntyped(CurrentFeature)));
  265. if (MUTR)
  266. for (size_t I = 0; I < MUTR->extraOutputsForLoggingSpecs().size(); ++I) {
  267. const char *RawData =
  268. reinterpret_cast<const char *>(MUTR->getUntypedExtraOutputValue(I));
  269. L->logTensorValue(CurrentFeature, RawData);
  270. ++CurrentFeature;
  271. }
  272. assert(CurrentFeature == DefaultDecisionPos);
  273. L->logTensorValue(DefaultDecisionPos,
  274. reinterpret_cast<const char *>(&Event.DefaultDecision));
  275. L->logTensorValue(DecisionPos,
  276. reinterpret_cast<const char *>(&Event.AdvisedDecision));
  277. L->endObservation();
  278. if (InlineSizeEstimatorAnalysis::isEvaluatorRequested())
  279. L->logReward(Event.Reward);
  280. // For debugging / later use
  281. Effects.push_back(Event.Effect);
  282. }
  283. DevelopmentModeMLInlineAdvisor::DevelopmentModeMLInlineAdvisor(
  284. Module &M, ModuleAnalysisManager &MAM,
  285. std::unique_ptr<MLModelRunner> ModelRunner,
  286. std::function<bool(CallBase &)> GetDefaultAdvice,
  287. std::unique_ptr<TrainingLogger> Logger)
  288. : MLInlineAdvisor(M, MAM, std::move(ModelRunner)),
  289. GetDefaultAdvice(GetDefaultAdvice),
  290. IsDoingInference(isa<ModelUnderTrainingRunner>(getModelRunner())),
  291. Logger(std::move(Logger)),
  292. InitialNativeSize(isLogging() ? getTotalSizeEstimate() : 0),
  293. CurrentNativeSize(InitialNativeSize) {
  294. // We cannot have the case of neither inference nor logging.
  295. assert(IsDoingInference || isLogging());
  296. }
  297. std::optional<size_t>
  298. DevelopmentModeMLInlineAdvisor::getNativeSizeEstimate(const Function &F) const {
  299. if (!InlineSizeEstimatorAnalysis::isEvaluatorRequested())
  300. return std::nullopt;
  301. auto &R =
  302. FAM.getResult<InlineSizeEstimatorAnalysis>(const_cast<Function &>(F));
  303. if (!R) {
  304. F.getParent()->getContext().emitError(
  305. "Native size estimator is not present.");
  306. return 0;
  307. }
  308. return *R;
  309. }
  310. std::unique_ptr<MLInlineAdvice>
  311. DevelopmentModeMLInlineAdvisor::getMandatoryAdviceImpl(CallBase &CB) {
  312. return std::make_unique<LoggingMLInlineAdvice>(
  313. /*Advisor=*/this,
  314. /*CB=*/CB, /*ORE=*/getCallerORE(CB), /*Recommendation=*/true,
  315. /*Logger=*/*Logger,
  316. /*CallerSizeEstimateBefore=*/getNativeSizeEstimate(*CB.getCaller()),
  317. /*CalleeSizeEstimateBefore=*/
  318. getNativeSizeEstimate(*CB.getCalledFunction()),
  319. /*DefaultDecision=*/true, /*Mandatory*/ true);
  320. }
  321. std::unique_ptr<MLInlineAdvice>
  322. DevelopmentModeMLInlineAdvisor::getAdviceFromModel(
  323. CallBase &CB, OptimizationRemarkEmitter &ORE) {
  324. if (IsDoingInference && !isLogging())
  325. return MLInlineAdvisor::getAdviceFromModel(CB, ORE);
  326. bool DefaultAdvice = GetDefaultAdvice(CB);
  327. auto Recommendation =
  328. IsDoingInference ? static_cast<bool>(ModelRunner->evaluate<int64_t>())
  329. : DefaultAdvice;
  330. return std::make_unique<LoggingMLInlineAdvice>(
  331. /*Advisor=*/this,
  332. /*CB=*/CB, /*ORE=*/ORE, /*Recommendation=*/Recommendation,
  333. /*Logger=*/*Logger,
  334. /*CallerSizeEstimateBefore=*/getNativeSizeEstimate(*CB.getCaller()),
  335. /*CalleeSizeEstimateBefore=*/
  336. getNativeSizeEstimate(*CB.getCalledFunction()),
  337. /*DefaultDecision=*/DefaultAdvice);
  338. }
  339. size_t DevelopmentModeMLInlineAdvisor::getTotalSizeEstimate() {
  340. if (!InlineSizeEstimatorAnalysis::isEvaluatorRequested())
  341. return 0;
  342. size_t Ret = 0;
  343. for (auto &F : M) {
  344. if (F.isDeclaration())
  345. continue;
  346. Ret += *getNativeSizeEstimate(F);
  347. }
  348. return Ret;
  349. }
  350. std::unique_ptr<InlineAdvisor> llvm::getDevelopmentModeAdvisor(
  351. Module &M, ModuleAnalysisManager &MAM,
  352. std::function<bool(CallBase &)> GetDefaultAdvice) {
  353. auto &Ctx = M.getContext();
  354. std::unique_ptr<MLModelRunner> Runner;
  355. if (TFModelUnderTrainingPath.empty())
  356. Runner.reset(new NoInferenceModelRunner(Ctx, getInputFeatures()));
  357. else
  358. Runner = ModelUnderTrainingRunner::createAndEnsureValid(
  359. Ctx, TFModelUnderTrainingPath, DecisionName, getInputFeatures(),
  360. TFOutputSpecOverride);
  361. if (!Runner)
  362. return nullptr;
  363. std::unique_ptr<TrainingLogger> Logger;
  364. if (!TrainingLog.empty())
  365. Logger = std::make_unique<TrainingLogger>(
  366. TrainingLog, dyn_cast<ModelUnderTrainingRunner>(Runner.get()));
  367. return std::make_unique<DevelopmentModeMLInlineAdvisor>(
  368. M, MAM, std::move(Runner), GetDefaultAdvice, std::move(Logger));
  369. }
  370. #endif // defined(LLVM_HAVE_TFLITE)