Skip to content

feat(cpu): add Ling-3.0-tiny mobile CPU support - #699

Draft
Aharrypotter wants to merge 4 commits into
UbiquitousLearning:mainfrom
Aharrypotter:feat/ling3-tiny
Draft

feat(cpu): add Ling-3.0-tiny mobile CPU support#699
Aharrypotter wants to merge 4 commits into
UbiquitousLearning:mainfrom
Aharrypotter:feat/ling3-tiny

Conversation

@Aharrypotter

@Aharrypotter Aharrypotter commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds end-to-end, text-only mobile CPU support for the pinned official
inclusionAI/Ling-3.0-tiny
checkpoint on ARM64 macOS and Android, rebased on main after #700/#701/#704.

Reviewer focus

  1. KimiDeltaAttention is the only new mllm operation (OpType 81):
    nn::Layer / Functional -> OpType + aops -> IR + serialization -> CPU backend -> kernel.
  2. Ling's q/k/v short convolutions register the upstream weighted
    nn::CausalDepthwiseConv1D
    (feat(model): Add LFM2.5-2.6B text generation on ARM CPU #701/refactor(qwen3.5): register stateful GDN runtime ops #704) with the current-first order,
    so they run on the existing optimized GDN convolution kernel. This PR adds
    no convolution operation or kernel of its own.
  3. Gated MLA is a composition of existing mllm operators. No new MLA kernel.
  4. Tests follow the post-refactor(qwen3.5): register stateful GDN runtime ops #704/refactor(minicpm5): move model-contract tests under tests/models #706 layout: KDA kernel fixture in
    tests/cpu/KernelTest.cpp (CPUKernelFocused), public-op test in
    tests/nn (KimiDeltaAttentionFocused), model tests in
    tests/models/ling3 (ling3 label), all registered with add_test.

Ling-3.0-tiny architecture

Ling-3.0-tiny is a 24-layer hybrid decoder built from six identical attention
groups:

( KDA -> KDA -> KDA -> Gated MLA ) x 6
    18 linear/recurrent layers       6 global-attention layers
Component Role in the architecture Implementation in this PR
KDA Linear-time recurrent attention. Q/K/V each use a width-4 causal depthwise convolution before the delta-rule state update; no KV cache growth. New KimiDeltaAttention operation and CPU kernel (safe-gate / softplus gate, explicit [B, H, D, D] state). Convolutions use the upstream CausalDepthwiseConv1D operation.
Gated MLA Six periodic global-attention layers. MLA compresses Q/KV projections, applies partial RoPE, caches K/V, and sigmoid-gates each head's output. Existing Linear, RMSNorm, RoPE/layout preparation, KV cache, MatMul, CausalMask, Softmax, Sigmoid paths.
MoE Dense FFN at layer 0; 128 routed experts with top-8 activation plus one shared expert elsewhere (about 1.3B of 7.9B parameters active per token). Official grouped noaux_tc routing, expert bias, normalized routed weights, existing KAI Linear/MLP execution.
Architecture diagram and exact tiny-checkpoint geometry Ling-3.0-tiny architecture
Surface Ling-3.0-tiny
Residual stream hidden size 1,536
KDA 16 heads x 128 dimensions; width-4 Q/K/V causal convolution
Gated MLA Q LoRA rank 256; KV LoRA rank 512; Q/K size 192 = 128 no-RoPE + 64 RoPE; V size 128
FFN / MoE dense layer 0 (intermediate 4,608); then E128A8 + one shared expert
Router 8 expert groups, select 4 groups, then top 8 experts; routed scale 2.5
Mobile runtime batch 1; 2,048-token cache; FP32 recurrent/KV state
Linear execution KAI packed INT4 weights, dynamic INT8 activations, FP32 operator inputs/outputs

Standard mllm abstraction

Ling3KimiDeltaAttention module
  -> nn::CausalDepthwiseConv1D("q_conv1d", C, K=4, bias=false, state_inplace=true, kCurrentFirst)   (upstream op)
  -> nn::KimiDeltaAttention("kda", safe_gate, lower_bound, state_inplace=true)                      (this PR)
       |-> eager: typed CPU factory -> CPUKimiDeltaAttentionOp -> kda::kimiDeltaAttentionF32
       `-> trace: linalg::KimiDeltaAttentionOp -> option serialization -> interpreter reconstruction

State transitions are explicit operation outputs ({output, updated_state});
the module only owns request-state allocation and reset. The model graph
includes no backend or kernel headers. The abstraction audit against main
reports 0 errors; its two heuristic warnings are the model-level router
orchestration and nn::Param reads of A_log / dt_bias, both classified as
model orchestration rather than operation escapes.

Validation (exact head 0aa7d264; code tree identical to 240ded10, the docs commit only touches examples/ling3/README.md)

Gate Result
Abstraction audit vs main PASS — 0 errors, 2 classified warnings
macOS ARM64 (Apple GCD threads) PASS — 40/40 focused tests; 49-token prompt generates 64 tokens, all 64 IDs identical to the pre-refactor Mac sequence
H20 Linux x86 (ARM backend off, fail-closed offline build) PASS — same 36 tests pass on the scalar paths; ctest -L 'cpu-kernel|ling3' registers 4 tests; supervised receipt linux-v1 rc=0
H20 Android cross-build PASS — NDK r28b, API 28, arm64-v8a, -march=armv8.2-a+fp16+fp16fml+dotprod+i8mm, no -ffast-math in any compile command; 12/12 AArch64 ELF, 9/9 linker64; supervised receipt android-v1 rc=0
OnePlus 13T (Android 16, 8 threads) PASS — 40/40 tests from the cross-built bundle; same prompt generates 64 tokens, all 64 IDs identical to the pre-refactor Android sequence
Focused test matrix
Suite Tests Mac H20 x86 OnePlus 13T
KimiDeltaAttentionKernelTest (scalar reference, both gates, bitwise prefill/decode and serial/parallel, validation) 4
Upstream CausalDepthwiseConv*KernelTest + GatedDeltaRuleKernelTest regression 13
Mllm-Test-Nn-KimiDeltaAttention (eager reference incl. 16x128 geometry, in-place state, chunked equivalence, invalid contracts, trace + serialization) 6
Upstream Mllm-Test-Nn-CausalDepthwiseConv1D / Mllm-Test-Nn-GatedDeltaRule 5 + 3
Mllm-Test-Ling3-Config / -RoPE / -Tokenizer 1 + 2 + 2
Mllm-Test-KaiW4A32Pack (AArch64 only) 4 n/a
Convert, audit, and run
cd examples/ling3
python3 validate_checkpoint.py /path/to/Ling-3.0-tiny --observed-revision a2ee06c0f2de5b171701aee7f73f70a1da75483b
python3 validate_converted_model.py /path/to/Ling-3.0-tiny.mllm /path/to/Ling-3.0-tiny
./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 '请用中文详细介绍 Ling-3.0-tiny 的混合注意力架构,并解释 KDA、MLA 和 MoE 各自的作用。' \
  --disable_thinking --max_new_tokens 64 --print_token_ids

Expected completion marker: LING3_RUN_OK prompt_tokens=49 generated_tokens=64.

Correctness and performance boundaries
  • KDA recurrence and convolution state are FP32. ARM builds must not use -ffast-math.
  • Routed experts use the correctness-first M=1 contract; grouped expert prefill is future work.
  • macOS and Android are each stable against their own pre-refactor 64-token sequence; they are not claimed bitwise identical to each other.
  • The OnePlus run is a correctness replay (single run, no warmup or thermal control). No performance claim is made.

Supported scope and limits

Supported: Ling-3.0-tiny text generation, batch 1, cache length up to 2,048, ARM64 macOS and Android, W4A32 KAI Linear execution.

Not claimed: other Ling checkpoints, vision/audio/MTP heads, batch sizes greater than 1, 128K/1M-token mobile execution, perplexity, task-quality, sustained-performance, energy, or formal memory benchmarks.

🤖 Generated with Claude Code

@coderabbitai

coderabbitai Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The PR adds Ling-3.0-tiny support for ARM64 CPU inference. It introduces the model, tokenizer, Kimi Delta Attention, causal depthwise convolution, checkpoint validation, runtime integration, examples, and CPU tests.

Changes

Ling-3 CPU runtime

Layer / File(s) Summary
Operator APIs and tensor wiring
mllm/core/..., mllm/nn/...
Adds Kimi Delta Attention and causal depthwise convolution operations, options, tensor validation, state handling, functional wrappers, and neural-network layers.
CPU kernels and backend dispatch
mllm/backends/cpu/...
Adds CPU kernels, operation implementations, threading, state copying, and backend factory registration.
IR and JSON serialization
mllm/compile/...
Adds Linalg IR declarations, RTTI kinds, binary serialization, and JSON deserialization for both operations.
Ling-3 configuration, model, and tokenizer
mllm/models/ling3/...
Adds configuration checks, hybrid MLA/KDA execution, sparse MoE routing, cache and recurrent state management, RoPE utilities, and tokenizer support.
Runner, checkpoint validation, and build wiring
examples/ling3/..., examples/CMakeLists.txt, README.md, README-ZH.md
Adds the Ling-3 runner, model and quantization configurations, checkpoint and converted-model validators, build targets, and deployment documentation.
Runtime and model validation tests
tests/cpu/...
Adds tests for KDA, causal convolution, configuration, RoPE, tokenizer behavior, and ARM Kai batched packing.

Estimated code review effort: 5 (Critical) | ~120 minutes

Mergeability Score: 🟡 Moderate · up to ee0e4

This PR adds Ling-3.0-tiny mobile support and new stateful CPU operations, but the current head still has concrete merge-readiness issues: a validation-passing routing configuration can trigger undefined behavior during expert selection, and checkpoint validation can accept inconsistent shard assignments that may load incorrect model data. Merge should wait for these correctness issues to be fixed or explicitly accepted.

Sequence Diagram(s)

sequenceDiagram
  participant Runner
  participant Ling3Tokenizer
  participant Ling3ForCausalLM
  participant CPUBackend
  Runner->>Ling3Tokenizer: tokenize prompt
  Runner->>Ling3ForCausalLM: start generation
  Ling3ForCausalLM->>CPUBackend: execute MLA, KDA, and convolution operations
  CPUBackend-->>Ling3ForCausalLM: outputs and updated states
  Ling3ForCausalLM-->>Runner: streamed token output and run status
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 2.63% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly and concisely identifies the primary change: adding Ling-3.0-tiny mobile CPU support in the CPU backend.
Description check ✅ Passed The description is complete and relevant. It explains the scope, architecture, implementation approach, validation results, supported limits, and reproducibility commands, and it satisfies the reposit…
  • Fix all pre-merge checks with AI
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@Aharrypotter
Aharrypotter marked this pull request as ready for review August 13, 2026 00:52

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 10

🧹 Nitpick comments (9)
tests/cpu/Ling3TokenizerTest.cpp (1)

30-41: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Document the LING3_OFFICIAL_TOKENIZER requirement so the target is not silently empty.

Both tests in this file skip when LING3_OFFICIAL_TOKENIZER is unset. In a default CI run, Mllm-Test-Ling3-Tokenizer therefore passes without asserting anything. Record the required environment variable in the Ling-3 documentation, so maintainers know how to enable real coverage.

#!/bin/bash
# Description: Check whether the tokenizer test environment variables are documented.
rg -n 'LING3_OFFICIAL_TOKENIZER|LING3_RUNTIME_CONFIG' --iglob '*.md' --iglob '*.txt' --iglob '*.yml' --iglob '*.yaml'
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/cpu/Ling3TokenizerTest.cpp` around lines 30 - 41, Document the required
LING3_OFFICIAL_TOKENIZER environment variable in the Ling-3 documentation,
including how maintainers should set it to enable
MatchesOfficialByteBPEAndNFCVectors and
RendersOfficialSingleTurnThinkingTemplates. Ensure the documentation is
discoverable in a supported Markdown, text, or YAML file and mention any related
LING3_RUNTIME_CONFIG requirement if applicable.
mllm/compile/jit/interpreter/AopsFromJson.hpp (1)

45-46: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Rename implementation-reserved helper names.

__kimiDeltaAttentionFromJson and __causalDepthwiseConv1dFromJson use C++ implementation-reserved identifiers. Rename them without a leading double underscore and update their declarations, definitions, and dispatch calls.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@mllm/compile/jit/interpreter/AopsFromJson.hpp` around lines 45 - 46, Rename
__kimiDeltaAttentionFromJson and __causalDepthwiseConv1dFromJson to equivalent
names without leading double underscores, updating their declarations,
definitions, and all dispatch call sites consistently.

Source: Coding guidelines

mllm/models/ling3/tokenization_ling3.hpp (3)

54-68: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Document that cluster composition covers accent marks only.

The cluster scan extends only while unicode_cpt_flags(...).is_accent_mark is true. Combining marks that are not classified as accent marks, for example Devanagari or Hebrew marks, stay decomposed, so the result is not full NFC. State this limitation in the comment so a later reader does not assume complete NFC coverage.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@mllm/models/ling3/tokenization_ling3.hpp` around lines 54 - 68, Update the
comment above the cluster scan to explicitly state that composition is limited
to combining marks classified as accent marks by unicode_cpt_flags, and that
other marks such as Devanagari or Hebrew remain decomposed rather than receiving
full NFC normalization.

195-204: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

The constructor parses tokenizer.json twice and does not handle parse failures.

bpe_.initFromSentencePieceJson(file_path) already reads the file, and lines 199-201 read and parse the whole document again. For a 157k-entry vocabulary this doubles peak memory during startup, which matters on mobile targets. nlohmann::json::parse also throws nlohmann::json::parse_error here, which escapes as a message without Ling-3 context.

Consider parsing once and passing the parsed document to the BPE loader, or wrap the parse in a try block and rethrow std::invalid_argument with the file path.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@mllm/models/ling3/tokenization_ling3.hpp` around lines 195 - 204, Update the
Ling-3 tokenizer constructor around bpe_.initFromSentencePieceJson and
tokenizer_json to avoid parsing tokenizer.json twice by parsing once and reusing
the parsed document with the BPE loader if supported. Handle
nlohmann::json::parse_error and rethrow std::invalid_argument containing clear
Ling-3 context and the file path, while preserving the NFC normalizer
validation.

260-268: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Reuse convert2Ids in convertMessage.

Both functions allocate a [1, N] int64 tensor and fill it with bpe_._lookup_vocab. The only difference is setMemType(kExtraInput) versus setMemType(kNormal). Extract one helper that takes the memory type, then call it from both places.

Also applies to: 297-304

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@mllm/models/ling3/tokenization_ling3.hpp` around lines 260 - 268, Refactor
the duplicated tensor allocation and vocabulary lookup logic in convert2Ids and
convertMessage into one helper that accepts the desired memory type. Have both
methods delegate to this helper, preserving kExtraInput for convert2Ids and
kNormal for convertMessage while keeping the existing tensor shape, type, name,
and token-to-ID behavior.
mllm/models/ling3/modeling_ling3.hpp (3)

466-473: 🚀 Performance & Scalability | 🔵 Trivial | ⚖️ Poor tradeoff

Padding values to qk_dim inflates the KV cache.

padLing3ValuesForCache expands each value vector from 128 to 192 elements so the value cache matches the key dimension in nn::StaticCache. At 2,048 tokens, 6 MLA layers, and 16 heads in FP32, the padding costs about 50% extra value-cache memory. If nn::StaticCache can accept separate key and value head dimensions, prefer that path.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@mllm/models/ling3/modeling_ling3.hpp` around lines 466 - 473, Update the
KV-cache path around padLing3ValuesForCache and cache->updateKVCache so keys use
qk_dim_ while values retain their native value dimension, avoiding padded value
storage. Use separate key/value head dimensions if nn::StaticCache supports
them, and adjust the subsequent attention matmul to consume the unpadded cached
values.

316-334: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Clarify why nn::Conv1D layers are registered but never executed.

q_conv1d_, k_conv1d_, and v_conv1d_ exist only to own the depthwise weights that q_causal_conv_, k_causal_conv_, and v_causal_conv_ consume. A reader can assume the nn::Conv1D layers run. Add a short comment that states they are weight holders for the checkpoint parameter names.

As per coding guidelines: "Add comments for complex algorithms or non-obvious logic."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@mllm/models/ling3/modeling_ling3.hpp` around lines 316 - 334, Add a concise
comment immediately before the q_conv1d_, k_conv1d_, and v_conv1d_ registrations
explaining that these Conv1D modules only hold depthwise weights for the
corresponding causal convolution modules and preserve checkpoint parameter
names; do not alter their execution or registration.

Source: Coding guidelines


283-293: 🚀 Performance & Scalability | 🔵 Trivial | ⚖️ Poor tradeoff

Per-token, per-expert dispatch limits prefill throughput.

moeInfer calls one expert MLP per (token, route) pair, so prefill performs tokens * top_k_ linear calls at M=1. The comment explains the reason, and the behavior is correct. If prefill latency becomes a problem, group tokens by expert id and run one call per expert with the gathered rows.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@mllm/models/ling3/modeling_ling3.hpp` around lines 283 - 293, Optimize
moeInfer’s per-token expert dispatch by grouping token rows by expert ID,
gathering each expert’s assigned inputs, and invoking each expert once on the
grouped batch instead of once per (token, route) pair. Scatter the resulting
rows back into routed_output using the original token/route positions,
preserving id_values ordering, topk_weights aggregation, and output dtype
behavior.
mllm/models/ling3/configuration_ling3.hpp (1)

127-139: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Cache numFullAttentionLayers() or document the linear cost.

numFullAttentionLayers() iterates all layers on every call. hasOfficialLing3TinyArchitecture calls it twice per invocation, and Ling3ForCausalLM plus the runner call it again. The cost is small at 24 layers, so this is only a clarity item. Consider computing the counts once in the constructor.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@mllm/models/ling3/configuration_ling3.hpp` around lines 127 - 139, Cache the
result of numFullAttentionLayers() during configuration initialization and have
subsequent callers reuse that stored count, including numKDALayers() and
architecture checks such as hasOfficialLing3TinyArchitecture. Alternatively,
document that the method intentionally performs a linear scan if caching cannot
fit the existing design.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@examples/ling3/README.md`:
- Around line 12-36: Update the Ling-3 README command sequence to establish the
required working directory before using relative paths: add an explicit cd
examples/ling3 step before the validation, conversion, and smoke-run commands,
or convert every referenced script, configuration, and runner path to
repository-relative paths.

In `@examples/ling3/validate_checkpoint.py`:
- Around line 200-209: Update read_safetensors_header to determine the file’s
remaining size after reading the 8-byte length, define or reuse a maximum
metadata-size limit, and reject header_length when it exceeds either the
remaining file size or that limit before calling file.read(header_length).
Preserve the existing truncated-header and JSON parsing behavior for valid
lengths.
- Around line 19-61: Add "hidden_act": "silu" to OFFICIAL_CONTRACT and extend
the negative validation tests to mutate hidden_act to a different activation,
asserting validate_config rejects the altered configuration.
- Around line 212-221: Update validate_shards to verify each tensor’s weight_map
assignment while scanning the header: require weight_map[name] to equal the
current shard_name before recording its descriptor, while preserving duplicate
detection. Add a regression test using two valid tensor names whose index
entries are swapped between shards and assert validation fails.

In `@mllm/core/aops/CausalDepthwiseConv1DOp.hpp`:
- Around line 11-25: Document the public causal-convolution contract at
mllm/core/aops/CausalDepthwiseConv1DOp.hpp:11-25, including [B,S,C], [C,1,K],
and [B,C,K-1] layouts, both outputs, state_inplace ownership/behavior, and
std::invalid_argument conditions. Document constructor behavior and the
two-output forward contract at mllm/nn/layers/CausalDepthwiseConv1D.hpp:11-17.
Document contiguous FP32 tensor requirements and the factory purpose at
mllm/backends/cpu/ops/CausalDepthwiseConv1DOp.hpp:10-23; these sites all require
comments or docstrings for their public APIs.

Apply the same fix in `@mllm/core/aops/KimiDeltaAttentionOp.hpp` around lines 11 -
30: Documents CPU restrictions and factory purpose.

Apply the same fix in `@mllm/nn/Functional.hpp` around lines 171 - 176: Documents
serialized keys, values, returns, and errors.

In `@mllm/core/aops/KimiDeltaAttentionOp.hpp`:
- Line 27: Update the const accessor KimiDeltaAttentionOp::options() to add the
[[nodiscard]] attribute, preserving its return type and existing behavior.

Apply the same fix in `@mllm/core/aops/CausalDepthwiseConv1DOp.hpp` at line 25:
Apply the same attribute to the causal-convolution accessor.

In `@mllm/models/ling3/configuration_ling3.hpp`:
- Around line 63-65: Update the Ling3 configuration constructor’s token-loading
logic alongside pad_token_id and eos_token_id to read end_of_text_token_id and
bos_token_id from the JSON config, preserving the existing defaults only when
the corresponding keys are absent if that matches the surrounding configuration
behavior. Ensure Ling3ForCausalLM uses the configured end_of_text_token_id
rather than a hardcoded default.

In `@mllm/models/ling3/modeling_ling3.hpp`:
- Around line 216-229: Update Ling3Config::validate() in configuration_ling3.hpp
to reject configurations where num_experts_per_tok exceeds topk_group *
(num_experts / n_group), preventing routed-expert selection from calling
std::partial_sort beyond ranked_experts. Add the validation alongside the
existing expert-count and group constraints, using the specified
invalid-argument error context.

In `@mllm/models/ling3/tokenization_ling3.hpp`:
- Around line 14-21: Add the direct standard-library includes <unordered_set>
and <cstdint> to the header containing the tokenization declarations, alongside
the existing includes. This must directly support
std::unordered_set<std::wstring> in added_tokens_ and uint32_t usages without
relying on transitive includes.

In `@tests/cpu/CMakeLists.txt`:
- Around line 13-28: Register Mllm-Test-Ling3-KDA, Mllm-Test-Ling3-Config,
Mllm-Test-Ling3-RoPE, and Mllm-Test-Ling3-Tokenizer with CTest using the
existing test-registration convention, then update the CI workflow to invoke
CTest so these registered tests run in CI.

---

Nitpick comments:
In `@mllm/compile/jit/interpreter/AopsFromJson.hpp`:
- Around line 45-46: Rename __kimiDeltaAttentionFromJson and
__causalDepthwiseConv1dFromJson to equivalent names without leading double
underscores, updating their declarations, definitions, and all dispatch call
sites consistently.

In `@mllm/models/ling3/configuration_ling3.hpp`:
- Around line 127-139: Cache the result of numFullAttentionLayers() during
configuration initialization and have subsequent callers reuse that stored
count, including numKDALayers() and architecture checks such as
hasOfficialLing3TinyArchitecture. Alternatively, document that the method
intentionally performs a linear scan if caching cannot fit the existing design.

In `@mllm/models/ling3/modeling_ling3.hpp`:
- Around line 466-473: Update the KV-cache path around padLing3ValuesForCache
and cache->updateKVCache so keys use qk_dim_ while values retain their native
value dimension, avoiding padded value storage. Use separate key/value head
dimensions if nn::StaticCache supports them, and adjust the subsequent attention
matmul to consume the unpadded cached values.
- Around line 316-334: Add a concise comment immediately before the q_conv1d_,
k_conv1d_, and v_conv1d_ registrations explaining that these Conv1D modules only
hold depthwise weights for the corresponding causal convolution modules and
preserve checkpoint parameter names; do not alter their execution or
registration.
- Around line 283-293: Optimize moeInfer’s per-token expert dispatch by grouping
token rows by expert ID, gathering each expert’s assigned inputs, and invoking
each expert once on the grouped batch instead of once per (token, route) pair.
Scatter the resulting rows back into routed_output using the original
token/route positions, preserving id_values ordering, topk_weights aggregation,
and output dtype behavior.

In `@mllm/models/ling3/tokenization_ling3.hpp`:
- Around line 54-68: Update the comment above the cluster scan to explicitly
state that composition is limited to combining marks classified as accent marks
by unicode_cpt_flags, and that other marks such as Devanagari or Hebrew remain
decomposed rather than receiving full NFC normalization.
- Around line 195-204: Update the Ling-3 tokenizer constructor around
bpe_.initFromSentencePieceJson and tokenizer_json to avoid parsing
tokenizer.json twice by parsing once and reusing the parsed document with the
BPE loader if supported. Handle nlohmann::json::parse_error and rethrow
std::invalid_argument containing clear Ling-3 context and the file path, while
preserving the NFC normalizer validation.
- Around line 260-268: Refactor the duplicated tensor allocation and vocabulary
lookup logic in convert2Ids and convertMessage into one helper that accepts the
desired memory type. Have both methods delegate to this helper, preserving
kExtraInput for convert2Ids and kNormal for convertMessage while keeping the
existing tensor shape, type, name, and token-to-ID behavior.

In `@tests/cpu/Ling3TokenizerTest.cpp`:
- Around line 30-41: Document the required LING3_OFFICIAL_TOKENIZER environment
variable in the Ling-3 documentation, including how maintainers should set it to
enable MatchesOfficialByteBPEAndNFCVectors and
RendersOfficialSingleTurnThinkingTemplates. Ensure the documentation is
discoverable in a supported Markdown, text, or YAML file and mention any related
LING3_RUNTIME_CONFIG requirement if applicable.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 64faf2a4-85de-4f5e-9a69-933bfaafff29

📥 Commits

Reviewing files that changed from the base of the PR and between 4978281 and ee0e405.

⛔ Files ignored due to path filters (1)
  • bench_assets/ling3_tiny_architecture.png is excluded by !**/*.png
📒 Files selected for processing (49)
  • README-ZH.md
  • README.md
  • examples/CMakeLists.txt
  • examples/ling3/CMakeLists.txt
  • examples/ling3/README.md
  • examples/ling3/config_tiny_w4a32_kai.json
  • examples/ling3/main.cpp
  • examples/ling3/quant_cfg_tiny_w4a32_kai.json
  • examples/ling3/test_validators.py
  • examples/ling3/validate_checkpoint.py
  • examples/ling3/validate_converted_model.py
  • mllm/backends/cpu/CPUBackend.cpp
  • mllm/backends/cpu/kernels/common/kda/kimi_delta_attention.cpp
  • mllm/backends/cpu/kernels/common/kda/kimi_delta_attention.hpp
  • mllm/backends/cpu/kernels/common/paged_attn/arch.hpp
  • mllm/backends/cpu/ops/CausalDepthwiseConv1DOp.cpp
  • mllm/backends/cpu/ops/CausalDepthwiseConv1DOp.hpp
  • mllm/backends/cpu/ops/KimiDeltaAttentionOp.cpp
  • mllm/backends/cpu/ops/KimiDeltaAttentionOp.hpp
  • mllm/compile/ir/GeneratedRTTIKind.hpp
  • mllm/compile/ir/NodeRTTIClassOfImpl.hpp
  • mllm/compile/ir/linalg/Op.cpp
  • mllm/compile/ir/linalg/Op.hpp
  • mllm/compile/ir/rtti_kind_gen.py
  • mllm/compile/jit/binary/LinalgIRSerialization.cpp
  • mllm/compile/jit/binary/LinalgIRSerialization.hpp
  • mllm/compile/jit/interpreter/AopsFromJson.cpp
  • mllm/compile/jit/interpreter/AopsFromJson.hpp
  • mllm/core/OpTypes.hpp
  • mllm/core/aops/CausalDepthwiseConv1DOp.cpp
  • mllm/core/aops/CausalDepthwiseConv1DOp.hpp
  • mllm/core/aops/KimiDeltaAttentionOp.cpp
  • mllm/core/aops/KimiDeltaAttentionOp.hpp
  • mllm/models/ling3/configuration_ling3.hpp
  • mllm/models/ling3/modeling_ling3.hpp
  • mllm/models/ling3/tokenization_ling3.hpp
  • mllm/nn/Functional.cpp
  • mllm/nn/Functional.hpp
  • mllm/nn/Nn.hpp
  • mllm/nn/layers/CausalDepthwiseConv1D.cpp
  • mllm/nn/layers/CausalDepthwiseConv1D.hpp
  • mllm/nn/layers/KimiDeltaAttention.cpp
  • mllm/nn/layers/KimiDeltaAttention.hpp
  • tests/cpu/CMakeLists.txt
  • tests/cpu/KaiW4A32PackTest.cpp
  • tests/cpu/Ling3ConfigTest.cpp
  • tests/cpu/Ling3KDATest.cpp
  • tests/cpu/Ling3RoPETest.cpp
  • tests/cpu/Ling3TokenizerTest.cpp

Comment thread examples/ling3/README.md
Comment on lines +12 to +36
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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

State the required working directory for these commands.

The commands reference validate_checkpoint.py, validate_converted_model.py, and config_tiny_w4a32_kai.json with relative paths. They fail when users run them from the repository root.

Add an explicit cd examples/ling3 step, or make all script, configuration, and runner paths repository-relative.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@examples/ling3/README.md` around lines 12 - 36, Update the Ling-3 README
command sequence to establish the required working directory before using
relative paths: add an explicit cd examples/ling3 step before the validation,
conversion, and smoke-run commands, or convert every referenced script,
configuration, and runner path to repository-relative paths.

Comment on lines +19 to +61
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,
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Add hidden_act to OFFICIAL_CONTRACT.

examples/ling3/config_tiny_w4a32_kai.json declares "hidden_act": "silu", but validate_config does not validate it. An altered source or runtime configuration can therefore pass this claimed contract validation with a different activation.

Add the field and add a negative test that changes hidden_act.

Proposed fix
     "rope_theta": 6000000,
+    "hidden_act": "silu",
     "layer_group_size": 4,
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@examples/ling3/validate_checkpoint.py` around lines 19 - 61, Add
"hidden_act": "silu" to OFFICIAL_CONTRACT and extend the negative validation
tests to mutate hidden_act to a different activation, asserting validate_config
rejects the altered configuration.

Comment on lines +200 to +209
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("<Q", raw_length)[0]
raw_header = file.read(header_length)
if len(raw_header) != header_length:
raise AssertionError(f"Truncated safetensors metadata: {path}")
return json.loads(raw_header)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Bound the safetensors header length before reading it.

Line 205 accepts an input-controlled unsigned 64-bit length. Line 206 can then request an excessive allocation for a malformed checkpoint. Reject lengths larger than the remaining file size and a defined maximum metadata size before the read.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@examples/ling3/validate_checkpoint.py` around lines 200 - 209, Update
read_safetensors_header to determine the file’s remaining size after reading the
8-byte length, define or reuse a maximum metadata-size limit, and reject
header_length when it exceeds either the remaining file size or that limit
before calling file.read(header_length). Preserve the existing truncated-header
and JSON parsing behavior for valid lengths.

Comment on lines +212 to +221
def validate_shards(checkpoint: Path, weight_map: dict[str, str], shapes: dict[str, list[int]]) -> 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"])

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Validate every weight_map shard assignment.

The validator stores descriptors only by tensor name. A checkpoint index that swaps two valid tensor names between shards passes the tensor-set and shape checks, even though its index points each loader to the wrong shard.

Require weight_map[name] == shard_name while scanning each shard. Add a regression test with two swapped index entries.

Proposed fix
         header = read_safetensors_header(checkpoint / shard_name)
         for name, descriptor in header.items():
             if name == "__metadata__":
                 continue
+            if weight_map.get(name) != shard_name:
+                raise AssertionError(f"{name}: index maps to {weight_map.get(name)!r}, found in {shard_name!r}")
             if name in actual:
                 raise AssertionError(f"Duplicate tensor across shards: {name}")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def validate_shards(checkpoint: Path, weight_map: dict[str, str], shapes: dict[str, list[int]]) -> 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"])
def validate_shards(checkpoint: Path, weight_map: dict[str, str], shapes: dict[str, list[int]]) -> 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 weight_map.get(name) != shard_name:
raise AssertionError(f"{name}: index maps to {weight_map.get(name)!r}, found in {shard_name!r}")
if name in actual:
raise AssertionError(f"Duplicate tensor across shards: {name}")
actual[name] = (descriptor["dtype"], descriptor["shape"])
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@examples/ling3/validate_checkpoint.py` around lines 212 - 221, Update
validate_shards to verify each tensor’s weight_map assignment while scanning the
header: require weight_map[name] to equal the current shard_name before
recording its descriptor, while preserving duplicate detection. Add a regression
test using two valid tensor names whose index entries are swapped between shards
and assert validation fails.

Comment on lines +11 to +25
struct CausalDepthwiseConv1DOpOptions : public BaseOpOptions<CausalDepthwiseConv1DOpOptions> {
bool state_inplace = false;
};

class CausalDepthwiseConv1DOp : public BaseOp {
public:
explicit CausalDepthwiseConv1DOp(const CausalDepthwiseConv1DOpOptions& options);

void load(const ParameterFile::ptr_t& ploader) override;
void trace(void* trace_context, const std::vector<Tensor>& inputs, std::vector<Tensor>& outputs) override;
void forward(const std::vector<Tensor>& inputs, std::vector<Tensor>& outputs) override;
void reshape(const std::vector<Tensor>& inputs, std::vector<Tensor>& outputs) override;
void setup(const std::vector<Tensor>& inputs, std::vector<Tensor>& outputs) override;

inline const CausalDepthwiseConv1DOpOptions& options() const { return options_; }

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Document the public stateful-operation APIs. Add clear documentation for tensor layouts, input and output ordering, state ownership, state_inplace aliasing behavior, constructor options, CPU restrictions, serialization fields, valid ranges, and validation errors across the new causal-convolution and Kimi Delta Attention declarations. Cover the corresponding layer, functional, backend, kernel, and serialization declarations so callers can use the contracts without reading implementation code.

📍 Affects 3 files
  • mllm/core/aops/CausalDepthwiseConv1DOp.hpp#L11-L25 (this comment)
  • mllm/core/aops/KimiDeltaAttentionOp.hpp#L11-L30
  • mllm/nn/Functional.hpp#L171-L176
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@mllm/core/aops/CausalDepthwiseConv1DOp.hpp` around lines 11 - 25, Document
the public causal-convolution contract at
mllm/core/aops/CausalDepthwiseConv1DOp.hpp:11-25, including [B,S,C], [C,1,K],
and [B,C,K-1] layouts, both outputs, state_inplace ownership/behavior, and
std::invalid_argument conditions. Document constructor behavior and the
two-output forward contract at mllm/nn/layers/CausalDepthwiseConv1D.hpp:11-17.
Document contiguous FP32 tensor requirements and the factory purpose at
mllm/backends/cpu/ops/CausalDepthwiseConv1DOp.hpp:10-23; these sites all require
comments or docstrings for their public APIs.

Apply the same fix in `@mllm/core/aops/KimiDeltaAttentionOp.hpp` around lines 11 -
30: Documents CPU restrictions and factory purpose.

Apply the same fix in `@mllm/nn/Functional.hpp` around lines 171 - 176: Documents
serialized keys, values, returns, and errors.

Source: Coding guidelines

void reshape(const std::vector<Tensor>& inputs, std::vector<Tensor>& outputs) override;
void setup(const std::vector<Tensor>& inputs, std::vector<Tensor>& outputs) override;

inline const KimiDeltaAttentionOpOptions& options() const { return options_; }

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Mark both public options() accessors as [[nodiscard]]. Apply this to the Kimi Delta Attention and causal-convolution option accessors. The Kimi accessor is relevant to the repository's clang-tidy configuration, which treats warnings as errors, and applying the attribute consistently prevents silently discarded configuration reads.

📍 Affects 2 files
  • mllm/core/aops/KimiDeltaAttentionOp.hpp#L27-L27 (this comment)
  • mllm/core/aops/CausalDepthwiseConv1DOp.hpp#L25-L25
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@mllm/core/aops/KimiDeltaAttentionOp.hpp` at line 27, Update the const
accessor KimiDeltaAttentionOp::options() to add the [[nodiscard]] attribute,
preserving its return type and existing behavior.

Apply the same fix in `@mllm/core/aops/CausalDepthwiseConv1DOp.hpp` at line 25:
Apply the same attribute to the causal-convolution accessor.

Source: Linters/SAST tools

Comment on lines +63 to +65
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);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Load end_of_text_token_id and bos_token_id from the config file.

The constructor reads pad_token_id and eos_token_id from JSON, but leaves end_of_text_token_id and bos_token_id at their hardcoded defaults. Ling3ForCausalLM uses end_of_text_token_id as an additional stop token (modeling_ling3.hpp Line 616). hasOfficialLing3TinyArchitecture does not verify either value, so a config that declares different ids is accepted while generation still stops on the hardcoded id.

🛠️ Proposed fix
     pad_token_id = config.at("pad_token_id");
     eos_token_id = config.at("eos_token_id");
+    end_of_text_token_id = config.value("end_of_text_token_id", end_of_text_token_id);
+    bos_token_id = config.value("bos_token_id", bos_token_id);
     max_cache_length = config.value("max_cache_length", max_cache_length);

Also applies to: 119-123

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@mllm/models/ling3/configuration_ling3.hpp` around lines 63 - 65, Update the
Ling3 configuration constructor’s token-loading logic alongside pad_token_id and
eos_token_id to read end_of_text_token_id and bos_token_id from the JSON config,
preserving the existing defaults only when the corresponding keys are absent if
that matches the surrounding configuration behavior. Ensure Ling3ForCausalLM
uses the configured end_of_text_token_id rather than a hardcoded default.

Comment on lines +216 to +229
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]; }

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Guard the routed-expert candidate count before std::partial_sort.

