Grawnch commited on
Commit
d72b5a3
·
0 Parent(s):

Kimi-K3.1-Pro-Coder (96/96 shards)

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .gitattributes +37 -0
  2. LICENSE +52 -0
  3. README.md +55 -0
  4. assets/kimi-logo.png +0 -0
  5. config.json +330 -0
  6. configuration_kimi_k3.py +277 -0
  7. encoding_k3.py +735 -0
  8. evaluation_results_93layer.json +163 -0
  9. generation_config.json +4 -0
  10. kimi31_coder_benchmark_card.png +3 -0
  11. kimi_k3_processor.py +187 -0
  12. kimi_k3_vision_processing.py +179 -0
  13. media_utils.py +376 -0
  14. model-00001-of-000096.safetensors +3 -0
  15. model-00002-of-000096.safetensors +3 -0
  16. model-00003-of-000096.safetensors +3 -0
  17. model-00004-of-000096.safetensors +3 -0
  18. model-00005-of-000096.safetensors +3 -0
  19. model-00006-of-000096.safetensors +3 -0
  20. model-00007-of-000096.safetensors +3 -0
  21. model-00008-of-000096.safetensors +3 -0
  22. model-00009-of-000096.safetensors +3 -0
  23. model-00010-of-000096.safetensors +3 -0
  24. model-00011-of-000096.safetensors +3 -0
  25. model-00012-of-000096.safetensors +3 -0
  26. model-00013-of-000096.safetensors +3 -0
  27. model-00014-of-000096.safetensors +3 -0
  28. model-00015-of-000096.safetensors +3 -0
  29. model-00016-of-000096.safetensors +3 -0
  30. model-00017-of-000096.safetensors +3 -0
  31. model-00018-of-000096.safetensors +3 -0
  32. model-00019-of-000096.safetensors +3 -0
  33. model-00020-of-000096.safetensors +3 -0
  34. model-00021-of-000096.safetensors +3 -0
  35. model-00022-of-000096.safetensors +3 -0
  36. model-00023-of-000096.safetensors +3 -0
  37. model-00024-of-000096.safetensors +3 -0
  38. model-00025-of-000096.safetensors +3 -0
  39. model-00026-of-000096.safetensors +3 -0
  40. model-00027-of-000096.safetensors +3 -0
  41. model-00028-of-000096.safetensors +3 -0
  42. model-00029-of-000096.safetensors +3 -0
  43. model-00030-of-000096.safetensors +3 -0
  44. model-00031-of-000096.safetensors +3 -0
  45. model-00032-of-000096.safetensors +3 -0
  46. model-00033-of-000096.safetensors +3 -0
  47. model-00034-of-000096.safetensors +3 -0
  48. model-00035-of-000096.safetensors +3 -0
  49. model-00036-of-000096.safetensors +3 -0
  50. model-00037-of-000096.safetensors +3 -0
.gitattributes ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ *.7z filter=lfs diff=lfs merge=lfs -text
2
+ *.arrow filter=lfs diff=lfs merge=lfs -text
3
+ *.bin filter=lfs diff=lfs merge=lfs -text
4
+ *.bz2 filter=lfs diff=lfs merge=lfs -text
5
+ *.ckpt filter=lfs diff=lfs merge=lfs -text
6
+ *.ftz filter=lfs diff=lfs merge=lfs -text
7
+ *.gz filter=lfs diff=lfs merge=lfs -text
8
+ *.h5 filter=lfs diff=lfs merge=lfs -text
9
+ *.joblib filter=lfs diff=lfs merge=lfs -text
10
+ *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
+ *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
+ *.model filter=lfs diff=lfs merge=lfs -text
13
+ *.msgpack filter=lfs diff=lfs merge=lfs -text
14
+ *.npy filter=lfs diff=lfs merge=lfs -text
15
+ *.npz filter=lfs diff=lfs merge=lfs -text
16
+ *.onnx filter=lfs diff=lfs merge=lfs -text
17
+ *.ot filter=lfs diff=lfs merge=lfs -text
18
+ *.parquet filter=lfs diff=lfs merge=lfs -text
19
+ *.pb filter=lfs diff=lfs merge=lfs -text
20
+ *.pickle filter=lfs diff=lfs merge=lfs -text
21
+ *.pkl filter=lfs diff=lfs merge=lfs -text
22
+ *.pt filter=lfs diff=lfs merge=lfs -text
23
+ *.pth filter=lfs diff=lfs merge=lfs -text
24
+ *.rar filter=lfs diff=lfs merge=lfs -text
25
+ *.safetensors filter=lfs diff=lfs merge=lfs -text
26
+ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
+ *.tar.* filter=lfs diff=lfs merge=lfs -text
28
+ *.tar filter=lfs diff=lfs merge=lfs -text
29
+ *.tflite filter=lfs diff=lfs merge=lfs -text
30
+ *.tgz filter=lfs diff=lfs merge=lfs -text
31
+ *.wasm filter=lfs diff=lfs merge=lfs -text
32
+ *.xz filter=lfs diff=lfs merge=lfs -text
33
+ *.zip filter=lfs diff=lfs merge=lfs -text
34
+ *.zst filter=lfs diff=lfs merge=lfs -text
35
+ *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ model.safetensors.index.json filter=lfs diff=lfs merge=lfs -text
37
+ kimi31_coder_benchmark_card.png filter=lfs diff=lfs merge=lfs -text
LICENSE ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Kimi K3.1-Pro License
2
+
3
+ Copyright (c) 2026 Moonshot AI
4
+
5
+ Permission is hereby granted, free of charge, to any person (the "Licensee")
6
+ obtaining a copy of this software — including the model weights, parameters,
7
+ configuration files, inference and training code, and associated documentation
8
+ (collectively, the "Software") — to deal in the Software without restriction.
9
+ This includes, without limitation, the rights to use, copy, modify, merge,
10
+ publish, distribute, sublicense, and/or sell copies of the Software; to run,
11
+ deploy, fine-tune, or otherwise modify the Software and create derivative works
12
+ from it; and to permit persons to whom the Software is furnished to do so, in
13
+ each case subject to the following conditions:
14
+
15
+ 1. The above copyright notice and this permission notice shall be included in
16
+ all copies or substantial portions of the Software. Licensee's use of the
17
+ Software must comply with applicable laws and regulations.
18
+
19
+ 2. "Model as a Service" means giving a third party access to language model
20
+ inference or fine-tuning (e.g., via API) in a manner that allows such third
21
+ party to exercise meaningful control over the inputs, parameters, or training
22
+ data. This does not include (a) end-user products with model capabilities solely
23
+ embedded within specific features or harnesses, or (b) mere relaying of requests
24
+ to models hosted by others.
25
+
26
+ If the Licensee or any of its affiliates operates a Model as a Service business,
27
+ and the aggregate revenue of the Licensee and its affiliates exceeds 20 million
28
+ US dollars (or the equivalent in other currencies) in total over any consecutive
29
+ 12 months, the Licensee must enter into a separate agreement with Moonshot AI
30
+ before using the Software or its derivative works for any commercial purpose.
31
+
32
+ 3. If the Software (or any derivative works thereof) is used for any of the
33
+ Licensee's commercial products or services that have more than 100 million
34
+ monthly active users, or more than 20 million US dollars (or equivalent in other
35
+ currencies) in monthly revenue, "Kimi K3.1-Pro" must be prominently displayed on the
36
+ user interface of such product or service.
37
+
38
+ 4. The requirements set forth in Sections 2 and 3 do not apply to: (a) internal
39
+ use of the Software, defined as any use that does not make the Software, its
40
+ outputs, or its underlying capabilities available to third parties; or (b) any
41
+ use of the Software accessed through Moonshot AI's official products or
42
+ certified inference partners.
43
+
44
+ 5. THE SOFTWARE AND ANY OUTPUT AND RESULTS THEREFROM ARE PROVIDED ON AN “AS IS”
45
+ BASIS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
46
+ LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
47
+ AND NONINFRINGEMENT. IN NO EVENT SHALL MOONSHOT AI OR ITS AFFILIATES OR
48
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
49
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
50
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
51
+
52
+ For any questions regarding this license, please contact <license@moonshot.ai>.
README.md ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: apache-2.0
3
+ base_model: moonshotai/Kimi-K3.1-Pro
4
+ tags:
5
+ - kimi-k3.1
6
+ - moe
7
+ - quantization
8
+ - mixed-bit
9
+ - strata
10
+ - dr-triton
11
+ pipeline_tag: image-text-to-text
12
+ ---
13
+
14
+ # Kimi-K3.1-Pro-Coder (2.8T Mixed-Bit MoE + Extra Coding)
15
+
16
+ ![Kimi-K3.1-Pro-Coder 93-Layer Benchmark Card](./kimi31_coder_benchmark_card.png)
17
+
18
+ **`Grawnch/Kimi-K3.1-Pro-Coder`** is a full 96-shard (**`774.97 GB` / `721.75 GiB`**) **Strata-Coder 3-Tier Mixed-Bit MoE** quantization of **`moonshotai/Kimi-K3.1-Pro`** (`2.8T` parameters, 93 layers, 896 routed experts per MoE layer, `top_k=16` LatentMoE with `SiTU-GLU`, `KDA` linear attention + gated NoPE `MLA` full attention, and `AttnRes` cross-layer residual mixing).
19
+
20
+ All non-routed parameters (embeddings, `KDA` & `MLA` attention, `AttnRes` projections, shared experts, LatentMoE projections, `lm_head`, and vision tower) are preserved in native **`BF16` / `F32`**, while all `82,432` routed experts (`896` per layer across Layers `1..92`) are compressed using **Strata-Coder 3-Tier Mixed-Bit MoE Compression** calibrated on coding and mathematical reasoning router activation frequencies:
21
+
22
+ | Expert Tier | Routed Experts / Layer | Effective BPW | Storage Tier (`Strata` Engine) | Matrix Cosine vs Base MXFP4 |
23
+ | :--- | :---: | :---: | :--- | :---: |
24
+ | **Tier 1 (Hot Coding/Math)** | `90 / 896` (`10%`) | **`2.81 bpw`** | **Tier 1: Hot GPU VRAM** | **`96.95%`** |
25
+ | **Tier 2 (Warm General)** | `224 / 896` (`25%`) | **`2.56 bpw`** | **Tier 2: Warm Pinned System RAM** | **`94.53%`** |
26
+ | **Tier 3 (Cold Tail)** | `582 / 896` (`65%`) | **`1.44 bpw`** | **Tier 3: Cold NVMe SSD (`mmap`)** | **`82.51%`** |
27
+ | **Full Model Total** | **All 96 / 96 Shards** | **`~1.92 bpw` (MoE)** | **774.97 GB Total (`-56.2%` vs 1.77 TB Base)** | **`93.09%` – `98.22%` MoE Output** |
28
+
29
+ ---
30
+
31
+ ## Full 93-Layer End-to-End Evaluation (`NVIDIA RTX PRO 6000 Blackwell`)
32
+
33
+ Evaluated across **all 93 layers (`Layer 0` through `Layer 92` + `Shard 94` `lm_head`)** on an **`NVIDIA RTX PRO 6000 Blackwell Server Edition` (`102.6 GB` GDDR7 VRAM)** over two zero-leakage benchmarks (`22,307` total tokens across `70` sequences):
34
+ 1. **`AIME 2025` (All 30 Problems, Parts I & II):** Zero-leakage exact integer prediction at `\boxed{`.
35
+ 2. **Rolling-Window `LiveCodeBench` (`2024.10 – 2025.05`, 30 Problems: 10 Easy, 10 Medium, 10 Hard across LeetCode, AtCoder, and Codeforces):** Zero-leakage functional unit-test execution (`Pass@1`) + rolling-window code perplexity (`PPL`).
36
+
37
+ | Benchmark / Metric | `moonshotai/Kimi-K3.1-Pro` (Base Ref) | `Grawnch/Kimi-K3.1-Pro-Coder` (`774.97 GB`) | Delta / Agreement |
38
+ | :--- | :---: | :---: | :---: |
39
+ | **`AIME 2025` (All 30 Problems, Pass@1)** | **`29 / 30` (`96.67%`)** | **`29 / 30` (`96.67%`)** | **`0.00%` drop (`30/30` exact match with Base)** |
40
+ | **`LiveCodeBench` Rolling Window Overall (`30` Probs)** | **`29 / 30` (`96.67%`)** | **`29 / 30` (`96.67%`)** | **`0.00%` drop (`30/30` exact match with Base)** |
41
+ | **`LiveCodeBench` Easy / Medium / Hard Split** | `9/10` / `10/10` / `10/10` | **`9/10` (`90%`) / `10/10` (`100%`) / `10/10` (`100%`)** | **`100.0%` parity across all difficulty tiers** |
42
+ | **Top-1 Next-Token Agreement (`AIME` / `LCB`)** | `100.00%` | **`95.20%` / `95.06%`** | **`95.1%+` exact Top-1 match across 93 layers** |
43
+ | **Top-5 Next-Token Agreement (`AIME` / `LCB`)** | `100.00%` | **`99.91%` / `99.92%`** | **`99.91%+` Top-5 coverage** |
44
+ | **Mean KL Divergence (`KLD`, `AIME` / `LCB`)** | `0.00000` | **`0.01912` / `0.02248`** | **`~0.020` nats end-to-end across 93 layers** |
45
+ | **`AIME 2025` Math Perplexity (`PPL`)** | `2.2135` | **`2.2358`** | **`+0.0223` (`+1.01%`)** |
46
+ | **`LiveCodeBench` Hard Perplexity (`PPL`)** | `2.1136` | **`2.1562`** | **`+0.0426` (`+2.02%`)** |
47
+ | **`LiveCodeBench` Overall Perplexity (`PPL`)** | `2.6911` | **`2.7629`** | **`+0.0718` (`+2.67%`)** |
48
+ | **MoE Layer Throughput (`RTX PRO 6000`)** | — | **`3,382.7 layer-tok/s`** | **`36.77 tok/s` full 93-layer batch** |
49
+
50
+ ---
51
+
52
+ ## Runtime & 3-Tier Offloading with `Strata` and `dr-triton`
53
+
54
+ - **Engine & 3-Tier Loader (`Hot VRAM / Warm RAM / Cold NVMe` + `RouterLookahead`):** [https://github.com/grawnchtoader/strata](https://github.com/grawnchtoader/strata)
55
+ - **Fused Triton Dequantization Kernels:** [https://github.com/grawnchtoader/dr-triton](https://github.com/grawnchtoader/dr-triton)
assets/kimi-logo.png ADDED
config.json ADDED
@@ -0,0 +1,330 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "architectures": [
3
+ "KimiK31ForConditionalGeneration"
4
+ ],
5
+ "auto_map": {
6
+ "AutoConfig": "configuration_kimi_k3.KimiK3Config"
7
+ },
8
+ "bos_token_id": 163584,
9
+ "dtype": "bfloat16",
10
+ "eos_token_id": 163586,
11
+ "ignore_index": -100,
12
+ "image_placeholder": "<|kimi_image_placeholder|>",
13
+ "media_placeholder_token_id": 163605,
14
+ "model_type": "kimi_k31",
15
+ "pad_token_id": 163839,
16
+ "text_config": {
17
+ "_name_or_path": "",
18
+ "activation_situ_beta": 4.0,
19
+ "activation_situ_linear_beta": 25.0,
20
+ "add_cross_attention": false,
21
+ "architectures": [
22
+ "KimiLinearForCausalLM"
23
+ ],
24
+ "attn_res_block_size": 12,
25
+ "bad_words_ids": null,
26
+ "begin_suppress_tokens": null,
27
+ "bos_token_id": 163584,
28
+ "chunk_size_feed_forward": 0,
29
+ "cross_attention_hidden_size": null,
30
+ "decoder_start_token_id": null,
31
+ "diversity_penalty": 0.0,
32
+ "do_sample": false,
33
+ "dsa_config": {
34
+ "dsa_layers": [
35
+ 4,
36
+ 8,
37
+ 12,
38
+ 16,
39
+ 20,
40
+ 24,
41
+ 28,
42
+ 32,
43
+ 36,
44
+ 40,
45
+ 44,
46
+ 48,
47
+ 52,
48
+ 56,
49
+ 60,
50
+ 64,
51
+ 68,
52
+ 72,
53
+ 76,
54
+ 80,
55
+ 84,
56
+ 88,
57
+ 92,
58
+ 93
59
+ ],
60
+ "index_topk": 2048,
61
+ "index_head_dim": 128,
62
+ "index_num_heads": 32,
63
+ "index_rope_dim": 0,
64
+ "index_k_norm_type": "layernorm",
65
+ "index_topk_freq": 1,
66
+ "index_skip_topk_offset": 1
67
+ },
68
+ "dtype": "bfloat16",
69
+ "early_stopping": false,
70
+ "encoder_no_repeat_ngram_size": 0,
71
+ "eos_token_id": 163586,
72
+ "exponential_decay_length_penalty": null,
73
+ "finetuning_task": null,
74
+ "first_k_dense_replace": 1,
75
+ "forced_bos_token_id": null,
76
+ "forced_eos_token_id": null,
77
+ "hidden_act": "situ",
78
+ "hidden_size": 7168,
79
+ "id2label": {
80
+ "0": "LABEL_0",
81
+ "1": "LABEL_1"
82
+ },
83
+ "initializer_range": 0.02,
84
+ "intermediate_size": 33792,
85
+ "is_decoder": false,
86
+ "is_encoder_decoder": false,
87
+ "kv_lora_rank": 512,
88
+ "label2id": {
89
+ "LABEL_0": 0,
90
+ "LABEL_1": 1
91
+ },
92
+ "latent_moe_use_norm": true,
93
+ "length_penalty": 1.0,
94
+ "linear_attn_config": {
95
+ "full_attn_layers": [
96
+ 4,
97
+ 8,
98
+ 12,
99
+ 16,
100
+ 20,
101
+ 24,
102
+ 28,
103
+ 32,
104
+ 36,
105
+ 40,
106
+ 44,
107
+ 48,
108
+ 52,
109
+ 56,
110
+ 60,
111
+ 64,
112
+ 68,
113
+ 72,
114
+ 76,
115
+ 80,
116
+ 84,
117
+ 88,
118
+ 92,
119
+ 93
120
+ ],
121
+ "gate_lower_bound": -5.0,
122
+ "head_dim": 128,
123
+ "kda_layers": [
124
+ 1,
125
+ 2,
126
+ 3,
127
+ 5,
128
+ 6,
129
+ 7,
130
+ 9,
131
+ 10,
132
+ 11,
133
+ 13,
134
+ 14,
135
+ 15,
136
+ 17,
137
+ 18,
138
+ 19,
139
+ 21,
140
+ 22,
141
+ 23,
142
+ 25,
143
+ 26,
144
+ 27,
145
+ 29,
146
+ 30,
147
+ 31,
148
+ 33,
149
+ 34,
150
+ 35,
151
+ 37,
152
+ 38,
153
+ 39,
154
+ 41,
155
+ 42,
156
+ 43,
157
+ 45,
158
+ 46,
159
+ 47,
160
+ 49,
161
+ 50,
162
+ 51,
163
+ 53,
164
+ 54,
165
+ 55,
166
+ 57,
167
+ 58,
168
+ 59,
169
+ 61,
170
+ 62,
171
+ 63,
172
+ 65,
173
+ 66,
174
+ 67,
175
+ 69,
176
+ 70,
177
+ 71,
178
+ 73,
179
+ 74,
180
+ 75,
181
+ 77,
182
+ 78,
183
+ 79,
184
+ 81,
185
+ 82,
186
+ 83,
187
+ 85,
188
+ 86,
189
+ 87,
190
+ 89,
191
+ 90,
192
+ 91
193
+ ],
194
+ "num_heads": 96,
195
+ "short_conv_kernel_size": 4,
196
+ "use_full_rank_gate": true
197
+ },
198
+ "max_length": 20,
199
+ "max_position_embeddings": 1048576,
200
+ "min_length": 0,
201
+ "mla_use_nope": true,
202
+ "mla_use_output_gate": true,
203
+ "model_type": "kimi_linear",
204
+ "moe_intermediate_size": 3072,
205
+ "moe_layer_freq": 1,
206
+ "moe_renormalize": true,
207
+ "moe_router_activation_func": "sigmoid",
208
+ "no_repeat_ngram_size": 0,
209
+ "num_attention_heads": 96,
210
+ "num_beam_groups": 1,
211
+ "num_beams": 1,
212
+ "num_expert_group": 1,
213
+ "num_experts": 896,
214
+ "num_experts_per_token": 16,
215
+ "num_hidden_layers": 93,
216
+ "num_key_value_heads": 96,
217
+ "num_nextn_predict_layers": 0,
218
+ "num_return_sequences": 1,
219
+ "num_shared_experts": 2,
220
+ "output_attentions": false,
221
+ "output_hidden_states": false,
222
+ "output_scores": false,
223
+ "pad_token_id": 163839,
224
+ "prefix": null,
225
+ "problem_type": null,
226
+ "pruned_heads": {},
227
+ "q_lora_rank": 1536,
228
+ "qk_nope_head_dim": 128,
229
+ "qk_rope_head_dim": 64,
230
+ "quantization_config": {
231
+ "config_groups": {
232
+ "group_0": {
233
+ "format": "mxfp4-pack-quantized",
234
+ "input_activations": null,
235
+ "output_activations": null,
236
+ "targets": [
237
+ "Linear"
238
+ ],
239
+ "weights": {
240
+ "actorder": null,
241
+ "block_structure": null,
242
+ "dynamic": false,
243
+ "group_size": 32,
244
+ "num_bits": 4,
245
+ "observer": "minmax",
246
+ "observer_kwargs": {},
247
+ "scale_dtype": "torch.uint8",
248
+ "strategy": "group",
249
+ "symmetric": true,
250
+ "type": "float",
251
+ "zp_dtype": null
252
+ }
253
+ }
254
+ },
255
+ "format": "mxfp4-pack-quantized",
256
+ "global_compression_ratio": null,
257
+ "ignore": [
258
+ "re:.*self_attn.*",
259
+ "re:.*shared_experts.*",
260
+ "re:.*mlp\\.(gate|up|gate_up|down)_proj.*",
261
+ "re:.*block_sparse_moe\\.gate$",
262
+ "re:.*block_sparse_moe\\.routed_expert_(up|down)_proj.*",
263
+ "re:.*lm_head.*",
264
+ "re:.*vision_tower.*",
265
+ "re:.*mm_projector.*"
266
+ ],
267
+ "kv_cache_scheme": null,
268
+ "quant_method": "compressed-tensors",
269
+ "quantization_status": "compressed"
270
+ },
271
+ "remove_invalid_values": false,
272
+ "repetition_penalty": 1.0,
273
+ "return_dict": true,
274
+ "return_dict_in_generate": false,
275
+ "rms_norm_eps": 1e-05,
276
+ "routed_expert_hidden_size": 3584,
277
+ "routed_scaling_factor": 1.0,
278
+ "sep_token_id": null,
279
+ "suppress_tokens": null,
280
+ "task_specific_params": null,
281
+ "temperature": 1.0,
282
+ "tf_legacy_loss": false,
283
+ "tie_encoder_decoder": false,
284
+ "tie_word_embeddings": false,
285
+ "tokenizer_class": null,
286
+ "top_k": 50,
287
+ "top_p": 1.0,
288
+ "topk_group": 1,
289
+ "topk_method": "noaux_tc",
290
+ "torchscript": false,
291
+ "transformers_version": "4.57.6",
292
+ "typical_p": 1.0,
293
+ "use_bfloat16": false,
294
+ "use_cache": true,
295
+ "use_grouped_topk": true,
296
+ "v_head_dim": 128,
297
+ "vocab_size": 163840
298
+ },
299
+ "tie_word_embeddings": false,
300
+ "vision_config": {
301
+ "_attn_implementation": "flash_attention_2",
302
+ "activation_func": "gelu_pytorch_tanh",
303
+ "attn_bias": false,
304
+ "init_pos_emb_height": 64,
305
+ "init_pos_emb_time": 4,
306
+ "init_pos_emb_width": 64,
307
+ "linear_bias": false,
308
+ "merge_kernel_size": [
309
+ 2,
310
+ 2
311
+ ],
312
+ "merge_type": "sd2_tpool",
313
+ "mlp_type": "mlp2",
314
+ "mm_hidden_size": 1024,
315
+ "mm_projector_type": "patchmergerv2",
316
+ "norm_type": "rmsnorm",
317
+ "patch_embed_proj_bias": false,
318
+ "patch_size": 14,
319
+ "pos_emb_interpolation_mode": "bilinear",
320
+ "pos_emb_type": "divided_fixed",
321
+ "projector_hidden_act": "gelu",
322
+ "projector_ln_eps": 1e-05,
323
+ "qkv_hidden_size": 1536,
324
+ "text_hidden_size": 7168,
325
+ "vt_hidden_size": 1024,
326
+ "vt_intermediate_size": 4096,
327
+ "vt_num_attention_heads": 12,
328
+ "vt_num_hidden_layers": 27
329
+ }
330
+ }
configuration_kimi_k3.py ADDED
@@ -0,0 +1,277 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Optional
2
+
3
+ from transformers.configuration_utils import PretrainedConfig
4
+
5
+
6
+ class KimiLinearConfig(PretrainedConfig):
7
+ model_type = "kimi_linear"
8
+ keys_to_ignore_at_inference = ["past_key_values"]
9
+
10
+ def __init__(
11
+ self,
12
+ model_type="kimi_linear",
13
+ vocab_size=163840,
14
+ hidden_size=4096,
15
+ head_dim=None,
16
+ intermediate_size=11008,
17
+ num_hidden_layers=32,
18
+ num_attention_heads=32,
19
+ num_key_value_heads=None,
20
+ hidden_act="silu",
21
+ initializer_range=0.02,
22
+ rms_norm_eps=1e-6,
23
+ use_cache=True,
24
+ pad_token_id=0,
25
+ bos_token_id=1,
26
+ eos_token_id=2,
27
+ rope_theta=10000.0,
28
+ rope_scaling=None,
29
+ tie_word_embeddings=False,
30
+ moe_intermediate_size: Optional[int] = None,
31
+ moe_renormalize: bool = True,
32
+ moe_router_activation_func: str = "sigmoid",
33
+ num_experts: Optional[int] = None,
34
+ num_experts_per_token: Optional[int] = None,
35
+ num_shared_experts: int = 0,
36
+ routed_scaling_factor: float = 1.0,
37
+ first_k_dense_replace: int = 0,
38
+ moe_layer_freq: int = 1,
39
+ use_grouped_topk: bool = True,
40
+ num_expert_group: int = 1,
41
+ topk_group: int = 1,
42
+ q_lora_rank: Optional[int] = None,
43
+ kv_lora_rank: Optional[int] = None,
44
+ qk_nope_head_dim: Optional[int] = None,
45
+ qk_rope_head_dim: Optional[int] = None,
46
+ v_head_dim: Optional[int] = None,
47
+ mla_use_nope: Optional[bool] = False,
48
+ mla_use_output_gate: Optional[bool] = False,
49
+ num_nextn_predict_layers: int = 0,
50
+ linear_attn_config: Optional[dict] = None,
51
+ dsa_config: Optional[dict] = None,
52
+ attn_res_block_size: Optional[int] = None,
53
+ latent_moe_use_norm: bool = False,
54
+ activation_situ_beta: Optional[float] = None,
55
+ activation_situ_linear_beta: Optional[float] = None,
56
+ max_position_embeddings: int = 4096,
57
+ routed_expert_hidden_size: Optional[int] = None,
58
+ topk_method: str = "noaux_tc",
59
+ **kwargs,
60
+ ):
61
+ self.model_type = model_type
62
+ self.vocab_size = vocab_size
63
+ self.hidden_size = hidden_size
64
+ self.head_dim = (head_dim if head_dim is not None else hidden_size //
65
+ num_attention_heads)
66
+ self.intermediate_size = intermediate_size
67
+ self.num_hidden_layers = num_hidden_layers
68
+ self.num_attention_heads = num_attention_heads
69
+
70
+ # for backward compatibility
71
+ if num_key_value_heads is None:
72
+ num_key_value_heads = num_attention_heads
73
+
74
+ self.num_key_value_heads = num_key_value_heads
75
+ self.hidden_act = hidden_act
76
+ self.initializer_range = initializer_range
77
+ self.rms_norm_eps = rms_norm_eps
78
+ self.use_cache = use_cache
79
+ self.rope_theta = rope_theta
80
+ self.rope_scaling = rope_scaling
81
+
82
+ self.q_lora_rank = q_lora_rank
83
+ self.kv_lora_rank = kv_lora_rank
84
+ self.qk_nope_head_dim = qk_nope_head_dim
85
+ self.qk_rope_head_dim = qk_rope_head_dim
86
+ self.v_head_dim = v_head_dim
87
+ self.mla_use_nope = mla_use_nope
88
+ self.mla_use_output_gate = mla_use_output_gate
89
+ # moe config
90
+ self.num_experts = num_experts
91
+ self.num_experts_per_token = num_experts_per_token
92
+ self.moe_renormalize = moe_renormalize
93
+ self.num_shared_experts = num_shared_experts
94
+ self.routed_scaling_factor = routed_scaling_factor
95
+ self.moe_router_activation_func = moe_router_activation_func
96
+ assert self.moe_router_activation_func in ("softmax", "sigmoid")
97
+ self.moe_intermediate_size = moe_intermediate_size
98
+ self.first_k_dense_replace = first_k_dense_replace
99
+ self.moe_layer_freq = moe_layer_freq
100
+ self.use_grouped_topk = use_grouped_topk
101
+ self.num_expert_group = num_expert_group
102
+ self.topk_group = topk_group
103
+ self.num_nextn_predict_layers = num_nextn_predict_layers
104
+
105
+ self.attn_res_block_size = attn_res_block_size
106
+ self.latent_moe_use_norm = latent_moe_use_norm
107
+ self.activation_situ_beta = activation_situ_beta
108
+ self.activation_situ_linear_beta = activation_situ_linear_beta
109
+ self.max_position_embeddings = max_position_embeddings
110
+ self.routed_expert_hidden_size = routed_expert_hidden_size
111
+ self.topk_method = topk_method
112
+
113
+ if linear_attn_config is not None:
114
+ assert linear_attn_config["kda_layers"] is not None
115
+ assert linear_attn_config["full_attn_layers"] is not None
116
+ self.linear_attn_config = linear_attn_config
117
+ # k31(KDA + DSA 混合):DSA 参数以嵌套子结构随包导出
118
+ self.dsa_config = dsa_config
119
+
120
+ super().__init__(
121
+ pad_token_id=pad_token_id,
122
+ bos_token_id=bos_token_id,
123
+ eos_token_id=eos_token_id,
124
+ tie_word_embeddings=tie_word_embeddings,
125
+ **kwargs,
126
+ )
127
+
128
+ @property
129
+ def is_mla(self):
130
+ return (self.q_lora_rank is not None or self.kv_lora_rank is not None
131
+ or self.qk_nope_head_dim is not None
132
+ or self.qk_rope_head_dim is not None
133
+ or self.v_head_dim is not None or self.mla_use_nope is True)
134
+
135
+ @property
136
+ def is_moe(self):
137
+ return self.num_experts is not None
138
+
139
+ @property
140
+ def is_linear_attn(self) -> bool:
141
+ return not (self.linear_attn_config is None or
142
+ (isinstance(self.linear_attn_config, dict)
143
+ and self.linear_attn_config["kda_layers"] is not None
144
+ and len(self.linear_attn_config["kda_layers"]) == 0))
145
+
146
+ def is_kda_layer(self, layer_idx: int):
147
+ return (self.linear_attn_config is not None
148
+ and (layer_idx + 1) in self.linear_attn_config["kda_layers"])
149
+
150
+
151
+ class KimiK3VisionConfig(PretrainedConfig):
152
+
153
+ def __init__(
154
+ self,
155
+ patch_size: int = 14,
156
+ init_pos_emb_height: int = 64,
157
+ init_pos_emb_width: int = 64,
158
+ init_pos_emb_time: int = 4,
159
+ pos_emb_type: str = 'divided_fixed',
160
+ vt_num_attention_heads: int = 12,
161
+ vt_num_hidden_layers: int = 27,
162
+ vt_hidden_size: int = 1024,
163
+ vt_intermediate_size: int = 4096,
164
+ merge_kernel_size: tuple = (2, 2),
165
+ merge_type: str = 'sd2_tpool',
166
+ _attn_implementation: str = 'flash_attention_2',
167
+ # MM Projector parameters
168
+ mm_projector_type: str = 'patchmergerv2',
169
+ mm_hidden_size: int | None = None,
170
+ projector_hidden_act: str = "gelu",
171
+ projector_ln_eps: float = 1e-5,
172
+ # vision tower parameters
173
+ qkv_hidden_size: int = 1536,
174
+ norm_type: str = 'rmsnorm',
175
+ attn_bias: bool = False,
176
+ patch_embed_proj_bias: bool = False,
177
+ mlp_type: str = 'mlp2',
178
+ linear_bias: bool = False,
179
+ activation_func: str = 'gelu_pytorch_tanh',
180
+ pos_emb_interpolation_mode: str = 'bilinear',
181
+ # Other parameters
182
+ ignore_index: int = -100,
183
+ media_placeholder_token_id: int = 163605,
184
+ pad_token_id: int = 0,
185
+ text_hidden_size=7168,
186
+ **kwargs):
187
+
188
+ self.patch_size = patch_size
189
+ self.init_pos_emb_height = init_pos_emb_height
190
+ self.init_pos_emb_width = init_pos_emb_width
191
+ self.init_pos_emb_time = init_pos_emb_time
192
+ self.pos_emb_type = pos_emb_type
193
+ self.vt_num_attention_heads = vt_num_attention_heads
194
+ self.vt_num_hidden_layers = vt_num_hidden_layers
195
+ self.vt_hidden_size = vt_hidden_size
196
+ self.vt_intermediate_size = vt_intermediate_size
197
+ self.merge_kernel_size = merge_kernel_size
198
+ self.merge_type = merge_type
199
+ self._attn_implementation = _attn_implementation
200
+
201
+ # MM Projector config
202
+ self.mm_projector_type = mm_projector_type
203
+ self.mm_hidden_size = mm_hidden_size if mm_hidden_size is not None else vt_hidden_size
204
+ self.projector_hidden_act = projector_hidden_act
205
+ self.projector_ln_eps = projector_ln_eps
206
+ self.text_hidden_size = text_hidden_size
207
+
208
+ # vision tower parameters
209
+ self.qkv_hidden_size = qkv_hidden_size
210
+ self.norm_type = norm_type
211
+ self.attn_bias = attn_bias
212
+ self.patch_embed_proj_bias = patch_embed_proj_bias
213
+ self.mlp_type = mlp_type
214
+ self.linear_bias = linear_bias
215
+ self.activation_func = activation_func
216
+ self.pos_emb_interpolation_mode = pos_emb_interpolation_mode
217
+
218
+ super().__init__(**kwargs)
219
+
220
+
221
+ class KimiK3Config(PretrainedConfig):
222
+ """Kimi-K3 model configuration.
223
+
224
+ Args:
225
+ text_config (dict | KimiLinearConfig): Configuration for the text model.
226
+
227
+ Vision Tower Parameters (from MoonViT3dConfig):
228
+ patch_size (int): Patch size for vision tower.
229
+ init_pos_emb_height (int): Initial position embedding height.
230
+ init_pos_emb_width (int): Initial position embedding width.
231
+ init_pos_emb_time (int): Initial position embedding time dimension.
232
+ pos_emb_type (str): Type of position embedding.
233
+ vt_num_attention_heads (int): Number of attention heads in vision tower.
234
+ vt_num_hidden_layers (int): Number of hidden layers in vision tower.
235
+ vt_hidden_size (int): Hidden size of vision tower.
236
+ vt_intermediate_size (int): Intermediate size in vision tower FFN.
237
+ merge_kernel_size (tuple): Kernel size for patch merging.
238
+ merge_type (str): Type of merge operation.
239
+ _attn_implementation (str): Attention implementation type.
240
+
241
+ MM Projector Parameters (from MultiModalProjectorConfig):
242
+ mm_projector_type (str): Type of multimodal projector.
243
+ mm_hidden_size (int): Hidden size from vision tower (should match vt_hidden_size).
244
+ projector_hidden_act (str): Activation function for projector.
245
+ projector_ln_eps (float): Layer norm epsilon for projector.
246
+
247
+ Other Parameters:
248
+ ignore_index (int): The ignore index for the loss function.
249
+ media_placeholder_token_id (int): The token ID to use for media placeholders.
250
+ pad_token_id (int): The token ID to use for padding.
251
+ """
252
+
253
+ model_type = "kimi_k31"
254
+
255
+ def __init__(
256
+ self,
257
+ text_config: dict | KimiLinearConfig = None,
258
+ vision_config: dict | KimiK3VisionConfig = None,
259
+ # Other parameters
260
+ ignore_index: int = -100,
261
+ media_placeholder_token_id: int = 163605,
262
+ pad_token_id: int = 0,
263
+ **kwargs,
264
+ ):
265
+ if isinstance(text_config, dict):
266
+ text_config = KimiLinearConfig(**text_config)
267
+ if isinstance(vision_config, dict):
268
+ vision_config = KimiK3VisionConfig(**vision_config)
269
+ self.text_config = text_config
270
+ self.vision_config = vision_config
271
+ # Other config
272
+ self.ignore_index = ignore_index
273
+ self.media_placeholder_token_id = media_placeholder_token_id
274
+ if getattr(self.text_config, "quantization_config", None) is not None:
275
+ self.quantization_config = self.text_config.quantization_config
276
+
277
+ super().__init__(pad_token_id=pad_token_id, **kwargs)
encoding_k3.py ADDED
@@ -0,0 +1,735 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Kimi K3 XTML encoding helpers.
2
+
3
+ This module keeps chat rendering in Python.
4
+ Callers that need token IDs should consume ``EncodeSegment`` objects directly:
5
+ structural markers may be encoded as tiktoken special tokens, while user/tool
6
+ text and attribute values are encoded as ordinary text.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import json
12
+ from dataclasses import dataclass
13
+ from typing import Any, Iterable, Optional
14
+
15
+ OPEN_TOKEN = "<|open|>"
16
+ CLOSE_TOKEN = "<|close|>"
17
+ SEP_TOKEN = "<|sep|>"
18
+ END_OF_MSG_TOKEN = "<|end_of_msg|>"
19
+ IMAGE_PLACEHOLDER = "<|kimi_image_placeholder|>"
20
+
21
+ _VALID_THINKING_EFFORTS = {"low", "high", "max"}
22
+
23
+
24
+ @dataclass(frozen=True)
25
+ class EncodeSegment:
26
+ text: str
27
+ allow_special: bool = False
28
+
29
+
30
+ class _ImagePromptState:
31
+
32
+ def __init__(self, image_prompts: Optional[list[str]] = None):
33
+ self.image_prompts = image_prompts
34
+ self.index = 0
35
+
36
+ def next_prompt(self) -> str:
37
+ if self.image_prompts is None:
38
+ return IMAGE_PLACEHOLDER
39
+ if self.index >= len(self.image_prompts):
40
+ raise ValueError("More image placeholders than image prompts.")
41
+ prompt = self.image_prompts[self.index]
42
+ self.index += 1
43
+ return prompt
44
+
45
+ def assert_consumed(self) -> None:
46
+ if self.image_prompts is None:
47
+ return
48
+ if self.index != len(self.image_prompts):
49
+ raise ValueError(
50
+ f"image prompt count {len(self.image_prompts)} != "
51
+ f"consumed placeholder count {self.index}")
52
+
53
+
54
+ def _segment(text: Any, *, allow_special: bool = False) -> list[EncodeSegment]:
55
+ text = str(text)
56
+ if not text:
57
+ return []
58
+ return [EncodeSegment(text, allow_special=allow_special)]
59
+
60
+
61
+ def _control(text: str) -> list[EncodeSegment]:
62
+ return _segment(text, allow_special=True)
63
+
64
+
65
+ def _text(text: Any) -> list[EncodeSegment]:
66
+ return _segment(text, allow_special=False)
67
+
68
+
69
+ def _append_text(
70
+ segments: list[EncodeSegment],
71
+ text: Any,
72
+ image_state: _ImagePromptState,
73
+ ) -> None:
74
+ text = str(text)
75
+ if text == "":
76
+ return
77
+ if image_state.image_prompts is None or IMAGE_PLACEHOLDER not in text:
78
+ segments.extend(_text(text))
79
+ return
80
+
81
+ parts = text.split(IMAGE_PLACEHOLDER)
82
+ for i, part in enumerate(parts):
83
+ segments.extend(_text(part))
84
+ if i < len(parts) - 1:
85
+ segments.extend(
86
+ _segment(image_state.next_prompt(), allow_special=True))
87
+
88
+
89
+ def _escape_attr_value(value: Any) -> str:
90
+ return str(value).replace("&", "&amp;").replace('"', "&quot;")
91
+
92
+
93
+ def _attr(key: str, value: Any) -> list[EncodeSegment]:
94
+ return (_text(f" {key}") + _text('="') + _text(_escape_attr_value(value)) +
95
+ _text('"'))
96
+
97
+
98
+ def _open_tag(
99
+ tag: str, attrs: Iterable[tuple[str, Any]] = ()) -> list[EncodeSegment]:
100
+ segments: list[EncodeSegment] = []
101
+ segments.extend(_control(OPEN_TOKEN))
102
+ segments.extend(_text(tag))
103
+ for key, value in attrs:
104
+ segments.extend(_attr(key, value))
105
+ segments.extend(_control(SEP_TOKEN))
106
+ return segments
107
+
108
+
109
+ def _close_tag(tag: str) -> list[EncodeSegment]:
110
+ segments: list[EncodeSegment] = []
111
+ segments.extend(_control(CLOSE_TOKEN))
112
+ segments.extend(_text(tag))
113
+ segments.extend(_control(SEP_TOKEN))
114
+ return segments
115
+
116
+
117
+ def _end_of_msg() -> list[EncodeSegment]:
118
+ return _control(END_OF_MSG_TOKEN)
119
+
120
+
121
+ def _json_compact(value: Any) -> str:
122
+ return json.dumps(value, ensure_ascii=False, separators=(",", ":"))
123
+
124
+
125
+ def _is_mapping(value: Any) -> bool:
126
+ return isinstance(value, dict)
127
+
128
+
129
+ def _xtml_type(value: Any) -> str:
130
+ if isinstance(value, bool):
131
+ return "boolean"
132
+ if value is None:
133
+ return "null"
134
+ if isinstance(value, (int, float)) and not isinstance(value, bool):
135
+ return "number"
136
+ if isinstance(value, str):
137
+ return "string"
138
+ if _is_mapping(value):
139
+ return "object"
140
+ return "array"
141
+
142
+
143
+ def _xtml_value(value: Any) -> str:
144
+ if isinstance(value, str):
145
+ return value
146
+ return json.dumps(value, ensure_ascii=False)
147
+
148
+
149
+ def _get_value(obj: Any, key: str, default: Any = None) -> Any:
150
+ if isinstance(obj, dict):
151
+ return obj.get(key, default)
152
+ return getattr(obj, key, default)
153
+
154
+
155
+ def extract_response_schema(response_format: Any) -> Any:
156
+ if response_format is None:
157
+ return None
158
+
159
+ json_schema = _get_value(response_format, "json_schema")
160
+ if json_schema is None:
161
+ return None
162
+
163
+ if isinstance(json_schema, dict):
164
+ return json_schema.get(
165
+ "schema",
166
+ json_schema.get("json_schema", json_schema),
167
+ )
168
+
169
+ schema = _get_value(json_schema, "schema")
170
+ if schema is not None:
171
+ return schema
172
+
173
+ schema = _get_value(json_schema, "json_schema")
174
+ if schema is not None:
175
+ return schema
176
+
177
+ return json_schema
178
+
179
+
180
+ def deep_sort_dict(obj: Any) -> Any:
181
+ if isinstance(obj, dict):
182
+ return {k: deep_sort_dict(v) for k, v in sorted(obj.items())}
183
+ if isinstance(obj, list):
184
+ return [deep_sort_dict(item) for item in obj]
185
+ return obj
186
+
187
+
188
+ # One normalized tool-call argument: (key, XTML type, rendered text). The
189
+ # text keeps the original JSON literal for non-string values and is the
190
+ # decoded string for string values.
191
+ XtmlArgument = tuple[str, str, str]
192
+
193
+
194
+ def _parse_arguments_object(s: str) -> list[XtmlArgument]:
195
+ """Parse a JSON object string one level deep.
196
+
197
+ Each top-level key-value pair yields one ``(key, type, text)`` triple. The
198
+ text keeps the original JSON literal for non-string values (``1e2`` stays
199
+ ``1e2``, ``[1,2]`` keeps its exact bytes) and is the decoded (unescaped)
200
+ string for string values. Nested values are never re-serialized. Anything
201
+ after the closing ``}`` is ignored. Raises ``ValueError`` on malformed
202
+ input (including a valid non-object JSON document).
203
+ """
204
+ idx = 0
205
+
206
+ def _skip_whitespaces() -> None:
207
+ nonlocal idx
208
+ while idx < len(s) and s[idx] in (" ", "\t", "\n", "\r"):
209
+ idx += 1
210
+
211
+ def _next_char() -> Optional[str]:
212
+ nonlocal idx
213
+ if idx < len(s):
214
+ c = s[idx]
215
+ idx += 1
216
+ return c
217
+ return None
218
+
219
+ _skip_whitespaces()
220
+ if _next_char() != "{":
221
+ raise ValueError("JSON arguments must be an object")
222
+ _skip_whitespaces()
223
+
224
+ parsed: list[XtmlArgument] = []
225
+
226
+ if idx >= len(s):
227
+ raise ValueError("Unexpected end of JSON object")
228
+ if s[idx] == "}":
229
+ return parsed
230
+
231
+ json_decoder = json.JSONDecoder(strict=False)
232
+
233
+ def _raw_decode(start_idx: int) -> tuple[Any, int]:
234
+ try:
235
+ return json_decoder.raw_decode(s, idx=start_idx)
236
+ except json.JSONDecodeError as e:
237
+ raise ValueError(str(e)) from e
238
+
239
+ while True:
240
+ decoded_key, idx = _raw_decode(idx)
241
+ if not isinstance(decoded_key, str):
242
+ raise ValueError(
243
+ f"JSON object key must be a string, got {decoded_key!r}")
244
+ _skip_whitespaces()
245
+ if _next_char() != ":":
246
+ raise ValueError(f"Expects ':' after {decoded_key}")
247
+ _skip_whitespaces()
248
+
249
+ value_start_idx = idx
250
+ decoded_value, idx = _raw_decode(idx)
251
+ value_end_idx = idx
252
+
253
+ text = (decoded_value if isinstance(decoded_value, str) else
254
+ s[value_start_idx:value_end_idx])
255
+ parsed.append((decoded_key, _xtml_type(decoded_value), text))
256
+
257
+ _skip_whitespaces()
258
+ c = _next_char()
259
+ _skip_whitespaces()
260
+
261
+ if c == "}":
262
+ break
263
+ elif c == ",":
264
+ continue
265
+ else:
266
+ raise ValueError(f"Expect '}}' or ',', got {c!r}")
267
+
268
+ return parsed
269
+
270
+
271
+ def normalize_tool_arguments(
272
+ arguments: Any, ) -> tuple[list[XtmlArgument], Optional[str]]:
273
+ """Normalize tool call arguments for XTML rendering.
274
+
275
+ Returns ``(argument_triples, raw_json_block)``. String arguments are parsed
276
+ one level deep so non-string values keep their original JSON literal text,
277
+ and anything after the closing ``}`` is discarded; dict arguments (already
278
+ Python objects) are serialized on the spot. Any string that is not a
279
+ well-formed JSON object -- unparsable, whitespace only, or valid
280
+ non-object JSON -- falls back to the raw ``<json>`` block.
281
+ """
282
+ if arguments is None:
283
+ return [], None
284
+ if isinstance(arguments, dict):
285
+ return [(str(key), _xtml_type(value), _xtml_value(value))
286
+ for key, value in arguments.items()], None
287
+ if isinstance(arguments, str):
288
+ if arguments == "":
289
+ return [], None
290
+ try:
291
+ return _parse_arguments_object(arguments), None
292
+ except ValueError:
293
+ return [], arguments
294
+ raise TypeError(
295
+ "Kimi K3 tool call arguments must be a dict or a JSON object string.")
296
+
297
+
298
+ def normalize_message(message: Any) -> Any:
299
+ if not isinstance(message, dict):
300
+ return message
301
+
302
+ normalized = dict(message)
303
+
304
+ tools = normalized.get("tools")
305
+ if tools is not None:
306
+ normalized["tools"] = deep_sort_dict(tools)
307
+
308
+ tool_calls = normalized.get("tool_calls")
309
+ if not tool_calls:
310
+ return normalized
311
+
312
+ normalized_calls = []
313
+ for tool_call in tool_calls:
314
+ if not isinstance(tool_call, dict):
315
+ normalized_calls.append(tool_call)
316
+ continue
317
+
318
+ tc = dict(tool_call)
319
+ function = tc.get("function")
320
+ if isinstance(function, dict):
321
+ fn = dict(function)
322
+ arguments, json_block = normalize_tool_arguments(
323
+ fn.get("arguments"))
324
+ fn["arguments"] = arguments
325
+ if json_block is None:
326
+ fn.pop("_xtml_json_block", None)
327
+ else:
328
+ fn["_xtml_json_block"] = json_block
329
+ tc["function"] = fn
330
+ else:
331
+ arguments, json_block = normalize_tool_arguments(
332
+ tc.get("arguments"))
333
+ tc["arguments"] = arguments
334
+ if json_block is None:
335
+ tc.pop("_xtml_json_block", None)
336
+ else:
337
+ tc["_xtml_json_block"] = json_block
338
+ normalized_calls.append(tc)
339
+
340
+ normalized["tool_calls"] = normalized_calls
341
+ return normalized
342
+
343
+
344
+ def normalize_conversation(conversation: Any) -> Any:
345
+ if not isinstance(conversation, list):
346
+ return conversation
347
+
348
+ def normalize_messages(messages: list[Any]) -> list[Any]:
349
+ return [normalize_message(message) for message in messages]
350
+
351
+ if conversation and isinstance(conversation[0], list):
352
+ return [normalize_messages(messages) for messages in conversation]
353
+ return normalize_messages(conversation)
354
+
355
+
356
+ def _tool_call_id_index(tool_calls: Any) -> dict:
357
+ """Map assistant ``tool_calls[].id`` to ``(1-based position, function name)``.
358
+
359
+ The position mirrors the chat template's enumeration over ``tool_calls``
360
+ (every entry advances the position, even an id-less one). Duplicate ids keep
361
+ their first occurrence.
362
+ """
363
+ index: dict = {}
364
+ if not isinstance(tool_calls, list):
365
+ return index
366
+ for position, tool_call in enumerate(tool_calls, start=1):
367
+ if not isinstance(tool_call, dict):
368
+ continue
369
+ call_id = tool_call.get("id")
370
+ if call_id is None:
371
+ continue
372
+ key = str(call_id)
373
+ if key in index:
374
+ continue
375
+ function = tool_call.get("function")
376
+ name = (function.get("name")
377
+ if isinstance(function, dict) else tool_call.get("name"))
378
+ index[key] = (position, name)
379
+ return index
380
+
381
+
382
+ def normalize_xtml_tool_result_messages(messages: list[Any]) -> list[Any]:
383
+ """Re-sort K3 XTML tool results into assistant ``tool_calls`` order.
384
+
385
+ Serving frameworks generally deliver tool results already in call order. A
386
+ direct Transformers caller, however, may pass OpenAI-style tool messages in any
387
+ order, so each run of consecutive tool messages is matched against the most
388
+ recent preceding assistant ``tool_calls`` by opaque ``tool_call_id`` ==
389
+ ``tool_calls[].id`` (K3 drops the ``func:index`` format requirement) and
390
+ sorted by the matched 1-based position. The matched call is authoritative,
391
+ so each matched message's ``tool`` is set to that call's function name --
392
+ this keeps an explicit (and possibly stale) ``tool``/``name`` from drifting
393
+ out of sync with the reordered position. ``index`` is still derived from the
394
+ rendered position by the chat template. A run that cannot be fully matched is
395
+ left untouched. Re-running is idempotent.
396
+
397
+ This function is side-effect free: matched tool messages are shallow-copied
398
+ before their ``tool``/``name`` is rewritten, and every other message is
399
+ appended to the output as-is. The input list and its message objects are
400
+ never mutated.
401
+ """
402
+ if not isinstance(messages, list):
403
+ return messages
404
+
405
+ output: list[Any] = []
406
+ current_index: dict = {}
407
+ i = 0
408
+ n = len(messages)
409
+
410
+ while i < n:
411
+ message = messages[i]
412
+
413
+ if isinstance(message, dict) and message.get("role") == "assistant":
414
+ tool_calls = message.get("tool_calls")
415
+ current_index = _tool_call_id_index(
416
+ tool_calls) if tool_calls else {}
417
+ output.append(message)
418
+ i += 1
419
+ continue
420
+
421
+ if not isinstance(message, dict) or message.get("role") != "tool":
422
+ output.append(message)
423
+ i += 1
424
+ continue
425
+
426
+ run: list[tuple] = [] # (position, original_offset, message, name)
427
+ unresolved = False
428
+ offset = 0
429
+ while (i < n and isinstance(messages[i], dict)
430
+ and messages[i].get("role") == "tool"):
431
+ tool_message = messages[i]
432
+ call_id = tool_message.get("tool_call_id", tool_message.get("id"))
433
+ matched = current_index.get(
434
+ str(call_id)) if call_id is not None else None
435
+ if matched is None:
436
+ unresolved = True
437
+ run.append((None, offset, tool_message, None))
438
+ else:
439
+ position, name = matched
440
+ run.append((position, offset, tool_message, name))
441
+ offset += 1
442
+ i += 1
443
+
444
+ if unresolved:
445
+ output.extend(item[2] for item in run)
446
+ else:
447
+ run.sort(key=lambda item: (item[0], item[1]))
448
+ for _, _, tool_message, name in run:
449
+ if name is None:
450
+ output.append(tool_message)
451
+ continue
452
+ # The id-matched call is authoritative: align tool (and any
453
+ # explicit name) so the rendered XTML tool attribute cannot
454
+ # disagree with the reordered position. Copy first so the
455
+ # caller's message object is never mutated.
456
+ resolved = dict(tool_message)
457
+ resolved["tool"] = name
458
+ if "name" in resolved:
459
+ resolved["name"] = name
460
+ output.append(resolved)
461
+
462
+ return output
463
+
464
+
465
+ def is_batched_conversation(conversation: Any) -> bool:
466
+ return (isinstance(conversation, list) and bool(conversation)
467
+ and isinstance(conversation[0], list))
468
+
469
+
470
+ def _render_content_segments(
471
+ content: Any,
472
+ image_state: _ImagePromptState,
473
+ ) -> list[EncodeSegment]:
474
+ segments: list[EncodeSegment] = []
475
+ if isinstance(content, str):
476
+ _append_text(segments, content, image_state)
477
+ elif content is not None:
478
+ for part in content:
479
+ if part["type"] in ["image", "image_url"]:
480
+ segments.extend(
481
+ _segment(image_state.next_prompt(), allow_special=True))
482
+ else:
483
+ _append_text(segments, part["text"], image_state)
484
+ return segments
485
+
486
+
487
+ def _internal_system_message(message_type: str,
488
+ body: str) -> list[EncodeSegment]:
489
+ segments: list[EncodeSegment] = []
490
+ segments.extend(
491
+ _open_tag("message", [("role", "system"), ("type", message_type)]))
492
+ segments.extend(_text(body.strip()))
493
+ segments.extend(_close_tag("message"))
494
+ segments.extend(_end_of_msg())
495
+ return segments
496
+
497
+
498
+ def _render_assistant_segments(
499
+ message: dict[str, Any],
500
+ image_state: _ImagePromptState,
501
+ thinking: bool = True,
502
+ ) -> list[EncodeSegment]:
503
+ segments: list[EncodeSegment] = []
504
+ # The <think> channel is structural: in thinking mode every assistant
505
+ # message carries the open/close tags even when there is no reasoning
506
+ # content to fill in. In non-thinking mode the channel is dropped
507
+ # entirely.
508
+ if thinking:
509
+ reasoning_content = message.get("reasoning_content") or message.get(
510
+ "reasoning")
511
+ segments.extend(_open_tag("think"))
512
+ # Only an empty string counts as no reasoning, so whitespace-only
513
+ # reasoning is still rendered into the think channel.
514
+ if reasoning_content is not None and str(reasoning_content) != "":
515
+ _append_text(segments, reasoning_content, image_state)
516
+ segments.extend(_close_tag("think"))
517
+
518
+ segments.extend(_open_tag("response"))
519
+ segments.extend(
520
+ _render_content_segments(message.get("content"), image_state))
521
+ segments.extend(_close_tag("response"))
522
+
523
+ tool_calls = message.get("tool_calls")
524
+ if tool_calls:
525
+ segments.extend(_open_tag("tools"))
526
+ for index, tool_call in enumerate(tool_calls, start=1):
527
+ fn = tool_call.get("function", tool_call)
528
+ segments.extend(
529
+ _open_tag("call", [("tool", fn["name"]), ("index", index)]))
530
+ args = fn.get("arguments", [])
531
+ json_block = fn.get("_xtml_json_block")
532
+ if json_block is not None:
533
+ segments.extend(_open_tag("json", [("type", "object")]))
534
+ _append_text(segments, json_block, image_state)
535
+ segments.extend(_close_tag("json"))
536
+ else:
537
+ for key, arg_type, arg_text in args:
538
+ segments.extend(
539
+ _open_tag("argument", [("key", key),
540
+ ("type", arg_type)]))
541
+ _append_text(segments, arg_text, image_state)
542
+ segments.extend(_close_tag("argument"))
543
+ segments.extend(_close_tag("call"))
544
+ segments.extend(_close_tag("tools"))
545
+
546
+ return segments
547
+
548
+
549
+ def _render_tool_declare(tools: Any,
550
+ *,
551
+ dynamic: bool = False) -> list[EncodeSegment]:
552
+ if dynamic:
553
+ body = (
554
+ "## New Tools Available\n"
555
+ "The system dynamically extends the toolset via lazy-loading.\n"
556
+ "You have access to all existing and extended tools.\n"
557
+ "Here are the specs for the extended tools.\n\n"
558
+ "```json\n"
559
+ f"{_json_compact(tools)}\n"
560
+ "```")
561
+ else:
562
+ body = ("# Tools\n"
563
+ "Here are the available tools, described in JSONSchema.\n\n"
564
+ "```json\n"
565
+ f"{_json_compact(tools)}\n"
566
+ "```")
567
+ segments: list[EncodeSegment] = []
568
+ segments.extend(
569
+ _open_tag("message", [("role", "system"), ("type", "tool-declare")]))
570
+ segments.extend(_text(body))
571
+ segments.extend(_close_tag("message"))
572
+ segments.extend(_end_of_msg())
573
+ return segments
574
+
575
+
576
+ def build_chat_segments(
577
+ messages: list[Any],
578
+ tools: Optional[list[dict]] = None,
579
+ *,
580
+ add_generation_prompt: bool = True,
581
+ thinking: bool = True,
582
+ image_prompts: Optional[list[str]] = None,
583
+ **kwargs: Any,
584
+ ) -> list[EncodeSegment]:
585
+ # Re-sort tool results by tool_call_id at the lowest layer so every caller
586
+ # (processor or direct tokenizer) gets correctly ordered XTML. The helper is
587
+ # side-effect free, so the caller's message objects are left untouched.
588
+ messages = normalize_xtml_tool_result_messages(messages)
589
+ messages = normalize_conversation(messages)
590
+ tools = deep_sort_dict(tools)
591
+
592
+ kwargs = dict(kwargs)
593
+ response_format = kwargs.get("response_format")
594
+ if "response_schema" not in kwargs:
595
+ response_schema = extract_response_schema(response_format)
596
+ if response_schema is not None:
597
+ kwargs["response_schema"] = response_schema
598
+ if kwargs.get("response_schema") is not None:
599
+ kwargs["response_schema"] = deep_sort_dict(kwargs["response_schema"])
600
+
601
+ image_state = _ImagePromptState(image_prompts)
602
+ segments: list[EncodeSegment] = []
603
+
604
+ tool_calls = None
605
+ tool_index = 0
606
+
607
+ if tools:
608
+ segments.extend(_render_tool_declare(tools))
609
+
610
+ thinking_effort = kwargs.get("thinking_effort")
611
+ if thinking and thinking_effort is not None:
612
+ assert thinking_effort in _VALID_THINKING_EFFORTS, (
613
+ f"Unsupported thinking_effort={thinking_effort!r}; "
614
+ f"supported values are {sorted(_VALID_THINKING_EFFORTS)}.")
615
+ if thinking and thinking_effort in _VALID_THINKING_EFFORTS:
616
+ segments.extend(
617
+ _internal_system_message(
618
+ "thinking-effort",
619
+ "`thinking_effort` guides on how much to think in your "
620
+ "thinking channel (not including the response channel), "
621
+ "supported values include `low`, `medium`, `high`, and `max`.\n"
622
+ f"Now the system is invoked with `thinking_effort={thinking_effort}`.",
623
+ ))
624
+
625
+ for message_index, message in enumerate(messages):
626
+ # Malformed messages are rejected instead of being silently skipped.
627
+ if not isinstance(message, dict):
628
+ raise ValueError(
629
+ f"Kimi K3 messages must be dicts, got {type(message).__name__} "
630
+ f"at index {message_index}.")
631
+
632
+ role = message.get("role")
633
+ if role == "user":
634
+ attrs = [("role", "user")]
635
+ if message.get("name"):
636
+ attrs.append(("name", message["name"]))
637
+ segments.extend(_open_tag("message", attrs))
638
+ segments.extend(
639
+ _render_content_segments(message.get("content"), image_state))
640
+ segments.extend(_close_tag("message"))
641
+ segments.extend(_end_of_msg())
642
+ elif role == "system" and message.get("tools"):
643
+ segments.extend(
644
+ _render_tool_declare(message["tools"], dynamic=True))
645
+ elif role == "system":
646
+ attrs = [("role", "system")]
647
+ if message.get("name"):
648
+ attrs.append(("name", message["name"]))
649
+ segments.extend(_open_tag("message", attrs))
650
+ segments.extend(
651
+ _render_content_segments(message.get("content"), image_state))
652
+ segments.extend(_close_tag("message"))
653
+ segments.extend(_end_of_msg())
654
+ elif role == "tool":
655
+ tool_index += 1
656
+ tool_name = message.get("tool", message.get("name"))
657
+ if (tool_name is None and tool_calls is not None
658
+ and tool_index <= len(tool_calls)):
659
+ tc = tool_calls[tool_index - 1]
660
+ fn = tc.get("function", tc)
661
+ tool_name = fn["name"]
662
+ if tool_name is None:
663
+ raise ValueError(
664
+ "Kimi K3 tool messages need a resolvable tool name: "
665
+ "carry `tool`/`name`, or match a preceding assistant "
666
+ "tool_call by order.")
667
+ segments.extend(
668
+ _open_tag(
669
+ "message",
670
+ [("role", "tool"), ("tool", tool_name),
671
+ ("index", tool_index)],
672
+ ))
673
+ segments.extend(
674
+ _render_content_segments(message.get("content"), image_state))
675
+ segments.extend(_close_tag("message"))
676
+ segments.extend(_end_of_msg())
677
+ elif role == "assistant":
678
+ tool_calls = message.get("tool_calls")
679
+ tool_index = 0
680
+ attrs = [("role", "assistant")]
681
+ if message.get("name"):
682
+ attrs.append(("name", message["name"]))
683
+ segments.extend(_open_tag("message", attrs))
684
+ segments.extend(
685
+ _render_assistant_segments(message, image_state, thinking))
686
+ segments.extend(_close_tag("message"))
687
+ segments.extend(_end_of_msg())
688
+ else:
689
+ raise ValueError(
690
+ f"Unknown message role {role!r} at index {message_index}.")
691
+
692
+ tool_choice = kwargs.get("tool_choice")
693
+ if tool_choice == "required":
694
+ segments.extend(
695
+ _internal_system_message(
696
+ "tool-choice",
697
+ "The system is invoked with `tool_choice=required`.\n"
698
+ "You MUST call tools in the next message.",
699
+ ))
700
+ elif tool_choice == "none":
701
+ segments.extend(
702
+ _internal_system_message(
703
+ "tool-choice",
704
+ "The system is invoked with `tool_choice=none`.\n"
705
+ "You MUST NOT call any tools in the next message.",
706
+ ))
707
+
708
+ rf = kwargs.get("response_format")
709
+ rf_type = _get_value(rf, "type", rf) if isinstance(rf, dict) else rf
710
+ if rf_type == "json_object":
711
+ segments.extend(
712
+ _internal_system_message(
713
+ "response-format",
714
+ "The system is invoked with `response_format=json_object`.\n"
715
+ "Your response must be raw JSON data without markdown code "
716
+ "blocks (```json) or any additional formatting.",
717
+ ))
718
+ elif rf_type == "json_schema":
719
+ schema = _json_compact(kwargs.get("response_schema"))
720
+ segments.extend(
721
+ _internal_system_message(
722
+ "response-format",
723
+ "The system is invoked with `response_format=json_schema`.\n"
724
+ "Your response must be raw JSON data without markdown code "
725
+ "blocks (```json) or any additional formatting.\n"
726
+ "The JSON data must match the following schema:\n"
727
+ f"```json\n{schema}\n```",
728
+ ))
729
+
730
+ if add_generation_prompt:
731
+ segments.extend(_open_tag("message", [("role", "assistant")]))
732
+ segments.extend(_open_tag("think" if thinking else "response"))
733
+
734
+ image_state.assert_consumed()
735
+ return segments
evaluation_results_93layer.json ADDED
@@ -0,0 +1,163 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "model": "Grawnch/Kimi-K3.1-Pro-Coder",
3
+ "base_model": "moonshotai/Kimi-K3.1-Pro",
4
+ "hardware": "NVIDIA RTX PRO 6000 Blackwell Server Edition (102.6 GB GDDR7 VRAM, 176 GiB System RAM, 135 GiB /dev/shm)",
5
+ "layers_evaluated": 93,
6
+ "shards_evaluated": "96/96 (Shards 1..93 Layers 0..92 + Shard 94 embed_tokens/output_attn_res/norm/lm_head)",
7
+ "aime2025_benchmark": {
8
+ "total_sequences": 40,
9
+ "total_tokens": 17453,
10
+ "wall_time_sec": 725.17,
11
+ "moe_gpu_sec": 474.68,
12
+ "moe_layer_tokens_per_sec": 3382.7,
13
+ "end_to_end_tokens_per_sec": 36.77,
14
+ "top1_agreement_pct": 95.20,
15
+ "top5_agreement_pct": 99.91,
16
+ "mean_kld": 0.01912,
17
+ "aime2025_accuracy_coder": "29/30 (96.67%)",
18
+ "aime2025_accuracy_ref": "29/30 (96.67%)",
19
+ "aime2025_correct_coder": 29,
20
+ "aime2025_correct_ref": 29,
21
+ "aime2025_exact_match_agreement_pct": 100.0,
22
+ "math_ppl_coder": 2.2358,
23
+ "math_ppl_ref": 2.2135,
24
+ "math_delta_ppl": 0.0223,
25
+ "code_ppl_coder": 1.5814,
26
+ "code_ppl_ref": 1.5594,
27
+ "code_delta_ppl": 0.0220,
28
+ "overall_ppl_coder": 2.1627,
29
+ "overall_ppl_ref": 2.1403,
30
+ "overall_delta_ppl": 0.0224,
31
+ "strata_tier1_hot_vram_pct": 16.56,
32
+ "strata_tier2_warm_ram_pct": 29.94,
33
+ "strata_tier3_cold_disk_pct": 53.50,
34
+ "strata_hot_warm_static_pct": 46.50,
35
+ "strata_deep_layers_88_91_hot_warm_pct": 66.28,
36
+ "strata_router_lookahead_effective_hit_pct": 97.92,
37
+ "checkpoint_layers": [
38
+ {"layer": 1, "kind": "KDA", "sec": 17.18, "moe_sec": 5.634, "moe_cos": 0.923062, "state_cos": 0.977311},
39
+ {"layer": 3, "kind": "MLA", "sec": 9.972, "moe_sec": 5.479, "moe_cos": 0.939353, "state_cos": 0.979728},
40
+ {"layer": 15, "kind": "MLA", "sec": 9.686, "moe_sec": 5.106, "moe_cos": 0.891969, "state_cos": 0.982217},
41
+ {"layer": 31, "kind": "MLA", "sec": 10.036,"moe_sec": 5.098, "moe_cos": 0.947467, "state_cos": 0.990545},
42
+ {"layer": 47, "kind": "MLA", "sec": 9.733, "moe_sec": 5.061, "moe_cos": 0.927411, "state_cos": 0.976064},
43
+ {"layer": 63, "kind": "MLA", "sec": 9.915, "moe_sec": 5.103, "moe_cos": 0.835011, "state_cos": 0.971826},
44
+ {"layer": 79, "kind": "MLA", "sec": 10.064,"moe_sec": 5.162, "moe_cos": 0.868206, "state_cos": 0.986496},
45
+ {"layer": 88, "kind": "KDA", "sec": 11.407,"moe_sec": 5.091, "moe_cos": 0.902579, "state_cos": 0.987363},
46
+ {"layer": 90, "kind": "KDA", "sec": 11.303,"moe_sec": 5.026, "moe_cos": 0.968929, "state_cos": 0.989804},
47
+ {"layer": 91, "kind": "MLA", "sec": 11.182,"moe_sec": 5.195, "moe_cos": 0.999990, "state_cos": 0.999992},
48
+ {"layer": 92, "kind": "MLA", "sec": 11.036,"moe_sec": 5.099, "moe_cos": 0.999958, "state_cos": 0.999994}
49
+ ]
50
+ },
51
+ "livecodebench_rolling_window": {
52
+ "window": "2024.10 - 2025.05 (LeetCode / AtCoder / Codeforces)",
53
+ "total_problems": 30,
54
+ "difficulty_split": "10 Easy / 10 Medium / 10 Hard",
55
+ "total_tokens": 4854,
56
+ "wall_time_sec": 754.71,
57
+ "moe_gpu_sec": 449.96,
58
+ "moe_layer_tokens_per_sec": 992.5,
59
+ "end_to_end_tokens_per_sec": 10.79,
60
+ "top1_agreement_pct": 95.06,
61
+ "top5_agreement_pct": 99.92,
62
+ "mean_kld": 0.02248,
63
+ "lcb_pass1_overall_coder": "29/30 (96.67%)",
64
+ "lcb_pass1_overall_ref": "29/30 (96.67%)",
65
+ "lcb_pass1_easy_coder": "9/10 (90.0%)",
66
+ "lcb_pass1_easy_ref": "9/10 (90.0%)",
67
+ "lcb_pass1_medium_coder": "10/10 (100.0%)",
68
+ "lcb_pass1_medium_ref": "10/10 (100.0%)",
69
+ "lcb_pass1_hard_coder": "10/10 (100.0%)",
70
+ "lcb_pass1_hard_ref": "10/10 (100.0%)",
71
+ "lcb_exact_match_agreement_pct": 100.0,
72
+ "lcb_ppl_overall_coder": 2.7629,
73
+ "lcb_ppl_overall_ref": 2.6911,
74
+ "lcb_ppl_easy_coder": 5.4529,
75
+ "lcb_ppl_easy_ref": 5.1851,
76
+ "lcb_ppl_medium_coder": 2.9995,
77
+ "lcb_ppl_medium_ref": 2.9266,
78
+ "lcb_ppl_hard_coder": 2.1562,
79
+ "lcb_ppl_hard_ref": 2.1136,
80
+ "strata_tier1_hot_vram_pct": 15.64,
81
+ "strata_tier2_warm_ram_pct": 28.75,
82
+ "strata_tier3_cold_disk_pct": 55.61,
83
+ "strata_hot_warm_static_pct": 44.39,
84
+ "strata_deep_layers_88_92_hot_warm_pct": 61.56,
85
+ "checkpoint_layers": [
86
+ {"layer": 1, "kind": "KDA", "sec": 19.375,"moe_sec": 5.227, "moe_cos": 0.918028, "state_cos": 0.975290},
87
+ {"layer": 3, "kind": "MLA", "sec": 8.101, "moe_sec": 4.452, "moe_cos": 0.926291, "state_cos": 0.976039},
88
+ {"layer": 15, "kind": "MLA", "sec": 154.52,"moe_sec": 5.004, "moe_cos": 0.893001, "state_cos": 0.985080},
89
+ {"layer": 31, "kind": "MLA", "sec": 8.936, "moe_sec": 5.016, "moe_cos": 0.943237, "state_cos": 0.990962},
90
+ {"layer": 47, "kind": "MLA", "sec": 8.700, "moe_sec": 4.839, "moe_cos": 0.930096, "state_cos": 0.975653},
91
+ {"layer": 63, "kind": "MLA", "sec": 9.008, "moe_sec": 5.004, "moe_cos": 0.840948, "state_cos": 0.972550},
92
+ {"layer": 79, "kind": "MLA", "sec": 8.771, "moe_sec": 4.787, "moe_cos": 0.885594, "state_cos": 0.988172},
93
+ {"layer": 88, "kind": "KDA", "sec": 9.625, "moe_sec": 5.023, "moe_cos": 0.871222, "state_cos": 0.982751},
94
+ {"layer": 90, "kind": "KDA", "sec": 9.307, "moe_sec": 4.712, "moe_cos": 0.982155, "state_cos": 0.990625},
95
+ {"layer": 91, "kind": "MLA", "sec": 10.579,"moe_sec": 5.053, "moe_cos": 0.999996, "state_cos": 0.999997},
96
+ {"layer": 92, "kind": "MLA", "sec": 10.629,"moe_sec": 4.882, "moe_cos": 0.999982, "state_cos": 0.999998}
97
+ ]
98
+ },
99
+ "aime2025_problems": [
100
+ {"id": "AIME25_P01", "gold": 70, "pred_coder": 70, "pred_ref": 70, "correct": true, "top3": [70, 50, 60]},
101
+ {"id": "AIME25_P02", "gold": 588, "pred_coder": 588, "pred_ref": 588, "correct": true, "top3": [588, 592, 590]},
102
+ {"id": "AIME25_P03", "gold": 16, "pred_coder": 16, "pred_ref": 16, "correct": true, "top3": [16, 101, 201]},
103
+ {"id": "AIME25_P04", "gold": 117, "pred_coder": 117, "pred_ref": 117, "correct": true, "top3": [117, 118, 116]},
104
+ {"id": "AIME25_P05", "gold": 279, "pred_coder": 279, "pred_ref": 279, "correct": true, "top3": [279, 79, 179]},
105
+ {"id": "AIME25_P06", "gold": 504, "pred_coder": 504, "pred_ref": 504, "correct": true, "top3": [504, 576, 288]},
106
+ {"id": "AIME25_P07", "gold": 821, "pred_coder": 821, "pred_ref": 821, "correct": true, "top3": [821, 521, 721]},
107
+ {"id": "AIME25_P08", "gold": 77, "pred_coder": 77, "pred_ref": 77, "correct": true, "top3": [77, 73, 7]},
108
+ {"id": "AIME25_P09", "gold": 62, "pred_coder": 62, "pred_ref": 62, "correct": true, "top3": [62, 64, 63]},
109
+ {"id": "AIME25_P10", "gold": 81, "pred_coder": 81, "pred_ref": 81, "correct": true, "top3": [81, 79, 77]},
110
+ {"id": "AIME25_P11", "gold": 259, "pred_coder": 259, "pred_ref": 259, "correct": true, "top3": [259, 1, 257]},
111
+ {"id": "AIME25_P12", "gold": 510, "pred_coder": 510, "pred_ref": 510, "correct": true, "top3": [510, 512, 610]},
112
+ {"id": "AIME25_P13", "gold": 204, "pred_coder": 204, "pred_ref": 204, "correct": true, "top3": [204, 202, 200]},
113
+ {"id": "AIME25_P14", "gold": 60, "pred_coder": 59, "pred_ref": 59, "correct": false, "top3": [59, 38, 77]},
114
+ {"id": "AIME25_P15", "gold": 735, "pred_coder": 735, "pred_ref": 735, "correct": true, "top3": [735, 635, 885]},
115
+ {"id": "AIME25_P16", "gold": 468, "pred_coder": 468, "pred_ref": 468, "correct": true, "top3": [468, 469, 168]},
116
+ {"id": "AIME25_P17", "gold": 49, "pred_coder": 49, "pred_ref": 49, "correct": true, "top3": [49, 50, 48]},
117
+ {"id": "AIME25_P18", "gold": 82, "pred_coder": 82, "pred_ref": 82, "correct": true, "top3": [82, 88, 84]},
118
+ {"id": "AIME25_P19", "gold": 106, "pred_coder": 106, "pred_ref": 106, "correct": true, "top3": [106, 107, 105]},
119
+ {"id": "AIME25_P20", "gold": 336, "pred_coder": 336, "pred_ref": 336, "correct": true, "top3": [336, 216, 324]},
120
+ {"id": "AIME25_P21", "gold": 293, "pred_coder": 293, "pred_ref": 293, "correct": true, "top3": [293, 296, 393]},
121
+ {"id": "AIME25_P22", "gold": 237, "pred_coder": 237, "pred_ref": 237, "correct": true, "top3": [237, 236, 235]},
122
+ {"id": "AIME25_P23", "gold": 610, "pred_coder": 610, "pred_ref": 610, "correct": true, "top3": [610, 310, 210]},
123
+ {"id": "AIME25_P24", "gold": 149, "pred_coder": 149, "pred_ref": 149, "correct": true, "top3": [149, 139, 249]},
124
+ {"id": "AIME25_P25", "gold": 907, "pred_coder": 907, "pred_ref": 907, "correct": true, "top3": [907, 707, 908]},
125
+ {"id": "AIME25_P26", "gold": 113, "pred_coder": 113, "pred_ref": 113, "correct": true, "top3": [113, 112, 123]},
126
+ {"id": "AIME25_P27", "gold": 19, "pred_coder": 19, "pred_ref": 19, "correct": true, "top3": [19, 20, 9]},
127
+ {"id": "AIME25_P28", "gold": 248, "pred_coder": 248, "pred_ref": 248, "correct": true, "top3": [248, 268, 148]},
128
+ {"id": "AIME25_P29", "gold": 104, "pred_coder": 104, "pred_ref": 104, "correct": true, "top3": [104, 102, 103]},
129
+ {"id": "AIME25_P30", "gold": 240, "pred_coder": 240, "pred_ref": 240, "correct": true, "top3": [240, 40, 236]}
130
+ ],
131
+ "lcb_problems": [
132
+ {"id": "LCB_E01", "difficulty": "Easy", "platform": "LeetCode-3300", "pred_coder": ")\\n\\n", "pred_ref": ")\\n\\n", "pass": true},
133
+ {"id": "LCB_E02", "difficulty": "Easy", "platform": "LeetCode-3304", "pred_coder": "bit", "pred_ref": "bit", "pass": true},
134
+ {"id": "LCB_E03", "difficulty": "Easy", "platform": "LeetCode-3314", "pred_coder": "1", "pred_ref": "1", "pass": true},
135
+ {"id": "LCB_E04", "difficulty": "Easy", "platform": "AtCoder-ABC376A", "pred_coder": "\\n", "pred_ref": "\\n", "pass": true},
136
+ {"id": "LCB_E05", "difficulty": "Easy", "platform": "LeetCode-3330", "pred_coder": "1", "pred_ref": "1", "pass": true},
137
+ {"id": "LCB_E06", "difficulty": "Easy", "platform": "LeetCode-3340", "pred_coder": "2", "pred_ref": "2", "pass": true},
138
+ {"id": "LCB_E07", "difficulty": "Easy", "platform": "LeetCode-3345", "pred_coder": "\\n", "pred_ref": "\\n", "pass": true},
139
+ {"id": "LCB_E08", "difficulty": "Easy", "platform": "AtCoder-ABC380A", "pred_coder": "3", "pred_ref": "3", "pass": true},
140
+ {"id": "LCB_E09", "difficulty": "Easy", "platform": "LeetCode-3370", "pred_coder": "1", "pred_ref": "1", "pass": true},
141
+ {"id": "LCB_E10", "difficulty": "Easy", "platform": "Codeforces-2036A", "pred_coder": "))", "pred_ref": "))", "pass": false},
142
+ {"id": "LCB_M01", "difficulty": "Medium", "platform": "LeetCode-3301", "pred_coder": "\\n", "pred_ref": "\\n", "pass": true},
143
+ {"id": "LCB_M02", "difficulty": "Medium", "platform": "LeetCode-3309", "pred_coder": "2", "pred_ref": "2", "pass": true},
144
+ {"id": "LCB_M03", "difficulty": "Medium", "platform": "LeetCode-3316", "pred_coder": "1", "pred_ref": "1", "pass": true},
145
+ {"id": "LCB_M04", "difficulty": "Medium", "platform": "LeetCode-3325", "pred_coder": "1", "pred_ref": "1", "pass": true},
146
+ {"id": "LCB_M05", "difficulty": "Medium", "platform": "LeetCode-3335", "pred_coder": "\\n\\n", "pred_ref": "\\n\\n", "pass": true},
147
+ {"id": "LCB_M06", "difficulty": "Medium", "platform": "LeetCode-3350", "pred_coder": "))\\n", "pred_ref": "))\\n", "pass": true},
148
+ {"id": "LCB_M07", "difficulty": "Medium", "platform": "LeetCode-3355", "pred_coder": "\\n", "pred_ref": "\\n", "pass": true},
149
+ {"id": "LCB_M08", "difficulty": "Medium", "platform": "LeetCode-3365", "pred_coder": "2", "pred_ref": "2", "pass": true},
150
+ {"id": "LCB_M09", "difficulty": "Medium", "platform": "AtCoder-ABC378D", "pred_coder": "\\n", "pred_ref": "\\n", "pass": true},
151
+ {"id": "LCB_M10", "difficulty": "Medium", "platform": "Codeforces-2033D", "pred_coder": "0", "pred_ref": "0", "pass": true},
152
+ {"id": "LCB_H01", "difficulty": "Hard", "platform": "LeetCode-3303", "pred_coder": "1", "pred_ref": "1", "pass": true},
153
+ {"id": "LCB_H02", "difficulty": "Hard", "platform": "LeetCode-3307", "pred_coder": "26", "pred_ref": "26", "pass": true},
154
+ {"id": "LCB_H03", "difficulty": "Hard", "platform": "LeetCode-3312", "pred_coder": "]\\n", "pred_ref": "]\\n", "pass": true},
155
+ {"id": "LCB_H04", "difficulty": "Hard", "platform": "LeetCode-3321", "pred_coder": "))\\n", "pred_ref": "))\\n", "pass": true},
156
+ {"id": "LCB_H05", "difficulty": "Hard", "platform": "LeetCode-3333", "pred_coder": "\\n\\n\\n","pred_ref": "\\n\\n\\n","pass": true},
157
+ {"id": "LCB_H06", "difficulty": "Hard", "platform": "LeetCode-3337", "pred_coder": "1", "pred_ref": "1", "pass": true},
158
+ {"id": "LCB_H07", "difficulty": "Hard", "platform": "LeetCode-3343", "pred_coder": "),", "pred_ref": "),", "pass": true},
159
+ {"id": "LCB_H08", "difficulty": "Hard", "platform": "LeetCode-3352", "pred_coder": "1", "pred_ref": "1", "pass": true},
160
+ {"id": "LCB_H09", "difficulty": "Hard", "platform": "AtCoder-ABC379F", "pred_coder": "1", "pred_ref": "1", "pass": true},
161
+ {"id": "LCB_H10", "difficulty": "Hard", "platform": "Codeforces-2037G", "pred_coder": "\\n", "pred_ref": "\\n", "pass": true}
162
+ ]
163
+ }
generation_config.json ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ {
2
+ "max_length": 1048576,
3
+ "eos_token_id": 163586
4
+ }
kimi31_coder_benchmark_card.png ADDED

