@@ -7,8 +7,8 @@ import CoreAI
77import CoreAIShared
88import Foundation
99
10- /// Core AI diffusion model function — loads a fresh InferenceFunction per call
11- /// for clean buffer state in stateless model evaluation .
10+ /// Core AI diffusion model function — manages a single InferenceFunction
11+ /// for stateless model evaluation (text encoder, UNet, VAE) .
1212public actor CoreAIDiffusionModelFunction {
1313 private let modelURL : URL
1414 private var model : AIModel ?
@@ -44,8 +44,7 @@ public actor CoreAIDiffusionModelFunction {
4444 // MARK: - [Float]-based API
4545
4646 public func run( floatInputs: [ ( [ Float ] , [ Int ] ) ] ) async throws -> [ Float ] {
47- if function == nil { try await loadResources ( ) }
48- guard let fn = function else { throw CoreAIDiffusionError . notLoaded }
47+ let fn = try await ensureLoaded ( )
4948
5049 var namedInputs : [ String : NDArray ] = [ : ]
5150 for (i, name) in fn. descriptor. inputNames. enumerated ( ) where i < floatInputs. count {
@@ -72,12 +71,11 @@ public actor CoreAIDiffusionModelFunction {
7271 namedInputs [ name] = array
7372 }
7473
75- return try await encodeAndSync ( inputs: namedInputs)
74+ return try await encodeAndSync ( fn : fn , inputs: namedInputs)
7675 }
7776
7877 public func run( intInputs: [ ( [ Int32 ] , [ Int ] ) ] ) async throws -> [ Float ] {
79- if function == nil { try await loadResources ( ) }
80- guard let fn = function else { throw CoreAIDiffusionError . notLoaded }
78+ let fn = try await ensureLoaded ( )
8179
8280 var namedInputs : [ String : NDArray ] = [ : ]
8381 for (i, name) in fn. descriptor. inputNames. enumerated ( ) where i < intInputs. count {
@@ -92,34 +90,31 @@ public actor CoreAIDiffusionModelFunction {
9290 namedInputs [ name] = array
9391 }
9492
95- return try await encodeAndSync ( inputs: namedInputs)
93+ return try await encodeAndSync ( fn : fn , inputs: namedInputs)
9694 }
9795
9896 // MARK: - NDArray-based API (for parity tests)
9997
10098 public func predict( inputs: [ String : NDArray ] ) async throws -> [ String : [ Float ] ] {
101- if function == nil { try await loadResources ( ) }
102- guard let fn = function else { throw CoreAIDiffusionError . notLoaded }
99+ let fn = try await ensureLoaded ( )
103100 try Self . requireSingleOutput ( fn)
104- let floats = try await encodeAndSync ( inputs: inputs)
101+ let floats = try await encodeAndSync ( fn : fn , inputs: inputs)
105102 return [ fn. descriptor. outputNames [ 0 ] : floats]
106103 }
107104
108105 public func predictAllOutputs( inputs: [ String : NDArray ] ) async throws -> [ String : [ Float ] ] {
109- if function == nil { try await loadResources ( ) }
110- guard function != nil else { throw CoreAIDiffusionError . notLoaded }
111- return try await encodeAndSyncAll ( inputs: inputs)
106+ let fn = try await ensureLoaded ( )
107+ return try await encodeAndSyncAll ( fn: fn, inputs: inputs)
112108 }
113109
114110 public func predictAutoNamed( inputs: [ NDArray ] ) async throws -> [ String : [ Float ] ] {
115- if function == nil { try await loadResources ( ) }
116- guard let fn = function else { throw CoreAIDiffusionError . notLoaded }
111+ let fn = try await ensureLoaded ( )
117112 try Self . requireSingleOutput ( fn)
118113 var namedInputs : [ String : NDArray ] = [ : ]
119114 for (i, name) in fn. descriptor. inputNames. enumerated ( ) where i < inputs. count {
120115 namedInputs [ name] = inputs [ i]
121116 }
122- let floats = try await encodeAndSync ( inputs: namedInputs)
117+ let floats = try await encodeAndSync ( fn : fn , inputs: namedInputs)
123118 return [ fn. descriptor. outputNames [ 0 ] : floats]
124119 }
125120
@@ -132,18 +127,16 @@ public actor CoreAIDiffusionModelFunction {
132127
133128 // MARK: - Core inference
134129
135- /// Run inference by loading a fresh function each call.
136- /// InferenceFunction.run() can return stale data on alternating calls for
137- /// stateless models, so we always load a fresh one.
138- private func encodeAndSync( inputs: [ String : NDArray ] ) async throws -> [ Float ] {
139- guard let mdl = model else { throw CoreAIDiffusionError . notLoaded }
140- guard let freshFn = try mdl. loadFunction ( named: " main " ) else {
141- throw CoreAIDiffusionError . functionNotFound ( " main " , modelURL)
142- }
130+ private func ensureLoaded( ) async throws -> InferenceFunction {
131+ if function == nil { try await loadResources ( ) }
132+ guard let fn = function else { throw CoreAIDiffusionError . notLoaded }
133+ return fn
134+ }
143135
144- var outputs = try await freshFn. run ( inputs: inputs)
136+ private func encodeAndSync( fn: InferenceFunction , inputs: [ String : NDArray ] ) async throws -> [ Float ] {
137+ var outputs = try await fn. run ( inputs: inputs)
145138
146- guard let outputName = freshFn . descriptor. outputNames. first,
139+ guard let outputName = fn . descriptor. outputNames. first,
147140 let srcArray = outputs. remove ( outputName) ? . ndArray
148141 else {
149142 return [ ]
@@ -152,27 +145,17 @@ public actor CoreAIDiffusionModelFunction {
152145 return try ndArrayToFloats ( srcArray)
153146 }
154147
155- /// Multi-output variant of `encodeAndSync`. Reads every declared output
156- /// into a flat `[Float]` array and returns them keyed by name.
157- private func encodeAndSyncAll( inputs: [ String : NDArray ] ) async throws -> [ String : [ Float ] ] {
158- guard let mdl = model else { throw CoreAIDiffusionError . notLoaded }
159- guard let freshFn = try mdl. loadFunction ( named: " main " ) else {
160- throw CoreAIDiffusionError . functionNotFound ( " main " , modelURL)
161- }
162-
163- var outputs = try await freshFn. run ( inputs: inputs)
148+ private func encodeAndSyncAll( fn: InferenceFunction , inputs: [ String : NDArray ] ) async throws -> [ String : [ Float ] ] {
149+ var outputs = try await fn. run ( inputs: inputs)
164150
165151 var result : [ String : [ Float ] ] = [ : ]
166- for name in freshFn . descriptor. outputNames {
152+ for name in fn . descriptor. outputNames {
167153 guard let srcArray = outputs. remove ( name) ? . ndArray else { continue }
168154 result [ name] = try ndArrayToFloats ( srcArray)
169155 }
170156 return result
171157 }
172158
173- /// Read an NDArray's contents into a flat `[Float]`, converting from
174- /// the underlying scalar type. Throws on unsupported types so the
175- /// caller doesn't silently misinterpret bytes.
176159 private func ndArrayToFloats( _ array: NDArray ) throws -> [ Float ] {
177160 var result = [ Float] ( )
178161 switch array. scalarType {
@@ -198,8 +181,7 @@ public actor CoreAIDiffusionModelFunction {
198181
199182 public var inputDescriptors : [ String : NDArrayDescriptor ] {
200183 get async throws {
201- if function == nil { try await loadResources ( ) }
202- guard let fn = function else { throw CoreAIDiffusionError . notLoaded }
184+ let fn = try await ensureLoaded ( )
203185 var result : [ String : NDArrayDescriptor ] = [ : ]
204186 for name in fn. descriptor. inputNames {
205187 if case . ndArray( let desc) = fn. descriptor. inputDescriptor ( of: name) {
@@ -212,8 +194,7 @@ public actor CoreAIDiffusionModelFunction {
212194
213195 public var outputDescriptors : [ String : NDArrayDescriptor ] {
214196 get async throws {
215- if function == nil { try await loadResources ( ) }
216- guard let fn = function else { throw CoreAIDiffusionError . notLoaded }
197+ let fn = try await ensureLoaded ( )
217198 var result : [ String : NDArrayDescriptor ] = [ : ]
218199 for name in fn. descriptor. outputNames {
219200 if case . ndArray( let desc) = fn. descriptor. outputDescriptor ( of: name) {
0 commit comments