ranked_experts holds top_groups_ * experts_per_group entries. std::partial_sort(begin, begin + top_k_, end) has undefined behavior when top_k_ exceeds that count. Ling3Config::validate() checks num_experts_per_tok <= num_experts and topk_group <= n_group, but it does not check num_experts_per_tok <= topk_group * (num_experts / n_group). A config with n_group = 8, topk_group = 1, and num_experts_per_tok = 32 passes validation and then reads past the end of the vector.

Add the missing constraint in Ling3Config::validate() in mllm/models/ling3/configuration_ling3.hpp.

🛡️ Proposed fix in `configuration_ling3.hpp` validate()
if (num_experts_per_tok > topk_group * (num_experts / n_group)) {
  throw std::invalid_argument("Ling-3 top-k exceeds the routable experts in the selected groups");
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@mllm/models/ling3/modeling_ling3.hpp` around lines 216 - 229, Update
Ling3Config::validate() in configuration_ling3.hpp to reject configurations
where num_experts_per_tok exceeds topk_group * (num_experts / n_group),
preventing routed-expert selection from calling std::partial_sort beyond
ranked_experts. Add the validation alongside the existing expert-count and group
constraints, using the specified invalid-argument error context.

Comment on lines +14 to +21
#include <algorithm>
#include <cwctype>
#include <fstream>
#include <stdexcept>
#include <string>
#include <string_view>
#include <unordered_map>
#include <vector>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Add the missing <unordered_set> and <cstdint> includes.

Line 311 declares std::unordered_set<std::wstring> added_tokens_, and lines 27 and 39 use uint32_t. Neither header is included, so the file compiles only when another header pulls them in transitively. This breaks on other standard libraries.

🛠️ Proposed fix
 `#include` <algorithm>
+#include <cstdint>
 `#include` <cwctype>
 `#include` <fstream>
 `#include` <stdexcept>
 `#include` <string>
 `#include` <string_view>
 `#include` <unordered_map>
+#include <unordered_set>
 `#include` <vector>

As per coding guidelines: "Ensure code is portable across supported platforms (e.g., Linux, Windows) unless explicitly platform-specific."

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
#include <algorithm>
#include <cwctype>
#include <fstream>
#include <stdexcept>
#include <string>
#include <string_view>
#include <unordered_map>
#include <vector>
#include <algorithm>
#include <cstdint>
#include <cwctype>
#include <fstream>
#include <stdexcept>
#include <string>
#include <string_view>
#include <unordered_map>
#include <unordered_set>
#include <vector>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@mllm/models/ling3/tokenization_ling3.hpp` around lines 14 - 21, Add the
direct standard-library includes <unordered_set> and <cstdint> to the header
containing the tokenization declarations, alongside the existing includes. This
must directly support std::unordered_set<std::wstring> in added_tokens_ and
uint32_t usages without relying on transitive includes.

Source: Coding guidelines

Comment thread tests/cpu/CMakeLists.txt Outdated
Comment on lines +13 to +28
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})

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Check how CPU tests are registered with CTest.
fd -t f 'CMakeLists.txt' tests | xargs -I{} sh -c 'echo "== {} =="; cat -n {}'
rg -n 'add_test|gtest_discover_tests|enable_testing|include\(GoogleTest\)' --iglob '*CMakeLists.txt' --iglob '*.cmake'

