Laya-MLX 实战指南:在 Apple Silicon 上以毫秒级延迟原生运行 Laya 类型化决策模型
【免费下载链接】laya-mlxNative MLX runtime for Laya typed decision models — 7–14 ms short decisions on M3 Max. No text generation, PyTorch, or cloud API.项目地址: https://gitcode.com/gh_mirrors/la/laya-mlx
Laya-MLX 是 Laya 类型化决策模型(typed decision models)在 Apple Silicon 上的原生 MLX 推理运行时,它把“给出一段文本状态,回答一组结构化问题”的推理压缩到单次双向前向传播,零输出 token、无 PyTorch/Transformers 推理依赖、不调用任何云端 API。本文基于仓库 README.zh-CN.md 与源码,完整讲解它的安装、Python/CLI 用法、三种问题类型(choice/score/noul)、支持的检查点、M3 Max 实测性能、权重转换、多语言路由,以及可复现的测试与 benchmark 流程,读完即可在本机跑通一条端到端的本地决策推理链路。
类型化决策:为什么不需要“生成”答案
常规大模型回答任何问题都需要逐 token 解码;而软件系统真正需要的往往只是一个分类结论:这个工单该转给哪个部门?这个请求有多紧急?用户是不是要求退款?Laya 用“类型化决策”直接回答这类约束问题,推理过程是一句可概括的流水线:
state + typed question → 双向编码器 → 决策头 → 概率三种问题类型由 laya_mlx/common.py 中的QTYPES定义,各对应一种确定性的结果格式:
choice:对若干命名选项输出分类概率(如billing / technical / sales三选一);score:对有序评分等级输出概率,并给出期望得分(如not urgent / soon / critical);noul:对某个命题输出P(true)(如“客户是否要求退款”)。
每个问题作为独立的一行输入双向编码器,编码器表示同时依赖 state 与问题本身——因此该运行时不宣称把 state 编码一次后跨任意问题复用 hidden states。结果保留上游的四位小数概率格式与action.act_probability字段;usage中的output_tokens恒为 0,这正是“0 输出 token”的由来(见 agent.py)。
编码器、决策 Transformer、评分头与动作头全部在 MLX 中运行,tokenization 使用 Hugging Face 的 Rust tokenizer(tokenizers库),原始预训练权重、问题格式、温度校准与输出 schema 均保留。需要说明的是,这是独立的 MLX 移植,并非 Convai Innovations 的官方发布;RLCD 训练与微调仍在上游项目进行,本仓库只负责推理与权重转换。
快速开始:三条命令跑通第一个决策
安装并加载 multilingual 检查点,对一个中文工单做一次部门分类:
pip install laya-mlximport laya_mlx as laya agent = laya.load("aac6fef/laya-multilingual-mlx") result = agent.predict( "发票被重复扣款,请退款。", { "department": { "type": "choice", "instructions": "Who should handle this?", "criteria": ["billing", "technical", "sales"], } }, ) print(result["answers"]["department"])首次load会从 Hugging Face 下载检查点,之后完全本地推理。predict是system_one的别名;state 可以是纯文本、JSON 字典或对话列表。返回结果形如:
{ "answers": { "department": { "type": "choice", "confidence": 0.9987, "action": {"act_probability": 0.9982}, "choice": "billing", "probabilities": {"billing": 0.9987, "technical": 0.0009, "sales": 0.0004} } }, "usage": {"input_tokens": 46, "output_tokens": 0} }choice的criteria可以是唯一标签列表,也可以扩展为{"标签": "描述"}的字典(描述会进入上下文,帮助模型区分相近选项);score的criteria必须是等级列表,score返回从 0 开始的期望等级;noul返回P(true)。仓库自带的 examples/questions.json 就是一个同时包含三种类型的最小示例。
一次提问多个问题
决策模型的价值在于一次前向传播并行回答多个问题。把questions扩展为一个以问题 ID 为键的字典即可:
result = agent.predict( "发票被重复扣款,请今天退款。", { "department": { "type": "choice", "instructions": "Which department should handle this request?", "criteria": ["billing", "technical", "sales"], }, "refund": { "type": "noul", "instructions": "Does the customer ask for money back?", }, }, ) print(result["answers"])batch_size=16(默认)控制每次前向计算的问题数,更多问题会自动分块处理(见 agent.py);内存充足时可以调大。batch_size必须是正整数,否则构造Agent时直接抛ValueError。
关键加载参数
laya.load对应 agent.py 中的Agent构造参数:
| 参数 | 默认值 | 说明 |
|---|---|---|
dtype | "float16" | 计算精度:float32/float16/bfloat16。默认 FP16;需要更接近原版 FP32 的数值时用"float32"。bfloat16可请求但不在已发布的验证矩阵内 |
batch_size | 16 | 单次前向的最大问题数,超出分块处理 |
device | None | "gpu"/"metal"/"cpu",None时用 MLX 默认设备 |
revision | None | 固定 Hub revision 哈希,保证可复现 |
subfolder | None | 从捆绑仓库中选择某个子目录检查点(如multilingual) |
compile | False | 启用mx.compile编译,适合重复负载;首次使用有编译开销与形状特化 |
pad_to_multiple | None | 把序列长度补齐到该值的整数倍(如 16),配合编译使用;某些负载可能变慢 |
cache_prompts | False | 前缀缓存,上限 128 个问题,共享 CPU 侧 state tokenization,但每个问题仍单独计算编码器 |
加载时会对每个参数名与形状做严格校验(strict=True),不支持的编码器与非默认 RoPE 缩放会直接失败;ModernBERT 的全局/局部注意力模式、滑动窗口边界、双 RoPE base 与首层归一化行为都被保留。以上三个优化选项默认全部关闭,实测数据见 docs/SNAKE_OPTIMIZATION.md。
支持的检查点
| 检查点 | 编码器 | 参数量 | 最大上下文 | 用途 |
|---|---|---|---|---|
convaiinnovations/laya | ModernBERT-large | 421M | 512 | 英文 |
convaiinnovations/laya-multilingual | mmBERT-base | 322M | 1,024 | 多语言输入(100+ 语言) |
convaiinnovations/laya-typed-decisions | ModernBERT-large | 421M | 1,024 | 上游 typed-decisions 工作流 |
上下文预算包含问题、选项与输入状态。中文等非英语输入应使用 multilingual 检查点——英文检查点在非英语输入上会剧烈退化并伴随高置信度,这是路由器必须存在的原因(详见下文)。
三个已转换的 FP16 MLX 权重仓库可直接传给laya.load(...):
aac6fef/laya-mlx(英文)aac6fef/laya-multilingual-mlx(多语言)aac6fef/laya-typed-decisions-mlx(typed-decisions 工作流)
每个模型仓库都附带模型卡、验证结果、来源、许可证与文件校验清单;三个仓库共 36 个文件均通过严格远端校验,固定版本与权重哈希记录在 benchmarks/results/hub-publication.json。
也可以加载上游捆绑仓库中的子目录:
# 在上游捆绑仓库中选择 multilingual 子目录 multi = laya.load("convaiinnovations/laya", subfolder="multilingual") # 固定 revision 以保证可复现 agent = laya.load( "convaiinnovations/laya", revision="c5d78730f3493e4fe16d61507ef4b78eef7318cf", )M3 Max 实测:13.4 ms 与 7.4 ms 的含义
| FP16,端到端 | Laya 421M | Multilingual 322M |
|---|---|---|
| 单个短问题 P50 | 13.42 ms | 7.39 ms |
| 单个短问题 P95 | 13.92 ms | 7.79 ms |
| 50 问题吞吐量 | 146.8 q/s | 395.0 q/s |
| 单个短问题 MLX 峰值分配 | 943.6 MiB | 687.6 MiB |
硬件为 M3 Max(40 核 GPU、128 GiB 内存)。计时包含提示准备、tokenization、张量构建、GPU 同步推理、校准与结果格式化,排除模型加载;50 问题吞吐量使用batch_size=64(公开 API 默认 16)。不同长度、问题数量与运行条件都会改变延迟,完整方法与每个计时样本见 BENCHMARKS.md。
移植一致性:三个检查点在 FP32 与 FP16 下均通过 63/63 验证问题的上游 argmax 对齐,合计 378/378 次比较;每个配置各执行 100 次重复调用,结果有限、确定,实测活跃内存增长为零。这验证的是移植保真度,不代表所有实际问题都能答对——模型能力与限制来自上游。
需要特别区分两个数字:13.4 / 7.4 ms 来自单问题 API 基准;而贪吃蛇 demo 的每帧会批量回答三个问题,帧耗时另有独立的 docs/SNAKE_BENCHMARKS.md 报告。README 顶部那张 GIF 就是真实本地运行的原速回放:每一步都调用 Laya,界面同时显示循环路径安全层及其接管次数。
安装与运行
环境要求
- Apple Silicon Mac(本机实测环境:M3 Max,40 核 GPU,128 GB 内存)
- macOS 14+
- Python 3.11+(实测 Python 3.12.13)
- MLX 0.32.2(
pyproject.toml中约束为mlx>=0.32.2,<0.33,仅 darwin/arm64)
实测 MLX 0.32.2 提供 macOS 14 / 15 / 26 的 wheel,本机选择了 26 构建;旧系统未在这台机器上实测。
方式一:PyPI 安装(推荐)
pip install laya-mlx方式二:源码开发安装
gh repo clone mizorewww/laya-mlx cd laya-mlx uv sync uv run python examples/quickstart.pyuv sync会按 pyproject.toml 解析依赖;模型权重单独下载,不进入 Git 仓库。若需运行贪吃蛇 demo,则要安装demoextra:
pip install 'laya-mlx[demo]' hf download aac6fef/laya-multilingual-mlx laya-snakehf download提前下载一次权重,游戏运行期间完全本地推理。终端至少需要104 列 × 35 行;空格暂停、↑/↓ 调速、R 重开、Q 退出。--max-speed持续满速运行,每一步都等待新的模型结果。
laya-snake --optimize --max-speed启用经过验证的编译与前缀复用路径:在同轮成对测试中,2,400 步达到75.40 步/秒,零死亡、安全接管 2 次,比 eager 基线快约6.5%。完整游戏表现、优化测量与一致性证据见 docs/SNAKE_OPTIMIZATION.md。
命令行预测
命令行支持文本或 JSON 状态输入,问题定义从 JSON 文件读取(格式见 examples/questions.json):
uv run laya-mlx predict \ --model aac6fef/laya-multilingual-mlx \ --state '发票被重复扣款,请退款。' \ --questions examples/questions.json也可用--state-file examples/state.json传入 JSON 状态(examples/state.json 展示了from / subject / body的邮件结构,与email_questions预置配合)。CLI 入口定义在 laya_mlx/cli.py:--state与--state-file互斥且必选其一,--questions必填,另有--dtype(默认float16)、--device(gpu/cpu)、--batch-size(默认 16)、--subfolder、--revision。若权重已下载在本地,把--model改成相应本地目录(如models/下的路径)即可避免再次下载。
转换权重:从上游检查点导出 MLX 格式
uv run laya-mlx convert \ --model convaiinnovations/laya \ --dtype float16 \ --output models/laya-mlx-fp16转换后可直接用laya.load("./models/laya-mlx-fp16")加载。输出目录包含model.safetensors、编码器与 agent 配置、tokenizer 文件以及mlx_config.json。注意两点:
- 已有目录不会被覆盖,模型权重不会提交到 GitHub;
- 原始检查点本身存储的就是 FP16 权重,这里的转换只是调整参数命名与计算精度,不是量化,也不涉及重新训练。选择 FP32 提高的是算术精度,而不是源权重的存储精度。
转换命令的具体参数解析与convert调用链见 laya_mlx/cli.py。
多语言路由与预置问题
上游路由的核心教训是:英文检查点在非英语输入上不是温和退化,而是近乎崩溃——例如在 20 选项的 MASSIVE intent 上,对印地语得分 0.100、韩语 0.103(随机猜测为 0.050),且报出高置信度(ECE 0.855)。因此脚本检测是第一路由信号。
from laya_mlx import Router, triage_questions router = Router(dtype="float16", max_loaded=2) result = router.predict({"message": "发票被重复扣款,请退款。"}, triage_questions()) print(result["routing"]) # multilingualRouter(laya_mlx/router.py)默认管理三个检查点:english、multilingual、typed-decisions,三者合计约 1.16B 参数。路由优先级为:显式model> 显式task> 检测到的工作流(需auto_task_detection=True显式开启)> 显式lang> 脚本/语言检测 > 默认值。
max_loaded:限制常驻模型数量,超出时按 LRU 驱逐。冷加载耗时数秒而语言检测只需微秒级,因此交替使用多语言的服务建议Router(preload=True)一次性驻留全部三个模型,或router.preload(["english", "multilingual"])只驻留所需两个;attach(name, agent):把进程里已构建的Agent登记给路由器,避免重复加载同一份 421M 参数;unload()/loaded:释放一个或全部模型、查看当前常驻列表;task="typed_decisions":显式选择 typed-decisions 检查点。该检查点针对四个合成工作流微调,永远不会被自动选中,除非你显式开启auto_task_detection=True,或传入task="typed_decisions",或问题 ID 集合与某个工作流签名完全匹配(精确集合匹配,见_TYPED_DECISION_WORKFLOWS)——这是刻意的设计,避免它成为静默默认值。
Router.predict的返回在system_one结果基础上追加routing字段,包含model、repo、reason与detection信息。
预置问题函数(laya_mlx/presets.py)覆盖常见生产场景,均可直接传入predict:
| 函数 | 场景 | 含问题 |
|---|---|---|
triage_questions() | 客服工单分诊 | intent(choice)、is_urgent(noul)、frustration(score)、refund_requested(noul)、churn_risk(noul) |
email_questions() | 入站邮件分诊与威胁过滤 | category(choice)、is_spam、is_phishing、urgency(score)、needs_reply |
guard_questions() | LLM 输入护栏 | jailbreak、prompt_injection、sensitive_data、harm_severity(score)、topic(choice) |
moderation_questions() | 内容安全与审核 | toxic、harassment、threat、spam、severity(score) |
router_questions() | 智能模型路由 | difficulty(score)、domain(choice)、needs_tools(noul)、is_sensitive(noul) |
此外 laya_mlx/email.py 提供clean_email_body、email_state,laya_mlx/lang.py 提供detect_language、detect_script、is_english,全部在 laya_mlx/init.py 中统一导出(__version__为 0.1.0)。
测试与 benchmark:完整复现链路
单元测试使用小型随机模型,并包含与 Transformers 及固定版本上游决策头的直接对比(fixture 见 tests/conftest.py);真实检查点验证则覆盖 tokenization、logits、校准概率、重复输出与活跃内存增长。完整复现流程:
uv sync --extra dev --extra reference --extra benchmark --extra demo source .venv/bin/activate gh repo clone NandhaKishorM/laya .upstream git -C .upstream checkout 6a5819129eb220570792e417e49723d697efd76f pytest -q python -m benchmarks.download python -m benchmarks.validate --repeats 100 python -m benchmarks.run --iterations 50 --warmup 5 python -m benchmarks.accuracy --per-class 64 python -m benchmarks.reportGPU 测试应串行运行。benchmark 在每个新进程中运行各后端/检查点,并把全部计时样本写入 benchmarks/results(原始数据都在这里,含 FP16/FP32 的 P50/P95、吞吐量、内存、数值一致性、重复运行与固定抽样分类测试)。pytest中的 integration 标记需要已下载的真实检查点(对应 pyproject.toml 中的integrationmarker)。GitHub Actions 只在 macOS arm64 runner 上跑小模型 CPU 测试,完整检查点的 GPU benchmark 是本地测量,不属于托管 CI。
关于“再快 10 倍”的性能研究结论
仓库还附有两份面向“能否再快一个数量级”的深入报告,均基于本地实测而非推测:
- docs/PERFORMANCE_RESEARCH.md:初步性能研究,分析实现瓶颈、MLX kernel 派发与受控实验计划;
- docs/MATH_10X_RESEARCH.md:数学分析——计算预算、带宽条件下界、真实权重谱、精确复用,以及蒸馏学生模型的设计空间;
- docs/ENGINEERING_10X_RESEARCH.md:工程实测——编译、量化、最后一层输出裁剪、自定义 Metal 核与代表性矩阵乘法实验。
experiments/ 目录保存研究脚本与原始数据,各实验变体的耗时与数值一致性单独记录。目前证据不支持在相同检查点下普遍再快 10 倍:部分场景的逐轮配对中位加速约为 1.03–1.08 倍;误差区间、量化保真结果与自定义 Metal 核的实测详见工程报告——这本身就是一份克制且严谨的结论。
小结
Laya-MLX 用“单次前向传播 + 三类结构化问题”把本地决策推理的延迟压到毫秒级:M3 Max 上单问题端到端中位耗时 13.42 ms(英文 421M)与 7.39 ms(多语言 322M),吞吐量分别达 146.8 q/s 与 395.0 q/s,且 378/378 次 argmax 对齐验证了移植保真度。它适合工单分诊、邮件分类、LLM 输入护栏、内容审核等需要低延迟、低依赖、完全本地的结构化决策场景。项目采用 Apache-2.0 协议,原作者与移植说明见 NOTICE。需要留意的是:英文检查点不能替代 multilingual 检查点,模型输出概率也不等于答案必然正确——这些限制与上游保持一致。
【免费下载链接】laya-mlxNative MLX runtime for Laya typed decision models — 7–14 ms short decisions on M3 Max. No text generation, PyTorch, or cloud API.项目地址: https://gitcode.com/gh_mirrors/la/laya-mlx
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考