Skip to content

Commit add25d5

Browse files
committed
[RF] Generalize Garimas AD scaling study and add plotting script
1 parent 624be91 commit add25d5

File tree

3 files changed

+209
-39
lines changed

3 files changed

+209
-39
lines changed

root/roofit/roofit/BenchmarkUtils.h

Lines changed: 2 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -4,42 +4,18 @@
44
#include <RooArgSet.h>
55
#include <RooRandom.h>
66
#include <RooAbsRealLValue.h>
7-
#include <RooFuncWrapper.h>
87

98
#include "benchmark/benchmark.h"
109

1110
#include <random>
1211

1312
namespace RooFitADBenchmarksUtils {
1413

15-
enum backend { Reference, BatchMode, CodeSquashNumDiff, CodeSquashAD };
1614
template <typename F>
17-
void doBenchmarks(F func, int rangeMin, int rangeMax, int step = 1, int numIterations = 1,
15+
void doBenchmarks(const char *name, int backend, F func, std::vector<long int> const &range, int numIterations = 1,
1816
benchmark::TimeUnit unit = benchmark::kMillisecond)
1917
{
20-
// Run the minimization with the reference NLL
21-
benchmark::RegisterBenchmark("NllReferenceMinimization", func)
22-
->ArgsProduct({{Reference}, benchmark::CreateDenseRange(rangeMin, rangeMax, step)})
23-
->Unit(unit)
24-
->Iterations(numIterations);
25-
26-
// Run the minimization with the reference NLL (BatchMode)
27-
benchmark::RegisterBenchmark("NllBatchModeMinimization", func)
28-
->ArgsProduct({{BatchMode}, benchmark::CreateDenseRange(rangeMin, rangeMax, step)})
29-
->Unit(unit)
30-
->Iterations(numIterations);
31-
32-
// Run the minimization with the code-squashed version with numerical-diff.
33-
benchmark::RegisterBenchmark("NllCodeSquash_NumDiff", func)
34-
->ArgsProduct({{CodeSquashNumDiff}, benchmark::CreateDenseRange(rangeMin, rangeMax, step)})
35-
->Unit(unit)
36-
->Iterations(numIterations);
37-
38-
// Run the minimization with the code-squashed version with AD.
39-
benchmark::RegisterBenchmark("NllCodeSquash_AD", func)
40-
->ArgsProduct({{CodeSquashAD}, benchmark::CreateDenseRange(rangeMin, rangeMax, step)})
41-
->Unit(unit)
42-
->Iterations(numIterations);
18+
benchmark::RegisterBenchmark(name, func)->ArgsProduct({{backend}, range})->Unit(unit)->Iterations(numIterations);
4319
}
4420

4521
void randomizeParameters(const RooArgSet &parameters)

root/roofit/roofit/benchCodeSquashAD.cxx

Lines changed: 48 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -62,15 +62,22 @@ std::unique_ptr<RooAbsPdf> createModel(RooRealVar &x, std::string const &channel
6262
return std::unique_ptr<RooAbsPdf>{static_cast<RooAbsPdf *>(model.cloneTree())};
6363
}
6464

65+
enum backend {
66+
Reference,
67+
BatchMode,
68+
CodeSquashNumDiff,
69+
CodeSquashAD
70+
};
71+
6572
static void BM_RooFuncWrapper_ManyParams_Minimization(benchmark::State &state)
6673
{
6774
using namespace RooFit;
6875

6976
counter++;
7077

71-
gInterpreter->ProcessLine("gErrorIgnoreLevel = 2001;");
72-
auto &msg = RooMsgService::instance();
73-
msg.setGlobalKillBelow(RooFit::WARNING);
78+
// gInterpreter->ProcessLine("gErrorIgnoreLevel = 2001;");
79+
// auto &msg = RooMsgService::instance();
80+
// msg.setGlobalKillBelow(RooFit::WARNING);
7481

7582
// Generate the same dataset for all backends.
7683
RooRandom::randomGenerator()->SetSeed(100);
@@ -117,26 +124,28 @@ static void BM_RooFuncWrapper_ManyParams_Minimization(benchmark::State &state)
117124
RooArgSet origParams;
118125
params.snapshot(origParams);
119126

120-
std::unique_ptr<RooAbsReal> nllRef{model.createNLL(data, EvalBackend::Legacy(), Offset("off"))};
121-
std::unique_ptr<RooAbsReal> nllRefBatch{model.createNLL(data, EvalBackend::Cpu(), Offset("off"))};
122-
std::unique_ptr<RooAbsReal> nllFunc{model.createNLL(data, EvalBackend::Codegen(), Offset("off"))};
123-
std::unique_ptr<RooAbsReal> nllFuncNoGrad{model.createNLL(data, EvalBackend::CodegenNoGrad(), Offset("off"))};
127+
std::cout << "nparams: " << params.size() << std::endl;
128+
129+
std::unique_ptr<RooAbsReal> nllRef{model.createNLL(data, EvalBackend::Legacy(), Offset("initial"))};
130+
std::unique_ptr<RooAbsReal> nllRefBatch{model.createNLL(data, EvalBackend::Cpu(), Offset("initial"))};
131+
std::unique_ptr<RooAbsReal> nllFunc{model.createNLL(data, EvalBackend::Codegen(), Offset("initial"))};
132+
std::unique_ptr<RooAbsReal> nllFuncNoGrad{model.createNLL(data, EvalBackend::CodegenNoGrad(), Offset("initial"))};
124133

125134
std::unique_ptr<RooMinimizer> m = nullptr;
126135

127136
int code = state.range(0);
128-
if (code == RooFitADBenchmarksUtils::backend::Reference) {
137+
if (code == backend::Reference) {
129138
m = std::make_unique<RooMinimizer>(*nllRef);
130-
} else if (code == RooFitADBenchmarksUtils::backend::CodeSquashNumDiff) {
139+
} else if (code == backend::CodeSquashNumDiff) {
131140
m = std::make_unique<RooMinimizer>(*nllFuncNoGrad);
132-
} else if (code == RooFitADBenchmarksUtils::backend::BatchMode) {
141+
} else if (code == backend::BatchMode) {
133142
m = std::make_unique<RooMinimizer>(*nllRefBatch);
134-
} else if (code == RooFitADBenchmarksUtils::backend::CodeSquashAD) {
143+
} else if (code == backend::CodeSquashAD) {
135144
m = std::make_unique<RooMinimizer>(*nllFunc);
136145
}
137146

138147
for (auto _ : state) {
139-
m->setPrintLevel(-1);
148+
// m->setPrintLevel(-1);
140149
m->setStrategy(0);
141150
params.assign(origParams);
142151

@@ -146,7 +155,33 @@ static void BM_RooFuncWrapper_ManyParams_Minimization(benchmark::State &state)
146155

147156
int main(int argc, char **argv)
148157
{
149-
RooFitADBenchmarksUtils::doBenchmarks(BM_RooFuncWrapper_ManyParams_Minimization, 2, 2, 1, 20);
158+
std::vector<long int> rangeLow{1, 2, 3, 4, 5, 6, 7, 8, 12, 17, 23, 28, 33, 38, 43, 49, 54, 59};
159+
std::vector<long int> rangeHigh{64, 70, 75, 80, 85, 90, 96, 100, 110, 120, 130, 140,
160+
150, 160, 170, 180, 190, 200, 225, 250, 275, 300, 325};
161+
std::vector<long int> range;
162+
range.insert(range.end(), rangeLow.begin(), rangeLow.end());
163+
range.insert(range.end(), rangeHigh.begin(), rangeHigh.end());
164+
165+
// Run the minimization with the reference NLL
166+
RooFitADBenchmarksUtils::doBenchmarks("NllReferenceMinimization", Reference,
167+
BM_RooFuncWrapper_ManyParams_Minimization, range, 1);
168+
169+
// Run the minimization with the reference NLL (BatchMode)
170+
RooFitADBenchmarksUtils::doBenchmarks("NllBatchModeMinimization", BatchMode,
171+
BM_RooFuncWrapper_ManyParams_Minimization, range, 1);
172+
173+
// Run the minimization with the code-squashed version with AD.
174+
RooFitADBenchmarksUtils::doBenchmarks("NllCodeSquash_AD", CodeSquashAD, BM_RooFuncWrapper_ManyParams_Minimization,
175+
range, 1);
176+
// RooFitADBenchmarksUtils::doBenchmarks("NllCodeSquash_AD", CodeSquashAD,
177+
// BM_RooFuncWrapper_ManyParams_Minimization, {250}, 1);
178+
179+
// Run the minimization with the code-squashed version with numerical-diff.
180+
// We can't go to more than 59 channels here, because since offsetting is
181+
// not supported, the fit will not converge anymore if the number of bins is
182+
// sufficiently high.
183+
RooFitADBenchmarksUtils::doBenchmarks("NllCodeSquash_NumDiff", CodeSquashNumDiff,
184+
BM_RooFuncWrapper_ManyParams_Minimization, rangeLow, 1);
150185

151186
benchmark::Initialize(&argc, argv);
152187
benchmark::RunSpecifiedBenchmarks();
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
#!/usr/bin/env python
2+
# coding: utf-8
3+
4+
5+
# Run on the output of benchCodeSquashAD piped into a file:
6+
# $ ./benchCodeSquashAD &> benchCodeSquashAD.out
7+
filename = "benchCodeSquashAD.out"
8+
9+
10+
import numpy as np
11+
import matplotlib.pyplot as plt
12+
import pandas as pd
13+
14+
15+
steps = {
16+
"NLL creation": "Creation of NLL object took ",
17+
"Function JIT": "Function JIT time: ",
18+
"Gradient generation": "Gradient generation time: ",
19+
"Gradient to machine code": "Gradient IR to machine code time: ",
20+
"Seeding step": "MnSeedGenerator Evaluated function and gradient in ",
21+
"NegativeG2LineSearch": "NegativeG2LineSearch Done after ",
22+
"Minimization": "VariableMetricBuilder Stop iterating after ",
23+
"Hesse": "MnHesse Done after ",
24+
"Total": "user ",
25+
}
26+
27+
28+
def time_string_to_seconds(s):
29+
s = s.replace("ms", "*1e-3")
30+
s = s.replace("s", "")
31+
s = s.replace("min", "*60+")
32+
s = s.replace("m", "*60+")
33+
s = s.replace("μ", "*1e-6")
34+
35+
return float(eval(s))
36+
37+
38+
with open(filename, "r") as f:
39+
lines = f.read().split("\n")
40+
41+
42+
def is_relevant(line):
43+
if "NegativeG2LineSearch" in line:
44+
return False
45+
if "iterations" in line:
46+
return True
47+
if "nparams" in line:
48+
return True
49+
if "FVAL = " in line:
50+
return True
51+
for key, val in steps.items():
52+
if val in line:
53+
return True
54+
return False
55+
56+
57+
lines = list(filter(is_relevant, lines))
58+
59+
60+
names = ["NllReferenceMinimization", "NllBatchModeMinimization", "NllCodeSquash_NumDiff", "NllCodeSquash_AD"]
61+
62+
backends = {
63+
"NllReferenceMinimization": "legacy",
64+
"NllBatchModeMinimization": "RooFit",
65+
"NllCodeSquash_NumDiff": "Hardcoded",
66+
"NllCodeSquash_AD": "RooFit AD",
67+
}
68+
69+
nll_line = {
70+
"NllReferenceMinimization": 1,
71+
"NllBatchModeMinimization": 2,
72+
"NllCodeSquash_NumDiff": 3,
73+
"NllCodeSquash_AD": 7,
74+
}
75+
76+
77+
def analyze_block(lines):
78+
# print(lines)
79+
data = dict()
80+
81+
name = lines[12].split("/")[0]
82+
83+
data["backend"] = backends[name]
84+
85+
def line_to_seconds(l):
86+
l = l.replace(" min ", "min")
87+
return time_string_to_seconds(" ".join(l.split(" ")[-2:]))
88+
89+
data["create_nll"] = line_to_seconds(lines[nll_line[name]])
90+
data["seeding"] = line_to_seconds(lines[9])
91+
data["migrad"] = line_to_seconds(lines[10])
92+
data["nparams"] = int(lines[0].split(" ")[-1]) - 1
93+
data["fval"] = float(lines[11].split(" ")[-1])
94+
95+
data["jitting"] = sum(line_to_seconds(lines[i]) for i in [4, 5, 6])
96+
97+
return data
98+
99+
100+
m_block = 13
101+
102+
103+
datas = []
104+
i = 0
105+
while i < len(lines):
106+
datas.append(analyze_block(lines[i : i + m_block]))
107+
i += m_block
108+
109+
110+
df = pd.DataFrame(datas)
111+
df = df.query("nparams > 10 and nparams < 2500 and backend != 'legacy'")
112+
113+
114+
t = np.arange(0.01, 5.0, 0.01)
115+
s1 = np.sin(2 * np.pi * t)
116+
s2 = np.exp(-t)
117+
s3 = np.sin(4 * np.pi * t)
118+
119+
f, (a0, a1) = plt.subplots(2, 1, gridspec_kw={"height_ratios": [2, 1]})
120+
121+
dfs = dict()
122+
123+
124+
for backend, df_g in df.groupby("backend"):
125+
dfs[backend] = df_g
126+
nparams = df_g["nparams"]
127+
vals = df_g.eval("migrad + seeding")
128+
# vals = df_g.eval("fval")
129+
if backend == "codegen_no_grad":
130+
continue
131+
if backend == "RooFit AD":
132+
a0.plot(nparams, vals + 0 * df_g["jitting"], label=backend)
133+
a0.plot(nparams, df_g["jitting"], label="JIT time", color="k", linewidth=1, linestyle="--")
134+
else:
135+
a0.plot(nparams, vals, label=backend)
136+
# plt.plot(nparams, df_g["jitting"], label=backend + "_jit")
137+
138+
nparams = dfs["RooFit"]["nparams"]
139+
140+
a0.legend(loc="upper left")
141+
142+
plt.tick_params("x", labelsize=6)
143+
144+
# make these tick labels invisible
145+
# plt.tick_params('x', labelbottom=False)
146+
vals = dfs["RooFit"]["migrad"].values / dfs["RooFit AD"]["migrad"].values
147+
148+
a1.plot(nparams, vals, color="k")
149+
150+
a1.set_xlabel("Number of parameters")
151+
a0.set_ylabel("Minimization time [s]")
152+
a0.set_ylim(0, 30)
153+
a0.set_xlim(0, nparams.to_numpy()[-1])
154+
155+
a1.set_ylabel("AD speedup")
156+
a1.set_ylim(1, 4.2)
157+
a1.set_xlim(0, nparams.to_numpy()[-1])
158+
159+
plt.savefig("scaling_study.png")

0 commit comments

Comments
 (0)