feat(AI): 集成MNN推理引擎替换MLX作为主AI运行时

- 引入MNN(alibaba) + Arm SME2 + CPU作为主AI运行时,支持A19/iPhone17的
  SME2和A17的NEON加速
- 添加MLX Swift作为兜底GPU推理方案,实现双后端切换机制
- 使用单一Qwen3.5-2B多模态模型(1.2GB),替代原有的LLM+VL分离架构
- 实现InferenceEngine.current引擎选择逻辑,真机默认MNN,模拟器回退MLX
- 更新AIAgent架构,通过MNNLLMBridge(ObjC++) → MNNBackend进行推理
- 修改队列机制防止并发推理导致OOM,使用信号量闸门控制显存占用
- 更新文档中的技术栈说明、模块边界和周次交付计划
```
This commit is contained in:
link2026
2026-06-15 09:24:59 +08:00
parent 6c6a950140
commit 9d856fcfc4
37 changed files with 2605 additions and 430 deletions

View File

@@ -1,5 +1,6 @@
import Foundation
import UIKit
import ImageIO
enum FileVaultError: Error {
case readFailed
@@ -10,7 +11,10 @@ enum FileVaultError: Error {
/// `@unchecked Sendable`:rootURL let, I/O (线),
/// actor / Task 访 `nonisolated`, ModelStore
final class FileVault: @unchecked Sendable {
/// `nonisolated`: `SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor`,
/// `thumbnailCache`( Sendable NSCache) MainActor, nonisolated I/O /
/// 访; I/O + , MainActor actor , nonisolated
nonisolated final class FileVault: @unchecked Sendable {
nonisolated static let shared: FileVault = {
do {
let appSupport = try FileManager.default.url(
@@ -28,6 +32,17 @@ final class FileVault: @unchecked Sendable {
let rootURL: URL
/// NSCache 线;
/// key = "@", TabView /
/// ( KB),
/// `nonisolated(unsafe)`: MainActor , Sendable NSCache 便
/// nonisolated MainActor, nonisolated I/O 访;NSCache 线, unsafe
private nonisolated(unsafe) let thumbnailCache: NSCache<NSString, UIImage> = {
let cache = NSCache<NSString, UIImage>()
cache.countLimit = 40
return cache
}()
init(rootURL: URL) throws {
self.rootURL = rootURL
try FileManager.default.createDirectory(
@@ -81,6 +96,33 @@ final class FileVault: @unchecked Sendable {
return image
}
/// ImageIO ,****:
/// 4000×3000 ~48MB RGBA, jetsam; 2000px MB
/// EXIF ,/
/// ( / ) readFailed, loadImage ,UI
nonisolated func loadDownsampledImage(relativePath: String, maxPixelSize: CGFloat) throws -> UIImage {
let cacheKey = "\(relativePath)@\(Int(maxPixelSize))" as NSString
if let cached = thumbnailCache.object(forKey: cacheKey) { return cached }
let url = try resolveSafePath(relativePath)
let srcOptions: [CFString: Any] = [kCGImageSourceShouldCache: false]
guard let src = CGImageSourceCreateWithURL(url as CFURL, srcOptions as CFDictionary) else {
throw FileVaultError.readFailed
}
let thumbOptions: [CFString: Any] = [
kCGImageSourceCreateThumbnailFromImageAlways: true,
kCGImageSourceCreateThumbnailWithTransform: true, // EXIF ,
kCGImageSourceShouldCacheImmediately: true, // 线,线
kCGImageSourceThumbnailMaxPixelSize: maxPixelSize
]
guard let cg = CGImageSourceCreateThumbnailAtIndex(src, 0, thumbOptions as CFDictionary) else {
throw FileVaultError.decodeFailed
}
let image = UIImage(cgImage: cg)
thumbnailCache.setObject(image, forKey: cacheKey)
return image
}
nonisolated func remove(relativePath: String) throws {
let url = try resolveSafePath(relativePath)
do {
@@ -88,6 +130,8 @@ final class FileVault: @unchecked Sendable {
} catch {
throw FileVaultError.removeFailed
}
// ,(,)
thumbnailCache.removeAllObjects()
}
/// Vault (/),;
@@ -99,6 +143,7 @@ final class FileVault: @unchecked Sendable {
try? fm.removeItem(at: url)
}
let remaining = (try? fm.contentsOfDirectory(at: rootURL, includingPropertiesForKeys: nil)) ?? []
thumbnailCache.removeAllObjects()
if !remaining.isEmpty {
throw FileVaultError.removeFailed
}