Skip to content

Commit 5fd51fc

Browse files
committed
Reland "[mlgo] Hook up the interactive runner to the mlgo-ed passes"
This reverts commit a772f0b. The main problem was related to how we handled `dbgs()` from the hosted compiler. Using explicit `subprocess.communicate`, and not relying on dbgs() being flushed until the end appears to address the problem. Also some fixes due to some bots running older pythons, so we can't have nice things like `int | float` and such.
1 parent 51fa032 commit 5fd51fc

16 files changed

+281
-33
lines changed

llvm/include/llvm/Analysis/InlineModelFeatureMaps.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,9 +129,10 @@ inlineCostFeatureToMlFeature(InlineCostFeatureIndex Feature) {
129129
constexpr size_t NumberOfFeatures =
130130
static_cast<size_t>(FeatureIndex::NumberOfFeatures);
131131

132-
extern const std::array<TensorSpec, NumberOfFeatures> FeatureMap;
132+
extern const std::vector<TensorSpec> FeatureMap;
133133

134134
extern const char *const DecisionName;
135+
extern const TensorSpec InlineDecisionSpec;
135136
extern const char *const DefaultDecisionName;
136137
extern const char *const RewardName;
137138

llvm/include/llvm/Analysis/InteractiveModelRunner.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ class InteractiveModelRunner : public MLModelRunner {
4848
static bool classof(const MLModelRunner *R) {
4949
return R->getKind() == MLModelRunner::Kind::Interactive;
5050
}
51-
void switchContext(StringRef Name) {
51+
void switchContext(StringRef Name) override {
5252
Log->switchContext(Name);
5353
Log->flush();
5454
}

llvm/include/llvm/Analysis/MLModelRunner.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ class MLModelRunner {
4949

5050
enum class Kind : int { Unknown, Release, Development, NoOp, Interactive };
5151
Kind getKind() const { return Type; }
52+
virtual void switchContext(StringRef Name) {}
5253

5354
protected:
5455
MLModelRunner(LLVMContext &Ctx, Kind Type, size_t NrInputs)

llvm/include/llvm/Analysis/ReleaseModeModelRunner.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,12 @@ class NoopSavedModelImpl final {
8585
void *arg_data(int) { llvm_unreachable(NOOP_MODEL_ERRMSG); }
8686
#undef NOOP_MODEL_ERRMSG
8787
};
88+
89+
template <class T> bool isEmbeddedModelEvaluatorValid() { return true; }
90+
91+
template <> inline bool isEmbeddedModelEvaluatorValid<NoopSavedModelImpl>() {
92+
return false;
93+
}
8894
} // namespace llvm
8995

9096
#endif // LLVM_ANALYSIS_RELEASEMODEMODELRUNNER_H

llvm/lib/Analysis/DevelopmentModeInlineAdvisor.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -283,7 +283,7 @@ TrainingLogger::TrainingLogger(StringRef LogFileName,
283283
FT.push_back(TensorSpec::createSpec<int64_t>(DefaultDecisionName, {1}));
284284

285285
DecisionPos = FT.size();
286-
FT.push_back(TensorSpec::createSpec<int64_t>(DecisionName, {1}));
286+
FT.push_back(InlineDecisionSpec);
287287
std::error_code EC;
288288
auto OS = std::make_unique<raw_fd_ostream>(TrainingLog, EC);
289289
if (EC)

llvm/lib/Analysis/InlineAdvisor.cpp

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -231,10 +231,8 @@ bool InlineAdvisorAnalysis::Result::tryCreate(
231231
#endif
232232
break;
233233
case InliningAdvisorMode::Release:
234-
#ifdef LLVM_HAVE_TF_AOT
235234
LLVM_DEBUG(dbgs() << "Using release-mode inliner policy.\n");
236235
Advisor = llvm::getReleaseModeAdvisor(M, MAM);
237-
#endif
238236
break;
239237
}
240238

llvm/lib/Analysis/MLInlineAdvisor.cpp

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,12 @@
1818
#include "llvm/Analysis/FunctionPropertiesAnalysis.h"
1919
#include "llvm/Analysis/InlineCost.h"
2020
#include "llvm/Analysis/InlineModelFeatureMaps.h"
21+
#include "llvm/Analysis/InteractiveModelRunner.h"
2122
#include "llvm/Analysis/LazyCallGraph.h"
2223
#include "llvm/Analysis/LoopInfo.h"
2324
#include "llvm/Analysis/MLModelRunner.h"
2425
#include "llvm/Analysis/OptimizationRemarkEmitter.h"
26+
#include "llvm/Analysis/ReleaseModeModelRunner.h"
2527
#include "llvm/Analysis/TargetTransformInfo.h"
2628
#include "llvm/IR/Dominators.h"
2729
#include "llvm/IR/InstIterator.h"
@@ -30,19 +32,37 @@
3032

3133
using namespace llvm;
3234

35+
static cl::opt<std::string> InteractiveChannelBaseName(
36+
"inliner-interactive-channel-base", cl::Hidden,
37+
cl::desc(
38+
"Base file path for the interactive mode. The incoming filename should "
39+
"have the name <inliner-interactive-channel-base>.in, while the "
40+
"outgoing name should be <inliner-interactive-channel-base>.out"));
41+
3342
#if defined(LLVM_HAVE_TF_AOT_INLINERSIZEMODEL)
34-
#include "llvm/Analysis/ReleaseModeModelRunner.h"
3543
// codegen-ed file
3644
#include "InlinerSizeModel.h" // NOLINT
45+
using CompiledModelType = llvm::InlinerSizeModel;
46+
#else
47+
using CompiledModelType = NoopSavedModelImpl;
48+
#endif
3749

3850
std::unique_ptr<InlineAdvisor>
3951
llvm::getReleaseModeAdvisor(Module &M, ModuleAnalysisManager &MAM) {
40-
auto AOTRunner =
41-
std::make_unique<ReleaseModeModelRunner<llvm::InlinerSizeModel>>(
42-
M.getContext(), FeatureMap, DecisionName);
52+
if (!llvm::isEmbeddedModelEvaluatorValid<CompiledModelType>() &&
53+
InteractiveChannelBaseName.empty())
54+
return nullptr;
55+
std::unique_ptr<MLModelRunner> AOTRunner;
56+
if (InteractiveChannelBaseName.empty())
57+
AOTRunner = std::make_unique<ReleaseModeModelRunner<CompiledModelType>>(
58+
M.getContext(), FeatureMap, DecisionName);
59+
else
60+
AOTRunner = std::make_unique<InteractiveModelRunner>(
61+
M.getContext(), FeatureMap, InlineDecisionSpec,
62+
InteractiveChannelBaseName + ".out",
63+
InteractiveChannelBaseName + ".in");
4364
return std::make_unique<MLInlineAdvisor>(M, MAM, std::move(AOTRunner));
4465
}
45-
#endif
4666

4767
#define DEBUG_TYPE "inline-ml"
4868

@@ -59,7 +79,7 @@ static cl::opt<bool> KeepFPICache(
5979
cl::init(false));
6080

6181
// clang-format off
62-
const std::array<TensorSpec, NumberOfFeatures> llvm::FeatureMap{
82+
const std::vector<TensorSpec> llvm::FeatureMap{
6383
#define POPULATE_NAMES(_, NAME) TensorSpec::createSpec<int64_t>(NAME, {1} ),
6484
// InlineCost features - these must come first
6585
INLINE_COST_FEATURE_ITERATOR(POPULATE_NAMES)
@@ -73,6 +93,8 @@ const std::array<TensorSpec, NumberOfFeatures> llvm::FeatureMap{
7393
// clang-format on
7494

7595
const char *const llvm::DecisionName = "inlining_decision";
96+
const TensorSpec llvm::InlineDecisionSpec =
97+
TensorSpec::createSpec<int64_t>(DecisionName, {1});
7698
const char *const llvm::DefaultDecisionName = "inlining_default";
7799
const char *const llvm::RewardName = "delta_size";
78100

@@ -94,7 +116,7 @@ MLInlineAdvisor::MLInlineAdvisor(Module &M, ModuleAnalysisManager &MAM,
94116
CG(MAM.getResult<LazyCallGraphAnalysis>(M)),
95117
InitialIRSize(getModuleIRSize()), CurrentIRSize(InitialIRSize) {
96118
assert(ModelRunner);
97-
119+
ModelRunner->switchContext("");
98120
// Extract the 'call site height' feature - the position of a call site
99121
// relative to the farthest statically reachable SCC node. We don't mutate
100122
// this value while inlining happens. Empirically, this feature proved
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
"""Utility for testing InteractiveModelRunner.
2+
3+
Use it from pass-specific tests by providing a main .py which calls this library's
4+
`run_interactive` with an appropriate callback to provide advice.
5+
6+
From .ll tests, just call the above-mentioned main as a prefix to the opt/llc
7+
invocation (with the appropriate flags enabling the interactive mode)
8+
9+
Examples:
10+
test/Transforms/Inline/ML/interactive-mode.ll
11+
test/CodeGen/MLRegalloc/interactive-mode.ll
12+
"""
13+
14+
import ctypes
15+
import log_reader
16+
import io
17+
import math
18+
import os
19+
import subprocess
20+
from typing import BinaryIO, Callable, Union
21+
22+
23+
def send(f: io.BufferedWriter, value: Union[int, float],
24+
spec: log_reader.TensorSpec):
25+
"""Send the `value` - currently just a scalar - formatted as per `spec`."""
26+
27+
# just int64 for now
28+
assert (spec.element_type == ctypes.c_int64)
29+
to_send = ctypes.c_int64(int(value))
30+
assert f.write(bytes(to_send)) == ctypes.sizeof(
31+
spec.element_type) * math.prod(spec.shape)
32+
f.flush()
33+
34+
35+
def run_interactive(temp_rootname: str,
36+
make_response: Callable[[list[log_reader.TensorValue]],
37+
Union[int, float]],
38+
process_and_args: list[str]):
39+
"""Host the compiler.
40+
Args:
41+
temp_rootname: the base file name from which to construct the 2 pipes for
42+
communicating with the compiler.
43+
make_response: a function that, given the current tensor values, provides a
44+
response.
45+
process_and_args: the full commandline for the compiler. It it assumed it
46+
contains a flag poiting to `temp_rootname` so that the InteractiveModeRunner
47+
would attempt communication on the same pair as this function opens.
48+
49+
This function sets up the communication with the compiler - via 2 files named
50+
`temp_rootname`.in and `temp_rootname`.out - prints out the received features,
51+
and sends back to the compiler an advice (which it gets from `make_response`).
52+
It's used for testing, and also to showcase how to set up communication in an
53+
interactive ML ("gym") environment.
54+
"""
55+
to_compiler = temp_rootname + ".in"
56+
from_compiler = temp_rootname + ".out"
57+
try:
58+
os.mkfifo(to_compiler, 0o666)
59+
os.mkfifo(from_compiler, 0o666)
60+
compiler_proc = subprocess.Popen(
61+
process_and_args, stderr=subprocess.PIPE, stdout=subprocess.DEVNULL)
62+
with io.BufferedWriter(io.FileIO(to_compiler, 'wb')) as tc:
63+
with io.BufferedReader(io.FileIO(from_compiler, 'rb')) as fc:
64+
tensor_specs, _, advice_spec = log_reader.read_header(fc)
65+
context = None
66+
while compiler_proc.poll() is None and (next_event := fc.readline()):
67+
last_context, observation_id, features, _ = log_reader.read_one_observation(
68+
context, next_event, fc, tensor_specs, None)
69+
if last_context != context:
70+
print(f'context: {last_context}')
71+
context = last_context
72+
print(f'observation: {observation_id}')
73+
tensor_values = []
74+
for fv in features:
75+
log_reader.pretty_print_tensor_value(fv)
76+
tensor_values.append(fv)
77+
send(tc, make_response(tensor_values), advice_spec)
78+
_, err = compiler_proc.communicate()
79+
print(err.decode('utf-8'))
80+
compiler_proc.wait()
81+
82+
finally:
83+
os.unlink(to_compiler)
84+
os.unlink(from_compiler)

llvm/lib/CodeGen/MLRegallocEvictAdvisor.cpp

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include "AllocationOrder.h"
1414
#include "RegAllocEvictionAdvisor.h"
1515
#include "RegAllocGreedy.h"
16+
#include "llvm/Analysis/InteractiveModelRunner.h"
1617
#include "llvm/Analysis/MLModelRunner.h"
1718
#include "llvm/Analysis/TensorSpec.h"
1819
#if defined(LLVM_HAVE_TF_AOT_REGALLOCEVICTMODEL) || defined(LLVM_HAVE_TFLITE)
@@ -52,6 +53,14 @@ using CompiledModelType = RegallocEvictModel;
5253
using CompiledModelType = NoopSavedModelImpl;
5354
#endif
5455

56+
static cl::opt<std::string> InteractiveChannelBaseName(
57+
"regalloc-evict-interactive-channel-base", cl::Hidden,
58+
cl::desc(
59+
"Base file path for the interactive mode. The incoming filename should "
60+
"have the name <regalloc-evict-interactive-channel-base>.in, while the "
61+
"outgoing name should be "
62+
"<regalloc-evict-interactive-channel-base>.out"));
63+
5564
// Options that only make sense in development mode
5665
#ifdef LLVM_HAVE_TFLITE
5766
#include "RegAllocScore.h"
@@ -213,6 +222,8 @@ static const std::vector<int64_t> PerLiveRangeShape{1, NumberOfInterferences};
213222
// will be guaranteed to be to a mask == 1 position. Using a macro here to
214223
// avoid 'not used' warnings (and keep cond compilation to a minimum)
215224
#define DecisionName "index_to_evict"
225+
static const TensorSpec DecisionSpec =
226+
TensorSpec::createSpec<int64_t>(DecisionName, {1});
216227

217228
// Named features index.
218229
enum FeatureIDs {
@@ -382,14 +393,21 @@ class ReleaseModeEvictionAdvisorAnalysis final
382393

383394
std::unique_ptr<RegAllocEvictionAdvisor>
384395
getAdvisor(const MachineFunction &MF, const RAGreedy &RA) override {
385-
if (!Runner)
386-
Runner = std::make_unique<ReleaseModeModelRunner<CompiledModelType>>(
387-
MF.getFunction().getContext(), InputFeatures, DecisionName);
396+
if (!Runner) {
397+
if (InteractiveChannelBaseName.empty())
398+
Runner = std::make_unique<ReleaseModeModelRunner<CompiledModelType>>(
399+
MF.getFunction().getContext(), InputFeatures, DecisionName);
400+
else
401+
Runner = std::make_unique<InteractiveModelRunner>(
402+
MF.getFunction().getContext(), InputFeatures, DecisionSpec,
403+
InteractiveChannelBaseName + ".out",
404+
InteractiveChannelBaseName + ".in");
405+
}
388406
return std::make_unique<MLEvictAdvisor>(
389407
MF, RA, Runner.get(), getAnalysis<MachineBlockFrequencyInfo>(),
390408
getAnalysis<MachineLoopInfo>());
391409
}
392-
std::unique_ptr<ReleaseModeModelRunner<CompiledModelType>> Runner;
410+
std::unique_ptr<MLModelRunner> Runner;
393411
};
394412

395413
// ===================================
@@ -398,8 +416,6 @@ class ReleaseModeEvictionAdvisorAnalysis final
398416
//
399417
// Features we log
400418
#ifdef LLVM_HAVE_TFLITE
401-
static const TensorSpec Output =
402-
TensorSpec::createSpec<int64_t>(DecisionName, {1});
403419
static const TensorSpec Reward = TensorSpec::createSpec<float>("reward", {1});
404420

405421
// Features we bind on the model. The tensor names have a prefix, and we also
@@ -512,7 +528,7 @@ class DevelopmentModeEvictionAdvisorAnalysis final
512528
// We always log the output; in particular, if we're not evaluating, we
513529
// don't have an output spec json file. That's why we handle the
514530
// 'normal' output separately.
515-
LFS.push_back(Output);
531+
LFS.push_back(DecisionSpec);
516532

517533
Log = std::make_unique<Logger>(std::move(OS), LFS, Reward,
518534
/*IncludeReward*/ true);
@@ -557,6 +573,7 @@ MLEvictAdvisor::MLEvictAdvisor(const MachineFunction &MF, const RAGreedy &RA,
557573
Runner(std::move(Runner)), MBFI(MBFI), Loops(Loops),
558574
InitialQSize(MLEvictAdvisor::getInitialQueueSize(MF)) {
559575
assert(this->Runner);
576+
Runner->switchContext(MF.getName());
560577
DoNotNormalize.set(FeatureIDs::mask);
561578
DoNotNormalize.set(FeatureIDs::is_free);
562579
DoNotNormalize.set(FeatureIDs::is_hint);
@@ -1134,7 +1151,10 @@ bool RegAllocScoring::runOnMachineFunction(MachineFunction &MF) {
11341151
#endif // #ifdef LLVM_HAVE_TFLITE
11351152

11361153
RegAllocEvictionAdvisorAnalysis *llvm::createReleaseModeAdvisor() {
1137-
return new ReleaseModeEvictionAdvisorAnalysis();
1154+
return llvm::isEmbeddedModelEvaluatorValid<CompiledModelType>() ||
1155+
!InteractiveChannelBaseName.empty()
1156+
? new ReleaseModeEvictionAdvisorAnalysis()
1157+
: nullptr;
11381158
}
11391159

11401160
// In all cases except development mode, we don't need scoring.

0 commit comments

Comments
 (0)