Skip to content

Fix mlnet performance benchmark timeouts by pre-provisioning the SSWE model into the Helix payload#5250

Draft
LoopedBard3 wants to merge 2 commits into
dotnet:mainfrom
LoopedBard3:loopedbard3/didactic-umbrella
Draft

Fix mlnet performance benchmark timeouts by pre-provisioning the SSWE model into the Helix payload#5250
LoopedBard3 wants to merge 2 commits into
dotnet:mainfrom
LoopedBard3:loopedbard3/didactic-umbrella

Conversation

@LoopedBard3

Copy link
Copy Markdown
Member

Problem

Every ML.NET performance benchmark in the performance-ci pipeline (public definitionId=38) is timing out. The whole mlnet work item hangs and is eventually killed at the work item timeout, discarding all ML.NET results, so every mlnet test shows as failed.

Example failing run: https://dev.azure.com/dnceng-public/public/_build/results?buildId=1478345

Root cause

The culprit is StochasticDualCoordinateAscentClassifierBench.TrainSentiment, which applies a pretrained SSWE word embedding. ML.NET downloads that model (sentiment.emd, ~70 MB) from aka.ms/mlnet-resources at benchmark runtime if it isn't already on disk. On the Helix machines that download now stalls (a hung connection, not a fast failure), so the benchmark hangs at // BeforeActualRun and the work item runs until it times out.

I reproduced this locally: with the model download blocked (stalling proxy) and the cache cleared, TrainSentiment hangs at // BeforeActualRun exactly like the Helix log; pre-provisioning the model + setting MICROSOFTML_RESOURCE_PATH runs it to completion with zero network. Updating Microsoft.ML (tested 5.0.0) does not help — the runtime download persists in all versions.

Fix

In scripts/run_performance_job.py, for run_kind == "mlnet" only:

  1. Download the SSWE model on the build agent (which has reliable connectivity) into the correlation payload at mlnet-resources/Text/Sswe/sentiment.emd, with retries and a blob→aka.ms fallback.
  2. Set MICROSOFTML_RESOURCE_PATH to that payload dir via the Helix pre-commands, so ML.NET loads the embedding from disk and never makes the network call.

This is best-effort and strictly gated on run_kind == "mlnet": non-mlnet runs are unaffected, and if the agent download fails it logs a warning and falls back to today's behavior.

Why now?

The benchmark hasn't changed since 2019 and this isn't caused by any PR — the trigger is environmental on the download path (Helix egress/proxy/TLS tightening, blob throttling, and/or a .NET 9+ HttpClient behavior change against this endpoint — .NET 8 sometimes completed while 9.0/main/ubuntu hung). It manifests as a multi-hour timeout because it's a stalled read rather than a fast failure. Pre-staging the asset removes the dependency regardless of which is the actual culprit.

Co-authored-by: Copilot 223556219+Copilot@users.noreply.github.com

The mlnet performance benchmarks (StochasticDualCoordinateAscentClassifierBench.TrainSentiment) apply a pretrained SSWE word embedding that ML.NET downloads (~70 MB) from aka.ms/mlnet-resources at benchmark runtime. That download stalls on the Helix machines, hanging the entire mlnet work item until it times out and is killed, discarding all mlnet results so every mlnet benchmark appears to fail.

Download the model on the build agent (reliable connectivity) into the correlation payload and point MICROSOFTML_RESOURCE_PATH at it via the Helix pre-commands, removing the runtime network dependency. Best-effort and strictly gated on run_kind == mlnet, so non-mlnet runs are unaffected and a download failure falls back to prior behavior.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings June 30, 2026 21:45

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR addresses persistent ML.NET benchmark work item timeouts in the performance-ci Helix pipeline by removing a runtime network dependency (ML.NET’s SSWE embedding download) and instead pre-staging the model inside the Helix correlation payload.

Changes:

  • Added a best-effort pre-provisioning step that downloads sentiment.emd (SSWE embedding) into the correlation payload for run_kind == "mlnet".
  • Wired Helix pre-commands to set MICROSOFTML_RESOURCE_PATH to the staged payload directory when provisioning succeeds.
  • Added retry + fallback URL logic for the model download.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +164 to +171
getLogger().info(f"Downloading ML.NET SSWE model from {url} (attempt {attempt})")
with urllib.request.urlopen(url, timeout=300) as response, open(dest, "wb") as f:
shutil.copyfileobj(response, f)
size = os.path.getsize(dest)
if size <= 0:
raise Exception("downloaded file is empty")
getLogger().info(f"Downloaded ML.NET SSWE model ({size} bytes) to {dest}")
return True

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch — addressed in 824daf2. The download now goes to a temp file, validates the size against Content-Length (when present) and a 60 MB minimum floor, then atomically replaces the destination via os.replace, removing the temp file on any failure. A truncated or early-closed response can no longer leave a corrupt sentiment.emd in the payload.

Download to a temp file, validate the size against Content-Length (when
present) and a minimum-size floor, then atomically replace the destination so
a truncated or early-closed response can't leave a corrupt sentiment.emd in
the payload. Also make the function docstring more concise.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants