[tmva][sofie] Restructure emitted code to be differentiable with Clad#18332
[tmva][sofie] Restructure emitted code to be differentiable with Clad#18332guitargeek wants to merge 5 commits intoroot-project:masterfrom
Conversation
tmva/sofie/inc/TMVA/SOFIE_common.hxx
Outdated
| return out; | ||
| } | ||
|
|
||
| inline void Copy(float const *b, float const *e, float *o) |
There was a problem hiding this comment.
Does providing a pullback for std::copy not work?
There was a problem hiding this comment.
No, I tried a bit but then gave up. This was my approach:
#include <Math/CladDerivator.h>
namespace std {
void copy_pullback(double const *first, double const *last, double *out_first, double *_d_out, double *_d_first,
double *_d_last, double *_d_out_first)
{
// Implementation doesn't matter yet, it doesn't compile anyway
}
} // namespace std
void fooImpl(double const *x, double *y)
{
std::copy(x, x + 1, y);
}
void foo(double const *x, double *y)
{
fooImpl(x, y);
}
double g(double *variables)
{
double out;
foo(variables, &out);
return out * variables[1];
}
void clademo()
{
// Call clad to generate the gradient of g.
auto g_grad = clad::gradient(g, "variables");
// Execute the generated gradient function.
double variables[]{3., 4.};
double grad_output[]{0., 0.};
g_grad.execute(variables, grad_output);
std::cout << "grad_output[0]: " << grad_output[0] << std::endl;
std::cout << "grad_output[1]: " << grad_output[1] << std::endl;
// Dump the generated gradient code to standard output.
g_grad.dump();
}It segfaults. I think Clad just doesn't play well with the STL algos that take iterators, it's better to avoid this, no?
In any case, supporting this is not crucial for this PR. I was refactoring things to avoid this copy call in the generated code anyway.
Once this PR is functional for our usecase (actually now, but I also want to make the ROOT CI pass again), I'll write up what was not perfect in Clad for this and open issues
There was a problem hiding this comment.
Ah, I see. Probably worth opening an issue in clad…
| }); | ||
|
|
||
| TMVA_SOFIE_Equal::Session s("Equal_FromONNX.dat"); | ||
| std::vector<bool> output = s.infer(input1.data(),input2.data()); |
There was a problem hiding this comment.
Did that fail to differentiate?
There was a problem hiding this comment.
No, I didn't even try to differentiate the models in the test. I'm solely focusing on the SBI usecase that we implement with LHCb. The reason why I changed this is because std::vector<bool> is not a good output type parameter. See:
Test Results 22 files 22 suites 3d 13h 28m 21s ⏱️ For more details on these failures, see this check. Results for commit 9ea0f01. ♻️ This comment has been updated with latest results. |
6b90cb6 to
87597cd
Compare
Proof of concept test for this PRTake this ONNX file (remove the VRlL_real_500k_evts_model.onnx.txt Here are the scripts to convert the model to C++ and then to differentiate it with Clad: // onnx_to_cpp.C
void onnx_to_cpp()
{
using namespace TMVA::Experimental;
SOFIE::RModelParser_ONNX parser;
SOFIE::RModel model = parser.Parse("./VRlL_real_500k_evts_model.onnx");
model.SetOptimizationLevel(SOFIE::OptimizationLevel::kBasic);
model.Generate();
model.PrintRequiredInputTensors();
model.OutputGenerated("./VRlL_real_500k_evts_model.hxx");
}// sofie_ad.C
#include "VRlL_real_500k_evts_model.hxx"
#include <Math/CladDerivator.h>
float my_func(TMVA_SOFIE_VRlL_real_500k_evts_model::Session const *session, float const *tensor_x,
float *tensor_theory_params)
{
float out = 0.;
TMVA_SOFIE_VRlL_real_500k_evts_model::doInfer(session, tensor_x, tensor_theory_params, &out);
return out;
}
void sofie_ad()
{
std::vector<float> input1{5.0, 2.0, 1.0, -1.0, 1.0};
std::vector<float> input2{0.0};
// Generated header file shall contain a Session class which requires
// initialization to load the corresponding weights.
TMVA_SOFIE_VRlL_real_500k_evts_model::Session s("VRlL_real_500k_evts_model.dat");
// Once instantiated the session object's infer method can be used
// std::vector<float> out = s.infer(input1.data(), input2.data());
auto func = [&](std::span<float> params) { return s.infer(input1.data(), params.data())[0]; };
auto numDiff = [&](int i) {
const float eps = 1e-4;
std::vector<float> p{input2};
p[i] = input2[i] - eps;
float funcValDown = func(p);
p[i] = input2[i] + eps;
float funcValUp = func(p);
return (funcValUp - funcValDown) / (2 * eps);
};
for (std::size_t i = 0; i < input2.size(); ++i) {
std::cout << i << ":" << std::endl;
std::cout << " numr : " << numDiff(i) << std::endl;
}
float grad_output[]{0., 0., 0., 0., 0.};
auto g_grad = clad::gradient<clad::opts::disable_tbr>(my_func, "tensor_theory_params");
g_grad.execute(&s, input1.data(), input2.data(), grad_output);
std::fill(std::begin(grad_output), std::end(grad_output), 0);
g_grad.execute(&s, input1.data(), input2.data(), grad_output);
std::cout << " clad : " << grad_output[0] << std::endl;
g_grad.dump();
}Note that Usage with expected output (replace |
89b638c to
a3d545f
Compare
3f40542 to
78fcc20
Compare
4c9920f to
97903fa
Compare
|
Why did we decide to not pursue this? |
|
@vgvassilev, sorry that was totally an accident. Maybe I confused it with another PR, or I wanted to close and re-open the PR to run the tests, but apparently I missed the "reopen" button. |
TMVA SOFIE development is challenging sometimes, because of how the tests are structured. The tests that covers many possible models imported from ONNX or ROOT have the issue that they includes **all** emitted code in the compiled executables. This means that one gets a build failure on the first model that generated invalid code, and that was it. Therefore, it's difficult to debug what is going wrong. This commit suggests include the generated code with the interpreter instead. Then, one can check for each individual model if the code was valid, and if not, skip over to the next test a print the emitted code that failed to compile. It has some performance overhead, but the tests still only take about 6 seconds. The drastically improved debugging experience justifies these few extra seconds spent on testing. This was motivated by the effort to refactor the SOFIE-emitted code to make it differentiable with Clad.
Like this, we don't have to add these forward declarations conditionally to the emitted code.
Restructure emitted code to be differentiable with Clad.