prepforge 0.2.1__tar.gz → 0.2.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (23) hide show
  1. {prepforge-0.2.1 → prepforge-0.2.2}/PKG-INFO +1 -1
  2. prepforge-0.2.2/major_project/core/train.py +156 -0
  3. {prepforge-0.2.1 → prepforge-0.2.2}/prepforge.egg-info/PKG-INFO +1 -1
  4. {prepforge-0.2.1 → prepforge-0.2.2}/pyproject.toml +1 -1
  5. prepforge-0.2.1/major_project/core/train.py +0 -212
  6. {prepforge-0.2.1 → prepforge-0.2.2}/LICENSE +0 -0
  7. {prepforge-0.2.1 → prepforge-0.2.2}/README.md +0 -0
  8. {prepforge-0.2.1 → prepforge-0.2.2}/major_project/__init__.py +0 -0
  9. {prepforge-0.2.1 → prepforge-0.2.2}/major_project/cli.py +0 -0
  10. {prepforge-0.2.1 → prepforge-0.2.2}/major_project/config.py +0 -0
  11. {prepforge-0.2.1 → prepforge-0.2.2}/major_project/core/__init__.py +0 -0
  12. {prepforge-0.2.1 → prepforge-0.2.2}/major_project/core/config_store.py +0 -0
  13. {prepforge-0.2.1 → prepforge-0.2.2}/major_project/core/model.py +0 -0
  14. {prepforge-0.2.1 → prepforge-0.2.2}/major_project/core/utils.py +0 -0
  15. {prepforge-0.2.1 → prepforge-0.2.2}/major_project/gui_app.py +0 -0
  16. {prepforge-0.2.1 → prepforge-0.2.2}/major_project/streamlit_app.py +0 -0
  17. {prepforge-0.2.1 → prepforge-0.2.2}/major_project/tui_app.py +0 -0
  18. {prepforge-0.2.1 → prepforge-0.2.2}/prepforge.egg-info/SOURCES.txt +0 -0
  19. {prepforge-0.2.1 → prepforge-0.2.2}/prepforge.egg-info/dependency_links.txt +0 -0
  20. {prepforge-0.2.1 → prepforge-0.2.2}/prepforge.egg-info/entry_points.txt +0 -0
  21. {prepforge-0.2.1 → prepforge-0.2.2}/prepforge.egg-info/requires.txt +0 -0
  22. {prepforge-0.2.1 → prepforge-0.2.2}/prepforge.egg-info/top_level.txt +0 -0
  23. {prepforge-0.2.1 → prepforge-0.2.2}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: prepforge
3
- Version: 0.2.1
3
+ Version: 0.2.2
4
4
  Summary: AI Exam Preparation System with TUI, GUI, and Streamlit
5
5
  Author-email: Himanshu Arora <ha0himanshuarora@gmail.com>
6
6
  License-Expression: MIT
