!pip install sentencepiece import sentencepiece as spm import os, json, numpy as np, tensorflow as tf from tensorflow.keras import layers, Model import requests from tensorflow import keras from tensorflow.keras import layers import tensorflow.keras.backend as K print('1') tf.get_logger().setLevel("ERROR") SEED = 42 tf.random.set_seed(SEED) np.random.seed(SEED) # TPU 초기화 try: resolver = tf.distribute.cluster_resolver.TPUClusterResolver(tpu="local") tf.tpu.experimental.initialize_tpu_system(resolver) strategy = tf.distribute.TPUStrategy(resolver) print("✅ TPU 초기화 완료:", resolver.cluster_spec().as_dict()) on_tpu = True except Exception as e: print("⚠️ TPU 미사용, GPU/CPU로 진행:", e) strategy = tf.distribute.get_strategy() on_tpu = False # Mixed precision from tensorflow.keras import mixed_precision policy = mixed_precision.Policy("mixed_bfloat16" if on_tpu else "float32") mixed_precision.set_global_policy(policy) print("✅ Mixed precision:", policy) # ======================= # 1) 파일 다운로드 # ======================= def download_file(url, save_path): r = requests.get(url, stream=True) r.raise_for_status() with open(save_path, "wb") as f: for chunk in r.iter_content(8192*2): f.write(chunk) print(f"✅ {save_path} 저장됨") DATA_PATH = "converted.jsonl" TOKENIZER_PATH = "ko_unigram.model" if not os.path.exists(DATA_PATH): download_file( "https://huggingface.co/datasets/Yuchan5386/SFT/resolve/main/data_shuffled_1.jsonl?download=true", DATA_PATH ) if not os.path.exists(TOKENIZER_PATH): download_file( "https://huggingface.co/Yuchan5386/inlam-100m/resolve/main/ko_unigram.model?download=true", TOKENIZER_PATH ) sp = spm.SentencePieceProcessor(TOKENIZER_PATH) pad_id = sp.piece_to_id("") if sp.piece_to_id("") != -1 else 0 start_id = sp.piece_to_id("") sep_id = sp.piece_to_id("") end_id = sp.piece_to_id("") unk_id = sp.piece_to_id("") vocab_size = sp.get_piece_size() print(f"✅ Vocabulary size: {vocab_size}") max_len = 512 batch_size = 128 def text_to_ids(text): return sp.encode(text, out_type=int) def ids_to_text(ids): return sp.decode(ids) def jsonl_stream(file_path): with open(file_path, "r", encoding="utf-8") as f: for line in f: data = json.loads(line) conversations = data.get("conversations", []) for i in range(0, len(conversations) - 1, 2): human_msg = conversations[i] gpt_msg = conversations[i + 1] if human_msg.get("from") != "human" or gpt_msg.get("from") != "gpt": continue prompt = human_msg.get("value", "").strip() response = gpt_msg.get("value", "").strip() full = f" {prompt} {response} " if "" not in full: continue sep_index = full.index("") input_text = full[:sep_index + len("")].strip() target_text = full[sep_index + len(""):].strip() input_ids = text_to_ids(input_text) target_ids = text_to_ids(target_text + " ") available_len = max_len - len(input_ids) if available_len <= 0: input_ids = input_ids[-max_len:] target_ids = [] target_mask = [0] * len(input_ids) else: target_ids = target_ids[:available_len] target_mask = [0] * len(input_ids) + [1] * len(target_ids) full_input = input_ids + target_ids pad_len = max_len - len(full_input) full_input += [pad_id] * pad_len target_mask += [0] * pad_len target_seq = full_input[1:] + [end_id] target_seq = target_seq[:max_len] masked_target = [ t if m == 1 else pad_id for t, m in zip(target_seq, target_mask) ] yield ( tf.convert_to_tensor(full_input, dtype=tf.int32), tf.convert_to_tensor(masked_target, dtype=tf.int32) ) dataset = tf.data.Dataset.from_generator( lambda: jsonl_stream(DATA_PATH), output_signature=( tf.TensorSpec(shape=(max_len,), dtype=tf.int32), tf.TensorSpec(shape=(max_len,), dtype=tf.int32), ), ) dataset = dataset.shuffle(1000, seed=SEED).batch(batch_size, drop_remainder=True).prefetch(tf.data.AUTOTUNE) with strategy.scope(): dist_dataset = strategy.experimental_distribute_dataset(dataset) class Lo(layers.Layer): def __init__(self, d_model): super().__init__() # 내부 계산은 float32로 유지 self.proj = layers.Dense(d_model, use_bias=True, dtype='float32') self.p = layers.Dense(128, use_bias=True, dtype='float32') self._out_dtype = 'float32' def call(self, x): # x may be bfloat16; cast to float32 for stable intermediate computation x_f32 = tf.cast(x, tf.float32) x = self.proj(x_f32) x = tf.nn.gelu(x) x = self.p(x) # cast back to model dtype for consistency return tf.cast(x, self._out_dtype) class LoU(layers.Layer): """ 안정화된 LoSoU 레이어 (동적 alpha 사용) - alpha 값을 입력에 따라 동적으로 계산: alpha = sigmoid(Linear(x)) - 누적합 대신 지수이동평균(EMA) 사용 (alpha: smoothing factor) - 내부 계산은 float32로 수행 (TPU bfloat16 안정성 향상) - EMA 결과 클리핑 및 작은 epsilon 적용 - 안전한 split 처리 (짝수 차원 가정; 아니라면 마지막 차원 pad 필요) """ def __init__(self, d_model, clip_value=5.0, eps=1e-6): super().__init__() # 대부분 연산을 float32로 수행 self.d_model = d_model self.clip_value = float(clip_value) self.eps = float(eps) # projection / gating layers in float32 self.Q = layers.Dense(d_model, dtype='float32') self.K = layers.Dense(d_model, dtype='float32') self.V = layers.Dense(d_model, dtype='float32') self.Qr = Lo(d_model) self.Kr = Lo(d_model) self.Vr = Lo(d_model) self.proj = layers.Dense(d_model, use_bias=True, dtype='float32') self.O = layers.Dense(d_model, dtype='float32') self.norm = layers.LayerNormalization(epsilon=1e-5, dtype='float32') self.norm1 = layers.LayerNormalization(epsilon=1e-5, dtype='float32') self.alpha_linear = layers.Dense(1, activation='sigmoid', dtype='float32') def _ema_over_time(self, score, alpha_dynamic): # score: (B, L, D) float32 in [0,1] roughly # alpha_dynamic: (B, L, 1) float32 in [0,1] # transpose to (L, B, D) to scan over time steps seq = tf.transpose(score, perm=[1, 0, 2]) # (L, B, D) alpha_seq = tf.transpose(alpha_dynamic, perm=[1, 0, 2]) # (L, B, 1) def step(prev_ema, inputs): x_t, alpha_t = inputs # prev_ema: (B, D), x_t: (B, D), alpha_t: (B, 1) new = alpha_t * x_t + (1.0 - alpha_t) * prev_ema return new # 초기값을 첫 step 값으로 설정 init = seq[0] # (B, D) first_alpha = alpha_seq[0] # (B, 1) # scan의 elems는 (L-1, B, D) 및 (L-1, B, 1) 이어야 함 remaining_seq = seq[1:] # (L-1, B, D) remaining_alpha = alpha_seq[1:] # (L-1, B, 1) # elems는 두 텐서의 튜플로 구성: (x_t, alpha_t) elems = (remaining_seq, remaining_alpha) ema_seq = tf.scan(fn=step, elems=elems, initializer=init) # 초기값 포함 ema_seq = tf.concat([tf.expand_dims(init, 0), ema_seq], axis=0) # (L, B, D) # transpose back to (B, L, D) ema = tf.transpose(ema_seq, perm=[1, 0, 2]) return ema def call(self, x): # x: (B, L, d_model) maybe bfloat16 or float32 # cast to float32 for all internal computations x_f32 = tf.cast(x, tf.float32) residual = x_f32 x_f32 = self.norm1(x) # Q, K, V q = self.Q(x_f32) k = self.K(x_f32) V = self.V(x_f32) q = self.Qr(q) k = self.Kr(k) v = self.Vr(v) # gating signals in (0,1) g_q = tf.nn.sigmoid(q) g_k = tf.nn.sigmoid(k) # elementwise product -> bounded roughly [0,1] score = g_q * g_k # 동적 alpha 계산: (B, L, d_model) -> (B, L, 1) alpha_dynamic = self.alpha_linear(x_f32) # (B, L, 1) # 필요시 alpha_dynamic에 대한 후처리 (예: min/max 등) 가능 # ex: alpha_dynamic = tf.clip_by_value(alpha_dynamic, 0.01, 0.99) # EMA across time (stable alternative to cumsum) score_ema = self._ema_over_time(score, alpha_dynamic) # optionally normalize by (mean + eps) across last dim to reduce scale variations mean_last = tf.reduce_mean(score_ema, axis=-1, keepdims=True) # (B, L, 1) denom = tf.maximum(mean_last, self.eps) score_norm = score_ema / denom # clip to avoid extremes score_clipped = tf.clip_by_value(score_norm, -self.clip_value, self.clip_value) # combine with V x_comb = score_clipped * V # (B, L, d_model) out = self.proj(x_comb) # (B, L, d_model) # ensure out dim even for split d = out.shape[-1] # this is an int (static shape) if d is not None and d % 2 == 1: out = tf.pad(out, [[0,0],[0,0],[0,1]]) a, b = tf.split(out, 2, axis=-1) gated = tf.nn.silu(a) * b out = self.O(gated) out = self.norm(out + residual) # cast back to original dtype for downstream layers return tf.cast(out, x.dtype) class ReLaM(tf.keras.Model): def __init__(self, vocab_size, max_seq_len, d_model, n_layers, dropout_rate=0.1): super().__init__() self.token_embedding = layers.Embedding(vocab_size, d_model) self.pos_embedding = layers.Embedding(max_seq_len, d_model) self.blocks = [LoU(d_model) for _ in range(n_layers)] # LayerNormalization은 float32로 해서 정밀도 문제 방지 self.ln_f = layers.LayerNormalization(epsilon=1e-5, dtype="float32") def call(self, x, training=False): batch_size, seq_len = tf.shape(x)[0], tf.shape(x)[1] positions = tf.range(seq_len)[tf.newaxis, :] x = self.token_embedding(x) + self.pos_embedding(positions) for block in self.blocks: x = block(x) x = self.ln_f(x) embedding_matrix = tf.cast(self.token_embedding.embeddings, x.dtype) logits = tf.matmul(x, embedding_matrix, transpose_b=True) return tf.cast(logits, tf.float32) loss_fn = tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True, reduction='none') def masked_loss(y_true, y_pred): loss = loss_fn(y_true, y_pred) mask = tf.cast(tf.not_equal(y_true, pad_id), tf.float32) masked_loss = tf.reduce_sum(loss * mask) / tf.reduce_sum(mask) return masked_loss def masked_perplexity(y_true, y_pred): loss = loss_fn(y_true, y_pred) mask = tf.cast(tf.not_equal(y_true, pad_id), tf.float32) avg_loss = tf.reduce_sum(loss * mask) / tf.reduce_sum(mask) return tf.exp(tf.minimum(avg_loss, 10.0)) # 수치 안정성 확보 def create_lr_schedule(initial_lr=5e-5, decay_steps=10000, decay_rate=0.9): return tf.keras.optimizers.schedules.ExponentialDecay( initial_learning_rate=initial_lr, decay_steps=decay_steps, decay_rate=decay_rate, staircase=False ) # 모델 생성 model = ReLaM( vocab_size=vocab_size, max_seq_len=max_len, d_model=512, n_layers=16 ) # 옵티마이저 설정 optimizer = tf.keras.optimizers.Adam( learning_rate=create_lr_schedule(), beta_1=0.9, beta_2=0.95, epsilon=1e-8, clipnorm=1.0 ) # 모델 컴파일 model.compile( optimizer=optimizer, loss=masked_loss, metrics=[ masked_perplexity ] ) # 더미 인풋으로 모델 초기화 dummy_input = np.zeros((1, max_len), dtype=np.int32) model(dummy_input) model.summary() # 학습 시작 history = model.fit( dataset, epochs=1, steps_per_epoch = encoded_inputs.shape[0] // batch_size, verbose=1 ) # 가중치 저장 model.save_weights("Cobra.weights.h5") print("모델 가중치 저장 완료!") def generate_text_topp(model, prompt, max_len=100, max_gen=98, p=0.9, temperature=0.8, min_len=20): model_input = text_to_ids(f" {prompt} ") model_input = model_input[:max_len] generated = list(model_input) for step in range(max_gen): if len(generated) > max_len: input_seq = generated[-max_len:] else: input_seq = generated input_padded = np.pad(input_seq, (0, max_len - len(input_seq)), constant_values=pad_id) input_tensor = tf.convert_to_tensor([input_padded]) logits = model(input_tensor, training=False) next_token_logits = logits[0, len(input_seq) - 1].numpy() next_token_logits[end_id] -= 5.0 next_token_logits[pad_id] -= 10.0 probs = tf.nn.softmax(next_token_logits / temperature).numpy() sorted_indices = np.argsort(probs)[::-1] sorted_probs = probs[sorted_indices] cumulative_probs = np.cumsum(sorted_probs) cutoff = np.searchsorted(cumulative_probs, p) top_indices = sorted_indices[:cutoff + 1] top_probs = sorted_probs[:cutoff + 1] top_probs /= np.sum(top_probs) next_token_id = np.random.choice(top_indices, p=top_probs) if next_token_id == end_id and len(generated) >= min_len: break generated.append(int(next_token_id)) return ids_to_text(generated) print("\n\n===== 생성 결과 =====") print(generate_text_topp(model, "안녕", p=0.9))