timorobrecht commited on
Commit
705de19
·
verified ·
1 Parent(s): 28bedb2

Upload folder using huggingface_hub

Browse files
README.md CHANGED
@@ -49,6 +49,9 @@ Configure external evaluators with:
49
  The tokenizer accepts raw text through standard calls such as
50
  `tokenizer(text)`, `tokenizer(text, add_special_tokens=False)`, and
51
  `tokenizer(texts, padding=True, truncation=True, return_tensors="pt")`.
 
 
 
52
 
53
  This export sets `patch_pathlib_utf8_open=true` in `config.json`. When loaded
54
  with `trust_remote_code=True`, the config installs a narrow Windows
 
49
  The tokenizer accepts raw text through standard calls such as
50
  `tokenizer(text)`, `tokenizer(text, add_special_tokens=False)`, and
51
  `tokenizer(texts, padding=True, truncation=True, return_tensors="pt")`.
52
+ It also accepts `return_offsets_mapping=True` for compatibility with
53
+ completion-ranking evaluators that need suffix masks. The model supports
54
+ `output_hidden_states=True` for representation extraction tasks.
55
 
56
  This export sets `patch_pathlib_utf8_open=true` in `config.json`. When loaded
57
  with `trust_remote_code=True`, the config installs a narrow Windows
__pycache__/modeling_pinyin_code.cpython-312.pyc ADDED
Binary file (15.4 kB). View file
 
__pycache__/tokenization_pinyin_code.cpython-312.pyc ADDED
Binary file (28.4 kB). View file
 
hf/modeling_pinyin_code.py CHANGED
@@ -139,10 +139,16 @@ class PinyinCodeModel(PinyinCodePreTrainedModel):
139
  attention_mask: torch.Tensor | None = None,
140
  inputs_embeds: torch.Tensor | None = None,
141
  position_ids: torch.Tensor | None = None,
 
142
  return_dict: bool | None = None,
143
  **kwargs,
144
  ) -> BaseModelOutput | tuple:
145
  return_dict = True if return_dict is None else return_dict
 
 
 
 
 
146
 
147
  if input_ids is None and inputs_embeds is None:
148
  raise ValueError("You must provide either input_ids or inputs_embeds")
@@ -173,14 +179,25 @@ class PinyinCodeModel(PinyinCodePreTrainedModel):
173
 
174
  x = inputs_embeds + self.position_embedding(position_ids)
175
  x = self.dropout(x)
 
176
  for block in self.blocks:
177
  x = block(x, attention_mask=attention_mask)
 
 
178
  hidden_states = self.ln_f(x)
 
 
179
 
180
  if not return_dict:
181
- return (hidden_states,)
 
 
 
182
 
183
- return BaseModelOutput(last_hidden_state=hidden_states)
 
 
 
184
 
185
 
186
  class PinyinCodeForCausalLM(PinyinCodeModel, GenerationMixin):
@@ -230,9 +247,10 @@ class PinyinCodeForCausalLM(PinyinCodeModel, GenerationMixin):
230
  input_ids: torch.Tensor | None = None,
231
  attention_mask: torch.Tensor | None = None,
232
  labels: torch.Tensor | None = None,
233
- inputs_embeds: torch.Tensor | None = None,
234
- position_ids: torch.Tensor | None = None,
235
- return_dict: bool | None = None,
 
236
  **kwargs,
237
  ) -> CausalLMOutput | tuple:
238
  return_dict = True if return_dict is None else return_dict
@@ -243,6 +261,7 @@ class PinyinCodeForCausalLM(PinyinCodeModel, GenerationMixin):
243
  attention_mask=attention_mask,
244
  inputs_embeds=inputs_embeds,
245
  position_ids=position_ids,
 
246
  return_dict=True,
247
  )
248
  logits = self.lm_head(decoder_outputs.last_hidden_state)
@@ -255,8 +274,14 @@ class PinyinCodeForCausalLM(PinyinCodeModel, GenerationMixin):
255
  ignore_index=-100,
256
  )
257
 
258
- if not return_dict:
259
- output = (logits,)
260
- return ((loss,) + output) if loss is not None else output
261
-
262
- return CausalLMOutput(loss=loss, logits=logits)
 
 
 
 
 
 
 
139
  attention_mask: torch.Tensor | None = None,
140
  inputs_embeds: torch.Tensor | None = None,
141
  position_ids: torch.Tensor | None = None,
142
+ output_hidden_states: bool | None = None,
143
  return_dict: bool | None = None,
144
  **kwargs,
145
  ) -> BaseModelOutput | tuple:
146
  return_dict = True if return_dict is None else return_dict
