来自阿布扎比的 K2-Horizon 到底怎么样?M5 实测:会做题,写中文却翻车
我们把来自阿布扎比的 K2-Horizon MoVA 接入 zLLM,在 Apple M5 24 GiB 上跑通 IQ3_XS、Q8 KV 与 Metal replay,再实测它做题和写中文究竟怎么样。
来自阿布扎比的 K2-Horizon 到底怎么样?让它算一道题,再让它写一篇文章, 得到的是两张很不一样的成绩单。
zLLM 接入 K2-Horizon-MoVA-36B-A4B 后,我在一台 Apple M5、24 GiB 统一内存的 Mac 上, 用 IQ3_XS 量化版本试了两道题,又让它写了两篇中文短文。蜗牛爬井和猫抓老鼠都答对了。 但到了菜市场随笔,正文开始混入英语、日语和阿拉伯语片段,最后因为重复生成而停止。
把温度降到零之后,文章有了改善,却没有完全解决问题。 不过,在评价模型之前还得回答一个更基础的问题:zLLM 为了让它在 Metal 上完整跑起来, 究竟接入了什么?K2-Horizon 并不是换一组矩阵尺寸就能复用普通 Transformer 的模型。 它把稀疏专家放进了 attention 的 value 路径,一层里会出现两套路由。
因此,这篇文章有两条主线:前半部分讲 zLLM 如何把 K2 的 GGUF、MoVA、MoE 和 KV cache 接成完整推理;后半部分再看会做题与能稳定写好中文之间的距离。
先认清来源:IFM 总部在阿布扎比
K2-Horizon 来自 Institute of Foundation Models,简称 IFM。 根据官方介绍,IFM 隶属穆罕默德·本·扎耶德人工智能大学(MBZUAI),总部位于阿联酋阿布扎比, 在巴黎和硅谷设有研究中心。K2 Horizon 系列于 2026 年 9 月 3 日发布。 IFM 机构介绍;官方发布公告
因此,把它直接称为“欧洲模型”并不准确。更准确地说,这是一个由总部位于阿布扎比、 在巴黎和硅谷设有研究中心的全球团队推出的模型。
36B 的容量,约 4B 的激活量
这次运行的模型全名是 K2-Horizon-MoVA-36B-A4B。 官方以 36B 总参数、每个 token 激活约 4B 参数的口径命名,标称上下文长度为 524,288 tokens。 这些是官方规格,本次没有测试 512K 长上下文。官方模型卡
普通 MoE 主要在前馈网络里挑选少量专家。K2-Horizon 的 MoVA 则把专家路由进一步用到 attention 的 value 路径,让注意力中的值表示也能使用稀疏专家。这是这个模型在架构上的一个特点。 IFM 对 MoVA 的说明
本地 GGUF 元数据对应 48 层、2,560 维 hidden、32 个 query heads 和 8 个 KV heads; value experts 是 64 选 4,前馈部分在前 3 个 dense 层之后使用 100 选 8 的 routed experts, 另有 shared expert。zLLM 已接入相应的分组 RMSNorm、attention output gate、MoVA、MoE 以及完整的 Metal prefill/decode 路径,并支持 Q8g64 KV cache。
实际使用的权重文件是:
K2-Horizon-MoVA-36B-A4B-IQ3_XS.gguf
文件大小为 15,695,083,968 bytes,约 14.62 GiB。 它是第三方低比特量化文件,内部混用了 IQ3_S、IQ3_XXS、Q3_K、Q6_K 和 F32 张量, 并不是所有参数都存成同一种“三比特”格式。
每个 token 只激活少量专家,减少的是计算量,不代表只需加载 4B 参数的权重。 本次测到的质量也只属于这个 GGUF 文件与运行配置,不能直接当成官方 BF16 模型的表现。
接入第一步:不能把 K2 当成普通 GQA
zLLM 没有把 K2 的常量塞进 Metal kernel。模型尺寸、层数、norm、attention、专家数量和
Top-K 都集中在独立的 K2HorizonConfig;打开 GGUF 时,再从元数据逐项读取并校验。
这些校验不只看 hidden size。当前实现会确认模型使用 sigmoid expert routing、选中专家权重需要 归一化、每个稀疏层带一个 shared expert、key/value head dimension 一致,以及 dense 前缀后 每层都进入 MoE。一个结构不符的 GGUF 会在装载阶段明确失败,而不是带着错误 shape 进入 GPU。
模型编排则放在独立的 runtime/k2_horizon/。它描述的是 K2 一层真实发生的事情:
hidden
→ grouped RMSNorm
→ query + attention gate
→ key
→ dense value(前 3 层)或 64 选 4 value experts(后 45 层)
→ RoPE + GQA + KV append
→ attention gate + output projection + residual
→ grouped RMSNorm
→ dense FFN(前 3 层)或 100 选 8 routed experts + shared expert
→ residual
这里最容易接错的是 value。普通 GQA 的 value 是一次线性投影;K2 从第 4 层开始,先用 sigmoid router 和 bias 从 64 个 value experts 中选 4 个,再计算并加权合并它们的输出。 它输出 8 个 KV heads、每个 128 维,共 1,024 维 value,随后才进入 attention。
FFN 还有另一套独立路由:100 个 routed experts 选 8 个,同时执行 shared expert, 选中权重归一化后再乘 2.5 的缩放因子。两套路由读的是不同权重,服务的是不同数据流, 不能因为它们都叫“专家”就合并成一个通用分支。
第三方 GGUF 把很小但敏感的 MoVA router 量化成了 IQ3_S。zLLM 装载时只把这部分局部解码为 F32 并常驻,让路由打分保持稳定;大块 value expert 权重仍保留量化格式交给 Metal 直接计算。 这个处理没有放宽整个权重层的契约,也避免为了一个小 router 展开整组专家权重。
接入第二步:IQ3_XS 不是一种 kernel
文件名写着 IQ3_XS,真正打开张量目录后却能看到五种存储类型:322 个 IQ3_S、 240 个 IQ3_XXS、3 个 Q3_K、1 个 Q6_K 和 232 个 F32 张量。 一个只实现“IQ3”的入口远远不够。
为完整执行这个文件,Metal 路径需要处理普通量化 GEMV、gate/up 融合、按专家编号索引的 gate/up/down、MoVA 的 64 选 4 value 计算,以及 250,624 词表的 Q6_K lm_head。 这些 kernel 直接读取 packed GGUF 权重;如果每生成一个 token 都先全量反量化, 14.62 GiB 文件带来的容量和带宽收益就会被破坏。
prefill 与 decode 也不能硬套一个执行形态。prefill 同时处理多行 token,先完成批量路由, 再按 expert 分组执行;decode 每次只有一行,走设备常驻的 Top-K 结果和 indexed expert kernel。 在 45 个稀疏层里,每层有 100 个 FFN experts,一共 4,500 组 expert 权重,约 10.00 GiB。 本次零配置运行会在加载阶段把它们预载到统一内存可见的 Metal 资源中,避免生成过程中逐层读盘。
接入第三步:24 GiB 里还要给 KV 留位置
“权重文件只有 14.62 GiB”不等于剩余内存都能拿来做上下文。模型运行还需要 expert resident 资源、activation、scratch、Metal 对象和系统本身。zLLM 启动时读取模型结构与机器预算, 再计算每 token 的 KV 成本。本机日志给出的结果是:
权重 14.62 GiB
KV 预算 0.96 GiB
KV 每 token 约 101,376 bytes
最终上下文 9,216 tokens
K2 在 24 GiB 机器上采用单独的四分之三工作集上限,并至少给系统留 2 GiB 余量。 KV cache 默认使用 Q8g64:每 64 个值共享一组量化参数。短上下文使用 direct attention; 超过 256 tokens 后切到 split-KV,把历史分段并行计算,再归并局部结果。 这也是为什么模型标称 524,288 tokens,而这台机器的零配置入口只选择 9,216 tokens。
完整 decode 怎么压进一次 replay
普通 decode 一轮要走完 48 层。早期 profile 里,单个 token 会产生约 1,100 个 Metal commands; 如果每轮都由 CPU 重建和提交这张图,调度成本会不断重复。
zLLM 为 K2 单独实现了完整单 token decode replay。第一次记录从已上传的 token embedding、48 层 attention、 两套 expert 路由、MoE、final norm、lm_head 到 argmax 的执行序列;之后只更新 token、position 和 KV 写入位置,再重放同一计划。Q8 attention 的 replay 会根据当前位置选择 direct 或 split-KV pipeline,因此跨过 256-token 边界后仍使用正确的长上下文路径。
这里有一个容易被性能数字掩盖的限制:greedy 生成可以重放整轮;使用 temperature/top-p 随机采样时,需要取得 logits 并维护采样状态,当前 K2 路径不会进入同一 replay 快路。 所以后面的官方采样写作测试,不能拿 greedy 的约 30 tok/s 直接类比。
“能出字”之前,先证明数值链路成立
接入验收没有停在“模型加载成功”。现有记录包含几组分层验证:
- K2 的 Q8 direct/split KV append 与 reference 对拍通过;
- BF16 split-KV 曾发现 vectorized kernel 参数绑定回归,修复后通过 CPU oracle;
- Q6_K GEMV 与解码权重后的 dot product 对拍通过;
- 250,624 词表下,Metal top-p sampling 用多个随机点与 CPU reference 逐点一致;
- 完整真实权重完成 load、prefill、decode、Q8 KV 和 replay 端到端运行。
同一组四道短题在 low/greedy 设置下,zLLM 与 llama.cpp 对同一个 GGUF 给出了逐题相同的错误答案; 切到 high reasoning 并给足预算后,既有 llama.cpp 记录在 3 个随机种子中得到 12/12 正确。 这个对照不能证明所有数值绝对一致,但它排除了“那四个答案只在 zLLM 上算错”的简单归因。
console 还踩过一个比 kernel 更隐蔽的坑:通用入口曾显式发送 enable_thinking=false。
K2 的 chat template 会把它解释成一个空思考围栏,相当于强制模型直接回答,输出容易出现多语种混杂。
修复后的入口不替 K2 决定这个字段,让模板保持默认 high thinking;MiniCPM5 和 Qwen
仍按各自已经验证的设置处理。模型支持不止是矩阵乘法,模板语义同样属于推理正确性。
现在跑多快,瓶颈在哪里
相同 55-token prompt、greedy、256-token completion 的既有记录中,llama.cpp 使用 F16 KV, prefill 为 0.322 秒,decode 为 7.800 秒,即 32.691 tok/s;zLLM 使用 Q8 KV,最好记录的 prefill 为 5.999 秒,decode 为 8.623 秒,即 29.690 tok/s,慢 9.18%。
这不是一场完全同配置的 KV 对照,但足以说明当前状态:decode 已接近,短 prompt prefill 仍明显落后。单 token profile 中,两套 sigmoid router——100 选 8 的 FFN 路由和 64 选 4 的 value 路由——排在 GPU 时间前列;256 tokens 之后的 split-KV 和 replay 提交成本也还有空间。
最后一轮压力测试曾因机器热降频,把直通 decode 从约 30 ms/token 拉到 80–100 ms/token。 因此文章保留降频前的可复现数据,不拿过热后的瞬时结果代表正常性能。
先给足思考预算,再看答案
测试采用 high 思考档,temperature 为 1.0,top_p 为 0.95,与官方推荐一致。 每个请求的上下文上限为 9,216 tokens,最多生成 4,096 tokens;问题之间不共享对话历史。 官方推荐设置
思考档位不是界面上的显示选项。本地模板会根据档位改变提示中的思考标记; 关闭思考时,还会注入一个空的思考围栏。拿一个仍在思考、却被几十个 token 上限截断的输出评分, 很容易把“还没答完”误写成“答错了”。
这里两道题都生成到了正常结束:
| 题目 | 正确答案 | 实测答案 | 生成 token 数,含思考 |
|---|---|---|---|
| 蜗牛白天爬 3 米、夜里滑 2 米,爬出 10 米深的井需要几天? | 第 8 天 | 第 8 天 | 1,344 |
| 3 只猫 3 分钟抓 3 只老鼠,9 只猫抓 9 只老鼠需要多久? | 3 分钟 | 3 分钟 | 700 |
蜗牛题的关键是最后一天爬出后不再下滑。猫抓老鼠的关键是工作并行,猫与老鼠数量同时增加三倍, 时间不变。模型最终都处理对了。
但第一道题的解释里已经出现了这样的句子:
每天 daytime 爬升 3 米, nighttime 回滑 2 米
后面还有“白天下爬到”这样的病句。答案正确,并没有让说明文字自动变得自然。
一写菜市场,问题就明显了
第一篇写作测试要求:
请写一篇400至600字的中文生活随笔,题目《雨停之后的菜市场》。通过具体人物、动作、声音和气味组织文章,语言自然克制,不写提纲,不解释写作过程,直接给出标题和正文。
提示要求全程使用中文,没有任何外语写作要求。模型给出的正文开头却是:
雨刚停,菜市场还不全是人。薄荷黑布遮在丑毛さらに码头上,粪水从竹筐的缝隙里滴着Becoming一条条银白色的短河。
さらに 是日语,Becoming 是英语。后面继续出现 Granny、へえ 和 السجائر,
末尾停在 ポリ袋ákááááááááá,运行时给出的结束原因是 repetition。
这已经超过了“有一点翻译腔”的程度。即使把外语删掉,句子也没有清楚的人物、动作与空间关系。 按只统计正文汉字的口径,最终只有 88 字,远没有完成要求的随笔。
第二篇换成说明性短文,题目是《小模型能做题,就能写好文章吗?》, 要求区分答案正确、推理可靠和语言自然,并给出一个具体例子。
这一次有了完整段落,但正文仍然写出:
Such表述语言自然。
最后把推理过程写成一段连贯的文字 describing the solving journey, 语言自然。
结尾甚至出现:
再 fingernail 推理的透明度和语言的自然度。
fingernail 在这里没有合理语义。问题还不只在词汇:文章把“语言自然”说成文字创作专属,
又试图用一道方程的解法证明写作能力,论证范围也没有把握好。
这篇的结束原因是 stop。它提醒我们:正常停止,只代表生成过程结束;文章是否合格,还要读正文。
温度降到零,能改善多少?
我保留 high 思考档,用完全相同的两条写作提示,把 temperature 改成 0,各再运行一次。
| 文体 | temperature=1.0 | temperature=0 |
|---|---|---|
| 菜市场随笔 | 多语种混杂,重复终止;正文 88 个汉字 | 正常结束,489 个汉字;仍夹杂英语 |
| 评价维度短文 | 正常结束,481 个汉字;有无意义英语插入 | 正常结束,652 个汉字;中文明显改善,但超出篇幅要求 |
这里的字数只统计标题后正文中的汉字,不含标点、拉丁字母与 Markdown 标记。
零温度的菜市场随笔有了完整场景,但仍写出 freshly 捕来的鲫鱼、Somehow 却不显得刺鼻、
女孩 grabs 住鱼。声音描写也很奇怪:模型把议价声写成“咔嚓”。
评价维度短文改善得更多,没有再出现前一轮那种无意义的外语插入。
文中的 AI 是常见缩写,不应算作语言失控。不过,它仍没有守住 400–600 字的要求。
因此,这轮结果不能简单概括成“它不会中文”。更准确的是: 在这组提示和当前运行环境下,中文写作的完成度、语言一致性与指令遵循还不稳定,文体和生成设置都会影响体验。
这里还留着一个工程问题:异常来自哪里?本次没有用相同提示对照 BF16 权重, 也没有在另一个引擎上重跑这两篇文章,因此无法分开量化损失、引擎数值、模板和模型自身的影响。 降低温度还会改变当前 zLLM K2 路径的采样状态与 replay decode 的启用资格, 这组对照也不能用来做严格的单一因素归因。
它足以说明当前这套本地配置的使用体验,却不足以给整个模型家族下结论。
在 zLLM 中运行
从 zLLM 仓库目录使用已经编译好的 release 程序,权重路径替换成自己的位置:
./target/release/zllm-metal \
/Volumes/ORICO/models/K2-Horizon-MoVA-36B-A4B-IQ3_XS.gguf
--release 属于 Cargo 的构建参数,不需要传给 zllm-metal。
加载完成后直接输入问题,/reset 清空对话,/exit 退出。
本机启动时自动选择了 9,216-token 上下文;官方标称的 512K 不等于这台机器的可用容量。
这次接入让 zLLM 多了一条可运行的 MoVA 模型路径,真实写作又给它补上了能力边界。 以后再看一个模型的做题成绩,我会顺手让它写一篇具体的生活随笔: 没有唯一答案的任务,往往更容易看清语言是否连贯、细节是否成立,以及一篇文章能不能真正交到读者手里。
测试记录
测试日期为 2026 年 9 月 6 日。环境为 Apple M5、24 GiB,GGUF IQ3_XS,Metal,Q8g64 KV cache。 共六次独立请求,temperature=1.0 的四次请求未固定随机种子;temperature=0 的两次为写作对照。 生成预算均为 4,096 tokens,包含思考与回答。本次采样生成不作为 replay 性能测试。
正式运行入口是 zllm-metal,传入 K2 的 GGUF 路径后自动识别并加载模型。
质量测试复用了 zllm-metal 的正式推理路径和已有 release 构建,没有重新编译引擎。
记录中的源码 HEAD 仅表示测试时的工作区版本,不等于已核实的二进制构建提交。
运行时返回合并文本流,未单独拆分思考与回答;本文只从明确标题后的正文摘录写作内容,
按末尾的最终答案核验做题结果,没有把思考中的英语算成中文正文错误。
完整提示、六次原始输出、结束原因和运行版本指纹(JSON) 可供复核。另附采样日志、零温度日志、 采样配置和零温度配置。 JSON 已将日志中的换行还原,模型文字保持原样。配置含测试机的路径,跨机器复现时需要修改。
算子验证与先前的性能记录见既有接入报告, 其中的数据与这次写作测试分别记录。
