Taka008 commited on
Commit
54685e7
·
verified ·
1 Parent(s): a171c7c

Upload llmjp4_harmony.py

Browse files
Files changed (1) hide show
  1. llmjp4_harmony.py +129 -0
llmjp4_harmony.py ADDED
@@ -0,0 +1,129 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Generic parser for OpenAI Harmony format.
2
+
3
+ from dataclasses import dataclass
4
+ from enum import Enum
5
+ from typing import Iterator, Sequence
6
+
7
+ from transformers import PreTrainedTokenizerBase as TokenizerLike
8
+
9
+
10
+ class HarmonyMessageEndType(Enum):
11
+ INCOMPLETE = 0
12
+ END = 1
13
+ CALL = 2
14
+
15
+
16
+ @dataclass(frozen=True)
17
+ class HarmonySequence:
18
+ """A data class representing a sequence of tokens in the Harmony format."""
19
+ token_ids: list[int]
20
+ start: int # Start position of the sequence in the original token sequence
21
+
22
+
23
+ @dataclass(frozen=True)
24
+ class HarmonyMessage:
25
+ """A data class representing a message in the Harmony format."""
26
+ end: HarmonyMessageEndType
27
+ role: HarmonySequence | None = None
28
+ channel: HarmonySequence | None = None
29
+ constrain: HarmonySequence | None = None
30
+ content: HarmonySequence | None = None
31
+
32
+
33
+ class HarmonyMessageParser:
34
+ """A parser that performs lexical analysis to extract Harmony messages."""
35
+
36
+ def __init__(self, tokenizer: TokenizerLike):
37
+ vocab = tokenizer.get_vocab()
38
+ self._begin_map = {
39
+ vocab["<|start|>"]: "role",
40
+ vocab["<|channel|>"]: "channel",
41
+ vocab["<|constrain|>"]: "constrain",
42
+ vocab["<|message|>"]: "content",
43
+ }
44
+ self._end_map = {
45
+ vocab["<|end|>"]: HarmonyMessageEndType.END,
46
+ vocab["<|return|>"]: HarmonyMessageEndType.END,
47
+ vocab["<|call|>"]: HarmonyMessageEndType.CALL,
48
+ }
49
+
50
+ def iter_messages(self, token_ids: Sequence[int]) -> Iterator[HarmonyMessage]:
51
+ """
52
+ Parse given token ids into messages.
53
+
54
+ Args:
55
+ token_ids: A sequence of token ids to be parsed.
56
+
57
+ Yields:
58
+ Detected HarmonyMessages.
59
+ """
60
+
61
+ message_dict: dict[str, HarmonySequence] = {}
62
+ section: str | None = None # None indicates out-of-message.
63
+ text_ids: list[int] = []
64
+ text_start: int | None = None
65
+
66
+ for token_position, token_id in enumerate(token_ids):
67
+ if token_id in self._begin_map:
68
+ if section is not None:
69
+ message_dict[section] = HarmonySequence(
70
+ token_ids=text_ids,
71
+ start=text_start,
72
+ )
73
+ section = self._begin_map[token_id]
74
+ text_ids = []
75
+ text_start = token_position + 1
76
+
77
+ elif token_id in self._end_map:
78
+ if section is not None:
79
+ message_dict[section] = HarmonySequence(
80
+ token_ids=text_ids,
81
+ start=text_start,
82
+ )
83
+
84
+ yield HarmonyMessage(**message_dict, end=self._end_map[token_id])
85
+
86
+ message_dict = {}
87
+ section = None
88
+ text_ids = []
89
+ text_start = None
90
+
91
+ else:
92
+ if section is not None:
93
+ text_ids.append(token_id)
94
+
95
+ if section is not None:
96
+ message_dict[section] = HarmonySequence(
97
+ token_ids=text_ids,
98
+ start=text_start,
99
+ )
100
+ yield HarmonyMessage(**message_dict, end=HarmonyMessageEndType.INCOMPLETE)
101
+
102
+ def get_all_messages(self, token_ids: Sequence[int]) -> list[HarmonyMessage]:
103
+ """
104
+ Parse given token ids into messages.
105
+
106
+ Args:
107
+ token_ids: A sequence of token ids to be parsed.
108
+
109
+ Returns:
110
+ A list of detected HarmonyMessages.
111
+ """
112
+ return list(self.iter_messages(token_ids))
113
+
114
+ def reverse_iter_messages(self, token_ids: Sequence[int]) -> Iterator[HarmonyMessage]:
115
+ """
116
+ Parse given token ids into messages in reverse order.
117
+
118
+ Args:
119
+ token_ids: A sequence of token ids to be parsed.
120
+
121
+ Yields:
122
+ Detected HarmonyMessages in reverse order.
123
+ """
124
+ end_position = len(token_ids)
125
+
126
+ for i in range(len(token_ids) - 1, -1, -1):
127
+ if token_ids[i] == self._start_id:
128
+ yield next(self.iter_messages(token_ids[i:end_position]))
129
+ end_position = i