feat(Me): 推理引擎切换页 + SME2 状态 + CLAUDE.md 更新(Phase 5 部分)
- InferenceSettingsView:MNN(CPU/SME2)/ MLX(GPU)单选切换,展示当前设备 SME2 探测状态(A19 启用 / A17 回退);走设计系统卡片,新文件不动 WIP 的 ModelManagementView - MeView:「模型管理」下新增「推理引擎」入口,detail 显示 MNN·SME2 / MNN·CPU / MLX·GPU - CLAUDE.md §2/§12:AI 运行时改为 MNN(主,SME2)+ MLX(兜底)双后端, 卖点 #2 明确 MNN+Arm SME2 端侧 CPU 加速为挑战赛考核点 模拟器 BUILD SUCCEEDED,0 error。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -22,9 +22,10 @@
|
||||
| UI | SwiftUI | iOS 17+,用 `@Observable` / `@Model` |
|
||||
| 持久化 | SwiftData | 见 §5 数据模型 |
|
||||
| 图表 | Swift Charts | iOS 16+ 原生 |
|
||||
| **AI 运行时** | **MLX Swift (Apple 官方)** | 不要建议 Core ML / llama.cpp / Ollama |
|
||||
| LLM | Qwen3-1.7B 4bit (HF: `mlx-community/Qwen3-1.7B-4bit`) | ~1.0GB,负责文本生成、关键词抽取、趋势解读 |
|
||||
| VL | Qwen2.5-VL-3B-Instruct 4bit (HF: `mlx-community/Qwen2.5-VL-3B-Instruct-4bit`) | ~2.0GB,负责拍照→结构化指标 |
|
||||
| **AI 运行时(主)** | **MNN (alibaba) + Arm SME2 + CPU** | 挑战赛考核点:Qwen + MNN + SME2 端侧 CPU 推理。device-only(xcframework 见 `scripts/build-mnn-xcframework.sh`),A19/iPhone17 启用 SME2、A17 回退 NEON。经 `MNNLLMBridge`(ObjC++)→ `MNNBackend` |
|
||||
| **AI 运行时(兜底)** | **MLX Swift (Apple 官方,Metal GPU)** | 双后端:`InferenceEngine` 切换,模拟器/兜底用 MLX。不要建议 Core ML / llama.cpp / Ollama |
|
||||
| LLM | Qwen3.5-2B 4bit(MNN 格式 + MLX `mlx-community/Qwen3.5-2B-4bit`) | 文本生成、关键词抽取、趋势解读 |
|
||||
| VL | Qwen3-VL-4B-Instruct 4bit (MLX `mlx-community/Qwen3-VL-4B-Instruct-4bit`) | 拍照→结构化指标。MNN VL 需 OMNI 构建,暂走 MLX |
|
||||
| 文档扫描 | VisionKit `VNDocumentCameraView` | 不要自己写透视校正 |
|
||||
| Face ID | LocalAuthentication | |
|
||||
| Live Activity | ActivityKit + WidgetExtension | demo 杀手锏,真机才能测 |
|
||||
@@ -281,7 +282,7 @@ C2 解读 Tab 底部显示一段 diff 文本,**由 `ReportCompareService` 计算
|
||||
## 12. 评委 PPT 卖点排序(写代码时记住为什么这么做)
|
||||
|
||||
1. 影像档案系统(统一 VL 拍照 + 归档) — 核心创意
|
||||
2. 100% 本地 + SME2 加速 — 技术亮点
|
||||
2. 100% 本地 + **MNN + Arm SME2 端侧 CPU 加速**(挑战赛考核点,MLX/GPU 兜底) — 技术亮点
|
||||
3. 本地 RAG 长期记忆 — 端侧不可替代性
|
||||
4. 隐私三件套(系统级加密 + Face ID + 永久删除) — 信任建立
|
||||
5. AI 趋势解读 — 长期价值
|
||||
|
||||
126
康康/Features/Me/InferenceSettingsView.swift
Normal file
126
康康/Features/Me/InferenceSettingsView.swift
Normal file
@@ -0,0 +1,126 @@
|
||||
import SwiftUI
|
||||
|
||||
/// 推理引擎设置:在 MNN(CPU/SME2,考核路径)与 MLX(GPU,兜底)间切换,并展示 SME2 探测状态。
|
||||
/// 切换只改持久化选择;下一次 AI 调用(prepare/generate)按新引擎加载。
|
||||
struct InferenceSettingsView: View {
|
||||
@AppStorage("kk.inferenceEngine") private var engineRaw = InferenceEngine.mnn.rawValue
|
||||
|
||||
private var selected: InferenceEngine {
|
||||
InferenceEngine(rawValue: engineRaw) ?? .mnn
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
ScrollView {
|
||||
VStack(spacing: 12) {
|
||||
HStack {
|
||||
Text("推理引擎")
|
||||
.font(.tjTitle())
|
||||
.foregroundStyle(Tj.Palette.text)
|
||||
Spacer()
|
||||
}
|
||||
.padding(.top, 4)
|
||||
.padding(.bottom, 6)
|
||||
|
||||
ForEach(InferenceEngine.allCases, id: \.self) { engine in
|
||||
engineRow(engine)
|
||||
}
|
||||
|
||||
sme2Card
|
||||
noteCard
|
||||
}
|
||||
.padding(.horizontal, 16)
|
||||
.padding(.vertical, 20)
|
||||
}
|
||||
.background(Tj.Palette.sand.ignoresSafeArea())
|
||||
}
|
||||
|
||||
private func engineRow(_ engine: InferenceEngine) -> some View {
|
||||
let available = engine.isAvailable
|
||||
let isOn = (selected == engine)
|
||||
return Button {
|
||||
guard available else { return }
|
||||
engineRaw = engine.rawValue
|
||||
} label: {
|
||||
HStack(spacing: 12) {
|
||||
ZStack {
|
||||
Circle().fill(isOn ? Tj.Palette.amber.opacity(0.25) : Tj.Palette.sand2)
|
||||
Image(systemName: engine == .mnn ? "cpu.fill" : "bolt.fill")
|
||||
.font(.tjScaled(18))
|
||||
.foregroundStyle(isOn ? Tj.Palette.ink : Tj.Palette.text2)
|
||||
}
|
||||
.frame(width: 44, height: 44)
|
||||
|
||||
VStack(alignment: .leading, spacing: 2) {
|
||||
Text(engine.displayName)
|
||||
.font(.tjScaled(15, weight: .semibold))
|
||||
.foregroundStyle(Tj.Palette.text)
|
||||
Text(subtitle(engine, available: available))
|
||||
.font(.tjScaled(12))
|
||||
.foregroundStyle(Tj.Palette.text3)
|
||||
.lineLimit(2)
|
||||
}
|
||||
Spacer()
|
||||
if isOn {
|
||||
Image(systemName: "checkmark.circle.fill")
|
||||
.font(.tjScaled(18))
|
||||
.foregroundStyle(Tj.Palette.leaf)
|
||||
}
|
||||
}
|
||||
.padding(14)
|
||||
.tjCard()
|
||||
.opacity(available ? 1 : 0.45)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.disabled(!available)
|
||||
}
|
||||
|
||||
private func subtitle(_ engine: InferenceEngine, available: Bool) -> String {
|
||||
switch engine {
|
||||
case .mnn:
|
||||
if !available { return String(appLoc: "本设备/模拟器不可用,自动回退 MLX") }
|
||||
return InferenceEngine.cpuSupportsSME2
|
||||
? String(appLoc: "端侧 CPU + SME2 加速 · 挑战赛考核路径")
|
||||
: String(appLoc: "端侧 CPU(本机无 SME2,NEON 回退)")
|
||||
case .mlx:
|
||||
return String(appLoc: "Metal GPU · 兜底 / 对照")
|
||||
}
|
||||
}
|
||||
|
||||
private var sme2Card: some View {
|
||||
let sme2 = InferenceEngine.cpuSupportsSME2
|
||||
return HStack(spacing: 12) {
|
||||
ZStack {
|
||||
Circle().fill(sme2 ? Tj.Palette.leafSoft : Tj.Palette.sand2)
|
||||
Image(systemName: sme2 ? "checkmark.seal.fill" : "minus.circle")
|
||||
.font(.tjScaled(18))
|
||||
.foregroundStyle(sme2 ? Tj.Palette.ink : Tj.Palette.text2)
|
||||
}
|
||||
.frame(width: 44, height: 44)
|
||||
VStack(alignment: .leading, spacing: 2) {
|
||||
Text("Arm SME2")
|
||||
.font(.tjScaled(15, weight: .medium))
|
||||
.foregroundStyle(Tj.Palette.text)
|
||||
Text(sme2 ? String(appLoc: "本设备支持,MNN 已启用 SME2 加速")
|
||||
: String(appLoc: "本设备不支持(需 A19/iPhone 17+)"))
|
||||
.font(.tjScaled(12))
|
||||
.foregroundStyle(Tj.Palette.text3)
|
||||
}
|
||||
Spacer()
|
||||
}
|
||||
.padding(14)
|
||||
.tjCard()
|
||||
}
|
||||
|
||||
private var noteCard: some View {
|
||||
Text("MNN 在端侧 CPU 上以 Arm SME2 指令集加速 Qwen 推理(本地、不上云)。切换后下一次 AI 调用生效。")
|
||||
.font(.tjScaled(12))
|
||||
.foregroundStyle(Tj.Palette.text3)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.padding(14)
|
||||
.tjCard()
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
InferenceSettingsView()
|
||||
}
|
||||
@@ -37,6 +37,7 @@ struct MeView: View {
|
||||
profileCard
|
||||
customMetricsCard
|
||||
modelManagementCard
|
||||
inferenceEngineCard
|
||||
languageCard
|
||||
fontScaleCard
|
||||
faceIDCard
|
||||
@@ -157,6 +158,22 @@ struct MeView: View {
|
||||
return readyCount == 0 ? String(appLoc: "未下载") : String(appLoc: "\(readyCount)/\(ModelKind.allCases.count) 就绪")
|
||||
}
|
||||
|
||||
private var inferenceEngineCard: some View {
|
||||
NavigationLink {
|
||||
InferenceSettingsView()
|
||||
} label: {
|
||||
settingsCard(title: String(appLoc: "推理引擎"), detail: engineDetail, icon: "cpu.fill")
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
}
|
||||
|
||||
private var engineDetail: String {
|
||||
switch InferenceEngine.current {
|
||||
case .mnn: return InferenceEngine.cpuSupportsSME2 ? "MNN · SME2" : "MNN · CPU"
|
||||
case .mlx: return "MLX · GPU"
|
||||
}
|
||||
}
|
||||
|
||||
private var languageCard: some View {
|
||||
NavigationLink {
|
||||
LanguageSettingsView()
|
||||
|
||||
Reference in New Issue
Block a user