OpenLab-NLP commited on
Commit
e2d970e
ยท
verified ยท
1 Parent(s): cbf33d0

Upload 3 files

Browse files
Files changed (3) hide show
  1. blocklm.weights.h5 +3 -0
  2. head.weights.h5 +3 -0
  3. xxx.py +333 -0
blocklm.weights.h5 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:53ee854cce43a1b6a1f226719e047907aa253e05e2e5fc0ca1eec1e87c9bb861
3
+ size 216686536
head.weights.h5 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:e3296cc846c6181641581f98bd611aa30fade2f654f4700e3295dc16e6bd6382
3
+ size 32777488
xxx.py ADDED
@@ -0,0 +1,333 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import sentencepiece as spm
2
+ import os, numpy as np, tensorflow as tf
3
+ from tensorflow.keras import layers
4
+
5
+ # --- ํ™˜๊ฒฝ ์„ค์ • ---
6
+ TOKENIZER_PATH = r"C:\Users\yuchan\write1\openlm\tokenizer.model"
7
+ sp = spm.SentencePieceProcessor(TOKENIZER_PATH)
8
+ pad_id = sp.piece_to_id("<pad>") if sp.piece_to_id("<pad>") != -1 else 0
9
+ end_id = sp.piece_to_id("</s>")
10
+
11
+ max_len = 1024
12
+ vocab_size = sp.get_piece_size()
13
+
14
+ def text_to_ids(text):
15
+ return sp.encode(text, out_type=int)
16
+
17
+ def ids_to_text(ids):
18
+ return sp.decode(ids)
19
+
20
+ class TimeMix(layers.Layer):
21
+ def __init__(self, d_model, layer_id, n_layers):
22
+ super().__init__()
23
+ self.d_model = d_model
24
+ ratio = (layer_id / (n_layers - 1)) if n_layers > 1 else 0.5
25
+
26
+ # ๊ธฐ๋ณธ ๋ฒ ์ด์Šค ํŒŒ๋ผ๋ฏธํ„ฐ
27
+ decay_speed = np.arange(d_model)
28
+ self.time_decay = tf.Variable(-5 + 8 * (decay_speed / (d_model - 1)) ** (0.7 + 1.3 * ratio), dtype=tf.float32)
29
+ self.time_first = tf.Variable(np.ones(d_model) * np.log(0.3), dtype=tf.float32)
30
+
31
+ # --- ๋™์  ํ”„๋กœ์ ์…˜ ๋ ˆ์ด์–ด (์ถ”๊ฐ€๋จ) ---
32
+ self.w_proj = layers.Dense(d_model, kernel_initializer='zeros', use_bias=False)
33
+ self.r_proj = layers.Dense(d_model, kernel_initializer='zeros', use_bias=False)
34
+ self.k_proj = layers.Dense(d_model, kernel_initializer='zeros', use_bias=False)
35
+ self.v_proj = layers.Dense(d_model, kernel_initializer='zeros', use_bias=False)
36
+
37
+ self.key = layers.Dense(d_model, use_bias=False)
38
+ self.value = layers.Dense(d_model, use_bias=False)
39
+ self.receptance = layers.Dense(d_model, use_bias=False)
40
+ self.output_projection = layers.Dense(d_model, use_bias=False)
41
+
42
+ # ๋ฏน์‹ฑ ๊ณ„์ˆ˜
43
+ self.tm_w = tf.Variable(1 - (ratio ** 0.5), dtype=tf.float32)
44
+ self.tm_k = tf.Variable(1 - (ratio ** 0.5), dtype=tf.float32)
45
+ self.tm_v = tf.Variable(1 - (ratio ** 0.5), dtype=tf.float32)
46
+ self.tm_r = tf.Variable(1 - (ratio ** 0.2), dtype=tf.float32)
47
+
48
+ def call(self, x, state):
49
+ # state: [last_x, aa, bb, pp]
50
+ last_x, aa, bb, pp = state
51
+ t_type = x.dtype
52
+
53
+ # ๋ฏน์‹ฑ ๊ณ„์ˆ˜ ์บ์ŠคํŒ…
54
+ tm_w = tf.cast(self.tm_w, t_type)
55
+ tm_k = tf.cast(self.tm_k, t_type)
56
+ tm_v = tf.cast(self.tm_v, t_type)
57
+ tm_r = tf.cast(self.tm_r, t_type)
58
+
59
+ # 1. ๋™์  ํŒŒ๋ผ๋ฏธํ„ฐ ์ƒ์„ฑ์„ ์œ„ํ•œ dx ๊ณ„์‚ฐ
60
+ dx = x * tm_w + last_x * (1 - tm_w)
61
+
62
+ # 2. ๋™์  w, r, k, v ๊ณ„์‚ฐ (ํ•ต์‹ฌ ๋ณ€๊ฒฝ ์‚ฌํ•ญ)
63
+ # ์ถ”๋ก  ์‹œ์—๋„ ๋งค๋ฒˆ x์— ๋”ฐ๋ผ ํŒŒ๋ผ๋ฏธํ„ฐ๊ฐ€ ๋ณ€ํ•ฉ๋‹ˆ๋‹ค.
64
+ w = tf.cast(self.time_decay, t_type) + tf.cast(self.w_proj(dx), t_type)
65
+ w = -tf.exp(tf.cast(w, tf.float32)) # ๊ฐ์‡ ์œจ (float32 ์ •๋ฐ€๋„)
66
+
67
+ r = self.receptance(x * tm_r + last_x * (1 - tm_r)) + self.r_proj(dx)
68
+ k = self.key(x * tm_k + last_x * (1 - tm_k)) + self.k_proj(dx)
69
+ v = self.value(x * tm_v + last_x * (1 - tm_v)) + self.v_proj(dx)
70
+
71
+ # 3. RNN ๋ชจ๋“œ WKV ์—ฐ์‚ฐ
72
+ u = tf.cast(self.time_first, tf.float32)
73
+ kv, vv = tf.cast(k, tf.float32), tf.cast(v, tf.float32)
74
+
75
+ ww = u + kv
76
+ p = tf.maximum(pp, ww)
77
+ e1, e2 = tf.exp(pp - p), tf.exp(ww - p)
78
+ wkv = (e1 * aa + e2 * vv) / (e1 * bb + e2 + 1e-12)
79
+
80
+ # ๋‹ค์Œ ์Šคํ…Œ์ดํŠธ๋ฅผ ์œ„ํ•œ ์—…๋ฐ์ดํŠธ (๋™์  w ์ ์šฉ)
81
+ ww_next = w + pp
82
+ p_next = tf.maximum(ww_next, kv)
83
+ e1_next, e2_next = tf.exp(ww_next - p_next), tf.exp(kv - p_next)
84
+
85
+ new_state = [x, e1_next * aa + e2_next * vv, e1_next * bb + e2_next, p_next]
86
+ return self.output_projection(tf.nn.sigmoid(r) * tf.cast(wkv, t_type)), new_state
87
+
88
+ class ChannelMix(layers.Layer):
89
+ def __init__(self, d_model, layer_id, n_layers):
90
+ super().__init__()
91
+ ratio = (layer_id / (n_layers - 1)) if n_layers > 1 else 0.5
92
+ self.time_mix_k = tf.Variable(1 - (ratio ** 0.5), dtype=tf.float32)
93
+ self.time_mix_r = tf.Variable(1 - (ratio ** 0.5), dtype=tf.float32)
94
+ self.key = layers.Dense(int(d_model * 4.25), use_bias=False)
95
+ self.receptance = layers.Dense(d_model, use_bias=False)
96
+ self.value = layers.Dense(d_model, use_bias=False)
97
+
98
+ def call(self, x, last_x):
99
+ t_type = x.dtype
100
+ tm_k, tm_r = tf.cast(self.time_mix_k, t_type), tf.cast(self.time_mix_r, t_type)
101
+ k = self.key(x * tm_k + last_x * (1 - tm_k))
102
+ r = self.receptance(x * tm_r + last_x * (1 - tm_r))
103
+ kv = self.value(tf.square(tf.nn.relu(k)))
104
+ return tf.nn.sigmoid(r) * kv, x
105
+
106
+ class Block(layers.Layer):
107
+ def __init__(self, d_model, layer_id, n_layers):
108
+ super().__init__()
109
+ self.ln = layers.LayerNormalization(epsilon=1e-5)
110
+ self.time_mix = TimeMix(d_model, layer_id, n_layers)
111
+ self.channel_mix = ChannelMix(d_model, layer_id, n_layers)
112
+ def call(self, x, state):
113
+ ln_x = self.ln(x)
114
+ tm_out, tm_state = self.time_mix(ln_x, state[:4])
115
+ x = x + tm_out
116
+ cm_out, cm_last_x = self.channel_mix(ln_x, state[4])
117
+ x = x + cm_out
118
+ return x, tm_state + [cm_last_x]
119
+
120
+ class Head(tf.keras.Model):
121
+ def __init__(self, vocab_size):
122
+ super().__init__()
123
+ self.lm_head = layers.Dense(vocab_size, use_bias=False, name="output_head")
124
+ def call(self, x):
125
+ return tf.cast(self.lm_head(x), tf.float32)
126
+
127
+ class LM(tf.keras.Model):
128
+ def __init__(self, d_model, n_layers):
129
+ super().__init__()
130
+ self.token_embedding = layers.Embedding(vocab_size, d_model)
131
+ self.blocks = [Block(d_model, i, n_layers) for i in range(n_layers)]
132
+ self.ln_f = layers.LayerNormalization(epsilon=1e-5, dtype=tf.float32)
133
+ def call(self, x, states):
134
+ x = self.token_embedding(x)
135
+ new_states = []
136
+ for i, block in enumerate(self.blocks):
137
+ x, b_state = block(x, states[i*5 : (i+1)*5])
138
+ new_states.extend(b_state)
139
+ return self.ln_f(x), new_states
140
+
141
+ # --- ์ดˆ๊ธฐํ™” ๋ฐ ๋กœ๋“œ ---
142
+ d_model, n_layers = 512, 10
143
+ blocklm = LM(d_model, n_layers)
144
+ head = Head(vocab_size)
145
+
146
+ def get_init_state():
147
+ return [tf.zeros((1, 1, d_model)) if i%5!=3 else tf.ones((1, 1, d_model))*-1e30 for i in range(n_layers*5)]
148
+
149
+ _o, _s = blocklm(tf.constant([[0]]), get_init_state())
150
+ _ = head(_o)
151
+ blocklm.load_weights(r"C:\Users\yuchan\write1\blocklm.weights.h5")
152
+ head.load_weights(r"C:\Users\yuchan\write1\head.weights.h5")
153
+
154
+
155
+ import numpy as np
156
+ import tensorflow as tf
157
+
158
+ class InferenceEngine:
159
+ def __init__(self, model, head, sp, config):
160
+ self.model = model
161
+ self.head = head
162
+ self.sp = sp
163
+ self.config = config
164
+ # SentencePiece์—์„œ ํŠน์ˆ˜ ํ† ํฐ ID ์ถ”์ถœ
165
+ self.pad_id = sp.piece_to_id("<pad>") if sp.piece_to_id("<pad>") != -1 else 0
166
+ # EOS ํ† ํฐ์ด ์—†์œผ๋ฉด ๋ณดํ†ต </s>๋‚˜ [EOS]๋ฅผ ์ฐพ์Šต๋‹ˆ๋‹ค.
167
+ self.eos_id = sp.piece_to_id("</s>")
168
+ if self.eos_id == -1:
169
+ self.eos_id = sp.piece_to_id("[EOS]")
170
+
171
+ def apply_repetition_penalty(self, logits, generated_ids, penalty):
172
+ """
173
+ Logits Scaling ๋ฐฉ์‹์˜ ํŒจ๋„ํ‹ฐ ๋ถ€์—ฌ (HuggingFace ์Šคํƒ€์ผ)
174
+ ์ตœ๊ทผ ์ƒ์„ฑ๋œ ํ† ํฐ๋“ค์ด ๋‹ค์‹œ ๋‚˜์˜ฌ ํ™•๋ฅ ์„ ์–ต์ œํ•ฉ๋‹ˆ๋‹ค.
175
+ """
176
+ if not generated_ids:
177
+ return logits
178
+
179
+ # ์„ค์ •๋œ ์œˆ๋„์šฐ ํฌ๊ธฐ ๋‚ด์—์„œ ๋“ฑ์žฅํ•œ ํ† ํฐ๋“ค๋งŒ ํŒจ๋„ํ‹ฐ ๋Œ€์ƒ
180
+ recent_ids = set(generated_ids[-self.config.get('penalty_window', 64):])
181
+
182
+ for token_id in recent_ids:
183
+ score = logits[token_id]
184
+ if score > 0:
185
+ # ์–‘์ˆ˜ ์Šค์ฝ”์–ด๋Š” ๋‚ฎ์ถ”๊ณ 
186
+ logits[token_id] /= penalty
187
+ else:
188
+ # ์Œ์ˆ˜ ์Šค์ฝ”์–ด๋Š” ๋” ๋‚ฎ๊ฒŒ (์ ˆ๋Œ“๊ฐ’์„ ํ‚ค์›€)
189
+ logits[token_id] *= penalty
190
+ return logits
191
+
192
+ def sample(self, logits, temp, top_k, top_p):
193
+ """Temperature, Top-K, Top-P๊ฐ€ ๊ฒฐํ•ฉ๋œ ์ˆ˜์น˜ ์•ˆ์ •์  ์ƒ˜ํ”Œ๋ง"""
194
+ # 1. Temperature ์ ์šฉ
195
+ if temp > 0:
196
+ logits = logits / temp
197
+ else:
198
+ # Greedy Search (๊ฐ€์žฅ ๋†’์€ ํ™•๋ฅ  ์„ ํƒ)
199
+ return np.argmax(logits)
200
+
201
+ # 2. Top-K ํ•„ํ„ฐ๋ง
202
+ if top_k > 0:
203
+ top_k = min(top_k, logits.shape[-1])
204
+ indices_to_remove = logits < np.sort(logits)[-top_k]
205
+ logits[indices_to_remove] = -float('inf')
206
+
207
+ # 3. Top-P (Nucleus) ํ•„ํ„ฐ๋ง
208
+ # ํ™•๋ฅ  ๋„๋ฉ”์ธ์—์„œ ๊ณ„์‚ฐํ•˜๊ธฐ ์œ„ํ•ด softmax ์ ์šฉ
209
+ probs = tf.nn.softmax(logits).numpy()
210
+ sorted_indices = np.argsort(probs)[::-1]
211
+ sorted_probs = probs[sorted_indices]
212
+ cumulative_probs = np.cumsum(sorted_probs)
213
+
214
+ # ๋ฌธ๋งฅ ์œ ์ง€๋ฅผ ์œ„ํ•ด ์ตœ์†Œ 1๊ฐœ ํ† ํฐ์€ ๋‚จ๊ธฐ๊ณ  ๋‚˜๋จธ์ง€๋Š” ์ œ๊ฑฐ
215
+ idx_to_remove = cumulative_probs > top_p
216
+ if np.any(idx_to_remove):
217
+ cutoff_idx = np.where(idx_to_remove)[0][0] + 1
218
+ cutoff_idx = max(1, cutoff_idx)
219
+ probs[sorted_indices[cutoff_idx:]] = 0
220
+ # ํ™•๋ฅ  ์žฌ์ •๊ทœํ™”
221
+ if np.sum(probs) > 0:
222
+ probs = probs / np.sum(probs)
223
+ else:
224
+ # ํด๋ฐฑ: ๊ฐ€์žฅ ๋†’์€ ํ™•๋ฅ  ํ† ํฐ์— 1 ๋ชฐ์•„์ฃผ๊ธฐ
225
+ probs[sorted_indices[0]] = 1.0
226
+
227
+ # 4. ์ตœ์ข… ์ƒ˜ํ”Œ๋ง
228
+ return np.random.choice(len(probs), p=probs)
229
+
230
+ @tf.function(reduce_retracing=True)
231
+ def model_step(self, token_id, states):
232
+ """TPU/GPU ๊ฐ€์†์„ ์œ„ํ•œ Graph ๋ชจ๋“œ ์ถ”๋ก  ์‹คํ–‰๋ถ€"""
233
+ out, next_states = self.model(token_id, states)
234
+ logits = self.head(out)
235
+ return logits, next_states
236
+
237
+ def generate(self, prompt,
238
+ max_new_tokens=512,
239
+ temperature=0.7,
240
+ top_k=40,
241
+ top_p=0.9,
242
+ repetition_penalty=1.2):
243
+ """
244
+ ์ŠคํŠธ๋ฆฌ๋ฐ ๋ฐฉ์‹์œผ๋กœ ํ…์ŠคํŠธ๋ฅผ ์ƒ์„ฑํ•˜๋Š” ์ œ๋„ˆ๋ ˆ์ดํ„ฐ.
245
+ """
246
+ # ์ž…๋ ฅ ํ…์ŠคํŠธ ์ธ์ฝ”๋”ฉ
247
+ input_ids = self.sp.encode(prompt)
248
+ states = get_init_state() # ์™ธ๋ถ€ ์ •์˜๋œ ์ดˆ๊ธฐ ์ƒํƒœ ํ•จ์ˆ˜ ํ˜ธ์ถœ
249
+ generated = []
250
+
251
+ # [1] Prefill ๋‹จ๊ณ„: ํ”„๋กฌํ”„ํŠธ์˜ ๋งˆ์ง€๋ง‰ ํ† ํฐ ์ „๊นŒ์ง€ ์ƒํƒœ๋ฅผ ๋ฏธ๋ฆฌ ๊ณ„์‚ฐ
252
+ if len(input_ids) > 1:
253
+ for i in range(len(input_ids) - 1):
254
+ _, states = self.model_step(tf.constant([[input_ids[i]]]), states)
255
+
256
+ # [2] Decoding ๋‹จ๊ณ„: ๋งˆ์ง€๋ง‰ ํ† ํฐ์„ ์‹œ์ž‘์œผ๋กœ ๋‹ค์Œ ํ† ํฐ๋“ค ์ƒ์„ฑ
257
+ curr_token_id = input_ids[-1]
258
+ prev_text = ""
259
+
260
+ for _ in range(max_new_tokens):
261
+ curr_token_tensor = tf.constant([[curr_token_id]])
262
+ logits_out, states = self.model_step(curr_token_tensor, states)
263
+
264
+ # (batch, seq, vocab) ๊ตฌ์กฐ์—์„œ ํ˜„์žฌ ํ† ํฐ์˜ ๋กœ์ง“๋งŒ ์ถ”์ถœ
265
+ logits = logits_out[0, 0].numpy()
266
+
267
+ # ํ›„์ฒ˜๋ฆฌ: ํŒจ๋„ํ‹ฐ ๋ฐ ํŒจ๋”ฉ ๋ฐฉ์ง€
268
+ logits = self.apply_repetition_penalty(logits, input_ids + generated, repetition_penalty)
269
+ logits[self.pad_id] = -float('inf')
270
+
271
+ # ์ƒ˜ํ”Œ๋ง ์‹คํ–‰
272
+ next_id = int(self.sample(logits, temperature, top_k, top_p))
273
+
274
+ # --- ์ค‘๋‹จ ์กฐ๊ฑด: EOS ํ† ํฐ ๊ฐ์ง€ ---
275
+ if next_id == self.eos_id:
276
+ break
277
+
278
+ generated.append(next_id)
279
+
280
+ # --- ๋„์–ด์“ฐ๊ธฐ ์œ ์ง€ ๋””์ฝ”๋”ฉ ๋กœ์ง ---
281
+ # ์ง€๊ธˆ๊นŒ์ง€ ์ƒ์„ฑ๋œ ํ† ํฐ๋“ค์„ ํ†ต์งธ๋กœ ๋””์ฝ”๋”ฉํ•œ ๋’ค,
282
+ # ์ด์ „์— ์ถœ๋ ฅํ•œ ํ…์ŠคํŠธ๋ฅผ ์ œ์™ธํ•œ ๋‚˜๋จธ์ง€(์ฆ๋ถ„)๋งŒ ์ถ”์ถœ
283
+ full_text = self.sp.decode(generated)
284
+ new_part = full_text[len(prev_text):]
285
+
286
+ if new_part:
287
+ yield new_part
288
+ prev_text = full_text
289
+
290
+ # ๋‹ค์Œ ๋ฃจํ”„๋ฅผ ์œ„ํ•ด ํ˜„์žฌ ํ† ํฐ ์—…๋ฐ์ดํŠธ
291
+ curr_token_id = next_id
292
+
293
+ # --- ์„ค์ • ๋ฐ ์—”์ง„ ์ดˆ๊ธฐํ™” ---
294
+ config = {
295
+ 'penalty_window': 64, # ์ตœ๊ทผ 64๊ฐœ ํ† ํฐ ์ด๋‚ด ๋ฐ˜๋ณต ์–ต์ œ
296
+ }
297
+
298
+ # InferenceEngine ์ธ์Šคํ„ด์Šค ์ƒ์„ฑ (blocklm, head, sp๋Š” ์™ธ๋ถ€ ์ •์˜ ์ƒํƒœ)
299
+ engine = InferenceEngine(blocklm, head, sp, config)
300
+
301
+ # --- ์‹คํ–‰ ๋ฃจํ”„ (Main Loop) ---
302
+ print("===== Advanced Dynamic RWKV Engine (No Omissions) =====")
303
+ while True:
304
+ try:
305
+ user_input = input("\n[User]: ").strip()
306
+ if user_input.lower() in ['exit', 'quit']:
307
+ print("ํ”„๋กœ๊ทธ๋žจ์„ ์ข…๋ฃŒํ•ฉ๋‹ˆ๋‹ค.")
308
+ break
309
+
310
+ if not user_input:
311
+ continue
312
+
313
+ # ๋ชจ๋ธ์—๊ฒŒ ๋‹ต๋ณ€ ํ˜•์‹์„ ๋ช…ํ™•ํžˆ ์ธ์ง€์‹œํ‚ค๋Š” ํ”„๋กฌํ”„ํŠธ ํ…œํ”Œ๋ฆฟ
314
+ # ๋ฌธ๋งฅ ์œ ์ง€๋ฅผ ์œ„ํ•ด ์งˆ๋ฌธ ๋’ค์— Answer:๋ฅผ ๋ถ™์ด๊ณ  ๊ณต๋ฐฑ์„ ์œ ๋„ํ•ฉ๋‹ˆ๋‹ค.
315
+ full_prompt = f"Question: {user_input}\nAnswer:"
316
+
317
+ print("[AI]:", end=" ", flush=True)
318
+
319
+ # ์ƒ์„ฑ ์‹œ์ž‘
320
+ for delta in engine.generate(
321
+ full_prompt,
322
+ max_new_tokens=1024,
323
+ temperature=0.7,
324
+ top_p=0.92,
325
+ repetition_penalty=1.2
326
+ ):
327
+ print(delta, end="", flush=True)
328
+
329
+ print() # ์ƒ์„ฑ ์ข…๋ฃŒ ํ›„ ์ค„๋ฐ”๊ฟˆ
330
+
331
+ except KeyboardInterrupt:
332
+ print("\n์ค‘๋‹จ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.")
333
+ break