4B 档位越来越挤,模型方开始卷什么

发布时间:2026/10/24 3:45:36
4B 档位越来越挤,模型方开始卷什么

4B 档位越来越挤,模型方开始卷什么

【免费下载链接】Spark-X2.6-4BSpark-X2.6-4B 保留了与 Spark-X2.5-4B 完全相同的架构,通过改进的后训练实现了能力提升。它在编程、智能体、长上下文、多语言及推理等方面均有显著进步。这些进展使开发者和用户在处理真实世界任务时,能够使用更强大、更实用的端侧 AI。项目地址: https://ai.gitcode.com/SparkLLM/Spark-X2.6-4B

过去一年,开源小模型的竞争几乎被压缩进同一个区间:4B 上下、能在单卡甚至笔记本上跑起来、还要在榜单上不掉队。参数量的“性价比曲线”已经被反复试探到极限,于是同档位出现了明显的路线分叉——有人把上下文拉到百万 token,有人把 200+ 语言写进卖点,有人干脆把“Agent 可用性”当成第一指标。

Spark-X2.6-4B 是这个趋势里一个很适合拆解的样本:它没有改架构,却在官方 30 项基准上的平均分从上一代的 52.1 拉到 63.3,并在 Agent、代码、长上下文几项上正面压过 9B 级竞品。本文不打算复述宣传口径,而是结合仓库里的 config.json、modeling_spark.py、chat_template.jinja 与 README.md,回答三个问题:4B 档位到底在卷什么、这些“卷”在源码层面留下了什么痕迹、以及下一阶段的胜负手为什么落在工具链和文档上。

一、先看清“挤”到什么程度:4B 打 9B 已经不是个例叙事

官方公布的对比表把六个模型放在同一张桌子上——Spark-X2.6-4B、Spark-X2.5-4B、Qwen3.5-9B、Qwen3.5-4B、Gemma4-12B、Gemma4-E4B,覆盖 Agent、Code、Math、Long Context、General & Knowledge 五类共 30 项任务,平均分依次是 63.3 / 52.1 / 49.1 / 43.2 / 47.0 / 29.7。

这张表里有几个值得停下来看的地方,它们决定了后面所有的路线判断:

  • 同代内的跃升幅度大于跨尺寸差距。X2.6 比 X2.5 高了 11.2 分,而 4B 与 9B 之间的差距是 14.2 分。也就是说,一次后训练迭代,几乎就吃掉了一个“参数翻倍”的红利。
  • 不是全面碾压。SciCode 上 Qwen3.5-4B 是 39.8,Spark-X2.6-4B 是 36.8;GPQA Diamond 上 Qwen3.5-9B 的 81.7 仍高于 77.1;高考数学 134.9 对 135.5,基本打平。这些“没赢”的格子恰恰说明:4B 的分数提升是有明确方向的,而不是通用能力的线性放大。
  • 带*的数字来自公开模型卡与论文,只有部分项目是同一套评测流程重跑。跨模型比较时,这个区分必须保留,否则很容易把评测口径差异当成能力差异。

“4B 追平 9B”这句话真正的信息量不在于名次,而在于:当分数差距被压缩到个位数,选型决策的权重就会从“谁的榜高”转移到“谁在我这套工程里跑得动”。

二、同档位的三条分叉:长上下文派、多语言派、Agent 派

长上下文派:把 1M 写进配置,而不是写进宣传页

Spark 系列的路线非常明确:把原生长上下文当作基础设施,而非可选项。config.json 里max_position_embeddings直接是1048576,同时sliding_window为512,layer_types是一个 36 层的混合排布——每 4 层里前 3 层是sliding_attention,第 4 层是full_attention。

"layer_types": [ "sliding_attention", "sliding_attention", "sliding_attention", "full_attention", ... 共 36 层,其中 full_attention 出现 9 次 ]

这不是简单的“把 RoPE 基频调大”。仓库里同时存在两套位置编码参数:

"rope_parameters": { "full_attention": { "partial_rotary_factor": 0.25, "rope_theta": 5000000 }, "sliding_attention": { "partial_rotary_factor": 1.0, "rope_theta": 10000 } }

全注意力层用theta=5e6做长程外推,并且只对 25% 的维度施加旋转(256 维里旋 64 维);滑动窗口层则保持theta=10000的全维度旋转,负责局部、高频的位置区分。这套“分层分参数”的设计在 configuration_spark.py 里被固化成了两个访问器:

def get_rope_theta(self, layer_type): params = self.rope_parameters.get(layer_type, {}) return params.get("rope_theta", 10000) def get_partial_rotary_factor(self, layer_type): params = self.rope_parameters.get(layer_type, {}) return params.get("partial_rotary_factor", 1.0)

