Skip to content

Commit 1ed7cd0

Browse files
committed
Resolve compiled .aimodelc assets when metadata references .aimodel
After `coreai-build compile` produces .aimodelc from .aimodel, the metadata.json still references the original .aimodel name. This caused silent load failures for all diffusion pipelines and required manual metadata editing. ModelBundle.resolveAssetURL now falls back from .aimodel to .aimodelc when the declared path doesn't exist on disk. One-way only (source → compiled), since the reverse would bypass an intentional user choice. Applied to: - ModelBundle.modelURL(for:) — LLMs, segmentation, VLMs - ModelBundle.verify() — bundle validation - SD3Pipeline+Resources — SD3 component loading - PipelineDescriptor+CoreAI — SD 1.x/2.x component loading Fixes #128
1 parent 5ed9981 commit 1ed7cd0

3 files changed

Lines changed: 29 additions & 12 deletions

File tree

swift/Sources/CoreAIDiffusionPipeline/Pipelines/PipelineDescriptor+CoreAI.swift

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
// be found in the LICENSE file or at https://opensource.org/licenses/BSD-3-Clause
55

66
import CoreAI
7+
import CoreAIShared
78
import Foundation
89

910
/// Loaded diffusion pipeline components backed by Core AI model functions.
@@ -51,14 +52,14 @@ extension PipelineDescriptor {
5152

5253
// Create model functions
5354
let unetFunction = CoreAIDiffusionModelFunction(
54-
modelURL: baseURL.appendingPathComponent(unetPath))
55+
modelURL: ModelBundle.resolveAssetURL(unetPath, in: baseURL))
5556
let decoderFunction = CoreAIDiffusionModelFunction(
56-
modelURL: baseURL.appendingPathComponent(decoderPath))
57+
modelURL: ModelBundle.resolveAssetURL(decoderPath, in: baseURL))
5758

5859
let encoderFunction: CoreAIDiffusionModelFunction?
5960
if let encoderPath = components.vaeEncoder {
6061
encoderFunction = CoreAIDiffusionModelFunction(
61-
modelURL: baseURL.appendingPathComponent(encoderPath))
62+
modelURL: ModelBundle.resolveAssetURL(encoderPath, in: baseURL))
6263
} else {
6364
encoderFunction = nil
6465
}
@@ -108,7 +109,7 @@ extension PipelineDescriptor {
108109
let textEncoderFunction: CoreAIDiffusionModelFunction
109110
if let tePath = components.textEncoder {
110111
textEncoderFunction = CoreAIDiffusionModelFunction(
111-
modelURL: baseURL.appendingPathComponent(tePath))
112+
modelURL: ModelBundle.resolveAssetURL(tePath, in: baseURL))
112113
} else {
113114
throw PipelineLoadError.missingComponent("text_encoder")
114115
}

swift/Sources/CoreAIDiffusionPipeline/Pipelines/SD3Pipeline+Resources.swift

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
// be found in the LICENSE file or at https://opensource.org/licenses/BSD-3-Clause
55

66
import CoreAI
7+
import CoreAIShared
78
import Foundation
89

910
extension SD3Pipeline {
@@ -29,13 +30,13 @@ extension SD3Pipeline {
2930
}
3031

3132
let transformer = CoreAIDiffusionModelFunction(
32-
modelURL: url.appendingPathComponent(transformerPath))
33+
modelURL: ModelBundle.resolveAssetURL(transformerPath, in: url))
3334
let textEncoder = CoreAIDiffusionModelFunction(
34-
modelURL: url.appendingPathComponent(textEncoderPath))
35+
modelURL: ModelBundle.resolveAssetURL(textEncoderPath, in: url))
3536
let textEncoder2 = CoreAIDiffusionModelFunction(
36-
modelURL: url.appendingPathComponent(textEncoder2Path))
37+
modelURL: ModelBundle.resolveAssetURL(textEncoder2Path, in: url))
3738
let decoder = CoreAIDiffusionModelFunction(
38-
modelURL: url.appendingPathComponent(decoderPath))
39+
modelURL: ModelBundle.resolveAssetURL(decoderPath, in: url))
3940

4041
let tokenizer = try Self.loadBPETokenizer(
4142
at: url.appendingPathComponent("tokenizer"))

swift/Sources/CoreAIShared/Bundle/ModelBundle.swift

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,10 @@ public struct ModelBundle: Sendable {
4848
}
4949

5050
/// Resolve a component's URL within the bundle by role key.
51+
/// Falls back to `.aimodelc` if the declared `.aimodel` path doesn't exist on disk.
5152
public func modelURL(for key: String) -> URL? {
5253
guard let path = assets[key] else { return nil }
53-
return bundlePath.appending(path: path)
54+
return Self.resolveAssetURL(path, in: bundlePath)
5455
}
5556

5657
/// Required-component variant — throws `BundleError.missingField` if absent.
@@ -61,13 +62,27 @@ public struct ModelBundle: Sendable {
6162
return url
6263
}
6364

65+
/// Resolve an asset path against a directory, falling back from `.aimodel` to `.aimodelc`.
66+
///
67+
/// When `coreai-build compile` produces a compiled `.aimodelc` from a source `.aimodel`,
68+
/// metadata.json still references the original name. This finds the compiled variant
69+
/// so users don't need to hand-edit metadata.json after compilation.
70+
public static func resolveAssetURL(_ path: String, in directory: URL) -> URL {
71+
let url = directory.appending(path: path)
72+
if FileManager.default.fileExists(atPath: url.path) { return url }
73+
if path.hasSuffix(".aimodel") {
74+
let compiled = directory.appending(path: path + "c")
75+
if FileManager.default.fileExists(atPath: compiled.path) { return compiled }
76+
}
77+
return url
78+
}
79+
6480
/// Verify all declared assets exist on disk. Throws `BundleError.missingAsset`
6581
/// with guidance if a component is missing (e.g. after manual compilation).
6682
public func verify() throws {
67-
let fm = FileManager.default
6883
for (key, filename) in assets {
69-
let url = bundlePath.appending(path: filename)
70-
if !fm.fileExists(atPath: url.path) {
84+
let url = Self.resolveAssetURL(filename, in: bundlePath)
85+
if !FileManager.default.fileExists(atPath: url.path) {
7186
throw BundleError.missingAsset(key: key, path: url)
7287
}
7388
}

0 commit comments

Comments
 (0)