microsoft / microsoft/winml-cli
Flaky e2e eval on Windows: `ort_fusion`/`save_onnx` fails with WinError 32 on shared system TEMP
@DingmaomaoBJTU is already working on this.
Since Jul 22, 2026.
- Dominant language
- Python
- Stars
- 40
- Forks
- 11
- Avg merge
- 1d 8h
- Merged PRs (30d)
- 50
Description
Summary
The e2e eval test TestEvalPerTask::test_zero_shot_image_classification
(openai/clip-vit-base-patch32, NPU/QNN) fails non-deterministically on Windows
with a temp-file locking error raised from the ort_fusion optimization pipe.
This is an environmental / flakiness issue tied to the shared system TEMP
directory, not a code regression — the same code passes as soon as a clean
TEMP directory is used.
Impact / Symptom
- Failing test:
tests/e2e/test_eval_e2e.py::TestEvalPerTask::test_zero_shot_image_classification - The CLIP vision model exports to ~335 MB, so
save_onnxtakes the
external-data path, which is where the failure surfaces. - Failure is intermittent: the nightly QNN e2e eval passed the previous run and
failed the next with no relevant code change in between.
Observed failures
CI (Windows, QNN/NPU) — WinError 32:
[ERROR winml.modelkit.optim.optimizer] ✗ ort_fusion failed: Failed to apply fusion optimizations
| Pipe: ort_fusion | Model info: {'model_type': 'clip'}
| Caused by: Failed to write ONNX model to C:\Users\...\AppData\Local\Temp\tmppn29c2ry.onnx:
[WinError 32] The process cannot access the file because it is being used by
another process: 'C:\Users\...\AppData\Local\Temp'
Error: Evaluation failed: Failed to load model 'openai/clip-vit-base-patch32'.
Root cause
Two pre-existing fragilities amplify any transient TEMP locking into a hard failure:
-
save_onnxmutates process-global CWD.
Insrc/winml/modelkit/onnx/persistence.py, the external-data branch does:os.chdir(path.parent) # path.parent == system TEMP here onnx.save_model(model, path.name, save_as_external_data=True, ...)The
WinError 32is reported againstpath.parent(the TEMP directory),
i.e. theos.chdirinto TEMP failed. A process-globalos.chdiris also
unsafe under any concurrency. -
ort_fusioncallssave_onnxwhile the temp file handle is still open.
Insrc/winml/modelkit/optim/pipes/fusion.py:with tempfile.NamedTemporaryFile(suffix=".onnx", delete=False) as f: input_path = f.name save_onnx(model, input_path) # OS handle `f` still open during writeWriting/opening the same path while the
NamedTemporaryFilehandle is open
invites Windows sharing violations.
Why this is not a regression
- No PR merged between the last passing run and the failing run touches the
failing path.optim/,eval/,onnx/persistence.py, and the e2e test file
were all last modified weeks earlier; the in-window PRs are recipe JSON
additions, a genai-only EP fix, andanalyze/+pattern/rule changes that are
fully decoupled fromeval/optim/export(verified: those packages do not
importanalyze/pattern). - Re-running the exact failing test on
mainwith a clean, dedicated TEMP passes
in ~20s.
Reproduction / workaround
Point both the system temp env vars and pytest's basetemp at a clean directory:
$env:TEMP = "<repo>\temp\e2e-clip-rerun\sys"; $env:TMP = $env:TEMP
uv run pytest "tests/e2e/test_eval_e2e.py::TestEvalPerTask::test_zero_shot_image_classification" `
-m e2e --basetemp="<repo>\temp\e2e-clip-rerun\pytest" -v
# => 1 passed in ~20s
Both must change: TEMP/TMP covers the CLI's internal tempfile
(save_onnx/ort_fusion), --basetemp covers pytest's own tmp_path.
Suggested fix
- In
ort_fusion, close the temp file handle before writing to it — create the
name withmkstemp+os.close(fd)(or movesave_onnxoutside the
withblock) so no OS handle is held whenonnx.save_modelopens the path. - Remove the process-global
os.chdirinsave_onnx; write to an absolute
path and pass an absolute/relativelocationtoonnx.save_modelinstead of
changing CWD. - (Optional) Have
ort_fusionuse a dedicatedtempfile.mkdtemp()per run and
clean it up, so a poisoned shared TEMP can't break unrelated code paths. - (CI) Run e2e jobs against a clean, job-scoped TEMP directory.
Environment
- OS: Windows (arm64)
- Python 3.11.14, pytest 9.1.1, onnxruntime QNN EP (NPU)
- Model:
openai/clip-vit-base-patch32(exported ~335 MB, external data)
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.