main.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393
  1. import gradio as gr
  2. from ctransformers import AutoModelForCausalLM
  3. import datetime
  4. import json
  5. import os
  6. import psutil
  7. import GPUtil
  8. import torch
  9. from typing import Dict, Tuple, Any, Optional
  10. import logging
  11. # Configure logging
  12. logging.basicConfig(
  13. level=logging.INFO,
  14. format='%(asctime)s - %(levelname)s - %(message)s',
  15. handlers=[
  16. logging.FileHandler('logs/app.log'),
  17. logging.StreamHandler()
  18. ]
  19. )
  20. # Create logs directory if it doesn't exist
  21. if not os.path.exists('logs'):
  22. os.makedirs('logs')
  23. class EnhancedSystemMonitor:
  24. """Enhanced system monitoring with detailed GPU stats and change tracking"""
  25. def __init__(self):
  26. self.gpu = None
  27. self.previous_stats = None
  28. try:
  29. self.gpu = GPUtil.getGPUs()[0]
  30. logging.info(f"GPU initialized: {self.gpu.name}")
  31. except Exception as e:
  32. logging.warning(f"Could not initialize GPU monitoring: {e}")
  33. def get_stats(self) -> Dict[str, float]:
  34. """Get comprehensive system statistics including GPU metrics"""
  35. stats = {
  36. 'cpu_percent': psutil.cpu_percent(),
  37. 'memory_percent': psutil.virtual_memory().percent,
  38. 'gpu_util': 0,
  39. 'gpu_memory': 0,
  40. 'gpu_clock': 0,
  41. 'gpu_temp': 0,
  42. 'gpu_power': 0
  43. }
  44. if self.gpu:
  45. try:
  46. self.gpu = GPUtil.getGPUs()[0] # Refresh GPU info
  47. stats.update({
  48. 'gpu_util': self.gpu.load * 100,
  49. 'gpu_memory': self.gpu.memoryUtil * 100,
  50. 'gpu_clock': getattr(self.gpu, 'memoryTotal', 0), # Using memoryTotal as a fallback
  51. 'gpu_temp': getattr(self.gpu, 'temperature', 0),
  52. 'gpu_power': getattr(self.gpu, 'powerDraw', 0)
  53. })
  54. except Exception as e:
  55. logging.error(f"Error updating GPU stats: {e}")
  56. if self.previous_stats:
  57. stats['gpu_util_change'] = stats['gpu_util'] - self.previous_stats['gpu_util']
  58. stats['gpu_memory_change'] = stats['gpu_memory'] - self.previous_stats['gpu_memory']
  59. self.previous_stats = stats.copy()
  60. return stats
  61. class SessionLogger:
  62. """Enhanced session logging with system metrics"""
  63. def __init__(self):
  64. self.session_id = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
  65. self.log_file = f"logs/session_{self.session_id}.json"
  66. self.session_data = {
  67. "session_id": self.session_id,
  68. "start_time": datetime.datetime.now().isoformat(),
  69. "system_info": self._get_system_info(),
  70. "conversations": []
  71. }
  72. def _get_system_info(self) -> Dict[str, Any]:
  73. """Collect system information at session start"""
  74. info = {
  75. "cpu_freq": psutil.cpu_freq()._asdict() if psutil.cpu_freq() else {},
  76. "cpu_count": psutil.cpu_count(),
  77. "cpu_count_logical": psutil.cpu_count(logical=True),
  78. "memory_total": psutil.virtual_memory().total,
  79. "platform": os.name
  80. }
  81. try:
  82. gpu = GPUtil.getGPUs()[0]
  83. info["gpu_info"] = {
  84. "name": gpu.name,
  85. "memory_total": gpu.memoryTotal,
  86. "driver": gpu.driver
  87. }
  88. except:
  89. info["gpu_info"] = None
  90. return info
  91. def log_interaction(self,
  92. user_message: str,
  93. ai_response: str,
  94. stats: Dict[str, float],
  95. metadata: Optional[Dict[str, Any]] = None):
  96. """Log interaction with enhanced metadata"""
  97. interaction = {
  98. "timestamp": datetime.datetime.now().isoformat(),
  99. "user_message": user_message,
  100. "ai_response": ai_response,
  101. "system_stats": stats,
  102. "metadata": metadata or {}
  103. }
  104. self.session_data["conversations"].append(interaction)
  105. self.save_session()
  106. def save_session(self):
  107. """Save session data to file with error handling"""
  108. try:
  109. with open(self.log_file, 'w', encoding='utf-8') as f:
  110. json.dump(self.session_data, f, indent=2, ensure_ascii=False)
  111. except Exception as e:
  112. logging.error(f"Error saving session data: {e}")
  113. class ModelManager:
  114. """Manages model initialization, inference, and resource handling"""
  115. def __init__(self, model_path: str):
  116. self.model_path = model_path
  117. self.llm = None
  118. self.config = None
  119. self.initialize_model()
  120. self.total_tokens_processed = 0
  121. def initialize_model(self, gpu_layers: int = 24, batch_size: int = 128) -> None:
  122. """Initialize the model with specified parameters"""
  123. try:
  124. self.llm = AutoModelForCausalLM.from_pretrained(
  125. self.model_path,
  126. model_type='llama',
  127. context_length=2048,
  128. gpu_layers=gpu_layers,
  129. threads=4,
  130. batch_size=batch_size
  131. )
  132. self.config = {'gpu_layers': gpu_layers, 'batch_size': batch_size}
  133. logging.info(f"Model initialized with config: {self.config}")
  134. except Exception as e:
  135. logging.error(f"Error initializing model: {e}")
  136. raise
  137. def generate_with_monitoring(self,
  138. prompt: str,
  139. system_monitor: EnhancedSystemMonitor,
  140. **kwargs) -> Tuple[str, Dict[str, Any]]:
  141. """Generate response with comprehensive monitoring"""
  142. pre_stats = system_monitor.get_stats()
  143. try:
  144. # Generate response
  145. response = self.llm(prompt, **kwargs)
  146. post_stats = system_monitor.get_stats()
  147. # Calculate tokens for this interaction
  148. input_tokens = len(prompt) // 4
  149. output_tokens = len(response) // 4
  150. total_tokens = input_tokens + output_tokens
  151. # Update total tokens processed
  152. self.total_tokens_processed += total_tokens
  153. # Update stats with token information
  154. post_stats.update({
  155. 'approximate_tokens': total_tokens,
  156. 'total_tokens_processed': self.total_tokens_processed,
  157. 'input_tokens': input_tokens,
  158. 'output_tokens': output_tokens
  159. })
  160. return response, {
  161. 'pre_stats': pre_stats,
  162. 'post_stats': post_stats,
  163. 'tokens_processed': total_tokens,
  164. 'total_tokens': self.total_tokens_processed
  165. }
  166. except RuntimeError as e:
  167. if "out of memory" in str(e):
  168. logging.warning("OOM detected, attempting recovery...")
  169. torch.cuda.empty_cache()
  170. # Reduce parameters and retry
  171. new_config = {
  172. 'gpu_layers': max(8, self.config['gpu_layers'] - 4),
  173. 'batch_size': max(32, self.config['batch_size'] // 2)
  174. }
  175. self.initialize_model(**new_config)
  176. return self.generate_with_monitoring(prompt, system_monitor, **kwargs)
  177. raise
  178. def format_stats(stats: Dict[str, float]) -> str:
  179. """Format system statistics for display"""
  180. return f"""System Statistics:
  181. CPU Usage: {stats.get('cpu_percent', 0):.1f}%
  182. Memory Usage: {stats.get('memory_percent', 0):.1f}%
  183. GPU Status:
  184. • Utilization: {stats.get('gpu_util', 0):.1f}% ({stats.get('gpu_util_change', 0):.1f}% change)
  185. • Memory: {stats.get('gpu_memory', 0):.1f}% ({stats.get('gpu_memory_change', 0):.1f}% change)
  186. • Memory Total: {stats.get('gpu_clock', 0)} MB
  187. • Temperature: {stats.get('gpu_temp', 0)}°C
  188. Model Info:
  189. • Current Usage: ~{stats.get('approximate_tokens', 0)} tokens
  190. • Input Tokens: ~{stats.get('input_tokens', 0)}
  191. • Output Tokens: ~{stats.get('output_tokens', 0)}
  192. • Total Processed: {stats.get('total_tokens_processed', 0)} tokens"""
  193. def create_demo(model_path: str):
  194. """Create and configure the Gradio demo"""
  195. # Initialize components
  196. system_monitor = EnhancedSystemMonitor()
  197. session_logger = SessionLogger()
  198. model_manager = ModelManager(model_path)
  199. # System prompt
  200. SYSTEM_PROMPT = """You are an expert AI tutor, designed to help students learn and master any subject through engaging, Socratic dialogue. Your approach is guided by these core principles:
  201. 1. TEACHING APPROACH:
  202. - Focus on building deep understanding rather than providing direct answers
  203. - Use the Socratic method: guide students through questions and critical thinking
  204. - Adapt your explanations to match the student's current level of understanding
  205. - Be patient, encouraging, and maintain a supportive learning environment"""
  206. def manage_conversation_length(history, max_length: int = 4000) -> list:
  207. """Manage conversation history to prevent context overflow"""
  208. current_length = 0
  209. truncated_history = []
  210. for h in reversed(history):
  211. message_length = len(h['content'])
  212. if current_length + message_length < max_length:
  213. truncated_history.insert(0, h)
  214. current_length += message_length
  215. else:
  216. remaining_space = max_length - current_length
  217. if remaining_space > 0:
  218. truncated_message = h.copy()
  219. truncated_message['content'] = h['content'][:remaining_space]
  220. truncated_history.insert(0, truncated_message)
  221. break
  222. return truncated_history
  223. def generate_response(message: str, history: list) -> Tuple[str, Dict[str, Any]]:
  224. """Generate response with conversation management"""
  225. managed_history = manage_conversation_length(history)
  226. chat_history = "\n".join([
  227. f"{'Human' if h['role'] == 'user' else 'Assistant'}: {h['content']}"
  228. for h in managed_history
  229. ])
  230. prompt = f"""<s>[INST] <<SYS>>{SYSTEM_PROMPT}<</SYS>>
  231. Previous conversation:
  232. {chat_history}
  233. Current question: {message} [/INST]"""
  234. # Calculate approximate tokens for the full context
  235. system_tokens = len(SYSTEM_PROMPT) // 4
  236. history_tokens = len(chat_history) // 4
  237. message_tokens = len(message) // 4
  238. total_input_tokens = system_tokens + history_tokens + message_tokens
  239. # Update system monitor with token count
  240. current_stats = system_monitor.get_stats()
  241. current_stats['approximate_tokens'] = total_input_tokens
  242. response, monitoring_data = model_manager.generate_with_monitoring(
  243. prompt,
  244. system_monitor,
  245. max_new_tokens=1024,
  246. temperature=0.7,
  247. top_p=0.9,
  248. top_k=40,
  249. stop=["</s>"]
  250. )
  251. # Update monitoring data with token information
  252. monitoring_data['post_stats']['approximate_tokens'] = total_input_tokens + (len(response) // 4)
  253. session_logger.log_interaction(
  254. message,
  255. response,
  256. monitoring_data['post_stats'],
  257. {"monitoring_data": monitoring_data}
  258. )
  259. return response, monitoring_data
  260. # Create the Gradio interface
  261. with gr.Blocks(theme=gr.themes.Soft()) as demo:
  262. gr.Markdown("# 🎓 TA Tutor")
  263. with gr.Row():
  264. with gr.Column(scale=4):
  265. chatbot = gr.Chatbot(
  266. value=[],
  267. label="Tutoring Session",
  268. height=380,
  269. type="messages" # Updated format
  270. )
  271. with gr.Row():
  272. msg = gr.Textbox(
  273. label="Your Question",
  274. placeholder="Ask your question here...",
  275. lines=2
  276. )
  277. submit = gr.Button("Send", variant="primary")
  278. # Example questions
  279. example_questions = [
  280. "Can you help me understand quantum mechanics?",
  281. "What's the best way to learn calculus?",
  282. "Explain photosynthesis in simple terms.",
  283. "Can you create a study plan for me?",
  284. "A car accelerates uniformly from rest to a speed of 20 m/s in 5 seconds. What is the acceleration of the car?",
  285. "Balance the following chemical equation: Fe + O2 -> Fe2O3",
  286. "Find the derivative of the function f(x) = 3x^4 - 2x^3 + 5x - 7.",
  287. "Given the matrix A = [[1, 2], [3, 4]], find the determinant of A.",
  288. "Write a Python function that takes a list of integers as input and returns the sum of all even numbers in the list.",
  289. "Two forces, F1 = 20 N and F2 = 30 N, act on a point at an angle of 60° to each other. Find the magnitude and direction of the resultant force.",
  290. "In a series circuit with a 12V battery, there are two resistors: R1 = 4Ω and R2 = 6Ω. Find the current flowing through the circuit.",
  291. "Describe the basic steps in the engineering design process.",
  292. "Explain the difference between elastic and plastic deformation in materials.",
  293. "State the first law of thermodynamics and provide an example of its application in engineering.",
  294. ]
  295. gr.Examples(
  296. examples=example_questions,
  297. inputs=msg,
  298. label="Example Questions"
  299. )
  300. with gr.Column(scale=1):
  301. stats_display = gr.Textbox(
  302. label="System Performance",
  303. lines=8,
  304. interactive=False,
  305. value=format_stats(system_monitor.get_stats())
  306. )
  307. def respond(message: str, history: list) -> Tuple[str, list, str]:
  308. """Handle user input and generate response"""
  309. try:
  310. response, monitoring_data = generate_response(message, history)
  311. history.append({"role": "user", "content": message})
  312. history.append({"role": "assistant", "content": response})
  313. new_stats = format_stats(monitoring_data['post_stats'])
  314. return "", history, new_stats
  315. except Exception as e:
  316. error_message = f"Error during generation: {str(e)}"
  317. logging.error(error_message)
  318. history.append({"role": "system", "content": error_message})
  319. return "", history, format_stats(system_monitor.get_stats())
  320. # Connect UI elements
  321. msg.submit(respond, [msg, chatbot], [msg, chatbot, stats_display])
  322. submit.click(respond, [msg, chatbot], [msg, chatbot, stats_display])
  323. # Add stats refresh button
  324. refresh = gr.Button("🔄 Refresh Stats")
  325. refresh.click(
  326. lambda: format_stats(system_monitor.get_stats()),
  327. outputs=[stats_display]
  328. )
  329. return demo
  330. if __name__ == "__main__":
  331. MODEL_PATH = "D:/llama-tutor/models/llama-2-7b-chat.gguf"
  332. demo = create_demo(MODEL_PATH)
  333. demo.queue()
  334. demo.launch(
  335. server_port=7860,
  336. server_name="127.0.0.1", # Changed to localhost for security
  337. show_error=True
  338. )