-
Notifications
You must be signed in to change notification settings - Fork 22
kimi-k3 : full-size model fixes and the MoonViT-3d vision tower #48
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: kimi-k3-text-base
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -168,3 +168,79 @@ def modify_tensors(self, data_torch: Tensor, name: str, bid: int | None) -> Iter | |
| name = name.replace("mm_projector.linear_", "mm_projector.proj.linear_", 1) | ||
|
|
||
| yield from super().modify_tensors(data_torch, name, bid) | ||
|
|
||
|
|
||
| @ModelBase.register("KimiK3ForConditionalGeneration") | ||
| class KimiK3VisionModel(MmprojModel): | ||
| """Kimi-K3 MoonViT-3d vision tower (image path). | ||
|
|
||
| Structurally the Kimi-K2.5 tower with RMSNorm, no biases, a non-square fused QKV | ||
| (qkv_hidden_size 1536 vs vt_hidden_size 1024) and a post-norm patchmergerv2 projector. | ||
| Video is out of scope: for t == 1 the temporal pool and temporal position term vanish. | ||
| """ | ||
|
|
||
| def __init__(self, *args, **kwargs): | ||
| super().__init__(*args, **kwargs) | ||
| assert self.hparams_vision is not None, "Kimi-K3 requires vision_config in config.json" | ||
| self.merge_kernel_size = tuple(self.hparams_vision.get("merge_kernel_size", [2, 2])) | ||
| self.patch_size = self.hparams_vision.get("patch_size", 14) | ||
| pos_emb_h = self.hparams_vision.get("init_pos_emb_height", 64) | ||
| self.hparams_vision["image_size"] = pos_emb_h * self.patch_size | ||
|
|
||
| def set_gguf_parameters(self): | ||
| super().set_gguf_parameters() | ||
| assert self.hparams_vision is not None | ||
| self.gguf_writer.add_clip_projector_type(gguf.VisionProjectorType.KIMIK3) | ||
|
|
||
| # qkv width != n_embd, so the runtime cannot derive d_head | ||
| n_head = self.hparams_vision["vt_num_attention_heads"] | ||
| qkv_hidden = self.hparams_vision.get("qkv_hidden_size") or self.hparams_vision["vt_hidden_size"] | ||
| assert qkv_hidden % n_head == 0, f"qkv_hidden_size {qkv_hidden} not divisible by {n_head} heads" | ||
| self.gguf_writer.add_vision_head_dim(qkv_hidden // n_head) | ||
|
|
||
| self.gguf_writer.add_vision_use_gelu(True) # activation_func is gelu_pytorch_tanh | ||
| self.gguf_writer.add_vision_attention_layernorm_eps( | ||
| self.hparams_vision.get("projector_ln_eps", 1e-5)) | ||
| self.gguf_writer.add_vision_projector_scale_factor(self.merge_kernel_size[0]) | ||
|
|
||
| in_patch_limit = self.preprocessor_config.get("media_proc_cfg", {}).get( | ||
| "in_patch_limit", self.preprocessor_config.get("in_patch_limit", 16384)) | ||
| pixels_per_patch = self.patch_size ** 2 | ||
| self.gguf_writer.add_vision_min_pixels(8 * pixels_per_patch) | ||
| self.gguf_writer.add_vision_max_pixels(in_patch_limit * pixels_per_patch) | ||
|
|
||
| @classmethod | ||
| def filter_tensors(cls, item: tuple[str, Callable[[], Tensor]]) -> tuple[str, Callable[[], Tensor]] | None: | ||
| name, _ = item | ||
| if not name.startswith(("vision_tower.", "mm_projector.")): | ||
| return None | ||
| return super().filter_tensors(item) | ||
|
|
||
| def modify_tensors(self, data_torch: Tensor, name: str, bid: int | None) -> Iterable[tuple[str, Tensor]]: | ||
| assert self.hparams_vision is not None | ||
| n_head = self.hparams_vision["vt_num_attention_heads"] | ||
|
|
||
| if "wqkv" in name and "weight" in name: | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
For Kimi-K3 checkpoints, Useful? React with 👍 / 👎. |
||
| # de-interleave Q/K so the runtime can use build_rope_2d(interleave_freq=false) | ||
| out_dim = data_torch.shape[0] | ||
| qkv_dim = out_dim // 3 | ||
| head_dim = qkv_dim // n_head | ||
| wq, wk, wv = (data_torch[:qkv_dim], data_torch[qkv_dim:2 * qkv_dim], data_torch[2 * qkv_dim:]) | ||
|
|
||
| def deinterleave(w: Tensor) -> Tensor: | ||
| return (w.reshape(n_head, head_dim // 4, 2, 2, w.shape[-1]) | ||
| .permute(0, 2, 1, 3, 4) | ||
| .reshape(w.shape[0], w.shape[-1])) | ||
|
|
||
| data_torch = torch.cat([deinterleave(wq), deinterleave(wk), wv], dim=0) | ||
|
|
||
| if "pos_emb.weight" in name: | ||
| # kept 3D: the runtime reads grid extents from ne[1]/ne[2] | ||
| pass | ||
|
|
||
| if "mm_projector.proj.0." in name: | ||
| name = name.replace(".proj.0.", ".proj.linear_1.") | ||
| elif "mm_projector.proj.2." in name: | ||
| name = name.replace(".proj.2.", ".proj.linear_2.") | ||
|
Comment on lines
+241
to
+244
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When converting the published PatchMergerV2 layout, the required Useful? React with 👍 / 👎. |
||
|
|
||
| yield from super().modify_tensors(data_torch, name, bid) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. would you mind pushing the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (or I can do that if you prefer)
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @ngxson will check! I was planning to make this as a PR to pwilkin's fork as well haha Or if you wanna take over that also works - but I shall re-check! I also did multiple images and Kimi works - https://unsloth.ai/docs/models/kimi-k3#run-kimi-k3-in-llama.cpp I'll double check other models |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,80 @@ | ||
| #include "models.h" | ||
|
|
||
| #include <cmath> | ||
| #include <cstring> | ||
|
|
||
| // Kimi-K3 MoonViT-3d, image path. | ||
| // Follows clip_graph_kimik25, but with RMSNorm, no biases, qkv width != n_embd, and a post-norm patchmergerv2 projector. | ||
| // Images only: at t == 1 the temporal pool and the temporal position term vanish. | ||
|
|
||
| ggml_tensor * clip_graph_kimik3::resize_position_embeddings_3d(uint32_t interpolation_mode) { | ||
| ggml_tensor * pos_embd = model.position_embeddings; | ||
| const int height = img.ny() / patch_size; | ||
| const int width = img.nx() / patch_size; | ||
|
|
||
| GGML_ASSERT(pos_embd); | ||
|
|
||
| const int64_t stored_c = pos_embd->ne[0]; | ||
| const int64_t orig_w = pos_embd->ne[1]; | ||
| const int64_t orig_h = pos_embd->ne[2]; | ||
|
|
||
| GGML_ASSERT(stored_c == n_embd); | ||
|
|
||
| if (height == (int) orig_h && width == (int) orig_w) { | ||
| return ggml_cont_2d(ctx0, pos_embd, n_embd, width * height); | ||
| } | ||
|
|
||
| pos_embd = ggml_permute(ctx0, pos_embd, 2, 1, 0, 3); | ||
| pos_embd = ggml_interpolate(ctx0, pos_embd, height, width, n_embd, 1, interpolation_mode); | ||
| pos_embd = ggml_permute(ctx0, pos_embd, 2, 1, 0, 3); | ||
| pos_embd = ggml_cont_2d(ctx0, pos_embd, n_embd, width * height); | ||
| return pos_embd; | ||
| } | ||
|
|
||
| ggml_cgraph * clip_graph_kimik3::build() { | ||
| ggml_tensor * pos_h = ggml_new_tensor_1d(ctx0, GGML_TYPE_I32, n_patches); | ||
| ggml_set_name(pos_h, "pos_h"); | ||
| ggml_set_input(pos_h); | ||
|
|
||
| ggml_tensor * pos_w = ggml_new_tensor_1d(ctx0, GGML_TYPE_I32, n_patches); | ||
| ggml_set_name(pos_w, "pos_w"); | ||
| ggml_set_input(pos_w); | ||
|
|
||
| ggml_tensor * learned_pos_embd = resize_position_embeddings_3d(GGML_SCALE_MODE_BILINEAR); | ||
|
|
||
| // Q/K are de-interleaved during conversion. | ||
| auto add_pos = [&](ggml_tensor * cur, const clip_layer &) { | ||
| return build_rope_2d(ctx0, cur, pos_w, pos_h, hparams.rope_theta, false); | ||
| }; | ||
|
|
||
| ggml_tensor * inp = build_inp(); | ||
| inp = ggml_add(ctx0, inp, learned_pos_embd); | ||
|
|
||
| ggml_tensor * cur = build_vit( | ||
| inp, n_patches, | ||
| NORM_TYPE_RMS, | ||
| hparams.ffn_op, | ||
| nullptr, | ||
| add_pos); | ||
| cb(cur, "vit_out", -1); | ||
|
|
||
| { | ||
| const int scale_factor = model.hparams.n_merge; | ||
| cur = build_patch_merge_permute(cur, scale_factor); | ||
|
|
||
| cur = build_ffn(cur, | ||
| model.mm_1_w, nullptr, | ||
| nullptr, nullptr, | ||
| model.mm_2_w, nullptr, | ||
| FFN_GELU, | ||
| -1); | ||
| cb(cur, "proj_mlp_out", -1); | ||
|
|
||
| cur = build_norm(cur, model.mm_post_norm_w, nullptr, NORM_TYPE_RMS, hparams.eps, -1); | ||
| cb(cur, "proj_out", -1); | ||
| } | ||
|
|
||
| ggml_build_forward_expand(gf, cur); | ||
|
|
||
| return gf; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The MoonViT-3d state dict also contains
vision_tower.patch_embed.pos_emb.time_weight, and this broad prefix filter retains it even though the image-only graph deliberately omits the temporal position term. There is no MMPROJ tensor-map entry or runtime tensor for this parameter, so it reachessuper().modify_tensors()and aborts conversion withCan not map tensor; explicitly discard it for the supportedt == 1path.Useful? React with 👍 / 👎.