Repository navigation
Conversation
Adds MoonViTEncoder (IVlmImageEncoder) and its loader for vision_tower.* and mm_projector.* read through the same checkpoint index as the text decoder. Token order follows NVIDIA NeMo kimi_k3/vision.py: patches in merge-block order, interleaved 2D RoPE (even pairs take the column angle, odd pairs the row angle), bilinear position resampling with align_corners off, GELU tanh in blocks, exact GELU and RMS post-norm in the projector. VisionPatchOps (shared) now owns the merge-block patchify and position order; the Qwen-VL encoders call it instead of QwenVlOps.Patchify. UNVERIFIED on real weights: the RMS-norm epsilon. The checkpoint does not store it. NeMo casts the tower to the model dtype (BF16), which would make PyTorch's default epsilon 2^-7; this uses the F32 default 1.19e-7.
2 of 3 tasks
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
MoonViTEncoder(IVlmImageEncoder,Familykimik3) undersrc/HartsyInference.LLM/Kimi/Vision/: 27 RMS-norm blocks, fused QKV split at load, interleaved 2D RoPE, bilinear position resampling, GELU (tanh) feed-forward, final RMS norm, and the patch merger (PatchMergerMLPV2).KimiVisionWeightLoaderreadsvision_tower.*andmm_projector.*throughIKimiK31WeightSource(the same index as the text decoder), converts to F32, and refuses a missing or misshapen tensor by name.VisionPatchOps(shared,LLM/Multimodal) owns the merge-block patchify and token positions.QwenVlOps.Patchifymoved there;Qwen25VlEncoderandQwen3VlEncodercall it. Qwen's output is unchanged: this is a pure move, andQwenVlOps.BuildRopenow takes its positions from the same helper.TextService.LoadVisionInto,HfTextDirectoryLoader) and the placeholder-to-splice step are left for the route owner.VisionPlaceholderPrompttakes the Kimi literals<|media_begin|>,<|media_pad|>,<|media_end|>through its constructor;VisionTokenSplicetakes the encoder output as is.Source for the open questions (NVIDIA NeMo,
main)https://raw.githubusercontent.com/NVIDIA-NeMo/Automodel/main/nemo_automodel/components/models/kimi_k3/vision.pyxq_ = torch.view_as_complex(xq.float().view(*xq.shape[:-1], -1, 2)). Even pairs take the column (x_pos = flat % max_width), odd pairs the row (ret[h, w, 2*i+1] = cis(w * ...)in the docstring,freqs_cis = cat([x_cis, y_cis], -1)). Not rotate-half.reshaped_seq = seq.view(t, new_height, kernel_height, new_width, kernel_width, d_model)thenpermute(0, 1, 3, 2, 4, 5), so a merged token holds its 2x2 patches in row-major order. The encoder runs in merge-block order, which is the same permutation, so the merge is a contiguous group.norm_typermsnorm,attn_bias/linear_biasfalse,activationgelu_pytorch_tanh. Projector:nn.GELU()(exact) between two bias-free linears, thenRMSNorm(eps=projector_ln_eps).https://huggingface.co/moonshotai/Kimi-K3.1-Pro/resolve/main/config.json,vision_config):pos_emb_interpolation_modebilinear,patch_embed_proj_biasfalse.model.safetensors.index.json; shardmodel-00096-of-000096header): all vision tensors BF16;vision_tower.patch_embed.pos_emb.weight[64, 64, 1024];vision_tower.encoder.blocks.N.wqkv.weight[4608, 1024];mm_projector.proj.0.weightandproj.2.weighthave no bias entries.kimi_k3_vision_processing.py,media_utils.py): images are resized, padded with zeros to a multiple of 28, then normalized with mean and std 0.5. Not implemented here; the route must reproduce it.UNVERIFIED
BlockNormEpsilon). The checkpoint does not store it. NeMo callscast_model_to_dtype(self.vision_tower, model_dtype), so a BF16 run makesnn.RMSNorm's default epsilon 2^-7 (0.0078125). This PR uses the F32 default, 1.19e-7, and the value is a config field. Confirm on real weights.modeling_*.pyexists for K3 (the repo file list has no modeling file). Nothing was compared against a Python reference run.align_corners=False. Taken from PyTorch'sF.interpolatedefault, because NeMo does not pass it.1/sqrt(head_dim)with head dim 128 (NeMo's eager and flash paths).t > 1) is not supported; the encoder takes one image.kimik3is unknown toMultimodalGenerator; the route must add a prompt format.Tests (CPU lane)
dotnet test tests/HartsyInference.LLM.Tests -f net10.0 --filter "Category!=SyntheticSmoke&Category!=Integration&Category!=GpuIntegration&Category!=GpuBenchmark&Category!=Slow&Network!=Real": 196 passed, 0 failed (includes 18 newKimi.Visiontests).g(1)andg(2)as constants; interleaved RoPE angles and the rotary table layout; loader refusals by name (missing tensor, wrong shape); BF16-to-F32 load through a real shard and index; encoder token count, finiteness, determinism, image dependence and size refusals.Changelog
One line under
## Unreleased, as requested in the task. No version bump (VersionSuffixunchanged).