147
+ output_hidden_states = (
148
+ self.config.output_hidden_states
149
+ if output_hidden_states is None
150
+ else output_hidden_states
151
+ )
152
 
153
  if input_ids is None and inputs_embeds is None:
154
  raise ValueError("You must provide either input_ids or inputs_embeds")
 
179
 
180
  x = inputs_embeds + self.position_embedding(position_ids)
181
  x = self.dropout(x)
182
+ all_hidden_states = (x,) if output_hidden_states else None
183
  for block in self.blocks:
184
  x = block(x, attention_mask=attention_mask)
185
+ if output_hidden_states:
186
+ all_hidden_states = all_hidden_states + (x,)
187
  hidden_states = self.ln_f(x)
188
+ if output_hidden_states:
189
+ all_hidden_states = all_hidden_states + (hidden_states,)
190
 
191
  if not return_dict:
192
+ output = (hidden_states,)
193
+ if output_hidden_states:
194
+ output = output + (all_hidden_states,)
195
+ return output
196
 
197
+ return BaseModelOutput(
198
+ last_hidden_state=hidden_states,
199
+ hidden_states=all_hidden_states,
200
+ )
201
 
202
 
203
  class PinyinCodeForCausalLM(PinyinCodeModel, GenerationMixin):
 
247
  input_ids: torch.Tensor | None = None,
248
  attention_mask: torch.Tensor | None = None,
249
  labels: torch.Tensor | None = None,
250
+ inputs_embeds: torch.Tensor | None = None,
251
+ position_ids: torch.Tensor | None = None,
252
+ output_hidden_states: bool | None = None,
253
+ return_dict: bool | None = None,
254
  **kwargs,
255
  ) -> CausalLMOutput | tuple:
256
  return_dict = True if return_dict is None else return_dict
 
261
  attention_mask=attention_mask,
262
  inputs_embeds=inputs_embeds,
263
  position_ids=position_ids,
264
+ output_hidden_states=output_hidden_states,
265
  return_dict=True,
266
  )
267
  logits = self.lm_head(decoder_outputs.last_hidden_state)
 
274
  ignore_index=-100,
275
  )
276
 
277
+ if not return_dict:
278
+ output = (logits,)
279
+ if decoder_outputs.hidden_states is not None:
280
+ output = output + (decoder_outputs.hidden_states,)
281
+ return ((loss,) + output) if loss is not None else output
282
+
283
+ return CausalLMOutput(
284
+ loss=loss,
285
+ logits=logits,
286
+ hidden_states=decoder_outputs.hidden_states,
287
+ )
hf/tokenization_pinyin_code.py CHANGED
@@ -2,12 +2,13 @@
2
 
3
  from __future__ import annotations
4
 
5
- import logging
6
- import re
7
- import shutil
8
- import unicodedata
9
- from pathlib import Path
10
- from typing import Any
 
11
 
12
  import sentencepiece as spm
13
  from transformers import PreTrainedTokenizer
@@ -77,7 +78,7 @@ def should_preserve_fallback_token(token: str) -> bool:
77
  return True
78
 
79
 
80
- class PinyinCodeTokenizer(PreTrainedTokenizer):
81
  """Slow tokenizer that preserves the existing SentencePiece model."""
82
 
83
  vocab_files_names = {"vocab_file": "tokenizer.model"}
@@ -276,30 +277,150 @@ class PinyinCodeTokenizer(PreTrainedTokenizer):
276
 
277
  return " ".join(tokens)
278
 
279
- def _preprocess_tokenizer_input(self, value: Any) -> Any:
280
- if value is None:
281
- return None
282
- if isinstance(value, str):
283
- return self._preprocess_raw_text(value)
284
  if isinstance(value, tuple):
285
  return tuple(self._preprocess_tokenizer_input(item) for item in value)
286
  if isinstance(value, list):
287
- return [self._preprocess_tokenizer_input(item) for item in value]
288
- return value
289
-
290
- def __call__(self, text=None, text_pair=None, *args, **kwargs):
291
- if "text_target" in kwargs:
292
- kwargs["text_target"] = self._preprocess_tokenizer_input(kwargs["text_target"])
293
- if "text_pair_target" in kwargs:
294
- kwargs["text_pair_target"] = self._preprocess_tokenizer_input(
295
- kwargs["text_pair_target"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
296
  )
297
-
298
- text = self._preprocess_tokenizer_input(text)
299
- text_pair = self._preprocess_tokenizer_input(text_pair)
300
- if text_pair is None:
301
- return super().__call__(text, *args, **kwargs)
302
- return super().__call__(text, text_pair, *args, **kwargs)
 
 
 
 
 
 
 
 
303
 
304
  def encode(self, text, text_pair=None, add_special_tokens=True, *args, **kwargs):
305
  kwargs["add_special_tokens"] = add_special_tokens
@@ -309,18 +430,43 @@ class PinyinCodeTokenizer(PreTrainedTokenizer):
309
  return super().encode(text, *args, **kwargs)
310
  return super().encode(text, text_pair, *args, **kwargs)
311
 
312
- def encode_plus(self, text, text_pair=None, *args, **kwargs):
313
- text = self._preprocess_tokenizer_input(text)
314
- text_pair = self._preprocess_tokenizer_input(text_pair)
315
- if text_pair is None:
316
- return super().encode_plus(text, *args, **kwargs)
317
- return super().encode_plus(text, text_pair, *args, **kwargs)
318
-
319
- def batch_encode_plus(self, batch_text_or_text_pairs, *args, **kwargs):
320
- batch_text_or_text_pairs = self._preprocess_tokenizer_input(
321
- batch_text_or_text_pairs
322
- )
323
- return super().batch_encode_plus(batch_text_or_text_pairs, *args, **kwargs)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
324
 
325
  @property
326
  def vocab_size(self) -> int:
 
2
 
3
  from __future__ import annotations
4
 
5
+ import logging
6
+ import math
7
+ import re
8
+ import shutil
9
+ import unicodedata
10
+ from pathlib import Path
11
+ from typing import Any
12
 
13
  import sentencepiece as spm
14
  from transformers import PreTrainedTokenizer
 
78
  return True
79
 
80
 
81
+ class PinyinCodeTokenizer(PreTrainedTokenizer):
82
  """Slow tokenizer that preserves the existing SentencePiece model."""
83
 
84
  vocab_files_names = {"vocab_file": "tokenizer.model"}
 
277
 
278
  return " ".join(tokens)
279
 
280
+ def _preprocess_tokenizer_input(self, value: Any) -> Any:
281
+ if value is None:
282
+ return None
283
+ if isinstance(value, str):
284
+ return self._preprocess_raw_text(value)
285
  if isinstance(value, tuple):
286
  return tuple(self._preprocess_tokenizer_input(item) for item in value)
287
  if isinstance(value, list):
288
+ return [self._preprocess_tokenizer_input(item) for item in value]
289
+ return value
290
+
291
+ def _non_content_token_ids(self) -> set[int]:
292
+ return {
293
+ token_id
294
+ for token_id in (
295
+ self.pad_token_id,
296
+ self.bos_token_id,
297
+ self.eos_token_id,
298
+ self.cls_token_id,
299
+ self.sep_token_id,
300
+ self.mask_token_id,
301
+ )
302
+ if token_id is not None
303
+ }
304
+
305
+ def _offset_source_text(self, value: Any, is_split_into_words: bool = False) -> str:
306
+ if value is None:
307
+ return ""
308
+ if isinstance(value, str):
309
+ return value
310
+ if isinstance(value, tuple):
311
+ return " ".join(self._offset_source_text(item) for item in value)
312
+ if isinstance(value, list):
313
+ separator = " " if is_split_into_words else ""
314
+ return separator.join(self._offset_source_text(item) for item in value)
315
+ return str(value)
316
+
317
+ def _synthetic_offset_mapping(self, text: Any, input_ids: Any, is_split_into_words: bool = False) -> list[tuple[int, int]]:
318
+ """Return slow-tokenizer-compatible offsets for evaluators that require them.
319
+
320
+ SentencePiece offsets are not available for this Python tokenizer because
321
+ raw Mandarin text is preprocessed into pinyin-code before encoding. These
322
+ spans conservatively distribute non-special tokens across the original
323
+ text so suffix/completion masking code can run without requiring a fast
324
+ tokenizer.
325
+ """
326
+ ids = input_ids.tolist() if hasattr(input_ids, "tolist") else list(input_ids)
327
+ source = self._offset_source_text(text, is_split_into_words=is_split_into_words)
328
+ source_length = len(source)
329
+ if not ids:
330
+ return []
331
+ if source_length == 0:
332
+ return [(0, 0) for _ in ids]
333
+
334
+ non_content_ids = self._non_content_token_ids()
335
+ content_positions = [
336
+ index for index, token_id in enumerate(ids) if int(token_id) not in non_content_ids
337
+ ]
338
+ if not content_positions:
339
+ return [(0, 0) for _ in ids]
340
+
341
+ offsets = [(0, 0) for _ in ids]
342
+ count = len(content_positions)
343
+ for ordinal, position in enumerate(content_positions):
344
+ start = math.floor(ordinal * source_length / count)
345
+ end = math.ceil((ordinal + 1) * source_length / count)
346
+ if end <= start:
347
+ end = min(source_length, start + 1)
348
+ offsets[position] = (start, end)
349
+ return offsets
350
+
351
+ def _with_optional_offsets(
352
+ self,
353
+ encoding,
354
+ original_text: Any,
355
+ return_offsets_mapping: bool,
356
+ is_split_into_words: bool = False,
357
+ return_tensors: str | None = None,
358
+ ):
359
+ if not return_offsets_mapping:
360
+ return encoding
361
+
362
+ input_ids = encoding["input_ids"]
363
+ tensor_input = hasattr(input_ids, "ndim")
364
+ input_ids_list = input_ids.tolist() if tensor_input else input_ids
365
+
366
+ is_batched = False
367
+ if tensor_input:
368
+ is_batched = input_ids.ndim > 1
369
+ elif input_ids_list and isinstance(input_ids_list[0], list):
370
+ is_batched = True
371
+
372
+ if is_batched:
373
+ if isinstance(original_text, list) and not is_split_into_words:
374
+ texts = original_text
375
+ else:
376
+ texts = [original_text] * len(input_ids_list)
377
+ offsets = [
378
+ self._synthetic_offset_mapping(text, ids, is_split_into_words=is_split_into_words)
379
+ for text, ids in zip(texts, input_ids_list)
380
+ ]
381
+ else:
382
+ offsets = self._synthetic_offset_mapping(
383
+ original_text,
384
+ input_ids_list,
385
+ is_split_into_words=is_split_into_words,
386
+ )
387
+
388
+ if return_tensors == "pt" or tensor_input:
389
+ try:
390
+ import torch
391
+
392
+ offsets = torch.tensor(offsets, dtype=torch.long)
393
+ except ImportError:
394
+ pass
395
+ encoding["offset_mapping"] = offsets
396
+ return encoding
397
+
398
+ def __call__(self, text=None, text_pair=None, *args, **kwargs):
399
+ original_text = text
400
+ return_offsets_mapping = bool(kwargs.pop("return_offsets_mapping", False))
401
+ is_split_into_words = bool(kwargs.get("is_split_into_words", False))
402
+ return_tensors = kwargs.get("return_tensors")
403
+
404
+ if "text_target" in kwargs:
405
+ kwargs["text_target"] = self._preprocess_tokenizer_input(kwargs["text_target"])
406
+ if "text_pair_target" in kwargs:
407
+ kwargs["text_pair_target"] = self._preprocess_tokenizer_input(
408
+ kwargs["text_pair_target"]
409
  )
410
+
411
+ text = self._preprocess_tokenizer_input(text)
412
+ text_pair = self._preprocess_tokenizer_input(text_pair)
413
+ if text_pair is None:
414
+ encoding = super().__call__(text, *args, **kwargs)
415
+ else:
416
+ encoding = super().__call__(text, text_pair, *args, **kwargs)
417
+ return self._with_optional_offsets(
418
+ encoding,
419
+ original_text,
420
+ return_offsets_mapping,
421
+ is_split_into_words=is_split_into_words,
422
+ return_tensors=return_tensors,
423
+ )
424
 
425
  def encode(self, text, text_pair=None, add_special_tokens=True, *args, **kwargs):
426
  kwargs["add_special_tokens"] = add_special_tokens
 
430
  return super().encode(text, *args, **kwargs)
431
  return super().encode(text, text_pair, *args, **kwargs)
432
 
433
+ def encode_plus(self, text, text_pair=None, *args, **kwargs):
434
+ original_text = text
435
+ return_offsets_mapping = bool(kwargs.pop("return_offsets_mapping", False))
436
+ is_split_into_words = bool(kwargs.get("is_split_into_words", False))
437
+ return_tensors = kwargs.get("return_tensors")
438
+
439
+ text = self._preprocess_tokenizer_input(text)
440
+ text_pair = self._preprocess_tokenizer_input(text_pair)
441
+ if text_pair is None:
442
+ encoding = super().encode_plus(text, *args, **kwargs)
443
+ else:
444
+ encoding = super().encode_plus(text, text_pair, *args, **kwargs)
445
+ return self._with_optional_offsets(
446
+ encoding,
447
+ original_text,
448
+ return_offsets_mapping,
449
+ is_split_into_words=is_split_into_words,
450
+ return_tensors=return_tensors,
451
+ )
452
+
453
+ def batch_encode_plus(self, batch_text_or_text_pairs, *args, **kwargs):
454
+ original_batch = batch_text_or_text_pairs
455
+ return_offsets_mapping = bool(kwargs.pop("return_offsets_mapping", False))
456
+ is_split_into_words = bool(kwargs.get("is_split_into_words", False))
457
+ return_tensors = kwargs.get("return_tensors")
458
+
459
+ batch_text_or_text_pairs = self._preprocess_tokenizer_input(
460
+ batch_text_or_text_pairs
461
+ )
462
+ encoding = super().batch_encode_plus(batch_text_or_text_pairs, *args, **kwargs)
463
+ return self._with_optional_offsets(
464
+ encoding,
465
+ original_batch,
466
+ return_offsets_mapping,
467
+ is_split_into_words=is_split_into_words,
468
+ return_tensors=return_tensors,
469
+ )
470
 
471
  @property
472
  def vocab_size(self) -> int:
modeling_pinyin_code.py CHANGED
@@ -139,10 +139,16 @@ class PinyinCodeModel(PinyinCodePreTrainedModel):
139
  attention_mask: torch.Tensor | None = None,
140
  inputs_embeds: torch.Tensor | None = None,
141
  position_ids: torch.Tensor | None = None,
 
142
  return_dict: bool | None = None,
143
  **kwargs,
144
  ) -> BaseModelOutput | tuple:
145
  return_dict = True if return_dict is None else return_dict
 
 
 
 
 
146
 
147
  if input_ids is None and inputs_embeds is None:
148
  raise ValueError("You must provide either input_ids or inputs_embeds")
@@ -173,14 +179,25 @@ class PinyinCodeModel(PinyinCodePreTrainedModel):
173
 
174
  x = inputs_embeds + self.position_embedding(position_ids)
175
  x = self.dropout(x)
 
176
  for block in self.blocks:
177
  x = block(x, attention_mask=attention_mask)
 
 
178
  hidden_states = self.ln_f(x)
 
 
179
 
180
  if not return_dict:
181
- return (hidden_states,)
 
 
 
182
 
183
- return BaseModelOutput(last_hidden_state=hidden_states)
 
 
 
184
 
185
 
186
  class PinyinCodeForCausalLM(PinyinCodeModel, GenerationMixin):
@@ -230,9 +247,10 @@ class PinyinCodeForCausalLM(PinyinCodeModel, GenerationMixin):
230
  input_ids: torch.Tensor | None = None,
231
  attention_mask: torch.Tensor | None = None,
232
  labels: torch.Tensor | None = None,
233
- inputs_embeds: torch.Tensor | None = None,
234
- position_ids: torch.Tensor | None = None,
235
- return_dict: bool | None = None,
 
236
  **kwargs,
237
  ) -> CausalLMOutput | tuple:
238
  return_dict = True if return_dict is None else return_dict
@@ -243,6 +261,7 @@ class PinyinCodeForCausalLM(PinyinCodeModel, GenerationMixin):
243
  attention_mask=attention_mask,
244
  inputs_embeds=inputs_embeds,
245
  position_ids=position_ids,
 
246
  return_dict=True,
247
  )
248
  logits = self.lm_head(decoder_outputs.last_hidden_state)
@@ -255,8 +274,14 @@ class PinyinCodeForCausalLM(PinyinCodeModel, GenerationMixin):
255
  ignore_index=-100,
256
  )
257
 
258
- if not return_dict:
259
- output = (logits,)
260
- return ((loss,) + output) if loss is not None else output
261
-
262
- return CausalLMOutput(loss=loss, logits=logits)
 
 
 
 
 
 
 
139
  attention_mask: torch.Tensor | None = None,
140
  inputs_embeds: torch.Tensor | None = None,
141
  position_ids: torch.Tensor | None = None,
142
+ output_hidden_states: bool | None = None,
143
  return_dict: bool | None = None,
144
  **kwargs,
145
  ) -> BaseModelOutput | tuple:
146
  return_dict = True if return_dict is None else return_dict
147
+ output_hidden_states = (
148
+ self.config.output_hidden_states
149
+ if output_hidden_states is None
150
+ else output_hidden_states
151
+ )
152
 
153
  if input_ids is None and inputs_embeds is None:
154
  raise ValueError("You must provide either input_ids or inputs_embeds")
 
179
 
180
  x = inputs_embeds + self.position_embedding(position_ids)
181
  x = self.dropout(x)
182
+ all_hidden_states = (x,) if output_hidden_states else None
183
  for block in self.blocks:
184
  x = block(x, attention_mask=attention_mask)
185
+ if output_hidden_states:
186
+ all_hidden_states = all_hidden_states + (x,)
187
  hidden_states = self.ln_f(x)
188
+ if output_hidden_states:
189
+ all_hidden_states = all_hidden_states + (hidden_states,)
190
 
191
  if not return_dict:
192
+ output = (hidden_states,)
193
+ if output_hidden_states:
194
+ output = output + (all_hidden_states,)
195
+ return output
196
 
197
+ return BaseModelOutput(
198
+ last_hidden_state=hidden_states,
199
+ hidden_states=all_hidden_states,
200
+ )
201
 
202
 
203
  class PinyinCodeForCausalLM(PinyinCodeModel, GenerationMixin):
 
247
  input_ids: torch.Tensor | None = None,
248
  attention_mask: torch.Tensor | None = None,
249
  labels: torch.Tensor | None = None,
250
+ inputs_embeds: torch.Tensor | None = None,
251
+ position_ids: torch.Tensor | None = None,
252
+ output_hidden_states: bool | None = None,
253
+ return_dict: bool | None = None,
254
  **kwargs,
255
  ) -> CausalLMOutput | tuple:
256
  return_dict = True if return_dict is None else return_dict
 
261
  attention_mask=attention_mask,
262
  inputs_embeds=inputs_embeds,
263
  position_ids=position_ids,
264
+ output_hidden_states=output_hidden_states,
265
  return_dict=True,
266
  )
267
  logits = self.lm_head(decoder_outputs.last_hidden_state)
 
274
  ignore_index=-100,
275
  )
276
 
277
+ if not return_dict:
278
+ output = (logits,)
279
+ if decoder_outputs.hidden_states is not None:
280
+ output = output + (decoder_outputs.hidden_states,)
281
+ return ((loss,) + output) if loss is not None else output
282
+
283
+ return CausalLMOutput(
284
+ loss=loss,
285
+ logits=logits,
286
+ hidden_states=decoder_outputs.hidden_states,
287
+ )
tokenization_pinyin_code.py CHANGED
@@ -2,12 +2,13 @@
2
 
3
  from __future__ import annotations
4
 
5
- import logging
6
- import re
7
- import shutil
8
- import unicodedata
9
- from pathlib import Path
10
- from typing import Any
 
11
 
12
  import sentencepiece as spm
13
  from transformers import PreTrainedTokenizer
@@ -77,7 +78,7 @@ def should_preserve_fallback_token(token: str) -> bool:
77
  return True
78
 
79
 
80
- class PinyinCodeTokenizer(PreTrainedTokenizer):
81
  """Slow tokenizer that preserves the existing SentencePiece model."""
82
 
83
  vocab_files_names = {"vocab_file": "tokenizer.model"}
@@ -276,30 +277,150 @@ class PinyinCodeTokenizer(PreTrainedTokenizer):
276
 
277
  return " ".join(tokens)
278
 
279
- def _preprocess_tokenizer_input(self, value: Any) -> Any:
280
- if value is None:
281
- return None
282
- if isinstance(value, str):
283
- return self._preprocess_raw_text(value)
284
  if isinstance(value, tuple):
285
  return tuple(self._preprocess_tokenizer_input(item) for item in value)
286
  if isinstance(value, list):
