SwiftUI-MLX本地大模型开发(三)

介绍

SwiftUI-MLX本地大模型开发(二)一文中,我们解决了模型定制与使用离线大模型的问题,今天讲解以下 3 个问题:

  1. 模型存储路径。
  2. 模型转换。
  3. iPad 运行。

模型存储路径

  • 模型下载的默认位置为:/Users/yangfan/Documents/huggingface/models/mlx-community(macOS)或者 sandbox 下的Documents/huggingface/models/mlx-community目录(iOS)。
  • 通过HubApi可以更改模型在本地的存储路径。
  • 案例:将模型存储位置更改为:/Users/yangfan/Downloads/mlx_models/models/mlx-community(macOS)或者 sandbox 下的Downloads/mlx_models/models/mlx-community目录(iOS)。
swift 复制代码
import Hub
import MLXLLM
import MLXLMCommon
import SwiftUI

struct ContentView: View {
    @State private var downloadProgress = 0.0

    var body: some View {
        VStack(spacing: 16) {
            Button {
                Task {
                    do {
                        try await generate()
                    } catch {
                        debugPrint(error)
                    }
                }
            } label: {
                Text("下载模型")
                    .foregroundStyle(.white)
                    .padding(.horizontal, 16)
                    .padding(.vertical, 8)
                    .background(.blue)
                    .cornerRadius(8)
            }
            .buttonStyle(.borderless)

            ProgressView("正在下载", value: downloadProgress, total: 100)
        }
        .padding(.horizontal)
        .padding(.top)
    }
}

extension ContentView {
    func generate() async throws {
        // 更改路径
        let hub = HubApi(downloadBase: URL.downloadsDirectory.appending(path: "mlx_models"))
        let modelConfiguration = ModelRegistry.llama3_2_1B_4bit
        // 使用新路径
        let modelContainer = try await LLMModelFactory.shared.loadContainer(hub: hub, configuration: modelConfiguration) { progress in
            print("Downloading \(modelConfiguration.name): \(Int(progress.fractionCompleted * 100))%")
            
            Task { @MainActor in
                downloadProgress = progress.fractionCompleted * 100
            }
        }
    }
}

转换模型

  • 转换。
bash 复制代码
# 安装mlx_lm
pip install mlx mlx-lm

# 下载模型到本地
modelscope download --model NousResearch/Hermes-3-Llama-3.2-3B --local_dir /Users/yangfan/Documents/modelscope/Hermes-3-Llama-3.2-3B

# 转换
# --hf-path:Hugging Face模型本地路径
mlx_lm.convert --hf-path /Users/yangfan/Documents/modelscope/Hermes-3-Llama-3.2-3B -q
# --mlx-path:转换后模型存储路径
mlx_lm.convert --hf-path /Users/yangfan/Documents/modelscope/Hermes-3-Llama-3.2-3B -q --mlx-path /Users/yangfan/Desktop/modelscope/Hermes-3-Llama-3.2-3B
  • 使用。
swift 复制代码
extension MLXLLM.ModelRegistry {
    public static let localModel = ModelConfiguration(
        directory: URL(fileURLWithPath: "/Users/yangfan/Desktop/modelscope/Hermes-3-Llama-3.2-3B"),
        overrideTokenizer: "PreTrainedTokenizer",
        defaultPrompt: ""
    )
}

iPad运行

前面的案例都运行在 Mac 上,如何将项目运行在 iPad 上?由于 SwiftUI 具有跨平台特性,因此 UI 代码不需要修改,我们需要修改的是模型的本地存储路径。

代码

swift 复制代码
// iPad存储路径
extension MLXLLM.ModelRegistry {
    public static let localModel = ModelConfiguration(
        directory: getModelDirectory(),
        overrideTokenizer: "PreTrainedTokenizer",
        defaultPrompt: ""
    )
    
    static func getModelDirectory() -> URL {
        let documentsDir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
        let modelDir = documentsDir.appendingPathComponent("Llama-3.2-3B-Instruct")
        return modelDir
    }
}

