根据提供的code differences信息,我发现没有具体的代码变更内容。因此生成一个通用的commit message:

```
docs(readme): 更新文档说明

- 添加项目使用说明
- 完善配置指南
```

注意:由于未提供具体的代码差异信息,以上为示例格式。请提供实际的代码变更内容以生成准确的commit message。
This commit is contained in:
link2026
2026-07-01 10:30:22 +08:00
parent 404abbf10b
commit 558682c8f8
13 changed files with 44 additions and 680 deletions

View File

@@ -39,10 +39,8 @@ actor AIRuntime {
private(set) var lastGenerateStats: GenerateStats?
/// ( / PPT )
/// MLX/GPU(Gemma-3n E2B);MNN/SME2 LLM
var activeBackendLabel: String {
if InferenceEngine.current == .mnn, mnnStatus == .ready {
return InferenceEngine.cpuSupportsSME2 ? "MNN · SME2" : "MNN · NEON"
}
#if targetEnvironment(simulator)
return "MLX · CPU(模拟器)"
#else
@@ -53,12 +51,6 @@ actor AIRuntime {
private var llmSession: LLMSession?
private var vlSession: VLSession?
// MARK: - MNN (CPU/SME2,)
// .mnn , VL() Qwen3.5-2B MNN ()
// MNN,VL 退 MLX Qwen3-VL-4B
private let mnn = MNNBackend()
private(set) var mnnStatus: Status = .notReady
// MARK: - Gemini (hybrid: Gemma ,)
// , OOM , /
// / Gemma-3n(MLX )·
@@ -67,10 +59,6 @@ actor AIRuntime {
nonisolated var cloudAvailable: Bool { CloudAI.isConfigured }
/// ( / )
nonisolated static var cloudLabel: String { "Gemini · \(CloudAI.model)" }
/// MNN (/ Models/Qwen3.5-2B-MNN)
nonisolated static var mnnModelFolder: URL {
ModelStore.shared.localURL(for: .mnnLLM)
}
// MARK: - (§3.1 OOM )
//
@@ -144,18 +132,8 @@ actor AIRuntime {
#endif
}
/// ,
/// :.mnn MNN(CPU/SME2);.mlx MLX(GPU)
/// ( MLX/GPU,Gemma-3n E2B),
func prepare() async throws {
// MNN MNN;( MLX, MNN )退 MLX,
// App (Phase 5)
let mnnReady = ModelStore.shared.isComplete(for: .mnnLLM)
if InferenceEngine.current == .mnn, mnnReady {
try await prepareMNN()
return
}
// MLX: MNN ()
await unloadMNN()
// ,
// return: ready, generate
// `guard status == .ready` ()
@@ -195,54 +173,12 @@ actor AIRuntime {
}
}
/// MNN : MLX LLM/VL
private func prepareMNN() async throws {
while mnnStatus == .loading {
try await Task.sleep(nanoseconds: 80_000_000)
}
if mnnStatus == .ready { return }
let folder = Self.mnnModelFolder
guard ModelStore.shared.isComplete(for: .mnnLLM) else {
mnnStatus = .error("MNN 模型未就绪")
throw AIRuntimeError.notReady
}
await acquireGate()
defer { releaseGate() }
if mnnStatus == .ready { return }
// : MLX LLM/VL, MNN
unloadLLM()
unloadVL()
mnnStatus = .loading
do {
try await mnn.load(folderURL: folder)
mnnStatus = .ready
} catch {
mnnStatus = .error("\(error)")
throw AIRuntimeError.modelLoadFailed("\(error)")
}
}
/// MNN,
private func unloadMNN() async {
guard mnnStatus != .notReady else { return }
await mnn.unload()
mnnStatus = .notReady
MLX.Memory.clearCache()
}
/// await prepare()
/// :, actor LLMSession await
/// priority = .background token (CancellationError )
func generate(prompt: String,
maxTokens: Int = 256,
priority: InferencePriority = .interactive) -> AsyncThrowingStream<TokenChunk, Error> {
if InferenceEngine.current == .mnn, mnnStatus == .ready {
return mnnGenerate(prompt: prompt, maxTokens: maxTokens, priority: priority)
}
// actor ,Task 访 self.status / self.llmSession
let snapshotStatus = status
let snapshotSession = llmSession
@@ -282,40 +218,6 @@ actor AIRuntime {
}
}
/// MNN(CPU/SME2) MLX :
private func mnnGenerate(prompt: String,
maxTokens: Int,
priority: InferencePriority) -> AsyncThrowingStream<TokenChunk, Error> {
let ready = (mnnStatus == .ready)
return AsyncThrowingStream { continuation in
let task = Task {
guard ready else {
continuation.finish(throwing: AIRuntimeError.notReady)
return
}
await self.acquireGate(priority)
defer { self.releaseGate() } // / / ,
do {
let stream = await self.mnn.generate(prompt: prompt, maxTokens: maxTokens)
for try await chunk in stream {
try Task.checkCancellation()
// :, token 退
//( MNNBackend.onTermination bridge.cancel())
if self.shouldPreempt(priority) { throw CancellationError() }
continuation.yield(chunk)
}
self.lastGenerateStats = await self.mnn.lastStats
continuation.finish()
} catch is CancellationError {
continuation.finish(throwing: CancellationError())
} catch {
continuation.finish(throwing: AIRuntimeError.inferenceFailed("\(error)"))
}
}
continuation.onTermination = { _ in task.cancel() }
}
}
// MARK: - Gemini (hybrid )
/// ( / ) OOM ,
@@ -353,7 +255,7 @@ actor AIRuntime {
// MARK: - ASR(SenseVoice via sherpa-mnn)
/// (SenseVoice, CPU + ): + LLM/VL/MNN ,
/// (SenseVoice, CPU + ): + LLM/VL ,
/// / App jetsam (§3.1 OOM )
///
/// organize( LLM), LLM :
@@ -365,7 +267,6 @@ actor AIRuntime {
defer { releaseGate() }
unloadLLM()
unloadVL()
await unloadMNN()
return try await body()
}
@@ -373,11 +274,6 @@ actor AIRuntime {
/// VL , load
func prepareVL() async throws {
// MNN :VL MNN (+), prepareMNN
if InferenceEngine.current == .mnn, ModelStore.shared.isComplete(for: .mnnLLM) {
try await prepareMNN()
return
}
while vlStatus == .loading {
try await Task.sleep(nanoseconds: 80_000_000)
}
@@ -398,7 +294,6 @@ actor AIRuntime {
// OOM (§3.1): VL(~3GB) LLM(~1GB), jetsam
unloadLLM()
await unloadMNN()
vlStatus = .loading
do {
@@ -438,16 +333,6 @@ actor AIRuntime {
func analyzeReport(imageURLs: [URL],
prompt: String,
maxTokens: Int = 512) async throws -> String {
// MNN : MNN
if InferenceEngine.current == .mnn, mnnStatus == .ready {
await acquireGate()
defer { releaseGate() }
do {
return try await mnn.analyze(imageURLs: imageURLs, prompt: prompt, maxTokens: maxTokens)
} catch {
throw AIRuntimeError.inferenceFailed("\(error)")
}
}
guard vlStatus == .ready, let session = vlSession else {
throw AIRuntimeError.notReady
}