Skip to content

Metal backend: stage compiled .mlmodelc next to source .mlpackage - #1274

Open
zhen-ke wants to merge 3 commits into
lightvector:masterfrom
zhen-ke:fix/coreml-modelc-leak
Open

zhen-ke wants to merge 3 commits into
lightvector:masterfrom
zhen-ke:fix/coreml-modelc-leak

Conversation

@zhen-ke

@zhen-ke zhen-ke commented Oct 27, 2026 •

Copy link
Copy Markdown

Summary

MLModel.compileModel(at:) writes the compiled .mlmodelc into NSTemporaryDirectory(),
which ignores a TMPDIR override. The source .mlpackage is created under
temp_directory_path(), which honors TMPDIR. A host that gives each launch its own
TMPDIR and reclaims it on exit never sees the compiled bundle, so it leaks (about 50 MiB
per ANE handle per launch).

Changes

  • After compileModel returns, move the bundle next to the source .mlpackage and load it from there. The destination is mlpackagePath.deletingLastPathComponent(); this does not look up the temp directory again.
  • If the move fails, log a warning and load the bundle from where compileModel left it.
  • KataGo never deletes the compiled bundle, on any path: MLModel may read it lazily. The owner of the temp directory reclaims it.

Fixes #1266.

MLModel.compileModel drops the .mlmodelc bundle in the process's
container-shared temp dir, ignoring TMPDIR overrides. Moving it next to
the source .mlpackage allows hosts managing per-launch TMPDIR to reclaim
it upon process termination, preventing disk leaks.

- Derives destination from mlpackagePath as the single source of truth.
- Fails hard if moveItem fails instead of falling back to leaked path.
- Cleans up artifacts on failure; retains bundle on success for MLModel lifetime.

Fixes lightvector#1266.
Comment thread cpp/neuralnet/metalbackend.swift Outdated
Comment on lines +387 to +400
var unusedCompiledURL: URL? = compiledURL
defer {
if let unusedCompiledURL {
deleteSourceModel(at: unusedCompiledURL, serverThreadIdx: serverThreadIdx)
}
}
if stagedURL.standardizedFileURL != compiledURL.standardizedFileURL {
try FileManager.default.moveItem(at: compiledURL, to: stagedURL)
unusedCompiledURL = stagedURL
}

printError("Metal backend \(serverThreadIdx): Loading compiled model...")
let model = try MLModel(contentsOf: compiledURL, configuration: config)
let model = try MLModel(contentsOf: stagedURL, configuration: config)
unusedCompiledURL = nil

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.

-        var unusedCompiledURL: URL? = compiledURL
-        defer {
-            if let unusedCompiledURL {
-                deleteSourceModel(at: unusedCompiledURL, serverThreadIdx: serverThreadIdx)
-            }
-        }
-        if stagedURL.standardizedFileURL != compiledURL.standardizedFileURL {
+        let modelURL: URL
+        do {
             try FileManager.default.moveItem(at: compiledURL, to: stagedURL)
-            unusedCompiledURL = stagedURL
+            modelURL = stagedURL
+        } catch {
+            printError("Metal backend \(serverThreadIdx): Warning: Failed to move compiled model to \(stagedURL.path), loading it from \(compiledURL.path): \(error)")
+            modelURL = compiledURL
         }
 
         printError("Metal backend \(serverThreadIdx): Loading compiled model...")
-        let model = try MLModel(contentsOf: stagedURL, configuration: config)
-        unusedCompiledURL = nil
+        let model = try MLModel(contentsOf: modelURL, configuration: config)

Deleting files on the failure paths is outside the agreed scope in steps 1-2 of #1266.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Updated in 5f6aacf — removed bundle deletion on the failure paths and fallback to loading from the original URL with a warning.

A failed move warns and loads from the compileModel URL instead of
failing the load. Leave the compiled bundle for the caller or the
system to reclaim.

@ChinChangYang ChinChangYang 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.

Please update PR description to match the code.

Comment thread cpp/neuralnet/metalbackend.swift Resolved
Comment thread cpp/neuralnet/metalbackend.swift Resolved
@ChinChangYang

Copy link
Copy Markdown
Contributor

Thanks, both comments are addressed. The PR description still doesn't match the code; please replace the Changes bullets with:

  • After compileModel returns, move the bundle next to the source .mlpackage and load it from there. The destination is mlpackagePath.deletingLastPathComponent(); this does not look up the temp directory again.
  • If the move fails, log a warning and load the bundle from where compileModel left it.
  • KataGo never deletes the compiled bundle, on any path: MLModel may read it lazily. The owner of the temp directory reclaims it.

@zhen-ke

zhen-ke commented Oct 29, 2026

Copy link
Copy Markdown
Author

Thanks, both comments are addressed. The PR description still doesn't match the code; please replace the Changes bullets with:

  • After compileModel returns, move the bundle next to the source .mlpackage and load it from there. The destination is mlpackagePath.deletingLastPathComponent(); this does not look up the temp directory again.
  • If the move fails, log a warning and load the bundle from where compileModel left it.
  • KataGo never deletes the compiled bundle, on any path: MLModel may read it lazily. The owner of the temp directory reclaims it.

Updated the PR description as suggested, thanks!

@ChinChangYang

Copy link
Copy Markdown
Contributor

I approved this PR.

Two minor issues:

  1. When $TMPDIR is not set, other users may modify the model under /tmp/.
  2. When $TMPDIR is set to "", the model will be stored under $CWD/.

This branch has not been deployed

No deployments
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.

Metal backend: compiled .mlmodelc bundles leak into shared temp dir and are never reclaimed

2 participants