而在 modeling_spark.py 中,模型一次性为每种层类型预先算好 cos/sin,再按层取用:

for lt in set(self.config.layer_types): rope_theta = self.config.get_rope_theta(lt) prf = self.config.get_partial_rotary_factor(lt) cos, sin = compute_rope_cos_sin(cache_position, head_dim, rope_theta, partial_rotary_factor=prf, device=device) rope_cache[lt] = (cos, sin)

效果在数据上很直观:Graphwalks BFS 256K F1 从上一代 47.1 跳到 92.2,1M 档从 2.5 到 34.2;OpenAI-MRCR v2 的 256K–512K 段从 9.7 到 65.6。这些不是“能塞进去”,而是“塞进去还能检索到”。

Agent 派:把基准选在工具调用上,而不是知识问答上

Spark-X2.6 的官方定位是 “On-Device Agents for Real-World Tasks”,基准选型也照着这个定位走。看下面这组 Agent 与长上下文的横向切面:

几个数字的落差值得注意:

基准Spark-X2.6-4BQwen3.5-9B说明
τ³-bench34.79.3通用 Agent 多轮任务,差距最悬殊
MCP-Atlas62.447.4*MCP 工具使用
BFCL-V471.866.1*函数调用
SWebBench71.733.2网页浏览与检索(自建基准)
PAgentBench74.153.8个人助理任务(自建基准)

SWebBench与PAgentBench是官方自建、尚未开源的两项基准。它们的存在本身就说明了路线选择:端侧 Agent 的真实瓶颈不在 MMLU,而在“会不会调工具、会不会多步走、能不能在浏览器里把事办完”。但也要承认,自建基准在第三方复现之前,只能当作方向性证据。

多语言派:词表 131072,但“200+ 语言”需要自己验证

config.json 的vocab_size是131072,对 4B 模型来说是相当宽的词表——这是多语言能力的前提成本。上一代 Spark-X2.5 的社区解读里明确提到“200+ 语言”与 1M 上下文并列作为卖点,X2.6 则把多语言与长上下文一起归入“显著进步的五个方向”。

但需要克制:本仓库的 README.md 元信息只声明了en与zh两种语言,官方基准表里也没有多语言分项。宽词表 ≠ 多语言达标,真正要判断一个 4B 模型能不能做小语种,只能自己拿业务语料跑一遍困惑度与生成质量。这一点在选型时比任何宣传语都可靠。

三、架构一行没改,为什么反而是理性的选择

X2.6 最反直觉的地方是:架构与 X2.5完全相同,能力提升全部来自后训练。仓库里的证据链是闭合的:

  • config.json 的architectures仍是Spark2_5ForCausalLM,model_type仍是spark2_5,auto_map指向 modeling_spark.py 与 configuration_spark.py 里的Spark2_5*类。
  • model.safetensors.index.json 的元数据写着total_parameters: 4112079360、total_size: 8224158720——4.11B 参数、8.22 GB,正好是 bf16 的两倍关系,说明这是一份标准精度的权重发布,而非量化包。
  • 结构参数:hidden_size=2560、num_hidden_layers=36、num_attention_heads=16、num_key_value_heads=4、intermediate_size=10240。

在这个结构里有两处“非常规”设计,值得单独拎出来:

其一,head_dim=256让 Q 维度大于隐藏维度。16 个注意力头 × 256 = 4096,而 hidden_size 只有 2560。也就是说 Q 投影是升维的(2560 → 4096),KV 则是4 × 256 = 1024每边。Q 升维的代价是显存与算力,收益是每个头有更大的表达空间——对小模型来说,这相当于在不增加层数的前提下“加深”单个注意力头的容量。

其二,逐头注意力输出门控。headwise_attn_output_gate: true、gate_attn_act_mode: "sigmoid",在 modeling_spark.py 里对应一个极小的投影:

self.g_proj = nn.Linear(self.hidden_size, self.num_heads, bias=config.attention_bias) # forward 中 gate_score = self.g_proj(hidden_states) # [bsz, seq, num_heads, 1] ... gate = torch.sigmoid(gate_score.float()).to(attn_output.dtype) attn_output = attn_output * gate

hidden_size → num_heads只有 2560 × 16 = 40960 个参数,相对 4B 总量几乎免费,却给了每个头一个输入相关的开关。这种“极低参数成本的表达力增强”是小模型架构设计的典型手法——在 4B 这个预算下,任何新结构都必须先回答“它值多少参数”。

