model-prototype / Model.py
Yuchan
Update Model.py
4f2d40f verified
raw
history blame
14.9 kB
!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("<pad>") if sp.piece_to_id("<pad>") != -1 else 0
start_id = sp.piece_to_id("<start>")
sep_id = sp.piece_to_id("<sep>")
end_id = sp.piece_to_id("<end>")
unk_id = sp.piece_to_id("<unk>")
vocab_size = sp.get_piece_size()
print(f"βœ… Vocabulary size: {vocab_size}")
max_len = 200
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"<start> {prompt} <sep> {response} <end>"
if "<sep>" not in full:
continue
sep_index = full.index("<sep>")
input_text = full[:sep_index + len("<sep>")].strip()
target_text = full[sep_index + len("<sep>"):].strip()
input_ids = text_to_ids(input_text)
target_ids = text_to_ids(target_text + " <end>")
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(96, 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 LoSoU(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(96, dtype='float32')
self.K = layers.Dense(96, dtype='float32')
self.V = Lo(d_model) # Lo already handles casting to model dtype; we'll cast back to float32
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')
# 동적 alpha 계산을 μœ„ν•œ λ ˆμ΄μ–΄
# alphaλŠ” [0, 1] λ²”μœ„μ—¬μ•Ό ν•˜λ―€λ‘œ sigmoid μ‚¬μš©
# μž…λ ₯ x의 d_model 차원을 μ‚¬μš©ν•˜μ—¬ 각 μƒ˜ν”Œμ— λŒ€ν•΄ alpha 계산
# 예: (B, L, d_model) -> (B, L, 1) -> (B, L, 1) with sigmoid
# λ˜λŠ” (B, L, d_model) -> (B, L, d_model) -> global reduce -> (B, L, 1)
# κ°„λ‹¨νžˆ 각 μœ„μΉ˜μ— λŒ€ν•΄ λ™μΌν•œ alpha μ‚¬μš© (μž…λ ₯의 평균 기반)
# λ˜λŠ” μœ„μΉ˜λ³„λ‘œ λ‹€λ₯΄κ²Œ μ‚¬μš© (각 μœ„μΉ˜μ— λŒ€ν•΄ 계산)
# μ—¬κΈ°μ„œλŠ” μœ„μΉ˜λ³„λ‘œ λ‹€λ₯΄κ²Œ 계산 (B, L, 1)
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
# Q, K, V
q = self.Q(x_f32) # (B, L, 96)
k = self.K(x_f32) # (B, L, 96)
V = tf.cast(self.V(x), tf.float32) # ensure V's output is float32
# 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) * 0.8 + 0.1 # (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 Block(layers.Layer):
def __init__(self, d_model, hyper_n):
super().__init__()
self.losou = [LoSoU(d_model) for _ in range(hyper_n)]
def call(self, x):
for losou in self.losou:
x = losou(x)
return x
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, 128)
self.pos_embedding = layers.Embedding(max_seq_len, d_model)
self.blocks = [Block(d_model, hyper_n=1) 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=256,
n_layers=1
)
# μ˜΅ν‹°λ§ˆμ΄μ € μ„€μ •
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"<start> {prompt} <sep>")
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))