struct ContentView: View {
    // 提示词
    @State private var prompt: String = "什么是SwiftUI?"
    // 输出结果
    @State private var response: String = ""
    @State private var isLoading: Bool = false

    var body: some View {
        VStack(spacing: 16) {
            // 顶部输入区域
            HStack {
                TextField("输入提示词...", text: $prompt)
                    .textFieldStyle(.roundedBorder)
                    .font(.system(size: 16))

                Button {
                    response = ""

                    Task {
                        do {
                            try await generate()
                        } catch {
                            debugPrint(error)
                        }
                    }
                } label: {
                    Text("生成")
                        .foregroundStyle(.white)
                        .padding(.horizontal, 16)
                        .padding(.vertical, 8)
                        .background(prompt.isEmpty ? Color.gray : Color.blue)
                        .cornerRadius(8)
                }
                .buttonStyle(.borderless)
                .disabled(prompt.isEmpty || isLoading)
            }
            .padding(.horizontal)
            .padding(.top)

            // 分隔线
            Rectangle()
                .fill(Color.gray.opacity(0.2))
                .frame(height: 1)

            // 响应展示区域
            if response != "" {
                ResponseBubble(text: response)
            }

            Spacer()
        }

        if isLoading {
            ProgressView()
                .progressViewStyle(.circular)
                .padding()
        }
    }
}

extension ContentView {
    // MARK: 文本生成
    func generate() async throws {
        isLoading = true
        // 加载模型
        let modelConfiguration = ModelRegistry.localModel
        let modelContainer = try await LLMModelFactory.shared.loadContainer(configuration: modelConfiguration) { progress in
            print("正在下载 \(modelConfiguration.name),当前进度 \(Int(progress.fractionCompleted * 100))%")
        }
        // 生成结果
        let _ = try await modelContainer.perform { [prompt] context in
            let input = try await context.processor.prepare(input: .init(prompt: prompt))
            let result = try MLXLMCommon.generate(input: input, parameters: .init(), context: context) { tokens in
                let text = context.tokenizer.decode(tokens: tokens)
                Task { @MainActor in
                    self.response = text
                    self.isLoading = false
                }
                return .more
            }
            return result
        }
    }
}

struct ResponseBubble: View {
    let text: String

    var body: some View {
        ScrollView {
            VStack(alignment: .leading, spacing: 8) {
                Text("AI")
                    .font(.system(size: 16))
                    .foregroundColor(.gray)

                Text(text)
                    .font(.system(size: 16))
                    .lineSpacing(4)
                    .padding()
                    .background(Color.blue.opacity(0.1))
                    .cornerRadius(12)
            }
        }
        .frame(maxWidth: .infinity, alignment: .leading)
        .padding()
    }
}

效果

相关推荐
火山引擎边缘云1 小时前
2025 全球分布式云大会演讲实录 | 沈建发:智启边缘,畅想未来:边缘计算新场景落地与 Al 趋势新畅想
人工智能·llm·边缘计算
OpenBayes贝式计算1 小时前
教程上新丨媲美 o3-mini,开源代码推理模型 DeepCoder-14B-Preview 狂揽 3k stars
人工智能·开源·llm
鸿蒙布道师2 小时前
鸿蒙NEXT开发文件预览工具类(ArkTs)
android·ios·华为·harmonyos·arkts·鸿蒙系统·huawei
鸿蒙布道师2 小时前
鸿蒙NEXT开发全局上下文管理类(ArkTs)
android·ios·华为·harmonyos·arkts·鸿蒙系统·huawei
Apifox2 小时前
Apifox 全面支持 LLMs.txt:让 AI 更好地理解你的 API 文档
llm·ai编程·cursor
归辞...3 小时前
【iOS】OC高级编程 iOS多线程与内存管理阅读笔记——自动引用计数(二)
笔记·ios·cocoa
码客前端4 小时前
ios接入穿山甲【Swift】
macos·ios·cocoa
SHIPKING3934 小时前
【LangChain核心组件】Memory:让大语言模型拥有持续对话记忆的工程实践
数据库·python·langchain·llm·memory
键盘敲没电4 小时前
【iOS】UITableView性能优化
ios·性能优化·ipad