Skip to content

Prebuilt: carry Kimi-K3 on top of TML Inkling for the nightly PR set - #46

Closed
danielhanchen wants to merge 12 commits into
inkling-on-b10173from
kimi-k3-on-inkling
Closed

Prebuilt: carry Kimi-K3 on top of TML Inkling for the nightly PR set#46
danielhanchen wants to merge 12 commits into
inkling-on-b10173from
kimi-k3-on-inkling

Conversation

@danielhanchen

Copy link
Copy Markdown
Member

Companion to #45. Kimi-K3 for the nightly prebuild, rebuilt so it merges onto the release tag.

kimi-k3-unsloth (#44) sits on ggml-org#26185's head, which was cut before the Parakeet clip graph and the WebGPU arch-skip rework landed upstream, so neither ggml-org#26185 nor #44 merges onto b10173 on its own. Inkling and Kimi-K3 also collide with each other, since both register a new architecture in the same registry files, so they have to be combined once rather than pinned as two independent heads.

Two steps, both merges:

  1. b10173 + kimi-k3: full-size model fixes and the MoonViT-3d vision tower #44 (which already contains model: add Kimi-K3 text model ggml-org/llama.cpp#26185)
  2. merge Prebuilt: carry TML Inkling on b10173 for the nightly PR set #45 (Inkling on b10173)
    • src/llama-arch.cpp - KIMI_K3 and INKLING in llm_arch_is_hybrid and llm_arch_supports_sm_tensor.
    • src/llama-model.cpp - both llama_model_* constructors and both LLAMA_ROPE_TYPE_NONE cases.
    • tools/mtmd/CMakeLists.txt - build models/kimik3.cpp and models/inkling.cpp.

The base branch is #45's head, so the diff here is the Kimi-K3 change plus the two-way registry resolution.

pwilkin and others added 12 commits July 27, 2026 19:49
Hybrid KDA (linear) + MLA (full) attention as in Kimi-Linear-48B, plus five
things that architecture does not have:

  1. cross-layer residual attention  (attn_res_block_size)
  2. latent MoE                      (routed experts run at n_expert_latent)
  3. situ activation                 (replaces SwiGLU everywhere)
  4. MLA output gate                 (sigmoid gate before o_proj)
  5. full-rank KDA gate              (single ssm_g instead of ssm_g_a/ssm_g_b)

K3's text_config reports KimiLinearForCausalLM - the older 48B architecture -
so get_model_architecture routes on the top-level name instead.

The KDA decay gate has two forms, selected by linear_attn_config's
gate_lower_bound. It is not a clamp: when set it swaps the activation entirely
(fla/ops/kda/gate.py), from -exp(A_log)*softplus(x) to
lower_bound*sigmoid(exp(A_log)*x). K3 sets it to -5.0; kimi-linear leaves it
unset, so that path is unchanged.

Cross-layer residuals reuse ggml_dsv4_hc_pre for the weighted sum. That op is
CPU + CUDA only, so Metal/Vulkan will fall back per-node until those kernels
exist.

The routed experts ship as compressed-tensors "mxfp4-pack-quantized". That is
bit-compatible with ggml's MXFP4 - same E2M1 code assignment, same E8M0 scale
byte, only the nibble positions within a block differ - so they are repacked
rather than dequantized, losslessly and without a ~5.5 TB bf16 round-trip.
The repack is built lazily because gguf_writer holds every added tensor until
the final write. DeepSeek-V4 was already doing the identical bit-shuffling, so
it now shares the helper.

Verified against Moonshot's own code path (transformers + fla's Triton KDA
kernels) on a tiny model exercising every K3-specific feature. Final-position
logits vs the fp32 reference: 6.7e-05 rel / corr 1.00000000 for both the
chunked and the recurrent delta-net path. MXFP4 blocks dequantize to the source
weights with 0.0e+00 error.

Assisted-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- `_res_parts` buffers (kind, tensor) pairs, not bare tensors
- `get_tensors` must return an Iterator, matching ModelBase
- LazyBase's `func` takes one argument, so pass the expert loaders through
  `args` instead of the closure
