|
| 1 | +#include "TMVA/RModelProfiler.hxx" |
| 2 | +#include "TMVA/SOFIE_common.hxx" |
| 3 | + |
| 4 | +namespace TMVA { |
| 5 | +namespace Experimental { |
| 6 | +namespace SOFIE { |
| 7 | + |
| 8 | +// The constructor now just registers the necessary C++ libraries. |
| 9 | +RModelProfiler::RModelProfiler(RModel &model) : fModel(model) |
| 10 | +{ |
| 11 | + fModel.AddNeededStdLib("chrono"); // for timing operators |
| 12 | + fModel.AddNeededStdLib("vector"); // for storing profiling results |
| 13 | + fModel.AddNeededStdLib("string"); // for operator names |
| 14 | + fModel.AddNeededStdLib("map"); // for the results map |
| 15 | + fModel.AddNeededStdLib("iostream"); // for printing results |
| 16 | + fModel.AddNeededStdLib("iomanip"); // for printing results |
| 17 | +} |
| 18 | + |
| 19 | +// This function generates the helper functions inside the Session struct. |
| 20 | +void RModelProfiler::GenerateUtilityFunctions() |
| 21 | +{ |
| 22 | + auto &gc = fModel.fProfilerGC; |
| 23 | + |
| 24 | + // Generate PrintProfilingResults function |
| 25 | + gc += " void PrintProfilingResults() const {\n"; |
| 26 | + gc += " if (fProfilingResults.empty()) {\n"; |
| 27 | + gc += " std::cout << \"No profiling results to display.\" << std::endl;\n"; |
| 28 | + gc += " return;\n"; |
| 29 | + gc += " }\n"; |
| 30 | + gc += "\n"; |
| 31 | + gc += " std::cout << \"\\n\" << std::string(50, '=') << std::endl;\n"; |
| 32 | + gc += " std::cout << \" AVERAGE PROFILING RESULTS\" << std::endl;\n"; |
| 33 | + gc += " std::cout << std::string(50, '=') << std::endl;\n"; |
| 34 | + gc += " for (const auto& op : fProfilingResults) {\n"; |
| 35 | + gc += " double sum = 0.0;\n"; |
| 36 | + gc += " for (double time : op.second) {\n"; |
| 37 | + gc += " sum += time;\n"; |
| 38 | + gc += " }\n"; |
| 39 | + gc += " double average = sum / op.second.size();\n"; |
| 40 | + gc += " std::cout << \" \" << std::left << std::setw(20) << op.first\n"; |
| 41 | + gc += " << \": \" << std::fixed << std::setprecision(6) << average << \" us\"\n"; |
| 42 | + gc += " << \" (over \" << op.second.size() << \" runs)\" << std::endl;\n"; |
| 43 | + gc += " }\n"; |
| 44 | + gc += " std::cout << std::string(50, '=') << \"\\n\" << std::endl;\n"; |
| 45 | + gc += " }\n"; |
| 46 | + gc += "\n"; |
| 47 | + |
| 48 | + // Generate ResetProfilingResults function |
| 49 | + gc += " void ResetProfilingResults() {\n"; |
| 50 | + gc += " fProfilingResults.clear();\n"; |
| 51 | + gc += " }\n"; |
| 52 | + gc += "\n"; |
| 53 | + |
| 54 | + // Generate GetOpAvgTime function |
| 55 | + gc += " std::map<std::string, double> GetOpAvgTime() const {\n"; |
| 56 | + gc += " if (fProfilingResults.empty()) {\n"; |
| 57 | + gc += " return {};\n"; |
| 58 | + gc += " }\n"; |
| 59 | + gc += "\n"; |
| 60 | + gc += " std::map<std::string, double> avg;\n"; |
| 61 | + gc += " for (const auto& op : fProfilingResults) {\n"; |
| 62 | + gc += " double mean = 0.0;\n"; |
| 63 | + gc += " for (double time : op.second) {\n"; |
| 64 | + gc += " mean += time;\n"; |
| 65 | + gc += " }\n"; |
| 66 | + gc += " mean /= op.second.size();\n"; |
| 67 | + gc += " avg[op.first] = mean;\n"; |
| 68 | + gc += " }\n"; |
| 69 | + gc += "\n"; |
| 70 | + gc += " return avg;\n"; |
| 71 | + gc += " }\n"; |
| 72 | + gc += "\n"; |
| 73 | + |
| 74 | + // Generate GetOpVariance function |
| 75 | + gc += " std::map<std::string, double> GetOpVariance() const {\n"; |
| 76 | + gc += " if (fProfilingResults.empty()) {\n"; |
| 77 | + gc += " return {};\n"; |
| 78 | + gc += " }\n"; |
| 79 | + gc += "\n"; |
| 80 | + gc += " std::map<std::string, double> variance;\n"; |
| 81 | + gc += " for (const auto& op : fProfilingResults) {\n"; |
| 82 | + gc += " // Var[X] = E[X^2] - E[X]^2\n"; |
| 83 | + gc += " double mean = 0.0, mean2 = 0.0;\n"; |
| 84 | + gc += " for (double time : op.second) {\n"; |
| 85 | + gc += " mean += time;\n"; |
| 86 | + gc += " mean2 += time * time;\n"; |
| 87 | + gc += " }\n"; |
| 88 | + gc += " mean /= op.second.size();\n"; |
| 89 | + gc += " mean2 /= op.second.size();\n"; |
| 90 | + gc += " variance[op.first] = mean2 - mean * mean;\n"; |
| 91 | + gc += " }\n"; |
| 92 | + gc += "\n"; |
| 93 | + gc += " return variance;\n"; |
| 94 | + gc += " }\n"; |
| 95 | +} |
| 96 | + |
| 97 | +// Main generation function for the profiler. |
| 98 | +void RModelProfiler::Generate() |
| 99 | +{ |
| 100 | + // Clear the profiler's code string to start fresh. |
| 101 | + fModel.fProfilerGC.clear(); |
| 102 | + auto &gc = fModel.fProfilerGC; |
| 103 | + |
| 104 | + // 1. Add the data member to the Session struct to store results. |
| 105 | + gc += "public:\n"; |
| 106 | + gc += " // Maps an operator name to a vector of its execution times (in microseconds).\n"; |
| 107 | + gc += " std::map<std::string, std::vector<double>> fProfilingResults;\n\n"; |
| 108 | + |
| 109 | + // 2. Generate and add the utility functions like PrintProfilingResults. |
| 110 | + GenerateUtilityFunctions(); |
| 111 | + |
| 112 | + // 3. Generate the signature for the profiled doInfer method. |
| 113 | + std::string doInferSignature = fModel.GenerateInferSignature(); |
| 114 | + if (!doInferSignature.empty()) doInferSignature += ", "; |
| 115 | + for (auto const &name : fModel.GetOutputTensorNames()) { |
| 116 | + doInferSignature += " std::vector<" + ConvertTypeToString(fModel.GetTensorType(name)) + "> &output_tensor_" + name + ","; |
| 117 | + } |
| 118 | + if (!fModel.GetOutputTensorNames().empty()) { |
| 119 | + doInferSignature.back() = ' '; |
| 120 | + } |
| 121 | + gc += "void doInfer(" + doInferSignature + ") {\n"; |
| 122 | + |
| 123 | + // 4. Generate the body of the doInfer method with timing instrumentation. |
| 124 | + gc += " // Timer variable for profiling\n"; |
| 125 | + gc += " std::chrono::steady_clock::time_point tp_start, tp_overall_start;\n\n"; |
| 126 | + gc += " tp_overall_start = std::chrono::steady_clock::now();\n\n"; |
| 127 | + |
| 128 | + for (size_t op_idx = 0; op_idx < fModel.fOperators.size(); ++op_idx) { |
| 129 | + const auto& op = fModel.fOperators[op_idx]; |
| 130 | + gc += " // -- Profiling for operator " + op->name + " --\n"; |
| 131 | + gc += " tp_start = std::chrono::steady_clock::now();\n\n"; |
| 132 | + |
| 133 | + // Add the actual operator inference code |
| 134 | + gc += op->Generate(std::to_string(op_idx)); |
| 135 | + |
| 136 | + // Add the code to stop the timer and store the result |
| 137 | + gc += "\n fProfilingResults[\"" + op->name + "\"].push_back(\n"; |
| 138 | + gc += " std::chrono::duration_cast<std::chrono::duration<double, std::micro>>(\n"; |
| 139 | + gc += " std::chrono::steady_clock::now() - tp_start).count());\n\n"; |
| 140 | + } |
| 141 | + |
| 142 | + // 5. Generate the code to fill the output tensors. |
| 143 | + gc += " using TMVA::Experimental::SOFIE::UTILITY::FillOutput;\n\n"; |
| 144 | + for (std::string const &name : fModel.GetOutputTensorNames()) { |
| 145 | + bool isIntermediate = fModel.fIntermediateTensorInfos.count(name) > 0; |
| 146 | + std::string n = isIntermediate ? std::to_string(ConvertShapeToLength(fModel.GetTensorShape(name))) |
| 147 | + : ConvertDynamicShapeToLength(fModel.GetDynamicTensorShape(name)); |
| 148 | + gc += " FillOutput(tensor_" + name + ", output_tensor_" + name + ", " + n + ");\n"; |
| 149 | + } |
| 150 | + |
| 151 | + gc += "\n // -- Record overall inference time --\n"; |
| 152 | + gc += " fProfilingResults[\"Overall_Time\"].push_back(\n"; |
| 153 | + gc += " std::chrono::duration_cast<std::chrono::duration<double, std::micro>>(\n"; |
| 154 | + gc += " std::chrono::steady_clock::now() - tp_overall_start).count());\n"; |
| 155 | + |
| 156 | + gc += "}\n\n"; // End of doInfer function |
| 157 | +} |
| 158 | + |
| 159 | +} // namespace SOFIE |
| 160 | +} // namespace Experimental |
| 161 | +} // namespace TMVA |
0 commit comments