Repository navigation
feat(spark): support Spark-X2.5-1.7B on CPU #709
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -13,6 +13,8 @@ mllm | |||||
|
|
||||||
| [📚 Documentation](https://ubiquitouslearning.github.io/mllm/) • [🚀 Quick Start](#quick-starts) • [💡 Examples](./examples/) • [🛠️ Installation](#installation) | ||||||
|
|
||||||
| [简体中文](./README-ZH.md) | ||||||
|
|
||||||
| </div> | ||||||
|
|
||||||
| ## Latest News | ||||||
|
|
@@ -109,6 +111,7 @@ The mllm framework integrates seamlessly with popular community frameworks' chec | |||||
| | [Qwen3.5-4B](https://huggingface.co/Qwen/Qwen3.5-4B) | [✔️ w4a8](./examples/qwen3_5/README.md) | | | | ||||||
| | [MiniCPM5-1B](https://huggingface.co/openbmb/MiniCPM5-1B) | [✔️ w4a8](./examples/minicpm5/README.md) | | | | ||||||
| | [MiniCPM5-2B](https://huggingface.co/openbmb/MiniCPM5-2B) | [✔️ w4a8](./examples/minicpm5/README.md) | | | | ||||||
| | [Spark-X2.5-1.7B](https://huggingface.co/XHToken/Spark-X2.5-1.7B) | [✔️ FP32 / KAI W4A32](./examples/spark2_5/README.md) | | | | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win Label KAI W4A32 as ARM-only. The table presents KAI W4A32 as generic CPU support. The supported scope limits this artifact to ARM. Change the label to Proposed documentation fix-| [Spark-X2.5-1.7B](https://huggingface.co/XHToken/Spark-X2.5-1.7B) | [✔️ FP32 / KAI W4A32](./examples/spark2_5/README.md) | | |
+| [Spark-X2.5-1.7B](https://huggingface.co/XHToken/Spark-X2.5-1.7B) | [✔️ FP32 / ARM KAI W4A32](./examples/spark2_5/README.md) | | |📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||
| | [LFM2.5-2.6B](https://huggingface.co/LiquidAI/LFM2.5-2.6B) | [✔️ w4a8](./examples/lfm2/README.md) | | | | ||||||
| | [DeepSeek-OCR](https://github.com/deepseek-ai/DeepSeek-OCR) | [✔️ w4a8](https://www.modelscope.cn/models/mllmTeam/DeepSeek-OCR-w4a8-i8mm-kai) | | | | ||||||
| | [SmolLM3](https://huggingface.co/blog/smollm3)| [✔️ w4a8](https://www.modelscope.cn/models/mllmTeam/SmolLM3-3B-w4a8-i8mm-kai) | | | | ||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| add_executable(mllm-spark25-runner main.cpp) | ||
| target_link_libraries(mllm-spark25-runner PRIVATE MllmRT MllmCPUBackend) | ||
| target_include_directories(mllm-spark25-runner PRIVATE ${MLLM_INCLUDE_DIR}) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,79 @@ | ||
| # Spark-X2.5-1.7B on CPU | ||
|
|
||
| This example implements `XHToken/Spark-X2.5-1.7B` at revision | ||
| `448e61eb392c00f2c403185c5b56d5e0665bfaab`: 28 dense decoder layers, | ||
| 8 query / 2 KV heads of dimension 256, headwise sigmoid attention gates, | ||
| GELU-gated MLPs, and shared source embedding/output weights. | ||
| Every fourth layer uses full attention with 64-dimensional partial RoPE; | ||
| the other layers use a 512-token causal window and full-dimensional RoPE. | ||
|
|
||
| The runtime supports batch-1 CPU text generation, FP32 and ARM KAI W4A32. | ||
| The supplied configs cap context at 4096 tokens; `max_cache_length` can be | ||
| increased to 8192 for larger requests. The checkpoint's 1M positional range is | ||
| not a tested mobile capacity claim. Full-attention KV grows with context; | ||
| sliding layers retain only the 511 past tokens needed by their next forward. | ||
| Chunked prefill uses absolute positions and per-query window boundaries. | ||
|
|
||
| ## Convert | ||
|
|
||
| Download the pinned checkpoint, including tokenizer.json and the index/shards. | ||
| Use separate paths for checkpoint, converted files, and build output. | ||
| The converter validates every tensor name, shape and source dtype, and refuses | ||
| to overwrite existing output or partial files. | ||
|
|
||
| ```bash | ||
| python examples/spark2_5/convert.py \ | ||
| --checkpoint /path/to/Spark-X2.5-1.7B \ | ||
| --output /path/to/spark-fp32.mllm | ||
|
|
||
| # Requires the repository's built pymllm mobile binding; packing is portable | ||
| # and can run on an x86 host. Use the candidate's matching runtime libraries. | ||
| python examples/spark2_5/convert.py \ | ||
| --checkpoint /path/to/Spark-X2.5-1.7B \ | ||
| --output /path/to/spark-w4a32.mllm --quantized | ||
| ``` | ||
|
|
||
| KAI quantizes QKV, attention output, FFN and output-head matrices. Norms, | ||
| lookup embedding and the small 8-output gate remain FP32. The output head is | ||
| derived from `model.embedding.weight`; lookup and projection have different | ||
| storage formats, so tied checkpoint weights do not imply one runtime buffer. | ||
| W4A32 uses FP32 interfaces and dynamically quantizes activations to INT8 inside | ||
| KleidiAI. Its artifact must be paired with `config_1.7B_w4a32_kai.json`. | ||
|
|
||
| ## Build and run | ||
|
|
||
| ```bash | ||
| cmake -S . -B build -DMLLM_ENABLE_EXAMPLE=ON | ||
| cmake --build build --target mllm-spark25-runner | ||
| OMP_NUM_THREADS=1 build/bin/mllm-spark25-runner \ | ||
| --model_path /path/to/spark-fp32.mllm \ | ||
| --config_path examples/spark2_5/config_1.7B_fp32.json \ | ||
| --tokenizer_path /path/to/Spark-X2.5-1.7B/tokenizer.json \ | ||
| --prompt "Explain how a sliding-window attention cache works." \ | ||
| --enable_thinking false --max_new_tokens 128 --print_token_ids | ||
| ``` | ||
|
|
||
| For Android, use the repository's Android NDK build settings and build the | ||
| same runner target with `MLLM_BUILD_ARM_BACKEND=ON`. Select an ARM ISA supported | ||
| by the target phone, including the instructions required by the chosen KAI | ||
| implementation. | ||
|
|
||
| The runner defaults to one CPU operation thread. For OpenMP builds, also set | ||
| `OMP_NUM_THREADS=1`, as shown above. Override operation threading with | ||
| `--engine_cpu_op_thread N`; multi-thread performance is not qualified by this | ||
| example. The CLI renders the official single-user-turn template, with an optional | ||
| system message. Thinking defaults to enabled; disabling it appends `</think>` | ||
| in the generation prompt. Decoding is greedy and stops at EOS id 1 or the | ||
| requested generation limit. UTF-8 bytes are buffered across token boundaries. | ||
| The runner does not provide tool execution or a conversation-history API. | ||
|
|
||
| ## Tests and numerical contract | ||
|
|
||
| `GELUFocused`, `SigmoidFocused`, `GroupedQueryAttentionFocused`, and `Spark25Focused` cover the | ||
| public operations, window/chunk semantics, synthetic model/reset, configuration | ||
| and tokenizer. Official tokenizer parity fixtures may be supplied through | ||
| `MLLM_SPARK_TOKENIZER_FIXTURES` (tokenizer.json and tokenizer_cases.json). | ||
| Spark requests erf GELU and accurate logistic Sigmoid; existing callers retain | ||
| their default approximations. FP32 runtime calculations are compared with an FP32 official-model oracle; | ||
| this is distinct from bitwise reproduction of the checkpoint's BF16 arithmetic. | ||
| Quantized quality and device execution require their own validation. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,77 @@ | ||
| { | ||
| "architectures": [ | ||
| "Spark2_5ForCausalLM" | ||
| ], | ||
| "attention_bias": false, | ||
| "attention_dropout": 0.0, | ||
| "auto_map": { | ||
| "AutoConfig": "configuration_spark.Spark2_5Config", | ||
| "AutoModel": "modeling_spark.Spark2_5Model", | ||
| "AutoModelForCausalLM": "modeling_spark.Spark2_5ForCausalLM" | ||
| }, | ||
| "bos_token_id": 0, | ||
| "dtype": "bfloat16", | ||
| "eos_token_id": 1, | ||
| "gate_attn_act_mode": "sigmoid", | ||
| "head_dim": 256, | ||
| "headwise_attn_output_gate": true, | ||
| "hidden_act": "gelu", | ||
| "hidden_size": 2048, | ||
| "initializer_range": 0.0221, | ||
| "intermediate_size": 6656, | ||
| "layer_types": [ | ||
| "sliding_attention", | ||
| "sliding_attention", | ||
| "sliding_attention", | ||
| "full_attention", | ||
| "sliding_attention", | ||
| "sliding_attention", | ||
| "sliding_attention", | ||
| "full_attention", | ||
| "sliding_attention", | ||
| "sliding_attention", | ||
| "sliding_attention", | ||
| "full_attention", | ||
| "sliding_attention", | ||
| "sliding_attention", | ||
| "sliding_attention", | ||
| "full_attention", | ||
| "sliding_attention", | ||
| "sliding_attention", | ||
| "sliding_attention", | ||
| "full_attention", | ||
| "sliding_attention", | ||
| "sliding_attention", | ||
| "sliding_attention", | ||
| "full_attention", | ||
| "sliding_attention", | ||
| "sliding_attention", | ||
| "sliding_attention", | ||
| "full_attention" | ||
| ], | ||
| "max_position_embeddings": 1048576, | ||
| "mlp_bias": false, | ||
| "model_type": "spark2_5", | ||
| "num_attention_heads": 8, | ||
| "num_hidden_layers": 28, | ||
| "num_key_value_heads": 2, | ||
| "pad_token_id": 2, | ||
| "rms_norm_eps": 1e-06, | ||
| "rope_parameters": { | ||
| "full_attention": { | ||
| "partial_rotary_factor": 0.25, | ||
| "rope_theta": 5000000 | ||
| }, | ||
| "sliding_attention": { | ||
| "partial_rotary_factor": 1.0, | ||
| "rope_theta": 10000 | ||
| } | ||
| }, | ||
| "sliding_window": 512, | ||
| "tie_word_embeddings": true, | ||
| "transformers_version": "4.57.1", | ||
| "use_cache": true, | ||
| "vocab_size": 131072, | ||
| "max_cache_length": 4096, | ||
| "linear_impl_type": "Default" | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,77 @@ | ||
| { | ||
| "architectures": [ | ||
| "Spark2_5ForCausalLM" | ||
| ], | ||
| "attention_bias": false, | ||
| "attention_dropout": 0.0, | ||
| "auto_map": { | ||
| "AutoConfig": "configuration_spark.Spark2_5Config", | ||
| "AutoModel": "modeling_spark.Spark2_5Model", | ||
| "AutoModelForCausalLM": "modeling_spark.Spark2_5ForCausalLM" | ||
| }, | ||
| "bos_token_id": 0, | ||
| "dtype": "bfloat16", | ||
| "eos_token_id": 1, | ||
| "gate_attn_act_mode": "sigmoid", | ||
| "head_dim": 256, | ||
| "headwise_attn_output_gate": true, | ||
| "hidden_act": "gelu", | ||
| "hidden_size": 2048, | ||
| "initializer_range": 0.0221, | ||
| "intermediate_size": 6656, | ||
| "layer_types": [ | ||
| "sliding_attention", | ||
| "sliding_attention", | ||
| "sliding_attention", | ||
| "full_attention", | ||
| "sliding_attention", | ||
| "sliding_attention", | ||
| "sliding_attention", | ||
| "full_attention", | ||
| "sliding_attention", | ||
| "sliding_attention", | ||
| "sliding_attention", | ||
| "full_attention", | ||
| "sliding_attention", | ||
| "sliding_attention", | ||
| "sliding_attention", | ||
| "full_attention", | ||
| "sliding_attention", | ||
| "sliding_attention", | ||
| "sliding_attention", | ||
| "full_attention", | ||
| "sliding_attention", | ||
| "sliding_attention", | ||
| "sliding_attention", | ||
| "full_attention", | ||
| "sliding_attention", | ||
| "sliding_attention", | ||
| "sliding_attention", | ||
| "full_attention" | ||
| ], | ||
| "max_position_embeddings": 1048576, | ||
| "mlp_bias": false, | ||
| "model_type": "spark2_5", | ||
| "num_attention_heads": 8, | ||
| "num_hidden_layers": 28, | ||
| "num_key_value_heads": 2, | ||
| "pad_token_id": 2, | ||
| "rms_norm_eps": 1e-06, | ||
| "rope_parameters": { | ||
| "full_attention": { | ||
| "partial_rotary_factor": 0.25, | ||
| "rope_theta": 5000000 | ||
| }, | ||
| "sliding_attention": { | ||
| "partial_rotary_factor": 1.0, | ||
| "rope_theta": 10000 | ||
| } | ||
| }, | ||
| "sliding_window": 512, | ||
| "tie_word_embeddings": true, | ||
| "transformers_version": "4.57.1", | ||
| "use_cache": true, | ||
| "vocab_size": 131072, | ||
| "max_cache_length": 4096, | ||
| "linear_impl_type": "KaiLinear_f32_qai8dxp_qsi4c32p_mxk_nxk_qai8dxp1x8_qsi4c32p8x8_1x8x32" | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Label KAI W4A32 as ARM-only in the Chinese model table. The Spark example documents KAI W4A32 only for ARM CPU, and the implementation builds its KAI kernels through the ARM backend. Update the entry to
FP32 / ARM KAI W4A32; otherwise, the Chinese table remains misleading and may prompt unsupported x86 builds.🤖 Prompt for AI Agents