gbrabbit commited on
Commit
f480fa2
Β·
1 Parent(s): 1bec0cd

Auto commit at 22-2025-08 19:14:51

Browse files
lily_llm_api/app_v2.py CHANGED
@@ -20,6 +20,12 @@ import io
20
  import os
21
  import json
22
  from pathlib import Path
 
 
 
 
 
 
23
 
24
  # logging 섀정을 λ¨Όμ € ꡬ성
25
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
@@ -854,11 +860,25 @@ def generate_sync(prompt: str, image_data_list: Optional[List[bytes]], max_lengt
854
  print(f"πŸ” [DEBUG] κ²°ν•©λœ 이미지 ν…μ„œ shape: {pixel_values.shape}")
855
  print(f"πŸ” [DEBUG] 이미지 ν…μ„œ dtype: {pixel_values.dtype}")
856
 
 
 
 
 
 
 
 
 
 
 
 
 
 
857
  if pixel_values.device != model.device:
858
  print(f"πŸ” [DEBUG] 이미지 ν…μ„œλ₯Ό λͺ¨λΈ λ””λ°”μ΄μŠ€λ‘œ 이동: {pixel_values.device} -> {model.device}")
859
  pixel_values = pixel_values.to(model.device)
860
 
861
  print(f"πŸ” [DEBUG] μ΅œμ’… 이미지 ν…μ„œ λ””λ°”μ΄μŠ€: {pixel_values.device}")
 
862
  print(f"πŸ” [DEBUG] λͺ¨λΈ 생성 μ‹œμž‘ - λ©€ν‹°λͺ¨λ‹¬")
863
 
864
  # LoRA μ–΄λŒ‘ν„°κ°€ 적용된 λͺ¨λΈμΈμ§€ 확인
@@ -869,18 +889,31 @@ def generate_sync(prompt: str, image_data_list: Optional[List[bytes]], max_lengt
869
  if lora_model:
870
  print(f"πŸ” [DEBUG] LoRA λͺ¨λΈλ‘œ λ©€ν‹°λͺ¨λ‹¬ 생성 μ‹€ν–‰")
871
  # πŸ”„ image_metas νŒŒλΌλ―Έν„° μΆ”κ°€ (곡식 방식)
872
- # πŸ”„ 메타데이터λ₯Ό ν…μ„œλ‘œ λ³€ν™˜ (λͺ¨λΈ μš”κ΅¬μ‚¬ν•­)
873
  import torch
874
  processed_image_metas = {}
875
- for key, value in combined_image_metas.items():
876
- if isinstance(value, list):
877
- if key == 'vision_grid_thw':
878
- # πŸ”„ vision_grid_thwλ₯Ό ν…μ„œλ‘œ λ³€ν™˜
879
- processed_image_metas[key] = torch.tensor(value, dtype=torch.long)
880
- print(f"πŸ” [DEBUG] {key} ν…μ„œ λ³€ν™˜: {value} -> {processed_image_metas[key].shape}")
 
 
 
 
 
 
881
  else:
882
- processed_image_metas[key] = value
 
 
883
  else:
 
 
 
 
 
884
  processed_image_metas[key] = value
885
 
886
  generate_kwargs = {
@@ -892,21 +925,43 @@ def generate_sync(prompt: str, image_data_list: Optional[List[bytes]], max_lengt
892
  }
893
  print(f"πŸ” [DEBUG] LoRA λͺ¨λΈ 생성 νŒŒλΌλ―Έν„°: {list(generate_kwargs.keys())}")
894
  print(f"πŸ” [DEBUG] 처리된 image_metas: {list(processed_image_metas.keys())}")
 
 
 
 
 
 
 
 
 
895
  generated_ids = lora_model.generate(**generate_kwargs)
896
  else:
897
  print(f"⚠️ [DEBUG] LoRA λͺ¨λΈμ„ κ°€μ Έμ˜¬ 수 μ—†μŒ, κΈ°λ³Έ λͺ¨λΈ μ‚¬μš©")
898
  # πŸ”„ image_metas νŒŒλΌλ―Έν„° μΆ”κ°€ (곡식 방식)
899
- # πŸ”„ 메타데이터λ₯Ό ν…μ„œλ‘œ λ³€ν™˜ (λͺ¨λΈ μš”κ΅¬μ‚¬ν•­)
900
  processed_image_metas = {}
901
- for key, value in combined_image_metas.items():
902
- if isinstance(value, list):
903
- if key == 'vision_grid_thw':
904
- # πŸ”„ vision_grid_thwλ₯Ό ν…μ„œλ‘œ λ³€ν™˜
905
- processed_image_metas[key] = torch.tensor(value, dtype=torch.long)
906
- print(f"πŸ” [DEBUG] {key} ν…μ„œ λ³€ν™˜: {value} -> {processed_image_metas[key].shape}")
 
 
 
 
 
 
907
  else:
908
- processed_image_metas[key] = value
 
 
909
  else:
 
 
 
 
 
910
  processed_image_metas[key] = value
911
 
912
  generate_kwargs = {
@@ -922,17 +977,30 @@ def generate_sync(prompt: str, image_data_list: Optional[List[bytes]], max_lengt
922
  else:
923
  print(f"πŸ” [DEBUG] LoRA μ–΄λŒ‘ν„° μ—†μŒ (λ©€ν‹°λͺ¨λ‹¬), κΈ°λ³Έ ���델 μ‚¬μš©")
924
  # πŸ”„ image_metas νŒŒλΌλ―Έν„° μΆ”κ°€ (곡식 방식)
925
- # πŸ”„ 메타데이터λ₯Ό ν…μ„œλ‘œ λ³€ν™˜ (λͺ¨λΈ μš”κ΅¬μ‚¬ν•­)
926
  processed_image_metas = {}
927
- for key, value in combined_image_metas.items():
928
- if isinstance(value, list):
929
- if key == 'vision_grid_thw':
930
- # πŸ”„ vision_grid_thwλ₯Ό ν…μ„œλ‘œ λ³€ν™˜
931
- processed_image_metas[key] = torch.tensor(value, dtype=torch.long)
932
- print(f"πŸ” [DEBUG] {key} ν…μ„œ λ³€ν™˜: {value} -> {processed_image_metas[key].shape}")
 
 
 
 
 
 
933
  else:
934
- processed_image_metas[key] = value
 
 
935
  else:
 
 
 
 
 
936
  processed_image_metas[key] = value
937
 
938
  generate_kwargs = {
 
20
  import os
21
  import json
22
  from pathlib import Path
23
+ import warnings
24
+
25
+ # πŸ”„ RoPE κ²½κ³  숨기기 (Kanana λͺ¨λΈ λ‚΄λΆ€ κ΅¬ν˜„ κ΄€λ ¨)
26
+ warnings.filterwarnings("ignore", message="The attention layers in this model are transitioning")
27
+ warnings.filterwarnings("ignore", message="rotary_pos_emb will be removed")
28
+ warnings.filterwarnings("ignore", message="position_embeddings will be mandatory")
29
 
30
  # logging 섀정을 λ¨Όμ € ꡬ성
31
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
 
860
  print(f"πŸ” [DEBUG] κ²°ν•©λœ 이미지 ν…μ„œ shape: {pixel_values.shape}")
861
  print(f"πŸ” [DEBUG] 이미지 ν…μ„œ dtype: {pixel_values.dtype}")
862
 
863
+ # πŸ”„ λͺ¨λΈκ³Ό λ™μΌν•œ dtype으둜 λ³€ν™˜ (μ„±λŠ₯ μ΅œμ ν™”)
864
+ if hasattr(model, 'dtype'):
865
+ target_dtype = model.dtype
866
+ if pixel_values.dtype != target_dtype:
867
+ print(f"πŸ” [DEBUG] 이미지 ν…μ„œ dtype λ³€ν™˜: {pixel_values.dtype} -> {target_dtype}")
868
+ pixel_values = pixel_values.to(dtype=target_dtype)
869
+ else:
870
+ # πŸ”„ λͺ¨λΈ dtype을 μ•Œ 수 μ—†λŠ” 경우 bfloat16 μ‚¬μš© (Kanana λͺ¨λΈ κΈ°λ³Έκ°’)
871
+ target_dtype = torch.bfloat16
872
+ if pixel_values.dtype != target_dtype:
873
+ print(f"πŸ” [DEBUG] 이미지 ν…μ„œ dtype λ³€ν™˜: {pixel_values.dtype} -> {target_dtype}")
874
+ pixel_values = pixel_values.to(dtype=target_dtype)
875
+
876
  if pixel_values.device != model.device:
877
  print(f"πŸ” [DEBUG] 이미지 ν…μ„œλ₯Ό λͺ¨λΈ λ””λ°”μ΄μŠ€λ‘œ 이동: {pixel_values.device} -> {model.device}")
878
  pixel_values = pixel_values.to(model.device)
879
 
880
  print(f"πŸ” [DEBUG] μ΅œμ’… 이미지 ν…μ„œ λ””λ°”μ΄μŠ€: {pixel_values.device}")
881
+ print(f"πŸ” [DEBUG] μ΅œμ’… 이미지 ν…μ„œ dtype: {pixel_values.dtype}")
882
  print(f"πŸ” [DEBUG] λͺ¨λΈ 생성 μ‹œμž‘ - λ©€ν‹°λͺ¨λ‹¬")
883
 
884
  # LoRA μ–΄λŒ‘ν„°κ°€ 적용된 λͺ¨λΈμΈμ§€ 확인
 
889
  if lora_model:
890
  print(f"πŸ” [DEBUG] LoRA λͺ¨λΈλ‘œ λ©€ν‹°λͺ¨λ‹¬ 생성 μ‹€ν–‰")
891
  # πŸ”„ image_metas νŒŒλΌλ―Έν„° μΆ”κ°€ (곡식 방식)
892
+ # πŸ”„ 메타데이터λ₯Ό 곡식 ꡬ쑰둜 λ³€ν™˜ (λͺ¨λΈ μš”κ΅¬μ‚¬ν•­)
893
  import torch
894
  processed_image_metas = {}
895
+
896
+ # πŸ”„ 곡식 방식: vision_grid_thwλ₯Ό ν…μ„œλ‘œ λ³€ν™˜
897
+ if 'vision_grid_thw' in combined_image_metas:
898
+ vision_grid = combined_image_metas['vision_grid_thw']
899
+ if isinstance(vision_grid, list):
900
+ # πŸ”„ Kanana λͺ¨λΈ μš”κ΅¬μ‚¬ν•­: (T, H, W) ν˜•νƒœμ˜ 3차원 ν…μ„œ
901
+ if len(vision_grid) == 1 and len(vision_grid[0]) == 3:
902
+ # [(1, 34, 52)] -> (1, 34, 52) ν…μ„œλ‘œ λ³€ν™˜
903
+ t, h, w = vision_grid[0]
904
+ # πŸ”„ 3차원 ν…μ„œλ‘œ λ³€ν™˜: (1, H, W) ν˜•νƒœ
905
+ processed_image_metas['vision_grid_thw'] = torch.tensor([[t, h, w]], dtype=torch.long)
906
+ print(f"πŸ” [DEBUG] vision_grid_thw ν…μ„œ λ³€ν™˜: {vision_grid} -> {processed_image_metas['vision_grid_thw'].shape}")
907
  else:
908
+ # πŸ”„ λ‹€λ₯Έ ν˜•νƒœμ˜ 경우 원본 μœ μ§€
909
+ processed_image_metas['vision_grid_thw'] = torch.tensor(vision_grid, dtype=torch.long)
910
+ print(f"πŸ” [DEBUG] vision_grid_thw ν…μ„œ λ³€ν™˜ (κΈ°οΏ½οΏ½): {vision_grid} -> {processed_image_metas['vision_grid_thw'].shape}")
911
  else:
912
+ processed_image_metas['vision_grid_thw'] = vision_grid
913
+
914
+ # πŸ”„ λ‹€λ₯Έ λ©”νƒ€λ°μ΄ν„°λŠ” κ·ΈλŒ€λ‘œ μœ μ§€
915
+ for key, value in combined_image_metas.items():
916
+ if key != 'vision_grid_thw':
917
  processed_image_metas[key] = value
918
 
919
  generate_kwargs = {
 
925
  }
926
  print(f"πŸ” [DEBUG] LoRA λͺ¨λΈ 생성 νŒŒλΌλ―Έν„°: {list(generate_kwargs.keys())}")
927
  print(f"πŸ” [DEBUG] 처리된 image_metas: {list(processed_image_metas.keys())}")
928
+ print(f"πŸ” [DEBUG] λͺ¨λΈ 생성 μ‹œμž‘... (νƒ€μž„μ•„μ›ƒ μ—†μŒ)")
929
+
930
+ # πŸ”„ 생성 μ „ μ΅œμ’… 검증
931
+ print(f"πŸ” [DEBUG] μ΅œμ’… νŒŒλΌλ―Έν„° 검증:")
932
+ print(f" - input_ids: {input_ids.shape}, dtype: {input_ids.dtype}")
933
+ print(f" - attention_mask: {attention_mask.shape}, dtype: {attention_mask.dtype}")
934
+ print(f" - pixel_values: {pixel_values.shape}, dtype: {pixel_values.dtype}")
935
+ print(f" - vision_grid_thw: {processed_image_metas.get('vision_grid_thw', 'None')}")
936
+
937
  generated_ids = lora_model.generate(**generate_kwargs)
938
  else:
939
  print(f"⚠️ [DEBUG] LoRA λͺ¨λΈμ„ κ°€μ Έμ˜¬ 수 μ—†μŒ, κΈ°λ³Έ λͺ¨λΈ μ‚¬μš©")
940
  # πŸ”„ image_metas νŒŒλΌλ―Έν„° μΆ”κ°€ (곡식 방식)
941
+ # πŸ”„ 메타데이터λ₯Ό 곡식 ꡬ쑰둜 λ³€ν™˜ (λͺ¨λΈ μš”κ΅¬μ‚¬ν•­)
942
  processed_image_metas = {}
943
+
944
+ # πŸ”„ 곡식 방식: vision_grid_thwλ₯Ό ν…μ„œλ‘œ λ³€ν™˜
945
+ if 'vision_grid_thw' in combined_image_metas:
946
+ vision_grid = combined_image_metas['vision_grid_thw']
947
+ if isinstance(vision_grid, list):
948
+ # πŸ”„ Kanana λͺ¨λΈ μš”κ΅¬μ‚¬ν•­: (T, H, W) ν˜•νƒœμ˜ 3차원 ν…μ„œ
949
+ if len(vision_grid) == 1 and len(vision_grid[0]) == 3:
950
+ # [(1, 34, 52)] -> (1, 34, 52) ν…μ„œλ‘œ λ³€ν™˜
951
+ t, h, w = vision_grid[0]
952
+ # πŸ”„ 3차원 ν…μ„œλ‘œ λ³€ν™˜: (1, H, W) ν˜•νƒœ
953
+ processed_image_metas['vision_grid_thw'] = torch.tensor([[t, h, w]], dtype=torch.long)
954
+ print(f"πŸ” [DEBUG] vision_grid_thw ν…μ„œ λ³€ν™˜: {vision_grid} -> {processed_image_metas['vision_grid_thw'].shape}")
955
  else:
956
+ # πŸ”„ λ‹€λ₯Έ ν˜•νƒœμ˜ 경우 원본 μœ μ§€
957
+ processed_image_metas['vision_grid_thw'] = torch.tensor(vision_grid, dtype=torch.long)
958
+ print(f"πŸ” [DEBUG] vision_grid_thw ν…μ„œ λ³€ν™˜ (κΈ°λ³Έ): {vision_grid} -> {processed_image_metas['vision_grid_thw'].shape}")
959
  else:
960
+ processed_image_metas['vision_grid_thw'] = vision_grid
961
+
962
+ # πŸ”„ λ‹€λ₯Έ λ©”νƒ€λ°μ΄ν„°λŠ” κ·ΈλŒ€λ‘œ μœ μ§€
963
+ for key, value in combined_image_metas.items():
964
+ if key != 'vision_grid_thw':
965
  processed_image_metas[key] = value
966
 
967
  generate_kwargs = {
 
977
  else:
978
  print(f"πŸ” [DEBUG] LoRA μ–΄λŒ‘ν„° μ—†μŒ (λ©€ν‹°λͺ¨λ‹¬), κΈ°λ³Έ ���델 μ‚¬μš©")
979
  # πŸ”„ image_metas νŒŒλΌλ―Έν„° μΆ”κ°€ (곡식 방식)
980
+ # πŸ”„ 메타데이터λ₯Ό 곡식 ꡬ쑰둜 λ³€ν™˜ (λͺ¨λΈ μš”κ΅¬μ‚¬ν•­)
981
  processed_image_metas = {}
982
+
983
+ # πŸ”„ 곡식 방식: vision_grid_thwλ₯Ό ν…μ„œλ‘œ λ³€ν™˜
984
+ if 'vision_grid_thw' in combined_image_metas:
985
+ vision_grid = combined_image_metas['vision_grid_thw']
986
+ if isinstance(vision_grid, list):
987
+ # πŸ”„ Kanana λͺ¨λΈ μš”κ΅¬μ‚¬ν•­: (T, H, W) ν˜•νƒœμ˜ 3차원 ν…μ„œ
988
+ if len(vision_grid) == 1 and len(vision_grid[0]) == 3:
989
+ # [(1, 34, 52)] -> (1, 34, 52) ν…μ„œλ‘œ λ³€ν™˜
990
+ t, h, w = vision_grid[0]
991
+ # πŸ”„ 3차원 ν…μ„œλ‘œ λ³€ν™˜: (1, H, W) ν˜•νƒœ
992
+ processed_image_metas['vision_grid_thw'] = torch.tensor([[t, h, w]], dtype=torch.long)
993
+ print(f"πŸ” [DEBUG] vision_grid_thw ν…μ„œ λ³€ν™˜: {vision_grid} -> {processed_image_metas['vision_grid_thw'].shape}")
994
  else:
995
+ # πŸ”„ λ‹€λ₯Έ ν˜•νƒœμ˜ 경우 원본 μœ μ§€
996
+ processed_image_metas['vision_grid_thw'] = torch.tensor(vision_grid, dtype=torch.long)
997
+ print(f"πŸ” [DEBUG] vision_grid_thw ν…μ„œ λ³€ν™˜ (κΈ°λ³Έ): {vision_grid} -> {processed_image_metas['vision_grid_thw'].shape}")
998
  else:
999
+ processed_image_metas['vision_grid_thw'] = vision_grid
1000
+
1001
+ # πŸ”„ λ‹€λ₯Έ λ©”νƒ€λ°μ΄ν„°λŠ” κ·ΈλŒ€λ‘œ μœ μ§€
1002
+ for key, value in combined_image_metas.items():
1003
+ if key != 'vision_grid_thw':
1004
  processed_image_metas[key] = value
1005
 
1006
  generate_kwargs = {
lily_llm_api/app_v2_250822_1812.py ADDED
The diff for this file is too large to render. See raw diff
 
lily_llm_core/lora_manager.py CHANGED
@@ -285,11 +285,27 @@ class LoRAManager:
285
  if base_model_path:
286
  self.load_base_model(base_model_path)
287
 
288
- # LoRA μ–΄λŒ‘ν„° λ‘œλ“œ
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
289
  self.lora_model = PeftModel.from_pretrained(
290
  self.base_model,
291
  adapter_path,
292
- torch_dtype=torch.float16 if self.device == "cuda" else torch.float32
293
  )
294
 
295
  # λ””λ°”μ΄μŠ€λ‘œ 이동
 
285
  if base_model_path:
286
  self.load_base_model(base_model_path)
287
 
288
+ # πŸ”„ λͺ¨λΈ νƒ€μž…μ— λ”°λ₯Έ dtype κ²°μ •
289
+ if hasattr(self.base_model, 'config') and hasattr(self.base_model.config, 'model_type'):
290
+ model_type = self.base_model.config.model_type
291
+ if model_type in ['kanana-1.5-v', 'vision2seq']:
292
+ # πŸ”„ Kanana λͺ¨λΈ: bfloat16 μ‚¬μš©
293
+ selected_dtype = torch.bfloat16
294
+ logger.info(f"πŸ” [DEBUG] Kanana λͺ¨λΈ 감지: {model_type} -> bfloat16 μ‚¬μš©")
295
+ else:
296
+ # πŸ”„ 기타 λͺ¨λΈ: κΈ°μ‘΄ 둜직 μ‚¬μš©
297
+ selected_dtype = torch.float16 if self.device == "cuda" else torch.float32
298
+ logger.info(f"πŸ” [DEBUG] 일반 λͺ¨λΈ 감지: {model_type} -> {selected_dtype} μ‚¬μš©")
299
+ else:
300
+ # πŸ”„ λͺ¨λΈ νƒ€μž…μ„ μ•Œ 수 μ—†λŠ” 경우: κΈ°μ‘΄ 둜직 μ‚¬μš©
301
+ selected_dtype = torch.float16 if self.device == "cuda" else torch.float32
302
+ logger.info(f"πŸ” [DEBUG] λͺ¨λΈ νƒ€μž… 미확인 -> {selected_dtype} μ‚¬μš©")
303
+
304
+ # LoRA μ–΄λŒ‘ν„° λ‘œλ“œ (λͺ¨λΈλ³„ 졜적 dtype μ‚¬μš©)
305
  self.lora_model = PeftModel.from_pretrained(
306
  self.base_model,
307
  adapter_path,
308
+ torch_dtype=selected_dtype
309
  )
310
 
311
  # λ””λ°”μ΄μŠ€λ‘œ 이동
lily_llm_core/lora_manager_250822_1812.py ADDED
@@ -0,0 +1,589 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ LoRA/QLoRA κ΄€λ¦¬μž (LoRA Manager)
4
+ LoRA μ–΄λŒ‘ν„°λ₯Ό λ‘œλ“œν•˜κ³  κ΄€λ¦¬ν•˜λŠ” μ‹œμŠ€ν…œ
5
+ """
6
+
7
+ import logging
8
+ import os
9
+ import json
10
+ import torch
11
+ from typing import Dict, Any, Optional, List, Union
12
+ from pathlib import Path
13
+ import warnings
14
+ import time
15
+
16
+ # loggerλ₯Ό λ¨Όμ € μ •μ˜
17
+ logger = logging.getLogger(__name__)
18
+
19
+ # PEFT κ΄€λ ¨ import (μ„€μΉ˜λ˜μ§€ μ•Šμ€ 경우 κ²½κ³ )
20
+ try:
21
+ logger.info("πŸ” PEFT 라이브러리 import μ‹œλ„ 쀑...")
22
+ from peft import (
23
+ LoraConfig,
24
+ get_peft_model,
25
+ PeftModel,
26
+ TaskType,
27
+ prepare_model_for_kbit_training
28
+ )
29
+ from peft.utils import get_peft_model_state_dict
30
+ PEFT_AVAILABLE = True
31
+ logger.info("βœ… PEFT 라이브러리 import 성곡")
32
+ except ImportError as e:
33
+ PEFT_AVAILABLE = False
34
+ logger.error(f"❌ PEFT 라이브러리 import μ‹€νŒ¨: {e}")
35
+ logger.error(f"❌ Python 경둜: {os.environ.get('PYTHONPATH', 'Not set')}")
36
+ logger.error(f"❌ ν˜„μž¬ μž‘μ—… 디렉토리: {os.getcwd()}")
37
+ warnings.warn(f"PEFT λΌμ΄λΈŒλŸ¬λ¦¬κ°€ μ„€μΉ˜λ˜μ§€ μ•Šμ•˜μŠ΅λ‹ˆλ‹€. LoRA κΈ°λŠ₯을 μ‚¬μš©ν•  수 μ—†μŠ΅λ‹ˆλ‹€. 였λ₯˜: {e}")
38
+
39
+ # Transformers κ΄€λ ¨ import
40
+ try:
41
+ logger.info("πŸ” Transformers 라이브러리 import μ‹œλ„ 쀑...")
42
+ from transformers import (
43
+ AutoModelForCausalLM,
44
+ AutoTokenizer,
45
+ BitsAndBytesConfig,
46
+ TrainingArguments,
47
+ Trainer,
48
+ DataCollatorForLanguageModeling
49
+ )
50
+ TRANSFORMERS_AVAILABLE = True
51
+ logger.info("βœ… Transformers 라이브러리 import 성곡")
52
+ except ImportError as e:
53
+ TRANSFORMERS_AVAILABLE = False
54
+ logger.error(f"❌ Transformers 라이브러리 import μ‹€νŒ¨: {e}")
55
+ warnings.warn(f"Transformers λΌμ΄λΈŒλŸ¬λ¦¬κ°€ μ„€μΉ˜λ˜μ§€ μ•Šμ•˜μŠ΅λ‹ˆλ‹€. 였λ₯˜: {e}")
56
+
57
+ class LoRAManager:
58
+ """LoRA/QLoRA λͺ¨λΈ 관리 클래슀"""
59
+
60
+ def __init__(self, base_model_path: str = None, device: str = "auto"):
61
+ """
62
+ Args:
63
+ base_model_path: κΈ°λ³Έ λͺ¨λΈ 경둜
64
+ device: μ‚¬μš©ν•  λ””λ°”μ΄μŠ€ ('auto', 'cpu', 'cuda', 'mps')
65
+ """
66
+ logger.info(f"πŸ”§ LoRA κ΄€λ¦¬μž μ΄ˆκΈ°ν™” μ‹œμž‘: PEFT_AVAILABLE={PEFT_AVAILABLE}, TRANSFORMERS_AVAILABLE={TRANSFORMERS_AVAILABLE}")
67
+
68
+ if not PEFT_AVAILABLE:
69
+ logger.error("❌ PEFT 라이브러리λ₯Ό μ‚¬μš©ν•  수 μ—†μŠ΅λ‹ˆλ‹€.")
70
+ logger.error("❌ pip install peftλ₯Ό μ‹€ν–‰ν–ˆλŠ”μ§€ ν™•μΈν•˜μ„Έμš”.")
71
+ logger.error("❌ κ°€μƒν™˜κ²½μ΄ ν™œμ„±ν™”λ˜μ–΄ μžˆλŠ”μ§€ ν™•μΈν•˜μ„Έμš”.")
72
+ raise ImportError("PEFT λΌμ΄λΈŒλŸ¬λ¦¬κ°€ ν•„μš”ν•©λ‹ˆλ‹€. pip install peftλ₯Ό μ‹€ν–‰ν•˜μ„Έμš”.")
73
+
74
+ if not TRANSFORMERS_AVAILABLE:
75
+ logger.error("❌ Transformers 라이브러리λ₯Ό μ‚¬μš©ν•  수 μ—†μŠ΅λ‹ˆλ‹€.")
76
+ logger.error("❌ pip install transformersλ₯Ό μ‹€ν–‰ν–ˆλŠ”μ§€ ν™•μΈν•˜μ„Έμš”.")
77
+ raise ImportError("Transformers λΌμ΄λΈŒλŸ¬λ¦¬κ°€ ν•„μš”ν•©λ‹ˆλ‹€. pip install transformersλ₯Ό μ‹€ν–‰ν•˜μ„Έμš”.")
78
+
79
+ self.base_model_path = base_model_path
80
+ self.device = self._get_device(device)
81
+
82
+ # λͺ¨λΈ 및 ν† ν¬λ‚˜μ΄μ €
83
+ self.base_model = None
84
+ self.tokenizer = None
85
+ self.lora_model = None
86
+
87
+ # LoRA μ„€μ •
88
+ self.lora_config = None
89
+ self.current_adapter_name = None
90
+
91
+ # μ–΄λŒ‘ν„° μ €μž₯ 경둜
92
+ self.adapters_dir = Path("lora_adapters")
93
+ self.adapters_dir.mkdir(exist_ok=True)
94
+
95
+ # λ‘œλ“œλœ μ–΄λŒ‘ν„° λͺ©λ‘
96
+ self.loaded_adapters = {}
97
+
98
+ logger.info(f"πŸ”§ LoRA κ΄€λ¦¬μž μ΄ˆκΈ°ν™”: device={self.device}")
99
+
100
+ def get_model(self):
101
+ """ν˜„μž¬ LoRA λͺ¨λΈ λ°˜ν™˜"""
102
+ if self.lora_model is not None:
103
+ return self.lora_model
104
+ elif self.base_model is not None:
105
+ return self.base_model
106
+ else:
107
+ logger.warning("⚠️ λ‘œλ“œλœ λͺ¨λΈμ΄ μ—†μŠ΅λ‹ˆλ‹€.")
108
+ return None
109
+
110
+ def _get_device(self, device: str) -> str:
111
+ """μ‚¬μš© κ°€λŠ₯ν•œ λ””λ°”μ΄μŠ€ 확인"""
112
+ if device == "auto":
113
+ if torch.cuda.is_available():
114
+ return "cuda"
115
+ elif torch.backends.mps.is_available():
116
+ return "mps"
117
+ else:
118
+ return "cpu"
119
+ return device
120
+
121
+ def load_base_model(self, model_path: str = None, model_type: str = "causal_lm") -> bool:
122
+ """κΈ°λ³Έ λͺ¨λΈ λ‘œλ“œ"""
123
+ try:
124
+ model_path = model_path or self.base_model_path
125
+ if not model_path:
126
+ raise ValueError("λͺ¨λΈ κ²½λ‘œκ°€ μ§€μ •λ˜μ§€ μ•Šμ•˜μŠ΅λ‹ˆλ‹€.")
127
+
128
+ logger.info(f"πŸ“₯ κΈ°λ³Έ λͺ¨λΈ λ‘œλ”© μ‹œμž‘: {model_path}")
129
+
130
+ # 경둜 μ •κ·œν™” 및 쑴재 확인
131
+ model_path = Path(model_path).resolve()
132
+ if not model_path.exists():
133
+ raise FileNotFoundError(f"λͺ¨λΈ κ²½οΏ½οΏ½οΏ½κ°€ μ‘΄μž¬ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€: {model_path}")
134
+
135
+ logger.info(f"πŸ” λͺ¨λΈ 경둜 확인: {model_path}")
136
+ logger.info(f"πŸ” 경둜 쑴재: {model_path.exists()}")
137
+ logger.info(f"πŸ” μ ˆλŒ€ 경둜: {model_path.absolute()}")
138
+
139
+ # ν† ν¬λ‚˜μ΄μ € λ‘œλ“œ
140
+ self.tokenizer = AutoTokenizer.from_pretrained(
141
+ str(model_path),
142
+ trust_remote_code=True,
143
+ local_files_only=True
144
+ )
145
+
146
+ # νŒ¨λ”© 토큰 μ„€μ •
147
+ if self.tokenizer.pad_token is None:
148
+ self.tokenizer.pad_token = self.tokenizer.eos_token
149
+
150
+ # λͺ¨λΈ λ‘œλ“œ
151
+ if model_type == "causal_lm":
152
+ self.base_model = AutoModelForCausalLM.from_pretrained(
153
+ str(model_path),
154
+ trust_remote_code=True,
155
+ local_files_only=True,
156
+ torch_dtype=torch.float16 if self.device == "cuda" else torch.float32,
157
+ device_map="auto" if self.device == "cuda" else None
158
+ )
159
+ elif model_type == "vision2seq":
160
+ # πŸ”„ Vision2Seq λͺ¨λΈ 지원 μΆ”κ°€ (kanana λ“±)
161
+ from transformers import AutoModelForVision2Seq
162
+ self.base_model = AutoModelForVision2Seq.from_pretrained(
163
+ str(model_path),
164
+ trust_remote_code=True,
165
+ local_files_only=True,
166
+ torch_dtype=torch.bfloat16 if self.device == "cuda" else torch.bfloat16,
167
+ device_map="auto" if self.device == "cuda" else None
168
+ )
169
+ else:
170
+ raise ValueError(f"μ§€μ›ν•˜μ§€ μ•ŠλŠ” λͺ¨λΈ νƒ€μž…: {model_type}")
171
+
172
+ # λ””λ°”μ΄μŠ€λ‘œ 이동
173
+ if self.device != "cuda": # cudaλŠ” device_map="auto" μ‚¬μš©
174
+ self.base_model = self.base_model.to(self.device)
175
+
176
+ self.base_model_path = model_path
177
+ logger.info(f"βœ… κΈ°λ³Έ λͺ¨λΈ λ‘œλ”© μ™„λ£Œ: {model_path}")
178
+ return True
179
+
180
+ except Exception as e:
181
+ logger.error(f"❌ κΈ°λ³Έ λͺ¨λΈ λ‘œλ”© μ‹€νŒ¨: {e}")
182
+ return False
183
+
184
+ def create_lora_config(self,
185
+ r: int = 16,
186
+ lora_alpha: int = 32,
187
+ target_modules: List[str] = None,
188
+ lora_dropout: float = 0.1,
189
+ bias: str = "none",
190
+ task_type: str = "CAUSAL_LM") -> LoraConfig:
191
+ """LoRA μ„€μ • 생성"""
192
+ if target_modules is None:
193
+ # 일반적인 λͺ¨λΈ μ•„ν‚€ν…μ²˜μ— λŒ€ν•œ κΈ°λ³Έκ°’
194
+ target_modules = ["q_proj", "v_proj", "k_proj", "o_proj", "gate_proj", "up_proj", "down_proj"]
195
+
196
+ # TaskType λ³€ν™˜ (μ•ˆμ „ν•œ 방식)
197
+ logger.info(f"πŸ” [DEBUG] μž…λ ₯된 task_type: {task_type}")
198
+
199
+ try:
200
+ # 직접 TaskType μ‚¬μš© (λ¬Έμžμ—΄ λ³€ν™˜ 제거)
201
+ if task_type == "CAUSAL_LM":
202
+ task_type_enum = TaskType.CAUSAL_LM
203
+ elif task_type == "VISION_2_SEQ":
204
+ # πŸ”„ Vision2Seq λͺ¨λΈ 지원 μΆ”κ°€
205
+ task_type_enum = TaskType.SEQ_2_SEQ_LM # Vision2SeqλŠ” SEQ_2_SEQ_LMκ³Ό μœ μ‚¬
206
+ elif task_type == "SEQ_2_SEQ_LM":
207
+ task_type_enum = TaskType.SEQ_2_SEQ_LM
208
+ elif task_type == "SEQUENCE_CLASSIFICATION":
209
+ task_type_enum = TaskType.SEQUENCE_CLASSIFICATION
210
+ elif task_type == "TOKEN_CLASSIFICATION":
211
+ task_type_enum = TaskType.TOKEN_CLASSIFICATION
212
+ elif task_type == "QUESTION_ANSWERING":
213
+ task_type_enum = TaskType.QUESTION_ANSWERING
214
+ else:
215
+ # κΈ°λ³Έκ°’μœΌλ‘œ CAUSAL_LM μ‚¬μš©
216
+ task_type_enum = TaskType.CAUSAL_LM
217
+ logger.warning(f"⚠️ μ•Œ 수 μ—†λŠ” task_type: {task_type}, κΈ°λ³Έκ°’ CAUSAL_LM μ‚¬μš©")
218
+ except Exception as e:
219
+ logger.error(f"❌ TaskType λ³€ν™˜ μ‹€νŒ¨: {e}, κΈ°λ³Έκ°’ CAUSAL_LM μ‚¬μš©")
220
+ task_type_enum = TaskType.CAUSAL_LM
221
+
222
+ logger.info(f"πŸ” [DEBUG] μ΅œμ’… μ„ νƒλœ TaskType: {task_type_enum}")
223
+
224
+ self.lora_config = LoraConfig(
225
+ r=r,
226
+ lora_alpha=lora_alpha,
227
+ target_modules=target_modules,
228
+ lora_dropout=lora_dropout,
229
+ bias=bias,
230
+ task_type=task_type_enum
231
+ )
232
+
233
+ logger.info(f"πŸ”§ LoRA μ„€μ • 생성: r={r}, alpha={lora_alpha}, target_modules={target_modules}")
234
+ return self.lora_config
235
+
236
+ def apply_lora_to_model(self, adapter_name: str = "default") -> bool:
237
+ """LoRAλ₯Ό κΈ°λ³Έ λͺ¨λΈμ— 적용"""
238
+ try:
239
+ if self.base_model is None:
240
+ raise ValueError("κΈ°λ³Έ λͺ¨λΈμ΄ λ‘œλ“œλ˜μ§€ μ•Šμ•˜μŠ΅λ‹ˆλ‹€.")
241
+
242
+ if self.lora_config is None:
243
+ raise ValueError("LoRA 섀정이 μƒμ„±λ˜μ§€ μ•Šμ•˜μŠ΅λ‹ˆλ‹€.")
244
+
245
+ logger.info(f"πŸ”— LoRA μ–΄λŒ‘ν„° 적용 μ‹œμž‘: {adapter_name}")
246
+
247
+ # LoRA λͺ¨λΈ 생성
248
+ self.lora_model = get_peft_model(self.base_model, self.lora_config)
249
+
250
+ # μ–΄λŒ‘ν„° 이름 μ„€μ •
251
+ self.current_adapter_name = adapter_name
252
+
253
+ # ν›ˆλ ¨ λͺ¨λ“œλ‘œ μ„€μ •
254
+ self.lora_model.train()
255
+
256
+ # λͺ¨λΈ 정보 좜λ ₯
257
+ self.lora_model.print_trainable_parameters()
258
+
259
+ logger.info(f"βœ… LoRA μ–΄λŒ‘ν„° 적용 μ™„λ£Œ: {adapter_name}")
260
+ return True
261
+
262
+ except Exception as e:
263
+ logger.error(f"❌ LoRA μ–΄λŒ‘ν„° 적용 μ‹€νŒ¨: {e}")
264
+ return False
265
+
266
+ def load_lora_adapter(self, adapter_path: str, adapter_name: str = None) -> bool:
267
+ """μ €μž₯된 LoRA μ–΄λŒ‘ν„° λ‘œλ“œ"""
268
+ try:
269
+ if not os.path.exists(adapter_path):
270
+ raise FileNotFoundError(f"μ–΄λŒ‘ν„° 경둜λ₯Ό 찾을 수 μ—†μŠ΅λ‹ˆλ‹€: {adapter_path}")
271
+
272
+ if adapter_name is None:
273
+ adapter_name = Path(adapter_path).stem
274
+
275
+ logger.info(f"πŸ“₯ LoRA μ–΄λŒ‘ν„° λ‘œλ”© μ‹œμž‘: {adapter_path}")
276
+
277
+ # κΈ°λ³Έ λͺ¨λΈμ΄ λ‘œλ“œλ˜μ§€ μ•Šμ€ 경우 λ‘œλ“œ
278
+ if self.base_model is None:
279
+ # μ–΄λŒ‘ν„° μ„€μ • νŒŒμΌμ—μ„œ κΈ°λ³Έ λͺ¨λΈ 경둜 확인
280
+ config_path = os.path.join(adapter_path, "adapter_config.json")
281
+ if os.path.exists(config_path):
282
+ with open(config_path, 'r') as f:
283
+ config = json.load(f)
284
+ base_model_path = config.get("base_model_name_or_path")
285
+ if base_model_path:
286
+ self.load_base_model(base_model_path)
287
+
288
+ # πŸ”„ λͺ¨λΈ νƒ€μž…μ— λ”°λ₯Έ dtype κ²°μ •
289
+ if hasattr(self.base_model, 'config') and hasattr(self.base_model.config, 'model_type'):
290
+ model_type = self.base_model.config.model_type
291
+ if model_type in ['kanana-1.5-v', 'vision2seq']:
292
+ # πŸ”„ Kanana λͺ¨λΈ: bfloat16 μ‚¬μš©
293
+ selected_dtype = torch.bfloat16
294
+ logger.info(f"πŸ” [DEBUG] Kanana λͺ¨λΈ 감지: {model_type} -> bfloat16 μ‚¬μš©")
295
+ else:
296
+ # πŸ”„ 기타 λͺ¨λΈ: κΈ°μ‘΄ 둜직 μ‚¬μš©
297
+ selected_dtype = torch.float16 if self.device == "cuda" else torch.float32
298
+ logger.info(f"πŸ” [DEBUG] 일반 λͺ¨λΈ 감지: {model_type} -> {selected_dtype} μ‚¬μš©")
299
+ else:
300
+ # πŸ”„ λͺ¨λΈ νƒ€μž…μ„ μ•Œ 수 μ—†λŠ” 경우: κΈ°μ‘΄ 둜직 μ‚¬μš©
301
+ selected_dtype = torch.float16 if self.device == "cuda" else torch.float32
302
+ logger.info(f"πŸ” [DEBUG] λͺ¨λΈ νƒ€μž… 미확인 -> {selected_dtype} μ‚¬μš©")
303
+
304
+ # LoRA μ–΄λŒ‘ν„° λ‘œλ“œ (λͺ¨λΈλ³„ 졜적 dtype μ‚¬μš©)
305
+ self.lora_model = PeftModel.from_pretrained(
306
+ self.base_model,
307
+ adapter_path,
308
+ torch_dtype=selected_dtype
309
+ )
310
+
311
+ # λ””λ°”μ΄μŠ€λ‘œ 이동
312
+ if self.device != "cuda":
313
+ self.lora_model = self.lora_model.to(self.device)
314
+
315
+ self.current_adapter_name = adapter_name
316
+ self.loaded_adapters[adapter_name] = adapter_path
317
+
318
+ logger.info(f"βœ… LoRA μ–΄λŒ‘ν„° λ‘œλ”© μ™„λ£Œ: {adapter_name}")
319
+ return True
320
+
321
+ except Exception as e:
322
+ logger.error(f"❌ LoRA μ–΄λŒ‘ν„° λ‘œλ”© μ‹€νŒ¨: {e}")
323
+ return False
324
+
325
+ def save_lora_adapter(self, adapter_name: str = None, output_dir: str = None) -> bool:
326
+ """LoRA μ–΄λŒ‘ν„° μ €μž₯"""
327
+ try:
328
+ if self.lora_model is None:
329
+ raise ValueError("LoRA λͺ¨λΈμ΄ λ‘œλ“œλ˜μ§€ μ•Šμ•˜μŠ΅λ‹ˆλ‹€.")
330
+
331
+ adapter_name = adapter_name or self.current_adapter_name or "default"
332
+ output_dir = output_dir or str(self.adapters_dir / adapter_name)
333
+
334
+ logger.info(f"πŸ’Ύ LoRA μ–΄λŒ‘ν„° μ €μž₯ μ‹œμž‘: {adapter_name} -> {output_dir}")
335
+
336
+ # μ–΄λŒ‘ν„° μ €μž₯
337
+ self.lora_model.save_pretrained(output_dir)
338
+
339
+ # ν† ν¬λ‚˜μ΄μ €λ„ μ €μž₯
340
+ if self.tokenizer:
341
+ self.tokenizer.save_pretrained(output_dir)
342
+
343
+ # μ–΄λŒ‘ν„° 정보 μ €μž₯
344
+ adapter_info = {
345
+ "adapter_name": adapter_name,
346
+ "base_model": self.base_model_path,
347
+ "lora_config": self.lora_config.to_dict() if self.lora_config else None,
348
+ "created_at": str(torch.tensor(time.time())),
349
+ "device": self.device
350
+ }
351
+
352
+ with open(os.path.join(output_dir, "adapter_info.json"), 'w') as f:
353
+ json.dump(adapter_info, f, indent=2)
354
+
355
+ logger.info(f"βœ… LoRA μ–΄λŒ‘ν„° μ €μž₯ μ™„λ£Œ: {output_dir}")
356
+ return True
357
+
358
+ except Exception as e:
359
+ logger.error(f"❌ LoRA μ–΄λŒ‘ν„° μ €μž₯ μ‹€νŒ¨: {e}")
360
+ return False
361
+
362
+ def merge_lora_with_base(self, output_path: str = None) -> bool:
363
+ """LoRA μ–΄λŒ‘ν„°λ₯Ό κΈ°λ³Έ λͺ¨λΈκ³Ό 병합"""
364
+ try:
365
+ if self.lora_model is None:
366
+ raise ValueError("LoRA λͺ¨λΈμ΄ λ‘œλ“œλ˜μ§€ μ•Šμ•˜μŠ΅λ‹ˆλ‹€.")
367
+
368
+ output_path = output_path or f"{self.base_model_path}_merged"
369
+
370
+ logger.info(f"πŸ”— LoRA μ–΄λŒ‘ν„° 병합 μ‹œμž‘: {output_path}")
371
+
372
+ # λ³‘ν•©λœ λͺ¨λΈ 생성
373
+ merged_model = self.lora_model.merge_and_unload()
374
+
375
+ # λ³‘ν•©λœ λͺ¨λΈ μ €μž₯
376
+ merged_model.save_pretrained(output_path)
377
+
378
+ # ν† ν¬λ‚˜μ΄μ €λ„ μ €μž₯
379
+ if self.tokenizer:
380
+ self.tokenizer.save_pretrained(output_path)
381
+
382
+ logger.info(f"βœ… LoRA μ–΄λŒ‘ν„° 병합 μ™„λ£Œ: {output_path}")
383
+ return True
384
+
385
+ except Exception as e:
386
+ logger.error(f"❌ LoRA μ–΄λŒ‘ν„° 병합 μ‹€νŒ¨: {e}")
387
+ return False
388
+
389
+ def list_available_adapters(self) -> List[Dict[str, Any]]:
390
+ """μ‚¬μš© κ°€λŠ₯ν•œ μ–΄λŒ‘ν„° λͺ©λ‘ λ°˜ν™˜"""
391
+ adapters = []
392
+
393
+ for adapter_dir in self.adapters_dir.iterdir():
394
+ if adapter_dir.is_dir():
395
+ config_path = adapter_dir / "adapter_config.json"
396
+ info_path = adapter_dir / "adapter_info.json"
397
+
398
+ adapter_info = {
399
+ "name": adapter_dir.name,
400
+ "path": str(adapter_dir),
401
+ "config_exists": config_path.exists(),
402
+ "info_exists": info_path.exists()
403
+ }
404
+
405
+ # μ–΄λŒ‘ν„° 정보 λ‘œλ“œ
406
+ if info_path.exists():
407
+ try:
408
+ with open(info_path, 'r') as f:
409
+ info = json.load(f)
410
+ adapter_info.update(info)
411
+ except Exception as e:
412
+ logger.warning(f"μ–΄λŒ‘ν„° 정보 λ‘œλ“œ μ‹€νŒ¨: {e}")
413
+
414
+ adapters.append(adapter_info)
415
+
416
+ return adapters
417
+
418
+ def get_adapter_stats(self) -> Dict[str, Any]:
419
+ """μ–΄λŒ‘ν„° 톡계 정보 λ°˜ν™˜"""
420
+ if self.lora_model is None:
421
+ return {"error": "LoRA λͺ¨λΈμ΄ λ‘œλ“œλ˜μ§€ μ•Šμ•˜μŠ΅λ‹ˆλ‹€."}
422
+
423
+ try:
424
+ # ν›ˆλ ¨ κ°€λŠ₯ν•œ νŒŒλΌλ―Έν„° 수
425
+ trainable_params = 0
426
+ all_param = 0
427
+
428
+ for param in self.lora_model.parameters():
429
+ all_param += param.numel()
430
+ if param.requires_grad:
431
+ trainable_params += param.numel()
432
+
433
+ return {
434
+ "adapter_name": self.current_adapter_name,
435
+ "trainable_params": trainable_params,
436
+ "all_params": all_param,
437
+ "trainable_ratio": trainable_params / all_param if all_param > 0 else 0,
438
+ "device": self.device,
439
+ "model_type": type(self.lora_model).__name__
440
+ }
441
+
442
+ except Exception as e:
443
+ logger.error(f"μ–΄λŒ‘ν„° 톡계 μˆ˜μ§‘ μ‹€νŒ¨: {e}")
444
+ return {"error": str(e)}
445
+
446
+ def switch_adapter(self, adapter_name: str) -> bool:
447
+ """λ‹€λ₯Έ μ–΄λŒ‘ν„°λ‘œ μ „ν™˜"""
448
+ try:
449
+ if adapter_name not in self.loaded_adapters:
450
+ # μ–΄λŒ‘ν„° λ‘œλ“œ
451
+ adapter_path = self.adapters_dir / adapter_name
452
+ if not adapter_path.exists():
453
+ raise FileNotFoundError(f"μ–΄λŒ‘ν„°λ₯Ό 찾을 수 μ—†μŠ΅λ‹ˆλ‹€: {adapter_name}")
454
+
455
+ return self.load_lora_adapter(str(adapter_path), adapter_name)
456
+ else:
457
+ # 이미 λ‘œλ“œλœ μ–΄λŒ‘ν„° μ‚¬μš©
458
+ self.current_adapter_name = adapter_name
459
+ logger.info(f"πŸ”„ μ–΄λŒ‘ν„° μ „ν™˜: {adapter_name}")
460
+ return True
461
+
462
+ except Exception as e:
463
+ logger.error(f"❌ μ–΄λŒ‘ν„° μ „ν™˜ μ‹€νŒ¨: {e}")
464
+ return False
465
+
466
+ def unload_adapter(self) -> bool:
467
+ """LoRA μ–΄λŒ‘ν„° μ–Έλ‘œλ“œ"""
468
+ try:
469
+ if self.lora_model is None:
470
+ return True
471
+
472
+ logger.info("πŸ—‘οΈ LoRA μ–΄λŒ‘ν„° μ–Έλ‘œλ“œ μ‹œμž‘")
473
+
474
+ # μ–΄λŒ‘ν„° 제거
475
+ self.lora_model = None
476
+ self.current_adapter_name = None
477
+ self.lora_config = None
478
+
479
+ logger.info("οΏ½οΏ½ LoRA μ–΄λŒ‘ν„° μ–Έλ‘œλ“œ μ™„λ£Œ")
480
+ return True
481
+
482
+ except Exception as e:
483
+ logger.error(f"❌ LoRA μ–΄λŒ‘ν„° μ–Έλ‘œλ“œ μ‹€νŒ¨: {e}")
484
+ return False
485
+
486
+ def generate_text(self, prompt: str, max_length: int = 100, temperature: float = 0.7) -> str:
487
+ """LoRA λͺ¨λΈμ„ μ‚¬μš©ν•œ ν…μŠ€νŠΈ 생성"""
488
+ try:
489
+ if self.lora_model is None:
490
+ raise ValueError("LoRA λͺ¨λΈμ΄ λ‘œλ“œλ˜μ§€ μ•Šμ•˜μŠ΅λ‹ˆλ‹€.")
491
+
492
+ if self.tokenizer is None:
493
+ raise ValueError("ν† ν¬λ‚˜μ΄μ €κ°€ λ‘œλ“œλ˜μ§€ μ•Šμ•˜μŠ΅λ‹ˆλ‹€.")
494
+
495
+ # μž…λ ₯ ν† ν¬λ‚˜μ΄μ§•
496
+ inputs = self.tokenizer(prompt, return_tensors="pt")
497
+
498
+ # token_type_ids 제거 (PEFT λͺ¨λΈμ—μ„œ μ§€μ›ν•˜μ§€ μ•ŠμŒ)
499
+ if 'token_type_ids' in inputs:
500
+ del inputs['token_type_ids']
501
+ logger.info("πŸ” token_type_ids 제거됨 (PEFT λͺ¨λΈ ν˜Έν™˜μ„±)")
502
+
503
+ inputs = {k: v.to(self.device) for k, v in inputs.items()}
504
+
505
+ # μΆ”λ‘  λͺ¨λ“œλ‘œ μ„€μ •
506
+ self.lora_model.eval()
507
+
508
+ with torch.no_grad():
509
+ outputs = self.lora_model.generate(
510
+ **inputs,
511
+ max_new_tokens=max_length,
512
+ temperature=temperature,
513
+ do_sample=True,
514
+ pad_token_id=self.tokenizer.eos_token_id
515
+ )
516
+
517
+ # 응닡 λ””μ½”λ”©
518
+ response = self.tokenizer.decode(outputs[0], skip_special_tokens=True)
519
+
520
+ # ν”„λ‘¬ν”„νŠΈ 제거
521
+ if response.startswith(prompt):
522
+ response = response[len(prompt):].strip()
523
+
524
+ return response
525
+
526
+ except Exception as e:
527
+ logger.error(f"❌ ν…μŠ€νŠΈ 생성 μ‹€νŒ¨: {e}")
528
+ return f"ν…μŠ€νŠΈ 생성 쀑 였λ₯˜κ°€ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€: {str(e)}"
529
+
530
+ def prepare_for_training(self, training_args: TrainingArguments = None) -> bool:
531
+ """ν›ˆλ ¨μ„ μœ„ν•œ λͺ¨λΈ μ€€λΉ„"""
532
+ try:
533
+ if self.lora_model is None:
534
+ raise ValueError("LoRA λͺ¨λΈμ΄ λ‘œλ“œλ˜μ§€ μ•Šμ•˜μŠ΅λ‹ˆλ‹€.")
535
+
536
+ logger.info("πŸ”§ ν›ˆλ ¨μ„ μœ„ν•œ λͺ¨λΈ μ€€λΉ„ μ‹œμž‘")
537
+
538
+ # κΈ°λ³Έ ν›ˆλ ¨ 인수
539
+ if training_args is None:
540
+ training_args = TrainingArguments(
541
+ output_dir="./lora_training_output",
542
+ num_train_epochs=3,
543
+ per_device_train_batch_size=4,
544
+ gradient_accumulation_steps=4,
545
+ learning_rate=2e-4,
546
+ warmup_steps=100,
547
+ logging_steps=10,
548
+ save_steps=500,
549
+ eval_steps=500,
550
+ evaluation_strategy="steps",
551
+ save_strategy="steps",
552
+ load_best_model_at_end=True,
553
+ metric_for_best_model="eval_loss",
554
+ greater_is_better=False,
555
+ fp16=torch.cuda.is_available(),
556
+ dataloader_pin_memory=False,
557
+ )
558
+
559
+ # ν›ˆλ ¨ λͺ¨λ“œλ‘œ μ„€μ •
560
+ self.lora_model.train()
561
+
562
+ # κ·Έλž˜λ””μ–ΈνŠΈ μ²΄ν¬ν¬μΈνŒ… ν™œμ„±ν™” (λ©”λͺ¨λ¦¬ μ ˆμ•½)
563
+ self.lora_model.gradient_checkpointing_enable()
564
+
565
+ # κ·Έλž˜λ””μ–ΈνŠΈ 클리핑 μ„€μ •
566
+ self.lora_model.enable_input_require_grads()
567
+
568
+ logger.info("βœ… ν›ˆλ ¨μ„ μœ„ν•œ λͺ¨λΈ μ€€λΉ„ μ™„λ£Œ")
569
+ return True
570
+
571
+ except Exception as e:
572
+ logger.error(f"❌ ν›ˆλ ¨ μ€€λΉ„ μ‹€νŒ¨: {e}")
573
+ return False
574
+
575
+ # μ „μ—­ LoRA κ΄€λ¦¬μž μΈμŠ€ν„΄μŠ€ (μ•ˆμ „ν•œ 생성)
576
+ try:
577
+ if PEFT_AVAILABLE and TRANSFORMERS_AVAILABLE:
578
+ lora_manager = LoRAManager()
579
+ logger.info("βœ… μ „μ—­ LoRA κ΄€λ¦¬μž μΈμŠ€ν„΄μŠ€ 생성 μ™„λ£Œ")
580
+ else:
581
+ lora_manager = None
582
+ logger.warning("⚠️ LoRA λΌμ΄λΈŒλŸ¬λ¦¬κ°€ μ‚¬μš© λΆˆκ°€λŠ₯ν•˜μ—¬ LoRA κ΄€λ¦¬μžλ₯Ό μƒμ„±ν•˜μ§€ μ•Šμ•˜μŠ΅λ‹ˆλ‹€.")
583
+ except Exception as e:
584
+ lora_manager = None
585
+ logger.error(f"❌ LoRA κ΄€λ¦¬μž μΈμŠ€ν„΄μŠ€ 생성 μ‹€νŒ¨: {e}")
586
+
587
+ def get_lora_manager() -> Optional[LoRAManager]:
588
+ """μ „μ—­ LoRA κ΄€λ¦¬μž λ°˜ν™˜ (None일 수 있음)"""
589
+ return lora_manager