From e3b324064d2d995202df44a130f9f66a569c784a Mon Sep 17 00:00:00 2001 From: Aharrypotter Date: Wed, 12 Aug 2026 16:54:26 +0800 Subject: [PATCH 1/4] feat(cpu): add Ling-3.0-tiny mobile support --- README-ZH.md | 1 + README.md | 1 + bench_assets/ling3_tiny_architecture.png | Bin 0 -> 365659 bytes examples/CMakeLists.txt | 1 + examples/ling3/CMakeLists.txt | 3 + examples/ling3/README.md | 58 ++ examples/ling3/config_tiny_w4a32_kai.json | 46 ++ examples/ling3/demo_prompt_v1.txt | 1 + examples/ling3/main.cpp | 124 ++++ examples/ling3/quant_cfg_tiny_w4a32_kai.json | 47 ++ examples/ling3/test_validators.py | 39 ++ examples/ling3/validate_checkpoint.py | 274 ++++++++ examples/ling3/validate_converted_model.py | 116 ++++ .../common/kda/kimi_delta_attention.cpp | 195 ++++++ .../common/kda/kimi_delta_attention.hpp | 28 + .../cpu/kernels/common/paged_attn/arch.hpp | 6 +- mllm/models/ling3/configuration_ling3.hpp | 206 ++++++ mllm/models/ling3/modeling_ling3.hpp | 651 ++++++++++++++++++ mllm/models/ling3/tokenization_ling3.hpp | 314 +++++++++ tests/cpu/CMakeLists.txt | 17 + tests/cpu/KaiW4A32PackTest.cpp | 39 ++ tests/cpu/Ling3ConfigTest.cpp | 28 + tests/cpu/Ling3KDATest.cpp | 149 ++++ tests/cpu/Ling3RoPETest.cpp | 56 ++ tests/cpu/Ling3TokenizerTest.cpp | 55 ++ 25 files changed, 2452 insertions(+), 3 deletions(-) create mode 100644 bench_assets/ling3_tiny_architecture.png create mode 100644 examples/ling3/CMakeLists.txt create mode 100644 examples/ling3/README.md create mode 100644 examples/ling3/config_tiny_w4a32_kai.json create mode 100644 examples/ling3/demo_prompt_v1.txt create mode 100644 examples/ling3/main.cpp create mode 100644 examples/ling3/quant_cfg_tiny_w4a32_kai.json create mode 100644 examples/ling3/test_validators.py create mode 100644 examples/ling3/validate_checkpoint.py create mode 100644 examples/ling3/validate_converted_model.py create mode 100644 mllm/backends/cpu/kernels/common/kda/kimi_delta_attention.cpp create mode 100644 mllm/backends/cpu/kernels/common/kda/kimi_delta_attention.hpp create mode 100644 mllm/models/ling3/configuration_ling3.hpp create mode 100644 mllm/models/ling3/modeling_ling3.hpp create mode 100644 mllm/models/ling3/tokenization_ling3.hpp create mode 100644 tests/cpu/Ling3ConfigTest.cpp create mode 100644 tests/cpu/Ling3KDATest.cpp create mode 100644 tests/cpu/Ling3RoPETest.cpp create mode 100644 tests/cpu/Ling3TokenizerTest.cpp diff --git a/README-ZH.md b/README-ZH.md index 9106c4153..2bf6db4ae 100644 --- a/README-ZH.md +++ b/README-ZH.md @@ -107,6 +107,7 @@ mllm 框架可以与主流社区框架的模型检查点无缝集成。通过 ml | [Qwen3-4B](https://github.com/QwenLM/Qwen3) | [✔️ w4a8](https://www.modelscope.cn/models/mllmTeam/Qwen3-4B-w4a8-i8mm-kai) | | | | Qwen3.5-2B | | ✔️ W4A16 / W8A8 | | | Qwen3.5-4B | | ✔️ W4A16 / W8A8 | | +| [Ling-3.0-tiny](https://huggingface.co/inclusionAI/Ling-3.0-tiny) | [✔️ W4A32,ARM64 macOS/Android](./examples/ling3/README.md) | | | | Qwen3-VL-2B-Instruct | | ✔️ W4A16 / W8A8 | | | Qwen3-VL-4B-Instruct | | ✔️ W4A16 / W8A8 | | | [DeepSeek-OCR](https://github.com/deepseek-ai/DeepSeek-OCR) | [✔️ w4a8](https://www.modelscope.cn/models/mllmTeam/DeepSeek-OCR-w4a8-i8mm-kai) | | | diff --git a/README.md b/README.md index a511778c8..793b1b514 100644 --- a/README.md +++ b/README.md @@ -108,6 +108,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) | | | | [LFM2.5-2.6B](https://huggingface.co/LiquidAI/LFM2.5-2.6B) | [✔️ w4a8](./examples/lfm2/README.md) | | | +| [Ling-3.0-tiny](https://huggingface.co/inclusionAI/Ling-3.0-tiny) | [✔️ W4A32, ARM64 macOS/Android](./examples/ling3/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) | | | | [Qwen2-VL-2B-Instruct](https://qwenlm.github.io/zh/blog/qwen2-vl/)|[✔️ w4a8](https://www.modelscope.cn/models/mllmTeam/Qwen2-VL-2B-Instruct-w4a32kai) || | diff --git a/bench_assets/ling3_tiny_architecture.png b/bench_assets/ling3_tiny_architecture.png new file mode 100644 index 0000000000000000000000000000000000000000..ef8fdeab3228bbe4b4d9e3b233da0192dbd19d57 GIT binary patch literal 365659 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ǎXVb$w`1UPd*ZWQ|3+JL&5~c;?KE7Mf8<4oTN*g zQRKxNy}dUgLF25gtBdZHr0y~%Q%2`QjLK1zrr z0i-)Nbk(@;y0idrfbQ1=TS-D#@#oi2NP2mBg@uOFO(&I?v(P_I4Nv>u%3)$+D#m|d z2G!zhjkT6>$7hStLdc_RTI#mT2!9?QUjpOMC-c^;<0%0D8V)fP=Sh5Std-Gn)rPv~ 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

  • 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

    SlSnuolD}WFF+sC>v0yMMd)lMf=~!m= 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

    V$pD1r?@-_48bmNIi{($ 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 literal 0 HcmV?d00001 diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index ed1cbeb82..34e544b6e 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -9,6 +9,7 @@ add_subdirectory(minicpm5) add_subdirectory(qwen3) add_subdirectory(qwen3_5) add_subdirectory(lfm2) +add_subdirectory(ling3) add_subdirectory(qwen3_service) add_subdirectory(qwen3_moe) add_subdirectory(deepseek_ocr) diff --git a/examples/ling3/CMakeLists.txt b/examples/ling3/CMakeLists.txt new file mode 100644 index 000000000..a27d757ac --- /dev/null +++ b/examples/ling3/CMakeLists.txt @@ -0,0 +1,3 @@ +add_executable(mllm-ling3-runner main.cpp) +target_link_libraries(mllm-ling3-runner PRIVATE MllmRT MllmCPUBackend) +target_include_directories(mllm-ling3-runner PRIVATE ${MLLM_INCLUDE_DIR}) diff --git a/examples/ling3/README.md b/examples/ling3/README.md new file mode 100644 index 000000000..016699d47 --- /dev/null +++ b/examples/ling3/README.md @@ -0,0 +1,58 @@ +# Ling-3.0-tiny mobile CPU runner + +This runner targets the pinned official checkpoint +`inclusionAI/Ling-3.0-tiny@a2ee06c0f2de5b171701aee7f73f70a1da75483b`. +The supported deployment envelope is batch 1, a 2048-token cache, float32 +recurrent state, and KAI W4A32 linear weights on ARM64 macOS and Android. +Build the ARM CPU backend without `-ffast-math`: Ling-3's recurrent gates are +not compatible with relaxed IEEE semantics. For Android, the validated ISA +flags are `-march=armv8.2-a+fp16+fp16fml+dotprod+i8mm`; do not append +`-ffast-math`. + +Validate the source checkpoint before conversion: + +```bash +python3 validate_checkpoint.py /path/to/Ling-3.0-tiny \ + --observed-revision a2ee06c0f2de5b171701aee7f73f70a1da75483b +``` + +Convert with the repository V2 converter and +`quant_cfg_tiny_w4a32_kai.json`, using model name `Ling-3.0-tiny`, then seal +the output descriptor table: + +```bash +python3 validate_converted_model.py /path/to/Ling-3.0-tiny.mllm \ + /path/to/Ling-3.0-tiny +``` + +Run one deterministic smoke request: + +```bash +./mllm-ling3-runner \ + --model_path /path/to/Ling-3.0-tiny.mllm \ + --tokenizer_path /path/to/Ling-3.0-tiny/tokenizer.json \ + --config_path config_tiny_w4a32_kai.json \ + --prompt '你好,请用一句话介绍你自己。' \ + --max_new_tokens 8 --print_token_ids +``` + +The runner emits `LING3_RUN_START`, generated token IDs, and +`LING3_RUN_OK`. A successful build or tokenizer-only test is not a full-model +runtime result; device evidence must retain the converted model SHA256 and +the runner/library identities together. + +For a longer deterministic correctness demo, use the checked-in prompt and a +64-token generation limit: + +```bash +./mllm-ling3-runner \ + --model_path /path/to/Ling-3.0-tiny.mllm \ + --tokenizer_path /path/to/Ling-3.0-tiny/tokenizer.json \ + --config_path config_tiny_w4a32_kai.json \ + --prompt_file demo_prompt_v1.txt --disable_thinking \ + --max_new_tokens 64 --print_token_ids +``` + +The expected completion marker is +`LING3_RUN_OK prompt_tokens=49 generated_tokens=64`. This is a generation +correctness demo, not a perplexity, model-quality, or performance benchmark. diff --git a/examples/ling3/config_tiny_w4a32_kai.json b/examples/ling3/config_tiny_w4a32_kai.json new file mode 100644 index 000000000..09e733d59 --- /dev/null +++ b/examples/ling3/config_tiny_w4a32_kai.json @@ -0,0 +1,46 @@ +{ + "architectures": ["BailingMoeV3ForCausalLM"], + "model_type": "bailing_hybrid", + "hidden_size": 1536, + "intermediate_size": 4608, + "num_hidden_layers": 24, + "num_attention_heads": 16, + "num_key_value_heads": 16, + "head_dim": 128, + "vocab_size": 157184, + "max_position_embeddings": 131072, + "rms_norm_eps": 1e-06, + "rope_theta": 6000000, + "hidden_act": "silu", + "tie_word_embeddings": false, + "use_qkv_bias": false, + "layer_group_size": 4, + "short_conv_kernel_size": 4, + "no_kda_lora": true, + "kda_safe_gate": true, + "kda_lower_bound": -5, + "q_lora_rank": 256, + "kv_lora_rank": 512, + "qk_rope_head_dim": 64, + "qk_nope_head_dim": 128, + "qk_head_dim": 192, + "v_head_dim": 128, + "rope_interleave": true, + "gated_attention_proj_granularity_type": "head_wise", + "num_experts": 128, + "num_shared_experts": 1, + "num_experts_per_tok": 8, + "n_group": 8, + "topk_group": 4, + "moe_intermediate_size": 512, + "moe_shared_expert_intermediate_size": 512, + "first_k_dense_replace": 1, + "routed_scaling_factor": 2.5, + "scoring_func": "sigmoid", + "topk_method": "noaux_tc", + "moe_router_enable_expert_bias": true, + "pad_token_id": 156892, + "eos_token_id": 156895, + "max_cache_length": 2048, + "linear_impl_type": "KaiLinear_f32_qai8dxp_qsi4c32p_mxk_nxk_qai8dxp1x8_qsi4c32p8x8_1x8x32" +} diff --git a/examples/ling3/demo_prompt_v1.txt b/examples/ling3/demo_prompt_v1.txt new file mode 100644 index 000000000..cf4cd72f4 --- /dev/null +++ b/examples/ling3/demo_prompt_v1.txt @@ -0,0 +1 @@ +请用中文详细介绍 Ling-3.0-tiny 的混合注意力架构,并解释 KDA、MLA 和 MoE 各自的作用。 diff --git a/examples/ling3/main.cpp b/examples/ling3/main.cpp new file mode 100644 index 000000000..b9e7009e9 --- /dev/null +++ b/examples/ling3/main.cpp @@ -0,0 +1,124 @@ +// Copyright (c) MLLM Team. +// Licensed under the MIT License. + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +using mllm::Argparse; + +namespace { + +std::string readPromptFile(const std::string& path) { + std::ifstream stream(path, std::ios::binary); + if (!stream) { throw std::invalid_argument("unable to read prompt_file: " + path); } + std::string text{std::istreambuf_iterator(stream), std::istreambuf_iterator()}; + while (!text.empty() && (text.back() == '\n' || text.back() == '\r')) { text.pop_back(); } + if (text.empty()) { throw std::invalid_argument("prompt_file must not be empty: " + path); } + return text; +} + +} // namespace + +MLLM_MAIN({ + auto engine_args = mllm::engineArgAttach(); + auto& help = Argparse::add("-h|--help").help("Show help message"); + auto& model_path = Argparse::add("-m|--model_path").help("MLLM V2 model path").required(true); + auto& tokenizer_path = Argparse::add("-t|--tokenizer_path").help("Official tokenizer.json").required(true); + auto& config_path = Argparse::add("-c|--config_path").help("Ling-3 mobile runtime config").required(true); + auto& prompt = Argparse::add("-p|--prompt").help("Run one prompt non-interactively").required(false); + auto& prompt_file = Argparse::add("--prompt_file").help("Read a prompt from a UTF-8 file").required(false); + auto& max_new_tokens = + Argparse::add("-g|--max_new_tokens").help("Maximum generated tokens (default: 8)").required(false); + auto& min_new_tokens = Argparse::add("--min_new_tokens").help("Suppress EOS until this many tokens").required(false); + auto& disable_thinking = + Argparse::add("--disable_thinking").help("Use the official thinking-off chat template").required(false); + auto& print_token_ids = Argparse::add("--print_token_ids").help("Print generated token IDs to stderr").required(false); + + for (int index = 1; index < argc; ++index) { + if (std::string(argv[index]) == "-h" || std::string(argv[index]) == "--help") { + Argparse::printHelp(); + return 0; + } + } + Argparse::parse(argc, argv); + mllm::configEngineWithArgs(engine_args); + (void)help; + + const auto config = mllm::models::ling3::Ling3Config(config_path.get()); + int generation_limit = max_new_tokens.isSet() ? max_new_tokens.get() : 8; + int minimum_generation = min_new_tokens.isSet() ? min_new_tokens.get() : 0; + if (generation_limit <= 0 || generation_limit > config.max_cache_length || minimum_generation < 0 + || minimum_generation > generation_limit) { + throw std::invalid_argument("generation lengths must satisfy 0 <= min_new_tokens <= max_new_tokens <= max_cache_length"); + } + if (prompt.isSet() && prompt_file.isSet()) { throw std::invalid_argument("prompt and prompt_file are mutually exclusive"); } + + std::string configured_prompt; + if (prompt_file.isSet()) { + configured_prompt = readPromptFile(prompt_file.get()); + } else if (prompt.isSet()) { + configured_prompt = prompt.get(); + } + + const auto parameters = mllm::load(model_path.get(), mllm::ModelFileVersion::kV2); + mllm::models::ling3::validateLing3ModelConfigMatch(config, parameters); + auto tokenizer = mllm::models::ling3::Ling3Tokenizer(tokenizer_path.get()); + auto model = mllm::models::ling3::Ling3ForCausalLM(config); + model.load(parameters); + fmt::print("Ling-3.0-tiny: {} layers ({} MLA + {} KDA), CPU threads={}\n", config.num_hidden_layers, + config.numFullAttentionLayers(), config.numKDALayers(), mllm::Context::instance().getCpuOpThreads()); + + int exit_code = 0; + while (true) { + std::string prompt_text = configured_prompt; + if (!prompt.isSet() && !prompt_file.isSet()) { + fmt::print("Prompt text (or 'exit/quit'): "); + if (!std::getline(std::cin, prompt_text) || prompt_text == "exit" || prompt_text == "quit") { break; } + } + if (prompt_text.empty()) { + if (prompt.isSet() || prompt_file.isSet()) { throw std::invalid_argument("prompt must not be empty"); } + continue; + } + try { + auto input = tokenizer.convertMessage({.prompt = prompt_text, + .system_prompt = "", + .enable_thinking = !(disable_thinking.isSet() && disable_thinking.get())}); + const int prompt_tokens = input.at("sequence").shape()[1]; + if (prompt_tokens + generation_limit - 1 > config.max_cache_length) { + throw std::invalid_argument("prompt plus generation exceeds max_cache_length"); + } + model.resetState(); + fmt::print("LING3_RUN_START prompt_tokens={} max_new_tokens={} min_new_tokens={}\nResponse: ", prompt_tokens, + generation_limit, minimum_generation); + int generated_tokens = 0; + for (const auto& step : model.chat(input, {{"max_length", mllm::AnyValue(generation_limit)}, + {"min_new_tokens", mllm::AnyValue(minimum_generation)}, + {"do_sample", mllm::AnyValue(false)}})) { + if (print_token_ids.isSet() && print_token_ids.get()) { fmt::print(stderr, "LING3_TOKEN_ID:{}\n", step.cur_token_id); } + fmt::print("{}", tokenizer.detokenizeBytes(step.cur_token_id)); + std::fflush(stdout); + ++generated_tokens; + } + fmt::print("\nLING3_RUN_OK prompt_tokens={} generated_tokens={}\n", prompt_tokens, generated_tokens); + } catch (const std::exception& error) { + fmt::print(stderr, "LING3_RUN_ERROR:{}\n", error.what()); + exit_code = 1; + } + if (prompt.isSet() || prompt_file.isSet()) { break; } + } + + model.perfSummary(); + mllm::memoryReport(); + return exit_code; +}) diff --git a/examples/ling3/quant_cfg_tiny_w4a32_kai.json b/examples/ling3/quant_cfg_tiny_w4a32_kai.json new file mode 100644 index 000000000..66855abac --- /dev/null +++ b/examples/ling3/quant_cfg_tiny_w4a32_kai.json @@ -0,0 +1,47 @@ +{ + "^model\\.layers\\.\\d+\\.attention\\.(q_proj|k_proj|v_proj|f_proj)\\.weight$": { + "hints": {"quant_method": "kai", "kai_matmul_triplet": "f32_qai8dxp_qsi4c32p", "kai_matmul_layout": "mxk_nxk", "kai_matmul_tile_cfg": "qai8dxp1x8_qsi4c32p8x8_1x8x32", "shape": [2048, 1536], "replace": true} + }, + "^model\\.layers\\.(?:[0-2]|[4-6]|8|9|10|12|13|14|16|17|18|20|21|22)\\.attention\\.g_proj\\.weight$": { + "hints": {"quant_method": "kai", "kai_matmul_triplet": "f32_qai8dxp_qsi4c32p", "kai_matmul_layout": "mxk_nxk", "kai_matmul_tile_cfg": "qai8dxp1x8_qsi4c32p8x8_1x8x32", "shape": [2048, 1536], "replace": true} + }, + "^model\\.layers\\.(?:3|7|11|15|19|23)\\.attention\\.g_proj\\.weight$": { + "hints": {"quant_method": "kai", "kai_matmul_triplet": "f32_qai8dxp_qsi4c32p", "kai_matmul_layout": "mxk_nxk", "kai_matmul_tile_cfg": "qai8dxp1x8_qsi4c32p8x8_1x8x32", "shape": [16, 1536], "replace": true} + }, + "^model\\.layers\\.\\d+\\.attention\\.b_proj\\.weight$": { + "hints": {"quant_method": "kai", "kai_matmul_triplet": "f32_qai8dxp_qsi4c32p", "kai_matmul_layout": "mxk_nxk", "kai_matmul_tile_cfg": "qai8dxp1x8_qsi4c32p8x8_1x8x32", "shape": [16, 1536], "replace": true} + }, + "^model\\.layers\\.\\d+\\.attention\\.o_proj\\.weight$": { + "hints": {"quant_method": "kai", "kai_matmul_triplet": "f32_qai8dxp_qsi4c32p", "kai_matmul_layout": "mxk_nxk", "kai_matmul_tile_cfg": "qai8dxp1x8_qsi4c32p8x8_1x8x32", "shape": [1536, 2048], "replace": true} + }, + "^model\\.layers\\.\\d+\\.attention\\.q_a_proj\\.weight$": { + "hints": {"quant_method": "kai", "kai_matmul_triplet": "f32_qai8dxp_qsi4c32p", "kai_matmul_layout": "mxk_nxk", "kai_matmul_tile_cfg": "qai8dxp1x8_qsi4c32p8x8_1x8x32", "shape": [256, 1536], "replace": true} + }, + "^model\\.layers\\.\\d+\\.attention\\.q_b_proj\\.weight$": { + "hints": {"quant_method": "kai", "kai_matmul_triplet": "f32_qai8dxp_qsi4c32p", "kai_matmul_layout": "mxk_nxk", "kai_matmul_tile_cfg": "qai8dxp1x8_qsi4c32p8x8_1x8x32", "shape": [3072, 256], "replace": true} + }, + "^model\\.layers\\.\\d+\\.attention\\.kv_a_proj_with_mqa\\.weight$": { + "hints": {"quant_method": "kai", "kai_matmul_triplet": "f32_qai8dxp_qsi4c32p", "kai_matmul_layout": "mxk_nxk", "kai_matmul_tile_cfg": "qai8dxp1x8_qsi4c32p8x8_1x8x32", "shape": [576, 1536], "replace": true} + }, + "^model\\.layers\\.\\d+\\.attention\\.kv_b_proj\\.weight$": { + "hints": {"quant_method": "kai", "kai_matmul_triplet": "f32_qai8dxp_qsi4c32p", "kai_matmul_layout": "mxk_nxk", "kai_matmul_tile_cfg": "qai8dxp1x8_qsi4c32p8x8_1x8x32", "shape": [4096, 512], "replace": true} + }, + "^model\\.layers\\.\\d+\\.attention\\.dense\\.weight$": { + "hints": {"quant_method": "kai", "kai_matmul_triplet": "f32_qai8dxp_qsi4c32p", "kai_matmul_layout": "mxk_nxk", "kai_matmul_tile_cfg": "qai8dxp1x8_qsi4c32p8x8_1x8x32", "shape": [1536, 2048], "replace": true} + }, + "^model\\.layers\\.0\\.mlp\\.(gate_proj|up_proj)\\.weight$": { + "hints": {"quant_method": "kai", "kai_matmul_triplet": "f32_qai8dxp_qsi4c32p", "kai_matmul_layout": "mxk_nxk", "kai_matmul_tile_cfg": "qai8dxp1x8_qsi4c32p8x8_1x8x32", "shape": [4608, 1536], "replace": true} + }, + "^model\\.layers\\.0\\.mlp\\.down_proj\\.weight$": { + "hints": {"quant_method": "kai", "kai_matmul_triplet": "f32_qai8dxp_qsi4c32p", "kai_matmul_layout": "mxk_nxk", "kai_matmul_tile_cfg": "qai8dxp1x8_qsi4c32p8x8_1x8x32", "shape": [1536, 4608], "replace": true} + }, + "^model\\.layers\\.(?:[1-9]|1\\d|2[0-3])\\.mlp\\.(?:experts\\.\\d+|shared_experts)\\.(gate_proj|up_proj)\\.weight$": { + "hints": {"quant_method": "kai", "kai_matmul_triplet": "f32_qai8dxp_qsi4c32p", "kai_matmul_layout": "mxk_nxk", "kai_matmul_tile_cfg": "qai8dxp1x8_qsi4c32p8x8_1x8x32", "shape": [512, 1536], "replace": true} + }, + "^model\\.layers\\.(?:[1-9]|1\\d|2[0-3])\\.mlp\\.(?:experts\\.\\d+|shared_experts)\\.down_proj\\.weight$": { + "hints": {"quant_method": "kai", "kai_matmul_triplet": "f32_qai8dxp_qsi4c32p", "kai_matmul_layout": "mxk_nxk", "kai_matmul_tile_cfg": "qai8dxp1x8_qsi4c32p8x8_1x8x32", "shape": [1536, 512], "replace": true} + }, + "^lm_head\\.weight$": { + "hints": {"quant_method": "kai", "kai_matmul_triplet": "f32_qai8dxp_qsi4c32p", "kai_matmul_layout": "mxk_nxk", "kai_matmul_tile_cfg": "qai8dxp1x8_qsi4c32p8x8_1x8x32", "shape": [157184, 1536], "replace": true} + } +} diff --git a/examples/ling3/test_validators.py b/examples/ling3/test_validators.py new file mode 100644 index 000000000..331a426de --- /dev/null +++ b/examples/ling3/test_validators.py @@ -0,0 +1,39 @@ +# Copyright (c) MLLM Team. +# Licensed under the MIT License. + +import json +import unittest +from pathlib import Path + +from validate_checkpoint import ( + FULL_ATTENTION_LAYERS, + expected_shapes, + validate_config, + validate_recipe, +) + + +HERE = Path(__file__).resolve().parent + + +class Ling3ValidatorTests(unittest.TestCase): + def test_expected_checkpoint_inventory_is_exact(self): + shapes = expected_shapes() + self.assertEqual(len(shapes), 9283) + self.assertEqual( + {layer for layer in range(24) if f"model.layers.{layer}.attention.q_a_proj.weight" in shapes}, + FULL_ATTENTION_LAYERS, + ) + + def test_runtime_and_recipe_match_official_contract(self): + runtime = json.loads((HERE / "config_tiny_w4a32_kai.json").read_text()) + validate_config(runtime, runtime) + recipe = json.loads((HERE / "quant_cfg_tiny_w4a32_kai.json").read_text()) + matched = validate_recipe(expected_shapes(), recipe) + self.assertGreater(len(matched), 9000) + self.assertNotIn("model.word_embeddings.weight", matched) + self.assertNotIn("model.layers.1.mlp.gate.weight", matched) + + +if __name__ == "__main__": + unittest.main() diff --git a/examples/ling3/validate_checkpoint.py b/examples/ling3/validate_checkpoint.py new file mode 100644 index 000000000..89ca6f560 --- /dev/null +++ b/examples/ling3/validate_checkpoint.py @@ -0,0 +1,274 @@ +#!/usr/bin/env python3 +# Copyright (c) MLLM Team. +# Licensed under the MIT License. + +"""Validate the pinned Ling-3.0-tiny source contract and W4A32 recipe.""" + +from __future__ import annotations + +import argparse +import json +import re +import struct +from pathlib import Path + + +PINNED_MODEL_ID = "inclusionAI/Ling-3.0-tiny" +PINNED_REVISION = "a2ee06c0f2de5b171701aee7f73f70a1da75483b" +FULL_ATTENTION_LAYERS = {3, 7, 11, 15, 19, 23} +OFFICIAL_CONTRACT = { + "architectures": ["BailingMoeV3ForCausalLM"], + "model_type": "bailing_hybrid", + "hidden_size": 1536, + "intermediate_size": 4608, + "num_hidden_layers": 24, + "num_attention_heads": 16, + "num_key_value_heads": 16, + "head_dim": 128, + "vocab_size": 157184, + "max_position_embeddings": 131072, + "rms_norm_eps": 1e-6, + "rope_theta": 6000000, + "layer_group_size": 4, + "short_conv_kernel_size": 4, + "no_kda_lora": True, + "kda_safe_gate": True, + "kda_lower_bound": -5, + "q_lora_rank": 256, + "kv_lora_rank": 512, + "qk_rope_head_dim": 64, + "qk_nope_head_dim": 128, + "qk_head_dim": 192, + "v_head_dim": 128, + "rope_interleave": True, + "gated_attention_proj_granularity_type": "head_wise", + "num_experts": 128, + "num_shared_experts": 1, + "num_experts_per_tok": 8, + "n_group": 8, + "topk_group": 4, + "moe_intermediate_size": 512, + "moe_shared_expert_intermediate_size": 512, + "first_k_dense_replace": 1, + "routed_scaling_factor": 2.5, + "scoring_func": "sigmoid", + "topk_method": "noaux_tc", + "moe_router_enable_expert_bias": True, + "tie_word_embeddings": False, + "use_qkv_bias": False, + "pad_token_id": 156892, + "eos_token_id": 156895, +} +KAI_HINTS = { + "quant_method": "kai", + "kai_matmul_triplet": "f32_qai8dxp_qsi4c32p", + "kai_matmul_layout": "mxk_nxk", + "kai_matmul_tile_cfg": "qai8dxp1x8_qsi4c32p8x8_1x8x32", +} + + +def expected_shapes() -> dict[str, list[int]]: + shapes: dict[str, list[int]] = { + "model.word_embeddings.weight": [157184, 1536], + "model.norm.weight": [1536], + "lm_head.weight": [157184, 1536], + } + for layer in range(24): + prefix = f"model.layers.{layer}" + shapes[f"{prefix}.input_layernorm.weight"] = [1536] + shapes[f"{prefix}.post_attention_layernorm.weight"] = [1536] + attention = f"{prefix}.attention" + if layer in FULL_ATTENTION_LAYERS: + shapes.update( + { + f"{attention}.q_a_proj.weight": [256, 1536], + f"{attention}.q_a_layernorm.weight": [256], + f"{attention}.q_b_proj.weight": [3072, 256], + f"{attention}.kv_a_proj_with_mqa.weight": [576, 1536], + f"{attention}.kv_a_layernorm.weight": [512], + f"{attention}.kv_b_proj.weight": [4096, 512], + f"{attention}.g_proj.weight": [16, 1536], + f"{attention}.dense.weight": [1536, 2048], + } + ) + else: + shapes.update( + { + f"{attention}.q_proj.weight": [2048, 1536], + f"{attention}.k_proj.weight": [2048, 1536], + f"{attention}.v_proj.weight": [2048, 1536], + f"{attention}.q_conv1d.weight": [2048, 1, 4], + f"{attention}.k_conv1d.weight": [2048, 1, 4], + f"{attention}.v_conv1d.weight": [2048, 1, 4], + f"{attention}.f_proj.weight": [2048, 1536], + f"{attention}.A_log": [16], + f"{attention}.dt_bias": [2048], + f"{attention}.b_proj.weight": [16, 1536], + f"{attention}.g_proj.weight": [2048, 1536], + f"{attention}.o_norm.weight": [128], + f"{attention}.o_proj.weight": [1536, 2048], + } + ) + mlp = f"{prefix}.mlp" + if layer == 0: + shapes[f"{mlp}.gate_proj.weight"] = [4608, 1536] + shapes[f"{mlp}.up_proj.weight"] = [4608, 1536] + shapes[f"{mlp}.down_proj.weight"] = [1536, 4608] + else: + shapes[f"{mlp}.gate.weight"] = [128, 1536] + shapes[f"{mlp}.gate.expert_bias"] = [128] + for expert in range(128): + expert_prefix = f"{mlp}.experts.{expert}" + shapes[f"{expert_prefix}.gate_proj.weight"] = [512, 1536] + shapes[f"{expert_prefix}.up_proj.weight"] = [512, 1536] + shapes[f"{expert_prefix}.down_proj.weight"] = [1536, 512] + shapes[f"{mlp}.shared_experts.gate_proj.weight"] = [512, 1536] + shapes[f"{mlp}.shared_experts.up_proj.weight"] = [512, 1536] + shapes[f"{mlp}.shared_experts.down_proj.weight"] = [1536, 512] + assert len(shapes) == 9283 + return shapes + + +def validate_config(checkpoint_config: dict, runtime_config: dict) -> None: + mismatches = [ + f"{key}={checkpoint_config.get(key)!r}, expected {value!r}" + for key, value in OFFICIAL_CONTRACT.items() + if checkpoint_config.get(key) != value + ] + if mismatches: + raise AssertionError("Checkpoint contract mismatch: " + "; ".join(mismatches)) + runtime_mismatches = [ + f"{key}={runtime_config.get(key)!r}, expected {value!r}" + for key, value in OFFICIAL_CONTRACT.items() + if runtime_config.get(key) != value + ] + if runtime_mismatches: + raise AssertionError("Runtime contract mismatch: " + "; ".join(runtime_mismatches)) + if runtime_config.get("max_cache_length") != 2048: + raise AssertionError("Mobile runtime must use the reviewed 2048-token cache bound") + expected_impl = ( + "KaiLinear_f32_qai8dxp_qsi4c32p_mxk_nxk_" + "qai8dxp1x8_qsi4c32p8x8_1x8x32" + ) + if runtime_config.get("linear_impl_type") != expected_impl: + raise AssertionError("Runtime config does not select the reviewed KAI W4A32 path") + + +def validate_recipe(shapes: dict[str, list[int]], recipe: dict) -> set[str]: + matched: set[str] = set() + for pattern_text, entry in recipe.items(): + pattern = re.compile(pattern_text) + names = {name for name in shapes if pattern.fullmatch(name)} + if not names: + raise AssertionError(f"Quantization pattern matched no tensors: {pattern_text}") + overlap = matched & names + if overlap: + raise AssertionError(f"Quantization patterns overlap: {sorted(overlap)[:5]}") + hints = entry.get("hints", {}) + for key, value in KAI_HINTS.items(): + if hints.get(key) != value: + raise AssertionError(f"{pattern_text}: invalid {key}") + if hints.get("replace") is not True: + raise AssertionError(f"{pattern_text}: KAI weights must replace source weights") + for name in names: + if shapes[name] != hints.get("shape"): + raise AssertionError( + f"{pattern_text}: {name} has shape {shapes[name]}, recipe says {hints.get('shape')}" + ) + matched.update(names) + + expected_quantized = { + name + for name, shape in shapes.items() + if name == "lm_head.weight" + or ( + name.endswith(".weight") + and len(shape) == 2 + and name != "model.word_embeddings.weight" + and not name.endswith(".mlp.gate.weight") + ) + } + if matched != expected_quantized: + raise AssertionError( + "Quantized tensor coverage mismatch: " + f"missing={sorted(expected_quantized - matched)[:8]}, " + f"extra={sorted(matched - expected_quantized)[:8]}" + ) + return matched + + +def read_safetensors_header(path: Path) -> dict: + with path.open("rb") as file: + raw_length = file.read(8) + if len(raw_length) != 8: + raise AssertionError(f"Truncated safetensors header: {path}") + header_length = struct.unpack(" None: + actual: dict[str, tuple[str, list[int]]] = {} + for shard_name in sorted(set(weight_map.values())): + header = read_safetensors_header(checkpoint / shard_name) + for name, descriptor in header.items(): + if name == "__metadata__": + continue + if name in actual: + raise AssertionError(f"Duplicate tensor across shards: {name}") + actual[name] = (descriptor["dtype"], descriptor["shape"]) + if set(actual) != set(shapes): + raise AssertionError( + f"Shard tensor set mismatch: missing={sorted(set(shapes) - set(actual))[:8]}, " + f"extra={sorted(set(actual) - set(shapes))[:8]}" + ) + for name, expected_shape in shapes.items(): + expected_dtype = ( + "F32" + if name.endswith(".A_log") + or name.endswith(".dt_bias") + or name.endswith(".gate.expert_bias") + else "BF16" + ) + if actual[name] != (expected_dtype, expected_shape): + raise AssertionError(f"{name}: expected {(expected_dtype, expected_shape)}, got {actual[name]}") + + +def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument("checkpoint", type=Path) + parser.add_argument("--runtime-config", type=Path, default=Path(__file__).with_name("config_tiny_w4a32_kai.json")) + parser.add_argument("--quant-config", type=Path, default=Path(__file__).with_name("quant_cfg_tiny_w4a32_kai.json")) + parser.add_argument("--observed-revision", required=True) + parser.add_argument("--index-only", action="store_true") + args = parser.parse_args() + if args.observed_revision != PINNED_REVISION: + raise AssertionError( + f"Checkpoint revision {args.observed_revision} does not match pinned {PINNED_REVISION}" + ) + checkpoint_config = json.loads((args.checkpoint / "config.json").read_text()) + runtime_config = json.loads(args.runtime_config.read_text()) + recipe = json.loads(args.quant_config.read_text()) + validate_config(checkpoint_config, runtime_config) + shapes = expected_shapes() + index = json.loads((args.checkpoint / "model.safetensors.index.json").read_text()) + weight_map = index.get("weight_map", {}) + if set(weight_map) != set(shapes): + raise AssertionError( + f"Index tensor set mismatch: missing={sorted(set(shapes) - set(weight_map))[:8]}, " + f"extra={sorted(set(weight_map) - set(shapes))[:8]}" + ) + validate_recipe(shapes, recipe) + if not args.index_only: + validate_shards(args.checkpoint, weight_map, shapes) + print( + "LING3_CHECKPOINT_OK " + f"model={PINNED_MODEL_ID} revision={PINNED_REVISION} tensors={len(shapes)} " + f"shards={len(set(weight_map.values()))} mode={'index' if args.index_only else 'full'}" + ) + + +if __name__ == "__main__": + main() diff --git a/examples/ling3/validate_converted_model.py b/examples/ling3/validate_converted_model.py new file mode 100644 index 000000000..ce1ea92d4 --- /dev/null +++ b/examples/ling3/validate_converted_model.py @@ -0,0 +1,116 @@ +#!/usr/bin/env python3 +# Copyright (c) MLLM Team. +# Licensed under the MIT License. + +"""Validate a converted Ling-3.0-tiny MLLM V2 model without loading tensor data.""" + +from __future__ import annotations + +import argparse +import json +import math +import re +import struct +from pathlib import Path + +from validate_checkpoint import expected_shapes, validate_config, validate_recipe + + +MODEL_HEADER = struct.Struct(" int: + if out_channels <= 0 or in_channels <= 0 or in_channels % 32: + raise AssertionError("KAI W4A32 shapes require positive dimensions and K divisible by 32") + nr = 8 + blocks_per_row = in_channels // 32 + bytes_per_nr_rows = nr * (blocks_per_row * (16 + 2) + 4 + 4) + return math.ceil(out_channels / nr) * bytes_per_nr_rows + + +def decode_c_string(value: bytes) -> str: + return value.split(b"\0", 1)[0].decode("utf-8") + + +def expected_descriptors(shapes: dict[str, list[int]], recipe: dict) -> dict[str, tuple[int, list[int], int]]: + expected = {name: (FLOAT32, shape, math.prod(shape) * 4) for name, shape in shapes.items()} + validate_recipe(shapes, recipe) + for pattern_text, entry in recipe.items(): + pattern = re.compile(pattern_text) + for name, shape in shapes.items(): + if pattern.fullmatch(name): + size = packed_size(shape[0], shape[1]) + expected[name] = (BYTE, [size], size) + return expected + + +def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument("model", type=Path) + parser.add_argument("checkpoint", type=Path) + parser.add_argument("--runtime-config", type=Path, default=Path(__file__).with_name("config_tiny_w4a32_kai.json")) + parser.add_argument("--quant-config", type=Path, default=Path(__file__).with_name("quant_cfg_tiny_w4a32_kai.json")) + parser.add_argument("--model-name", default="Ling-3.0-tiny") + args = parser.parse_args() + source_config = json.loads((args.checkpoint / "config.json").read_text()) + runtime_config = json.loads(args.runtime_config.read_text()) + recipe = json.loads(args.quant_config.read_text()) + validate_config(source_config, runtime_config) + expected = expected_descriptors(expected_shapes(), recipe) + + file_size = args.model.stat().st_size + with args.model.open("rb") as model_file: + raw_header = model_file.read(MODEL_HEADER.size) + if len(raw_header) != MODEL_HEADER.size: + raise AssertionError("Truncated MLLM model header") + magic, version, raw_name, num_parameters, descriptor_offset = MODEL_HEADER.unpack(raw_header) + identity = (magic, version, decode_c_string(raw_name), descriptor_offset) + expected_identity = (MODEL_MAGIC, MODEL_VERSION, args.model_name, MODEL_HEADER.size) + if identity != expected_identity: + raise AssertionError(f"Invalid model identity: expected {expected_identity}, got {identity}") + if num_parameters != len(expected): + raise AssertionError(f"Expected {len(expected)} parameters, model declares {num_parameters}") + actual: dict[str, tuple[int, list[int], int, int]] = {} + for expected_id in range(num_parameters): + raw_descriptor = model_file.read(PARAMETER_DESCRIPTOR.size) + if len(raw_descriptor) != PARAMETER_DESCRIPTOR.size: + raise AssertionError(f"Truncated descriptor {expected_id}") + values = PARAMETER_DESCRIPTOR.unpack(raw_descriptor) + parameter_id, dtype, size, offset, shape_length = values[:5] + if parameter_id != expected_id or shape_length > 16: + raise AssertionError(f"Invalid parameter descriptor {expected_id}") + name = decode_c_string(values[21]) + if name in actual: + raise AssertionError(f"Duplicate converted parameter: {name}") + actual[name] = (dtype, list(values[5:21])[:shape_length], size, offset) + + if set(actual) != set(expected): + raise AssertionError( + f"Converted tensor set mismatch: missing={sorted(set(expected) - set(actual))[:8]}, " + f"extra={sorted(set(actual) - set(expected))[:8]}" + ) + data_start = MODEL_HEADER.size + len(expected) * PARAMETER_DESCRIPTOR.size + next_offset = data_start + for name, descriptor in sorted(actual.items(), key=lambda item: item[1][3]): + dtype, shape, size, offset = descriptor + if (dtype, shape, size) != expected[name]: + raise AssertionError(f"{name}: expected {expected[name]}, got {(dtype, shape, size)}") + if offset != next_offset: + raise AssertionError(f"{name}: expected offset {next_offset}, got {offset}") + next_offset += size + if next_offset != file_size: + raise AssertionError(f"Converted data ends at {next_offset}, file size is {file_size}") + quantized = sum(dtype == BYTE for dtype, _, _, _ in actual.values()) + print( + f"LING3_CONVERTED_MODEL_OK model={args.model} tensors={len(actual)} " + f"kai_w4a32={quantized} bytes={file_size}" + ) + + +if __name__ == "__main__": + main() diff --git a/mllm/backends/cpu/kernels/common/kda/kimi_delta_attention.cpp b/mllm/backends/cpu/kernels/common/kda/kimi_delta_attention.cpp new file mode 100644 index 000000000..1609a2281 --- /dev/null +++ b/mllm/backends/cpu/kernels/common/kda/kimi_delta_attention.cpp @@ -0,0 +1,195 @@ +// Copyright (c) MLLM Team. +// Licensed under the MIT License. + +#include "mllm/backends/cpu/kernels/common/kda/kimi_delta_attention.hpp" + +#include "mllm/engine/Context.hpp" +#include "mllm/core/Parallel.hpp" + +#include +#include +#include +#include +#include + +#if defined(__linux__) || defined(__ANDROID__) +#include +#endif + +#if defined(__aarch64__) +#include +#endif + +namespace mllm::cpu::kda { +namespace { + +constexpr int kMaxParallelLanes = 8; +constexpr std::size_t kMinParallelWork = 65536; + +int availableCpuCount(int fallback) { +#if defined(__linux__) || defined(__ANDROID__) + cpu_set_t affinity = {}; + if (sched_getaffinity(0, sizeof(affinity), &affinity) == 0) { return CPU_COUNT(&affinity); } +#endif + return fallback; +} + +float stableSigmoid(float value) { + if (value >= 0.0F) { + const float exp_value = std::exp(-value); + return 1.0F / (1.0F + exp_value); + } + const float exp_value = std::exp(value); + return exp_value / (1.0F + exp_value); +} + +float stableSoftplus(float value) { + if (value > 20.0F) { return value; } + if (value < -20.0F) { return std::exp(value); } + return std::log1p(std::exp(value)); +} + +float squaredNorm(const float* values, int length) { + float sum = 0.0F; + int index = 0; +#if defined(__aarch64__) + float32x4_t vector_sum = vdupq_n_f32(0.0F); + for (; index + 4 <= length; index += 4) { + const float32x4_t value = vld1q_f32(values + index); + vector_sum = vmlaq_f32(vector_sum, value, value); + } + sum = vaddvq_f32(vector_sum); +#endif + for (; index < length; ++index) { sum += values[index] * values[index]; } + return sum; +} + +void normalize(const float* source, float scale, float* destination, int length) { + int index = 0; +#if defined(__aarch64__) + for (; index + 4 <= length; index += 4) { vst1q_f32(destination + index, vmulq_n_f32(vld1q_f32(source + index), scale)); } +#endif + for (; index < length; ++index) { destination[index] = source[index] * scale; } +} + +void decayAndAccumulatePrediction(float* state_row, float decay, float key, float* prediction, int length) { + int index = 0; +#if defined(__aarch64__) + for (; index + 4 <= length; index += 4) { + float32x4_t state_value = vmulq_n_f32(vld1q_f32(state_row + index), decay); + vst1q_f32(state_row + index, state_value); + float32x4_t predicted = vld1q_f32(prediction + index); + predicted = vmlaq_n_f32(predicted, state_value, key); + vst1q_f32(prediction + index, predicted); + } +#endif + for (; index < length; ++index) { + state_row[index] *= decay; + prediction[index] += key * state_row[index]; + } +} + +void updateAndAccumulateOutput(float* state_row, float key, float query, const float* delta, float* output, int length) { + int index = 0; +#if defined(__aarch64__) + for (; index + 4 <= length; index += 4) { + float32x4_t state_value = vld1q_f32(state_row + index); + state_value = vmlaq_n_f32(state_value, vld1q_f32(delta + index), key); + vst1q_f32(state_row + index, state_value); + float32x4_t output_value = vld1q_f32(output + index); + output_value = vmlaq_n_f32(output_value, state_value, query); + vst1q_f32(output + index, output_value); + } +#endif + for (; index < length; ++index) { + state_row[index] += key * delta[index]; + output[index] += query * state_row[index]; + } +} + +} // namespace + +void kimiDeltaAttentionF32(const float* q, const float* k, const float* v, const float* gate_logits, const float* beta, + const float* a_log, const float* dt_bias, float* state, float* output, int batch_size, + int sequence_length, int num_heads, int head_dim, bool safe_gate, float lower_bound) { + kimiDeltaAttentionF32(q, k, v, gate_logits, beta, a_log, dt_bias, state, output, batch_size, sequence_length, num_heads, + head_dim, safe_gate, lower_bound, ::mllm::Context::instance().getCpuOpThreads()); +} + +void kimiDeltaAttentionF32(const float* q, const float* k, const float* v, const float* gate_logits, const float* beta, + const float* a_log, const float* dt_bias, float* state, float* output, int batch_size, + int sequence_length, int num_heads, int head_dim, bool safe_gate, float lower_bound, + int thread_count) { + if (q == nullptr || k == nullptr || v == nullptr || gate_logits == nullptr || beta == nullptr || a_log == nullptr + || dt_bias == nullptr || state == nullptr || output == nullptr) { + throw std::invalid_argument("Kimi Delta Attention received a null pointer"); + } + if (batch_size <= 0 || sequence_length <= 0 || num_heads <= 0 || head_dim <= 0 || thread_count <= 0) { + throw std::invalid_argument("Kimi Delta Attention received an invalid shape"); + } + if (safe_gate && (!std::isfinite(lower_bound) || lower_bound >= 0.0F)) { + throw std::invalid_argument("Kimi Delta Attention safe gate requires a finite negative lower bound"); + } + + const float query_dim_scale = 1.0F / std::sqrt(static_cast(head_dim)); + const int task_count = batch_size * num_heads; + const std::size_t work = static_cast(task_count) * sequence_length * head_dim * head_dim; + const int parallel_lanes = std::min({thread_count, task_count, availableCpuCount(thread_count), kMaxParallelLanes}); + const bool use_parallel = parallel_lanes > 1 && work >= kMinParallelWork; + const int scheduled_lanes = use_parallel ? parallel_lanes : 1; + + const auto run_lane = [&](int lane) { + std::vector normalized_query(static_cast(head_dim)); + std::vector normalized_key(static_cast(head_dim)); + std::vector decay(static_cast(head_dim)); + std::vector prediction(static_cast(head_dim)); + std::vector delta(static_cast(head_dim)); + + for (int task = lane; task < task_count; task += scheduled_lanes) { + const int batch = task / num_heads; + const int head = task % num_heads; + const std::size_t state_base = (static_cast(batch) * num_heads + head) * head_dim * head_dim; + const float a_scale = std::exp(a_log[head]); + + for (int token = 0; token < sequence_length; ++token) { + const std::size_t vector_base = + ((static_cast(batch) * sequence_length + token) * num_heads + head) * head_dim; + const std::size_t beta_index = (static_cast(batch) * sequence_length + token) * num_heads + head; + + const float query_scale = query_dim_scale / std::sqrt(squaredNorm(q + vector_base, head_dim) + 1.0e-6F); + const float key_scale = 1.0F / std::sqrt(squaredNorm(k + vector_base, head_dim) + 1.0e-6F); + normalize(q + vector_base, query_scale, normalized_query.data(), head_dim); + normalize(k + vector_base, key_scale, normalized_key.data(), head_dim); + + for (int key_dim = 0; key_dim < head_dim; ++key_dim) { + const float gate_input = gate_logits[vector_base + key_dim] + dt_bias[head * head_dim + key_dim]; + const float log_decay = + safe_gate ? lower_bound * stableSigmoid(a_scale * gate_input) : -a_scale * stableSoftplus(gate_input); + decay[key_dim] = std::exp(log_decay); + } + + std::fill(prediction.begin(), prediction.end(), 0.0F); + for (int key_dim = 0; key_dim < head_dim; ++key_dim) { + float* state_row = state + state_base + static_cast(key_dim) * head_dim; + decayAndAccumulatePrediction(state_row, decay[key_dim], normalized_key[key_dim], prediction.data(), head_dim); + } + for (int value_dim = 0; value_dim < head_dim; ++value_dim) { + delta[value_dim] = beta[beta_index] * (v[vector_base + value_dim] - prediction[value_dim]); + } + + float* token_output = output + vector_base; + std::fill(token_output, token_output + head_dim, 0.0F); + for (int key_dim = 0; key_dim < head_dim; ++key_dim) { + float* state_row = state + state_base + static_cast(key_dim) * head_dim; + updateAndAccumulateOutput(state_row, normalized_key[key_dim], normalized_query[key_dim], delta.data(), token_output, + head_dim); + } + } + } + }; + + MLLM_CONDITIONAL_PARALLEL_FOR(use_parallel, scheduled_lanes, lane, 0, scheduled_lanes, 1, + { run_lane(static_cast(lane)); }); +} + +} // namespace mllm::cpu::kda diff --git a/mllm/backends/cpu/kernels/common/kda/kimi_delta_attention.hpp b/mllm/backends/cpu/kernels/common/kda/kimi_delta_attention.hpp new file mode 100644 index 000000000..38a9ac32a --- /dev/null +++ b/mllm/backends/cpu/kernels/common/kda/kimi_delta_attention.hpp @@ -0,0 +1,28 @@ +// Copyright (c) MLLM Team. +// Licensed under the MIT License. + +#pragma once + +namespace mllm::cpu::kda { + +// Kimi Delta Attention recurrent update used by Ling-3.0. +// +// q/k/v/g: [B, S, H, D] +// beta: [B, S, H] (already passed through sigmoid) +// a_log: [H] +// dt_bias: [H, D] +// state: [B, H, D, D], updated in place +// output: [B, S, H, D] +// +// q and k are L2-normalized inside this function and q is additionally +// scaled by 1 / sqrt(D), matching the FLA KDA reference implementation. +void kimiDeltaAttentionF32(const float* q, const float* k, const float* v, const float* gate_logits, const float* beta, + const float* a_log, const float* dt_bias, float* state, float* output, int batch_size, + int sequence_length, int num_heads, int head_dim, bool safe_gate, float lower_bound); + +void kimiDeltaAttentionF32(const float* q, const float* k, const float* v, const float* gate_logits, const float* beta, + const float* a_log, const float* dt_bias, float* state, float* output, int batch_size, + int sequence_length, int num_heads, int head_dim, bool safe_gate, float lower_bound, + int thread_count); + +} // namespace mllm::cpu::kda diff --git a/mllm/backends/cpu/kernels/common/paged_attn/arch.hpp b/mllm/backends/cpu/kernels/common/paged_attn/arch.hpp index 4abb818d5..3e0901518 100644 --- a/mllm/backends/cpu/kernels/common/paged_attn/arch.hpp +++ b/mllm/backends/cpu/kernels/common/paged_attn/arch.hpp @@ -14,6 +14,7 @@ #include #include "mllm/backends/cpu/kernels/arm/math.hpp" #elif defined(MLLM_HOST_ARCH_X86_64) || defined(MLLM_HOST_ARCH_X86) +#include #else #endif @@ -36,9 +37,8 @@ struct VectorDotProduct { template<> struct VectorDotProduct { __MLLM_UNSAFE_OPT_BEGIN_O3 // Do not open fast math here - static MLLM_FORCE_INLINE void - run(const mllm_fp32_t* __restrict__ __lhs, const mllm_fp32_t* __restrict__ __rhs, mllm_fp32_t* __restrict__ __out, - size_t len) { + static MLLM_FORCE_INLINE void run(const mllm_fp32_t* __restrict__ __lhs, const mllm_fp32_t* __restrict__ __rhs, + mllm_fp32_t* __restrict__ __out, size_t len) { mllm_fp32_t sum = 0.0f; for (size_t i = 0; i < len; ++i) { sum += __lhs[i] * __rhs[i]; } *__out = sum; diff --git a/mllm/models/ling3/configuration_ling3.hpp b/mllm/models/ling3/configuration_ling3.hpp new file mode 100644 index 000000000..65309bb43 --- /dev/null +++ b/mllm/models/ling3/configuration_ling3.hpp @@ -0,0 +1,206 @@ +// Copyright (c) MLLM Team. +// Licensed under the MIT License. + +#pragma once + +#include "mllm/core/aops/LinearOp.hpp" +#include "mllm/engine/ConfigFile.hpp" + +#include +#include +#include +#include + +namespace mllm::models::ling3 { + +struct Ling3Config : protected ConfigFile { + Ling3Config() = default; + + explicit Ling3Config(const std::string& file_path) : ConfigFile(file_path) { + const auto& config = data(); + hidden_size = config.at("hidden_size"); + intermediate_size = config.at("intermediate_size"); + num_hidden_layers = config.at("num_hidden_layers"); + num_attention_heads = config.at("num_attention_heads"); + num_key_value_heads = config.at("num_key_value_heads"); + head_dim = config.at("head_dim"); + vocab_size = config.at("vocab_size"); + max_position_embeddings = config.at("max_position_embeddings"); + rms_norm_eps = config.at("rms_norm_eps"); + rope_theta = config.at("rope_theta"); + hidden_act = config.at("hidden_act"); + tie_word_embeddings = config.at("tie_word_embeddings"); + use_qkv_bias = config.at("use_qkv_bias"); + + layer_group_size = config.at("layer_group_size"); + short_conv_kernel_size = config.at("short_conv_kernel_size"); + no_kda_lora = config.at("no_kda_lora"); + kda_safe_gate = config.at("kda_safe_gate"); + kda_lower_bound = config.at("kda_lower_bound"); + + q_lora_rank = config.at("q_lora_rank"); + kv_lora_rank = config.at("kv_lora_rank"); + qk_rope_head_dim = config.at("qk_rope_head_dim"); + qk_nope_head_dim = config.at("qk_nope_head_dim"); + qk_head_dim = config.at("qk_head_dim"); + v_head_dim = config.at("v_head_dim"); + rope_interleave = config.at("rope_interleave"); + gated_attention_proj_granularity_type = config.at("gated_attention_proj_granularity_type"); + + num_experts = config.at("num_experts"); + num_shared_experts = config.at("num_shared_experts"); + num_experts_per_tok = config.at("num_experts_per_tok"); + n_group = config.at("n_group"); + topk_group = config.at("topk_group"); + moe_intermediate_size = config.at("moe_intermediate_size"); + moe_shared_expert_intermediate_size = config.at("moe_shared_expert_intermediate_size"); + first_k_dense_replace = config.at("first_k_dense_replace"); + routed_scaling_factor = config.at("routed_scaling_factor"); + scoring_func = config.at("scoring_func"); + topk_method = config.at("topk_method"); + moe_router_enable_expert_bias = config.at("moe_router_enable_expert_bias"); + + pad_token_id = config.at("pad_token_id"); + eos_token_id = config.at("eos_token_id"); + max_cache_length = config.value("max_cache_length", max_cache_length); + if (config.contains("linear_impl_type")) { + const auto impl_name = config.at("linear_impl_type").get(); + linear_impl_type = aops::str2LinearImplTypes(impl_name); + if (linear_impl_type == aops::LinearImplTypes::kDefault && impl_name != "Default") { + throw std::invalid_argument("Ling-3 contains an unsupported linear_impl_type: " + impl_name); + } + } + + validate(); + } + + int32_t hidden_size = 1536; + int32_t intermediate_size = 4608; + int32_t num_hidden_layers = 24; + int32_t num_attention_heads = 16; + int32_t num_key_value_heads = 16; + int32_t head_dim = 128; + int32_t vocab_size = 157184; + int32_t max_position_embeddings = 131072; + float rms_norm_eps = 1.0e-6F; + float rope_theta = 6000000.0F; + std::string hidden_act = "silu"; + bool tie_word_embeddings = false; + bool use_qkv_bias = false; + + int32_t layer_group_size = 4; + int32_t short_conv_kernel_size = 4; + bool no_kda_lora = true; + bool kda_safe_gate = true; + float kda_lower_bound = -5.0F; + + int32_t q_lora_rank = 256; + int32_t kv_lora_rank = 512; + int32_t qk_rope_head_dim = 64; + int32_t qk_nope_head_dim = 128; + int32_t qk_head_dim = 192; + int32_t v_head_dim = 128; + bool rope_interleave = true; + std::string gated_attention_proj_granularity_type = "head_wise"; + + int32_t num_experts = 128; + int32_t num_shared_experts = 1; + int32_t num_experts_per_tok = 8; + int32_t n_group = 8; + int32_t topk_group = 4; + int32_t moe_intermediate_size = 512; + int32_t moe_shared_expert_intermediate_size = 512; + int32_t first_k_dense_replace = 1; + float routed_scaling_factor = 2.5F; + std::string scoring_func = "sigmoid"; + std::string topk_method = "noaux_tc"; + bool moe_router_enable_expert_bias = true; + + int64_t pad_token_id = 156892; + int64_t eos_token_id = 156895; + int64_t end_of_text_token_id = 156892; + int64_t bos_token_id = 156891; + int32_t max_cache_length = 2048; + + aops::LinearImplTypes linear_impl_type = aops::LinearImplTypes::kDefault; + + [[nodiscard]] bool isFullAttentionLayer(int32_t layer_index) const { + return (layer_index + 1) % layer_group_size == 0 || layer_index >= num_hidden_layers / layer_group_size * layer_group_size; + } + + [[nodiscard]] int32_t numFullAttentionLayers() const { + int32_t count = 0; + for (int32_t layer = 0; layer < num_hidden_layers; ++layer) { + if (isFullAttentionLayer(layer)) { ++count; } + } + return count; + } + + [[nodiscard]] int32_t numKDALayers() const { return num_hidden_layers - numFullAttentionLayers(); } + + private: + void validate() const { + if (hidden_size <= 0 || intermediate_size <= 0 || num_hidden_layers <= 0 || num_attention_heads <= 0 + || num_key_value_heads <= 0 || head_dim <= 0 || vocab_size <= 0 || max_position_embeddings <= 0 + || max_cache_length <= 0) { + throw std::invalid_argument("Ling-3 contains invalid model dimensions"); + } + if (layer_group_size <= 0 || short_conv_kernel_size <= 1 || q_lora_rank <= 0 || kv_lora_rank <= 0 || qk_rope_head_dim <= 0 + || qk_nope_head_dim <= 0 || qk_head_dim != qk_nope_head_dim + qk_rope_head_dim || v_head_dim <= 0) { + throw std::invalid_argument("Ling-3 contains invalid hybrid attention dimensions"); + } + if (num_experts <= 0 || num_shared_experts != 1 || num_experts_per_tok <= 0 || num_experts_per_tok > num_experts + || n_group <= 0 || num_experts % n_group != 0 || topk_group <= 0 || topk_group > n_group || moe_intermediate_size <= 0 + || moe_shared_expert_intermediate_size <= 0 || first_k_dense_replace < 0 || first_k_dense_replace > num_hidden_layers) { + throw std::invalid_argument("Ling-3 contains invalid MoE dimensions"); + } + if (!std::isfinite(rms_norm_eps) || rms_norm_eps <= 0.0F || !std::isfinite(rope_theta) || rope_theta <= 0.0F + || !std::isfinite(kda_lower_bound) || kda_lower_bound >= 0.0F || !std::isfinite(routed_scaling_factor) + || routed_scaling_factor <= 0.0F) { + throw std::invalid_argument("Ling-3 contains invalid numerical parameters"); + } + if (hidden_act != "silu" || scoring_func != "sigmoid" || topk_method != "noaux_tc" || !no_kda_lora || !kda_safe_gate + || !rope_interleave || gated_attention_proj_granularity_type != "head_wise" || tie_word_embeddings || use_qkv_bias + || !moe_router_enable_expert_bias) { + throw std::invalid_argument("Ling-3 CPU received an unsupported architecture variant"); + } + } +}; + +inline bool hasOfficialLing3TinyArchitecture(const Ling3Config& config) { + return config.hidden_size == 1536 && config.intermediate_size == 4608 && config.num_hidden_layers == 24 + && config.num_attention_heads == 16 && config.num_key_value_heads == 16 && config.head_dim == 128 + && config.vocab_size == 157184 && config.max_position_embeddings == 131072 && config.rms_norm_eps == 1.0e-6F + && config.rope_theta == 6000000.0F && config.layer_group_size == 4 && config.numFullAttentionLayers() == 6 + && config.numKDALayers() == 18 && config.short_conv_kernel_size == 4 && config.q_lora_rank == 256 + && config.kv_lora_rank == 512 && config.qk_rope_head_dim == 64 && config.qk_nope_head_dim == 128 + && config.qk_head_dim == 192 && config.v_head_dim == 128 && config.num_experts == 128 + && config.num_experts_per_tok == 8 && config.n_group == 8 && config.topk_group == 4 + && config.moe_intermediate_size == 512 && config.moe_shared_expert_intermediate_size == 512 + && config.first_k_dense_replace == 1 && config.routed_scaling_factor == 2.5F && config.pad_token_id == 156892 + && config.eos_token_id == 156895; +} + +inline void validateLing3ModelConfigMatch(const Ling3Config& config, const ParameterFile::ptr_t& parameter_file) { + constexpr auto kEmbeddingWeight = "model.word_embeddings.weight"; + constexpr auto kLmHeadWeight = "lm_head.weight"; + if (!hasOfficialLing3TinyArchitecture(config)) { + throw std::invalid_argument("Ling-3 model/config mismatch: expected inclusionAI/Ling-3.0-tiny"); + } + if (parameter_file == nullptr || parameter_file->version() != ModelFileVersion::kV2) { + throw std::invalid_argument("Ling-3 mobile CPU inference requires an MLLM V2 model file"); + } + if (!parameter_file->has(kEmbeddingWeight) || !parameter_file->has(kLmHeadWeight)) { + throw std::invalid_argument("Ling-3 model file is missing untied embedding or LM-head weights"); + } + const auto embedding = parameter_file->pull(kEmbeddingWeight); + if (embedding.dtype() != kFloat32 || embedding.shape() != Tensor::shape_t({config.vocab_size, config.hidden_size})) { + throw std::invalid_argument("Ling-3 model file has an incompatible word embedding descriptor"); + } + const auto lm_head = parameter_file->pull(kLmHeadWeight); + if (lm_head.dtype() != kByte || lm_head.shape().size() != 1) { + throw std::invalid_argument("Ling-3 mobile model requires a packed KAI W4A32 LM head"); + } +} + +} // namespace mllm::models::ling3 diff --git a/mllm/models/ling3/modeling_ling3.hpp b/mllm/models/ling3/modeling_ling3.hpp new file mode 100644 index 000000000..c9f9cba9b --- /dev/null +++ b/mllm/models/ling3/modeling_ling3.hpp @@ -0,0 +1,651 @@ +// Copyright (c) MLLM Team. +// Licensed under the MIT License. + +#pragma once + +#include "mllm/backends/cpu/kernels/common/gdn/gated_delta_net.hpp" +#include "mllm/backends/cpu/kernels/common/kda/kimi_delta_attention.hpp" +#include "mllm/mllm.hpp" +#include "mllm/models/ARGeneration.hpp" +#include "mllm/models/ling3/configuration_ling3.hpp" +#include "mllm/nn/Functional.hpp" +#include "mllm/nn/Module.hpp" +#include "mllm/nn/Nn.hpp" +#include "mllm/nn/lmcache/StaticCache.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace mllm::models::ling3 { + +inline Tensor makeLing3RoPEInvFreq(int rotary_dim, float rope_theta) { + auto inv_freq = Tensor::empty({rotary_dim / 2}, kFloat32, kCPU).alloc(); + auto* values = inv_freq.ptr(); + for (int index = 0; index < rotary_dim / 2; ++index) { + values[index] = 1.0F / std::pow(rope_theta, 2.0F * static_cast(index) / static_cast(rotary_dim)); + } + return inv_freq; +} + +inline std::pair makeLing3RotaryEmbedding(const Tensor& position_ids, const Tensor& inv_freq) { + if (position_ids.shape().size() != 2 || position_ids.dtype() != kInt64 || inv_freq.shape().size() != 1 + || inv_freq.dtype() != kFloat32) { + throw std::invalid_argument("Ling-3 RoPE received invalid position ids or frequencies"); + } + const int batch = position_ids.shape()[0]; + const int sequence = position_ids.shape()[1]; + const int half_dim = inv_freq.shape()[0]; + const int rotary_dim = half_dim * 2; + auto cos = Tensor::empty({batch, sequence, rotary_dim}, kFloat32, kCPU).alloc(); + auto sin = Tensor::empty({batch, sequence, rotary_dim}, kFloat32, kCPU).alloc(); + const auto* positions = position_ids.ptr(); + const auto* frequencies = inv_freq.ptr(); + auto* cos_values = cos.ptr(); + auto* sin_values = sin.ptr(); + for (int b = 0; b < batch; ++b) { + for (int s = 0; s < sequence; ++s) { + for (int dim = 0; dim < half_dim; ++dim) { + const float angle = static_cast(positions[b * sequence + s]) * frequencies[dim]; + const float cos_value = std::cos(angle); + const float sin_value = std::sin(angle); + const std::size_t base = (static_cast(b) * sequence + s) * rotary_dim; + cos_values[base + dim] = cos_value; + cos_values[base + half_dim + dim] = cos_value; + sin_values[base + dim] = sin_value; + sin_values[base + half_dim + dim] = sin_value; + } + } + } + return {cos, sin}; +} + +// Converts adjacent-pair RoPE storage [x0, x1, x2, x3, ...] to the +// half-split layout used by the official Bailing implementation while applying +// the rotation. Input/output are [B, H, S, D]; cos/sin are [B, S, D]. +inline Tensor applyLing3InterleavedRoPE(Tensor input, const Tensor& cos, const Tensor& sin) { + if (input.dtype() != kFloat32 || cos.dtype() != kFloat32 || sin.dtype() != kFloat32 || input.shape().size() != 4 + || cos.shape().size() != 3 || sin.shape() != cos.shape()) { + throw std::invalid_argument("Ling-3 interleaved RoPE currently requires float32 tensors"); + } + const int batch = input.shape()[0]; + const int heads = input.shape()[1]; + const int sequence = input.shape()[2]; + const int dim = input.shape()[3]; + if (dim <= 0 || dim % 2 != 0 || cos.shape()[0] != batch || cos.shape()[1] != sequence || cos.shape()[2] != dim) { + throw std::invalid_argument("Ling-3 interleaved RoPE received incompatible shapes"); + } + auto source = input.contiguous(); + auto output = Tensor::empty(input.shape(), kFloat32, kCPU).alloc(); + const auto* source_values = source.ptr(); + const auto* cos_values = cos.ptr(); + const auto* sin_values = sin.ptr(); + auto* output_values = output.ptr(); + const int half_dim = dim / 2; + for (int b = 0; b < batch; ++b) { + for (int h = 0; h < heads; ++h) { + for (int s = 0; s < sequence; ++s) { + const std::size_t vector_base = ((static_cast(b) * heads + h) * sequence + s) * dim; + const std::size_t rope_base = (static_cast(b) * sequence + s) * dim; + for (int pair = 0; pair < half_dim; ++pair) { + const float even = source_values[vector_base + pair * 2]; + const float odd = source_values[vector_base + pair * 2 + 1]; + const float cos_value = cos_values[rope_base + pair]; + const float sin_value = sin_values[rope_base + pair]; + output_values[vector_base + pair] = even * cos_value - odd * sin_value; + output_values[vector_base + half_dim + pair] = odd * cos_value + even * sin_value; + } + } + } + } + return output; +} + +inline Tensor padLing3ValuesForCache(Tensor input, int output_dim) { + if (input.dtype() != kFloat32 || input.shape().size() != 4 || input.shape()[3] <= 0 || input.shape()[3] > output_dim) { + throw std::invalid_argument("Ling-3 MLA value-cache padding received an invalid tensor"); + } + auto source = input.contiguous(); + auto output = Tensor::zeros({input.shape()[0], input.shape()[1], input.shape()[2], output_dim}, kFloat32, kCPU); + const int input_dim = input.shape()[3]; + const std::size_t vectors = static_cast(input.shape()[0]) * input.shape()[1] * input.shape()[2]; + const auto* source_values = source.ptr(); + auto* output_values = output.ptr(); + for (std::size_t vector = 0; vector < vectors; ++vector) { + std::memcpy(output_values + vector * output_dim, source_values + vector * input_dim, + static_cast(input_dim) * sizeof(float)); + } + return output; +} + +class Ling3MLP final : public nn::Module { + public: + Ling3MLP() = default; + Ling3MLP(const std::string& name, const Ling3Config& config, const std::optional& intermediate_size = std::nullopt) + : nn::Module(name) { + const int32_t intermediate = intermediate_size.value_or(config.intermediate_size); + gate_proj_ = reg("gate_proj", config.hidden_size, intermediate, false, config.linear_impl_type); + up_proj_ = reg("up_proj", config.hidden_size, intermediate, false, config.linear_impl_type); + down_proj_ = reg("down_proj", intermediate, config.hidden_size, false, config.linear_impl_type); + activation_ = reg("act"); + } + + std::vector forward(const std::vector& inputs, const std::vector& args) override { + return {down_proj_(activation_(gate_proj_(inputs[0])) * up_proj_(inputs[0]))}; + } + + private: + nn::Linear gate_proj_; + nn::Linear up_proj_; + nn::Linear down_proj_; + nn::SiLU activation_; +}; + +class Ling3MoEGate final : public nn::Module { + public: + Ling3MoEGate() = default; + Ling3MoEGate(const std::string& name, const Ling3Config& config) : nn::Module(name) { + hidden_size_ = config.hidden_size; + num_experts_ = config.num_experts; + top_k_ = config.num_experts_per_tok; + num_groups_ = config.n_group; + top_groups_ = config.topk_group; + routed_scaling_factor_ = config.routed_scaling_factor; + weight_ = reg("weight", getModuleName() + ".weight"); + expert_bias_ = reg("expert_bias", getModuleName() + ".expert_bias"); + } + + std::vector forward(const std::vector& inputs, const std::vector& args) override { + auto hidden = inputs[0]; + if (hidden.dtype() != kFloat32 || hidden.shape().size() != 3 || hidden.shape()[2] != hidden_size_ + || weight_.weight().dtype() != kFloat32 || expert_bias_.weight().dtype() != kFloat32) { + throw std::invalid_argument("Ling-3 MoE router requires float32 tensors with official shapes"); + } + hidden = hidden.view({-1, hidden_size_}); + auto logits = nn::functional::matmul(hidden, weight_.weight(), false, true).contiguous(); + const int tokens = hidden.shape()[0]; + auto topk_ids = Tensor::empty({tokens, top_k_}, kInt32, kCPU).alloc(); + auto topk_weights = Tensor::empty({tokens, top_k_}, kFloat32, kCPU).alloc(); + const auto* logits_values = logits.ptr(); + const auto* bias_values = expert_bias_.weight().ptr(); + auto* id_values = topk_ids.ptr(); + auto* weight_values = topk_weights.ptr(); + const int experts_per_group = num_experts_ / num_groups_; + + std::vector scores(static_cast(num_experts_)); + std::vector> ranked_groups(static_cast(num_groups_)); + std::vector> ranked_experts; + ranked_experts.reserve(static_cast(top_groups_ * experts_per_group)); + for (int token = 0; token < tokens; ++token) { + for (int expert = 0; expert < num_experts_; ++expert) { + const float value = logits_values[static_cast(token) * num_experts_ + expert]; + scores[expert] = value >= 0.0F ? 1.0F / (1.0F + std::exp(-value)) : std::exp(value) / (1.0F + std::exp(value)); + } + for (int group = 0; group < num_groups_; ++group) { + float largest = -std::numeric_limits::infinity(); + float second_largest = -std::numeric_limits::infinity(); + for (int offset = 0; offset < experts_per_group; ++offset) { + const int expert = group * experts_per_group + offset; + const float routed_score = scores[expert] + bias_values[expert]; + if (routed_score > largest) { + second_largest = largest; + largest = routed_score; + } else if (routed_score > second_largest) { + second_largest = routed_score; + } + } + ranked_groups[group] = {largest + second_largest, group}; + } + std::partial_sort(ranked_groups.begin(), ranked_groups.begin() + top_groups_, ranked_groups.end(), + [](const auto& lhs, const auto& rhs) { + return lhs.first != rhs.first ? lhs.first > rhs.first : lhs.second < rhs.second; + }); + ranked_experts.clear(); + for (int group_index = 0; group_index < top_groups_; ++group_index) { + const int group = ranked_groups[group_index].second; + for (int offset = 0; offset < experts_per_group; ++offset) { + const int expert = group * experts_per_group + offset; + ranked_experts.emplace_back(scores[expert] + bias_values[expert], expert); + } + } + std::partial_sort(ranked_experts.begin(), ranked_experts.begin() + top_k_, ranked_experts.end(), + [](const auto& lhs, const auto& rhs) { + return lhs.first != rhs.first ? lhs.first > rhs.first : lhs.second < rhs.second; + }); + float score_sum = 1.0e-20F; + for (int route = 0; route < top_k_; ++route) { score_sum += scores[ranked_experts[route].second]; } + for (int route = 0; route < top_k_; ++route) { + const int expert = ranked_experts[route].second; + id_values[token * top_k_ + route] = expert; + weight_values[token * top_k_ + route] = scores[expert] / score_sum * routed_scaling_factor_; + } + } + return {topk_ids, topk_weights}; + } + + private: + int32_t hidden_size_ = 0; + int32_t num_experts_ = 0; + int32_t top_k_ = 0; + int32_t num_groups_ = 0; + int32_t top_groups_ = 0; + float routed_scaling_factor_ = 1.0F; + nn::Param weight_; + nn::Param expert_bias_; +}; + +class Ling3SparseMoE final : public nn::Module { + public: + Ling3SparseMoE() = default; + Ling3SparseMoE(const std::string& name, const Ling3Config& config) : nn::Module(name) { + top_k_ = config.num_experts_per_tok; + experts_ = reg>("experts", config.num_experts, config, + std::optional(config.moe_intermediate_size)); + gate_ = reg("gate", config); + shared_experts_ = + reg("shared_experts", config, + std::optional(config.moe_shared_expert_intermediate_size * config.num_shared_experts)); + } + + std::vector forward(const std::vector& inputs, const std::vector& args) override { + auto identity = inputs[0]; + const auto original_shape = identity.shape(); + auto routing = gate_(identity); + auto hidden = identity.view({-1, identity.shape().back()}); + auto routed = moeInfer(hidden, routing[0], routing[1]).view(original_shape); + return {routed + shared_experts_(identity)[0]}; + } + + private: + Tensor moeInfer(const Tensor& input, Tensor& topk_ids, Tensor& topk_weights) { + const int tokens = topk_ids.shape()[0]; + const int hidden_size = input.shape()[1]; + auto routed_output = Tensor::empty({tokens, top_k_, hidden_size}, kFloat32, kCPU).alloc(); + const auto* id_values = topk_ids.ptr(); + auto* routed_values = routed_output.ptr(); + // Keep each routed expert invocation at M=1. KAI's dynamic activation + // quantization is row-local, and this avoids platform-dependent grouped + // prefill results while retaining the same token/expert routing contract. + for (int token = 0; token < tokens; ++token) { + auto expert_input = input[{{token, token + 1}, kAll}]; + for (int route = 0; route < top_k_; ++route) { + const int expert = id_values[token * top_k_ + route]; + auto expert_output = experts_.list()[expert](expert_input)[0].contiguous(); + std::memcpy(routed_values + (static_cast(token) * top_k_ + route) * hidden_size, + expert_output.ptr(), static_cast(hidden_size) * sizeof(float)); + } + } + return routed_output.mul_(topk_weights.unsqueeze(-1)).sum(1).to(routed_output.dtype()); + } + + int32_t top_k_ = 0; + nn::ModuleList experts_; + Ling3MoEGate gate_; + Ling3MLP shared_experts_; +}; + +class Ling3KimiDeltaAttention final : public nn::Module { + public: + Ling3KimiDeltaAttention() = default; + Ling3KimiDeltaAttention(const std::string& name, const Ling3Config& config) : nn::Module(name) { + hidden_size_ = config.hidden_size; + num_heads_ = config.num_attention_heads; + head_dim_ = config.head_dim; + projection_size_ = num_heads_ * head_dim_; + conv_size_ = config.short_conv_kernel_size; + safe_gate_ = config.kda_safe_gate; + lower_bound_ = config.kda_lower_bound; + + q_proj_ = reg("q_proj", hidden_size_, projection_size_, false, config.linear_impl_type); + k_proj_ = reg("k_proj", hidden_size_, projection_size_, false, config.linear_impl_type); + v_proj_ = reg("v_proj", hidden_size_, projection_size_, false, config.linear_impl_type); + q_conv1d_ = reg("q_conv1d", projection_size_, projection_size_, conv_size_, 1, conv_size_ - 1, 1, + projection_size_, false); + k_conv1d_ = reg("k_conv1d", projection_size_, projection_size_, conv_size_, 1, conv_size_ - 1, 1, + projection_size_, false); + v_conv1d_ = reg("v_conv1d", projection_size_, projection_size_, conv_size_, 1, conv_size_ - 1, 1, + projection_size_, false); + f_proj_ = reg("f_proj", hidden_size_, projection_size_, false, config.linear_impl_type); + b_proj_ = reg("b_proj", hidden_size_, num_heads_, false, config.linear_impl_type); + g_proj_ = reg("g_proj", hidden_size_, projection_size_, false, config.linear_impl_type); + A_log_ = reg("A_log", getModuleName() + ".A_log"); + dt_bias_ = reg("dt_bias", getModuleName() + ".dt_bias"); + o_norm_ = reg("o_norm", config.rms_norm_eps, false); + o_proj_ = reg("o_proj", projection_size_, hidden_size_, false, config.linear_impl_type); + silu_ = reg("conv_activation"); + sigmoid_ = reg("gate_activation"); + } + + void resetState(int batch_size) { + if (batch_size <= 0) { throw std::invalid_argument("Ling-3 KDA reset requires a positive batch size"); } + recurrent_state_ = Tensor::zeros({batch_size, num_heads_, head_dim_, head_dim_}, kFloat32, kCPU); + q_conv_state_ = Tensor::zeros({batch_size, projection_size_, conv_size_ - 1}, kFloat32, kCPU); + k_conv_state_ = Tensor::zeros({batch_size, projection_size_, conv_size_ - 1}, kFloat32, kCPU); + v_conv_state_ = Tensor::zeros({batch_size, projection_size_, conv_size_ - 1}, kFloat32, kCPU); + } + + std::vector forward(const std::vector& inputs, const std::vector& args) override { + auto hidden = inputs[0]; + const int batch = hidden.shape()[0]; + const int sequence = hidden.shape()[1]; + if (recurrent_state_.isNil() || recurrent_state_.shape()[0] != batch) { resetState(batch); } + + auto q = q_proj_(hidden).contiguous(); + auto k = k_proj_(hidden).contiguous(); + auto v = v_proj_(hidden).contiguous(); + auto gate_logits = f_proj_(hidden).contiguous(); + auto beta = sigmoid_(b_proj_(hidden)).contiguous(); + const auto q_weight = q_conv1d_.weight(); + const auto k_weight = k_conv1d_.weight(); + const auto v_weight = v_conv1d_.weight(); + const auto a_log = A_log_.weight(); + const auto dt_bias = dt_bias_.weight(); + if (q.dtype() != kFloat32 || k.dtype() != kFloat32 || v.dtype() != kFloat32 || gate_logits.dtype() != kFloat32 + || beta.dtype() != kFloat32 || q_weight.dtype() != kFloat32 || k_weight.dtype() != kFloat32 + || v_weight.dtype() != kFloat32 || a_log.dtype() != kFloat32 || dt_bias.dtype() != kFloat32) { + throw std::invalid_argument("Ling-3 KDA requires float32 recurrent activations and parameters"); + } + + auto q_conv = Tensor::empty({batch, sequence, projection_size_}, kFloat32, kCPU).alloc(); + auto k_conv = Tensor::empty({batch, sequence, projection_size_}, kFloat32, kCPU).alloc(); + auto v_conv = Tensor::empty({batch, sequence, projection_size_}, kFloat32, kCPU).alloc(); + cpu::gdn::depthwiseCausalConvF32(q.ptr(), q_weight.ptr(), q_conv_state_.ptr(), q_conv.ptr(), + batch, sequence, projection_size_, conv_size_); + cpu::gdn::depthwiseCausalConvF32(k.ptr(), k_weight.ptr(), k_conv_state_.ptr(), k_conv.ptr(), + batch, sequence, projection_size_, conv_size_); + cpu::gdn::depthwiseCausalConvF32(v.ptr(), v_weight.ptr(), v_conv_state_.ptr(), v_conv.ptr(), + batch, sequence, projection_size_, conv_size_); + q = silu_(q_conv).view({batch, sequence, num_heads_, head_dim_}).contiguous(); + k = silu_(k_conv).view({batch, sequence, num_heads_, head_dim_}).contiguous(); + v = silu_(v_conv).view({batch, sequence, num_heads_, head_dim_}).contiguous(); + gate_logits = gate_logits.view({batch, sequence, num_heads_, head_dim_}).contiguous(); + auto output = Tensor::empty({batch, sequence, num_heads_, head_dim_}, kFloat32, kCPU).alloc(); + cpu::kda::kimiDeltaAttentionF32(q.ptr(), k.ptr(), v.ptr(), gate_logits.ptr(), beta.ptr(), + a_log.ptr(), dt_bias.ptr(), recurrent_state_.ptr(), + output.ptr(), batch, sequence, num_heads_, head_dim_, safe_gate_, lower_bound_); + + output = output.view({batch * sequence * num_heads_, head_dim_}); + auto output_gate = sigmoid_(g_proj_(hidden).view({batch * sequence * num_heads_, head_dim_})); + output = o_norm_(output) * output_gate; + output = output.view({batch, sequence, projection_size_}); + return {o_proj_(output)}; + } + + private: + int32_t hidden_size_ = 0; + int32_t num_heads_ = 0; + int32_t head_dim_ = 0; + int32_t projection_size_ = 0; + int32_t conv_size_ = 0; + bool safe_gate_ = true; + float lower_bound_ = -5.0F; + nn::Linear q_proj_, k_proj_, v_proj_; + nn::Conv1D q_conv1d_, k_conv1d_, v_conv1d_; + nn::Linear f_proj_, b_proj_, g_proj_; + nn::Param A_log_, dt_bias_; + nn::RMSNorm o_norm_; + nn::Linear o_proj_; + nn::SiLU silu_; + nn::Sigmoid sigmoid_; + Tensor recurrent_state_, q_conv_state_, k_conv_state_, v_conv_state_; +}; + +class Ling3MultiLatentAttention final : public nn::Module { + public: + int32_t cache_layer_index_ = 0; + + Ling3MultiLatentAttention() = default; + Ling3MultiLatentAttention(const std::string& name, const Ling3Config& config) : nn::Module(name) { + hidden_size_ = config.hidden_size; + num_heads_ = config.num_attention_heads; + q_lora_rank_ = config.q_lora_rank; + kv_lora_rank_ = config.kv_lora_rank; + qk_rope_dim_ = config.qk_rope_head_dim; + qk_nope_dim_ = config.qk_nope_head_dim; + qk_dim_ = config.qk_head_dim; + value_dim_ = config.v_head_dim; + q_a_proj_ = reg("q_a_proj", hidden_size_, q_lora_rank_, config.use_qkv_bias, config.linear_impl_type); + q_a_layernorm_ = reg("q_a_layernorm", config.rms_norm_eps, false); + q_b_proj_ = reg("q_b_proj", q_lora_rank_, num_heads_ * qk_dim_, false, config.linear_impl_type); + kv_a_proj_ = reg("kv_a_proj_with_mqa", hidden_size_, kv_lora_rank_ + qk_rope_dim_, config.use_qkv_bias, + config.linear_impl_type); + kv_a_layernorm_ = reg("kv_a_layernorm", config.rms_norm_eps, false); + kv_b_proj_ = + reg("kv_b_proj", kv_lora_rank_, num_heads_ * (qk_nope_dim_ + value_dim_), false, config.linear_impl_type); + g_proj_ = reg("g_proj", hidden_size_, num_heads_, false, config.linear_impl_type); + dense_ = reg("dense", num_heads_ * value_dim_, hidden_size_, config.use_qkv_bias, config.linear_impl_type); + mask_ = reg("mask"); + softmax_ = reg("softmax", -1); + sigmoid_ = reg("gate_sigmoid"); + } + + std::vector forward(const std::vector& inputs, const std::vector& args) override { + auto hidden = inputs[0]; + const auto cos = inputs[1]; + const auto sin = inputs[2]; + auto* cache = args[0].get(); + const int batch = hidden.shape()[0]; + const int sequence = hidden.shape()[1]; + + auto query = q_b_proj_(q_a_layernorm_(q_a_proj_(hidden))).view({batch, sequence, num_heads_, qk_dim_}).transpose(1, 2); + auto q_nope = query[{kAll, kAll, kAll, {0, qk_nope_dim_}}].contiguous(); + auto q_rope = query[{kAll, kAll, kAll, {qk_nope_dim_, qk_dim_}}].contiguous(); + + auto compressed_kv = kv_a_proj_(hidden); + auto kv_latent = compressed_kv[{kAll, kAll, {0, kv_lora_rank_}}].contiguous(); + auto k_rope = compressed_kv[{kAll, kAll, {kv_lora_rank_, kv_lora_rank_ + qk_rope_dim_}}] + .contiguous() + .view({batch, sequence, 1, qk_rope_dim_}) + .transpose(1, 2); + auto kv_expanded = + kv_b_proj_(kv_a_layernorm_(kv_latent)).view({batch, sequence, num_heads_, qk_nope_dim_ + value_dim_}).transpose(1, 2); + auto k_nope = kv_expanded[{kAll, kAll, kAll, {0, qk_nope_dim_}}].contiguous(); + auto value = kv_expanded[{kAll, kAll, kAll, {qk_nope_dim_, qk_nope_dim_ + value_dim_}}].contiguous(); + + q_rope = applyLing3InterleavedRoPE(q_rope, cos, sin); + k_rope = applyLing3InterleavedRoPE(k_rope, cos, sin).repeat(num_heads_, 1); + query = nn::functional::concat({q_nope, q_rope}, -1); + auto key = nn::functional::concat({k_nope, k_rope}, -1); + auto padded_value = padLing3ValuesForCache(value, qk_dim_); + auto cached = cache->updateKVCache(cache_layer_index_, key, padded_value); + key = cached[0]; + padded_value = cached[1]; + + auto attention = nn::functional::matmul(query, key, false, true) * (1.0F / std::sqrt(static_cast(qk_dim_))); + attention = softmax_(mask_(attention)); + auto output = nn::functional::matmul(attention, padded_value); + output = output[{kAll, kAll, kAll, {0, value_dim_}}].contiguous().transpose(1, 2); + auto gate = sigmoid_(g_proj_(hidden)).unsqueeze(-1); + output = output * gate; + output = output.view({batch, sequence, num_heads_ * value_dim_}); + return {dense_(output)}; + } + + private: + int32_t hidden_size_ = 0, num_heads_ = 0, q_lora_rank_ = 0, kv_lora_rank_ = 0; + int32_t qk_rope_dim_ = 0, qk_nope_dim_ = 0, qk_dim_ = 0, value_dim_ = 0; + nn::Linear q_a_proj_, q_b_proj_, kv_a_proj_, kv_b_proj_, g_proj_, dense_; + nn::RMSNorm q_a_layernorm_, kv_a_layernorm_; + nn::CausalMask mask_; + nn::Softmax softmax_; + nn::Sigmoid sigmoid_; +}; + +class Ling3KDAdecoder final : public nn::Module { + public: + Ling3KimiDeltaAttention attention_; + + Ling3KDAdecoder() = default; + Ling3KDAdecoder(const std::string& name, const Ling3Config& config, int32_t layer_index) : nn::Module(name) { + attention_ = reg("attention", config); + input_layernorm_ = reg("input_layernorm", config.rms_norm_eps, false); + post_attention_layernorm_ = reg("post_attention_layernorm", config.rms_norm_eps, false); + use_moe_ = layer_index >= config.first_k_dense_replace; + if (use_moe_) { + moe_ = reg("mlp", config); + } else { + dense_mlp_ = reg("mlp", config); + } + } + + std::vector forward(const std::vector& inputs, const std::vector& args) override { + auto hidden = inputs[0]; + auto attention_output = attention_(input_layernorm_(hidden))[0]; + hidden = hidden + attention_output; + auto mlp_input = post_attention_layernorm_(hidden); + return {hidden + (use_moe_ ? moe_(mlp_input)[0] : dense_mlp_(mlp_input)[0])}; + } + + private: + bool use_moe_ = false; + nn::RMSNorm input_layernorm_, post_attention_layernorm_; + Ling3MLP dense_mlp_; + Ling3SparseMoE moe_; +}; + +class Ling3MLADecoder final : public nn::Module { + public: + Ling3MultiLatentAttention attention_; + + Ling3MLADecoder() = default; + Ling3MLADecoder(const std::string& name, const Ling3Config& config, int32_t layer_index) : nn::Module(name) { + attention_ = reg("attention", config); + input_layernorm_ = reg("input_layernorm", config.rms_norm_eps, false); + post_attention_layernorm_ = reg("post_attention_layernorm", config.rms_norm_eps, false); + use_moe_ = layer_index >= config.first_k_dense_replace; + if (use_moe_) { + moe_ = reg("mlp", config); + } else { + dense_mlp_ = reg("mlp", config); + } + } + + std::vector forward(const std::vector& inputs, const std::vector& args) override { + auto hidden = inputs[0]; + auto attention_output = attention_(input_layernorm_(hidden), inputs[1], inputs[2], args[0])[0]; + hidden = hidden + attention_output; + auto mlp_input = post_attention_layernorm_(hidden); + return {hidden + (use_moe_ ? moe_(mlp_input)[0] : dense_mlp_(mlp_input)[0])}; + } + + private: + bool use_moe_ = false; + nn::RMSNorm input_layernorm_, post_attention_layernorm_; + Ling3MLP dense_mlp_; + Ling3SparseMoE moe_; +}; + +class Ling3Model final : public nn::Module { + public: + Ling3Model() = default; + Ling3Model(const std::string& name, const Ling3Config& config) : nn::Module(name) { + word_embeddings_ = reg("word_embeddings", config.vocab_size, config.hidden_size); + int32_t kda_index = 0; + int32_t mla_index = 0; + for (int32_t layer = 0; layer < config.num_hidden_layers; ++layer) { + const std::string layer_name = "layers." + std::to_string(layer); + if (config.isFullAttentionLayer(layer)) { + auto decoder = reg(layer_name, config, layer); + decoder.attention_.cache_layer_index_ = mla_index; + mla_layers_.push_back(std::move(decoder)); + layer_types_.push_back(0); + layer_dispatch_.push_back(mla_index++); + } else { + kda_layers_.push_back(reg(layer_name, config, layer)); + layer_types_.push_back(1); + layer_dispatch_.push_back(kda_index++); + } + } + norm_ = reg("norm", config.rms_norm_eps, false); + } + + void resetKDAStates(int32_t batch_size) { + for (auto& layer : kda_layers_) { layer.attention_.resetState(batch_size); } + } + + std::vector forward(const std::vector& inputs, const std::vector& args) override { + auto hidden = word_embeddings_(inputs[0]); + for (size_t layer = 0; layer < layer_types_.size(); ++layer) { + if (layer_types_[layer] == 0) { + hidden = mla_layers_[layer_dispatch_[layer]](hidden, inputs[1], inputs[2], args[0])[0]; + } else { + hidden = kda_layers_[layer_dispatch_[layer]](hidden)[0]; + } + } + return {norm_(hidden)}; + } + + private: + nn::Embedding word_embeddings_; + nn::RMSNorm norm_; + std::vector mla_layers_; + std::vector kda_layers_; + std::vector layer_types_; + std::vector layer_dispatch_; +}; + +class Ling3ForCausalLM final : public ARGeneration, public nn::Module { + public: + explicit Ling3ForCausalLM(const Ling3Config& config) { + if (!hasOfficialLing3TinyArchitecture(config)) { + throw std::invalid_argument("Ling-3 CPU currently supports inclusionAI/Ling-3.0-tiny only"); + } + kv_cache_ = nn::StaticCache(config.max_cache_length, config.numFullAttentionLayers(), config.num_attention_heads, + config.num_key_value_heads, config.qk_head_dim, kFloat32, kFloat32, kCPU, false); + model_ = reg("model", config); + lm_head_ = reg("lm_head", config.hidden_size, config.vocab_size, false, config.linear_impl_type); + registerBuffer("inv_freq", makeLing3RoPEInvFreq(config.qk_rope_head_dim, config.rope_theta)); + eos_token_id_ = config.eos_token_id; + additional_eos_token_ids_.insert(config.end_of_text_token_id); + max_length_ = config.max_cache_length; + } + + ARGenerationOutputPast forward(const ARGenerationOutputPast& input, const ARGenerationArgs& args) override { + auto sequence = input.at("sequence"); + if (sequence.shape().size() != 2 || sequence.shape()[0] != 1 || sequence.shape()[1] <= 0 || sequence.dtype() != kInt64 + || sequence.device() != kCPU) { + throw std::invalid_argument("Ling-3 CPU expects a non-empty rank-2 int64 CPU sequence with batch size 1"); + } + const int sequence_length = sequence.shape()[1]; + const int cached_tokens = kv_cache_.getCurrentSeqCnt(0); + if (sequence_length > max_length_ || cached_tokens > max_length_ - sequence_length) { + throw std::invalid_argument("Ling-3 sequence exceeds the configured KV-cache capacity"); + } + + auto position_ids = Tensor::empty({1, sequence_length}, kInt64, kCPU).alloc(); + auto* positions = position_ids.ptr(); + if (input.count("position_ids") != 0 && sequence_length == 1) { + auto previous_positions = input.at("position_ids"); + previous_positions = previous_positions.contiguous(); + positions[0] = previous_positions.ptr()[previous_positions.numel() - 1] + 1; + } else { + for (int index = 0; index < sequence_length; ++index) { positions[index] = cached_tokens + index; } + } + auto [cos, sin] = makeLing3RotaryEmbedding(position_ids, getBuffer("inv_freq")); + auto hidden = model_(sequence, cos, sin, AnyValue(&kv_cache_))[0]; + hidden = hidden[{kAll, {sequence_length - 1}, kAll}]; + return {{"sequence", lm_head_(hidden)}, {"position_ids", position_ids}}; + } + + void resetState(int32_t batch_size = 1) { + if (batch_size != 1) { throw std::invalid_argument("Ling-3 CPU currently supports batch size 1 only"); } + kv_cache_.clearCache(); + model_.resetKDAStates(batch_size); + } + + nn::StaticCache& kvCache() { return kv_cache_; } + + private: + Ling3Model model_; + nn::Linear lm_head_; + nn::StaticCache kv_cache_; +}; + +} // namespace mllm::models::ling3 diff --git a/mllm/models/ling3/tokenization_ling3.hpp b/mllm/models/ling3/tokenization_ling3.hpp new file mode 100644 index 000000000..03d77bfb4 --- /dev/null +++ b/mllm/models/ling3/tokenization_ling3.hpp @@ -0,0 +1,314 @@ +// Copyright (c) MLLM Team. +// Licensed under the MIT License. + +#pragma once + +#include "mllm/models/ARGeneration.hpp" +#include "mllm/preprocessor/tokenizers/AutoTokenizer.hpp" +#include "mllm/preprocessor/tokenizers/BPE.hpp" +#include "mllm/preprocessor/tokenizers/Unicode.hpp" +#include "mllm/preprocessor/tokenizers/llama_cpp_unicode/unicode.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace mllm::models::ling3 { + +inline std::string normalizeLing3NFC(const std::string& text) { + const auto codepoints = unicode_cpts_from_utf8(text); + std::vector normalized; + normalized.reserve(codepoints.size()); + for (size_t index = 0; index < codepoints.size();) { + // Unicode Hangul composition is algorithmic and the bundled llama.cpp + // table does not represent it as an ordinary combining-mark pair. + constexpr uint32_t kLBase = 0x1100; + constexpr uint32_t kVBase = 0x1161; + constexpr uint32_t kTBase = 0x11A7; + constexpr uint32_t kSBase = 0xAC00; + constexpr uint32_t kLCount = 19; + constexpr uint32_t kVCount = 21; + constexpr uint32_t kTCount = 28; + const uint32_t current = codepoints[index]; + if (current >= kLBase && current < kLBase + kLCount && index + 1 < codepoints.size() && codepoints[index + 1] >= kVBase + && codepoints[index + 1] < kVBase + kVCount) { + const uint32_t l_index = current - kLBase; + const uint32_t v_index = codepoints[index + 1] - kVBase; + uint32_t syllable = kSBase + (l_index * kVCount + v_index) * kTCount; + index += 2; + if (index < codepoints.size() && codepoints[index] > kTBase && codepoints[index] < kTBase + kTCount) { + syllable += codepoints[index] - kTBase; + ++index; + } + normalized.push_back(syllable); + continue; + } + + // Preserve code points that are already NFC. The bundled normalizer's + // legacy NFD table stores only the base of a precomposed character, so + // normalizing an entire string would incorrectly turn "é" into "e". + // Restrict it to explicit base + combining-mark clusters. + size_t cluster_end = index + 1; + while (cluster_end < codepoints.size() && unicode_cpt_flags(codepoints[cluster_end]).is_accent_mark) { ++cluster_end; } + if (cluster_end > index + 1) { + const std::vector cluster(codepoints.begin() + static_cast(index), + codepoints.begin() + static_cast(cluster_end)); + const auto composed = unicode_cpts_normalize_nfc(cluster); + normalized.insert(normalized.end(), composed.begin(), composed.end()); + } else { + normalized.push_back(current); + } + index = cluster_end; + } + std::string result; + for (const auto codepoint : normalized) { result += unicode_cpt_to_utf8(codepoint); } + return result; +} + +inline bool ling3TokenizerMatchPattern(const std::wstring& text, size_t& position, std::wstring& matched) { + if (position >= text.size()) { return false; } + + static const std::wstring contractions[] = {L"'s", L"'d", L"'m", L"'t", L"'ll", L"'ve", L"'re"}; + for (const auto& contraction : contractions) { + bool matches = position + contraction.size() <= text.size(); + for (size_t index = 0; matches && index < contraction.size(); ++index) { + matches = std::towlower(text[position + index]) == contraction[index]; + } + if (matches) { + matched = text.substr(position, contraction.size()); + position += contraction.size(); + return true; + } + } + + // [^\r\n\p{L}\p{N}]?+\p{L}+ + { + const size_t original_position = position; + if (!preprocessor::isLetter(text[position]) && !preprocessor::isDigit(text[position]) && text[position] != L'\r' + && text[position] != L'\n') { + ++position; + } + if (position < text.size() && preprocessor::isLetter(text[position])) { + while (position < text.size() && preprocessor::isLetter(text[position])) { ++position; } + matched = text.substr(original_position, position - original_position); + return true; + } + position = original_position; + } + + // \p{N} + if (preprocessor::isDigit(text[position])) { + matched = text.substr(position, 1); + ++position; + return true; + } + + // " ?[^\s\p{L}\p{N}]++[\r\n]*" + { + const size_t original_position = position; + if (text[position] == L' ') { ++position; } + if (position < text.size() && !std::iswspace(text[position]) && !preprocessor::isLetter(text[position]) + && !preprocessor::isDigit(text[position])) { + while (position < text.size() && !std::iswspace(text[position]) && !preprocessor::isLetter(text[position]) + && !preprocessor::isDigit(text[position])) { + ++position; + } + while (position < text.size() && (text[position] == L'\r' || text[position] == L'\n')) { ++position; } + matched = text.substr(original_position, position - original_position); + return true; + } + position = original_position; + } + + // \s*[\r\n]. Greedy \s* backtracks to the last available line break. + { + const size_t start = position; + size_t scan = position; + size_t last_line_break = std::wstring::npos; + while (scan < text.size() && std::iswspace(text[scan])) { + if (text[scan] == L'\r' || text[scan] == L'\n') { last_line_break = scan + 1; } + ++scan; + } + if (last_line_break != std::wstring::npos) { + position = last_line_break; + matched = text.substr(start, position - start); + return true; + } + } + + // \s+(?!\S), followed by the final \s+ fallback. + if (std::iswspace(text[position])) { + const size_t start = position; + while (position < text.size() && std::iswspace(text[position])) { ++position; } + if (position >= text.size()) { + matched = text.substr(start, position - start); + return true; + } + if (position - start > 1) { + --position; + matched = text.substr(start, position - start); + return true; + } + position = start; + while (position < text.size() && std::iswspace(text[position])) { ++position; } + matched = text.substr(start, position - start); + return true; + } + return false; +} + +inline std::vector ling3RegexSplit(const std::string& text) { + const auto wide_text = preprocessor::utf8string2WideString(text); + std::vector pieces; + size_t position = 0; + while (position < wide_text.size()) { + std::wstring matched; + if (ling3TokenizerMatchPattern(wide_text, position, matched)) { + pieces.push_back(std::move(matched)); + } else { + pieces.push_back(wide_text.substr(position, 1)); + ++position; + } + } + return pieces; +} + +struct Ling3Message { + std::string prompt; + std::string system_prompt; + bool enable_thinking = true; +}; + +class Ling3Tokenizer final : public preprocessor::AutoTokenizer { + public: + explicit Ling3Tokenizer(const std::string& file_path) { + preprocessor::initLocal(); + preprocessor::makeBytes2UnicodeMap(bytes_to_unicode_); + for (const auto& [byte, codepoint] : bytes_to_unicode_) { unicode_to_bytes_.insert({codepoint, byte}); } + if (!bpe_.initFromSentencePieceJson(file_path)) { + throw std::invalid_argument("Ling-3 tokenizer could not load tokenizer.json"); + } + + std::ifstream tokenizer_file(file_path); + if (!tokenizer_file.is_open()) { throw std::invalid_argument("Ling-3 tokenizer.json is not readable"); } + const auto tokenizer_json = nlohmann::json::parse(tokenizer_file); + if (!tokenizer_json.contains("normalizer") || tokenizer_json["normalizer"].value("type", "") != "NFC") { + throw std::invalid_argument("Ling-3 tokenizer requires the official NFC normalizer"); + } + if (!tokenizer_json.contains("added_tokens")) { + throw std::invalid_argument("Ling-3 tokenizer.json is missing added_tokens"); + } + for (const auto& added_token : tokenizer_json["added_tokens"]) { + const auto content = added_token.at("content").get(); + const auto wide_content = preprocessor::utf8string2WideString(content); + special_tokens_trie_.add(wide_content); + added_tokens_.insert(wide_content); + } + } + + std::vector _tokenize(const std::string& text) override { + std::vector tokens; + for (const auto& piece : ling3RegexSplit(normalizeLing3NFC(text))) { + const auto utf8_piece = preprocessor::wideString2Utf8String(piece); + std::wstring mapped; + for (const unsigned char byte : utf8_piece) { mapped.push_back(bytes_to_unicode_.at(byte)); } + auto bpe_tokens = bpe_._bpe(mapped); + tokens.insert(tokens.end(), bpe_tokens.begin(), bpe_tokens.end()); + } + return tokens; + } + + std::vector tokenize(const std::string& text) override { + const auto split_tokens = special_tokens_trie_.split(preprocessor::utf8string2WideString(text)); + std::vector tokens; + for (const auto& token : split_tokens) { + if (special_tokens_trie_.isSpecialToken(token)) { + tokens.push_back(token); + } else { + auto ordinary_tokens = _tokenize(preprocessor::wideString2Utf8String(token)); + tokens.insert(tokens.end(), ordinary_tokens.begin(), ordinary_tokens.end()); + } + } + return tokens; + } + + std::wstring _detokenize(int64_t token_id) override { return bpe_._lookup_inverse_vocab(token_id); } + + std::string detokenizeBytes(int64_t token_id) { + const auto token = _detokenize(token_id); + if (added_tokens_.count(token) != 0) { return preprocessor::wideString2Utf8String(token); } + std::string bytes; + for (const auto codepoint : token) { + const auto iterator = unicode_to_bytes_.find(codepoint); + if (iterator == unicode_to_bytes_.end()) { + throw std::runtime_error("Ling-3 tokenizer encountered an unknown byte-unicode symbol"); + } + bytes.push_back(static_cast(iterator->second)); + } + return bytes; + } + + std::wstring detokenize(int64_t token_id) override { return preprocessor::utf8string2WideString(detokenizeBytes(token_id)); } + + Tensor convert2Ids(const std::vector& tokens) override { + auto result = Tensor::empty({1, static_cast(tokens.size())}, kInt64, kCPU) + .setMemType(kExtraInput) + .setName("ling3-tokenizer-i0") + .alloc(); + auto* ids = result.ptr(); + for (size_t index = 0; index < tokens.size(); ++index) { ids[index] = bpe_._lookup_vocab(tokens[index]); } + return result; + } + + ARGenerationOutputPast convertMessage(const Ling3Message& message) { + if (message.prompt.empty()) { throw std::invalid_argument("Ling-3 prompt must not be empty"); } + static constexpr std::string_view kReservedMarkers[] = { + "SYSTEM", + "HUMAN", + "ASSISTANT", + "<|role_end|>", + }; + for (const auto marker : kReservedMarkers) { + if (message.prompt.find(marker) != std::string::npos) { + throw std::invalid_argument("Ling-3 prompt must not inject reserved chat-template markers"); + } + } + + const std::string thinking_option = message.enable_thinking ? "on" : "off"; + std::string rendered = "SYSTEM"; + if (message.system_prompt.empty()) { + rendered += "detailed thinking " + thinking_option + "<|role_end|>"; + } else if (message.system_prompt.find("detailed thinking on") != std::string::npos + || message.system_prompt.find("detailed thinking off") != std::string::npos) { + rendered += message.system_prompt + "<|role_end|>"; + } else { + rendered += message.system_prompt + "\ndetailed thinking " + thinking_option + "<|role_end|>"; + } + rendered += "HUMAN" + message.prompt + "<|role_end|>ASSISTANT"; + rendered += message.enable_thinking ? "\n" : "\n"; + + const auto tokens = tokenize(rendered); + auto sequence = Tensor::empty({1, static_cast(tokens.size())}, kInt64, kCPU) + .setMemType(kNormal) + .setName("ling3-tokenizer-i0") + .alloc(); + auto* ids = sequence.ptr(); + for (size_t index = 0; index < tokens.size(); ++index) { ids[index] = bpe_._lookup_vocab(tokens[index]); } + return {{"sequence", sequence}}; + } + + private: + preprocessor::BPE bpe_; + std::unordered_map bytes_to_unicode_; + std::unordered_map unicode_to_bytes_; + std::unordered_set added_tokens_; +}; + +} // namespace mllm::models::ling3 diff --git a/tests/cpu/CMakeLists.txt b/tests/cpu/CMakeLists.txt index 70ba14ac4..bcf988994 100644 --- a/tests/cpu/CMakeLists.txt +++ b/tests/cpu/CMakeLists.txt @@ -2,6 +2,23 @@ add_executable(Mllm-Test-CPUKernel KernelTest.cpp) target_link_libraries(Mllm-Test-CPUKernel PRIVATE gtest_main MllmRT MllmCPUBackend) target_include_directories(Mllm-Test-CPUKernel PRIVATE ${MLLM_INCLUDE_DIR}) +add_executable(Mllm-Test-Ling3-KDA Ling3KDATest.cpp) +target_link_libraries(Mllm-Test-Ling3-KDA PRIVATE gtest_main MllmCPUBackend) +target_include_directories(Mllm-Test-Ling3-KDA PRIVATE ${MLLM_INCLUDE_DIR}) + +add_executable(Mllm-Test-Ling3-Config Ling3ConfigTest.cpp) +target_link_libraries(Mllm-Test-Ling3-Config PRIVATE gtest_main MllmCPUBackend) +target_include_directories(Mllm-Test-Ling3-Config PRIVATE ${MLLM_INCLUDE_DIR}) +target_compile_definitions(Mllm-Test-Ling3-Config PRIVATE LING3_EXAMPLE_DIR="${CMAKE_CURRENT_SOURCE_DIR}/../../examples/ling3") + +add_executable(Mllm-Test-Ling3-RoPE Ling3RoPETest.cpp) +target_link_libraries(Mllm-Test-Ling3-RoPE PRIVATE gtest_main MllmCPUBackend) +target_include_directories(Mllm-Test-Ling3-RoPE PRIVATE ${MLLM_INCLUDE_DIR}) + +add_executable(Mllm-Test-Ling3-Tokenizer Ling3TokenizerTest.cpp) +target_link_libraries(Mllm-Test-Ling3-Tokenizer PRIVATE gtest_main MllmCPUBackend) +target_include_directories(Mllm-Test-Ling3-Tokenizer PRIVATE ${MLLM_INCLUDE_DIR}) + add_executable(Mllm-Test-KaiW4A32Pack KaiW4A32PackTest.cpp) target_link_libraries(Mllm-Test-KaiW4A32Pack PRIVATE gtest_main MllmCPUBackend) target_include_directories(Mllm-Test-KaiW4A32Pack PRIVATE ${MLLM_INCLUDE_DIR}) diff --git a/tests/cpu/KaiW4A32PackTest.cpp b/tests/cpu/KaiW4A32PackTest.cpp index 78653639e..f1653d022 100644 --- a/tests/cpu/KaiW4A32PackTest.cpp +++ b/tests/cpu/KaiW4A32PackTest.cpp @@ -14,6 +14,7 @@ #include "mllm/backends/cpu/kernels/common/kai_w4a32_pack.hpp" #if defined(__aarch64__) +#include "mllm/backends/cpu/kernels/arm/linear/kai.hpp" #include "kai_matmul_clamp_f32_qai8dxp1x4_qsi4c32p4x4_1x4_neon_dotprod.h" #include "kai_matmul_clamp_f32_qai8dxp1x8_qsi4c32p4x8_1x4x32_neon_dotprod.h" #include "kai_matmul_clamp_f32_qai8dxp1x8_qsi4c32p8x8_1x8x32_neon_dotprod.h" @@ -91,6 +92,44 @@ TEST(KaiW4A32PackTest, SharedTileParametersMatchArmUkernels) { EXPECT_EQ(tile.sr, test_case.get_sr()) << test_case.name; } } + +TEST(KaiW4A32PackTest, BatchedRowsMatchIndependentRows) { + constexpr int kRows = 7; + constexpr int kOutChannels = 17; + constexpr int kInChannels = 64; + using Helper = mllm::cpu::arm::KaiLinear_f32_qai8dxp_qsi4c32p_mxk_nxk; + constexpr auto kTile = Helper::Tiles::qai8dxp1x8_qsi4c32p8x8_1x8x32; + + std::vector weights(kOutChannels * kInChannels); + std::vector input(kRows * kInChannels); + for (int index = 0; index < static_cast(weights.size()); ++index) { + weights[index] = std::sin(static_cast(index) * 0.071F) * 0.30F + - std::cos(static_cast(index) * 0.019F) * 0.05F; + } + for (int index = 0; index < static_cast(input.size()); ++index) { + input[index] = std::sin(static_cast(index) * 0.037F) * 0.50F + + std::cos(static_cast(index) * 0.013F) * 0.07F; + } + + Helper helper; + std::vector packed(helper.quant_pack_rhs_size(kOutChannels, kInChannels, kTile)); + helper.quant_pack_rhs_offline(packed.data(), weights.data(), nullptr, kOutChannels, kInChannels, kTile); + std::vector batched_workspace(helper.workspace_size(kRows, kInChannels, kTile)); + std::vector batched(kRows * kOutChannels); + helper.matmul(batched.data(), input.data(), packed.data(), batched_workspace.data(), kRows, kInChannels, kOutChannels, + kTile, 1); + + std::vector row_workspace(helper.workspace_size(1, kInChannels, kTile)); + std::vector row(kOutChannels); + for (int row_index = 0; row_index < kRows; ++row_index) { + helper.matmul(row.data(), input.data() + row_index * kInChannels, packed.data(), row_workspace.data(), 1, kInChannels, + kOutChannels, kTile, 1); + for (int channel = 0; channel < kOutChannels; ++channel) { + EXPECT_FLOAT_EQ(batched[row_index * kOutChannels + channel], row[channel]) + << "row=" << row_index << " channel=" << channel; + } + } +} #endif } // namespace diff --git a/tests/cpu/Ling3ConfigTest.cpp b/tests/cpu/Ling3ConfigTest.cpp new file mode 100644 index 000000000..4311c1760 --- /dev/null +++ b/tests/cpu/Ling3ConfigTest.cpp @@ -0,0 +1,28 @@ +// Copyright (c) MLLM Team. +// Licensed under the MIT License. + +#include "mllm/models/ling3/configuration_ling3.hpp" + +#include + +#include +#include + +namespace { + +std::string ling3RuntimeConfigPath() { + const char* value = std::getenv("LING3_RUNTIME_CONFIG"); + return value == nullptr ? std::string(LING3_EXAMPLE_DIR) + "/config_tiny_w4a32_kai.json" : std::string(value); +} + +} // namespace + +TEST(Ling3Config, AcceptsPinnedTinyRuntimeContract) { + const mllm::models::ling3::Ling3Config config(ling3RuntimeConfigPath()); + EXPECT_TRUE(mllm::models::ling3::hasOfficialLing3TinyArchitecture(config)); + EXPECT_EQ(config.numFullAttentionLayers(), 6); + EXPECT_EQ(config.numKDALayers(), 18); + for (int layer = 0; layer < config.num_hidden_layers; ++layer) { + EXPECT_EQ(config.isFullAttentionLayer(layer), (layer + 1) % 4 == 0); + } +} diff --git a/tests/cpu/Ling3KDATest.cpp b/tests/cpu/Ling3KDATest.cpp new file mode 100644 index 000000000..b8488845a --- /dev/null +++ b/tests/cpu/Ling3KDATest.cpp @@ -0,0 +1,149 @@ +// Copyright (c) MLLM Team. +// Licensed under the MIT License. + +#include "mllm/backends/cpu/kernels/common/kda/kimi_delta_attention.hpp" + +#include + +#include +#include +#include +#include + +namespace { + +float sigmoid(float value) { return 1.0F / (1.0F + std::exp(-value)); } + +void referenceKDA(const std::vector& q, const std::vector& k, const std::vector& v, + const std::vector& gate, const std::vector& beta, const std::vector& a_log, + const std::vector& dt_bias, std::vector& state, std::vector& output, int batch, + int sequence, int heads, int dim, bool safe_gate, float lower_bound) { + const float query_dim_scale = 1.0F / std::sqrt(static_cast(dim)); + for (int b = 0; b < batch; ++b) { + for (int h = 0; h < heads; ++h) { + const std::size_t state_base = (static_cast(b) * heads + h) * dim * dim; + for (int s = 0; s < sequence; ++s) { + const std::size_t vector_base = ((static_cast(b) * sequence + s) * heads + h) * dim; + float q_norm_sq = 0.0F; + float k_norm_sq = 0.0F; + for (int d = 0; d < dim; ++d) { + q_norm_sq += q[vector_base + d] * q[vector_base + d]; + k_norm_sq += k[vector_base + d] * k[vector_base + d]; + } + const float q_scale = query_dim_scale / std::sqrt(q_norm_sq + 1.0e-6F); + const float k_scale = 1.0F / std::sqrt(k_norm_sq + 1.0e-6F); + std::vector normalized_q(dim); + std::vector normalized_k(dim); + std::vector prediction(dim, 0.0F); + for (int d = 0; d < dim; ++d) { + normalized_q[d] = q[vector_base + d] * q_scale; + normalized_k[d] = k[vector_base + d] * k_scale; + } + for (int kd = 0; kd < dim; ++kd) { + const float gate_input = gate[vector_base + kd] + dt_bias[h * dim + kd]; + const float log_decay = safe_gate ? lower_bound * sigmoid(std::exp(a_log[h]) * gate_input) + : -std::exp(a_log[h]) * std::log1p(std::exp(gate_input)); + const float decay = std::exp(log_decay); + for (int vd = 0; vd < dim; ++vd) { + const std::size_t state_index = state_base + kd * dim + vd; + state[state_index] *= decay; + prediction[vd] += normalized_k[kd] * state[state_index]; + } + } + for (int vd = 0; vd < dim; ++vd) { + const float delta = + beta[(static_cast(b) * sequence + s) * heads + h] * (v[vector_base + vd] - prediction[vd]); + for (int kd = 0; kd < dim; ++kd) { + const std::size_t state_index = state_base + kd * dim + vd; + state[state_index] += normalized_k[kd] * delta; + output[vector_base + vd] += normalized_q[kd] * state[state_index]; + } + } + } + } + } +} + +std::vector makeValues(std::size_t count, float scale, float offset = 0.0F) { + std::vector values(count); + for (std::size_t i = 0; i < count; ++i) { + values[i] = offset + scale * static_cast((static_cast(i * 37U % 29U) - 14)); + } + return values; +} + +TEST(Ling3KDA, MatchesSafeGateReference) { + constexpr int kBatch = 2; + constexpr int kSequence = 5; + constexpr int kHeads = 3; + constexpr int kDim = 8; + const std::size_t vector_count = static_cast(kBatch) * kSequence * kHeads * kDim; + const std::size_t state_count = static_cast(kBatch) * kHeads * kDim * kDim; + + auto q = makeValues(vector_count, 0.017F); + auto k = makeValues(vector_count, -0.013F, 0.02F); + auto v = makeValues(vector_count, 0.011F, -0.03F); + auto gate = makeValues(vector_count, 0.019F, 0.1F); + auto beta = makeValues(static_cast(kBatch) * kSequence * kHeads, 0.01F, 0.45F); + auto a_log = makeValues(kHeads, 0.02F, 0.3F); + auto dt_bias = makeValues(static_cast(kHeads) * kDim, 0.015F, -0.1F); + auto expected_state = makeValues(state_count, 0.003F); + auto actual_state = expected_state; + std::vector expected_output(vector_count, 0.0F); + std::vector actual_output(vector_count, 0.0F); + + referenceKDA(q, k, v, gate, beta, a_log, dt_bias, expected_state, expected_output, kBatch, kSequence, kHeads, kDim, true, + -5.0F); + mllm::cpu::kda::kimiDeltaAttentionF32(q.data(), k.data(), v.data(), gate.data(), beta.data(), a_log.data(), dt_bias.data(), + actual_state.data(), actual_output.data(), kBatch, kSequence, kHeads, kDim, true, -5.0F, + 1); + + for (std::size_t i = 0; i < vector_count; ++i) { EXPECT_NEAR(actual_output[i], expected_output[i], 2.0e-6F); } + for (std::size_t i = 0; i < state_count; ++i) { EXPECT_NEAR(actual_state[i], expected_state[i], 2.0e-6F); } +} + +TEST(Ling3KDA, PrefillAndTokenwiseDecodeAreEquivalent) { + constexpr int kBatch = 1; + constexpr int kSequence = 7; + constexpr int kHeads = 2; + constexpr int kDim = 8; + const std::size_t vector_count = static_cast(kBatch) * kSequence * kHeads * kDim; + const std::size_t state_count = static_cast(kBatch) * kHeads * kDim * kDim; + auto q = makeValues(vector_count, 0.012F); + auto k = makeValues(vector_count, 0.009F, -0.02F); + auto v = makeValues(vector_count, -0.008F, 0.01F); + auto gate = makeValues(vector_count, 0.014F); + auto beta = makeValues(static_cast(kBatch) * kSequence * kHeads, 0.008F, 0.5F); + auto a_log = makeValues(kHeads, 0.03F, 0.2F); + auto dt_bias = makeValues(static_cast(kHeads) * kDim, 0.01F, -0.05F); + std::vector prefill_state(state_count, 0.0F); + std::vector decode_state(state_count, 0.0F); + std::vector prefill_output(vector_count, 0.0F); + std::vector decode_output(vector_count, 0.0F); + + mllm::cpu::kda::kimiDeltaAttentionF32(q.data(), k.data(), v.data(), gate.data(), beta.data(), a_log.data(), dt_bias.data(), + prefill_state.data(), prefill_output.data(), kBatch, kSequence, kHeads, kDim, true, + -5.0F, 1); + const std::size_t token_width = static_cast(kHeads) * kDim; + for (int token = 0; token < kSequence; ++token) { + const std::size_t offset = static_cast(token) * token_width; + mllm::cpu::kda::kimiDeltaAttentionF32(q.data() + offset, k.data() + offset, v.data() + offset, gate.data() + offset, + beta.data() + static_cast(token) * kHeads, a_log.data(), dt_bias.data(), + decode_state.data(), decode_output.data() + offset, kBatch, 1, kHeads, kDim, true, + -5.0F, 1); + } + + EXPECT_EQ(prefill_output, decode_output); + EXPECT_EQ(prefill_state, decode_state); +} + +TEST(Ling3KDA, RejectsInvalidSafeGateBound) { + std::vector values(4, 0.0F); + std::vector state(4, 0.0F); + EXPECT_THROW(mllm::cpu::kda::kimiDeltaAttentionF32(values.data(), values.data(), values.data(), values.data(), values.data(), + values.data(), values.data(), state.data(), values.data(), 1, 1, 1, 2, + true, 0.0F, 1), + std::invalid_argument); +} + +} // namespace diff --git a/tests/cpu/Ling3RoPETest.cpp b/tests/cpu/Ling3RoPETest.cpp new file mode 100644 index 000000000..91182e453 --- /dev/null +++ b/tests/cpu/Ling3RoPETest.cpp @@ -0,0 +1,56 @@ +// Copyright (c) MLLM Team. +// Licensed under the MIT License. + +#include "mllm/models/ling3/modeling_ling3.hpp" +#include "mllm/mllm.hpp" + +#include + +#include + +class Ling3RoPETest : public ::testing::Test { + protected: + static void SetUpTestSuite() { mllm::initializeContext(); } +}; + +TEST_F(Ling3RoPETest, AppliesOfficialAdjacentPairInterleave) { + auto input = mllm::Tensor::empty({1, 1, 2, 4}, mllm::kFloat32, mllm::kCPU).alloc(); + auto cos = mllm::Tensor::empty({1, 2, 4}, mllm::kFloat32, mllm::kCPU).alloc(); + auto sin = mllm::Tensor::empty({1, 2, 4}, mllm::kFloat32, mllm::kCPU).alloc(); + for (int index = 0; index < 8; ++index) { input.ptr()[index] = static_cast(index + 1); } + const float angles[] = {0.2F, -0.4F, 0.7F, 0.3F}; + for (int token = 0; token < 2; ++token) { + for (int pair = 0; pair < 2; ++pair) { + const float value = angles[token * 2 + pair]; + cos.ptr()[token * 4 + pair] = std::cos(value); + cos.ptr()[token * 4 + 2 + pair] = std::cos(value); + sin.ptr()[token * 4 + pair] = std::sin(value); + sin.ptr()[token * 4 + 2 + pair] = std::sin(value); + } + } + + const auto output = mllm::models::ling3::applyLing3InterleavedRoPE(input, cos, sin); + for (int token = 0; token < 2; ++token) { + for (int pair = 0; pair < 2; ++pair) { + const float even = input.ptr()[token * 4 + pair * 2]; + const float odd = input.ptr()[token * 4 + pair * 2 + 1]; + const float angle = angles[token * 2 + pair]; + EXPECT_NEAR(output.ptr()[token * 4 + pair], even * std::cos(angle) - odd * std::sin(angle), 1.0e-6F); + EXPECT_NEAR(output.ptr()[token * 4 + 2 + pair], odd * std::cos(angle) + even * std::sin(angle), 1.0e-6F); + } + } +} + +TEST_F(Ling3RoPETest, PadsMLAValuesWithoutChangingPayload) { + auto input = mllm::Tensor::empty({1, 2, 3, 4}, mllm::kFloat32, mllm::kCPU).alloc(); + for (int index = 0; index < 24; ++index) { input.ptr()[index] = static_cast(index + 1); } + const auto output = mllm::models::ling3::padLing3ValuesForCache(input, 6); + ASSERT_EQ(output.shape(), (mllm::Tensor::shape_t{1, 2, 3, 6})); + for (int vector = 0; vector < 6; ++vector) { + for (int dim = 0; dim < 4; ++dim) { + EXPECT_EQ(output.ptr()[vector * 6 + dim], input.ptr()[vector * 4 + dim]); + } + EXPECT_EQ(output.ptr()[vector * 6 + 4], 0.0F); + EXPECT_EQ(output.ptr()[vector * 6 + 5], 0.0F); + } +} diff --git a/tests/cpu/Ling3TokenizerTest.cpp b/tests/cpu/Ling3TokenizerTest.cpp new file mode 100644 index 000000000..5dc1ac5f1 --- /dev/null +++ b/tests/cpu/Ling3TokenizerTest.cpp @@ -0,0 +1,55 @@ +// Copyright (c) MLLM Team. +// Licensed under the MIT License. + +#include "mllm/models/ling3/tokenization_ling3.hpp" +#include "mllm/mllm.hpp" + +#include + +#include +#include +#include + +namespace { + +std::string officialTokenizerPath() { + const char* value = std::getenv("LING3_OFFICIAL_TOKENIZER"); + return value == nullptr ? std::string() : std::string(value); +} + +std::vector ids(mllm::models::ling3::Ling3Tokenizer& tokenizer, const std::string& text) { + const auto tensor = tokenizer.convert2Ids(tokenizer.tokenize(text)); + return {tensor.ptr(), tensor.ptr() + tensor.numel()}; +} + +class Ling3TokenizerTest : public ::testing::Test { + protected: + static void SetUpTestSuite() { mllm::initializeContext(); } +}; + +TEST_F(Ling3TokenizerTest, MatchesOfficialByteBPEAndNFCVectors) { + const auto path = officialTokenizerPath(); + if (path.empty()) { GTEST_SKIP() << "LING3_OFFICIAL_TOKENIZER is not set"; } + mllm::models::ling3::Ling3Tokenizer tokenizer(path); + EXPECT_EQ(ids(tokenizer, "Hello, world!"), (std::vector{14455, 11, 1931, 0})); + EXPECT_EQ(ids(tokenizer, "你好,世界!"), (std::vector{34355, 44291, 859})); + EXPECT_EQ(ids(tokenizer, "e\xCC\x81 café"), (std::vector{2900, 67656})); +} + +TEST_F(Ling3TokenizerTest, RendersOfficialSingleTurnThinkingTemplates) { + const auto path = officialTokenizerPath(); + if (path.empty()) { GTEST_SKIP() << "LING3_OFFICIAL_TOKENIZER is not set"; } + mllm::models::ling3::Ling3Tokenizer tokenizer(path); + auto enabled = tokenizer.convertMessage({"你好", "", true}).at("sequence"); + const std::vector enabled_expected = {157151, 90827, 157152, 14136, 5381, 6350, 366, 156895, 157151, 39, 116171, + 157152, 34355, 156895, 157151, 8469, 7342, 5468, 157152, 198, 156903}; + EXPECT_EQ(std::vector(enabled.ptr(), enabled.ptr() + enabled.numel()), enabled_expected); + + auto disabled = tokenizer.convertMessage({"Hello", "", false}).at("sequence"); + const std::vector disabled_expected = {157151, 90827, 157152, 14136, 5381, 6350, 928, 156895, + 157151, 39, 116171, 157152, 14455, 156895, 157151, 8469, + 7342, 5468, 157152, 198, 156903, 156904}; + EXPECT_EQ(std::vector(disabled.ptr(), disabled.ptr() + disabled.numel()), disabled_expected); +} + +} // namespace From 57413708f3da5aedb4fd05ff53049869a80fd85f Mon Sep 17 00:00:00 2001 From: Aharrypotter Date: Thu, 13 Aug 2026 08:35:53 +0800 Subject: [PATCH 2/4] refactor(cpu): route Ling stateful ops through mllm abstractions Ling's two reusable stateful primitives no longer call CPU kernels from model code. KimiDeltaAttention becomes a formal mllm operation (OpType 81; upstream retired value 76) with nn::Layer / Functional frontends, aops contract, linalg IR, option serialization/interpreter reconstruction, and a typed CPU factory whose backend op calls the existing KDA kernel. The q/k/v short convolutions register the upstream weighted nn::CausalDepthwiseConv1D (#701/#704) with the current-first accumulation order and in-place [B, C, K-1] history, so they keep running on the existing optimized GDN convolution kernel; no convolution operation or kernel is added by this branch. --- examples/ling3/README.md | 6 +- examples/ling3/demo_prompt_v1.txt | 1 - mllm/backends/cpu/CPUBackend.cpp | 3 +- .../backends/cpu/ops/KimiDeltaAttentionOp.cpp | 31 ++ .../backends/cpu/ops/KimiDeltaAttentionOp.hpp | 24 ++ mllm/compile/ir/GeneratedRTTIKind.hpp | 301 ++++++++--------- mllm/compile/ir/NodeRTTIClassOfImpl.hpp | 304 +++++++++--------- mllm/compile/ir/linalg/Op.cpp | 1 + mllm/compile/ir/linalg/Op.hpp | 2 + mllm/compile/ir/rtti_kind_gen.py | 1 + .../jit/binary/LinalgIRSerialization.cpp | 7 + .../jit/binary/LinalgIRSerialization.hpp | 1 + mllm/compile/jit/interpreter/AopsFromJson.cpp | 17 + mllm/compile/jit/interpreter/AopsFromJson.hpp | 1 + mllm/core/OpTypes.hpp | 2 + mllm/core/aops/KimiDeltaAttentionOp.cpp | 83 +++++ mllm/core/aops/KimiDeltaAttentionOp.hpp | 33 ++ mllm/models/ling3/modeling_ling3.hpp | 68 ++-- mllm/nn/Functional.cpp | 11 + mllm/nn/Functional.hpp | 5 + mllm/nn/Nn.hpp | 1 + mllm/nn/layers/KimiDeltaAttention.cpp | 18 ++ mllm/nn/layers/KimiDeltaAttention.hpp | 20 ++ tests/cpu/CMakeLists.txt | 34 +- tests/cpu/Ling3KDATest.cpp | 163 ++++++++++ 25 files changed, 795 insertions(+), 343 deletions(-) delete mode 100644 examples/ling3/demo_prompt_v1.txt create mode 100644 mllm/backends/cpu/ops/KimiDeltaAttentionOp.cpp create mode 100644 mllm/backends/cpu/ops/KimiDeltaAttentionOp.hpp create mode 100644 mllm/core/aops/KimiDeltaAttentionOp.cpp create mode 100644 mllm/core/aops/KimiDeltaAttentionOp.hpp create mode 100644 mllm/nn/layers/KimiDeltaAttention.cpp create mode 100644 mllm/nn/layers/KimiDeltaAttention.hpp diff --git a/examples/ling3/README.md b/examples/ling3/README.md index 016699d47..fd9e8419d 100644 --- a/examples/ling3/README.md +++ b/examples/ling3/README.md @@ -41,15 +41,15 @@ The runner emits `LING3_RUN_START`, generated token IDs, and runtime result; device evidence must retain the converted model SHA256 and the runner/library identities together. -For a longer deterministic correctness demo, use the checked-in prompt and a -64-token generation limit: +For a longer correctness demo, pass the prompt directly and use a 64-token +generation limit: ```bash ./mllm-ling3-runner \ --model_path /path/to/Ling-3.0-tiny.mllm \ --tokenizer_path /path/to/Ling-3.0-tiny/tokenizer.json \ --config_path config_tiny_w4a32_kai.json \ - --prompt_file demo_prompt_v1.txt --disable_thinking \ + --prompt '请用中文详细介绍 Ling-3.0-tiny 的混合注意力架构,并解释 KDA、MLA 和 MoE 各自的作用。' --disable_thinking \ --max_new_tokens 64 --print_token_ids ``` diff --git a/examples/ling3/demo_prompt_v1.txt b/examples/ling3/demo_prompt_v1.txt deleted file mode 100644 index cf4cd72f4..000000000 --- a/examples/ling3/demo_prompt_v1.txt +++ /dev/null @@ -1 +0,0 @@ -请用中文详细介绍 Ling-3.0-tiny 的混合注意力架构,并解释 KDA、MLA 和 MoE 各自的作用。 diff --git a/mllm/backends/cpu/CPUBackend.cpp b/mllm/backends/cpu/CPUBackend.cpp index 190f61109..c984eea8c 100644 --- a/mllm/backends/cpu/CPUBackend.cpp +++ b/mllm/backends/cpu/CPUBackend.cpp @@ -40,6 +40,7 @@ #include "mllm/backends/cpu/ops/ISTFTOp.hpp" #include "mllm/backends/cpu/ops/IndexOp.hpp" #include "mllm/backends/cpu/ops/KVCacheOp.hpp" +#include "mllm/backends/cpu/ops/KimiDeltaAttentionOp.hpp" #include "mllm/backends/cpu/ops/LayerNormOp.hpp" #include "mllm/backends/cpu/ops/LinearOp.hpp" #include "mllm/backends/cpu/ops/MatMulOp.hpp" @@ -88,7 +89,7 @@ CPUBackend::CPUBackend() : Backend(kCPU, createCPUAllocator()) { CPUArgsortOpFactory, CPUCloneOpFactory, CPUAvgPool1dOpFactory, CPUFlashAttention2SwaSinkOpFactory, CPURadixAttnRelaxOpFactory, CPURadixAttnSwaSinkOpFactory, CPUEqualOpFactory, CPUWhereOpFactory, CPUGatherOpFactory, CPUCausalDepthwiseConv1DOpFactory, CPUGatedDeltaRuleOpFactory, - CPUGroupedQueryAttentionOpFactory, CPUParallelLinearOpFactory>(); + CPUGroupedQueryAttentionOpFactory, CPUParallelLinearOpFactory, CPUKimiDeltaAttentionOpFactory>(); } CPUBackend::~CPUBackend() { diff --git a/mllm/backends/cpu/ops/KimiDeltaAttentionOp.cpp b/mllm/backends/cpu/ops/KimiDeltaAttentionOp.cpp new file mode 100644 index 000000000..8d9b76177 --- /dev/null +++ b/mllm/backends/cpu/ops/KimiDeltaAttentionOp.cpp @@ -0,0 +1,31 @@ +// Copyright (c) MLLM Team. +// Licensed under the MIT License. + +#include "mllm/backends/cpu/ops/KimiDeltaAttentionOp.hpp" + +#include +#include + +#include "mllm/backends/cpu/kernels/common/kda/kimi_delta_attention.hpp" + +namespace mllm::cpu { + +CPUKimiDeltaAttentionOp::CPUKimiDeltaAttentionOp(const aops::KimiDeltaAttentionOpOptions& options) + : aops::KimiDeltaAttentionOp(options) {} + +void CPUKimiDeltaAttentionOp::forward(const std::vector& inputs, std::vector& outputs) { + for (const auto& input : inputs) { + if (!input.isContiguous()) { throw std::invalid_argument("KimiDeltaAttention CPU inputs must be contiguous"); } + } + + const auto& q = inputs[0]; + auto& output = outputs[0]; + auto& updated_state = outputs[1]; + if (!options_.state_inplace) { std::memcpy(updated_state.ptr(), inputs[7].ptr(), inputs[7].bytes()); } + kda::kimiDeltaAttentionF32(inputs[0].ptr(), inputs[1].ptr(), inputs[2].ptr(), inputs[3].ptr(), + inputs[4].ptr(), inputs[5].ptr(), inputs[6].ptr(), updated_state.ptr(), + output.ptr(), q.shape()[0], q.shape()[1], q.shape()[2], q.shape()[3], options_.safe_gate, + options_.lower_bound, options_.getThreads()); +} + +} // namespace mllm::cpu diff --git a/mllm/backends/cpu/ops/KimiDeltaAttentionOp.hpp b/mllm/backends/cpu/ops/KimiDeltaAttentionOp.hpp new file mode 100644 index 000000000..406c5af8f --- /dev/null +++ b/mllm/backends/cpu/ops/KimiDeltaAttentionOp.hpp @@ -0,0 +1,24 @@ +// Copyright (c) MLLM Team. +// Licensed under the MIT License. + +#pragma once + +#include "mllm/core/aops/KimiDeltaAttentionOp.hpp" + +namespace mllm::cpu { + +class CPUKimiDeltaAttentionOp final : public aops::KimiDeltaAttentionOp { + public: + explicit CPUKimiDeltaAttentionOp(const aops::KimiDeltaAttentionOpOptions& options); + + void forward(const std::vector& inputs, std::vector& outputs) override; +}; + +class CPUKimiDeltaAttentionOpFactory : public TypedOpFactory { + public: + std::shared_ptr createOpImpl(const aops::KimiDeltaAttentionOpOptions& options) override { + return std::make_shared(options); + } +}; + +} // namespace mllm::cpu diff --git a/mllm/compile/ir/GeneratedRTTIKind.hpp b/mllm/compile/ir/GeneratedRTTIKind.hpp index 7114ef549..891ec5441 100644 --- a/mllm/compile/ir/GeneratedRTTIKind.hpp +++ b/mllm/compile/ir/GeneratedRTTIKind.hpp @@ -1,4 +1,4 @@ -// Auto generated: 2026-08-21 15:20:12 +// Auto generated: 2026-09-06 09:09:14 // do not modify this file #pragma once @@ -12,159 +12,162 @@ namespace mllm::ir { // // RTTI Kind (RK) enum NodeKind : uint32_t { - RK_None, - RK_Op, - RK_Op_LinalgIROp, - RK_Op_LinalgIROp_RegisterOp, - RK_Op_LinalgIROp_CustomKernelOp, - RK_Op_LinalgIROp_FillOp, - RK_Op_LinalgIROp_AddOp, - RK_Op_LinalgIROp_SubOp, - RK_Op_LinalgIROp_STFTOp, - RK_Op_LinalgIROp_ISTFTOp, - RK_Op_LinalgIROp_MulOp, - RK_Op_LinalgIROp_DivOp, - RK_Op_LinalgIROp_AbsOp, - RK_Op_LinalgIROp_LogOp, - RK_Op_LinalgIROp_MatMulOp, - RK_Op_LinalgIROp_EmbeddingOp, - RK_Op_LinalgIROp_LinearOp, - RK_Op_LinalgIROp_RoPEOp, - RK_Op_LinalgIROp_SoftmaxOp, - RK_Op_LinalgIROp_TransposeOp, - RK_Op_LinalgIROp_RMSNormOp, - RK_Op_LinalgIROp_SiLUOp, - RK_Op_LinalgIROp_KVCacheOp, - RK_Op_LinalgIROp_CausalMaskOp, - RK_Op_LinalgIROp_CastTypeOp, - RK_Op_LinalgIROp_X2XOp, - RK_Op_LinalgIROp_ViewOp, - RK_Op_LinalgIROp_SplitOp, - RK_Op_LinalgIROp_FlashAttention2Op, - RK_Op_LinalgIROp_CausalDepthwiseConv1DOp, - RK_Op_LinalgIROp_GroupedQueryAttentionOp, - RK_Op_LinalgIROp_ParallelLinearOp, - RK_Op_LinalgIROp_GatedDeltaRuleOp, - RK_Op_LinalgIROp_RepeatOp, - RK_Op_LinalgIROp_PermuteOp, - RK_Op_LinalgIROp_Conv1DOp, - RK_Op_LinalgIROp_Conv2DOp, - RK_Op_LinalgIROp_Conv3DOp, - RK_Op_LinalgIROp_GELUOp, - RK_Op_LinalgIROp_LayerNormOp, - RK_Op_LinalgIROp_MultimodalRoPEOp, - RK_Op_LinalgIROp_VisionRoPEOp, - RK_Op_LinalgIROp_QuickGELUOp, - RK_Op_LinalgIROp_CopyOp, - RK_Op_LinalgIROp_CloneOp, - RK_Op_LinalgIROp_NegOp, - RK_Op_LinalgIROp_ConcatOp, - RK_Op_LinalgIROp_ReduceMinOp, - RK_Op_LinalgIROp_ReduceMaxOp, - RK_Op_LinalgIROp_ReduceSumOp, - RK_Op_LinalgIROp_ReLUOp, - RK_Op_LinalgIROp_ContiguousOp, - RK_Op_LinalgIROp_ReshapeOp, - RK_Op_LinalgIROp_SliceOp, - RK_Op_LinalgIROp_ParamOp, - RK_Op_LinalgIROp_IndexOp, - RK_Op_LinalgIROp_TopKOp, - RK_Op_LinalgIROp_MeanOp, - RK_Op_LinalgIROp_ClipOp, - RK_Op_LinalgIROp_ExpOp, - RK_Op_LinalgIROp_SinOp, - RK_Op_LinalgIROp_CosOp, - RK_Op_LinalgIROp_PagedAttnOp, - RK_Op_LinalgIROp_LayerNorm2DOp, - RK_Op_LinalgIROp_PadOp, - RK_Op_LinalgIROp_InterpolateOp, - RK_Op_LinalgIROp_EinsumOp, - RK_Op_LinalgIROp_StackOp, - RK_Op_LinalgIROp_MaskedScatterOp, - RK_Op_LinalgIROp_ScatterOp, - RK_Op_LinalgIROp_GatherOp, - RK_Op_LinalgIROp_ArgsortOp, - RK_Op_LinalgIROp_FlashAttention2SwaSinkOp, - RK_Op_LinalgIROp_RadixAttnRelaxOp, - RK_Op_LinalgIROp_RadixAttnSwaSinkOp, - RK_Op_LinalgIROp_EqualOp, - RK_Op_LinalgIROp_WhereOp, - RK_Op_LinalgIROp_SigmoidOp, - RK_Op_LinalgIROp_CustomizedOp, - RK_Op_LinalgIROp_Last, - RK_Op_GraphIROp, - RK_Op_GraphIROp_SubGraphOp, - RK_Op_GraphIROp_CallGraphOp, - RK_Op_GraphIROp_Last, - RK_Op_TensorIROp, - RK_Op_TensorIROp_AllocOp, - RK_Op_TensorIROp_RegisterOp, - RK_Op_TensorIROp_FreeOp, - RK_Op_TensorIROp_Last, - RK_Op_BuiltinIROp, - RK_Op_BuiltinIROp_ModuleOp, - RK_Op_BuiltinIROp_Last, - RK_Op_ControlFlowIROp, - RK_Op_ControlFlowIROp_ReturnOp, - RK_Op_ControlFlowIROp_Last, - RK_Op_ProgramIROp, - RK_Op_ProgramIROp_FragmentOp, - RK_Op_ProgramIROp_KernelLaunchOp, - RK_Op_ProgramIROp_KernelSymbolOp, - RK_Op_ProgramIROp_ValueSymbolOp, - RK_Op_ProgramIROp_JumpOp, - RK_Op_ProgramIROp_LabelOp, - RK_Op_ProgramIROp_ExitOp, - RK_Op_ProgramIROp_RetOp, - RK_Op_ProgramIROp_EntryPointOp, - RK_Op_ProgramIROp_AllocOp, - RK_Op_ProgramIROp_FreeOp, - RK_Op_ProgramIROp_ModeConfigOp, - RK_Op_ProgramIROp_BindOp, - RK_Op_ProgramIROp_Last, - RK_Op_DbgIROp, - RK_Op_DbgIROp_CommentOp, - RK_Op_DbgIROp_HintsOp, - RK_Op_DbgIROp_Last, - RK_Op_Last, - RK_Val, - RK_Val_LinalgIRVal, - RK_Val_GraphIRVal, - RK_Val_TensorIRVal, - RK_Val_TensorIRVal_TensorVal, - RK_Val_TensorIRVal_Last, - RK_Val_BuiltinIRVal, - RK_Val_ControlFlowIRVal, - RK_Val_ProgramIRVal, - RK_Val_DbgIRVal, - RK_Val_Last, - RK_Attr, - RK_Attr_LinalgIRAttr, - RK_Attr_LinalgIRAttr_QuantizationAnnotation, - RK_Attr_LinalgIRAttr_QuantizationSpec, - RK_Attr_LinalgIRAttr_Last, - RK_Attr_GraphIRAttr, - RK_Attr_TensorIRAttr, - RK_Attr_BuiltinIRAttr, - RK_Attr_BuiltinIRAttr_IntAttr, - RK_Attr_BuiltinIRAttr_FPAttr, - RK_Attr_BuiltinIRAttr_StrAttr, - RK_Attr_BuiltinIRAttr_SymbolAttr, - RK_Attr_BuiltinIRAttr_BoolAttr, - RK_Attr_BuiltinIRAttr_VectorFP32Attr, - RK_Attr_BuiltinIRAttr_VectorInt16Attr, - RK_Attr_BuiltinIRAttr_VectorUInt16Attr, - RK_Attr_BuiltinIRAttr_Last, - RK_Attr_ControlFlowIRAttr, - RK_Attr_ProgramIRAttr, - RK_Attr_DbgIRAttr, - RK_Attr_Last, +RK_None, +RK_Op, +RK_Op_LinalgIROp, +RK_Op_LinalgIROp_RegisterOp, +RK_Op_LinalgIROp_CustomKernelOp, +RK_Op_LinalgIROp_FillOp, +RK_Op_LinalgIROp_AddOp, +RK_Op_LinalgIROp_SubOp, +RK_Op_LinalgIROp_STFTOp, +RK_Op_LinalgIROp_ISTFTOp, +RK_Op_LinalgIROp_MulOp, +RK_Op_LinalgIROp_DivOp, +RK_Op_LinalgIROp_AbsOp, +RK_Op_LinalgIROp_LogOp, +RK_Op_LinalgIROp_MatMulOp, +RK_Op_LinalgIROp_EmbeddingOp, +RK_Op_LinalgIROp_LinearOp, +RK_Op_LinalgIROp_RoPEOp, +RK_Op_LinalgIROp_SoftmaxOp, +RK_Op_LinalgIROp_TransposeOp, +RK_Op_LinalgIROp_RMSNormOp, +RK_Op_LinalgIROp_SiLUOp, +RK_Op_LinalgIROp_KVCacheOp, +RK_Op_LinalgIROp_CausalMaskOp, +RK_Op_LinalgIROp_CastTypeOp, +RK_Op_LinalgIROp_X2XOp, +RK_Op_LinalgIROp_ViewOp, +RK_Op_LinalgIROp_SplitOp, +RK_Op_LinalgIROp_FlashAttention2Op, +RK_Op_LinalgIROp_CausalDepthwiseConv1DOp, +RK_Op_LinalgIROp_GroupedQueryAttentionOp, +RK_Op_LinalgIROp_ParallelLinearOp, +RK_Op_LinalgIROp_GatedDeltaRuleOp, +RK_Op_LinalgIROp_RepeatOp, +RK_Op_LinalgIROp_PermuteOp, +RK_Op_LinalgIROp_Conv1DOp, +RK_Op_LinalgIROp_Conv2DOp, +RK_Op_LinalgIROp_Conv3DOp, +RK_Op_LinalgIROp_GELUOp, +RK_Op_LinalgIROp_LayerNormOp, +RK_Op_LinalgIROp_MultimodalRoPEOp, +RK_Op_LinalgIROp_VisionRoPEOp, +RK_Op_LinalgIROp_QuickGELUOp, +RK_Op_LinalgIROp_KimiDeltaAttentionOp, +RK_Op_LinalgIROp_CopyOp, +RK_Op_LinalgIROp_CloneOp, +RK_Op_LinalgIROp_NegOp, +RK_Op_LinalgIROp_ConcatOp, +RK_Op_LinalgIROp_ReduceMinOp, +RK_Op_LinalgIROp_ReduceMaxOp, +RK_Op_LinalgIROp_ReduceSumOp, +RK_Op_LinalgIROp_ReLUOp, +RK_Op_LinalgIROp_ContiguousOp, +RK_Op_LinalgIROp_ReshapeOp, +RK_Op_LinalgIROp_SliceOp, +RK_Op_LinalgIROp_ParamOp, +RK_Op_LinalgIROp_IndexOp, +RK_Op_LinalgIROp_TopKOp, +RK_Op_LinalgIROp_MeanOp, +RK_Op_LinalgIROp_ClipOp, +RK_Op_LinalgIROp_ExpOp, +RK_Op_LinalgIROp_SinOp, +RK_Op_LinalgIROp_CosOp, +RK_Op_LinalgIROp_PagedAttnOp, +RK_Op_LinalgIROp_LayerNorm2DOp, +RK_Op_LinalgIROp_PadOp, +RK_Op_LinalgIROp_InterpolateOp, +RK_Op_LinalgIROp_EinsumOp, +RK_Op_LinalgIROp_StackOp, +RK_Op_LinalgIROp_MaskedScatterOp, +RK_Op_LinalgIROp_ScatterOp, +RK_Op_LinalgIROp_GatherOp, +RK_Op_LinalgIROp_ArgsortOp, +RK_Op_LinalgIROp_FlashAttention2SwaSinkOp, +RK_Op_LinalgIROp_RadixAttnRelaxOp, +RK_Op_LinalgIROp_RadixAttnSwaSinkOp, +RK_Op_LinalgIROp_EqualOp, +RK_Op_LinalgIROp_WhereOp, +RK_Op_LinalgIROp_SigmoidOp, +RK_Op_LinalgIROp_CustomizedOp, +RK_Op_LinalgIROp_Last, +RK_Op_GraphIROp, +RK_Op_GraphIROp_SubGraphOp, +RK_Op_GraphIROp_CallGraphOp, +RK_Op_GraphIROp_Last, +RK_Op_TensorIROp, +RK_Op_TensorIROp_AllocOp, +RK_Op_TensorIROp_RegisterOp, +RK_Op_TensorIROp_FreeOp, +RK_Op_TensorIROp_Last, +RK_Op_BuiltinIROp, +RK_Op_BuiltinIROp_ModuleOp, +RK_Op_BuiltinIROp_Last, +RK_Op_ControlFlowIROp, +RK_Op_ControlFlowIROp_ReturnOp, +RK_Op_ControlFlowIROp_Last, +RK_Op_ProgramIROp, +RK_Op_ProgramIROp_FragmentOp, +RK_Op_ProgramIROp_KernelLaunchOp, +RK_Op_ProgramIROp_KernelSymbolOp, +RK_Op_ProgramIROp_ValueSymbolOp, +RK_Op_ProgramIROp_JumpOp, +RK_Op_ProgramIROp_LabelOp, +RK_Op_ProgramIROp_ExitOp, +RK_Op_ProgramIROp_RetOp, +RK_Op_ProgramIROp_EntryPointOp, +RK_Op_ProgramIROp_AllocOp, +RK_Op_ProgramIROp_FreeOp, +RK_Op_ProgramIROp_ModeConfigOp, +RK_Op_ProgramIROp_BindOp, +RK_Op_ProgramIROp_Last, +RK_Op_DbgIROp, +RK_Op_DbgIROp_CommentOp, +RK_Op_DbgIROp_HintsOp, +RK_Op_DbgIROp_Last, +RK_Op_Last, +RK_Val, +RK_Val_LinalgIRVal, +RK_Val_GraphIRVal, +RK_Val_TensorIRVal, +RK_Val_TensorIRVal_TensorVal, +RK_Val_TensorIRVal_Last, +RK_Val_BuiltinIRVal, +RK_Val_ControlFlowIRVal, +RK_Val_ProgramIRVal, +RK_Val_DbgIRVal, +RK_Val_Last, +RK_Attr, +RK_Attr_LinalgIRAttr, +RK_Attr_LinalgIRAttr_QuantizationAnnotation, +RK_Attr_LinalgIRAttr_QuantizationSpec, +RK_Attr_LinalgIRAttr_Last, +RK_Attr_GraphIRAttr, +RK_Attr_TensorIRAttr, +RK_Attr_BuiltinIRAttr, +RK_Attr_BuiltinIRAttr_IntAttr, +RK_Attr_BuiltinIRAttr_FPAttr, +RK_Attr_BuiltinIRAttr_StrAttr, +RK_Attr_BuiltinIRAttr_SymbolAttr, +RK_Attr_BuiltinIRAttr_BoolAttr, +RK_Attr_BuiltinIRAttr_VectorFP32Attr, +RK_Attr_BuiltinIRAttr_VectorInt16Attr, +RK_Attr_BuiltinIRAttr_VectorUInt16Attr, +RK_Attr_BuiltinIRAttr_Last, +RK_Attr_ControlFlowIRAttr, +RK_Attr_ProgramIRAttr, +RK_Attr_DbgIRAttr, +RK_Attr_Last, }; + class Node; template struct NodeRTTIClassOfImpl { static inline bool classof(Node* v) { return false; } }; -} // namespace mllm::ir +} + \ No newline at end of file diff --git a/mllm/compile/ir/NodeRTTIClassOfImpl.hpp b/mllm/compile/ir/NodeRTTIClassOfImpl.hpp index 252c3fe4d..bde2105f3 100644 --- a/mllm/compile/ir/NodeRTTIClassOfImpl.hpp +++ b/mllm/compile/ir/NodeRTTIClassOfImpl.hpp @@ -1,4 +1,4 @@ -// Auto generated: 2026-08-21 15:20:12 +// Auto generated: 2026-09-06 09:09:14 // do not modify this file #pragma once namespace mllm::ir { @@ -9,392 +9,408 @@ template struct NodeRTTIClassOfImpl { static inline bool classof(Node* v) { return false; } }; -#endif //! RTTI_Node_IMPL +#endif //! RTTI_Node_IMPL -#define RTTI_RK_OP_IMPL(v) return (v)->getKind() >= RK_Op && (v)->getKind() <= RK_Op_Last +#define RTTI_RK_OP_IMPL(v) \ + return (v)->getKind() >= RK_Op && (v)->getKind() <= RK_Op_Last \ -#define RTTI_RK_OP_LINALGIROP_IMPL(v) return (v)->getKind() >= RK_Op_LinalgIROp && (v)->getKind() <= RK_Op_LinalgIROp_Last +#define RTTI_RK_OP_LINALGIROP_IMPL(v) \ + return (v)->getKind() >= RK_Op_LinalgIROp && (v)->getKind() <= RK_Op_LinalgIROp_Last \ #define RTTI_RK_OP_LINALGIROP_REGISTEROP_IMPL(v) \ - return (v)->getKind() >= RK_Op_LinalgIROp_RegisterOp && (v)->getKind() <= RK_Op_LinalgIROp_RegisterOp + return (v)->getKind() >= RK_Op_LinalgIROp_RegisterOp && (v)->getKind() <= RK_Op_LinalgIROp_RegisterOp \ #define RTTI_RK_OP_LINALGIROP_CUSTOMKERNELOP_IMPL(v) \ - return (v)->getKind() >= RK_Op_LinalgIROp_CustomKernelOp && (v)->getKind() <= RK_Op_LinalgIROp_CustomKernelOp + return (v)->getKind() >= RK_Op_LinalgIROp_CustomKernelOp && (v)->getKind() <= RK_Op_LinalgIROp_CustomKernelOp \ #define RTTI_RK_OP_LINALGIROP_FILLOP_IMPL(v) \ - return (v)->getKind() >= RK_Op_LinalgIROp_FillOp && (v)->getKind() <= RK_Op_LinalgIROp_FillOp + return (v)->getKind() >= RK_Op_LinalgIROp_FillOp && (v)->getKind() <= RK_Op_LinalgIROp_FillOp \ #define RTTI_RK_OP_LINALGIROP_ADDOP_IMPL(v) \ - return (v)->getKind() >= RK_Op_LinalgIROp_AddOp && (v)->getKind() <= RK_Op_LinalgIROp_AddOp + return (v)->getKind() >= RK_Op_LinalgIROp_AddOp && (v)->getKind() <= RK_Op_LinalgIROp_AddOp \ #define RTTI_RK_OP_LINALGIROP_SUBOP_IMPL(v) \ - return (v)->getKind() >= RK_Op_LinalgIROp_SubOp && (v)->getKind() <= RK_Op_LinalgIROp_SubOp + return (v)->getKind() >= RK_Op_LinalgIROp_SubOp && (v)->getKind() <= RK_Op_LinalgIROp_SubOp \ #define RTTI_RK_OP_LINALGIROP_STFTOP_IMPL(v) \ - return (v)->getKind() >= RK_Op_LinalgIROp_STFTOp && (v)->getKind() <= RK_Op_LinalgIROp_STFTOp + return (v)->getKind() >= RK_Op_LinalgIROp_STFTOp && (v)->getKind() <= RK_Op_LinalgIROp_STFTOp \ #define RTTI_RK_OP_LINALGIROP_ISTFTOP_IMPL(v) \ - return (v)->getKind() >= RK_Op_LinalgIROp_ISTFTOp && (v)->getKind() <= RK_Op_LinalgIROp_ISTFTOp + return (v)->getKind() >= RK_Op_LinalgIROp_ISTFTOp && (v)->getKind() <= RK_Op_LinalgIROp_ISTFTOp \ #define RTTI_RK_OP_LINALGIROP_MULOP_IMPL(v) \ - return (v)->getKind() >= RK_Op_LinalgIROp_MulOp && (v)->getKind() <= RK_Op_LinalgIROp_MulOp + return (v)->getKind() >= RK_Op_LinalgIROp_MulOp && (v)->getKind() <= RK_Op_LinalgIROp_MulOp \ #define RTTI_RK_OP_LINALGIROP_DIVOP_IMPL(v) \ - return (v)->getKind() >= RK_Op_LinalgIROp_DivOp && (v)->getKind() <= RK_Op_LinalgIROp_DivOp + return (v)->getKind() >= RK_Op_LinalgIROp_DivOp && (v)->getKind() <= RK_Op_LinalgIROp_DivOp \ #define RTTI_RK_OP_LINALGIROP_ABSOP_IMPL(v) \ - return (v)->getKind() >= RK_Op_LinalgIROp_AbsOp && (v)->getKind() <= RK_Op_LinalgIROp_AbsOp + return (v)->getKind() >= RK_Op_LinalgIROp_AbsOp && (v)->getKind() <= RK_Op_LinalgIROp_AbsOp \ #define RTTI_RK_OP_LINALGIROP_LOGOP_IMPL(v) \ - return (v)->getKind() >= RK_Op_LinalgIROp_LogOp && (v)->getKind() <= RK_Op_LinalgIROp_LogOp + return (v)->getKind() >= RK_Op_LinalgIROp_LogOp && (v)->getKind() <= RK_Op_LinalgIROp_LogOp \ #define RTTI_RK_OP_LINALGIROP_MATMULOP_IMPL(v) \ - return (v)->getKind() >= RK_Op_LinalgIROp_MatMulOp && (v)->getKind() <= RK_Op_LinalgIROp_MatMulOp + return (v)->getKind() >= RK_Op_LinalgIROp_MatMulOp && (v)->getKind() <= RK_Op_LinalgIROp_MatMulOp \ #define RTTI_RK_OP_LINALGIROP_EMBEDDINGOP_IMPL(v) \ - return (v)->getKind() >= RK_Op_LinalgIROp_EmbeddingOp && (v)->getKind() <= RK_Op_LinalgIROp_EmbeddingOp + return (v)->getKind() >= RK_Op_LinalgIROp_EmbeddingOp && (v)->getKind() <= RK_Op_LinalgIROp_EmbeddingOp \ #define RTTI_RK_OP_LINALGIROP_LINEAROP_IMPL(v) \ - return (v)->getKind() >= RK_Op_LinalgIROp_LinearOp && (v)->getKind() <= RK_Op_LinalgIROp_LinearOp + return (v)->getKind() >= RK_Op_LinalgIROp_LinearOp && (v)->getKind() <= RK_Op_LinalgIROp_LinearOp \ #define RTTI_RK_OP_LINALGIROP_ROPEOP_IMPL(v) \ - return (v)->getKind() >= RK_Op_LinalgIROp_RoPEOp && (v)->getKind() <= RK_Op_LinalgIROp_RoPEOp + return (v)->getKind() >= RK_Op_LinalgIROp_RoPEOp && (v)->getKind() <= RK_Op_LinalgIROp_RoPEOp \ #define RTTI_RK_OP_LINALGIROP_SOFTMAXOP_IMPL(v) \ - return (v)->getKind() >= RK_Op_LinalgIROp_SoftmaxOp && (v)->getKind() <= RK_Op_LinalgIROp_SoftmaxOp + return (v)->getKind() >= RK_Op_LinalgIROp_SoftmaxOp && (v)->getKind() <= RK_Op_LinalgIROp_SoftmaxOp \ #define RTTI_RK_OP_LINALGIROP_TRANSPOSEOP_IMPL(v) \ - return (v)->getKind() >= RK_Op_LinalgIROp_TransposeOp && (v)->getKind() <= RK_Op_LinalgIROp_TransposeOp + return (v)->getKind() >= RK_Op_LinalgIROp_TransposeOp && (v)->getKind() <= RK_Op_LinalgIROp_TransposeOp \ #define RTTI_RK_OP_LINALGIROP_RMSNORMOP_IMPL(v) \ - return (v)->getKind() >= RK_Op_LinalgIROp_RMSNormOp && (v)->getKind() <= RK_Op_LinalgIROp_RMSNormOp + return (v)->getKind() >= RK_Op_LinalgIROp_RMSNormOp && (v)->getKind() <= RK_Op_LinalgIROp_RMSNormOp \ #define RTTI_RK_OP_LINALGIROP_SILUOP_IMPL(v) \ - return (v)->getKind() >= RK_Op_LinalgIROp_SiLUOp && (v)->getKind() <= RK_Op_LinalgIROp_SiLUOp + return (v)->getKind() >= RK_Op_LinalgIROp_SiLUOp && (v)->getKind() <= RK_Op_LinalgIROp_SiLUOp \ #define RTTI_RK_OP_LINALGIROP_KVCACHEOP_IMPL(v) \ - return (v)->getKind() >= RK_Op_LinalgIROp_KVCacheOp && (v)->getKind() <= RK_Op_LinalgIROp_KVCacheOp + return (v)->getKind() >= RK_Op_LinalgIROp_KVCacheOp && (v)->getKind() <= RK_Op_LinalgIROp_KVCacheOp \ #define RTTI_RK_OP_LINALGIROP_CAUSALMASKOP_IMPL(v) \ - return (v)->getKind() >= RK_Op_LinalgIROp_CausalMaskOp && (v)->getKind() <= RK_Op_LinalgIROp_CausalMaskOp + return (v)->getKind() >= RK_Op_LinalgIROp_CausalMaskOp && (v)->getKind() <= RK_Op_LinalgIROp_CausalMaskOp \ #define RTTI_RK_OP_LINALGIROP_CASTTYPEOP_IMPL(v) \ - return (v)->getKind() >= RK_Op_LinalgIROp_CastTypeOp && (v)->getKind() <= RK_Op_LinalgIROp_CastTypeOp + return (v)->getKind() >= RK_Op_LinalgIROp_CastTypeOp && (v)->getKind() <= RK_Op_LinalgIROp_CastTypeOp \ #define RTTI_RK_OP_LINALGIROP_X2XOP_IMPL(v) \ - return (v)->getKind() >= RK_Op_LinalgIROp_X2XOp && (v)->getKind() <= RK_Op_LinalgIROp_X2XOp + return (v)->getKind() >= RK_Op_LinalgIROp_X2XOp && (v)->getKind() <= RK_Op_LinalgIROp_X2XOp \ #define RTTI_RK_OP_LINALGIROP_VIEWOP_IMPL(v) \ - return (v)->getKind() >= RK_Op_LinalgIROp_ViewOp && (v)->getKind() <= RK_Op_LinalgIROp_ViewOp + return (v)->getKind() >= RK_Op_LinalgIROp_ViewOp && (v)->getKind() <= RK_Op_LinalgIROp_ViewOp \ #define RTTI_RK_OP_LINALGIROP_SPLITOP_IMPL(v) \ - return (v)->getKind() >= RK_Op_LinalgIROp_SplitOp && (v)->getKind() <= RK_Op_LinalgIROp_SplitOp + return (v)->getKind() >= RK_Op_LinalgIROp_SplitOp && (v)->getKind() <= RK_Op_LinalgIROp_SplitOp \ #define RTTI_RK_OP_LINALGIROP_FLASHATTENTION2OP_IMPL(v) \ - return (v)->getKind() >= RK_Op_LinalgIROp_FlashAttention2Op && (v)->getKind() <= RK_Op_LinalgIROp_FlashAttention2Op + return (v)->getKind() >= RK_Op_LinalgIROp_FlashAttention2Op && (v)->getKind() <= RK_Op_LinalgIROp_FlashAttention2Op \ -#define RTTI_RK_OP_LINALGIROP_CAUSALDEPTHWISECONV1DOP_IMPL(v) \ - return (v)->getKind() >= RK_Op_LinalgIROp_CausalDepthwiseConv1DOp \ - && (v)->getKind() <= RK_Op_LinalgIROp_CausalDepthwiseConv1DOp +#define RTTI_RK_OP_LINALGIROP_CAUSALDEPTHWISECONV1DOP_IMPL(v) \ + return (v)->getKind() >= RK_Op_LinalgIROp_CausalDepthwiseConv1DOp && (v)->getKind() <= RK_Op_LinalgIROp_CausalDepthwiseConv1DOp \ -#define RTTI_RK_OP_LINALGIROP_GROUPEDQUERYATTENTIONOP_IMPL(v) \ - return (v)->getKind() >= RK_Op_LinalgIROp_GroupedQueryAttentionOp \ - && (v)->getKind() <= RK_Op_LinalgIROp_GroupedQueryAttentionOp +#define RTTI_RK_OP_LINALGIROP_GROUPEDQUERYATTENTIONOP_IMPL(v) \ + return (v)->getKind() >= RK_Op_LinalgIROp_GroupedQueryAttentionOp && (v)->getKind() <= RK_Op_LinalgIROp_GroupedQueryAttentionOp \ #define RTTI_RK_OP_LINALGIROP_PARALLELLINEAROP_IMPL(v) \ - return (v)->getKind() >= RK_Op_LinalgIROp_ParallelLinearOp && (v)->getKind() <= RK_Op_LinalgIROp_ParallelLinearOp + return (v)->getKind() >= RK_Op_LinalgIROp_ParallelLinearOp && (v)->getKind() <= RK_Op_LinalgIROp_ParallelLinearOp \ #define RTTI_RK_OP_LINALGIROP_GATEDDELTARULEOP_IMPL(v) \ - return (v)->getKind() >= RK_Op_LinalgIROp_GatedDeltaRuleOp && (v)->getKind() <= RK_Op_LinalgIROp_GatedDeltaRuleOp + return (v)->getKind() >= RK_Op_LinalgIROp_GatedDeltaRuleOp && (v)->getKind() <= RK_Op_LinalgIROp_GatedDeltaRuleOp \ #define RTTI_RK_OP_LINALGIROP_REPEATOP_IMPL(v) \ - return (v)->getKind() >= RK_Op_LinalgIROp_RepeatOp && (v)->getKind() <= RK_Op_LinalgIROp_RepeatOp + return (v)->getKind() >= RK_Op_LinalgIROp_RepeatOp && (v)->getKind() <= RK_Op_LinalgIROp_RepeatOp \ #define RTTI_RK_OP_LINALGIROP_PERMUTEOP_IMPL(v) \ - return (v)->getKind() >= RK_Op_LinalgIROp_PermuteOp && (v)->getKind() <= RK_Op_LinalgIROp_PermuteOp + return (v)->getKind() >= RK_Op_LinalgIROp_PermuteOp && (v)->getKind() <= RK_Op_LinalgIROp_PermuteOp \ #define RTTI_RK_OP_LINALGIROP_CONV1DOP_IMPL(v) \ - return (v)->getKind() >= RK_Op_LinalgIROp_Conv1DOp && (v)->getKind() <= RK_Op_LinalgIROp_Conv1DOp + return (v)->getKind() >= RK_Op_LinalgIROp_Conv1DOp && (v)->getKind() <= RK_Op_LinalgIROp_Conv1DOp \ #define RTTI_RK_OP_LINALGIROP_CONV2DOP_IMPL(v) \ - return (v)->getKind() >= RK_Op_LinalgIROp_Conv2DOp && (v)->getKind() <= RK_Op_LinalgIROp_Conv2DOp + return (v)->getKind() >= RK_Op_LinalgIROp_Conv2DOp && (v)->getKind() <= RK_Op_LinalgIROp_Conv2DOp \ #define RTTI_RK_OP_LINALGIROP_CONV3DOP_IMPL(v) \ - return (v)->getKind() >= RK_Op_LinalgIROp_Conv3DOp && (v)->getKind() <= RK_Op_LinalgIROp_Conv3DOp + return (v)->getKind() >= RK_Op_LinalgIROp_Conv3DOp && (v)->getKind() <= RK_Op_LinalgIROp_Conv3DOp \ #define RTTI_RK_OP_LINALGIROP_GELUOP_IMPL(v) \ - return (v)->getKind() >= RK_Op_LinalgIROp_GELUOp && (v)->getKind() <= RK_Op_LinalgIROp_GELUOp + return (v)->getKind() >= RK_Op_LinalgIROp_GELUOp && (v)->getKind() <= RK_Op_LinalgIROp_GELUOp \ #define RTTI_RK_OP_LINALGIROP_LAYERNORMOP_IMPL(v) \ - return (v)->getKind() >= RK_Op_LinalgIROp_LayerNormOp && (v)->getKind() <= RK_Op_LinalgIROp_LayerNormOp + return (v)->getKind() >= RK_Op_LinalgIROp_LayerNormOp && (v)->getKind() <= RK_Op_LinalgIROp_LayerNormOp \ #define RTTI_RK_OP_LINALGIROP_MULTIMODALROPEOP_IMPL(v) \ - return (v)->getKind() >= RK_Op_LinalgIROp_MultimodalRoPEOp && (v)->getKind() <= RK_Op_LinalgIROp_MultimodalRoPEOp + return (v)->getKind() >= RK_Op_LinalgIROp_MultimodalRoPEOp && (v)->getKind() <= RK_Op_LinalgIROp_MultimodalRoPEOp \ #define RTTI_RK_OP_LINALGIROP_VISIONROPEOP_IMPL(v) \ - return (v)->getKind() >= RK_Op_LinalgIROp_VisionRoPEOp && (v)->getKind() <= RK_Op_LinalgIROp_VisionRoPEOp + return (v)->getKind() >= RK_Op_LinalgIROp_VisionRoPEOp && (v)->getKind() <= RK_Op_LinalgIROp_VisionRoPEOp \ #define RTTI_RK_OP_LINALGIROP_QUICKGELUOP_IMPL(v) \ - return (v)->getKind() >= RK_Op_LinalgIROp_QuickGELUOp && (v)->getKind() <= RK_Op_LinalgIROp_QuickGELUOp + return (v)->getKind() >= RK_Op_LinalgIROp_QuickGELUOp && (v)->getKind() <= RK_Op_LinalgIROp_QuickGELUOp \ + +#define RTTI_RK_OP_LINALGIROP_KIMIDELTAATTENTIONOP_IMPL(v) \ + return (v)->getKind() >= RK_Op_LinalgIROp_KimiDeltaAttentionOp && (v)->getKind() <= RK_Op_LinalgIROp_KimiDeltaAttentionOp \ #define RTTI_RK_OP_LINALGIROP_COPYOP_IMPL(v) \ - return (v)->getKind() >= RK_Op_LinalgIROp_CopyOp && (v)->getKind() <= RK_Op_LinalgIROp_CopyOp + return (v)->getKind() >= RK_Op_LinalgIROp_CopyOp && (v)->getKind() <= RK_Op_LinalgIROp_CopyOp \ #define RTTI_RK_OP_LINALGIROP_CLONEOP_IMPL(v) \ - return (v)->getKind() >= RK_Op_LinalgIROp_CloneOp && (v)->getKind() <= RK_Op_LinalgIROp_CloneOp + return (v)->getKind() >= RK_Op_LinalgIROp_CloneOp && (v)->getKind() <= RK_Op_LinalgIROp_CloneOp \ #define RTTI_RK_OP_LINALGIROP_NEGOP_IMPL(v) \ - return (v)->getKind() >= RK_Op_LinalgIROp_NegOp && (v)->getKind() <= RK_Op_LinalgIROp_NegOp + return (v)->getKind() >= RK_Op_LinalgIROp_NegOp && (v)->getKind() <= RK_Op_LinalgIROp_NegOp \ #define RTTI_RK_OP_LINALGIROP_CONCATOP_IMPL(v) \ - return (v)->getKind() >= RK_Op_LinalgIROp_ConcatOp && (v)->getKind() <= RK_Op_LinalgIROp_ConcatOp + return (v)->getKind() >= RK_Op_LinalgIROp_ConcatOp && (v)->getKind() <= RK_Op_LinalgIROp_ConcatOp \ #define RTTI_RK_OP_LINALGIROP_REDUCEMINOP_IMPL(v) \ - return (v)->getKind() >= RK_Op_LinalgIROp_ReduceMinOp && (v)->getKind() <= RK_Op_LinalgIROp_ReduceMinOp + return (v)->getKind() >= RK_Op_LinalgIROp_ReduceMinOp && (v)->getKind() <= RK_Op_LinalgIROp_ReduceMinOp \ #define RTTI_RK_OP_LINALGIROP_REDUCEMAXOP_IMPL(v) \ - return (v)->getKind() >= RK_Op_LinalgIROp_ReduceMaxOp && (v)->getKind() <= RK_Op_LinalgIROp_ReduceMaxOp + return (v)->getKind() >= RK_Op_LinalgIROp_ReduceMaxOp && (v)->getKind() <= RK_Op_LinalgIROp_ReduceMaxOp \ #define RTTI_RK_OP_LINALGIROP_REDUCESUMOP_IMPL(v) \ - return (v)->getKind() >= RK_Op_LinalgIROp_ReduceSumOp && (v)->getKind() <= RK_Op_LinalgIROp_ReduceSumOp + return (v)->getKind() >= RK_Op_LinalgIROp_ReduceSumOp && (v)->getKind() <= RK_Op_LinalgIROp_ReduceSumOp \ #define RTTI_RK_OP_LINALGIROP_RELUOP_IMPL(v) \ - return (v)->getKind() >= RK_Op_LinalgIROp_ReLUOp && (v)->getKind() <= RK_Op_LinalgIROp_ReLUOp + return (v)->getKind() >= RK_Op_LinalgIROp_ReLUOp && (v)->getKind() <= RK_Op_LinalgIROp_ReLUOp \ #define RTTI_RK_OP_LINALGIROP_CONTIGUOUSOP_IMPL(v) \ - return (v)->getKind() >= RK_Op_LinalgIROp_ContiguousOp && (v)->getKind() <= RK_Op_LinalgIROp_ContiguousOp + return (v)->getKind() >= RK_Op_LinalgIROp_ContiguousOp && (v)->getKind() <= RK_Op_LinalgIROp_ContiguousOp \ #define RTTI_RK_OP_LINALGIROP_RESHAPEOP_IMPL(v) \ - return (v)->getKind() >= RK_Op_LinalgIROp_ReshapeOp && (v)->getKind() <= RK_Op_LinalgIROp_ReshapeOp + return (v)->getKind() >= RK_Op_LinalgIROp_ReshapeOp && (v)->getKind() <= RK_Op_LinalgIROp_ReshapeOp \ #define RTTI_RK_OP_LINALGIROP_SLICEOP_IMPL(v) \ - return (v)->getKind() >= RK_Op_LinalgIROp_SliceOp && (v)->getKind() <= RK_Op_LinalgIROp_SliceOp + return (v)->getKind() >= RK_Op_LinalgIROp_SliceOp && (v)->getKind() <= RK_Op_LinalgIROp_SliceOp \ #define RTTI_RK_OP_LINALGIROP_PARAMOP_IMPL(v) \ - return (v)->getKind() >= RK_Op_LinalgIROp_ParamOp && (v)->getKind() <= RK_Op_LinalgIROp_ParamOp + return (v)->getKind() >= RK_Op_LinalgIROp_ParamOp && (v)->getKind() <= RK_Op_LinalgIROp_ParamOp \ #define RTTI_RK_OP_LINALGIROP_INDEXOP_IMPL(v) \ - return (v)->getKind() >= RK_Op_LinalgIROp_IndexOp && (v)->getKind() <= RK_Op_LinalgIROp_IndexOp + return (v)->getKind() >= RK_Op_LinalgIROp_IndexOp && (v)->getKind() <= RK_Op_LinalgIROp_IndexOp \ #define RTTI_RK_OP_LINALGIROP_TOPKOP_IMPL(v) \ - return (v)->getKind() >= RK_Op_LinalgIROp_TopKOp && (v)->getKind() <= RK_Op_LinalgIROp_TopKOp + return (v)->getKind() >= RK_Op_LinalgIROp_TopKOp && (v)->getKind() <= RK_Op_LinalgIROp_TopKOp \ #define RTTI_RK_OP_LINALGIROP_MEANOP_IMPL(v) \ - return (v)->getKind() >= RK_Op_LinalgIROp_MeanOp && (v)->getKind() <= RK_Op_LinalgIROp_MeanOp + return (v)->getKind() >= RK_Op_LinalgIROp_MeanOp && (v)->getKind() <= RK_Op_LinalgIROp_MeanOp \ #define RTTI_RK_OP_LINALGIROP_CLIPOP_IMPL(v) \ - return (v)->getKind() >= RK_Op_LinalgIROp_ClipOp && (v)->getKind() <= RK_Op_LinalgIROp_ClipOp + return (v)->getKind() >= RK_Op_LinalgIROp_ClipOp && (v)->getKind() <= RK_Op_LinalgIROp_ClipOp \ #define RTTI_RK_OP_LINALGIROP_EXPOP_IMPL(v) \ - return (v)->getKind() >= RK_Op_LinalgIROp_ExpOp && (v)->getKind() <= RK_Op_LinalgIROp_ExpOp + return (v)->getKind() >= RK_Op_LinalgIROp_ExpOp && (v)->getKind() <= RK_Op_LinalgIROp_ExpOp \ #define RTTI_RK_OP_LINALGIROP_SINOP_IMPL(v) \ - return (v)->getKind() >= RK_Op_LinalgIROp_SinOp && (v)->getKind() <= RK_Op_LinalgIROp_SinOp + return (v)->getKind() >= RK_Op_LinalgIROp_SinOp && (v)->getKind() <= RK_Op_LinalgIROp_SinOp \ #define RTTI_RK_OP_LINALGIROP_COSOP_IMPL(v) \ - return (v)->getKind() >= RK_Op_LinalgIROp_CosOp && (v)->getKind() <= RK_Op_LinalgIROp_CosOp + return (v)->getKind() >= RK_Op_LinalgIROp_CosOp && (v)->getKind() <= RK_Op_LinalgIROp_CosOp \ #define RTTI_RK_OP_LINALGIROP_PAGEDATTNOP_IMPL(v) \ - return (v)->getKind() >= RK_Op_LinalgIROp_PagedAttnOp && (v)->getKind() <= RK_Op_LinalgIROp_PagedAttnOp + return (v)->getKind() >= RK_Op_LinalgIROp_PagedAttnOp && (v)->getKind() <= RK_Op_LinalgIROp_PagedAttnOp \ #define RTTI_RK_OP_LINALGIROP_LAYERNORM2DOP_IMPL(v) \ - return (v)->getKind() >= RK_Op_LinalgIROp_LayerNorm2DOp && (v)->getKind() <= RK_Op_LinalgIROp_LayerNorm2DOp + return (v)->getKind() >= RK_Op_LinalgIROp_LayerNorm2DOp && (v)->getKind() <= RK_Op_LinalgIROp_LayerNorm2DOp \ #define RTTI_RK_OP_LINALGIROP_PADOP_IMPL(v) \ - return (v)->getKind() >= RK_Op_LinalgIROp_PadOp && (v)->getKind() <= RK_Op_LinalgIROp_PadOp + return (v)->getKind() >= RK_Op_LinalgIROp_PadOp && (v)->getKind() <= RK_Op_LinalgIROp_PadOp \ #define RTTI_RK_OP_LINALGIROP_INTERPOLATEOP_IMPL(v) \ - return (v)->getKind() >= RK_Op_LinalgIROp_InterpolateOp && (v)->getKind() <= RK_Op_LinalgIROp_InterpolateOp + return (v)->getKind() >= RK_Op_LinalgIROp_InterpolateOp && (v)->getKind() <= RK_Op_LinalgIROp_InterpolateOp \ #define RTTI_RK_OP_LINALGIROP_EINSUMOP_IMPL(v) \ - return (v)->getKind() >= RK_Op_LinalgIROp_EinsumOp && (v)->getKind() <= RK_Op_LinalgIROp_EinsumOp + return (v)->getKind() >= RK_Op_LinalgIROp_EinsumOp && (v)->getKind() <= RK_Op_LinalgIROp_EinsumOp \ #define RTTI_RK_OP_LINALGIROP_STACKOP_IMPL(v) \ - return (v)->getKind() >= RK_Op_LinalgIROp_StackOp && (v)->getKind() <= RK_Op_LinalgIROp_StackOp + return (v)->getKind() >= RK_Op_LinalgIROp_StackOp && (v)->getKind() <= RK_Op_LinalgIROp_StackOp \ #define RTTI_RK_OP_LINALGIROP_MASKEDSCATTEROP_IMPL(v) \ - return (v)->getKind() >= RK_Op_LinalgIROp_MaskedScatterOp && (v)->getKind() <= RK_Op_LinalgIROp_MaskedScatterOp + return (v)->getKind() >= RK_Op_LinalgIROp_MaskedScatterOp && (v)->getKind() <= RK_Op_LinalgIROp_MaskedScatterOp \ #define RTTI_RK_OP_LINALGIROP_SCATTEROP_IMPL(v) \ - return (v)->getKind() >= RK_Op_LinalgIROp_ScatterOp && (v)->getKind() <= RK_Op_LinalgIROp_ScatterOp + return (v)->getKind() >= RK_Op_LinalgIROp_ScatterOp && (v)->getKind() <= RK_Op_LinalgIROp_ScatterOp \ #define RTTI_RK_OP_LINALGIROP_GATHEROP_IMPL(v) \ - return (v)->getKind() >= RK_Op_LinalgIROp_GatherOp && (v)->getKind() <= RK_Op_LinalgIROp_GatherOp + return (v)->getKind() >= RK_Op_LinalgIROp_GatherOp && (v)->getKind() <= RK_Op_LinalgIROp_GatherOp \ #define RTTI_RK_OP_LINALGIROP_ARGSORTOP_IMPL(v) \ - return (v)->getKind() >= RK_Op_LinalgIROp_ArgsortOp && (v)->getKind() <= RK_Op_LinalgIROp_ArgsortOp + return (v)->getKind() >= RK_Op_LinalgIROp_ArgsortOp && (v)->getKind() <= RK_Op_LinalgIROp_ArgsortOp \ -#define RTTI_RK_OP_LINALGIROP_FLASHATTENTION2SWASINKOP_IMPL(v) \ - return (v)->getKind() >= RK_Op_LinalgIROp_FlashAttention2SwaSinkOp \ - && (v)->getKind() <= RK_Op_LinalgIROp_FlashAttention2SwaSinkOp +#define RTTI_RK_OP_LINALGIROP_FLASHATTENTION2SWASINKOP_IMPL(v) \ + return (v)->getKind() >= RK_Op_LinalgIROp_FlashAttention2SwaSinkOp && (v)->getKind() <= RK_Op_LinalgIROp_FlashAttention2SwaSinkOp \ #define RTTI_RK_OP_LINALGIROP_RADIXATTNRELAXOP_IMPL(v) \ - return (v)->getKind() >= RK_Op_LinalgIROp_RadixAttnRelaxOp && (v)->getKind() <= RK_Op_LinalgIROp_RadixAttnRelaxOp + return (v)->getKind() >= RK_Op_LinalgIROp_RadixAttnRelaxOp && (v)->getKind() <= RK_Op_LinalgIROp_RadixAttnRelaxOp \ #define RTTI_RK_OP_LINALGIROP_RADIXATTNSWASINKOP_IMPL(v) \ - return (v)->getKind() >= RK_Op_LinalgIROp_RadixAttnSwaSinkOp && (v)->getKind() <= RK_Op_LinalgIROp_RadixAttnSwaSinkOp + return (v)->getKind() >= RK_Op_LinalgIROp_RadixAttnSwaSinkOp && (v)->getKind() <= RK_Op_LinalgIROp_RadixAttnSwaSinkOp \ #define RTTI_RK_OP_LINALGIROP_EQUALOP_IMPL(v) \ - return (v)->getKind() >= RK_Op_LinalgIROp_EqualOp && (v)->getKind() <= RK_Op_LinalgIROp_EqualOp + return (v)->getKind() >= RK_Op_LinalgIROp_EqualOp && (v)->getKind() <= RK_Op_LinalgIROp_EqualOp \ #define RTTI_RK_OP_LINALGIROP_WHEREOP_IMPL(v) \ - return (v)->getKind() >= RK_Op_LinalgIROp_WhereOp && (v)->getKind() <= RK_Op_LinalgIROp_WhereOp + return (v)->getKind() >= RK_Op_LinalgIROp_WhereOp && (v)->getKind() <= RK_Op_LinalgIROp_WhereOp \ #define RTTI_RK_OP_LINALGIROP_SIGMOIDOP_IMPL(v) \ - return (v)->getKind() >= RK_Op_LinalgIROp_SigmoidOp && (v)->getKind() <= RK_Op_LinalgIROp_SigmoidOp + return (v)->getKind() >= RK_Op_LinalgIROp_SigmoidOp && (v)->getKind() <= RK_Op_LinalgIROp_SigmoidOp \ #define RTTI_RK_OP_LINALGIROP_CUSTOMIZEDOP_IMPL(v) \ - return (v)->getKind() >= RK_Op_LinalgIROp_CustomizedOp && (v)->getKind() <= RK_Op_LinalgIROp_CustomizedOp + return (v)->getKind() >= RK_Op_LinalgIROp_CustomizedOp && (v)->getKind() <= RK_Op_LinalgIROp_CustomizedOp \ -#define RTTI_RK_OP_GRAPHIROP_IMPL(v) return (v)->getKind() >= RK_Op_GraphIROp && (v)->getKind() <= RK_Op_GraphIROp_Last +#define RTTI_RK_OP_GRAPHIROP_IMPL(v) \ + return (v)->getKind() >= RK_Op_GraphIROp && (v)->getKind() <= RK_Op_GraphIROp_Last \ #define RTTI_RK_OP_GRAPHIROP_SUBGRAPHOP_IMPL(v) \ - return (v)->getKind() >= RK_Op_GraphIROp_SubGraphOp && (v)->getKind() <= RK_Op_GraphIROp_SubGraphOp + return (v)->getKind() >= RK_Op_GraphIROp_SubGraphOp && (v)->getKind() <= RK_Op_GraphIROp_SubGraphOp \ #define RTTI_RK_OP_GRAPHIROP_CALLGRAPHOP_IMPL(v) \ - return (v)->getKind() >= RK_Op_GraphIROp_CallGraphOp && (v)->getKind() <= RK_Op_GraphIROp_CallGraphOp + return (v)->getKind() >= RK_Op_GraphIROp_CallGraphOp && (v)->getKind() <= RK_Op_GraphIROp_CallGraphOp \ -#define RTTI_RK_OP_TENSORIROP_IMPL(v) return (v)->getKind() >= RK_Op_TensorIROp && (v)->getKind() <= RK_Op_TensorIROp_Last +#define RTTI_RK_OP_TENSORIROP_IMPL(v) \ + return (v)->getKind() >= RK_Op_TensorIROp && (v)->getKind() <= RK_Op_TensorIROp_Last \ #define RTTI_RK_OP_TENSORIROP_ALLOCOP_IMPL(v) \ - return (v)->getKind() >= RK_Op_TensorIROp_AllocOp && (v)->getKind() <= RK_Op_TensorIROp_AllocOp + return (v)->getKind() >= RK_Op_TensorIROp_AllocOp && (v)->getKind() <= RK_Op_TensorIROp_AllocOp \ #define RTTI_RK_OP_TENSORIROP_REGISTEROP_IMPL(v) \ - return (v)->getKind() >= RK_Op_TensorIROp_RegisterOp && (v)->getKind() <= RK_Op_TensorIROp_RegisterOp + return (v)->getKind() >= RK_Op_TensorIROp_RegisterOp && (v)->getKind() <= RK_Op_TensorIROp_RegisterOp \ #define RTTI_RK_OP_TENSORIROP_FREEOP_IMPL(v) \ - return (v)->getKind() >= RK_Op_TensorIROp_FreeOp && (v)->getKind() <= RK_Op_TensorIROp_FreeOp + return (v)->getKind() >= RK_Op_TensorIROp_FreeOp && (v)->getKind() <= RK_Op_TensorIROp_FreeOp \ -#define RTTI_RK_OP_BUILTINIROP_IMPL(v) return (v)->getKind() >= RK_Op_BuiltinIROp && (v)->getKind() <= RK_Op_BuiltinIROp_Last +#define RTTI_RK_OP_BUILTINIROP_IMPL(v) \ + return (v)->getKind() >= RK_Op_BuiltinIROp && (v)->getKind() <= RK_Op_BuiltinIROp_Last \ #define RTTI_RK_OP_BUILTINIROP_MODULEOP_IMPL(v) \ - return (v)->getKind() >= RK_Op_BuiltinIROp_ModuleOp && (v)->getKind() <= RK_Op_BuiltinIROp_ModuleOp + return (v)->getKind() >= RK_Op_BuiltinIROp_ModuleOp && (v)->getKind() <= RK_Op_BuiltinIROp_ModuleOp \ #define RTTI_RK_OP_CONTROLFLOWIROP_IMPL(v) \ - return (v)->getKind() >= RK_Op_ControlFlowIROp && (v)->getKind() <= RK_Op_ControlFlowIROp_Last + return (v)->getKind() >= RK_Op_ControlFlowIROp && (v)->getKind() <= RK_Op_ControlFlowIROp_Last \ #define RTTI_RK_OP_CONTROLFLOWIROP_RETURNOP_IMPL(v) \ - return (v)->getKind() >= RK_Op_ControlFlowIROp_ReturnOp && (v)->getKind() <= RK_Op_ControlFlowIROp_ReturnOp + return (v)->getKind() >= RK_Op_ControlFlowIROp_ReturnOp && (v)->getKind() <= RK_Op_ControlFlowIROp_ReturnOp \ -#define RTTI_RK_OP_PROGRAMIROP_IMPL(v) return (v)->getKind() >= RK_Op_ProgramIROp && (v)->getKind() <= RK_Op_ProgramIROp_Last +#define RTTI_RK_OP_PROGRAMIROP_IMPL(v) \ + return (v)->getKind() >= RK_Op_ProgramIROp && (v)->getKind() <= RK_Op_ProgramIROp_Last \ #define RTTI_RK_OP_PROGRAMIROP_FRAGMENTOP_IMPL(v) \ - return (v)->getKind() >= RK_Op_ProgramIROp_FragmentOp && (v)->getKind() <= RK_Op_ProgramIROp_FragmentOp + return (v)->getKind() >= RK_Op_ProgramIROp_FragmentOp && (v)->getKind() <= RK_Op_ProgramIROp_FragmentOp \ #define RTTI_RK_OP_PROGRAMIROP_KERNELLAUNCHOP_IMPL(v) \ - return (v)->getKind() >= RK_Op_ProgramIROp_KernelLaunchOp && (v)->getKind() <= RK_Op_ProgramIROp_KernelLaunchOp + return (v)->getKind() >= RK_Op_ProgramIROp_KernelLaunchOp && (v)->getKind() <= RK_Op_ProgramIROp_KernelLaunchOp \ #define RTTI_RK_OP_PROGRAMIROP_KERNELSYMBOLOP_IMPL(v) \ - return (v)->getKind() >= RK_Op_ProgramIROp_KernelSymbolOp && (v)->getKind() <= RK_Op_ProgramIROp_KernelSymbolOp + return (v)->getKind() >= RK_Op_ProgramIROp_KernelSymbolOp && (v)->getKind() <= RK_Op_ProgramIROp_KernelSymbolOp \ #define RTTI_RK_OP_PROGRAMIROP_VALUESYMBOLOP_IMPL(v) \ - return (v)->getKind() >= RK_Op_ProgramIROp_ValueSymbolOp && (v)->getKind() <= RK_Op_ProgramIROp_ValueSymbolOp + return (v)->getKind() >= RK_Op_ProgramIROp_ValueSymbolOp && (v)->getKind() <= RK_Op_ProgramIROp_ValueSymbolOp \ #define RTTI_RK_OP_PROGRAMIROP_JUMPOP_IMPL(v) \ - return (v)->getKind() >= RK_Op_ProgramIROp_JumpOp && (v)->getKind() <= RK_Op_ProgramIROp_JumpOp + return (v)->getKind() >= RK_Op_ProgramIROp_JumpOp && (v)->getKind() <= RK_Op_ProgramIROp_JumpOp \ #define RTTI_RK_OP_PROGRAMIROP_LABELOP_IMPL(v) \ - return (v)->getKind() >= RK_Op_ProgramIROp_LabelOp && (v)->getKind() <= RK_Op_ProgramIROp_LabelOp + return (v)->getKind() >= RK_Op_ProgramIROp_LabelOp && (v)->getKind() <= RK_Op_ProgramIROp_LabelOp \ #define RTTI_RK_OP_PROGRAMIROP_EXITOP_IMPL(v) \ - return (v)->getKind() >= RK_Op_ProgramIROp_ExitOp && (v)->getKind() <= RK_Op_ProgramIROp_ExitOp + return (v)->getKind() >= RK_Op_ProgramIROp_ExitOp && (v)->getKind() <= RK_Op_ProgramIROp_ExitOp \ #define RTTI_RK_OP_PROGRAMIROP_RETOP_IMPL(v) \ - return (v)->getKind() >= RK_Op_ProgramIROp_RetOp && (v)->getKind() <= RK_Op_ProgramIROp_RetOp + return (v)->getKind() >= RK_Op_ProgramIROp_RetOp && (v)->getKind() <= RK_Op_ProgramIROp_RetOp \ #define RTTI_RK_OP_PROGRAMIROP_ENTRYPOINTOP_IMPL(v) \ - return (v)->getKind() >= RK_Op_ProgramIROp_EntryPointOp && (v)->getKind() <= RK_Op_ProgramIROp_EntryPointOp + return (v)->getKind() >= RK_Op_ProgramIROp_EntryPointOp && (v)->getKind() <= RK_Op_ProgramIROp_EntryPointOp \ #define RTTI_RK_OP_PROGRAMIROP_ALLOCOP_IMPL(v) \ - return (v)->getKind() >= RK_Op_ProgramIROp_AllocOp && (v)->getKind() <= RK_Op_ProgramIROp_AllocOp + return (v)->getKind() >= RK_Op_ProgramIROp_AllocOp && (v)->getKind() <= RK_Op_ProgramIROp_AllocOp \ #define RTTI_RK_OP_PROGRAMIROP_FREEOP_IMPL(v) \ - return (v)->getKind() >= RK_Op_ProgramIROp_FreeOp && (v)->getKind() <= RK_Op_ProgramIROp_FreeOp + return (v)->getKind() >= RK_Op_ProgramIROp_FreeOp && (v)->getKind() <= RK_Op_ProgramIROp_FreeOp \ #define RTTI_RK_OP_PROGRAMIROP_MODECONFIGOP_IMPL(v) \ - return (v)->getKind() >= RK_Op_ProgramIROp_ModeConfigOp && (v)->getKind() <= RK_Op_ProgramIROp_ModeConfigOp + return (v)->getKind() >= RK_Op_ProgramIROp_ModeConfigOp && (v)->getKind() <= RK_Op_ProgramIROp_ModeConfigOp \ #define RTTI_RK_OP_PROGRAMIROP_BINDOP_IMPL(v) \ - return (v)->getKind() >= RK_Op_ProgramIROp_BindOp && (v)->getKind() <= RK_Op_ProgramIROp_BindOp + return (v)->getKind() >= RK_Op_ProgramIROp_BindOp && (v)->getKind() <= RK_Op_ProgramIROp_BindOp \ -#define RTTI_RK_OP_DBGIROP_IMPL(v) return (v)->getKind() >= RK_Op_DbgIROp && (v)->getKind() <= RK_Op_DbgIROp_Last +#define RTTI_RK_OP_DBGIROP_IMPL(v) \ + return (v)->getKind() >= RK_Op_DbgIROp && (v)->getKind() <= RK_Op_DbgIROp_Last \ #define RTTI_RK_OP_DBGIROP_COMMENTOP_IMPL(v) \ - return (v)->getKind() >= RK_Op_DbgIROp_CommentOp && (v)->getKind() <= RK_Op_DbgIROp_CommentOp + return (v)->getKind() >= RK_Op_DbgIROp_CommentOp && (v)->getKind() <= RK_Op_DbgIROp_CommentOp \ #define RTTI_RK_OP_DBGIROP_HINTSOP_IMPL(v) \ - return (v)->getKind() >= RK_Op_DbgIROp_HintsOp && (v)->getKind() <= RK_Op_DbgIROp_HintsOp + return (v)->getKind() >= RK_Op_DbgIROp_HintsOp && (v)->getKind() <= RK_Op_DbgIROp_HintsOp \ -#define RTTI_RK_VAL_IMPL(v) return (v)->getKind() >= RK_Val && (v)->getKind() <= RK_Val_Last +#define RTTI_RK_VAL_IMPL(v) \ + return (v)->getKind() >= RK_Val && (v)->getKind() <= RK_Val_Last \ -#define RTTI_RK_VAL_LINALGIRVAL_IMPL(v) return (v)->getKind() >= RK_Val_LinalgIRVal && (v)->getKind() <= RK_Val_LinalgIRVal +#define RTTI_RK_VAL_LINALGIRVAL_IMPL(v) \ + return (v)->getKind() >= RK_Val_LinalgIRVal && (v)->getKind() <= RK_Val_LinalgIRVal \ -#define RTTI_RK_VAL_GRAPHIRVAL_IMPL(v) return (v)->getKind() >= RK_Val_GraphIRVal && (v)->getKind() <= RK_Val_GraphIRVal +#define RTTI_RK_VAL_GRAPHIRVAL_IMPL(v) \ + return (v)->getKind() >= RK_Val_GraphIRVal && (v)->getKind() <= RK_Val_GraphIRVal \ -#define RTTI_RK_VAL_TENSORIRVAL_IMPL(v) return (v)->getKind() >= RK_Val_TensorIRVal && (v)->getKind() <= RK_Val_TensorIRVal_Last +#define RTTI_RK_VAL_TENSORIRVAL_IMPL(v) \ + return (v)->getKind() >= RK_Val_TensorIRVal && (v)->getKind() <= RK_Val_TensorIRVal_Last \ #define RTTI_RK_VAL_TENSORIRVAL_TENSORVAL_IMPL(v) \ - return (v)->getKind() >= RK_Val_TensorIRVal_TensorVal && (v)->getKind() <= RK_Val_TensorIRVal_TensorVal + return (v)->getKind() >= RK_Val_TensorIRVal_TensorVal && (v)->getKind() <= RK_Val_TensorIRVal_TensorVal \ -#define RTTI_RK_VAL_BUILTINIRVAL_IMPL(v) return (v)->getKind() >= RK_Val_BuiltinIRVal && (v)->getKind() <= RK_Val_BuiltinIRVal +#define RTTI_RK_VAL_BUILTINIRVAL_IMPL(v) \ + return (v)->getKind() >= RK_Val_BuiltinIRVal && (v)->getKind() <= RK_Val_BuiltinIRVal \ #define RTTI_RK_VAL_CONTROLFLOWIRVAL_IMPL(v) \ - return (v)->getKind() >= RK_Val_ControlFlowIRVal && (v)->getKind() <= RK_Val_ControlFlowIRVal + return (v)->getKind() >= RK_Val_ControlFlowIRVal && (v)->getKind() <= RK_Val_ControlFlowIRVal \ -#define RTTI_RK_VAL_PROGRAMIRVAL_IMPL(v) return (v)->getKind() >= RK_Val_ProgramIRVal && (v)->getKind() <= RK_Val_ProgramIRVal +#define RTTI_RK_VAL_PROGRAMIRVAL_IMPL(v) \ + return (v)->getKind() >= RK_Val_ProgramIRVal && (v)->getKind() <= RK_Val_ProgramIRVal \ -#define RTTI_RK_VAL_DBGIRVAL_IMPL(v) return (v)->getKind() >= RK_Val_DbgIRVal && (v)->getKind() <= RK_Val_DbgIRVal +#define RTTI_RK_VAL_DBGIRVAL_IMPL(v) \ + return (v)->getKind() >= RK_Val_DbgIRVal && (v)->getKind() <= RK_Val_DbgIRVal \ -#define RTTI_RK_ATTR_IMPL(v) return (v)->getKind() >= RK_Attr && (v)->getKind() <= RK_Attr_Last +#define RTTI_RK_ATTR_IMPL(v) \ + return (v)->getKind() >= RK_Attr && (v)->getKind() <= RK_Attr_Last \ #define RTTI_RK_ATTR_LINALGIRATTR_IMPL(v) \ - return (v)->getKind() >= RK_Attr_LinalgIRAttr && (v)->getKind() <= RK_Attr_LinalgIRAttr_Last + return (v)->getKind() >= RK_Attr_LinalgIRAttr && (v)->getKind() <= RK_Attr_LinalgIRAttr_Last \ -#define RTTI_RK_ATTR_LINALGIRATTR_QUANTIZATIONANNOTATION_IMPL(v) \ - return (v)->getKind() >= RK_Attr_LinalgIRAttr_QuantizationAnnotation \ - && (v)->getKind() <= RK_Attr_LinalgIRAttr_QuantizationAnnotation +#define RTTI_RK_ATTR_LINALGIRATTR_QUANTIZATIONANNOTATION_IMPL(v) \ + return (v)->getKind() >= RK_Attr_LinalgIRAttr_QuantizationAnnotation && (v)->getKind() <= RK_Attr_LinalgIRAttr_QuantizationAnnotation \ #define RTTI_RK_ATTR_LINALGIRATTR_QUANTIZATIONSPEC_IMPL(v) \ - return (v)->getKind() >= RK_Attr_LinalgIRAttr_QuantizationSpec && (v)->getKind() <= RK_Attr_LinalgIRAttr_QuantizationSpec + return (v)->getKind() >= RK_Attr_LinalgIRAttr_QuantizationSpec && (v)->getKind() <= RK_Attr_LinalgIRAttr_QuantizationSpec \ -#define RTTI_RK_ATTR_GRAPHIRATTR_IMPL(v) return (v)->getKind() >= RK_Attr_GraphIRAttr && (v)->getKind() <= RK_Attr_GraphIRAttr +#define RTTI_RK_ATTR_GRAPHIRATTR_IMPL(v) \ + return (v)->getKind() >= RK_Attr_GraphIRAttr && (v)->getKind() <= RK_Attr_GraphIRAttr \ #define RTTI_RK_ATTR_TENSORIRATTR_IMPL(v) \ - return (v)->getKind() >= RK_Attr_TensorIRAttr && (v)->getKind() <= RK_Attr_TensorIRAttr + return (v)->getKind() >= RK_Attr_TensorIRAttr && (v)->getKind() <= RK_Attr_TensorIRAttr \ #define RTTI_RK_ATTR_BUILTINIRATTR_IMPL(v) \ - return (v)->getKind() >= RK_Attr_BuiltinIRAttr && (v)->getKind() <= RK_Attr_BuiltinIRAttr_Last + return (v)->getKind() >= RK_Attr_BuiltinIRAttr && (v)->getKind() <= RK_Attr_BuiltinIRAttr_Last \ #define RTTI_RK_ATTR_BUILTINIRATTR_INTATTR_IMPL(v) \ - return (v)->getKind() >= RK_Attr_BuiltinIRAttr_IntAttr && (v)->getKind() <= RK_Attr_BuiltinIRAttr_IntAttr + return (v)->getKind() >= RK_Attr_BuiltinIRAttr_IntAttr && (v)->getKind() <= RK_Attr_BuiltinIRAttr_IntAttr \ #define RTTI_RK_ATTR_BUILTINIRATTR_FPATTR_IMPL(v) \ - return (v)->getKind() >= RK_Attr_BuiltinIRAttr_FPAttr && (v)->getKind() <= RK_Attr_BuiltinIRAttr_FPAttr + return (v)->getKind() >= RK_Attr_BuiltinIRAttr_FPAttr && (v)->getKind() <= RK_Attr_BuiltinIRAttr_FPAttr \ #define RTTI_RK_ATTR_BUILTINIRATTR_STRATTR_IMPL(v) \ - return (v)->getKind() >= RK_Attr_BuiltinIRAttr_StrAttr && (v)->getKind() <= RK_Attr_BuiltinIRAttr_StrAttr + return (v)->getKind() >= RK_Attr_BuiltinIRAttr_StrAttr && (v)->getKind() <= RK_Attr_BuiltinIRAttr_StrAttr \ #define RTTI_RK_ATTR_BUILTINIRATTR_SYMBOLATTR_IMPL(v) \ - return (v)->getKind() >= RK_Attr_BuiltinIRAttr_SymbolAttr && (v)->getKind() <= RK_Attr_BuiltinIRAttr_SymbolAttr + return (v)->getKind() >= RK_Attr_BuiltinIRAttr_SymbolAttr && (v)->getKind() <= RK_Attr_BuiltinIRAttr_SymbolAttr \ #define RTTI_RK_ATTR_BUILTINIRATTR_BOOLATTR_IMPL(v) \ - return (v)->getKind() >= RK_Attr_BuiltinIRAttr_BoolAttr && (v)->getKind() <= RK_Attr_BuiltinIRAttr_BoolAttr + return (v)->getKind() >= RK_Attr_BuiltinIRAttr_BoolAttr && (v)->getKind() <= RK_Attr_BuiltinIRAttr_BoolAttr \ #define RTTI_RK_ATTR_BUILTINIRATTR_VECTORFP32ATTR_IMPL(v) \ - return (v)->getKind() >= RK_Attr_BuiltinIRAttr_VectorFP32Attr && (v)->getKind() <= RK_Attr_BuiltinIRAttr_VectorFP32Attr + return (v)->getKind() >= RK_Attr_BuiltinIRAttr_VectorFP32Attr && (v)->getKind() <= RK_Attr_BuiltinIRAttr_VectorFP32Attr \ #define RTTI_RK_ATTR_BUILTINIRATTR_VECTORINT16ATTR_IMPL(v) \ - return (v)->getKind() >= RK_Attr_BuiltinIRAttr_VectorInt16Attr && (v)->getKind() <= RK_Attr_BuiltinIRAttr_VectorInt16Attr + return (v)->getKind() >= RK_Attr_BuiltinIRAttr_VectorInt16Attr && (v)->getKind() <= RK_Attr_BuiltinIRAttr_VectorInt16Attr \ #define RTTI_RK_ATTR_BUILTINIRATTR_VECTORUINT16ATTR_IMPL(v) \ - return (v)->getKind() >= RK_Attr_BuiltinIRAttr_VectorUInt16Attr && (v)->getKind() <= RK_Attr_BuiltinIRAttr_VectorUInt16Attr + return (v)->getKind() >= RK_Attr_BuiltinIRAttr_VectorUInt16Attr && (v)->getKind() <= RK_Attr_BuiltinIRAttr_VectorUInt16Attr \ #define RTTI_RK_ATTR_CONTROLFLOWIRATTR_IMPL(v) \ - return (v)->getKind() >= RK_Attr_ControlFlowIRAttr && (v)->getKind() <= RK_Attr_ControlFlowIRAttr + return (v)->getKind() >= RK_Attr_ControlFlowIRAttr && (v)->getKind() <= RK_Attr_ControlFlowIRAttr \ #define RTTI_RK_ATTR_PROGRAMIRATTR_IMPL(v) \ - return (v)->getKind() >= RK_Attr_ProgramIRAttr && (v)->getKind() <= RK_Attr_ProgramIRAttr + return (v)->getKind() >= RK_Attr_ProgramIRAttr && (v)->getKind() <= RK_Attr_ProgramIRAttr \ -#define RTTI_RK_ATTR_DBGIRATTR_IMPL(v) return (v)->getKind() >= RK_Attr_DbgIRAttr && (v)->getKind() <= RK_Attr_DbgIRAttr +#define RTTI_RK_ATTR_DBGIRATTR_IMPL(v) \ + return (v)->getKind() >= RK_Attr_DbgIRAttr && (v)->getKind() <= RK_Attr_DbgIRAttr \ -} // namespace mllm::ir +} diff --git a/mllm/compile/ir/linalg/Op.cpp b/mllm/compile/ir/linalg/Op.cpp index 3b315e6f2..a5a8f1bff 100644 --- a/mllm/compile/ir/linalg/Op.cpp +++ b/mllm/compile/ir/linalg/Op.cpp @@ -84,6 +84,7 @@ LINALG_AOPS_DECL(OpTypes::kMultimodalRoPE, MultimodalRoPEOp); LINALG_AOPS_DECL(OpTypes::kVisionRoPE, VisionRoPEOp); LINALG_AOPS_DECL(OpTypes::kQuickGELU, QuickGELUOp); +LINALG_AOPS_DECL(OpTypes::kKimiDeltaAttention, KimiDeltaAttentionOp); LINALG_AOPS_DECL(OpTypes::kCopy, CopyOp); LINALG_AOPS_DECL(OpTypes::kClone, CloneOp); diff --git a/mllm/compile/ir/linalg/Op.hpp b/mllm/compile/ir/linalg/Op.hpp index f95911cc9..d3514bcae 100644 --- a/mllm/compile/ir/linalg/Op.hpp +++ b/mllm/compile/ir/linalg/Op.hpp @@ -49,6 +49,7 @@ class LayerNormOp; class MultimodalRoPEOp; class VisionRoPEOp; class QuickGELUOp; +class KimiDeltaAttentionOp; class CopyOp; class CloneOp; class NegOp; @@ -219,6 +220,7 @@ LINALG_AOPS_DEFINE(MultimodalRoPEOp, MULTIMODALROPEOP); LINALG_AOPS_DEFINE(VisionRoPEOp, VISIONROPEOP); LINALG_AOPS_DEFINE(QuickGELUOp, QUICKGELUOP); +LINALG_AOPS_DEFINE(KimiDeltaAttentionOp, KIMIDELTAATTENTIONOP); LINALG_AOPS_DEFINE(CopyOp, COPYOP); LINALG_AOPS_DEFINE(CloneOp, CLONEOP); diff --git a/mllm/compile/ir/rtti_kind_gen.py b/mllm/compile/ir/rtti_kind_gen.py index eda139259..41dd654e5 100644 --- a/mllm/compile/ir/rtti_kind_gen.py +++ b/mllm/compile/ir/rtti_kind_gen.py @@ -261,6 +261,7 @@ def define_lianlg_ir(ir: dict): op.derive(Cls("MultimodalRoPEOp")) op.derive(Cls("VisionRoPEOp")) op.derive(Cls("QuickGELUOp")) + op.derive(Cls("KimiDeltaAttentionOp")) op.derive(Cls("CopyOp")) op.derive(Cls("CloneOp")) op.derive(Cls("NegOp")) diff --git a/mllm/compile/jit/binary/LinalgIRSerialization.cpp b/mllm/compile/jit/binary/LinalgIRSerialization.cpp index 2a8138853..d005783f7 100644 --- a/mllm/compile/jit/binary/LinalgIRSerialization.cpp +++ b/mllm/compile/jit/binary/LinalgIRSerialization.cpp @@ -39,6 +39,7 @@ #include "mllm/core/aops/STFTOp.hpp" #include "mllm/core/aops/Conv1DOp.hpp" #include "mllm/core/aops/LayerNormOp.hpp" +#include "mllm/core/aops/KimiDeltaAttentionOp.hpp" #include "mllm/compile/jit/binary/LinalgIRSerialization.hpp" namespace mllm::jit::binary { @@ -87,6 +88,7 @@ nlohmann::json dumpLinalgIROptions(const ir::linalg::LinalgIROp::ptr_t& op) { CASE(MultimodalRoPE) CASE(VisionRoPE) CASE(QuickGELU) + CASE(KimiDeltaAttention) CASE(Copy) CASE(Clone) CASE(Neg) @@ -346,6 +348,11 @@ nlohmann::json dumpVisionRoPEOpIROptions(const ir::linalg::LinalgIROp::ptr_t& op nlohmann::json dumpQuickGELUOpIROptions(const ir::linalg::LinalgIROp::ptr_t& op) { return {}; } +nlohmann::json dumpKimiDeltaAttentionOpIROptions(const ir::linalg::LinalgIROp::ptr_t& op) { + const auto options = static_cast(op->getAOp())->options(); + return {{"safe_gate", options.safe_gate}, {"lower_bound", options.lower_bound}, {"state_inplace", options.state_inplace}}; +} + nlohmann::json dumpCopyOpIROptions(const ir::linalg::LinalgIROp::ptr_t& op) { return {}; } nlohmann::json dumpCloneOpIROptions(const ir::linalg::LinalgIROp::ptr_t& op) { return {}; } diff --git a/mllm/compile/jit/binary/LinalgIRSerialization.hpp b/mllm/compile/jit/binary/LinalgIRSerialization.hpp index ca3c32d48..e61299d22 100644 --- a/mllm/compile/jit/binary/LinalgIRSerialization.hpp +++ b/mllm/compile/jit/binary/LinalgIRSerialization.hpp @@ -51,6 +51,7 @@ nlohmann::json dumpLayerNormOpIROptions(const ir::linalg::LinalgIROp::ptr_t& op) nlohmann::json dumpMultimodalRoPEOpIROptions(const ir::linalg::LinalgIROp::ptr_t& op); nlohmann::json dumpVisionRoPEOpIROptions(const ir::linalg::LinalgIROp::ptr_t& op); nlohmann::json dumpQuickGELUOpIROptions(const ir::linalg::LinalgIROp::ptr_t& op); +nlohmann::json dumpKimiDeltaAttentionOpIROptions(const ir::linalg::LinalgIROp::ptr_t& op); nlohmann::json dumpCopyOpIROptions(const ir::linalg::LinalgIROp::ptr_t& op); nlohmann::json dumpCloneOpIROptions(const ir::linalg::LinalgIROp::ptr_t& op); nlohmann::json dumpNegOpIROptions(const ir::linalg::LinalgIROp::ptr_t& op); diff --git a/mllm/compile/jit/interpreter/AopsFromJson.cpp b/mllm/compile/jit/interpreter/AopsFromJson.cpp index f3ecdcbb4..37e655c55 100644 --- a/mllm/compile/jit/interpreter/AopsFromJson.cpp +++ b/mllm/compile/jit/interpreter/AopsFromJson.cpp @@ -32,6 +32,7 @@ #include "mllm/core/aops/LayerNormOp.hpp" #include "mllm/core/aops/VisionRoPEOp.hpp" #include "mllm/core/aops/QuickGELUOp.hpp" +#include "mllm/core/aops/KimiDeltaAttentionOp.hpp" #include "mllm/core/aops/CloneOp.hpp" #include "mllm/core/aops/ConcatOp.hpp" #include "mllm/core/aops/ReduceOps.hpp" @@ -129,6 +130,8 @@ BaseOp::ptr_t aopsFromJson(const nlohmann::json& json) { return __visionRopeFromJson(json); } else if (op_type == "QuickGELU") { return __quickGeluFromJson(json); + } else if (op_type == "KimiDeltaAttention") { + return __kimiDeltaAttentionFromJson(json); } else if (op_type == "Copy") { return __copyFromJson(json); } else if (op_type == "Clone") { @@ -793,6 +796,20 @@ BaseOp::ptr_t __quickGeluFromJson(const nlohmann::json& json) { return op; } +BaseOp::ptr_t __kimiDeltaAttentionFromJson(const nlohmann::json& json) { + aops::KimiDeltaAttentionOpOptions options; + if (json.contains("op_options")) { + const auto& opts = json["op_options"]; + if (opts.contains("safe_gate")) options.safe_gate = opts["safe_gate"]; + if (opts.contains("lower_bound")) options.lower_bound = opts["lower_bound"]; + if (opts.contains("state_inplace")) options.state_inplace = opts["state_inplace"]; + } + + DeviceTypes backend = DeviceTypes::kCPU; + if (json.contains("backend")) { backend = str2DeviceType(json["backend"]); } + return Context::instance().getBackend(backend)->createOp(OpTypes::kKimiDeltaAttention, options); +} + BaseOp::ptr_t __copyFromJson(const nlohmann::json& json) { aops::CopyOpOptions options; diff --git a/mllm/compile/jit/interpreter/AopsFromJson.hpp b/mllm/compile/jit/interpreter/AopsFromJson.hpp index 87d6f2e71..19e07b753 100644 --- a/mllm/compile/jit/interpreter/AopsFromJson.hpp +++ b/mllm/compile/jit/interpreter/AopsFromJson.hpp @@ -47,6 +47,7 @@ BaseOp::ptr_t __layerNormFromJson(const nlohmann::json& json); BaseOp::ptr_t __multimodalRopeFromJson(const nlohmann::json& json); BaseOp::ptr_t __visionRopeFromJson(const nlohmann::json& json); BaseOp::ptr_t __quickGeluFromJson(const nlohmann::json& json); +BaseOp::ptr_t __kimiDeltaAttentionFromJson(const nlohmann::json& json); BaseOp::ptr_t __copyFromJson(const nlohmann::json& json); BaseOp::ptr_t __cloneFromJson(const nlohmann::json& json); BaseOp::ptr_t __negFromJson(const nlohmann::json& json); diff --git a/mllm/core/OpTypes.hpp b/mllm/core/OpTypes.hpp index 76777fe0c..8ec7e95e8 100644 --- a/mllm/core/OpTypes.hpp +++ b/mllm/core/OpTypes.hpp @@ -106,6 +106,7 @@ enum class OpTypes : int32_t { kGroupedQueryAttention = 78, kParallelLinear = 79, kGatedDeltaRule = 80, + kKimiDeltaAttention = 81, // Dynamic Op Start for user to register there own ops. kDynamicOp_Start = 4096, @@ -195,6 +196,7 @@ inline std::string optype2Str(OpTypes type) { case OpTypes::kGroupedQueryAttention: return "GroupedQueryAttention"; case OpTypes::kParallelLinear: return "ParallelLinear"; case OpTypes::kGatedDeltaRule: return "GatedDeltaRule"; + case OpTypes::kKimiDeltaAttention: return "KimiDeltaAttention"; case OpTypes::kDynamicOp_Start: return "DynamicOp_Start"; case OpTypes::kOpType_End: return "OpType_End"; default: return "Unknown"; diff --git a/mllm/core/aops/KimiDeltaAttentionOp.cpp b/mllm/core/aops/KimiDeltaAttentionOp.cpp new file mode 100644 index 000000000..5d065af23 --- /dev/null +++ b/mllm/core/aops/KimiDeltaAttentionOp.cpp @@ -0,0 +1,83 @@ +// Copyright (c) MLLM Team. +// Licensed under the MIT License. + +#include "mllm/core/aops/KimiDeltaAttentionOp.hpp" + +#include +#include + +#include "mllm/compile/ir/linalg/Op.hpp" +#include "mllm/core/Tensor.hpp" +#include "mllm/utils/Common.hpp" + +namespace mllm::aops { + +KimiDeltaAttentionOp::KimiDeltaAttentionOp(const KimiDeltaAttentionOpOptions& options) + : BaseOp(OpTypes::kKimiDeltaAttention), options_(options) {} + +void KimiDeltaAttentionOp::load(const ParameterFile::ptr_t& ploader) { MLLM_EMPTY_SCOPE; } + +void KimiDeltaAttentionOp::trace(void* trace_context, const std::vector& inputs, std::vector& outputs) { + auto* ir_ctx = static_cast(trace_context); + auto i_irs = ir::tensor::wrapTensors2TensorIR(ir_ctx, inputs); + auto o_irs = ir::tensor::wrapTensors2TensorIR(ir_ctx, outputs); + ir_ctx->create(shared_from_this(), i_irs, o_irs); +} + +void KimiDeltaAttentionOp::forward(const std::vector& inputs, std::vector& outputs) { + NYI("KimiDeltaAttentionOp::forward not implemented in aops base."); +} + +void KimiDeltaAttentionOp::reshape(const std::vector& inputs, std::vector& outputs) { + if (inputs.size() != 8) { throw std::invalid_argument("KimiDeltaAttention expects 8 input tensors"); } + + const auto& q = inputs[0]; + const auto& k = inputs[1]; + const auto& v = inputs[2]; + const auto& gate = inputs[3]; + const auto& beta = inputs[4]; + const auto& a_log = inputs[5]; + const auto& dt_bias = inputs[6]; + const auto& state = inputs[7]; + if (q.rank() != 4 || k.shape() != q.shape() || v.shape() != q.shape() || gate.shape() != q.shape()) { + throw std::invalid_argument("KimiDeltaAttention q, k, v, and gate must share [B, S, H, D] shape"); + } + const auto batch = q.shape()[0]; + const auto sequence = q.shape()[1]; + const auto heads = q.shape()[2]; + const auto dim = q.shape()[3]; + if (beta.shape() != Tensor::shape_t{batch, sequence, heads}) { + throw std::invalid_argument("KimiDeltaAttention beta must have [B, S, H] shape"); + } + if (a_log.numel() != static_cast(heads) || dt_bias.numel() != static_cast(heads) * dim) { + throw std::invalid_argument("KimiDeltaAttention decay parameters must contain H and H * D values"); + } + if (state.shape() != Tensor::shape_t{batch, heads, dim, dim}) { + throw std::invalid_argument("KimiDeltaAttention state must have [B, H, D, D] shape"); + } + for (const auto& input : inputs) { + if (input.dtype() != kFloat32 || input.device() != q.device()) { + throw std::invalid_argument("KimiDeltaAttention requires float32 inputs on one device"); + } + } + if (options_.safe_gate && (!std::isfinite(options_.lower_bound) || options_.lower_bound >= 0.0F)) { + throw std::invalid_argument("KimiDeltaAttention safe-gate lower bound must be finite and negative"); + } + + outputs.emplace_back(Tensor::empty(q.shape(), q.dtype(), q.device())); + if (options_.state_inplace) { + outputs.emplace_back(state); + } else { + outputs.emplace_back(Tensor::empty(state.shape(), state.dtype(), state.device())); + } +} + +void KimiDeltaAttentionOp::setup(const std::vector& inputs, std::vector& outputs) { + if (options_.state_inplace) { + outputs[0].alloc(); + } else { + BaseOp::setup(inputs, outputs); + } +} + +} // namespace mllm::aops diff --git a/mllm/core/aops/KimiDeltaAttentionOp.hpp b/mllm/core/aops/KimiDeltaAttentionOp.hpp new file mode 100644 index 000000000..44fbe0da4 --- /dev/null +++ b/mllm/core/aops/KimiDeltaAttentionOp.hpp @@ -0,0 +1,33 @@ +// Copyright (c) MLLM Team. +// Licensed under the MIT License. + +#pragma once + +#include "mllm/core/BaseOp.hpp" +#include "mllm/core/ParameterFile.hpp" + +namespace mllm::aops { + +struct KimiDeltaAttentionOpOptions : public BaseOpOptions { + bool safe_gate = true; + float lower_bound = -5.0F; + bool state_inplace = false; +}; + +class KimiDeltaAttentionOp : public BaseOp { + public: + explicit KimiDeltaAttentionOp(const KimiDeltaAttentionOpOptions& options); + + void load(const ParameterFile::ptr_t& ploader) override; + void trace(void* trace_context, const std::vector& inputs, std::vector& outputs) override; + void forward(const std::vector& inputs, std::vector& outputs) override; + void reshape(const std::vector& inputs, std::vector& outputs) override; + void setup(const std::vector& inputs, std::vector& outputs) override; + + inline const KimiDeltaAttentionOpOptions& options() const { return options_; } + + protected: + KimiDeltaAttentionOpOptions options_; +}; + +} // namespace mllm::aops diff --git a/mllm/models/ling3/modeling_ling3.hpp b/mllm/models/ling3/modeling_ling3.hpp index c9f9cba9b..9c478bb5a 100644 --- a/mllm/models/ling3/modeling_ling3.hpp +++ b/mllm/models/ling3/modeling_ling3.hpp @@ -3,8 +3,6 @@ #pragma once -#include "mllm/backends/cpu/kernels/common/gdn/gated_delta_net.hpp" -#include "mllm/backends/cpu/kernels/common/kda/kimi_delta_attention.hpp" #include "mllm/mllm.hpp" #include "mllm/models/ARGeneration.hpp" #include "mllm/models/ling3/configuration_ling3.hpp" @@ -26,6 +24,10 @@ namespace mllm::models::ling3 { +// Model-local tensor preparation: Ling-3 uses an adjacent-pair checkpoint RoPE +// layout that must be converted to mllm's half-split attention layout. These +// helpers prepare RoPE/cache tensors only; reusable compute is dispatched +// through registered nn layers and functional operations below. inline Tensor makeLing3RoPEInvFreq(int rotary_dim, float rope_theta) { auto inv_freq = Tensor::empty({rotary_dim / 2}, kFloat32, kCPU).alloc(); auto* values = inv_freq.ptr(); @@ -168,6 +170,10 @@ class Ling3MoEGate final : public nn::Module { || weight_.weight().dtype() != kFloat32 || expert_bias_.weight().dtype() != kFloat32) { throw std::invalid_argument("Ling-3 MoE router requires float32 tensors with official shapes"); } + // Model-level orchestration: the official Ling grouped top-k rule includes + // expert bias and a top-2-per-group score, which the existing generic mllm + // MoE layers do not represent. Matmul and every expert MLP remain normal + // nn operations; this loop only produces routing ids and weights. hidden = hidden.view({-1, hidden_size_}); auto logits = nn::functional::matmul(hidden, weight_.weight(), false, true).contiguous(); const int tokens = hidden.shape()[0]; @@ -270,7 +276,8 @@ class Ling3SparseMoE final : public nn::Module { auto routed_output = Tensor::empty({tokens, top_k_, hidden_size}, kFloat32, kCPU).alloc(); const auto* id_values = topk_ids.ptr(); auto* routed_values = routed_output.ptr(); - // Keep each routed expert invocation at M=1. KAI's dynamic activation + // Model-level sparse dispatch: keep each routed expert invocation at M=1. + // KAI's dynamic activation // quantization is row-local, and this avoids platform-dependent grouped // prefill results while retaining the same token/expert routing contract. for (int token = 0; token < tokens; ++token) { @@ -293,6 +300,10 @@ class Ling3SparseMoE final : public nn::Module { class Ling3KimiDeltaAttention final : public nn::Module { public: + // The official checkpoint was validated against the current-first + // accumulation order (current tap first, then the K - 1 history taps). + static constexpr auto kConvAccumulationOrder = aops::CausalDepthwiseConv1DAccumulationOrder::kCurrentFirst; + Ling3KimiDeltaAttention() = default; Ling3KimiDeltaAttention(const std::string& name, const Ling3Config& config) : nn::Module(name) { hidden_size_ = config.hidden_size; @@ -306,12 +317,16 @@ class Ling3KimiDeltaAttention final : public nn::Module { q_proj_ = reg("q_proj", hidden_size_, projection_size_, false, config.linear_impl_type); k_proj_ = reg("k_proj", hidden_size_, projection_size_, false, config.linear_impl_type); v_proj_ = reg("v_proj", hidden_size_, projection_size_, false, config.linear_impl_type); - q_conv1d_ = reg("q_conv1d", projection_size_, projection_size_, conv_size_, 1, conv_size_ - 1, 1, - projection_size_, false); - k_conv1d_ = reg("k_conv1d", projection_size_, projection_size_, conv_size_, 1, conv_size_ - 1, 1, - projection_size_, false); - v_conv1d_ = reg("v_conv1d", projection_size_, projection_size_, conv_size_, 1, conv_size_ - 1, 1, - projection_size_, false); + // Stateful depthwise causal short convolutions. The registered operation + // owns the official `{q,k,v}_conv1d.weight` parameters and the explicit + // [B, C, K - 1] history; the current-first accumulation order reuses the + // existing GDN causal-convolution kernel. + q_conv1d_ = reg("q_conv1d", projection_size_, conv_size_, /*bias=*/false, + /*state_inplace=*/true, kConvAccumulationOrder); + k_conv1d_ = reg("k_conv1d", projection_size_, conv_size_, /*bias=*/false, + /*state_inplace=*/true, kConvAccumulationOrder); + v_conv1d_ = reg("v_conv1d", projection_size_, conv_size_, /*bias=*/false, + /*state_inplace=*/true, kConvAccumulationOrder); f_proj_ = reg("f_proj", hidden_size_, projection_size_, false, config.linear_impl_type); b_proj_ = reg("b_proj", hidden_size_, num_heads_, false, config.linear_impl_type); g_proj_ = reg("g_proj", hidden_size_, projection_size_, false, config.linear_impl_type); @@ -321,10 +336,13 @@ class Ling3KimiDeltaAttention final : public nn::Module { o_proj_ = reg("o_proj", projection_size_, hidden_size_, false, config.linear_impl_type); silu_ = reg("conv_activation"); sigmoid_ = reg("gate_activation"); + kda_ = reg("kda", safe_gate_, lower_bound_, /*state_inplace=*/true); } void resetState(int batch_size) { if (batch_size <= 0) { throw std::invalid_argument("Ling-3 KDA reset requires a positive batch size"); } + // Module-owned request state. State transitions themselves are explicit + // outputs of the registered causal-convolution and KDA operations. recurrent_state_ = Tensor::zeros({batch_size, num_heads_, head_dim_, head_dim_}, kFloat32, kCPU); q_conv_state_ = Tensor::zeros({batch_size, projection_size_, conv_size_ - 1}, kFloat32, kCPU); k_conv_state_ = Tensor::zeros({batch_size, projection_size_, conv_size_ - 1}, kFloat32, kCPU); @@ -342,34 +360,25 @@ class Ling3KimiDeltaAttention final : public nn::Module { auto v = v_proj_(hidden).contiguous(); auto gate_logits = f_proj_(hidden).contiguous(); auto beta = sigmoid_(b_proj_(hidden)).contiguous(); - const auto q_weight = q_conv1d_.weight(); - const auto k_weight = k_conv1d_.weight(); - const auto v_weight = v_conv1d_.weight(); const auto a_log = A_log_.weight(); const auto dt_bias = dt_bias_.weight(); if (q.dtype() != kFloat32 || k.dtype() != kFloat32 || v.dtype() != kFloat32 || gate_logits.dtype() != kFloat32 - || beta.dtype() != kFloat32 || q_weight.dtype() != kFloat32 || k_weight.dtype() != kFloat32 - || v_weight.dtype() != kFloat32 || a_log.dtype() != kFloat32 || dt_bias.dtype() != kFloat32) { + || beta.dtype() != kFloat32 || a_log.dtype() != kFloat32 || dt_bias.dtype() != kFloat32) { throw std::invalid_argument("Ling-3 KDA requires float32 recurrent activations and parameters"); } - auto q_conv = Tensor::empty({batch, sequence, projection_size_}, kFloat32, kCPU).alloc(); - auto k_conv = Tensor::empty({batch, sequence, projection_size_}, kFloat32, kCPU).alloc(); - auto v_conv = Tensor::empty({batch, sequence, projection_size_}, kFloat32, kCPU).alloc(); - cpu::gdn::depthwiseCausalConvF32(q.ptr(), q_weight.ptr(), q_conv_state_.ptr(), q_conv.ptr(), - batch, sequence, projection_size_, conv_size_); - cpu::gdn::depthwiseCausalConvF32(k.ptr(), k_weight.ptr(), k_conv_state_.ptr(), k_conv.ptr(), - batch, sequence, projection_size_, conv_size_); - cpu::gdn::depthwiseCausalConvF32(v.ptr(), v_weight.ptr(), v_conv_state_.ptr(), v_conv.ptr(), - batch, sequence, projection_size_, conv_size_); + auto [q_conv, updated_q_conv_state] = q_conv1d_(q, q_conv_state_); + auto [k_conv, updated_k_conv_state] = k_conv1d_(k, k_conv_state_); + auto [v_conv, updated_v_conv_state] = v_conv1d_(v, v_conv_state_); + q_conv_state_ = std::move(updated_q_conv_state); + k_conv_state_ = std::move(updated_k_conv_state); + v_conv_state_ = std::move(updated_v_conv_state); q = silu_(q_conv).view({batch, sequence, num_heads_, head_dim_}).contiguous(); k = silu_(k_conv).view({batch, sequence, num_heads_, head_dim_}).contiguous(); v = silu_(v_conv).view({batch, sequence, num_heads_, head_dim_}).contiguous(); gate_logits = gate_logits.view({batch, sequence, num_heads_, head_dim_}).contiguous(); - auto output = Tensor::empty({batch, sequence, num_heads_, head_dim_}, kFloat32, kCPU).alloc(); - cpu::kda::kimiDeltaAttentionF32(q.ptr(), k.ptr(), v.ptr(), gate_logits.ptr(), beta.ptr(), - a_log.ptr(), dt_bias.ptr(), recurrent_state_.ptr(), - output.ptr(), batch, sequence, num_heads_, head_dim_, safe_gate_, lower_bound_); + auto [output, updated_state] = kda_(q, k, v, gate_logits, beta, a_log, dt_bias, recurrent_state_); + recurrent_state_ = std::move(updated_state); output = output.view({batch * sequence * num_heads_, head_dim_}); auto output_gate = sigmoid_(g_proj_(hidden).view({batch * sequence * num_heads_, head_dim_})); @@ -387,13 +396,14 @@ class Ling3KimiDeltaAttention final : public nn::Module { bool safe_gate_ = true; float lower_bound_ = -5.0F; nn::Linear q_proj_, k_proj_, v_proj_; - nn::Conv1D q_conv1d_, k_conv1d_, v_conv1d_; + nn::CausalDepthwiseConv1D q_conv1d_, k_conv1d_, v_conv1d_; nn::Linear f_proj_, b_proj_, g_proj_; nn::Param A_log_, dt_bias_; nn::RMSNorm o_norm_; nn::Linear o_proj_; nn::SiLU silu_; nn::Sigmoid sigmoid_; + nn::KimiDeltaAttention kda_; Tensor recurrent_state_, q_conv_state_, k_conv_state_, v_conv_state_; }; @@ -619,6 +629,8 @@ class Ling3ForCausalLM final : public ARGeneration, public nn::Module { throw std::invalid_argument("Ling-3 sequence exceeds the configured KV-cache capacity"); } + // Autoregressive input orchestration; position ids feed the model-local + // checkpoint-layout RoPE preparation above. auto position_ids = Tensor::empty({1, sequence_length}, kInt64, kCPU).alloc(); auto* positions = position_ids.ptr(); if (input.count("position_ids") != 0 && sequence_length == 1) { diff --git a/mllm/nn/Functional.cpp b/mllm/nn/Functional.cpp index 5f78c13eb..7f8bd5ce3 100644 --- a/mllm/nn/Functional.cpp +++ b/mllm/nn/Functional.cpp @@ -27,6 +27,7 @@ #include "mllm/core/aops/RadixAttnWithSinkAndSwaDiffDimOp.hpp" #include "mllm/core/aops/WhereOp.hpp" #include "mllm/core/aops/GatedDeltaRuleOp.hpp" +#include "mllm/core/aops/KimiDeltaAttentionOp.hpp" #include "mllm/engine/Context.hpp" namespace mllm::nn::functional { @@ -250,6 +251,16 @@ mllm::Tensor sigmoid(const Tensor& x) { return ctx.buildOpAndSubmitTask(OpTypes::kSigmoid, aops::SigmoidOpOptions{}, {x})[0]; } +std::array kimiDeltaAttention(const Tensor& q, const Tensor& k, const Tensor& v, const Tensor& gate_logits, + const Tensor& beta, const Tensor& a_log, const Tensor& dt_bias, const Tensor& state, + bool safe_gate, float lower_bound, bool state_inplace) { + auto outputs = Context::instance().buildOpAndSubmitTask( + OpTypes::kKimiDeltaAttention, + aops::KimiDeltaAttentionOpOptions{.safe_gate = safe_gate, .lower_bound = lower_bound, .state_inplace = state_inplace}, + {q, k, v, gate_logits, beta, a_log, dt_bias, state}); + return {outputs[0], outputs[1]}; +} + mllm::Tensor gather(const Tensor& x, int dim, const Tensor& indices) { auto& ctx = mllm::Context::instance(); return ctx.buildOpAndSubmitTask(OpTypes::kGather, aops::GatherOpOptions{.dim = dim}, {x, indices})[0]; diff --git a/mllm/nn/Functional.hpp b/mllm/nn/Functional.hpp index e7c1e135c..f75daac91 100644 --- a/mllm/nn/Functional.hpp +++ b/mllm/nn/Functional.hpp @@ -14,6 +14,7 @@ #include "mllm/core/aops/InterpolateOp.hpp" #include "mllm/core/aops/GroupedQueryAttentionOp.hpp" #include "mllm/core/aops/GatedDeltaRuleOp.hpp" +#include "mllm/core/aops/KimiDeltaAttentionOp.hpp" #include "mllm/core/aops/RadixAttnWithSinkAndSwaDiffDimOp.hpp" #include "mllm/engine/Context.hpp" @@ -178,6 +179,10 @@ mllm::Tensor where(const Tensor& mask, const Tensor& original, const Tensor& v); mllm::Tensor sigmoid(const Tensor& x); +std::array kimiDeltaAttention(const Tensor& q, const Tensor& k, const Tensor& v, const Tensor& gate_logits, + const Tensor& beta, const Tensor& a_log, const Tensor& dt_bias, const Tensor& state, + bool safe_gate = true, float lower_bound = -5.0F, bool state_inplace = false); + mllm::Tensor gather(const Tensor& x, int dim, const Tensor& indices); } // namespace mllm::nn::functional diff --git a/mllm/nn/Nn.hpp b/mllm/nn/Nn.hpp index 3b0ab764a..a05181700 100644 --- a/mllm/nn/Nn.hpp +++ b/mllm/nn/Nn.hpp @@ -36,3 +36,4 @@ #include "mllm/nn/layers/LayerNorm2D.hpp" // IWYU pragma: export #include "mllm/nn/layers/CausalDepthwiseConv1D.hpp" // IWYU pragma: export #include "mllm/nn/layers/GatedDeltaRule.hpp" // IWYU pragma: export +#include "mllm/nn/layers/KimiDeltaAttention.hpp" // IWYU pragma: export diff --git a/mllm/nn/layers/KimiDeltaAttention.cpp b/mllm/nn/layers/KimiDeltaAttention.cpp new file mode 100644 index 000000000..931c20d3b --- /dev/null +++ b/mllm/nn/layers/KimiDeltaAttention.cpp @@ -0,0 +1,18 @@ +// Copyright (c) MLLM Team. +// Licensed under the MIT License. + +#include "mllm/nn/layers/KimiDeltaAttention.hpp" + +namespace mllm::nn { + +KimiDeltaAttention::KimiDeltaAttention() : Layer(OpTypes::kKimiDeltaAttention, aops::KimiDeltaAttentionOpOptions{}) {} + +KimiDeltaAttention::KimiDeltaAttention(const aops::KimiDeltaAttentionOpOptions& options) + : Layer(OpTypes::kKimiDeltaAttention, options) {} + +KimiDeltaAttention::KimiDeltaAttention(bool safe_gate, float lower_bound, bool state_inplace) + : Layer(OpTypes::kKimiDeltaAttention, + aops::KimiDeltaAttentionOpOptions{ + .safe_gate = safe_gate, .lower_bound = lower_bound, .state_inplace = state_inplace}) {} + +} // namespace mllm::nn diff --git a/mllm/nn/layers/KimiDeltaAttention.hpp b/mllm/nn/layers/KimiDeltaAttention.hpp new file mode 100644 index 000000000..1e25fa972 --- /dev/null +++ b/mllm/nn/layers/KimiDeltaAttention.hpp @@ -0,0 +1,20 @@ +// Copyright (c) MLLM Team. +// Licensed under the MIT License. + +#pragma once + +#include "mllm/core/aops/KimiDeltaAttentionOp.hpp" +#include "mllm/nn/Layer.hpp" + +namespace mllm::nn { + +class KimiDeltaAttention : public Layer { + public: + KimiDeltaAttention(); + explicit KimiDeltaAttention(const aops::KimiDeltaAttentionOpOptions& options); + KimiDeltaAttention(bool safe_gate, float lower_bound, bool state_inplace = false); + + MLLM_LAYER_ANY_INPUTS_2_OUTPUTS_FORWARD +}; + +} // namespace mllm::nn diff --git a/tests/cpu/CMakeLists.txt b/tests/cpu/CMakeLists.txt index bcf988994..2e433c93c 100644 --- a/tests/cpu/CMakeLists.txt +++ b/tests/cpu/CMakeLists.txt @@ -2,23 +2,6 @@ add_executable(Mllm-Test-CPUKernel KernelTest.cpp) target_link_libraries(Mllm-Test-CPUKernel PRIVATE gtest_main MllmRT MllmCPUBackend) target_include_directories(Mllm-Test-CPUKernel PRIVATE ${MLLM_INCLUDE_DIR}) -add_executable(Mllm-Test-Ling3-KDA Ling3KDATest.cpp) -target_link_libraries(Mllm-Test-Ling3-KDA PRIVATE gtest_main MllmCPUBackend) -target_include_directories(Mllm-Test-Ling3-KDA PRIVATE ${MLLM_INCLUDE_DIR}) - -add_executable(Mllm-Test-Ling3-Config Ling3ConfigTest.cpp) -target_link_libraries(Mllm-Test-Ling3-Config PRIVATE gtest_main MllmCPUBackend) -target_include_directories(Mllm-Test-Ling3-Config PRIVATE ${MLLM_INCLUDE_DIR}) -target_compile_definitions(Mllm-Test-Ling3-Config PRIVATE LING3_EXAMPLE_DIR="${CMAKE_CURRENT_SOURCE_DIR}/../../examples/ling3") - -add_executable(Mllm-Test-Ling3-RoPE Ling3RoPETest.cpp) -target_link_libraries(Mllm-Test-Ling3-RoPE PRIVATE gtest_main MllmCPUBackend) -target_include_directories(Mllm-Test-Ling3-RoPE PRIVATE ${MLLM_INCLUDE_DIR}) - -add_executable(Mllm-Test-Ling3-Tokenizer Ling3TokenizerTest.cpp) -target_link_libraries(Mllm-Test-Ling3-Tokenizer PRIVATE gtest_main MllmCPUBackend) -target_include_directories(Mllm-Test-Ling3-Tokenizer PRIVATE ${MLLM_INCLUDE_DIR}) - add_executable(Mllm-Test-KaiW4A32Pack KaiW4A32PackTest.cpp) target_link_libraries(Mllm-Test-KaiW4A32Pack PRIVATE gtest_main MllmCPUBackend) target_include_directories(Mllm-Test-KaiW4A32Pack PRIVATE ${MLLM_INCLUDE_DIR}) @@ -50,3 +33,20 @@ add_test( Mllm-Test-CPUKernel --gtest_filter=CausalDepthwiseConvKernelTest.*:CausalDepthwiseConvCurrentFirstKernelTest.*:GatedDeltaRuleKernelTest.*) set_tests_properties(CPUKernelFocused PROPERTIES LABELS cpu-kernel) + +add_executable(Mllm-Test-Ling3-KDA Ling3KDATest.cpp) +target_link_libraries(Mllm-Test-Ling3-KDA PRIVATE gtest_main MllmCPUBackend) +target_include_directories(Mllm-Test-Ling3-KDA PRIVATE ${MLLM_INCLUDE_DIR}) + +add_executable(Mllm-Test-Ling3-Config Ling3ConfigTest.cpp) +target_link_libraries(Mllm-Test-Ling3-Config PRIVATE gtest_main MllmCPUBackend) +target_include_directories(Mllm-Test-Ling3-Config PRIVATE ${MLLM_INCLUDE_DIR}) +target_compile_definitions(Mllm-Test-Ling3-Config PRIVATE LING3_EXAMPLE_DIR="${CMAKE_CURRENT_SOURCE_DIR}/../../examples/ling3") + +add_executable(Mllm-Test-Ling3-RoPE Ling3RoPETest.cpp) +target_link_libraries(Mllm-Test-Ling3-RoPE PRIVATE gtest_main MllmCPUBackend) +target_include_directories(Mllm-Test-Ling3-RoPE PRIVATE ${MLLM_INCLUDE_DIR}) + +add_executable(Mllm-Test-Ling3-Tokenizer Ling3TokenizerTest.cpp) +target_link_libraries(Mllm-Test-Ling3-Tokenizer PRIVATE gtest_main MllmCPUBackend) +target_include_directories(Mllm-Test-Ling3-Tokenizer PRIVATE ${MLLM_INCLUDE_DIR}) diff --git a/tests/cpu/Ling3KDATest.cpp b/tests/cpu/Ling3KDATest.cpp index b8488845a..596423462 100644 --- a/tests/cpu/Ling3KDATest.cpp +++ b/tests/cpu/Ling3KDATest.cpp @@ -2,6 +2,13 @@ // Licensed under the MIT License. #include "mllm/backends/cpu/kernels/common/kda/kimi_delta_attention.hpp" +#include "mllm/compile/ir/Trace.hpp" +#include "mllm/compile/ir/linalg/Op.hpp" +#include "mllm/compile/jit/binary/LinalgIRSerialization.hpp" +#include "mllm/compile/jit/interpreter/AopsFromJson.hpp" +#include "mllm/core/aops/KimiDeltaAttentionOp.hpp" +#include "mllm/mllm.hpp" +#include "mllm/nn/Functional.hpp" #include @@ -10,6 +17,8 @@ #include #include +#include + namespace { float sigmoid(float value) { return 1.0F / (1.0F + std::exp(-value)); } @@ -72,6 +81,15 @@ std::vector makeValues(std::size_t count, float scale, float offset = 0.0 return values; } +void ensureCpuContext() { + static const bool kContextInitialized = [] { + mllm::initializeContext(); + mllm::Context::instance().setCpuOpThreads(1); + return true; + }(); + (void)kContextInitialized; +} + TEST(Ling3KDA, MatchesSafeGateReference) { constexpr int kBatch = 2; constexpr int kSequence = 5; @@ -146,4 +164,149 @@ TEST(Ling3KDA, RejectsInvalidSafeGateBound) { std::invalid_argument); } +TEST(Ling3KDA, FunctionalDispatchReturnsOutputAndUpdatedState) { + ensureCpuContext(); + constexpr int kBatch = 1; + constexpr int kSequence = 3; + constexpr int kHeads = 2; + constexpr int kDim = 4; + const std::size_t vector_count = static_cast(kBatch) * kSequence * kHeads * kDim; + const std::size_t state_count = static_cast(kBatch) * kHeads * kDim * kDim; + + const auto q = makeValues(vector_count, 0.017F); + const auto k = makeValues(vector_count, -0.013F, 0.02F); + const auto v = makeValues(vector_count, 0.011F, -0.03F); + const auto gate = makeValues(vector_count, 0.019F, 0.1F); + const auto beta = makeValues(static_cast(kBatch) * kSequence * kHeads, 0.01F, 0.45F); + const auto a_log = makeValues(kHeads, 0.02F, 0.3F); + const auto dt_bias = makeValues(static_cast(kHeads) * kDim, 0.015F, -0.1F); + const auto initial_state = makeValues(state_count, 0.003F); + auto expected_state = initial_state; + std::vector expected_output(vector_count, 0.0F); + referenceKDA(q, k, v, gate, beta, a_log, dt_bias, expected_state, expected_output, kBatch, kSequence, kHeads, kDim, true, + -5.0F); + auto state_input = mllm::Tensor::fromVector(initial_state, {kBatch, kHeads, kDim, kDim}); + + auto [output, updated_state] = mllm::nn::functional::kimiDeltaAttention( + mllm::Tensor::fromVector(q, {kBatch, kSequence, kHeads, kDim}), + mllm::Tensor::fromVector(k, {kBatch, kSequence, kHeads, kDim}), + mllm::Tensor::fromVector(v, {kBatch, kSequence, kHeads, kDim}), + mllm::Tensor::fromVector(gate, {kBatch, kSequence, kHeads, kDim}), + mllm::Tensor::fromVector(beta, {kBatch, kSequence, kHeads}), mllm::Tensor::fromVector(a_log, {kHeads}), + mllm::Tensor::fromVector(dt_bias, {kHeads * kDim}), state_input, true, -5.0F); + + const auto actual_output = output.toVector(); + const auto actual_state = updated_state.toVector(); + EXPECT_EQ(state_input.toVector(), initial_state); + for (std::size_t i = 0; i < vector_count; ++i) { EXPECT_NEAR(actual_output[i], expected_output[i], 2.0e-6F); } + for (std::size_t i = 0; i < state_count; ++i) { EXPECT_NEAR(actual_state[i], expected_state[i], 2.0e-6F); } +} + +TEST(Ling3KDA, FunctionalDispatchMatchesKernelAtTinyProductionGeometry) { + ensureCpuContext(); + constexpr int kBatch = 1; + constexpr int kSequence = 49; + constexpr int kHeads = 16; + constexpr int kDim = 128; + const std::size_t vector_count = static_cast(kBatch) * kSequence * kHeads * kDim; + const std::size_t state_count = static_cast(kBatch) * kHeads * kDim * kDim; + const auto q = makeValues(vector_count, 0.0017F); + const auto k = makeValues(vector_count, -0.0013F, 0.002F); + const auto v = makeValues(vector_count, 0.0011F, -0.003F); + const auto gate = makeValues(vector_count, 0.0019F, 0.01F); + const auto beta = makeValues(static_cast(kBatch) * kSequence * kHeads, 0.001F, 0.45F); + const auto a_log = makeValues(kHeads, 0.002F, 0.3F); + const auto dt_bias = makeValues(static_cast(kHeads) * kDim, 0.0015F, -0.1F); + const auto initial_state = makeValues(state_count, 0.0003F); + auto expected_state = initial_state; + std::vector expected_output(vector_count, 0.0F); + mllm::cpu::kda::kimiDeltaAttentionF32(q.data(), k.data(), v.data(), gate.data(), beta.data(), a_log.data(), dt_bias.data(), + expected_state.data(), expected_output.data(), kBatch, kSequence, kHeads, kDim, true, + -5.0F, 8); + + auto [output, updated_state] = mllm::nn::functional::kimiDeltaAttention( + mllm::Tensor::fromVector(q, {kBatch, kSequence, kHeads, kDim}), + mllm::Tensor::fromVector(k, {kBatch, kSequence, kHeads, kDim}), + mllm::Tensor::fromVector(v, {kBatch, kSequence, kHeads, kDim}), + mllm::Tensor::fromVector(gate, {kBatch, kSequence, kHeads, kDim}), + mllm::Tensor::fromVector(beta, {kBatch, kSequence, kHeads}), mllm::Tensor::fromVector(a_log, {kHeads}), + mllm::Tensor::fromVector(dt_bias, {kHeads * kDim}), mllm::Tensor::fromVector(initial_state, {kBatch, kHeads, kDim, kDim}), + true, -5.0F); + + EXPECT_EQ(output.toVector(), expected_output); + EXPECT_EQ(updated_state.toVector(), expected_state); +} + +TEST(Ling3KDA, FunctionalInplaceStateAliasesAndMutatesInput) { + ensureCpuContext(); + constexpr int kBatch = 1; + constexpr int kSequence = 3; + constexpr int kHeads = 2; + constexpr int kDim = 4; + const std::size_t vector_count = static_cast(kBatch) * kSequence * kHeads * kDim; + const std::size_t state_count = static_cast(kBatch) * kHeads * kDim * kDim; + const auto q = makeValues(vector_count, 0.017F); + const auto k = makeValues(vector_count, -0.013F, 0.02F); + const auto v = makeValues(vector_count, 0.011F, -0.03F); + const auto gate = makeValues(vector_count, 0.019F, 0.1F); + const auto beta = makeValues(static_cast(kBatch) * kSequence * kHeads, 0.01F, 0.45F); + const auto a_log = makeValues(kHeads, 0.02F, 0.3F); + const auto dt_bias = makeValues(static_cast(kHeads) * kDim, 0.015F, -0.1F); + const auto initial_state = makeValues(state_count, 0.003F); + auto expected_state = initial_state; + std::vector expected_output(vector_count, 0.0F); + mllm::cpu::kda::kimiDeltaAttentionF32(q.data(), k.data(), v.data(), gate.data(), beta.data(), a_log.data(), dt_bias.data(), + expected_state.data(), expected_output.data(), kBatch, kSequence, kHeads, kDim, true, + -5.0F, 8); + auto state = mllm::Tensor::fromVector(initial_state, {kBatch, kHeads, kDim, kDim}); + + auto [output, updated_state] = mllm::nn::functional::kimiDeltaAttention( + mllm::Tensor::fromVector(q, {kBatch, kSequence, kHeads, kDim}), + mllm::Tensor::fromVector(k, {kBatch, kSequence, kHeads, kDim}), + mllm::Tensor::fromVector(v, {kBatch, kSequence, kHeads, kDim}), + mllm::Tensor::fromVector(gate, {kBatch, kSequence, kHeads, kDim}), + mllm::Tensor::fromVector(beta, {kBatch, kSequence, kHeads}), mllm::Tensor::fromVector(a_log, {kHeads}), + mllm::Tensor::fromVector(dt_bias, {kHeads * kDim}), state, true, -5.0F, true); + + EXPECT_EQ(updated_state.ptr(), state.ptr()); + EXPECT_EQ(output.toVector(), expected_output); + EXPECT_EQ(state.toVector(), expected_state); +} + +TEST(Ling3KDA, FunctionalDispatchProducesLinalgIR) { + ensureCpuContext(); + auto q = mllm::Tensor::empty({1, 2, 2, 4}, mllm::kFloat32, mllm::kCPU); + auto beta = mllm::Tensor::empty({1, 2, 2}, mllm::kFloat32, mllm::kCPU); + auto a_log = mllm::Tensor::empty({2}, mllm::kFloat32, mllm::kCPU); + auto dt_bias = mllm::Tensor::empty({8}, mllm::kFloat32, mllm::kCPU); + auto state = mllm::Tensor::empty({1, 2, 4, 4}, mllm::kFloat32, mllm::kCPU); + + mllm::ir::lowlevel::traceStart(); + auto outputs = mllm::nn::functional::kimiDeltaAttention(q, q, q, q, beta, a_log, dt_bias, state, true, -5.0F, true); + auto ir_context = mllm::ir::lowlevel::traceStop(); + + EXPECT_EQ(outputs[0].shape(), q.shape()); + EXPECT_EQ(outputs[1].shape(), state.shape()); + mllm::ir::linalg::LinalgIROp::ptr_t kda_ir; + for (const auto& op : ir_context->topLevelOp()->cast_()->getTopRegion()->ops()) { + if (op->isa_()) { + kda_ir = op->cast_(); + break; + } + } + ASSERT_NE(kda_ir, nullptr); + + const auto options = mllm::jit::binary::dumpLinalgIROptions(kda_ir); + EXPECT_EQ(options.at("safe_gate"), true); + EXPECT_FLOAT_EQ(options.at("lower_bound").get(), -5.0F); + EXPECT_EQ(options.at("state_inplace"), true); + const auto restored = + mllm::jit::interpreter::aopsFromJson(nlohmann::json{{"op_type", "KimiDeltaAttention"}, {"op_options", options}}); + const auto restored_kda = std::dynamic_pointer_cast(restored); + ASSERT_NE(restored_kda, nullptr); + EXPECT_EQ(restored_kda->options().safe_gate, true); + EXPECT_FLOAT_EQ(restored_kda->options().lower_bound, -5.0F); + EXPECT_EQ(restored_kda->options().state_inplace, true); +} + } // namespace From 240ded103ee41835fbc7fe338c00f1a1908f7ee3 Mon Sep 17 00:00:00 2001 From: Aharrypotter Date: Sun, 6 Sep 2026 11:23:23 +0800 Subject: [PATCH 3/4] refactor(ling3): adopt upstream test layout for the Ling stateful ops Split the branch-local Ling3KDATest into the layers that the repository now maintains separately (#704/#706): - tests/cpu/KimiDeltaAttentionKernelTest.hpp: scalar-reference fixture for the KDA kernel (both gate variants, NEON lane blocks and tails, bitwise prefill-vs-tokenwise and serial-vs-parallel checks, argument validation), registered in KernelTest.cpp and the CPUKernelFocused ctest filter. - tests/nn/KimiDeltaAttentionTest.cpp: public nn::KimiDeltaAttention contract through a Module (eager reference match including the 16x128 production head geometry, in-place vs copied state, chunked prefill/decode equivalence, invalid geometry/options, trace plus option serialization round trip), registered with add_test. - tests/models/ling3: config, tokenizer and RoPE tests with add_test registration, the `ling3` label, and an MLLM_LING3_EXAMPLE_DIR override for on-device runs. The causal-convolution contract is covered by the upstream tests/nn/CausalDepthwiseConv1DTest.cpp and the CausalDepthwiseConv kernel suites, so the branch-local copies are removed. --- tests/cpu/CMakeLists.txt | 20 +- tests/cpu/KernelTest.cpp | 32 +++ tests/cpu/KimiDeltaAttentionKernelTest.hpp | 249 +++++++++++++++++ tests/models/CMakeLists.txt | 1 + tests/models/ling3/CMakeLists.txt | 18 ++ tests/models/ling3/Ling3ConfigTest.cpp | 30 ++ tests/models/ling3/Ling3RoPETest.cpp | 56 ++++ tests/models/ling3/Ling3TokenizerTest.cpp | 55 ++++ tests/nn/CMakeLists.txt | 5 + tests/nn/KimiDeltaAttentionTest.cpp | 303 +++++++++++++++++++++ 10 files changed, 751 insertions(+), 18 deletions(-) create mode 100644 tests/cpu/KimiDeltaAttentionKernelTest.hpp create mode 100644 tests/models/ling3/CMakeLists.txt create mode 100644 tests/models/ling3/Ling3ConfigTest.cpp create mode 100644 tests/models/ling3/Ling3RoPETest.cpp create mode 100644 tests/models/ling3/Ling3TokenizerTest.cpp create mode 100644 tests/nn/KimiDeltaAttentionTest.cpp diff --git a/tests/cpu/CMakeLists.txt b/tests/cpu/CMakeLists.txt index 2e433c93c..027f16df2 100644 --- a/tests/cpu/CMakeLists.txt +++ b/tests/cpu/CMakeLists.txt @@ -31,22 +31,6 @@ add_test( NAME CPUKernelFocused COMMAND Mllm-Test-CPUKernel - --gtest_filter=CausalDepthwiseConvKernelTest.*:CausalDepthwiseConvCurrentFirstKernelTest.*:GatedDeltaRuleKernelTest.*) + --gtest_filter=CausalDepthwiseConvKernelTest.*:CausalDepthwiseConvCurrentFirstKernelTest.*:GatedDeltaRuleKernelTest.*:KimiDeltaAttentionKernelTest.* +) set_tests_properties(CPUKernelFocused PROPERTIES LABELS cpu-kernel) - -add_executable(Mllm-Test-Ling3-KDA Ling3KDATest.cpp) -target_link_libraries(Mllm-Test-Ling3-KDA PRIVATE gtest_main MllmCPUBackend) -target_include_directories(Mllm-Test-Ling3-KDA PRIVATE ${MLLM_INCLUDE_DIR}) - -add_executable(Mllm-Test-Ling3-Config Ling3ConfigTest.cpp) -target_link_libraries(Mllm-Test-Ling3-Config PRIVATE gtest_main MllmCPUBackend) -target_include_directories(Mllm-Test-Ling3-Config PRIVATE ${MLLM_INCLUDE_DIR}) -target_compile_definitions(Mllm-Test-Ling3-Config PRIVATE LING3_EXAMPLE_DIR="${CMAKE_CURRENT_SOURCE_DIR}/../../examples/ling3") - -add_executable(Mllm-Test-Ling3-RoPE Ling3RoPETest.cpp) -target_link_libraries(Mllm-Test-Ling3-RoPE PRIVATE gtest_main MllmCPUBackend) -target_include_directories(Mllm-Test-Ling3-RoPE PRIVATE ${MLLM_INCLUDE_DIR}) - -add_executable(Mllm-Test-Ling3-Tokenizer Ling3TokenizerTest.cpp) -target_link_libraries(Mllm-Test-Ling3-Tokenizer PRIVATE gtest_main MllmCPUBackend) -target_include_directories(Mllm-Test-Ling3-Tokenizer PRIVATE ${MLLM_INCLUDE_DIR}) diff --git a/tests/cpu/KernelTest.cpp b/tests/cpu/KernelTest.cpp index b3f3d4d99..9e1a6892f 100644 --- a/tests/cpu/KernelTest.cpp +++ b/tests/cpu/KernelTest.cpp @@ -630,6 +630,38 @@ TEST_F(GatedDeltaRuleKernelTest, ProductionGroupedHeadGeometry8LaneIsBitwiseStab /*repeats=*/24)); } +//===----------------------------------------------------------------------===// +// Kimi delta attention +//===----------------------------------------------------------------------===// +#include "KimiDeltaAttentionKernelTest.hpp" +TEST_F(KimiDeltaAttentionKernelTest, MatchesScalarReferenceAcrossGateVariants) { + EXPECT_NO_FATAL_FAILURE(testMatchesScalarReference({ + // Scalar tails only (D < 4) and exact NEON lane blocks, both gates. + {/*batch=*/1, /*sequence=*/1, /*heads=*/1, /*dim=*/3, /*safe_gate=*/true, /*lower_bound=*/-5.0F, /*threads=*/1}, + {/*batch=*/2, /*sequence=*/5, /*heads=*/3, /*dim=*/8, /*safe_gate=*/true, /*lower_bound=*/-5.0F, /*threads=*/1}, + {/*batch=*/2, /*sequence=*/5, /*heads=*/3, /*dim=*/8, /*safe_gate=*/false, /*lower_bound=*/0.0F, /*threads=*/1}, + {/*batch=*/1, /*sequence=*/4, /*heads=*/2, /*dim=*/6, /*safe_gate=*/true, /*lower_bound=*/-2.5F, /*threads=*/1}, + // Ling-3.0-tiny head geometry with the multi-threaded (batch, head) partition. + {/*batch=*/1, /*sequence=*/9, /*heads=*/16, /*dim=*/128, /*safe_gate=*/true, /*lower_bound=*/-5.0F, /*threads=*/8}, + })); +} + +TEST_F(KimiDeltaAttentionKernelTest, PrefillAndTokenwiseDecodeAreBitwiseEqual) { + EXPECT_NO_FATAL_FAILURE(testPrefillAndTokenwiseDecodeAreBitwiseEqual( + {/*batch=*/1, /*sequence=*/7, /*heads=*/2, /*dim=*/8, /*safe_gate=*/true, /*lower_bound=*/-5.0F, /*threads=*/1})); + EXPECT_NO_FATAL_FAILURE(testPrefillAndTokenwiseDecodeAreBitwiseEqual( + {/*batch=*/1, /*sequence=*/49, /*heads=*/16, /*dim=*/128, /*safe_gate=*/true, /*lower_bound=*/-5.0F, /*threads=*/8})); +} + +TEST_F(KimiDeltaAttentionKernelTest, ParallelLanesMatchSerialBitwise) { + EXPECT_NO_FATAL_FAILURE(testParallelLanesMatchSerialBitwise( + {/*batch=*/2, /*sequence=*/16, /*heads=*/16, /*dim=*/128, /*safe_gate=*/true, /*lower_bound=*/-5.0F, /*threads=*/8})); +} + +TEST_F(KimiDeltaAttentionKernelTest, RejectsNullBuffersAndInvalidGeometry) { + EXPECT_NO_FATAL_FAILURE(testRejectsNullBuffersAndInvalidGeometry()); +} + //===----------------------------------------------------------------------===// // Parallel linear //===----------------------------------------------------------------------===// diff --git a/tests/cpu/KimiDeltaAttentionKernelTest.hpp b/tests/cpu/KimiDeltaAttentionKernelTest.hpp new file mode 100644 index 000000000..b359cfa05 --- /dev/null +++ b/tests/cpu/KimiDeltaAttentionKernelTest.hpp @@ -0,0 +1,249 @@ +// Copyright (c) MLLM Team. +// Licensed under the MIT License. + +#pragma once + +// Focused oracle for the Kimi Delta Attention (KDA) recurrent kernel. +// +// The reference below is an independent scalar implementation of the frozen +// contract (L2-normalised q/k, 1/sqrt(D) query scaling, safe-gate or softplus +// log-decay, delta-rule state update). It is deliberately not routed through +// the production kernel, so the NEON row helpers inside kimiDeltaAttentionF32 +// cannot validate themselves. Output and final state are compared separately: +// an output-only comparison would miss a corrupted state that only shows up in +// the next chunk. + +#include + +#include +#include +#include +#include + +#include "mllm/backends/cpu/kernels/common/kda/kimi_delta_attention.hpp" +#include "KernelTestHelper.hpp" + +namespace kimi_delta_attention_kernel_test { + +using mllm::cpu::kda::kimiDeltaAttentionF32; + +struct Geometry { + int batch = 1; + int sequence = 1; + int heads = 1; + int dim = 1; + bool safe_gate = true; + float lower_bound = -5.0F; + int thread_count = 1; + + [[nodiscard]] std::size_t vectorCount() const { + return static_cast(batch) * sequence * heads * dim; + } + [[nodiscard]] std::size_t betaCount() const { return static_cast(batch) * sequence * heads; } + [[nodiscard]] std::size_t stateCount() const { return static_cast(batch) * heads * dim * dim; } +}; + +// Deterministic index-derived fill. No RNG, so every host reproduces the same +// bytes without carrying a seed through the evidence record. +inline float patternValue(std::size_t index, float scale, float offset) { + return offset + scale * static_cast(static_cast(index * 37U % 29U) - 14); +} + +inline std::vector makeBuffer(std::size_t count, float scale, float offset = 0.0F) { + std::vector values(count); + for (std::size_t index = 0; index < count; ++index) { values[index] = patternValue(index, scale, offset); } + return values; +} + +struct Inputs { + std::vector q, k, v, gate, beta, a_log, dt_bias, state; +}; + +inline Inputs makeInputs(const Geometry& geometry, float scale = 1.0F) { + Inputs inputs; + inputs.q = makeBuffer(geometry.vectorCount(), 0.017F * scale); + inputs.k = makeBuffer(geometry.vectorCount(), -0.013F * scale, 0.02F * scale); + inputs.v = makeBuffer(geometry.vectorCount(), 0.011F * scale, -0.03F * scale); + inputs.gate = makeBuffer(geometry.vectorCount(), 0.019F * scale, 0.1F * scale); + inputs.beta = makeBuffer(geometry.betaCount(), 0.01F * scale, 0.45F); + inputs.a_log = makeBuffer(static_cast(geometry.heads), 0.02F * scale, 0.3F); + inputs.dt_bias = makeBuffer(static_cast(geometry.heads) * geometry.dim, 0.015F * scale, -0.1F * scale); + inputs.state = makeBuffer(geometry.stateCount(), 0.003F * scale); + return inputs; +} + +inline float referenceSigmoid(float value) { return 1.0F / (1.0F + std::exp(-value)); } + +// Independent scalar reference for the frozen contract: +// q/k/v/gate [B, S, H, D], beta [B, S, H], a_log [H], dt_bias [H, D], +// state [B, H, D, D] updated in place, output [B, S, H, D]. +inline void referenceKimiDeltaAttention(const Inputs& inputs, std::vector& state, std::vector& output, + const Geometry& geometry) { + const int dim = geometry.dim; + const float query_dim_scale = 1.0F / std::sqrt(static_cast(dim)); + for (int b = 0; b < geometry.batch; ++b) { + for (int h = 0; h < geometry.heads; ++h) { + const std::size_t state_base = (static_cast(b) * geometry.heads + h) * dim * dim; + for (int s = 0; s < geometry.sequence; ++s) { + const std::size_t vector_base = ((static_cast(b) * geometry.sequence + s) * geometry.heads + h) * dim; + float q_norm_sq = 0.0F; + float k_norm_sq = 0.0F; + for (int d = 0; d < dim; ++d) { + q_norm_sq += inputs.q[vector_base + d] * inputs.q[vector_base + d]; + k_norm_sq += inputs.k[vector_base + d] * inputs.k[vector_base + d]; + } + const float q_scale = query_dim_scale / std::sqrt(q_norm_sq + 1.0e-6F); + const float k_scale = 1.0F / std::sqrt(k_norm_sq + 1.0e-6F); + std::vector normalized_q(dim); + std::vector normalized_k(dim); + std::vector prediction(dim, 0.0F); + for (int d = 0; d < dim; ++d) { + normalized_q[d] = inputs.q[vector_base + d] * q_scale; + normalized_k[d] = inputs.k[vector_base + d] * k_scale; + } + for (int kd = 0; kd < dim; ++kd) { + const float gate_input = inputs.gate[vector_base + kd] + inputs.dt_bias[h * dim + kd]; + const float log_decay = geometry.safe_gate + ? geometry.lower_bound * referenceSigmoid(std::exp(inputs.a_log[h]) * gate_input) + : -std::exp(inputs.a_log[h]) * std::log1p(std::exp(gate_input)); + const float decay = std::exp(log_decay); + for (int vd = 0; vd < dim; ++vd) { + const std::size_t state_index = state_base + kd * dim + vd; + state[state_index] *= decay; + prediction[vd] += normalized_k[kd] * state[state_index]; + } + } + const float beta = inputs.beta[(static_cast(b) * geometry.sequence + s) * geometry.heads + h]; + for (int vd = 0; vd < dim; ++vd) { + const float delta = beta * (inputs.v[vector_base + vd] - prediction[vd]); + for (int kd = 0; kd < dim; ++kd) { + const std::size_t state_index = state_base + kd * dim + vd; + state[state_index] += normalized_k[kd] * delta; + output[vector_base + vd] += normalized_q[kd] * state[state_index]; + } + } + } + } + } +} + +// Output and updated state must both match the scalar reference within a +// small tolerance: the NEON helpers reorder the D-lane accumulations. +inline void testMatchesScalarReference(const std::vector& geometries, float tolerance = 2.0e-6F) { + for (const auto& geometry : geometries) { + SCOPED_TRACE(::testing::Message() << "B=" << geometry.batch << " S=" << geometry.sequence << " H=" << geometry.heads + << " D=" << geometry.dim << " safe_gate=" << geometry.safe_gate + << " threads=" << geometry.thread_count); + const auto inputs = makeInputs(geometry); + auto expected_state = inputs.state; + auto actual_state = inputs.state; + std::vector expected_output(geometry.vectorCount(), 0.0F); + std::vector actual_output(geometry.vectorCount(), 0.0F); + + referenceKimiDeltaAttention(inputs, expected_state, expected_output, geometry); + kimiDeltaAttentionF32(inputs.q.data(), inputs.k.data(), inputs.v.data(), inputs.gate.data(), inputs.beta.data(), + inputs.a_log.data(), inputs.dt_bias.data(), actual_state.data(), actual_output.data(), + geometry.batch, geometry.sequence, geometry.heads, geometry.dim, geometry.safe_gate, + geometry.lower_bound, geometry.thread_count); + + for (std::size_t i = 0; i < expected_output.size(); ++i) { + ASSERT_NEAR(actual_output[i], expected_output[i], tolerance) << "output index " << i; + } + for (std::size_t i = 0; i < expected_state.size(); ++i) { + ASSERT_NEAR(actual_state[i], expected_state[i], tolerance) << "state index " << i; + } + } +} + +// One-shot prefill and token-by-token decode must be bitwise identical: the +// recurrence has no cross-token reduction, so any drift would be a state bug. +inline void testPrefillAndTokenwiseDecodeAreBitwiseEqual(const Geometry& geometry) { + ASSERT_EQ(geometry.batch, 1) << "tokenwise replay is defined for batch 1"; + const auto inputs = makeInputs(geometry); + std::vector prefill_state(geometry.stateCount(), 0.0F); + std::vector decode_state(geometry.stateCount(), 0.0F); + std::vector prefill_output(geometry.vectorCount(), 0.0F); + std::vector decode_output(geometry.vectorCount(), 0.0F); + + kimiDeltaAttentionF32(inputs.q.data(), inputs.k.data(), inputs.v.data(), inputs.gate.data(), inputs.beta.data(), + inputs.a_log.data(), inputs.dt_bias.data(), prefill_state.data(), prefill_output.data(), + geometry.batch, geometry.sequence, geometry.heads, geometry.dim, geometry.safe_gate, + geometry.lower_bound, geometry.thread_count); + const std::size_t token_width = static_cast(geometry.heads) * geometry.dim; + for (int token = 0; token < geometry.sequence; ++token) { + const std::size_t offset = static_cast(token) * token_width; + kimiDeltaAttentionF32(inputs.q.data() + offset, inputs.k.data() + offset, inputs.v.data() + offset, + inputs.gate.data() + offset, inputs.beta.data() + static_cast(token) * geometry.heads, + inputs.a_log.data(), inputs.dt_bias.data(), decode_state.data(), decode_output.data() + offset, + geometry.batch, 1, geometry.heads, geometry.dim, geometry.safe_gate, geometry.lower_bound, + geometry.thread_count); + } + + EXPECT_EQ(prefill_output, decode_output); + EXPECT_EQ(prefill_state, decode_state); +} + +// Lanes partition (batch, head) tasks and never share state rows, so a +// multi-threaded run must reproduce the serial run bitwise. +inline void testParallelLanesMatchSerialBitwise(const Geometry& geometry) { + ASSERT_GT(geometry.thread_count, 1); + const auto inputs = makeInputs(geometry, 0.1F); + auto serial_state = inputs.state; + auto parallel_state = inputs.state; + std::vector serial_output(geometry.vectorCount(), 0.0F); + std::vector parallel_output(geometry.vectorCount(), 0.0F); + + kimiDeltaAttentionF32(inputs.q.data(), inputs.k.data(), inputs.v.data(), inputs.gate.data(), inputs.beta.data(), + inputs.a_log.data(), inputs.dt_bias.data(), serial_state.data(), serial_output.data(), geometry.batch, + geometry.sequence, geometry.heads, geometry.dim, geometry.safe_gate, geometry.lower_bound, + /*thread_count=*/1); + kimiDeltaAttentionF32(inputs.q.data(), inputs.k.data(), inputs.v.data(), inputs.gate.data(), inputs.beta.data(), + inputs.a_log.data(), inputs.dt_bias.data(), parallel_state.data(), parallel_output.data(), + geometry.batch, geometry.sequence, geometry.heads, geometry.dim, geometry.safe_gate, + geometry.lower_bound, geometry.thread_count); + + EXPECT_EQ(serial_output, parallel_output); + EXPECT_EQ(serial_state, parallel_state); +} + +inline void testRejectsNullBuffersAndInvalidGeometry() { + std::vector values(8, 0.0F); + std::vector state(8, 0.0F); + const auto call = [&](const float* q, int batch, int sequence, int heads, int dim, bool safe_gate, float lower_bound, + int threads) { + kimiDeltaAttentionF32(q, values.data(), values.data(), values.data(), values.data(), values.data(), values.data(), + state.data(), values.data(), batch, sequence, heads, dim, safe_gate, lower_bound, threads); + }; + EXPECT_THROW(call(nullptr, 1, 1, 1, 2, true, -5.0F, 1), std::invalid_argument); + EXPECT_THROW(call(values.data(), 0, 1, 1, 2, true, -5.0F, 1), std::invalid_argument); + EXPECT_THROW(call(values.data(), 1, 1, 1, 2, true, -5.0F, 0), std::invalid_argument); + // The safe gate needs a finite, strictly negative lower bound. + EXPECT_THROW(call(values.data(), 1, 1, 1, 2, true, 0.0F, 1), std::invalid_argument); + EXPECT_THROW(call(values.data(), 1, 1, 1, 2, true, INFINITY, 1), std::invalid_argument); + // The softplus gate ignores the bound entirely. + EXPECT_NO_THROW(call(values.data(), 1, 1, 1, 2, false, 0.0F, 1)); +} + +} // namespace kimi_delta_attention_kernel_test + +class KimiDeltaAttentionKernelTest : public KernelTest { + public: + KimiDeltaAttentionKernelTest() = default; + ~KimiDeltaAttentionKernelTest() override = default; + + void testMatchesScalarReference(const std::vector& geometries) { + kimi_delta_attention_kernel_test::testMatchesScalarReference(geometries); + } + + void testPrefillAndTokenwiseDecodeAreBitwiseEqual(const kimi_delta_attention_kernel_test::Geometry& geometry) { + kimi_delta_attention_kernel_test::testPrefillAndTokenwiseDecodeAreBitwiseEqual(geometry); + } + + void testParallelLanesMatchSerialBitwise(const kimi_delta_attention_kernel_test::Geometry& geometry) { + kimi_delta_attention_kernel_test::testParallelLanesMatchSerialBitwise(geometry); + } + + void testRejectsNullBuffersAndInvalidGeometry() { + kimi_delta_attention_kernel_test::testRejectsNullBuffersAndInvalidGeometry(); + } +}; diff --git a/tests/models/CMakeLists.txt b/tests/models/CMakeLists.txt index c427145b8..e4ae8a325 100644 --- a/tests/models/CMakeLists.txt +++ b/tests/models/CMakeLists.txt @@ -1 +1,2 @@ add_subdirectory(qwen3_5) +add_subdirectory(ling3) diff --git a/tests/models/ling3/CMakeLists.txt b/tests/models/ling3/CMakeLists.txt new file mode 100644 index 000000000..e1c701546 --- /dev/null +++ b/tests/models/ling3/CMakeLists.txt @@ -0,0 +1,18 @@ +add_executable(Mllm-Test-Ling3-Config Ling3ConfigTest.cpp) +target_link_libraries(Mllm-Test-Ling3-Config PRIVATE gtest_main MllmCPUBackend) +target_include_directories(Mllm-Test-Ling3-Config PRIVATE ${MLLM_INCLUDE_DIR}) +target_compile_definitions(Mllm-Test-Ling3-Config + PRIVATE LING3_EXAMPLE_DIR="${CMAKE_CURRENT_SOURCE_DIR}/../../../examples/ling3") + +add_executable(Mllm-Test-Ling3-Tokenizer Ling3TokenizerTest.cpp) +target_link_libraries(Mllm-Test-Ling3-Tokenizer PRIVATE gtest_main MllmCPUBackend) +target_include_directories(Mllm-Test-Ling3-Tokenizer PRIVATE ${MLLM_INCLUDE_DIR}) + +add_executable(Mllm-Test-Ling3-RoPE Ling3RoPETest.cpp) +target_link_libraries(Mllm-Test-Ling3-RoPE PRIVATE gtest_main MllmCPUBackend) +target_include_directories(Mllm-Test-Ling3-RoPE PRIVATE ${MLLM_INCLUDE_DIR}) + +add_test(NAME Ling3ConfigFocused COMMAND Mllm-Test-Ling3-Config) +add_test(NAME Ling3TokenizerFocused COMMAND Mllm-Test-Ling3-Tokenizer) +add_test(NAME Ling3RoPEFocused COMMAND Mllm-Test-Ling3-RoPE) +set_tests_properties(Ling3ConfigFocused Ling3TokenizerFocused Ling3RoPEFocused PROPERTIES LABELS ling3) diff --git a/tests/models/ling3/Ling3ConfigTest.cpp b/tests/models/ling3/Ling3ConfigTest.cpp new file mode 100644 index 000000000..f90a770c0 --- /dev/null +++ b/tests/models/ling3/Ling3ConfigTest.cpp @@ -0,0 +1,30 @@ +// Copyright (c) MLLM Team. +// Licensed under the MIT License. + +#include "mllm/models/ling3/configuration_ling3.hpp" + +#include + +#include +#include + +namespace { + +auto exampleDir() -> std::string { + const char* example_dir_override = std::getenv("MLLM_LING3_EXAMPLE_DIR"); + return example_dir_override == nullptr ? std::string(LING3_EXAMPLE_DIR) : std::string(example_dir_override); +} + +std::string ling3RuntimeConfigPath() { return exampleDir() + "/config_tiny_w4a32_kai.json"; } + +} // namespace + +TEST(Ling3Config, AcceptsPinnedTinyRuntimeContract) { + const mllm::models::ling3::Ling3Config config(ling3RuntimeConfigPath()); + EXPECT_TRUE(mllm::models::ling3::hasOfficialLing3TinyArchitecture(config)); + EXPECT_EQ(config.numFullAttentionLayers(), 6); + EXPECT_EQ(config.numKDALayers(), 18); + for (int layer = 0; layer < config.num_hidden_layers; ++layer) { + EXPECT_EQ(config.isFullAttentionLayer(layer), (layer + 1) % 4 == 0); + } +} diff --git a/tests/models/ling3/Ling3RoPETest.cpp b/tests/models/ling3/Ling3RoPETest.cpp new file mode 100644 index 000000000..91182e453 --- /dev/null +++ b/tests/models/ling3/Ling3RoPETest.cpp @@ -0,0 +1,56 @@ +// Copyright (c) MLLM Team. +// Licensed under the MIT License. + +#include "mllm/models/ling3/modeling_ling3.hpp" +#include "mllm/mllm.hpp" + +#include + +#include + +class Ling3RoPETest : public ::testing::Test { + protected: + static void SetUpTestSuite() { mllm::initializeContext(); } +}; + +TEST_F(Ling3RoPETest, AppliesOfficialAdjacentPairInterleave) { + auto input = mllm::Tensor::empty({1, 1, 2, 4}, mllm::kFloat32, mllm::kCPU).alloc(); + auto cos = mllm::Tensor::empty({1, 2, 4}, mllm::kFloat32, mllm::kCPU).alloc(); + auto sin = mllm::Tensor::empty({1, 2, 4}, mllm::kFloat32, mllm::kCPU).alloc(); + for (int index = 0; index < 8; ++index) { input.ptr()[index] = static_cast(index + 1); } + const float angles[] = {0.2F, -0.4F, 0.7F, 0.3F}; + for (int token = 0; token < 2; ++token) { + for (int pair = 0; pair < 2; ++pair) { + const float value = angles[token * 2 + pair]; + cos.ptr()[token * 4 + pair] = std::cos(value); + cos.ptr()[token * 4 + 2 + pair] = std::cos(value); + sin.ptr()[token * 4 + pair] = std::sin(value); + sin.ptr()[token * 4 + 2 + pair] = std::sin(value); + } + } + + const auto output = mllm::models::ling3::applyLing3InterleavedRoPE(input, cos, sin); + for (int token = 0; token < 2; ++token) { + for (int pair = 0; pair < 2; ++pair) { + const float even = input.ptr()[token * 4 + pair * 2]; + const float odd = input.ptr()[token * 4 + pair * 2 + 1]; + const float angle = angles[token * 2 + pair]; + EXPECT_NEAR(output.ptr()[token * 4 + pair], even * std::cos(angle) - odd * std::sin(angle), 1.0e-6F); + EXPECT_NEAR(output.ptr()[token * 4 + 2 + pair], odd * std::cos(angle) + even * std::sin(angle), 1.0e-6F); + } + } +} + +TEST_F(Ling3RoPETest, PadsMLAValuesWithoutChangingPayload) { + auto input = mllm::Tensor::empty({1, 2, 3, 4}, mllm::kFloat32, mllm::kCPU).alloc(); + for (int index = 0; index < 24; ++index) { input.ptr()[index] = static_cast(index + 1); } + const auto output = mllm::models::ling3::padLing3ValuesForCache(input, 6); + ASSERT_EQ(output.shape(), (mllm::Tensor::shape_t{1, 2, 3, 6})); + for (int vector = 0; vector < 6; ++vector) { + for (int dim = 0; dim < 4; ++dim) { + EXPECT_EQ(output.ptr()[vector * 6 + dim], input.ptr()[vector * 4 + dim]); + } + EXPECT_EQ(output.ptr()[vector * 6 + 4], 0.0F); + EXPECT_EQ(output.ptr()[vector * 6 + 5], 0.0F); + } +} diff --git a/tests/models/ling3/Ling3TokenizerTest.cpp b/tests/models/ling3/Ling3TokenizerTest.cpp new file mode 100644 index 000000000..5dc1ac5f1 --- /dev/null +++ b/tests/models/ling3/Ling3TokenizerTest.cpp @@ -0,0 +1,55 @@ +// Copyright (c) MLLM Team. +// Licensed under the MIT License. + +#include "mllm/models/ling3/tokenization_ling3.hpp" +#include "mllm/mllm.hpp" + +#include + +#include +#include +#include + +namespace { + +std::string officialTokenizerPath() { + const char* value = std::getenv("LING3_OFFICIAL_TOKENIZER"); + return value == nullptr ? std::string() : std::string(value); +} + +std::vector ids(mllm::models::ling3::Ling3Tokenizer& tokenizer, const std::string& text) { + const auto tensor = tokenizer.convert2Ids(tokenizer.tokenize(text)); + return {tensor.ptr(), tensor.ptr() + tensor.numel()}; +} + +class Ling3TokenizerTest : public ::testing::Test { + protected: + static void SetUpTestSuite() { mllm::initializeContext(); } +}; + +TEST_F(Ling3TokenizerTest, MatchesOfficialByteBPEAndNFCVectors) { + const auto path = officialTokenizerPath(); + if (path.empty()) { GTEST_SKIP() << "LING3_OFFICIAL_TOKENIZER is not set"; } + mllm::models::ling3::Ling3Tokenizer tokenizer(path); + EXPECT_EQ(ids(tokenizer, "Hello, world!"), (std::vector{14455, 11, 1931, 0})); + EXPECT_EQ(ids(tokenizer, "你好,世界!"), (std::vector{34355, 44291, 859})); + EXPECT_EQ(ids(tokenizer, "e\xCC\x81 café"), (std::vector{2900, 67656})); +} + +TEST_F(Ling3TokenizerTest, RendersOfficialSingleTurnThinkingTemplates) { + const auto path = officialTokenizerPath(); + if (path.empty()) { GTEST_SKIP() << "LING3_OFFICIAL_TOKENIZER is not set"; } + mllm::models::ling3::Ling3Tokenizer tokenizer(path); + auto enabled = tokenizer.convertMessage({"你好", "", true}).at("sequence"); + const std::vector enabled_expected = {157151, 90827, 157152, 14136, 5381, 6350, 366, 156895, 157151, 39, 116171, + 157152, 34355, 156895, 157151, 8469, 7342, 5468, 157152, 198, 156903}; + EXPECT_EQ(std::vector(enabled.ptr(), enabled.ptr() + enabled.numel()), enabled_expected); + + auto disabled = tokenizer.convertMessage({"Hello", "", false}).at("sequence"); + const std::vector disabled_expected = {157151, 90827, 157152, 14136, 5381, 6350, 928, 156895, + 157151, 39, 116171, 157152, 14455, 156895, 157151, 8469, + 7342, 5468, 157152, 198, 156903, 156904}; + EXPECT_EQ(std::vector(disabled.ptr(), disabled.ptr() + disabled.numel()), disabled_expected); +} + +} // namespace diff --git a/tests/nn/CMakeLists.txt b/tests/nn/CMakeLists.txt index bd003caac..b9a96593f 100644 --- a/tests/nn/CMakeLists.txt +++ b/tests/nn/CMakeLists.txt @@ -26,5 +26,10 @@ add_executable(Mllm-Test-Nn-GatedDeltaRule GatedDeltaRuleTest.cpp) target_link_libraries(Mllm-Test-Nn-GatedDeltaRule PRIVATE gtest_main MllmRT MllmCPUBackend) target_include_directories(Mllm-Test-Nn-GatedDeltaRule PRIVATE ${MLLM_INCLUDE_DIR}) +add_executable(Mllm-Test-Nn-KimiDeltaAttention KimiDeltaAttentionTest.cpp) +target_link_libraries(Mllm-Test-Nn-KimiDeltaAttention PRIVATE gtest_main MllmRT MllmCPUBackend) +target_include_directories(Mllm-Test-Nn-KimiDeltaAttention PRIVATE ${MLLM_INCLUDE_DIR}) + include(GoogleTest) add_test(NAME GatedDeltaRuleFocused COMMAND Mllm-Test-Nn-GatedDeltaRule) +add_test(NAME KimiDeltaAttentionFocused COMMAND Mllm-Test-Nn-KimiDeltaAttention) diff --git a/tests/nn/KimiDeltaAttentionTest.cpp b/tests/nn/KimiDeltaAttentionTest.cpp new file mode 100644 index 000000000..3e5614e06 --- /dev/null +++ b/tests/nn/KimiDeltaAttentionTest.cpp @@ -0,0 +1,303 @@ +// Copyright (c) MLLM Team. +// Licensed under the MIT License. + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "mllm/compile/ir/Trace.hpp" +#include "mllm/compile/ir/linalg/Op.hpp" +#include "mllm/compile/jit/binary/LinalgIRSerialization.hpp" +#include "mllm/compile/jit/interpreter/AopsFromJson.hpp" +#include "mllm/mllm.hpp" +#include "mllm/nn/Nn.hpp" + +namespace { + +using mllm::Tensor; + +class KimiDeltaAttentionTest : public testing::Test { + protected: + static void SetUpTestSuite() { mllm::initializeContext(); } +}; + +class KimiDeltaAttentionModule final : public mllm::nn::Module { + public: + KimiDeltaAttentionModule(std::string name, bool safe_gate, float lower_bound, bool state_inplace) : Module(std::move(name)) { + kda_ = reg("kda", safe_gate, lower_bound, state_inplace); + } + + std::vector forward(const std::vector& inputs, const std::vector&) override { + auto [output, state] = kda_(inputs[0], inputs[1], inputs[2], inputs[3], inputs[4], inputs[5], inputs[6], inputs[7]); + return {output, state}; + } + + private: + mllm::nn::KimiDeltaAttention kda_; +}; + +template +auto findOp(const mllm::ir::node_ptr_t& node) -> typename OpType::ptr_t { + if (node->isa_()) { return node->cast_(); } + if (!node->isa_()) { return nullptr; } + for (const auto& region : node->cast_()->regions()) { + for (const auto& op : region->ops()) { + if (auto found = findOp(op)) { return found; } + } + } + return nullptr; +} + +Tensor patterned(const Tensor::shape_t& shape, float scale, float offset = 0.0F) { + auto tensor = Tensor::empty(shape, mllm::kFloat32, mllm::kCPU).alloc(); + for (int index = 0; index < tensor.numel(); ++index) { + tensor.ptr()[index] = std::sin(static_cast(index + 1) * scale) + offset; + } + return tensor; +} + +float stableSigmoid(float value) { + if (value >= 0.0F) { + const float exp_value = std::exp(-value); + return 1.0F / (1.0F + exp_value); + } + const float exp_value = std::exp(value); + return exp_value / (1.0F + exp_value); +} + +// Independent scalar reference of the public contract: +// q/k/v/gate [B, S, H, D], beta [B, S, H] (post-sigmoid), a_log [H], +// dt_bias [H * D], state [B, H, D, D] -> output [B, S, H, D], new state. +std::pair, std::vector> referenceKimiDeltaAttention( + const std::vector& q, const std::vector& k, const std::vector& v, const std::vector& gate, + const std::vector& beta, const std::vector& a_log, const std::vector& dt_bias, + std::vector state, int batch, int sequence, int heads, int dim, bool safe_gate, float lower_bound) { + const float query_dim_scale = 1.0F / std::sqrt(static_cast(dim)); + std::vector output(static_cast(batch) * sequence * heads * dim, 0.0F); + std::vector normalized_q(dim); + std::vector normalized_k(dim); + std::vector prediction(dim); + for (int b = 0; b < batch; ++b) { + for (int h = 0; h < heads; ++h) { + const std::size_t state_base = (static_cast(b) * heads + h) * dim * dim; + const float a_scale = std::exp(a_log[h]); + for (int s = 0; s < sequence; ++s) { + const std::size_t vector_base = ((static_cast(b) * sequence + s) * heads + h) * dim; + float q_norm_sq = 0.0F; + float k_norm_sq = 0.0F; + for (int d = 0; d < dim; ++d) { + q_norm_sq += q[vector_base + d] * q[vector_base + d]; + k_norm_sq += k[vector_base + d] * k[vector_base + d]; + } + const float q_scale = query_dim_scale / std::sqrt(q_norm_sq + 1.0e-6F); + const float k_scale = 1.0F / std::sqrt(k_norm_sq + 1.0e-6F); + for (int d = 0; d < dim; ++d) { + normalized_q[d] = q[vector_base + d] * q_scale; + normalized_k[d] = k[vector_base + d] * k_scale; + prediction[d] = 0.0F; + } + for (int kd = 0; kd < dim; ++kd) { + const float gate_input = gate[vector_base + kd] + dt_bias[h * dim + kd]; + const float log_decay = safe_gate ? lower_bound * stableSigmoid(a_scale * gate_input) + : -a_scale * std::log1p(std::exp(gate_input)); + const float decay = std::exp(log_decay); + for (int vd = 0; vd < dim; ++vd) { + const std::size_t state_index = state_base + kd * dim + vd; + state[state_index] *= decay; + prediction[vd] += normalized_k[kd] * state[state_index]; + } + } + const float beta_value = beta[(static_cast(b) * sequence + s) * heads + h]; + for (int vd = 0; vd < dim; ++vd) { + const float delta = beta_value * (v[vector_base + vd] - prediction[vd]); + for (int kd = 0; kd < dim; ++kd) { + const std::size_t state_index = state_base + kd * dim + vd; + state[state_index] += normalized_k[kd] * delta; + output[vector_base + vd] += normalized_q[kd] * state[state_index]; + } + } + } + } + } + return {output, state}; +} + +void expectNear(const Tensor& actual, const std::vector& expected, float tolerance) { + ASSERT_EQ(actual.numel(), expected.size()); + for (int index = 0; index < actual.numel(); ++index) { + ASSERT_NEAR(actual.ptr()[index], expected[index], tolerance) << "index " << index; + } +} + +struct Case { + int batch; + int sequence; + int heads; + int dim; + bool safe_gate; + float lower_bound; + float tolerance; +}; + +void runEagerReferenceCase(const Case& test_case, const std::string& module_name) { + SCOPED_TRACE(::testing::Message() << module_name << " B=" << test_case.batch << " S=" << test_case.sequence + << " H=" << test_case.heads << " D=" << test_case.dim + << " safe_gate=" << test_case.safe_gate); + const Tensor::shape_t vector_shape = {test_case.batch, test_case.sequence, test_case.heads, test_case.dim}; + auto q = patterned(vector_shape, 0.03F); + auto k = patterned(vector_shape, 0.05F, 0.1F); + auto v = patterned(vector_shape, 0.07F, -0.2F); + auto gate = patterned(vector_shape, 0.09F, -0.2F); + auto beta = patterned({test_case.batch, test_case.sequence, test_case.heads}, 0.11F, 0.5F); + auto a_log = patterned({test_case.heads}, 0.13F, -0.4F); + auto dt_bias = patterned({test_case.heads * test_case.dim}, 0.15F, -0.1F); + auto state = patterned({test_case.batch, test_case.heads, test_case.dim, test_case.dim}, 0.017F); + const auto state_before = state.toVector(); + const auto [expected_output, expected_state] = referenceKimiDeltaAttention( + q.toVector(), k.toVector(), v.toVector(), gate.toVector(), beta.toVector(), + a_log.toVector(), dt_bias.toVector(), state_before, test_case.batch, test_case.sequence, test_case.heads, + test_case.dim, test_case.safe_gate, test_case.lower_bound); + + KimiDeltaAttentionModule module(module_name, test_case.safe_gate, test_case.lower_bound, /*state_inplace=*/false); + const auto outputs = module(q, k, v, gate, beta, a_log, dt_bias, state); + + ASSERT_EQ(outputs.size(), 2); + EXPECT_EQ(outputs[0].shape(), vector_shape); + EXPECT_EQ(outputs[1].shape(), state.shape()); + EXPECT_NE(outputs[1].ptr(), state.ptr()); + EXPECT_EQ(state.toVector(), state_before); + expectNear(outputs[0], expected_output, test_case.tolerance); + expectNear(outputs[1], expected_state, test_case.tolerance); +} + +TEST_F(KimiDeltaAttentionTest, EagerMatchesIndependentReferenceAndPreservesInputState) { + runEagerReferenceCase({2, 5, 3, 8, true, -5.0F, 2.0e-6F}, "kimi_delta_attention_safe_gate"); + runEagerReferenceCase({1, 4, 2, 8, false, 0.0F, 2.0e-6F}, "kimi_delta_attention_softplus_gate"); + runEagerReferenceCase({1, 3, 2, 6, true, -2.5F, 2.0e-6F}, "kimi_delta_attention_custom_bound"); +} + +TEST_F(KimiDeltaAttentionTest, EagerMatchesIndependentReferenceAtProductionHeadGeometry) { + // Ling-3.0-tiny geometry: 16 heads of 128 lanes; the batched lane helpers + // and multi-threaded (batch, head) partition are all exercised here. + runEagerReferenceCase({1, 49, 16, 128, true, -5.0F, 5.0e-5F}, "kimi_delta_attention_production"); +} + +TEST_F(KimiDeltaAttentionTest, InplaceStateOutputAliasesAndMutatesInput) { + auto q = patterned({1, 3, 2, 4}, 0.03F); + auto k = patterned(q.shape(), 0.05F); + auto v = patterned(q.shape(), 0.07F); + auto gate = patterned(q.shape(), 0.09F, -0.2F); + auto beta = patterned({1, 3, 2}, 0.11F, 0.5F); + auto a_log = patterned({2}, 0.13F, -0.4F); + auto dt_bias = patterned({8}, 0.15F, -0.1F); + auto state = patterned({1, 2, 4, 4}, 0.017F); + const auto state_before = state.toVector(); + const auto* state_storage = state.ptr(); + + KimiDeltaAttentionModule module("kimi_delta_attention_inplace", true, -5.0F, /*state_inplace=*/true); + const auto outputs = module(q, k, v, gate, beta, a_log, dt_bias, state); + + ASSERT_EQ(outputs.size(), 2); + EXPECT_EQ(outputs[1].ptr(), state_storage); + EXPECT_NE(state.toVector(), state_before); + EXPECT_EQ(outputs[1].toVector(), state.toVector()); +} + +TEST_F(KimiDeltaAttentionTest, ChunkedPrefillAndDecodeMatchOneShot) { + constexpr int kSequence = 7; + constexpr int kHeads = 2; + constexpr int kDim = 8; + auto q = patterned({1, kSequence, kHeads, kDim}, 0.03F); + auto k = patterned(q.shape(), 0.05F, 0.1F); + auto v = patterned(q.shape(), 0.07F, -0.2F); + auto gate = patterned(q.shape(), 0.09F, -0.2F); + auto beta = patterned({1, kSequence, kHeads}, 0.11F, 0.5F); + auto a_log = patterned({kHeads}, 0.13F, -0.4F); + auto dt_bias = patterned({kHeads * kDim}, 0.15F, -0.1F); + const auto initial_state = patterned({1, kHeads, kDim, kDim}, 0.017F).toVector(); + + KimiDeltaAttentionModule one_shot("kimi_delta_attention_one_shot", true, -5.0F, false); + const auto expected = + one_shot(q, k, v, gate, beta, a_log, dt_bias, Tensor::fromVector(initial_state, {1, kHeads, kDim, kDim})); + const auto expected_output = expected[0].toVector(); + + KimiDeltaAttentionModule chunked("kimi_delta_attention_chunked", true, -5.0F, false); + auto state = Tensor::fromVector(initial_state, {1, kHeads, kDim, kDim}); + std::vector chunked_output; + const std::vector> chunks = {{0, 3}, {3, 4}, {4, 5}, {5, 7}}; + for (const auto& [begin, end] : chunks) { + const auto length = end - begin; + // Token slices [begin, end) of a [1, S, H, D] activation or a [1, S, H] gate. + const auto slice = [&](const Tensor& tensor, int width, const Tensor::shape_t& shape) { + const auto values = tensor.toVector(); + const auto first = values.begin() + static_cast(begin) * width; + return Tensor::fromVector(std::vector(first, first + static_cast(length) * width), shape); + }; + const Tensor::shape_t vector_shape = {1, length, kHeads, kDim}; + const auto outputs = chunked(slice(q, kHeads * kDim, vector_shape), slice(k, kHeads * kDim, vector_shape), + slice(v, kHeads * kDim, vector_shape), slice(gate, kHeads * kDim, vector_shape), + slice(beta, kHeads, {1, length, kHeads}), a_log, dt_bias, state); + const auto values = outputs[0].toVector(); + chunked_output.insert(chunked_output.end(), values.begin(), values.end()); + state = outputs[1]; + } + + EXPECT_EQ(chunked_output, expected_output); + EXPECT_EQ(state.toVector(), expected[1].toVector()); +} + +TEST_F(KimiDeltaAttentionTest, RejectsInvalidGeometryAndOptions) { + auto q = patterned({1, 2, 2, 4}, 0.03F); + auto beta = patterned({1, 2, 2}, 0.11F, 0.5F); + auto a_log = patterned({2}, 0.13F, -0.4F); + auto dt_bias = patterned({8}, 0.15F, -0.1F); + auto state = patterned({1, 2, 4, 4}, 0.017F); + + KimiDeltaAttentionModule module("kimi_delta_attention_invalid", true, -5.0F, false); + // beta must be [B, S, H]. + EXPECT_THROW((void)module(q, q, q, q, patterned({1, 2, 2, 1}, 0.1F), a_log, dt_bias, state), std::invalid_argument); + // state must be [B, H, D, D]. + EXPECT_THROW((void)module(q, q, q, q, beta, a_log, dt_bias, patterned({1, 2, 4, 3}, 0.1F)), std::invalid_argument); + // dt_bias must hold H * D values. + EXPECT_THROW((void)module(q, q, q, q, beta, a_log, patterned({2}, 0.1F), state), std::invalid_argument); + // The safe gate requires a finite, strictly negative lower bound. + KimiDeltaAttentionModule invalid_bound("kimi_delta_attention_invalid_bound", true, 0.0F, false); + EXPECT_THROW((void)invalid_bound(q, q, q, q, beta, a_log, dt_bias, state), std::invalid_argument); +} + +TEST_F(KimiDeltaAttentionTest, TraceAndSerializationPreserveGateAndStateOptions) { + KimiDeltaAttentionModule module("kimi_delta_attention_trace", true, -3.5F, true); + auto ir_context = mllm::ir::trace( + module, Tensor::empty({1, 2, 2, 4}, mllm::kFloat32, mllm::kCPU), Tensor::empty({1, 2, 2, 4}, mllm::kFloat32, mllm::kCPU), + Tensor::empty({1, 2, 2, 4}, mllm::kFloat32, mllm::kCPU), Tensor::empty({1, 2, 2, 4}, mllm::kFloat32, mllm::kCPU), + Tensor::empty({1, 2, 2}, mllm::kFloat32, mllm::kCPU), Tensor::empty({2}, mllm::kFloat32, mllm::kCPU), + Tensor::empty({8}, mllm::kFloat32, mllm::kCPU), Tensor::empty({1, 2, 4, 4}, mllm::kFloat32, mllm::kCPU)); + auto op = findOp(ir_context->topLevelOp()); + ASSERT_NE(op, nullptr); + EXPECT_EQ(op->getAOp()->getOpType(), mllm::OpTypes::kKimiDeltaAttention); + EXPECT_EQ(op->inputs().size(), 8); + EXPECT_EQ(op->outputs().size(), 2); + + const auto options = mllm::jit::binary::dumpLinalgIROptions(op); + EXPECT_TRUE(options.at("safe_gate").get()); + EXPECT_FLOAT_EQ(options.at("lower_bound").get(), -3.5F); + EXPECT_TRUE(options.at("state_inplace").get()); + + const auto restored = mllm::jit::interpreter::aopsFromJson( + nlohmann::json{{"op_type", "KimiDeltaAttention"}, {"backend", "CPU"}, {"op_options", options}}); + ASSERT_NE(restored, nullptr); + EXPECT_EQ(restored->getOpType(), mllm::OpTypes::kKimiDeltaAttention); + const auto restored_options = std::static_pointer_cast(restored)->options(); + EXPECT_TRUE(restored_options.safe_gate); + EXPECT_FLOAT_EQ(restored_options.lower_bound, -3.5F); + EXPECT_TRUE(restored_options.state_inplace); +} + +} // namespace From 0aa7d264281462a13c64dc49627966c58fcba6b0 Mon Sep 17 00:00:00 2001 From: Aharrypotter Date: Sun, 6 Sep 2026 11:23:23 +0800 Subject: [PATCH 4/4] docs(ling3): document the retained test targets --- examples/ling3/README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/examples/ling3/README.md b/examples/ling3/README.md index fd9e8419d..cbe6259e6 100644 --- a/examples/ling3/README.md +++ b/examples/ling3/README.md @@ -56,3 +56,15 @@ generation limit: The expected completion marker is `LING3_RUN_OK prompt_tokens=49 generated_tokens=64`. This is a generation correctness demo, not a perplexity, model-quality, or performance benchmark. + +## Retained tests + +The Ling-specific coverage follows the repository test layout (`ctest` labels +in parentheses): + +| Layer | Target | Notes | +| --- | --- | --- | +| KDA kernel | `Mllm-Test-CPUKernel --gtest_filter='KimiDeltaAttentionKernelTest.*'` (`cpu-kernel`) | scalar-reference oracle, bitwise prefill/decode and serial/parallel checks | +| Public `nn::KimiDeltaAttention` | `Mllm-Test-Nn-KimiDeltaAttention` (`KimiDeltaAttentionFocused`) | eager reference, in-place state, chunked equivalence, trace + serialization | +| Causal convolution | upstream `Mllm-Test-Nn-CausalDepthwiseConv1D` and the `CausalDepthwiseConv*KernelTest` suites | Ling registers the upstream operation with the current-first order | +| Model | `Mllm-Test-Ling3-Config`, `Mllm-Test-Ling3-RoPE`, `Mllm-Test-Ling3-Tokenizer` (`ling3`) | set `MLLM_LING3_EXAMPLE_DIR` on a device; the tokenizer test skips unless `LING3_OFFICIAL_TOKENIZER` points at the official `tokenizer.json` |