← 所有文章

在 Apple Silicon 上使用 MLX:当你需要自己的模型,而非苹果的模型

苹果的 Foundation Models 框架只给你一个模型:系统自带的那一个,封装完好、免费,并按苹果的节奏更新。对于大多数设备端的语言任务,它就是正确的工具,越过它反而是个错误。但有些工作需要一个由你自己挑选的模型:某个特定的开源权重 LLM、一个你固定锁住的版本、一个用自有数据训练出来的微调模型,或是系统模型并不具备的某种能力。当你需要在设备上运行自己的模型时,Foundation Models 之下的那一层,就是 MLX1

MLX 是苹果面向 Apple Silicon 机器学习的数组框架,配有可直接嵌入应用的 Swift API(MLX Swift)2。它不是供你调用的系统框架,而是一个由你随应用一同打包发布的库,连同模型权重一起。这一区别决定了整桩取舍,而理解它,正是你判断该不该往下沉一层、还是安守苹果给你的位置的关键。

太长不看

  • MLX 是一个类似 NumPy、专为 Apple Silicon 打造的数组框架,具备惰性求值、可组合的函数变换以及 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 没有这些代价,因为这些账由苹果替你结了。

MLX 是什么,以及为何 Apple Silicon 让它成为可能

MLX 提供了一套看上去与 NumPy 如出一辙的数组和运算,外加机器学习所需的各种变换:自动微分、向量化,以及构建计算图、只在你读取结果时才真正执行的惰性求值2。单论这些,它和市面上一打框架并无二致。真正让 MLX 能在你口袋里的设备上跑起一个数十亿参数模型的,是它的内存模型。

在桌面 GPU 上,数据存在系统 RAM 里,你要把它经由总线拷贝到 GPU 独立的显存中去计算,再把结果拷回来。这次拷贝就是那笔税,对于大模型而言更是苦不堪言。Apple Silicon 采用统一内存:CPU、GPU 和神经网络引擎都能直接寻址同一个内存池。MLX 正是围绕这一事实构建的3。一个数组并不存在“在 CPU 上”还是“在 GPU 上”之分;它就在内存里,任何处理器都能就地对它运算。没有拷贝,没有总线税。一个量化到 4 比特的 30 亿参数模型只占几个 GB,运行时无需那些往返搬运——而在显存相近的独立 GPU 机器上,同样的工作正是被这些往返拖得不切实际。苹果多年前做出的硬件抉择,正是设备端推理一个真正的模型得以可行的根本原因,而那套基于图块、统一内存的架构,就是 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)")

若要做逐 token 呈现的 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.jsonadapters.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:) 装上另一个),让同一个基础模型按不同功能呈现不同的行为。这一模式与苹果通过 Foundation Models 自定义适配器为系统模型提供的做法如出一辙:区别在于,这里基础模型、训练流程乃至最终结果都归你所有,而不是去适配一个你看不见的模型。

抉择:Foundation Models、MLX,还是云端

三个层级,选错了,要么折损能力,要么白白堆上一摞本可避免的活儿。

  • Foundation Models——当系统模型能胜任任务时。免费、私密、零权重要发布、零内存要你管理,外加白白到手的系统集成。默认就选这里。苹果为之打造的那些设备端语言任务(摘要、分类、抽取、改写、结构化输出)就该归在这里,没有二话。
  • MLX——当你需要一个系统不给你的模型时:某个特定的开源权重 LLM、一个不会随 OS 更新而漂移的锁定版本、一个领域微调,或是一种落在 Foundation Models 范围之外的架构(视觉语言模型、非文本模型)。你以应用体积、内存和所有权为代价,换来掌控权。
  • 云端——当模型确实非大不可时:前沿推理、长上下文分析,以及一切最大模型能做、而几十亿参数的设备端模型做不到的事。设备端并不是前沿模型的替代品;它是曲线上另一个不同的点。

诚实的解读是:MLX 是出于某个具体理由而审慎地退一步,并不是更好的默认。如果你说不出 Foundation Models 在你这个功能上到底缺了哪项能力,那你就不需要 MLX——硬上它,就意味着背上数个 GB 的权重,和一份你本不必承担的内存预算。

何时不该动用 MLX

  • 系统模型已经能做到。 把 Foundation Models 的任务清单重读一遍。如果你的需求就在单子上,到此为止。
  • 你担不起权重的代价。 一个量化后的小模型依然是个大资产。如果应用体积或首次启动下载对你的用户是个实打实的约束,那么单凭这一点,问题或许就已经有了答案。
  • 你需要为某个固定模型走神经网络引擎那条最低功耗的路。 对于一个已知、已发布、不再变动的模型,Core ML 及其转换器能以最紧致的功耗和延迟瞄准神经网络引擎。MLX 的长处在于灵活性和研究级的迭代;Core ML 的长处在于一个锁定的生产模型。它们是不同的工具,“设备端机器学习”从来不是一个单一的决定。
  • 你不会去维护它。 自带模型意味着它的更新、它的安全、它的漂移都归你管。系统模型由苹果替你更新。如果你没有人手去拥有一个模型,就别去采用它。

MLX 所奖赏的本领,是对“何时用它”的克制。这个框架确实卓尔不凡:一个真正的语言模型,针对你的领域做过微调,完全运行在设备上,无需服务器、没有按 token 计费的成本,跑在一台内存架构正是为此而生的硬件上。当你已经说清了理由,这份能力值得伸手去取。没有理由就去取,你不过是拿苹果那个免费、有人维护、已集成的模型,换来了一个更沉重、无人维护、如今归你所有的副本。判断力才是这桩活儿的全部。