@@ -0,0 +1,156 @@
1
+ from unsloth import FastLanguageModel
2
+ from datasets import load_dataset, Dataset
3
+ from transformers import TrainingArguments
4
+ from trl import SFTTrainer
5
+
6
+ from major_project.config import get_model
7
+
8
+
9
+ def train_model(
10
+ dataset_path,
11
+ output_dir,
12
+ epochs=2,
13
+ lr=2e-4,
14
+ batch_size=2,
15
+ max_seq_length=512,
16
+ limit=None,
17
+ subset=None,
18
+ ):
19
+ # -----------------------------
20
+ # LOAD MODEL FROM CONFIG
21
+ # -----------------------------
22
+ model_name = get_model()
23
+ print(f"[TRAIN] Using model: {model_name}")
24
+
25
+ model, tokenizer = FastLanguageModel.from_pretrained(
26
+ model_name=model_name,
27
+ max_seq_length=max_seq_length,
28
+ load_in_4bit=True,
29
+ )
30
+
31
+ # -----------------------------
32
+ # APPLY LoRA
33
+ # -----------------------------
34
+ model = FastLanguageModel.get_peft_model(
35
+ model,
36
+ r=16,
37
+ target_modules=[
38
+ "q_proj",
39
+ "k_proj",
40
+ "v_proj",
41
+ "o_proj",
42
+ "gate_proj",
43
+ "up_proj",
44
+ "down_proj",
45
+ ],
46
+ lora_alpha=16,
47
+ lora_dropout=0,
48
+ bias="none",
49
+ use_gradient_checkpointing="unsloth",
50
+ )
51
+
52
+ # -----------------------------
53
+ # LOAD DATASET
54
+ # -----------------------------
55
+ dataset = load_dataset("json", data_files=dataset_path, split="train")
56
+ assert isinstance(dataset, Dataset)
57
+
58
+ print(f"[TRAIN] Original dataset size: {len(dataset)}")
59
+
60
+ # -----------------------------
61
+ # APPLY LIMIT / SUBSET
62
+ # -----------------------------
63
+ if limit is not None:
64
+ dataset = dataset.select(range(min(limit, len(dataset))))
65
+ print(f"[TRAIN] Using first {len(dataset)} samples")
66
+
67
+ elif subset is not None:
68
+ size = int(len(dataset) * subset)
69
+ dataset = dataset.select(range(size))
70
+ print(f"[TRAIN] Using {subset * 100:.1f}% → {len(dataset)} samples")
71
+
72
+ # -----------------------------
73
+ # FORMAT DATA
74
+ # -----------------------------
75
+ def format_prompt(example):
76
+ instruction = example.get("instruction", "")
77
+ input_text = example.get("input", "")
78
+ output = example.get("output", "")
79
+
80
+ prompt = f"{instruction}\n{input_text}".strip()
81
+
82
+ user_part = f"""<|begin_of_text|>
83
+ <|start_header_id|>user<|end_header_id|>
84
+
85
+ {prompt}
86
+
87
+ <|start_header_id|>assistant<|end_header_id|>
88
+
89
+ """
90
+
91
+ full_text = user_part + output
92
+
93
+ user_tokens = tokenizer(user_part, add_special_tokens=False)
94
+ full_tokens = tokenizer(
95
+ full_text,
96
+ truncation=True,
97
+ max_length=max_seq_length,
98
+ )
99
+
100
+ labels = full_tokens["input_ids"].copy()
101
+ user_len = len(user_tokens["input_ids"])
102
+
103
+ # Mask user part
104
+ labels[:user_len] = [-100] * user_len
105
+
106
+ return {
107
+ "input_ids": full_tokens["input_ids"],
108
+ "attention_mask": full_tokens["attention_mask"],
109
+ "labels": labels,
110
+ }
111
+
112
+ dataset = dataset.map(format_prompt, batched=False)
113
+
114
+ # Keep only required columns
115
+ dataset = dataset.remove_columns(
116
+ [
117
+ col
118
+ for col in dataset.column_names
119
+ if col not in ["input_ids", "attention_mask", "labels"]
120
+ ]
121
+ )
122
+
123
+ # -----------------------------
124
+ # TRAINER
125
+ # -----------------------------
126
+ training_args = TrainingArguments(
127
+ per_device_train_batch_size=batch_size,
128
+ num_train_epochs=epochs,
129
+ learning_rate=lr,
130
+ output_dir=output_dir,
131
+ logging_steps=10,
132
+ save_steps=500,
133
+ optim="adamw_8bit",
134
+ report_to="none", # avoids wandb issues
135
+ )
136
+
137
+ trainer = SFTTrainer(
138
+ model=model,
139
+ tokenizer=tokenizer,
140
+ train_dataset=dataset,
141
+ args=training_args,
142
+ )
143
+
144
+ # -----------------------------
145
+ # TRAIN
146
+ # -----------------------------
147
+ print("[TRAIN] Starting training...")
148
+ trainer.train()
149
+
150
+ # -----------------------------
151
+ # SAVE
152
+ # -----------------------------
153
+ model.save_pretrained(output_dir)
154
+ tokenizer.save_pretrained(output_dir)
155
+
156
+ print("[TRAIN] Training complete.")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: prepforge
3
- Version: 0.2.1
3
+ Version: 0.2.2
4
4
  Summary: AI Exam Preparation System with TUI, GUI, and Streamlit
5
5
  Author-email: Himanshu Arora <ha0himanshuarora@gmail.com>
6
6
  License-Expression: MIT
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "prepforge"
3
- version = "0.2.1"
3
+ version = "0.2.2"
4
4
  description = "AI Exam Preparation System with TUI, GUI, and Streamlit"
5
5
  authors = [
6
6
  { name = "Himanshu Arora", email = "ha0himanshuarora@gmail.com" }
@@ -1,212 +0,0 @@
1
- import os
2
-
3
- # -----------------------------
4
- # 🔥 FORCE OFFLINE MODE
5
- # -----------------------------
6
- os.environ["TRANSFORMERS_OFFLINE"] = "1"
7
- os.environ["HF_HUB_OFFLINE"] = "1"
8
-
9
- import streamlit as st
10
-
11
- # 🔥 CORE IMPORTS
12
- from major_project.core.model import load_model
13
- from major_project.core.utils import (
14
- build_prompt,
15
- generate_stream,
16
- chat_history,
17
- last_mcq_block,
18
- )
19
- from major_project.config import DEFAULT_APP_NAME, DEFAULT_LORA, DEFAULT_MODEL
20
-
21
- # -----------------------------
22
- # CONFIG
23
- # -----------------------------
24
- APP_NAME = DEFAULT_APP_NAME
25
- st.set_page_config(page_title=APP_NAME, layout="wide")
26
-
27
-
28
- # -----------------------------
29
- # MODEL CACHE
30
- # -----------------------------
31
- @st.cache_resource
32
- def cached_model(base_model, lora_path):
33
- return load_model(base_model, lora_path)
34
-
35
-
36
- # -----------------------------
37
- # SIDEBAR
38
- # -----------------------------
39
- st.sidebar.title("⚙️ Model Settings")
40
-
41
- # ❌ OLD (kept but unused safely)
42
- model_name = DEFAULT_MODEL
43
-
44
- model_mode = st.sidebar.selectbox("Model Type", ["Base Model", "LoRA Adapter"])
45
-
46
- lora_path = DEFAULT_LORA
47
- if model_mode == "LoRA Adapter":
48
- lora_path = st.sidebar.text_input("LoRA Path")
49
-
50
- load_btn = st.sidebar.button("🚀 Load Model")
51
-
52
-
53
- # -----------------------------
54
- # SESSION STATE
55
- # -----------------------------
56
- if "model" not in st.session_state:
57
- st.session_state.model = None
58
- st.session_state.tokenizer = None
59
- st.session_state.is_gguf = False
60
-
61
- if "history" not in st.session_state:
62
- st.session_state.history = []
63
-
64
- if "last_mcq" not in st.session_state:
65
- st.session_state.last_mcq = ""
66
-
67
-
68
- # -----------------------------
69
- # LOAD MODEL
70
- # -----------------------------
71
- if load_btn:
72
- if model_mode == "LoRA Adapter" and not lora_path:
73
- st.sidebar.error("Provide LoRA path")
74
- else:
75
- with st.spinner("Loading model..."):
76
- # 🔥 FIX: Always use config-selected model
77
- model, tokenizer = cached_model(
78
- None, # ← THIS FIXES YOUR BUG
79
- lora_path if model_mode == "LoRA Adapter" else None,
80
- )
81
-
82
- st.session_state.model = model
83
- st.session_state.tokenizer = tokenizer
84
- st.session_state.is_gguf = (
85
- isinstance(model, dict) and model.get("type") == "gguf"
86
- )
87
-
88
- backend = "GGUF (llama.cpp)" if st.session_state.is_gguf else "HuggingFace"
89
- st.sidebar.success(f"Model loaded [{backend}]")
90
-
91
- # 🔥 NEW: Show actual model path/name
92
- if st.session_state.is_gguf:
93
- st.sidebar.info(f"Using: {model.get('path')}")
94
- else:
95
- st.sidebar.info("Using HuggingFace model (from config)")
96
-
97
-
98
- model = st.session_state.model
99
- tokenizer = st.session_state.tokenizer
100
- is_gguf = st.session_state.is_gguf
101
-
102
-
103
- # -----------------------------
104
- # SAFETY (FIXED)
105
- # -----------------------------
106
- if model is None:
107
- st.title(f"🎓 {APP_NAME}")
108
- st.warning("Load model from sidebar")
109
- st.stop()
110
-
111
-
112
- # -----------------------------
113
- # UI
114
- # -----------------------------
115
- st.title(f"🎓 {APP_NAME}")
116
-
117
- mode = st.sidebar.selectbox(
118
- "Mode", ["Chat", "Notes", "Study Plan", "MCQ Generator", "Practice"]
119
- )
120
-
121
- length = st.sidebar.slider("Response Length", 100, 2000, 600)
122
- count = st.sidebar.number_input("Number of Questions", 1, 50, 5)
123
-
124
-
125
- # -----------------------------
126
- # RESET
127
- # -----------------------------
128
- if st.sidebar.button("🧹 Reset Chat"):
129
- st.session_state.history = []
130
- chat_history.clear()
131
- st.session_state.last_mcq = ""
132
-
133
-
134
- # -----------------------------
135
- # DISPLAY HISTORY
136
- # -----------------------------
137
- for msg in st.session_state.history:
138
- with st.chat_message(msg["role"]):
139
- st.write(msg["content"])
140
-
141
-
142
- # -----------------------------
143
- # INPUT
144
- # -----------------------------
145
- user_input = st.chat_input(f"Ask {APP_NAME}...")
146
-
147
- if user_input:
148
- with st.chat_message("user"):
149
- st.write(user_input)
150
-
151
- prompt = build_prompt(mode, user_input, count)
152
-
153
- with st.chat_message("assistant"):
154
- placeholder = st.empty()
155
- response = ""
156
-
157
- # =========================================================
158
- # 🔥 GGUF STREAMING
159
- # =========================================================
160
- if is_gguf:
161
- try:
162
- llm = model["llm"]
163
-
164
- stream = llm(
165
- prompt,
166
- max_tokens=length,
167
- stream=True,
168
- temperature=0.3,
169
- )
170
-
171
- for chunk in stream:
172
- token = chunk["choices"][0]["text"]
173
- response += token
174
- placeholder.markdown(response)
175
-
176
- except Exception as e:
177
- placeholder.error(f"GGUF Error: {e}")
178
- st.stop()
179
-
180
- # =========================================================
181
- # 🔥 HF STREAMING (UNCHANGED)
182
- # =========================================================
183
- else:
184
- streamer = generate_stream(model, tokenizer, prompt, length)
185
-
186
- for token in streamer:
187
- response += token
188
- placeholder.markdown(response)
189
-
190
- st.session_state.history.append({"role": "user", "content": user_input})
191
- st.session_state.history.append({"role": "assistant", "content": response})
192
-
193
- # 🔥 MCQ STORAGE
194
- if "SECTION 1" in response and "SECTION 2" in response:
195
- st.session_state.last_mcq = response
196
-
197
-
198
- # -----------------------------
199
- # SAVE CHAT
200
- # -----------------------------
201
- if st.session_state.history:
202
- chat_text = f"=== {APP_NAME} Notes ===\n\n"
203
-
204
- for msg in st.session_state.history:
205
- role = "User" if msg["role"] == "user" else APP_NAME
206
- chat_text += f"{role}:\n{msg['content']}\n\n"
207
-
208
- st.sidebar.download_button(
209
- "💾 Download Chat",
210
- chat_text,
211
- file_name=f"{APP_NAME.lower()}_chat.txt",
212
- )
File without changes
File without changes
File without changes