架构冻结的工程意义同样明显:权重量产、算子适配、NPU/GPU 后端、量化校准全部可以复用。X2.6 能在 1 个月左右完成换代(README 写着 “A little over a month has passed since Spark-X2.5”),靠的正是把变量收敛到数据与后训练配方上。

四、竞争维度确实在迁移:从“分数”到“能不能接进我的系统”

如果说架构是模型方的事,那么部署矩阵就是使用方的事。而这个仓库在“工程可用性”上的投入密度,已经超过了多数 4B 模型。

推理后端:一份权重,七种跑法

README.md 给出的路径覆盖了几乎全部主流栈:

  • Transformers:pip install transformers==4.57.1+trust_remote_code=True,三个依赖项必须对齐,否则auto_map加载会失败。
  • SGLang / vLLM:OpenAI 兼容 API,--context-length 1048576,--tool-call-parser spark25,--reasoning-parser qwen3。
  • Ascend NPU:A2 / A3 / 950DT 三套镜像分别给出,这不是“顺手支持”,而是国产化部署的硬需求。
  • MLX:Spark-MLX-LLM直接跑原始 HF checkpoint,不需要 GGUF 转换,覆盖 Apple silicon、Linux CPU、CUDA 12/13。
  • llama.cpp:明确标注原生spark2_5支持需要b10828或更新版本。
  • Ollama / LM Studio / Unsloth:分别给了最低版本(v0.34.1、0.4.0)。

这张吞吐图的价值不在“快了多少”,而在于它把测试条件写清楚了:RTX 4090 上llama.cpp · Q4_K_M与TensorRT · INT4并列,Mac M5 Pro 上llama.cpp与MLX · INT4并列,横轴是 4K / 32K / 128K / 256K 四档上下文,并且注明了 “Missing measurements are omitted. Each test group uses its own throughput scale.”——分区独立坐标轴这种细节,比一个漂亮的倍数更能说明数据是实测的。

思考预算:把“延迟”做成一个可调参数

这是一个真正面向产品侧的接口设计。README 写的是三档none / low / high,但落到模板上是两种机制的组合。chat_template.jinja 里:

{%- set enable_thinking = enable_thinking | default(true) %} {%- if enable_thinking is true %} {%- set resolved_reasoning_effort = reasoning_effort|default('high') %} {%- if resolved_reasoning_effort not in ('high', 'low') %} {{- raise_exception('Unexpected reasoning effort ' ~ reasoning_effort ~ '...') }} {%- endif %}

即:enable_thinking=false对应none,enable_thinking=true时再由reasoning_effort在low/high之间选。提示词层面做的事情很朴素——注入一句`thinking_effort` guides on how much to think in your thinking channel,然后根据档位改写;生成提示部分则决定是否预填<think>:

{%- if add_generation_prompt %} {{- '<|start▁of▁sentence|><|Bot|>' }} {%- if enable_thinking is defined and enable_thinking %}{{- '<think>' }}{%- endif %} {%- if enable_thinking is defined and not enable_thinking %}{{- '</think>' }}{%- endif %} {%- endif %}

关闭思考时直接补一个空闭合</think>,模型就会跳过思考通道进入回答通道。这是个很“工程”的技巧:不改模型、不动解码器,用模板控制 token 流形态,从而在简单问答上换回首 token 延迟。

工具调用协议:写进模板,而不是写在文档里

Agent 能力要落地,第一步是让模型输出能被解析。chat_template.jinja 把工具描述与调用格式都定义成了特殊 token 结构:

{{- '<tool_call>' + tool_call.function.name }} {%- for k, v in args.items() %} {{- '<arg_key>' ~ k ~ '</arg_key><arg_value>' ~ (...) ~ '</arg_value>' }} {%- endfor %} {{- '</tool_call>' }}

并且对历史消息有强校验:

{%- if tool_call.function.arguments is not mapping %} {{- raise_exception('tool_call.function.arguments must be a dictionary; normalize JSON strings before apply_chat_template') }} {%- endif %}

这条报错信息非常关键:它意味着调用方必须先把 arguments 反序列化成 dict,直接传 JSON 字符串会在模板渲染阶段炸掉。这是典型的“文档不写清就会踩”的坑,也是工具链成熟度的真实刻度。

对应的服务端参数是--tool-call-parser spark25。注意命名:解析器叫spark25,模型叫Spark-X2.6,二者不同名是刻意的——协议格式沿用了 X2.5,属于兼容性保留。

五、源码里那些“还没补齐”的细节,才是选型该看的地方

如果只读 README,会以为一切齐备。但把配置文件摊开对照,会发现几处需要在落地前确认的地方——这些不是黑点,而是任何快速迭代的开源模型都会有的接缝,提前知道能省掉一整天的排查。