常见问题

苹果的 MLX 框架是什么?

MLX 是一个面向 Apple Silicon 机器学习的数组框架,配有类 NumPy 的 API、可组合的函数变换(自动微分、向量化)、惰性计算以及 Metal 后端2。MLX Swift 是用于将其嵌入应用的 Swift API,让你能够在设备上运行并微调自己的模型。

MLX 如何利用 Apple Silicon 的统一内存?

MLX 的数组存放在共享内存中,因此运算可以在 CPU 或 GPU 上运行,无需在彼此独立的内存池之间拷贝数据3。正是这种零搬运的特性,让 Apple Silicon 的统一内存架构在设备端模型执行上如此高效。

我能用 MLX 在设备上运行一个开源权重 LLM 吗?

可以。LLMModelFactory.shared.loadContainer(from:using:configuration:) 会从 Hugging Face Hub 加载一个量化模型,例如 mlx-community/Llama-3.2-3B-Instruct-4bitChatSession 提供 respond(to:) 用于单次调用,而 container.generate(input:parameters:) 会以流的形式输出 .chunk(text) 事件,用于增量输出4

我该如何用 MLX 微调一个模型?

用一个 LoRA 适配器,而非一个全新模型。LoRAContainer.from(directory:) 会从一个含有 adapter_config.jsonadapters.safetensors 的目录里加载适配器;通过 container.update 应用后,它会把模型的 Linear 层换成 LoRALinear 层,并且能在运行时热切换适配器5

MLX、Foundation Models 与 Core ML:我该用哪个?

当苹果的系统模型能胜任任务时,默认选 Foundation Models(免费、私密、零权重要发布)1。只有当你需要一个系统不给你的模型时,才动用 MLX:某个特定的开源权重 LLM、一个锁定的版本、一个领域微调,或是落在 Foundation Models 范围之外的架构。当一个锁定的生产模型需要神经网络引擎那条最低功耗的路时,用 Core ML;而当模型确实非得是前沿规模不可时,选 云端

我什么时候不该动用 MLX?

当系统模型已经能做到时;当你担不起发布数个 GB 权重的代价时;当一个固定模型由 Core ML 那条最低功耗的神经网络引擎路径来承载会更合适时;或者当你没有人手去拥有一个模型的更新、安全与漂移时。MLX 是出于一个明确理由而审慎地退一步,并不是更好的默认。



  1. 把 MLX 相对于 Foundation Models 框架进行定位:Foundation Models 暴露的是苹果固定的设备端系统模型(参见 Apple Foundation Models:设备端 LLM 框架);MLX 运行的则是由你挑选并微调的模型。二者在设备端技术栈的不同层级上满足不同的需求。 

  2. Apple Machine Learning Research,MLXMLX Swift。MLX 是一个面向 Apple Silicon 机器学习的数组框架,配有类 NumPy 的 API、可组合的函数变换(自动微分、向量化)、惰性计算以及 Metal 后端。MLX Swift 是用于将其嵌入应用的 Swift API。 

  3. MLX 文档,统一内存。MLX 的数组存放在共享内存中;运算可以在 CPU 或 GPU 上运行,无需在彼此独立的内存池之间搬运数据——正是这一特性,让 Apple Silicon 的统一内存架构在设备端模型执行上如此高效。硬件背景参见:Apple Silicon 的 TBDR 与统一内存。 

  4. Apple Machine Learning Research,MLX Swift Examples / MLX Swift LMLLMModelFactory.shared.loadContainer(from:using:configuration:) 会从 Hugging Face Hub 加载一个量化模型(例如 mlx-community/Llama-3.2-3B-Instruct-4bit);ChatSession 提供 respond(to:) 用于单次调用,而 container.generate(input:parameters:) 会通过 GenerateParametersUserInput 输出一个 .chunk(text) 事件流,用于增量输出。 

  5. Apple Machine Learning Research,MLX Swift LM LoRA 适配器参考LoRAContainer.from(directory:) 会从一个含有 adapter_config.jsonadapters.safetensors 的目录里加载适配器;通过 container.update 应用后,adapter.load(into: context.model) 会把模型的 Linear 层替换成 LoRALinear 层,而 unload(from:) 会卸下一个,从而让适配器能在运行时热切换。可对照苹果在 Foundation Models 自定义适配器中的系统模型路径。 

  6. 作者的 MLX 上手实践:一个自主的机器学习研究循环,通过 MLX 在 Apple Silicon 上运行固定预算的训练实验,自主修改架构与超参数,以最小化验证集的 bits-per-byte,并只保留有改进的结果。此处描述的统一内存与量化行为,正反映了那段实验经历。 

相关文章

Apple Foundation Models:端侧LLM框架详解

Apple 的 Foundation Models 框架:LanguageModelSession、@Generable 引导式生成、工具调用、可用性,以及何时该离开端侧模型。

3 分钟阅读

Apple Vision框架:大多数开发者忽略的设备端CV

Apple Vision提供两打以上的设备端CV操作。大多数开发者却默认使用OpenAI Vision处理那些Vision在毫秒内、免费、设备端就能完成的任务。

2 分钟阅读

构建AI系统:从RAG到智能体

我构建了一个3,500行代码的智能体系统,包含86个钩子和共识验证机制。以下是我在RAG、微调和智能体编排方面的经验总结。

2 分钟阅读