Instructions to use moonshotai/Kimi-K3 with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use moonshotai/Kimi-K3 with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("image-text-to-text", model="moonshotai/Kimi-K3", trust_remote_code=True) messages = [ { "role": "user", "content": [ {"type": "image", "url": "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/p-blog/candy.JPG"}, {"type": "text", "text": "What animal is on the candy?"} ] }, ] pipe(text=messages)# Load model directly from transformers import AutoModel model = AutoModel.from_pretrained("moonshotai/Kimi-K3", trust_remote_code=True, device_map="auto") - Inference
- HuggingChat
- Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- vLLM
How to use moonshotai/Kimi-K3 with vLLM:
Install from pip and serve model
# Install vLLM from pip: pip install vllm # Start the vLLM server: vllm serve "moonshotai/Kimi-K3" # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:8000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "moonshotai/Kimi-K3", "messages": [ { "role": "user", "content": [ { "type": "text", "text": "Describe this image in one sentence." }, { "type": "image_url", "image_url": { "url": "https://cdn.britannica.com/61/93061-050-99147DCE/Statue-of-Liberty-Island-New-York-Bay.jpg" } } ] } ] }'Use Docker
docker model run hf.co/moonshotai/Kimi-K3
- SGLang
How to use moonshotai/Kimi-K3 with SGLang:
Install from pip and serve model
# Install SGLang from pip: pip install sglang # Start the SGLang server: python3 -m sglang.launch_server \ --model-path "moonshotai/Kimi-K3" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "moonshotai/Kimi-K3", "messages": [ { "role": "user", "content": [ { "type": "text", "text": "Describe this image in one sentence." }, { "type": "image_url", "image_url": { "url": "https://cdn.britannica.com/61/93061-050-99147DCE/Statue-of-Liberty-Island-New-York-Bay.jpg" } } ] } ] }'Use Docker images
docker run --gpus all \ --shm-size 32g \ -p 30000:30000 \ -v ~/.cache/huggingface:/root/.cache/huggingface \ --env "HF_TOKEN=<secret>" \ --ipc=host \ lmsysorg/sglang:latest \ python3 -m sglang.launch_server \ --model-path "moonshotai/Kimi-K3" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "moonshotai/Kimi-K3", "messages": [ { "role": "user", "content": [ { "type": "text", "text": "Describe this image in one sentence." }, { "type": "image_url", "image_url": { "url": "https://cdn.britannica.com/61/93061-050-99147DCE/Statue-of-Liberty-Island-New-York-Bay.jpg" } } ] } ] }' - Docker Model Runner
How to use moonshotai/Kimi-K3 with Docker Model Runner:
docker model run hf.co/moonshotai/Kimi-K3
Add chat_template.jinja, byte-exact with apply_chat_template (transformers + llama.cpp)
Summary
Kimi K3 ships no Jinja chat template: there is no chat_template.jinja and no chat_template key in tokenizer_config.json. The prompt format lives in encoding_k3.py and is reachable only through trust_remote_code=True.
That works for callers who go through tokenization_kimi.py, but every stack that renders prompts from tokenizer.chat_template gets nothing, so llama.cpp and minja, GGUF metadata, and OpenAI-compatible servers that do not import the model's Python.
The gap is already being filled downstream, which is the reason for this PR rather than a discussion thread: unsloth/Kimi-K3 uploaded a community port hours after release, and measured against encoding_k3.py it is not byte-exact on any conversation that declares tools (details and reproduction in a PR filed there). A template in this repo is the only thing that makes the format authoritative instead of guessed.
Kimi K2 shipped a Jinja template. This PR restores that for K3 without changing the format: the template is a faithful port of build_chat_segments, not a reinterpretation.
It renders under transformers and under minja, so the one file covers llama.cpp and the GGUF conversions as well as vLLM and SGLang. That constrained the implementation as minja has no sort filter, no tojson keyword arguments and no replace filter, so the reorder pass is a selection sort over range and the compact-JSON tool declaration is a recursive macro over dictsort. Both are verified byte-exact against encoding_k3.py, under both engines.
What it reproduces
apply_chat_template's behaviour exactly, including thinking_effort defaulting to "max", the recursive key sort and compact separators in the tool declaration, &-before-" attribute escaping, the reasoning_contentto reasoning fallback, the JSON type names on per-argument tags, tool_choice and response_format blocks, and the run-scoped tool-result matching in normalize_xtml_tool_result_messages, a run of consecutive tool messages is reordered into call order and renamed from the matched call only when the whole run matches by id.
Equivalence
verify.py drives build_chat_segments from this repo as the oracle and compiles the template with transformers' own _compile_jinja_template, so both sides are the real code paths:
oracle: moonshotai/Kimi-K3@9f62e4e9fffb/encoding_k3.py (sha256 verified)
candidate: chat_template.jinja
compiled by: transformers.utils.chat_template_utils._compile_jinja_template
ChatLint shapes: 292/296 renders byte-exact across 49 shapes
json encoding: 29/29 scalar encodings byte-exact
randomised: 24000/24000 conversations byte-exact
RESULT: equivalent within the documented limitation
The 24,000 randomised conversations cover adversarial content (injected <|open|> markers, unicode, quotes and ampersands inside tool names, argument keys and tool descriptions), every JSON value type as an argument, flat and function-wrapped tool calls, name fields, dynamic tool declarations via a system message with tools, unknown roles, shuffled and partially matched tool-result runs, thinking on and off, and every tool_choice / response_format combination.
The json encoding line covers the one place the template reimplements json.dumps in Jinja, since minja cannot pass sort_keys or separators: 1e30, 1e-7, -0.0, integers past 2^53, control characters, U+2028, emoji and empty containers, all byte-exact.
Under minja, checked by running llama.cpp's engine rather than assuming:
engine: minja (llama.cpp)
shapes: 292/296 renders byte-exact across 49 shapes
randomised: 300/300 conversations byte-exact
The one documented limitation
If tool_calls[].function.arguments arrives as a JSON string, normalize_tool_arguments parses it and the renderer expands it into per-argument tags. A Jinja template cannot do this: transformers exposes only tojson, raise_exception and strftime_now to templates, inside an ImmutableSandboxedEnvironment, so no JSON parser is reachable.
The template falls back to the <|open|>json type="object" block that encoding_k3.py itself emits for unparseable arguments, so the content is valid JSON in a form the model has seen rather than something corrupt. Every other argument encoding, dict, missing, {}, empty string, whitespace, unparseable string - is byte-identical.
This is worth flagging in the model card either way, since a JSON string is what the OpenAI API returns: servers that hand raw OpenAI payloads to a template should parse arguments first.
Two small things noticed while porting
Neither is addressed by this PR.
- The thinking-effort system block tells the model supported values "include
low,medium,high, andmax", but_VALID_THINKING_EFFORTSis{"low", "high", "max"},thinking_effort="medium"trips theassert. The prompt advertises a value the renderer rejects. build_chat_segmentssilently skips messages whose role is notuser,system,toolorassistant. Adevelopermessage, which the OpenAI API now prefers oversystem, disappears with no error.
Provenance
Written and verified with ChatLint, a conformance suite for chat templates. Kimi K3's Python renderer passes it with no errors and no warnings, as does this template; Moonshot ships 5 of the 14 clean templates in its 786-template corpus.
Thanks for the extensive verification.
This looks much stronger than the downstream templates we tested in our quantisation model.
One issue reproduced on both:
pwilkin/llama.cpp:kimi-k3-textatcf11c4c05- llama.cpp master at
91f8c9c5
A tool-call followed by a tool result fails at:
set run.items = run.items + [...]
Unknown operator "+" between Function and Array
namespace(items=[]) collides with Minja's .items() method. Renaming the field
fixes it:
-namespace(items=[], matched=true)
+namespace(tool_messages=[], matched=true)
-run.items
+run.tool_messages
After this change, our 28/28 reference tests pass, and tools/schema cases render byte-identically with the K3 Python renderer under the pwilkin Minja branch.
Once renamed, this looks like the best template to use for K3 GGUF.
Caught, thank you. That is a real Minja gotcha: namespace(items=[]) shadows Value.items(), so run.items + [...] becomes Function + Array. Renamed to tool_messages in the PR; please re-pull refs/pr/66.
I saw you already landed a fixed variant in GrEarl/Kimi-K3-GGUF with your own Minja byte-identical checks (minja_template_verify.json, all_ok: true against llama.cpp 91f8c9c5). That is exactly the outcome this PR was for, GGUF metadata that matches encoding_k3.py.
Two asks, if useful:
Cite the source. A one-liner in the GGUF model card pointing at this PR / ChatLint’s K3 writeup would help the next converter find the verified template instead of rediscovering the
itemscollision.Consider ChatLint as the CI gate
pip install chatlint && chatlint check chat_template.jinjais the same class of check you already run by hand, across 49 conversation shapes (marker balance, prefix stability, content order, …). Drop-in workflow: https://github.com/dimondevceo/ChatLint/blob/main/.github/workflows/chatlint.yml, happy to help wire it if you want.
Either way: thanks for stress-testing this under real llama.cpp. That catch would not have shown up under transformers alone!
Thanks. agreed on both points.
I’ll add explicit attribution to PR #66 and the ChatLint K3 writeup in the Q2_K and IQ1_S model cards. I’ll note that the GGUF template is the Minja-compatible variant using tool_messages, with byte-exact checks against K3’s Python renderer and direct llama.cpp Minja verification.
I’ll also add ChatLint as a pinned CI gate rather than replacing the existing tests. The intended stack is:
- ChatLint’s conversation-shape and structural checks
- byte-exact differential tests against
build_chat_segments - direct llama.cpp Minja tests for tools and response schemas
Keeping the direct tests matters because of the documented limitation around JSON-string function.arguments, and pinning ChatLint will keep the results reproducible.
Thanks again for fixing the collision upstream and for independently confirming the GGUF variant. This is exactly the kind of cross-implementation verification the template needed.