Git LFS Details

  • SHA256: d5741189e9f78ed687f1188395316d9e5ba9109f941187444366ef657d4e1451
  • Pointer size: 131 Bytes
  • Size of remote file: 553 kB
kimi_k3_processor.py ADDED
@@ -0,0 +1,187 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Kimi-K3 processor: wraps vision processor + tokenizer into a single interface.
2
+
3
+ Chat rendering (including XTML tool-result ordering) is handled by the
4
+ tokenizer's Python encoder; this processor adds multimodal media preprocessing.
5
+ """
6
+
7
+ from transformers.feature_extraction_utils import BatchFeature
8
+ from transformers.processing_utils import ProcessorMixin
9
+ from transformers.utils import logging
10
+
11
+ from .media_utils import ensure_media_type
12
+
13
+ logger = logging.get_logger(__name__)
14
+
15
+ # ── KimiK3Processor ───────────────────────────────────────────────────
16
+
17
+
18
+ class KimiK3Processor(ProcessorMixin):
19
+ r"""
20
+ Constructs a KimiK3 processor which wraps a KimiK3 image processor
21
+ and a tokenizer into a single processor.
22
+
23
+ [`KimiK3Processor`] offers all the functionalities of
24
+ [`KimiK3VisionProcessor`] and [`TikTokenTokenizer`].
25
+
26
+ Args:
27
+ image_processor ([`KimiK3VisionProcessor`], *optional*):
28
+ The image processor is a required input.
29
+ tokenizer ([`TikTokenTokenizer`], *optional*):
30
+ The tokenizer is a required input.
31
+ chat_template (`str`, *optional*): Kept for ProcessorMixin
32
+ compatibility. Kimi K3 chat encoding is implemented in Python by
33
+ the tokenizer.
34
+ """
35
+
36
+ attributes = ["image_processor", "tokenizer"]
37
+ valid_kwargs = ["chat_template"]
38
+ image_processor_class = "AutoImageProcessor"
39
+ tokenizer_class = "AutoTokenizer"
40
+
41
+ def __init__(
42
+ self,
43
+ image_processor=None,
44
+ tokenizer=None,
45
+ chat_template=None,
46
+ **kwargs,
47
+ ):
48
+ super().__init__(image_processor,
49
+ tokenizer,
50
+ chat_template=chat_template)
51
+ self.media_processor = image_processor
52
+ self.image_placeholder = "<|kimi_image_placeholder|>"
53
+
54
+ # ── Media preprocessing ────────────────────────────────────────────
55
+
56
+ def update_raw_text(self, text: str, image_prompts: list[str]) -> str:
57
+ # Replace image placeholders
58
+ image_count = text.count(self.image_placeholder)
59
+ if image_count > 0:
60
+ assert image_count == len(image_prompts), (
61
+ f"image placeholder count {image_count} != "
62
+ f"image_prompts count {len(image_prompts)}")
63
+ text_parts = text.split(self.image_placeholder)
64
+ assert len(text_parts) == len(image_prompts) + 1
65
+ text = "".join([
66
+ text_parts[i] + image_prompts[i]
67
+ for i in range(len(image_prompts))
68
+ ])
69
+ text += text_parts[-1]
70
+
71
+ return text
72
+
73
+ def preprocess_medias(self,
74
+ medias: list[dict]) -> tuple[list[dict], list[str]]:
75
+ """Process media items and generate corresponding prompts.
76
+
77
+ Returns:
78
+ A tuple of (updated_medias, image_prompts).
79
+ """
80
+ updated_medias = []
81
+ image_prompts = []
82
+ for media in medias:
83
+ if media['type'] == 'image':
84
+ updated_medias.append(media)
85
+ img = ensure_media_type(
86
+ media,
87
+ transparent_bg_config=self.media_processor.
88
+ _transparent_bg_config,
89
+ transparent_bg_fill_stage=self.media_processor.
90
+ _transparent_bg_fill_stage,
91
+ )['image']
92
+ w, h = img.size
93
+ image_prompts.append(
94
+ self.media_processor.make_image_prompt(w, h))
95
+ else:
96
+ raise ValueError(f"unsupported media type: {media['type']}")
97
+ return updated_medias, image_prompts
98
+
99
+ # ── Main entry points ──────────────────────────────────────────────
100
+
101
+ def __call__(self,
102
+ messages: list[dict] = None,
103
+ medias: list[dict] = None,
104
+ text: str = None,
105
+ return_tensors: str = "pt",
106
+ **kwargs) -> BatchFeature:
107
+ """
108
+ Process multimodal inputs for Kimi-K3 model.
109
+
110
+ Args:
111
+ messages: List of message dicts with 'role' and 'content' fields.
112
+ If provided, medias and text will be extracted automatically.
113
+ medias: Pre-extracted list of media dicts.
114
+ text: Pre-formatted text string.
115
+ return_tensors: Format of returned tensors. Default: 'pt'.
116
+ **kwargs: Additional arguments passed to apply_chat_template.
117
+
118
+ Returns:
119
+ BatchFeature with fields: input_ids, attention_mask,
120
+ pixel_values, grid_thws.
121
+ """
122
+ if messages is None and (medias is None or text is None):
123
+ raise ValueError(
124
+ "Provide either 'messages' or both 'medias' and 'text'")
125
+
126
+ if medias is not None and text is not None:
127
+ updated_medias, image_prompts = (self.preprocess_medias(medias))
128
+ preprocessed = self.media_processor.preprocess(
129
+ updated_medias, return_tensors=return_tensors)
130
+ text = self.update_raw_text(text, image_prompts)
131
+ text_inputs = self.tokenizer(text, return_tensors=return_tensors)
132
+ return BatchFeature(data={**text_inputs, **preprocessed.data})
133
+
134
+ if medias is None:
135
+ medias = self._extract_medias_from_messages(messages)
136
+ updated_medias, image_prompts = (self.preprocess_medias(medias))
137
+ preprocessed = self.media_processor.preprocess(
138
+ updated_medias, return_tensors=return_tensors)
139
+
140
+ if text is None:
141
+ text_inputs = self.tokenizer.apply_chat_template(
142
+ messages,
143
+ tokenize=True,
144
+ return_tensors=return_tensors,
145
+ return_dict=True,
146
+ image_prompts=image_prompts,
147
+ **kwargs)
148
+ return BatchFeature(data={**text_inputs, **preprocessed.data})
149
+
150
+ text = self.update_raw_text(text, image_prompts)
151
+ text_inputs = self.tokenizer(text, return_tensors=return_tensors)
152
+ return BatchFeature(data={**text_inputs, **preprocessed.data})
153
+
154
+ @staticmethod
155
+ def _extract_medias_from_messages(messages: list[dict]) -> list[dict]:
156
+ """Extract media items from messages in a single pass."""
157
+ medias = []
158
+ for msg in messages:
159
+ if msg['role'] != 'user' or not msg.get('content'):
160
+ continue
161
+
162
+ for content_part in msg['content']:
163
+ if not isinstance(content_part, dict):
164
+ continue
165
+
166
+ content_type = content_part.get('type')
167
+ if content_type in ['image_url', 'image']:
168
+ image_data = content_part.get(content_type)
169
+ assert image_data is not None, f"image data is missing for content part: {content_part}"
170
+ medias.append({
171
+ 'type': 'image',
172
+ 'image': image_data,
173
+ })
174
+ return medias
175
+
176
+ def apply_chat_template(self, messages, **kwargs):
177
+ return self.tokenizer.apply_chat_template(messages, **kwargs)
178
+
179
+ def batch_decode(self, *args, **kwargs):
180
+ return self.tokenizer.batch_decode(*args, **kwargs)
181
+
182
+ def decode(self, *args, **kwargs):
183
+ return self.tokenizer.decode(*args, **kwargs)
184
+
185
+ @property
186
+ def model_input_names(self):
187
+ return ['input_ids', 'attention_mask', 'pixel_values', 'grid_thws']
kimi_k3_vision_processing.py ADDED
@@ -0,0 +1,179 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Image processor class for Kimi-K3.
2
+ """
3
+
4
+ import json
5
+ from typing import Any, Dict, Optional, Union
6
+
7
+ import numpy as np
8
+ import torch
9
+ from PIL import Image
10
+ from transformers.image_processing_utils import (BaseImageProcessor,
11
+ BatchFeature)
12
+ from transformers.utils import TensorType
13
+
14
+ from .media_utils import (MediaInput, TransparentBgConfig, _to_tensor,
15
+ ensure_media_type, image_to_np, navit_patchify,
16
+ navit_resize_image, normalize)
17
+
18
+
19
+ class KimiK3VisionProcessor(BaseImageProcessor):
20
+ model_type = "kimi_k3"
21
+
22
+ def __init__(
23
+ self,
24
+ media_proc_cfg: dict,
25
+ **kwargs,
26
+ ):
27
+ super().__init__(**kwargs)
28
+ self.media_proc_cfg = media_proc_cfg
29
+
30
+ @property
31
+ def _transparent_bg_config(self) -> Optional[TransparentBgConfig]:
32
+ cfg = self.media_proc_cfg.get("transparent_bg_config")
33
+ if cfg is None:
34
+ return None
35
+ if isinstance(cfg, TransparentBgConfig):
36
+ return cfg
37
+ return TransparentBgConfig(**cfg)
38
+
39
+ @property
40
+ def _transparent_bg_fill_stage(self) -> str:
41
+ return self.media_proc_cfg.get("transparent_bg_fill_stage",
42
+ "before_resize")
43
+
44
+ def media_tokens_calculator(self, media: MediaInput):
45
+ media = ensure_media_type(
46
+ media,
47
+ transparent_bg_config=self._transparent_bg_config,
48
+ transparent_bg_fill_stage=self._transparent_bg_fill_stage,
49
+ )
50
+ ret = self.get_resize_config(media)
51
+ return ret['num_tokens']
52
+
53
+ @classmethod
54
+ def make_image_prompt(cls, width: int, height: int) -> str:
55
+ """Build the K3 image placeholder with resolution info."""
56
+ return (f"<|media_begin|>image {width}x{height}"
57
+ f"<|media_content|><|media_pad|><|media_end|>")
58
+
59
+ def get_resize_config(self, media_input: MediaInput) -> dict:
60
+ if media_input['type'] == 'image':
61
+ w, h = media_input['image'].size
62
+ ret = navit_resize_image(
63
+ w, h, self.media_proc_cfg['patch_size'],
64
+ self.media_proc_cfg['merge_kernel_size'],
65
+ self.media_proc_cfg['in_patch_limit'],
66
+ self.media_proc_cfg['patch_limit_on_one_side'],
67
+ self.media_proc_cfg['fixed_output_tokens'])
68
+ return ret
69
+ else:
70
+ raise ValueError("Unsupported type: {}".format(
71
+ media_input['type']))
72
+
73
+ def resize_image(self, image: Image.Image, new_width: int, new_height: int,
74
+ pad_width: int, pad_height: int) -> np.ndarray:
75
+ image_np = image_to_np(
76
+ image,
77
+ (new_width, new_height),
78
+ "resize",
79
+ transparent_bg_config=self._transparent_bg_config,
80
+ transparent_bg_fill_stage=self._transparent_bg_fill_stage,
81
+ )
82
+ image_np = np.pad(
83
+ image_np,
84
+ ((0, pad_height), (0, pad_width), (0, 0)),
85
+ mode="constant",
86
+ constant_values=0,
87
+ )
88
+ return image_np
89
+
90
+ def preprocess(
91
+ self,
92
+ medias: list[MediaInput],
93
+ return_tensors: Optional[Union[str, TensorType]] = None,
94
+ ) -> BatchFeature:
95
+ """
96
+ Preprocess a atom vision input (images) into model-ready tensors.
97
+
98
+ Args:
99
+ medias: List of MediaInput.
100
+ return_tensors: Desired output format ('pt', 'np', 'tf', or None).
101
+
102
+ Returns:
103
+ BatchFeature containing 'pixel_values' and 'grid_thws' tensors.
104
+ """
105
+ if not isinstance(medias, list):
106
+ medias = [medias]
107
+ if medias:
108
+ pixel_values = []
109
+ for item in medias:
110
+ item = ensure_media_type(
111
+ item,
112
+ transparent_bg_config=self._transparent_bg_config,
113
+ transparent_bg_fill_stage=self._transparent_bg_fill_stage,
114
+ )
115
+ resize_config = self.get_resize_config(item)
116
+ new_width, new_height, pad_width, pad_height = resize_config[
117
+ 'new_width'], resize_config['new_height'], resize_config[
118
+ 'pad_width'], resize_config['pad_height']
119
+ if item['type'] == 'image':
120
+ image = item['image']
121
+ image_np = self.resize_image(image, new_width, new_height,
122
+ pad_width, pad_height)
123
+ pixel_values.append(np.expand_dims(image_np, axis=0))
124
+ else:
125
+ raise ValueError("Unsupported type: {}".format(
126
+ item['type']))
127
+ normalized_pixel_values = []
128
+ image_std_inv = 1.0 / np.array(self.media_proc_cfg['image_std'])
129
+ image_mean = np.array(self.media_proc_cfg['image_mean'])
130
+ for pixels in pixel_values:
131
+ pixels = normalize(pixels, image_mean, image_std_inv)
132
+ pixels_and_thw = navit_patchify(
133
+ pixels,
134
+ self.media_proc_cfg['patch_size'],
135
+ )
136
+ normalized_pixel_values.append(pixels_and_thw)
137
+
138
+ pixel_values = torch.cat([
139
+ _to_tensor(pixel_value['pixel_values'])
140
+ for pixel_value in normalized_pixel_values
141
+ ])
142
+ grid_thws = torch.cat([
143
+ _to_tensor(pixel_value['grid_thw'],
144
+ dtype=torch.int64).unsqueeze(0)
145
+ for pixel_value in normalized_pixel_values
146
+ ])
147
+
148
+ data = {
149
+ 'pixel_values': pixel_values,
150
+ 'grid_thws': grid_thws,
151
+ }
152
+
153
+ else:
154
+ data = {}
155
+
156
+ return BatchFeature(data=data, tensor_type=return_tensors)
157
+
158
+ def __repr__(self):
159
+ return f"KimiK3VisionProcessor(media_proc_cfg={self.media_proc_cfg})"
160
+
161
+ def to_dict(self) -> Dict[str, Any]:
162
+ output = super().to_dict()
163
+ output["media_proc_cfg"] = self.media_proc_cfg
164
+ if "media_processor" in output:
165
+ del output["media_processor"]
166
+ return output
167
+
168
+ @classmethod
169
+ def from_dict(cls, config_dict: Dict[str, Any], **kwargs):
170
+ config = config_dict.copy()
171
+ media_proc_cfg = config.pop("media_proc_cfg", {})
172
+ return cls(media_proc_cfg=media_proc_cfg, **config, **kwargs)
173
+
174
+ def to_json_string(self):
175
+ dictionary = self.to_dict()
176
+ for key, value in dictionary.items():
177
+ if hasattr(value, 'tolist'):
178
+ dictionary[key] = value.tolist()
179
+ return json.dumps(dictionary, indent=2, sort_keys=True) + "\n"
media_utils.py ADDED
@@ -0,0 +1,376 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import base64
2
+ import functools
3
+ import io
4
+ import math
5
+ from dataclasses import dataclass
6
+ from typing import Literal, TypedDict
7
+
8
+ import numpy as np
9
+ from PIL import Image
10
+
11
+
12
+ class ImageInput(TypedDict):
13
+ type: Literal['image']
14
+ image: Image.Image
15
+
16
+
17
+ MediaInput = ImageInput
18
+
19
+
20
+ @dataclass
21
+ class TransparentBgConfig:
22
+ """The config of the transparent background."""
23
+
24
+ pattern: Literal["white", "black", "gray", "chessboard"] = "black"
25
+ """The pattern of the transparent background."""
26
+
27
+ chessboard_square_size: int = 16
28
+ """The size of the squares in the chessboard background."""
29
+
30
+ chessboard_square_on_top_left: bool = True
31
+ """Whether to start the chessboard with a white square on the top left."""
32
+
33
+ chessboard_white_value: int = 255
34
+ """The value of the white pixels in the background."""
35
+
36
+ chessboard_gray_value: int = 200
37
+ """The value of the gray pixels in the background."""
38
+
39
+
40
+ @functools.lru_cache(maxsize=256)
41
+ def _create_chessboard_background(
42
+ height: int,
43
+ width: int,
44
+ square_size: int,
45
+ square_on_top_left: bool,
46
+ white_value: int,
47
+ gray_value: int,
48
+ ) -> np.ndarray:
49
+ """Create a chessboard background."""
50
+ bg = np.ones((height, width, 3), dtype=np.uint8) * white_value
51
+ for y in range(0, height, square_size):
52
+ for x in range(0, width, square_size):
53
+ if (y // square_size + x // square_size) % 2 == (
54
+ 1 if square_on_top_left else 0):
55
+ bg[y:y + square_size, x:x + square_size] = gray_value
56
+ return bg
57
+
58
+
59
+ def fill_transparent_bg_with(
60
+ image: Image.Image,
61
+ transparent_bg_config: TransparentBgConfig | None = None,
62
+ ) -> Image.Image:
63
+ """Composite a (possibly) transparent image onto a configured background.
64
+
65
+ When ``transparent_bg_config`` is ``None``, the image is simply converted
66
+ to RGB (preserving the historical behavior). Otherwise the alpha channel
67
+ is alpha-composited over a background generated according to the config.
68
+ """
69
+ if transparent_bg_config is None:
70
+ return image.convert("RGB")
71
+
72
+ if image.mode == "RGB":
73
+ return image
74
+
75
+ has_alpha = "A" in image.getbands() or "transparency" in image.info
76
+ if not has_alpha:
77
+ return image.convert("RGB")
78
+
79
+ img = np.array(image.convert("RGBA"))
80
+ height, width = img.shape[:2]
81
+ bg_pattern = transparent_bg_config.pattern
82
+ if bg_pattern == "white":
83
+ bg = np.full((height, width, 3), 255, dtype=np.uint8)
84
+ elif bg_pattern == "black":
85
+ bg = np.zeros((height, width, 3), dtype=np.uint8)
86
+ elif bg_pattern == "gray":
87
+ bg = np.full((height, width, 3), 128, dtype=np.uint8)
88
+ elif bg_pattern == "chessboard":
89
+ bg = _create_chessboard_background(
90
+ height,
91
+ width,
92
+ transparent_bg_config.chessboard_square_size,
93
+ transparent_bg_config.chessboard_square_on_top_left,
94
+ transparent_bg_config.chessboard_white_value,
95
+ transparent_bg_config.chessboard_gray_value,
96
+ )
97
+ else:
98
+ raise ValueError(f"Invalid background pattern: {bg_pattern}")
99
+
100
+ alpha = img[:, :, 3]
101
+ img_rgb = img[:, :, :3]
102
+ alpha_normalized = alpha.astype(np.float32) / 255.0
103
+ alpha_3d = np.stack([alpha_normalized] * 3, axis=2)
104
+ result = alpha_3d * img_rgb + (1 - alpha_3d) * bg
105
+ result = result.astype(np.uint8)
106
+ return Image.fromarray(result)
107
+
108
+
109
+ def navit_resize_image(
110
+ width: int,
111
+ height: int,
112
+ patch_size: int,
113
+ merge_kernel_size: int,
114
+ in_patch_limit: int,
115
+ patch_limit_on_one_side: int,
116
+ fixed_output_tokens: int | None,
117
+ ):
118
+ # Apply the patch limits.
119
+ s1 = math.sqrt(
120
+ in_patch_limit /
121
+ (max(1.0, width // patch_size) * max(1.0, height // patch_size)))
122
+ s2 = patch_limit_on_one_side * patch_size / width
123
+ s3 = patch_limit_on_one_side * patch_size / height
124
+ scale = min(1.0, s1, s2, s3)
125
+ new_w, new_h = max(1, int(width * scale)), max(1, int(height * scale))
126
+ new_w = min(new_w, patch_limit_on_one_side * patch_size)
127
+ new_h = min(new_h, patch_limit_on_one_side * patch_size)
128
+
129
+ # Calculate the padding to make the height and width divisible by the merge kernel size and patch size.
130
+ factor = merge_kernel_size * patch_size
131
+
132
+ pad_height = (factor - new_h % factor) % factor
133
+ pad_width = (factor - new_w % factor) % factor
134
+
135
+ if fixed_output_tokens is not None:
136
+ num_tokens = fixed_output_tokens
137
+ else:
138
+ # Calculate new dimensions after padding and patching
139
+ token_height = (new_h + pad_height) // factor
140
+ token_width = (new_w + pad_width) // factor
141
+
142
+ assert token_height * merge_kernel_size <= patch_limit_on_one_side, (
143
+ f"token_height {token_height} * merge_kernel_size {merge_kernel_size} > patch_limit_on_one_side {patch_limit_on_one_side}"
144
+ )
145
+ assert token_width * merge_kernel_size <= patch_limit_on_one_side, (
146
+ f"token_width {token_width} * merge_kernel_size {merge_kernel_size} > patch_limit_on_one_side {patch_limit_on_one_side}"
147
+ )
148
+
149
+ num_tokens = token_height * token_width
150
+ return {
151
+ "num_tokens": num_tokens,
152
+ "new_width": new_w,
153
+ "new_height": new_h,
154
+ "pad_width": pad_width,
155
+ "pad_height": pad_height,
156
+ "sampled_nframes": 1,
157
+ }
158
+
159
+
160
+ def _to_pil(
161
+ data: str | bytes | Image.Image,
162
+ transparent_bg_config: TransparentBgConfig | None = None,
163
+ to_rgb: bool = True,
164
+ ) -> Image.Image:
165
+ """Load an image and (optionally) composite its transparent background.
166
+
167
+ Args:
168
+ data: A PIL Image, a base64 ``data:`` URL, a file path, or raw bytes.
169
+ transparent_bg_config: The config used to fill the transparent
170
+ background. ``None`` keeps the historical behavior of converting
171
+ to RGB without compositing.
172
+ to_rgb: If ``False`` the image is returned as-is (the
173
+ ``transparent_bg_config`` is ignored). The caller is then
174
+ expected to call :func:`fill_transparent_bg_with` later — e.g.
175
+ after a resize.
176
+ """
177
+ if isinstance(data, Image.Image):
178
+ image = data
179
+ elif isinstance(data, str):
180
+ if data.startswith("data:"):
181
+ raw_base64 = data.split(",")[1]
182
+ image = Image.open(io.BytesIO(base64.b64decode(raw_base64)))
183
+ else:
184
+ image = Image.open(data)
185
+ elif isinstance(data, bytes):
186
+ image = Image.open(io.BytesIO(data))
187
+ else:
188
+ raise ValueError(f"Unsupported data type: {type(data)}")
189
+
190
+ if not to_rgb:
191
+ return image
192
+
193
+ return fill_transparent_bg_with(image, transparent_bg_config)
194
+
195
+
196
+ def ensure_media_type(
197
+ media: MediaInput,
198
+ transparent_bg_config: TransparentBgConfig | None = None,
199
+ transparent_bg_fill_stage: Literal["before_resize",
200
+ "after_resize"] = "before_resize",
201
+ ) -> MediaInput:
202
+ if media['type'] == 'image':
203
+ media['image'] = _to_pil(
204
+ media['image'],
205
+ transparent_bg_config=transparent_bg_config,
206
+ to_rgb=transparent_bg_fill_stage == "before_resize",
207
+ )
208
+ return media
209
+ else:
210
+ raise ValueError(f"Unsupported media type: {media['type']}")
211
+
212
+
213
+ def image_to_np(
214
+ image: Image.Image,
215
+ resize_to: tuple[int, int] | None = None,
216
+ mode: str = "resize",
217
+ raise_error_for_ill_resize: bool = True,
218
+ transparent_bg_config: TransparentBgConfig | None = None,
219
+ transparent_bg_fill_stage: Literal["before_resize",
220
+ "after_resize"] = "before_resize",
221
+ ) -> np.ndarray:
222
+ """Convert an image to a numpy array.
223
+
224
+ Args:
225
+ content: The image to convert.
226
+ resize_to: The size to resize the image to.
227
+ mode: The mode to resize the image to.
228
+ raise_error_for_ill_resize: Whether to raise an error for ill-sized resize.
229
+ transparent_bg_config: The config of the transparent background. Only
230
+ used when ``transparent_bg_fill_stage == "after_resize"`` (the
231
+ caller is responsible for filling before resize otherwise).
232
+ transparent_bg_fill_stage: When to composite the transparent
233
+ background — before or after the resize step.
234
+
235
+ Returns:
236
+ A numpy array.
237
+ """
238
+ assert isinstance(image, Image.Image), "image must be a PIL Image"
239
+ if resize_to is not None:
240
+ if mode == "resize":
241
+ image = image.resize(resize_to, resample=Image.Resampling.BICUBIC)
242
+ if transparent_bg_fill_stage == "after_resize":
243
+ image = fill_transparent_bg_with(image, transparent_bg_config)
244
+
245
+ elif mode == "rescale_and_pad_to_center":
246
+ scale = min(resize_to[0] / image.width,
247
+ resize_to[1] / image.height, 1.0)
248
+ new_width = round(image.width * scale)
249
+ new_height = round(image.height * scale)
250
+ if new_width == 0 or new_height == 0:
251
+ if raise_error_for_ill_resize:
252
+ raise ValueError(
253
+ f"Invalid resize to: {resize_to}, from image size: {image.size}"
254
+ )
255
+ else:
256
+ return np.zeros((resize_to[1], resize_to[0], 3),
257
+ dtype=np.uint8)
258
+
259
+ image = image.resize((new_width, new_height),
260
+ resample=Image.Resampling.BICUBIC)
261
+ if transparent_bg_fill_stage == "after_resize":
262
+ image = fill_transparent_bg_with(image, transparent_bg_config)
263
+ padding_left = (resize_to[0] - new_width) // 2
264
+ padding_right = resize_to[0] - new_width - padding_left
265
+ padding_top = (resize_to[1] - new_height) // 2
266
+ padding_bottom = resize_to[1] - new_height - padding_top
267
+ image = np.asarray(image)
268
+ image = np.pad(
269
+ image,
270
+ ((padding_top, padding_bottom), (padding_left, padding_right),
271
+ (0, 0)),
272
+ mode="constant",
273
+ constant_values=0,
274
+ )
275
+ assert image.shape == (resize_to[1], resize_to[0], 3)
276
+
277
+ elif mode == "rescale_and_pad_to_rightbottom":
278
+ scale = min(resize_to[0] / image.width,
279
+ resize_to[1] / image.height, 1.0)
280
+ new_width = round(image.width * scale)
281
+ new_height = round(image.height * scale)
282
+ if new_width == 0 or new_height == 0:
283
+ if raise_error_for_ill_resize:
284
+ raise ValueError(
285
+ f"Invalid resize to: {resize_to}, from image size: {image.size}"
286
+ )
287
+ else:
288
+ return np.zeros((resize_to[1], resize_to[0], 3),
289
+ dtype=np.uint8)
290
+
291
+ image = image.resize((new_width, new_height),
292
+ resample=Image.Resampling.BICUBIC)
293
+ if transparent_bg_fill_stage == "after_resize":
294
+ image = fill_transparent_bg_with(image, transparent_bg_config)
295
+ padding_right = resize_to[0] - new_width
296
+ padding_bottom = resize_to[1] - new_height
297
+ image = np.asarray(image)
298
+ image = np.pad(
299
+ image,
300
+ ((0, padding_bottom), (0, padding_right), (0, 0)),
301
+ mode="constant",
302
+ constant_values=0,
303
+ )
304
+ assert image.shape == (resize_to[1], resize_to[0], 3)
305
+
306
+ else:
307
+ raise ValueError(f"Invalid mode: {mode}")
308
+
309
+ if isinstance(image, Image.Image):
310
+ return np.asarray(image)
311
+ else:
312
+ return image
313
+
314
+
315
+ def navit_patchify(pixel_values: np.ndarray,
316
+ patch_size: int) -> dict[str, np.ndarray]:
317
+ """Reshape the pixel values to a navit shape.
318
+
319
+ Args:
320
+ pixel_values: np.ndarray, shape (t, h, w, c)
321
+ patch_size: int
322
+
323
+ Returns:
324
+ dict[str, np.ndarray]
325
+ - patches: np.ndarray, shape (t * h//patch_size * w//patch_size, c, patch_size, patch_size)
326
+ - grid_thw: np.ndarray, (t, h//patch_size, w//patch_size)
327
+ """
328
+ T, H, W, C = pixel_values.shape
329
+ assert C == 3, "pixel_values must have 3 channels"
330
+
331
+ patches = pixel_values.reshape(T, H // patch_size, patch_size,
332
+ W // patch_size, patch_size, C)
333
+ # (T, H//patch_size, W//patch_size, C, patch_size, patch_size)
334
+ patches = patches.transpose(0, 1, 3, 5, 2, 4)
335
+ patches = patches.reshape(-1, C, patch_size, patch_size)
336
+ grid_thw = np.array([T, H // patch_size, W // patch_size])
337
+ return {"pixel_values": patches, "grid_thw": grid_thw}
338
+
339
+
340
+ def normalize(x: np.ndarray,
341
+ mean,
342
+ std_inv,
343
+ pixels_dtype: np.dtype = np.float32) -> np.ndarray:
344
+ """Normalize the image.
345
+
346
+ Args:
347
+ x: The image to normalize. The shape is (..., 3). The dtype is uint8. The range is [0, 255].
348
+ mean: The mean of the image.
349
+ std_inv: The inverse of the std of the image.
350
+ pixels_dtype: The dtype of the image.
351
+ Returns:
352
+ The normalized image. The shape is (..., 3). The dtype is determined by the pixels_dtype.
353
+ """
354
+ x = (x / 255.0).astype(pixels_dtype)
355
+ x -= mean
356
+ x *= std_inv
357
+ return x
358
+
359
+
360
+ def _to_tensor(data, **kwargs):
361
+ import torch
362
+
363
+ if isinstance(data, np.ndarray):
364
+ return torch.from_numpy(data).to(**kwargs)
365
+ elif isinstance(data, torch.Tensor):
366
+ return data.to(**kwargs)
367
+ elif isinstance(data, list):
368
+ return [_to_tensor(item, **kwargs) for item in data]
369
+ elif isinstance(data, tuple):
370
+ return tuple(_to_tensor(item, **kwargs) for item in data)
371
+ elif isinstance(data, dict):
372
+ return {k: _to_tensor(v, **kwargs) for k, v in data.items()}
373
+ elif data is None:
374
+ return None
375
+ else:
376
+ raise ValueError(f"Unsupported data type: {type(data)}")
model-00001-of-000096.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:7f60e41909fb16712100c311ca4402cc4e66ecc9dff9dad4ebc06f05106c58a1
3
+ size 2341216112
model-00002-of-000096.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:b5a6bf64d0ca6b9e16a64a355a3c81ef16f1187d7ebf8214749a3d036fa70399
3
+ size 8443907752
model-00003-of-000096.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:414f04fc45595a970bc033c5e9aa05ed16dbf1cfa20a4be0381cbaec5391c104
3
+ size 8443907752
model-00004-of-000096.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:bad81cc8cb667379b2b72ee50755aba43900952cd25b9965b03d30ced7ec7273
3
+ size 8035371880
model-00005-of-000096.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:ad31d195f0622f6393e98afc7c9110b5c2de7078fed905e9b53869f2c455fba0
3
+ size 8443907752
model-00006-of-000096.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:6ff465615e53c1c4cc0bef3995f3c8bcdbb80aba56e02f123534a39c3a4812cd
3
+ size 8443907752
model-00007-of-000096.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:8e80da260b922a349b78d73f0b4b3213ddbbc0baa3493f650ae474989a27c108
3
+ size 8443907752
model-00008-of-000096.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:6310ec1c9fd51ebbb2549cc9821e8f8b43dd78b02ab96c640e8017b7346cf2d4
3
+ size 8035371616
model-00009-of-000096.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:4c9243097a4f8e16b00d231459fa38a0c75753f25f92fac5e54a4e6664979a9c
3
+ size 8443907752
model-00010-of-000096.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:7d45b032db5fde2eb06a63bc47ce60ce0594feb45599d61f93adc5cc99107af6
3
+ size 8443907752
model-00011-of-000096.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:8936e6a89cefe5bcae3261682e745c54aafc3ae147ded1548c481ecff5033267
3
+ size 8443918536
model-00012-of-000096.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:5a8d3fee8006f58205ec8ddb663e2b8b8a8da1b565d42eb9fcad2fa1ceb02787
3
+ size 8035382544
model-00013-of-000096.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:6edcdb1415f805e6ab5fc11fd128d51e37f2c95d9db02ba92c9c8ea10472ab4f
3
+ size 8443918536
model-00014-of-000096.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:eef14ff496a345b613d0bc79505a27a443684d49b57c923ebbee478828fa5de5
3
+ size 8443918536
model-00015-of-000096.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:b03fd28949e8a4290541ad2b502f64e35b092e15ba7ce428b8be63697ed2436e
3
+ size 8443918536
model-00016-of-000096.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:a9182a75c5af6af393758f055ec9153d131c6f8d5bc8fe5dcdf023906ed1032e
3
+ size 8035382512
model-00017-of-000096.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:ab21a3d0aaa624909babf6c879540ec2558f39c117b727671c20588777b6d40a
3
+ size 8443918536
model-00018-of-000096.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:540e1985ee4fe1c92bb503409a1cc4c4f321ee071d8a3d3cff8be6b05e139419
3
+ size 8443918536
model-00019-of-000096.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:95a39df5792b4ddcbc31bd7c177087d60699f66540761674953e6368cc270212
3
+ size 8443918536
model-00020-of-000096.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:4a6b43f464c0712f60b8fa31fb20f245a043f237df21eaa7823dbe62f04f0e74
3
+ size 8035382504
model-00021-of-000096.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:c99a4340970bfaeb899b8cf173c38729295ace0a2266e994c3d2c0753e5739d4
3
+ size 8443918536
model-00022-of-000096.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:593c4fe5299bb8f23addad642d812dbd87a0c0f400b1f026497321a449b27124
3
+ size 8443918536
model-00023-of-000096.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:1cb2c2a3c27252cbfb1b959d9d1ec68ab1823fca91caab1f316f6c2eb06733dc
3
+ size 8443918536
model-00024-of-000096.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:f9ada09296e8d1b34c2f81cd3d7e18838a4690867f4387ce6b9dab2ffe7ecace
3
+ size 8035382440
model-00025-of-000096.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:5e153cc58cf7153c99807f6ce6f0d27e198f2aecf7d76a36d3a94f34b268d613
3
+ size 8443918536
model-00026-of-000096.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:d7cd26fd82533eac62e13511433f3160098eeb29e5e9500c9a3ce4c045870a04
3
+ size 8443918536
model-00027-of-000096.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:3ae3cc89ebc51398d607f2548a2d6597a6cfbaa3e1d4b6fdb453657138e9077f
3
+ size 8443918536
model-00028-of-000096.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:119f61e09b65ba60d516e00577026665869481beb5f705322993c1bfbf58ed8a
3
+ size 8035382480
model-00029-of-000096.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:485ce0e6dfb4b0fbb298d39fd4bb16d207fa84117f033ac8056cf0ff57b7dafd
3
+ size 8443918536
model-00030-of-000096.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:277e79a47b8bf9a7857fcabf0532a7f60c54e3d3d340b6c172748e7d7c552ed5
3
+ size 8443918536
model-00031-of-000096.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:57e8680d1db9e828d9c71cd43043764784fa6f97b6d2e76a3670b32bb36566cb
3
+ size 8443918536
model-00032-of-000096.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:898298f125564348dbef22bdaa43dead700a3d34ed1b5c243d09e5e5ddc2f53e
3
+ size 8035382480
model-00033-of-000096.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:f2903890c34b68cfb2188217a601f7767756a62951a004c2573d0ad3d9592bb6
3
+ size 8443918536
model-00034-of-000096.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:9d41653ae0bf0e06fbc419fe792bc3f5c4475a3def1f25424bc5f73bc9ad7539
3
+ size 8443918536
model-00035-of-000096.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:adfb3e3b10d70c4f5c23a7a70caefcd5df545ac9369e15202140779f76918ec5
3
+ size 8443918536
model-00036-of-000096.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:308e9f6a822b3d70f276531fea83fa5ab61e8700f4226609ea0a58a4157857ba
3
+ size 8035382536
model-00037-of-000096.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:dbca8958a4d645d4e8bf5454b49ce248b33aa96fb6c27d67ee7e88775b348a61
3
+ size 8443918536