Text Generation
MLX
Safetensors
kimi_k3
Mixture of Experts
reap
pruned
kimi
apple-silicon
custom_code
Instructions to use pipenetwork/Kimi-K3-REAP73-zh-code-MLX-mxfp4-q8 with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- MLX
How to use pipenetwork/Kimi-K3-REAP73-zh-code-MLX-mxfp4-q8 with MLX:
# Make sure mlx-lm is installed # pip install --upgrade mlx-lm # if on a CUDA device, also pip install mlx[cuda] # Generate text with mlx-lm from mlx_lm import load, generate model, tokenizer = load("pipenetwork/Kimi-K3-REAP73-zh-code-MLX-mxfp4-q8") prompt = "Once upon a time in" text = generate(model, tokenizer, prompt=prompt, verbose=True) - Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- LM Studio
- MLX LM
How to use pipenetwork/Kimi-K3-REAP73-zh-code-MLX-mxfp4-q8 with MLX LM:
Generate or start a chat session
# Install MLX LM uv tool install mlx-lm # Generate some text mlx_lm.generate --model "pipenetwork/Kimi-K3-REAP73-zh-code-MLX-mxfp4-q8" --prompt "Once upon a time"
| """Kimi-K3 multimodal glue for mlx-vlm: vision tower -> prompt -> text tower. | |
| The only genuinely interesting part is `merge_image_features`. | |
| K3's processor rewrites each `<|kimi_image_placeholder|>` in the raw text into | |
| <|media_begin|>image {W}x{H}<|media_content|><|media_pad|><|media_end|> | |
| so the tokenized prompt carries **exactly one** `<|media_pad|>` (163605) per | |
| image, which must then EXPAND into that image's full token count. That differs | |
| from the more common LLaVA-style arrangement (and from mlx-vlm's existing | |
| Kimi-VL glue), where the processor has already emitted one placeholder per image | |
| token and merging is a same-length scatter. | |
| Getting this wrong is quiet rather than loud: a same-length scatter would write | |
| one image token and silently drop the rest, leaving a model that still generates | |
| fluent text while being effectively blind to most of the picture. | |
| """ | |
| from typing import Any, List, Optional | |
| import mlx.core as mx | |
| import mlx.nn as nn | |
| from .config import ModelConfig | |
| from .language import LanguageModel | |
| from .vision import VisionModel | |
| try: | |
| from mlx_vlm.models.base import InputEmbeddingsFeatures | |
| except ImportError: # allow importing the glue without mlx-vlm present | |
| from dataclasses import dataclass | |
| class InputEmbeddingsFeatures: # type: ignore[no-redef] | |
| inputs_embeds: mx.array | |
| attention_mask: Optional[mx.array] = None | |
| position_ids: Optional[mx.array] = None | |
| def merge_image_features( | |
| inputs_embeds: mx.array, | |
| input_ids: mx.array, | |
| image_features: List[mx.array], | |
| placeholder_id: int, | |
| pad_token_id: Optional[int] = None, | |
| ): | |
| """Expand each placeholder token into its image's feature block. | |
| inputs_embeds : (B, S, D) token embeddings | |
| input_ids : (B, S) | |
| image_features: list of (n_i, D), one per image, in prompt order | |
| returns : (B, S', D) merged embeddings and a (B, S') attention mask, | |
| where S' = S - K + sum(n_i) for K placeholders | |
| Rows are built by concatenating the spans between placeholders, which is | |
| O(images) concatenations rather than O(sequence length) scatter indices, and | |
| is far easier to check than the reference's index arithmetic. With B > 1 the | |
| rows are LEFT-padded to the longest merged length, matching the reference's | |
| `left_padding` branch (it is what generation with a KV cache expects). | |
| """ | |
| B, S = input_ids.shape | |
| ids = input_ids.tolist() | |
| n_ph_total = sum(1 for row in ids for t in row if t == placeholder_id) | |
| if n_ph_total != len(image_features): | |
| raise ValueError( | |
| f"{n_ph_total} <|media_pad|> placeholder(s) in the prompt but " | |
| f"{len(image_features)} image feature block(s). K3 uses exactly one " | |
| f"placeholder per image." | |
| ) | |
| rows, masks, taken = [], [], 0 | |
| for b in range(B): | |
| parts, mask_parts, prev = [], [], 0 | |
| for j, tok in enumerate(ids[b]): | |
| if tok != placeholder_id: | |
| continue | |
| if j > prev: | |
| parts.append(inputs_embeds[b, prev:j]) | |
| mask_parts.append(mx.ones((j - prev,), dtype=mx.int32)) | |
| feat = image_features[taken].astype(inputs_embeds.dtype) | |
| taken += 1 | |
| parts.append(feat) | |
| mask_parts.append(mx.ones((feat.shape[0],), dtype=mx.int32)) | |
| prev = j + 1 | |
| if prev < S: | |
| parts.append(inputs_embeds[b, prev:]) | |
| mask_parts.append(mx.ones((S - prev,), dtype=mx.int32)) | |
| rows.append(mx.concatenate(parts, axis=0)) | |
| masks.append(mx.concatenate(mask_parts, axis=0)) | |
| if B == 1: | |
| merged = rows[0][None] | |
| mask = masks[0][None] | |
| else: | |
| longest = max(r.shape[0] for r in rows) | |
| padded, padded_masks = [], [] | |
| for r, m in zip(rows, masks): | |
| gap = longest - r.shape[0] | |
| if gap: | |
| r = mx.concatenate([mx.zeros((gap, r.shape[-1]), r.dtype), r], axis=0) | |
| m = mx.concatenate([mx.zeros((gap,), mx.int32), m], axis=0) | |
| padded.append(r) | |
| padded_masks.append(m) | |
| merged = mx.stack(padded) | |
| mask = mx.stack(padded_masks) | |
| # zero out embeddings at pad positions, as the reference does | |
| if pad_token_id is not None: | |
| merged = mx.where(mask[..., None] == 0, mx.zeros_like(merged), merged) | |
| return merged, mask | |
| class Model(nn.Module): | |
| def __init__(self, config: ModelConfig): | |
| super().__init__() | |
| self.config = config | |
| self.model_type = config.model_type | |
| self.vision_tower = VisionModel(config.vision_config) | |
| self.language_model = LanguageModel(config.text_config) | |
| # ------------------------------------------------------------------ api | |
| def layers(self): | |
| return self.language_model.model.model.layers | |
| def make_cache(self): | |
| return self.language_model.model.make_cache() | |
| def get_input_embeddings( | |
| self, | |
| input_ids: Optional[mx.array] = None, | |
| pixel_values: Optional[mx.array] = None, | |
| **kwargs, | |
| ) -> InputEmbeddingsFeatures: | |
| embed = self.language_model.model.model.embed_tokens | |
| inputs_embeds = embed(input_ids) | |
| if pixel_values is None: | |
| return InputEmbeddingsFeatures(inputs_embeds=inputs_embeds) | |
| grids = ( | |
| kwargs.pop("grid_thws", None) | |
| or kwargs.pop("image_grid_thw", None) | |
| or kwargs.pop("image_grid_hws", None) | |
| ) | |
| if grids is None: | |
| raise ValueError("grid_thws is required alongside pixel_values") | |
| if isinstance(grids, mx.array): | |
| grids = grids.tolist() | |
| grids = [tuple(int(v) for v in g) for g in grids] | |
| feats = kwargs.get("cached_image_features") | |
| if feats is None: | |
| feats = self.vision_tower(pixel_values, grids) | |
| placeholder = kwargs.pop("image_token_id", None) or self.config.media_placeholder_token_id | |
| merged, mask = merge_image_features( | |
| inputs_embeds, input_ids, feats, placeholder, self.config.pad_token_id | |
| ) | |
| return InputEmbeddingsFeatures(inputs_embeds=merged, attention_mask=mask) | |
| def __call__( | |
| self, | |
| input_ids: mx.array, | |
| pixel_values: Optional[mx.array] = None, | |
| cache: Optional[List[Any]] = None, | |
| **kwargs, | |
| ) -> mx.array: | |
| feats = self.get_input_embeddings(input_ids, pixel_values, **kwargs) | |
| embeds = feats.inputs_embeds | |
| # After merging, the token axis no longer lines up with input_ids, so the | |
| # text tower must be driven by the embeddings' own length. | |
| ids = mx.zeros(embeds.shape[:2], dtype=mx.int32) | |
| return self.language_model(ids, cache=cache, inputs_embeds=embeds) | |
| def sanitize(self, weights): | |
| """Route source keys to the two towers. | |
| `vision_tower.*` / `mm_projector.*` are consumed by the vision tower; | |
| `language_model.*` by the text tower. Both delegate to the same sanitize | |
| functions the standalone models use, so there is one implementation of | |
| the key mapping rather than a wrapper-specific copy that could drift. | |
| """ | |
| vis = {k: v for k, v in weights.items() | |
| if k.startswith(("vision_tower.", "mm_projector."))} | |
| txt = {k: v for k, v in weights.items() if not k.startswith(("vision_tower.", "mm_projector."))} | |
| out = {f"vision_tower.{k}": v for k, v in self.vision_tower.sanitize(vis).items()} | |
| out.update({f"language_model.{k}": v for k, v in self.language_model.sanitize(txt).items()}) | |
| return out | |