Repository: UbiquitousLearning/mllm

Length of output: 10968


🏁 Script executed:

#!/bin/bash
printf '%s\n' '== CMake test setup ==' 
fd -t f 'CMakeLists.txt' . | xargs rg -n -H 'enable_testing|include\(GoogleTest\)|gtest_discover_tests|add_test|add_subdirectory\(tests\)'
printf '%s\n' '== CI test commands =='
fd -t f .github | xargs -r rg -n -H 'ctest|CTest|Mllm-Test|tests|cmake --build|cmake --install'

Repository: UbiquitousLearning/mllm

Length of output: 1050


🏁 Script executed:

#!/bin/bash
printf '%s\n' '== Top-level test configuration =='
sed -n '260,290p' CMakeLists.txt
printf '%s\n' '== GitHub workflow files =='
fd -t f .github | sort
printf '%s\n' '== Test-related workflow and script references =='
rg -n -H -i 'ctest|test$|tests|cmake' .github scripts 2>/dev/null | head -200

Repository: UbiquitousLearning/mllm

Length of output: 4967


Make the Ling3 tests run in CI.

The four targets have no add_test or gtest_discover_tests registration. Only Mllm-Test-Qwen35-Multimodal is registered in tests/cpu/CMakeLists.txt. The repository workflows also do not invoke CTest. Register the four targets and add a CI test step that runs them.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/cpu/CMakeLists.txt` around lines 13 - 28, Register Mllm-Test-Ling3-KDA,
Mllm-Test-Ling3-Config, Mllm-Test-Ling3-RoPE, and Mllm-Test-Ling3-Tokenizer with
CTest using the existing test-registration convention, then update the CI
workflow to invoke CTest so these registered tests run in CI.

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 (UbiquitousLearning#701/UbiquitousLearning#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.
Split the branch-local Ling3KDATest into the layers that the repository
now maintains separately (UbiquitousLearning#704/UbiquitousLearning#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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant