yujiepan commited on
Commit
86fb60d
·
verified ·
1 Parent(s): afc8974

Upload folder using huggingface_hub

Browse files
.meta/create_model.py ADDED
@@ -0,0 +1,341 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ def create_model(source_model_id: str, source_revision: str, save_folder: str) -> str:
2
+ import json
3
+ from pathlib import Path
4
+
5
+ import torch
6
+ from huggingface_hub import file_exists, hf_hub_download, list_repo_files
7
+ from safetensors.torch import load_file, save_file
8
+ from transformers import AutoConfig, AutoModel, GenerationConfig, set_seed
9
+
10
+ source_model_id, save_folder # noqa: B018
11
+
12
+ # Tiny attention cadence (physical 0-indexed layer ids): source dsa_layers are 1-indexed
13
+ # [4,8,...,92,93] -> physical [3,7,...,91,92] (24 layers). The 17-layer tiny keeps the
14
+ # same ~3:1 KDA:DSA cadence with five DSA layers at physical [3,7,11,15,16], i.e.
15
+ # 1-indexed [4,8,12,16,17]; the remaining layers are KDA (layer 0 is the dense-MLP prefix).
16
+ dsa_layers_1idx = [4, 8, 12, 16, 17]
17
+ dsa_physical_layers = [layer - 1 for layer in dsa_layers_1idx]
18
+ kda_layers_1idx = [i for i in range(1, 18) if i not in dsa_layers_1idx]
19
+ k3_modeling_repo = "moonshotai/Kimi-K3"
20
+ k3_modeling_revision = "9f62e4e9fffbd0a83ddd60e1c209d828994b3569"
21
+
22
+ folder = Path(save_folder)
23
+ folder.mkdir(parents=True, exist_ok=True)
24
+
25
+ # K3.1-Pro config/processor/tokenizer/config code (shipped by the source repo).
26
+ suffixes = (".json", ".py", ".model", ".jinja")
27
+ for filename in list_repo_files(source_model_id, repo_type="model", revision=source_revision):
28
+ if filename.endswith(suffixes) and not filename.endswith(".index.json"):
29
+ hf_hub_download(
30
+ source_model_id,
31
+ filename,
32
+ repo_type="model",
33
+ revision=source_revision,
34
+ local_dir=folder,
35
+ )
36
+ # The K3 reference modeling (not shipped by K3.1-Pro) implements the KDA + MLA backbone.
37
+ for filename in ("modeling_kimi_linear.py", "modeling_kimi_k3.py"):
38
+ source = hf_hub_download(
39
+ k3_modeling_repo,
40
+ filename,
41
+ repo_type="model",
42
+ revision=k3_modeling_revision,
43
+ )
44
+ (folder / filename).write_bytes(Path(source).read_bytes())
45
+
46
+ def replace_file(filepath, replacements):
47
+ with open(filepath, "r", encoding="utf-8") as handle:
48
+ code = handle.read()
49
+ for old, new in replacements:
50
+ if old not in code:
51
+ if new in code:
52
+ continue
53
+ raise ValueError(f"Expected code was not found in {filepath}: {old}")
54
+ code = code.replace(old, new)
55
+ with open(filepath, "w", encoding="utf-8") as handle:
56
+ handle.write(code)
57
+
58
+ force_flash_code = ''' if getattr(config, "_attn_implementation", None) is not None:
59
+ if config._attn_implementation != "flash_attention_2":
60
+ logger.warning_once(
61
+ f"Ignoring the provided attention implementation {config._attn_implementation}")
62
+ logger.warning_once("Using flash_attention_2 backend instead.")
63
+ config._attn_implementation = "flash_attention_2"
64
+ else:
65
+ config._attn_implementation = "flash_attention_2"'''
66
+ per_channel_gate_code = """ g = self.f_b_proj(self.f_a_proj(hidden_states))
67
+ g = rearrange(g, '... (h d) -> ... h d', d=self.head_dim)
68
+ beta = self.b_proj(hidden_states).float()"""
69
+ per_channel_gate_compat_code = """ g = self.f_b_proj(self.f_a_proj(hidden_states))
70
+ g = rearrange(g, '... (h d) -> ... h d', d=self.head_dim)
71
+ # The released K3 checkpoint stores per-channel decay shared by all heads.
72
+ g = self.gate_lower_bound * torch.sigmoid(
73
+ self.A_log.float().exp().view(1, 1, 1, self.head_dim)
74
+ * (g.float() + self.dt_bias.float().view(1, 1, self.num_heads, self.head_dim))
75
+ ).to(g.dtype)
76
+ beta = self.b_proj(hidden_states).float()"""
77
+ causal_mask_code = """ causal_mask = create_causal_mask(
78
+ config=self.config,
79
+ input_embeds=inputs_embeds,
80
+ attention_mask=attention_mask,
81
+ cache_position=cache_position,
82
+ past_key_values=past_key_values,
83
+ position_ids=position_ids,
84
+ )"""
85
+ causal_mask_compat_code = """ if version.parse(transformers.__version__) >= version.parse("5.0.0"):
86
+ causal_mask = create_causal_mask(
87
+ config=self.config,
88
+ inputs_embeds=inputs_embeds,
89
+ attention_mask=attention_mask,
90
+ past_key_values=past_key_values,
91
+ position_ids=position_ids,
92
+ )
93
+ else:
94
+ causal_mask = create_causal_mask(
95
+ config=self.config,
96
+ input_embeds=inputs_embeds,
97
+ attention_mask=attention_mask,
98
+ cache_position=cache_position,
99
+ past_key_values=past_key_values,
100
+ position_ids=position_ids,
101
+ )"""
102
+
103
+ replace_file(
104
+ f"{folder}/modeling_kimi_linear.py",
105
+ [
106
+ (
107
+ "from transformers.modeling_utils import ALL_ATTENTION_FUNCTIONS, PreTrainedModel",
108
+ "from transformers.modeling_utils import ALL_ATTENTION_FUNCTIONS, OutputRecorder, PreTrainedModel",
109
+ ),
110
+ (
111
+ "from transformers.utils.generic import OutputRecorder, check_model_inputs",
112
+ "from transformers.utils.generic import check_model_inputs",
113
+ ),
114
+ (
115
+ ' _tied_weights_keys = ["lm_head.weight"]',
116
+ """ _tied_weights_keys = (
117
+ {"lm_head.weight": "model.embed_tokens.weight"}
118
+ if version.parse(transformers.__version__) >= version.parse("5.0.0")
119
+ else ["lm_head.weight"]
120
+ )""",
121
+ ),
122
+ (causal_mask_code, causal_mask_compat_code),
123
+ (
124
+ force_flash_code,
125
+ ' config._attn_implementation = getattr(config, "_attn_implementation", "eager")',
126
+ ),
127
+ (
128
+ " self.num_heads, dtype=torch.float32).uniform_(1, 16)))",
129
+ " self.head_dim, dtype=torch.float32).uniform_(1, 16)))",
130
+ ),
131
+ (per_channel_gate_code, per_channel_gate_compat_code),
132
+ (
133
+ """ use_qk_l2norm_in_kernel=True,
134
+ use_gate_in_kernel=True,
135
+ use_beta_sigmoid_in_kernel=True,""",
136
+ """ use_qk_l2norm_in_kernel=True,
137
+ use_gate_in_kernel=False,
138
+ use_beta_sigmoid_in_kernel=True,""",
139
+ ),
140
+ (" safe_gate=self.gate_lower_bound is not None,", " safe_gate=False,"),
141
+ ],
142
+ )
143
+ replace_file(
144
+ f"{folder}/modeling_kimi_k3.py",
145
+ [
146
+ (" def tie_weights(self):", " def tie_weights(self, *args, **kwargs):"),
147
+ (" _supports_sdpa = True", " _supports_sdpa = False"),
148
+ (
149
+ " first_layer_past_key_value = past_key_values[0][0][:, :, :, 0]",
150
+ """ if hasattr(past_key_values, "key_cache"):
151
+ first_key_cache = next(
152
+ key_cache for key_cache in past_key_values.key_cache if key_cache is not None
153
+ )
154
+ first_layer_past_key_value = first_key_cache[:, :, :, 0]
155
+ else:
156
+ first_layer_past_key_value = past_key_values[0][0][:, :, :, 0]""",
157
+ ),
158
+ ],
159
+ )
160
+
161
+ with open(folder / "config.json", encoding="utf-8") as handle:
162
+ config_json = json.load(handle)
163
+
164
+ quantization_config = config_json["text_config"].pop("quantization_config")
165
+ # Upstream declares every Linear as a target; only routed expert w1/w2/w3 are MXFP4 in the shards.
166
+ quantization_config["config_groups"]["group_0"]["targets"] = [
167
+ "re:.*block_sparse_moe\\.experts\\.\\d+\\.(w1|w2|w3)$"
168
+ ]
169
+
170
+ text_config = config_json["text_config"]
171
+ # DSA layers are 1-indexed in the source dsa_config; the K3 reference modeling selects KDA vs MLA
172
+ # through linear_attn_config. Derive an equivalent schedule so the K3 backbone constructs exactly
173
+ # the K3.1 KDA/DSA layer mix (DSA layers are built as the same MLA attention core here).
174
+ text_config["linear_attn_config"] = {
175
+ "full_attn_layers": list(dsa_layers_1idx),
176
+ "kda_layers": list(kda_layers_1idx),
177
+ "num_heads": 8,
178
+ "head_dim": 128,
179
+ "short_conv_kernel_size": 4,
180
+ "gate_lower_bound": -5.0,
181
+ "use_full_rank_gate": True,
182
+ }
183
+ # Keep the source DSA indexer contract; only physical layer ids shrink with num_hidden_layers.
184
+ text_config["dsa_config"].update(
185
+ {
186
+ "dsa_layers": list(dsa_layers_1idx),
187
+ "index_topk": 2048,
188
+ "index_head_dim": 128,
189
+ "index_num_heads": 32,
190
+ "index_rope_dim": 0,
191
+ "index_k_norm_type": "layernorm",
192
+ "index_topk_freq": 1,
193
+ "index_skip_topk_offset": 1,
194
+ }
195
+ )
196
+ # Kernel-sensitive geometry mirrors tiny-random/kimi-k3: KDA head_dim and MLA ranks stay at
197
+ # source values, only head count and q LoRA / layer count / widths shrink.
198
+ text_config.update(
199
+ {
200
+ "attn_res_block_size": 8,
201
+ "first_k_dense_replace": 1,
202
+ "hidden_size": 8,
203
+ "intermediate_size": 32,
204
+ "kv_lora_rank": 512,
205
+ "moe_intermediate_size": 256,
206
+ "num_attention_heads": 8,
207
+ "num_experts": 64,
208
+ "num_hidden_layers": 17,
209
+ "num_key_value_heads": 8,
210
+ "q_lora_rank": 256,
211
+ "routed_expert_hidden_size": 256,
212
+ "_attn_implementation": "eager",
213
+ }
214
+ )
215
+ config_json["vision_config"].update(
216
+ {
217
+ "_attn_implementation": "eager",
218
+ "init_pos_emb_height": 8,
219
+ "init_pos_emb_width": 8,
220
+ "mm_hidden_size": 64,
221
+ "qkv_hidden_size": 64,
222
+ "text_hidden_size": 8,
223
+ "vt_hidden_size": 64,
224
+ "vt_intermediate_size": 128,
225
+ "vt_num_attention_heads": 2,
226
+ "vt_num_hidden_layers": 2,
227
+ }
228
+ )
229
+ # Map the K3.1 architecture to the reused K3 modeling implementation.
230
+ config_json["architectures"] = ["KimiK3ForConditionalGeneration"]
231
+ config_json["auto_map"] = {
232
+ "AutoConfig": "configuration_kimi_k3.KimiK3Config",
233
+ "AutoModel": "modeling_kimi_k3.KimiK3ForConditionalGeneration",
234
+ "AutoModelForCausalLM": "modeling_kimi_k3.KimiK3ForConditionalGeneration",
235
+ }
236
+ with open(folder / "config.json", "w", encoding="utf-8") as handle:
237
+ json.dump(config_json, handle, indent=2)
238
+
239
+ config = AutoConfig.from_pretrained(folder, trust_remote_code=True)
240
+ torch.set_default_dtype(torch.bfloat16)
241
+ model = AutoModel.from_config(config, trust_remote_code=True, attn_implementation="eager")
242
+ torch.set_default_dtype(torch.float32)
243
+ if file_exists(filename="generation_config.json", repo_id=source_model_id):
244
+ model.generation_config = GenerationConfig.from_pretrained(
245
+ source_model_id, trust_remote_code=True, revision=source_revision
246
+ )
247
+ set_seed(42)
248
+ model = model.cpu()
249
+ num_params = sum(p.numel() for p in model.parameters())
250
+ with torch.no_grad():
251
+ for name, parameter in sorted(model.named_parameters()):
252
+ torch.nn.init.normal_(parameter, 0, 0.1)
253
+ print(name, parameter.shape, parameter.dtype, f"{parameter.numel() / num_params:.2%}")
254
+ model.save_pretrained(folder)
255
+
256
+ # Repack routed experts to native MXFP4 (two nibbles per U8 byte, E8M0 group scale), then add
257
+ # the K3.1 gated-DSA indexer tensors that the K3 modeling does not construct.
258
+ model_path = folder / "model.safetensors"
259
+ state_dict = load_file(str(model_path))
260
+
261
+ # Add the LSA indexer on each physical DSA layer. Shapes follow the source schema:
262
+ # wq_b [index_num_heads*index_head_dim, q_lora_rank]
263
+ # wk [index_head_dim, hidden_size], weights_proj [index_num_heads, hidden_size]
264
+ # k_norm.weight/bias [index_head_dim] (LayerNorm, BF16)
265
+ index_heads = config_json["text_config"]["dsa_config"]["index_num_heads"]
266
+ index_head_dim = config_json["text_config"]["dsa_config"]["index_head_dim"]
267
+ hidden = config_json["text_config"]["hidden_size"]
268
+ q_lora = config_json["text_config"]["q_lora_rank"]
269
+ generator = torch.manual_seed(1234)
270
+ for physical_layer in dsa_physical_layers:
271
+ prefix = f"language_model.model.layers.{physical_layer}.self_attn.indexer"
272
+ state_dict[f"{prefix}.wq_b.weight"] = (
273
+ torch.randn(index_heads * index_head_dim, q_lora, generator=generator).to(torch.bfloat16) * 0.02
274
+ )
275
+ state_dict[f"{prefix}.wk.weight"] = (
276
+ torch.randn(index_head_dim, hidden, generator=generator).to(torch.bfloat16) * 0.02
277
+ )
278
+ state_dict[f"{prefix}.weights_proj.weight"] = (
279
+ torch.randn(index_heads, hidden, generator=generator).to(torch.bfloat16) * 0.02
280
+ )
281
+ state_dict[f"{prefix}.k_norm.weight"] = torch.ones(index_head_dim, dtype=torch.bfloat16)
282
+ state_dict[f"{prefix}.k_norm.bias"] = torch.zeros(index_head_dim, dtype=torch.bfloat16)
283
+
284
+ fp4_boundaries = torch.tensor([0.25, 0.75, 1.25, 1.75, 2.5, 3.5, 5.0])
285
+ for name in list(state_dict):
286
+ if ".block_sparse_moe.experts." in name and name.endswith(".weight"):
287
+ weight = state_dict.pop(name).float()
288
+ out_features, in_features = weight.shape
289
+ assert in_features % 32 == 0
290
+ blocks = weight.view(out_features, in_features // 32, 32)
291
+ scale_exponents = torch.round(
292
+ torch.log2(blocks.abs().amax(dim=-1).clamp_min(torch.finfo(torch.float32).tiny) / 6)
293
+ ).clamp(-126, 127)
294
+ scales = torch.pow(2.0, scale_exponents)
295
+ normalized = (blocks / scales.unsqueeze(-1)).clamp(-6, 6)
296
+ codes = torch.bucketize(normalized.abs(), fp4_boundaries)
297
+ codes += normalized.signbit() * 8
298
+ state_dict[name.removesuffix(".weight") + ".weight_packed"] = (codes[:, ::2] | (codes[:, 1::2] << 4)).to(
299
+ torch.uint8
300
+ )
301
+ state_dict[name.removesuffix(".weight") + ".weight_scale"] = (scale_exponents + 127).to(torch.uint8)
302
+ elif name.endswith(
303
+ (
304
+ ".block_sparse_moe.gate.e_score_correction_bias",
305
+ ".self_attn.A_log",
306
+ ".self_attn.dt_bias",
307
+ ".self_attn.k_conv1d.weight",
308
+ ".self_attn.o_norm.weight",
309
+ ".self_attn.q_conv1d.weight",
310
+ ".self_attn.v_conv1d.weight",
311
+ )
312
+ ):
313
+ state_dict[name] = state_dict[name].float()
314
+
315
+ # Schema assertion: indexer tensors on every physical DSA layer.
316
+ for physical_layer in dsa_physical_layers:
317
+ for tensor_name in (
318
+ "wq_b.weight",
319
+ "wk.weight",
320
+ "weights_proj.weight",
321
+ "k_norm.weight",
322
+ "k_norm.bias",
323
+ ):
324
+ assert f"language_model.model.layers.{physical_layer}.self_attn.indexer.{tensor_name}" in state_dict
325
+ save_file(state_dict, str(model_path), metadata={"format": "pt"})
326
+
327
+ saved_config = json.loads((folder / "config.json").read_text())
328
+ saved_config["quantization_config"] = quantization_config
329
+ saved_config["text_config"]["quantization_config"] = quantization_config
330
+ with open(folder / "config.json", "w", encoding="utf-8") as handle:
331
+ json.dump(saved_config, handle, indent=2)
332
+
333
+ representation = str(model)
334
+ return representation
335
+
336
+
337
+ create_model(
338
+ source_model_id="moonshotai/Kimi-K3.1-Pro",
339
+ source_revision="6d0a0202956796543364d666ab19c6ad1a494ee1",
340
+ save_folder="/tmp/kimi-k31",
341
+ )
.meta/env.json ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "source_model": {
3
+ "name": "moonshotai/Kimi-K3.1-Pro",
4
+ "commit_id": "6d0a0202956796543364d666ab19c6ad1a494ee1"
5
+ },
6
+ "environments": {
7
+ "creation": {
8
+ "python": "3.12.13",
9
+ "packages": {
10
+ "accelerate": "1.13.0",
11
+ "einops": "0.9.0.dev0",
12
+ "fla-core": "0.5.2",
13
+ "huggingface_hub": "2.3.0",
14
+ "numpy": "2.5.2",
15
+ "pillow": "12.3.0",
16
+ "safetensors": "0.8.0",
17
+ "tokenizers": "0.23.3",
18
+ "torch": "2.13.0+cu126",
19
+ "transformers": "5.22.0.dev0"
20
+ },
21
+ "cuda": "12.6"
22
+ }
23
+ }
24
+ }
.meta/model.log ADDED
@@ -0,0 +1,392 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ KimiK3ForConditionalGeneration(
2
+ (vision_tower): MoonViT3dPretrainedModel(
3
+ (patch_embed): MoonVision3dPatchEmbed(
4
+ (proj): Conv2d(3, 64, kernel_size=(14, 14), stride=(14, 14), bias=False)
5
+ (pos_emb): Learnable2DInterpPosEmbDivided_fixed()
6
+ )
7
+ (encoder): MoonViT3dEncoder(
8
+ (rope_2d): Rope2DPosEmbRepeated(dim=32, max_height=512, max_width=512, theta_base=10000)
9
+ (blocks): ModuleList(
10
+ (0-1): 2 x MoonViTEncoderLayer(
11
+ (norm0): RMSNorm((64,), eps=None, elementwise_affine=True)
12
+ (norm1): RMSNorm((64,), eps=None, elementwise_affine=True)
13
+ (mlp): MLP2(
14
+ (fc0): Linear(in_features=64, out_features=128, bias=False)
15
+ (fc1): Linear(in_features=128, out_features=64, bias=False)
16
+ (activation): GELUTanh()
17
+ )
18
+ (wqkv): Linear(in_features=64, out_features=192, bias=False)
19
+ (wo): Linear(in_features=64, out_features=64, bias=False)
20
+ )
21
+ )
22
+ (final_layernorm): RMSNorm((64,), eps=None, elementwise_affine=True)
23
+ )
24
+ )
25
+ (mm_projector): PatchMergerMLPV2(
26
+ (proj): Sequential(
27
+ (0): Linear(in_features=256, out_features=256, bias=False)
28
+ (1): GELU(approximate='none')
29
+ (2): Linear(in_features=256, out_features=8, bias=False)
30
+ )
31
+ (post_norm): RMSNorm((8,), eps=1e-05, elementwise_affine=True)
32
+ )
33
+ (language_model): KimiLinearForCausalLM(
34
+ (model): KimiLinearModel(
35
+ (embed_tokens): Embedding(163840, 8, padding_idx=163839)
36
+ (layers): ModuleList(
37
+ (0): KimiDecoderLayer(
38
+ (self_attn): KimiDeltaAttention(
39
+ (q_proj): Linear(in_features=8, out_features=1024, bias=False)
40
+ (k_proj): Linear(in_features=8, out_features=1024, bias=False)
41
+ (v_proj): Linear(in_features=8, out_features=1024, bias=False)
42
+ (q_conv1d): ShortConvolution(1024, 1024, kernel_size=(4,), stride=(1,), padding=(3,), groups=1024, bias=False, activation=silu, backend=triton)
43
+ (k_conv1d): ShortConvolution(1024, 1024, kernel_size=(4,), stride=(1,), padding=(3,), groups=1024, bias=False, activation=silu, backend=triton)
44
+ (v_conv1d): ShortConvolution(1024, 1024, kernel_size=(4,), stride=(1,), padding=(3,), groups=1024, bias=False, activation=silu, backend=triton)
45
+ (f_a_proj): Linear(in_features=8, out_features=128, bias=False)
46
+ (f_b_proj): Linear(in_features=128, out_features=1024, bias=False)
47
+ (b_proj): Linear(in_features=8, out_features=8, bias=False)
48
+ (g_proj): Linear(in_features=8, out_features=1024, bias=False)
49
+ (o_norm): FusedRMSNormGated(128, eps=1e-05, activation=sigmoid)
50
+ (o_proj): Linear(in_features=1024, out_features=8, bias=False)
51
+ )
52
+ (mlp): KimiMLP(
53
+ (gate_proj): Linear(in_features=8, out_features=32, bias=False)
54
+ (up_proj): Linear(in_features=8, out_features=32, bias=False)
55
+ (down_proj): Linear(in_features=32, out_features=8, bias=False)
56
+ (act_fn): SituAndMul()
57
+ )
58
+ (input_layernorm): KimiRMSNorm()
59
+ (post_attention_layernorm): KimiRMSNorm()
60
+ (self_attention_res_norm): KimiRMSNorm()
61
+ (mlp_res_norm): KimiRMSNorm()
62
+ (self_attention_res_proj): Linear(in_features=8, out_features=1, bias=False)
63
+ (mlp_res_proj): Linear(in_features=8, out_features=1, bias=False)
64
+ )
65
+ (1-2): 2 x KimiDecoderLayer(
66
+ (self_attn): KimiDeltaAttention(
67
+ (q_proj): Linear(in_features=8, out_features=1024, bias=False)
68
+ (k_proj): Linear(in_features=8, out_features=1024, bias=False)
69
+ (v_proj): Linear(in_features=8, out_features=1024, bias=False)
70
+ (q_conv1d): ShortConvolution(1024, 1024, kernel_size=(4,), stride=(1,), padding=(3,), groups=1024, bias=False, activation=silu, backend=triton)
71
+ (k_conv1d): ShortConvolution(1024, 1024, kernel_size=(4,), stride=(1,), padding=(3,), groups=1024, bias=False, activation=silu, backend=triton)
72
+ (v_conv1d): ShortConvolution(1024, 1024, kernel_size=(4,), stride=(1,), padding=(3,), groups=1024, bias=False, activation=silu, backend=triton)
73
+ (f_a_proj): Linear(in_features=8, out_features=128, bias=False)
74
+ (f_b_proj): Linear(in_features=128, out_features=1024, bias=False)
75
+ (b_proj): Linear(in_features=8, out_features=8, bias=False)
76
+ (g_proj): Linear(in_features=8, out_features=1024, bias=False)
77
+ (o_norm): FusedRMSNormGated(128, eps=1e-05, activation=sigmoid)
78
+ (o_proj): Linear(in_features=1024, out_features=8, bias=False)
79
+ )
80
+ (block_sparse_moe): KimiSparseMoeBlock(
81
+ (experts): ModuleList(
82
+ (0-63): 64 x KimiBlockSparseMLP(
83
+ (w1): Linear(in_features=256, out_features=256, bias=False)
84
+ (w2): Linear(in_features=256, out_features=256, bias=False)
85
+ (w3): Linear(in_features=256, out_features=256, bias=False)
86
+ (act_fn): SituAndMul()
87
+ )
88
+ )
89
+ (gate): KimiMoEGate()
90
+ (shared_experts): KimiMLP(
91
+ (gate_proj): Linear(in_features=8, out_features=512, bias=False)
92
+ (up_proj): Linear(in_features=8, out_features=512, bias=False)
93
+ (down_proj): Linear(in_features=512, out_features=8, bias=False)
94
+ (act_fn): SituAndMul()
95
+ )
96
+ (routed_expert_down_proj): Linear(in_features=8, out_features=256, bias=False)
97
+ (routed_expert_up_proj): Linear(in_features=256, out_features=8, bias=False)
98
+ (routed_expert_norm): KimiRMSNorm()
99
+ )
100
+ (input_layernorm): KimiRMSNorm()
101
+ (post_attention_layernorm): KimiRMSNorm()
102
+ (self_attention_res_norm): KimiRMSNorm()
103
+ (mlp_res_norm): KimiRMSNorm()
104
+ (self_attention_res_proj): Linear(in_features=8, out_features=1, bias=False)
105
+ (mlp_res_proj): Linear(in_features=8, out_features=1, bias=False)
106
+ )
107
+ (3): KimiDecoderLayer(
108
+ (self_attn): KimiMLAAttention(
109
+ (q_a_proj): Linear(in_features=8, out_features=256, bias=False)
110
+ (q_a_layernorm): KimiRMSNorm()
111
+ (q_b_proj): Linear(in_features=256, out_features=1536, bias=False)
112
+ (kv_a_proj_with_mqa): Linear(in_features=8, out_features=576, bias=False)
113
+ (kv_a_layernorm): KimiRMSNorm()
114
+ (kv_b_proj): Linear(in_features=512, out_features=2048, bias=False)
115
+ (o_proj): Linear(in_features=1024, out_features=8, bias=False)
116
+ (g_proj): Linear(in_features=8, out_features=1024, bias=False)
117
+ )
118
+ (block_sparse_moe): KimiSparseMoeBlock(
119
+ (experts): ModuleList(
120
+ (0-63): 64 x KimiBlockSparseMLP(
121
+ (w1): Linear(in_features=256, out_features=256, bias=False)
122
+ (w2): Linear(in_features=256, out_features=256, bias=False)
123
+ (w3): Linear(in_features=256, out_features=256, bias=False)
124
+ (act_fn): SituAndMul()
125
+ )
126
+ )
127
+ (gate): KimiMoEGate()
128
+ (shared_experts): KimiMLP(
129
+ (gate_proj): Linear(in_features=8, out_features=512, bias=False)
130
+ (up_proj): Linear(in_features=8, out_features=512, bias=False)
131
+ (down_proj): Linear(in_features=512, out_features=8, bias=False)
132
+ (act_fn): SituAndMul()
133
+ )
134
+ (routed_expert_down_proj): Linear(in_features=8, out_features=256, bias=False)
135
+ (routed_expert_up_proj): Linear(in_features=256, out_features=8, bias=False)
136
+ (routed_expert_norm): KimiRMSNorm()
137
+ )
138
+ (input_layernorm): KimiRMSNorm()
139
+ (post_attention_layernorm): KimiRMSNorm()
140
+ (self_attention_res_norm): KimiRMSNorm()
141
+ (mlp_res_norm): KimiRMSNorm()
142
+ (self_attention_res_proj): Linear(in_features=8, out_features=1, bias=False)
143
+ (mlp_res_proj): Linear(in_features=8, out_features=1, bias=False)
144
+ )
145
+ (4-6): 3 x KimiDecoderLayer(
146
+ (self_attn): KimiDeltaAttention(
147
+ (q_proj): Linear(in_features=8, out_features=1024, bias=False)
148
+ (k_proj): Linear(in_features=8, out_features=1024, bias=False)
149
+ (v_proj): Linear(in_features=8, out_features=1024, bias=False)
150
+ (q_conv1d): ShortConvolution(1024, 1024, kernel_size=(4,), stride=(1,), padding=(3,), groups=1024, bias=False, activation=silu, backend=triton)
151
+ (k_conv1d): ShortConvolution(1024, 1024, kernel_size=(4,), stride=(1,), padding=(3,), groups=1024, bias=False, activation=silu, backend=triton)
152
+ (v_conv1d): ShortConvolution(1024, 1024, kernel_size=(4,), stride=(1,), padding=(3,), groups=1024, bias=False, activation=silu, backend=triton)
153
+ (f_a_proj): Linear(in_features=8, out_features=128, bias=False)
154
+ (f_b_proj): Linear(in_features=128, out_features=1024, bias=False)
155
+ (b_proj): Linear(in_features=8, out_features=8, bias=False)
156
+ (g_proj): Linear(in_features=8, out_features=1024, bias=False)
157
+ (o_norm): FusedRMSNormGated(128, eps=1e-05, activation=sigmoid)
158
+ (o_proj): Linear(in_features=1024, out_features=8, bias=False)
159
+ )
160
+ (block_sparse_moe): KimiSparseMoeBlock(
161
+ (experts): ModuleList(
162
+ (0-63): 64 x KimiBlockSparseMLP(
163
+ (w1): Linear(in_features=256, out_features=256, bias=False)
164
+ (w2): Linear(in_features=256, out_features=256, bias=False)
165
+ (w3): Linear(in_features=256, out_features=256, bias=False)
166
+ (act_fn): SituAndMul()
167
+ )
168
+ )
169
+ (gate): KimiMoEGate()
170
+ (shared_experts): KimiMLP(
171
+ (gate_proj): Linear(in_features=8, out_features=512, bias=False)
172
+ (up_proj): Linear(in_features=8, out_features=512, bias=False)
173
+ (down_proj): Linear(in_features=512, out_features=8, bias=False)
174
+ (act_fn): SituAndMul()
175
+ )
176
+ (routed_expert_down_proj): Linear(in_features=8, out_features=256, bias=False)
177
+ (routed_expert_up_proj): Linear(in_features=256, out_features=8, bias=False)
178
+ (routed_expert_norm): KimiRMSNorm()
179
+ )
180
+ (input_layernorm): KimiRMSNorm()
181
+ (post_attention_layernorm): KimiRMSNorm()
182
+ (self_attention_res_norm): KimiRMSNorm()
183
+ (mlp_res_norm): KimiRMSNorm()
184
+ (self_attention_res_proj): Linear(in_features=8, out_features=1, bias=False)
185
+ (mlp_res_proj): Linear(in_features=8, out_features=1, bias=False)
186
+ )
187
+ (7): KimiDecoderLayer(
188
+ (self_attn): KimiMLAAttention(
189
+ (q_a_proj): Linear(in_features=8, out_features=256, bias=False)
190
+ (q_a_layernorm): KimiRMSNorm()
191
+ (q_b_proj): Linear(in_features=256, out_features=1536, bias=False)
192
+ (kv_a_proj_with_mqa): Linear(in_features=8, out_features=576, bias=False)
193
+ (kv_a_layernorm): KimiRMSNorm()
194
+ (kv_b_proj): Linear(in_features=512, out_features=2048, bias=False)
195
+ (o_proj): Linear(in_features=1024, out_features=8, bias=False)
196
+ (g_proj): Linear(in_features=8, out_features=1024, bias=False)
197
+ )
198
+ (block_sparse_moe): KimiSparseMoeBlock(
199
+ (experts): ModuleList(
200
+ (0-63): 64 x KimiBlockSparseMLP(
201
+ (w1): Linear(in_features=256, out_features=256, bias=False)
202
+ (w2): Linear(in_features=256, out_features=256, bias=False)
203
+ (w3): Linear(in_features=256, out_features=256, bias=False)
204
+ (act_fn): SituAndMul()
205
+ )
206
+ )
207
+ (gate): KimiMoEGate()
208
+ (shared_experts): KimiMLP(
209
+ (gate_proj): Linear(in_features=8, out_features=512, bias=False)
210
+ (up_proj): Linear(in_features=8, out_features=512, bias=False)
211
+ (down_proj): Linear(in_features=512, out_features=8, bias=False)
212
+ (act_fn): SituAndMul()
213
+ )
214
+ (routed_expert_down_proj): Linear(in_features=8, out_features=256, bias=False)
215
+ (routed_expert_up_proj): Linear(in_features=256, out_features=8, bias=False)
216
+ (routed_expert_norm): KimiRMSNorm()
217
+ )
218
+ (input_layernorm): KimiRMSNorm()
219
+ (post_attention_layernorm): KimiRMSNorm()
220
+ (self_attention_res_norm): KimiRMSNorm()
221
+ (mlp_res_norm): KimiRMSNorm()
222
+ (self_attention_res_proj): Linear(in_features=8, out_features=1, bias=False)
223
+ (mlp_res_proj): Linear(in_features=8, out_features=1, bias=False)
224
+ )
225
+ (8-10): 3 x KimiDecoderLayer(
226
+ (self_attn): KimiDeltaAttention(
227
+ (q_proj): Linear(in_features=8, out_features=1024, bias=False)
228
+ (k_proj): Linear(in_features=8, out_features=1024, bias=False)
229
+ (v_proj): Linear(in_features=8, out_features=1024, bias=False)
230
+ (q_conv1d): ShortConvolution(1024, 1024, kernel_size=(4,), stride=(1,), padding=(3,), groups=1024, bias=False, activation=silu, backend=triton)
231
+ (k_conv1d): ShortConvolution(1024, 1024, kernel_size=(4,), stride=(1,), padding=(3,), groups=1024, bias=False, activation=silu, backend=triton)
232
+ (v_conv1d): ShortConvolution(1024, 1024, kernel_size=(4,), stride=(1,), padding=(3,), groups=1024, bias=False, activation=silu, backend=triton)
233
+ (f_a_proj): Linear(in_features=8, out_features=128, bias=False)
234
+ (f_b_proj): Linear(in_features=128, out_features=1024, bias=False)
235
+ (b_proj): Linear(in_features=8, out_features=8, bias=False)
236
+ (g_proj): Linear(in_features=8, out_features=1024, bias=False)
237
+ (o_norm): FusedRMSNormGated(128, eps=1e-05, activation=sigmoid)
238
+ (o_proj): Linear(in_features=1024, out_features=8, bias=False)
239
+ )
240
+ (block_sparse_moe): KimiSparseMoeBlock(
241
+ (experts): ModuleList(
242
+ (0-63): 64 x KimiBlockSparseMLP(
243
+ (w1): Linear(in_features=256, out_features=256, bias=False)
244
+ (w2): Linear(in_features=256, out_features=256, bias=False)
245
+ (w3): Linear(in_features=256, out_features=256, bias=False)
246
+ (act_fn): SituAndMul()
247
+ )
248
+ )
249
+ (gate): KimiMoEGate()
250
+ (shared_experts): KimiMLP(
251
+ (gate_proj): Linear(in_features=8, out_features=512, bias=False)
252
+ (up_proj): Linear(in_features=8, out_features=512, bias=False)
253
+ (down_proj): Linear(in_features=512, out_features=8, bias=False)
254
+ (act_fn): SituAndMul()
255
+ )
256
+ (routed_expert_down_proj): Linear(in_features=8, out_features=256, bias=False)
257
+ (routed_expert_up_proj): Linear(in_features=256, out_features=8, bias=False)
258
+ (routed_expert_norm): KimiRMSNorm()
259
+ )
260
+ (input_layernorm): KimiRMSNorm()
261
+ (post_attention_layernorm): KimiRMSNorm()
262
+ (self_attention_res_norm): KimiRMSNorm()
263
+ (mlp_res_norm): KimiRMSNorm()
264
+ (self_attention_res_proj): Linear(in_features=8, out_features=1, bias=False)
265
+ (mlp_res_proj): Linear(in_features=8, out_features=1, bias=False)
266
+ )
267
+ (11): KimiDecoderLayer(
268
+ (self_attn): KimiMLAAttention(
269
+ (q_a_proj): Linear(in_features=8, out_features=256, bias=False)
270
+ (q_a_layernorm): KimiRMSNorm()
271
+ (q_b_proj): Linear(in_features=256, out_features=1536, bias=False)
272
+ (kv_a_proj_with_mqa): Linear(in_features=8, out_features=576, bias=False)
273
+ (kv_a_layernorm): KimiRMSNorm()
274
+ (kv_b_proj): Linear(in_features=512, out_features=2048, bias=False)
275
+ (o_proj): Linear(in_features=1024, out_features=8, bias=False)
276
+ (g_proj): Linear(in_features=8, out_features=1024, bias=False)
277
+ )
278
+ (block_sparse_moe): KimiSparseMoeBlock(
279
+ (experts): ModuleList(
280
+ (0-63): 64 x KimiBlockSparseMLP(
281
+ (w1): Linear(in_features=256, out_features=256, bias=False)
282
+ (w2): Linear(in_features=256, out_features=256, bias=False)
283
+ (w3): Linear(in_features=256, out_features=256, bias=False)
284
+ (act_fn): SituAndMul()
285
+ )
286
+ )
287
+ (gate): KimiMoEGate()
288
+ (shared_experts): KimiMLP(
289
+ (gate_proj): Linear(in_features=8, out_features=512, bias=False)
290
+ (up_proj): Linear(in_features=8, out_features=512, bias=False)
291
+ (down_proj): Linear(in_features=512, out_features=8, bias=False)
292
+ (act_fn): SituAndMul()
293
+ )
294
+ (routed_expert_down_proj): Linear(in_features=8, out_features=256, bias=False)
295
+ (routed_expert_up_proj): Linear(in_features=256, out_features=8, bias=False)
296
+ (routed_expert_norm): KimiRMSNorm()
297
+ )
298
+ (input_layernorm): KimiRMSNorm()
299
+ (post_attention_layernorm): KimiRMSNorm()
300
+ (self_attention_res_norm): KimiRMSNorm()
301
+ (mlp_res_norm): KimiRMSNorm()
302
+ (self_attention_res_proj): Linear(in_features=8, out_features=1, bias=False)
303
+ (mlp_res_proj): Linear(in_features=8, out_features=1, bias=False)
304
+ )
305
+ (12-14): 3 x KimiDecoderLayer(
306
+ (self_attn): KimiDeltaAttention(
307
+ (q_proj): Linear(in_features=8, out_features=1024, bias=False)
308
+ (k_proj): Linear(in_features=8, out_features=1024, bias=False)
309
+ (v_proj): Linear(in_features=8, out_features=1024, bias=False)
310
+ (q_conv1d): ShortConvolution(1024, 1024, kernel_size=(4,), stride=(1,), padding=(3,), groups=1024, bias=False, activation=silu, backend=triton)
311
+ (k_conv1d): ShortConvolution(1024, 1024, kernel_size=(4,), stride=(1,), padding=(3,), groups=1024, bias=False, activation=silu, backend=triton)
312
+ (v_conv1d): ShortConvolution(1024, 1024, kernel_size=(4,), stride=(1,), padding=(3,), groups=1024, bias=False, activation=silu, backend=triton)
313
+ (f_a_proj): Linear(in_features=8, out_features=128, bias=False)
314
+ (f_b_proj): Linear(in_features=128, out_features=1024, bias=False)
315
+ (b_proj): Linear(in_features=8, out_features=8, bias=False)
316
+ (g_proj): Linear(in_features=8, out_features=1024, bias=False)
317
+ (o_norm): FusedRMSNormGated(128, eps=1e-05, activation=sigmoid)
318
+ (o_proj): Linear(in_features=1024, out_features=8, bias=False)
319
+ )
320
+ (block_sparse_moe): KimiSparseMoeBlock(
321
+ (experts): ModuleList(
322
+ (0-63): 64 x KimiBlockSparseMLP(
323
+ (w1): Linear(in_features=256, out_features=256, bias=False)
324
+ (w2): Linear(in_features=256, out_features=256, bias=False)
325
+ (w3): Linear(in_features=256, out_features=256, bias=False)
326
+ (act_fn): SituAndMul()
327
+ )
328
+ )
329
+ (gate): KimiMoEGate()
330
+ (shared_experts): KimiMLP(
331
+ (gate_proj): Linear(in_features=8, out_features=512, bias=False)
332
+ (up_proj): Linear(in_features=8, out_features=512, bias=False)
333
+ (down_proj): Linear(in_features=512, out_features=8, bias=False)
334
+ (act_fn): SituAndMul()
335
+ )
336
+ (routed_expert_down_proj): Linear(in_features=8, out_features=256, bias=False)
337
+ (routed_expert_up_proj): Linear(in_features=256, out_features=8, bias=False)
338
+ (routed_expert_norm): KimiRMSNorm()
339
+ )
340
+ (input_layernorm): KimiRMSNorm()
341
+ (post_attention_layernorm): KimiRMSNorm()
342
+ (self_attention_res_norm): KimiRMSNorm()
343
+ (mlp_res_norm): KimiRMSNorm()
344
+ (self_attention_res_proj): Linear(in_features=8, out_features=1, bias=False)
345
+ (mlp_res_proj): Linear(in_features=8, out_features=1, bias=False)
346
+ )
347
+ (15-16): 2 x KimiDecoderLayer(
348
+ (self_attn): KimiMLAAttention(
349
+ (q_a_proj): Linear(in_features=8, out_features=256, bias=False)
350
+ (q_a_layernorm): KimiRMSNorm()
351
+ (q_b_proj): Linear(in_features=256, out_features=1536, bias=False)
352
+ (kv_a_proj_with_mqa): Linear(in_features=8, out_features=576, bias=False)
353
+ (kv_a_layernorm): KimiRMSNorm()
354
+ (kv_b_proj): Linear(in_features=512, out_features=2048, bias=False)
355
+ (o_proj): Linear(in_features=1024, out_features=8, bias=False)
356
+ (g_proj): Linear(in_features=8, out_features=1024, bias=False)
357
+ )
358
+ (block_sparse_moe): KimiSparseMoeBlock(
359
+ (experts): ModuleList(
360
+ (0-63): 64 x KimiBlockSparseMLP(
361
+ (w1): Linear(in_features=256, out_features=256, bias=False)
362
+ (w2): Linear(in_features=256, out_features=256, bias=False)
363
+ (w3): Linear(in_features=256, out_features=256, bias=False)
364
+ (act_fn): SituAndMul()
365
+ )
366
+ )
367
+ (gate): KimiMoEGate()
368
+ (shared_experts): KimiMLP(
369
+ (gate_proj): Linear(in_features=8, out_features=512, bias=False)
370
+ (up_proj): Linear(in_features=8, out_features=512, bias=False)
371
+ (down_proj): Linear(in_features=512, out_features=8, bias=False)
372
+ (act_fn): SituAndMul()
373
+ )
374
+ (routed_expert_down_proj): Linear(in_features=8, out_features=256, bias=False)
375
+ (routed_expert_up_proj): Linear(in_features=256, out_features=8, bias=False)
376
+ (routed_expert_norm): KimiRMSNorm()
377
+ )
378
+ (input_layernorm): KimiRMSNorm()
379
+ (post_attention_layernorm): KimiRMSNorm()
380
+ (self_attention_res_norm): KimiRMSNorm()
381
+ (mlp_res_norm): KimiRMSNorm()
382
+ (self_attention_res_proj): Linear(in_features=8, out_features=1, bias=False)
383
+ (mlp_res_proj): Linear(in_features=8, out_features=1, bias=False)
384
+ )
385
+ )
386
+ (norm): KimiRMSNorm()
387
+ (output_attn_res_norm): KimiRMSNorm()
388
+ (output_attn_res_proj): Linear(in_features=8, out_features=1, bias=False)
389
+ )
390
+ (lm_head): Linear(in_features=8, out_features=163840, bias=False)
391
+ )
392
+ )
README.md ADDED
@@ -0,0 +1,115 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ library_name: transformers
3
+ pipeline_tag: image-text-to-text
4
+ base_model:
5
+ - moonshotai/Kimi-K3.1-Pro
6
+ ---
7
+
8
+ This is a randomly initialized tiny model based on [moonshotai/Kimi-K3.1-Pro](https://huggingface.co/moonshotai/Kimi-K3.1-Pro) for testing and debugging.
9
+
10
+ | Feature | Source | This repo |
11
+ |---|---|---|
12
+ | Checkpoint tensor elements | 57.36B BF16, 11.12M F32, 1.45T U8 | 17.73M BF16, 163.84K F32, 106.95M U8 |
13
+ | Safetensors weight files | 1.56 TB (96 files) | 144.02 MB (model.safetensors) |
14
+ | Total / DSA-gated layers | 93 / 24 (plus 69 KDA, 1 dense) | 17 / 5 (plus 11 KDA, 1 dense) |
15
+ | Attention composition | 69 KDA + 24 gated DSA | 11 KDA + 5 DSA cadence |
16
+ | Hidden width / MLP width | 7,168 / 33,792 | 8 / 32 |
17
+ | Full-attn Q heads / head dim | 96 / 128 (nope 128, rope 64, v 128) | 8 / 128 (same per-head dims) |
18
+ | MLA q / kv LoRA rank | 1,536 / 512 | 256 / 512 |
19
+ | Routed / shared experts, top-k | 896 / 2, 16 | 64 / 2, 16 |
20
+ | LSA index heads / head dim / topk | 32 / 128 / 2,048 | 32 / 128 / 2,048 (unchanged) |
21
+ | Physical DSA indexer layers | 24 (dsa_layers - 1) | [3, 7, 11, 15, 16] |
22
+ | Routed expert precision | MXFP4 packed (group 32, E8M0 scales) | Same |
23
+ | Vision depth / hidden / heads | 27 / 1,024 / 12 | 2 / 64 / 2 |
24
+ | Patch size | 14 | Same |
25
+ | Vocabulary rows / tying | 163,840 / untied | Same |
26
+ | Maximum context length | 1,048,576 | Same |
27
+
28
+ Kimi K3.1 Pro ships configuration and processor code but no modeling implementation; the released K3 remote modeling (`modeling_kimi_k3.py` / `modeling_kimi_linear.py`) provides the matching KDA + MLA + latent-MoE backbone and is included with the same eager-attention / causal-mask / per-channel-gate compatibility patches used by [tiny-random/kimi-k3](https://huggingface.co/tiny-random/kimi-k3). The 24 source gated-DSA layers carry an LSA `indexer` (q/k/head-gate projections and a LayerNorm); the tiny model keeps this exact indexer contract on five physical layers and ships the tensors forward-compatibly. Released Transformers has no gated-DSA operator yet, so those layers run their MLA attention core under Transformers, and serving examples for vLLM/SGLang require a runtime build with KimiK31 DSA support. Routed experts are stored as native packed MXFP4 (two 4-bit codes per byte, one E8M0 scale per group of 32), matching the source checkpoint.
29
+
30
+ | File path | Size |
31
+ |------|------|
32
+ | model.safetensors | 144.0MB |
33
+
34
+ ### Example usage
35
+
36
+ #### Transformers (unverified)
37
+
38
+ Loads with the pinned K3 reference modeling on GPU (KDA delta attention uses Triton via fla-core). The K3.1 gated-DSA layers run their MLA attention core; the LSA indexer tensors are present but not consumed because released Transformers lacks the gated-DSA operator.
39
+
40
+ ```python
41
+ def transformers_example(model_id: str):
42
+ import numpy as np
43
+ import torch
44
+ from PIL import Image
45
+ from transformers import AutoModel, AutoProcessor
46
+
47
+ model_id = str(model_id)
48
+ processor = AutoProcessor.from_pretrained(model_id, trust_remote_code=True)
49
+ model = AutoModel.from_pretrained(
50
+ model_id,
51
+ dtype=torch.bfloat16,
52
+ device_map="cuda",
53
+ trust_remote_code=True,
54
+ attn_implementation="eager",
55
+ ).eval()
56
+ image = Image.fromarray(np.random.default_rng(42).integers(0, 255, (56, 56, 3), dtype=np.uint8))
57
+ messages = [
58
+ {
59
+ "role": "user",
60
+ "content": [
61
+ {"type": "image", "image": image},
62
+ {"type": "text", "text": "Hello, world!"},
63
+ ],
64
+ }
65
+ ]
66
+ inputs = processor(messages=messages, return_tensors="pt").to(model.device)
67
+ with torch.no_grad():
68
+ outputs = model.generate(**inputs, max_new_tokens=16)
69
+ generated_ids = outputs.sequences if hasattr(outputs, "sequences") else outputs
70
+ print(processor.decode(generated_ids[0].detach().cpu().tolist()))
71
+
72
+
73
+ if __name__ == "__main__":
74
+ transformers_example(model_id="scratch/kimi-k31")
75
+ ```
76
+
77
+ #### vLLM serving (unverified)
78
+
79
+ # K3.1 gated-DSA serving requires a vLLM build with KimiK31 DSA support (newer than the
80
+ # released wheel at tiny-model creation time). See https://recipes.vllm.ai/moonshotai/Kimi-K3.1-Pro
81
+ vllm serve scratch/kimi-k31 \
82
+ --trust-remote-code \
83
+ --tensor-parallel-size 8 \
84
+ --load-format safetensors \
85
+ --max-model-len 131072 \
86
+ --gpu-memory-utilization 0.90
87
+
88
+
89
+ #### SGLang serving (unverified)
90
+
91
+ # K3.1 gated-DSA serving requires an SGLang build newer than 0.5.23 (the KimiK31 DSA indexer
92
+ # kernels had not landed in the released wheel at tiny-model creation time).
93
+ python3 -m sglang.launch_server \
94
+ --model-path scratch/kimi-k31 \
95
+ --trust-remote-code \
96
+ --tp-size 8 \
97
+ --chunked-prefill-size 16384 \
98
+ --cuda-graph-backend-prefill breakable \
99
+ --dtype bfloat16 \
100
+ --kv-cache-dtype fp8_e4m3 \
101
+ --reasoning-parser kimi_k3 \
102
+ --tool-call-parser kimi_k3 \
103
+ --mem-fraction-static 0.85
104
+
105
+ ### Creating this repo
106
+
107
+ - Code: [.meta/create_model.py](.meta/create_model.py)
108
+ - Model structure: [.meta/model.log](.meta/model.log)
109
+ - Environment: [.meta/env.json](.meta/env.json)
110
+
111
+ ### Change log
112
+
113
+ - 2026-11-05: Initial version. Kimi K3.1 Pro is K3 (69 KDA + 24 attention layers) with the 24 full-attention layers replaced by gated DSA sparse attention carrying an LSA indexer. The tiny model follows the proven tiny-random/kimi-k3 geometry (residual 8, 8 attention heads, q LoRA 256, kv LoRA 512, 17 layers in the 12 KDA + 5 DSA cadence) and keeps the source DSA indexer contract (32 index heads, head dim 128, topk 2048) on five physical layers. The MXFP4 routed-expert packing and K3 KDA/MLA compatibility patches are preserved; the released K3 remote modeling constructs the DSA layers as dense MLA because no released Transformers/SGLang runtime yet ships the K3.1 gated-DSA operator (the indexer tensors are included forward-compatibly).
114
+
115
+ *This repo is generated by GPT-6.1-Sol.*
config.json ADDED
@@ -0,0 +1,276 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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_k31",
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
+ "bos_token_id": 163584,
28
+ "chunk_size_feed_forward": 0,
29
+ "cross_attention_hidden_size": null,
30
+ "decoder_start_token_id": null,
31
+ "dsa_config": {
32
+ "dsa_layers": [
33
+ 4,
34
+ 8,
35
+ 12,
36
+ 16,
37
+ 17
38
+ ],
39
+ "index_head_dim": 128,
40
+ "index_k_norm_type": "layernorm",
41
+ "index_num_heads": 32,
42
+ "index_rope_dim": 0,
43
+ "index_skip_topk_offset": 1,
44
+ "index_topk": 2048,
45
+ "index_topk_freq": 1
46
+ },
47
+ "dtype": "bfloat16",
48
+ "eos_token_id": 163586,
49
+ "finetuning_task": null,
50
+ "first_k_dense_replace": 1,
51
+ "head_dim": 1,
52
+ "hidden_act": "situ",
53
+ "hidden_size": 8,
54
+ "id2label": {
55
+ "0": "LABEL_0",
56
+ "1": "LABEL_1"
57
+ },
58
+ "initializer_range": 0.02,
59
+ "intermediate_size": 32,
60
+ "is_decoder": false,
61
+ "is_encoder_decoder": false,
62
+ "kv_lora_rank": 512,
63
+ "label2id": {
64
+ "LABEL_0": 0,
65
+ "LABEL_1": 1
66
+ },
67
+ "latent_moe_use_norm": true,
68
+ "linear_attn_config": {
69
+ "full_attn_layers": [
70
+ 4,
71
+ 8,
72
+ 12,
73
+ 16,
74
+ 17
75
+ ],
76
+ "gate_lower_bound": -5.0,
77
+ "head_dim": 128,
78
+ "kda_layers": [
79
+ 1,
80
+ 2,
81
+ 3,
82
+ 5,
83
+ 6,
84
+ 7,
85
+ 9,
86
+ 10,
87
+ 11,
88
+ 13,
89
+ 14,
90
+ 15
91
+ ],
92
+ "num_heads": 8,
93
+ "short_conv_kernel_size": 4,
94
+ "use_full_rank_gate": true
95
+ },
96
+ "max_position_embeddings": 1048576,
97
+ "mla_use_nope": true,
98
+ "mla_use_output_gate": true,
99
+ "model_type": "kimi_linear",
100
+ "moe_intermediate_size": 256,
101
+ "moe_layer_freq": 1,
102
+ "moe_renormalize": true,
103
+ "moe_router_activation_func": "sigmoid",
104
+ "num_attention_heads": 8,
105
+ "num_expert_group": 1,
106
+ "num_experts": 64,
107
+ "num_experts_per_token": 16,
108
+ "num_hidden_layers": 17,
109
+ "num_key_value_heads": 8,
110
+ "num_nextn_predict_layers": 0,
111
+ "num_shared_experts": 2,
112
+ "output_attentions": false,
113
+ "output_hidden_states": false,
114
+ "pad_token_id": 163839,
115
+ "prefix": null,
116
+ "problem_type": null,
117
+ "pruned_heads": {},
118
+ "q_lora_rank": 256,
119
+ "qk_nope_head_dim": 128,
120
+ "qk_rope_head_dim": 64,
121
+ "return_dict": true,
122
+ "rms_norm_eps": 1e-05,
123
+ "rope_parameters": {
124
+ "rope_theta": 10000.0,
125
+ "rope_type": "default"
126
+ },
127
+ "rope_theta": 10000.0,
128
+ "routed_expert_hidden_size": 256,
129
+ "routed_scaling_factor": 1.0,
130
+ "sep_token_id": null,
131
+ "task_specific_params": null,
132
+ "tf_legacy_loss": false,
133
+ "tie_encoder_decoder": false,
134
+ "tie_word_embeddings": false,
135
+ "tokenizer_class": null,
136
+ "topk_group": 1,
137
+ "topk_method": "noaux_tc",
138
+ "torchscript": false,
139
+ "use_bfloat16": false,
140
+ "use_cache": true,
141
+ "use_grouped_topk": true,
142
+ "v_head_dim": 128,
143
+ "vocab_size": 163840,
144
+ "quantization_config": {
145
+ "config_groups": {
146
+ "group_0": {
147
+ "format": "mxfp4-pack-quantized",
148
+ "input_activations": null,
149
+ "output_activations": null,
150
+ "targets": [
151
+ "re:.*block_sparse_moe\\.experts\\.\\d+\\.(w1|w2|w3)$"
152
+ ],
153
+ "weights": {
154
+ "actorder": null,
155
+ "block_structure": null,
156
+ "dynamic": false,
157
+ "group_size": 32,
158
+ "num_bits": 4,
159
+ "observer": "minmax",
160
+ "observer_kwargs": {},
161
+ "scale_dtype": "torch.uint8",
162
+ "strategy": "group",
163
+ "symmetric": true,
164
+ "type": "float",
165
+ "zp_dtype": null
166
+ }
167
+ }
168
+ },
169
+ "format": "mxfp4-pack-quantized",
170
+ "global_compression_ratio": null,
171
+ "ignore": [
172
+ "re:.*self_attn.*",
173
+ "re:.*shared_experts.*",
174
+ "re:.*mlp\\.(gate|up|gate_up|down)_proj.*",
175
+ "re:.*block_sparse_moe\\.gate$",
176
+ "re:.*block_sparse_moe\\.routed_expert_(up|down)_proj.*",
177
+ "re:.*lm_head.*",
178
+ "re:.*vision_tower.*",
179
+ "re:.*mm_projector.*"
180
+ ],
181
+ "kv_cache_scheme": null,
182
+ "quant_method": "compressed-tensors",
183
+ "quantization_status": "compressed"
184
+ }
185
+ },
186
+ "tie_word_embeddings": false,
187
+ "transformers_version": "5.22.0.dev0",
188
+ "vision_config": {
189
+ "_name_or_path": "",
190
+ "activation_func": "gelu_pytorch_tanh",
191
+ "architectures": null,
192
+ "attn_bias": false,
193
+ "chunk_size_feed_forward": 0,
194
+ "dtype": null,
195
+ "id2label": {
196
+ "0": "LABEL_0",
197
+ "1": "LABEL_1"
198
+ },
199
+ "init_pos_emb_height": 8,
200
+ "init_pos_emb_time": 4,
201
+ "init_pos_emb_width": 8,
202
+ "is_encoder_decoder": false,
203
+ "label2id": {
204
+ "LABEL_0": 0,
205
+ "LABEL_1": 1
206
+ },
207
+ "linear_bias": false,
208
+ "merge_kernel_size": [
209
+ 2,
210
+ 2
211
+ ],
212
+ "merge_type": "sd2_tpool",
213
+ "mlp_type": "mlp2",
214
+ "mm_hidden_size": 64,
215
+ "mm_projector_type": "patchmergerv2",
216
+ "model_type": "",
217
+ "norm_type": "rmsnorm",
218
+ "output_attentions": false,
219
+ "output_hidden_states": false,
220
+ "patch_embed_proj_bias": false,
221
+ "patch_size": 14,
222
+ "pos_emb_interpolation_mode": "bilinear",
223
+ "pos_emb_type": "divided_fixed",
224
+ "problem_type": null,
225
+ "projector_hidden_act": "gelu",
226
+ "projector_ln_eps": 1e-05,
227
+ "qkv_hidden_size": 64,
228
+ "return_dict": true,
229
+ "text_hidden_size": 8,
230
+ "vt_hidden_size": 64,
231
+ "vt_intermediate_size": 128,
232
+ "vt_num_attention_heads": 2,
233
+ "vt_num_hidden_layers": 2
234
+ },
235
+ "quantization_config": {
236
+ "config_groups": {
237
+ "group_0": {
238
+ "format": "mxfp4-pack-quantized",
239
+ "input_activations": null,
240
+ "output_activations": null,
241
+ "targets": [
242
+ "re:.*block_sparse_moe\\.experts\\.\\d+\\.(w1|w2|w3)$"
243
+ ],
244
+ "weights": {
245
+ "actorder": null,
246
+ "block_structure": null,
247
+ "dynamic": false,
248
+ "group_size": 32,
249
+ "num_bits": 4,
250
+ "observer": "minmax",
251
+ "observer_kwargs": {},
252
+ "scale_dtype": "torch.uint8",
253
+ "strategy": "group",
254
+ "symmetric": true,
255
+ "type": "float",
256
+ "zp_dtype": null
257
+ }
258
+ }
259
+ },
260
+ "format": "mxfp4-pack-quantized",
261
+ "global_compression_ratio": null,
262
+ "ignore": [
263
+ "re:.*self_attn.*",
264
+ "re:.*shared_experts.*",
265
+ "re:.*mlp\\.(gate|up|gate_up|down)_proj.*",
266
+ "re:.*block_sparse_moe\\.gate$",
267
+ "re:.*block_sparse_moe\\.routed_expert_(up|down)_proj.*",
268
+ "re:.*lm_head.*",
269
+ "re:.*vision_tower.*",
270
+ "re:.*mm_projector.*"
271
+ ],
272
+ "kv_cache_scheme": null,
273
+ "quant_method": "compressed-tensors",
274
+ "quantization_status": "compressed"
275
+ }
276
+ }
configuration_kimi_k3.py ADDED
@@ -0,0 +1,277 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Optional
2
+
3
+ from transformers.configuration_utils import PretrainedConfig
4
+
5
+
6
+ class KimiLinearConfig(PretrainedConfig):
7
+ model_type = "kimi_linear"
8
+ keys_to_ignore_at_inference = ["past_key_values"]
9
+
10
+ def __init__(
11
+ self,
12
+ model_type="kimi_linear",
13
+ vocab_size=163840,
14
+ hidden_size=4096,
15
+ head_dim=None,
16
+ intermediate_size=11008,
17
+ num_hidden_layers=32,
18
+ num_attention_heads=32,
19
+ num_key_value_heads=None,
20
+ hidden_act="silu",
21
+ initializer_range=0.02,
22
+ rms_norm_eps=1e-6,
23
+ use_cache=True,
24
+ pad_token_id=0,
25
+ bos_token_id=1,
26
+ eos_token_id=2,
27
+ rope_theta=10000.0,
28
+ rope_scaling=None,
29
+ tie_word_embeddings=False,
30
+ moe_intermediate_size: Optional[int] = None,
31
+ moe_renormalize: bool = True,
32
+ moe_router_activation_func: str = "sigmoid",
33
+ num_experts: Optional[int] = None,
34
+ num_experts_per_token: Optional[int] = None,
35
+ num_shared_experts: int = 0,
36
+ routed_scaling_factor: float = 1.0,
37
+ first_k_dense_replace: int = 0,
38
+ moe_layer_freq: int = 1,
39
+ use_grouped_topk: bool = True,
40
+ num_expert_group: int = 1,
41
+ topk_group: int = 1,
42
+ q_lora_rank: Optional[int] = None,
43
+ kv_lora_rank: Optional[int] = None,
44
+ qk_nope_head_dim: Optional[int] = None,
45
+ qk_rope_head_dim: Optional[int] = None,
46
+ v_head_dim: Optional[int] = None,
47
+ mla_use_nope: Optional[bool] = False,
48
+ mla_use_output_gate: Optional[bool] = False,
49
+ num_nextn_predict_layers: int = 0,
50
+ linear_attn_config: Optional[dict] = None,
51
+ dsa_config: Optional[dict] = None,
52
+ attn_res_block_size: Optional[int] = None,
53
+ latent_moe_use_norm: bool = False,
54
+ activation_situ_beta: Optional[float] = None,
55
+ activation_situ_linear_beta: Optional[float] = None,
56
+ max_position_embeddings: int = 4096,
57
+ routed_expert_hidden_size: Optional[int] = None,
58
+ topk_method: str = "noaux_tc",
59
+ **kwargs,
60
+ ):
61
+ self.model_type = model_type
62
+ self.vocab_size = vocab_size
63
+ self.hidden_size = hidden_size
64
+ self.head_dim = (head_dim if head_dim is not None else hidden_size //
65
+ num_attention_heads)
66
+ self.intermediate_size = intermediate_size
67
+ self.num_hidden_layers = num_hidden_layers
68
+ self.num_attention_heads = num_attention_heads
69
+
70
+ # for backward compatibility
71
+ if num_key_value_heads is None:
72
+ num_key_value_heads = num_attention_heads
73
+
74
+ self.num_key_value_heads = num_key_value_heads
75
+ self.hidden_act = hidden_act
76
+ self.initializer_range = initializer_range
77
+ self.rms_norm_eps = rms_norm_eps
78
+ self.use_cache = use_cache
79
+ self.rope_theta = rope_theta
80
+ self.rope_scaling = rope_scaling
81
+
82
+ self.q_lora_rank = q_lora_rank
83
+ self.kv_lora_rank = kv_lora_rank
84
+ self.qk_nope_head_dim = qk_nope_head_dim
85
+ self.qk_rope_head_dim = qk_rope_head_dim
86
+ self.v_head_dim = v_head_dim
87
+ self.mla_use_nope = mla_use_nope
88
+ self.mla_use_output_gate = mla_use_output_gate
89
+ # moe config
90
+ self.num_experts = num_experts
91
+ self.num_experts_per_token = num_experts_per_token
92
+ self.moe_renormalize = moe_renormalize
93
+ self.num_shared_experts = num_shared_experts
94
+ self.routed_scaling_factor = routed_scaling_factor
95
+ self.moe_router_activation_func = moe_router_activation_func
96
+ assert self.moe_router_activation_func in ("softmax", "sigmoid")
97
+ self.moe_intermediate_size = moe_intermediate_size
98
+ self.first_k_dense_replace = first_k_dense_replace
99
+ self.moe_layer_freq = moe_layer_freq
100
+ self.use_grouped_topk = use_grouped_topk
101
+ self.num_expert_group = num_expert_group
102
+ self.topk_group = topk_group
103
+ self.num_nextn_predict_layers = num_nextn_predict_layers
104
+
105
+ self.attn_res_block_size = attn_res_block_size
106
+ self.latent_moe_use_norm = latent_moe_use_norm
107
+ self.activation_situ_beta = activation_situ_beta
108
+ self.activation_situ_linear_beta = activation_situ_linear_beta
109
+ self.max_position_embeddings = max_position_embeddings
110
+ self.routed_expert_hidden_size = routed_expert_hidden_size
111
+ self.topk_method = topk_method
112
+
113
+ if linear_attn_config is not None:
114
+ assert linear_attn_config["kda_layers"] is not None
115
+ assert linear_attn_config["full_attn_layers"] is not None
116
+ self.linear_attn_config = linear_attn_config
117
+ # k31(KDA + DSA 混合):DSA 参数以嵌套子结构随包导出
118
+ self.dsa_config = dsa_config
119
+
120
+ super().__init__(
121
+ pad_token_id=pad_token_id,
122
+ bos_token_id=bos_token_id,
123
+ eos_token_id=eos_token_id,
124
+ tie_word_embeddings=tie_word_embeddings,
125
+ **kwargs,
126
+ )
127
+
128
+ @property
129
+ def is_mla(self):
130
+ return (self.q_lora_rank is not None or self.kv_lora_rank is not None
131
+ or self.qk_nope_head_dim is not None
132
+ or self.qk_rope_head_dim is not None
133
+ or self.v_head_dim is not None or self.mla_use_nope is True)
134
+
135
+ @property
136
+ def is_moe(self):
137
+ return self.num_experts is not None
138
+
139
+ @property
140
+ def is_linear_attn(self) -> bool:
141
+ return not (self.linear_attn_config is None or
142
+ (isinstance(self.linear_attn_config, dict)
143
+ and self.linear_attn_config["kda_layers"] is not None
144
+ and len(self.linear_attn_config["kda_layers"]) == 0))
145
+
146
+ def is_kda_layer(self, layer_idx: int):
147
+ return (self.linear_attn_config is not None
148
+ and (layer_idx + 1) in self.linear_attn_config["kda_layers"])
149
+
150
+
151
+ class KimiK3VisionConfig(PretrainedConfig):
152
+
153
+ def __init__(
154
+ self,
155
+ patch_size: int = 14,
156
+ init_pos_emb_height: int = 64,
157
+ init_pos_emb_width: int = 64,
158
+ init_pos_emb_time: int = 4,
159
+ pos_emb_type: str = 'divided_fixed',
160
+ vt_num_attention_heads: int = 12,
161
+ vt_num_hidden_layers: int = 27,
162
+ vt_hidden_size: int = 1024,
163
+ vt_intermediate_size: int = 4096,
164
+ merge_kernel_size: tuple = (2, 2),
165
+ merge_type: str = 'sd2_tpool',
166
+ _attn_implementation: str = 'flash_attention_2',
167
+ # MM Projector parameters
168
+ mm_projector_type: str = 'patchmergerv2',
169
+ mm_hidden_size: int | None = None,
170
+ projector_hidden_act: str = "gelu",
171
+ projector_ln_eps: float = 1e-5,
172
+ # vision tower parameters
173
+ qkv_hidden_size: int = 1536,
174
+ norm_type: str = 'rmsnorm',
175
+ attn_bias: bool = False,
176
+ patch_embed_proj_bias: bool = False,
177
+ mlp_type: str = 'mlp2',
178
+ linear_bias: bool = False,
179
+ activation_func: str = 'gelu_pytorch_tanh',
180
+ pos_emb_interpolation_mode: str = 'bilinear',
181
+ # Other parameters
182
+ ignore_index: int = -100,
183
+ media_placeholder_token_id: int = 163605,
184
+ pad_token_id: int = 0,
185
+ text_hidden_size=7168,
186
+ **kwargs):
187
+
188
+ self.patch_size = patch_size
189
+ self.init_pos_emb_height = init_pos_emb_height
190
+ self.init_pos_emb_width = init_pos_emb_width
191
+ self.init_pos_emb_time = init_pos_emb_time
192
+ self.pos_emb_type = pos_emb_type
193
+ self.vt_num_attention_heads = vt_num_attention_heads
194
+ self.vt_num_hidden_layers = vt_num_hidden_layers
195
+ self.vt_hidden_size = vt_hidden_size
196
+ self.vt_intermediate_size = vt_intermediate_size
197
+ self.merge_kernel_size = merge_kernel_size
198
+ self.merge_type = merge_type
199
+ self._attn_implementation = _attn_implementation
200
+
201
+ # MM Projector config
202
+ self.mm_projector_type = mm_projector_type
203
+ self.mm_hidden_size = mm_hidden_size if mm_hidden_size is not None else vt_hidden_size
204
+ self.projector_hidden_act = projector_hidden_act
205
+ self.projector_ln_eps = projector_ln_eps
206
+ self.text_hidden_size = text_hidden_size
207
+
208
+ # vision tower parameters
209
+ self.qkv_hidden_size = qkv_hidden_size
210
+ self.norm_type = norm_type
211
+ self.attn_bias = attn_bias
212
+ self.patch_embed_proj_bias = patch_embed_proj_bias
213
+ self.mlp_type = mlp_type
214
+ self.linear_bias = linear_bias
215
+ self.activation_func = activation_func
216
+ self.pos_emb_interpolation_mode = pos_emb_interpolation_mode
217
+
218
+ super().__init__(**kwargs)
219
+
220
+
221
+ class KimiK3Config(PretrainedConfig):
222
+ """Kimi-K3 model configuration.
223
+
224
+ Args:
225
+ text_config (dict | KimiLinearConfig): Configuration for the text model.
226
+
227
+ Vision Tower Parameters (from MoonViT3dConfig):
228
+ patch_size (int): Patch size for vision tower.
229
+ init_pos_emb_height (int): Initial position embedding height.
230
+ init_pos_emb_width (int): Initial position embedding width.
231
+ init_pos_emb_time (int): Initial position embedding time dimension.
232
+ pos_emb_type (str): Type of position embedding.
233
+ vt_num_attention_heads (int): Number of attention heads in vision tower.
234
+ vt_num_hidden_layers (int): Number of hidden layers in vision tower.
235
+ vt_hidden_size (int): Hidden size of vision tower.
236
+ vt_intermediate_size (int): Intermediate size in vision tower FFN.
237
+ merge_kernel_size (tuple): Kernel size for patch merging.
238
+ merge_type (str): Type of merge operation.
239
+ _attn_implementation (str): Attention implementation type.
240
+
241
+ MM Projector Parameters (from MultiModalProjectorConfig):
242
+ mm_projector_type (str): Type of multimodal projector.
243
+ mm_hidden_size (int): Hidden size from vision tower (should match vt_hidden_size).
244
+ projector_hidden_act (str): Activation function for projector.
245
+ projector_ln_eps (float): Layer norm epsilon for projector.
246
+
247
+ Other Parameters:
248
+ ignore_index (int): The ignore index for the loss function.
249
+ media_placeholder_token_id (int): The token ID to use for media placeholders.
250
+ pad_token_id (int): The token ID to use for padding.
251
+ """
252
+
253
+ model_type = "kimi_k31"
254
+
255
+ def __init__(
256
+ self,
257
+ text_config: dict | KimiLinearConfig = None,
258
+ vision_config: dict | KimiK3VisionConfig = None,
259
+ # Other parameters
260
+ ignore_index: int = -100,
261
+ media_placeholder_token_id: int = 163605,
262
+ pad_token_id: int = 0,
263
+ **kwargs,
264
+ ):
265
+ if isinstance(text_config, dict):
266
+ text_config = KimiLinearConfig(**text_config)
267
+ if isinstance(vision_config, dict):
268
+ vision_config = KimiK3VisionConfig(**vision_config)
269
+ self.text_config = text_config
270
+ self.vision_config = vision_config
271
+ # Other config
272
+ self.ignore_index = ignore_index
273
+ self.media_placeholder_token_id = media_placeholder_token_id
274
+ if getattr(self.text_config, "quantization_config", None) is not None:
275
+ self.quantization_config = self.text_config.quantization_config
276
+
277
+ super().__init__(pad_token_id=pad_token_id, **kwargs)
encoding_k3.py ADDED
@@ -0,0 +1,735 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Kimi K3 XTML encoding helpers.
2
+
3
+ This module keeps chat rendering in Python.
4
+ Callers that need token IDs should consume ``EncodeSegment`` objects directly:
5
+ structural markers may be encoded as tiktoken special tokens, while user/tool
6
+ text and attribute values are encoded as ordinary text.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import json
12
+ from dataclasses import dataclass
13
+ from typing import Any, Iterable, Optional
14
+
15
+ OPEN_TOKEN = "<|open|>"
16
+ CLOSE_TOKEN = "<|close|>"
17
+ SEP_TOKEN = "<|sep|>"
18
+ END_OF_MSG_TOKEN = "<|end_of_msg|>"
19
+ IMAGE_PLACEHOLDER = "<|kimi_image_placeholder|>"
20
+
21
+ _VALID_THINKING_EFFORTS = {"low", "medium", "high", "max"}
22
+
23
+
24
+ @dataclass(frozen=True)
25
+ class EncodeSegment:
26
+ text: str
27
+ allow_special: bool = False
28
+
29
+
30
+ class _ImagePromptState:
31
+
32
+ def __init__(self, image_prompts: Optional[list[str]] = None):
33
+ self.image_prompts = image_prompts
34
+ self.index = 0
35
+
36
+ def next_prompt(self) -> str:
37
+ if self.image_prompts is None:
38
+ return IMAGE_PLACEHOLDER
39
+ if self.index >= len(self.image_prompts):
40
+ raise ValueError("More image placeholders than image prompts.")
41
+ prompt = self.image_prompts[self.index]
42
+ self.index += 1
43
+ return prompt
44
+
45
+ def assert_consumed(self) -> None:
46
+ if self.image_prompts is None:
47
+ return
48
+ if self.index != len(self.image_prompts):
49
+ raise ValueError(
50
+ f"image prompt count {len(self.image_prompts)} != "
51
+ f"consumed placeholder count {self.index}")
52
+
53
+
54
+ def _segment(text: Any, *, allow_special: bool = False) -> list[EncodeSegment]:
55
+ text = str(text)
56
+ if not text:
57
+ return []
58
+ return [EncodeSegment(text, allow_special=allow_special)]
59
+
60
+
61
+ def _control(text: str) -> list[EncodeSegment]:
62
+ return _segment(text, allow_special=True)
63
+
64
+
65
+ def _text(text: Any) -> list[EncodeSegment]:
66
+ return _segment(text, allow_special=False)
67
+
68
+
69
+ def _append_text(
70
+ segments: list[EncodeSegment],
71
+ text: Any,
72
+ image_state: _ImagePromptState,
73
+ ) -> None:
74
+ text = str(text)
75
+ if text == "":
76
+ return
77
+ if image_state.image_prompts is None or IMAGE_PLACEHOLDER not in text:
78
+ segments.extend(_text(text))
79
+ return
80
+
81
+ parts = text.split(IMAGE_PLACEHOLDER)
82
+ for i, part in enumerate(parts):
83
+ segments.extend(_text(part))
84
+ if i < len(parts) - 1:
85
+ segments.extend(
86
+ _segment(image_state.next_prompt(), allow_special=True))
87
+
88
+
89
+ def _escape_attr_value(value: Any) -> str:
90
+ return str(value).replace("&", "&amp;").replace('"', "&quot;")
91
+
92
+
93
+ def _attr(key: str, value: Any) -> list[EncodeSegment]:
94
+ return (_text(f" {key}") + _text('="') + _text(_escape_attr_value(value)) +
95
+ _text('"'))
96
+
97
+
98
+ def _open_tag(
99
+ tag: str, attrs: Iterable[tuple[str, Any]] = ()) -> list[EncodeSegment]:
100
+ segments: list[EncodeSegment] = []
101
+ segments.extend(_control(OPEN_TOKEN))
102
+ segments.extend(_text(tag))
103
+ for key, value in attrs:
104
+ segments.extend(_attr(key, value))
105
+ segments.extend(_control(SEP_TOKEN))
106
+ return segments
107
+
108
+
109
+ def _close_tag(tag: str) -> list[EncodeSegment]:
110
+ segments: list[EncodeSegment] = []
111
+ segments.extend(_control(CLOSE_TOKEN))
112
+ segments.extend(_text(tag))
113
+ segments.extend(_control(SEP_TOKEN))
114
+ return segments
115
+
116
+
117
+ def _end_of_msg() -> list[EncodeSegment]:
118
+ return _control(END_OF_MSG_TOKEN)
119
+
120
+
121
+ def _json_compact(value: Any) -> str:
122
+ return json.dumps(value, ensure_ascii=False, separators=(",", ":"))
123
+
124
+
125
+ def _is_mapping(value: Any) -> bool:
126
+ return isinstance(value, dict)
127
+
128
+
129
+ def _xtml_type(value: Any) -> str:
130
+ if isinstance(value, bool):
131
+ return "boolean"
132
+ if value is None:
133
+ return "null"
134
+ if isinstance(value, (int, float)) and not isinstance(value, bool):
135
+ return "number"
136
+ if isinstance(value, str):
137
+ return "string"
138
+ if _is_mapping(value):
139
+ return "object"
140
+ return "array"
141
+
142
+
143
+ def _xtml_value(value: Any) -> str:
144
+ if isinstance(value, str):
145
+ return value
146
+ return json.dumps(value, ensure_ascii=False)
147
+
148
+
149
+ def _get_value(obj: Any, key: str, default: Any = None) -> Any:
150
+ if isinstance(obj, dict):
151
+ return obj.get(key, default)
152
+ return getattr(obj, key, default)
153
+
154
+
155
+ def extract_response_schema(response_format: Any) -> Any:
156
+ if response_format is None:
157
+ return None
158
+
159
+ json_schema = _get_value(response_format, "json_schema")
160
+ if json_schema is None:
161
+ return None
162
+
163
+ if isinstance(json_schema, dict):
164
+ return json_schema.get(
165
+ "schema",
166
+ json_schema.get("json_schema", json_schema),
167
+ )
168
+
169
+ schema = _get_value(json_schema, "schema")
170
+ if schema is not None:
171
+ return schema
172
+
173
+ schema = _get_value(json_schema, "json_schema")
174
+ if schema is not None:
175
+ return schema
176
+
177
+ return json_schema
178
+
179
+
180
+ def deep_sort_dict(obj: Any) -> Any:
181
+ if isinstance(obj, dict):
182
+ return {k: deep_sort_dict(v) for k, v in sorted(obj.items())}
183
+ if isinstance(obj, list):
184
+ return [deep_sort_dict(item) for item in obj]
185
+ return obj
186
+
187
+
188
+ # One normalized tool-call argument: (key, XTML type, rendered text). The
189
+ # text keeps the original JSON literal for non-string values and is the
190
+ # decoded string for string values.
191
+ XtmlArgument = tuple[str, str, str]
192
+
193
+
194
+ def _parse_arguments_object(s: str) -> list[XtmlArgument]:
195
+ """Parse a JSON object string one level deep.
196
+
197
+ Each top-level key-value pair yields one ``(key, type, text)`` triple. The
198
+ text keeps the original JSON literal for non-string values (``1e2`` stays
199
+ ``1e2``, ``[1,2]`` keeps its exact bytes) and is the decoded (unescaped)
200
+ string for string values. Nested values are never re-serialized. Anything
201
+ after the closing ``}`` is ignored. Raises ``ValueError`` on malformed
202
+ input (including a valid non-object JSON document).
203
+ """
204
+ idx = 0
205
+
206
+ def _skip_whitespaces() -> None:
207
+ nonlocal idx
208
+ while idx < len(s) and s[idx] in (" ", "\t", "\n", "\r"):
209
+ idx += 1
210
+
211
+ def _next_char() -> Optional[str]:
212
+ nonlocal idx
213
+ if idx < len(s):
214
+ c = s[idx]
215
+ idx += 1
216
+ return c
217
+ return None
218
+
219
+ _skip_whitespaces()
220
+ if _next_char() != "{":
221
+ raise ValueError("JSON arguments must be an object")
222
+ _skip_whitespaces()
223
+
224
+ parsed: list[XtmlArgument] = []
225
+
226
+ if idx >= len(s):
227
+ raise ValueError("Unexpected end of JSON object")
228
+ if s[idx] == "}":
229
+ return parsed
230
+
231
+ json_decoder = json.JSONDecoder(strict=False)
232
+
233
+ def _raw_decode(start_idx: int) -> tuple[Any, int]:
234
+ try:
235
+ return json_decoder.raw_decode(s, idx=start_idx)
236
+ except json.JSONDecodeError as e:
237
+ raise ValueError(str(e)) from e
238
+
239
+ while True:
240
+ decoded_key, idx = _raw_decode(idx)
241
+ if not isinstance(decoded_key, str):
242
+ raise ValueError(
243
+ f"JSON object key must be a string, got {decoded_key!r}")
244
+ _skip_whitespaces()
245
+ if _next_char() != ":":
246
+ raise ValueError(f"Expects ':' after {decoded_key}")
247
+ _skip_whitespaces()
248
+
249
+ value_start_idx = idx
250
+ decoded_value, idx = _raw_decode(idx)
251
+ value_end_idx = idx
252
+
253
+ text = (decoded_value if isinstance(decoded_value, str) else
254
+ s[value_start_idx:value_end_idx])
255
+ parsed.append((decoded_key, _xtml_type(decoded_value), text))
256
+
257
+ _skip_whitespaces()
258
+ c = _next_char()
259
+ _skip_whitespaces()
260
+
261
+ if c == "}":
262
+ break
263
+ elif c == ",":
264
+ continue
265
+ else:
266
+ raise ValueError(f"Expect '}}' or ',', got {c!r}")
267
+
268
+ return parsed
269
+
270
+
271
+ def normalize_tool_arguments(
272
+ arguments: Any, ) -> tuple[list[XtmlArgument], Optional[str]]:
273
+ """Normalize tool call arguments for XTML rendering.
274
+
275
+ Returns ``(argument_triples, raw_json_block)``. String arguments are parsed
276
+ one level deep so non-string values keep their original JSON literal text,
277
+ and anything after the closing ``}`` is discarded; dict arguments (already
278
+ Python objects) are serialized on the spot. Any string that is not a
279
+ well-formed JSON object -- unparsable, whitespace only, or valid
280
+ non-object JSON -- falls back to the raw ``<json>`` block.
281
+ """
282
+ if arguments is None:
283
+ return [], None
284
+ if isinstance(arguments, dict):
285
+ return [(str(key), _xtml_type(value), _xtml_value(value))
286
+ for key, value in arguments.items()], None
287
+ if isinstance(arguments, str):
288
+ if arguments == "":
289
+ return [], None
290
+ try:
291
+ return _parse_arguments_object(arguments), None
292
+ except ValueError:
293
+ return [], arguments
294
+ raise TypeError(
295
+ "Kimi K3 tool call arguments must be a dict or a JSON object string.")
296
+
297
+
298
+ def normalize_message(message: Any) -> Any:
299
+ if not isinstance(message, dict):
300
+ return message
301
+
302
+ normalized = dict(message)
303
+
304
+ tools = normalized.get("tools")
305
+ if tools is not None:
306
+ normalized["tools"] = deep_sort_dict(tools)
307
+
308
+ tool_calls = normalized.get("tool_calls")
309
+ if not tool_calls:
310
+ return normalized
311
+
312
+ normalized_calls = []
313
+ for tool_call in tool_calls:
314
+ if not isinstance(tool_call, dict):
315
+ normalized_calls.append(tool_call)
316
+ continue
317
+
318
+ tc = dict(tool_call)
319
+ function = tc.get("function")
320
+ if isinstance(function, dict):
321
+ fn = dict(function)
322
+ arguments, json_block = normalize_tool_arguments(
323
+ fn.get("arguments"))
324
+ fn["arguments"] = arguments
325
+ if json_block is None:
326
+ fn.pop("_xtml_json_block", None)
327
+ else:
328
+ fn["_xtml_json_block"] = json_block
329
+ tc["function"] = fn
330
+ else:
331
+ arguments, json_block = normalize_tool_arguments(
332
+ tc.get("arguments"))
333
+ tc["arguments"] = arguments
334
+ if json_block is None:
335
+ tc.pop("_xtml_json_block", None)
336
+ else:
337
+ tc["_xtml_json_block"] = json_block
338
+ normalized_calls.append(tc)
339
+
340
+ normalized["tool_calls"] = normalized_calls
341
+ return normalized
342
+
343
+
344
+ def normalize_conversation(conversation: Any) -> Any:
345
+ if not isinstance(conversation, list):
346
+ return conversation
347
+
348
+ def normalize_messages(messages: list[Any]) -> list[Any]:
349
+ return [normalize_message(message) for message in messages]
350
+
351
+ if conversation and isinstance(conversation[0], list):
352
+ return [normalize_messages(messages) for messages in conversation]
353
+ return normalize_messages(conversation)
354
+
355
+
356
+ def _tool_call_id_index(tool_calls: Any) -> dict:
357
+ """Map assistant ``tool_calls[].id`` to ``(1-based position, function name)``.
358
+
359
+ The position mirrors the chat template's enumeration over ``tool_calls``
360
+ (every entry advances the position, even an id-less one). Duplicate ids keep
361
+ their first occurrence.
362
+ """
363
+ index: dict = {}
364
+ if not isinstance(tool_calls, list):
365
+ return index
366
+ for position, tool_call in enumerate(tool_calls, start=1):
367
+ if not isinstance(tool_call, dict):
368
+ continue
369
+ call_id = tool_call.get("id")
370
+ if call_id is None:
371
+ continue
372
+ key = str(call_id)
373
+ if key in index:
374
+ continue
375
+ function = tool_call.get("function")
376
+ name = (function.get("name")
377
+ if isinstance(function, dict) else tool_call.get("name"))
378
+ index[key] = (position, name)
379
+ return index
380
+
381
+
382
+ def normalize_xtml_tool_result_messages(messages: list[Any]) -> list[Any]:
383
+ """Re-sort K3 XTML tool results into assistant ``tool_calls`` order.
384
+
385
+ Serving frameworks generally deliver tool results already in call order. A
386
+ direct Transformers caller, however, may pass OpenAI-style tool messages in any
387
+ order, so each run of consecutive tool messages is matched against the most
388
+ recent preceding assistant ``tool_calls`` by opaque ``tool_call_id`` ==
389
+ ``tool_calls[].id`` (K3 drops the ``func:index`` format requirement) and
390
+ sorted by the matched 1-based position. The matched call is authoritative,
391
+ so each matched message's ``tool`` is set to that call's function name --
392
+ this keeps an explicit (and possibly stale) ``tool``/``name`` from drifting
393
+ out of sync with the reordered position. ``index`` is still derived from the
394
+ rendered position by the chat template. A run that cannot be fully matched is
395
+ left untouched. Re-running is idempotent.
396
+
397
+ This function is side-effect free: matched tool messages are shallow-copied
398
+ before their ``tool``/``name`` is rewritten, and every other message is
399
+ appended to the output as-is. The input list and its message objects are
400
+ never mutated.
401
+ """
402
+ if not isinstance(messages, list):
403
+ return messages
404
+
405
+ output: list[Any] = []
406
+ current_index: dict = {}
407
+ i = 0
408
+ n = len(messages)
409
+
410
+ while i < n:
411
+ message = messages[i]
412
+
413
+ if isinstance(message, dict) and message.get("role") == "assistant":
414
+ tool_calls = message.get("tool_calls")
415
+ current_index = _tool_call_id_index(
416
+ tool_calls) if tool_calls else {}
417
+ output.append(message)
418
+ i += 1
419
+ continue
420
+
421
+ if not isinstance(message, dict) or message.get("role") != "tool":
422
+ output.append(message)
423
+ i += 1
424
+ continue
425
+
426
+ run: list[tuple] = [] # (position, original_offset, message, name)
427
+ unresolved = False
428
+ offset = 0
429
+ while (i < n and isinstance(messages[i], dict)
430
+ and messages[i].get("role") == "tool"):
431
+ tool_message = messages[i]
432
+ call_id = tool_message.get("tool_call_id", tool_message.get("id"))
433
+ matched = current_index.get(
434
+ str(call_id)) if call_id is not None else None
435
+ if matched is None:
436
+ unresolved = True
437
+ run.append((None, offset, tool_message, None))
438
+ else:
439
+ position, name = matched
440
+ run.append((position, offset, tool_message, name))
441
+ offset += 1
442
+ i += 1
443
+
444
+ if unresolved:
445
+ output.extend(item[2] for item in run)
446
+ else:
447
+ run.sort(key=lambda item: (item[0], item[1]))
448
+ for _, _, tool_message, name in run:
449
+ if name is None:
450
+ output.append(tool_message)
451
+ continue
452
+ # The id-matched call is authoritative: align tool (and any
453
+ # explicit name) so the rendered XTML tool attribute cannot
454
+ # disagree with the reordered position. Copy first so the
455
+ # caller's message object is never mutated.
456
+ resolved = dict(tool_message)
457
+ resolved["tool"] = name
458
+ if "name" in resolved:
459
+ resolved["name"] = name
460
+ output.append(resolved)
461
+
462
+ return output
463
+
464
+
465
+ def is_batched_conversation(conversation: Any) -> bool:
466
+ return (isinstance(conversation, list) and bool(conversation)
467
+ and isinstance(conversation[0], list))
468
+
469
+
470
+ def _render_content_segments(
471
+ content: Any,
472
+ image_state: _ImagePromptState,
473
+ ) -> list[EncodeSegment]:
474
+ segments: list[EncodeSegment] = []
475
+ if isinstance(content, str):
476
+ _append_text(segments, content, image_state)
477
+ elif content is not None:
478
+ for part in content:
479
+ if part["type"] in ["image", "image_url"]:
480
+ segments.extend(
481
+ _segment(image_state.next_prompt(), allow_special=True))
482
+ else:
483
+ _append_text(segments, part["text"], image_state)
484
+ return segments
485
+
486
+
487
+ def _internal_system_message(message_type: str,
488
+ body: str) -> list[EncodeSegment]:
489
+ segments: list[EncodeSegment] = []
490
+ segments.extend(
491
+ _open_tag("message", [("role", "system"), ("type", message_type)]))
492
+ segments.extend(_text(body.strip()))
493
+ segments.extend(_close_tag("message"))
494
+ segments.extend(_end_of_msg())
495
+ return segments
496
+
497
+
498
+ def _render_assistant_segments(
499
+ message: dict[str, Any],
500
+ image_state: _ImagePromptState,
501
+ thinking: bool = True,
502
+ ) -> list[EncodeSegment]:
503
+ segments: list[EncodeSegment] = []
504
+ # The <think> channel is structural: in thinking mode every assistant
505
+ # message carries the open/close tags even when there is no reasoning
506
+ # content to fill in. In non-thinking mode the channel is dropped
507
+ # entirely.
508
+ if thinking:
509
+ reasoning_content = message.get("reasoning_content") or message.get(
510
+ "reasoning")
511
+ segments.extend(_open_tag("think"))
512
+ # Only an empty string counts as no reasoning, so whitespace-only
513
+ # reasoning is still rendered into the think channel.
514
+ if reasoning_content is not None and str(reasoning_content) != "":
515
+ _append_text(segments, reasoning_content, image_state)
516
+ segments.extend(_close_tag("think"))
517
+
518
+ segments.extend(_open_tag("response"))
519
+ segments.extend(
520
+ _render_content_segments(message.get("content"), image_state))
521
+ segments.extend(_close_tag("response"))
522
+
523
+ tool_calls = message.get("tool_calls")
524
+ if tool_calls:
525
+ segments.extend(_open_tag("tools"))
526
+ for index, tool_call in enumerate(tool_calls, start=1):
527
+ fn = tool_call.get("function", tool_call)
528
+ segments.extend(
529
+ _open_tag("call", [("tool", fn["name"]), ("index", index)]))
530
+ args = fn.get("arguments", [])
531
+ json_block = fn.get("_xtml_json_block")
532
+ if json_block is not None:
533
+ segments.extend(_open_tag("json", [("type", "object")]))
534
+ _append_text(segments, json_block, image_state)
535
+ segments.extend(_close_tag("json"))
536
+ else:
537
+ for key, arg_type, arg_text in args:
538
+ segments.extend(
539
+ _open_tag("argument", [("key", key),
540
+ ("type", arg_type)]))
541
+ _append_text(segments, arg_text, image_state)
542
+ segments.extend(_close_tag("argument"))
543
+ segments.extend(_close_tag("call"))
544
+ segments.extend(_close_tag("tools"))
545
+
546
+ return segments
547
+
548
+
549
+ def _render_tool_declare(tools: Any,
550
+ *,
551
+ dynamic: bool = False) -> list[EncodeSegment]:
552
+ if dynamic:
553
+ body = (
554
+ "## New Tools Available\n"
555
+ "The system dynamically extends the toolset via lazy-loading.\n"
556
+ "You have access to all existing and extended tools.\n"
557
+ "Here are the specs for the extended tools.\n\n"
558
+ "```json\n"
559
+ f"{_json_compact(tools)}\n"
560
+ "```")
561
+ else:
562
+ body = ("# Tools\n"
563
+ "Here are the available tools, described in JSONSchema.\n\n"
564
+ "```json\n"
565
+ f"{_json_compact(tools)}\n"
566
+ "```")
567
+ segments: list[EncodeSegment] = []
568
+ segments.extend(
569
+ _open_tag("message", [("role", "system"), ("type", "tool-declare")]))
570
+ segments.extend(_text(body))
571
+ segments.extend(_close_tag("message"))
572
+ segments.extend(_end_of_msg())
573
+ return segments
574
+
575
+
576
+ def build_chat_segments(
577
+ messages: list[Any],
578
+ tools: Optional[list[dict]] = None,
579
+ *,
580
+ add_generation_prompt: bool = True,
581
+ thinking: bool = True,
582
+ image_prompts: Optional[list[str]] = None,
583
+ **kwargs: Any,
584
+ ) -> list[EncodeSegment]:
585
+ # Re-sort tool results by tool_call_id at the lowest layer so every caller
586
+ # (processor or direct tokenizer) gets correctly ordered XTML. The helper is
587
+ # side-effect free, so the caller's message objects are left untouched.
588
+ messages = normalize_xtml_tool_result_messages(messages)
589
+ messages = normalize_conversation(messages)
590
+ tools = deep_sort_dict(tools)
591
+
592
+ kwargs = dict(kwargs)
593
+ response_format = kwargs.get("response_format")
594
+ if "response_schema" not in kwargs:
595
+ response_schema = extract_response_schema(response_format)
596
+ if response_schema is not None:
597
+ kwargs["response_schema"] = response_schema
598
+ if kwargs.get("response_schema") is not None:
599
+ kwargs["response_schema"] = deep_sort_dict(kwargs["response_schema"])
600
+
601
+ image_state = _ImagePromptState(image_prompts)
602
+ segments: list[EncodeSegment] = []
603
+
604
+ tool_calls = None
605
+ tool_index = 0
606
+
607
+ if tools:
608
+ segments.extend(_render_tool_declare(tools))
609
+
610
+ thinking_effort = kwargs.get("thinking_effort")
611
+ if thinking and thinking_effort is not None:
612
+ assert thinking_effort in _VALID_THINKING_EFFORTS, (
613
+ f"Unsupported thinking_effort={thinking_effort!r}; "
614
+ f"supported values are {sorted(_VALID_THINKING_EFFORTS)}.")
615
+ if thinking and thinking_effort in _VALID_THINKING_EFFORTS:
616
+ segments.extend(
617
+ _internal_system_message(
618
+ "thinking-effort",
619
+ "`thinking_effort` guides on how much to think in your "
620
+ "thinking channel (not including the response channel), "
621
+ "supported values include `low`, `medium`, `high`, and `max`.\n"
622
+ f"Now the system is invoked with `thinking_effort={thinking_effort}`.",
623
+ ))
624
+
625
+ for message_index, message in enumerate(messages):
626
+ # Malformed messages are rejected instead of being silently skipped.
627
+ if not isinstance(message, dict):
628
+ raise ValueError(
629
+ f"Kimi K3 messages must be dicts, got {type(message).__name__} "
630
+ f"at index {message_index}.")
631
+
632
+ role = message.get("role")
633
+ if role == "user":
634
+ attrs = [("role", "user")]
635
+ if message.get("name"):
636
+ attrs.append(("name", message["name"]))
637
+ segments.extend(_open_tag("message", attrs))
638
+ segments.extend(
639
+ _render_content_segments(message.get("content"), image_state))
640
+ segments.extend(_close_tag("message"))
641
+ segments.extend(_end_of_msg())
642
+ elif role == "system" and message.get("tools"):
643
+ segments.extend(
644
+ _render_tool_declare(message["tools"], dynamic=True))
645
+ elif role == "system":
646
+ attrs = [("role", "system")]
647
+ if message.get("name"):
648
+ attrs.append(("name", message["name"]))
649
+ segments.extend(_open_tag("message", attrs))
650
+ segments.extend(
651
+ _render_content_segments(message.get("content"), image_state))
652
+ segments.extend(_close_tag("message"))
653
+ segments.extend(_end_of_msg())
654
+ elif role == "tool":
655
+ tool_index += 1
656
+ tool_name = message.get("tool", message.get("name"))
657
+ if (tool_name is None and tool_calls is not None
658
+ and tool_index <= len(tool_calls)):
659
+ tc = tool_calls[tool_index - 1]
660
+ fn = tc.get("function", tc)
661
+ tool_name = fn["name"]
662
+ if tool_name is None:
663
+ raise ValueError(
664
+ "Kimi K3 tool messages need a resolvable tool name: "
665
+ "carry `tool`/`name`, or match a preceding assistant "
666
+ "tool_call by order.")
667
+ segments.extend(
668
+ _open_tag(
669
+ "message",
670
+ [("role", "tool"), ("tool", tool_name),
671
+ ("index", tool_index)],
672
+ ))
673
+ segments.extend(
674
+ _render_content_segments(message.get("content"), image_state))
675
+ segments.extend(_close_tag("message"))
676
+ segments.extend(_end_of_msg())
677
+ elif role == "assistant":
678
+ tool_calls = message.get("tool_calls")
679
+ tool_index = 0
680
+ attrs = [("role", "assistant")]
681
+ if message.get("name"):
682
+ attrs.append(("name", message["name"]))
683
+ segments.extend(_open_tag("message", attrs))
684
+ segments.extend(
685
+ _render_assistant_segments(message, image_state, thinking))
686
+ segments.extend(_close_tag("message"))
687
+ segments.extend(_end_of_msg())
688
+ else:
689
+ raise ValueError(
690
+ f"Unknown message role {role!r} at index {message_index}.")
691
+
692
+ tool_choice = kwargs.get("tool_choice")
693
+ if tool_choice == "required":
694
+ segments.extend(
695
+ _internal_system_message(
696
+ "tool-choice",
697
+ "The system is invoked with `tool_choice=required`.\n"
698
+ "You MUST call tools in the next message.",
699
+ ))
700
+ elif tool_choice == "none":
701
+ segments.extend(
702
+ _internal_system_message(
703
+ "tool-choice",
704
+ "The system is invoked with `tool_choice=none`.\n"
705
+ "You MUST NOT call any tools in the next message.",
706
+ ))
707
+
708
+ rf = kwargs.get("response_format")
709
+ rf_type = _get_value(rf, "type", rf) if isinstance(rf, dict) else rf
710
+ if rf_type == "json_object":
711
+ segments.extend(
712
+ _internal_system_message(
713
+ "response-format",
714
+ "The system is invoked with `response_format=json_object`.\n"
715
+ "Your response must be raw JSON data without markdown code "
716
+ "blocks (```json) or any additional formatting.",
717
+ ))
718
+ elif rf_type == "json_schema":
719
+ schema = _json_compact(kwargs.get("response_schema"))
720
+ segments.extend(
721
+ _internal_system_message(
722
+ "response-format",
723
+ "The system is invoked with `response_format=json_schema`.\n"
724
+ "Your response must be raw JSON data without markdown code "
725
+ "blocks (```json) or any additional formatting.\n"
726
+ "The JSON data must match the following schema:\n"
727
+ f"```json\n{schema}\n```",
728
+ ))
729
+
730
+ if add_generation_prompt:
731
+ segments.extend(_open_tag("message", [("role", "assistant")]))
732
+ segments.extend(_open_tag("think" if thinking else "response"))
733
+
734
+ image_state.assert_consumed()
735
+ return segments
generation_config.json ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ {
2
+ "eos_token_id": 163586,
3
+ "max_length": 1048576,
4
+ "transformers_version": "5.22.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:181c021dfd2926da51ec6b2fb8c96a57a2f39f86afbed4306b77a4c6bd9456de
3
+ size 144021592
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,1325 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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_mask_sizes(self, cache_position: torch.Tensor, layer_idx: int) -> tuple[int, int]:
207
+ """
208
+ Return a tuple (kv_length, kv_offset) corresponding to the length and offset that will be returned for
209
+ the given layer at `layer_idx`.
210
+ The masks are then prepared according to the given lengths (kv_length, kv_offset) and patterns for each layer.
211
+ """
212
+ kv_offset = 0
213
+ query_length = cache_position.shape[0]
214
+ past_seen_tokens = self.get_seq_length(layer_idx)
215
+ kv_length = query_length + past_seen_tokens
216
+ return kv_length, kv_offset
217
+
218
+ @property
219
+ def has_previous_state(self):
220
+ """We have a previous state if the last linear (conv) layer was already updated."""
221
+ if self.last_linear_layer == -1:
222
+ return False
223
+ return self.conv_states[self.last_linear_layer] is not None
224
+
225
+
226
+ class KimiRMSNorm(nn.Module):
227
+ def __init__(self, hidden_size, eps=1e-6):
228
+ super().__init__()
229
+ self.weight = nn.Parameter(torch.ones(hidden_size))
230
+ self.variance_epsilon = eps
231
+
232
+ def forward(self, hidden_states):
233
+ dtype = hidden_states.dtype
234
+ x = hidden_states.float()
235
+ x = x * torch.rsqrt(x.pow(2).mean(-1, keepdim=True) + self.variance_epsilon)
236
+ return self.weight * x.to(dtype)
237
+
238
+
239
+ ALL_LAYERNORM_LAYERS.append(KimiRMSNorm)
240
+
241
+
242
+ class KimiBlockSparseMLP(nn.Module):
243
+ def __init__(self, config: KimiLinearConfig, hidden_size=None, intermediate_size=None):
244
+ super().__init__()
245
+ self.config = config
246
+ self.ffn_dim = config.intermediate_size if intermediate_size is None else intermediate_size
247
+ self.hidden_dim = config.hidden_size if hidden_size is None else hidden_size
248
+
249
+ self.w1 = nn.Linear(self.hidden_dim, self.ffn_dim, bias=False) # gate
250
+ self.w2 = nn.Linear(self.ffn_dim, self.hidden_dim, bias=False) # down
251
+ self.w3 = nn.Linear(self.hidden_dim, self.ffn_dim, bias=False) # up
252
+
253
+ if config.hidden_act == "situ":
254
+ beta, linear_beta = _get_situ_activation_params(config)
255
+ self.act_fn = SituAndMul(
256
+ beta=beta,
257
+ linear_beta=linear_beta,
258
+ )
259
+ else:
260
+ self.act_fn = ACT2FN[config.hidden_act]
261
+
262
+ def forward(self, hidden_states):
263
+ if self.config.hidden_act == "situ":
264
+ gate_up = torch.cat([self.w1(hidden_states), self.w3(hidden_states)], dim=-1)
265
+ current_hidden_states = self.act_fn(gate_up)
266
+ else:
267
+ current_hidden_states = self.act_fn(
268
+ self.w1(hidden_states)) * self.w3(hidden_states)
269
+ current_hidden_states = self.w2(current_hidden_states)
270
+ return current_hidden_states
271
+
272
+
273
+ class KimiMLP(nn.Module):
274
+ def __init__(self, config: KimiLinearConfig, hidden_size=None, intermediate_size=None):
275
+ super().__init__()
276
+ self.config = config
277
+ self.hidden_size = config.hidden_size if hidden_size is None else hidden_size
278
+ self.intermediate_size = config.intermediate_size if intermediate_size is None else intermediate_size
279
+ self.gate_proj = nn.Linear(
280
+ self.hidden_size, self.intermediate_size, bias=False)
281
+ self.up_proj = nn.Linear(
282
+ self.hidden_size, self.intermediate_size, bias=False)
283
+ self.down_proj = nn.Linear(
284
+ self.intermediate_size, self.hidden_size, bias=False)
285
+ if config.hidden_act == "situ":
286
+ beta, linear_beta = _get_situ_activation_params(config)
287
+ self.act_fn = SituAndMul(
288
+ beta=beta,
289
+ linear_beta=linear_beta,
290
+ )
291
+ else:
292
+ self.act_fn = ACT2FN[config.hidden_act]
293
+
294
+ def forward(self, x):
295
+ if self.config.hidden_act == "situ":
296
+ gate_up = torch.cat([self.gate_proj(x), self.up_proj(x)], dim=-1)
297
+ down_proj = self.down_proj(self.act_fn(gate_up))
298
+ else:
299
+ down_proj = self.down_proj(self.act_fn(
300
+ self.gate_proj(x)) * self.up_proj(x))
301
+ return down_proj
302
+
303
+
304
+ def repeat_kv(hidden_states: torch.Tensor, n_rep: int) -> torch.Tensor:
305
+ """Expand the key/value heads from `num_key_value_heads` to `num_attention_heads`."""
306
+ if n_rep == 1:
307
+ return hidden_states
308
+ return torch.repeat_interleave(hidden_states, dim=1, repeats=n_rep)
309
+
310
+
311
+ def eager_attention_forward(
312
+ module: nn.Module,
313
+ query: torch.Tensor,
314
+ key: torch.Tensor,
315
+ value: torch.Tensor,
316
+ attention_mask: torch.Tensor | None,
317
+ scaling: float,
318
+ dropout: float = 0.0,
319
+ **kwargs: Unpack[TransformersKwargs],
320
+ ):
321
+ key = repeat_kv(key, module.num_key_value_groups)
322
+ value = repeat_kv(value, module.num_key_value_groups)
323
+
324
+ scores = torch.einsum("bhqd,bhkd->bhqk", query, key) * scaling
325
+ if attention_mask is not None:
326
+ scores = scores + attention_mask[:, :, :, : key.shape[-2]]
327
+
328
+ probs = F.softmax(scores, dim=-1, dtype=torch.float32).to(query.dtype)
329
+ probs = F.dropout(probs, p=dropout, training=module.training)
330
+ out = torch.einsum("bhqk,bhkd->bhqd", probs, value).transpose(1, 2).contiguous()
331
+
332
+ return out, probs
333
+
334
+
335
+ class KimiMLAAttention(nn.Module):
336
+ """
337
+ Multi-Latent Attention adapted from deepseek-v3
338
+ """
339
+
340
+ def __init__(self, config: KimiLinearConfig, layer_idx: int):
341
+ nn.Module.__init__(self)
342
+ self.config = config
343
+ self.layer_idx = layer_idx
344
+ self.hidden_size = config.hidden_size
345
+ self.num_heads = config.num_attention_heads
346
+ self.num_key_value_heads = config.num_key_value_heads
347
+ self.num_key_value_groups = self.num_heads // self.num_key_value_heads
348
+
349
+ self.attention_dropout = getattr(config, "attention_dropout", 0.0)
350
+
351
+ try:
352
+ self.q_lora_rank = config.q_lora_rank
353
+ self.qk_rope_head_dim = config.qk_rope_head_dim
354
+ self.kv_lora_rank = config.kv_lora_rank
355
+ self.v_head_dim = config.v_head_dim
356
+ self.qk_nope_head_dim = config.qk_nope_head_dim
357
+ self.q_head_dim = self.qk_nope_head_dim + self.qk_rope_head_dim
358
+ self.use_nope = config.mla_use_nope
359
+ self.scaling = self.q_head_dim ** (-0.5)
360
+ except Exception as e:
361
+ raise ValueError(
362
+ f"Kimi MLA config is not found or not properly formatted: {e}")
363
+
364
+ if self.q_lora_rank is not None:
365
+ self.q_a_proj = nn.Linear(
366
+ self.hidden_size, self.q_lora_rank, bias=False,
367
+ )
368
+ self.q_a_layernorm = KimiRMSNorm(self.q_lora_rank)
369
+ self.q_b_proj = nn.Linear(
370
+ self.q_lora_rank,
371
+ self.num_heads * self.q_head_dim,
372
+ bias=False,
373
+ )
374
+ else:
375
+ self.q_proj = nn.Linear(
376
+ self.hidden_size, self.num_heads * self.q_head_dim, bias=False,
377
+ )
378
+ self.kv_a_proj_with_mqa = nn.Linear(
379
+ self.hidden_size,
380
+ self.kv_lora_rank + self.qk_rope_head_dim,
381
+ bias=False,
382
+ )
383
+ self.kv_a_layernorm = KimiRMSNorm(self.kv_lora_rank)
384
+ self.kv_b_proj = nn.Linear(
385
+ self.kv_lora_rank,
386
+ self.num_heads
387
+ * (self.q_head_dim - self.qk_rope_head_dim + self.v_head_dim),
388
+ bias=False,
389
+ )
390
+ self.o_proj = nn.Linear(
391
+ self.num_heads * self.v_head_dim,
392
+ self.hidden_size,
393
+ bias=False,
394
+ )
395
+ self.is_causal = True
396
+ assert self.use_nope
397
+
398
+ self.use_output_gate = getattr(config, "mla_use_output_gate", False)
399
+ if self.use_output_gate:
400
+ projection_size = self.num_heads * self.v_head_dim
401
+ self.g_proj = nn.Linear(self.hidden_size, projection_size, bias=False)
402
+
403
+ self.rotary_emb = None
404
+
405
+ def forward(
406
+ self,
407
+ hidden_states: torch.Tensor,
408
+ attention_mask: torch.Tensor | None = None,
409
+ position_ids: torch.LongTensor | None = None,
410
+ past_key_values: Cache | None = None,
411
+ **kwargs,
412
+ ) -> tuple[torch.Tensor, torch.Tensor | None, tuple[torch.Tensor] | None]:
413
+ batch_size, seq_length = hidden_states.shape[:-1]
414
+ query_shape = (batch_size, seq_length, -1, self.q_head_dim)
415
+ key_shape = (batch_size, seq_length, -1,
416
+ self.qk_nope_head_dim + self.v_head_dim)
417
+
418
+ if self.q_lora_rank is not None:
419
+ q_states = self.q_b_proj(self.q_a_layernorm(self.q_a_proj(hidden_states)))
420
+ else:
421
+ q_states = self.q_proj(hidden_states)
422
+ q_states = q_states.view(query_shape).transpose(1, 2)
423
+ q_pass, q_rot = torch.split(
424
+ q_states, [self.qk_nope_head_dim, self.qk_rope_head_dim], dim=-1)
425
+
426
+ compressed_kv = self.kv_a_proj_with_mqa(hidden_states)
427
+ k_pass, k_rot = torch.split(
428
+ compressed_kv, [self.kv_lora_rank, self.qk_rope_head_dim], dim=-1)
429
+
430
+ k_pass = self.kv_b_proj(self.kv_a_layernorm(
431
+ k_pass)).view(key_shape).transpose(1, 2)
432
+ k_pass, value_states = torch.split(
433
+ k_pass, [self.qk_nope_head_dim, self.v_head_dim], dim=-1)
434
+
435
+ k_rot = k_rot.view(batch_size, 1, seq_length, self.qk_rope_head_dim)
436
+
437
+ k_rot = k_rot.expand(*k_pass.shape[:-1], -1)
438
+
439
+ query_states = torch.cat((q_pass, q_rot), dim=-1)
440
+ key_states = torch.cat((k_pass, k_rot), dim=-1)
441
+
442
+ if past_key_values is not None:
443
+ key_states, value_states = past_key_values.update(
444
+ key_states, value_states, self.layer_idx)
445
+
446
+ if self.config._attn_implementation == "flash_attention_2" and self.q_head_dim != self.v_head_dim:
447
+ value_states = F.pad(
448
+ value_states, [0, self.q_head_dim - self.v_head_dim])
449
+
450
+ attention_interface: Callable = eager_attention_forward
451
+ if self.config._attn_implementation != "eager":
452
+ attention_interface = ALL_ATTENTION_FUNCTIONS[self.config._attn_implementation]
453
+
454
+ attn_output, _ = attention_interface(
455
+ self,
456
+ query_states,
457
+ key_states,
458
+ value_states,
459
+ attention_mask,
460
+ dropout=0.0 if not self.training else self.attention_dropout,
461
+ scaling=self.scaling,
462
+ **kwargs,
463
+ )
464
+
465
+ if self.config._attn_implementation == "flash_attention_2" and self.q_head_dim != self.v_head_dim:
466
+ attn_output = attn_output[:, :, :, : self.v_head_dim]
467
+
468
+ attn_output = attn_output.reshape(
469
+ batch_size, seq_length, -1).contiguous()
470
+ if self.use_output_gate:
471
+ g = self.g_proj(hidden_states).sigmoid()
472
+ attn_output = attn_output * g
473
+ attn_output = self.o_proj(attn_output)
474
+ return attn_output
475
+
476
+
477
+ class KimiDeltaAttention(nn.Module):
478
+ def __init__(self, config: KimiLinearConfig, layer_idx: int):
479
+ super().__init__()
480
+ self.config = config
481
+ self.mode = "chunk"
482
+
483
+ self.hidden_size = config.hidden_size
484
+ self.conv_size = config.linear_attn_config["short_conv_kernel_size"]
485
+ self.head_dim = config.linear_attn_config["head_dim"]
486
+ self.num_heads = config.linear_attn_config["num_heads"]
487
+ self.head_k_dim = self.head_dim
488
+ self.num_k_heads = self.num_heads
489
+
490
+ self.layer_idx = layer_idx
491
+
492
+ assert self.mode in [
493
+ 'chunk', 'fused_recurrent'], f"Not supported mode `{self.mode}`."
494
+
495
+ projection_k_size = self.head_k_dim * self.num_k_heads
496
+ projection_size = self.head_dim * self.num_heads
497
+
498
+ self.q_proj = nn.Linear(
499
+ self.hidden_size, projection_k_size, bias=False)
500
+ self.k_proj = nn.Linear(
501
+ self.hidden_size, projection_k_size, bias=False)
502
+ self.v_proj = nn.Linear(self.hidden_size, projection_size, bias=False)
503
+
504
+ self.q_conv1d = ShortConvolution(
505
+ hidden_size=projection_k_size,
506
+ kernel_size=self.conv_size,
507
+ activation='silu',
508
+ )
509
+ self.k_conv1d = ShortConvolution(
510
+ hidden_size=projection_k_size,
511
+ kernel_size=self.conv_size,
512
+ activation='silu',
513
+ )
514
+ self.v_conv1d = ShortConvolution(
515
+ hidden_size=projection_size,
516
+ kernel_size=self.conv_size,
517
+ activation='silu',
518
+ )
519
+
520
+ self.A_log = torch.nn.Parameter(torch.log(torch.empty(
521
+ self.head_dim, dtype=torch.float32).uniform_(1, 16)))
522
+
523
+ self.f_a_proj = nn.Linear(self.hidden_size, self.head_dim, bias=False)
524
+ self.f_b_proj = nn.Linear(self.head_dim, projection_size, bias=False)
525
+
526
+ self.dt_bias = nn.Parameter(
527
+ torch.empty(projection_size, dtype=torch.float32))
528
+
529
+ self.b_proj = nn.Linear(self.hidden_size, self.num_heads, bias=False)
530
+
531
+ self.use_full_rank_gate = config.linear_attn_config.get("use_full_rank_gate", False)
532
+ self.gate_lower_bound = config.linear_attn_config.get("gate_lower_bound", None)
533
+ if self.use_full_rank_gate:
534
+ self.g_proj = nn.Linear(self.hidden_size, projection_size, bias=False)
535
+ else:
536
+ self.g_a_proj = nn.Linear(self.hidden_size, self.head_dim, bias=False)
537
+ self.g_b_proj = nn.Linear(self.head_dim, projection_size, bias=False)
538
+
539
+ self.o_norm = FusedRMSNormGated(
540
+ self.head_dim, eps=config.rms_norm_eps, activation='sigmoid')
541
+ self.o_proj = nn.Linear(projection_size, self.hidden_size, bias=False)
542
+
543
+ def forward(
544
+ self,
545
+ hidden_states: torch.Tensor,
546
+ attention_mask: torch.Tensor | None = None,
547
+ cache_params: KimiDynamicCache | None = None,
548
+ **kwargs: Unpack[dict],
549
+ ) -> tuple[torch.Tensor, torch.Tensor | None, Cache | None]:
550
+ if attention_mask is not None:
551
+ if attention_mask.dim() != 2:
552
+ attention_mask = kwargs.get("padding_mask")
553
+
554
+ if attention_mask is not None and attention_mask.dim() != 2:
555
+ raise ValueError(
556
+ "attention_mask must be a 0-1 matrix of shape [batch_size, seq_len] "
557
+ "(0 = padding). 3D masks are not supported here.",
558
+ )
559
+ use_cache = cache_params is not None
560
+ batch_size, q_len, _ = hidden_states.shape
561
+ mode = 'fused_recurrent' if use_cache and q_len == 1 else self.mode
562
+ if self.training:
563
+ assert mode == 'chunk', "Only chunk mode is supported in training."
564
+
565
+ cu_seqlens = kwargs.get('cu_seqlens')
566
+ indices = None
567
+ if attention_mask is not None:
568
+ indices, cu_seqlens, _ = get_unpad_data(attention_mask[:, -q_len:])
569
+ hidden_states = index_first_axis(
570
+ rearrange(hidden_states, "b s ... -> (b s) ..."), indices).unsqueeze(0)
571
+
572
+ conv_state_q, conv_state_k, conv_state_v = None, None, None
573
+ recurrent_state = None
574
+ if cache_params is not None:
575
+ if cache_params.conv_states[self.layer_idx] is not None:
576
+ conv_state_q, conv_state_k, conv_state_v = cache_params.conv_states[
577
+ self.layer_idx]
578
+ recurrent_state = cache_params.recurrent_states[self.layer_idx]
579
+
580
+ q_proj_states = self.q_proj(hidden_states)
581
+ k_proj_states = self.k_proj(hidden_states)
582
+ v_proj_states = self.v_proj(hidden_states)
583
+ q, conv_state_q = self.q_conv1d(
584
+ x=q_proj_states,
585
+ cache=conv_state_q,
586
+ output_final_state=use_cache,
587
+ cu_seqlens=cu_seqlens,
588
+ )
589
+ k, conv_state_k = self.k_conv1d(
590
+ x=k_proj_states,
591
+ cache=conv_state_k,
592
+ output_final_state=use_cache,
593
+ cu_seqlens=cu_seqlens,
594
+ )
595
+ v, conv_state_v = self.v_conv1d(
596
+ x=v_proj_states,
597
+ cache=conv_state_v,
598
+ output_final_state=use_cache,
599
+ cu_seqlens=cu_seqlens,
600
+ )
601
+ g = self.f_b_proj(self.f_a_proj(hidden_states))
602
+ g = rearrange(g, '... (h d) -> ... h d', d=self.head_dim)
603
+ # The released K3 checkpoint stores per-channel decay shared by all heads.
604
+ g = self.gate_lower_bound * torch.sigmoid(
605
+ self.A_log.float().exp().view(1, 1, 1, self.head_dim)
606
+ * (g.float() + self.dt_bias.float().view(1, 1, self.num_heads, self.head_dim))
607
+ ).to(g.dtype)
608
+ beta = self.b_proj(hidden_states).float()
609
+
610
+ q, k = map(lambda x: rearrange(
611
+ x, '... (h d) -> ... h d', d=self.head_k_dim), (q, k))
612
+ v = rearrange(v, '... (h d) -> ... h d', d=self.head_dim)
613
+
614
+ if mode == 'chunk':
615
+ o, recurrent_state = chunk_kda(
616
+ q=q,
617
+ k=k,
618
+ v=v,
619
+ g=g,
620
+ beta=beta,
621
+ A_log=self.A_log,
622
+ dt_bias=self.dt_bias,
623
+ initial_state=recurrent_state,
624
+ output_final_state=True,
625
+ use_qk_l2norm_in_kernel=True,
626
+ use_gate_in_kernel=False,
627
+ use_beta_sigmoid_in_kernel=True,
628
+ safe_gate=False,
629
+ lower_bound=self.gate_lower_bound,
630
+ transpose_state_layout=True,
631
+ cu_seqlens=cu_seqlens,
632
+ )
633
+ else:
634
+ o, recurrent_state = fused_recurrent_kda(
635
+ q=q,
636
+ k=k,
637
+ v=v,
638
+ g=g,
639
+ beta=beta,
640
+ A_log=self.A_log,
641
+ dt_bias=self.dt_bias,
642
+ initial_state=recurrent_state,
643
+ output_final_state=True,
644
+ use_qk_l2norm_in_kernel=True,
645
+ use_gate_in_kernel=False,
646
+ use_beta_sigmoid_in_kernel=True,
647
+ lower_bound=self.gate_lower_bound,
648
+ transpose_state_layout=True,
649
+ cu_seqlens=cu_seqlens,
650
+ )
651
+ if cache_params is not None:
652
+ cache_params.recurrent_states[self.layer_idx] = recurrent_state
653
+ cache_params.conv_states[self.layer_idx] = (
654
+ conv_state_q, conv_state_k, conv_state_v)
655
+
656
+ if self.use_full_rank_gate:
657
+ g = self.g_proj(hidden_states)
658
+ else:
659
+ g = self.g_b_proj(self.g_a_proj(hidden_states))
660
+ g = rearrange(g, '... (h d) -> ... h d', d=self.head_dim)
661
+ o = self.o_norm(o, g)
662
+
663
+ o = rearrange(o, 'b t h d -> b t (h d)')
664
+ o = self.o_proj(o)
665
+ if attention_mask is not None:
666
+ o = pad_input(o.squeeze(0), indices, batch_size, q_len)
667
+
668
+ return o
669
+
670
+
671
+ class KimiMoEGate(nn.Module):
672
+ """
673
+ MoEGate adapted from Deepseek-V3.
674
+ Parameter correspondences:
675
+ num_experts -> n_routed_experts
676
+ num_experts_per_token -> num_experts_per_tok
677
+ num_expert_group -> n_group
678
+ moe_router_activation_func -> scoring_func
679
+ """
680
+
681
+ def __init__(self, config: KimiLinearConfig):
682
+ super().__init__()
683
+ self.config = config
684
+ self.top_k = config.num_experts_per_token
685
+ self.num_experts = config.num_experts
686
+ self.routed_scaling_factor = config.routed_scaling_factor
687
+ self.moe_router_activation_func = config.moe_router_activation_func
688
+ self.num_expert_group = getattr(config, "num_expert_group", 1)
689
+ self.topk_group = getattr(config, "topk_group", 1)
690
+
691
+ # topk selection algorithm
692
+ self.moe_renormalize = config.moe_renormalize
693
+ self.gating_dim = config.hidden_size
694
+ self.weight = nn.Parameter(
695
+ torch.empty((self.num_experts, self.gating_dim)),
696
+ )
697
+
698
+ self.e_score_correction_bias = nn.Parameter(
699
+ torch.empty(self.num_experts),
700
+ )
701
+ self.reset_parameters()
702
+
703
+ def reset_parameters(self) -> None:
704
+ import torch.nn.init as init
705
+
706
+ init.kaiming_uniform_(self.weight, a=math.sqrt(5))
707
+
708
+ def forward(self, hidden_states):
709
+ bsz, seq_len, h = hidden_states.shape
710
+ # compute gating score
711
+ hidden_states = hidden_states.view(-1, h)
712
+ logits = F.linear(
713
+ hidden_states.type(torch.float32), self.weight.type(
714
+ torch.float32), None,
715
+ )
716
+ if self.moe_router_activation_func == "sigmoid":
717
+ scores = logits.sigmoid()
718
+ elif self.moe_router_activation_func == "softmax":
719
+ scores = logits.softmax(dim=1)
720
+ else:
721
+ raise NotImplementedError(
722
+ f"insupportable scoring function for MoE gating: {self.moe_router_activation_func}",
723
+ )
724
+
725
+ # select top-k experts
726
+ assert not self.training
727
+ scores = scores.view(bsz * seq_len, -1)
728
+ scores_for_choice = scores + self.e_score_correction_bias.unsqueeze(0)
729
+ if self.num_expert_group > 1 and self.num_expert_group > self.topk_group:
730
+ group_scores = (
731
+ scores_for_choice.view(
732
+ bsz * seq_len, self.num_expert_group, -1).topk(2, dim=-1)[0].sum(dim=-1)
733
+ ) # [n, num_expert_group]
734
+ group_idx = torch.topk(
735
+ group_scores, k=self.topk_group, dim=-1, sorted=False,
736
+ )[
737
+ 1
738
+ ] # [n, top_k_group]
739
+ group_mask = torch.zeros_like(group_scores) # [n, num_expert_group]
740
+ group_mask.scatter_(1, group_idx, 1) # [n, num_expert_group]
741
+ score_mask = (
742
+ group_mask.unsqueeze(-1)
743
+ .expand(
744
+ bsz * seq_len, self.num_expert_group, self.num_experts // self.num_expert_group,
745
+ )
746
+ .reshape(bsz * seq_len, -1)
747
+ ) # [n, e]
748
+ tmp_scores = scores_for_choice.masked_fill(
749
+ ~score_mask.bool(), float("-inf")) # [n, e]
750
+ else:
751
+ tmp_scores = scores_for_choice
752
+ _, topk_idx = torch.topk(
753
+ tmp_scores, k=self.top_k, dim=-1, sorted=False,
754
+ )
755
+ topk_weight = scores.gather(1, topk_idx)
756
+
757
+ # norm gate to sum 1
758
+ if self.top_k > 1 and self.moe_renormalize:
759
+ denominator = topk_weight.sum(dim=-1, keepdim=True) + 1e-20
760
+ topk_weight = topk_weight / denominator
761
+ # must multiply the scaling factor
762
+ topk_weight = topk_weight * self.routed_scaling_factor
763
+
764
+ return topk_idx, topk_weight
765
+
766
+
767
+ class KimiSparseMoeBlock(nn.Module):
768
+ """
769
+ Adapted from Deepseek-V3's MOE implementation
770
+ The namings are consistent with Kimi's version.
771
+ """
772
+
773
+ def __init__(self, config: KimiLinearConfig):
774
+ super().__init__()
775
+ self.config = config
776
+ self.hidden_dim = config.hidden_size
777
+ self.num_experts = config.num_experts
778
+ self.top_k = config.num_experts_per_token
779
+ self.moe_renormalize = config.moe_renormalize
780
+
781
+ self.use_latent_moe = getattr(config, "routed_expert_hidden_size", None) is not None
782
+ self.moe_hidden_size = (
783
+ config.routed_expert_hidden_size
784
+ if self.use_latent_moe else config.hidden_size
785
+ )
786
+ self.latent_moe_use_norm = getattr(config, "latent_moe_use_norm", False)
787
+
788
+ self.ep_size = 1
789
+ self.experts_per_rank = config.num_experts
790
+ self.ep_rank = 0
791
+ self.experts = nn.ModuleList(
792
+ [
793
+ KimiBlockSparseMLP(
794
+ config,
795
+ hidden_size=self.moe_hidden_size,
796
+ intermediate_size=config.moe_intermediate_size,
797
+ )
798
+ for _ in range(config.num_experts)
799
+ ],
800
+ )
801
+ self.gate = KimiMoEGate(config)
802
+ if config.num_shared_experts is not None:
803
+ intermediate_size = config.moe_intermediate_size * config.num_shared_experts
804
+ self.shared_experts = KimiMLP(
805
+ config=config, intermediate_size=intermediate_size,
806
+ )
807
+
808
+ if self.use_latent_moe:
809
+ self.routed_expert_down_proj = nn.Linear(
810
+ config.hidden_size, self.moe_hidden_size, bias=False,
811
+ )
812
+ self.routed_expert_up_proj = nn.Linear(
813
+ self.moe_hidden_size, config.hidden_size, bias=False,
814
+ )
815
+ if self.latent_moe_use_norm:
816
+ self.routed_expert_norm = KimiRMSNorm(
817
+ self.moe_hidden_size, eps=config.rms_norm_eps,
818
+ )
819
+
820
+ def forward(self, hidden_states):
821
+ identity = hidden_states
822
+ orig_shape = hidden_states.shape
823
+ topk_idx, topk_weight = self.gate(hidden_states)
824
+ hidden_states = hidden_states.view(-1, hidden_states.shape[-1])
825
+
826
+ if self.use_latent_moe:
827
+ hidden_states = self.routed_expert_down_proj(hidden_states)
828
+
829
+ if not self.training:
830
+ y = self.moe_infer(hidden_states, topk_idx, topk_weight)
831
+ else:
832
+ raise NotImplementedError("Training mode is not supported in KimiSparseMoeBlock")
833
+
834
+ if self.use_latent_moe:
835
+ if self.latent_moe_use_norm:
836
+ y = self.routed_expert_norm(y)
837
+ y = self.routed_expert_up_proj(y)
838
+
839
+ y = y.view(*orig_shape)
840
+
841
+ if self.config.num_shared_experts is not None:
842
+ y = y + self.shared_experts(identity)
843
+ return y
844
+
845
+ @torch.no_grad()
846
+ def moe_infer(self, x, topk_ids, topk_weight):
847
+ cnts = topk_ids.new_zeros((topk_ids.shape[0], len(self.experts)))
848
+ cnts.scatter_(1, topk_ids, 1)
849
+ tokens_per_expert = cnts.sum(dim=0)
850
+ idxs = topk_ids.view(-1).argsort()
851
+ sorted_tokens = x[idxs // topk_ids.shape[1]]
852
+
853
+ tokens_per_expert = tokens_per_expert.cpu().numpy()
854
+
855
+ outputs = []
856
+ start_idx = 0
857
+ for i, num_tokens in enumerate(tokens_per_expert):
858
+ end_idx = start_idx + num_tokens
859
+ if num_tokens == 0:
860
+ continue
861
+ expert = self.experts[i + self.ep_rank * self.experts_per_rank]
862
+ tokens_for_this_expert = sorted_tokens[start_idx:end_idx]
863
+ expert_out = expert(tokens_for_this_expert)
864
+ outputs.append(expert_out)
865
+ start_idx = end_idx
866
+
867
+ outs = torch.cat(outputs, dim=0) if len(
868
+ outputs) else sorted_tokens.new_empty(0)
869
+
870
+ new_x = torch.empty_like(outs)
871
+ new_x[idxs] = outs
872
+ final_out = (
873
+ new_x.view(*topk_ids.shape, -1)
874
+ .type(topk_weight.dtype)
875
+ .mul_(topk_weight.unsqueeze(dim=-1))
876
+ .sum(dim=1)
877
+ .type(new_x.dtype)
878
+ )
879
+ return final_out
880
+
881
+
882
+ class KimiDecoderLayer(nn.Module):
883
+ def __init__(self, config: KimiLinearConfig, layer_idx: int):
884
+ super().__init__()
885
+ self.hidden_size = config.hidden_size
886
+ self.config = config
887
+ self.layer_idx = layer_idx
888
+ if config.is_kda_layer(layer_idx):
889
+ self.is_linear_attn = True
890
+ self.self_attn = KimiDeltaAttention(
891
+ config=config, layer_idx=layer_idx)
892
+ elif config.is_mla:
893
+ self.is_linear_attn = False
894
+ self.self_attn = KimiMLAAttention(
895
+ config=config, layer_idx=layer_idx)
896
+ else:
897
+ raise NotImplementedError
898
+ if (
899
+ config.num_experts is not None
900
+ and layer_idx >= config.first_k_dense_replace
901
+ and layer_idx % getattr(config, "moe_layer_freq", 1) == 0
902
+ ):
903
+ self.block_sparse_moe = KimiSparseMoeBlock(config)
904
+ else:
905
+ self.mlp = KimiMLP(config)
906
+ self.input_layernorm = KimiRMSNorm(
907
+ config.hidden_size, eps=config.rms_norm_eps)
908
+ self.post_attention_layernorm = KimiRMSNorm(
909
+ config.hidden_size, eps=config.rms_norm_eps)
910
+
911
+ # Attention residual
912
+ self.use_attn_residuals = getattr(config, "attn_res_block_size", None) is not None
913
+ if self.use_attn_residuals:
914
+ self.attn_res_block_size = config.attn_res_block_size
915
+ self.self_attention_res_norm = KimiRMSNorm(
916
+ config.hidden_size, eps=config.rms_norm_eps)
917
+ self.mlp_res_norm = KimiRMSNorm(
918
+ config.hidden_size, eps=config.rms_norm_eps)
919
+ self.self_attention_res_proj = nn.Linear(
920
+ config.hidden_size, 1, bias=False)
921
+ self.mlp_res_proj = nn.Linear(
922
+ config.hidden_size, 1, bias=False)
923
+
924
+ def forward(
925
+ self,
926
+ hidden_states: torch.Tensor,
927
+ attention_mask: torch.Tensor | None = None,
928
+ position_ids: torch.LongTensor | None = None,
929
+ past_key_values: tuple[torch.Tensor] | None = None,
930
+ output_attentions: bool | None = False,
931
+ use_cache: bool | None = False,
932
+ block_residual: torch.Tensor | None = None,
933
+ **kwargs: Unpack[FlashAttentionKwargs],
934
+ ):
935
+ if self.use_attn_residuals:
936
+ return self._forward_attn_residual(
937
+ hidden_states, attention_mask, position_ids,
938
+ past_key_values, output_attentions, use_cache,
939
+ block_residual, **kwargs)
940
+
941
+ residual = hidden_states
942
+
943
+ hidden_states = self.input_layernorm(hidden_states)
944
+
945
+ # Self Attention
946
+ if self.is_linear_attn is False:
947
+ hidden_states = self.self_attn(
948
+ hidden_states=hidden_states,
949
+ attention_mask=attention_mask,
950
+ position_ids=position_ids,
951
+ past_key_values=past_key_values,
952
+ output_attentions=output_attentions,
953
+ use_cache=use_cache,
954
+ **kwargs,
955
+ )
956
+ else:
957
+ hidden_states = self.self_attn(
958
+ hidden_states=hidden_states,
959
+ attention_mask=attention_mask,
960
+ cache_params=past_key_values,
961
+ output_attentions=output_attentions,
962
+ use_cache=use_cache,
963
+ **kwargs,
964
+ )
965
+ hidden_states = residual + hidden_states
966
+
967
+ # Fully Connected
968
+ residual = hidden_states
969
+ hidden_states = self.post_attention_layernorm(hidden_states)
970
+ if hasattr(self, "block_sparse_moe"):
971
+ hidden_states = self.block_sparse_moe(hidden_states)
972
+ else:
973
+ hidden_states = self.mlp(hidden_states)
974
+ hidden_states = residual + hidden_states
975
+
976
+ return hidden_states
977
+
978
+ def _forward_attn_residual(
979
+ self,
980
+ hidden_states: torch.Tensor,
981
+ attention_mask: torch.Tensor | None = None,
982
+ position_ids: torch.LongTensor | None = None,
983
+ past_key_values: tuple[torch.Tensor] | None = None,
984
+ output_attentions: bool | None = False,
985
+ use_cache: bool | None = False,
986
+ block_residual: torch.Tensor | None = None,
987
+ **kwargs: Unpack[FlashAttentionKwargs],
988
+ ):
989
+ batch_size, seq_len, hidden_size = hidden_states.shape
990
+ prefix_sum = hidden_states
991
+
992
+ if block_residual is not None and block_residual.shape[1] > 0:
993
+ hidden_states = _apply_attn_res(
994
+ prefix_sum.view(-1, hidden_size),
995
+ block_residual,
996
+ self.self_attention_res_proj,
997
+ self.self_attention_res_norm,
998
+ ).view(batch_size, seq_len, hidden_size)
999
+
1000
+ if self.layer_idx % self.attn_res_block_size == 0:
1001
+ block_residual = torch.cat(
1002
+ [block_residual, prefix_sum.view(-1, hidden_size).unsqueeze(1)], dim=1)
1003
+ prefix_sum = None
1004
+
1005
+ hidden_states = self.input_layernorm(hidden_states)
1006
+
1007
+ # Self Attention
1008
+ if self.is_linear_attn is False:
1009
+ hidden_states = self.self_attn(
1010
+ hidden_states=hidden_states,
1011
+ attention_mask=attention_mask,
1012
+ position_ids=position_ids,
1013
+ past_key_values=past_key_values,
1014
+ output_attentions=output_attentions,
1015
+ use_cache=use_cache,
1016
+ **kwargs,
1017
+ )
1018
+ else:
1019
+ hidden_states = self.self_attn(
1020
+ hidden_states=hidden_states,
1021
+ attention_mask=attention_mask,
1022
+ cache_params=past_key_values,
1023
+ output_attentions=output_attentions,
1024
+ use_cache=use_cache,
1025
+ **kwargs,
1026
+ )
1027
+
1028
+ if prefix_sum is not None:
1029
+ prefix_sum = prefix_sum + hidden_states
1030
+ else:
1031
+ prefix_sum = hidden_states
1032
+
1033
+ hidden_states = _apply_attn_res(
1034
+ prefix_sum.view(-1, hidden_size),
1035
+ block_residual,
1036
+ self.mlp_res_proj,
1037
+ self.mlp_res_norm,
1038
+ ).view(batch_size, seq_len, hidden_size)
1039
+
1040
+ hidden_states = self.post_attention_layernorm(hidden_states)
1041
+ if hasattr(self, "block_sparse_moe"):
1042
+ hidden_states = self.block_sparse_moe(hidden_states)
1043
+ else:
1044
+ hidden_states = self.mlp(hidden_states)
1045
+
1046
+ if prefix_sum is None:
1047
+ prefix_sum = hidden_states
1048
+ else:
1049
+ prefix_sum = prefix_sum + hidden_states
1050
+
1051
+ return prefix_sum, block_residual
1052
+
1053
+
1054
+ class KimiPreTrainedModel(PreTrainedModel):
1055
+ config_class = KimiLinearConfig
1056
+ base_model_prefix = "model"
1057
+ supports_gradient_checkpointing = True
1058
+ _no_split_modules = ["KimiDecoderLayer"]
1059
+ _skip_keys_device_placement = "past_key_values"
1060
+ _supports_flash_attn_2 = True
1061
+ _can_record_outputs = {
1062
+ "router_logits": OutputRecorder(KimiBlockSparseMLP, index=1),
1063
+ "hidden_states": KimiDecoderLayer,
1064
+ "attentions": KimiMLAAttention,
1065
+ }
1066
+ _is_stateful = True
1067
+
1068
+ def _init_weights(self, module):
1069
+ std = self.config.initializer_range
1070
+ if isinstance(module, nn.Linear):
1071
+ module.weight.data.normal_(mean=0.0, std=std)
1072
+ if module.bias is not None:
1073
+ module.bias.data.zero_()
1074
+ elif isinstance(module, nn.Embedding):
1075
+ module.weight.data.normal_(mean=0.0, std=std)
1076
+ if module.padding_idx is not None:
1077
+ module.weight.data[module.padding_idx].zero_()
1078
+
1079
+
1080
+ def _apply_attn_res(prefix_sum, block_residual, proj, norm):
1081
+ """
1082
+ prefix_sum: (num_tokens, hidden_size)
1083
+ block_residual: (num_tokens, num_blocks, hidden_size)
1084
+ """
1085
+ v = torch.cat((block_residual, prefix_sum.unsqueeze(1)), dim=1)
1086
+ v_float = v.float()
1087
+ variance = v_float.pow(2).mean(-1, keepdim=True)
1088
+ k = v_float * torch.rsqrt(variance + norm.variance_epsilon)
1089
+ score_weight = norm.weight.float() * proj.weight.squeeze(0).float()
1090
+ scores = (k * score_weight).sum(-1)
1091
+ probs = scores.softmax(-1).unsqueeze(1)
1092
+ hidden_states = torch.matmul(probs, v_float).squeeze(1)
1093
+ return hidden_states.to(v.dtype)
1094
+
1095
+ class KimiLinearModel(KimiPreTrainedModel):
1096
+ def __init__(self, config: KimiLinearConfig):
1097
+ super().__init__(config)
1098
+ self.padding_idx = config.pad_token_id
1099
+ self.vocab_size = config.vocab_size
1100
+
1101
+ self.embed_tokens = nn.Embedding(
1102
+ config.vocab_size, config.hidden_size, self.padding_idx)
1103
+ self.layers = nn.ModuleList([KimiDecoderLayer(
1104
+ config, layer_idx) for layer_idx in range(config.num_hidden_layers)])
1105
+ self.norm = KimiRMSNorm(
1106
+ config.hidden_size, eps=config.rms_norm_eps)
1107
+
1108
+ self.use_attn_residuals = getattr(config, "attn_res_block_size", None) is not None
1109
+ if self.use_attn_residuals:
1110
+ self.output_attn_res_norm = KimiRMSNorm(
1111
+ config.hidden_size, eps=config.rms_norm_eps)
1112
+ self.output_attn_res_proj = nn.Linear(
1113
+ config.hidden_size, 1, bias=False)
1114
+
1115
+ config._attn_implementation = getattr(config, "_attn_implementation", "eager")
1116
+
1117
+ self._use_flash_attention_2 = config._attn_implementation == "flash_attention_2"
1118
+ self.gradient_checkpointing = False
1119
+ # Initialize weights and apply final processing
1120
+ self.post_init()
1121
+
1122
+ def _update_linear_attn_mask(self, attention_mask, cache_position):
1123
+ """
1124
+ NOTE: Left-padding is used for linear attention mask.
1125
+ No need for zeroing states when
1126
+ 1. Cached forward
1127
+ 2. Attending to all inputs
1128
+ """
1129
+ linear_attn_mask = attention_mask
1130
+ if cache_position[0] > 0 or (attention_mask is not None and torch.all(attention_mask == 1)):
1131
+ linear_attn_mask = None
1132
+ return linear_attn_mask
1133
+
1134
+ @check_model_inputs
1135
+ # @auto_docstring
1136
+ def forward(
1137
+ self,
1138
+ input_ids: torch.LongTensor = None,
1139
+ attention_mask: torch.Tensor | None = None,
1140
+ position_ids: torch.LongTensor | None = None,
1141
+ past_key_values: Cache | None = None,
1142
+ inputs_embeds: torch.FloatTensor | None = None,
1143
+ cache_position: torch.LongTensor | None = None,
1144
+ use_cache: bool | None = None,
1145
+ **kwargs: Unpack[TransformersKwargs],
1146
+ ) -> tuple | BaseModelOutputWithPast:
1147
+
1148
+ use_cache = use_cache if use_cache is not None else self.config.use_cache
1149
+
1150
+ if (input_ids is None) and (inputs_embeds is None):
1151
+ raise ValueError(
1152
+ "You must specify exactly one of input_ids or inputs_embeds")
1153
+
1154
+ # Get inputs_embeds
1155
+ if inputs_embeds is None:
1156
+ inputs_embeds = self.embed_tokens(input_ids)
1157
+
1158
+ if use_cache and past_key_values is None:
1159
+ past_key_values = KimiDynamicCache(config=self.config)
1160
+
1161
+ if cache_position is None:
1162
+ past_seen_tokens = past_key_values.get_seq_length(
1163
+ ) if past_key_values is not None else 0
1164
+ cache_position: torch.Tensor = torch.arange(
1165
+ past_seen_tokens, past_seen_tokens + inputs_embeds.shape[1], device=inputs_embeds.device,
1166
+ )
1167
+
1168
+ if position_ids is None:
1169
+ position_ids = cache_position.unsqueeze(0)
1170
+
1171
+ if version.parse(transformers.__version__) >= version.parse("5.0.0"):
1172
+ causal_mask = create_causal_mask(
1173
+ config=self.config,
1174
+ inputs_embeds=inputs_embeds,
1175
+ attention_mask=attention_mask,
1176
+ past_key_values=past_key_values,
1177
+ position_ids=position_ids,
1178
+ )
1179
+ else:
1180
+ causal_mask = create_causal_mask(
1181
+ config=self.config,
1182
+ input_embeds=inputs_embeds,
1183
+ attention_mask=attention_mask,
1184
+ cache_position=cache_position,
1185
+ past_key_values=past_key_values,
1186
+ position_ids=position_ids,
1187
+ )
1188
+ linear_attn_mask = self._update_linear_attn_mask(
1189
+ attention_mask, cache_position)
1190
+
1191
+ hidden_states = inputs_embeds
1192
+ if past_key_values is not None:
1193
+ assert isinstance(past_key_values, KimiDynamicCache)
1194
+
1195
+ block_residual = None
1196
+ if self.use_attn_residuals:
1197
+ block_residual = hidden_states.new_zeros(
1198
+ hidden_states.shape[0] * hidden_states.shape[1], 0,
1199
+ hidden_states.shape[2])
1200
+
1201
+ for decoder_layer in self.layers:
1202
+ layer_mask = linear_attn_mask if decoder_layer.is_linear_attn else causal_mask
1203
+
1204
+ if self.use_attn_residuals:
1205
+ hidden_states, block_residual = decoder_layer(
1206
+ hidden_states,
1207
+ attention_mask=layer_mask,
1208
+ past_key_values=past_key_values,
1209
+ cache_position=cache_position,
1210
+ block_residual=block_residual,
1211
+ **kwargs,
1212
+ )
1213
+ else:
1214
+ hidden_states = decoder_layer(
1215
+ hidden_states,
1216
+ attention_mask=layer_mask,
1217
+ past_key_values=past_key_values,
1218
+ cache_position=cache_position,
1219
+ **kwargs,
1220
+ )
1221
+
1222
+ if self.use_attn_residuals:
1223
+ hidden_states = self._apply_output_attn_res(
1224
+ hidden_states, block_residual)
1225
+
1226
+ hidden_states = self.norm(hidden_states)
1227
+
1228
+ return BaseModelOutputWithPast(
1229
+ last_hidden_state=hidden_states,
1230
+ past_key_values=past_key_values,
1231
+ )
1232
+
1233
+ def _apply_output_attn_res(self, hidden_states, block_residual):
1234
+ batch_size, seq_len, hidden_size = hidden_states.shape
1235
+ return _apply_attn_res(
1236
+ hidden_states.view(-1, hidden_size),
1237
+ block_residual,
1238
+ self.output_attn_res_proj,
1239
+ self.output_attn_res_norm,
1240
+ ).view(batch_size, seq_len, hidden_size)
1241
+
1242
+
1243
+ class KimiLinearForCausalLM(KimiPreTrainedModel, GenerationMixin):
1244
+ @classmethod
1245
+ def _supports_default_dynamic_cache(cls) -> bool:
1246
+ return False
1247
+
1248
+ _tied_weights_keys = (
1249
+ {"lm_head.weight": "model.embed_tokens.weight"}
1250
+ if version.parse(transformers.__version__) >= version.parse("5.0.0")
1251
+ else ["lm_head.weight"]
1252
+ )
1253
+
1254
+ def __init__(self, config):
1255
+ super().__init__(config)
1256
+ self.model = KimiLinearModel(config)
1257
+ self.vocab_size = config.vocab_size
1258
+ self.lm_head = nn.Linear(
1259
+ config.hidden_size, config.vocab_size, bias=False)
1260
+
1261
+ # Initialize weights and apply final processing
1262
+ self.post_init()
1263
+
1264
+ @can_return_tuple
1265
+ # @auto_docstring
1266
+ def forward(
1267
+ self,
1268
+ input_ids: torch.LongTensor = None,
1269
+ attention_mask: torch.Tensor | None = None,
1270
+ position_ids: torch.LongTensor | None = None,
1271
+ past_key_values: list[torch.FloatTensor] | None = None,
1272
+ inputs_embeds: torch.FloatTensor | None = None,
1273
+ labels: torch.LongTensor | None = None,
1274
+ use_cache: bool | None = None,
1275
+ output_attentions: bool | None = None,
1276
+ output_hidden_states: bool | None = None,
1277
+ generation_mode: bool | None = None,
1278
+ return_dict: bool | None = None,
1279
+ cache_position: torch.LongTensor | None = None,
1280
+ **kwargs: Unpack[TransformersKwargs],
1281
+ ) -> tuple | CausalLMOutputWithPast:
1282
+ r"""
1283
+ Args:
1284
+ labels (`torch.LongTensor` of shape `(batch_size, sequence_length)`, *optional*):
1285
+ Labels for computing the masked language modeling loss. Indices should either be in `[0, ...,
1286
+ config.vocab_size]` or -100 (see `input_ids` docstring). Tokens with indices set to `-100` are ignored
1287
+ (masked), the loss is only computed for the tokens with labels in `[0, ..., config.vocab_size]`.
1288
+ """
1289
+
1290
+ output_attentions = output_attentions if output_attentions is not None else self.config.output_attentions
1291
+ output_hidden_states = (
1292
+ output_hidden_states if output_hidden_states is not None else self.config.output_hidden_states
1293
+ )
1294
+ return_dict = return_dict if return_dict is not None else self.config.use_return_dict
1295
+
1296
+ outputs = self.model(
1297
+ input_ids=input_ids,
1298
+ attention_mask=attention_mask,
1299
+ position_ids=position_ids,
1300
+ past_key_values=past_key_values,
1301
+ inputs_embeds=inputs_embeds,
1302
+ use_cache=use_cache,
1303
+ output_attentions=output_attentions,
1304
+ output_hidden_states=output_hidden_states,
1305
+ return_dict=return_dict,
1306
+ cache_position=cache_position,
1307
+ )
1308
+
1309
+ logits = outputs[0]
1310
+ if generation_mode:
1311
+ logits = logits[:, -1:]
1312
+ logits = self.lm_head(logits)
1313
+
1314
+ loss = None
1315
+ if labels is not None:
1316
+ loss = self.loss_function(
1317
+ logits, labels, self.vocab_size, **kwargs)
1318
+
1319
+ return CausalLMOutputWithPast(
1320
+ loss=loss,
1321
+ logits=logits,
1322
+ past_key_values=outputs.past_key_values,
1323
+ hidden_states=outputs.hidden_states,
1324
+ attentions=outputs.attentions,
1325
+ )
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,412 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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,
155
+ text: str,
156
+ allow_special_tokens: bool = True) -> List[int]:
157
+ # The tiktoken tokenizer can handle <=400k chars without
158
+ # pyo3_runtime.PanicException.
159
+ TIKTOKEN_MAX_ENCODE_CHARS = 400_000
160
+
161
+ # https://github.com/openai/tiktoken/issues/195
162
+ # Here we iterate over subsequences and split if we exceed the limit
163
+ # of max consecutive non-whitespace or whitespace characters.
164
+ MAX_NO_WHITESPACES_CHARS = 25_000
165
+
166
+ t: List[int] = []
167
+ for i in range(0, len(text), TIKTOKEN_MAX_ENCODE_CHARS):
168
+ for substr in self._split_whitespaces_or_nonwhitespaces(
169
+ text[i:i + TIKTOKEN_MAX_ENCODE_CHARS],
170
+ MAX_NO_WHITESPACES_CHARS,
171
+ ):
172
+ if allow_special_tokens:
173
+ t.extend(
174
+ # structural markers: encode <|...|> as their special token IDs
175
+ self.model.encode(
176
+ substr,
177
+ allowed_special="all",
178
+ ))
179
+ else:
180
+ t.extend(
181
+ # user/tool text: encode any <|...|> as ordinary BPE tokens (never as control tokens)
182
+ self.model.encode(
183
+ substr,
184
+ disallowed_special=(),
185
+ ))
186
+
187
+ return t
188
+
189
+ def encode(self,
190
+ text: str,
191
+ allow_special_tokens: bool = True,
192
+ **kwargs) -> List[int]:
193
+ """
194
+ Encodes a string into a list of token IDs.
195
+
196
+ Args:
197
+ text (str): The input string to be encoded.
198
+
199
+ Returns:
200
+ list[int]: A list of token IDs.
201
+ """
202
+ # If there are other args, we should call super().encode because there are a lot of code
203
+ # to handle those args. supper().encode finally will call _tokenize and _convert_token_to_id.
204
+ # NOTE: our encode method is not compatible with the super().encode method,
205
+ # e.g. split_special_tokens' default is True in our encode method.
206
+ if len(kwargs) > 0:
207
+ logger.warning(f"Calling super().encode with {kwargs}")
208
+ return super().encode(text, **kwargs)
209
+
210
+ assert type(text) is str
211
+ return self._encode_text_piece(
212
+ text, allow_special_tokens=allow_special_tokens)
213
+
214
+ def decode(self, token_ids: Union[int, List[int]], **kwargs) -> str:
215
+ """
216
+ Decodes a list of token IDs into a string.
217
+
218
+ Args:
219
+ token_ids (List[int]): The list of token IDs to be decoded.
220
+
221
+ Returns:
222
+ str: The decoded string.
223
+ """
224
+ # If there are other args, we should call super().decode because there are a lot of code
225
+ # to handle those args. supper().encode finally will call convert_tokens_to_string and _convert_id_to_token.
226
+ if len(kwargs) > 0:
227
+ return super().decode(token_ids, **kwargs)
228
+
229
+ if type(token_ids) is int:
230
+ token_ids = [token_ids]
231
+
232
+ return self.model.decode(cast(List[int], token_ids))
233
+
234
+ @staticmethod
235
+ def _split_whitespaces_or_nonwhitespaces(
236
+ s: str, max_consecutive_slice_len: int) -> Iterator[str]:
237
+ """
238
+ Splits the string `s` so that each substring contains no more than `max_consecutive_slice_len`
239
+ consecutive whitespaces or consecutive non-whitespaces.
240
+ """
241
+ current_slice_len = 0
242
+ current_slice_is_space = s[0].isspace() if len(s) > 0 else False
243
+ slice_start = 0
244
+
245
+ for i in range(len(s)):
246
+ is_now_space = s[i].isspace()
247
+
248
+ if current_slice_is_space ^ is_now_space:
249
+ current_slice_len = 1
250
+ current_slice_is_space = is_now_space
251
+ else:
252
+ current_slice_len += 1
253
+ if current_slice_len > max_consecutive_slice_len:
254
+ yield s[slice_start:i]
255
+ slice_start = i
256
+ current_slice_len = 1
257
+ yield s[slice_start:]
258
+
259
+ def _encode_chat_segments(self, segments) -> List[int]:
260
+ token_ids: List[int] = []
261
+ for segment in segments:
262
+ token_ids.extend(
263
+ self._encode_text_piece(
264
+ segment.text,
265
+ allow_special_tokens=segment.allow_special,
266
+ ))
267
+ return token_ids
268
+
269
+ @staticmethod
270
+ def _truncate(ids: List[int],
271
+ truncation: bool = False,
272
+ max_length: Optional[int] = None) -> List[int]:
273
+ if truncation and max_length is not None:
274
+ return ids[:max_length]
275
+ return ids
276
+
277
+ def _format_chat_token_output(self,
278
+ encoded_inputs: List[List[int]],
279
+ *,
280
+ is_batched: bool,
281
+ padding=False,
282
+ truncation: bool = False,
283
+ max_length: Optional[int] = None,
284
+ return_tensors=None,
285
+ return_dict: bool = False):
286
+ encoded_inputs = [
287
+ self._truncate(ids, truncation=truncation, max_length=max_length)
288
+ for ids in encoded_inputs
289
+ ]
290
+
291
+ needs_batch_encoding = (is_batched or padding
292
+ or return_tensors is not None or return_dict)
293
+ if not needs_batch_encoding:
294
+ return encoded_inputs[0]
295
+
296
+ features = [{
297
+ "input_ids": ids,
298
+ "attention_mask": [1] * len(ids)
299
+ } for ids in encoded_inputs]
300
+ batch = self.pad(features,
301
+ padding=padding,
302
+ max_length=max_length if padding else None,
303
+ return_attention_mask=True,
304
+ return_tensors=return_tensors)
305
+
306
+ if return_dict:
307
+ return batch
308
+ if is_batched:
309
+ return batch["input_ids"]
310
+ return batch["input_ids"][0] if return_tensors is None else batch[
311
+ "input_ids"]
312
+
313
+ """ ----- Below are the abstract methods required by PreTrainedTokenizer ----- """
314
+
315
+ @property
316
+ def vocab_size(self) -> int:
317
+ return self.n_words
318
+
319
+ def get_vocab(self) -> Dict[str, int]:
320
+ return self.encoder
321
+
322
+ def _tokenize(self, text: str, **kwargs) -> List[str]:
323
+ return [self.decoder[t] for t in self.encode(text)]
324
+
325
+ def _convert_token_to_id(self, token: str) -> int:
326
+ return self.encoder.get(token, self.unk_id)
327
+
328
+ def _convert_id_to_token(self, index: int) -> str:
329
+ return self.decoder.get(index)
330
+
331
+ @staticmethod
332
+ def clean_up_tokenization(out_string: str) -> str:
333
+ return out_string
334
+
335
+ def convert_tokens_to_string(self, tokens: List[str]) -> str:
336
+ text = ''.join(tokens)
337
+ text = bytearray([self.byte_decoder[c]
338
+ for c in text]).decode('utf-8', 'replace')
339
+ return text
340
+
341
+ def save_vocabulary(self,
342
+ save_directory: str,
343
+ filename_prefix: Optional[str] = None) -> Tuple[str]:
344
+ if not os.path.isdir(save_directory):
345
+ raise ValueError(
346
+ f"vocabulary path ({save_directory}) should be a directory")
347
+ out_vocab_file = os.path.join(
348
+ save_directory,
349
+ (filename_prefix + "-" if filename_prefix else "") +
350
+ VOCAB_FILES_NAMES["vocab_file"])
351
+
352
+ if os.path.abspath(self.vocab_file) != os.path.abspath(
353
+ out_vocab_file) and os.path.isfile(self.vocab_file):
354
+ copyfile(self.vocab_file, out_vocab_file)
355
+
356
+ return (out_vocab_file, )
357
+
358
+ def apply_chat_template(self,
359
+ conversation,
360
+ tools: Optional[list[dict]] = None,
361
+ tokenize: bool = False,
362
+ add_generation_prompt: bool = True,
363
+ thinking: bool = True,
364
+ padding=False,
365
+ truncation: bool = False,
366
+ max_length: Optional[int] = None,
367
+ return_tensors=None,
368
+ return_dict: bool = False,
369
+ **kwargs):
370
+ # Tokenizer-level rendering reorders tool result messages to match
371
+ # assistant tool_calls, normalizes per-call arguments and response
372
+ # schema, then encodes the resulting XTML structure segment-by-segment.
373
+ is_batched = is_batched_conversation(conversation)
374
+ conversations = conversation if is_batched else [conversation]
375
+ image_prompts = kwargs.pop("image_prompts", None)
376
+ if is_batched and image_prompts is not None:
377
+ raise ValueError("image_prompts is only supported for one chat.")
378
+
379
+ # by default set thinking effort to max
380
+ kwargs.setdefault("thinking_effort", "max")
381
+
382
+ segment_batches = [
383
+ build_chat_segments(
384
+ messages,
385
+ tools=tools,
386
+ add_generation_prompt=add_generation_prompt,
387
+ thinking=thinking,
388
+ image_prompts=image_prompts,
389
+ **kwargs,
390
+ ) for messages in conversations
391
+ ]
392
+
393
+ if not tokenize:
394
+ rendered = [
395
+ "".join(segment.text for segment in segments)
396
+ for segments in segment_batches
397
+ ]
398
+ return rendered if is_batched else rendered[0]
399
+
400
+ encoded_inputs = [
401
+ self._encode_chat_segments(segments)
402
+ for segments in segment_batches
403
+ ]
404
+ return self._format_chat_token_output(
405
+ encoded_inputs,
406
+ is_batched=is_batched,
407
+ padding=padding,
408
+ truncation=truncation,
409
+ max_length=max_length,
410
+ return_tensors=return_tensors,
411
+ return_dict=return_dict,
412
+ )
tokenizer_config.json ADDED
@@ -0,0 +1,156 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ "model_max_length": 1000000000000000019884624838656,
147
+ "pad_token": "[PAD]",
148
+ "unk_token": "[UNK]",
149
+ "tokenizer_class": "TikTokenTokenizer",
150
+ "auto_map": {
151
+ "AutoTokenizer": [
152
+ "tokenization_kimi.TikTokenTokenizer",
153
+ null
154
+ ]
155
+ }
156
+ }