diff --git a/.github/workflows/integration_tests_transfertuning.yml b/.github/workflows/integration_tests_transfertuning.yml index e1574a2d8..9bf91e8d2 100644 --- a/.github/workflows/integration_tests_transfertuning.yml +++ b/.github/workflows/integration_tests_transfertuning.yml @@ -75,3 +75,6 @@ jobs: docc -g -O3 -docc-tune=sequential -docc-transfer-tune -docc-save-temps matmul.c -o matmul.out ./matmul.out cd ../../../../ + + cd llvm/integration/ + pytest -v -rx polybench_transfertuning_test.py diff --git a/CMakeLists.txt b/CMakeLists.txt index fe6fa55f0..1a7114900 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -71,7 +71,6 @@ if(NOT DEFINED PARENT_PROJECT_INSTALL_PREFIX) endif() add_subdirectory(sdfg) -add_subdirectory(examples/applications) add_subdirectory(opt) add_subdirectory(rpc) add_subdirectory(rtl) diff --git a/examples/applications/CMakeLists.txt b/examples/applications/CMakeLists.txt deleted file mode 100644 index fc34c1870..000000000 --- a/examples/applications/CMakeLists.txt +++ /dev/null @@ -1,13 +0,0 @@ -# Boost -set(Boost_USE_STATIC_LIBS OFF) -set(Boost_USE_MULTITHREADED ON) -set(Boost_USE_STATIC_RUNTIME OFF) -find_package(Boost REQUIRED COMPONENTS program_options) - -# tutorials - -add_executable(rpc_opt - rpc_opt.cpp -) -target_link_libraries(rpc_opt PRIVATE sdfg::sdfg sdfg::rpc Boost::program_options) -target_include_directories(rpc_opt PRIVATE $) diff --git a/examples/applications/rpc_opt.cpp b/examples/applications/rpc_opt.cpp deleted file mode 100644 index 4553f1efe..000000000 --- a/examples/applications/rpc_opt.cpp +++ /dev/null @@ -1,209 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include "sdfg/codegen/code_generators/cpp_code_generator.h" -#include "sdfg/passes/rpc/rpc_context.h" -#include "sdfg/passes/rpc/rpc_scheduler.h" -#include "sdfg/passes/scheduler/loop_scheduling_pass.h" -#include "sdfg/passes/scheduler/scheduler_registry.h" -#include "sdfg/serializer/json_serializer.h" -#include "sdfg/transformations/rpc_node_transform.h" - -using json = nlohmann::json; -namespace po = boost::program_options; -using namespace sdfg; - -std::unique_ptr build_demo_sdfg() { - auto builder = std::make_unique("sdfg_test", FunctionType_CPU); - - auto& root = builder->subject().root(); - types::Scalar base_desc(types::PrimitiveType::Float); - types::Array desc_1(base_desc, symbolic::integer(64)); - types::Pointer desc_2(desc_1); - - builder->add_container("A", desc_2, true); - builder->add_container("B", desc_2, true); - builder->add_container("C", desc_2, true); - - types::Scalar sym_desc(types::PrimitiveType::UInt64); - builder->add_container("K", sym_desc, true); - builder->add_container("N", sym_desc, true); - builder->add_container("M", sym_desc, true); - builder->add_container("i", sym_desc); - builder->add_container("j", sym_desc); - builder->add_container("k", sym_desc); - - // Define loop 1 - auto bound = symbolic::integer(64); - auto indvar = symbolic::symbol("i"); - - auto& loop = builder->add_map( - root, - indvar, - symbolic::Lt(symbolic::symbol("i"), bound), - symbolic::integer(0), - symbolic::add(symbolic::symbol("i"), symbolic::integer(1)), - structured_control_flow::ScheduleType_Sequential::create() - ); - auto& body = loop.root(); - - // Define loop 2 - auto bound_2 = symbolic::integer(64); - auto indvar_2 = symbolic::symbol("j"); - - auto& loop_2 = builder->add_for( - body, - indvar_2, - symbolic::Lt(symbolic::symbol("j"), bound_2), - symbolic::integer(0), - symbolic::add(symbolic::symbol("j"), symbolic::integer(1)) - ); - - auto& body_2 = loop_2.root(); - - // Define loop 3 - auto bound_3 = symbolic::integer(64); - auto indvar_3 = symbolic::symbol("k"); - - auto& loop_3 = builder->add_map( - body_2, - indvar_3, - symbolic::Lt(symbolic::symbol("k"), bound_3), - symbolic::integer(0), - symbolic::add(symbolic::symbol("k"), symbolic::integer(1)), - structured_control_flow::ScheduleType_Sequential::create() - ); - - auto& body_3 = loop_3.root(); - - // Add computation - auto& block = builder->add_block(body_3); - auto& a_in = builder->add_access(block, "A"); - auto& b_in = builder->add_access(block, "B"); - auto& c_in = builder->add_access(block, "C"); - auto& c_out = builder->add_access(block, "C"); - - { - auto& tasklet = builder->add_tasklet(block, data_flow::TaskletCode::fp_fma, "_out", {"_in1", "_in2", "_in3"}); - builder->add_computational_memlet(block, a_in, tasklet, "_in1", {symbolic::symbol("i"), symbolic::symbol("j")}); - builder->add_computational_memlet(block, b_in, tasklet, "_in2", {symbolic::symbol("j"), symbolic::symbol("k")}); - builder->add_computational_memlet(block, c_in, tasklet, "_in3", {symbolic::symbol("i"), symbolic::symbol("k")}); - builder->add_computational_memlet(block, tasklet, "_out", c_out, {symbolic::symbol("i"), symbolic::symbol("k")}); - } - - return builder->move(); -} - -int main(int argc, char* argv[]) { - std::filesystem::path sdfg_path; - std::filesystem::path output_prefix; - std::string category; - std::string target; - - po::options_description desc("Allowed options"); - desc.add_options() - ("help", "produce help message") - ("input,i", po::value(&sdfg_path)->default_value(""), "path to sdfg json file") - ("output,o", po::value(&output_prefix)->default_value("./"), "prefix of generated files") - ("category,c", po::value(&category)->default_value("server"), "category to tune for") - ("target", po::value(&target)->default_value("sequential"), "target device"); - - po::variables_map vm; - po::store(po::parse_command_line(argc, argv, desc), vm); - po::notify(vm); - - // Register default dispatchers - codegen::register_default_dispatchers(); - sdfg::serializer::register_default_serializers(); - - // Create sdfg - std::unique_ptr sdfg; - if (sdfg_path.empty()) { - sdfg = build_demo_sdfg(); - } else { - serializer::JSONSerializer serializer; - nlohmann::json j; - std::ifstream sdfg_file(sdfg_path); - if (sdfg_file.is_open()) { - sdfg_file >> j; - sdfg_file.close(); - } - sdfg = serializer.deserialize(j); - } - - - auto sdfg_initial = sdfg->clone(); - auto builder = std::make_unique(sdfg); - - // Generate code for initial sdfg - - auto localPrefix = output_prefix.filename().string(); - auto parent = output_prefix.parent_path(); - auto init_source_path = parent / (localPrefix + sdfg_initial->name() + "_init.cpp"); - auto init_header_path = parent / (localPrefix + sdfg_initial->name() + "_init.h"); - - auto instrumentation_plan = codegen::InstrumentationPlan::none(*sdfg_initial); - auto arg_capture_plan = sdfg::codegen::ArgCapturePlan::none(*sdfg_initial); - analysis::AnalysisManager analysis_manager_initial(*sdfg_initial); - codegen::CPPCodeGenerator - code_generator(*sdfg_initial, analysis_manager_initial, *instrumentation_plan, *arg_capture_plan); - bool success = code_generator.generate(); - success &= code_generator.as_source(init_header_path, init_source_path); - if (!success) { - std::cerr << "Code generation for initial sdfg failed" << std::endl; - return 1; - } - - // RPC node transform - - sdfg::analysis::AnalysisManager analysis_manager(builder->subject()); - auto& loop_analysis = analysis_manager.get(); - auto outer_loops = loop_analysis.outermost_loops(); - - passes::rpc::SimpleRpcContextBuilder b; - b.initialize_local_default(); - b.from_env(); - b.from_docc_config(); - b.server = "http://localhost:8080/docc"; - auto ctx = b.build(); - - passes::rpc::register_rpc_loop_opt(std::move(ctx), target, category, true); - - passes::scheduler::LoopSchedulingPass - loop_scheduling_pass({passes::scheduler::SchedulerRegistry::instance().get_loop_scheduler("rpc")}, nullptr); - loop_scheduling_pass.run(*builder, analysis_manager); - - // generate code for tuned sdfg - - auto sdfg_final = builder->move(); - - auto source_path = parent / (localPrefix + sdfg_final->name() + "_opt.cpp"); - auto header_path = parent / (localPrefix + sdfg_final->name() + "_opt.h"); - - auto instrumentation_plan_opt = codegen::InstrumentationPlan::none(*sdfg_final); - auto arg_capture_plan_opt = sdfg::codegen::ArgCapturePlan::none(*sdfg_final); - analysis::AnalysisManager analysis_manager_opt(*sdfg_final); - codegen::CPPCodeGenerator - code_generator_opt(*sdfg_final, analysis_manager_opt, *instrumentation_plan_opt, *arg_capture_plan_opt); - - success = code_generator_opt.generate(); - success &= code_generator.as_source(header_path, source_path); - if (!success) { - std::cerr << "Code generation failed" << std::endl; - return 1; - } - - return 0; -}; diff --git a/examples/transfer-server/src/ts/transfertuning/index.ts b/examples/transfer-server/src/ts/transfertuning/index.ts index a22d2daff..e1c182ccf 100644 --- a/examples/transfer-server/src/ts/transfertuning/index.ts +++ b/examples/transfer-server/src/ts/transfertuning/index.ts @@ -5,7 +5,7 @@ import path from 'path'; const router = new Hono() -router.post('/transfertune', async (c) => { +router.post('/transfertune_sdfg', async (c) => { // Optional testing mode: if paths are provided via headers, load JSONs from disk. const filePath = path.resolve(__dirname, '../../../res/matmul'); const hint = c.req.header('RPC-Hint') ?? filePath diff --git a/llvm/integration/polybench_transfertuning_test.py b/llvm/integration/polybench_transfertuning_test.py index 15a11b656..073dc323b 100644 --- a/llvm/integration/polybench_transfertuning_test.py +++ b/llvm/integration/polybench_transfertuning_test.py @@ -109,10 +109,6 @@ def test_correlation(datatype, compiler="clang-19", size="MEDIUM_DATASET"): transformation_verification = TransformationVerification( { - # "RPCNodeTransform": { - # "loop_nests": {}, - # "tuned_loops": 9, - # } } ) @@ -166,7 +162,7 @@ def test_covariance(datatype, compiler="clang-19", size="MEDIUM_DATASET"): ) transformation_verification = TransformationVerification( - {"RPCNodeTransform": {"loop_nests": {}, "tuned_loops": 6}} + {"RPCNodeTransform": {"loop_nests": {}}} ) test_case = benchmark_path / "covariance.c" @@ -224,7 +220,7 @@ def test_gemm(datatype, compiler="clang-19", size="MEDIUM_DATASET"): ) transformation_verification = TransformationVerification( - {"RPCNodeTransform": {"loop_nests": {}, "tuned_loops": 4}} + {"RPCNodeTransform": {"loop_nests": {}}} ) test_case = benchmark_path / "gemm.c" @@ -277,10 +273,6 @@ def test_gemver(datatype, compiler="clang-19", size="MEDIUM_DATASET"): transformation_verification = TransformationVerification( { - # "RPCNodeTransform": { - # "loop_nests": {}, - # "tuned_loops": 2, - # } } ) @@ -340,7 +332,7 @@ def test_gesummv(datatype, compiler="clang-19", size="MEDIUM_DATASET"): ) transformation_verification = TransformationVerification( - {"RPCNodeTransform": {"loop_nests": {}, "tuned_loops": 3}} + {"RPCNodeTransform": {"loop_nests": {}}} ) test_case = benchmark_path / "gesummv.c" @@ -398,7 +390,7 @@ def test_symm(datatype, compiler="clang-19", size="MEDIUM_DATASET"): ) transformation_verification = TransformationVerification( - {"RPCNodeTransform": {"loop_nests": {}, "tuned_loops": 3}} + {"RPCNodeTransform": {"loop_nests": {}}} ) test_case = benchmark_path / "symm.c" @@ -457,10 +449,6 @@ def test_syr2k(datatype, compiler="clang-19", size="MEDIUM_DATASET"): transformation_verification = TransformationVerification( { - # "RPCNodeTransform": { - # "loop_nests": {}, - # "tuned_loops": 4, - # } } ) @@ -519,7 +507,7 @@ def test_syrk(datatype, compiler="clang-19", size="MEDIUM_DATASET"): ) transformation_verification = TransformationVerification( - {"RPCNodeTransform": {"loop_nests": {}, "tuned_loops": 4}} + {"RPCNodeTransform": {"loop_nests": {}}} ) test_case = benchmark_path / "syrk.c" @@ -633,7 +621,7 @@ def test_2mm(datatype, compiler="clang-19", size="MEDIUM_DATASET"): ) transformation_verification = TransformationVerification( - {"RPCNodeTransform": {"loop_nests": {}, "tuned_loops": 6}} + {"RPCNodeTransform": {"loop_nests": {}}} ) test_case = benchmark_path / "2mm.c" @@ -686,7 +674,7 @@ def test_3mm(datatype, compiler="clang-19", size="MEDIUM_DATASET"): ) transformation_verification = TransformationVerification( - {"RPCNodeTransform": {"loop_nests": {}, "tuned_loops": 7}} + {"RPCNodeTransform": {"loop_nests": {}}} ) test_case = benchmark_path / "3mm.c" @@ -738,7 +726,7 @@ def test_atax(datatype, compiler="clang-19", size="MEDIUM_DATASET"): ) transformation_verification = TransformationVerification( - {"RPCNodeTransform": {"loop_nests": {}, "tuned_loops": 5}} + {"RPCNodeTransform": {"loop_nests": {}}} ) test_case = benchmark_path / "atax.c" @@ -796,7 +784,7 @@ def test_bicg(datatype, compiler="clang-19", size="MEDIUM_DATASET"): ) transformation_verification = TransformationVerification( - {"RPCNodeTransform": {"loop_nests": {}, "tuned_loops": 3}} + {"RPCNodeTransform": {"loop_nests": {}}} ) test_case = benchmark_path / "bicg.c" @@ -910,7 +898,7 @@ def test_mvt(datatype, compiler="clang-19", size="MEDIUM_DATASET"): ) transformation_verification = TransformationVerification( - {"RPCNodeTransform": {"loop_nests": {}, "tuned_loops": 2}} + {"RPCNodeTransform": {"loop_nests": {}}} ) test_case = benchmark_path / "mvt.c" @@ -968,7 +956,7 @@ def test_cholesky(datatype, compiler="clang-19", size="MEDIUM_DATASET"): ) transformation_verification = TransformationVerification( - {"RPCNodeTransform": {"loop_nests": {}, "tuned_loops": 7}} + {"RPCNodeTransform": {"loop_nests": {}}} ) test_case = benchmark_path / "cholesky.c" @@ -1138,7 +1126,7 @@ def test_lu(datatype, compiler="clang-19", size="MEDIUM_DATASET"): ) transformation_verification = TransformationVerification( - {"RPCNodeTransform": {"loop_nests": {}, "tuned_loops": 6}} + {"RPCNodeTransform": {"loop_nests": {}}} ) test_case = benchmark_path / "lu.c" @@ -1196,7 +1184,7 @@ def test_trisolv(datatype, compiler="clang-19", size="MEDIUM_DATASET"): ) transformation_verification = TransformationVerification( - {"RPCNodeTransform": {"loop_nests": {}, "tuned_loops": 2}} + {"RPCNodeTransform": {"loop_nests": {}}} ) test_case = benchmark_path / "trisolv.c" @@ -1249,7 +1237,7 @@ def test_deriche(datatype, compiler="clang-19", size="MEDIUM_DATASET"): ) transformation_verification = TransformationVerification( - {"RPCNodeTransform": {"loop_nests": {}, "tuned_loops": 3}} + {"RPCNodeTransform": {"loop_nests": {}}} ) test_case = benchmark_path / "deriche.c" @@ -1294,7 +1282,7 @@ def test_floyd_warshall(compiler="clang-19", size="MEDIUM_DATASET"): Path(__file__).parent / "tests" / "polybench" / "medley" / "floyd-warshall" ) - transformation_verification = TransformationVerification({"RPCNodeTransform": {1}}) + transformation_verification = TransformationVerification({"RPCNodeTransform": {}}) test_case = benchmark_path / "floyd-warshall.c" runner = TestRunner( @@ -1636,7 +1624,7 @@ def test_seidel_2d(datatype, compiler="clang-19", size="MEDIUM_DATASET"): ) transformation_verification = TransformationVerification( - {"RPCNodeTransform": {"loop_nests": {1}, "tuned_loops": 2}} + {"RPCNodeTransform": {"loop_nests": {}}} ) test_case = benchmark_path / "seidel-2d.c" runner = TestRunner( diff --git a/llvm/integration/test_runner.py b/llvm/integration/test_runner.py index 489b2c5c2..f6eab56a4 100644 --- a/llvm/integration/test_runner.py +++ b/llvm/integration/test_runner.py @@ -62,10 +62,9 @@ def verify(self, output_dir, stderr: str = None) -> None: loopnest_to_region = {region.get("loopnest_index"): region for region in regions} for transformation, params in self._transformations.items(): - # Check for new dict format with 'loop_nests' and 'max_tuned_loops' - if isinstance(params, dict) and 'loop_nests' in params and 'tuned_loops' in params: + # Check for new dict format with 'loop_nests' and optional 'tuned_loops' + if isinstance(params, dict) and 'loop_nests' in params: loop_nests = params['loop_nests'] - tuned_loops = params['tuned_loops'] # Check that transformation is present in all specified loop_nests for idx in loop_nests: region = loopnest_to_region.get(idx) @@ -74,20 +73,23 @@ def verify(self, output_dir, stderr: str = None) -> None: assert transformation in transformations, ( f"Transformation {transformation} not found in region with loopnest_index {idx}." ) - # Count total number of times transformation was applied - applied_count = 0 - for region in regions: - transformations = region.get("transformations", {}) - if transformation in transformations: - # Check if 'applied' is True (if present) - t = transformations[transformation] - if isinstance(t, dict) and t.get('applied', True): - applied_count += 1 - elif t is True: - applied_count += 1 - assert applied_count == tuned_loops, ( - f"Transformation {transformation} applied {applied_count} times but expected {tuned_loops} times" - ) + # Only verify the applied loop count when a 'tuned_loops' count is given. + if 'tuned_loops' in params: + tuned_loops = params['tuned_loops'] + # Count total number of times transformation was applied + applied_count = 0 + for region in regions: + transformations = region.get("transformations", {}) + if transformation in transformations: + # Check if 'applied' is True (if present) + t = transformations[transformation] + if isinstance(t, dict) and t.get('applied', True): + applied_count += 1 + elif t is True: + applied_count += 1 + assert applied_count == tuned_loops, ( + f"Transformation {transformation} applied {applied_count} times but expected {tuned_loops} times" + ) else: # Fallback to old behavior: params is a set of indices for idx in params: diff --git a/llvm/src/passes/scheduling/scheduling_pass.cpp b/llvm/src/passes/scheduling/scheduling_pass.cpp index e44565153..c447e7229 100644 --- a/llvm/src/passes/scheduling/scheduling_pass.cpp +++ b/llvm/src/passes/scheduling/scheduling_pass.cpp @@ -2,6 +2,8 @@ #include "docc/analysis/sdfg_registry.h" #include "docc/cmd_args.h" +#include "sdfg/passes/rpc/daisytuner_rpc_context.h" +#include "sdfg/passes/rpc/rpc_scheduling_pass.h" #include "sdfg/passes/scheduler/loop_scheduling_pass.h" namespace docc { @@ -30,9 +32,6 @@ llvm::PreservedAnalyses SchedulingPass:: auto offload_unknown_sizes = docc::DOCC_OFFLOAD_UNKNOWN_SIZES.getValue(); std::vector schedulers; - if (target != "tenstorrent" && remote_tuning) { - schedulers.push_back(scheduler_registry_.get_loop_scheduler("rpc")); - } if (target != "sequential") { schedulers.push_back(scheduler_registry_.get_loop_scheduler(target)); } @@ -42,6 +41,15 @@ llvm::PreservedAnalyses SchedulingPass:: sdfg::analysis::AnalysisManager analysis_manager(builder.subject()); if (report_) report_->in_scope(&builder.subject()); + if (target != "tenstorrent" && remote_tuning) { + auto category = docc::DOCC_TRANSFERTUNE_CATEGORY.getValue(); + std::shared_ptr context = + sdfg::passes::rpc::DaisytunerRpcContext::from_docc_config(); + docc::target::TargetOptions rpc_options{.target = target, .category = category, .remote_tuning = true}; + sdfg::passes::scheduler::RpcOptimizationPass rpc_optimization_pass(context, rpc_options, false); + rpc_optimization_pass.run_pass(builder, analysis_manager); + } + sdfg::passes::scheduler::LoopSchedulingPass loop_scheduling_pass(schedulers, report_, offload_unknown_sizes); loop_scheduling_pass.run(builder, analysis_manager); }); diff --git a/llvm/src/plugin.cpp b/llvm/src/plugin.cpp index 7c0be719f..94e70b00f 100644 --- a/llvm/src/plugin.cpp +++ b/llvm/src/plugin.cpp @@ -30,7 +30,6 @@ #include #include #include -#include #include #include @@ -49,7 +48,6 @@ #include "docc/passes/dumps/pass_report_collector.h" #include "docc/passes/function_to_sdfg_pass.h" #include "docc/passes/inlining/argument_expansion_pass.h" -#include "docc/passes/scheduling/docc_backend_context.h" #include "docc/passes/scheduling/einsum_pass.h" #include "docc/passes/scheduling/normalization_pass.h" #include "docc/passes/scheduling/scheduling_pass.h" @@ -79,20 +77,8 @@ extern "C" LLVM_ATTRIBUTE_WEAK ::llvm::PassPluginLibraryInfo llvmGetPassPluginIn [](llvm::PassBuilder &PB) { docc::register_sdfg_dispatchers(); - auto target = docc::DOCC_TUNE.getValue(); - auto category = docc::DOCC_TRANSFERTUNE_CATEGORY.getValue(); - - auto remote_tuning = docc::DOCC_TRANSFERTUNE.getValue(); - auto &sched_registry = sdfg::passes::scheduler::SchedulerRegistry::instance(); - if (remote_tuning) { - std::shared_ptr context = - sdfg::passes::rpc::DaisytunerRpcContext::from_docc_config(); - sdfg::passes::rpc::register_rpc_loop_opt(context, target, category); - // TODO don't use global state here - } - // Compile-Time Pass Registration PB.registerPipelineStartEPCallback([&sched_registry]( llvm::ModulePassManager &MPM, llvm::OptimizationLevel Level diff --git a/python/bindings/bindings.cpp b/python/bindings/bindings.cpp index 157ee6598..cae752274 100644 --- a/python/bindings/bindings.cpp +++ b/python/bindings/bindings.cpp @@ -48,7 +48,6 @@ #include #include "docc/target/docc_target.h" -#include "sdfg/passes/rpc/rpc_scheduler.h" #include "sdfg/passes/scheduler/cuda_scheduler.h" #ifdef DOCC_HAS_TARGET_ET diff --git a/python/bindings/py_structured_sdfg.cpp b/python/bindings/py_structured_sdfg.cpp index 1e63a6ab3..12b1f7614 100644 --- a/python/bindings/py_structured_sdfg.cpp +++ b/python/bindings/py_structured_sdfg.cpp @@ -32,6 +32,7 @@ #include #include #include +#include #include #include #include @@ -67,7 +68,6 @@ #include "sdfg/passes/redundant_load_elimination_pass.h" #include "sdfg/passes/rpc/daisytuner_rpc_context.h" #include "sdfg/passes/rpc/rpc_context.h" -#include "sdfg/passes/rpc/rpc_scheduler.h" #include "sdfg/passes/scheduler/vectorize_scheduler.h" #include "sdfg/passes/schedules/expansion_pass.h" #include "sdfg/passes/targets/target_mapping_pass.h" @@ -434,15 +434,28 @@ void PyStructuredSDFG::schedule(const docc::target::TargetOptions& options) { docc::plugins::apply_lib_node_target_mapping(docc_context_, builder, analysis_manager, options); - std::vector> schedulers; + // CPU Opt Pipeline + if (options.target == "sequential" || options.target == "openmp") { + sdfg::passes::Pipeline dce = sdfg::passes::Pipeline::dead_code_elimination(); + sdfg::passes::DeadDataElimination dde; + sdfg::passes::SymbolPropagation symbol_propagation_pass; + symbol_propagation_pass.run(builder, analysis_manager); + dde.run(builder, analysis_manager); + dce.run(builder, analysis_manager); + } if (options.remote_tuning) { std::shared_ptr context = sdfg::passes::rpc::DaisytunerRpcContext::from_docc_config(); - schedulers.push_back(std::make_shared(context, options.target, options.category) - ); + sdfg::passes::scheduler::RpcOptimizationPass + rpc_optimization_pass(context, options, enable_fusion_in_normalize_); + rpc_optimization_pass.run(builder, analysis_manager); } + // Acquire target-specific loop schedulers only after remote tuning, since they are consumed + // solely by the LoopSchedulingPass below. + std::vector> schedulers; + auto* handler = docc_context_.get_target_handler(options.target); if (handler) { auto target_schedulers = handler->safe_get_target_loop_schedulers(options); @@ -451,16 +464,6 @@ void PyStructuredSDFG::schedule(const docc::target::TargetOptions& options) { } } - // CPU Opt Pipeline - if (options.target == "sequential" || options.target == "openmp") { - sdfg::passes::Pipeline dce = sdfg::passes::Pipeline::dead_code_elimination(); - sdfg::passes::DeadDataElimination dde; - sdfg::passes::SymbolPropagation symbol_propagation_pass; - symbol_propagation_pass.run(builder, analysis_manager); - dde.run(builder, analysis_manager); - dce.run(builder, analysis_manager); - } - auto mapped = schedulers | std::views::transform([&](auto& n) { return n.get(); }); std::vector unwrapped_schedulers(mapped.begin(), mapped.end()); diff --git a/rpc/CMakeLists.txt b/rpc/CMakeLists.txt index b16fd6097..ef1d51c97 100644 --- a/rpc/CMakeLists.txt +++ b/rpc/CMakeLists.txt @@ -8,7 +8,7 @@ find_package(CURL REQUIRED) set(SOURCE_FILES src/passes/rpc/rpc_context.cpp src/passes/rpc/daisytuner_rpc_context.cpp - src/passes/rpc/rpc_scheduler.cpp + src/passes/rpc/rpc_scheduling_pass.cpp src/transformations/rpc_node_transform.cpp src/util/utils_curl.cpp ) diff --git a/rpc/include/sdfg/passes/rpc/daisytuner_rpc_context.h b/rpc/include/sdfg/passes/rpc/daisytuner_rpc_context.h index 57b1eca04..139430c87 100644 --- a/rpc/include/sdfg/passes/rpc/daisytuner_rpc_context.h +++ b/rpc/include/sdfg/passes/rpc/daisytuner_rpc_context.h @@ -11,7 +11,7 @@ namespace sdfg::passes::rpc { class DaisytunerRpcContext : public SimpleRpcContext { public: - inline static constexpr auto DEFAULT_ENDPOINT = "transfertune"; + inline static constexpr auto DEFAULT_ENDPOINT = "transfertune_sdfg"; inline static constexpr auto DEFAULT_AUTH_HEADER = "Authorization"; inline static constexpr auto DEFAULT_SERVER = "https://docc-backend-1080482399950.europe-west1.run.app/docc"; diff --git a/rpc/include/sdfg/passes/rpc/rpc_responses.h b/rpc/include/sdfg/passes/rpc/rpc_responses.h index a6c646a73..b772cecde 100644 --- a/rpc/include/sdfg/passes/rpc/rpc_responses.h +++ b/rpc/include/sdfg/passes/rpc/rpc_responses.h @@ -24,10 +24,20 @@ struct RpcSdfgResult { std::unique_ptr sdfg = nullptr; }; +/** + * Result for a single tuned region. The multi-cutout endpoint returns one of these per element, + * each carrying its own optimization metadata and (optional) local replay recipe. + */ +struct RpcRegionResult { + std::optional element_id; + std::optional local_replay; + RpcOptimizationMetadata metadata; +}; + struct RpcOptResponse { std::optional sdfg_result; std::optional local_replay; - RpcOptimizationMetadata metadata; + std::vector results; std::optional error; }; @@ -36,6 +46,7 @@ struct RpcOptRequest { std::string category; std::string target; analysis::LoopInfo loop_info; + bool enable_fusion = true; }; } // namespace sdfg::passes::rpc diff --git a/rpc/include/sdfg/passes/rpc/rpc_scheduler.h b/rpc/include/sdfg/passes/rpc/rpc_scheduler.h deleted file mode 100644 index 81cbfda1b..000000000 --- a/rpc/include/sdfg/passes/rpc/rpc_scheduler.h +++ /dev/null @@ -1,67 +0,0 @@ -#pragma once - -#include -#include -#include "sdfg/passes/rpc/rpc_context.h" -#include "sdfg/passes/scheduler/loop_scheduler.h" -#include "sdfg/structured_control_flow/map.h" - -namespace sdfg { -namespace passes { -namespace rpc { - -class RPCScheduler : public scheduler::LoopScheduler { -private: - std::shared_ptr rpc_context_; - const std::string target_; - const std::string category_; - const bool print_steps_; - -public: - scheduler::SchedulerAction find( - builder::StructuredSDFGBuilder& builder, - analysis::AnalysisManager& analysis_manager, - structured_control_flow::StructuredLoop& loop, - bool offload_unknown_sizes = false - ) override; - - scheduler::SchedulerAction find( - builder::StructuredSDFGBuilder& builder, - analysis::AnalysisManager& analysis_manager, - structured_control_flow::While& loop, - bool offload_unknown_sizes = false - ) override; - - bool can_apply_schedule( - builder::StructuredSDFGBuilder& builder, - analysis::AnalysisManager& analysis_manager, - structured_control_flow::StructuredLoop& loop, - bool offload_unknown_sizes = false - ) override; - - void apply_schedule( - builder::StructuredSDFGBuilder& builder, - analysis::AnalysisManager& analysis_manager, - structured_control_flow::StructuredLoop& loop, - bool offload_unknown_sizes = false - ) override; - - RPCScheduler( - std::shared_ptr rpc_context, std::string target, std::string category, bool print_steps = false - ); - - static std::string target() { return "rpc"; } - - std::unordered_set compatible_types() override; -}; - -void register_rpc_loop_opt( - std::shared_ptr rpc_context, - const std::string& target, - const std::string& category, - bool print_steps = false -); - -} // namespace rpc -} // namespace passes -} // namespace sdfg diff --git a/rpc/include/sdfg/passes/rpc/rpc_scheduling_pass.h b/rpc/include/sdfg/passes/rpc/rpc_scheduling_pass.h new file mode 100644 index 000000000..a8f4b5165 --- /dev/null +++ b/rpc/include/sdfg/passes/rpc/rpc_scheduling_pass.h @@ -0,0 +1,41 @@ +#pragma once + +#include +#include +#include +#include "sdfg/optimization_report/pass_report_consumer.h" +#include "sdfg/passes/rpc/rpc_context.h" + +namespace sdfg { +namespace transformations { +class Recorder; +} +namespace passes { +namespace scheduler { + +class RpcOptimizationPass : public Pass { +private: + std::shared_ptr rpc_context_; + docc::target::TargetOptions options_; + sdfg::PassReportConsumer* report_ = nullptr; + bool enable_fusion_ = true; + +public: + RpcOptimizationPass( + std::shared_ptr rpc_context, + docc::target::TargetOptions options, + bool enable_fusion = true, + sdfg::PassReportConsumer* report = nullptr + ) + : rpc_context_(rpc_context), options_(std::move(options)), report_(report), enable_fusion_(enable_fusion) {} + ~RpcOptimizationPass() override = default; + + bool run_pass(builder::StructuredSDFGBuilder& builder, analysis::AnalysisManager& analysis_manager) override; + + std::string name() override { return "RpcOptimizationPass"; } +}; + + +} // namespace scheduler +} // namespace passes +} // namespace sdfg diff --git a/rpc/include/sdfg/transformations/rpc_node_transform.h b/rpc/include/sdfg/transformations/rpc_node_transform.h index 4f2fc8696..f193c5065 100644 --- a/rpc/include/sdfg/transformations/rpc_node_transform.h +++ b/rpc/include/sdfg/transformations/rpc_node_transform.h @@ -30,6 +30,8 @@ class RPCNodeTransform : public sdfg::transformations::Transformation { bool dump_steps_; + bool enable_fusion_; + std::string get_node_id_str() const; std::variant, std::string> @@ -43,6 +45,7 @@ class RPCNodeTransform : public sdfg::transformations::Transformation { const std::string& target, const std::string& category, sdfg::passes::rpc::RpcContext& rpc_context, + bool enable_fusion = true, bool print_steps = false ); diff --git a/rpc/src/passes/rpc/rpc_context.cpp b/rpc/src/passes/rpc/rpc_context.cpp index 55268de45..c92fcae32 100644 --- a/rpc/src/passes/rpc/rpc_context.cpp +++ b/rpc/src/passes/rpc/rpc_context.cpp @@ -23,7 +23,7 @@ std::shared_ptr SimpleRpcContextBuilder::build(bool print) con SimpleRpcContextBuilder& SimpleRpcContextBuilder::initialize_local_default() { this->server = "http://localhost:8080/docc"; - this->endpoint = "transfertune"; + this->endpoint = "transfertune_sdfg"; return *this; } diff --git a/rpc/src/passes/rpc/rpc_scheduler.cpp b/rpc/src/passes/rpc/rpc_scheduler.cpp deleted file mode 100644 index cd0e69b70..000000000 --- a/rpc/src/passes/rpc/rpc_scheduler.cpp +++ /dev/null @@ -1,95 +0,0 @@ -#include "sdfg/passes/rpc/rpc_scheduler.h" -#include - -#include "sdfg/passes/scheduler/scheduler_registry.h" -#include "sdfg/transformations/rpc_node_transform.h" - -namespace sdfg { -namespace passes { -namespace rpc { - -RPCScheduler::RPCScheduler( - std::shared_ptr rpc_context, std::string target, std::string category, bool print_steps -) - : LoopScheduler(), rpc_context_(std::move(rpc_context)), target_(std::move(target)), category_(std::move(category)), - print_steps_(print_steps) {} - -scheduler::SchedulerAction RPCScheduler::find( - builder::StructuredSDFGBuilder& builder, - analysis::AnalysisManager& analysis_manager, - structured_control_flow::StructuredLoop& loop, - bool offload_unknown_sizes -) { - auto& loop_analysis = analysis_manager.get(); - auto loop_info = loop_analysis.loop_info(&loop); - if (loop_info.loopnest_index == -1 || loop_info.has_side_effects) { - return scheduler::NEXT; - } - - transformations::RPCNodeTransform rpc_transform(loop, target_, category_, *rpc_context_); - rpc_transform.set_report(report_); - if (rpc_transform.can_be_applied(builder, analysis_manager)) { - return scheduler::NEXT; - } - - if (loop_info.num_maps <= 1) { - return scheduler::NEXT; - } else { - return scheduler::CHILDREN; - } -} - -scheduler::SchedulerAction RPCScheduler::find( - builder::StructuredSDFGBuilder& builder, - analysis::AnalysisManager& analysis_manager, - structured_control_flow::While& loop, - bool offload_unknown_sizes -) { - auto& loop_analysis = analysis_manager.get(); - auto loop_info = loop_analysis.loop_info(&loop); - if (loop_info.loopnest_index == -1 || loop_info.has_side_effects) { - return scheduler::NEXT; - } else { - return scheduler::CHILDREN; - } -} - -bool RPCScheduler::can_apply_schedule( - builder::StructuredSDFGBuilder& builder, - analysis::AnalysisManager& analysis_manager, - structured_control_flow::StructuredLoop& loop, - bool offload_unknown_sizes -) { - transformations::RPCNodeTransform rpc_transform(loop, target_, category_, *rpc_context_); - rpc_transform.set_report(report_); - return rpc_transform.can_be_applied(builder, analysis_manager); -} - -void RPCScheduler::apply_schedule( - builder::StructuredSDFGBuilder& builder, - analysis::AnalysisManager& analysis_manager, - structured_control_flow::StructuredLoop& loop, - bool offload_unknown_sizes -) { - transformations::RPCNodeTransform rpc_transform(loop, target_, category_, *rpc_context_, print_steps_); - rpc_transform.set_report(report_); - if (rpc_transform.can_be_applied(builder, analysis_manager)) { - rpc_transform.apply(builder, analysis_manager); - } -} - -std::unordered_set RPCScheduler::compatible_types() { return {ScheduleTypeCategory::None}; } - -void register_rpc_loop_opt( - std::shared_ptr rpc_context, - const std::string& target, - const std::string& category, - bool print_steps -) { - scheduler::SchedulerRegistry::instance() - .register_loop_scheduler(RPCScheduler::target(), rpc_context, target, category, print_steps); -} - -} // namespace rpc -} // namespace passes -} // namespace sdfg diff --git a/rpc/src/passes/rpc/rpc_scheduling_pass.cpp b/rpc/src/passes/rpc/rpc_scheduling_pass.cpp new file mode 100644 index 000000000..997949123 --- /dev/null +++ b/rpc/src/passes/rpc/rpc_scheduling_pass.cpp @@ -0,0 +1,27 @@ +#include "sdfg/passes/rpc/rpc_scheduling_pass.h" +#include "sdfg/analysis/analysis.h" +#include "sdfg/builder/structured_sdfg_builder.h" +#include "sdfg/transformations/rpc_node_transform.h" + + +namespace sdfg { +namespace passes { +namespace scheduler { + +bool RpcOptimizationPass::run_pass(builder::StructuredSDFGBuilder& builder, analysis::AnalysisManager& analysis_manager) { + auto& root = builder.subject().root(); + + transformations::RPCNodeTransform + rpc_transform(root, options_.target, options_.category, *rpc_context_, enable_fusion_); + + rpc_transform.set_report(report_); + if (rpc_transform.can_be_applied(builder, analysis_manager)) { + rpc_transform.apply(builder, analysis_manager); + return true; + } + return false; +} + +} // namespace scheduler +} // namespace passes +} // namespace sdfg diff --git a/rpc/src/transformations/rpc_node_transform.cpp b/rpc/src/transformations/rpc_node_transform.cpp index 2a5e16256..40a98c3ab 100644 --- a/rpc/src/transformations/rpc_node_transform.cpp +++ b/rpc/src/transformations/rpc_node_transform.cpp @@ -30,9 +30,11 @@ RPCNodeTransform::RPCNodeTransform( const std::string& target, const std::string& category, sdfg::passes::rpc::RpcContext& rpc_context, + bool enable_fusion, bool dump_steps ) - : node_(node), target_(target), category_(category), rpc_context_(rpc_context), dump_steps_(dump_steps) {} + : node_(node), target_(target), category_(category), rpc_context_(rpc_context), dump_steps_(dump_steps), + enable_fusion_(enable_fusion) {} std::string RPCNodeTransform::name() const { return "RPCNodeTransform"; } @@ -40,32 +42,41 @@ std::string RPCNodeTransform::get_node_id_str() const { return std::to_string(th bool RPCNodeTransform:: can_be_applied(sdfg::builder::StructuredSDFGBuilder& builder, sdfg::analysis::AnalysisManager& analysis_manager) { - // Get loop info - auto& loop_analysis = analysis_manager.get(); - auto loop_info = loop_analysis.loop_info(&this->node_); - if (report_) { - int loopnest_idx = loop_info.loopnest_index; - if (loopnest_idx >= 0) { - report_->in_outermost_loop(loopnest_idx); + bool applicable = false; + auto outermost_loops = loop_analysis.outermost_loops(); + + for (auto outermost : outermost_loops) { + auto loop_info = loop_analysis.loop_info(outermost); + + if (!loop_info.has_side_effects) { + applicable = true; + break; } } - // Re-check for side effects with fresh loop info - if (loop_info.has_side_effects) { + if (!applicable) { if (report_) { - report_->transform_impossible(this->name(), "Loopnest side effects (" + get_node_id_str() + ")"); + report_->transform_impossible(this->name(), "No applicable loop (" + get_node_id_str() + ")"); } + DEBUG_PRINTLN( + "[RPC] Skipping node " << get_node_id_str() + << ": no applicable loop (all outermost loops have side effects), no request sent" + ); return false; } - // Create cutout SDFG - std::unique_ptr loop_sdfg = util::cutout(builder.subject(), analysis_manager, this->node_); + DEBUG_PRINTLN( + "[RPC] can_be_applied for node " << get_node_id_str() << ": querying " << rpc_context_.get_remote_address() + ); - // Loop info is only used for information on the loop structure auto opt_resp = query_rpc_server( - {.sdfg = *loop_sdfg, .category = this->category_, .target = this->target_, .loop_info = loop_info}, rpc_context_ + {.sdfg = builder.subject(), + .category = this->category_, + .target = this->target_, + .enable_fusion = this->enable_fusion_}, + rpc_context_ ); // In case query was successful, store response @@ -76,23 +87,13 @@ bool RPCNodeTransform:: bool can_apply = this->opt_resp_ != nullptr && (this->opt_resp_->sdfg_result.has_value() || this->opt_resp_->local_replay.has_value()); - if (report_) { - if (!can_apply) { - std::string error_msg; - if (std::holds_alternative(opt_resp)) { - error_msg = std::get(opt_resp); - } else if (this->opt_resp_->error.has_value()) { - error_msg = this->opt_resp_->error.value(); - } - report_->transform_impossible( - this->name(), "No opt. SDFG received (" + get_node_id_str() + ", " + error_msg + ")" - ); - } else { - nlohmann::json j; - this->to_json(j); - report_->transform_applied(this->name(), j); - } + if (!can_apply) { + DEBUG_PRINTLN( + "[RPC] Skipping node " << get_node_id_str() << ": server returned no applicable optimization from " + << rpc_context_.get_remote_address() + ); } + return can_apply; } @@ -123,12 +124,28 @@ std::variant, std::string> RPCNodeT {"sdfg", sdfg_json}, {"category", request.category}, {"target", request.target}, - {"loop_info", analysis::loop_info_to_json(request.loop_info)} + {"enable_fusion", request.enable_fusion} }; std::string payload_str = payload.dump(); + // Log where the request is going and what it carries. Header values (which may contain auth + // tokens) are intentionally omitted; only header keys are printed. + const std::string remote_address = context.get_remote_address(); + DEBUG_PRINTLN( + "[RPC] Sending optimization request to " << remote_address << " (target=" << request.target << ", category=" + << request.category << ", payload=" << payload_str.size() << " bytes)" + ); + for (const auto& [key, value] : context_headers) { + DEBUG_PRINTLN("[RPC] header: " << key); + } + // Send query - HttpResult res = post_json(curl_handle, context.get_remote_address(), payload_str, headers); + HttpResult res = post_json(curl_handle, remote_address, payload_str, headers); + + DEBUG_PRINTLN( + "[RPC] Received response from " << remote_address << " (http_status=" << res.http_status + << ", curl_code=" << res.curl_code << ", body=" << res.body.size() << " bytes)" + ); auto rpc_response = parse_rpc_response(res); @@ -181,21 +198,62 @@ std::variant, std::string> RPCNodeT rpc_response->local_replay = std::move(recipe); } - auto json_metadata = parsed.find("metadata"); - if (json_metadata != parsed.end()) { - auto& meta = rpc_response->metadata; - auto json_region_id = json_metadata->find("region_id"); - if (json_region_id != json_metadata->end()) { + auto parse_metadata = [](const nlohmann::json& json_metadata) { + passes::rpc::RpcOptimizationMetadata meta; + auto json_region_id = json_metadata.find("region_id"); + if (json_region_id != json_metadata.end() && !json_region_id->is_null()) { meta.region_id = json_region_id->get(); } - auto json_speedup = json_metadata->find("speedup"); - if (json_speedup != json_metadata->end()) { + auto json_speedup = json_metadata.find("speedup"); + if (json_speedup != json_metadata.end() && !json_speedup->is_null()) { meta.speedup = json_speedup->get(); } - auto json_vector_distance = json_metadata->find("vector_distance"); - if (json_vector_distance != json_metadata->end()) { + auto json_vector_distance = json_metadata.find("vector_distance"); + if (json_vector_distance != json_metadata.end() && !json_vector_distance->is_null()) { meta.vector_distance = json_vector_distance->get(); } + return meta; + }; + + auto parse_local_replay = [](const nlohmann::json& json_replay) { + passes::rpc::RpcLocalReplayRecipe recipe; + recipe.sequence = json_replay.at("sequence"); + return recipe; + }; + + // The multi-cutout endpoint returns a "results" array, each entry carrying its own + // element_id, local replay recipe, and metadata. + auto json_results = parsed.find("results"); + if (json_results != parsed.end() && json_results->is_array()) { + for (const auto& json_result : *json_results) { + passes::rpc::RpcRegionResult region_result; + + auto json_element_id = json_result.find("element_id"); + if (json_element_id != json_result.end() && !json_element_id->is_null()) { + region_result.element_id = json_element_id->get(); + } + + auto json_result_replay = json_result.find("local_replay"); + if (json_result_replay != json_result.end() && !json_result_replay->is_null()) { + region_result.local_replay = parse_local_replay(*json_result_replay); + } + + auto json_metadata = json_result.find("metadata"); + if (json_metadata != json_result.end()) { + region_result.metadata = parse_metadata(*json_metadata); + } + + rpc_response->results.push_back(std::move(region_result)); + } + } else { + // Single-region responses expose metadata (and replay) at the top level. + auto json_metadata = parsed.find("metadata"); + if (json_metadata != parsed.end()) { + passes::rpc::RpcRegionResult region_result; + region_result.metadata = parse_metadata(*json_metadata); + region_result.local_replay = rpc_response->local_replay; + rpc_response->results.push_back(std::move(region_result)); + } } } catch (const std::exception& e) { std::cerr << "[ERROR] Failed to parse RPC optimization response: " << e.what() << std::endl; @@ -204,7 +262,6 @@ std::variant, std::string> RPCNodeT return std::move(rpc_response); } - void RPCNodeTransform:: apply(sdfg::builder::StructuredSDFGBuilder& builder, sdfg::analysis::AnalysisManager& analysis_manager) { auto& opt = *this->opt_resp_; @@ -216,19 +273,26 @@ void RPCNodeTransform:: int element_id = this->node_.element_id(); if (opt.sdfg_result.has_value()) { - auto parent_scope = static_cast(this->node_.get_parent()); - size_t index = parent_scope->index(this->node_); + auto& sdfg_response = opt.sdfg_result->sdfg; // this consumes the SDFG result + if (this->node_.get_parent() == nullptr) { + // Whole-SDFG case: node_ is the root sequence. Replace its body in place with the + // optimized SDFG's body. + auto& root = static_cast(this->node_); + builder.remove_children(root); + builder.move_children(sdfg_response->root(), root); + } else { + // Nested-loop case: splice the optimized children into the parent in place of the loop. - auto& sdfg_response = opt.sdfg_result->sdfg; - - // TODO: add transitions from after loop to tmp_scope + auto parent_scope = static_cast(this->node_.get_parent()); + size_t index = parent_scope->index(this->node_); - auto num_children = opt.sdfg_result->sdfg->root().size(); - builder.move_children(opt.sdfg_result->sdfg->root(), *parent_scope, index); // move all optimized children into - // place - builder.remove_child(*parent_scope, index + num_children); // remove old loop + auto num_children = sdfg_response->root().size(); + builder.move_children(sdfg_response->root(), *parent_scope, index); // move all optimized children into + // place + builder.remove_child(*parent_scope, index + num_children); // remove old loop + } if (opt.sdfg_result->sdfg->element_counter() > builder.subject().element_counter()) { builder.set_element_counter(opt.sdfg_result->sdfg->element_counter()); @@ -260,51 +324,47 @@ void RPCNodeTransform:: if (opt.local_replay.has_value()) { auto recipe = opt.local_replay.value(); - std::cout << "Applied RPC optimization sequence with speedup " << opt.metadata.speedup - << " and vector distance " << opt.metadata.vector_distance << " to loopnest " << element_id << ":\n"; - - if (dump_steps_) { - print_transformation_sequence(recipe.sequence); + for (const auto& region_result : opt.results) { + DEBUG_PRINTLN( + "[RPC] Applied RPC optimization sequence with speedup " + << region_result.metadata.speedup << " and vector distance " << region_result.metadata.vector_distance + << " to loopnest " << element_id + ); } - } else { - std::cout << "RPC: Applied plain SDFG with speedup " << opt.metadata.speedup << " and vector distance " - << opt.metadata.vector_distance << "\n"; + for (const auto& region_result : opt.results) { + DEBUG_PRINTLN( + "[RPC] Applied plain SDFG with speedup " << region_result.metadata.speedup << " and vector distance " + << region_result.metadata.vector_distance + ); + } } } - void RPCNodeTransform::to_json(nlohmann::json& j) const { j["transformation_type"] = name(); - nlohmann::json params = {{"target", target_}, {"category", category_}, {"speedup", opt_resp_->metadata.speedup}}; - if (opt_resp_->metadata.region_id.has_value()) { - params["region_id"] = opt_resp_->metadata.region_id.value(); - } - params["vector_distance"] = opt_resp_->metadata.vector_distance; - j["parameters"] = params; -} - -void RPCNodeTransform::print_transformation_sequence(const nlohmann::json& sequence) const { - if (sequence.empty()) { - std::cerr << "Nothing to tune, code already optimized" << std::endl; - } else { - for (auto& desc : sequence) { - bool fail = false; - auto transformation_type = desc.find("transformation_type"); - std::cout << "\t" << transformation_type->get(); - auto transformation_parameter = desc.find("parameters"); - if (transformation_parameter != desc.end()) { - std::cout << " ("; - for (auto& [key, value] : transformation_parameter->items()) { - std::cout << key << "=" << value << ", "; - } - std::cout << ")"; - } - std::cout << "\n"; + nlohmann::json params = {{"target", target_}, {"category", category_}}; + nlohmann::json results_array = nlohmann::json::array(); + for (const auto& region_result : opt_resp_->results) { + nlohmann::json entry; + if (region_result.element_id.has_value()) { + entry["element_id"] = region_result.element_id.value(); + } + nlohmann::json metadata = { + {"speedup", region_result.metadata.speedup}, {"vector_distance", region_result.metadata.vector_distance} + }; + if (region_result.metadata.region_id.has_value()) { + metadata["region_id"] = region_result.metadata.region_id.value(); } + entry["metadata"] = metadata; + if (region_result.local_replay.has_value()) { + entry["local_replay"] = {{"sequence", region_result.local_replay->sequence}}; + } + results_array.push_back(entry); } + params["results"] = results_array; + j["parameters"] = params; } - } // namespace transformations } // namespace sdfg diff --git a/rpc/tests/passes/rpc/polybench_transfertuning_test.cpp b/rpc/tests/passes/rpc/polybench_transfertuning_test.cpp index 6661379b5..42da59b63 100644 --- a/rpc/tests/passes/rpc/polybench_transfertuning_test.cpp +++ b/rpc/tests/passes/rpc/polybench_transfertuning_test.cpp @@ -5,20 +5,18 @@ #include "sdfg/builder/structured_sdfg_builder.h" #include "sdfg/passes/normalization/normalization.h" #include "sdfg/passes/pipeline.h" -#include "sdfg/passes/rpc/rpc_scheduler.h" -#include "sdfg/passes/scheduler/loop_scheduling_pass.h" +#include "sdfg/passes/rpc/daisytuner_rpc_context.h" +#include "sdfg/passes/rpc/rpc_scheduling_pass.h" #include "sdfg/structured_sdfg.h" #include "fixtures/polybench.h" -#include "sdfg/passes/scheduler/scheduler_registry.h" using namespace sdfg; // Runs the RPC scheduling pass on a polybench SDFG after normalizing loops // (data parallelism + loop normalization), matching the real compiler pipeline. -// This exercises the full LoopSchedulingPass traversal including the CHILDREN -// descent path, catching regressions like use-after-free when find() -// invalidates the analysis cache via cutout(). +// RpcOptimizationPass sends the whole SDFG to the transfer server in one request, +// exercising the multi-nest handling path. static bool run_rpc_scheduling(std::unique_ptr init_sdfg) { builder::StructuredSDFGBuilder builder(init_sdfg); analysis::AnalysisManager analysis_manager(builder.subject()); @@ -31,9 +29,10 @@ static bool run_rpc_scheduling(std::unique_ptr init_sdfg) { analysis_manager.invalidate_all(); - passes::scheduler::LoopSchedulingPass - loop_scheduling_pass({passes::scheduler::SchedulerRegistry::instance().get_loop_scheduler("rpc")}, nullptr); - return loop_scheduling_pass.run(builder, analysis_manager); + auto rpc_context = passes::rpc::DaisytunerRpcContext::from_docc_config(); + passes::scheduler::RpcOptimizationPass + rpc_optimization_pass(rpc_context, docc::target::TargetOptions{"sequential", "server", false}); + return rpc_optimization_pass.run(builder, analysis_manager); } // Tests with multiple loop nests where the scheduler must descend (CHILDREN) diff --git a/rpc/tests/passes/rpc/rpc_loop_opt_test.cpp b/rpc/tests/passes/rpc/rpc_loop_opt_test.cpp index a55f681c3..1fa323476 100644 --- a/rpc/tests/passes/rpc/rpc_loop_opt_test.cpp +++ b/rpc/tests/passes/rpc/rpc_loop_opt_test.cpp @@ -1,14 +1,11 @@ #include #include -#include - #include "sdfg/analysis/loop_analysis.h" #include "sdfg/builder/structured_sdfg_builder.h" #include "sdfg/deepcopy/structured_sdfg_deep_copy.h" #include "sdfg/passes/rpc/rpc_context.h" -#include "sdfg/passes/scheduler/loop_scheduling_pass.h" -#include "sdfg/passes/scheduler/scheduler_registry.h" +#include "sdfg/passes/rpc/rpc_scheduling_pass.h" #include "sdfg/serializer/json_serializer.h" #include "sdfg/structured_control_flow/map.h" #include "sdfg/structured_control_flow/sequence.h" @@ -126,9 +123,12 @@ TEST_F(RPCLoopOptTest, Matmul_FMA) { auto& loop_analysis = analysis_manager.get(); auto outer_loops = loop_analysis.outermost_loops(); - passes::scheduler::LoopSchedulingPass - loop_scheduling_pass({passes::scheduler::SchedulerRegistry::instance().get_loop_scheduler("rpc")}, nullptr); - loop_scheduling_pass.run(*builder_, analysis_manager); + passes::rpc::SimpleRpcContextBuilder ctx_builder; + auto rpc_context = ctx_builder.initialize_local_default().from_env().from_header_env().build(); + + passes::scheduler::RpcOptimizationPass + rpc_optimization_pass(rpc_context, docc::target::TargetOptions{"sequential", "server", false}); + rpc_optimization_pass.run(*builder_, analysis_manager); sdfg::analysis::AnalysisManager test_analysis_manager(builder_->subject()); auto& test_loop_analysis = test_analysis_manager.get(); @@ -173,9 +173,12 @@ TEST_F(RPCLoopOptTest, Double_Matmul) { EXPECT_EQ(outer_loops.size(), 2); - passes::scheduler::LoopSchedulingPass - loop_scheduling_pass({passes::scheduler::SchedulerRegistry::instance().get_loop_scheduler("rpc")}, nullptr); - loop_scheduling_pass.run(*builder_, analysis_manager); + passes::rpc::SimpleRpcContextBuilder ctx_builder; + auto rpc_context = ctx_builder.initialize_local_default().from_env().from_header_env().build(); + + passes::scheduler::RpcOptimizationPass + rpc_optimization_pass(rpc_context, docc::target::TargetOptions{"sequential", "server", false}); + rpc_optimization_pass.run(*builder_, analysis_manager); sdfg::analysis::AnalysisManager test_analysis_manager(builder_->subject()); @@ -209,11 +212,11 @@ TEST_F(RPCLoopOptTest, Double_Matmul) { // // This test serializes a multi-child response SDFG to disk and points the // transfer server at it via SDFG-Result-Path header, then runs -// LoopSchedulingPass and verifies all children ended up in the target SDFG. +// RpcOptimizationPass and verifies all children ended up in the target SDFG. class RPCLoopOptMoveChildrenTest : public ::testing::Test { protected: std::unique_ptr builder_; - std::string target_key_ = "rpc_move_children"; + std::shared_ptr test_ctx_; void SetUp() override { // Build the input SDFG (same matmul as RPCLoopOptTest) @@ -358,11 +361,7 @@ class RPCLoopOptMoveChildrenTest : public ::testing::Test { // Build RPC context from env vars (same mechanism as test.cpp main) passes::rpc::SimpleRpcContextBuilder ctx_builder; - auto test_ctx = ctx_builder.initialize_local_default().from_env().from_header_env().build(); - - // Register scheduler under a test-specific key - passes::scheduler::SchedulerRegistry::instance() - .register_loop_scheduler(target_key_, test_ctx, "sequential", "server", false); + test_ctx_ = ctx_builder.initialize_local_default().from_env().from_header_env().build(); } void TearDown() override { unsetenv("RPC_HEADER"); } @@ -373,9 +372,9 @@ TEST_F(RPCLoopOptMoveChildrenTest, MoveAllChildrenFromRPCResult) { // Run the scheduling pass through the full pipeline analysis::AnalysisManager analysis_manager(builder_->subject()); - passes::scheduler::LoopSchedulingPass - loop_scheduling_pass({passes::scheduler::SchedulerRegistry::instance().get_loop_scheduler(target_key_)}, nullptr); - loop_scheduling_pass.run(*builder_, analysis_manager); + passes::scheduler::RpcOptimizationPass + rpc_optimization_pass(test_ctx_, docc::target::TargetOptions{"sequential", "server", false}); + rpc_optimization_pass.run(*builder_, analysis_manager); // After the pass: the single map should be replaced by all 3 children from the response EXPECT_EQ(builder_->subject().root().size(), 3) diff --git a/rpc/tests/test.cpp b/rpc/tests/test.cpp index e9ba87ad3..1bb903c2f 100644 --- a/rpc/tests/test.cpp +++ b/rpc/tests/test.cpp @@ -1,8 +1,6 @@ #include #include "sdfg/codegen/dispatchers/node_dispatcher_registry.h" -#include "sdfg/passes/rpc/rpc_context.h" -#include "sdfg/passes/rpc/rpc_scheduler.h" #include "sdfg/serializer/json_serializer.h" int main(int argc, char **argv) { @@ -10,14 +8,5 @@ int main(int argc, char **argv) { sdfg::codegen::register_default_dispatchers(); sdfg::serializer::register_default_serializers(); - sdfg::passes::rpc::SimpleRpcContextBuilder ctxBuilder; - auto ctx_ = ctxBuilder - .initialize_local_default() // localhost:8080/docc - .from_env() // $SDFG_RPC_CONFIG can override - .from_header_env() // $RPC_HEADER can override/add headers - .build(); - - sdfg::passes::rpc::register_rpc_loop_opt(ctx_, "sequential", "server", true); - return RUN_ALL_TESTS(); } diff --git a/rpc/tests/test_daisytuner_rpc.cpp b/rpc/tests/test_daisytuner_rpc.cpp index ea3b70eb8..bdf70e073 100644 --- a/rpc/tests/test_daisytuner_rpc.cpp +++ b/rpc/tests/test_daisytuner_rpc.cpp @@ -1,27 +1,11 @@ #include #include "sdfg/codegen/dispatchers/node_dispatcher_registry.h" -#include "sdfg/passes/rpc/daisytuner_rpc_context.h" -#include "sdfg/passes/rpc/rpc_scheduler.h" #include "sdfg/serializer/json_serializer.h" - -class DaisytunerRpcTestEnvironment : public ::testing::Environment { -public: - void SetUp() override { - try { - auto ctx = sdfg::passes::rpc::DaisytunerRpcContext::from_docc_config(); - sdfg::passes::rpc::register_rpc_loop_opt(ctx, "sequential", "server", true); - } catch (const std::exception& e) { - // Optionally print a warning or skip tests - } - } -}; - int main(int argc, char **argv) { testing::InitGoogleTest(&argc, argv); sdfg::codegen::register_default_dispatchers(); sdfg::serializer::register_default_serializers(); - ::testing::AddGlobalTestEnvironment(new DaisytunerRpcTestEnvironment); return RUN_ALL_TESTS(); } diff --git a/rpc/tests/transformations/rpc_node_transform_test.cpp b/rpc/tests/transformations/rpc_node_transform_test.cpp index 15030e0eb..0e277f924 100644 --- a/rpc/tests/transformations/rpc_node_transform_test.cpp +++ b/rpc/tests/transformations/rpc_node_transform_test.cpp @@ -138,7 +138,7 @@ TEST_F(RPCNodeTransformTest, Matmul_FMA) { EXPECT_EQ(outer_loops.size(), 1); auto outer_loop = static_cast(outer_loops[0]); - sdfg::transformations::RPCNodeTransform transfer_tuning(*outer_loop, "sequential", "server", *ctx_, true); + sdfg::transformations::RPCNodeTransform transfer_tuning(*outer_loop, "sequential", "server", *ctx_, true, true); ASSERT_TRUE(transfer_tuning.can_be_applied(builder, analysis_manager)); transfer_tuning.apply(builder, analysis_manager); @@ -190,7 +190,7 @@ TEST_F(RPCNodeTransformTest, Double_Matmul) { EXPECT_EQ(outer_loops.size(), 2); auto outer_loop = static_cast(outer_loops[0]); - sdfg::transformations::RPCNodeTransform transfer_tuning(*outer_loop, "sequential", "server", *ctx_, true); + sdfg::transformations::RPCNodeTransform transfer_tuning(*outer_loop, "sequential", "server", *ctx_, true, true); ASSERT_TRUE(transfer_tuning.can_be_applied(builder, analysis_manager)); transfer_tuning.apply(builder, analysis_manager); @@ -215,7 +215,8 @@ TEST_F(RPCNodeTransformTest, UniqueElementIDs) { size_t element_counter_before_first_apply = main_builder.subject().element_counter(); auto* first_outer_loop = static_cast(initial_outer_loops[0]); - sdfg::transformations::RPCNodeTransform first_rpc_transform(*first_outer_loop, "sequential", "server", *ctx_, false); + sdfg::transformations::RPCNodeTransform + first_rpc_transform(*first_outer_loop, "sequential", "server", *ctx_, true, false); ASSERT_TRUE(first_rpc_transform.can_be_applied(main_builder, main_analysis_manager)); first_rpc_transform.apply(main_builder, main_analysis_manager); @@ -230,7 +231,7 @@ TEST_F(RPCNodeTransformTest, UniqueElementIDs) { auto* second_outer_loop = static_cast(remaining_outer_loops.back()); sdfg::transformations::RPCNodeTransform - second_rpc_transform(*second_outer_loop, "sequential", "server", *ctx_, false); + second_rpc_transform(*second_outer_loop, "sequential", "server", *ctx_, true, false); ASSERT_TRUE(second_rpc_transform.can_be_applied(main_builder, main_analysis_manager)); second_rpc_transform.apply(main_builder, main_analysis_manager); @@ -261,7 +262,7 @@ TEST_F(RPCNodeTransformTest, HandleUnauthenticatedError) { EXPECT_EQ(outer_loops.size(), 1); auto outer_loop = static_cast(outer_loops[0]); - sdfg::transformations::RPCNodeTransform transform(*outer_loop, "sequential", "server", *ctx_, true); + sdfg::transformations::RPCNodeTransform transform(*outer_loop, "sequential", "server", *ctx_, true, true); // Test parse_rpc_response handles 401 errors auto response = transform.parse_rpc_response(result); @@ -290,7 +291,7 @@ TEST_F(RPCNodeTransformTest, HandleOtherHttpErrors) { EXPECT_EQ(outer_loops.size(), 1); auto outer_loop = static_cast(outer_loops[0]); - sdfg::transformations::RPCNodeTransform transform(*outer_loop, "sequential", "server", *ctx_, true); + sdfg::transformations::RPCNodeTransform transform(*outer_loop, "sequential", "server", *ctx_, true, true); // Test parse_rpc_response handles HTTP errors auto response = transform.parse_rpc_response(result); @@ -337,7 +338,7 @@ TEST_F(RPCNodeTransformTest, ApplyUnwrapsReferenceTypedContainersFromResponse) { ASSERT_EQ(outer_loops.size(), 1); auto outer_loop = static_cast(outer_loops[0]); - sdfg::transformations::RPCNodeTransform transform(*outer_loop, "sequential", "server", *test_ctx, false); + sdfg::transformations::RPCNodeTransform transform(*outer_loop, "sequential", "server", *test_ctx, true, false); ASSERT_TRUE(transform.can_be_applied(builder, analysis_manager)); transform.apply(builder, analysis_manager);