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

@@ -450,6 +450,8 @@ struct HealthExportService {
var reports: [Report]
var diaries: [DiaryEntry]
var profile: UserProfile
/// () AI current_meds
var medications: [Medication] = []
/// (, LLM) ##
var trends: [ExportTrend] = []
}
@@ -530,6 +532,9 @@ struct HealthExportService {
// Profile()
let profile = UserProfileStore.loadOrCreate(in: ctx)
// (, AI current_meds)
let medications = (try? ctx.fetch(FetchDescriptor<Medication>())) ?? []
// (, LLM)
// in-window ; indicators series
let trends = ExportTrendBuilder.build(
@@ -546,6 +551,7 @@ struct HealthExportService {
reports: reports,
diaries: diaries,
profile: profile,
medications: medications,
trends: trends
)
}
@@ -561,6 +567,7 @@ struct HealthExportService {
let indicators = (try? ctx.fetch(indicatorDesc)) ?? []
let diaries = (try? ctx.fetch(diaryDesc)) ?? []
let profile = UserProfileStore.loadOrCreate(in: ctx)
let medications = (try? ctx.fetch(FetchDescriptor<Medication>())) ?? []
let dates = indicators.map(\.capturedAt) + diaries.map(\.createdAt)
let fromDate = dates.min() ?? Date()
@@ -581,6 +588,7 @@ struct HealthExportService {
reports: [],
diaries: diaries,
profile: profile,
medications: medications,
trends: trends
)
}
@@ -611,7 +619,11 @@ struct HealthExportService {
if !profile.allergies.isEmpty { profDict["allergies"] = profile.allergies }
if !profile.chronicConditions.isEmpty { profDict["chronic"] = profile.chronicConditions }
if !profile.familyHistory.isEmpty { profDict["family_history"] = profile.familyHistory }
if !profile.currentMedications.isEmpty { profDict["current_meds"] = profile.currentMedications }
// current_meds (Medication); profile.currentMedications
let medNames = snapshot.medications.map { m in
m.detailLine.isEmpty ? m.name : "\(m.name) \(m.detailLine)"
}
if !medNames.isEmpty { profDict["current_meds"] = medNames }
root["profile"] = profDict
// symptoms
@@ -681,7 +693,8 @@ struct HealthExportService {
/// :///, profile
/// LLM,,
static func isEffectivelyEmpty(_ s: Snapshot) -> Bool {
guard s.symptoms.isEmpty, s.indicators.isEmpty, s.reports.isEmpty, s.diaries.isEmpty else {
guard s.symptoms.isEmpty, s.indicators.isEmpty, s.reports.isEmpty,
s.diaries.isEmpty, s.medications.isEmpty else {
return false
}
let p = s.profile
@@ -693,7 +706,6 @@ struct HealthExportService {
&& p.allergies.isEmpty
&& p.chronicConditions.isEmpty
&& p.familyHistory.isEmpty
&& p.currentMedications.isEmpty
}
/// :6 ,,