Image-Text-to-Text
Transformers
Safetensors
kimi_k3
feature-extraction
compressed-tensors
conversational
custom_code
Eval Results
8-bit precision
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
Update encoding_k3.py
#173
by YyWangMsh - opened
- encoding_k3.py +125 -22
encoding_k3.py
CHANGED
|
@@ -187,21 +187,117 @@ def deep_sort_dict(obj: Any) -> Any:
|
|
| 187 |
return obj
|
| 188 |
|
| 189 |
|
| 190 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 191 |
if arguments is None:
|
| 192 |
-
return
|
| 193 |
if isinstance(arguments, dict):
|
| 194 |
-
return
|
|
|
|
|
|
|
|
|
|
| 195 |
if isinstance(arguments, str):
|
| 196 |
-
if
|
| 197 |
-
return
|
| 198 |
try:
|
| 199 |
-
|
| 200 |
-
except
|
| 201 |
-
return
|
| 202 |
-
if not isinstance(parsed, dict):
|
| 203 |
-
raise ValueError("Kimi K3 tool call arguments must be a JSON object.")
|
| 204 |
-
return parsed, None
|
| 205 |
raise TypeError(
|
| 206 |
"Kimi K3 tool call arguments must be a dict or a JSON object string."
|
| 207 |
)
|
|
@@ -421,7 +517,9 @@ def _render_assistant_segments(
|
|
| 421 |
"reasoning"
|
| 422 |
)
|
| 423 |
segments.extend(_open_tag("think"))
|
| 424 |
-
|
|
|
|
|
|
|
| 425 |
_append_text(segments, reasoning_content, image_state)
|
| 426 |
segments.extend(_close_tag("think"))
|
| 427 |
|
|
@@ -437,21 +535,18 @@ def _render_assistant_segments(
|
|
| 437 |
segments.extend(
|
| 438 |
_open_tag("call", [("tool", fn["name"]), ("index", index)])
|
| 439 |
)
|
| 440 |
-
args = fn.get("arguments",
|
| 441 |
json_block = fn.get("_xtml_json_block")
|
| 442 |
if json_block is not None:
|
| 443 |
segments.extend(_open_tag("json", [("type", "object")]))
|
| 444 |
_append_text(segments, json_block, image_state)
|
| 445 |
segments.extend(_close_tag("json"))
|
| 446 |
-
|
| 447 |
-
for key,
|
| 448 |
segments.extend(
|
| 449 |
-
_open_tag(
|
| 450 |
-
"argument",
|
| 451 |
-
[("key", key), ("type", _xtml_type(value))],
|
| 452 |
-
)
|
| 453 |
)
|
| 454 |
-
_append_text(segments,
|
| 455 |
segments.extend(_close_tag("argument"))
|
| 456 |
segments.extend(_close_tag("call"))
|
| 457 |
segments.extend(_close_tag("tools"))
|
|
@@ -538,10 +633,14 @@ def build_chat_segments(
|
|
| 538 |
)
|
| 539 |
|
| 540 |
for message_index, message in enumerate(messages):
|
|
|
|
| 541 |
if not isinstance(message, dict):
|
| 542 |
-
|
|
|
|
|
|
|
|
|
|
| 543 |
|
| 544 |
-
role = message
|
| 545 |
if role == "user":
|
| 546 |
attrs = [("role", "user")]
|
| 547 |
if message.get("name"):
|
|
@@ -596,6 +695,10 @@ def build_chat_segments(
|
|
| 596 |
segments.extend(_render_assistant_segments(message, image_state, thinking))
|
| 597 |
segments.extend(_close_tag("message"))
|
| 598 |
segments.extend(_end_of_msg())
|
|
|
|
|
|
|
|
|
|
|
|
|
| 599 |
|
| 600 |
tool_choice = kwargs.get("tool_choice")
|
| 601 |
if tool_choice == "required":
|
|
|
|
| 187 |
return obj
|
| 188 |
|
| 189 |
|
| 190 |
+
# One normalized tool-call argument: (key, XTML type, rendered text). The
|
| 191 |
+
# text keeps the original JSON literal for non-string values and is the
|
| 192 |
+
# decoded string for string values.
|
| 193 |
+
XtmlArgument = tuple[str, str, str]
|
| 194 |
+
|
| 195 |
+
|
| 196 |
+
def _parse_arguments_object(s: str) -> list[XtmlArgument]:
|
| 197 |
+
"""Parse a JSON object string one level deep.
|
| 198 |
+
|
| 199 |
+
Each top-level key-value pair yields one ``(key, type, text)`` triple. The
|
| 200 |
+
text keeps the original JSON literal for non-string values (``1e2`` stays
|
| 201 |
+
``1e2``, ``[1,2]`` keeps its exact bytes) and is the decoded (unescaped)
|
| 202 |
+
string for string values. Nested values are never re-serialized. Anything
|
| 203 |
+
after the closing ``}`` is ignored. Raises ``ValueError`` on malformed
|
| 204 |
+
input (including a valid non-object JSON document).
|
| 205 |
+
"""
|
| 206 |
+
idx = 0
|
| 207 |
+
|
| 208 |
+
def _skip_whitespaces() -> None:
|
| 209 |
+
nonlocal idx
|
| 210 |
+
while idx < len(s) and s[idx] in (" ", "\t", "\n", "\r"):
|
| 211 |
+
idx += 1
|
| 212 |
+
|
| 213 |
+
def _next_char() -> Optional[str]:
|
| 214 |
+
nonlocal idx
|
| 215 |
+
if idx < len(s):
|
| 216 |
+
c = s[idx]
|
| 217 |
+
idx += 1
|
| 218 |
+
return c
|
| 219 |
+
return None
|
| 220 |
+
|
| 221 |
+
_skip_whitespaces()
|
| 222 |
+
if _next_char() != "{":
|
| 223 |
+
raise ValueError("JSON arguments must be an object")
|
| 224 |
+
_skip_whitespaces()
|
| 225 |
+
|
| 226 |
+
parsed: list[XtmlArgument] = []
|
| 227 |
+
|
| 228 |
+
if idx >= len(s):
|
| 229 |
+
raise ValueError("Unexpected end of JSON object")
|
| 230 |
+
if s[idx] == "}":
|
| 231 |
+
return parsed
|
| 232 |
+
|
| 233 |
+
json_decoder = json.JSONDecoder(strict=False)
|
| 234 |
+
|
| 235 |
+
def _raw_decode(start_idx: int) -> tuple[Any, int]:
|
| 236 |
+
try:
|
| 237 |
+
return json_decoder.raw_decode(s, idx=start_idx)
|
| 238 |
+
except json.JSONDecodeError as e:
|
| 239 |
+
raise ValueError(str(e)) from e
|
| 240 |
+
|
| 241 |
+
while True:
|
| 242 |
+
decoded_key, idx = _raw_decode(idx)
|
| 243 |
+
if not isinstance(decoded_key, str):
|
| 244 |
+
raise ValueError(f"JSON object key must be a string, got {decoded_key!r}")
|
| 245 |
+
_skip_whitespaces()
|
| 246 |
+
if _next_char() != ":":
|
| 247 |
+
raise ValueError(f"Expects ':' after {decoded_key}")
|
| 248 |
+
_skip_whitespaces()
|
| 249 |
+
|
| 250 |
+
value_start_idx = idx
|
| 251 |
+
decoded_value, idx = _raw_decode(idx)
|
| 252 |
+
value_end_idx = idx
|
| 253 |
+
|
| 254 |
+
text = (
|
| 255 |
+
decoded_value
|
| 256 |
+
if isinstance(decoded_value, str)
|
| 257 |
+
else s[value_start_idx:value_end_idx]
|
| 258 |
+
)
|
| 259 |
+
parsed.append((decoded_key, _xtml_type(decoded_value), text))
|
| 260 |
+
|
| 261 |
+
_skip_whitespaces()
|
| 262 |
+
c = _next_char()
|
| 263 |
+
_skip_whitespaces()
|
| 264 |
+
|
| 265 |
+
if c == "}":
|
| 266 |
+
break
|
| 267 |
+
elif c == ",":
|
| 268 |
+
continue
|
| 269 |
+
else:
|
| 270 |
+
raise ValueError(f"Expect '}}' or ',', got {c!r}")
|
| 271 |
+
|
| 272 |
+
return parsed
|
| 273 |
+
|
| 274 |
+
|
| 275 |
+
def normalize_tool_arguments(
|
| 276 |
+
arguments: Any,
|
| 277 |
+
) -> tuple[list[XtmlArgument], Optional[str]]:
|
| 278 |
+
"""Normalize tool call arguments for XTML rendering.
|
| 279 |
+
|
| 280 |
+
Returns ``(argument_triples, raw_json_block)``. String arguments are parsed
|
| 281 |
+
one level deep so non-string values keep their original JSON literal text,
|
| 282 |
+
and anything after the closing ``}`` is discarded; dict arguments (already
|
| 283 |
+
Python objects) are serialized on the spot. Any string that is not a
|
| 284 |
+
well-formed JSON object -- unparseable, whitespace only, or valid
|
| 285 |
+
non-object JSON -- falls back to the raw ``<json>`` block.
|
| 286 |
+
"""
|
| 287 |
if arguments is None:
|
| 288 |
+
return [], None
|
| 289 |
if isinstance(arguments, dict):
|
| 290 |
+
return [
|
| 291 |
+
(str(key), _xtml_type(value), _xtml_value(value))
|
| 292 |
+
for key, value in arguments.items()
|
| 293 |
+
], None
|
| 294 |
if isinstance(arguments, str):
|
| 295 |
+
if arguments == "":
|
| 296 |
+
return [], None
|
| 297 |
try:
|
| 298 |
+
return _parse_arguments_object(arguments), None
|
| 299 |
+
except ValueError:
|
| 300 |
+
return [], arguments
|
|
|
|
|
|
|
|
|
|
| 301 |
raise TypeError(
|
| 302 |
"Kimi K3 tool call arguments must be a dict or a JSON object string."
|
| 303 |
)
|
|
|
|
| 517 |
"reasoning"
|
| 518 |
)
|
| 519 |
segments.extend(_open_tag("think"))
|
| 520 |
+
# Only an empty string counts as no reasoning, so whitespace-only
|
| 521 |
+
# reasoning is still rendered into the think channel.
|
| 522 |
+
if reasoning_content is not None and str(reasoning_content) != "":
|
| 523 |
_append_text(segments, reasoning_content, image_state)
|
| 524 |
segments.extend(_close_tag("think"))
|
| 525 |
|
|
|
|
| 535 |
segments.extend(
|
| 536 |
_open_tag("call", [("tool", fn["name"]), ("index", index)])
|
| 537 |
)
|
| 538 |
+
args = fn.get("arguments", [])
|
| 539 |
json_block = fn.get("_xtml_json_block")
|
| 540 |
if json_block is not None:
|
| 541 |
segments.extend(_open_tag("json", [("type", "object")]))
|
| 542 |
_append_text(segments, json_block, image_state)
|
| 543 |
segments.extend(_close_tag("json"))
|
| 544 |
+
else:
|
| 545 |
+
for key, arg_type, arg_text in args:
|
| 546 |
segments.extend(
|
| 547 |
+
_open_tag("argument", [("key", key), ("type", arg_type)])
|
|
|
|
|
|
|
|
|
|
| 548 |
)
|
| 549 |
+
_append_text(segments, arg_text, image_state)
|
| 550 |
segments.extend(_close_tag("argument"))
|
| 551 |
segments.extend(_close_tag("call"))
|
| 552 |
segments.extend(_close_tag("tools"))
|
|
|
|
| 633 |
)
|
| 634 |
|
| 635 |
for message_index, message in enumerate(messages):
|
| 636 |
+
# Malformed messages are rejected instead of being silently skipped.
|
| 637 |
if not isinstance(message, dict):
|
| 638 |
+
raise ValueError(
|
| 639 |
+
f"Kimi K3 messages must be dicts, got {type(message).__name__} "
|
| 640 |
+
f"at index {message_index}."
|
| 641 |
+
)
|
| 642 |
|
| 643 |
+
role = message.get("role")
|
| 644 |
if role == "user":
|
| 645 |
attrs = [("role", "user")]
|
| 646 |
if message.get("name"):
|
|
|
|
| 695 |
segments.extend(_render_assistant_segments(message, image_state, thinking))
|
| 696 |
segments.extend(_close_tag("message"))
|
| 697 |
segments.extend(_end_of_msg())
|
| 698 |
+
else:
|
| 699 |
+
raise ValueError(
|
| 700 |
+
f"Unknown message role {role!r} at index {message_index}."
|
| 701 |
+
)
|
| 702 |
|
| 703 |
tool_choice = kwargs.get("tool_choice")
|
| 704 |
if tool_choice == "required":
|