在Apple Silicon上运行MLX:当你需要自己的模型,而非Apple的模型
Apple的Foundation Models框架只交给您一个模型:系统自带、封闭、免费,并按Apple自己的节奏更新。对大多数端侧语言任务而言,它就是正确的工具,越过它反而是个错误。但有些工作需要一个由您来选择的模型:某个特定的开放权重LLM、一个您固定下来的版本、一个用自有数据训练的微调模型,或者系统模型并不具备的某种能力。当您需要在端侧运行属于自己的模型时,Foundation Models之下的那一层就是MLX1。
MLX是Apple面向Apple Silicon机器学习的数组框架,并配有Swift API(MLX Swift),可直接嵌入到应用中2。它不是一个供您调用的系统框架,而是一个您随应用一起分发的库,连同模型权重一并打包。这一区别就是全部的取舍所在,而理解它,正是您判断该不该下沉一层、还是留在Apple为您安排好的位置的依据。
太长不看
- MLX是一个为Apple Silicon打造的、类似NumPy的数组框架,具备惰性求值、可组合的函数变换以及Metal后端2。
- 统一内存模型,正是它能在手机上跑起来的原因。 数组存放在CPU与GPU共享的同一个内存池中,因此MLX在两者之上操作的是同一批缓冲区,无需为主机到设备的拷贝付出代价3。
- 借助
LLMModelFactory在端侧运行开放权重LLM:指向一个像mlx-community/Llama-3.2-3B-Instruct-4bit这样的量化模型,再通过ChatSession生成文本4。 - 使用LoRA适配器进行微调:训练一个小巧的适配器,分发
adapters.safetensors,运行时通过load(into:)把基础模型的Linear层替换为LoRALinear5。 - 拥有自己的模型是有代价的:应用体积(权重很大)、内存压力、没有系统集成,而且每一次更新都得由您自己负责。Foundation Models没有这些代价,因为这些都由Apple买单。
MLX是什么,以及为什么Apple Silicon让它成为可能
MLX给您的是看起来像NumPy的数组与运算,再加上机器学习所需的各种变换:自动微分、向量化,以及惰性求值——后者会构建一张计算图,只有在您读取结果时才真正执行2。单论这些,它和十几个框架并无二致。真正让MLX能在您口袋里的设备上跑起一个数十亿参数模型的,是它的内存模型。
在桌面级GPU上,数据存放在系统RAM中,您要把它通过总线拷贝到GPU独立的显存里去计算,算完再拷回来。这次拷贝就是代价,而对一个大模型来说,这代价相当残酷。Apple Silicon采用的是统一内存:CPU、GPU和Neural Engine都能直接寻址的同一个内存池。MLX正是围绕这一事实构建的3。一个数组并不”在CPU上”或”在GPU上”,它就在内存里,任何处理器都能原地对它进行操作。没有拷贝,没有总线代价。一个量化到4位的30亿参数模型只占用几个GB,运行起来也无需那些往返搬运——而在一台内存相近、却配着独立GPU的机器上,正是这些往返会让同样的工作变得不切实际。Apple多年前做出的那个硬件决策,正是端侧推理一个真正的模型之所以可行的根本原因,而这套基于图块、统一内存的架构就是MLX赖以立足的基底。
在端侧运行一个LLM
从”我想要某个特定模型”到屏幕上出现文字,这条路并不长。MLX Swift的LLM层会从Hugging Face Hub加载一个量化模型并运行它4:
let container = try await LLMModelFactory.shared.loadContainer(
from: HubClient.default,
using: TokenizersLoader(),
configuration: .init(id: "mlx-community/Llama-3.2-3B-Instruct-4bit")
)
let session = ChatSession(container)
let response = try await session.respond(to: "Summarize this in one line: \(text)")
如果要做逐词更新的UI,则改为生成一个流,随着分块到达逐步渲染4:
let input = try await container.prepare(input: UserInput(prompt: prompt))
let stream = try await container.generate(input: input, parameters: GenerateParameters())
for await event in stream {
if case let .chunk(text) = event { /* append to UI */ }
}
有两个细节承载了大部分实际分量。第一,模型ID里的4bit并不是可有可无的修饰:正是量化让模型得以装进内存、并在设备上以可用的速度运行。您分发的是4位(或更低)的权重,而非全精度。第二,即便经过量化,权重依然很大,所以您要慎重决定:是把它打包进应用(即开即用,但下载包很臃肿),还是首次启动时再去拉取(二进制精简,但要等待,还得处理一条失败路径)。Foundation Models从不抛出这个问题,因为模型早已在设备上。而用了MLX,权重就成了您自己的事。
微调:一个LoRA适配器,而不是一个新模型
引入自有模型的理由,很少是基础模型本身,而是要教会它理解您的领域。在端侧对一个数十亿参数的模型做全量微调并不是明智之举。LoRA(低秩适配)才是:您训练一小组适配器权重去调整基础模型的行为,而基础模型本身保持不动。适配器是以MB计的,而非GB5。
MLX Swift会从一个包含adapter_config.json和adapters.safetensors的目录里加载训练好的适配器,再把它应用到一个已经加载进容器的模型上5:
let adapter = try LoRAContainer.from(directory: adapterURL)
await container.update { context in
try? adapter.load(into: context.model) // swaps Linear layers for LoRALinear
}
load(into:)会把模型标准的Linear层替换为LoRALinear层,后者将适配器的低秩增量折叠进去,于是推理便反映出您的微调结果。由于模型存放在容器内部,您通过container.update来应用适配器,并且可以在运行时热切换适配器(unload(from:)卸下一个,load(into:)装上另一个),让同一个基础模型在不同功能下表现出不同的行为。这一模式与Apple针对系统模型通过Foundation Models自定义适配器所提供的做法如出一辙:区别在于,这里基础模型、训练流程乃至最终结果都归您所有,而不是去适配一个您看不见内部的模型。
抉择:Foundation Models、MLX,还是云端
三个层次,选错了,要么牺牲能力,要么背上一堆本可避免的工作。
- 当系统模型能胜任任务时,选Foundation Models。免费、私密、无需分发任何权重、无需您管理任何内存,还白送系统集成。默认就选这里。Apple为之打造的那些端侧语言任务(摘要、分类、抽取、改写、结构化输出)就属于这里,没有例外。
- 当您需要一个系统不会给您的模型时,选MLX:某个特定的开放权重LLM、一个不会随OS更新而改变的固定版本、一个领域微调模型,或者某种超出Foundation Models范畴的架构(视觉语言模型,或非文本模型)。您付出的是应用体积、内存和维护责任,换来的是控制权。
- 当模型确实必须很大时,选云端:前沿推理、长上下文分析,以及那些最大模型才能做、而一个数十亿参数的端侧模型无能为力的事。端侧并不是前沿模型的替代品,它只是曲线上的另一个点。
诚实的解读是:MLX是出于某个具体理由而刻意下沉的一步,而不是一个更好的默认选项。如果您说不出Foundation Models对您的功能究竟欠缺哪项能力,那您就不需要MLX——而分发它,意味着背上数GB的权重和一笔您本不必承担的内存预算。
何时不要动用MLX
- 系统模型已经能做。 重读一遍Foundation Models的任务清单。如果您的需求就在名单上,到此为止。
- 您承担不起权重的代价。 一个量化的小模型仍是一笔不小的资产。如果应用体积或首次启动下载对您的用户来说是个实实在在的约束,那这个约束本身可能就替您做了决定。
- 您要为一个固定模型榨取Neural Engine最低功耗的那条路径。 对于一个已知、已定型、不会再变的模型,Core ML及其转换器能以最紧凑的功耗与延迟瞄准Neural Engine。MLX的长处在于灵活性和科研级的迭代;Core ML的长处在于一个锁定的生产模型。它们是不同的工具,”端侧机器学习”从来不是一个单一的决策。
- 您不会去维护它。 拥有自己的模型,就意味着它的更新、安全和漂移都归您负责。系统模型由Apple替您更新。如果您没有配备人力去维护一个模型,那就别去采用它。
MLX奖励的本领,是对”何时该用它”保持克制。这个框架确实了不起:一个真正的语言模型,针对您的领域做过微调,完全在设备上运行,无需服务器、没有按token计费的成本,跑在一套内存架构恰为此而生的硬件之上。当您已经说清了理由,这份能力值得去争取。若没有理由就伸手去取,您不过是把Apple那个免费、有维护、已集成的模型,换成了一份更沉重、无人维护、如今归您所有的复制品。判断力,才是这份工作的全部。
-
把MLX相对于Foundation Models框架做个定位:Foundation Models暴露的是Apple固定的端侧系统模型(参见Apple Foundation Models:端侧LLM框架);MLX运行的是由您选择并微调的模型。两者在端侧技术栈的不同层次上满足不同的需求。 ↩
-
Apple Machine Learning Research,MLX与MLX Swift。MLX是面向Apple Silicon机器学习的数组框架,具备类似NumPy的API、可组合的函数变换(自动微分、向量化)、惰性计算以及Metal后端。MLX Swift则是用于将其嵌入应用的Swift API。 ↩↩↩
-
MLX文档,统一内存。MLX数组存放在共享内存中;运算可以在CPU或GPU上运行,而无需在彼此独立的内存池之间搬运数据——正是这一特性,让Apple Silicon的统一内存架构在端侧模型执行上如此高效。关于硬件的背景:Apple Silicon的TBDR与统一内存。 ↩↩
-
Apple Machine Learning Research,MLX Swift Examples / MLX Swift LM。
LLMModelFactory.shared.loadContainer(from:using:configuration:)会从Hugging Face Hub加载一个量化模型(例如mlx-community/Llama-3.2-3B-Instruct-4bit);ChatSession提供用于单次调用的respond(to:),而container.generate(input:parameters:)会经由GenerateParameters和UserInput产出一个.chunk(text)事件流,用于增量输出。 ↩↩↩ -
Apple Machine Learning Research,MLX Swift LM LoRA适配器参考。
LoRAContainer.from(directory:)会从一个包含adapter_config.json和adapters.safetensors的目录加载适配器;通过container.update应用后,adapter.load(into: context.model)会把模型的Linear层替换为LoRALinear层,而unload(from:)则卸下其中一个,从而让适配器可以在运行时热切换。可对照Apple在Foundation Models自定义适配器中的系统模型路径。 ↩↩↩ -
作者亲手实践的MLX工作:一个自主的机器学习研究循环,通过MLX在Apple Silicon上运行固定预算的训练实验,自主修改架构与超参数以最小化验证集的bits-per-byte,只保留有改进的结果。本文所述的统一内存与量化行为,正源自这一实验。 ↩