- borrowing KimiLinearModel.set_vocab from an unrelated TextModel is
  deliberate and safe, but not expressible in the signature

No behaviour change: the MXFP4 repack still dequantizes to the source weights
with 0.0e+00 error and end-to-end logits are unchanged (8.386e-03 rel,
corr 0.99996630).

Assisted-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: Boris Dvorkin  <b_dvorkin@niuitmo.ru>
K3's assistant output is an XTML-ish tagged format built by the template's
open_tag/close_tag macros. Two properties break generic parsing:

1. The generation prompt ends with open_tag('think'), so the completion
   starts inside the think section with no opening marker in the output
   (thinking_forced_open).
2. Only <|open|>/<|close|>/<|sep|>/<|end_of_msg|> are special tokens; tag
   names ("think", "response", "message") are ordinary text tokens.

Adds common_chat_params_init_kimi_k3 (PEG_NATIVE) with detection on the
marker trio, reasoning extraction, response unwrapping, and tool-call
parsing of the tools/call/argument tag structure with argument types
taken from the tool schema. Includes the K3 chat template fixture and 9
test-chat cases derived from real generations of the full 2.8T model.

Verified end-to-end against Kimi-K3-Q2_K (GrEarl/Kimi-K3-GGUF) on 8x B200:
content, reasoning_content, streaming deltas, and tool_calls all correct;
finish_reason stop/tool_calls as appropriate.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Per-role message-start markers for token-level span splitting. User and
assistant messages carry only the role attribute, so their full opener
(through <|sep|>) is used; system and tool messages continue with more
attributes (type=/tool=/index=), so those delimiters stop after the
role's closing quote. Verified against the K3 tiktoken vocabulary that
the closing quote is always a standalone token across all attribute
variants, so the token-level prefix match stays exact.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
chat : add Kimi K3 chat format (reasoning, content, typed tool calls)
Graph node budget, LLM_TYPE for the 93-layer config, four hparams promoted to
required, and an E8M0 NaN check during the MXFP4 repack. Details in the PR
description.

Assisted-by: Claude Code
New kimik3 projector type, its graph builder and the mmproj converter. Also adds
an optional clip.%s.attention.head_dim so build_vit stops deriving d_head from
n_embd, which is wrong whenever a tower's qkv width differs from n_embd.

Assisted-by: Claude Code
…uild

kimi-k3-unsloth was rebased onto ggml-org#26185's head, which was cut
before the Parakeet clip graph and the WebGPU arch-skip rework landed
upstream. Two add/add conflicts:

- tools/mtmd/models/models.h: keep clip_graph_parakeet and
  clip_graph_kimik3, both registered
- tests/test-llama-archs.cpp: upstream replaced the WebGPU skip list with
  DEEPSEEK32/GLM_DSA after fixing the binding alias issues; keep that list
  and append KIMI_K3, which upstream never evaluated

Assisted-by: Claude Code
Inkling and Kimi-K3 both register a new architecture, so they collide in
the arch/model/mtmd registries. Both sides are kept:

- src/llama-arch.cpp: KIMI_K3 and INKLING in llm_arch_is_hybrid and
  llm_arch_supports_sm_tensor
- src/llama-model.cpp: both llama_model_* constructors and both
  LLAMA_ROPE_TYPE_NONE cases
- tools/mtmd/CMakeLists.txt: build models/kimik3.cpp and models/inkling.cpp

Assisted-by: Claude Code
@danielhanchen
danielhanchen deleted the branch inkling-on-b10173 July 29, 2026 10:23
@danielhanchen

Copy link
Copy Markdown
Member Author

Superseded by #47. The Inkling merge and the conflict fixes went onto #44's branch instead, so the set pins #44 directly.

@danielhanchen
danielhanchen deleted the kimi-k3-on-inkling branch July 29, 2026 10:23

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2b4c6d0675

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +189 to +190
{%- if thinking is undefined -%}
{%- set thinking = true -%}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Honor the standard thinking toggle

