yujiepan commited on
Commit
6462954
·
verified ·
1 Parent(s): 9041431

Upload folder using huggingface_hub

Browse files
.meta.json ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "einops": "0.9.0.dev0",
3
+ "fla-core": "0.5.2",
4
+ "safetensors": "0.8.0",
5
+ "torch": "2.13.0+cu126",
6
+ "transformers": "5.15.0.dev0",
7
+ "triton": "3.7.1",
8
+ "vllm": "0.1.dev1+g5a3eba034.d20260730.cu126"
9
+ }
README.md ADDED
@@ -0,0 +1,872 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ library_name: transformers
3
+ base_model:
4
+ - moonshotai/Kimi-K3
5
+ ---
6
+
7
+ This tiny model is intended for debugging. It is randomly initialized using the configuration adapted from [moonshotai/Kimi-K3](https://huggingface.co/moonshotai/Kimi-K3).
8
+
9
+ Note:
10
+ - Structural comparison with the original `moonshotai/Kimi-K3`:
11
+
12
+ | Structural feature | Original K3 | This tiny model |
13
+ |---|---|---|
14
+ | Attention cycle | 3 KDA + 1 MLA per group | Same |
15
+ | Ending | Final layer is MLA | Same |
16
+ | KDA : MLA ratio | 69:24 (~3:1) | 12:5 (~3:1) |
17
+ | Total layers | 93 | 17 (4 groups + final MLA) |
18
+ | FFN layout | Layer 0 Dense MLP, others MoE | Same |
19
+ | MoE routing | top-16, 2 shared experts, group=1 | Same |
20
+ | Routed experts | 896 | 64 |
21
+ | AttnRes checkpoint | Every 12 layers (3 groups) | Every 8 layers (2 groups), proportionally scaled |
22
+ | KDA kernel params | num_heads=96, head_dim=128, conv=4, gate_lower_bound=-5 | heads 96 -> 8; per-head dims same |
23
+ | MLA kernel params | 96 heads, q/kv LoRA ranks 1536/512, nope/rope/v head dims | heads 96 -> 8, q rank 1536 -> 256, kv rank stays 512; kernel dims same |
24
+ | MoE quantization | Only routed expert w1/w2/w3 MXFP4, group_size=32 | Same |
25
+ | Vision head_dim | 32 | Same |
26
+ | MTP | None | None |
27
+
28
+ - What is shrunk: layer count (93 -> 17), residual width (hidden_size=8),
29
+ attention heads (96 -> 8) and MLA q LoRA rank (1536 -> 256). The
30
+ kv_lora_rank stays 512 because vLLM's Kimi fused MLA decode kernel requires
31
+ cache/query head size 512 + 64 = 576. Per-head dims are unchanged; routed experts (896 -> 64,
32
+ still >> top-k=16), expert/MLP intermediate sizes, and vision width/depth.
33
+
34
+ - The creation of this model was assisted by GPT-5.5 and Kimi-K3.
35
+
36
+ | File path | Size |
37
+ |------|------|
38
+ | model.safetensors | 133.5MB |
39
+
40
+
41
+ ### Example usage:
42
+
43
+ - vLLM
44
+
45
+ ```bash
46
+ # Tested on NVIDIA H20. If you run into issues, please open an issue.
47
+ VLLM_ENABLE_K3_LATENT_MOE_TAIL_FUSION=1 \
48
+ FLASHINFER_DISABLE_VERSION_CHECK=1 \
49
+ VLLM_ENABLE_CUDA_COMPATIBILITY=1 \
50
+ VLLM_CUDA_COMPATIBILITY_PATH=/usr/local/cuda/compat \
51
+ vllm serve tiny-random/kimi-k3 \
52
+ --trust-remote-code \
53
+ --tensor-parallel-size 2 \
54
+ --load-format safetensors \
55
+ --moe-backend marlin \
56
+ --gpu-memory-utilization 0.70 \
57
+ --compilation-config '{"cudagraph_mode":"FULL","cudagraph_capture_sizes":[1,2,4,8],"max_cudagraph_capture_size":8}'
58
+ ```
59
+
60
+ - SGLang
61
+
62
+ ```bash
63
+ # Not verified yet; this mirrors the vLLM-tested parallelism and backend choices.
64
+ python3 -m sglang.launch_server \
65
+ --model-path tiny-random/kimi-k3 \
66
+ --trust-remote-code \
67
+ --tp-size 2 \
68
+ --ep-size 2 \
69
+ --moe-runner-backend marlin \
70
+ --decode-attention-backend flashmla \
71
+ --mamba-full-memory-ratio 0.45 \
72
+ --mem-fraction-static 0.70 \
73
+ --cuda-graph-max-bs 8
74
+ ```
75
+
76
+ - Transformers
77
+
78
+ ```python
79
+ import numpy as np
80
+ import torch
81
+ from PIL import Image
82
+ from transformers import AutoModel, AutoProcessor
83
+
84
+ model_id = "tiny-random/kimi-k3"
85
+ processor = AutoProcessor.from_pretrained(model_id, trust_remote_code=True)
86
+ model = AutoModel.from_pretrained(
87
+ model_id,
88
+ dtype=torch.bfloat16,
89
+ device_map='cuda',
90
+ trust_remote_code=True,
91
+ attn_implementation='eager',
92
+ ).eval()
93
+ image = Image.fromarray(np.random.default_rng(42).integers(0, 256, (56, 56, 3), dtype=np.uint8))
94
+ tools = [{
95
+ 'type': 'function',
96
+ 'function': {
97
+ 'name': 'get_image_size',
98
+ 'description': 'Return the width and height of an image.',
99
+ 'parameters': {
100
+ 'type': 'object',
101
+ 'properties': {
102
+ 'image_index': {'type': 'integer', 'description': 'Zero-based image index.'},
103
+ },
104
+ 'required': ['image_index'],
105
+ },
106
+ },
107
+ }]
108
+ messages = [{
109
+ 'role': 'user',
110
+ 'content': [
111
+ {'type': 'image', 'image': image},
112
+ {'type': 'text', 'text': 'Use the available tool to get this image size.'},
113
+ ],
114
+ }]
115
+ inputs = processor(
116
+ messages=messages,
117
+ tools=tools,
118
+ tool_choice='required',
119
+ return_tensors='pt',
120
+ ).to(model.device)
121
+ with torch.no_grad():
122
+ outputs = model.generate(**inputs, max_new_tokens=32)
123
+ generated_ids = outputs.sequences if hasattr(outputs, 'sequences') else outputs
124
+ print(processor.decode(generated_ids[0].detach().cpu().tolist()))
125
+ ```
126
+
127
+ ### Codes to create this repo:
128
+
129
+ <details><summary>Click to expand</summary>
130
+
131
+ ```python
132
+ import json
133
+ from pathlib import Path
134
+
135
+ import accelerate
136
+ import torch
137
+ from huggingface_hub import file_exists, hf_hub_download, list_repo_files
138
+ from safetensors.torch import load_file, save_file
139
+ from transformers import AutoConfig, AutoModel, GenerationConfig, set_seed
140
+
141
+ source_model_id = "moonshotai/Kimi-K3"
142
+ save_folder = "/tmp/tiny-random/kimi-k3" # pyright: ignore[reportUnusedExpression] # codegen marker
143
+
144
+ Path(save_folder).mkdir(parents=True, exist_ok=True)
145
+ suffixes = ['.json', '.py', '.model', '.jinja']
146
+ for filename in list_repo_files(
147
+ source_model_id,
148
+ repo_type='model',
149
+ revision=source_revision,
150
+ ):
151
+ if any(filename.endswith(suffix) for suffix in suffixes) and not filename.endswith('.index.json'):
152
+ hf_hub_download(
153
+ repo_id=source_model_id,
154
+ filename=filename,
155
+ repo_type='model',
156
+ revision=source_revision,
157
+ local_dir=save_folder,
158
+ )
159
+
160
+ def replace_file(filepath, replacements):
161
+ with open(filepath, 'r', encoding='utf-8') as f:
162
+ code = f.read()
163
+ for old_string, new_string in replacements:
164
+ if old_string not in code:
165
+ if new_string in code:
166
+ continue
167
+ raise ValueError(f'Expected code was not found in {filepath}: {old_string}')
168
+ code = code.replace(old_string, new_string)
169
+ with open(filepath, 'w', encoding='utf-8') as f:
170
+ f.write(code)
171
+
172
+ # The upstream reference implementation forces FlashAttention for MLA even
173
+ # when eager attention is requested. Allow the tiny MLA layer to use eager
174
+ # attention without requiring the separate flash-attn package.
175
+ force_flash_code = ''' if getattr(config, "_attn_implementation", None) is not None:
176
+ if config._attn_implementation != "flash_attention_2":
177
+ logger.warning_once(
178
+ f"Ignoring the provided attention implementation {config._attn_implementation}")
179
+ logger.warning_once("Using flash_attention_2 backend instead.")
180
+ config._attn_implementation = "flash_attention_2"
181
+ else:
182
+ config._attn_implementation = "flash_attention_2"'''
183
+ per_channel_gate_code = ''' g = self.f_b_proj(self.f_a_proj(hidden_states))
184
+ g = rearrange(g, '... (h d) -> ... h d', d=self.head_dim)
185
+ beta = self.b_proj(hidden_states).float()'''
186
+ per_channel_gate_compat_code = ''' g = self.f_b_proj(self.f_a_proj(hidden_states))
187
+ g = rearrange(g, '... (h d) -> ... h d', d=self.head_dim)
188
+ # The released K3 checkpoint stores per-channel decay shared by all heads.
189
+ g = self.gate_lower_bound * torch.sigmoid(
190
+ self.A_log.float().exp().view(1, 1, 1, self.head_dim)
191
+ * (g.float() + self.dt_bias.float().view(1, 1, self.num_heads, self.head_dim))
192
+ ).to(g.dtype)
193
+ beta = self.b_proj(hidden_states).float()'''
194
+ causal_mask_code = ''' causal_mask = create_causal_mask(
195
+ config=self.config,
196
+ input_embeds=inputs_embeds,
197
+ attention_mask=attention_mask,
198
+ cache_position=cache_position,
199
+ past_key_values=past_key_values,
200
+ position_ids=position_ids,
201
+ )'''
202
+ causal_mask_compat_code = ''' if version.parse(transformers.__version__) >= version.parse("5.0.0"):
203
+ causal_mask = create_causal_mask(
204
+ config=self.config,
205
+ inputs_embeds=inputs_embeds,
206
+ attention_mask=attention_mask,
207
+ past_key_values=past_key_values,
208
+ position_ids=position_ids,
209
+ )
210
+ else:
211
+ causal_mask = create_causal_mask(
212
+ config=self.config,
213
+ input_embeds=inputs_embeds,
214
+ attention_mask=attention_mask,
215
+ cache_position=cache_position,
216
+ past_key_values=past_key_values,
217
+ position_ids=position_ids,
218
+ )'''
219
+ cache_api_code = ''' def get_mask_sizes(self, cache_position: torch.Tensor, layer_idx: int) -> tuple[int, int]:
220
+ """
221
+ Return a tuple (kv_length, kv_offset) corresponding to the length and offset that will be returned for
222
+ the given layer at `layer_idx`.
223
+ The masks are then prepared according to the given lengths (kv_length, kv_offset) and patterns for each layer.
224
+ """
225
+ kv_offset = 0
226
+ query_length = cache_position.shape[0]'''
227
+ cache_api_compat_code = ''' def get_query_offset(self, layer_idx: int) -> int:
228
+ return self.get_seq_length(layer_idx)
229
+
230
+ def get_mask_sizes(self, cache_position: torch.Tensor | int, layer_idx: int) -> tuple[int, int]:
231
+ """
232
+ Return a tuple (kv_length, kv_offset) corresponding to the length and offset that will be returned for
233
+ the given layer at `layer_idx`.
234
+ The masks are then prepared according to the given lengths (kv_length, kv_offset) and patterns for each layer.
235
+ """
236
+ kv_offset = 0
237
+ query_length = cache_position if isinstance(cache_position, int) else cache_position.shape[0]'''
238
+
239
+ replace_file(f'{save_folder}/modeling_kimi_linear.py', [
240
+ # Transformers 5 compatibility.
241
+ (
242
+ 'from transformers.modeling_utils import ALL_ATTENTION_FUNCTIONS, PreTrainedModel',
243
+ 'from transformers.modeling_utils import ALL_ATTENTION_FUNCTIONS, OutputRecorder, PreTrainedModel',
244
+ ),
245
+ (
246
+ 'from transformers.utils.generic import OutputRecorder, check_model_inputs',
247
+ 'from transformers.utils.generic import check_model_inputs',
248
+ ),
249
+ (
250
+ ' _tied_weights_keys = ["lm_head.weight"]',
251
+ ''' _tied_weights_keys = (
252
+ {"lm_head.weight": "model.embed_tokens.weight"}
253
+ if version.parse(transformers.__version__) >= version.parse("5.0.0")
254
+ else ["lm_head.weight"]
255
+ )''',
256
+ ),
257
+ (causal_mask_code, causal_mask_compat_code),
258
+ (cache_api_code, cache_api_compat_code),
259
+ # Allow eager MLA instead of forcing the optional flash-attn package.
260
+ (
261
+ force_flash_code,
262
+ ' config._attn_implementation = getattr(config, "_attn_implementation", "eager")',
263
+ ),
264
+ # Fix stale K3 reference code: released shards store A_log as
265
+ # [head_dim], and the decay is applied per channel across all heads.
266
+ (
267
+ ' self.num_heads, dtype=torch.float32).uniform_(1, 16)))',
268
+ ' self.head_dim, dtype=torch.float32).uniform_(1, 16)))',
269
+ ),
270
+ (per_channel_gate_code, per_channel_gate_compat_code),
271
+ (
272
+ ''' use_qk_l2norm_in_kernel=True,
273
+ use_gate_in_kernel=True,
274
+ use_beta_sigmoid_in_kernel=True,''',
275
+ ''' use_qk_l2norm_in_kernel=True,
276
+ use_gate_in_kernel=False,
277
+ use_beta_sigmoid_in_kernel=True,''',
278
+ ),
279
+ (
280
+ ' safe_gate=self.gate_lower_bound is not None,',
281
+ ' safe_gate=False,',
282
+ ),
283
+ ])
284
+ replace_file(f'{save_folder}/modeling_kimi_k3.py', [
285
+ (' def tie_weights(self):', ' def tie_weights(self, *args, **kwargs):'),
286
+ (" _supports_sdpa = True", " _supports_sdpa = False"),
287
+ (
288
+ ' first_layer_past_key_value = past_key_values[0][0][:, :, :, 0]',
289
+ ''' if hasattr(past_key_values, "key_cache"):
290
+ first_key_cache = next(
291
+ key_cache for key_cache in past_key_values.key_cache if key_cache is not None
292
+ )
293
+ first_layer_past_key_value = first_key_cache[:, :, :, 0]
294
+ else:
295
+ first_layer_past_key_value = past_key_values[0][0][:, :, :, 0]''',
296
+ ),
297
+ ])
298
+
299
+ with open(f'{save_folder}/config.json', encoding='utf-8') as f:
300
+ config_json = json.load(f)
301
+
302
+ quantization_config = config_json['text_config'].pop('quantization_config')
303
+ # Upstream declares every Linear as a target, but the released shards and
304
+ # community loaders agree that only routed expert w1/w2/w3 are MXFP4.
305
+ # Narrow only the scope; preserve format, 4-bit float encoding,
306
+ # group_size=32, U8 E8M0 scales, symmetry, and all other strategy fields.
307
+ quantization_config['config_groups']['group_0']['targets'] = [
308
+ 're:.*block_sparse_moe\\.experts\\.\\d+\\.(w1|w2|w3)$'
309
+ ]
310
+
311
+ # Preserve the kernel-sensitive dims from upstream: KDA head_dim=128,
312
+ # MLA kv_lora_rank=512, qk_nope=128, qk_rope=64, v=128, conv kernel=4.
313
+ # vLLM's Kimi fused MLA decode kernel requires latent KV rank 512 and
314
+ # cache/query head size 512 + 64 = 576, so only shrink head count and
315
+ # q_lora_rank; q_b_proj still drops 54MB->0.75MB.
316
+ # Keep the upstream cadence: four 4-layer groups plus the final MLA layer.
317
+ # One attention-residual checkpoint every two groups (block_size=8), so
318
+ # block boundaries land on layers 0, 8, 16 (0-based).
319
+ # MXFP4 only targets routed expert w1/w2/w3, whose input dims are
320
+ # routed_expert_hidden_size / moe_intermediate_size, so those two must stay
321
+ # divisible by group_size=32; hidden_size itself is never quantized and can
322
+ # shrink to 8. Inference engines pad MXFP4 buffers (sglang/vllm round the
323
+ # per-partition intermediate up to 128 and hidden up to 256, packing 2
324
+ # codes per byte along hidden) and their weight loaders only tolerate
325
+ # padding along the shard dim, so keep intermediate >= 256 (128 per
326
+ # partition at TP2) and routed_expert_hidden_size >= 256.
327
+ config_json['text_config'].update({
328
+ 'attn_res_block_size': 8,
329
+ 'first_k_dense_replace': 1,
330
+ 'hidden_size': 8,
331
+ 'intermediate_size': 32,
332
+ 'kv_lora_rank': 512,
333
+ 'moe_intermediate_size': 256,
334
+ 'num_attention_heads': 8,
335
+ 'num_experts': 64,
336
+ 'num_hidden_layers': 17,
337
+ 'num_key_value_heads': 8,
338
+ 'q_lora_rank': 256,
339
+ 'routed_expert_hidden_size': 256,
340
+ '_attn_implementation': 'eager',
341
+ })
342
+ config_json['text_config']['linear_attn_config'].update({
343
+ 'full_attn_layers': [4, 8, 12, 16, 17],
344
+ 'kda_layers': [1, 2, 3, 5, 6, 7, 9, 10, 11, 13, 14, 15],
345
+ 'num_heads': 8,
346
+ })
347
+ config_json['vision_config'].update({
348
+ '_attn_implementation': 'eager',
349
+ 'init_pos_emb_height': 8,
350
+ 'init_pos_emb_width': 8,
351
+ 'mm_hidden_size': 64,
352
+ 'qkv_hidden_size': 64,
353
+ 'text_hidden_size': 8,
354
+ 'vt_hidden_size': 64,
355
+ 'vt_intermediate_size': 128,
356
+ # Vision attention head size = qkv_hidden_size / heads = 64 / 2 = 32.
357
+ 'vt_num_attention_heads': 2,
358
+ 'vt_num_hidden_layers': 2,
359
+ })
360
+ with open(f'{save_folder}/config.json', 'w', encoding='utf-8') as f:
361
+ json.dump(config_json, f, indent=2)
362
+
363
+ config = AutoConfig.from_pretrained(save_folder, trust_remote_code=True)
364
+ print(config)
365
+ torch.set_default_dtype(torch.bfloat16)
366
+ model = AutoModel.from_config(
367
+ config,
368
+ trust_remote_code=True,
369
+ attn_implementation='eager',
370
+ )
371
+ torch.set_default_dtype(torch.float32)
372
+ if file_exists(
373
+ filename='generation_config.json',
374
+ repo_id=source_model_id,
375
+ repo_type='model',
376
+ revision=source_revision,
377
+ ):
378
+ model.generation_config = GenerationConfig.from_pretrained(
379
+ source_model_id,
380
+ trust_remote_code=True,
381
+ revision=source_revision,
382
+ )
383
+ set_seed(42)
384
+ model = model.cpu()
385
+ num_params = sum(p.numel() for p in model.parameters())
386
+ with torch.no_grad():
387
+ for name, parameter in sorted(model.named_parameters()):
388
+ torch.nn.init.normal_(parameter, 0, 0.1)
389
+ print(name, parameter.shape, parameter.dtype, f'{parameter.numel() / num_params:.2%}')
390
+ model.save_pretrained(save_folder)
391
+
392
+ # Match the official checkpoint schema: routed experts are native MXFP4
393
+ # (two values per U8 byte, one E8M0 scale per group of 32 values).
394
+ model_path = Path(save_folder) / 'model.safetensors'
395
+ state_dict = load_file(str(model_path))
396
+ fp4_boundaries = torch.tensor([0.25, 0.75, 1.25, 1.75, 2.5, 3.5, 5.0])
397
+ for name in list(state_dict):
398
+ if '.block_sparse_moe.experts.' in name and name.endswith('.weight'):
399
+ weight = state_dict.pop(name).float()
400
+ out_features, in_features = weight.shape
401
+ assert in_features % 32 == 0
402
+ packed_name = name.removesuffix('.weight') + '.weight_packed'
403
+ scale_name = name.removesuffix('.weight') + '.weight_scale'
404
+ blocks = weight.view(out_features, in_features // 32, 32)
405
+ # E2M1 represents magnitudes {0, .5, 1, 1.5, 2, 3, 4, 6}.
406
+ # E8M0 stores the shared power-of-two scale as exponent + bias 127.
407
+ scale_exponents = torch.round(
408
+ torch.log2(blocks.abs().amax(dim=-1).clamp_min(torch.finfo(torch.float32).tiny) / 6)
409
+ ).clamp(-126, 127)
410
+ scales = torch.pow(2.0, scale_exponents)
411
+ normalized = (blocks / scales.unsqueeze(-1)).clamp(-6, 6)
412
+ codes = torch.bucketize(normalized.abs(), fp4_boundaries)
413
+ codes += normalized.signbit() * 8
414
+ codes = codes.view(out_features, in_features)
415
+ state_dict[packed_name] = (
416
+ codes[:, ::2] | (codes[:, 1::2] << 4)
417
+ ).to(torch.uint8)
418
+ state_dict[scale_name] = (scale_exponents + 127).to(torch.uint8)
419
+ elif name.endswith((
420
+ '.block_sparse_moe.gate.e_score_correction_bias',
421
+ '.self_attn.A_log',
422
+ '.self_attn.dt_bias',
423
+ '.self_attn.k_conv1d.weight',
424
+ '.self_attn.o_norm.weight',
425
+ '.self_attn.q_conv1d.weight',
426
+ '.self_attn.v_conv1d.weight',
427
+ )):
428
+ state_dict[name] = state_dict[name].float()
429
+ dtype_counts = {
430
+ dtype: sum(tensor.dtype == dtype for tensor in state_dict.values())
431
+ for dtype in (torch.bfloat16, torch.float32, torch.uint8)
432
+ }
433
+ # Derive the expected schema from the config instead of hardcoding counts:
434
+ # each MoE layer has num_experts × (w1|w2|w3) × (packed, scale) U8 tensors,
435
+ # and F32 tensors are per-MoE e_score_correction_bias plus per-KDA A_log,
436
+ # dt_bias, o_norm, and k/q/v conv1d weights.
437
+ text_cfg = config_json['text_config']
438
+ num_moe_layers = text_cfg['num_hidden_layers'] - text_cfg['first_k_dense_replace']
439
+ num_kda_layers = len(text_cfg['linear_attn_config']['kda_layers'])
440
+ expected_uint8 = num_moe_layers * text_cfg['num_experts'] * 3 * 2
441
+ expected_f32 = num_moe_layers + num_kda_layers * 6
442
+ assert dtype_counts == {
443
+ torch.bfloat16: len(state_dict) - expected_uint8 - expected_f32,
444
+ torch.float32: expected_f32,
445
+ torch.uint8: expected_uint8,
446
+ }
447
+ assert not any('mtp' in name.lower() for name in state_dict)
448
+ print('Top 20 keys with largest storage size:')
449
+ for name, tensor in sorted(state_dict.items(), key=lambda x: x[1].numel() * x[1].element_size(), reverse=True)[:20]:
450
+ print(f'{name}: {tensor.numel()} elements, {tensor.numel() * tensor.element_size() / 1024**2:.2f} MB')
451
+ save_file(state_dict, str(model_path), metadata={'format': 'pt'})
452
+
453
+ with open(f'{save_folder}/config.json', encoding='utf-8') as f:
454
+ saved_config = json.load(f)
455
+ saved_config['quantization_config'] = quantization_config
456
+ saved_config['text_config']['quantization_config'] = quantization_config
457
+ with open(f'{save_folder}/config.json', 'w', encoding='utf-8') as f:
458
+ json.dump(saved_config, f, indent=2)
459
+ ```
460
+
461
+ </details>
462
+
463
+ ### Printing the model:
464
+
465
+ <details><summary>Click to expand</summary>
466
+
467
+ ```text
468
+ KimiK3ForConditionalGeneration(
469
+ (vision_tower): MoonViT3dPretrainedModel(
470
+ (patch_embed): MoonVision3dPatchEmbed(
471
+ (proj): Conv2d(3, 64, kernel_size=(14, 14), stride=(14, 14), bias=False)
472
+ (pos_emb): Learnable2DInterpPosEmbDivided_fixed()
473
+ )
474
+ (encoder): MoonViT3dEncoder(
475
+ (rope_2d): Rope2DPosEmbRepeated(dim=32, max_height=512, max_width=512, theta_base=10000)
476
+ (blocks): ModuleList(
477
+ (0-1): 2 x MoonViTEncoderLayer(
478
+ (norm0): RMSNorm((64,), eps=None, elementwise_affine=True)
479
+ (norm1): RMSNorm((64,), eps=None, elementwise_affine=True)
480
+ (mlp): MLP2(
481
+ (fc0): Linear(in_features=64, out_features=128, bias=False)
482
+ (fc1): Linear(in_features=128, out_features=64, bias=False)
483
+ (activation): GELUTanh()
484
+ )
485
+ (wqkv): Linear(in_features=64, out_features=192, bias=False)
486
+ (wo): Linear(in_features=64, out_features=64, bias=False)
487
+ )
488
+ )
489
+ (final_layernorm): RMSNorm((64,), eps=None, elementwise_affine=True)
490
+ )
491
+ )
492
+ (mm_projector): PatchMergerMLPV2(
493
+ (proj): Sequential(
494
+ (0): Linear(in_features=256, out_features=256, bias=False)
495
+ (1): GELU(approximate='none')
496
+ (2): Linear(in_features=256, out_features=8, bias=False)
497
+ )
498
+ (post_norm): RMSNorm((8,), eps=1e-05, elementwise_affine=True)
499
+ )
500
+ (language_model): KimiLinearForCausalLM(
501
+ (model): KimiLinearModel(
502
+ (embed_tokens): Embedding(163840, 8, padding_idx=163839)
503
+ (layers): ModuleList(
504
+ (0): KimiDecoderLayer(
505
+ (self_attn): KimiDeltaAttention(
506
+ (q_proj): Linear(in_features=8, out_features=1024, bias=False)
507
+ (k_proj): Linear(in_features=8, out_features=1024, bias=False)
508
+ (v_proj): Linear(in_features=8, out_features=1024, bias=False)
509
+ (q_conv1d): ShortConvolution(1024, 1024, kernel_size=(4,), stride=(1,), padding=(3,), groups=1024, bias=False, activation=silu, backend=triton)
510
+ (k_conv1d): ShortConvolution(1024, 1024, kernel_size=(4,), stride=(1,), padding=(3,), groups=1024, bias=False, activation=silu, backend=triton)
511
+ (v_conv1d): ShortConvolution(1024, 1024, kernel_size=(4,), stride=(1,), padding=(3,), groups=1024, bias=False, activation=silu, backend=triton)
512
+ (f_a_proj): Linear(in_features=8, out_features=128, bias=False)
513
+ (f_b_proj): Linear(in_features=128, out_features=1024, bias=False)
514
+ (b_proj): Linear(in_features=8, out_features=8, bias=False)
515
+ (g_proj): Linear(in_features=8, out_features=1024, bias=False)
516
+ (o_norm): FusedRMSNormGated(128, eps=1e-05, activation=sigmoid)
517
+ (o_proj): Linear(in_features=1024, out_features=8, bias=False)
518
+ )
519
+ (mlp): KimiMLP(
520
+ (gate_proj): Linear(in_features=8, out_features=32, bias=False)
521
+ (up_proj): Linear(in_features=8, out_features=32, bias=False)
522
+ (down_proj): Linear(in_features=32, out_features=8, bias=False)
523
+ (act_fn): SituAndMul()
524
+ )
525
+ (input_layernorm): KimiRMSNorm()
526
+ (post_attention_layernorm): KimiRMSNorm()
527
+ (self_attention_res_norm): KimiRMSNorm()
528
+ (mlp_res_norm): KimiRMSNorm()
529
+ (self_attention_res_proj): Linear(in_features=8, out_features=1, bias=False)
530
+ (mlp_res_proj): Linear(in_features=8, out_features=1, bias=False)
531
+ )
532
+ (1-2): 2 x KimiDecoderLayer(
533
+ (self_attn): KimiDeltaAttention(
534
+ (q_proj): Linear(in_features=8, out_features=1024, bias=False)
535
+ (k_proj): Linear(in_features=8, out_features=1024, bias=False)
536
+ (v_proj): Linear(in_features=8, out_features=1024, bias=False)
537
+ (q_conv1d): ShortConvolution(1024, 1024, kernel_size=(4,), stride=(1,), padding=(3,), groups=1024, bias=False, activation=silu, backend=triton)
538
+ (k_conv1d): ShortConvolution(1024, 1024, kernel_size=(4,), stride=(1,), padding=(3,), groups=1024, bias=False, activation=silu, backend=triton)
539
+ (v_conv1d): ShortConvolution(1024, 1024, kernel_size=(4,), stride=(1,), padding=(3,), groups=1024, bias=False, activation=silu, backend=triton)
540
+ (f_a_proj): Linear(in_features=8, out_features=128, bias=False)
541
+ (f_b_proj): Linear(in_features=128, out_features=1024, bias=False)
542
+ (b_proj): Linear(in_features=8, out_features=8, bias=False)
543
+ (g_proj): Linear(in_features=8, out_features=1024, bias=False)
544
+ (o_norm): FusedRMSNormGated(128, eps=1e-05, activation=sigmoid)
545
+ (o_proj): Linear(in_features=1024, out_features=8, bias=False)
546
+ )
547
+ (block_sparse_moe): KimiSparseMoeBlock(
548
+ (experts): ModuleList(
549
+ (0-63): 64 x KimiBlockSparseMLP(
550
+ (w1): Linear(in_features=256, out_features=256, bias=False)
551
+ (w2): Linear(in_features=256, out_features=256, bias=False)
552
+ (w3): Linear(in_features=256, out_features=256, bias=False)
553
+ (act_fn): SituAndMul()
554
+ )
555
+ )
556
+ (gate): KimiMoEGate()
557
+ (shared_experts): KimiMLP(
558
+ (gate_proj): Linear(in_features=8, out_features=512, bias=False)
559
+ (up_proj): Linear(in_features=8, out_features=512, bias=False)
560
+ (down_proj): Linear(in_features=512, out_features=8, bias=False)
561
+ (act_fn): SituAndMul()
562
+ )
563
+ (routed_expert_down_proj): Linear(in_features=8, out_features=256, bias=False)
564
+ (routed_expert_up_proj): Linear(in_features=256, out_features=8, bias=False)
565
+ (routed_expert_norm): KimiRMSNorm()
566
+ )
567
+ (input_layernorm): KimiRMSNorm()
568
+ (post_attention_layernorm): KimiRMSNorm()
569
+ (self_attention_res_norm): KimiRMSNorm()
570
+ (mlp_res_norm): KimiRMSNorm()
571
+ (self_attention_res_proj): Linear(in_features=8, out_features=1, bias=False)
572
+ (mlp_res_proj): Linear(in_features=8, out_features=1, bias=False)
573
+ )
574
+ (3): KimiDecoderLayer(
575
+ (self_attn): KimiMLAAttention(
576
+ (q_a_proj): Linear(in_features=8, out_features=256, bias=False)
577
+ (q_a_layernorm): KimiRMSNorm()
578
+ (q_b_proj): Linear(in_features=256, out_features=1536, bias=False)
579
+ (kv_a_proj_with_mqa): Linear(in_features=8, out_features=576, bias=False)
580
+ (kv_a_layernorm): KimiRMSNorm()
581
+ (kv_b_proj): Linear(in_features=512, out_features=2048, bias=False)
582
+ (o_proj): Linear(in_features=1024, out_features=8, bias=False)
583
+ (g_proj): Linear(in_features=8, out_features=1024, bias=False)
584
+ )
585
+ (block_sparse_moe): KimiSparseMoeBlock(
586
+ (experts): ModuleList(
587
+ (0-63): 64 x KimiBlockSparseMLP(
588
+ (w1): Linear(in_features=256, out_features=256, bias=False)
589
+ (w2): Linear(in_features=256, out_features=256, bias=False)
590
+ (w3): Linear(in_features=256, out_features=256, bias=False)
591
+ (act_fn): SituAndMul()
592
+ )
593
+ )
594
+ (gate): KimiMoEGate()
595
+ (shared_experts): KimiMLP(
596
+ (gate_proj): Linear(in_features=8, out_features=512, bias=False)
597
+ (up_proj): Linear(in_features=8, out_features=512, bias=False)
598
+ (down_proj): Linear(in_features=512, out_features=8, bias=False)
599
+ (act_fn): SituAndMul()
600
+ )
601
+ (routed_expert_down_proj): Linear(in_features=8, out_features=256, bias=False)
602
+ (routed_expert_up_proj): Linear(in_features=256, out_features=8, bias=False)
603
+ (routed_expert_norm): KimiRMSNorm()
604
+ )
605
+ (input_layernorm): KimiRMSNorm()
606
+ (post_attention_layernorm): KimiRMSNorm()
607
+ (self_attention_res_norm): KimiRMSNorm()
608
+ (mlp_res_norm): KimiRMSNorm()
609
+ (self_attention_res_proj): Linear(in_features=8, out_features=1, bias=False)
610
+ (mlp_res_proj): Linear(in_features=8, out_features=1, bias=False)
611
+ )
612
+ (4-6): 3 x KimiDecoderLayer(
613
+ (self_attn): KimiDeltaAttention(
614
+ (q_proj): Linear(in_features=8, out_features=1024, bias=False)
615
+ (k_proj): Linear(in_features=8, out_features=1024, bias=False)
616
+ (v_proj): Linear(in_features=8, out_features=1024, bias=False)
617
+ (q_conv1d): ShortConvolution(1024, 1024, kernel_size=(4,), stride=(1,), padding=(3,), groups=1024, bias=False, activation=silu, backend=triton)
618
+ (k_conv1d): ShortConvolution(1024, 1024, kernel_size=(4,), stride=(1,), padding=(3,), groups=1024, bias=False, activation=silu, backend=triton)
619
+ (v_conv1d): ShortConvolution(1024, 1024, kernel_size=(4,), stride=(1,), padding=(3,), groups=1024, bias=False, activation=silu, backend=triton)
620
+ (f_a_proj): Linear(in_features=8, out_features=128, bias=False)
621
+ (f_b_proj): Linear(in_features=128, out_features=1024, bias=False)
622
+ (b_proj): Linear(in_features=8, out_features=8, bias=False)
623
+ (g_proj): Linear(in_features=8, out_features=1024, bias=False)
624
+ (o_norm): FusedRMSNormGated(128, eps=1e-05, activation=sigmoid)
625
+ (o_proj): Linear(in_features=1024, out_features=8, bias=False)
626
+ )
627
+ (block_sparse_moe): KimiSparseMoeBlock(
628
+ (experts): ModuleList(
629
+ (0-63): 64 x KimiBlockSparseMLP(
630
+ (w1): Linear(in_features=256, out_features=256, bias=False)
631
+ (w2): Linear(in_features=256, out_features=256, bias=False)
632
+ (w3): Linear(in_features=256, out_features=256, bias=False)
633
+ (act_fn): SituAndMul()
634
+ )
635
+ )
636
+ (gate): KimiMoEGate()
637
+ (shared_experts): KimiMLP(
638
+ (gate_proj): Linear(in_features=8, out_features=512, bias=False)
639
+ (up_proj): Linear(in_features=8, out_features=512, bias=False)
640
+ (down_proj): Linear(in_features=512, out_features=8, bias=False)
641
+ (act_fn): SituAndMul()
642
+ )
643
+ (routed_expert_down_proj): Linear(in_features=8, out_features=256, bias=False)
644
+ (routed_expert_up_proj): Linear(in_features=256, out_features=8, bias=False)
645
+ (routed_expert_norm): KimiRMSNorm()
646
+ )
647
+ (input_layernorm): KimiRMSNorm()
648
+ (post_attention_layernorm): KimiRMSNorm()
649
+ (self_attention_res_norm): KimiRMSNorm()
650
+ (mlp_res_norm): KimiRMSNorm()
651
+ (self_attention_res_proj): Linear(in_features=8, out_features=1, bias=False)
652
+ (mlp_res_proj): Linear(in_features=8, out_features=1, bias=False)
653
+ )
654
+ (7): KimiDecoderLayer(
655
+ (self_attn): KimiMLAAttention(
656
+ (q_a_proj): Linear(in_features=8, out_features=256, bias=False)
657
+ (q_a_layernorm): KimiRMSNorm()
658
+ (q_b_proj): Linear(in_features=256, out_features=1536, bias=False)
659
+ (kv_a_proj_with_mqa): Linear(in_features=8, out_features=576, bias=False)
660
+ (kv_a_layernorm): KimiRMSNorm()
661
+ (kv_b_proj): Linear(in_features=512, out_features=2048, bias=False)
662
+ (o_proj): Linear(in_features=1024, out_features=8, bias=False)
663
+ (g_proj): Linear(in_features=8, out_features=1024, bias=False)
664
+ )
665
+ (block_sparse_moe): KimiSparseMoeBlock(
666
+ (experts): ModuleList(
667
+ (0-63): 64 x KimiBlockSparseMLP(
668
+ (w1): Linear(in_features=256, out_features=256, bias=False)
669
+ (w2): Linear(in_features=256, out_features=256, bias=False)
670
+ (w3): Linear(in_features=256, out_features=256, bias=False)
671
+ (act_fn): SituAndMul()
672
+ )
673
+ )
674
+ (gate): KimiMoEGate()
675
+ (shared_experts): KimiMLP(
676
+ (gate_proj): Linear(in_features=8, out_features=512, bias=False)
677
+ (up_proj): Linear(in_features=8, out_features=512, bias=False)
678
+ (down_proj): Linear(in_features=512, out_features=8, bias=False)
679
+ (act_fn): SituAndMul()
680
+ )
681
+ (routed_expert_down_proj): Linear(in_features=8, out_features=256, bias=False)
682
+ (routed_expert_up_proj): Linear(in_features=256, out_features=8, bias=False)
683
+ (routed_expert_norm): KimiRMSNorm()
684
+ )
685
+ (input_layernorm): KimiRMSNorm()
686
+ (post_attention_layernorm): KimiRMSNorm()
687
+ (self_attention_res_norm): KimiRMSNorm()
688
+ (mlp_res_norm): KimiRMSNorm()
689
+ (self_attention_res_proj): Linear(in_features=8, out_features=1, bias=False)
690
+ (mlp_res_proj): Linear(in_features=8, out_features=1, bias=False)
691
+ )
692
+ (8-10): 3 x KimiDecoderLayer(
693
+ (self_attn): KimiDeltaAttention(
694
+ (q_proj): Linear(in_features=8, out_features=1024, bias=False)
695
+ (k_proj): Linear(in_features=8, out_features=1024, bias=False)
696
+ (v_proj): Linear(in_features=8, out_features=1024, bias=False)
697
+ (q_conv1d): ShortConvolution(1024, 1024, kernel_size=(4,), stride=(1,), padding=(3,), groups=1024, bias=False, activation=silu, backend=triton)
698
+ (k_conv1d): ShortConvolution(1024, 1024, kernel_size=(4,), stride=(1,), padding=(3,), groups=1024, bias=False, activation=silu, backend=triton)
699
+ (v_conv1d): ShortConvolution(1024, 1024, kernel_size=(4,), stride=(1,), padding=(3,), groups=1024, bias=False, activation=silu, backend=triton)
700
+ (f_a_proj): Linear(in_features=8, out_features=128, bias=False)
701
+ (f_b_proj): Linear(in_features=128, out_features=1024, bias=False)
702
+ (b_proj): Linear(in_features=8, out_features=8, bias=False)
703
+ (g_proj): Linear(in_features=8, out_features=1024, bias=False)
704
+ (o_norm): FusedRMSNormGated(128, eps=1e-05, activation=sigmoid)
705
+ (o_proj): Linear(in_features=1024, out_features=8, bias=False)
706
+ )
707
+ (block_sparse_moe): KimiSparseMoeBlock(
708
+ (experts): ModuleList(
709
+ (0-63): 64 x KimiBlockSparseMLP(
710
+ (w1): Linear(in_features=256, out_features=256, bias=False)
711
+ (w2): Linear(in_features=256, out_features=256, bias=False)
712
+ (w3): Linear(in_features=256, out_features=256, bias=False)
713
+ (act_fn): SituAndMul()
714
+ )
715
+ )
716
+ (gate): KimiMoEGate()
717
+ (shared_experts): KimiMLP(
718
+ (gate_proj): Linear(in_features=8, out_features=512, bias=False)
719
+ (up_proj): Linear(in_features=8, out_features=512, bias=False)
720
+ (down_proj): Linear(in_features=512, out_features=8, bias=False)
721
+ (act_fn): SituAndMul()
722
+ )
723
+ (routed_expert_down_proj): Linear(in_features=8, out_features=256, bias=False)
724
+ (routed_expert_up_proj): Linear(in_features=256, out_features=8, bias=False)
725
+ (routed_expert_norm): KimiRMSNorm()
726
+ )
727
+ (input_layernorm): KimiRMSNorm()
728
+ (post_attention_layernorm): KimiRMSNorm()
729
+ (self_attention_res_norm): KimiRMSNorm()
730
+ (mlp_res_norm): KimiRMSNorm()
731
+ (self_attention_res_proj): Linear(in_features=8, out_features=1, bias=False)
732
+ (mlp_res_proj): Linear(in_features=8, out_features=1, bias=False)
733
+ )
734
+ (11): KimiDecoderLayer(
735
+ (self_attn): KimiMLAAttention(
736
+ (q_a_proj): Linear(in_features=8, out_features=256, bias=False)
737
+ (q_a_layernorm): KimiRMSNorm()
738
+ (q_b_proj): Linear(in_features=256, out_features=1536, bias=False)
739
+ (kv_a_proj_with_mqa): Linear(in_features=8, out_features=576, bias=False)
740
+ (kv_a_layernorm): KimiRMSNorm()
741
+ (kv_b_proj): Linear(in_features=512, out_features=2048, bias=False)
742
+ (o_proj): Linear(in_features=1024, out_features=8, bias=False)
743
+ (g_proj): Linear(in_features=8, out_features=1024, bias=False)
744
+ )
745
+ (block_sparse_moe): KimiSparseMoeBlock(
746
+ (experts): ModuleList(
747
+ (0-63): 64 x KimiBlockSparseMLP(
748
+ (w1): Linear(in_features=256, out_features=256, bias=False)
749
+ (w2): Linear(in_features=256, out_features=256, bias=False)
750
+ (w3): Linear(in_features=256, out_features=256, bias=False)
751
+ (act_fn): SituAndMul()
752
+ )
753
+ )
754
+ (gate): KimiMoEGate()
755
+ (shared_experts): KimiMLP(
756
+ (gate_proj): Linear(in_features=8, out_features=512, bias=False)
757
+ (up_proj): Linear(in_features=8, out_features=512, bias=False)
758
+ (down_proj): Linear(in_features=512, out_features=8, bias=False)
759
+ (act_fn): SituAndMul()
760
+ )
761
+ (routed_expert_down_proj): Linear(in_features=8, out_features=256, bias=False)
762
+ (routed_expert_up_proj): Linear(in_features=256, out_features=8, bias=False)
763
+ (routed_expert_norm): KimiRMSNorm()
764
+ )
765
+ (input_layernorm): KimiRMSNorm()
766
+ (post_attention_layernorm): KimiRMSNorm()
767
+ (self_attention_res_norm): KimiRMSNorm()
768
+ (mlp_res_norm): KimiRMSNorm()
769
+ (self_attention_res_proj): Linear(in_features=8, out_features=1, bias=False)
770
+ (mlp_res_proj): Linear(in_features=8, out_features=1, bias=False)
771
+ )
772
+ (12-14): 3 x KimiDecoderLayer(
773
+ (self_attn): KimiDeltaAttention(
774
+ (q_proj): Linear(in_features=8, out_features=1024, bias=False)
775
+ (k_proj): Linear(in_features=8, out_features=1024, bias=False)
776
+ (v_proj): Linear(in_features=8, out_features=1024, bias=False)
777
+ (q_conv1d): ShortConvolution(1024, 1024, kernel_size=(4,), stride=(1,), padding=(3,), groups=1024, bias=False, activation=silu, backend=triton)
778
+ (k_conv1d): ShortConvolution(1024, 1024, kernel_size=(4,), stride=(1,), padding=(3,), groups=1024, bias=False, activation=silu, backend=triton)
779
+ (v_conv1d): ShortConvolution(1024, 1024, kernel_size=(4,), stride=(1,), padding=(3,), groups=1024, bias=False, activation=silu, backend=triton)
780
+ (f_a_proj): Linear(in_features=8, out_features=128, bias=False)
781
+ (f_b_proj): Linear(in_features=128, out_features=1024, bias=False)
782
+ (b_proj): Linear(in_features=8, out_features=8, bias=False)
783
+ (g_proj): Linear(in_features=8, out_features=1024, bias=False)
784
+ (o_norm): FusedRMSNormGated(128, eps=1e-05, activation=sigmoid)
785
+ (o_proj): Linear(in_features=1024, out_features=8, bias=False)
786
+ )
787
+ (block_sparse_moe): KimiSparseMoeBlock(
788
+ (experts): ModuleList(
789
+ (0-63): 64 x KimiBlockSparseMLP(
790
+ (w1): Linear(in_features=256, out_features=256, bias=False)
791
+ (w2): Linear(in_features=256, out_features=256, bias=False)
792
+ (w3): Linear(in_features=256, out_features=256, bias=False)
793
+ (act_fn): SituAndMul()
794
+ )
795
+ )
796
+ (gate): KimiMoEGate()
797
+ (shared_experts): KimiMLP(
798
+ (gate_proj): Linear(in_features=8, out_features=512, bias=False)
799
+ (up_proj): Linear(in_features=8, out_features=512, bias=False)
800
+ (down_proj): Linear(in_features=512, out_features=8, bias=False)
801
+ (act_fn): SituAndMul()
802
+ )
803
+ (routed_expert_down_proj): Linear(in_features=8, out_features=256, bias=False)
804
+ (routed_expert_up_proj): Linear(in_features=256, out_features=8, bias=False)
805
+ (routed_expert_norm): KimiRMSNorm()
806
+ )
807
+ (input_layernorm): KimiRMSNorm()
808
+ (post_attention_layernorm): KimiRMSNorm()
809
+ (self_attention_res_norm): KimiRMSNorm()
810
+ (mlp_res_norm): KimiRMSNorm()
811
+ (self_attention_res_proj): Linear(in_features=8, out_features=1, bias=False)
812
+ (mlp_res_proj): Linear(in_features=8, out_features=1, bias=False)
813
+ )
814
+ (15-16): 2 x KimiDecoderLayer(
815
+ (self_attn): KimiMLAAttention(
816
+ (q_a_proj): Linear(in_features=8, out_features=256, bias=False)
817
+ (q_a_layernorm): KimiRMSNorm()
818
+ (q_b_proj): Linear(in_features=256, out_features=1536, bias=False)
819
+ (kv_a_proj_with_mqa): Linear(in_features=8, out_features=576, bias=False)
820
+ (kv_a_layernorm): KimiRMSNorm()
821
+ (kv_b_proj): Linear(in_features=512, out_features=2048, bias=False)
822
+ (o_proj): Linear(in_features=1024, out_features=8, bias=False)
823
+ (g_proj): Linear(in_features=8, out_features=1024, bias=False)
824
+ )
825
+ (block_sparse_moe): KimiSparseMoeBlock(
826
+ (experts): ModuleList(
827
+ (0-63): 64 x KimiBlockSparseMLP(
828
+ (w1): Linear(in_features=256, out_features=256, bias=False)
829
+ (w2): Linear(in_features=256, out_features=256, bias=False)
830
+ (w3): Linear(in_features=256, out_features=256, bias=False)
831
+ (act_fn): SituAndMul()
832
+ )
833
+ )
834
+ (gate): KimiMoEGate()
835
+ (shared_experts): KimiMLP(
836
+ (gate_proj): Linear(in_features=8, out_features=512, bias=False)
837
+ (up_proj): Linear(in_features=8, out_features=512, bias=False)
838
+ (down_proj): Linear(in_features=512, out_features=8, bias=False)
839
+ (act_fn): SituAndMul()
840
+ )
841
+ (routed_expert_down_proj): Linear(in_features=8, out_features=256, bias=False)
842
+ (routed_expert_up_proj): Linear(in_features=256, out_features=8, bias=False)
843
+ (routed_expert_norm): KimiRMSNorm()
844
+ )
845
+ (input_layernorm): KimiRMSNorm()
846
+ (post_attention_layernorm): KimiRMSNorm()
847
+ (self_attention_res_norm): KimiRMSNorm()
848
+ (mlp_res_norm): KimiRMSNorm()
849
+ (self_attention_res_proj): Linear(in_features=8, out_features=1, bias=False)
850
+ (mlp_res_proj): Linear(in_features=8, out_features=1, bias=False)
851
+ )
852
+ )
853
+ (norm): KimiRMSNorm()
854
+ (output_attn_res_norm): KimiRMSNorm()
855
+ (output_attn_res_proj): Linear(in_features=8, out_features=1, bias=False)
856
+ )
857
+ (lm_head): Linear(in_features=8, out_features=163840, bias=False)
858
+ )
859
+ )
860
+ ```
861
+
862
+ </details>
863
+
864
+ ### Test environment:
865
+
866
+ - einops: 0.9.0.dev0
867
+ - fla-core: 0.5.2
868
+ - safetensors: 0.8.0
869
+ - torch: 2.13.0+cu126
870
+ - transformers: 5.15.0.dev0
871
+ - triton: 3.7.1
872
+ - vllm: 0.1.dev1+g5a3eba034.d20260730.cu126
config.json ADDED
@@ -0,0 +1,261 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "architectures": [
3
+ "KimiK3ForConditionalGeneration"
4
+ ],
5
+ "auto_map": {
6
+ "AutoConfig": "configuration_kimi_k3.KimiK3Config",
7
+ "AutoModel": "modeling_kimi_k3.KimiK3ForConditionalGeneration",
8
+ "AutoModelForCausalLM": "modeling_kimi_k3.KimiK3ForConditionalGeneration"
9
+ },
10
+ "bos_token_id": 163584,
11
+ "dtype": "bfloat16",
12
+ "eos_token_id": 163586,
13
+ "ignore_index": -100,
14
+ "image_placeholder": "<|kimi_image_placeholder|>",
15
+ "media_placeholder_token_id": 163605,
16
+ "model_type": "kimi_k3",
17
+ "pad_token_id": 163839,
18
+ "text_config": {
19
+ "_name_or_path": "",
20
+ "activation_situ_beta": 4.0,
21
+ "activation_situ_linear_beta": 25.0,
22
+ "add_cross_attention": false,
23
+ "architectures": [
24
+ "KimiLinearForCausalLM"
25
+ ],
26
+ "attn_res_block_size": 8,
27
+ "auto_map": {
28
+ "AutoConfig": "configuration_kimi_k3.KimiLinearConfig",
29
+ "AutoModel": "modeling_kimi_linear.KimiLinearModel",
30
+ "AutoModelForCausalLM": "modeling_kimi_linear.KimiLinearForCausalLM"
31
+ },
32
+ "bos_token_id": 163584,
33
+ "chunk_size_feed_forward": 0,
34
+ "cross_attention_hidden_size": null,
35
+ "decoder_start_token_id": null,
36
+ "dtype": "bfloat16",
37
+ "eos_token_id": 163586,
38
+ "finetuning_task": null,
39
+ "first_k_dense_replace": 1,
40
+ "head_dim": 1,
41
+ "hidden_act": "situ",
42
+ "hidden_size": 8,
43
+ "id2label": {
44
+ "0": "LABEL_0",
45
+ "1": "LABEL_1"
46
+ },
47
+ "initializer_range": 0.02,
48
+ "intermediate_size": 32,
49
+ "is_decoder": false,
50
+ "is_encoder_decoder": false,
51
+ "kv_lora_rank": 512,
52
+ "label2id": {
53
+ "LABEL_0": 0,
54
+ "LABEL_1": 1
55
+ },
56
+ "latent_moe_use_norm": true,
57
+ "linear_attn_config": {
58
+ "full_attn_layers": [
59
+ 4,
60
+ 8,
61
+ 12,
62
+ 16,
63
+ 17
64
+ ],
65
+ "gate_lower_bound": -5.0,
66
+ "head_dim": 128,
67
+ "kda_layers": [
68
+ 1,
69
+ 2,
70
+ 3,
71
+ 5,
72
+ 6,
73
+ 7,
74
+ 9,
75
+ 10,
76
+ 11,
77
+ 13,
78
+ 14,
79
+ 15
80
+ ],
81
+ "num_heads": 8,
82
+ "short_conv_kernel_size": 4,
83
+ "use_full_rank_gate": true
84
+ },
85
+ "max_position_embeddings": 1048576,
86
+ "mla_use_nope": true,
87
+ "mla_use_output_gate": true,
88
+ "model_type": "kimi_linear",
89
+ "moe_intermediate_size": 256,
90
+ "moe_layer_freq": 1,
91
+ "moe_renormalize": true,
92
+ "moe_router_activation_func": "sigmoid",
93
+ "num_attention_heads": 8,
94
+ "num_expert_group": 1,
95
+ "num_experts": 64,
96
+ "num_experts_per_token": 16,
97
+ "num_hidden_layers": 17,
98
+ "num_key_value_heads": 8,
99
+ "num_nextn_predict_layers": 0,
100
+ "num_shared_experts": 2,
101
+ "output_attentions": false,
102
+ "output_hidden_states": false,
103
+ "pad_token_id": 163839,
104
+ "prefix": null,
105
+ "problem_type": null,
106
+ "pruned_heads": {},
107
+ "q_lora_rank": 256,
108
+ "qk_nope_head_dim": 128,
109
+ "qk_rope_head_dim": 64,
110
+ "return_dict": true,
111
+ "rms_norm_eps": 1e-05,
112
+ "rope_parameters": {
113
+ "rope_theta": 10000.0,
114
+ "rope_type": "default"
115
+ },
116
+ "rope_theta": 10000.0,
117
+ "routed_expert_hidden_size": 256,
118
+ "routed_scaling_factor": 1.0,
119
+ "sep_token_id": null,
120
+ "task_specific_params": null,
121
+ "tf_legacy_loss": false,
122
+ "tie_encoder_decoder": false,
123
+ "tie_word_embeddings": false,
124
+ "tokenizer_class": null,
125
+ "topk_group": 1,
126
+ "topk_method": "noaux_tc",
127
+ "torchscript": false,
128
+ "use_bfloat16": false,
129
+ "use_cache": true,
130
+ "use_grouped_topk": true,
131
+ "v_head_dim": 128,
132
+ "vocab_size": 163840,
133
+ "quantization_config": {
134
+ "config_groups": {
135
+ "group_0": {
136
+ "format": "mxfp4-pack-quantized",
137
+ "input_activations": null,
138
+ "output_activations": null,
139
+ "targets": [
140
+ "re:.*block_sparse_moe\\.experts\\.\\d+\\.(w1|w2|w3)$"
141
+ ],
142
+ "weights": {
143
+ "actorder": null,
144
+ "block_structure": null,
145
+ "dynamic": false,
146
+ "group_size": 32,
147
+ "num_bits": 4,
148
+ "observer": "minmax",
149
+ "observer_kwargs": {},
150
+ "scale_dtype": "torch.uint8",
151
+ "strategy": "group",
152
+ "symmetric": true,
153
+ "type": "float",
154
+ "zp_dtype": null
155
+ }
156
+ }
157
+ },
158
+ "format": "mxfp4-pack-quantized",
159
+ "global_compression_ratio": null,
160
+ "ignore": [
161
+ "re:.*self_attn.*",
162
+ "re:.*shared_experts.*",
163
+ "re:.*mlp\\.(gate|up|gate_up|down)_proj.*",
164
+ "re:.*lm_head.*",
165
+ "re:.*vision_tower.*",
166
+ "re:.*mm_projector.*"
167
+ ],
168
+ "kv_cache_scheme": null,
169
+ "quant_method": "compressed-tensors",
170
+ "quantization_status": "compressed"
171
+ }
172
+ },
173
+ "tie_word_embeddings": false,
174
+ "transformers_version": "5.15.0.dev0",
175
+ "vision_config": {
176
+ "_name_or_path": "",
177
+ "activation_func": "gelu_pytorch_tanh",
178
+ "architectures": null,
179
+ "attn_bias": false,
180
+ "chunk_size_feed_forward": 0,
181
+ "dtype": null,
182
+ "id2label": {
183
+ "0": "LABEL_0",
184
+ "1": "LABEL_1"
185
+ },
186
+ "init_pos_emb_height": 8,
187
+ "init_pos_emb_time": 4,
188
+ "init_pos_emb_width": 8,
189
+ "is_encoder_decoder": false,
190
+ "label2id": {
191
+ "LABEL_0": 0,
192
+ "LABEL_1": 1
193
+ },
194
+ "linear_bias": false,
195
+ "merge_kernel_size": [
196
+ 2,
197
+ 2
198
+ ],
199
+ "merge_type": "sd2_tpool",
200
+ "mlp_type": "mlp2",
201
+ "mm_hidden_size": 64,
202
+ "mm_projector_type": "patchmergerv2",
203
+ "model_type": "",
204
+ "norm_type": "rmsnorm",
205
+ "output_attentions": false,
206
+ "output_hidden_states": false,
207
+ "patch_embed_proj_bias": false,
208
+ "patch_size": 14,
209
+ "pos_emb_interpolation_mode": "bilinear",
210
+ "pos_emb_type": "divided_fixed",
211
+ "problem_type": null,
212
+ "projector_hidden_act": "gelu",
213
+ "projector_ln_eps": 1e-05,
214
+ "qkv_hidden_size": 64,
215
+ "return_dict": true,
216
+ "text_hidden_size": 8,
217
+ "vt_hidden_size": 64,
218
+ "vt_intermediate_size": 128,
219
+ "vt_num_attention_heads": 2,
220
+ "vt_num_hidden_layers": 2
221
+ },
222
+ "quantization_config": {
223
+ "config_groups": {
224
+ "group_0": {
225
+ "format": "mxfp4-pack-quantized",
226
+ "input_activations": null,
227
+ "output_activations": null,
228
+ "targets": [
229
+ "re:.*block_sparse_moe\\.experts\\.\\d+\\.(w1|w2|w3)$"
230
+ ],
231
+ "weights": {
232
+ "actorder": null,
233
+ "block_structure": null,
234
+ "dynamic": false,
235
+ "group_size": 32,
236
+ "num_bits": 4,
237
+ "observer": "minmax",
238
+ "observer_kwargs": {},
239
+ "scale_dtype": "torch.uint8",
240
+ "strategy": "group",
241
+ "symmetric": true,
242
+ "type": "float",
243
+ "zp_dtype": null
244
+ }
245
+ }
246
+ },
247
+ "format": "mxfp4-pack-quantized",
248
+ "global_compression_ratio": null,
249
+ "ignore": [
250
+ "re:.*self_attn.*",
251
+ "re:.*shared_experts.*",
252
+ "re:.*mlp\\.(gate|up|gate_up|down)_proj.*",
253
+ "re:.*lm_head.*",
254
+ "re:.*vision_tower.*",
255
+ "re:.*mm_projector.*"
256
+ ],
257
+ "kv_cache_scheme": null,
258
+ "quant_method": "compressed-tensors",
259
+ "quantization_status": "compressed"
260
+ }
261
+ }
configuration_kimi_k3.py ADDED
@@ -0,0 +1,285 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ attn_res_block_size: Optional[int] = None,
52
+ latent_moe_use_norm: bool = False,
53
+ activation_situ_beta: Optional[float] = None,
54
+ activation_situ_linear_beta: Optional[float] = None,
55
+ max_position_embeddings: int = 4096,
56
+ routed_expert_hidden_size: Optional[int] = None,
57
+ topk_method: str = "noaux_tc",
58
+ **kwargs,
59
+ ):
60
+ self.model_type = model_type
61
+ self.vocab_size = vocab_size
62
+ self.hidden_size = hidden_size
63
+ self.head_dim = (
64
+ head_dim if head_dim is not None else hidden_size // num_attention_heads
65
+ )
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
+
118
+ super().__init__(
119
+ pad_token_id=pad_token_id,
120
+ bos_token_id=bos_token_id,
121
+ eos_token_id=eos_token_id,
122
+ tie_word_embeddings=tie_word_embeddings,
123
+ **kwargs,
124
+ )
125
+
126
+ @property
127
+ def is_mla(self):
128
+ return (
129
+ self.q_lora_rank is not None
130
+ 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
134
+ or self.mla_use_nope is True
135
+ )
136
+
137
+ @property
138
+ def is_moe(self):
139
+ return self.num_experts is not None
140
+
141
+ @property
142
+ def is_linear_attn(self) -> bool:
143
+ return not (
144
+ self.linear_attn_config is None
145
+ or (
146
+ isinstance(self.linear_attn_config, dict)
147
+ and self.linear_attn_config["kda_layers"] is not None
148
+ and len(self.linear_attn_config["kda_layers"]) == 0
149
+ )
150
+ )
151
+
152
+ def is_kda_layer(self, layer_idx: int):
153
+ return (
154
+ self.linear_attn_config is not None
155
+ and (layer_idx + 1) in self.linear_attn_config["kda_layers"]
156
+ )
157
+
158
+
159
+ class KimiK3VisionConfig(PretrainedConfig):
160
+
161
+ def __init__(
162
+ self,
163
+ patch_size: int = 14,
164
+ init_pos_emb_height: int = 64,
165
+ init_pos_emb_width: int = 64,
166
+ init_pos_emb_time: int = 4,
167
+ pos_emb_type: str = 'divided_fixed',
168
+ vt_num_attention_heads: int = 12,
169
+ vt_num_hidden_layers: int = 27,
170
+ vt_hidden_size: int = 1024,
171
+ vt_intermediate_size: int = 4096,
172
+ merge_kernel_size: tuple = (2, 2),
173
+ merge_type: str = 'sd2_tpool',
174
+ _attn_implementation: str = 'flash_attention_2',
175
+ # MM Projector parameters
176
+ mm_projector_type: str = 'patchmergerv2',
177
+ mm_hidden_size: int | None = None,
178
+ projector_hidden_act: str = "gelu",
179
+ projector_ln_eps: float = 1e-5,
180
+ # vision tower parameters
181
+ qkv_hidden_size: int = 1536,
182
+ norm_type: str = 'rmsnorm',
183
+ attn_bias: bool = False,
184
+ patch_embed_proj_bias: bool = False,
185
+ mlp_type: str = 'mlp2',
186
+ linear_bias: bool = False,
187
+ activation_func: str = 'gelu_pytorch_tanh',
188
+ pos_emb_interpolation_mode: str = 'bilinear',
189
+ # Other parameters
190
+ ignore_index: int = -100,
191
+ media_placeholder_token_id: int = 163605,
192
+ pad_token_id: int = 0,
193
+ text_hidden_size=7168,
194
+ **kwargs):
195
+
196
+ self.patch_size = patch_size
197
+ self.init_pos_emb_height = init_pos_emb_height
198
+ self.init_pos_emb_width = init_pos_emb_width
199
+ self.init_pos_emb_time = init_pos_emb_time
200
+ self.pos_emb_type = pos_emb_type
201
+ self.vt_num_attention_heads = vt_num_attention_heads
202
+ self.vt_num_hidden_layers = vt_num_hidden_layers
203
+ self.vt_hidden_size = vt_hidden_size
204
+ self.vt_intermediate_size = vt_intermediate_size
205
+ self.merge_kernel_size = merge_kernel_size
206
+ self.merge_type = merge_type
207
+ self._attn_implementation = _attn_implementation
208
+
209
+ # MM Projector config
210
+ self.mm_projector_type = mm_projector_type
211
+ self.mm_hidden_size = mm_hidden_size if mm_hidden_size is not None else vt_hidden_size
212
+ self.projector_hidden_act = projector_hidden_act
213
+ self.projector_ln_eps = projector_ln_eps
214
+ self.text_hidden_size = text_hidden_size
215
+
216
+ # vision tower parameters
217
+ self.qkv_hidden_size = qkv_hidden_size
218
+ self.norm_type = norm_type
219
+ self.attn_bias = attn_bias
220
+ self.patch_embed_proj_bias = patch_embed_proj_bias
221
+ self.mlp_type = mlp_type
222
+ self.linear_bias = linear_bias
223
+ self.activation_func = activation_func
224
+ self.pos_emb_interpolation_mode = pos_emb_interpolation_mode
225
+
226
+ super().__init__(**kwargs)
227
+
228
+
229
+ class KimiK3Config(PretrainedConfig):
230
+ """Kimi-K3 model configuration.
231
+
232
+ Args:
233
+ text_config (dict | KimiLinearConfig): Configuration for the text model.
234
+
235
+ Vision Tower Parameters (from MoonViT3dConfig):
236
+ patch_size (int): Patch size for vision tower.
237
+ init_pos_emb_height (int): Initial position embedding height.
238
+ init_pos_emb_width (int): Initial position embedding width.
239
+ init_pos_emb_time (int): Initial position embedding time dimension.
240
+ pos_emb_type (str): Type of position embedding.
241
+ vt_num_attention_heads (int): Number of attention heads in vision tower.
242
+ vt_num_hidden_layers (int): Number of hidden layers in vision tower.
243
+ vt_hidden_size (int): Hidden size of vision tower.
244
+ vt_intermediate_size (int): Intermediate size in vision tower FFN.
245
+ merge_kernel_size (tuple): Kernel size for patch merging.
246
+ merge_type (str): Type of merge operation.
247
+ _attn_implementation (str): Attention implementation type.
248
+
249
+ MM Projector Parameters (from MultiModalProjectorConfig):
250
+ mm_projector_type (str): Type of multimodal projector.
251
+ mm_hidden_size (int): Hidden size from vision tower (should match vt_hidden_size).
252
+ projector_hidden_act (str): Activation function for projector.
253
+ projector_ln_eps (float): Layer norm epsilon for projector.
254
+
255
+ Other Parameters:
256
+ ignore_index (int): The ignore index for the loss function.
257
+ media_placeholder_token_id (int): The token ID to use for media placeholders.
258
+ pad_token_id (int): The token ID to use for padding.
259
+ """
260
+
261
+ model_type = "kimi_k3"
262
+
263
+ def __init__(
264
+ self,
265
+ text_config: dict | KimiLinearConfig = None,
266
+ vision_config: dict | KimiK3VisionConfig = None,
267
+ # Other parameters
268
+ ignore_index: int = -100,
269
+ media_placeholder_token_id: int = 163605,
270
+ pad_token_id: int = 0,
271
+ **kwargs,
272
+ ):
273
+ if isinstance(text_config, dict):
274
+ text_config = KimiLinearConfig(**text_config)
275
+ if isinstance(vision_config, dict):
276
+ vision_config = KimiK3VisionConfig(**vision_config)
277
+ self.text_config = text_config
278
+ self.vision_config = vision_config
279
+ # Other config
280
+ self.ignore_index = ignore_index
281
+ self.media_placeholder_token_id = media_placeholder_token_id
282
+ if getattr(self.text_config, "quantization_config", None) is not None:
283
+ self.quantization_config = self.text_config.quantization_config
284
+
285
+ super().__init__(pad_token_id=pad_token_id, **kwargs)
encoding_k3.py ADDED
@@ -0,0 +1,647 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ def __init__(self, image_prompts: Optional[list[str]] = None):
32
+ self.image_prompts = image_prompts
33
+ self.index = 0
34
+
35
+ def next_prompt(self) -> str:
36
+ if self.image_prompts is None:
37
+ return IMAGE_PLACEHOLDER
38
+ if self.index >= len(self.image_prompts):
39
+ raise ValueError("More image placeholders than image prompts.")
40
+ prompt = self.image_prompts[self.index]
41
+ self.index += 1
42
+ return prompt
43
+
44
+ def assert_consumed(self) -> None:
45
+ if self.image_prompts is None:
46
+ return
47
+ if self.index != len(self.image_prompts):
48
+ raise ValueError(
49
+ f"image prompt count {len(self.image_prompts)} != "
50
+ f"consumed placeholder count {self.index}"
51
+ )
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(_segment(image_state.next_prompt(),
86
+ 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 (
95
+ _text(f" {key}")
96
+ + _text('="')
97
+ + _text(_escape_attr_value(value))
98
+ + _text('"')
99
+ )
100
+
101
+
102
+ def _open_tag(tag: str, attrs: Iterable[tuple[str, Any]] = ()) -> list[EncodeSegment]:
103
+ segments: list[EncodeSegment] = []
104
+ segments.extend(_control(OPEN_TOKEN))
105
+ segments.extend(_text(tag))
106
+ for key, value in attrs:
107
+ segments.extend(_attr(key, value))
108
+ segments.extend(_control(SEP_TOKEN))
109
+ return segments
110
+
111
+ def _close_tag(tag: str) -> list[EncodeSegment]:
112
+ segments: list[EncodeSegment] = []
113
+ segments.extend(_control(CLOSE_TOKEN))
114
+ segments.extend(_text(tag))
115
+ segments.extend(_control(SEP_TOKEN))
116
+ return segments
117
+
118
+
119
+ def _end_of_msg() -> list[EncodeSegment]:
120
+ return _control(END_OF_MSG_TOKEN)
121
+
122
+
123
+ def _json_compact(value: Any) -> str:
124
+ return json.dumps(value, ensure_ascii=False, separators=(",", ":"))
125
+
126
+
127
+ def _is_mapping(value: Any) -> bool:
128
+ return isinstance(value, dict)
129
+
130
+
131
+ def _xtml_type(value: Any) -> str:
132
+ if isinstance(value, bool):
133
+ return "boolean"
134
+ if value is None:
135
+ return "null"
136
+ if isinstance(value, (int, float)) and not isinstance(value, bool):
137
+ return "number"
138
+ if isinstance(value, str):
139
+ return "string"
140
+ if _is_mapping(value):
141
+ return "object"
142
+ return "array"
143
+
144
+
145
+ def _xtml_value(value: Any) -> str:
146
+ if isinstance(value, str):
147
+ return value
148
+ return json.dumps(value, ensure_ascii=False)
149
+
150
+
151
+ def _get_value(obj: Any, key: str, default: Any = None) -> Any:
152
+ if isinstance(obj, dict):
153
+ return obj.get(key, default)
154
+ return getattr(obj, key, default)
155
+
156
+
157
+ def extract_response_schema(response_format: Any) -> Any:
158
+ if response_format is None:
159
+ return None
160
+
161
+ json_schema = _get_value(response_format, "json_schema")
162
+ if json_schema is None:
163
+ return None
164
+
165
+ if isinstance(json_schema, dict):
166
+ return json_schema.get(
167
+ "schema",
168
+ json_schema.get("json_schema", json_schema),
169
+ )
170
+
171
+ schema = _get_value(json_schema, "schema")
172
+ if schema is not None:
173
+ return schema
174
+
175
+ schema = _get_value(json_schema, "json_schema")
176
+ if schema is not None:
177
+ return schema
178
+
179
+ return json_schema
180
+
181
+
182
+ def deep_sort_dict(obj: Any) -> Any:
183
+ if isinstance(obj, dict):
184
+ return {k: deep_sort_dict(v) for k, v in sorted(obj.items())}
185
+ if isinstance(obj, list):
186
+ return [deep_sort_dict(item) for item in obj]
187
+ return obj
188
+
189
+
190
+ def normalize_tool_arguments(arguments: Any) -> tuple[dict[str, Any], Optional[str]]:
191
+ if arguments is None:
192
+ return {}, None
193
+ if isinstance(arguments, dict):
194
+ return arguments, None
195
+ if isinstance(arguments, str):
196
+ if not arguments.strip():
197
+ return {}, None
198
+ try:
199
+ parsed = json.loads(arguments)
200
+ except json.JSONDecodeError:
201
+ return {}, arguments
202
+ if not isinstance(parsed, dict):
203
+ raise ValueError("Kimi K3 tool call arguments must be a JSON object.")
204
+ return parsed, None
205
+ raise TypeError(
206
+ "Kimi K3 tool call arguments must be a dict or a JSON object string."
207
+ )
208
+
209
+
210
+ def normalize_message(message: Any) -> Any:
211
+ if not isinstance(message, dict):
212
+ return message
213
+
214
+ normalized = dict(message)
215
+
216
+ tools = normalized.get("tools")
217
+ if tools is not None:
218
+ normalized["tools"] = deep_sort_dict(tools)
219
+
220
+ tool_calls = normalized.get("tool_calls")
221
+ if not tool_calls:
222
+ return normalized
223
+
224
+ normalized_calls = []
225
+ for tool_call in tool_calls:
226
+ if not isinstance(tool_call, dict):
227
+ normalized_calls.append(tool_call)
228
+ continue
229
+
230
+ tc = dict(tool_call)
231
+ function = tc.get("function")
232
+ if isinstance(function, dict):
233
+ fn = dict(function)
234
+ arguments, json_block = normalize_tool_arguments(fn.get("arguments"))
235
+ fn["arguments"] = arguments
236
+ if json_block is None:
237
+ fn.pop("_xtml_json_block", None)
238
+ else:
239
+ fn["_xtml_json_block"] = json_block
240
+ tc["function"] = fn
241
+ else:
242
+ arguments, json_block = normalize_tool_arguments(tc.get("arguments"))
243
+ tc["arguments"] = arguments
244
+ if json_block is None:
245
+ tc.pop("_xtml_json_block", None)
246
+ else:
247
+ tc["_xtml_json_block"] = json_block
248
+ normalized_calls.append(tc)
249
+
250
+ normalized["tool_calls"] = normalized_calls
251
+ return normalized
252
+
253
+
254
+ def normalize_conversation(conversation: Any) -> Any:
255
+ if not isinstance(conversation, list):
256
+ return conversation
257
+
258
+ def normalize_messages(messages: list[Any]) -> list[Any]:
259
+ return [normalize_message(message) for message in messages]
260
+
261
+ if conversation and isinstance(conversation[0], list):
262
+ return [normalize_messages(messages) for messages in conversation]
263
+ return normalize_messages(conversation)
264
+
265
+
266
+ def _tool_call_id_index(tool_calls: Any) -> dict:
267
+ """Map assistant ``tool_calls[].id`` to ``(1-based position, function name)``.
268
+
269
+ The position mirrors the chat template's enumeration over ``tool_calls``
270
+ (every entry advances the position, even an id-less one). Duplicate ids keep
271
+ their first occurrence.
272
+ """
273
+ index: dict = {}
274
+ if not isinstance(tool_calls, list):
275
+ return index
276
+ for position, tool_call in enumerate(tool_calls, start=1):
277
+ if not isinstance(tool_call, dict):
278
+ continue
279
+ call_id = tool_call.get("id")
280
+ if call_id is None:
281
+ continue
282
+ key = str(call_id)
283
+ if key in index:
284
+ continue
285
+ function = tool_call.get("function")
286
+ name = (
287
+ function.get("name") if isinstance(function, dict) else tool_call.get("name")
288
+ )
289
+ index[key] = (position, name)
290
+ return index
291
+
292
+
293
+ def normalize_xtml_tool_result_messages(messages: list[Any]) -> list[Any]:
294
+ """Re-sort K3 XTML tool results into assistant ``tool_calls`` order.
295
+
296
+ Serving frameworks generally deliver tool results already in call order. A
297
+ direct Transformers caller, however, may pass OpenAI-style tool messages in any
298
+ order, so each run of consecutive tool messages is matched against the most
299
+ recent preceding assistant ``tool_calls`` by opaque ``tool_call_id`` ==
300
+ ``tool_calls[].id`` (K3 drops the ``func:index`` format requirement) and
301
+ sorted by the matched 1-based position. The matched call is authoritative,
302
+ so each matched message's ``tool`` is set to that call's function name --
303
+ this keeps an explicit (and possibly stale) ``tool``/``name`` from drifting
304
+ out of sync with the reordered position. ``index`` is still derived from the
305
+ rendered position by the chat template. A run that cannot be fully matched is
306
+ left untouched. Re-running is idempotent.
307
+
308
+ This function is side-effect free: matched tool messages are shallow-copied
309
+ before their ``tool``/``name`` is rewritten, and every other message is
310
+ appended to the output as-is. The input list and its message objects are
311
+ never mutated.
312
+ """
313
+ if not isinstance(messages, list):
314
+ return messages
315
+
316
+ output: list[Any] = []
317
+ current_index: dict = {}
318
+ i = 0
319
+ n = len(messages)
320
+
321
+ while i < n:
322
+ message = messages[i]
323
+
324
+ if isinstance(message, dict) and message.get("role") == "assistant":
325
+ tool_calls = message.get("tool_calls")
326
+ current_index = _tool_call_id_index(tool_calls) if tool_calls else {}
327
+ output.append(message)
328
+ i += 1
329
+ continue
330
+
331
+ if not isinstance(message, dict) or message.get("role") != "tool":
332
+ output.append(message)
333
+ i += 1
334
+ continue
335
+
336
+ run: list[tuple] = [] # (position, original_offset, message, name)
337
+ unresolved = False
338
+ offset = 0
339
+ while (
340
+ i < n and isinstance(messages[i], dict) and messages[i].get("role") == "tool"
341
+ ):
342
+ tool_message = messages[i]
343
+ call_id = tool_message.get("tool_call_id", tool_message.get("id"))
344
+ matched = current_index.get(str(call_id)) if call_id is not None else None
345
+ if matched is None:
346
+ unresolved = True
347
+ run.append((None, offset, tool_message, None))
348
+ else:
349
+ position, name = matched
350
+ run.append((position, offset, tool_message, name))
351
+ offset += 1
352
+ i += 1
353
+
354
+ if unresolved:
355
+ output.extend(item[2] for item in run)
356
+ else:
357
+ run.sort(key=lambda item: (item[0], item[1]))
358
+ for _, _, tool_message, name in run:
359
+ if name is None:
360
+ output.append(tool_message)
361
+ continue
362
+ # The id-matched call is authoritative: align tool (and any
363
+ # explicit name) so the rendered XTML tool attribute cannot
364
+ # disagree with the reordered position. Copy first so the
365
+ # caller's message object is never mutated.
366
+ resolved = dict(tool_message)
367
+ resolved["tool"] = name
368
+ if "name" in resolved:
369
+ resolved["name"] = name
370
+ output.append(resolved)
371
+
372
+ return output
373
+
374
+
375
+ def is_batched_conversation(conversation: Any) -> bool:
376
+ return (
377
+ isinstance(conversation, list)
378
+ and bool(conversation)
379
+ and isinstance(conversation[0], list)
380
+ )
381
+
382
+
383
+ def _render_content_segments(
384
+ content: Any,
385
+ image_state: _ImagePromptState,
386
+ ) -> list[EncodeSegment]:
387
+ segments: list[EncodeSegment] = []
388
+ if isinstance(content, str):
389
+ _append_text(segments, content, image_state)
390
+ elif content is not None:
391
+ for part in content:
392
+ if part["type"] in ["image", "image_url"]:
393
+ segments.extend(
394
+ _segment(image_state.next_prompt(), allow_special=True))
395
+ else:
396
+ _append_text(segments, part["text"], image_state)
397
+ return segments
398
+
399
+
400
+ def _internal_system_message(message_type: str, body: str) -> list[EncodeSegment]:
401
+ segments: list[EncodeSegment] = []
402
+ segments.extend(_open_tag("message", [("role", "system"), ("type", message_type)]))
403
+ segments.extend(_text(body.strip()))
404
+ segments.extend(_close_tag("message"))
405
+ segments.extend(_end_of_msg())
406
+ return segments
407
+
408
+
409
+ def _render_assistant_segments(
410
+ message: dict[str, Any],
411
+ image_state: _ImagePromptState,
412
+ thinking: bool = True,
413
+ ) -> list[EncodeSegment]:
414
+ segments: list[EncodeSegment] = []
415
+ # The <think> channel is structural: in thinking mode every assistant
416
+ # message carries the open/close tags even when there is no reasoning
417
+ # content to fill in. In non-thinking mode the channel is dropped
418
+ # entirely.
419
+ if thinking:
420
+ reasoning_content = message.get("reasoning_content") or message.get(
421
+ "reasoning"
422
+ )
423
+ segments.extend(_open_tag("think"))
424
+ if reasoning_content is not None and str(reasoning_content).strip():
425
+ _append_text(segments, reasoning_content, image_state)
426
+ segments.extend(_close_tag("think"))
427
+
428
+ segments.extend(_open_tag("response"))
429
+ segments.extend(_render_content_segments(message.get("content"), image_state))
430
+ segments.extend(_close_tag("response"))
431
+
432
+ tool_calls = message.get("tool_calls")
433
+ if tool_calls:
434
+ segments.extend(_open_tag("tools"))
435
+ for index, tool_call in enumerate(tool_calls, start=1):
436
+ fn = tool_call.get("function", tool_call)
437
+ segments.extend(
438
+ _open_tag("call", [("tool", fn["name"]), ("index", index)])
439
+ )
440
+ args = fn.get("arguments", {})
441
+ json_block = fn.get("_xtml_json_block")
442
+ if json_block is not None:
443
+ segments.extend(_open_tag("json", [("type", "object")]))
444
+ _append_text(segments, json_block, image_state)
445
+ segments.extend(_close_tag("json"))
446
+ elif _is_mapping(args):
447
+ for key, value in args.items():
448
+ segments.extend(
449
+ _open_tag(
450
+ "argument",
451
+ [("key", key), ("type", _xtml_type(value))],
452
+ )
453
+ )
454
+ _append_text(segments, _xtml_value(value), image_state)
455
+ segments.extend(_close_tag("argument"))
456
+ segments.extend(_close_tag("call"))
457
+ segments.extend(_close_tag("tools"))
458
+
459
+ return segments
460
+
461
+
462
+ def _render_tool_declare(tools: Any, *, dynamic: bool = False) -> list[EncodeSegment]:
463
+ if dynamic:
464
+ body = (
465
+ "## New Tools Available\n"
466
+ "The system dynamically extends the toolset via lazy-loading.\n"
467
+ "You have access to all existing and extended tools.\n"
468
+ "Here are the specs for the extended tools.\n\n"
469
+ "```json\n"
470
+ f"{_json_compact(tools)}\n"
471
+ "```"
472
+ )
473
+ else:
474
+ body = (
475
+ "# Tools\n"
476
+ "Here are the available tools, described in JSONSchema.\n\n"
477
+ "```json\n"
478
+ f"{_json_compact(tools)}\n"
479
+ "```"
480
+ )
481
+ segments: list[EncodeSegment] = []
482
+ segments.extend(_open_tag("message", [("role", "system"), ("type", "tool-declare")]))
483
+ segments.extend(_text(body))
484
+ segments.extend(_close_tag("message"))
485
+ segments.extend(_end_of_msg())
486
+ return segments
487
+
488
+
489
+ def build_chat_segments(
490
+ messages: list[Any],
491
+ tools: Optional[list[dict]] = None,
492
+ *,
493
+ add_generation_prompt: bool = True,
494
+ thinking: bool = True,
495
+ image_prompts: Optional[list[str]] = None,
496
+ **kwargs: Any,
497
+ ) -> list[EncodeSegment]:
498
+ # Re-sort tool results by tool_call_id at the lowest layer so every caller
499
+ # (processor or direct tokenizer) gets correctly ordered XTML. The helper is
500
+ # side-effect free, so the caller's message objects are left untouched.
501
+ messages = normalize_xtml_tool_result_messages(messages)
502
+ messages = normalize_conversation(messages)
503
+ tools = deep_sort_dict(tools)
504
+
505
+ kwargs = dict(kwargs)
506
+ response_format = kwargs.get("response_format")
507
+ if "response_schema" not in kwargs:
508
+ response_schema = extract_response_schema(response_format)
509
+ if response_schema is not None:
510
+ kwargs["response_schema"] = response_schema
511
+ if kwargs.get("response_schema") is not None:
512
+ kwargs["response_schema"] = deep_sort_dict(kwargs["response_schema"])
513
+
514
+ image_state = _ImagePromptState(image_prompts)
515
+ segments: list[EncodeSegment] = []
516
+
517
+ tool_calls = None
518
+ tool_index = 0
519
+
520
+ if tools:
521
+ segments.extend(_render_tool_declare(tools))
522
+
523
+ thinking_effort = kwargs.get("thinking_effort")
524
+ if thinking and thinking_effort is not None:
525
+ assert thinking_effort in _VALID_THINKING_EFFORTS, (
526
+ f"Unsupported thinking_effort={thinking_effort!r}; "
527
+ f"supported values are {sorted(_VALID_THINKING_EFFORTS)}."
528
+ )
529
+ if thinking and thinking_effort in _VALID_THINKING_EFFORTS:
530
+ segments.extend(
531
+ _internal_system_message(
532
+ "thinking-effort",
533
+ "`thinking_effort` guides on how much to think in your "
534
+ "thinking channel (not including the response channel), "
535
+ "supported values include `low`, `medium`, `high`, and `max`.\n"
536
+ f"Now the system is invoked with `thinking_effort={thinking_effort}`.",
537
+ )
538
+ )
539
+
540
+ for message_index, message in enumerate(messages):
541
+ if not isinstance(message, dict):
542
+ continue
543
+
544
+ role = message["role"]
545
+ if role == "user":
546
+ attrs = [("role", "user")]
547
+ if message.get("name"):
548
+ attrs.append(("name", message["name"]))
549
+ segments.extend(_open_tag("message", attrs))
550
+ segments.extend(_render_content_segments(message.get("content"), image_state))
551
+ segments.extend(_close_tag("message"))
552
+ segments.extend(_end_of_msg())
553
+ elif role == "system" and message.get("tools"):
554
+ segments.extend(_render_tool_declare(message["tools"], dynamic=True))
555
+ elif role == "system":
556
+ attrs = [("role", "system")]
557
+ if message.get("name"):
558
+ attrs.append(("name", message["name"]))
559
+ segments.extend(_open_tag("message", attrs))
560
+ segments.extend(_render_content_segments(message.get("content"), image_state))
561
+ segments.extend(_close_tag("message"))
562
+ segments.extend(_end_of_msg())
563
+ elif role == "tool":
564
+ tool_index += 1
565
+ tool_name = message.get("tool", message.get("name"))
566
+ if (
567
+ tool_name is None
568
+ and tool_calls is not None
569
+ and tool_index <= len(tool_calls)
570
+ ):
571
+ tc = tool_calls[tool_index - 1]
572
+ fn = tc.get("function", tc)
573
+ tool_name = fn["name"]
574
+ if tool_name is None:
575
+ raise ValueError(
576
+ "Kimi K3 tool messages need a resolvable tool name: "
577
+ "carry `tool`/`name`, or match a preceding assistant "
578
+ "tool_call by order."
579
+ )
580
+ segments.extend(
581
+ _open_tag(
582
+ "message",
583
+ [("role", "tool"), ("tool", tool_name), ("index", tool_index)],
584
+ )
585
+ )
586
+ segments.extend(_render_content_segments(message.get("content"), image_state))
587
+ segments.extend(_close_tag("message"))
588
+ segments.extend(_end_of_msg())
589
+ elif role == "assistant":
590
+ tool_calls = message.get("tool_calls")
591
+ tool_index = 0
592
+ attrs = [("role", "assistant")]
593
+ if message.get("name"):
594
+ attrs.append(("name", message["name"]))
595
+ segments.extend(_open_tag("message", attrs))
596
+ segments.extend(_render_assistant_segments(message, image_state, thinking))
597
+ segments.extend(_close_tag("message"))
598
+ segments.extend(_end_of_msg())
599
+
600
+ tool_choice = kwargs.get("tool_choice")
601
+ if tool_choice == "required":
602
+ segments.extend(
603
+ _internal_system_message(
604
+ "tool-choice",
605
+ "The system is invoked with `tool_choice=required`.\n"
606
+ "You MUST call tools in the next message.",
607
+ )
608
+ )
609
+ elif tool_choice == "none":
610
+ segments.extend(
611
+ _internal_system_message(
612
+ "tool-choice",
613
+ "The system is invoked with `tool_choice=none`.\n"
614
+ "You MUST NOT call any tools in the next message.",
615
+ )
616
+ )
617
+
618
+ rf = kwargs.get("response_format")
619
+ rf_type = _get_value(rf, "type", rf) if isinstance(rf, dict) else rf
620
+ if rf_type == "json_object":
621
+ segments.extend(
622
+ _internal_system_message(
623
+ "response-format",
624
+ "The system is invoked with `response_format=json_object`.\n"
625
+ "Your response must be raw JSON data without markdown code "
626
+ "blocks (```json) or any additional formatting.",
627
+ )
628
+ )
629
+ elif rf_type == "json_schema":
630
+ schema = _json_compact(kwargs.get("response_schema"))
631
+ segments.extend(
632
+ _internal_system_message(
633
+ "response-format",
634
+ "The system is invoked with `response_format=json_schema`.\n"
635
+ "Your response must be raw JSON data without markdown code "
636
+ "blocks (```json) or any additional formatting.\n"
637
+ "The JSON data must match the following schema:\n"
638
+ f"```json\n{schema}\n```",
639
+ )
640
+ )
641
+
642
+ if add_generation_prompt:
643
+ segments.extend(_open_tag("message", [("role", "assistant")]))
644
+ segments.extend(_open_tag("think" if thinking else "response"))
645
+
646
+ image_state.assert_consumed()
647
+ return segments
generation_config.json ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ {
2
+ "eos_token_id": 163586,
3
+ "max_length": 1048576,
4
+ "transformers_version": "5.15.0.dev0",
5
+ "trust_remote_code": true
6
+ }
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.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:8ec5cd600c8c3c487654d1d59bebb57e3bbce3ce28cb415b564cb154f62424db
3
+ size 133512888
modeling_kimi_k3.py ADDED
@@ -0,0 +1,1323 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # coding=utf-8
2
+ # Copyright 2025-2026 The Moonshot AI Team and HuggingFace Inc. team. All rights reserved.
3
+ #
4
+ # The code is based on llava (llava/modeling_llava.py), but modified for Kimi-K3.
5
+ #
6
+ # Licensing Information:
7
+ # - Code derived from llava (llava/modeling_llava.py) is licensed under the Apache License, Version 2.0.
8
+ # - Other parts of the code are licensed under the Kimi K3 License (see the LICENSE file in this repository).
9
+ #
10
+ # Apache License, Version 2.0:
11
+ # Licensed under the Apache License, Version 2.0 (the "License");
12
+ # you may not use this file except in compliance with the License.
13
+ # You may obtain a copy of the License at
14
+ #
15
+ # http://www.apache.org/licenses/LICENSE-2.0
16
+ #
17
+ # Unless required by applicable law or agreed to in writing, software
18
+ # distributed under the License is distributed on an "AS IS" BASIS,
19
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20
+ # See the License for the specific language governing permissions and
21
+ # limitations under the License.
22
+
23
+
24
+ # NOTE: Reference implementation for model architecture; see the model card for production deployment.
25
+ import math
26
+ from collections.abc import Sequence
27
+ from copy import deepcopy
28
+ from typing import Optional
29
+
30
+ import numpy as np
31
+ import torch
32
+ import torch.nn as nn
33
+ import torch.nn.functional as F
34
+ from transformers import activations
35
+
36
+ try:
37
+ from transformers.activations import PytorchGELUTanh
38
+ except ImportError:
39
+ from transformers.activations import GELUTanh
40
+ activations.PytorchGELUTanh = GELUTanh
41
+ PytorchGELUTanh = GELUTanh
42
+ from transformers.activations import PytorchGELUTanh
43
+ from transformers.configuration_utils import PretrainedConfig
44
+ from transformers.modeling_utils import PreTrainedModel
45
+ from transformers.models.llava.modeling_llava import \
46
+ LlavaCausalLMOutputWithPast
47
+ from transformers.utils import is_flash_attn_2_available
48
+
49
+ from .configuration_kimi_k3 import KimiK3Config
50
+ from .modeling_kimi_linear import KimiLinearForCausalLM
51
+
52
+ # Flash attention imports
53
+ if is_flash_attn_2_available():
54
+ from flash_attn import flash_attn_varlen_func
55
+ else:
56
+ flash_attn_varlen_func = None
57
+
58
+
59
+ def multihead_attention(
60
+ q: torch.Tensor,
61
+ k: torch.Tensor,
62
+ v: torch.Tensor,
63
+ q_cu_seqlens: torch.Tensor | None = None,
64
+ k_cu_seqlens: torch.Tensor | None = None,
65
+ max_seqlen_q: int | None = None,
66
+ max_seqlen_k: int | None = None,
67
+ deterministic: bool = False,
68
+ ):
69
+ """Multi-head attention using flash attention 2.
70
+
71
+ Args:
72
+ q, k, v: tensor of shape (batch_size, seqlen, num_heads, head_dim),
73
+ or (tot_seqlens, num_heads, head_dim) if packing.
74
+ q_cu_seqlens (torch.Tensor): cumulative sequence lengths of q.
75
+ The first element should be 0 and the last element should be q.shape[0].
76
+ k_cu_seqlens (torch.Tensor): cumulative sequence lengths of k.
77
+ The first element should be 0 and the last element should be k.shape[0].
78
+
79
+ Returns:
80
+ output: shape (batch_size, seqlen, dim) or (tot_seqlens, dim) if packing,
81
+ where dim = num_heads * head_dim
82
+ """
83
+ attn_out = flash_attn_varlen_func(
84
+ q,
85
+ k,
86
+ v,
87
+ q_cu_seqlens,
88
+ k_cu_seqlens,
89
+ max_seqlen_q,
90
+ max_seqlen_k,
91
+ causal=False,
92
+ deterministic=deterministic,
93
+ )
94
+ if isinstance(attn_out, tuple):
95
+ attn_out = attn_out[0]
96
+
97
+ attn_out = attn_out.flatten(start_dim=-2)
98
+
99
+ return attn_out
100
+
101
+
102
+ def eager_attention(
103
+ q: torch.Tensor,
104
+ k: torch.Tensor,
105
+ v: torch.Tensor,
106
+ q_cu_seqlens: Optional[torch.Tensor] = None,
107
+ k_cu_seqlens: Optional[torch.Tensor] = None,
108
+ **kwargs,
109
+ ) -> torch.Tensor:
110
+ seq_length = q.shape[0]
111
+ attention_mask = torch.zeros([1, seq_length, seq_length],
112
+ device=q.device,
113
+ dtype=torch.bool)
114
+ for i in range(1, len(q_cu_seqlens)):
115
+ attention_mask[
116
+ ...,
117
+ q_cu_seqlens[i - 1]:q_cu_seqlens[i],
118
+ q_cu_seqlens[i - 1]:q_cu_seqlens[i],
119
+ ] = True
120
+ q = q.transpose(0, 1)
121
+ k = k.transpose(0, 1)
122
+ v = v.transpose(0, 1)
123
+
124
+ attn_weight = q @ k.transpose(-2, -1) / math.sqrt(q.shape[-1])
125
+ attn_weight = attn_weight.masked_fill(
126
+ ~attention_mask, torch.finfo(attn_weight.dtype).min)
127
+ attn_weight = torch.softmax(attn_weight, dim=-1,
128
+ dtype=torch.float32).to(q.dtype)
129
+
130
+ attn_output = attn_weight @ v
131
+ attn_output = attn_output.transpose(0, 1)
132
+ attn_output = attn_output.reshape(seq_length, -1)
133
+ return attn_output
134
+
135
+
136
+ VL_VISION_ATTENTION_FUNCTIONS = {
137
+ "flash_attention_2": multihead_attention,
138
+ "eager": eager_attention,
139
+ }
140
+
141
+
142
+ def _apply_rope_input_validation(x, freqs_cis):
143
+ assert x.ndim == freqs_cis.ndim + 1, (x.shape, freqs_cis.shape)
144
+ assert x.shape[:-2] == freqs_cis.shape[:-1], (x.shape, freqs_cis.shape)
145
+ assert x.shape[-1] == 2 * freqs_cis.shape[-1], (x.shape, freqs_cis.shape)
146
+ assert freqs_cis.dtype == torch.complex64, freqs_cis.dtype
147
+
148
+
149
+ def get_rope_shape_decorate(func):
150
+ _get_rope_shape_first_call_flag = set()
151
+
152
+ def wrapper(org, interpolation_mode, shape):
153
+ key = (org.requires_grad, torch.is_grad_enabled(), interpolation_mode)
154
+ if key not in _get_rope_shape_first_call_flag:
155
+ _get_rope_shape_first_call_flag.add(key)
156
+ _ = func(org, interpolation_mode, shape=(64, 64))
157
+ return func(org, interpolation_mode, shape)
158
+
159
+ return wrapper
160
+
161
+
162
+ @get_rope_shape_decorate
163
+ @torch.compile(dynamic=True)
164
+ def get_rope_shape(org, interpolation_mode, shape):
165
+ return (F.interpolate(
166
+ org.permute((2, 0, 1)).unsqueeze(0),
167
+ size=shape,
168
+ mode=interpolation_mode,
169
+ ).squeeze(0).permute((1, 2, 0)).flatten(end_dim=1))
170
+
171
+
172
+ def apply_rope(xq: torch.Tensor, xk: torch.Tensor,
173
+ freqs_cis: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor]:
174
+ """
175
+ Args: (The leading dimensions of all inputs should be the same)
176
+ xq: query, tensor of shape (..., num_heads, head_dim)
177
+ xk: key, tensor of shape (..., num_heads, head_dim)
178
+ freqs_cis: tensor of shape (..., head_dim/2), dtype=torch.complex64. It contains the precomputed cis(freqs) for each position in the 2D grid.
179
+ Returns:
180
+ xq_out, xk_out: tensors of shape (..., num_heads, head_dim)
181
+ """
182
+ _apply_rope_input_validation(xq, freqs_cis)
183
+ _apply_rope_input_validation(xk, freqs_cis)
184
+
185
+ freqs_cis = freqs_cis.unsqueeze(-2) # ..., 1, head_dim/2
186
+ # ..., num_heads, head_dim/2
187
+ xq_ = torch.view_as_complex(xq.float().view(*xq.shape[:-1], -1, 2))
188
+ xk_ = torch.view_as_complex(xk.float().view(*xq.shape[:-1], -1, 2))
189
+ xq_out = torch.view_as_real(xq_ * freqs_cis).flatten(
190
+ -2) # ..., num_heads, head_dim
191
+ xk_out = torch.view_as_real(xk_ * freqs_cis).flatten(
192
+ -2) # ..., num_heads, head_dim
193
+ return xq_out.type_as(xq), xk_out.type_as(xk)
194
+
195
+
196
+ def get_1d_sincos_pos_embed_from_grid(embed_dim, pos):
197
+ """
198
+ From:
199
+ https://github.com/OpenGVLab/InternVideo/blob/421f6d2361fc8f61a3394244571f2601a4e99e29/InternVideo2/multi_modality/models/backbones/internvideo2/pos_embed.py#L86
200
+ embed_dim: output dimension for each position
201
+ pos: a list of positions to be encoded: size (M,)
202
+ out: (M, D)
203
+ """
204
+ assert embed_dim % 2 == 0
205
+ omega = np.arange(embed_dim // 2, dtype=np.float32)
206
+ omega /= embed_dim / 2.0
207
+ omega = 1.0 / 10000**omega # (D/2,)
208
+
209
+ pos = pos.reshape(-1) # (M,)
210
+ out = np.einsum('m,d->md', pos, omega) # (M, D/2), outer product
211
+
212
+ emb_sin = np.sin(out) # (M, D/2)
213
+ emb_cos = np.cos(out) # (M, D/2)
214
+
215
+ emb = np.concatenate([emb_sin, emb_cos], axis=1) # (M, D)
216
+ return emb
217
+
218
+
219
+ def get_1d_sincos_pos_embed(embed_dim, t_size, cls_token=False):
220
+ """
221
+ t_size: int of the temporal size
222
+ return:
223
+ pos_embed: [t_size, embed_dim] or [1+t_size, embed_dim] (w/ or w/o cls_token)
224
+ """
225
+ grid_t = np.arange(t_size, dtype=np.float32)
226
+ pos_embed = get_1d_sincos_pos_embed_from_grid(embed_dim, grid_t)
227
+ if cls_token:
228
+ pos_embed = np.concatenate([np.zeros([1, embed_dim]), pos_embed],
229
+ axis=0)
230
+ return pos_embed
231
+
232
+
233
+ class Learnable2DInterpPosEmbDivided_fixed(nn.Module):
234
+
235
+ def __init__(self,
236
+ height: int,
237
+ width: int,
238
+ num_frames: int,
239
+ dim: int,
240
+ interpolation_mode: str = 'bicubic') -> None:
241
+ super().__init__()
242
+ self.height = height
243
+ self.width = width
244
+ self.num_frames = num_frames
245
+ self.dim = dim
246
+ self.interpolation_mode = interpolation_mode
247
+ self.weight = nn.Parameter(torch.empty(height, width, dim))
248
+ self.register_buffer('time_weight',
249
+ torch.from_numpy(
250
+ get_1d_sincos_pos_embed(
251
+ self.dim,
252
+ self.num_frames)).float().unsqueeze(1),
253
+ persistent=False)
254
+
255
+ self.reset_parameters()
256
+
257
+ def reset_parameters(self):
258
+ nn.init.normal_(self.weight)
259
+
260
+ def forward(self, x: torch.Tensor,
261
+ grid_thws: torch.Tensor) -> torch.Tensor:
262
+ pos_embs = []
263
+ for t, h, w in grid_thws.tolist():
264
+ assert t <= self.num_frames, f't:{t} > self.num_frames:{self.num_frames}'
265
+ if (h, w) == self.weight.shape[:-1]:
266
+ pos_emb_2d = self.weight.flatten(end_dim=1)
267
+ else:
268
+ pos_emb_2d = get_rope_shape(
269
+ self.weight,
270
+ interpolation_mode=self.interpolation_mode,
271
+ shape=(h, w),
272
+ )
273
+
274
+ if t == 1:
275
+ pos_emb_3d = pos_emb_2d
276
+ else:
277
+ pos_emb_3d = pos_emb_2d.unsqueeze(0).repeat(
278
+ t, 1, 1) + self.time_weight[0:t]
279
+
280
+ pos_embs.append(pos_emb_3d.reshape(-1, pos_emb_3d.shape[-1]))
281
+
282
+ out = x + torch.cat(pos_embs)
283
+ return out
284
+
285
+
286
+ class MoonVision3dPatchEmbed(nn.Module):
287
+
288
+ def __init__(self,
289
+ out_dim: int,
290
+ in_dim: int = 3,
291
+ patch_size: int | tuple[int, int] = (14, 14),
292
+ pos_emb_height: int = 14,
293
+ pos_emb_width: int = 14,
294
+ pos_emb_time: int = 4,
295
+ pos_emb_type: str = 'divided_fixed',
296
+ patch_embed_proj_bias: bool = True,
297
+ pos_emb_interpolation_mode: str = 'bicubic'):
298
+ super().__init__()
299
+ assert isinstance(
300
+ patch_size,
301
+ int | Sequence), f'Invalid patch_size type: {type(patch_size)}'
302
+ if isinstance(patch_size, int):
303
+ patch_size = (patch_size, patch_size)
304
+ assert (len(patch_size) == 2
305
+ ), f'Expected patch_size to be a tuple of 2, got {patch_size}'
306
+ self.patch_size = patch_size
307
+
308
+ self.proj = nn.Conv2d(in_dim,
309
+ out_dim,
310
+ kernel_size=patch_size,
311
+ stride=patch_size,
312
+ bias=patch_embed_proj_bias)
313
+
314
+ if pos_emb_type == 'divided_fixed':
315
+ self.pos_emb = Learnable2DInterpPosEmbDivided_fixed(
316
+ height=pos_emb_height,
317
+ width=pos_emb_width,
318
+ num_frames=pos_emb_time,
319
+ dim=out_dim,
320
+ interpolation_mode=pos_emb_interpolation_mode)
321
+ else:
322
+ raise NotImplementedError(
323
+ f'Not support pos_emb_type: {pos_emb_type}')
324
+
325
+ def forward(self, x: torch.Tensor,
326
+ grid_thws: torch.Tensor) -> torch.Tensor:
327
+ """
328
+ Args:
329
+ x (L, Channels): input tensor
330
+ grid_hws (N, 3): temporal, height and width
331
+
332
+ Returns:
333
+ (L, Cout) tensor
334
+ """
335
+ x = self.proj(x).view(x.size(0), -1)
336
+ # apply positional embedding
337
+ x = self.pos_emb(x, grid_thws)
338
+ return x
339
+
340
+
341
+ class Rope2DPosEmbRepeated(nn.Module):
342
+ """2D rotary position embedding with multi-resolution support.
343
+
344
+ This class is intended to be used in the following way:
345
+ 1. Before training, create an instance of Rope2DPosEmb. This instance will hold the precomputed cis.
346
+ 2. Before each forward pass, call `get_freqs_cis_by_*` to get the `freqs_cis` tensor for this iteration.
347
+ 3. During the forward pass, pass the `freqs_cis` tensor to each attention layer, and call `apply` just before each attention operation.
348
+ The rope is shared across all attention layers and all heads.
349
+
350
+ Refs:
351
+ - RoFormer: https://arxiv.org/abs/2104.09864
352
+ - VisionLLaMA: https://arxiv.org/abs/2403.00522
353
+ - https://github.com/Meituan-AutoML/VisionLLaMA/blob/main/dit/models.py
354
+
355
+ Args:
356
+ dim (int): usually the multi-head attention dimension, should be divisible by 4 (TODO: relax this constraint if needed)
357
+ max_height (int): the maximum height of the 2D grid
358
+ max_width (int): the maximum width of the 2D grid
359
+ theta_base (float): the base of the theta
360
+ device (str): the device to store the precomputed cis
361
+ """
362
+
363
+ def __init__(self,
364
+ dim: int,
365
+ max_height: int,
366
+ max_width: int,
367
+ theta_base=10000):
368
+ super().__init__()
369
+ self.dim = dim
370
+ assert self.dim % 4 == 0, 'dim must be divisible by 4'
371
+ self.max_height = max_height
372
+ self.max_width = max_width
373
+ self.theta_base = theta_base
374
+
375
+ def extra_repr(self):
376
+ return f'dim={self.dim}, max_height={self.max_height}, max_width={self.max_width}, theta_base={self.theta_base}'
377
+
378
+ def _precompute_freqs_cis(self, device: torch.device) -> torch.Tensor:
379
+ """Calculate the cis(freqs) for each position in the 2D grid.
380
+
381
+ Return: complex tensor of shape (max_height, max_width, dim//2) and value:
382
+ height axis: ret[h, w, 2*i] = cis(h * theta_base**(-4*i/dim))
383
+ weight axis: ret[h, w, 2*i+1] = cis(w * theta_base**(-4*i/dim)) with (i in [0, dim//4))
384
+ note: `cis` is a mathematical notation defined by cis x = cos x + i sin x,
385
+ """
386
+ N = self.max_height * self.max_width
387
+ flat_pos = torch.arange(0, N).float().to(device)
388
+ x_pos = flat_pos % self.max_width
389
+ y_pos = flat_pos // self.max_width
390
+ dim_range = (torch.arange(0, self.dim,
391
+ 4)[:(self.dim // 4)].float().to(device)
392
+ ) # C/4
393
+ freqs = 1.0 / (self.theta_base**(dim_range / self.dim))
394
+ x_freqs = torch.outer(x_pos, freqs).float() # N, C/4
395
+ y_freqs = torch.outer(y_pos, freqs).float() # N, C/4
396
+ x_cis = torch.polar(torch.ones_like(x_freqs), x_freqs) # N, C/4
397
+ y_cis = torch.polar(torch.ones_like(y_freqs), y_freqs) # N, C/4
398
+ # N, C/4, 2
399
+ freqs_cis = torch.cat(
400
+ [x_cis.unsqueeze(dim=-1),
401
+ y_cis.unsqueeze(dim=-1)], dim=-1)
402
+ # max_height, max_width, C/2
403
+ freqs_cis = freqs_cis.reshape(self.max_height, self.max_width, -1)
404
+ return freqs_cis
405
+
406
+ def get_freqs_cis(self, grid_thws: torch.Tensor,
407
+ device: torch.device) -> torch.Tensor:
408
+ """
409
+ Args:
410
+ grid_thws (torch.Tensor): grid time, height and width
411
+
412
+ Returns:
413
+ freqs_cis: tensor of shape (sum(t * height * width), dim//2)
414
+ """
415
+ if not hasattr(self, 'freqs_cis'):
416
+ self.register_buffer('freqs_cis',
417
+ self._precompute_freqs_cis(device),
418
+ persistent=False)
419
+
420
+ shapes = grid_thws.tolist()
421
+ assert all(1 <= h <= self.max_height and 1 <= w <= self.max_width
422
+ for t, h, w in shapes), (
423
+ shapes,
424
+ self.max_height,
425
+ self.max_width,
426
+ )
427
+ freqs_cis = torch.cat(
428
+ [
429
+ self.freqs_cis[:h, :w].reshape(-1, self.dim // 2).repeat(t, 1)
430
+ for t, h, w in shapes
431
+ ],
432
+ dim=0,
433
+ )
434
+ return freqs_cis
435
+
436
+
437
+ class MLP2(nn.Module):
438
+ """
439
+ Args:
440
+ dims: [in_dim, hidden_dim, out_dim]
441
+ bias: whether to use bias in linear layer.
442
+ """
443
+
444
+ def __init__(self, dims: list[int], activation, bias=True):
445
+ super().__init__()
446
+ assert len(dims) == 3
447
+ self.fc0 = nn.Linear(dims[0], dims[1], bias=bias)
448
+ self.fc1 = nn.Linear(dims[1], dims[2], bias=bias)
449
+ self.activation = activation
450
+ for m in [self.fc0, self.fc1]:
451
+ nn.init.trunc_normal_(m.weight, std=math.sqrt(2 / m.in_features))
452
+ if m.bias is not None:
453
+ nn.init.zeros_(m.bias)
454
+
455
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
456
+ x = self.fc0(x)
457
+ x = self.activation(x)
458
+ return self.fc1(x)
459
+
460
+
461
+ class MoonViTEncoderLayer(nn.Module):
462
+
463
+ def __init__(
464
+ self,
465
+ num_heads: int,
466
+ hidden_dim: int,
467
+ mlp_dim: int,
468
+ qkv_hidden_size: int | None = None,
469
+ norm_type: str = 'layernorm',
470
+ mlp_type: str = 'mlp2',
471
+ *,
472
+ attn_implementation: str = 'flash_attention_2',
473
+ activation=F.gelu,
474
+ attn_bias: bool = False,
475
+ linear_bias: bool = True,
476
+ use_deterministic_attn: bool = False,
477
+ ):
478
+ super().__init__()
479
+ self.num_heads = num_heads
480
+ self.hidden_dim = hidden_dim
481
+ self.qkv_hidden_size = hidden_dim if qkv_hidden_size is None else qkv_hidden_size
482
+ self.hidden_size_per_attention_head = self.qkv_hidden_size // self.num_heads
483
+ self.attn_implementation = attn_implementation
484
+ self.use_deterministic_attn = use_deterministic_attn
485
+
486
+ if norm_type == "layernorm":
487
+ self.norm0 = nn.LayerNorm(hidden_dim)
488
+ self.norm1 = nn.LayerNorm(hidden_dim)
489
+ elif norm_type == "rmsnorm":
490
+ self.norm0 = nn.RMSNorm(hidden_dim)
491
+ self.norm1 = nn.RMSNorm(hidden_dim)
492
+ else:
493
+ raise NotImplementedError(f"Not support norm_type: {norm_type}")
494
+
495
+ if mlp_type == "mlp2":
496
+ self.mlp = MLP2([hidden_dim, mlp_dim, hidden_dim],
497
+ activation,
498
+ bias=linear_bias)
499
+ else:
500
+ raise NotImplementedError(f"Not support mlp_type: {mlp_type}")
501
+
502
+ self.wqkv = nn.Linear(hidden_dim,
503
+ self.qkv_hidden_size * 3,
504
+ bias=attn_bias)
505
+ self.wo = nn.Linear(self.qkv_hidden_size, hidden_dim, bias=attn_bias)
506
+
507
+ def attention_qkvpacked(
508
+ self,
509
+ x: torch.Tensor,
510
+ cu_seqlens: torch.Tensor,
511
+ max_seqlen: torch.Tensor,
512
+ rope_freqs_cis: torch.Tensor | None = None,
513
+ ):
514
+ """
515
+ Args:
516
+ x (torch.Tensor): (batch_size, seqlen, hidden_dim)
517
+ cu_seqlens (torch.Tensor):
518
+ """
519
+ xqkv = self.wqkv(x)
520
+
521
+ qkv_shape = xqkv.size()[:-1] + (
522
+ 3,
523
+ self.num_heads,
524
+ self.hidden_size_per_attention_head,
525
+ )
526
+ # xqkv: (batch_size, seqlen, 3, nheads, headdim)
527
+ xqkv = xqkv.view(*qkv_shape)
528
+ xq, xk, xv = torch.unbind(xqkv, dim=-3)
529
+
530
+ xq, xk = apply_rope(xq, xk, rope_freqs_cis)
531
+
532
+ attn_func = VL_VISION_ATTENTION_FUNCTIONS[self.attn_implementation]
533
+ attn_out = attn_func(xq,
534
+ xk,
535
+ xv,
536
+ q_cu_seqlens=cu_seqlens,
537
+ k_cu_seqlens=cu_seqlens,
538
+ max_seqlen_k=max_seqlen,
539
+ max_seqlen_q=max_seqlen,
540
+ deterministic=self.use_deterministic_attn)
541
+
542
+ attn_out = self.wo(attn_out)
543
+ return attn_out
544
+
545
+ def forward(
546
+ self,
547
+ hidden_states: torch.Tensor,
548
+ cu_seqlens: torch.Tensor,
549
+ max_seqlen: int,
550
+ rope_freqs_cis: torch.Tensor | None = None,
551
+ ):
552
+ residual = hidden_states
553
+ hidden_states = self.norm0(hidden_states)
554
+
555
+ hidden_states = self.attention_qkvpacked(hidden_states, cu_seqlens,
556
+ max_seqlen, rope_freqs_cis)
557
+ hidden_states = residual + hidden_states
558
+
559
+ residual = hidden_states
560
+ hidden_states = self.norm1(hidden_states)
561
+ hidden_states = self.mlp(hidden_states)
562
+ hidden_states = residual + hidden_states
563
+
564
+ return hidden_states
565
+
566
+
567
+ class MoonViT3dEncoder(nn.Module):
568
+
569
+ def __init__(self,
570
+ hidden_dim: int,
571
+ num_layers: int,
572
+ block_cfg: dict,
573
+ use_deterministic_attn: bool = False) -> None:
574
+ super().__init__()
575
+ self.use_deterministic_attn = use_deterministic_attn
576
+
577
+ qkv_hidden_size = block_cfg['hidden_dim'] if block_cfg.get(
578
+ 'qkv_hidden_size') is None else block_cfg['qkv_hidden_size']
579
+ self.rope_2d = Rope2DPosEmbRepeated(
580
+ qkv_hidden_size // block_cfg['num_heads'], 512, 512)
581
+ self.blocks = nn.ModuleList([
582
+ MoonViTEncoderLayer(
583
+ **block_cfg,
584
+ use_deterministic_attn=self.use_deterministic_attn)
585
+ for _ in range(num_layers)
586
+ ])
587
+ norm_type = block_cfg.get('norm_type', 'layernorm')
588
+ if norm_type == "layernorm":
589
+ self.final_layernorm = nn.LayerNorm(hidden_dim)
590
+ elif norm_type == "rmsnorm":
591
+ self.final_layernorm = nn.RMSNorm(hidden_dim)
592
+ else:
593
+ raise NotImplementedError(f"Not support norm_type: {norm_type}")
594
+
595
+ def forward(
596
+ self,
597
+ hidden_states: torch.Tensor,
598
+ grid_thws: torch.Tensor,
599
+ ) -> torch.Tensor:
600
+ rope_freqs_cis = self.rope_2d.get_freqs_cis(
601
+ grid_thws=grid_thws, device=hidden_states.device)
602
+
603
+ lengths = torch.cat((
604
+ torch.zeros(1, dtype=grid_thws.dtype, device=grid_thws.device),
605
+ grid_thws[:, 0] * grid_thws[:, 1] * grid_thws[:, 2],
606
+ ))
607
+
608
+ max_seqlen = lengths.max()
609
+ cu_seqlens = lengths.to(hidden_states.device).cumsum(dim=0,
610
+ dtype=torch.int32)
611
+ for block in self.blocks:
612
+ hidden_states = block(hidden_states,
613
+ cu_seqlens,
614
+ max_seqlen,
615
+ rope_freqs_cis=rope_freqs_cis)
616
+
617
+ hidden_states = self.final_layernorm(hidden_states)
618
+ return hidden_states
619
+
620
+
621
+ def tpool_patch_merger(
622
+ x: torch.Tensor,
623
+ grid_thws: torch.Tensor,
624
+ merge_kernel_size: tuple[int, int] = (2, 2),
625
+ ) -> list[torch.Tensor]:
626
+ d_model = x.size(-1)
627
+
628
+ outputs = []
629
+ pre_sum = 0
630
+ for t, h, w in grid_thws.tolist():
631
+ # Get the current sequence
632
+ seq = x[pre_sum:pre_sum + t * h * w]
633
+ # Reshape along self.merge_kernel_size and concat to the last dimension
634
+ kernel_height, kernel_width = merge_kernel_size
635
+ new_height, new_width = h // kernel_height, w // kernel_width
636
+ reshaped_seq = seq.view(t, new_height, kernel_height, new_width,
637
+ kernel_width, d_model)
638
+ reshaped_seq = reshaped_seq.permute(0, 1,
639
+ 3, 2, 4, 5).contiguous().mean(
640
+ dim=0) # temporal pooling
641
+ padded_seq = reshaped_seq.view(new_height * new_width,
642
+ kernel_height * kernel_width, -1)
643
+ outputs.append(padded_seq)
644
+ pre_sum += t * h * w
645
+
646
+ return outputs
647
+
648
+
649
+ class MoonViT3dPretrainedModel(PreTrainedModel):
650
+ config_class = None
651
+ model_type = 'moonvit3d'
652
+ _no_split_modules = ['MoonViTEncoderLayer']
653
+ _supports_flash_attn_2 = True
654
+ _supports_sdpa = False
655
+
656
+ def __init__(self, config, *inputs, **kwargs):
657
+ super().__init__(config, *inputs, **kwargs)
658
+ config = deepcopy(config)
659
+ self.merge_kernel_size = config.merge_kernel_size
660
+ self.patch_size = config.patch_size
661
+ self.merge_type = config.merge_type
662
+
663
+ self.patch_embed = MoonVision3dPatchEmbed(
664
+ out_dim=config.hidden_size,
665
+ patch_size=config.patch_size,
666
+ pos_emb_height=config.init_pos_emb_height,
667
+ pos_emb_width=config.init_pos_emb_width,
668
+ pos_emb_time=config.init_pos_emb_time,
669
+ pos_emb_type=config.pos_emb_type,
670
+ patch_embed_proj_bias=getattr(config, 'patch_embed_proj_bias',
671
+ True),
672
+ pos_emb_interpolation_mode=getattr(
673
+ config, 'pos_emb_interpolation_mode', 'bicubic'),
674
+ )
675
+
676
+ self.encoder = MoonViT3dEncoder(
677
+ hidden_dim=config.hidden_size,
678
+ num_layers=config.num_hidden_layers,
679
+ block_cfg={
680
+ 'num_heads': config.num_attention_heads,
681
+ 'hidden_dim': config.hidden_size,
682
+ 'qkv_hidden_size': getattr(config, 'qkv_hidden_size', None),
683
+ 'mlp_dim': config.intermediate_size,
684
+ 'norm_type': getattr(config, 'norm_type', 'layernorm'),
685
+ 'mlp_type': getattr(config, 'mlp_type', 'mlp2'),
686
+ 'activation': PytorchGELUTanh(),
687
+ 'attn_bias': getattr(config, 'attn_bias', True),
688
+ 'linear_bias': getattr(config, 'linear_bias', True),
689
+ 'attn_implementation': config._attn_implementation,
690
+ },
691
+ use_deterministic_attn=getattr(self, 'use_deterministic_attn',
692
+ False))
693
+
694
+ def forward(self, pixel_values: torch.Tensor,
695
+ grid_thws: torch.Tensor) -> torch.Tensor:
696
+ """
697
+ Args:
698
+ pixel_values (torch.Tensor): The input pixel values.
699
+ grid_thws (torch.Tensor): Temporal, height and width.
700
+
701
+ Returns:
702
+ torch.Tensor: The output tokens.
703
+ """
704
+ # grid_thws = grid_thws.to('cpu')
705
+ assert grid_thws.ndim == 2, f'grid_thws should be 2D, got {grid_thws.ndim}'
706
+ assert grid_thws.size(1) == 3, f'No support for thw: {grid_thws}'
707
+ hidden_states = self.patch_embed(pixel_values, grid_thws)
708
+ hidden_states = self.encoder(hidden_states, grid_thws)
709
+ if self.merge_type == 'sd2_tpool': # spatial downsampling 2x with temporal pooling all
710
+ hidden_states = tpool_patch_merger(
711
+ hidden_states,
712
+ grid_thws,
713
+ merge_kernel_size=self.merge_kernel_size)
714
+ else:
715
+ raise NotImplementedError(f'Not support {self.merge_type}')
716
+
717
+ return hidden_states
718
+
719
+
720
+ # ============================================================================
721
+ # MM Projector Helper Classes (from mm_projector/modeling_mm_projectors.py)
722
+ # ============================================================================
723
+
724
+
725
+ class IdentityMap(nn.Module):
726
+
727
+ def __init__(self):
728
+ super().__init__()
729
+
730
+ def forward(self, x, *args, **kwargs):
731
+ return x
732
+
733
+
734
+ class MLP(nn.Module):
735
+
736
+ def __init__(self, config):
737
+ super().__init__()
738
+ # TODO, use faster LayerNorm
739
+ self.pre_norm = nn.LayerNorm(config.mm_hidden_size)
740
+ self.proj = nn.Sequential(
741
+ nn.Linear(config.mm_hidden_size, config.hidden_size), nn.GELU(),
742
+ nn.Linear(config.hidden_size, config.hidden_size))
743
+
744
+ def forward(self, x, *args, **kwargs):
745
+ assert isinstance(x,
746
+ list | tuple), f'x is not a list or tuple: {type(x)}'
747
+ lengths = [item.shape[0] for item in x]
748
+ x = torch.cat(x, dim=0)
749
+ x = self.pre_norm(x)
750
+ x = self.proj(x)
751
+ x = torch.split(x, lengths, dim=0)
752
+
753
+ return x
754
+
755
+
756
+ class PatchMergerMLP(nn.Module):
757
+
758
+ def __init__(self, config):
759
+ super().__init__()
760
+ eps = config.projector_ln_eps
761
+ self.hidden_size = config.mm_hidden_size * (
762
+ config.merge_kernel_size[0] * config.merge_kernel_size[1])
763
+ self.pre_norm = nn.LayerNorm(config.mm_hidden_size, eps=eps)
764
+ self.proj = nn.Sequential(
765
+ nn.Linear(self.hidden_size, self.hidden_size),
766
+ nn.GELU(),
767
+ nn.Linear(self.hidden_size, config.hidden_size),
768
+ )
769
+
770
+ def forward(self, x, *args, **kwargs):
771
+ if isinstance(x, list) or isinstance(x, tuple):
772
+ x = [
773
+ self.proj(self.pre_norm(item).view(item.shape[0], -1))
774
+ for item in x
775
+ ]
776
+ else:
777
+ # B, N, N_k, C = x.shape
778
+ B = x.shape[0]
779
+ x = self.proj(self.pre_norm(x).view(B, -1, self.hidden_size))
780
+ return x
781
+
782
+
783
+ class PatchMergerMLPV2(nn.Module):
784
+
785
+ def __init__(self, config):
786
+ super().__init__()
787
+ eps = config.projector_ln_eps
788
+ self.hidden_size = config.mm_hidden_size * (
789
+ config.merge_kernel_size[0] * config.merge_kernel_size[1])
790
+ self.proj = nn.Sequential(
791
+ nn.Linear(self.hidden_size, self.hidden_size, bias=False),
792
+ nn.GELU(),
793
+ nn.Linear(self.hidden_size, config.hidden_size, bias=False),
794
+ )
795
+ self.post_norm = nn.RMSNorm(config.hidden_size, eps=eps)
796
+ for m in self.proj.modules():
797
+ if isinstance(m, nn.Linear):
798
+ nn.init.trunc_normal_(m.weight,
799
+ std=math.sqrt(2 / m.in_features))
800
+ if m.bias is not None:
801
+ nn.init.zeros_(m.bias)
802
+
803
+ def forward(self, x, *args, **kwargs):
804
+ if isinstance(x, list) or isinstance(x, tuple):
805
+ lengths = [item.shape[0] for item in x]
806
+ x = torch.concat([item.view(item.shape[0], -1) for item in x],
807
+ dim=0)
808
+ x = self.post_norm(self.proj(x))
809
+ x = torch.split(x, lengths, dim=0)
810
+ else:
811
+ # B, N, N_k, C = x.shape
812
+ B = x.shape[0]
813
+ x = self.proj(x.view(B, -1, self.hidden_size))
814
+ x = self.post_norm(x)
815
+ return x
816
+
817
+
818
+ class KimiK3PreTrainedModel(PreTrainedModel):
819
+ config_class = KimiK3Config
820
+ base_model_prefix = "model"
821
+ _no_split_modules = [
822
+ "MoonViT3dPretrainedModel",
823
+ "MoonViTEncoderLayer",
824
+ "KimiDecoderLayer",
825
+ "PatchMergerMLP",
826
+ "PatchMergerMLPV2",
827
+ ]
828
+ _skip_keys_device_placement = "past_key_values"
829
+ _supports_flash_attn_2 = True
830
+ _supports_sdpa = False
831
+
832
+ def _init_weights(self, module):
833
+ # important: this ported version of Llava isn't meant for training from scratch - only
834
+ # inference and fine-tuning - so the proper init weights code has been removed - the original codebase
835
+ # https://github.com/haotian-liu/LLaVA/tree/main/llava should serve for that purpose
836
+ std = (self.config.initializer_range if hasattr(
837
+ self.config, "initializer_range") else
838
+ self.config.text_config.initializer_range)
839
+
840
+ if hasattr(module, "class_embedding"):
841
+ module.class_embedding.data.normal_(mean=0.0, std=std)
842
+
843
+ if isinstance(module, (nn.Linear, nn.Conv2d)):
844
+ module.weight.data.normal_(mean=0.0, std=std)
845
+ if module.bias is not None:
846
+ module.bias.data.zero_()
847
+ elif isinstance(module, nn.Embedding):
848
+ module.weight.data.normal_(mean=0.0, std=std)
849
+ if module.padding_idx is not None:
850
+ module.weight.data[module.padding_idx].zero_()
851
+
852
+
853
+ class VisionTowerConfig(PretrainedConfig):
854
+ model_type = 'moonvit3d'
855
+
856
+ def __init__(self, config: KimiK3Config, **kwargs):
857
+ super().__init__(**kwargs)
858
+ self.patch_size = config.patch_size
859
+ self.init_pos_emb_height = config.init_pos_emb_height
860
+ self.init_pos_emb_width = config.init_pos_emb_width
861
+ self.init_pos_emb_time = config.init_pos_emb_time
862
+ self.pos_emb_type = config.pos_emb_type
863
+ self.num_attention_heads = config.vt_num_attention_heads
864
+ self.num_hidden_layers = config.vt_num_hidden_layers
865
+ self.hidden_size = config.vt_hidden_size
866
+ self.intermediate_size = config.vt_intermediate_size
867
+ self.merge_kernel_size = config.merge_kernel_size
868
+ self.merge_type = config.merge_type
869
+ self._attn_implementation = config._attn_implementation
870
+ self.qkv_hidden_size = getattr(config, 'qkv_hidden_size', None)
871
+ self.norm_type = getattr(config, 'norm_type', 'layernorm')
872
+ self.attn_bias = getattr(config, 'attn_bias', True)
873
+ self.patch_embed_proj_bias = getattr(config, 'patch_embed_proj_bias',
874
+ True)
875
+ self.mlp_type = getattr(config, 'mlp_type', 'mlp2')
876
+ self.linear_bias = getattr(config, 'linear_bias', True)
877
+ self.pos_emb_interpolation_mode = getattr(
878
+ config, 'pos_emb_interpolation_mode', 'bilinear')
879
+
880
+
881
+ class ProjectorConfig:
882
+
883
+ def __init__(self, config: KimiK3Config):
884
+ self.mm_projector_type = config.mm_projector_type
885
+ self.mm_hidden_size = config.mm_hidden_size
886
+ self.hidden_size = config.text_hidden_size
887
+ self.merge_kernel_size = config.merge_kernel_size
888
+ self.projector_hidden_act = config.projector_hidden_act
889
+ self.projector_ln_eps = config.projector_ln_eps
890
+
891
+
892
+ # ref https://github.com/huggingface/transformers/blob/78b2929c0554b79e0489b451ce4ece14d265ead2/src/transformers/models/llava/modeling_llava.py#L240
893
+ class KimiK3ForConditionalGeneration(KimiK3PreTrainedModel):
894
+
895
+ @classmethod
896
+ def _supports_default_dynamic_cache(cls) -> bool:
897
+ return False
898
+
899
+ def __init__(self, config: KimiK3Config):
900
+ super().__init__(config)
901
+
902
+ vt_config = VisionTowerConfig(config.vision_config)
903
+ self.vision_tower = MoonViT3dPretrainedModel(vt_config)
904
+
905
+ proj_config = ProjectorConfig(config.vision_config)
906
+ if proj_config.mm_projector_type == 'identity':
907
+ self.mm_projector = IdentityMap()
908
+ elif proj_config.mm_projector_type == 'mlp':
909
+ self.mm_projector = MLP(proj_config)
910
+ elif proj_config.mm_projector_type == 'patchmerger':
911
+ self.mm_projector = PatchMergerMLP(proj_config)
912
+ elif proj_config.mm_projector_type == 'patchmergerv2':
913
+ self.mm_projector = PatchMergerMLPV2(proj_config)
914
+ else:
915
+ raise ValueError(
916
+ f"Unsupported mm_projector_type: {proj_config.mm_projector_type}"
917
+ )
918
+
919
+ self.language_model = KimiLinearForCausalLM(config.text_config)
920
+ self.post_init()
921
+
922
+ if hasattr(self.language_model, 'dtype'):
923
+ target_dtype = self.language_model.dtype
924
+ self.vision_tower = self.vision_tower.to(dtype=target_dtype)
925
+ self.mm_projector = self.mm_projector.to(dtype=target_dtype)
926
+
927
+ def get_input_embeddings(self):
928
+ return self.language_model.get_input_embeddings()
929
+
930
+ def set_input_embeddings(self, value):
931
+ self.language_model.set_input_embeddings(value)
932
+
933
+ def get_output_embeddings(self):
934
+ return self.language_model.get_output_embeddings()
935
+
936
+ def set_output_embeddings(self, new_embeddings):
937
+ self.language_model.set_output_embeddings(new_embeddings)
938
+
939
+ def set_decoder(self, decoder):
940
+ self.language_model.set_decoder(decoder)
941
+
942
+ def get_decoder(self):
943
+ return self.language_model.get_decoder()
944
+
945
+ def tie_weights(self, *args, **kwargs):
946
+ return self.language_model.tie_weights()
947
+
948
+ def resize_token_embeddings(self,
949
+ new_num_tokens: int | None = None,
950
+ pad_to_multiple_of=None) -> nn.Embedding:
951
+ model_embeds = self.language_model.resize_token_embeddings(
952
+ new_num_tokens, pad_to_multiple_of)
953
+ # update vocab size
954
+ self.config.text_config.vocab_size = model_embeds.num_embeddings
955
+ self.vocab_size = model_embeds.num_embeddings
956
+ return model_embeds
957
+
958
+ def _merge_input_ids_with_image_features(
959
+ self,
960
+ image_features: list[torch.Tensor],
961
+ inputs_embeds: torch.Tensor,
962
+ input_ids: torch.Tensor,
963
+ attention_mask: torch.Tensor,
964
+ labels: torch.Tensor | None = None,
965
+ ):
966
+ """
967
+ Args:
968
+ image_features (:obj:`torch.Tensor` of shape :obj:`(num_image_tokens, embed_dim)`):
969
+ The image features to merge with the input embeddings.
970
+ inputs_embeds (:obj:`torch.Tensor` of shape :obj:`(batch_size, sequence_length, embed_dim)`):
971
+ The input embeddings.
972
+ input_ids (:obj:`torch.Tensor` of shape :obj:`(batch_size, sequence_length)`):
973
+ The input ids.
974
+ attention_mask (:obj:`torch.Tensor` of shape :obj:`(batch_size, sequence_length)`):
975
+ The attention mask.
976
+ labels (:obj:`torch.Tensor` of shape :obj:`(batch_size, sequence_length)`, *optional*):
977
+ The labels.
978
+ """
979
+ _, embed_dim = image_features[0].shape
980
+ feature_lengths = [x.shape[0] for x in image_features]
981
+ image_features = torch.cat(image_features, dim=0)
982
+
983
+ image_token_index: int = self.config.media_placeholder_token_id
984
+ pad_token_id: int = self.config.pad_token_id
985
+ ignore_index: int = self.config.ignore_index
986
+
987
+ batch_size, sequence_length = input_ids.shape
988
+ left_padding = not torch.sum(
989
+ input_ids[:, -1] == torch.tensor(pad_token_id))
990
+
991
+ # 1. Create a mask to know where special image tokens are
992
+ _token_occupation_table = torch.ones_like(input_ids.flatten())
993
+ _token_occupation_table[input_ids.flatten() ==
994
+ image_token_index] = torch.tensor(
995
+ feature_lengths,
996
+ dtype=torch.long,
997
+ device=input_ids.device)
998
+ _token_occupation_table = _token_occupation_table.reshape(
999
+ input_ids.shape)
1000
+
1001
+ max_embed_dim = _token_occupation_table.sum(-1).max().item()
1002
+ assert (
1003
+ max_embed_dim >= sequence_length
1004
+ ), f"The maximum embedding dimension ({max_embed_dim}) is less than the sequence length ({sequence_length})"
1005
+ batch_indices, non_image_indices = torch.where(
1006
+ input_ids != image_token_index)
1007
+
1008
+ # 2. Compute the positions where text should be written
1009
+ # Calculate new positions for text tokens in merged image-text sequence.
1010
+ new_token_positions = torch.cumsum(_token_occupation_table, -1) - 1
1011
+ nb_image_pad = max_embed_dim - 1 - new_token_positions[:, -1]
1012
+ if left_padding:
1013
+ new_token_positions += nb_image_pad[:,
1014
+ None] # offset for left padding
1015
+ text_to_overwrite = new_token_positions[batch_indices,
1016
+ non_image_indices]
1017
+
1018
+ # 3. Create the full embedding, already padded to the maximum position
1019
+ final_embedding = torch.zeros(
1020
+ batch_size,
1021
+ max_embed_dim,
1022
+ embed_dim,
1023
+ dtype=inputs_embeds.dtype,
1024
+ device=inputs_embeds.device,
1025
+ )
1026
+ final_attention_mask = torch.zeros(batch_size,
1027
+ max_embed_dim,
1028
+ dtype=attention_mask.dtype,
1029
+ device=inputs_embeds.device)
1030
+ if labels is not None:
1031
+ final_labels = torch.full(
1032
+ (batch_size, max_embed_dim),
1033
+ ignore_index,
1034
+ dtype=input_ids.dtype,
1035
+ device=input_ids.device,
1036
+ )
1037
+ # In case the Vision model or the Language model has been offloaded to CPU, we need to manually
1038
+ # set the corresponding tensors into their correct target device.
1039
+ target_device = inputs_embeds.device
1040
+ batch_indices, non_image_indices, text_to_overwrite = (
1041
+ batch_indices.to(target_device),
1042
+ non_image_indices.to(target_device),
1043
+ text_to_overwrite.to(target_device),
1044
+ )
1045
+ attention_mask = attention_mask.to(target_device)
1046
+
1047
+ # 4. Fill the embeddings based on the mask.
1048
+ final_embedding[batch_indices,
1049
+ text_to_overwrite] = inputs_embeds[batch_indices,
1050
+ non_image_indices]
1051
+ final_attention_mask[batch_indices,
1052
+ text_to_overwrite] = attention_mask[
1053
+ batch_indices, non_image_indices]
1054
+ if labels is not None:
1055
+ final_labels[batch_indices,
1056
+ text_to_overwrite] = labels[batch_indices,
1057
+ non_image_indices]
1058
+
1059
+ # 5. Fill the embeddings corresponding to the images. Anything that is not `text_positions` needs filling (#29835)
1060
+ image_to_overwrite = torch.full((batch_size, max_embed_dim),
1061
+ True,
1062
+ dtype=torch.bool,
1063
+ device=inputs_embeds.device)
1064
+ image_to_overwrite[batch_indices, text_to_overwrite] = False
1065
+ image_to_overwrite &= image_to_overwrite.cumsum(
1066
+ -1) - 1 >= nb_image_pad[:, None].to(target_device)
1067
+
1068
+ if image_to_overwrite.sum() != image_features.shape[:-1].numel():
1069
+ raise ValueError(
1070
+ f"The input provided to the model are wrong. The number of image tokens is {image_to_overwrite.sum()} while"
1071
+ f" the number of image features given to the model is {image_features.shape[:-1].numel()}. "
1072
+ "This prevents correct indexing and breaks batch generation.")
1073
+
1074
+ final_embedding[image_to_overwrite] = (
1075
+ image_features.contiguous().reshape(-1,
1076
+ embed_dim).to(target_device))
1077
+ final_attention_mask |= image_to_overwrite
1078
+ position_ids = (final_attention_mask.cumsum(-1) - 1).masked_fill_(
1079
+ (final_attention_mask == 0), 1)
1080
+
1081
+ # 6. Mask out the embedding at padding positions, as we later use the past_key_value value to determine the non-attended tokens.
1082
+ batch_indices, pad_indices = torch.where(input_ids == pad_token_id)
1083
+ indices_to_mask = new_token_positions[batch_indices, pad_indices]
1084
+
1085
+ final_embedding[batch_indices, indices_to_mask] = 0
1086
+
1087
+ if labels is None:
1088
+ final_labels = None
1089
+
1090
+ return final_embedding, final_attention_mask, final_labels, position_ids
1091
+
1092
+ def _extract_image_features(self, pixel_values: torch.Tensor,
1093
+ grid_thws: torch.Tensor) -> list[torch.Tensor]:
1094
+ """
1095
+ Args:
1096
+ pixel_values (:obj:`torch.FloatTensor` of shape :obj:`(batch_size, num_channels, height, width)`):
1097
+ The pixel values of the images processed by image processor.
1098
+ grid_thws (:obj:`torch.Tensor` of shape :obj:`(batch_size, 3)`):
1099
+ The grid, height, width of the images.
1100
+
1101
+ Returns:
1102
+ selected_image_feature (:obj:`torch.FloatTensor` of shape :obj:`(num_image_tokens, embed_dim)`):
1103
+ The selected image features to use as input to the projector head.
1104
+
1105
+ """
1106
+
1107
+ target_dtype = self.vision_tower.patch_embed.proj.weight.dtype
1108
+ pixel_values = pixel_values.to(target_dtype)
1109
+
1110
+ image_features = self.vision_tower(pixel_values, grid_thws)
1111
+ return image_features
1112
+
1113
+ def forward(
1114
+ self,
1115
+ input_ids: torch.LongTensor | None = None,
1116
+ pixel_values: torch.FloatTensor | list[torch.FloatTensor]
1117
+ | None = None,
1118
+ grid_thws: torch.Tensor | None = None,
1119
+ attention_mask: torch.Tensor | None = None,
1120
+ position_ids: torch.LongTensor | None = None,
1121
+ past_key_values: list[torch.FloatTensor] | None = None,
1122
+ inputs_embeds: torch.FloatTensor | None = None,
1123
+ labels: torch.LongTensor | None = None,
1124
+ use_cache: bool | None = None,
1125
+ output_attentions: bool | None = None,
1126
+ output_hidden_states: bool | None = None,
1127
+ return_dict: bool | None = None,
1128
+ ) -> tuple | LlavaCausalLMOutputWithPast:
1129
+ r"""
1130
+ Args:
1131
+ labels (`torch.LongTensor` of shape `(batch_size, sequence_length)`, *optional*):
1132
+ Labels for computing the masked language modeling loss. Indices should either be in `[0, ...,
1133
+ config.vocab_size]` or -100 (see `input_ids` docstring). Tokens with indices set to `-100` are ignored
1134
+ (masked), the loss is only computed for the tokens with labels in `[0, ..., config.vocab_size]`.
1135
+
1136
+ ```"""
1137
+ assert self.vision_tower is not None, "vision_tower is not loaded"
1138
+ output_attentions = (output_attentions if output_attentions is not None
1139
+ else self.config.output_attentions)
1140
+ output_hidden_states = (output_hidden_states
1141
+ if output_hidden_states is not None else
1142
+ self.config.output_hidden_states)
1143
+ return_dict = return_dict if return_dict is not None else self.config.use_return_dict
1144
+
1145
+ if inputs_embeds is None:
1146
+ # 1. Extra the input embeddings
1147
+ inputs_embeds = self.get_input_embeddings()(input_ids)
1148
+
1149
+ # 2. Merge text and images
1150
+ if pixel_values is not None and len(
1151
+ pixel_values) > 0 and input_ids.shape[1] != 1:
1152
+ image_features = self._extract_image_features(
1153
+ pixel_values, grid_thws)
1154
+ if self.mm_projector:
1155
+ image_features = self.mm_projector(image_features)
1156
+
1157
+ inputs_embeds = inputs_embeds.to(
1158
+ image_features[0].dtype) # num_tokens, embed_dim
1159
+ inputs_embeds, attention_mask, labels, position_ids = (
1160
+ self._merge_input_ids_with_image_features(
1161
+ image_features,
1162
+ inputs_embeds,
1163
+ input_ids,
1164
+ attention_mask,
1165
+ labels,
1166
+ ))
1167
+
1168
+ # In case input_ids.shape[1] == 1 & pixel_values==None & past_key_values != None, we are in the case of
1169
+ # generation with cache
1170
+ elif (past_key_values is not None and pixel_values is not None
1171
+ and input_ids.shape[1] == 1):
1172
+ # Retrieve the first layer to inspect the logits and mask out the hidden states
1173
+ # that are set to 0
1174
+ if hasattr(past_key_values, "key_cache"):
1175
+ first_key_cache = next(
1176
+ key_cache for key_cache in past_key_values.key_cache if key_cache is not None
1177
+ )
1178
+ first_layer_past_key_value = first_key_cache[:, :, :, 0]
1179
+ else:
1180
+ first_layer_past_key_value = past_key_values[0][0][:, :, :, 0]
1181
+
1182
+ # Sum all dimensions of head_dim (-2) to avoid random errors such as: https://github.com/huggingface/transformers/pull/28032#issuecomment-1863691941
1183
+ batch_index, non_attended_tokens = torch.where(
1184
+ first_layer_past_key_value.float().sum(-2) == 0)
1185
+
1186
+ # Get the target length
1187
+ target_length = input_ids.shape[1]
1188
+ past_length = first_layer_past_key_value.shape[-1]
1189
+
1190
+ extended_attention_mask = torch.ones(
1191
+ (attention_mask.shape[0], past_length),
1192
+ dtype=attention_mask.dtype,
1193
+ device=attention_mask.device,
1194
+ )
1195
+
1196
+ # Filter out only the tokens that can be un-attended, this can happen
1197
+ # if one uses Llava + Fused modules where the cache on the
1198
+ # first iteration is already big enough, or if one passes custom cache
1199
+ valid_indices = non_attended_tokens < extended_attention_mask.size(
1200
+ -1)
1201
+ new_batch_index = batch_index[valid_indices]
1202
+ new_non_attended_tokens = non_attended_tokens[valid_indices]
1203
+
1204
+ # Zero-out the places where we don't need to attend
1205
+ extended_attention_mask[new_batch_index,
1206
+ new_non_attended_tokens] = 0
1207
+
1208
+ attention_mask = torch.cat(
1209
+ (extended_attention_mask, attention_mask[:,
1210
+ -target_length:]),
1211
+ dim=1)
1212
+ position_ids = torch.sum(attention_mask,
1213
+ dim=1).unsqueeze(-1) - 1
1214
+
1215
+ outputs = self.language_model(
1216
+ attention_mask=attention_mask,
1217
+ position_ids=position_ids,
1218
+ past_key_values=past_key_values,
1219
+ inputs_embeds=inputs_embeds,
1220
+ use_cache=use_cache,
1221
+ output_attentions=output_attentions,
1222
+ output_hidden_states=output_hidden_states,
1223
+ return_dict=return_dict,
1224
+ )
1225
+
1226
+ logits = outputs[0]
1227
+
1228
+ loss = None
1229
+ if labels is not None:
1230
+ # Shift so that tokens < n predict n
1231
+ if attention_mask is not None:
1232
+ shift_attention_mask = attention_mask[..., 1:]
1233
+ shift_logits = logits[..., :-1, :][shift_attention_mask.to(
1234
+ logits.device) != 0].contiguous()
1235
+ shift_labels = labels[..., 1:][shift_attention_mask.to(
1236
+ labels.device) != 0].contiguous()
1237
+ else:
1238
+ shift_logits = logits[..., :-1, :].contiguous()
1239
+ shift_labels = labels[..., 1:].contiguous()
1240
+ # Flatten the tokens
1241
+ loss_fct = nn.CrossEntropyLoss()
1242
+ loss = loss_fct(
1243
+ shift_logits.view(-1, shift_logits.size(-1)),
1244
+ shift_labels.view(-1).to(shift_logits.device),
1245
+ )
1246
+
1247
+ if not return_dict:
1248
+ output = (logits, ) + outputs[1:]
1249
+ return (loss, ) + output if loss is not None else output
1250
+
1251
+ return LlavaCausalLMOutputWithPast(
1252
+ loss=loss,
1253
+ logits=logits,
1254
+ past_key_values=outputs.past_key_values,
1255
+ hidden_states=outputs.hidden_states,
1256
+ attentions=outputs.attentions,
1257
+ )
1258
+
1259
+ def prepare_inputs_for_generation(
1260
+ self,
1261
+ input_ids,
1262
+ past_key_values=None,
1263
+ inputs_embeds=None,
1264
+ pixel_values=None,
1265
+ grid_thws=None,
1266
+ attention_mask=None,
1267
+ **kwargs,
1268
+ ):
1269
+ if past_key_values is not None:
1270
+ if hasattr(past_key_values, "get_seq_length"):
1271
+ cache_length = past_key_values.get_seq_length()
1272
+ past_length = getattr(past_key_values, 'seen_tokens',
1273
+ cache_length)
1274
+ else:
1275
+ cache_length = past_length = past_key_values[0][0].shape[2]
1276
+
1277
+ # Keep only the unprocessed tokens:
1278
+ # 1 - If the length of the attention_mask exceeds the length of input_ids, then we are in a setting where
1279
+ # some of the inputs are exclusively passed as part of the cache (e.g. when passing input_embeds as
1280
+ # input)
1281
+ if attention_mask is not None and attention_mask.shape[
1282
+ 1] > input_ids.shape[1]:
1283
+ input_ids = input_ids[:, -(attention_mask.shape[1] -
1284
+ past_length):]
1285
+ # 2 - If the past_length is smaller than input_ids', then input_ids holds all input tokens. We can discard
1286
+ # input_ids based on the past_length.
1287
+ elif past_length < input_ids.shape[1]:
1288
+ input_ids = input_ids[:, past_length:]
1289
+ # 3 - Otherwise (past_length >= input_ids.shape[1]), let's assume input_ids only has unprocessed tokens.
1290
+ elif self.config.media_placeholder_token_id in input_ids:
1291
+ input_ids = input_ids[:, input_ids.shape[1] - 1:]
1292
+ # If the cache has seen more tokens than it can hold, then the cache has a size limit. Let's discard the
1293
+ # older attention values, as their corresponding values are not part of the input.
1294
+ if cache_length < past_length and attention_mask is not None:
1295
+ attention_mask = attention_mask[:, -(cache_length +
1296
+ input_ids.shape[1]):]
1297
+
1298
+ position_ids = kwargs.get("position_ids", None)
1299
+ if attention_mask is not None and position_ids is None:
1300
+ # create position_ids on the fly for batch generation
1301
+ position_ids = attention_mask.long().cumsum(-1) - 1
1302
+ position_ids.masked_fill_(attention_mask == 0, 1)
1303
+ if past_key_values:
1304
+ position_ids = position_ids[:, -input_ids.shape[1]:]
1305
+
1306
+ # if `inputs_embeds` are passed, we only want to use them in the 1st generation step
1307
+ if inputs_embeds is not None and past_key_values is None:
1308
+ model_inputs = {"inputs_embeds": inputs_embeds}
1309
+ else:
1310
+ model_inputs = {"input_ids": input_ids}
1311
+
1312
+ model_inputs.update({
1313
+ "position_ids": position_ids,
1314
+ "past_key_values": past_key_values,
1315
+ "use_cache": kwargs.get("use_cache"),
1316
+ "attention_mask": attention_mask,
1317
+ "pixel_values": pixel_values,
1318
+ "grid_thws": grid_thws,
1319
+ })
1320
+ return model_inputs
1321
+
1322
+ def _reorder_cache(self, *args, **kwargs):
1323
+ return self.language_model._reorder_cache(*args, **kwargs)
modeling_kimi_linear.py ADDED
@@ -0,0 +1,1328 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # coding=utf-8
2
+ # Copyright 2025-2026 The Moonshot AI Team, DeepSeek-AI, and HuggingFace Inc. team. All rights reserved.
3
+ #
4
+ # The multi-head latent attention, MoE gating and sparse MoE block in this file are
5
+ # adapted from DeepSeek-V3 (DeepSeek-V3/modeling_deepseek.py). They have been
6
+ # extensively modified and extended for the Kimi-Linear architecture.
7
+ #
8
+ # Licensing Information:
9
+ # - Code adapted from DeepSeek-V3 (DeepSeek-V3/modeling_deepseek.py) is licensed under the Apache License, Version 2.0.
10
+ # - Other parts of the code are licensed under the Kimi K3 License (see the LICENSE file in this repository).
11
+ #
12
+ # Apache License, Version 2.0:
13
+ # Licensed under the Apache License, Version 2.0 (the "License");
14
+ # you may not use this file except in compliance with the License.
15
+ # You may obtain a copy of the License at
16
+ #
17
+ # http://www.apache.org/licenses/LICENSE-2.0
18
+ #
19
+ # Unless required by applicable law or agreed to in writing, software
20
+ # distributed under the License is distributed on an "AS IS" BASIS,
21
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
22
+ # See the License for the specific language governing permissions and
23
+ # limitations under the License.
24
+ import math
25
+ from collections.abc import Callable
26
+ from typing import Any
27
+
28
+ import torch
29
+ import torch.nn.functional as F
30
+ import transformers
31
+ from einops import rearrange
32
+ from packaging import version
33
+ from torch import nn
34
+ from transformers.activations import ACT2FN
35
+ from transformers.cache_utils import Cache
36
+ from transformers.generation import GenerationMixin
37
+ from transformers.masking_utils import create_causal_mask
38
+ from transformers.modeling_flash_attention_utils import FlashAttentionKwargs
39
+ from transformers.modeling_outputs import BaseModelOutputWithPast, CausalLMOutputWithPast
40
+ from transformers.modeling_utils import ALL_ATTENTION_FUNCTIONS, OutputRecorder, PreTrainedModel
41
+ from transformers.processing_utils import Unpack
42
+ from transformers.pytorch_utils import ALL_LAYERNORM_LAYERS
43
+ from transformers.utils import TransformersKwargs, auto_docstring, can_return_tuple, logging
44
+ from transformers.utils.generic import check_model_inputs
45
+
46
+ try:
47
+ from fla.modules import FusedRMSNormGated, ShortConvolution
48
+ from fla.ops.kda import chunk_kda, fused_recurrent_kda
49
+ # from fla.ops.kda.gate import fused_kda_gate # deprecated, gate is now computed inside chunk_kda/fused_recurrent_kda
50
+ from fla.ops.utils.index import prepare_cu_seqlens_from_mask, prepare_lens_from_mask
51
+ from fla.utils import tensor_cache
52
+ except ImportError:
53
+ raise ImportError("Plese run `pip install -U fla-core`")
54
+
55
+ from .configuration_kimi_k3 import KimiLinearConfig
56
+
57
+ assert version.parse(transformers.__version__) >= version.parse("4.56.0"), \
58
+ "Please upgrade transformers to >= 4.56.0"
59
+
60
+ logger = logging.get_logger(__name__)
61
+
62
+
63
+ # Register Moonshot-specific activation functions
64
+ class SituAndMul(nn.Module):
65
+ """
66
+ SituAndMul activation: beta * tanh(gate / beta) * sigmoid(gate) * up
67
+ When linear_beta is set, up is also transformed by linear_beta * tanh(up / linear_beta).
68
+ """
69
+
70
+ def __init__(self, beta: float = 1.0, linear_beta: float | None = None):
71
+ super().__init__()
72
+ self.beta = beta
73
+ self.linear_beta = linear_beta
74
+
75
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
76
+ d = x.shape[-1] // 2
77
+ gate = x[..., :d].to(torch.float32)
78
+ up = x[..., d:].to(torch.float32)
79
+ situ_a = self.beta * torch.tanh(gate / self.beta) * torch.sigmoid(gate)
80
+ if self.linear_beta is not None:
81
+ up = self.linear_beta * torch.tanh(up / self.linear_beta)
82
+ return (situ_a * up).to(x.dtype)
83
+
84
+
85
+ ACT2FN["situ"] = SituAndMul
86
+
87
+
88
+ def _get_situ_activation_params(config: KimiLinearConfig):
89
+ beta = getattr(config, "activation_situ_beta", None)
90
+ linear_beta = getattr(config, "activation_situ_linear_beta", None)
91
+ return beta or 1.0, linear_beta
92
+
93
+
94
+ def index_first_axis(x, indices):
95
+ return x[indices]
96
+
97
+
98
+ @tensor_cache
99
+ def get_unpad_data(
100
+ attention_mask: torch.Tensor,
101
+ ) -> tuple[torch.Tensor, torch.Tensor, int]:
102
+ lens = prepare_lens_from_mask(attention_mask)
103
+ indices = torch.nonzero(attention_mask.flatten(), as_tuple=False).flatten()
104
+ max_seqlen_in_batch = lens.max().item()
105
+ cu_seqlens = prepare_cu_seqlens_from_mask(attention_mask)
106
+ return indices, cu_seqlens, max_seqlen_in_batch
107
+
108
+
109
+ def pad_input(
110
+ hidden_states: torch.Tensor,
111
+ indices: torch.LongTensor,
112
+ batch_size: int,
113
+ seq_len: int,
114
+ ) -> torch.Tensor:
115
+ out = hidden_states.new_zeros((batch_size * seq_len, *hidden_states.shape[1:]))
116
+ out[indices] = hidden_states
117
+ return out.view(batch_size, seq_len, *hidden_states.shape[1:])
118
+
119
+
120
+ class KimiDynamicCache:
121
+ """
122
+ Dynamic cache for Kimi model.
123
+ Inspired by Qwen3-Next
124
+ """
125
+ is_compileable = False
126
+
127
+ def __init__(self, config: KimiLinearConfig):
128
+ super().__init__()
129
+ self.config = config
130
+
131
+ if config.linear_attn_config is not None:
132
+ self.layer_types = []
133
+ for i in range(config.num_hidden_layers):
134
+ if config.is_kda_layer(i):
135
+ self.layer_types.append("linear_attention")
136
+ else:
137
+ self.layer_types.append("full_attention")
138
+ else:
139
+ self.layer_types = ["full_attention"] * config.num_hidden_layers
140
+
141
+ self.transformer_layers = [
142
+ i for i in range(config.num_hidden_layers) if self.layer_types[i] == "full_attention"
143
+ ]
144
+
145
+ linear_layers = [i for i in range(
146
+ config.num_hidden_layers) if self.layer_types[i] == "linear_attention"]
147
+ self.last_linear_layer = linear_layers[-1] if linear_layers else -1
148
+
149
+ self.conv_states = [None for _ in range(config.num_hidden_layers)]
150
+ self.recurrent_states = [None for _ in range(config.num_hidden_layers)]
151
+ self.key_cache = [None for _ in range(config.num_hidden_layers)]
152
+ self.value_cache = [None for _ in range(config.num_hidden_layers)]
153
+
154
+ def __len__(self):
155
+ return len(self.layer_types)
156
+
157
+ def update(
158
+ self,
159
+ key_states: torch.Tensor,
160
+ value_states: torch.Tensor,
161
+ layer_idx: int,
162
+ cache_kwargs: dict[str, Any] | None = None,
163
+ ) -> tuple[torch.Tensor, torch.Tensor]:
164
+ if self.key_cache[layer_idx] is None:
165
+ self.key_cache[layer_idx] = key_states
166
+ self.value_cache[layer_idx] = value_states
167
+ else:
168
+ self.key_cache[layer_idx] = torch.cat(
169
+ [self.key_cache[layer_idx], key_states], dim=2)
170
+ self.value_cache[layer_idx] = torch.cat(
171
+ [self.value_cache[layer_idx], value_states], dim=2)
172
+
173
+ return self.key_cache[layer_idx], self.value_cache[layer_idx]
174
+
175
+ def reorder_cache(self, beam_idx: torch.LongTensor):
176
+ """Reorders the cache for beam search, given the selected beam indices."""
177
+ for layer_idx in range(len(self.key_cache)):
178
+ if self.key_cache[layer_idx] is not None:
179
+ device = self.key_cache[layer_idx].device
180
+ beam_idx = beam_idx.to(device)
181
+ self.key_cache[layer_idx] = self.key_cache[layer_idx].index_select(
182
+ 0, beam_idx)
183
+ self.value_cache[layer_idx] = self.value_cache[layer_idx].index_select(
184
+ 0, beam_idx)
185
+
186
+ if self.conv_states[layer_idx] is not None:
187
+ device = self.conv_states[layer_idx][0].device
188
+ beam_idx = beam_idx.to(device)
189
+ q_conv, k_conv, v_conv = self.conv_states[layer_idx]
190
+ self.conv_states[layer_idx] = (
191
+ q_conv.index_select(0, beam_idx),
192
+ k_conv.index_select(0, beam_idx),
193
+ v_conv.index_select(0, beam_idx),
194
+ )
195
+ self.recurrent_states[layer_idx] = self.recurrent_states[layer_idx].index_select(
196
+ 0, beam_idx)
197
+
198
+ def get_seq_length(self, layer_idx: int | None = 0) -> int:
199
+ """Returns the sequence length of the cached states. A layer index can be optionally passed."""
200
+ # take any layer that contains cache and not empty tensor
201
+ layer_idx = self.transformer_layers[0] if layer_idx not in self.transformer_layers else layer_idx
202
+ if len(self.key_cache) <= layer_idx or self.key_cache[layer_idx] is None:
203
+ return 0
204
+ return self.key_cache[layer_idx].shape[-2]
205
+
206
+ def get_query_offset(self, layer_idx: int) -> int:
207
+ return self.get_seq_length(layer_idx)
208
+
209
+ def get_mask_sizes(self, cache_position: torch.Tensor | int, layer_idx: int) -> tuple[int, int]:
210
+ """
211
+ Return a tuple (kv_length, kv_offset) corresponding to the length and offset that will be returned for
212
+ the given layer at `layer_idx`.
213
+ The masks are then prepared according to the given lengths (kv_length, kv_offset) and patterns for each layer.
214
+ """
215
+ kv_offset = 0
216
+ query_length = cache_position if isinstance(cache_position, int) else cache_position.shape[0]
217
+ past_seen_tokens = self.get_seq_length(layer_idx)
218
+ kv_length = query_length + past_seen_tokens
219
+ return kv_length, kv_offset
220
+
221
+ @property
222
+ def has_previous_state(self):
223
+ """We have a previous state if the last linear (conv) layer was already updated."""
224
+ if self.last_linear_layer == -1:
225
+ return False
226
+ return self.conv_states[self.last_linear_layer] is not None
227
+
228
+
229
+ class KimiRMSNorm(nn.Module):
230
+ def __init__(self, hidden_size, eps=1e-6):
231
+ super().__init__()
232
+ self.weight = nn.Parameter(torch.ones(hidden_size))
233
+ self.variance_epsilon = eps
234
+
235
+ def forward(self, hidden_states):
236
+ dtype = hidden_states.dtype
237
+ x = hidden_states.float()
238
+ x = x * torch.rsqrt(x.pow(2).mean(-1, keepdim=True) + self.variance_epsilon)
239
+ return self.weight * x.to(dtype)
240
+
241
+
242
+ ALL_LAYERNORM_LAYERS.append(KimiRMSNorm)
243
+
244
+
245
+ class KimiBlockSparseMLP(nn.Module):
246
+ def __init__(self, config: KimiLinearConfig, hidden_size=None, intermediate_size=None):
247
+ super().__init__()
248
+ self.config = config
249
+ self.ffn_dim = config.intermediate_size if intermediate_size is None else intermediate_size
250
+ self.hidden_dim = config.hidden_size if hidden_size is None else hidden_size
251
+
252
+ self.w1 = nn.Linear(self.hidden_dim, self.ffn_dim, bias=False) # gate
253
+ self.w2 = nn.Linear(self.ffn_dim, self.hidden_dim, bias=False) # down
254
+ self.w3 = nn.Linear(self.hidden_dim, self.ffn_dim, bias=False) # up
255
+
256
+ if config.hidden_act == "situ":
257
+ beta, linear_beta = _get_situ_activation_params(config)
258
+ self.act_fn = SituAndMul(
259
+ beta=beta,
260
+ linear_beta=linear_beta,
261
+ )
262
+ else:
263
+ self.act_fn = ACT2FN[config.hidden_act]
264
+
265
+ def forward(self, hidden_states):
266
+ if self.config.hidden_act == "situ":
267
+ gate_up = torch.cat([self.w1(hidden_states), self.w3(hidden_states)], dim=-1)
268
+ current_hidden_states = self.act_fn(gate_up)
269
+ else:
270
+ current_hidden_states = self.act_fn(
271
+ self.w1(hidden_states)) * self.w3(hidden_states)
272
+ current_hidden_states = self.w2(current_hidden_states)
273
+ return current_hidden_states
274
+
275
+
276
+ class KimiMLP(nn.Module):
277
+ def __init__(self, config: KimiLinearConfig, hidden_size=None, intermediate_size=None):
278
+ super().__init__()
279
+ self.config = config
280
+ self.hidden_size = config.hidden_size if hidden_size is None else hidden_size
281
+ self.intermediate_size = config.intermediate_size if intermediate_size is None else intermediate_size
282
+ self.gate_proj = nn.Linear(
283
+ self.hidden_size, self.intermediate_size, bias=False)
284
+ self.up_proj = nn.Linear(
285
+ self.hidden_size, self.intermediate_size, bias=False)
286
+ self.down_proj = nn.Linear(
287
+ self.intermediate_size, self.hidden_size, bias=False)
288
+ if config.hidden_act == "situ":
289
+ beta, linear_beta = _get_situ_activation_params(config)
290
+ self.act_fn = SituAndMul(
291
+ beta=beta,
292
+ linear_beta=linear_beta,
293
+ )
294
+ else:
295
+ self.act_fn = ACT2FN[config.hidden_act]
296
+
297
+ def forward(self, x):
298
+ if self.config.hidden_act == "situ":
299
+ gate_up = torch.cat([self.gate_proj(x), self.up_proj(x)], dim=-1)
300
+ down_proj = self.down_proj(self.act_fn(gate_up))
301
+ else:
302
+ down_proj = self.down_proj(self.act_fn(
303
+ self.gate_proj(x)) * self.up_proj(x))
304
+ return down_proj
305
+
306
+
307
+ def repeat_kv(hidden_states: torch.Tensor, n_rep: int) -> torch.Tensor:
308
+ """Expand the key/value heads from `num_key_value_heads` to `num_attention_heads`."""
309
+ if n_rep == 1:
310
+ return hidden_states
311
+ return torch.repeat_interleave(hidden_states, dim=1, repeats=n_rep)
312
+
313
+
314
+ def eager_attention_forward(
315
+ module: nn.Module,
316
+ query: torch.Tensor,
317
+ key: torch.Tensor,
318
+ value: torch.Tensor,
319
+ attention_mask: torch.Tensor | None,
320
+ scaling: float,
321
+ dropout: float = 0.0,
322
+ **kwargs: Unpack[TransformersKwargs],
323
+ ):
324
+ key = repeat_kv(key, module.num_key_value_groups)
325
+ value = repeat_kv(value, module.num_key_value_groups)
326
+
327
+ scores = torch.einsum("bhqd,bhkd->bhqk", query, key) * scaling
328
+ if attention_mask is not None:
329
+ scores = scores + attention_mask[:, :, :, : key.shape[-2]]
330
+
331
+ probs = F.softmax(scores, dim=-1, dtype=torch.float32).to(query.dtype)
332
+ probs = F.dropout(probs, p=dropout, training=module.training)
333
+ out = torch.einsum("bhqk,bhkd->bhqd", probs, value).transpose(1, 2).contiguous()
334
+
335
+ return out, probs
336
+
337
+
338
+ class KimiMLAAttention(nn.Module):
339
+ """
340
+ Multi-Latent Attention adapted from deepseek-v3
341
+ """
342
+
343
+ def __init__(self, config: KimiLinearConfig, layer_idx: int):
344
+ nn.Module.__init__(self)
345
+ self.config = config
346
+ self.layer_idx = layer_idx
347
+ self.hidden_size = config.hidden_size
348
+ self.num_heads = config.num_attention_heads
349
+ self.num_key_value_heads = config.num_key_value_heads
350
+ self.num_key_value_groups = self.num_heads // self.num_key_value_heads
351
+
352
+ self.attention_dropout = getattr(config, "attention_dropout", 0.0)
353
+
354
+ try:
355
+ self.q_lora_rank = config.q_lora_rank
356
+ self.qk_rope_head_dim = config.qk_rope_head_dim
357
+ self.kv_lora_rank = config.kv_lora_rank
358
+ self.v_head_dim = config.v_head_dim
359
+ self.qk_nope_head_dim = config.qk_nope_head_dim
360
+ self.q_head_dim = self.qk_nope_head_dim + self.qk_rope_head_dim
361
+ self.use_nope = config.mla_use_nope
362
+ self.scaling = self.q_head_dim ** (-0.5)
363
+ except Exception as e:
364
+ raise ValueError(
365
+ f"Kimi MLA config is not found or not properly formatted: {e}")
366
+
367
+ if self.q_lora_rank is not None:
368
+ self.q_a_proj = nn.Linear(
369
+ self.hidden_size, self.q_lora_rank, bias=False,
370
+ )
371
+ self.q_a_layernorm = KimiRMSNorm(self.q_lora_rank)
372
+ self.q_b_proj = nn.Linear(
373
+ self.q_lora_rank,
374
+ self.num_heads * self.q_head_dim,
375
+ bias=False,
376
+ )
377
+ else:
378
+ self.q_proj = nn.Linear(
379
+ self.hidden_size, self.num_heads * self.q_head_dim, bias=False,
380
+ )
381
+ self.kv_a_proj_with_mqa = nn.Linear(
382
+ self.hidden_size,
383
+ self.kv_lora_rank + self.qk_rope_head_dim,
384
+ bias=False,
385
+ )
386
+ self.kv_a_layernorm = KimiRMSNorm(self.kv_lora_rank)
387
+ self.kv_b_proj = nn.Linear(
388
+ self.kv_lora_rank,
389
+ self.num_heads
390
+ * (self.q_head_dim - self.qk_rope_head_dim + self.v_head_dim),
391
+ bias=False,
392
+ )
393
+ self.o_proj = nn.Linear(
394
+ self.num_heads * self.v_head_dim,
395
+ self.hidden_size,
396
+ bias=False,
397
+ )
398
+ self.is_causal = True
399
+ assert self.use_nope
400
+
401
+ self.use_output_gate = getattr(config, "mla_use_output_gate", False)
402
+ if self.use_output_gate:
403
+ projection_size = self.num_heads * self.v_head_dim
404
+ self.g_proj = nn.Linear(self.hidden_size, projection_size, bias=False)
405
+
406
+ self.rotary_emb = None
407
+
408
+ def forward(
409
+ self,
410
+ hidden_states: torch.Tensor,
411
+ attention_mask: torch.Tensor | None = None,
412
+ position_ids: torch.LongTensor | None = None,
413
+ past_key_values: Cache | None = None,
414
+ **kwargs,
415
+ ) -> tuple[torch.Tensor, torch.Tensor | None, tuple[torch.Tensor] | None]:
416
+ batch_size, seq_length = hidden_states.shape[:-1]
417
+ query_shape = (batch_size, seq_length, -1, self.q_head_dim)
418
+ key_shape = (batch_size, seq_length, -1,
419
+ self.qk_nope_head_dim + self.v_head_dim)
420
+
421
+ if self.q_lora_rank is not None:
422
+ q_states = self.q_b_proj(self.q_a_layernorm(self.q_a_proj(hidden_states)))
423
+ else:
424
+ q_states = self.q_proj(hidden_states)
425
+ q_states = q_states.view(query_shape).transpose(1, 2)
426
+ q_pass, q_rot = torch.split(
427
+ q_states, [self.qk_nope_head_dim, self.qk_rope_head_dim], dim=-1)
428
+
429
+ compressed_kv = self.kv_a_proj_with_mqa(hidden_states)
430
+ k_pass, k_rot = torch.split(
431
+ compressed_kv, [self.kv_lora_rank, self.qk_rope_head_dim], dim=-1)
432
+
433
+ k_pass = self.kv_b_proj(self.kv_a_layernorm(
434
+ k_pass)).view(key_shape).transpose(1, 2)
435
+ k_pass, value_states = torch.split(
436
+ k_pass, [self.qk_nope_head_dim, self.v_head_dim], dim=-1)
437
+
438
+ k_rot = k_rot.view(batch_size, 1, seq_length, self.qk_rope_head_dim)
439
+
440
+ k_rot = k_rot.expand(*k_pass.shape[:-1], -1)
441
+
442
+ query_states = torch.cat((q_pass, q_rot), dim=-1)
443
+ key_states = torch.cat((k_pass, k_rot), dim=-1)
444
+
445
+ if past_key_values is not None:
446
+ key_states, value_states = past_key_values.update(
447
+ key_states, value_states, self.layer_idx)
448
+
449
+ if self.config._attn_implementation == "flash_attention_2" and self.q_head_dim != self.v_head_dim:
450
+ value_states = F.pad(
451
+ value_states, [0, self.q_head_dim - self.v_head_dim])
452
+
453
+ attention_interface: Callable = eager_attention_forward
454
+ if self.config._attn_implementation != "eager":
455
+ attention_interface = ALL_ATTENTION_FUNCTIONS[self.config._attn_implementation]
456
+
457
+ attn_output, _ = attention_interface(
458
+ self,
459
+ query_states,
460
+ key_states,
461
+ value_states,
462
+ attention_mask,
463
+ dropout=0.0 if not self.training else self.attention_dropout,
464
+ scaling=self.scaling,
465
+ **kwargs,
466
+ )
467
+
468
+ if self.config._attn_implementation == "flash_attention_2" and self.q_head_dim != self.v_head_dim:
469
+ attn_output = attn_output[:, :, :, : self.v_head_dim]
470
+
471
+ attn_output = attn_output.reshape(
472
+ batch_size, seq_length, -1).contiguous()
473
+ if self.use_output_gate:
474
+ g = self.g_proj(hidden_states).sigmoid()
475
+ attn_output = attn_output * g
476
+ attn_output = self.o_proj(attn_output)
477
+ return attn_output
478
+
479
+
480
+ class KimiDeltaAttention(nn.Module):
481
+ def __init__(self, config: KimiLinearConfig, layer_idx: int):
482
+ super().__init__()
483
+ self.config = config
484
+ self.mode = "chunk"
485
+
486
+ self.hidden_size = config.hidden_size
487
+ self.conv_size = config.linear_attn_config["short_conv_kernel_size"]
488
+ self.head_dim = config.linear_attn_config["head_dim"]
489
+ self.num_heads = config.linear_attn_config["num_heads"]
490
+ self.head_k_dim = self.head_dim
491
+ self.num_k_heads = self.num_heads
492
+
493
+ self.layer_idx = layer_idx
494
+
495
+ assert self.mode in [
496
+ 'chunk', 'fused_recurrent'], f"Not supported mode `{self.mode}`."
497
+
498
+ projection_k_size = self.head_k_dim * self.num_k_heads
499
+ projection_size = self.head_dim * self.num_heads
500
+
501
+ self.q_proj = nn.Linear(
502
+ self.hidden_size, projection_k_size, bias=False)
503
+ self.k_proj = nn.Linear(
504
+ self.hidden_size, projection_k_size, bias=False)
505
+ self.v_proj = nn.Linear(self.hidden_size, projection_size, bias=False)
506
+
507
+ self.q_conv1d = ShortConvolution(
508
+ hidden_size=projection_k_size,
509
+ kernel_size=self.conv_size,
510
+ activation='silu',
511
+ )
512
+ self.k_conv1d = ShortConvolution(
513
+ hidden_size=projection_k_size,
514
+ kernel_size=self.conv_size,
515
+ activation='silu',
516
+ )
517
+ self.v_conv1d = ShortConvolution(
518
+ hidden_size=projection_size,
519
+ kernel_size=self.conv_size,
520
+ activation='silu',
521
+ )
522
+
523
+ self.A_log = torch.nn.Parameter(torch.log(torch.empty(
524
+ self.head_dim, dtype=torch.float32).uniform_(1, 16)))
525
+
526
+ self.f_a_proj = nn.Linear(self.hidden_size, self.head_dim, bias=False)
527
+ self.f_b_proj = nn.Linear(self.head_dim, projection_size, bias=False)
528
+
529
+ self.dt_bias = nn.Parameter(
530
+ torch.empty(projection_size, dtype=torch.float32))
531
+
532
+ self.b_proj = nn.Linear(self.hidden_size, self.num_heads, bias=False)
533
+
534
+ self.use_full_rank_gate = config.linear_attn_config.get("use_full_rank_gate", False)
535
+ self.gate_lower_bound = config.linear_attn_config.get("gate_lower_bound", None)
536
+ if self.use_full_rank_gate:
537
+ self.g_proj = nn.Linear(self.hidden_size, projection_size, bias=False)
538
+ else:
539
+ self.g_a_proj = nn.Linear(self.hidden_size, self.head_dim, bias=False)
540
+ self.g_b_proj = nn.Linear(self.head_dim, projection_size, bias=False)
541
+
542
+ self.o_norm = FusedRMSNormGated(
543
+ self.head_dim, eps=config.rms_norm_eps, activation='sigmoid')
544
+ self.o_proj = nn.Linear(projection_size, self.hidden_size, bias=False)
545
+
546
+ def forward(
547
+ self,
548
+ hidden_states: torch.Tensor,
549
+ attention_mask: torch.Tensor | None = None,
550
+ cache_params: KimiDynamicCache | None = None,
551
+ **kwargs: Unpack[dict],
552
+ ) -> tuple[torch.Tensor, torch.Tensor | None, Cache | None]:
553
+ if attention_mask is not None:
554
+ if attention_mask.dim() != 2:
555
+ attention_mask = kwargs.get("padding_mask")
556
+
557
+ if attention_mask is not None and attention_mask.dim() != 2:
558
+ raise ValueError(
559
+ "attention_mask must be a 0-1 matrix of shape [batch_size, seq_len] "
560
+ "(0 = padding). 3D masks are not supported here.",
561
+ )
562
+ use_cache = cache_params is not None
563
+ batch_size, q_len, _ = hidden_states.shape
564
+ mode = 'fused_recurrent' if use_cache and q_len == 1 else self.mode
565
+ if self.training:
566
+ assert mode == 'chunk', "Only chunk mode is supported in training."
567
+
568
+ cu_seqlens = kwargs.get('cu_seqlens')
569
+ indices = None
570
+ if attention_mask is not None:
571
+ indices, cu_seqlens, _ = get_unpad_data(attention_mask[:, -q_len:])
572
+ hidden_states = index_first_axis(
573
+ rearrange(hidden_states, "b s ... -> (b s) ..."), indices).unsqueeze(0)
574
+
575
+ conv_state_q, conv_state_k, conv_state_v = None, None, None
576
+ recurrent_state = None
577
+ if cache_params is not None:
578
+ if cache_params.conv_states[self.layer_idx] is not None:
579
+ conv_state_q, conv_state_k, conv_state_v = cache_params.conv_states[
580
+ self.layer_idx]
581
+ recurrent_state = cache_params.recurrent_states[self.layer_idx]
582
+
583
+ q_proj_states = self.q_proj(hidden_states)
584
+ k_proj_states = self.k_proj(hidden_states)
585
+ v_proj_states = self.v_proj(hidden_states)
586
+ q, conv_state_q = self.q_conv1d(
587
+ x=q_proj_states,
588
+ cache=conv_state_q,
589
+ output_final_state=use_cache,
590
+ cu_seqlens=cu_seqlens,
591
+ )
592
+ k, conv_state_k = self.k_conv1d(
593
+ x=k_proj_states,
594
+ cache=conv_state_k,
595
+ output_final_state=use_cache,
596
+ cu_seqlens=cu_seqlens,
597
+ )
598
+ v, conv_state_v = self.v_conv1d(
599
+ x=v_proj_states,
600
+ cache=conv_state_v,
601
+ output_final_state=use_cache,
602
+ cu_seqlens=cu_seqlens,
603
+ )
604
+ g = self.f_b_proj(self.f_a_proj(hidden_states))
605
+ g = rearrange(g, '... (h d) -> ... h d', d=self.head_dim)
606
+ # The released K3 checkpoint stores per-channel decay shared by all heads.
607
+ g = self.gate_lower_bound * torch.sigmoid(
608
+ self.A_log.float().exp().view(1, 1, 1, self.head_dim)
609
+ * (g.float() + self.dt_bias.float().view(1, 1, self.num_heads, self.head_dim))
610
+ ).to(g.dtype)
611
+ beta = self.b_proj(hidden_states).float()
612
+
613
+ q, k = map(lambda x: rearrange(
614
+ x, '... (h d) -> ... h d', d=self.head_k_dim), (q, k))
615
+ v = rearrange(v, '... (h d) -> ... h d', d=self.head_dim)
616
+
617
+ if mode == 'chunk':
618
+ o, recurrent_state = chunk_kda(
619
+ q=q,
620
+ k=k,
621
+ v=v,
622
+ g=g,
623
+ beta=beta,
624
+ A_log=self.A_log,
625
+ dt_bias=self.dt_bias,
626
+ initial_state=recurrent_state,
627
+ output_final_state=True,
628
+ use_qk_l2norm_in_kernel=True,
629
+ use_gate_in_kernel=False,
630
+ use_beta_sigmoid_in_kernel=True,
631
+ safe_gate=False,
632
+ lower_bound=self.gate_lower_bound,
633
+ transpose_state_layout=True,
634
+ cu_seqlens=cu_seqlens,
635
+ )
636
+ else:
637
+ o, recurrent_state = fused_recurrent_kda(
638
+ q=q,
639
+ k=k,
640
+ v=v,
641
+ g=g,
642
+ beta=beta,
643
+ A_log=self.A_log,
644
+ dt_bias=self.dt_bias,
645
+ initial_state=recurrent_state,
646
+ output_final_state=True,
647
+ use_qk_l2norm_in_kernel=True,
648
+ use_gate_in_kernel=False,
649
+ use_beta_sigmoid_in_kernel=True,
650
+ lower_bound=self.gate_lower_bound,
651
+ transpose_state_layout=True,
652
+ cu_seqlens=cu_seqlens,
653
+ )
654
+ if cache_params is not None:
655
+ cache_params.recurrent_states[self.layer_idx] = recurrent_state
656
+ cache_params.conv_states[self.layer_idx] = (
657
+ conv_state_q, conv_state_k, conv_state_v)
658
+
659
+ if self.use_full_rank_gate:
660
+ g = self.g_proj(hidden_states)
661
+ else:
662
+ g = self.g_b_proj(self.g_a_proj(hidden_states))
663
+ g = rearrange(g, '... (h d) -> ... h d', d=self.head_dim)
664
+ o = self.o_norm(o, g)
665
+
666
+ o = rearrange(o, 'b t h d -> b t (h d)')
667
+ o = self.o_proj(o)
668
+ if attention_mask is not None:
669
+ o = pad_input(o.squeeze(0), indices, batch_size, q_len)
670
+
671
+ return o
672
+
673
+
674
+ class KimiMoEGate(nn.Module):
675
+ """
676
+ MoEGate adapted from Deepseek-V3.
677
+ Parameter correspondences:
678
+ num_experts -> n_routed_experts
679
+ num_experts_per_token -> num_experts_per_tok
680
+ num_expert_group -> n_group
681
+ moe_router_activation_func -> scoring_func
682
+ """
683
+
684
+ def __init__(self, config: KimiLinearConfig):
685
+ super().__init__()
686
+ self.config = config
687
+ self.top_k = config.num_experts_per_token
688
+ self.num_experts = config.num_experts
689
+ self.routed_scaling_factor = config.routed_scaling_factor
690
+ self.moe_router_activation_func = config.moe_router_activation_func
691
+ self.num_expert_group = getattr(config, "num_expert_group", 1)
692
+ self.topk_group = getattr(config, "topk_group", 1)
693
+
694
+ # topk selection algorithm
695
+ self.moe_renormalize = config.moe_renormalize
696
+ self.gating_dim = config.hidden_size
697
+ self.weight = nn.Parameter(
698
+ torch.empty((self.num_experts, self.gating_dim)),
699
+ )
700
+
701
+ self.e_score_correction_bias = nn.Parameter(
702
+ torch.empty(self.num_experts),
703
+ )
704
+ self.reset_parameters()
705
+
706
+ def reset_parameters(self) -> None:
707
+ import torch.nn.init as init
708
+
709
+ init.kaiming_uniform_(self.weight, a=math.sqrt(5))
710
+
711
+ def forward(self, hidden_states):
712
+ bsz, seq_len, h = hidden_states.shape
713
+ # compute gating score
714
+ hidden_states = hidden_states.view(-1, h)
715
+ logits = F.linear(
716
+ hidden_states.type(torch.float32), self.weight.type(
717
+ torch.float32), None,
718
+ )
719
+ if self.moe_router_activation_func == "sigmoid":
720
+ scores = logits.sigmoid()
721
+ elif self.moe_router_activation_func == "softmax":
722
+ scores = logits.softmax(dim=1)
723
+ else:
724
+ raise NotImplementedError(
725
+ f"insupportable scoring function for MoE gating: {self.moe_router_activation_func}",
726
+ )
727
+
728
+ # select top-k experts
729
+ assert not self.training
730
+ scores = scores.view(bsz * seq_len, -1)
731
+ scores_for_choice = scores + self.e_score_correction_bias.unsqueeze(0)
732
+ if self.num_expert_group > 1 and self.num_expert_group > self.topk_group:
733
+ group_scores = (
734
+ scores_for_choice.view(
735
+ bsz * seq_len, self.num_expert_group, -1).topk(2, dim=-1)[0].sum(dim=-1)
736
+ ) # [n, num_expert_group]
737
+ group_idx = torch.topk(
738
+ group_scores, k=self.topk_group, dim=-1, sorted=False,
739
+ )[
740
+ 1
741
+ ] # [n, top_k_group]
742
+ group_mask = torch.zeros_like(group_scores) # [n, num_expert_group]
743
+ group_mask.scatter_(1, group_idx, 1) # [n, num_expert_group]
744
+ score_mask = (
745
+ group_mask.unsqueeze(-1)
746
+ .expand(
747
+ bsz * seq_len, self.num_expert_group, self.num_experts // self.num_expert_group,
748
+ )
749
+ .reshape(bsz * seq_len, -1)
750
+ ) # [n, e]
751
+ tmp_scores = scores_for_choice.masked_fill(
752
+ ~score_mask.bool(), float("-inf")) # [n, e]
753
+ else:
754
+ tmp_scores = scores_for_choice
755
+ _, topk_idx = torch.topk(
756
+ tmp_scores, k=self.top_k, dim=-1, sorted=False,
757
+ )
758
+ topk_weight = scores.gather(1, topk_idx)
759
+
760
+ # norm gate to sum 1
761
+ if self.top_k > 1 and self.moe_renormalize:
762
+ denominator = topk_weight.sum(dim=-1, keepdim=True) + 1e-20
763
+ topk_weight = topk_weight / denominator
764
+ # must multiply the scaling factor
765
+ topk_weight = topk_weight * self.routed_scaling_factor
766
+
767
+ return topk_idx, topk_weight
768
+
769
+
770
+ class KimiSparseMoeBlock(nn.Module):
771
+ """
772
+ Adapted from Deepseek-V3's MOE implementation
773
+ The namings are consistent with Kimi's version.
774
+ """
775
+
776
+ def __init__(self, config: KimiLinearConfig):
777
+ super().__init__()
778
+ self.config = config
779
+ self.hidden_dim = config.hidden_size
780
+ self.num_experts = config.num_experts
781
+ self.top_k = config.num_experts_per_token
782
+ self.moe_renormalize = config.moe_renormalize
783
+
784
+ self.use_latent_moe = getattr(config, "routed_expert_hidden_size", None) is not None
785
+ self.moe_hidden_size = (
786
+ config.routed_expert_hidden_size
787
+ if self.use_latent_moe else config.hidden_size
788
+ )
789
+ self.latent_moe_use_norm = getattr(config, "latent_moe_use_norm", False)
790
+
791
+ self.ep_size = 1
792
+ self.experts_per_rank = config.num_experts
793
+ self.ep_rank = 0
794
+ self.experts = nn.ModuleList(
795
+ [
796
+ KimiBlockSparseMLP(
797
+ config,
798
+ hidden_size=self.moe_hidden_size,
799
+ intermediate_size=config.moe_intermediate_size,
800
+ )
801
+ for _ in range(config.num_experts)
802
+ ],
803
+ )
804
+ self.gate = KimiMoEGate(config)
805
+ if config.num_shared_experts is not None:
806
+ intermediate_size = config.moe_intermediate_size * config.num_shared_experts
807
+ self.shared_experts = KimiMLP(
808
+ config=config, intermediate_size=intermediate_size,
809
+ )
810
+
811
+ if self.use_latent_moe:
812
+ self.routed_expert_down_proj = nn.Linear(
813
+ config.hidden_size, self.moe_hidden_size, bias=False,
814
+ )
815
+ self.routed_expert_up_proj = nn.Linear(
816
+ self.moe_hidden_size, config.hidden_size, bias=False,
817
+ )
818
+ if self.latent_moe_use_norm:
819
+ self.routed_expert_norm = KimiRMSNorm(
820
+ self.moe_hidden_size, eps=config.rms_norm_eps,
821
+ )
822
+
823
+ def forward(self, hidden_states):
824
+ identity = hidden_states
825
+ orig_shape = hidden_states.shape
826
+ topk_idx, topk_weight = self.gate(hidden_states)
827
+ hidden_states = hidden_states.view(-1, hidden_states.shape[-1])
828
+
829
+ if self.use_latent_moe:
830
+ hidden_states = self.routed_expert_down_proj(hidden_states)
831
+
832
+ if not self.training:
833
+ y = self.moe_infer(hidden_states, topk_idx, topk_weight)
834
+ else:
835
+ raise NotImplementedError("Training mode is not supported in KimiSparseMoeBlock")
836
+
837
+ if self.use_latent_moe:
838
+ if self.latent_moe_use_norm:
839
+ y = self.routed_expert_norm(y)
840
+ y = self.routed_expert_up_proj(y)
841
+
842
+ y = y.view(*orig_shape)
843
+
844
+ if self.config.num_shared_experts is not None:
845
+ y = y + self.shared_experts(identity)
846
+ return y
847
+
848
+ @torch.no_grad()
849
+ def moe_infer(self, x, topk_ids, topk_weight):
850
+ cnts = topk_ids.new_zeros((topk_ids.shape[0], len(self.experts)))
851
+ cnts.scatter_(1, topk_ids, 1)
852
+ tokens_per_expert = cnts.sum(dim=0)
853
+ idxs = topk_ids.view(-1).argsort()
854
+ sorted_tokens = x[idxs // topk_ids.shape[1]]
855
+
856
+ tokens_per_expert = tokens_per_expert.cpu().numpy()
857
+
858
+ outputs = []
859
+ start_idx = 0
860
+ for i, num_tokens in enumerate(tokens_per_expert):
861
+ end_idx = start_idx + num_tokens
862
+ if num_tokens == 0:
863
+ continue
864
+ expert = self.experts[i + self.ep_rank * self.experts_per_rank]
865
+ tokens_for_this_expert = sorted_tokens[start_idx:end_idx]
866
+ expert_out = expert(tokens_for_this_expert)
867
+ outputs.append(expert_out)
868
+ start_idx = end_idx
869
+
870
+ outs = torch.cat(outputs, dim=0) if len(
871
+ outputs) else sorted_tokens.new_empty(0)
872
+
873
+ new_x = torch.empty_like(outs)
874
+ new_x[idxs] = outs
875
+ final_out = (
876
+ new_x.view(*topk_ids.shape, -1)
877
+ .type(topk_weight.dtype)
878
+ .mul_(topk_weight.unsqueeze(dim=-1))
879
+ .sum(dim=1)
880
+ .type(new_x.dtype)
881
+ )
882
+ return final_out
883
+
884
+
885
+ class KimiDecoderLayer(nn.Module):
886
+ def __init__(self, config: KimiLinearConfig, layer_idx: int):
887
+ super().__init__()
888
+ self.hidden_size = config.hidden_size
889
+ self.config = config
890
+ self.layer_idx = layer_idx
891
+ if config.is_kda_layer(layer_idx):
892
+ self.is_linear_attn = True
893
+ self.self_attn = KimiDeltaAttention(
894
+ config=config, layer_idx=layer_idx)
895
+ elif config.is_mla:
896
+ self.is_linear_attn = False
897
+ self.self_attn = KimiMLAAttention(
898
+ config=config, layer_idx=layer_idx)
899
+ else:
900
+ raise NotImplementedError
901
+ if (
902
+ config.num_experts is not None
903
+ and layer_idx >= config.first_k_dense_replace
904
+ and layer_idx % getattr(config, "moe_layer_freq", 1) == 0
905
+ ):
906
+ self.block_sparse_moe = KimiSparseMoeBlock(config)
907
+ else:
908
+ self.mlp = KimiMLP(config)
909
+ self.input_layernorm = KimiRMSNorm(
910
+ config.hidden_size, eps=config.rms_norm_eps)
911
+ self.post_attention_layernorm = KimiRMSNorm(
912
+ config.hidden_size, eps=config.rms_norm_eps)
913
+
914
+ # Attention residual
915
+ self.use_attn_residuals = getattr(config, "attn_res_block_size", None) is not None
916
+ if self.use_attn_residuals:
917
+ self.attn_res_block_size = config.attn_res_block_size
918
+ self.self_attention_res_norm = KimiRMSNorm(
919
+ config.hidden_size, eps=config.rms_norm_eps)
920
+ self.mlp_res_norm = KimiRMSNorm(
921
+ config.hidden_size, eps=config.rms_norm_eps)
922
+ self.self_attention_res_proj = nn.Linear(
923
+ config.hidden_size, 1, bias=False)
924
+ self.mlp_res_proj = nn.Linear(
925
+ config.hidden_size, 1, bias=False)
926
+
927
+ def forward(
928
+ self,
929
+ hidden_states: torch.Tensor,
930
+ attention_mask: torch.Tensor | None = None,
931
+ position_ids: torch.LongTensor | None = None,
932
+ past_key_values: tuple[torch.Tensor] | None = None,
933
+ output_attentions: bool | None = False,
934
+ use_cache: bool | None = False,
935
+ block_residual: torch.Tensor | None = None,
936
+ **kwargs: Unpack[FlashAttentionKwargs],
937
+ ):
938
+ if self.use_attn_residuals:
939
+ return self._forward_attn_residual(
940
+ hidden_states, attention_mask, position_ids,
941
+ past_key_values, output_attentions, use_cache,
942
+ block_residual, **kwargs)
943
+
944
+ residual = hidden_states
945
+
946
+ hidden_states = self.input_layernorm(hidden_states)
947
+
948
+ # Self Attention
949
+ if self.is_linear_attn is False:
950
+ hidden_states = self.self_attn(
951
+ hidden_states=hidden_states,
952
+ attention_mask=attention_mask,
953
+ position_ids=position_ids,
954
+ past_key_values=past_key_values,
955
+ output_attentions=output_attentions,
956
+ use_cache=use_cache,
957
+ **kwargs,
958
+ )
959
+ else:
960
+ hidden_states = self.self_attn(
961
+ hidden_states=hidden_states,
962
+ attention_mask=attention_mask,
963
+ cache_params=past_key_values,
964
+ output_attentions=output_attentions,
965
+ use_cache=use_cache,
966
+ **kwargs,
967
+ )
968
+ hidden_states = residual + hidden_states
969
+
970
+ # Fully Connected
971
+ residual = hidden_states
972
+ hidden_states = self.post_attention_layernorm(hidden_states)
973
+ if hasattr(self, "block_sparse_moe"):
974
+ hidden_states = self.block_sparse_moe(hidden_states)
975
+ else:
976
+ hidden_states = self.mlp(hidden_states)
977
+ hidden_states = residual + hidden_states
978
+
979
+ return hidden_states
980
+
981
+ def _forward_attn_residual(
982
+ self,
983
+ hidden_states: torch.Tensor,
984
+ attention_mask: torch.Tensor | None = None,
985
+ position_ids: torch.LongTensor | None = None,
986
+ past_key_values: tuple[torch.Tensor] | None = None,
987
+ output_attentions: bool | None = False,
988
+ use_cache: bool | None = False,
989
+ block_residual: torch.Tensor | None = None,
990
+ **kwargs: Unpack[FlashAttentionKwargs],
991
+ ):
992
+ batch_size, seq_len, hidden_size = hidden_states.shape
993
+ prefix_sum = hidden_states
994
+
995
+ if block_residual is not None and block_residual.shape[1] > 0:
996
+ hidden_states = _apply_attn_res(
997
+ prefix_sum.view(-1, hidden_size),
998
+ block_residual,
999
+ self.self_attention_res_proj,
1000
+ self.self_attention_res_norm,
1001
+ ).view(batch_size, seq_len, hidden_size)
1002
+
1003
+ if self.layer_idx % self.attn_res_block_size == 0:
1004
+ block_residual = torch.cat(
1005
+ [block_residual, prefix_sum.view(-1, hidden_size).unsqueeze(1)], dim=1)
1006
+ prefix_sum = None
1007
+
1008
+ hidden_states = self.input_layernorm(hidden_states)
1009
+
1010
+ # Self Attention
1011
+ if self.is_linear_attn is False:
1012
+ hidden_states = self.self_attn(
1013
+ hidden_states=hidden_states,
1014
+ attention_mask=attention_mask,
1015
+ position_ids=position_ids,
1016
+ past_key_values=past_key_values,
1017
+ output_attentions=output_attentions,
1018
+ use_cache=use_cache,
1019
+ **kwargs,
1020
+ )
1021
+ else:
1022
+ hidden_states = self.self_attn(
1023
+ hidden_states=hidden_states,
1024
+ attention_mask=attention_mask,
1025
+ cache_params=past_key_values,
1026
+ output_attentions=output_attentions,
1027
+ use_cache=use_cache,
1028
+ **kwargs,
1029
+ )
1030
+
1031
+ if prefix_sum is not None:
1032
+ prefix_sum = prefix_sum + hidden_states
1033
+ else:
1034
+ prefix_sum = hidden_states
1035
+
1036
+ hidden_states = _apply_attn_res(
1037
+ prefix_sum.view(-1, hidden_size),
1038
+ block_residual,
1039
+ self.mlp_res_proj,
1040
+ self.mlp_res_norm,
1041
+ ).view(batch_size, seq_len, hidden_size)
1042
+
1043
+ hidden_states = self.post_attention_layernorm(hidden_states)
1044
+ if hasattr(self, "block_sparse_moe"):
1045
+ hidden_states = self.block_sparse_moe(hidden_states)
1046
+ else:
1047
+ hidden_states = self.mlp(hidden_states)
1048
+
1049
+ if prefix_sum is None:
1050
+ prefix_sum = hidden_states
1051
+ else:
1052
+ prefix_sum = prefix_sum + hidden_states
1053
+
1054
+ return prefix_sum, block_residual
1055
+
1056
+
1057
+ class KimiPreTrainedModel(PreTrainedModel):
1058
+ config_class = KimiLinearConfig
1059
+ base_model_prefix = "model"
1060
+ supports_gradient_checkpointing = True
1061
+ _no_split_modules = ["KimiDecoderLayer"]
1062
+ _skip_keys_device_placement = "past_key_values"
1063
+ _supports_flash_attn_2 = True
1064
+ _can_record_outputs = {
1065
+ "router_logits": OutputRecorder(KimiBlockSparseMLP, index=1),
1066
+ "hidden_states": KimiDecoderLayer,
1067
+ "attentions": KimiMLAAttention,
1068
+ }
1069
+ _is_stateful = True
1070
+
1071
+ def _init_weights(self, module):
1072
+ std = self.config.initializer_range
1073
+ if isinstance(module, nn.Linear):
1074
+ module.weight.data.normal_(mean=0.0, std=std)
1075
+ if module.bias is not None:
1076
+ module.bias.data.zero_()
1077
+ elif isinstance(module, nn.Embedding):
1078
+ module.weight.data.normal_(mean=0.0, std=std)
1079
+ if module.padding_idx is not None:
1080
+ module.weight.data[module.padding_idx].zero_()
1081
+
1082
+
1083
+ def _apply_attn_res(prefix_sum, block_residual, proj, norm):
1084
+ """
1085
+ prefix_sum: (num_tokens, hidden_size)
1086
+ block_residual: (num_tokens, num_blocks, hidden_size)
1087
+ """
1088
+ v = torch.cat((block_residual, prefix_sum.unsqueeze(1)), dim=1)
1089
+ v_float = v.float()
1090
+ variance = v_float.pow(2).mean(-1, keepdim=True)
1091
+ k = v_float * torch.rsqrt(variance + norm.variance_epsilon)
1092
+ score_weight = norm.weight.float() * proj.weight.squeeze(0).float()
1093
+ scores = (k * score_weight).sum(-1)
1094
+ probs = scores.softmax(-1).unsqueeze(1)
1095
+ hidden_states = torch.matmul(probs, v_float).squeeze(1)
1096
+ return hidden_states.to(v.dtype)
1097
+
1098
+ class KimiLinearModel(KimiPreTrainedModel):
1099
+ def __init__(self, config: KimiLinearConfig):
1100
+ super().__init__(config)
1101
+ self.padding_idx = config.pad_token_id
1102
+ self.vocab_size = config.vocab_size
1103
+
1104
+ self.embed_tokens = nn.Embedding(
1105
+ config.vocab_size, config.hidden_size, self.padding_idx)
1106
+ self.layers = nn.ModuleList([KimiDecoderLayer(
1107
+ config, layer_idx) for layer_idx in range(config.num_hidden_layers)])
1108
+ self.norm = KimiRMSNorm(
1109
+ config.hidden_size, eps=config.rms_norm_eps)
1110
+
1111
+ self.use_attn_residuals = getattr(config, "attn_res_block_size", None) is not None
1112
+ if self.use_attn_residuals:
1113
+ self.output_attn_res_norm = KimiRMSNorm(
1114
+ config.hidden_size, eps=config.rms_norm_eps)
1115
+ self.output_attn_res_proj = nn.Linear(
1116
+ config.hidden_size, 1, bias=False)
1117
+
1118
+ config._attn_implementation = getattr(config, "_attn_implementation", "eager")
1119
+
1120
+ self._use_flash_attention_2 = config._attn_implementation == "flash_attention_2"
1121
+ self.gradient_checkpointing = False
1122
+ # Initialize weights and apply final processing
1123
+ self.post_init()
1124
+
1125
+ def _update_linear_attn_mask(self, attention_mask, cache_position):
1126
+ """
1127
+ NOTE: Left-padding is used for linear attention mask.
1128
+ No need for zeroing states when
1129
+ 1. Cached forward
1130
+ 2. Attending to all inputs
1131
+ """
1132
+ linear_attn_mask = attention_mask
1133
+ if cache_position[0] > 0 or (attention_mask is not None and torch.all(attention_mask == 1)):
1134
+ linear_attn_mask = None
1135
+ return linear_attn_mask
1136
+
1137
+ @check_model_inputs
1138
+ # @auto_docstring
1139
+ def forward(
1140
+ self,
1141
+ input_ids: torch.LongTensor = None,
1142
+ attention_mask: torch.Tensor | None = None,
1143
+ position_ids: torch.LongTensor | None = None,
1144
+ past_key_values: Cache | None = None,
1145
+ inputs_embeds: torch.FloatTensor | None = None,
1146
+ cache_position: torch.LongTensor | None = None,
1147
+ use_cache: bool | None = None,
1148
+ **kwargs: Unpack[TransformersKwargs],
1149
+ ) -> tuple | BaseModelOutputWithPast:
1150
+
1151
+ use_cache = use_cache if use_cache is not None else self.config.use_cache
1152
+
1153
+ if (input_ids is None) and (inputs_embeds is None):
1154
+ raise ValueError(
1155
+ "You must specify exactly one of input_ids or inputs_embeds")
1156
+
1157
+ # Get inputs_embeds
1158
+ if inputs_embeds is None:
1159
+ inputs_embeds = self.embed_tokens(input_ids)
1160
+
1161
+ if use_cache and past_key_values is None:
1162
+ past_key_values = KimiDynamicCache(config=self.config)
1163
+
1164
+ if cache_position is None:
1165
+ past_seen_tokens = past_key_values.get_seq_length(
1166
+ ) if past_key_values is not None else 0
1167
+ cache_position: torch.Tensor = torch.arange(
1168
+ past_seen_tokens, past_seen_tokens + inputs_embeds.shape[1], device=inputs_embeds.device,
1169
+ )
1170
+
1171
+ if position_ids is None:
1172
+ position_ids = cache_position.unsqueeze(0)
1173
+
1174
+ if version.parse(transformers.__version__) >= version.parse("5.0.0"):
1175
+ causal_mask = create_causal_mask(
1176
+ config=self.config,
1177
+ inputs_embeds=inputs_embeds,
1178
+ attention_mask=attention_mask,
1179
+ past_key_values=past_key_values,
1180
+ position_ids=position_ids,
1181
+ )
1182
+ else:
1183
+ causal_mask = create_causal_mask(
1184
+ config=self.config,
1185
+ input_embeds=inputs_embeds,
1186
+ attention_mask=attention_mask,
1187
+ cache_position=cache_position,
1188
+ past_key_values=past_key_values,
1189
+ position_ids=position_ids,
1190
+ )
1191
+ linear_attn_mask = self._update_linear_attn_mask(
1192
+ attention_mask, cache_position)
1193
+
1194
+ hidden_states = inputs_embeds
1195
+ if past_key_values is not None:
1196
+ assert isinstance(past_key_values, KimiDynamicCache)
1197
+
1198
+ block_residual = None
1199
+ if self.use_attn_residuals:
1200
+ block_residual = hidden_states.new_zeros(
1201
+ hidden_states.shape[0] * hidden_states.shape[1], 0,
1202
+ hidden_states.shape[2])
1203
+
1204
+ for decoder_layer in self.layers:
1205
+ layer_mask = linear_attn_mask if decoder_layer.is_linear_attn else causal_mask
1206
+
1207
+ if self.use_attn_residuals:
1208
+ hidden_states, block_residual = decoder_layer(
1209
+ hidden_states,
1210
+ attention_mask=layer_mask,
1211
+ past_key_values=past_key_values,
1212
+ cache_position=cache_position,
1213
+ block_residual=block_residual,
1214
+ **kwargs,
1215
+ )
1216
+ else:
1217
+ hidden_states = decoder_layer(
1218
+ hidden_states,
1219
+ attention_mask=layer_mask,
1220
+ past_key_values=past_key_values,
1221
+ cache_position=cache_position,
1222
+ **kwargs,
1223
+ )
1224
+
1225
+ if self.use_attn_residuals:
1226
+ hidden_states = self._apply_output_attn_res(
1227
+ hidden_states, block_residual)
1228
+
1229
+ hidden_states = self.norm(hidden_states)
1230
+
1231
+ return BaseModelOutputWithPast(
1232
+ last_hidden_state=hidden_states,
1233
+ past_key_values=past_key_values,
1234
+ )
1235
+
1236
+ def _apply_output_attn_res(self, hidden_states, block_residual):
1237
+ batch_size, seq_len, hidden_size = hidden_states.shape
1238
+ return _apply_attn_res(
1239
+ hidden_states.view(-1, hidden_size),
1240
+ block_residual,
1241
+ self.output_attn_res_proj,
1242
+ self.output_attn_res_norm,
1243
+ ).view(batch_size, seq_len, hidden_size)
1244
+
1245
+
1246
+ class KimiLinearForCausalLM(KimiPreTrainedModel, GenerationMixin):
1247
+ @classmethod
1248
+ def _supports_default_dynamic_cache(cls) -> bool:
1249
+ return False
1250
+
1251
+ _tied_weights_keys = (
1252
+ {"lm_head.weight": "model.embed_tokens.weight"}
1253
+ if version.parse(transformers.__version__) >= version.parse("5.0.0")
1254
+ else ["lm_head.weight"]
1255
+ )
1256
+
1257
+ def __init__(self, config):
1258
+ super().__init__(config)
1259
+ self.model = KimiLinearModel(config)
1260
+ self.vocab_size = config.vocab_size
1261
+ self.lm_head = nn.Linear(
1262
+ config.hidden_size, config.vocab_size, bias=False)
1263
+
1264
+ # Initialize weights and apply final processing
1265
+ self.post_init()
1266
+
1267
+ @can_return_tuple
1268
+ # @auto_docstring
1269
+ def forward(
1270
+ self,
1271
+ input_ids: torch.LongTensor = None,
1272
+ attention_mask: torch.Tensor | None = None,
1273
+ position_ids: torch.LongTensor | None = None,
1274
+ past_key_values: list[torch.FloatTensor] | None = None,
1275
+ inputs_embeds: torch.FloatTensor | None = None,
1276
+ labels: torch.LongTensor | None = None,
1277
+ use_cache: bool | None = None,
1278
+ output_attentions: bool | None = None,
1279
+ output_hidden_states: bool | None = None,
1280
+ generation_mode: bool | None = None,
1281
+ return_dict: bool | None = None,
1282
+ cache_position: torch.LongTensor | None = None,
1283
+ **kwargs: Unpack[TransformersKwargs],
1284
+ ) -> tuple | CausalLMOutputWithPast:
1285
+ r"""
1286
+ Args:
1287
+ labels (`torch.LongTensor` of shape `(batch_size, sequence_length)`, *optional*):
1288
+ Labels for computing the masked language modeling loss. Indices should either be in `[0, ...,
1289
+ config.vocab_size]` or -100 (see `input_ids` docstring). Tokens with indices set to `-100` are ignored
1290
+ (masked), the loss is only computed for the tokens with labels in `[0, ..., config.vocab_size]`.
1291
+ """
1292
+
1293
+ output_attentions = output_attentions if output_attentions is not None else self.config.output_attentions
1294
+ output_hidden_states = (
1295
+ output_hidden_states if output_hidden_states is not None else self.config.output_hidden_states
1296
+ )
1297
+ return_dict = return_dict if return_dict is not None else self.config.use_return_dict
1298
+
1299
+ outputs = self.model(
1300
+ input_ids=input_ids,
1301
+ attention_mask=attention_mask,
1302
+ position_ids=position_ids,
1303
+ past_key_values=past_key_values,
1304
+ inputs_embeds=inputs_embeds,
1305
+ use_cache=use_cache,
1306
+ output_attentions=output_attentions,
1307
+ output_hidden_states=output_hidden_states,
1308
+ return_dict=return_dict,
1309
+ cache_position=cache_position,
1310
+ )
1311
+
1312
+ logits = outputs[0]
1313
+ if generation_mode:
1314
+ logits = logits[:, -1:]
1315
+ logits = self.lm_head(logits)
1316
+
1317
+ loss = None
1318
+ if labels is not None:
1319
+ loss = self.loss_function(
1320
+ logits, labels, self.vocab_size, **kwargs)
1321
+
1322
+ return CausalLMOutputWithPast(
1323
+ loss=loss,
1324
+ logits=logits,
1325
+ past_key_values=outputs.past_key_values,
1326
+ hidden_states=outputs.hidden_states,
1327
+ attentions=outputs.attentions,
1328
+ )
preprocessor_config.json ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "media_proc_cfg": {
3
+ "in_patch_limit": 65536,
4
+ "patch_size": 14,
5
+ "image_mean": [
6
+ 0.5,
7
+ 0.5,
8
+ 0.5
9
+ ],
10
+ "image_std": [
11
+ 0.5,
12
+ 0.5,
13
+ 0.5
14
+ ],
15
+ "merge_kernel_size": 2,
16
+ "fixed_output_tokens": null,
17
+ "patch_limit_on_one_side": 512,
18
+ "in_patch_limit_each_frame": 16384,
19
+ "in_patch_limit_video": 655360,
20
+ "sample_fps": 8.0,
21
+ "max_num_frames_each_video": null,
22
+ "temporal_merge_kernel_size": 4,
23
+ "timestamp_mode": "hh:mm:ss.fff",
24
+ "transparent_bg_config": {
25
+ "pattern": "chessboard",
26
+ "chessboard_square_size": 8,
27
+ "chessboard_square_on_top_left": true,
28
+ "chessboard_white_value": 255,
29
+ "chessboard_gray_value": 180
30
+ },
31
+ "transparent_bg_fill_stage": "after_resize",
32
+ "config_type": "media_proc.processors.moonvit.MoonViTMediaProcessorConfig"
33
+ },
34
+ "auto_map": {
35
+ "AutoProcessor": "kimi_k3_processor.KimiK3Processor",
36
+ "AutoImageProcessor": "kimi_k3_vision_processing.KimiK3VisionProcessor"
37
+ }
38
+ }
tiktoken.model ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:b6c497a7469b33ced9c38afb1ad6e47f03f5e5dc05f15930799210ec050c5103
3
+ size 2795286
tokenization_kimi.py ADDED
@@ -0,0 +1,408 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from logging import getLogger
3
+ from pathlib import Path
4
+ from shutil import copyfile
5
+ from typing import Dict, Iterator, List, Optional, Tuple, Union, cast
6
+
7
+ import tiktoken
8
+ from tiktoken.load import load_tiktoken_bpe
9
+ from tokenizers import AddedToken
10
+ from transformers.convert_slow_tokenizer import bytes_to_unicode
11
+ from transformers.tokenization_utils import PreTrainedTokenizer
12
+
13
+ try:
14
+ from .encoding_k3 import build_chat_segments, is_batched_conversation
15
+ except ImportError: # pragma: no cover - supports direct file execution/import.
16
+ from encoding_k3 import build_chat_segments, is_batched_conversation
17
+
18
+ logger = getLogger(__name__)
19
+ VOCAB_FILES_NAMES = {"vocab_file": "tiktoken.model"}
20
+
21
+
22
+ class TikTokenTokenizer(PreTrainedTokenizer):
23
+ """
24
+ Tokenizing and encoding/decoding text using the Tiktoken tokenizer. See megatron/tokenizer/tiktoken_tokenizer.py.
25
+
26
+ This tokenizer inherits from [`PreTrainedTokenizer`] which contains most of the main methods. Users should refer to
27
+ this superclass for more information regarding those methods.
28
+
29
+ Args:
30
+ vocab_file (`str`):
31
+ The path to the Tiktoken model file.
32
+ bos_token (`str` or `tokenizers.AddedToken`, *optional*, defaults to `"<|begin_of_text|>",`):
33
+ The beginning of sequence token that was used during pretraining. Can be used a sequence classifier token.
34
+ eos_token (`str` or `tokenizers.AddedToken`, *optional*, defaults to `"<|end_of_text|>"`):
35
+ The end of sequence token.
36
+ unk_token (`str` or `tokenizers.AddedToken`, *optional*, defaults to `"<|reserved_special_token_249|>"`):
37
+ The unknown token. A token that is not in the vocabulary cannot be converted to an ID and is set to be this
38
+ token instead. The second to last item in special_tokens.
39
+ pad_token (`str` or `tokenizers.AddedToken`, *optional*, defaults to `"<|reserved_special_token_250|>"`):
40
+ The token used for padding, for example when batching sequences of different lengths.
41
+ additional_special_tokens (list of `str`, *optional*):
42
+ A tuple or a list of additional tokens, which will be marked as `special`, meaning that they will be
43
+ skipped when decoding if `skip_special_tokens` is set to `True`.
44
+ """
45
+
46
+ vocab_files_names = VOCAB_FILES_NAMES
47
+
48
+ model_input_names = ["input_ids", "attention_mask"]
49
+
50
+ special_tokens: Dict[str, int]
51
+
52
+ num_reserved_special_tokens = 256
53
+
54
+ pat_str = "|".join([
55
+ r"""[\p{Han}]+""",
56
+ r"""[^\r\n\p{L}\p{N}]?[\p{Lu}\p{Lt}\p{Lm}\p{Lo}\p{M}&&[^\p{Han}]]*[\p{Ll}\p{Lm}\p{Lo}\p{M}&&[^\p{Han}]]+(?i:'s|'t|'re|'ve|'m|'ll|'d)?""",
57
+ r"""[^\r\n\p{L}\p{N}]?[\p{Lu}\p{Lt}\p{Lm}\p{Lo}\p{M}&&[^\p{Han}]]+[\p{Ll}\p{Lm}\p{Lo}\p{M}&&[^\p{Han}]]*(?i:'s|'t|'re|'ve|'m|'ll|'d)?""",
58
+ r"""\p{N}{1,3}""",
59
+ r""" ?[^\s\p{L}\p{N}]+[\r\n]*""",
60
+ r"""\s*[\r\n]+""",
61
+ r"""\s+(?!\S)""",
62
+ r"""\s+""",
63
+ ])
64
+
65
+ def __init__(
66
+ self,
67
+ vocab_file,
68
+ bos_token: Union[str, AddedToken] = "[BOS]",
69
+ eos_token: Union[str, AddedToken] = "[EOS]",
70
+ unk_token: Union[str, AddedToken, None] = None,
71
+ pad_token: Union[str, AddedToken, None] = None,
72
+ additional_special_tokens: List[str] = None,
73
+ added_tokens_decoder: Optional[dict] = None,
74
+ **kwargs,
75
+ ):
76
+ assert os.path.isfile(vocab_file), vocab_file
77
+
78
+ if additional_special_tokens is None:
79
+ additional_special_tokens = [
80
+ "<|im_end|>",
81
+ "<|im_user|>",
82
+ "<|im_assistant|>",
83
+ "<|start_header_id|>",
84
+ "<|end_header_id|>",
85
+ "[EOT]",
86
+ "<|im_system|>",
87
+ "<|im_middle|>",
88
+ ]
89
+
90
+ if added_tokens_decoder:
91
+ special_tokens_mapping = {
92
+ i: added_tokens_decoder[i].content
93
+ for i in added_tokens_decoder
94
+ }
95
+ else:
96
+ special_tokens_mapping = {}
97
+
98
+ self.vocab_file = vocab_file
99
+ mergeable_ranks = load_tiktoken_bpe(vocab_file)
100
+ num_base_tokens = len(mergeable_ranks)
101
+ self.special_tokens = {
102
+ special_tokens_mapping.get(i, f"<|reserved_token_{i}|>"): i
103
+ for i in range(num_base_tokens, num_base_tokens +
104
+ self.num_reserved_special_tokens)
105
+ }
106
+
107
+ self.model = tiktoken.Encoding(
108
+ name=Path(vocab_file).name,
109
+ pat_str=self.pat_str,
110
+ mergeable_ranks=mergeable_ranks,
111
+ special_tokens=self.special_tokens,
112
+ )
113
+ logger.info(f"Reloaded tiktoken model from {vocab_file}")
114
+
115
+ self.n_words: int = self.model.n_vocab
116
+ # BOS / EOS token IDs
117
+ self.bos_id: int = self.special_tokens[str(bos_token)]
118
+ self.eos_id: int = self.special_tokens[str(eos_token)]
119
+ logger.info(
120
+ f"#words: {self.n_words} - BOS ID: {self.bos_id} - EOS ID: {self.eos_id}"
121
+ )
122
+
123
+ self.pad_id: int = self.special_tokens[str(pad_token)]
124
+ self.unk_id: int = self.special_tokens[str(unk_token)]
125
+
126
+ self.byte_encoder = bytes_to_unicode()
127
+ self.byte_decoder = {v: k for k, v in self.byte_encoder.items()}
128
+
129
+ self.decoder = {}
130
+ for i in range(self.n_words):
131
+ # Taken from https://gist.github.com/xenova/a452a6474428de0182b17605a98631ee
132
+ decoding = ''.join([
133
+ self.byte_encoder[ord(char)] for char in
134
+ self.model.decode_single_token_bytes(i).decode('latin-1')
135
+ ])
136
+ self.decoder[i] = decoding
137
+
138
+ self.encoder = {}
139
+ for i in range(self.n_words):
140
+ if i in self.decoder:
141
+ self.encoder[self.decoder[i]] = i
142
+
143
+ super().__init__(
144
+ bos_token=bos_token,
145
+ eos_token=eos_token,
146
+ unk_token=unk_token,
147
+ pad_token=pad_token,
148
+ additional_special_tokens=additional_special_tokens,
149
+ added_tokens_decoder=added_tokens_decoder,
150
+ **kwargs,
151
+ )
152
+ self.all_special_ids_set = set(self.all_special_ids)
153
+
154
+ def _encode_text_piece(self, text: str,
155
+ allow_special_tokens: bool = True) -> List[int]:
156
+ # The tiktoken tokenizer can handle <=400k chars without
157
+ # pyo3_runtime.PanicException.
158
+ TIKTOKEN_MAX_ENCODE_CHARS = 400_000
159
+
160
+ # https://github.com/openai/tiktoken/issues/195
161
+ # Here we iterate over subsequences and split if we exceed the limit
162
+ # of max consecutive non-whitespace or whitespace characters.
163
+ MAX_NO_WHITESPACES_CHARS = 25_000
164
+
165
+ t: List[int] = []
166
+ for i in range(0, len(text), TIKTOKEN_MAX_ENCODE_CHARS):
167
+ for substr in self._split_whitespaces_or_nonwhitespaces(
168
+ text[i:i + TIKTOKEN_MAX_ENCODE_CHARS],
169
+ MAX_NO_WHITESPACES_CHARS,
170
+ ):
171
+ if allow_special_tokens:
172
+ t.extend(
173
+ # structural markers: encode <|...|> as their special token IDs
174
+ self.model.encode(
175
+ substr,
176
+ allowed_special="all",
177
+ ))
178
+ else:
179
+ t.extend(
180
+ # user/tool text: encode any <|...|> as ordinary BPE tokens (never as control tokens)
181
+ self.model.encode(
182
+ substr,
183
+ disallowed_special=(),
184
+ ))
185
+
186
+ return t
187
+
188
+ def encode(self,
189
+ text: str,
190
+ allow_special_tokens: bool = True,
191
+ **kwargs) -> List[int]:
192
+ """
193
+ Encodes a string into a list of token IDs.
194
+
195
+ Args:
196
+ text (str): The input string to be encoded.
197
+
198
+ Returns:
199
+ list[int]: A list of token IDs.
200
+ """
201
+ # If there are other args, we should call super().encode because there are a lot of code
202
+ # to handle those args. supper().encode finally will call _tokenize and _convert_token_to_id.
203
+ # NOTE: our encode method is not compatible with the super().encode method,
204
+ # e.g. split_special_tokens' default is True in our encode method.
205
+ if len(kwargs) > 0:
206
+ logger.warning(f"Calling super().encode with {kwargs}")
207
+ return super().encode(text, **kwargs)
208
+
209
+ assert type(text) is str
210
+ return self._encode_text_piece(text,
211
+ allow_special_tokens=allow_special_tokens)
212
+
213
+ def decode(self, token_ids: Union[int, List[int]], **kwargs) -> str:
214
+ """
215
+ Decodes a list of token IDs into a string.
216
+
217
+ Args:
218
+ token_ids (List[int]): The list of token IDs to be decoded.
219
+
220
+ Returns:
221
+ str: The decoded string.
222
+ """
223
+ # If there are other args, we should call super().decode because there are a lot of code
224
+ # to handle those args. supper().encode finally will call convert_tokens_to_string and _convert_id_to_token.
225
+ if len(kwargs) > 0:
226
+ return super().decode(token_ids, **kwargs)
227
+
228
+ if type(token_ids) is int:
229
+ token_ids = [token_ids]
230
+
231
+ return self.model.decode(cast(List[int], token_ids))
232
+
233
+ @staticmethod
234
+ def _split_whitespaces_or_nonwhitespaces(
235
+ s: str, max_consecutive_slice_len: int) -> Iterator[str]:
236
+ """
237
+ Splits the string `s` so that each substring contains no more than `max_consecutive_slice_len`
238
+ consecutive whitespaces or consecutive non-whitespaces.
239
+ """
240
+ current_slice_len = 0
241
+ current_slice_is_space = s[0].isspace() if len(s) > 0 else False
242
+ slice_start = 0
243
+
244
+ for i in range(len(s)):
245
+ is_now_space = s[i].isspace()
246
+
247
+ if current_slice_is_space ^ is_now_space:
248
+ current_slice_len = 1
249
+ current_slice_is_space = is_now_space
250
+ else:
251
+ current_slice_len += 1
252
+ if current_slice_len > max_consecutive_slice_len:
253
+ yield s[slice_start:i]
254
+ slice_start = i
255
+ current_slice_len = 1
256
+ yield s[slice_start:]
257
+
258
+ def _encode_chat_segments(self, segments) -> List[int]:
259
+ token_ids: List[int] = []
260
+ for segment in segments:
261
+ token_ids.extend(
262
+ self._encode_text_piece(
263
+ segment.text,
264
+ allow_special_tokens=segment.allow_special,
265
+ ))
266
+ return token_ids
267
+
268
+ @staticmethod
269
+ def _truncate(ids: List[int],
270
+ truncation: bool = False,
271
+ max_length: Optional[int] = None) -> List[int]:
272
+ if truncation and max_length is not None:
273
+ return ids[:max_length]
274
+ return ids
275
+
276
+ def _format_chat_token_output(self,
277
+ encoded_inputs: List[List[int]],
278
+ *,
279
+ is_batched: bool,
280
+ padding=False,
281
+ truncation: bool = False,
282
+ max_length: Optional[int] = None,
283
+ return_tensors=None,
284
+ return_dict: bool = False):
285
+ encoded_inputs = [
286
+ self._truncate(ids, truncation=truncation, max_length=max_length)
287
+ for ids in encoded_inputs
288
+ ]
289
+
290
+ needs_batch_encoding = (
291
+ is_batched or padding or return_tensors is not None or return_dict)
292
+ if not needs_batch_encoding:
293
+ return encoded_inputs[0]
294
+
295
+ features = [{
296
+ "input_ids": ids,
297
+ "attention_mask": [1] * len(ids)
298
+ } for ids in encoded_inputs]
299
+ batch = self.pad(features,
300
+ padding=padding,
301
+ max_length=max_length if padding else None,
302
+ return_attention_mask=True,
303
+ return_tensors=return_tensors)
304
+
305
+ if return_dict:
306
+ return batch
307
+ if is_batched:
308
+ return batch["input_ids"]
309
+ return batch["input_ids"][0] if return_tensors is None else batch[
310
+ "input_ids"]
311
+
312
+ """ ----- Below are the abstract methods required by PreTrainedTokenizer ----- """
313
+
314
+ @property
315
+ def vocab_size(self) -> int:
316
+ return self.n_words
317
+
318
+ def get_vocab(self) -> Dict[str, int]:
319
+ return self.encoder
320
+
321
+ def _tokenize(self, text: str, **kwargs) -> List[str]:
322
+ return [self.decoder[t] for t in self.encode(text)]
323
+
324
+ def _convert_token_to_id(self, token: str) -> int:
325
+ return self.encoder.get(token, self.unk_id)
326
+
327
+ def _convert_id_to_token(self, index: int) -> str:
328
+ return self.decoder.get(index)
329
+
330
+ @staticmethod
331
+ def clean_up_tokenization(out_string: str) -> str:
332
+ return out_string
333
+
334
+ def convert_tokens_to_string(self, tokens: List[str]) -> str:
335
+ text = ''.join(tokens)
336
+ text = bytearray([self.byte_decoder[c]
337
+ for c in text]).decode('utf-8', 'replace')
338
+ return text
339
+
340
+ def save_vocabulary(self,
341
+ save_directory: str,
342
+ filename_prefix: Optional[str] = None) -> Tuple[str]:
343
+ if not os.path.isdir(save_directory):
344
+ raise ValueError(
345
+ f"vocabulary path ({save_directory}) should be a directory")
346
+ out_vocab_file = os.path.join(
347
+ save_directory,
348
+ (filename_prefix + "-" if filename_prefix else "") +
349
+ VOCAB_FILES_NAMES["vocab_file"])
350
+
351
+ if os.path.abspath(self.vocab_file) != os.path.abspath(
352
+ out_vocab_file) and os.path.isfile(self.vocab_file):
353
+ copyfile(self.vocab_file, out_vocab_file)
354
+
355
+ return (out_vocab_file, )
356
+
357
+ def apply_chat_template(self,
358
+ conversation,
359
+ tools: Optional[list[dict]] = None,
360
+ tokenize: bool = False,
361
+ add_generation_prompt: bool = True,
362
+ thinking: bool = True,
363
+ padding=False,
364
+ truncation: bool = False,
365
+ max_length: Optional[int] = None,
366
+ return_tensors=None,
367
+ return_dict: bool = False,
368
+ **kwargs):
369
+ # Tokenizer-level rendering reorders tool result messages to match
370
+ # assistant tool_calls, normalizes per-call arguments and response
371
+ # schema, then encodes the resulting XTML structure segment-by-segment.
372
+ is_batched = is_batched_conversation(conversation)
373
+ conversations = conversation if is_batched else [conversation]
374
+ image_prompts = kwargs.pop("image_prompts", None)
375
+ if is_batched and image_prompts is not None:
376
+ raise ValueError("image_prompts is only supported for one chat.")
377
+
378
+ # by default set thinking effort to max
379
+ kwargs.setdefault("thinking_effort", "max")
380
+
381
+ segment_batches = [
382
+ build_chat_segments(
383
+ messages,
384
+ tools=tools,
385
+ add_generation_prompt=add_generation_prompt,
386
+ thinking=thinking,
387
+ image_prompts=image_prompts,
388
+ **kwargs,
389
+ ) for messages in conversations
390
+ ]
391
+
392
+ if not tokenize:
393
+ rendered = ["".join(segment.text for segment in segments)
394
+ for segments in segment_batches]
395
+ return rendered if is_batched else rendered[0]
396
+
397
+ encoded_inputs = [
398
+ self._encode_chat_segments(segments) for segments in segment_batches
399
+ ]
400
+ return self._format_chat_token_output(
401
+ encoded_inputs,
402
+ is_batched=is_batched,
403
+ padding=padding,
404
+ truncation=truncation,
405
+ max_length=max_length,
406
+ return_tensors=return_tensors,
407
+ return_dict=return_dict,
408
+ )
tokenizer_config.json ADDED
@@ -0,0 +1,157 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "added_tokens_decoder": {
3
+ "163584": {
4
+ "content": "[BOS]",
5
+ "lstrip": false,
6
+ "normalized": false,
7
+ "rstrip": false,
8
+ "single_word": false,
9
+ "special": true
10
+ },
11
+ "163585": {
12
+ "content": "[EOS]",
13
+ "lstrip": false,
14
+ "normalized": false,
15
+ "rstrip": false,
16
+ "single_word": false,
17
+ "special": true
18
+ },
19
+ "163586": {
20
+ "content": "<|end_of_msg|>",
21
+ "lstrip": false,
22
+ "normalized": false,
23
+ "rstrip": false,
24
+ "single_word": false,
25
+ "special": true
26
+ },
27
+ "163587": {
28
+ "content": "<|open|>",
29
+ "lstrip": false,
30
+ "normalized": false,
31
+ "rstrip": false,
32
+ "single_word": false,
33
+ "special": false
34
+ },
35
+ "163588": {
36
+ "content": "<|close|>",
37
+ "lstrip": false,
38
+ "normalized": false,
39
+ "rstrip": false,
40
+ "single_word": false,
41
+ "special": false
42
+ },
43
+ "163589": {
44
+ "content": "<|sep|>",
45
+ "lstrip": false,
46
+ "normalized": false,
47
+ "rstrip": false,
48
+ "single_word": false,
49
+ "special": false
50
+ },
51
+ "163590": {
52
+ "content": "[start_header_id]",
53
+ "lstrip": false,
54
+ "normalized": false,
55
+ "rstrip": false,
56
+ "single_word": false,
57
+ "special": true
58
+ },
59
+ "163591": {
60
+ "content": "[end_header_id]",
61
+ "lstrip": false,
62
+ "normalized": false,
63
+ "rstrip": false,
64
+ "single_word": false,
65
+ "special": true
66
+ },
67
+ "163593": {
68
+ "content": "[EOT]",
69
+ "lstrip": false,
70
+ "normalized": false,
71
+ "rstrip": false,
72
+ "single_word": false,
73
+ "special": true
74
+ },
75
+ "163602": {
76
+ "content": "<|media_begin|>",
77
+ "lstrip": false,
78
+ "normalized": false,
79
+ "rstrip": false,
80
+ "single_word": false,
81
+ "special": true
82
+ },
83
+ "163603": {
84
+ "content": "<|media_content|>",
85
+ "lstrip": false,
86
+ "normalized": false,
87
+ "rstrip": false,
88
+ "single_word": false,
89
+ "special": true
90
+ },
91
+ "163604": {
92
+ "content": "<|media_end|>",
93
+ "lstrip": false,
94
+ "normalized": false,
95
+ "rstrip": false,
96
+ "single_word": false,
97
+ "special": true
98
+ },
99
+ "163605": {
100
+ "content": "<|media_pad|>",
101
+ "lstrip": false,
102
+ "normalized": false,
103
+ "rstrip": false,
104
+ "single_word": false,
105
+ "special": true
106
+ },
107
+ "163649": {
108
+ "content": "<osagent_mode>",
109
+ "lstrip": false,
110
+ "normalized": false,
111
+ "rstrip": false,
112
+ "single_word": false,
113
+ "special": true
114
+ },
115
+ "163838": {
116
+ "content": "[UNK]",
117
+ "lstrip": false,
118
+ "normalized": false,
119
+ "rstrip": false,
120
+ "single_word": false,
121
+ "special": true
122
+ },
123
+ "163839": {
124
+ "content": "[PAD]",
125
+ "lstrip": false,
126
+ "normalized": false,
127
+ "rstrip": false,
128
+ "single_word": false,
129
+ "special": true
130
+ }
131
+ },
132
+ "additional_special_tokens": [
133
+ "<|end_of_msg|>",
134
+ "[start_header_id]",
135
+ "[end_header_id]",
136
+ "[EOT]",
137
+ "<|media_begin|>",
138
+ "<|media_content|>",
139
+ "<|media_end|>",
140
+ "<|media_pad|>",
141
+ "<osagent_mode>"
142
+ ],
143
+ "bos_token": "[BOS]",
144
+ "clean_up_tokenization_spaces": false,
145
+ "eos_token": "[EOS]",
146
+ "extra_special_tokens": {},
147
+ "model_max_length": 1000000000000000019884624838656,
148
+ "pad_token": "[PAD]",
149
+ "tokenizer_class": "TikTokenTokenizer",
150
+ "unk_token": "[UNK]",
151
+ "auto_map": {
152
+ "AutoTokenizer": [
153
+ "tokenization_kimi.TikTokenTokenizer",
154
+ null
155
+ ]
156
+ }
157
+ }