Add continuous resource monitoring to AutoML.IMonitor - #6520

Merged
LittleLittleCloud merged 26 commits into
dotnet:mainfrom
andrasfuchs:add-cancellation-and-resource-monitoring-to-imonitor
Feb 9, 2023
Merged

Add continuous resource monitoring to AutoML.IMonitor#6520
LittleLittleCloud merged 26 commits into
dotnet:mainfrom
andrasfuchs:add-cancellation-and-resource-monitoring-to-imonitor

Conversation

@andrasfuchs

Copy link
Copy Markdown
Contributor

Partially fixes#6320, #6426, #6425 and helps investigating further problems with AutoML trials.

This PR lets the user cancel trials based various performance metrics. It changed my user experience with AutoML experiments significantly, because I regularly had crashes and failed trials when I tried to run experiments for a long time. With this modification I could implement my own IMonitor and react to changes in memory demand, virtual memory usage, remaining disk space and I could skip a trial if it was running unexpectedly long without terminating the experiment.

Before the modifications in this PR my experiments usually stopped with an error in a few hours, but since I have much more control over the experiment with these modifications I could run much longer experiment without any issues.

On the technical level I moved the performance-related properties of the TrialSettings class into a separate TrialPerformanceMetrics subclass, I added a timer to check for those CPU and memory metrics, and I added a new ReportTrialResourceUsage event to the IMonitor class that is called periodically during the trial-run. I also added the CancellationTokenSource class of the trial to the TrialSettings so that the user can skip a trial if they wish.

You can also check my custom IMonitor implementation where the resource monitoring and cancellation logic is demonstrated.

@andrasfuchsandrasfuchs changed the title Add cancellation and resource monitoring to imonitorAdd continuous resource monitoring to AutoML.IMonitorDec 6, 2022
@codecov

codecovBot commented Dec 6, 2022

Copy link
Copy Markdown

Codecov Report

Merging #6520 (509f963) into main (8c0ceaf) will increase coverage by 0.03%.
The diff coverage is 46.15%.

❗ Current head 509f963 differs from pull request most recent head 5a27af4. Consider uploading reports for the commit 5a27af4 to get more accurate results

Additional details and impacted files
@@ Coverage Diff @@## main #6520 +/- ##
==========================================
+ Coverage 68.40% 68.43% +0.03% 
==========================================
Files 1174 1174 Lines 248045 248047 +2 Branches 25909 25909 ==========================================
+ Hits 169670 169756 +86 + Misses 71604 71538 -66 + Partials 6771 6753 -18 
FlagCoverage Δ
Debug68.43% <46.15%> (+0.03%)⬆️
production62.83% <ø> (+0.05%)⬆️
test88.88% <46.15%> (-0.05%)⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Impacted FilesCoverage Δ
src/Microsoft.ML.SearchSpace/Parameter.cs74.05% <ø> (ø)
...Microsoft.ML.AutoML.Tests/AutoMLExperimentTests.cs89.34% <30.00%> (-7.19%)⬇️
test/Microsoft.ML.AutoML.Tests/AutoFitTests.cs81.64% <100.00%> (ø)
...c/Microsoft.ML.FastTree/Utils/ThreadTaskManager.cs79.48% <0.00%> (-20.52%)⬇️
src/Microsoft.ML.Core/Data/ProgressReporter.cs70.95% <0.00%> (-6.99%)⬇️
src/Microsoft.ML.Maml/MAML.cs25.38% <0.00%> (-1.53%)⬇️
...ML.Transforms/Text/StopWordsRemovingTransformer.cs86.38% <0.00%> (-0.15%)⬇️
...soft.ML.Data/DataLoadSave/Text/TextLoaderCursor.cs89.77% <0.00%> (+0.15%)⬆️
src/Microsoft.ML.Data/Utils/LossFunctions.cs67.35% <0.00%> (+0.51%)⬆️
... and 5 more

@JakeRadMSFT

Copy link
Copy Markdown
Member

@LittleLittleCloud can you help review this?

Thanks @andrasfuchs for contributing!

@LittleLittleCloud

LittleLittleCloud commented Dec 21, 2022

Copy link
Copy Markdown
Member

@andrasfuchs I just come back from vacation, sorry for the late reply.

Comment threadsrc/Microsoft.ML.AutoML/AutoMLExperiment/AutoMLExperiment.cs
@JakeRadMSFT

Copy link
Copy Markdown
Member

@LittleLittleCloud would we want to ask @andrasfuchs to bring over any of his resource early quitting code?

@andrasfuchs

Copy link
Copy Markdown
ContributorAuthor

@JakeRadMSFT
I have these 3 checks in place in my IMonitor class:
a, running time (for this I think you would need the trial start time, that I have just removed)
b, memory usage (you already have this with AutoMLExperiment.SetMaximumMemoryUsageInMegaByte)
c, virtual memory availability (this is Windows only, at least until the Core team implements a more general approach)

Comment threadsrc/Microsoft.ML.AutoML/AutoMLExperiment/IMonitor.cs Outdated
Comment threadsrc/Microsoft.ML.AutoML/AutoMLExperiment/AutoMLExperiment.cs Outdated
LittleLittleCloudand others added 3 commits January 3, 2023 11:58
…on-and-resource-monitoring-to-imonitor
move ReportTrialResourceUsage to IPerformanceMonitor and rename it to OnPerformanceMetricsUpdatedHandler
@andrasfuchs

Copy link
Copy Markdown
ContributorAuthor

Thank you for the PR, I already modified my own code to test it.

It looks good, I just have a few remarks:

  • I inherited my own IPerformanceMonitor implementation from DefaultPerformanceMonitor, because I wanted to keep the monitoring methods. It took my a while to realize how to get its constructor parameters and I ended up copy-pasting code.
  • Having the IMonitor and IPerformanceMonitor in separate classes isn't an issue, but since we don't have the starting time of the trial in TrialSettings anymore, I needed to measure the trial's runtime based on its first performance metrics update event handler call which isn't precise, because that event isn't guarantied to be called when a new trial starts. It's probably alright, I don't think it has a practical drawbacks even if the update period is long.
  • The performance metrics updated event handler is called even after the TrialCompleted event and before the next trial's TrialRunning event. It might cause some strange states if the IPerformanceMonitor tries to cancel the trial during this period (due timeout for example). This is more obvious if the update period is small.

@LittleLittleCloud

Copy link
Copy Markdown
Member

Good to hear that the new change in IPerformanceMonitor works for you. And let me know if you met any other issues, whether or not related to this one.

The performance metrics updated event handler is called even after the TrialCompleted event and before the next trial's TrialRunning event. It might cause some strange states if the IPerformanceMonitor tries to cancel the trial during this period (due timeout for example). This is more obvious if the update period is small.

That's a good point. And it's the side effect of moving ReportTrialResourceUsage from IMonitor to IPerformanceMonitor. So as a work-around you might want to check if taskCancellationSource is still cancellable before cancelling a trial. And it will be a good idea to stop firing event from IPerformanceMonitor once the training (pipeline.fit) has stopped.

I inherited my own IPerformanceMonitor implementation from DefaultPerformanceMonitor, because I wanted to keep the monitoring methods. It took my a while to realize how to get its constructor parameters and I ended up copy-pasting code.

That's one way, you can also just list the parameter in constructor and rely on dependency injection if all parameters are available in AutoMLExperiment.ServiceCollection.

@andrasfuchs

Copy link
Copy Markdown
ContributorAuthor

I added a small change to pause the performance monitor when the trial is completed and I also trigger the PerformanceMetricsUpdated event just after the trial has started. These changes would solve the two issues I mentioned above.

I have a few more notes:
(1) I think that setting a custom IPerformanceMonitor could be simpler from the user's perspective: could we use a similar syntax as we have for IMonitor with AutoMLExperiment.SetMonitor(IMonitor)?

(2) The other thing I think would be useful in the TrialSettings object is a string array with the estimators of the pipeline. It's an important information, but we only have it from _pipeline.ToString(trialSettings.Parameter) in the IMonitor and IPerformanceMonitor event handlers at the moment. Since just the last estimator is usually changing, I still need to do some string-splitting to get the useful, last one, this isn't ideal. Would you be open to adding a string[] Estimators to TrialSettings?

(3) I know that you preferred the removal of StartedAtUtc property from TrialSettings before, but I still think it would be practical to include it there, because checking the runtime of the trial in a performance monitor is typical I think, and it would make that a lot simpler for the end user. What do you think?

(4) The CPU measurement seems to be off a little, sometimes it goes above 100%:
image

@LittleLittleCloud

LittleLittleCloud commented Jan 6, 2023

Copy link
Copy Markdown
Member

Thanks for the new change that you made, it's really awesome.

(1) I think that setting a custom IPerformanceMonitor could be simpler from the user's perspective: could we use a similar syntax as we have for IMonitor with AutoMLExperiment.SetMonitor(IMonitor)?

IPerformanceMonitor has a different lifespan compared to IMonitor. IPerformanceMonitor's lifespan is transient and AutoMLExperiment will create a new instance of it every time it starts a new trial. While the lifetime of IMonitor will live the entire training process. Because of that, we can't just pass an instance of IPerformanceMonitor like how IMonitor does.

(2) The other thing I think would be useful in the TrialSettings object is a string array with the estimators of the pipeline. It's an important information, but we only have it from _pipeline.ToString(trialSettings.Parameter) in the IMonitor and IPerformanceMonitor event handlers at the moment. Since just the last estimator is usually changing, I still need to do some string-splitting to get the useful, last one, this isn't ideal. Would you be open to adding a string[] Estimators to TrialSettings?

Would it be helpful if we provide a helper function for SweepablePipeline which extracts a list of Estimators from Parameter otherwise? I'm still hesitant to add a string[] Estimator in trialSetting class since it's duplicated information.

(3) I know that you preferred the removal of StartedAtUtc property from TrialSettings before, but I still think it would be practical to include it there, because checking the runtime of the trial in a performance monitor is typical I think, and it would make that a lot simpler for the end user. What do you think?

Sure, and maybe add an EndAtUtc as well

(4) The CPU measurement seems to be off a little, sometimes it goes above 100%:

Might be data racing in SampleCpuAndMemoryUsage? Maybe we need to add a lock to that handker

@andrasfuchs

Copy link
Copy Markdown
ContributorAuthor

(1) I didn't know that, makes sense, thank you for the explanation!

(2) A helper function to get the estimator names from the Parameter would be perfect.

(3) I added StartedAtUtc and EndedAtUtc to TrialSettings.

(4) I tried to encapsulate the whole SampleCpuAndMemoryUsage() method in a lock {}, but it didn't solve the problem.
image

(5) The cancellation logic stopped working somewhere along the way:
image
I call the trialCancellationTokenSource.Cancel(), but the trial keeps running. Do you know why?

@LittleLittleCloud

Copy link
Copy Markdown
Member

(4) I tried to encapsulate the whole SampleCpuAndMemoryUsage() method in a lock {}, but it didn't solve the problem.

The CPU usage is calculated using the following formula:

cpuUsage = (Process.TotalProcessorTime - _totalCpuProcessorTime) / (Environment.ProcessorCount * _checkIntervalInMilliseconds)

where _totalCpuProcessorTime is the most recent sampled Process.TotalProcessorTime.

if cpuUsage is over 100%, one situation is (Environment.ProcessorCount * _checkIntervalInMilliseconds) being smaller than actual sampling interval, which might cause by waiting and getting _lock?

To verify that hypothesis, maybe the formula needs to be updated to