When callers set enable_thinking=false (including through reasoning_effort: "none"), the rendering context provides enable_thinking, not thinking, but this template defaults the unrelated thinking variable to true. Kimi-K3 therefore still inserts the thinking-effort message and opens a think section, so the standard API cannot disable reasoning for this model.

Useful? React with 👍 / 👎.

Comment thread common/chat.cpp
Comment on lines +2250 to +2251
auto value = type == "string" ? p.tool_arg_string_value(p.until(ARG_END)) :
p.tool_arg_value(p.until(ARG_END));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Constrain tagged arguments to their property schemas

With tools enabled, both parser branches accept arbitrary text through until(ARG_END) rather than attaching prop.value() as a schema constraint. Consequently the generated grammar permits invalid values such as text for a numeric property, and the mapper then emits malformed JSON such as {"arg":abc}; nullable-string and other union schemas can also be serialized incorrectly because only a literal string type is recognized.

Useful? React with 👍 / 👎.

Comment thread common/chat.cpp
Comment on lines +2175 to +2176
{ COMMON_CHAT_ROLE_ASSISTANT, "<|open|>message role=\"assistant\"<|sep|>" },
{ COMMON_CHAT_ROLE_USER, "<|open|>message role=\"user\"<|sep|>" },

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Match named user and assistant message openers

When a user or assistant message has a name, the new template inserts name="..." before <|sep|>, so these full delimiters never occur in the rendered prompt. Since server message-span checkpointing uses exact token matches, named user messages are not recognized as user boundaries; use the same role-only prefix form already used for system and tool messages.

Useful? React with 👍 / 👎.

{%- endmacro -%}

{%- macro json_sorted(value) -%}
{#- Minja は tojson(sort_keys=true) を実装していない。K3 参照実装は

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Replace the non-ASCII template comment

This newly added comment contains Japanese Unicode, while the repository explicitly requires avoiding any Unicode characters. Rewrite it using ASCII text so the template follows the documented contribution standard.

AGENTS.md reference: AGENTS.md:L73-L73

Useful? React with 👍 / 👎.

case LLM_ARCH_PADDLEOCR:
case LLM_ARCH_MIMO2:
case LLM_ARCH_KIMI_LINEAR:
case LLM_ARCH_KIMI_K3:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Populate the mandatory K3 fixture metadata

Adding K3 as a mandatory MoE architecture makes the CPU architecture test instantiate this fixture, but get_gguf_ctx() never writes the newly required kimi-k3.expert_latent_length, attention-residual, or SITU keys. Running test-llama-archs --arch kimi-k3 therefore exits during model loading with key not found in model: kimi-k3.expert_latent_length, before any backend comparison runs.

Useful? React with 👍 / 👎.

Comment thread src/llama-hparams.h
Comment on lines +179 to +183
uint32_t n_expert_latent = 0; // routed_expert_hidden_size (0 = experts run at n_embd)
uint32_t attn_res_block_size = 0; // 0 = no cross-layer attention residuals
float kda_gate_lower_bound = -INFINITY;
float situ_beta = 1.0f;
float situ_linear_beta = 0.0f; // 0 = no linear-beta transform on the up branch

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Serialize the required K3 hyperparameters

These new K3 values are required again by load_arch_hparams, but llama_model_saver::add_kv_from_model() does not serialize any of them and K3 is still reported as a saver-supported architecture. Calling llama_model_save_to_file on a loaded K3 model consequently produces a file that cannot be reloaded because expert_latent_length and the other mandatory K3 metadata are absent.

Useful? React with 👍 / 👎.

Comment thread conversion/kimivl.py
Comment on lines +173 to +174
@ModelBase.register("KimiK3ForConditionalGeneration")
class KimiK3VisionModel(MmprojModel):

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Register K3 with the multimodal converter

This new vision converter is unreachable during normal mmproj conversion: get_model_class(..., mmproj=true) rejects architectures absent from MMPROJ_MODEL_MAP before importing conversion.kimivl, and the commit adds K3 only to TEXT_MODEL_MAP. Every K3 mmproj conversion therefore raises Architecture 'KimiK3ForConditionalGeneration' not supported instead of constructing this class.

Useful? React with 👍 / 👎.

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.

5 participants