Commit ea8fa36
authored
feat(model): Add LFM2.5-2.6B text generation on ARM CPU (#701)
* feat(cpu): add LFM2.5-2.6B support
* refactor: register LFM2 hybrid compute paths
* fix: synchronize parallel linear fallback threads
* fix(cpu): address LFM2.5 runtime review findings
* perf(cpu): extend KAI shared-input fusion to I8MM prefill
Generalize matmul_shared_input_m1 into matmul_shared_input with an M
dimension so gate/up and q/k/v projections share one packed LHS during
prefill as well as decode. Work is distributed over the combined M x N
tile grid with overflow guards, and matmul_shared_input_m1 stays as a
thin wrapper.
The product path remains gated on the existing I8MM prefill screen, so
hosts without I8MM keep the per-projection fallback. Add two ARM
benchmarks that compare the fused and independent paths and assert
bitwise-equal sentinel hashes.
* refactor(cpu): make the causal conv operation target-neutral
Move the history-first depthwise causal convolution out of the gated
delta net directory into kernels/common/causal_conv, where a reusable
causal-convolution primitive belongs, and give it its own focused
bitwise oracle instead of hosting it in the Qwen3.5 GDN test.
Rename the activation hook from MLLM_LFM2_SHORT_CONV_TRACE to
MLLM_CAUSAL_CONV1D_TRACE and emit one marker per accumulation order, so
a framework-level operation no longer reports under a single model's
name. Drop the remaining model-specific wording from the shared kernel
and grouped-query attention comments.
* refactor(models): share default RoPE tables and guard fused projection names
Lift the plain default-RoPE inverse-frequency and sin/cos table helpers
out of the LFM2 and MiniCPM5 model headers into one shared model-side
header, with the input validation the model-local copies never had.
Both models used the identical no-scaling variant, so the generated
tables are unchanged. The helpers stay under mllm/models because they
materialize constant operation inputs rather than performing tensor
computation, which nn/llm_components must not host.
ParallelLinear resolves parameters in its parent scope to keep original
checkpoint names, which makes ambiguous projection names bind the wrong
tensors. Reject duplicate, empty, and scope-escaping names in reshape
and load, and document why the operation's own name is not part of the
parameter path.
* refactor: converge grouped-query attention into one operation
Decode-only grouped-query attention and the general path were two
framework operations with overlapping semantics, so a new model had no
way to tell which one it should reach for. Fold the decode operation
into GroupedQueryAttention as the DecodeNativeKV implementation: it
keeps its own reduction order and single-query-position contract, and
still runs the same decode kernel, so MiniCPM5 generation is unchanged.
nn::functional::groupedQueryAttentionDecode stays as the public entry
point. Graphs serialized under the old "GroupedQueryAttentionDecode"
op type still reconstruct, and OpTypes value 76 is retired rather than
reused so an old graph can never alias a different operation.
* fix(cpu): honour the output stride in the GQA decode fallback
The decode kernel declines any tensor whose last-dimension stride is not
1, so the scalar fallback is reached precisely when a non-unit output
stride is possible — yet it indexed the output as if the value dimension
were contiguous. Multiply by the output stride, matching what the
DirectStrided path already does and what the kernel is handed.
Not reachable today: reshape allocates the output through Tensor::empty,
so the stride is 1 and the emitted addresses are unchanged. This keeps
the fallback correct for any caller that supplies a strided output.
* fix(benchmark): parse sizes with a fixed-width integer type
.clang-tidy enables google-* with WarningsAsErrors '*', so the plain
long from std::strtol trips google-runtime-int and fails the build.
Parse with int64_t and std::strtoll in both benchmark drivers; the range
guard and return type are unchanged.
* fix(preprocessor): honour the checkpoint's ignore_merges flag
The LFM2.5 checkpoint sets model.ignore_merges, which keeps a token that
is already a vocabulary entry intact instead of rebuilding it from the
merge table. The shared BPE ignored the flag, and roughly 2% of
vocabulary entries longer than two characters cannot be reconstructed by
merges alone, so ordinary prose produced different ids than the
checkpoint's own tokenizer: "Croatia" became C/roat/ia rather than one
token, and so did words like congruence, PREFIX, and Türkiye.
Read the flag and short-circuit whole vocabulary entries when it is set.
Every other checkpoint in the tree reports ignore_merges false, so their
tokenization is bit-identical.
The existing pinned-oracle strings happen to contain no merge-unreachable
word, which is why they passed while the ids were wrong. Add a case that
does contain one; it fails without this fix.
* fix(cpu): release the fused projection's prefill workspace
CPUParallelLinearOp cached its KleidiAI LHS-pack scratch for every M and
only ever grew it, so a prefill-sized buffer stayed resident for the rest
of the process. CPULinearOp already avoids this by returning a throwaway
buffer whenever M != 1; the fused operation did not carry that policy
over when the shared-input path was extended to prefill.
On the 2.6B product configuration this pins a prefill workspace in each
of the 38 fused projections while decode needs about two kilobytes per
operation. Take M and apply the same policy.
The workspace is fully rewritten before any tile reads it, so this does
not affect results.
* fix(benchmark): initialize the runtime context before measuring
The parallel-linear driver never called mllm::initializeContext(), so on
the default threading vendor every tile-parallel call aborts and the
driver only survives at threads=1 - the one setting its multi-worker
screen is not about. Its shared-input sibling and the other CPU
benchmarks already initialize the context.
* fix(lfm2): reject a zero head count before dividing by it
head_dim's default divides hidden_size by num_attention_heads while
parsing. That expression is an ordinary function argument, so it is
evaluated whether or not the config supplies head_dim, and it runs long
before validate() can reject the value. A config with
num_attention_heads set to zero therefore divided by zero instead of
throwing. Check it where it is read.
* test(cpu): align LFM2 coverage with upstream layout
* test(cpu): keep the unified kernel registry concise
* test(nn): cover causal conv and parallel linear ops
* refactor(cpu): decouple parallel linear dispatch
* refactor(nn): encapsulate grouped attention options
* refactor(nn): hide causal convolution op options
* refactor(cpu): scope ARM OpenMP to owning sources1 parent 50ad5a9 commit ea8fa36
85 files changed
Lines changed: 4910 additions & 553 deletions
File tree
- benchmarks/cpu
- examples
- lfm2
- mllm
- backends/cpu
- kernels
- arm/linear
- common
- causal_conv
- linear
- parallel_linear
- x86
- ops
- compile
- ir
- linalg
- jit
- binary
- interpreter
- core
- aops
- models
- common
- lfm2
- minicpm5
- qwen3_5
- nn
- layers
- llm_components
- preprocessor
- tokenizers
- tests
- cpu
- nn
Some content is hidden
Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
107 | 107 | | |
108 | 108 | | |
109 | 109 | | |
| 110 | + | |
110 | 111 | | |
111 | 112 | | |
112 | 113 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | 2 | | |
3 | 3 | | |
4 | | - | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
| 241 | + | |
| 242 | + | |
| 243 | + | |
| 244 | + | |
| 245 | + | |
| 246 | + | |
| 247 | + | |
| 248 | + | |
| 249 | + | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
| 257 | + | |
| 258 | + | |
| 259 | + | |
| 260 | + | |
| 261 | + | |
| 262 | + | |
| 263 | + | |
| 264 | + | |
| 265 | + | |
| 266 | + | |
| 267 | + | |
| 268 | + | |
| 269 | + | |
| 270 | + | |
| 271 | + | |
| 272 | + | |
| 273 | + | |
| 274 | + | |
| 275 | + | |
| 276 | + | |
| 277 | + | |
| 278 | + | |
| 279 | + | |
| 280 | + | |
| 281 | + | |
| 282 | + | |
| 283 | + | |
| 284 | + | |
| 285 | + | |
| 286 | + | |
| 287 | + | |
| 288 | + | |
| 289 | + | |
| 290 | + | |
| 291 | + | |
| 292 | + | |
| 293 | + | |
| 294 | + | |
| 295 | + | |
| 296 | + | |
| 297 | + | |
0 commit comments