-
Notifications
You must be signed in to change notification settings - Fork 24
kimi-k3 : the MoonViT-3d vision tower and full-size loading fixes #70
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-upstream
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) | ||
|
Comment on lines
+213
to
+217
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.
Once dispatch reaches this converter, MoonViT's Useful? React with 👍 / 👎. |
||
|
|
||
| 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: | ||
| # 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.") | ||
|
|
||
| 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.
After the mmproj dispatch is fixed, the checkpoint's Useful? React with 👍 / 👎. |
||
| 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.
When
convert_hf_to_gguf.py --mmprojprocesses a Kimi-K3 config,get_model_class(..., mmproj=True)checksMMPROJ_MODEL_MAPbefore importing this decorated class, but that map has noKimiK3ForConditionalGenerationentry. The command therefore raisesNotImplementedError: Architecture 'KimiK3ForConditionalGeneration' not supportedbefore any vision tensors are converted; add the architecture-to-kimivldispatch entry alongside Kimi-K2.5.Useful? React with 👍 / 👎.