cpuUsage = (Process.TotalProcessorTime - _totalCpuProcessorTime) / (Environment.ProcessorCount * (Utc.Now - _lastSamplingUtcTime)

@LittleLittleCloud
LittleLittleCloud merged commit d239fda into dotnet:mainFeb 9, 2023
@JakeRadMSFT

Copy link
Copy Markdown
Member

@LittleLittleCloud@andrasfuchs thanks for getting this in!

@andrasfuchs

Copy link
Copy Markdown
ContributorAuthor

Yes, thank you @LittleLittleCloud for finishing this up and thank you @JakeRadMSFT and @luisquintanilla for keeping an eye on this!

@ghostghost locked as resolved and limited conversation to collaborators Mar 16, 2023
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add resource (CPU,RAM,GPU,thread count) monitoring to AutoML experiments

3 participants

@andrasfuchs@JakeRadMSFT@LittleLittleCloud
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Add continuous resource monitoring to AutoML.IMonitor - #6520

Merged
LittleLittleCloud merged 26 commits into
dotnet:mainfrom
andrasfuchs:add-cancellation-and-resource-monitoring-to-imonitor
Feb 9, 2023
Merged

Add continuous resource monitoring to AutoML.IMonitor#6520
LittleLittleCloud merged 26 commits into
dotnet:mainfrom
andrasfuchs:add-cancellation-and-resource-monitoring-to-imonitor

Conversation

@andrasfuchs

Copy link
Copy Markdown
Contributor

Partially fixes#6320, #6426, #6425 and helps investigating further problems with AutoML trials.

This PR lets the user cancel trials based various performance metrics. It changed my user experience with AutoML experiments significantly, because I regularly had crashes and failed trials when I tried to run experiments for a long time. With this modification I could implement my own IMonitor and react to changes in memory demand, virtual memory usage, remaining disk space and I could skip a trial if it was running unexpectedly long without terminating the experiment.

Before the modifications in this PR my experiments usually stopped with an error in a few hours, but since I have much more control over the experiment with these modifications I could run much longer experiment without any issues.

On the technical level I moved the performance-related properties of the TrialSettings class into a separate TrialPerformanceMetrics subclass, I added a timer to check for those CPU and memory metrics, and I added a new ReportTrialResourceUsage event to the IMonitor class that is called periodically during the trial-run. I also added the CancellationTokenSource class of the trial to the TrialSettings so that the user can skip a trial if they wish.

You can also check my custom IMonitor implementation where the resource monitoring and cancellation logic is demonstrated.

@andrasfuchsandrasfuchs changed the title Add cancellation and resource monitoring to imonitorAdd continuous resource monitoring to AutoML.IMonitorDec 6, 2022
@codecov

codecovBot commented Dec 6, 2022

Copy link
Copy Markdown

Codecov Report

Merging #6520 (509f963) into main (8c0ceaf) will increase coverage by 0.03%.
The diff coverage is 46.15%.

❗ Current head 509f963 differs from pull request most recent head 5a27af4. Consider uploading reports for the commit 5a27af4 to get more accurate results

Additional details and impacted files
@@ Coverage Diff @@## main #6520 +/- ##
==========================================
+ Coverage 68.40% 68.43% +0.03% 
==========================================
Files 1174 1174 Lines 248045 248047 +2 Branches 25909 25909 ==========================================
+ Hits 169670 169756 +86 + Misses 71604 71538 -66 + Partials 6771 6753 -18 
FlagCoverage Δ
Debug68.43% <46.15%> (+0.03%)⬆️
production62.83% <ø> (+0.05%)⬆️
test88.88% <46.15%> (-0.05%)⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Impacted FilesCoverage Δ
src/Microsoft.ML.SearchSpace/Parameter.cs74.05% <ø> (ø)
...Microsoft.ML.AutoML.Tests/AutoMLExperimentTests.cs89.34% <30.00%> (-7.19%)⬇️
test/Microsoft.ML.AutoML.Tests/AutoFitTests.cs81.64% <100.00%> (ø)
...c/Microsoft.ML.FastTree/Utils/ThreadTaskManager.cs79.48% <0.00%> (-20.52%)⬇️
src/Microsoft.ML.Core/Data/ProgressReporter.cs70.95% <0.00%> (-6.99%)⬇️
src/Microsoft.ML.Maml/MAML.cs25.38% <0.00%> (-1.53%)⬇️
...ML.Transforms/Text/StopWordsRemovingTransformer.cs86.38% <0.00%> (-0.15%)⬇️
...soft.ML.Data/DataLoadSave/Text/TextLoaderCursor.cs89.77% <0.00%> (+0.15%)⬆️
src/Microsoft.ML.Data/Utils/LossFunctions.cs67.35% <0.00%> (+0.51%)⬆️
... and 5 more

@JakeRadMSFT

Copy link
Copy Markdown
Member

@LittleLittleCloud can you help review this?

Thanks @andrasfuchs for contributing!

@LittleLittleCloud

LittleLittleCloud commented Dec 21, 2022

Copy link
Copy Markdown
Member

@andrasfuchs I just come back from vacation, sorry for the late reply.

Comment threadsrc/Microsoft.ML.AutoML/AutoMLExperiment/AutoMLExperiment.cs
@JakeRadMSFT

Copy link
Copy Markdown
Member

@LittleLittleCloud would we want to ask @andrasfuchs to bring over any of his resource early quitting code?

@andrasfuchs

Copy link
Copy Markdown
ContributorAuthor

@JakeRadMSFT
I have these 3 checks in place in my IMonitor class:
a, running time (for this I think you would need the trial start time, that I have just removed)
b, memory usage (you already have this with AutoMLExperiment.SetMaximumMemoryUsageInMegaByte)
c, virtual memory availability (this is Windows only, at least until the Core team implements a more general approach)

Comment threadsrc/Microsoft.ML.AutoML/AutoMLExperiment/IMonitor.cs Outdated
Comment threadsrc/Microsoft.ML.AutoML/AutoMLExperiment/AutoMLExperiment.cs Outdated
LittleLittleCloudand others added 3 commits January 3, 2023 11:58
…on-and-resource-monitoring-to-imonitor
move ReportTrialResourceUsage to IPerformanceMonitor and rename it to OnPerformanceMetricsUpdatedHandler
@andrasfuchs

Copy link
Copy Markdown
ContributorAuthor

Thank you for the PR, I already modified my own code to test it.

It looks good, I just have a few remarks:

  • I inherited my own IPerformanceMonitor implementation from DefaultPerformanceMonitor, because I wanted to keep the monitoring methods. It took my a while to realize how to get its constructor parameters and I ended up copy-pasting code.
  • Having the IMonitor and IPerformanceMonitor in separate classes isn't an issue, but since we don't have the starting time of the trial in TrialSettings anymore, I needed to measure the trial's runtime based on its first performance metrics update event handler call which isn't precise, because that event isn't guarantied to be called when a new trial starts. It's probably alright, I don't think it has a practical drawbacks even if the update period is long.
  • The performance metrics updated event handler is called even after the TrialCompleted event and before the next trial's TrialRunning event. It might cause some strange states if the IPerformanceMonitor tries to cancel the trial during this period (due timeout for example). This is more obvious if the update period is small.

@LittleLittleCloud

Copy link
Copy Markdown
Member

Good to hear that the new change in IPerformanceMonitor works for you. And let me know if you met any other issues, whether or not related to this one.

The performance metrics updated event handler is called even after the TrialCompleted event and before the next trial's TrialRunning event. It might cause some strange states if the IPerformanceMonitor tries to cancel the trial during this period (due timeout for example). This is more obvious if the update period is small.

That's a good point. And it's the side effect of moving ReportTrialResourceUsage from IMonitor to IPerformanceMonitor. So as a work-around you might want to check if taskCancellationSource is still cancellable before cancelling a trial. And it will be a good idea to stop firing event from IPerformanceMonitor once the training (pipeline.fit) has stopped.

I inherited my own IPerformanceMonitor implementation from DefaultPerformanceMonitor, because I wanted to keep the monitoring methods. It took my a while to realize how to get its constructor parameters and I ended up copy-pasting code.

That's one way, you can also just list the parameter in constructor and rely on dependency injection if all parameters are available in AutoMLExperiment.ServiceCollection.

@andrasfuchs

Copy link
Copy Markdown
ContributorAuthor

I added a small change to pause the performance monitor when the trial is completed and I also trigger the PerformanceMetricsUpdated event just after the trial has started. These changes would solve the two issues I mentioned above.

I have a few more notes:
(1) I think that setting a custom IPerformanceMonitor could be simpler from the user's perspective: could we use a similar syntax as we have for IMonitor with AutoMLExperiment.SetMonitor(IMonitor)?

(2) The other thing I think would be useful in the TrialSettings object is a string array with the estimators of the pipeline. It's an important information, but we only have it from _pipeline.ToString(trialSettings.Parameter) in the IMonitor and IPerformanceMonitor event handlers at the moment. Since just the last estimator is usually changing, I still need to do some string-splitting to get the useful, last one, this isn't ideal. Would you be open to adding a string[] Estimators to TrialSettings?

(3) I know that you preferred the removal of StartedAtUtc property from TrialSettings before, but I still think it would be practical to include it there, because checking the runtime of the trial in a performance monitor is typical I think, and it would make that a lot simpler for the end user. What do you think?

(4) The CPU measurement seems to be off a little, sometimes it goes above 100%:
image

@LittleLittleCloud

LittleLittleCloud commented Jan 6, 2023

Copy link
Copy Markdown
Member

Thanks for the new change that you made, it's really awesome.

(1) I think that setting a custom IPerformanceMonitor could be simpler from the user's perspective: could we use a similar syntax as we have for IMonitor with AutoMLExperiment.SetMonitor(IMonitor)?

IPerformanceMonitor has a different lifespan compared to IMonitor. IPerformanceMonitor's lifespan is transient and AutoMLExperiment will create a new instance of it every time it starts a new trial. While the lifetime of IMonitor will live the entire training process. Because of that, we can't just pass an instance of IPerformanceMonitor like how IMonitor does.

(2) The other thing I think would be useful in the TrialSettings object is a string array with the estimators of the pipeline. It's an important information, but we only have it from _pipeline.ToString(trialSettings.Parameter) in the IMonitor and IPerformanceMonitor event handlers at the moment. Since just the last estimator is usually changing, I still need to do some string-splitting to get the useful, last one, this isn't ideal. Would you be open to adding a string[] Estimators to TrialSettings?

Would it be helpful if we provide a helper function for SweepablePipeline which extracts a list of Estimators from Parameter otherwise? I'm still hesitant to add a string[] Estimator in trialSetting class since it's duplicated information.

(3) I know that you preferred the removal of StartedAtUtc property from TrialSettings before, but I still think it would be practical to include it there, because checking the runtime of the trial in a performance monitor is typical I think, and it would make that a lot simpler for the end user. What do you think?

Sure, and maybe add an EndAtUtc as well

(4) The CPU measurement seems to be off a little, sometimes it goes above 100%:

Might be data racing in SampleCpuAndMemoryUsage? Maybe we need to add a lock to that handker

@andrasfuchs

Copy link
Copy Markdown
ContributorAuthor

(1) I didn't know that, makes sense, thank you for the explanation!

(2) A helper function to get the estimator names from the Parameter would be perfect.

(3) I added StartedAtUtc and EndedAtUtc to TrialSettings.

(4) I tried to encapsulate the whole SampleCpuAndMemoryUsage() method in a lock {}, but it didn't solve the problem.
image

(5) The cancellation logic stopped working somewhere along the way:
image
I call the trialCancellationTokenSource.Cancel(), but the trial keeps running. Do you know why?

@LittleLittleCloud

Copy link
Copy Markdown
Member

(4) I tried to encapsulate the whole SampleCpuAndMemoryUsage() method in a lock {}, but it didn't solve the problem.

The CPU usage is calculated using the following formula:

cpuUsage = (Process.TotalProcessorTime - _totalCpuProcessorTime) / (Environment.ProcessorCount * _checkIntervalInMilliseconds)

where _totalCpuProcessorTime is the most recent sampled Process.TotalProcessorTime.

if cpuUsage is over 100%, one situation is (Environment.ProcessorCount * _checkIntervalInMilliseconds) being smaller than actual sampling interval, which might cause by waiting and getting _lock?

To verify that hypothesis, maybe the formula needs to be updated to

cpuUsage = (Process.TotalProcessorTime - _totalCpuProcessorTime) / (Environment.ProcessorCount * (Utc.Now - _lastSamplingUtcTime)

@LittleLittleCloud
LittleLittleCloud merged commit d239fda into dotnet:mainFeb 9, 2023
@JakeRadMSFT

Copy link
Copy Markdown
Member

@LittleLittleCloud@andrasfuchs thanks for getting this in!

@andrasfuchs

Copy link
Copy Markdown
ContributorAuthor

Yes, thank you @LittleLittleCloud for finishing this up and thank you @JakeRadMSFT and @luisquintanilla for keeping an eye on this!

@ghostghost locked as resolved and limited conversation to collaborators Mar 16, 2023
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add resource (CPU,RAM,GPU,thread count) monitoring to AutoML experiments

3 participants

@andrasfuchs@JakeRadMSFT@LittleLittleCloud
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Add continuous resource monitoring to AutoML.IMonitor - #6520

Merged
LittleLittleCloud merged 26 commits into
dotnet:mainfrom
andrasfuchs:add-cancellation-and-resource-monitoring-to-imonitor
Feb 9, 2023
Merged

Add continuous resource monitoring to AutoML.IMonitor#6520
LittleLittleCloud merged 26 commits into
dotnet:mainfrom
andrasfuchs:add-cancellation-and-resource-monitoring-to-imonitor

Conversation

@andrasfuchs

Copy link
Copy Markdown
Contributor

Partially fixes#6320, #6426, #6425 and helps investigating further problems with AutoML trials.

This PR lets the user cancel trials based various performance metrics. It changed my user experience with AutoML experiments significantly, because I regularly had crashes and failed trials when I tried to run experiments for a long time. With this modification I could implement my own IMonitor and react to changes in memory demand, virtual memory usage, remaining disk space and I could skip a trial if it was running unexpectedly long without terminating the experiment.

Before the modifications in this PR my experiments usually stopped with an error in a few hours, but since I have much more control over the experiment with these modifications I could run much longer experiment without any issues.

On the technical level I moved the performance-related properties of the TrialSettings class into a separate TrialPerformanceMetrics subclass, I added a timer to check for those CPU and memory metrics, and I added a new ReportTrialResourceUsage event to the IMonitor class that is called periodically during the trial-run. I also added the CancellationTokenSource class of the trial to the TrialSettings so that the user can skip a trial if they wish.

You can also check my custom IMonitor implementation where the resource monitoring and cancellation logic is demonstrated.

@andrasfuchsandrasfuchs changed the title Add cancellation and resource monitoring to imonitorAdd continuous resource monitoring to AutoML.IMonitorDec 6, 2022
@codecov

codecovBot commented Dec 6, 2022

Copy link
Copy Markdown

Codecov Report

Merging #6520 (509f963) into main (8c0ceaf) will increase coverage by 0.03%.
The diff coverage is 46.15%.

❗ Current head 509f963 differs from pull request most recent head 5a27af4. Consider uploading reports for the commit 5a27af4 to get more accurate results

Additional details and impacted files
@@ Coverage Diff @@## main #6520 +/- ##
==========================================
+ Coverage 68.40% 68.43% +0.03% 
==========================================
Files 1174 1174 Lines 248045 248047 +2 Branches 25909 25909 ==========================================
+ Hits 169670 169756 +86 + Misses 71604 71538 -66 + Partials 6771 6753 -18 
FlagCoverage Δ
Debug68.43% <46.15%> (+0.03%)⬆️
production62.83% <ø> (+0.05%)⬆️
test88.88% <46.15%> (-0.05%)⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Impacted FilesCoverage Δ
src/Microsoft.ML.SearchSpace/Parameter.cs74.05% <ø> (ø)
...Microsoft.ML.AutoML.Tests/AutoMLExperimentTests.cs89.34% <30.00%> (-7.19%)⬇️
test/Microsoft.ML.AutoML.Tests/AutoFitTests.cs81.64% <100.00%> (ø)
...c/Microsoft.ML.FastTree/Utils/ThreadTaskManager.cs79.48% <0.00%> (-20.52%)⬇️
src/Microsoft.ML.Core/Data/ProgressReporter.cs70.95% <0.00%> (-6.99%)⬇️
src/Microsoft.ML.Maml/MAML.cs25.38% <0.00%> (-1.53%)⬇️
...ML.Transforms/Text/StopWordsRemovingTransformer.cs86.38% <0.00%> (-0.15%)⬇️
...soft.ML.Data/DataLoadSave/Text/TextLoaderCursor.cs89.77% <0.00%> (+0.15%)⬆️
src/Microsoft.ML.Data/Utils/LossFunctions.cs67.35% <0.00%> (+0.51%)⬆️
... and 5 more

@JakeRadMSFT

Copy link
Copy Markdown
Member

@LittleLittleCloud can you help review this?

Thanks @andrasfuchs for contributing!

@LittleLittleCloud

LittleLittleCloud commented Dec 21, 2022

Copy link
Copy Markdown
Member

@andrasfuchs I just come back from vacation, sorry for the late reply.

Comment threadsrc/Microsoft.ML.AutoML/AutoMLExperiment/AutoMLExperiment.cs
@JakeRadMSFT

Copy link
Copy Markdown
Member

@LittleLittleCloud would we want to ask @andrasfuchs to bring over any of his resource early quitting code?

@andrasfuchs

Copy link
Copy Markdown
ContributorAuthor

@JakeRadMSFT
I have these 3 checks in place in my IMonitor class:
a, running time (for this I think you would need the trial start time, that I have just removed)
b, memory usage (you already have this with AutoMLExperiment.SetMaximumMemoryUsageInMegaByte)
c, virtual memory availability (this is Windows only, at least until the Core team implements a more general approach)

Comment threadsrc/Microsoft.ML.AutoML/AutoMLExperiment/IMonitor.cs Outdated
Comment threadsrc/Microsoft.ML.AutoML/AutoMLExperiment/AutoMLExperiment.cs Outdated
LittleLittleCloudand others added 3 commits January 3, 2023 11:58
…on-and-resource-monitoring-to-imonitor
move ReportTrialResourceUsage to IPerformanceMonitor and rename it to OnPerformanceMetricsUpdatedHandler
@andrasfuchs

Copy link
Copy Markdown
ContributorAuthor

Thank you for the PR, I already modified my own code to test it.

It looks good, I just have a few remarks:

  • I inherited my own IPerformanceMonitor implementation from DefaultPerformanceMonitor, because I wanted to keep the monitoring methods. It took my a while to realize how to get its constructor parameters and I ended up copy-pasting code.
  • Having the IMonitor and IPerformanceMonitor in separate classes isn't an issue, but since we don't have the starting time of the trial in TrialSettings anymore, I needed to measure the trial's runtime based on its first performance metrics update event handler call which isn't precise, because that event isn't guarantied to be called when a new trial starts. It's probably alright, I don't think it has a practical drawbacks even if the update period is long.
  • The performance metrics updated event handler is called even after the TrialCompleted event and before the next trial's TrialRunning event. It might cause some strange states if the IPerformanceMonitor tries to cancel the trial during this period (due timeout for example). This is more obvious if the update period is small.

@LittleLittleCloud

Copy link
Copy Markdown
Member

Good to hear that the new change in IPerformanceMonitor works for you. And let me know if you met any other issues, whether or not related to this one.

The performance metrics updated event handler is called even after the TrialCompleted event and before the next trial's TrialRunning event. It might cause some strange states if the IPerformanceMonitor tries to cancel the trial during this period (due timeout for example). This is more obvious if the update period is small.

That's a good point. And it's the side effect of moving ReportTrialResourceUsage from IMonitor to IPerformanceMonitor. So as a work-around you might want to check if taskCancellationSource is still cancellable before cancelling a trial. And it will be a good idea to stop firing event from IPerformanceMonitor once the training (pipeline.fit) has stopped.

I inherited my own IPerformanceMonitor implementation from DefaultPerformanceMonitor, because I wanted to keep the monitoring methods. It took my a while to realize how to get its constructor parameters and I ended up copy-pasting code.

That's one way, you can also just list the parameter in constructor and rely on dependency injection if all parameters are available in AutoMLExperiment.ServiceCollection.

@andrasfuchs

Copy link
Copy Markdown
ContributorAuthor

I added a small change to pause the performance monitor when the trial is completed and I also trigger the PerformanceMetricsUpdated event just after the trial has started. These changes would solve the two issues I mentioned above.

I have a few more notes:
(1) I think that setting a custom IPerformanceMonitor could be simpler from the user's perspective: could we use a similar syntax as we have for IMonitor with AutoMLExperiment.SetMonitor(IMonitor)?

(2) The other thing I think would be useful in the TrialSettings object is a string array with the estimators of the pipeline. It's an important information, but we only have it from _pipeline.ToString(trialSettings.Parameter) in the IMonitor and IPerformanceMonitor event handlers at the moment. Since just the last estimator is usually changing, I still need to do some string-splitting to get the useful, last one, this isn't ideal. Would you be open to adding a string[] Estimators to TrialSettings?

(3) I know that you preferred the removal of StartedAtUtc property from TrialSettings before, but I still think it would be practical to include it there, because checking the runtime of the trial in a performance monitor is typical I think, and it would make that a lot simpler for the end user. What do you think?

(4) The CPU measurement seems to be off a little, sometimes it goes above 100%:
image

@LittleLittleCloud

LittleLittleCloud commented Jan 6, 2023

Copy link
Copy Markdown
Member

Thanks for the new change that you made, it's really awesome.

(1) I think that setting a custom IPerformanceMonitor could be simpler from the user's perspective: could we use a similar syntax as we have for IMonitor with AutoMLExperiment.SetMonitor(IMonitor)?

IPerformanceMonitor has a different lifespan compared to IMonitor. IPerformanceMonitor's lifespan is transient and AutoMLExperiment will create a new instance of it every time it starts a new trial. While the lifetime of IMonitor will live the entire training process. Because of that, we can't just pass an instance of IPerformanceMonitor like how IMonitor does.

(2) The other thing I think would be useful in the TrialSettings object is a string array with the estimators of the pipeline. It's an important information, but we only have it from _pipeline.ToString(trialSettings.Parameter) in the IMonitor and IPerformanceMonitor event handlers at the moment. Since just the last estimator is usually changing, I still need to do some string-splitting to get the useful, last one, this isn't ideal. Would you be open to adding a string[] Estimators to TrialSettings?

Would it be helpful if we provide a helper function for SweepablePipeline which extracts a list of Estimators from Parameter otherwise? I'm still hesitant to add a string[] Estimator in trialSetting class since it's duplicated information.

(3) I know that you preferred the removal of StartedAtUtc property from TrialSettings before, but I still think it would be practical to include it there, because checking the runtime of the trial in a performance monitor is typical I think, and it would make that a lot simpler for the end user. What do you think?

Sure, and maybe add an EndAtUtc as well

(4) The CPU measurement seems to be off a little, sometimes it goes above 100%:

Might be data racing in SampleCpuAndMemoryUsage? Maybe we need to add a lock to that handker

@andrasfuchs

Copy link
Copy Markdown
ContributorAuthor

(1) I didn't know that, makes sense, thank you for the explanation!

(2) A helper function to get the estimator names from the Parameter would be perfect.

(3) I added StartedAtUtc and EndedAtUtc to TrialSettings.

(4) I tried to encapsulate the whole SampleCpuAndMemoryUsage() method in a lock {}, but it didn't solve the problem.
image

(5) The cancellation logic stopped working somewhere along the way:
image
I call the trialCancellationTokenSource.Cancel(), but the trial keeps running. Do you know why?

@LittleLittleCloud

Copy link
Copy Markdown
Member

(4) I tried to encapsulate the whole SampleCpuAndMemoryUsage() method in a lock {}, but it didn't solve the problem.

The CPU usage is calculated using the following formula:

cpuUsage = (Process.TotalProcessorTime - _totalCpuProcessorTime) / (Environment.ProcessorCount * _checkIntervalInMilliseconds)

where _totalCpuProcessorTime is the most recent sampled Process.TotalProcessorTime.

if cpuUsage is over 100%, one situation is (Environment.ProcessorCount * _checkIntervalInMilliseconds) being smaller than actual sampling interval, which might cause by waiting and getting _lock?

To verify that hypothesis, maybe the formula needs to be updated to

cpuUsage = (Process.TotalProcessorTime - _totalCpuProcessorTime) / (Environment.ProcessorCount * (Utc.Now - _lastSamplingUtcTime)

@LittleLittleCloud
LittleLittleCloud merged commit d239fda into dotnet:mainFeb 9, 2023
@JakeRadMSFT

Copy link
Copy Markdown
Member

@LittleLittleCloud@andrasfuchs thanks for getting this in!

@andrasfuchs

Copy link
Copy Markdown
ContributorAuthor

Yes, thank you @LittleLittleCloud for finishing this up and thank you @JakeRadMSFT and @luisquintanilla for keeping an eye on this!

@ghostghost locked as resolved and limited conversation to collaborators Mar 16, 2023
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add resource (CPU,RAM,GPU,thread count) monitoring to AutoML experiments

3 participants

@andrasfuchs@JakeRadMSFT@LittleLittleCloud
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Add continuous resource monitoring to AutoML.IMonitor - #6520

Merged
LittleLittleCloud merged 26 commits into
dotnet:mainfrom
andrasfuchs:add-cancellation-and-resource-monitoring-to-imonitor
Feb 9, 2023
Merged

Add continuous resource monitoring to AutoML.IMonitor#6520
LittleLittleCloud merged 26 commits into
dotnet:mainfrom
andrasfuchs:add-cancellation-and-resource-monitoring-to-imonitor

Conversation

@andrasfuchs

Copy link
Copy Markdown
Contributor

Partially fixes#6320, #6426, #6425 and helps investigating further problems with AutoML trials.

This PR lets the user cancel trials based various performance metrics. It changed my user experience with AutoML experiments significantly, because I regularly had crashes and failed trials when I tried to run experiments for a long time. With this modification I could implement my own IMonitor and react to changes in memory demand, virtual memory usage, remaining disk space and I could skip a trial if it was running unexpectedly long without terminating the experiment.

Before the modifications in this PR my experiments usually stopped with an error in a few hours, but since I have much more control over the experiment with these modifications I could run much longer experiment without any issues.

On the technical level I moved the performance-related properties of the TrialSettings class into a separate TrialPerformanceMetrics subclass, I added a timer to check for those CPU and memory metrics, and I added a new ReportTrialResourceUsage event to the IMonitor class that is called periodically during the trial-run. I also added the CancellationTokenSource class of the trial to the TrialSettings so that the user can skip a trial if they wish.

You can also check my custom IMonitor implementation where the resource monitoring and cancellation logic is demonstrated.

@andrasfuchsandrasfuchs changed the title Add cancellation and resource monitoring to imonitorAdd continuous resource monitoring to AutoML.IMonitorDec 6, 2022
@codecov

codecovBot commented Dec 6, 2022

Copy link
Copy Markdown

Codecov Report

Merging #6520 (509f963) into main (8c0ceaf) will increase coverage by 0.03%.
The diff coverage is 46.15%.

❗ Current head 509f963 differs from pull request most recent head 5a27af4. Consider uploading reports for the commit 5a27af4 to get more accurate results

Additional details and impacted files
@@ Coverage Diff @@## main #6520 +/- ##
==========================================
+ Coverage 68.40% 68.43% +0.03% 
==========================================
Files 1174 1174 Lines 248045 248047 +2 Branches 25909 25909 ==========================================
+ Hits 169670 169756 +86 + Misses 71604 71538 -66 + Partials 6771 6753 -18 
FlagCoverage Δ
Debug68.43% <46.15%> (+0.03%)⬆️
production62.83% <ø> (+0.05%)⬆️
test88.88% <46.15%> (-0.05%)⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Impacted FilesCoverage Δ
src/Microsoft.ML.SearchSpace/Parameter.cs74.05% <ø> (ø)
...Microsoft.ML.AutoML.Tests/AutoMLExperimentTests.cs89.34% <30.00%> (-7.19%)⬇️
test/Microsoft.ML.AutoML.Tests/AutoFitTests.cs81.64% <100.00%> (ø)
...c/Microsoft.ML.FastTree/Utils/ThreadTaskManager.cs79.48% <0.00%> (-20.52%)⬇️
src/Microsoft.ML.Core/Data/ProgressReporter.cs70.95% <0.00%> (-6.99%)⬇️
src/Microsoft.ML.Maml/MAML.cs25.38% <0.00%> (-1.53%)⬇️
...ML.Transforms/Text/StopWordsRemovingTransformer.cs86.38% <0.00%> (-0.15%)⬇️
...soft.ML.Data/DataLoadSave/Text/TextLoaderCursor.cs89.77% <0.00%> (+0.15%)⬆️
src/Microsoft.ML.Data/Utils/LossFunctions.cs67.35% <0.00%> (+0.51%)⬆️
... and 5 more

@JakeRadMSFT

Copy link
Copy Markdown
Member

@LittleLittleCloud can you help review this?

Thanks @andrasfuchs for contributing!

@LittleLittleCloud

LittleLittleCloud commented Dec 21, 2022

Copy link
Copy Markdown
Member

@andrasfuchs I just come back from vacation, sorry for the late reply.

Comment threadsrc/Microsoft.ML.AutoML/AutoMLExperiment/AutoMLExperiment.cs
@JakeRadMSFT

Copy link
Copy Markdown
Member

@LittleLittleCloud would we want to ask @andrasfuchs to bring over any of his resource early quitting code?

@andrasfuchs

Copy link
Copy Markdown
ContributorAuthor

@JakeRadMSFT
I have these 3 checks in place in my IMonitor class:
a, running time (for this I think you would need the trial start time, that I have just removed)
b, memory usage (you already have this with AutoMLExperiment.SetMaximumMemoryUsageInMegaByte)
c, virtual memory availability (this is Windows only, at least until the Core team implements a more general approach)

Comment threadsrc/Microsoft.ML.AutoML/AutoMLExperiment/IMonitor.cs Outdated
Comment threadsrc/Microsoft.ML.AutoML/AutoMLExperiment/AutoMLExperiment.cs Outdated
LittleLittleCloudand others added 3 commits January 3, 2023 11:58
…on-and-resource-monitoring-to-imonitor
move ReportTrialResourceUsage to IPerformanceMonitor and rename it to OnPerformanceMetricsUpdatedHandler
@andrasfuchs

Copy link
Copy Markdown
ContributorAuthor

Thank you for the PR, I already modified my own code to test it.

It looks good, I just have a few remarks:

  • I inherited my own IPerformanceMonitor implementation from DefaultPerformanceMonitor, because I wanted to keep the monitoring methods. It took my a while to realize how to get its constructor parameters and I ended up copy-pasting code.
  • Having the IMonitor and IPerformanceMonitor in separate classes isn't an issue, but since we don't have the starting time of the trial in TrialSettings anymore, I needed to measure the trial's runtime based on its first performance metrics update event handler call which isn't precise, because that event isn't guarantied to be called when a new trial starts. It's probably alright, I don't think it has a practical drawbacks even if the update period is long.
  • The performance metrics updated event handler is called even after the TrialCompleted event and before the next trial's TrialRunning event. It might cause some strange states if the IPerformanceMonitor tries to cancel the trial during this period (due timeout for example). This is more obvious if the update period is small.

@LittleLittleCloud

Copy link
Copy Markdown
Member

Good to hear that the new change in IPerformanceMonitor works for you. And let me know if you met any other issues, whether or not related to this one.

The performance metrics updated event handler is called even after the TrialCompleted event and before the next trial's TrialRunning event. It might cause some strange states if the IPerformanceMonitor tries to cancel the trial during this period (due timeout for example). This is more obvious if the update period is small.

That's a good point. And it's the side effect of moving ReportTrialResourceUsage from IMonitor to IPerformanceMonitor. So as a work-around you might want to check if taskCancellationSource is still cancellable before cancelling a trial. And it will be a good idea to stop firing event from IPerformanceMonitor once the training (pipeline.fit) has stopped.

I inherited my own IPerformanceMonitor implementation from DefaultPerformanceMonitor, because I wanted to keep the monitoring methods. It took my a while to realize how to get its constructor parameters and I ended up copy-pasting code.

That's one way, you can also just list the parameter in constructor and rely on dependency injection if all parameters are available in AutoMLExperiment.ServiceCollection.

@andrasfuchs

Copy link
Copy Markdown
ContributorAuthor

I added a small change to pause the performance monitor when the trial is completed and I also trigger the PerformanceMetricsUpdated event just after the trial has started. These changes would solve the two issues I mentioned above.

I have a few more notes:
(1) I think that setting a custom IPerformanceMonitor could be simpler from the user's perspective: could we use a similar syntax as we have for IMonitor with AutoMLExperiment.SetMonitor(IMonitor)?

(2) The other thing I think would be useful in the TrialSettings object is a string array with the estimators of the pipeline. It's an important information, but we only have it from _pipeline.ToString(trialSettings.Parameter) in the IMonitor and IPerformanceMonitor event handlers at the moment. Since just the last estimator is usually changing, I still need to do some string-splitting to get the useful, last one, this isn't ideal. Would you be open to adding a string[] Estimators to TrialSettings?

(3) I know that you preferred the removal of StartedAtUtc property from TrialSettings before, but I still think it would be practical to include it there, because checking the runtime of the trial in a performance monitor is typical I think, and it would make that a lot simpler for the end user. What do you think?

(4) The CPU measurement seems to be off a little, sometimes it goes above 100%:
image

@LittleLittleCloud

LittleLittleCloud commented Jan 6, 2023

Copy link
Copy Markdown
Member

Thanks for the new change that you made, it's really awesome.

(1) I think that setting a custom IPerformanceMonitor could be simpler from the user's perspective: could we use a similar syntax as we have for IMonitor with AutoMLExperiment.SetMonitor(IMonitor)?

IPerformanceMonitor has a different lifespan compared to IMonitor. IPerformanceMonitor's lifespan is transient and AutoMLExperiment will create a new instance of it every time it starts a new trial. While the lifetime of IMonitor will live the entire training process. Because of that, we can't just pass an instance of IPerformanceMonitor like how IMonitor does.

(2) The other thing I think would be useful in the TrialSettings object is a string array with the estimators of the pipeline. It's an important information, but we only have it from _pipeline.ToString(trialSettings.Parameter) in the IMonitor and IPerformanceMonitor event handlers at the moment. Since just the last estimator is usually changing, I still need to do some string-splitting to get the useful, last one, this isn't ideal. Would you be open to adding a string[] Estimators to TrialSettings?

Would it be helpful if we provide a helper function for SweepablePipeline which extracts a list of Estimators from Parameter otherwise? I'm still hesitant to add a string[] Estimator in trialSetting class since it's duplicated information.

(3) I know that you preferred the removal of StartedAtUtc property from TrialSettings before, but I still think it would be practical to include it there, because checking the runtime of the trial in a performance monitor is typical I think, and it would make that a lot simpler for the end user. What do you think?

Sure, and maybe add an EndAtUtc as well

(4) The CPU measurement seems to be off a little, sometimes it goes above 100%:

Might be data racing in SampleCpuAndMemoryUsage? Maybe we need to add a lock to that handker

@andrasfuchs

Copy link
Copy Markdown
ContributorAuthor

(1) I didn't know that, makes sense, thank you for the explanation!

(2) A helper function to get the estimator names from the Parameter would be perfect.

(3) I added StartedAtUtc and EndedAtUtc to TrialSettings.

(4) I tried to encapsulate the whole SampleCpuAndMemoryUsage() method in a lock {}, but it didn't solve the problem.
image

(5) The cancellation logic stopped working somewhere along the way:
image
I call the trialCancellationTokenSource.Cancel(), but the trial keeps running. Do you know why?

@LittleLittleCloud

Copy link
Copy Markdown
Member

(4) I tried to encapsulate the whole SampleCpuAndMemoryUsage() method in a lock {}, but it didn't solve the problem.

The CPU usage is calculated using the following formula:

cpuUsage = (Process.TotalProcessorTime - _totalCpuProcessorTime) / (Environment.ProcessorCount * _checkIntervalInMilliseconds)

where _totalCpuProcessorTime is the most recent sampled Process.TotalProcessorTime.

if cpuUsage is over 100%, one situation is (Environment.ProcessorCount * _checkIntervalInMilliseconds) being smaller than actual sampling interval, which might cause by waiting and getting _lock?

To verify that hypothesis, maybe the formula needs to be updated to

cpuUsage = (Process.TotalProcessorTime - _totalCpuProcessorTime) / (Environment.ProcessorCount * (Utc.Now - _lastSamplingUtcTime)

@LittleLittleCloud
LittleLittleCloud merged commit d239fda into dotnet:mainFeb 9, 2023
@JakeRadMSFT

Copy link
Copy Markdown
Member

@LittleLittleCloud@andrasfuchs thanks for getting this in!

@andrasfuchs

Copy link
Copy Markdown
ContributorAuthor

Yes, thank you @LittleLittleCloud for finishing this up and thank you @JakeRadMSFT and @luisquintanilla for keeping an eye on this!

@ghostghost locked as resolved and limited conversation to collaborators Mar 16, 2023
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add resource (CPU,RAM,GPU,thread count) monitoring to AutoML experiments

3 participants

@andrasfuchs@JakeRadMSFT@LittleLittleCloud
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Add continuous resource monitoring to AutoML.IMonitor - #6520

Merged
LittleLittleCloud merged 26 commits into
dotnet:mainfrom
andrasfuchs:add-cancellation-and-resource-monitoring-to-imonitor
Feb 9, 2023
Merged

Add continuous resource monitoring to AutoML.IMonitor#6520
LittleLittleCloud merged 26 commits into
dotnet:mainfrom
andrasfuchs:add-cancellation-and-resource-monitoring-to-imonitor

Conversation

@andrasfuchs

Copy link
Copy Markdown
Contributor

Partially fixes#6320, #6426, #6425 and helps investigating further problems with AutoML trials.

This PR lets the user cancel trials based various performance metrics. It changed my user experience with AutoML experiments significantly, because I regularly had crashes and failed trials when I tried to run experiments for a long time. With this modification I could implement my own IMonitor and react to changes in memory demand, virtual memory usage, remaining disk space and I could skip a trial if it was running unexpectedly long without terminating the experiment.

Before the modifications in this PR my experiments usually stopped with an error in a few hours, but since I have much more control over the experiment with these modifications I could run much longer experiment without any issues.

On the technical level I moved the performance-related properties of the TrialSettings class into a separate TrialPerformanceMetrics subclass, I added a timer to check for those CPU and memory metrics, and I added a new ReportTrialResourceUsage event to the IMonitor class that is called periodically during the trial-run. I also added the CancellationTokenSource class of the trial to the TrialSettings so that the user can skip a trial if they wish.

You can also check my custom IMonitor implementation where the resource monitoring and cancellation logic is demonstrated.

@andrasfuchsandrasfuchs changed the title Add cancellation and resource monitoring to imonitorAdd continuous resource monitoring to AutoML.IMonitorDec 6, 2022
@codecov

codecovBot commented Dec 6, 2022

Copy link
Copy Markdown

Codecov Report

Merging #6520 (509f963) into main (8c0ceaf) will increase coverage by 0.03%.
The diff coverage is 46.15%.

❗ Current head 509f963 differs from pull request most recent head 5a27af4. Consider uploading reports for the commit 5a27af4 to get more accurate results

Additional details and impacted files
@@ Coverage Diff @@## main #6520 +/- ##
==========================================
+ Coverage 68.40% 68.43% +0.03% 
==========================================
Files 1174 1174 Lines 248045 248047 +2 Branches 25909 25909 ==========================================
+ Hits 169670 169756 +86 + Misses 71604 71538 -66 + Partials 6771 6753 -18 
FlagCoverage Δ
Debug68.43% <46.15%> (+0.03%)⬆️
production62.83% <ø> (+0.05%)⬆️
test88.88% <46.15%> (-0.05%)⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Impacted FilesCoverage Δ
src/Microsoft.ML.SearchSpace/Parameter.cs74.05% <ø> (ø)
...Microsoft.ML.AutoML.Tests/AutoMLExperimentTests.cs89.34% <30.00%> (-7.19%)⬇️
test/Microsoft.ML.AutoML.Tests/AutoFitTests.cs81.64% <100.00%> (ø)
...c/Microsoft.ML.FastTree/Utils/ThreadTaskManager.cs79.48% <0.00%> (-20.52%)⬇️
src/Microsoft.ML.Core/Data/ProgressReporter.cs70.95% <0.00%> (-6.99%)⬇️
src/Microsoft.ML.Maml/MAML.cs25.38% <0.00%> (-1.53%)⬇️
...ML.Transforms/Text/StopWordsRemovingTransformer.cs86.38% <0.00%> (-0.15%)⬇️
...soft.ML.Data/DataLoadSave/Text/TextLoaderCursor.cs89.77% <0.00%> (+0.15%)⬆️
src/Microsoft.ML.Data/Utils/LossFunctions.cs67.35% <0.00%> (+0.51%)⬆️
... and 5 more

@JakeRadMSFT

Copy link
Copy Markdown
Member

@LittleLittleCloud can you help review this?

Thanks @andrasfuchs for contributing!

@LittleLittleCloud

LittleLittleCloud commented Dec 21, 2022

Copy link
Copy Markdown
Member

@andrasfuchs I just come back from vacation, sorry for the late reply.

Comment threadsrc/Microsoft.ML.AutoML/AutoMLExperiment/AutoMLExperiment.cs
@JakeRadMSFT

Copy link
Copy Markdown
Member

@LittleLittleCloud would we want to ask @andrasfuchs to bring over any of his resource early quitting code?

@andrasfuchs

Copy link
Copy Markdown
ContributorAuthor

@JakeRadMSFT
I have these 3 checks in place in my IMonitor class:
a, running time (for this I think you would need the trial start time, that I have just removed)
b, memory usage (you already have this with AutoMLExperiment.SetMaximumMemoryUsageInMegaByte)
c, virtual memory availability (this is Windows only, at least until the Core team implements a more general approach)

Comment threadsrc/Microsoft.ML.AutoML/AutoMLExperiment/IMonitor.cs Outdated
Comment threadsrc/Microsoft.ML.AutoML/AutoMLExperiment/AutoMLExperiment.cs Outdated
LittleLittleCloudand others added 3 commits January 3, 2023 11:58
…on-and-resource-monitoring-to-imonitor
move ReportTrialResourceUsage to IPerformanceMonitor and rename it to OnPerformanceMetricsUpdatedHandler
@andrasfuchs

Copy link
Copy Markdown
ContributorAuthor

Thank you for the PR, I already modified my own code to test it.

It looks good, I just have a few remarks:

  • I inherited my own IPerformanceMonitor implementation from DefaultPerformanceMonitor, because I wanted to keep the monitoring methods. It took my a while to realize how to get its constructor parameters and I ended up copy-pasting code.
  • Having the IMonitor and IPerformanceMonitor in separate classes isn't an issue, but since we don't have the starting time of the trial in TrialSettings anymore, I needed to measure the trial's runtime based on its first performance metrics update event handler call which isn't precise, because that event isn't guarantied to be called when a new trial starts. It's probably alright, I don't think it has a practical drawbacks even if the update period is long.
  • The performance metrics updated event handler is called even after the TrialCompleted event and before the next trial's TrialRunning event. It might cause some strange states if the IPerformanceMonitor tries to cancel the trial during this period (due timeout for example). This is more obvious if the update period is small.

@LittleLittleCloud

Copy link
Copy Markdown
Member

Good to hear that the new change in IPerformanceMonitor works for you. And let me know if you met any other issues, whether or not related to this one.

The performance metrics updated event handler is called even after the TrialCompleted event and before the next trial's TrialRunning event. It might cause some strange states if the IPerformanceMonitor tries to cancel the trial during this period (due timeout for example). This is more obvious if the update period is small.

That's a good point. And it's the side effect of moving ReportTrialResourceUsage from IMonitor to IPerformanceMonitor. So as a work-around you might want to check if taskCancellationSource is still cancellable before cancelling a trial. And it will be a good idea to stop firing event from IPerformanceMonitor once the training (pipeline.fit) has stopped.

I inherited my own IPerformanceMonitor implementation from DefaultPerformanceMonitor, because I wanted to keep the monitoring methods. It took my a while to realize how to get its constructor parameters and I ended up copy-pasting code.

That's one way, you can also just list the parameter in constructor and rely on dependency injection if all parameters are available in AutoMLExperiment.ServiceCollection.

@andrasfuchs

Copy link
Copy Markdown
ContributorAuthor

I added a small change to pause the performance monitor when the trial is completed and I also trigger the PerformanceMetricsUpdated event just after the trial has started. These changes would solve the two issues I mentioned above.

I have a few more notes:
(1) I think that setting a custom IPerformanceMonitor could be simpler from the user's perspective: could we use a similar syntax as we have for IMonitor with AutoMLExperiment.SetMonitor(IMonitor)?

(2) The other thing I think would be useful in the TrialSettings object is a string array with the estimators of the pipeline. It's an important information, but we only have it from _pipeline.ToString(trialSettings.Parameter) in the IMonitor and IPerformanceMonitor event handlers at the moment. Since just the last estimator is usually changing, I still need to do some string-splitting to get the useful, last one, this isn't ideal. Would you be open to adding a string[] Estimators to TrialSettings?

(3) I know that you preferred the removal of StartedAtUtc property from TrialSettings before, but I still think it would be practical to include it there, because checking the runtime of the trial in a performance monitor is typical I think, and it would make that a lot simpler for the end user. What do you think?

(4) The CPU measurement seems to be off a little, sometimes it goes above 100%:
image

@LittleLittleCloud

LittleLittleCloud commented Jan 6, 2023

Copy link
Copy Markdown
Member

Thanks for the new change that you made, it's really awesome.

(1) I think that setting a custom IPerformanceMonitor could be simpler from the user's perspective: could we use a similar syntax as we have for IMonitor with AutoMLExperiment.SetMonitor(IMonitor)?

IPerformanceMonitor has a different lifespan compared to IMonitor. IPerformanceMonitor's lifespan is transient and AutoMLExperiment will create a new instance of it every time it starts a new trial. While the lifetime of IMonitor will live the entire training process. Because of that, we can't just pass an instance of IPerformanceMonitor like how IMonitor does.

(2) The other thing I think would be useful in the TrialSettings object is a string array with the estimators of the pipeline. It's an important information, but we only have it from _pipeline.ToString(trialSettings.Parameter) in the IMonitor and IPerformanceMonitor event handlers at the moment. Since just the last estimator is usually changing, I still need to do some string-splitting to get the useful, last one, this isn't ideal. Would you be open to adding a string[] Estimators to TrialSettings?

Would it be helpful if we provide a helper function for SweepablePipeline which extracts a list of Estimators from Parameter otherwise? I'm still hesitant to add a string[] Estimator in trialSetting class since it's duplicated information.

(3) I know that you preferred the removal of StartedAtUtc property from TrialSettings before, but I still think it would be practical to include it there, because checking the runtime of the trial in a performance monitor is typical I think, and it would make that a lot simpler for the end user. What do you think?

Sure, and maybe add an EndAtUtc as well

(4) The CPU measurement seems to be off a little, sometimes it goes above 100%:

Might be data racing in SampleCpuAndMemoryUsage? Maybe we need to add a lock to that handker

@andrasfuchs

Copy link
Copy Markdown
ContributorAuthor

(1) I didn't know that, makes sense, thank you for the explanation!

(2) A helper function to get the estimator names from the Parameter would be perfect.

(3) I added StartedAtUtc and EndedAtUtc to TrialSettings.

(4) I tried to encapsulate the whole SampleCpuAndMemoryUsage() method in a lock {}, but it didn't solve the problem.
image

(5) The cancellation logic stopped working somewhere along the way:
image
I call the trialCancellationTokenSource.Cancel(), but the trial keeps running. Do you know why?

@LittleLittleCloud

Copy link
Copy Markdown
Member

(4) I tried to encapsulate the whole SampleCpuAndMemoryUsage() method in a lock {}, but it didn't solve the problem.

The CPU usage is calculated using the following formula:

cpuUsage = (Process.TotalProcessorTime - _totalCpuProcessorTime) / (Environment.ProcessorCount * _checkIntervalInMilliseconds)

where _totalCpuProcessorTime is the most recent sampled Process.TotalProcessorTime.

if cpuUsage is over 100%, one situation is (Environment.ProcessorCount * _checkIntervalInMilliseconds) being smaller than actual sampling interval, which might cause by waiting and getting _lock?

To verify that hypothesis, maybe the formula needs to be updated to

cpuUsage = (Process.TotalProcessorTime - _totalCpuProcessorTime) / (Environment.ProcessorCount * (Utc.Now - _lastSamplingUtcTime)

@LittleLittleCloud
LittleLittleCloud merged commit d239fda into dotnet:mainFeb 9, 2023
@JakeRadMSFT

Copy link
Copy Markdown
Member

@LittleLittleCloud@andrasfuchs thanks for getting this in!

@andrasfuchs

Copy link
Copy Markdown
ContributorAuthor

Yes, thank you @LittleLittleCloud for finishing this up and thank you @JakeRadMSFT and @luisquintanilla for keeping an eye on this!

@ghostghost locked as resolved and limited conversation to collaborators Mar 16, 2023
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add resource (CPU,RAM,GPU,thread count) monitoring to AutoML experiments

3 participants

@andrasfuchs@JakeRadMSFT@LittleLittleCloud
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Add continuous resource monitoring to AutoML.IMonitor - #6520

Merged
LittleLittleCloud merged 26 commits into
dotnet:mainfrom
andrasfuchs:add-cancellation-and-resource-monitoring-to-imonitor
Feb 9, 2023
Merged

Add continuous resource monitoring to AutoML.IMonitor#6520
LittleLittleCloud merged 26 commits into
dotnet:mainfrom
andrasfuchs:add-cancellation-and-resource-monitoring-to-imonitor

Conversation

@andrasfuchs

Copy link
Copy Markdown
Contributor

Partially fixes#6320, #6426, #6425 and helps investigating further problems with AutoML trials.

This PR lets the user cancel trials based various performance metrics. It changed my user experience with AutoML experiments significantly, because I regularly had crashes and failed trials when I tried to run experiments for a long time. With this modification I could implement my own IMonitor and react to changes in memory demand, virtual memory usage, remaining disk space and I could skip a trial if it was running unexpectedly long without terminating the experiment.

Before the modifications in this PR my experiments usually stopped with an error in a few hours, but since I have much more control over the experiment with these modifications I could run much longer experiment without any issues.

On the technical level I moved the performance-related properties of the TrialSettings class into a separate TrialPerformanceMetrics subclass, I added a timer to check for those CPU and memory metrics, and I added a new ReportTrialResourceUsage event to the IMonitor class that is called periodically during the trial-run. I also added the CancellationTokenSource class of the trial to the TrialSettings so that the user can skip a trial if they wish.

You can also check my custom IMonitor implementation where the resource monitoring and cancellation logic is demonstrated.

@andrasfuchsandrasfuchs changed the title Add cancellation and resource monitoring to imonitorAdd continuous resource monitoring to AutoML.IMonitorDec 6, 2022
@codecov

codecovBot commented Dec 6, 2022

Copy link
Copy Markdown

Codecov Report

Merging #6520 (509f963) into main (8c0ceaf) will increase coverage by 0.03%.
The diff coverage is 46.15%.

❗ Current head 509f963 differs from pull request most recent head 5a27af4. Consider uploading reports for the commit 5a27af4 to get more accurate results

Additional details and impacted files
@@ Coverage Diff @@## main #6520 +/- ##
==========================================
+ Coverage 68.40% 68.43% +0.03% 
==========================================
Files 1174 1174 Lines 248045 248047 +2 Branches 25909 25909 ==========================================
+ Hits 169670 169756 +86 + Misses 71604 71538 -66 + Partials 6771 6753 -18 
FlagCoverage Δ
Debug68.43% <46.15%> (+0.03%)⬆️
production62.83% <ø> (+0.05%)⬆️
test88.88% <46.15%> (-0.05%)⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Impacted FilesCoverage Δ
src/Microsoft.ML.SearchSpace/Parameter.cs74.05% <ø> (ø)
...Microsoft.ML.AutoML.Tests/AutoMLExperimentTests.cs89.34% <30.00%> (-7.19%)⬇️
test/Microsoft.ML.AutoML.Tests/AutoFitTests.cs81.64% <100.00%> (ø)
...c/Microsoft.ML.FastTree/Utils/ThreadTaskManager.cs79.48% <0.00%> (-20.52%)⬇️
src/Microsoft.ML.Core/Data/ProgressReporter.cs70.95% <0.00%> (-6.99%)⬇️
src/Microsoft.ML.Maml/MAML.cs25.38% <0.00%> (-1.53%)⬇️
...ML.Transforms/Text/StopWordsRemovingTransformer.cs86.38% <0.00%> (-0.15%)⬇️
...soft.ML.Data/DataLoadSave/Text/TextLoaderCursor.cs89.77% <0.00%> (+0.15%)⬆️
src/Microsoft.ML.Data/Utils/LossFunctions.cs67.35% <0.00%> (+0.51%)⬆️
... and 5 more

@JakeRadMSFT

Copy link
Copy Markdown
Member

@LittleLittleCloud can you help review this?

Thanks @andrasfuchs for contributing!

@LittleLittleCloud

LittleLittleCloud commented Dec 21, 2022

Copy link
Copy Markdown
Member

@andrasfuchs I just come back from vacation, sorry for the late reply.

Comment threadsrc/Microsoft.ML.AutoML/AutoMLExperiment/AutoMLExperiment.cs
@JakeRadMSFT

Copy link
Copy Markdown
Member

@LittleLittleCloud would we want to ask @andrasfuchs to bring over any of his resource early quitting code?

@andrasfuchs

Copy link
Copy Markdown
ContributorAuthor

@JakeRadMSFT
I have these 3 checks in place in my IMonitor class:
a, running time (for this I think you would need the trial start time, that I have just removed)
b, memory usage (you already have this with AutoMLExperiment.SetMaximumMemoryUsageInMegaByte)
c, virtual memory availability (this is Windows only, at least until the Core team implements a more general approach)

Comment threadsrc/Microsoft.ML.AutoML/AutoMLExperiment/IMonitor.cs Outdated
Comment threadsrc/Microsoft.ML.AutoML/AutoMLExperiment/AutoMLExperiment.cs Outdated
LittleLittleCloudand others added 3 commits January 3, 2023 11:58
…on-and-resource-monitoring-to-imonitor
move ReportTrialResourceUsage to IPerformanceMonitor and rename it to OnPerformanceMetricsUpdatedHandler
@andrasfuchs

Copy link
Copy Markdown
ContributorAuthor

Thank you for the PR, I already modified my own code to test it.

It looks good, I just have a few remarks:

  • I inherited my own IPerformanceMonitor implementation from DefaultPerformanceMonitor, because I wanted to keep the monitoring methods. It took my a while to realize how to get its constructor parameters and I ended up copy-pasting code.
  • Having the IMonitor and IPerformanceMonitor in separate classes isn't an issue, but since we don't have the starting time of the trial in TrialSettings anymore, I needed to measure the trial's runtime based on its first performance metrics update event handler call which isn't precise, because that event isn't guarantied to be called when a new trial starts. It's probably alright, I don't think it has a practical drawbacks even if the update period is long.
  • The performance metrics updated event handler is called even after the TrialCompleted event and before the next trial's TrialRunning event. It might cause some strange states if the IPerformanceMonitor tries to cancel the trial during this period (due timeout for example). This is more obvious if the update period is small.

@LittleLittleCloud

Copy link
Copy Markdown
Member

Good to hear that the new change in IPerformanceMonitor works for you. And let me know if you met any other issues, whether or not related to this one.

The performance metrics updated event handler is called even after the TrialCompleted event and before the next trial's TrialRunning event. It might cause some strange states if the IPerformanceMonitor tries to cancel the trial during this period (due timeout for example). This is more obvious if the update period is small.

That's a good point. And it's the side effect of moving ReportTrialResourceUsage from IMonitor to IPerformanceMonitor. So as a work-around you might want to check if taskCancellationSource is still cancellable before cancelling a trial. And it will be a good idea to stop firing event from IPerformanceMonitor once the training (pipeline.fit) has stopped.

I inherited my own IPerformanceMonitor implementation from DefaultPerformanceMonitor, because I wanted to keep the monitoring methods. It took my a while to realize how to get its constructor parameters and I ended up copy-pasting code.

That's one way, you can also just list the parameter in constructor and rely on dependency injection if all parameters are available in AutoMLExperiment.ServiceCollection.

@andrasfuchs

Copy link
Copy Markdown
ContributorAuthor

I added a small change to pause the performance monitor when the trial is completed and I also trigger the PerformanceMetricsUpdated event just after the trial has started. These changes would solve the two issues I mentioned above.

I have a few more notes:
(1) I think that setting a custom IPerformanceMonitor could be simpler from the user's perspective: could we use a similar syntax as we have for IMonitor with AutoMLExperiment.SetMonitor(IMonitor)?

(2) The other thing I think would be useful in the TrialSettings object is a string array with the estimators of the pipeline. It's an important information, but we only have it from _pipeline.ToString(trialSettings.Parameter) in the IMonitor and IPerformanceMonitor event handlers at the moment. Since just the last estimator is usually changing, I still need to do some string-splitting to get the useful, last one, this isn't ideal. Would you be open to adding a string[] Estimators to TrialSettings?

(3) I know that you preferred the removal of StartedAtUtc property from TrialSettings before, but I still think it would be practical to include it there, because checking the runtime of the trial in a performance monitor is typical I think, and it would make that a lot simpler for the end user. What do you think?

(4) The CPU measurement seems to be off a little, sometimes it goes above 100%:
image

@LittleLittleCloud

LittleLittleCloud commented Jan 6, 2023

Copy link
Copy Markdown
Member

Thanks for the new change that you made, it's really awesome.

(1) I think that setting a custom IPerformanceMonitor could be simpler from the user's perspective: could we use a similar syntax as we have for IMonitor with AutoMLExperiment.SetMonitor(IMonitor)?

IPerformanceMonitor has a different lifespan compared to IMonitor. IPerformanceMonitor's lifespan is transient and AutoMLExperiment will create a new instance of it every time it starts a new trial. While the lifetime of IMonitor will live the entire training process. Because of that, we can't just pass an instance of IPerformanceMonitor like how IMonitor does.

(2) The other thing I think would be useful in the TrialSettings object is a string array with the estimators of the pipeline. It's an important information, but we only have it from _pipeline.ToString(trialSettings.Parameter) in the IMonitor and IPerformanceMonitor event handlers at the moment. Since just the last estimator is usually changing, I still need to do some string-splitting to get the useful, last one, this isn't ideal. Would you be open to adding a string[] Estimators to TrialSettings?

Would it be helpful if we provide a helper function for SweepablePipeline which extracts a list of Estimators from Parameter otherwise? I'm still hesitant to add a string[] Estimator in trialSetting class since it's duplicated information.

(3) I know that you preferred the removal of StartedAtUtc property from TrialSettings before, but I still think it would be practical to include it there, because checking the runtime of the trial in a performance monitor is typical I think, and it would make that a lot simpler for the end user. What do you think?

Sure, and maybe add an EndAtUtc as well

(4) The CPU measurement seems to be off a little, sometimes it goes above 100%:

Might be data racing in SampleCpuAndMemoryUsage? Maybe we need to add a lock to that handker

@andrasfuchs

Copy link
Copy Markdown
ContributorAuthor

(1) I didn't know that, makes sense, thank you for the explanation!

(2) A helper function to get the estimator names from the Parameter would be perfect.

(3) I added StartedAtUtc and EndedAtUtc to TrialSettings.

(4) I tried to encapsulate the whole SampleCpuAndMemoryUsage() method in a lock {}, but it didn't solve the problem.
image

(5) The cancellation logic stopped working somewhere along the way:
image
I call the trialCancellationTokenSource.Cancel(), but the trial keeps running. Do you know why?

@LittleLittleCloud

Copy link
Copy Markdown
Member

(4) I tried to encapsulate the whole SampleCpuAndMemoryUsage() method in a lock {}, but it didn't solve the problem.

The CPU usage is calculated using the following formula:

cpuUsage = (Process.TotalProcessorTime - _totalCpuProcessorTime) / (Environment.ProcessorCount * _checkIntervalInMilliseconds)

where _totalCpuProcessorTime is the most recent sampled Process.TotalProcessorTime.

if cpuUsage is over 100%, one situation is (Environment.ProcessorCount * _checkIntervalInMilliseconds) being smaller than actual sampling interval, which might cause by waiting and getting _lock?

To verify that hypothesis, maybe the formula needs to be updated to

cpuUsage = (Process.TotalProcessorTime - _totalCpuProcessorTime) / (Environment.ProcessorCount * (Utc.Now - _lastSamplingUtcTime)

@LittleLittleCloud
LittleLittleCloud merged commit d239fda into dotnet:mainFeb 9, 2023
@JakeRadMSFT

Copy link
Copy Markdown
Member

@LittleLittleCloud@andrasfuchs thanks for getting this in!

@andrasfuchs

Copy link
Copy Markdown
ContributorAuthor

Yes, thank you @LittleLittleCloud for finishing this up and thank you @JakeRadMSFT and @luisquintanilla for keeping an eye on this!

@ghostghost locked as resolved and limited conversation to collaborators Mar 16, 2023
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add resource (CPU,RAM,GPU,thread count) monitoring to AutoML experiments

3 participants

@andrasfuchs@JakeRadMSFT@LittleLittleCloud
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Add continuous resource monitoring to AutoML.IMonitor - #6520

Merged
LittleLittleCloud merged 26 commits into
dotnet:mainfrom
andrasfuchs:add-cancellation-and-resource-monitoring-to-imonitor
Feb 9, 2023
Merged

Add continuous resource monitoring to AutoML.IMonitor#6520
LittleLittleCloud merged 26 commits into
dotnet:mainfrom
andrasfuchs:add-cancellation-and-resource-monitoring-to-imonitor

Conversation

@andrasfuchs

Copy link
Copy Markdown
Contributor

Partially fixes#6320, #6426, #6425 and helps investigating further problems with AutoML trials.

This PR lets the user cancel trials based various performance metrics. It changed my user experience with AutoML experiments significantly, because I regularly had crashes and failed trials when I tried to run experiments for a long time. With this modification I could implement my own IMonitor and react to changes in memory demand, virtual memory usage, remaining disk space and I could skip a trial if it was running unexpectedly long without terminating the experiment.

Before the modifications in this PR my experiments usually stopped with an error in a few hours, but since I have much more control over the experiment with these modifications I could run much longer experiment without any issues.

On the technical level I moved the performance-related properties of the TrialSettings class into a separate TrialPerformanceMetrics subclass, I added a timer to check for those CPU and memory metrics, and I added a new ReportTrialResourceUsage event to the IMonitor class that is called periodically during the trial-run. I also added the CancellationTokenSource class of the trial to the TrialSettings so that the user can skip a trial if they wish.

You can also check my custom IMonitor implementation where the resource monitoring and cancellation logic is demonstrated.

@andrasfuchsandrasfuchs changed the title Add cancellation and resource monitoring to imonitorAdd continuous resource monitoring to AutoML.IMonitorDec 6, 2022
@codecov

codecovBot commented Dec 6, 2022

Copy link
Copy Markdown

Codecov Report

Merging #6520 (509f963) into main (8c0ceaf) will increase coverage by 0.03%.
The diff coverage is 46.15%.

❗ Current head 509f963 differs from pull request most recent head 5a27af4. Consider uploading reports for the commit 5a27af4 to get more accurate results

Additional details and impacted files
@@ Coverage Diff @@## main #6520 +/- ##
==========================================
+ Coverage 68.40% 68.43% +0.03% 
==========================================
Files 1174 1174 Lines 248045 248047 +2 Branches 25909 25909 ==========================================
+ Hits 169670 169756 +86 + Misses 71604 71538 -66 + Partials 6771 6753 -18 
FlagCoverage Δ
Debug68.43% <46.15%> (+0.03%)⬆️
production62.83% <ø> (+0.05%)⬆️
test88.88% <46.15%> (-0.05%)⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Impacted FilesCoverage Δ
src/Microsoft.ML.SearchSpace/Parameter.cs74.05% <ø> (ø)
...Microsoft.ML.AutoML.Tests/AutoMLExperimentTests.cs89.34% <30.00%> (-7.19%)⬇️
test/Microsoft.ML.AutoML.Tests/AutoFitTests.cs81.64% <100.00%> (ø)
...c/Microsoft.ML.FastTree/Utils/ThreadTaskManager.cs79.48% <0.00%> (-20.52%)⬇️
src/Microsoft.ML.Core/Data/ProgressReporter.cs70.95% <0.00%> (-6.99%)⬇️
src/Microsoft.ML.Maml/MAML.cs25.38% <0.00%> (-1.53%)⬇️
...ML.Transforms/Text/StopWordsRemovingTransformer.cs86.38% <0.00%> (-0.15%)⬇️
...soft.ML.Data/DataLoadSave/Text/TextLoaderCursor.cs89.77% <0.00%> (+0.15%)⬆️
src/Microsoft.ML.Data/Utils/LossFunctions.cs67.35% <0.00%> (+0.51%)⬆️
... and 5 more

@JakeRadMSFT

Copy link
Copy Markdown
Member

@LittleLittleCloud can you help review this?

Thanks @andrasfuchs for contributing!

@LittleLittleCloud

LittleLittleCloud commented Dec 21, 2022

Copy link
Copy Markdown
Member

@andrasfuchs I just come back from vacation, sorry for the late reply.

Comment threadsrc/Microsoft.ML.AutoML/AutoMLExperiment/AutoMLExperiment.cs
@JakeRadMSFT

Copy link
Copy Markdown
Member

@LittleLittleCloud would we want to ask @andrasfuchs to bring over any of his resource early quitting code?

@andrasfuchs

Copy link
Copy Markdown
ContributorAuthor

@JakeRadMSFT
I have these 3 checks in place in my IMonitor class:
a, running time (for this I think you would need the trial start time, that I have just removed)
b, memory usage (you already have this with AutoMLExperiment.SetMaximumMemoryUsageInMegaByte)
c, virtual memory availability (this is Windows only, at least until the Core team implements a more general approach)

Comment threadsrc/Microsoft.ML.AutoML/AutoMLExperiment/IMonitor.cs Outdated
Comment threadsrc/Microsoft.ML.AutoML/AutoMLExperiment/AutoMLExperiment.cs Outdated
LittleLittleCloudand others added 3 commits January 3, 2023 11:58
…on-and-resource-monitoring-to-imonitor
move ReportTrialResourceUsage to IPerformanceMonitor and rename it to OnPerformanceMetricsUpdatedHandler
@andrasfuchs

Copy link
Copy Markdown
ContributorAuthor

Thank you for the PR, I already modified my own code to test it.

It looks good, I just have a few remarks:

  • I inherited my own IPerformanceMonitor implementation from DefaultPerformanceMonitor, because I wanted to keep the monitoring methods. It took my a while to realize how to get its constructor parameters and I ended up copy-pasting code.
  • Having the IMonitor and IPerformanceMonitor in separate classes isn't an issue, but since we don't have the starting time of the trial in TrialSettings anymore, I needed to measure the trial's runtime based on its first performance metrics update event handler call which isn't precise, because that event isn't guarantied to be called when a new trial starts. It's probably alright, I don't think it has a practical drawbacks even if the update period is long.
  • The performance metrics updated event handler is called even after the TrialCompleted event and before the next trial's TrialRunning event. It might cause some strange states if the IPerformanceMonitor tries to cancel the trial during this period (due timeout for example). This is more obvious if the update period is small.

@LittleLittleCloud

Copy link
Copy Markdown
Member

Good to hear that the new change in IPerformanceMonitor works for you. And let me know if you met any other issues, whether or not related to this one.

The performance metrics updated event handler is called even after the TrialCompleted event and before the next trial's TrialRunning event. It might cause some strange states if the IPerformanceMonitor tries to cancel the trial during this period (due timeout for example). This is more obvious if the update period is small.

That's a good point. And it's the side effect of moving ReportTrialResourceUsage from IMonitor to IPerformanceMonitor. So as a work-around you might want to check if taskCancellationSource is still cancellable before cancelling a trial. And it will be a good idea to stop firing event from IPerformanceMonitor once the training (pipeline.fit) has stopped.

I inherited my own IPerformanceMonitor implementation from DefaultPerformanceMonitor, because I wanted to keep the monitoring methods. It took my a while to realize how to get its constructor parameters and I ended up copy-pasting code.

That's one way, you can also just list the parameter in constructor and rely on dependency injection if all parameters are available in AutoMLExperiment.ServiceCollection.

@andrasfuchs

Copy link
Copy Markdown
ContributorAuthor

I added a small change to pause the performance monitor when the trial is completed and I also trigger the PerformanceMetricsUpdated event just after the trial has started. These changes would solve the two issues I mentioned above.

I have a few more notes:
(1) I think that setting a custom IPerformanceMonitor could be simpler from the user's perspective: could we use a similar syntax as we have for IMonitor with AutoMLExperiment.SetMonitor(IMonitor)?

(2) The other thing I think would be useful in the TrialSettings object is a string array with the estimators of the pipeline. It's an important information, but we only have it from _pipeline.ToString(trialSettings.Parameter) in the IMonitor and IPerformanceMonitor event handlers at the moment. Since just the last estimator is usually changing, I still need to do some string-splitting to get the useful, last one, this isn't ideal. Would you be open to adding a string[] Estimators to TrialSettings?

(3) I know that you preferred the removal of StartedAtUtc property from TrialSettings before, but I still think it would be practical to include it there, because checking the runtime of the trial in a performance monitor is typical I think, and it would make that a lot simpler for the end user. What do you think?

(4) The CPU measurement seems to be off a little, sometimes it goes above 100%:
image

@LittleLittleCloud

LittleLittleCloud commented Jan 6, 2023

Copy link
Copy Markdown
Member

Thanks for the new change that you made, it's really awesome.

(1) I think that setting a custom IPerformanceMonitor could be simpler from the user's perspective: could we use a similar syntax as we have for IMonitor with AutoMLExperiment.SetMonitor(IMonitor)?

IPerformanceMonitor has a different lifespan compared to IMonitor. IPerformanceMonitor's lifespan is transient and AutoMLExperiment will create a new instance of it every time it starts a new trial. While the lifetime of IMonitor will live the entire training process. Because of that, we can't just pass an instance of IPerformanceMonitor like how IMonitor does.

(2) The other thing I think would be useful in the TrialSettings object is a string array with the estimators of the pipeline. It's an important information, but we only have it from _pipeline.ToString(trialSettings.Parameter) in the IMonitor and IPerformanceMonitor event handlers at the moment. Since just the last estimator is usually changing, I still need to do some string-splitting to get the useful, last one, this isn't ideal. Would you be open to adding a string[] Estimators to TrialSettings?

Would it be helpful if we provide a helper function for SweepablePipeline which extracts a list of Estimators from Parameter otherwise? I'm still hesitant to add a string[] Estimator in trialSetting class since it's duplicated information.

(3) I know that you preferred the removal of StartedAtUtc property from TrialSettings before, but I still think it would be practical to include it there, because checking the runtime of the trial in a performance monitor is typical I think, and it would make that a lot simpler for the end user. What do you think?

Sure, and maybe add an EndAtUtc as well

(4) The CPU measurement seems to be off a little, sometimes it goes above 100%:

Might be data racing in SampleCpuAndMemoryUsage? Maybe we need to add a lock to that handker

@andrasfuchs

Copy link
Copy Markdown
ContributorAuthor

(1) I didn't know that, makes sense, thank you for the explanation!

(2) A helper function to get the estimator names from the Parameter would be perfect.

(3) I added StartedAtUtc and EndedAtUtc to TrialSettings.

(4) I tried to encapsulate the whole SampleCpuAndMemoryUsage() method in a lock {}, but it didn't solve the problem.
image

(5) The cancellation logic stopped working somewhere along the way:
image
I call the trialCancellationTokenSource.Cancel(), but the trial keeps running. Do you know why?

@LittleLittleCloud

Copy link
Copy Markdown
Member

(4) I tried to encapsulate the whole SampleCpuAndMemoryUsage() method in a lock {}, but it didn't solve the problem.

The CPU usage is calculated using the following formula:

cpuUsage = (Process.TotalProcessorTime - _totalCpuProcessorTime) / (Environment.ProcessorCount * _checkIntervalInMilliseconds)

where _totalCpuProcessorTime is the most recent sampled Process.TotalProcessorTime.

if cpuUsage is over 100%, one situation is (Environment.ProcessorCount * _checkIntervalInMilliseconds) being smaller than actual sampling interval, which might cause by waiting and getting _lock?

To verify that hypothesis, maybe the formula needs to be updated to

cpuUsage = (Process.TotalProcessorTime - _totalCpuProcessorTime) / (Environment.ProcessorCount * (Utc.Now - _lastSamplingUtcTime)

@LittleLittleCloud
LittleLittleCloud merged commit d239fda into dotnet:mainFeb 9, 2023
@JakeRadMSFT

Copy link
Copy Markdown
Member

@LittleLittleCloud@andrasfuchs thanks for getting this in!

@andrasfuchs

Copy link
Copy Markdown
ContributorAuthor

Yes, thank you @LittleLittleCloud for finishing this up and thank you @JakeRadMSFT and @luisquintanilla for keeping an eye on this!

@ghostghost locked as resolved and limited conversation to collaborators Mar 16, 2023
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add resource (CPU,RAM,GPU,thread count) monitoring to AutoML experiments

3 participants

@andrasfuchs@JakeRadMSFT@LittleLittleCloud
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Add continuous resource monitoring to AutoML.IMonitor - #6520

Merged
LittleLittleCloud merged 26 commits into
dotnet:mainfrom
andrasfuchs:add-cancellation-and-resource-monitoring-to-imonitor
Feb 9, 2023
Merged

Add continuous resource monitoring to AutoML.IMonitor#6520
LittleLittleCloud merged 26 commits into
dotnet:mainfrom
andrasfuchs:add-cancellation-and-resource-monitoring-to-imonitor

Conversation

@andrasfuchs

Copy link
Copy Markdown
Contributor

Partially fixes#6320, #6426, #6425 and helps investigating further problems with AutoML trials.

This PR lets the user cancel trials based various performance metrics. It changed my user experience with AutoML experiments significantly, because I regularly had crashes and failed trials when I tried to run experiments for a long time. With this modification I could implement my own IMonitor and react to changes in memory demand, virtual memory usage, remaining disk space and I could skip a trial if it was running unexpectedly long without terminating the experiment.

Before the modifications in this PR my experiments usually stopped with an error in a few hours, but since I have much more control over the experiment with these modifications I could run much longer experiment without any issues.

On the technical level I moved the performance-related properties of the TrialSettings class into a separate TrialPerformanceMetrics subclass, I added a timer to check for those CPU and memory metrics, and I added a new ReportTrialResourceUsage event to the IMonitor class that is called periodically during the trial-run. I also added the CancellationTokenSource class of the trial to the TrialSettings so that the user can skip a trial if they wish.

You can also check my custom IMonitor implementation where the resource monitoring and cancellation logic is demonstrated.

@andrasfuchsandrasfuchs changed the title Add cancellation and resource monitoring to imonitorAdd continuous resource monitoring to AutoML.IMonitorDec 6, 2022
@codecov

codecovBot commented Dec 6, 2022

Copy link
Copy Markdown

Codecov Report

Merging #6520 (509f963) into main (8c0ceaf) will increase coverage by 0.03%.
The diff coverage is 46.15%.

❗ Current head 509f963 differs from pull request most recent head 5a27af4. Consider uploading reports for the commit 5a27af4 to get more accurate results

Additional details and impacted files
@@ Coverage Diff @@## main #6520 +/- ##
==========================================
+ Coverage 68.40% 68.43% +0.03% 
==========================================
Files 1174 1174 Lines 248045 248047 +2 Branches 25909 25909 ==========================================
+ Hits 169670 169756 +86 + Misses 71604 71538 -66 + Partials 6771 6753 -18 
FlagCoverage Δ
Debug68.43% <46.15%> (+0.03%)⬆️
production62.83% <ø> (+0.05%)⬆️
test88.88% <46.15%> (-0.05%)⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Impacted FilesCoverage Δ
src/Microsoft.ML.SearchSpace/Parameter.cs74.05% <ø> (ø)
...Microsoft.ML.AutoML.Tests/AutoMLExperimentTests.cs89.34% <30.00%> (-7.19%)⬇️
test/Microsoft.ML.AutoML.Tests/AutoFitTests.cs81.64% <100.00%> (ø)
...c/Microsoft.ML.FastTree/Utils/ThreadTaskManager.cs79.48% <0.00%> (-20.52%)⬇️
src/Microsoft.ML.Core/Data/ProgressReporter.cs70.95% <0.00%> (-6.99%)⬇️
src/Microsoft.ML.Maml/MAML.cs25.38% <0.00%> (-1.53%)⬇️
...ML.Transforms/Text/StopWordsRemovingTransformer.cs86.38% <0.00%> (-0.15%)⬇️
...soft.ML.Data/DataLoadSave/Text/TextLoaderCursor.cs89.77% <0.00%> (+0.15%)⬆️
src/Microsoft.ML.Data/Utils/LossFunctions.cs67.35% <0.00%> (+0.51%)⬆️
... and 5 more

@JakeRadMSFT

Copy link
Copy Markdown
Member

@LittleLittleCloud can you help review this?

Thanks @andrasfuchs for contributing!

@LittleLittleCloud

LittleLittleCloud commented Dec 21, 2022

Copy link
Copy Markdown
Member

@andrasfuchs I just come back from vacation, sorry for the late reply.

Comment threadsrc/Microsoft.ML.AutoML/AutoMLExperiment/AutoMLExperiment.cs
@JakeRadMSFT

Copy link
Copy Markdown
Member

@LittleLittleCloud would we want to ask @andrasfuchs to bring over any of his resource early quitting code?

@andrasfuchs

Copy link
Copy Markdown
ContributorAuthor

@JakeRadMSFT
I have these 3 checks in place in my IMonitor class:
a, running time (for this I think you would need the trial start time, that I have just removed)
b, memory usage (you already have this with AutoMLExperiment.SetMaximumMemoryUsageInMegaByte)
c, virtual memory availability (this is Windows only, at least until the Core team implements a more general approach)

Comment threadsrc/Microsoft.ML.AutoML/AutoMLExperiment/IMonitor.cs Outdated
Comment threadsrc/Microsoft.ML.AutoML/AutoMLExperiment/AutoMLExperiment.cs Outdated
LittleLittleCloudand others added 3 commits January 3, 2023 11:58
…on-and-resource-monitoring-to-imonitor
move ReportTrialResourceUsage to IPerformanceMonitor and rename it to OnPerformanceMetricsUpdatedHandler
@andrasfuchs

Copy link
Copy Markdown
ContributorAuthor

Thank you for the PR, I already modified my own code to test it.

It looks good, I just have a few remarks:

  • I inherited my own IPerformanceMonitor implementation from DefaultPerformanceMonitor, because I wanted to keep the monitoring methods. It took my a while to realize how to get its constructor parameters and I ended up copy-pasting code.
  • Having the IMonitor and IPerformanceMonitor in separate classes isn't an issue, but since we don't have the starting time of the trial in TrialSettings anymore, I needed to measure the trial's runtime based on its first performance metrics update event handler call which isn't precise, because that event isn't guarantied to be called when a new trial starts. It's probably alright, I don't think it has a practical drawbacks even if the update period is long.
  • The performance metrics updated event handler is called even after the TrialCompleted event and before the next trial's TrialRunning event. It might cause some strange states if the IPerformanceMonitor tries to cancel the trial during this period (due timeout for example). This is more obvious if the update period is small.

@LittleLittleCloud

Copy link
Copy Markdown
Member

Good to hear that the new change in IPerformanceMonitor works for you. And let me know if you met any other issues, whether or not related to this one.

The performance metrics updated event handler is called even after the TrialCompleted event and before the next trial's TrialRunning event. It might cause some strange states if the IPerformanceMonitor tries to cancel the trial during this period (due timeout for example). This is more obvious if the update period is small.

That's a good point. And it's the side effect of moving ReportTrialResourceUsage from IMonitor to IPerformanceMonitor. So as a work-around you might want to check if taskCancellationSource is still cancellable before cancelling a trial. And it will be a good idea to stop firing event from IPerformanceMonitor once the training (pipeline.fit) has stopped.

I inherited my own IPerformanceMonitor implementation from DefaultPerformanceMonitor, because I wanted to keep the monitoring methods. It took my a while to realize how to get its constructor parameters and I ended up copy-pasting code.

That's one way, you can also just list the parameter in constructor and rely on dependency injection if all parameters are available in AutoMLExperiment.ServiceCollection.

@andrasfuchs

Copy link
Copy Markdown
ContributorAuthor

I added a small change to pause the performance monitor when the trial is completed and I also trigger the PerformanceMetricsUpdated event just after the trial has started. These changes would solve the two issues I mentioned above.

I have a few more notes:
(1) I think that setting a custom IPerformanceMonitor could be simpler from the user's perspective: could we use a similar syntax as we have for IMonitor with AutoMLExperiment.SetMonitor(IMonitor)?

(2) The other thing I think would be useful in the TrialSettings object is a string array with the estimators of the pipeline. It's an important information, but we only have it from _pipeline.ToString(trialSettings.Parameter) in the IMonitor and IPerformanceMonitor event handlers at the moment. Since just the last estimator is usually changing, I still need to do some string-splitting to get the useful, last one, this isn't ideal. Would you be open to adding a string[] Estimators to TrialSettings?

(3) I know that you preferred the removal of StartedAtUtc property from TrialSettings before, but I still think it would be practical to include it there, because checking the runtime of the trial in a performance monitor is typical I think, and it would make that a lot simpler for the end user. What do you think?

(4) The CPU measurement seems to be off a little, sometimes it goes above 100%:
image

@LittleLittleCloud

LittleLittleCloud commented Jan 6, 2023

Copy link
Copy Markdown
Member

Thanks for the new change that you made, it's really awesome.

(1) I think that setting a custom IPerformanceMonitor could be simpler from the user's perspective: could we use a similar syntax as we have for IMonitor with AutoMLExperiment.SetMonitor(IMonitor)?

IPerformanceMonitor has a different lifespan compared to IMonitor. IPerformanceMonitor's lifespan is transient and AutoMLExperiment will create a new instance of it every time it starts a new trial. While the lifetime of IMonitor will live the entire training process. Because of that, we can't just pass an instance of IPerformanceMonitor like how IMonitor does.

(2) The other thing I think would be useful in the TrialSettings object is a string array with the estimators of the pipeline. It's an important information, but we only have it from _pipeline.ToString(trialSettings.Parameter) in the IMonitor and IPerformanceMonitor event handlers at the moment. Since just the last estimator is usually changing, I still need to do some string-splitting to get the useful, last one, this isn't ideal. Would you be open to adding a string[] Estimators to TrialSettings?

Would it be helpful if we provide a helper function for SweepablePipeline which extracts a list of Estimators from Parameter otherwise? I'm still hesitant to add a string[] Estimator in trialSetting class since it's duplicated information.

(3) I know that you preferred the removal of StartedAtUtc property from TrialSettings before, but I still think it would be practical to include it there, because checking the runtime of the trial in a performance monitor is typical I think, and it would make that a lot simpler for the end user. What do you think?

Sure, and maybe add an EndAtUtc as well

(4) The CPU measurement seems to be off a little, sometimes it goes above 100%:

Might be data racing in SampleCpuAndMemoryUsage? Maybe we need to add a lock to that handker

@andrasfuchs

Copy link
Copy Markdown
ContributorAuthor

(1) I didn't know that, makes sense, thank you for the explanation!

(2) A helper function to get the estimator names from the Parameter would be perfect.

(3) I added StartedAtUtc and EndedAtUtc to TrialSettings.

(4) I tried to encapsulate the whole SampleCpuAndMemoryUsage() method in a lock {}, but it didn't solve the problem.
image

(5) The cancellation logic stopped working somewhere along the way:
image
I call the trialCancellationTokenSource.Cancel(), but the trial keeps running. Do you know why?

@LittleLittleCloud

Copy link
Copy Markdown
Member

(4) I tried to encapsulate the whole SampleCpuAndMemoryUsage() method in a lock {}, but it didn't solve the problem.

The CPU usage is calculated using the following formula:

cpuUsage = (Process.TotalProcessorTime - _totalCpuProcessorTime) / (Environment.ProcessorCount * _checkIntervalInMilliseconds)

where _totalCpuProcessorTime is the most recent sampled Process.TotalProcessorTime.

if cpuUsage is over 100%, one situation is (Environment.ProcessorCount * _checkIntervalInMilliseconds) being smaller than actual sampling interval, which might cause by waiting and getting _lock?

To verify that hypothesis, maybe the formula needs to be updated to

cpuUsage = (Process.TotalProcessorTime - _totalCpuProcessorTime) / (Environment.ProcessorCount * (Utc.Now - _lastSamplingUtcTime)

@LittleLittleCloud
LittleLittleCloud merged commit d239fda into dotnet:mainFeb 9, 2023
@JakeRadMSFT

Copy link
Copy Markdown
Member

@LittleLittleCloud@andrasfuchs thanks for getting this in!

@andrasfuchs

Copy link
Copy Markdown
ContributorAuthor

Yes, thank you @LittleLittleCloud for finishing this up and thank you @JakeRadMSFT and @luisquintanilla for keeping an eye on this!

@ghostghost locked as resolved and limited conversation to collaborators Mar 16, 2023
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add resource (CPU,RAM,GPU,thread count) monitoring to AutoML experiments

3 participants

@andrasfuchs@JakeRadMSFT@LittleLittleCloud