介紹
在 SwiftUI-MLX本地大模型開發(二)一文中,我們解決了模型定制與使用離線大模型的問題,今天講解以下 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)。
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
}
}
}
}
轉換模型
- 轉換。
# 安裝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
- 使用。
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 代碼不需要修改,我們需要修改的是模型的本地存儲路徑。
代碼
// 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()
}
}
效果
iPad運行效果.gif