287
- return [self._preprocess_tokenizer_input(item) for item in value]
288
- return value
289
-
290
- def __call__(self, text=None, text_pair=None, *args, **kwargs):
291
- if "text_target" in kwargs:
292
- kwargs["text_target"] = self._preprocess_tokenizer_input(kwargs["text_target"])
293
- if "text_pair_target" in kwargs:
294
- kwargs["text_pair_target"] = self._preprocess_tokenizer_input(
295
- kwargs["text_pair_target"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
296
  )
297
-
298
- text = self._preprocess_tokenizer_input(text)
299
- text_pair = self._preprocess_tokenizer_input(text_pair)
300
- if text_pair is None:
301
- return super().__call__(text, *args, **kwargs)
302
- return super().__call__(text, text_pair, *args, **kwargs)
 
 
 
 
 
 
 
 
303
 
304
  def encode(self, text, text_pair=None, add_special_tokens=True, *args, **kwargs):
305
  kwargs["add_special_tokens"] = add_special_tokens
@@ -309,18 +430,43 @@ class PinyinCodeTokenizer(PreTrainedTokenizer):
309
  return super().encode(text, *args, **kwargs)
310
  return super().encode(text, text_pair, *args, **kwargs)
311
 
312
- def encode_plus(self, text, text_pair=None, *args, **kwargs):
313
- text = self._preprocess_tokenizer_input(text)
314
- text_pair = self._preprocess_tokenizer_input(text_pair)
315
- if text_pair is None:
316
- return super().encode_plus(text, *args, **kwargs)
317
- return super().encode_plus(text, text_pair, *args, **kwargs)
318
-
319
- def batch_encode_plus(self, batch_text_or_text_pairs, *args, **kwargs):
320
- batch_text_or_text_pairs = self._preprocess_tokenizer_input(
321
- batch_text_or_text_pairs
322
- )
323
- return super().batch_encode_plus(batch_text_or_text_pairs, *args, **kwargs)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
324
 
325
  @property
326
  def vocab_size(self) -> int:
 
2
 
3
  from __future__ import annotations
4
 
5
+ import logging
6
+ import math
7
+ import re
8
+ import shutil
9
+ import unicodedata
10
+ from pathlib import Path
11
+ from typing import Any
12
 
13
  import sentencepiece as spm
14
  from transformers import PreTrainedTokenizer
 
78
  return True
79
 
80
 
81
+ class PinyinCodeTokenizer(PreTrainedTokenizer):
82
  """Slow tokenizer that preserves the existing SentencePiece model."""
83
 
84
  vocab_files_names = {"vocab_file": "tokenizer.model"}
 
277
 
278
  return " ".join(tokens)
279
 
280
+ def _preprocess_tokenizer_input(self, value: Any) -> Any:
281
+ if value is None:
282
+ return None
283
+ if isinstance(value, str):
284
+ return self._preprocess_raw_text(value)
285
  if isinstance(value, tuple):
286
  return tuple(self._preprocess_tokenizer_input(item) for item in value)
287
  if isinstance(value, list):
288
+ return [self._preprocess_tokenizer_input(item) for item in value]
289
+ return value
290
+
291
+ def _non_content_token_ids(self) -> set[int]:
292
+ return {
293
+ token_id
294
+ for token_id in (
295
+ self.pad_token_id,
296
+ self.bos_token_id,
297
+ self.eos_token_id,
298
+ self.cls_token_id,
299
+ self.sep_token_id,
300
+ self.mask_token_id,
301
+ )
302
+ if token_id is not None
303
+ }
304
+
305
+ def _offset_source_text(self, value: Any, is_split_into_words: bool = False) -> str:
306
+ if value is None:
307
+ return ""
308
+ if isinstance(value, str):
309
+ return value
310
+ if isinstance(value, tuple):
311
+ return " ".join(self._offset_source_text(item) for item in value)
312
+ if isinstance(value, list):
313
+ separator = " " if is_split_into_words else ""
314
+ return separator.join(self._offset_source_text(item) for item in value)
315
+ return str(value)
316
+
317
+ def _synthetic_offset_mapping(self, text: Any, input_ids: Any, is_split_into_words: bool = False) -> list[tuple[int, int]]:
318
+ """Return slow-tokenizer-compatible offsets for evaluators that require them.
319
+
320
+ SentencePiece offsets are not available for this Python tokenizer because
321
+ raw Mandarin text is preprocessed into pinyin-code before encoding. These
322
+ spans conservatively distribute non-special tokens across the original
323
+ text so suffix/completion masking code can run without requiring a fast
324
+ tokenizer.
325
+ """
326
+ ids = input_ids.tolist() if hasattr(input_ids, "tolist") else list(input_ids)
327
+ source = self._offset_source_text(text, is_split_into_words=is_split_into_words)
328
+ source_length = len(source)
329
+ if not ids:
330
+ return []
331
+ if source_length == 0:
332
+ return [(0, 0) for _ in ids]
333
+
334
+ non_content_ids = self._non_content_token_ids()
335
+ content_positions = [
336
+ index for index, token_id in enumerate(ids) if int(token_id) not in non_content_ids
337
+ ]
338
+ if not content_positions:
339
+ return [(0, 0) for _ in ids]
340
+
341
+ offsets = [(0, 0) for _ in ids]
342
+ count = len(content_positions)
343
+ for ordinal, position in enumerate(content_positions):
344
+ start = math.floor(ordinal * source_length / count)
345
+ end = math.ceil((ordinal + 1) * source_length / count)
346
+ if end <= start:
347
+ end = min(source_length, start + 1)
348
+ offsets[position] = (start, end)
349
+ return offsets
350
+
351
+ def _with_optional_offsets(
352
+ self,
353
+ encoding,
354
+ original_text: Any,
355
+ return_offsets_mapping: bool,
356
+ is_split_into_words: bool = False,
357
+ return_tensors: str | None = None,
358
+ ):
359
+ if not return_offsets_mapping:
360
+ return encoding
361
+
362
+ input_ids = encoding["input_ids"]
363
+ tensor_input = hasattr(input_ids, "ndim")
364
+ input_ids_list = input_ids.tolist() if tensor_input else input_ids
365
+
366
+ is_batched = False
367
+ if tensor_input:
368
+ is_batched = input_ids.ndim > 1
369
+ elif input_ids_list and isinstance(input_ids_list[0], list):
370
+ is_batched = True
371
+
372
+ if is_batched:
373
+ if isinstance(original_text, list) and not is_split_into_words:
374
+ texts = original_text
375
+ else:
376
+ texts = [original_text] * len(input_ids_list)
377
+ offsets = [
378
+ self._synthetic_offset_mapping(text, ids, is_split_into_words=is_split_into_words)
379
+ for text, ids in zip(texts, input_ids_list)
380
+ ]
381
+ else:
382
+ offsets = self._synthetic_offset_mapping(
383
+ original_text,
384
+ input_ids_list,
385
+ is_split_into_words=is_split_into_words,
386
+ )
387
+
388
+ if return_tensors == "pt" or tensor_input:
389
+ try:
390
+ import torch
391
+
392
+ offsets = torch.tensor(offsets, dtype=torch.long)
393
+ except ImportError:
394
+ pass
395
+ encoding["offset_mapping"] = offsets
396
+ return encoding
397
+
398
+ def __call__(self, text=None, text_pair=None, *args, **kwargs):
399
+ original_text = text
400
+ return_offsets_mapping = bool(kwargs.pop("return_offsets_mapping", False))
401
+ is_split_into_words = bool(kwargs.get("is_split_into_words", False))
402
+ return_tensors = kwargs.get("return_tensors")
403
+
404
+ if "text_target" in kwargs:
405
+ kwargs["text_target"] = self._preprocess_tokenizer_input(kwargs["text_target"])
406
+ if "text_pair_target" in kwargs:
407
+ kwargs["text_pair_target"] = self._preprocess_tokenizer_input(
408
+ kwargs["text_pair_target"]
409
  )
410
+
411
+ text = self._preprocess_tokenizer_input(text)
412
+ text_pair = self._preprocess_tokenizer_input(text_pair)
413
+ if text_pair is None:
414
+ encoding = super().__call__(text, *args, **kwargs)
415
+ else:
416
+ encoding = super().__call__(text, text_pair, *args, **kwargs)
417
+ return self._with_optional_offsets(
418
+ encoding,
419
+ original_text,
420
+ return_offsets_mapping,
421
+ is_split_into_words=is_split_into_words,
422
+ return_tensors=return_tensors,
423
+ )
424
 
425
  def encode(self, text, text_pair=None, add_special_tokens=True, *args, **kwargs):
426
  kwargs["add_special_tokens"] = add_special_tokens
 
430
  return super().encode(text, *args, **kwargs)
431
  return super().encode(text, text_pair, *args, **kwargs)
432
 
433
+ def encode_plus(self, text, text_pair=None, *args, **kwargs):
434
+ original_text = text
435
+ return_offsets_mapping = bool(kwargs.pop("return_offsets_mapping", False))
436
+ is_split_into_words = bool(kwargs.get("is_split_into_words", False))
437
+ return_tensors = kwargs.get("return_tensors")
438
+
439
+ text = self._preprocess_tokenizer_input(text)
440
+ text_pair = self._preprocess_tokenizer_input(text_pair)
441
+ if text_pair is None:
442
+ encoding = super().encode_plus(text, *args, **kwargs)
443
+ else:
444
+ encoding = super().encode_plus(text, text_pair, *args, **kwargs)
445
+ return self._with_optional_offsets(
446
+ encoding,
447
+ original_text,
448
+ return_offsets_mapping,
449
+ is_split_into_words=is_split_into_words,
450
+ return_tensors=return_tensors,
451
+ )
452
+
453
+ def batch_encode_plus(self, batch_text_or_text_pairs, *args, **kwargs):
454
+ original_batch = batch_text_or_text_pairs
455
+ return_offsets_mapping = bool(kwargs.pop("return_offsets_mapping", False))
456
+ is_split_into_words = bool(kwargs.get("is_split_into_words", False))
457
+ return_tensors = kwargs.get("return_tensors")
458
+
459
+ batch_text_or_text_pairs = self._preprocess_tokenizer_input(
460
+ batch_text_or_text_pairs
461
+ )
462
+ encoding = super().batch_encode_plus(batch_text_or_text_pairs, *args, **kwargs)
463
+ return self._with_optional_offsets(
464
+ encoding,
465
+ original_batch,
466
+ return_offsets_mapping,
467
+ is_split_into_words=is_split_into_words,
468
+ return_tensors=return_tensors,
469
+ )
470
 
471
  @property
472
  def vocab_size(self) -> int: