diff --git a/tools/projmgr/include/ProjMgrMlops.h b/tools/projmgr/include/ProjMgrMlops.h index 4fd263be2..4a0595515 100644 --- a/tools/projmgr/include/ProjMgrMlops.h +++ b/tools/projmgr/include/ProjMgrMlops.h @@ -135,7 +135,7 @@ class ProjMgrMlops { std::string BuildActive(const std::string& targetType, const std::string& targetSet) const; std::string GetCustomScalar(const CustomItem& custom, const std::string& key) const; std::string BuildVelaOptions(const MlopsNpuType& npu, const MlopsVelaItem& vela) const; - void SetMlopsRunType(MlopsRunType& run, const std::string& targetType, const std::string& targetSet, + bool SetMlopsRunType(MlopsRunType& run, const std::string& targetType, const TargetSetItem& targetSet, const std::vector& contexts, const std::string& outBaseDir, const std::string& solutionName) const; }; diff --git a/tools/projmgr/include/ProjMgrWorker.h b/tools/projmgr/include/ProjMgrWorker.h index 566bccf47..f95f3740d 100644 --- a/tools/projmgr/include/ProjMgrWorker.h +++ b/tools/projmgr/include/ProjMgrWorker.h @@ -1397,6 +1397,8 @@ class ProjMgrWorker { void ResolvePackRequirement(ContextItem& context, const PackItem& packEntry, bool ignoreCBuildPack); void FormatResolvedPackIds(); void RetrieveToolchainConfigFiles(); + bool PushImageOnlyTargetType(const std::string& targetType, const std::vector& images, StrVec& imageOnlyTargetTypes); + }; #endif // PROJMGRWORKER_H diff --git a/tools/projmgr/src/ProjMgrMlops.cpp b/tools/projmgr/src/ProjMgrMlops.cpp index d4857b73a..2ec569de0 100644 --- a/tools/projmgr/src/ProjMgrMlops.cpp +++ b/tools/projmgr/src/ProjMgrMlops.cpp @@ -115,18 +115,37 @@ string ProjMgrMlops::BuildVelaOptions(const MlopsNpuType& npu, const MlopsVelaIt return options; } -void ProjMgrMlops::SetMlopsRunType(MlopsRunType& run, const string& targetType, const string& targetSet, +bool ProjMgrMlops::SetMlopsRunType(MlopsRunType& run, const string& targetType, const TargetSetItem& targetSet, const vector& contexts, const string& outBaseDir, const string& solutionName) const { - run.active = BuildActive(targetType, targetSet); + run.active = BuildActive(targetType, targetSet.set); run.cbuildRun = outBaseDir + '/' + solutionName + '+' + targetType + ".cbuild-run.yml"; - for (const auto& context : contexts) { + for (auto context : contexts) { if (context.outputTypes.elf.on) { MlopsOutputType output; output.file = context.directories.cprj + '/' + context.directories.outdir + '/' + context.outputTypes.elf.filename; output.type = RteConstants::OUTPUT_TYPE_ELF; run.output.push_back(output); } + if (context.imageOnly) { + for (auto item : targetSet.images) { + if (!item.image.empty()) { + if (!m_worker->ProcessSequenceRelative(context, item.image, context.csolution->directory, false)) { + return false; + } + if (RteFsUtils::IsRelative(item.image)) { + RteFsUtils::NormalizePath(item.image, context.directories.cprj); + } + if (ProjMgrUtils::FileTypeFromExtension(item.image) == RteConstants::OUTPUT_TYPE_ELF) { + MlopsOutputType output; + output.file = item.image; + output.type = RteConstants::OUTPUT_TYPE_ELF; + run.output.push_back(output); + } + } + } + } } + return true; } bool ProjMgrMlops::CollectSettings(const CsolutionItem& csolution, MlopsType& mlops) { @@ -157,22 +176,20 @@ bool ProjMgrMlops::CollectSettings(const CsolutionItem& csolution, MlopsType& ml {hardwareTargetSet, hardwareType, hardwareContexts}, {simulatorTargetSet, simulatorType, simulatorContexts}}; for (auto& [targetSet, targetType, ref] : refs) { for (const auto& entry : targetSet.images) { - if (!entry.context.empty()) { - const string contextName = entry.context + "+" + targetType; - if (contexts->find(contextName) != contexts->end()) { - // process context precedences if needed - auto& context = contexts->at(contextName); - if (!context.precedences) { - if (!m_worker->ParseContextLayers(context) || !m_worker->LoadPacks(context) || - !m_worker->ProcessPrecedences(context, BoardOrDevice::Both) || - !m_worker->SetTargetAttributes(context, context.targetAttributes)) { - return false; - } - m_worker->CollectNpuInfo(context); + const string contextName = (entry.context.empty() ? csolution.name : entry.context) + "+" + targetType; + if (contexts->find(contextName) != contexts->end()) { + // process context precedences if needed + auto& context = contexts->at(contextName); + if (!context.precedences) { + if (!m_worker->ParseContextLayers(context) || !m_worker->LoadPacks(context) || + !m_worker->ProcessPrecedences(context, BoardOrDevice::Both) || + !m_worker->SetTargetAttributes(context, context.targetAttributes)) { + return false; } - ref.push_back(context); - pnames.insert(context.deviceItem.pname); + m_worker->CollectNpuInfo(context); } + ref.push_back(context); + pnames.insert(context.deviceItem.pname); } } } @@ -185,7 +202,7 @@ bool ProjMgrMlops::CollectSettings(const CsolutionItem& csolution, MlopsType& ml for (const auto& [ref, t] : contextRefs) { if (!t.targetType.empty() && ref.empty()) { // print error if context for specified target type was not found - ProjMgrLogger::Get().Error("mlops: no project-context specified for target '" + + ProjMgrLogger::Get().Error("mlops: no image or project-context specified for target '" + t.targetType + (t.targetSet.empty() ? "" : '@' + t.targetSet) + "'"); return false; } @@ -198,8 +215,8 @@ bool ProjMgrMlops::CollectSettings(const CsolutionItem& csolution, MlopsType& ml ContextItem& hardwareContext = hardwareFound ? hardwareContexts.front() : emptyContext; ContextItem& simulatorContext = simulatorFound ? simulatorContexts.front() : emptyContext; - // if hardware is not determined use first default context for processor type and access sequences - ContextItem& context = hardwareFound ? hardwareContext : contexts->begin()->second; + // if hardware is not determined use first selected context for processor type and access sequences + ContextItem& context = hardwareFound ? hardwareContext : contexts->at(m_worker->GetSelectedContexts().front()); // mlops description mlops.description = solutionMlops.description; @@ -289,13 +306,17 @@ bool ProjMgrMlops::CollectSettings(const CsolutionItem& csolution, MlopsType& ml if (hardwareFound) { // set hardware run types const string outBaseDir = hardwareContext.directories.cprj + "/" + hardwareContext.directories.outBaseDir; - SetMlopsRunType(mlops.hardware, hardwareType, hardwareTargetSet.set, hardwareContexts, outBaseDir, csolution.name); + if (!SetMlopsRunType(mlops.hardware, hardwareType, hardwareTargetSet, hardwareContexts, outBaseDir, csolution.name)) { + return false; + } } if (simulatorFound) { // set simulator run types const string outBaseDir = simulatorContext.directories.cprj + "/" + simulatorContext.directories.outBaseDir; - SetMlopsRunType(mlops.simulator, simulatorType, simulatorTargetSet.set, simulatorContexts, outBaseDir, csolution.name); + if (!SetMlopsRunType(mlops.simulator, simulatorType, simulatorTargetSet, simulatorContexts, outBaseDir, csolution.name)) { + return false; + } // get debugger model and config-file mlops.simulator.model = GetCustomScalar(simulatorTargetSet.debugger.custom, "model"); diff --git a/tools/projmgr/src/ProjMgrWorker.cpp b/tools/projmgr/src/ProjMgrWorker.cpp index 7c2f53933..00c6be800 100644 --- a/tools/projmgr/src/ProjMgrWorker.cpp +++ b/tools/projmgr/src/ProjMgrWorker.cpp @@ -64,13 +64,34 @@ ProjMgrWorker::~ProjMgrWorker(void) { } } +bool ProjMgrWorker::PushImageOnlyTargetType(const string& targetType, const vector& images, StrVec& imageOnlyTargetTypes) { + if (images.empty()) { + return false; + } + for (const auto& item : images) { + if (!item.context.empty()) { + return false; + } + } + CollectionUtils::PushBackUniquely(imageOnlyTargetTypes, targetType); + return true; +} + void ProjMgrWorker::AddImageOnlyContext() { + StrVec imageOnlyTargetTypes; + // add image-only context for active target-set if (!m_activeTargetSet.images.empty()) { - for (const auto& item : m_activeTargetSet.images) { - if (!item.context.empty()) { - return; + PushImageOnlyTargetType(m_activeTargetType, m_activeTargetSet.images, imageOnlyTargetTypes); + } + // add other image-only contexts (if any) + for (const auto& [targetType, item] : m_parser->GetCsolution().targetTypes) { + for (const auto& targetSet : item.targetSet) { + if (PushImageOnlyTargetType(targetType, targetSet.images, imageOnlyTargetTypes)) { + break; } } + } + for (const auto& targetType : imageOnlyTargetTypes) { ContextDesc descriptor; ContextItem context; context.imageOnly = true; @@ -79,7 +100,7 @@ void ProjMgrWorker::AddImageOnlyContext() { context.cproject = &m_imageOnly; context.cproject->name = name; context.cproject->directory = context.csolution->directory; - AddContext(descriptor, { "", m_activeTargetType }, context); + AddContext(descriptor, { "", targetType }, context); } } diff --git a/tools/projmgr/test/data/MLOps/extended.csolution.yml b/tools/projmgr/test/data/MLOps/extended.csolution.yml index 2be151585..14477d359 100644 --- a/tools/projmgr/test/data/MLOps/extended.csolution.yml +++ b/tools/projmgr/test/data/MLOps/extended.csolution.yml @@ -3,6 +3,9 @@ solution: compiler: AC6 + packs: + - pack: ARM::RteTest_DFP + mlops: # enable *.cbuild-mlops.yml description: ML model with extended configuration npu: @@ -34,8 +37,6 @@ solution: - type: Simulator device: RteTest_ARMCM0_Dual - define: - - SIMULATOR variables: - AI-Layer: $SolutionDir()$/ai_layer/ai_layer.clayer.yml target-set: diff --git a/tools/projmgr/test/data/MLOps/failure_image_only_hw.csolution.yml b/tools/projmgr/test/data/MLOps/failure_image_only_hw.csolution.yml new file mode 100644 index 000000000..28109faa4 --- /dev/null +++ b/tools/projmgr/test/data/MLOps/failure_image_only_hw.csolution.yml @@ -0,0 +1,18 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/Open-CMSIS-Pack/devtools/main/tools/projmgr/schemas/csolution.schema.json + +solution: + compiler: AC6 + + packs: + - pack: ARM::RteTest_DFP + + mlops: + description: Image-only MLOps with malformed hardware image path + + target-types: + - type: Hardware + device: RteTest_ARMCM0_Dual:cm0_core0 + target-set: + - set: + images: + - image: $BadVar$/hardware.elf diff --git a/tools/projmgr/test/data/MLOps/failure_image_only_sim.csolution.yml b/tools/projmgr/test/data/MLOps/failure_image_only_sim.csolution.yml new file mode 100644 index 000000000..fcdfbc324 --- /dev/null +++ b/tools/projmgr/test/data/MLOps/failure_image_only_sim.csolution.yml @@ -0,0 +1,20 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/Open-CMSIS-Pack/devtools/main/tools/projmgr/schemas/csolution.schema.json + +solution: + compiler: AC6 + + packs: + - pack: ARM::RteTest_DFP + + mlops: + description: Image-only MLOps with malformed simulator image path + + target-types: + - type: Simulator + device: RteTest_ARMCM0_Dual:cm0_core0 + target-set: + - set: FVP-Test + debugger: + name: Arm-FVP + images: + - image: $BadVar$/simulator.elf diff --git a/tools/projmgr/test/data/MLOps/minimal.csolution.yml b/tools/projmgr/test/data/MLOps/minimal.csolution.yml index dff4b2f8c..f71a61080 100644 --- a/tools/projmgr/test/data/MLOps/minimal.csolution.yml +++ b/tools/projmgr/test/data/MLOps/minimal.csolution.yml @@ -3,11 +3,14 @@ solution: compiler: AC6 + packs: + - pack: ARM::RteTest_DFP + mlops: description: ML model with minimal configuration - model: + model: clayer: $AI-Layer$ - + target-types: - type: Hardware device: RteTest_ARMCM0_Dual diff --git a/tools/projmgr/test/data/MLOps/minimal_image_only.csolution.yml b/tools/projmgr/test/data/MLOps/minimal_image_only.csolution.yml new file mode 100644 index 000000000..5c0128229 --- /dev/null +++ b/tools/projmgr/test/data/MLOps/minimal_image_only.csolution.yml @@ -0,0 +1,35 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/Open-CMSIS-Pack/devtools/main/tools/projmgr/schemas/csolution.schema.json + +solution: + compiler: AC6 + + packs: + - pack: ARM::RteTest_DFP + + mlops: + description: ML model with minimal image-only configuration + model: + clayer: $AI-Layer$ + + target-types: + - type: Hardware + device: RteTest_ARMCM0_Dual:cm0_core0 + variables: + - AI-Layer: $SolutionDir()$/ai_layer/ai_layer.clayer.yml + target-set: + - set: + images: + - image: images/hardware.elf + + - type: Simulator + device: RteTest_ARMCM0_Dual:cm0_core0 + variables: + - AI-Layer: $SolutionDir()$/ai_layer/ai_layer.clayer.yml + target-set: + - set: FVP-Test + debugger: + name: Arm-FVP + model: FVP_Corstone_SSE-320 + config-file: fvp/fvp_config.txt + images: + - image: images/simulator.elf diff --git a/tools/projmgr/test/data/MLOps/ref/minimal_image_only.cbuild-mlops.yml b/tools/projmgr/test/data/MLOps/ref/minimal_image_only.cbuild-mlops.yml new file mode 100644 index 000000000..ce08796ce --- /dev/null +++ b/tools/projmgr/test/data/MLOps/ref/minimal_image_only.cbuild-mlops.yml @@ -0,0 +1,27 @@ +cbuild-mlops: + description: ML model with minimal image-only configuration + processor: + type: Cortex-M0 + npu: + type: Ethos-U55 + macs: 128 + vela: + ini: .cmsis/vela_deviceLevel.ini + options: --accelerator-config ethos-u55-128 + model: + clayer: ai_layer/ai_layer.clayer.yml + name: Algorithm + hardware: + active: Hardware + cbuild-run: out/minimal_image_only+Hardware.cbuild-run.yml + output: + - file: images/hardware.elf + type: elf + simulator: + active: Simulator@FVP-Test + cbuild-run: out/minimal_image_only+Simulator.cbuild-run.yml + output: + - file: images/simulator.elf + type: elf + model: FVP_Corstone_SSE-320 + config-file: fvp/fvp_config.txt diff --git a/tools/projmgr/test/src/ProjMgrUnitTests.cpp b/tools/projmgr/test/src/ProjMgrUnitTests.cpp index 564303b8d..06ad0f14c 100644 --- a/tools/projmgr/test/src/ProjMgrUnitTests.cpp +++ b/tools/projmgr/test/src/ProjMgrUnitTests.cpp @@ -7769,7 +7769,7 @@ TEST_F(ProjMgrUnitTests, GenerateMLOps) { const vector> failureCases = { { "failure1", "mlops: target type 'OtherHardware' not found" }, { "failure2", "mlops: target set 'Simulator@OtherSet' not found" }, - { "failure3", "mlops: no project-context specified for target 'Simulator@FVP-Test'" }, + { "failure3", "mlops: no image or project-context specified for target 'Simulator@FVP-Test'" }, }; for (const auto& [name, expectedError] : failureCases) { csolution = testinput_folder + "/MLOps/" + name + ".csolution.yml"; @@ -7791,3 +7791,25 @@ TEST_F(ProjMgrUnitTests, GenerateMLOps) { cbuildIdx["build-idx"]["cbuilds"][0]["messages"]["errors"][0].as()); RteFsUtils::RemoveDir(mlopsOutput); } + +TEST_F(ProjMgrUnitTests, GenerateMLOps_ImageOnly) { + char* argv[5]; + string csolution = testinput_folder + "/MLOps/minimal_image_only.csolution.yml"; + argv[1] = (char*)"convert"; + argv[2] = (char*)csolution.c_str(); + argv[3] = (char*)"--active"; + argv[4] = (char*)""; + EXPECT_EQ(0, RunProjMgr(5, argv, m_envp)); + ProjMgrTestEnv::CompareFile(testinput_folder + "/MLOps/minimal_image_only.cbuild-mlops.yml", + testinput_folder + "/MLOps/ref/minimal_image_only.cbuild-mlops.yml"); + + // SetMlopsRunType failure for hardware: malformed access sequence in hardware image path + csolution = testinput_folder + "/MLOps/failure_image_only_hw.csolution.yml"; + argv[2] = (char*)csolution.c_str(); + EXPECT_EQ(1, RunProjMgr(5, argv, m_envp)); + + // SetMlopsRunType failure for simulator: malformed access sequence in simulator image path + csolution = testinput_folder + "/MLOps/failure_image_only_sim.csolution.yml"; + argv[2] = (char*)csolution.c_str(); + EXPECT_EQ(1, RunProjMgr(5, argv, m_envp)); +}