其一,1M 上下文与分词器上限的错位。config.json 的max_position_embeddings是1048576,generation_config.json 的max_tokens也是1048576,但 tokenizer_config.json 里model_max_length是131072。模型能算 1M,不代表分词器/框架不会在 128K 处提前截断或告警。实际长文档任务必须显式检查并覆盖这个字段。

其二,1M 上下文的显存账要自己算。按 bf16 计,每 token 每层 KV 为2 × 4 heads × 256 dim × 2 bytes = 4 KB。9 个全注意力层意味着每 token 约 36 KB,1M token 就是约 36 GB——这还没算激活值。27 个滑动窗口层因为sliding_window=512,整体只占约 54 MB,可以视为常数。所以官方注释里那句 “This setting requires sufficient device memory; reduce--context-lengthwhen necessary” 不是客套话,而是硬约束。想真正吃满 1M,要么上 FP8/INT4 KV cache,要么接受多卡切分。

其三,服务名不统一。SGLang 用--served-model-name spark2.6,vLLM 的示例用--served-model-name spark25,而客户端 curl 里又分别对应"model": "spark2.6"和"model": "spark25"。照抄命令时如果只改一半,会得到model not found。这类细节不影响能力,但直接影响“半小时能不能跑通”。

其四,模板对reasoning_effort的取值范围是硬校验。传入medium会直接抛异常,而不是静默降级。对集成方来说这是好事(快速失败),对不读模板的人来说是坑。

把这几条放在一起看,结论其实很清楚:4B 档位的竞争,已经从“谁的分数高”转向“谁能被无痛集成”。一个模型的真实成本 = 权重加载成本 + 后端适配成本 + 协议对齐成本 + 长上下文显存成本。前两项 Spark 做得不错,后两项还有打磨空间。

六、下一步的胜负手:谁先把工具链和文档补齐

回到标题的问题——4B 档位越来越挤,模型方开始卷什么?从 Spark-X2.6-4B 这个样本能看出三层答案。

第一层,卷“同架构下的后训练效率”。架构冻结、参数不变、一个月一版,把迭代周期压到可以跟上应用侧节奏。这是能力层面的竞争,也是门槛最低的一层——只要数据和算力到位,别人也能做。

第二层,卷“能力的方向选择”。把 1M 上下文、Agent 工具调用、可控思考预算做成默认能力,而不是论文里的可选项。选择哪条路线,决定了模型在哪些场景里“看起来很强”。风险也在这里:自建基准(SWebBench、PAgentBench)在没有第三方复现前,说服力有限。

第三层,卷“接入成本”。这一层最不起眼,却最决定留存。判断标准很具体:

  • 后端是否覆盖你的硬件(NVIDIA / Ascend / Apple Silicon / CPU);
  • 版本约束是否写清(Transformers 4.57.1、llama.cpp b10828、Ollama v0.34.1、LM Studio 0.4.0);
  • 协议是否有官方 parser 直接可用(spark25);
  • 是否需要格式转换(MLX 路线明确“No GGUF conversion is required”,这就是降低摩擦);
  • 微调链路是否闭环(README 指向 Llama-Factory);
  • 许可证是否清晰(LICENSE 为 Apache 2.0,商用无附加条款)。

这六条全部满足,一个 4B 模型才算“能用”;缺一条,团队就要多花一到两天做适配。而 4B 档位的现实是:候选模型往往不止一个,谁的接入成本低,谁就先被接进去;一旦接进去,除非能力差距大到无法忽视,否则很难被替换。

所以真正的胜负手不是下一个基准点,而是文档与工具链的完备度。Spark-X2.6-4B 已经把权重、模板、解析器、多后端命令、NPU 镜像都摆了出来,剩下的缺口也很具体:分词器长度与 1M 上下文的声明对齐、服务命名统一、思考档位与模板取值的文档映射。这些是“补文档”就能解决的,但也恰恰是最容易被跳过的一步。

4B 档位的下一轮竞争,大概率不会发生在榜单上,而会发生在某个开发者照着 README 敲命令的那十分钟里。

【免费下载链接】Spark-X2.6-4BSpark-X2.6-4B 保留了与 Spark-X2.5-4B 完全相同的架构,通过改进的后训练实现了能力提升。它在编程、智能体、长上下文、多语言及推理等方面均有显著进步。这些进展使开发者和用户在处理真实世界任务时,能够使用更强大、更实用的端侧 AI。项目地址: https://ai.gitcode.com/SparkLLM/Spark-X2.6-4B

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考