npcpy 1.3.13__tar.gz → 1.3.15__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.
- {npcpy-1.3.13/npcpy.egg-info → npcpy-1.3.15}/PKG-INFO +4 -4
- {npcpy-1.3.13 → npcpy-1.3.15}/README.md +3 -3
- {npcpy-1.3.13 → npcpy-1.3.15}/npcpy/ft/diff.py +45 -30
- {npcpy-1.3.13 → npcpy-1.3.15}/npcpy/ft/rl.py +134 -51
- {npcpy-1.3.13 → npcpy-1.3.15}/npcpy/serve.py +42 -15
- {npcpy-1.3.13 → npcpy-1.3.15/npcpy.egg-info}/PKG-INFO +4 -4
- {npcpy-1.3.13 → npcpy-1.3.15}/npcpy.egg-info/SOURCES.txt +1 -0
- {npcpy-1.3.13 → npcpy-1.3.15}/setup.py +1 -1
- {npcpy-1.3.13 → npcpy-1.3.15}/tests/test_command_history.py +114 -91
- npcpy-1.3.15/tests/test_documentation_examples.py +436 -0
- npcpy-1.3.15/tests/test_load.py +281 -0
- npcpy-1.3.15/tests/test_serve.py +135 -0
- npcpy-1.3.15/tests/test_text.py +214 -0
- npcpy-1.3.15/tests/test_tools.py +211 -0
- npcpy-1.3.13/tests/test_load.py +0 -284
- npcpy-1.3.13/tests/test_serve.py +0 -150
- npcpy-1.3.13/tests/test_text.py +0 -256
- npcpy-1.3.13/tests/test_tools.py +0 -989
- {npcpy-1.3.13 → npcpy-1.3.15}/LICENSE +0 -0
- {npcpy-1.3.13 → npcpy-1.3.15}/MANIFEST.in +0 -0
- {npcpy-1.3.13 → npcpy-1.3.15}/npcpy/__init__.py +0 -0
- {npcpy-1.3.13 → npcpy-1.3.15}/npcpy/build_funcs.py +0 -0
- {npcpy-1.3.13 → npcpy-1.3.15}/npcpy/data/__init__.py +0 -0
- {npcpy-1.3.13 → npcpy-1.3.15}/npcpy/data/audio.py +0 -0
- {npcpy-1.3.13 → npcpy-1.3.15}/npcpy/data/data_models.py +0 -0
- {npcpy-1.3.13 → npcpy-1.3.15}/npcpy/data/image.py +0 -0
- {npcpy-1.3.13 → npcpy-1.3.15}/npcpy/data/load.py +0 -0
- {npcpy-1.3.13 → npcpy-1.3.15}/npcpy/data/text.py +0 -0
- {npcpy-1.3.13 → npcpy-1.3.15}/npcpy/data/video.py +0 -0
- {npcpy-1.3.13 → npcpy-1.3.15}/npcpy/data/web.py +0 -0
- {npcpy-1.3.13 → npcpy-1.3.15}/npcpy/ft/__init__.py +0 -0
- {npcpy-1.3.13 → npcpy-1.3.15}/npcpy/ft/ge.py +0 -0
- {npcpy-1.3.13 → npcpy-1.3.15}/npcpy/ft/memory_trainer.py +0 -0
- {npcpy-1.3.13 → npcpy-1.3.15}/npcpy/ft/model_ensembler.py +0 -0
- {npcpy-1.3.13 → npcpy-1.3.15}/npcpy/ft/sft.py +0 -0
- {npcpy-1.3.13 → npcpy-1.3.15}/npcpy/ft/usft.py +0 -0
- {npcpy-1.3.13 → npcpy-1.3.15}/npcpy/gen/__init__.py +0 -0
- {npcpy-1.3.13 → npcpy-1.3.15}/npcpy/gen/audio_gen.py +0 -0
- {npcpy-1.3.13 → npcpy-1.3.15}/npcpy/gen/embeddings.py +0 -0
- {npcpy-1.3.13 → npcpy-1.3.15}/npcpy/gen/image_gen.py +0 -0
- {npcpy-1.3.13 → npcpy-1.3.15}/npcpy/gen/ocr.py +0 -0
- {npcpy-1.3.13 → npcpy-1.3.15}/npcpy/gen/response.py +0 -0
- {npcpy-1.3.13 → npcpy-1.3.15}/npcpy/gen/video_gen.py +0 -0
- {npcpy-1.3.13 → npcpy-1.3.15}/npcpy/gen/world_gen.py +0 -0
- {npcpy-1.3.13 → npcpy-1.3.15}/npcpy/llm_funcs.py +0 -0
- {npcpy-1.3.13 → npcpy-1.3.15}/npcpy/main.py +0 -0
- {npcpy-1.3.13 → npcpy-1.3.15}/npcpy/memory/__init__.py +0 -0
- {npcpy-1.3.13 → npcpy-1.3.15}/npcpy/memory/command_history.py +0 -0
- {npcpy-1.3.13 → npcpy-1.3.15}/npcpy/memory/kg_vis.py +0 -0
- {npcpy-1.3.13 → npcpy-1.3.15}/npcpy/memory/knowledge_graph.py +0 -0
- {npcpy-1.3.13 → npcpy-1.3.15}/npcpy/memory/memory_processor.py +0 -0
- {npcpy-1.3.13 → npcpy-1.3.15}/npcpy/memory/search.py +0 -0
- {npcpy-1.3.13 → npcpy-1.3.15}/npcpy/mix/__init__.py +0 -0
- {npcpy-1.3.13 → npcpy-1.3.15}/npcpy/mix/debate.py +0 -0
- {npcpy-1.3.13 → npcpy-1.3.15}/npcpy/ml_funcs.py +0 -0
- {npcpy-1.3.13 → npcpy-1.3.15}/npcpy/npc_array.py +0 -0
- {npcpy-1.3.13 → npcpy-1.3.15}/npcpy/npc_compiler.py +0 -0
- {npcpy-1.3.13 → npcpy-1.3.15}/npcpy/npc_sysenv.py +0 -0
- {npcpy-1.3.13 → npcpy-1.3.15}/npcpy/npcs.py +0 -0
- {npcpy-1.3.13 → npcpy-1.3.15}/npcpy/sql/__init__.py +0 -0
- {npcpy-1.3.13 → npcpy-1.3.15}/npcpy/sql/ai_function_tools.py +0 -0
- {npcpy-1.3.13 → npcpy-1.3.15}/npcpy/sql/database_ai_adapters.py +0 -0
- {npcpy-1.3.13 → npcpy-1.3.15}/npcpy/sql/database_ai_functions.py +0 -0
- {npcpy-1.3.13 → npcpy-1.3.15}/npcpy/sql/model_runner.py +0 -0
- {npcpy-1.3.13 → npcpy-1.3.15}/npcpy/sql/npcsql.py +0 -0
- {npcpy-1.3.13 → npcpy-1.3.15}/npcpy/sql/sql_model_compiler.py +0 -0
- {npcpy-1.3.13 → npcpy-1.3.15}/npcpy/tools.py +0 -0
- {npcpy-1.3.13 → npcpy-1.3.15}/npcpy/work/__init__.py +0 -0
- {npcpy-1.3.13 → npcpy-1.3.15}/npcpy/work/browser.py +0 -0
- {npcpy-1.3.13 → npcpy-1.3.15}/npcpy/work/desktop.py +0 -0
- {npcpy-1.3.13 → npcpy-1.3.15}/npcpy/work/plan.py +0 -0
- {npcpy-1.3.13 → npcpy-1.3.15}/npcpy/work/trigger.py +0 -0
- {npcpy-1.3.13 → npcpy-1.3.15}/npcpy.egg-info/dependency_links.txt +0 -0
- {npcpy-1.3.13 → npcpy-1.3.15}/npcpy.egg-info/requires.txt +0 -0
- {npcpy-1.3.13 → npcpy-1.3.15}/npcpy.egg-info/top_level.txt +0 -0
- {npcpy-1.3.13 → npcpy-1.3.15}/setup.cfg +0 -0
- {npcpy-1.3.13 → npcpy-1.3.15}/tests/test_audio.py +0 -0
- {npcpy-1.3.13 → npcpy-1.3.15}/tests/test_image.py +0 -0
- {npcpy-1.3.13 → npcpy-1.3.15}/tests/test_llm_funcs.py +0 -0
- {npcpy-1.3.13 → npcpy-1.3.15}/tests/test_npc_array.py +0 -0
- {npcpy-1.3.13 → npcpy-1.3.15}/tests/test_npc_compiler.py +0 -0
- {npcpy-1.3.13 → npcpy-1.3.15}/tests/test_npcsql.py +0 -0
- {npcpy-1.3.13 → npcpy-1.3.15}/tests/test_response.py +0 -0
- {npcpy-1.3.13 → npcpy-1.3.15}/tests/test_web.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: npcpy
|
|
3
|
-
Version: 1.3.
|
|
3
|
+
Version: 1.3.15
|
|
4
4
|
Summary: npcpy is the premier open-source library for integrating LLMs and Agents into python systems.
|
|
5
5
|
Home-page: https://github.com/NPC-Worldwide/npcpy
|
|
6
6
|
Author: Christopher Agostino
|
|
@@ -305,7 +305,7 @@ ggm = NPC(
|
|
|
305
305
|
isabel = NPC(
|
|
306
306
|
name='Isabel Allende',
|
|
307
307
|
primary_directive='You are Isabel Allende, weaving stories with emotion and history. Analyze texts and provide insight.',
|
|
308
|
-
model='llama3.2
|
|
308
|
+
model='llama3.2',
|
|
309
309
|
provider='ollama',
|
|
310
310
|
|
|
311
311
|
)
|
|
@@ -359,7 +359,7 @@ LLM responses can be obtained without NPCs as well.
|
|
|
359
359
|
|
|
360
360
|
```python
|
|
361
361
|
from npcpy.llm_funcs import get_llm_response
|
|
362
|
-
response = get_llm_response("Who was the celtic Messenger god?", model='
|
|
362
|
+
response = get_llm_response("Who was the celtic Messenger god?", model='qwen3:4b', provider='ollama')
|
|
363
363
|
print(response['response'])
|
|
364
364
|
```
|
|
365
365
|
|
|
@@ -400,7 +400,7 @@ Return structured outputs by specifying `format='json'` or passing a Pydantic sc
|
|
|
400
400
|
|
|
401
401
|
```python
|
|
402
402
|
from npcpy.llm_funcs import get_llm_response
|
|
403
|
-
response = get_llm_response("What is the sentiment of the american people towards the repeal of Roe v Wade? Return a json object with `sentiment` as the key and a float value from -1 to 1 as the value", model='
|
|
403
|
+
response = get_llm_response("What is the sentiment of the american people towards the repeal of Roe v Wade? Return a json object with `sentiment` as the key and a float value from -1 to 1 as the value", model='deepseek-chat', provider='deepseek', format='json')
|
|
404
404
|
|
|
405
405
|
print(response['response'])
|
|
406
406
|
```
|
|
@@ -209,7 +209,7 @@ ggm = NPC(
|
|
|
209
209
|
isabel = NPC(
|
|
210
210
|
name='Isabel Allende',
|
|
211
211
|
primary_directive='You are Isabel Allende, weaving stories with emotion and history. Analyze texts and provide insight.',
|
|
212
|
-
model='llama3.2
|
|
212
|
+
model='llama3.2',
|
|
213
213
|
provider='ollama',
|
|
214
214
|
|
|
215
215
|
)
|
|
@@ -263,7 +263,7 @@ LLM responses can be obtained without NPCs as well.
|
|
|
263
263
|
|
|
264
264
|
```python
|
|
265
265
|
from npcpy.llm_funcs import get_llm_response
|
|
266
|
-
response = get_llm_response("Who was the celtic Messenger god?", model='
|
|
266
|
+
response = get_llm_response("Who was the celtic Messenger god?", model='qwen3:4b', provider='ollama')
|
|
267
267
|
print(response['response'])
|
|
268
268
|
```
|
|
269
269
|
|
|
@@ -304,7 +304,7 @@ Return structured outputs by specifying `format='json'` or passing a Pydantic sc
|
|
|
304
304
|
|
|
305
305
|
```python
|
|
306
306
|
from npcpy.llm_funcs import get_llm_response
|
|
307
|
-
response = get_llm_response("What is the sentiment of the american people towards the repeal of Roe v Wade? Return a json object with `sentiment` as the key and a float value from -1 to 1 as the value", model='
|
|
307
|
+
response = get_llm_response("What is the sentiment of the american people towards the repeal of Roe v Wade? Return a json object with `sentiment` as the key and a float value from -1 to 1 as the value", model='deepseek-chat', provider='deepseek', format='json')
|
|
308
308
|
|
|
309
309
|
print(response['response'])
|
|
310
310
|
```
|
|
@@ -180,52 +180,66 @@ if TORCH_AVAILABLE:
|
|
|
180
180
|
noise = torch.randn_like(x)
|
|
181
181
|
return sqrt_alpha * x + sqrt_one_minus * noise, noise
|
|
182
182
|
|
|
183
|
-
def train(self, dataloader):
|
|
183
|
+
def train(self, dataloader, progress_callback=None):
|
|
184
184
|
optimizer = torch.optim.AdamW(
|
|
185
|
-
self.model.parameters(),
|
|
185
|
+
self.model.parameters(),
|
|
186
186
|
lr=self.config.learning_rate
|
|
187
187
|
)
|
|
188
|
-
|
|
188
|
+
|
|
189
189
|
os.makedirs(self.config.output_model_path, exist_ok=True)
|
|
190
190
|
checkpoint_dir = os.path.join(
|
|
191
|
-
self.config.output_model_path,
|
|
191
|
+
self.config.output_model_path,
|
|
192
192
|
'checkpoints'
|
|
193
193
|
)
|
|
194
194
|
os.makedirs(checkpoint_dir, exist_ok=True)
|
|
195
|
-
|
|
195
|
+
|
|
196
196
|
global_step = 0
|
|
197
|
-
|
|
197
|
+
total_batches = len(dataloader)
|
|
198
|
+
loss_history = []
|
|
199
|
+
|
|
198
200
|
for epoch in range(self.config.num_epochs):
|
|
199
201
|
self.model.train()
|
|
200
202
|
epoch_loss = 0.0
|
|
201
|
-
|
|
203
|
+
|
|
202
204
|
pbar = tqdm(dataloader, desc=f'Epoch {epoch+1}')
|
|
203
205
|
for batch_idx, (images, captions) in enumerate(pbar):
|
|
204
206
|
images = images.to(self.device)
|
|
205
207
|
batch_size = images.shape[0]
|
|
206
|
-
|
|
208
|
+
|
|
207
209
|
t = torch.randint(
|
|
208
|
-
0,
|
|
209
|
-
self.config.timesteps,
|
|
210
|
-
(batch_size,),
|
|
210
|
+
0,
|
|
211
|
+
self.config.timesteps,
|
|
212
|
+
(batch_size,),
|
|
211
213
|
device=self.device
|
|
212
214
|
).long()
|
|
213
|
-
|
|
215
|
+
|
|
214
216
|
noisy_images, noise = self.add_noise(images, t)
|
|
215
|
-
|
|
217
|
+
|
|
216
218
|
predicted_noise = self.model(noisy_images, t)
|
|
217
|
-
|
|
219
|
+
|
|
218
220
|
loss = F.mse_loss(predicted_noise, noise)
|
|
219
|
-
|
|
221
|
+
|
|
220
222
|
optimizer.zero_grad()
|
|
221
223
|
loss.backward()
|
|
222
224
|
optimizer.step()
|
|
223
|
-
|
|
225
|
+
|
|
224
226
|
epoch_loss += loss.item()
|
|
225
227
|
global_step += 1
|
|
226
|
-
|
|
228
|
+
|
|
227
229
|
pbar.set_postfix({'loss': loss.item()})
|
|
228
|
-
|
|
230
|
+
|
|
231
|
+
# Report progress via callback
|
|
232
|
+
if progress_callback:
|
|
233
|
+
progress_callback({
|
|
234
|
+
'epoch': epoch + 1,
|
|
235
|
+
'total_epochs': self.config.num_epochs,
|
|
236
|
+
'batch': batch_idx + 1,
|
|
237
|
+
'total_batches': total_batches,
|
|
238
|
+
'step': global_step,
|
|
239
|
+
'loss': loss.item(),
|
|
240
|
+
'loss_history': loss_history[-100:], # Last 100 losses
|
|
241
|
+
})
|
|
242
|
+
|
|
229
243
|
if global_step % self.config.checkpoint_frequency == 0:
|
|
230
244
|
ckpt_path = os.path.join(
|
|
231
245
|
checkpoint_dir,
|
|
@@ -238,8 +252,9 @@ if TORCH_AVAILABLE:
|
|
|
238
252
|
'optimizer_state_dict': optimizer.state_dict(),
|
|
239
253
|
'loss': loss.item(),
|
|
240
254
|
}, ckpt_path)
|
|
241
|
-
|
|
255
|
+
|
|
242
256
|
avg_loss = epoch_loss / len(dataloader)
|
|
257
|
+
loss_history.append(avg_loss)
|
|
243
258
|
print(f'Epoch {epoch+1} avg loss: {avg_loss:.6f}')
|
|
244
259
|
|
|
245
260
|
final_path = os.path.join(
|
|
@@ -300,35 +315,35 @@ else:
|
|
|
300
315
|
DiffusionTrainer = None
|
|
301
316
|
|
|
302
317
|
|
|
303
|
-
def train_diffusion(image_paths, captions=None, config=None,
|
|
304
|
-
resume_from=None):
|
|
318
|
+
def train_diffusion(image_paths, captions=None, config=None,
|
|
319
|
+
resume_from=None, progress_callback=None):
|
|
305
320
|
if not TORCH_AVAILABLE:
|
|
306
321
|
raise ImportError(
|
|
307
322
|
"PyTorch not available. Install: pip install torch torchvision"
|
|
308
323
|
)
|
|
309
|
-
|
|
324
|
+
|
|
310
325
|
if config is None:
|
|
311
326
|
config = DiffusionConfig()
|
|
312
|
-
|
|
327
|
+
|
|
313
328
|
if captions is None:
|
|
314
329
|
captions = [''] * len(image_paths)
|
|
315
|
-
|
|
330
|
+
|
|
316
331
|
dataset = ImageDataset(image_paths, captions, config.image_size)
|
|
317
332
|
dataloader = DataLoader(
|
|
318
|
-
dataset,
|
|
319
|
-
batch_size=config.batch_size,
|
|
333
|
+
dataset,
|
|
334
|
+
batch_size=config.batch_size,
|
|
320
335
|
shuffle=True,
|
|
321
336
|
num_workers=0
|
|
322
337
|
)
|
|
323
|
-
|
|
338
|
+
|
|
324
339
|
trainer = DiffusionTrainer(config)
|
|
325
|
-
|
|
340
|
+
|
|
326
341
|
if resume_from and os.path.exists(resume_from):
|
|
327
342
|
checkpoint = torch.load(resume_from, map_location=trainer.device)
|
|
328
343
|
trainer.model.load_state_dict(checkpoint['model_state_dict'])
|
|
329
344
|
print(f'Resumed from {resume_from}')
|
|
330
|
-
|
|
331
|
-
output_path = trainer.train(dataloader)
|
|
345
|
+
|
|
346
|
+
output_path = trainer.train(dataloader, progress_callback=progress_callback)
|
|
332
347
|
|
|
333
348
|
gc.collect()
|
|
334
349
|
if torch.cuda.is_available():
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
from dataclasses import dataclass
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
|
+
from typing import List
|
|
2
3
|
|
|
3
4
|
from datetime import datetime
|
|
4
5
|
import glob
|
|
@@ -12,7 +13,8 @@ try:
|
|
|
12
13
|
import torch
|
|
13
14
|
from transformers import (
|
|
14
15
|
AutoModelForCausalLM,
|
|
15
|
-
AutoTokenizer
|
|
16
|
+
AutoTokenizer,
|
|
17
|
+
BitsAndBytesConfig
|
|
16
18
|
)
|
|
17
19
|
from trl import DPOTrainer, DPOConfig
|
|
18
20
|
except:
|
|
@@ -23,6 +25,7 @@ except:
|
|
|
23
25
|
torch = None
|
|
24
26
|
AutoModelForCausalLM = None
|
|
25
27
|
AutoTokenizer = None
|
|
28
|
+
BitsAndBytesConfig = None
|
|
26
29
|
|
|
27
30
|
|
|
28
31
|
import random
|
|
@@ -44,6 +47,24 @@ class RLConfig:
|
|
|
44
47
|
beta: float = 0.5
|
|
45
48
|
max_length: int = 512
|
|
46
49
|
max_prompt_length: int = 256
|
|
50
|
+
# Quantization options
|
|
51
|
+
use_4bit: bool = False
|
|
52
|
+
use_8bit: bool = False
|
|
53
|
+
# Precision options
|
|
54
|
+
fp16: bool = False
|
|
55
|
+
bf16: bool = False
|
|
56
|
+
# LoRA configuration
|
|
57
|
+
lora_r: int = 8
|
|
58
|
+
lora_alpha: int = 16
|
|
59
|
+
lora_dropout: float = 0.1
|
|
60
|
+
lora_target_modules: List[str] = field(
|
|
61
|
+
default_factory=lambda: ["q_proj", "k_proj", "v_proj", "o_proj"]
|
|
62
|
+
)
|
|
63
|
+
# Training options
|
|
64
|
+
max_pairs: int = 200
|
|
65
|
+
warmup_steps: int = 5
|
|
66
|
+
logging_steps: int = 5
|
|
67
|
+
save_steps: int = 20
|
|
47
68
|
|
|
48
69
|
|
|
49
70
|
class TaskExecutor:
|
|
@@ -207,8 +228,8 @@ def create_preference_pairs(
|
|
|
207
228
|
f"Warning: Only {len(pairs)} pairs found. "
|
|
208
229
|
"May overfit."
|
|
209
230
|
)
|
|
210
|
-
|
|
211
|
-
return Dataset.from_list(pairs
|
|
231
|
+
|
|
232
|
+
return Dataset.from_list(pairs)
|
|
212
233
|
|
|
213
234
|
|
|
214
235
|
def train_with_dpo(
|
|
@@ -218,84 +239,121 @@ def train_with_dpo(
|
|
|
218
239
|
|
|
219
240
|
if config is None:
|
|
220
241
|
config = RLConfig()
|
|
221
|
-
|
|
242
|
+
|
|
222
243
|
preference_dataset = create_preference_pairs(
|
|
223
244
|
traces,
|
|
224
245
|
min_reward_gap=config.min_reward_gap
|
|
225
246
|
)
|
|
226
|
-
|
|
247
|
+
|
|
227
248
|
if preference_dataset is None or len(preference_dataset) == 0:
|
|
228
249
|
print("No valid preference pairs. Cannot train.")
|
|
229
250
|
return None
|
|
230
|
-
|
|
251
|
+
|
|
252
|
+
# Limit pairs if specified
|
|
253
|
+
if config.max_pairs and len(preference_dataset) > config.max_pairs:
|
|
254
|
+
preference_dataset = preference_dataset.select(range(config.max_pairs))
|
|
255
|
+
|
|
256
|
+
print(f"Training with {len(preference_dataset)} preference pairs")
|
|
257
|
+
|
|
258
|
+
# Build model loading kwargs
|
|
259
|
+
model_kwargs = {
|
|
260
|
+
"device_map": "auto",
|
|
261
|
+
"trust_remote_code": True,
|
|
262
|
+
"low_cpu_mem_usage": True
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
# Handle quantization
|
|
266
|
+
if config.use_4bit:
|
|
267
|
+
if BitsAndBytesConfig is None:
|
|
268
|
+
raise ImportError("bitsandbytes required for 4-bit. pip install bitsandbytes")
|
|
269
|
+
model_kwargs["quantization_config"] = BitsAndBytesConfig(
|
|
270
|
+
load_in_4bit=True,
|
|
271
|
+
bnb_4bit_quant_type="nf4",
|
|
272
|
+
bnb_4bit_compute_dtype=torch.float16,
|
|
273
|
+
bnb_4bit_use_double_quant=True
|
|
274
|
+
)
|
|
275
|
+
print("Using 4-bit quantization")
|
|
276
|
+
elif config.use_8bit:
|
|
277
|
+
if BitsAndBytesConfig is None:
|
|
278
|
+
raise ImportError("bitsandbytes required for 8-bit. pip install bitsandbytes")
|
|
279
|
+
model_kwargs["quantization_config"] = BitsAndBytesConfig(
|
|
280
|
+
load_in_8bit=True
|
|
281
|
+
)
|
|
282
|
+
print("Using 8-bit quantization")
|
|
283
|
+
else:
|
|
284
|
+
# Set dtype based on precision config
|
|
285
|
+
if config.bf16:
|
|
286
|
+
model_kwargs["torch_dtype"] = torch.bfloat16
|
|
287
|
+
elif config.fp16:
|
|
288
|
+
model_kwargs["torch_dtype"] = torch.float16
|
|
289
|
+
else:
|
|
290
|
+
model_kwargs["torch_dtype"] = torch.float32
|
|
291
|
+
|
|
231
292
|
model = AutoModelForCausalLM.from_pretrained(
|
|
232
293
|
config.base_model_name,
|
|
233
|
-
|
|
234
|
-
device_map="auto",
|
|
235
|
-
low_cpu_mem_usage=True
|
|
294
|
+
**model_kwargs
|
|
236
295
|
)
|
|
237
|
-
|
|
296
|
+
|
|
238
297
|
tokenizer = AutoTokenizer.from_pretrained(
|
|
239
298
|
config.base_model_name,
|
|
240
299
|
trust_remote_code=True
|
|
241
300
|
)
|
|
242
|
-
|
|
301
|
+
|
|
243
302
|
if tokenizer.pad_token is None:
|
|
244
303
|
tokenizer.pad_token = tokenizer.eos_token
|
|
245
|
-
|
|
304
|
+
|
|
246
305
|
peft_config = LoraConfig(
|
|
247
|
-
r=
|
|
248
|
-
lora_alpha=
|
|
249
|
-
lora_dropout=
|
|
306
|
+
r=config.lora_r,
|
|
307
|
+
lora_alpha=config.lora_alpha,
|
|
308
|
+
lora_dropout=config.lora_dropout,
|
|
250
309
|
bias="none",
|
|
251
310
|
task_type="CAUSAL_LM",
|
|
252
|
-
target_modules=
|
|
253
|
-
"q_proj",
|
|
254
|
-
"k_proj",
|
|
255
|
-
"v_proj",
|
|
256
|
-
"o_proj"
|
|
257
|
-
]
|
|
311
|
+
target_modules=config.lora_target_modules
|
|
258
312
|
)
|
|
259
|
-
|
|
313
|
+
|
|
314
|
+
# Select optimizer based on quantization
|
|
315
|
+
if config.use_4bit or config.use_8bit:
|
|
316
|
+
optim = "paged_adamw_8bit"
|
|
317
|
+
else:
|
|
318
|
+
optim = "adamw_torch"
|
|
319
|
+
|
|
260
320
|
training_args = DPOConfig(
|
|
261
321
|
output_dir="./dpo_results",
|
|
262
|
-
per_device_train_batch_size=
|
|
263
|
-
|
|
264
|
-
),
|
|
265
|
-
gradient_accumulation_steps=(
|
|
266
|
-
config.gradient_accumulation_steps
|
|
267
|
-
),
|
|
322
|
+
per_device_train_batch_size=config.per_device_train_batch_size,
|
|
323
|
+
gradient_accumulation_steps=config.gradient_accumulation_steps,
|
|
268
324
|
learning_rate=config.learning_rate,
|
|
269
325
|
num_train_epochs=config.num_train_epochs,
|
|
270
326
|
weight_decay=0.1,
|
|
271
327
|
beta=config.beta,
|
|
272
|
-
logging_steps=
|
|
273
|
-
save_steps=
|
|
328
|
+
logging_steps=config.logging_steps,
|
|
329
|
+
save_steps=config.save_steps,
|
|
274
330
|
remove_unused_columns=False,
|
|
275
331
|
max_length=config.max_length,
|
|
276
332
|
max_prompt_length=config.max_prompt_length,
|
|
277
333
|
dataloader_num_workers=0,
|
|
278
|
-
fp16=
|
|
279
|
-
bf16=
|
|
280
|
-
optim=
|
|
281
|
-
warmup_steps=
|
|
334
|
+
fp16=config.fp16 or config.use_4bit,
|
|
335
|
+
bf16=config.bf16,
|
|
336
|
+
optim=optim,
|
|
337
|
+
warmup_steps=config.warmup_steps,
|
|
282
338
|
save_strategy="steps",
|
|
283
|
-
save_total_limit=
|
|
339
|
+
save_total_limit=2
|
|
284
340
|
)
|
|
285
|
-
|
|
341
|
+
|
|
286
342
|
trainer = DPOTrainer(
|
|
287
343
|
model,
|
|
288
344
|
args=training_args,
|
|
289
345
|
train_dataset=preference_dataset,
|
|
290
|
-
peft_config=peft_config
|
|
346
|
+
peft_config=peft_config,
|
|
347
|
+
processing_class=tokenizer
|
|
291
348
|
)
|
|
292
|
-
|
|
349
|
+
|
|
293
350
|
print("Starting DPO training...")
|
|
294
351
|
trainer.train()
|
|
295
|
-
|
|
352
|
+
|
|
353
|
+
os.makedirs(config.adapter_path, exist_ok=True)
|
|
296
354
|
trainer.save_model(config.adapter_path)
|
|
297
355
|
print(f"Adapter saved to {config.adapter_path}")
|
|
298
|
-
|
|
356
|
+
|
|
299
357
|
return config.adapter_path
|
|
300
358
|
|
|
301
359
|
|
|
@@ -333,28 +391,53 @@ def run_rl_training(
|
|
|
333
391
|
|
|
334
392
|
def load_rl_model(
|
|
335
393
|
base_model_id: str,
|
|
336
|
-
adapter_path: str
|
|
394
|
+
adapter_path: str,
|
|
395
|
+
use_4bit: bool = False,
|
|
396
|
+
use_8bit: bool = False,
|
|
397
|
+
merge_adapter: bool = True
|
|
337
398
|
):
|
|
338
|
-
|
|
339
399
|
print(f"Loading base model: {base_model_id}")
|
|
400
|
+
|
|
401
|
+
model_kwargs = {
|
|
402
|
+
"device_map": "auto",
|
|
403
|
+
"trust_remote_code": True
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
if use_4bit:
|
|
407
|
+
if BitsAndBytesConfig is None:
|
|
408
|
+
raise ImportError("bitsandbytes required for 4-bit")
|
|
409
|
+
model_kwargs["quantization_config"] = BitsAndBytesConfig(
|
|
410
|
+
load_in_4bit=True,
|
|
411
|
+
bnb_4bit_quant_type="nf4",
|
|
412
|
+
bnb_4bit_compute_dtype=torch.float16,
|
|
413
|
+
bnb_4bit_use_double_quant=True
|
|
414
|
+
)
|
|
415
|
+
elif use_8bit:
|
|
416
|
+
if BitsAndBytesConfig is None:
|
|
417
|
+
raise ImportError("bitsandbytes required for 8-bit")
|
|
418
|
+
model_kwargs["quantization_config"] = BitsAndBytesConfig(
|
|
419
|
+
load_in_8bit=True
|
|
420
|
+
)
|
|
421
|
+
else:
|
|
422
|
+
model_kwargs["torch_dtype"] = torch.float16
|
|
423
|
+
|
|
340
424
|
model = AutoModelForCausalLM.from_pretrained(
|
|
341
425
|
base_model_id,
|
|
342
|
-
|
|
343
|
-
device_map="auto",
|
|
344
|
-
attn_implementation='eager'
|
|
426
|
+
**model_kwargs
|
|
345
427
|
)
|
|
346
|
-
|
|
428
|
+
|
|
347
429
|
tokenizer = AutoTokenizer.from_pretrained(
|
|
348
430
|
base_model_id,
|
|
349
431
|
trust_remote_code=True
|
|
350
432
|
)
|
|
351
|
-
|
|
433
|
+
|
|
352
434
|
if tokenizer.pad_token is None:
|
|
353
435
|
tokenizer.pad_token = tokenizer.eos_token
|
|
354
|
-
|
|
436
|
+
|
|
355
437
|
if adapter_path and os.path.exists(adapter_path):
|
|
356
438
|
print(f"Loading adapter: {adapter_path}")
|
|
357
439
|
model = PeftModel.from_pretrained(model, adapter_path)
|
|
358
|
-
|
|
359
|
-
|
|
440
|
+
if merge_adapter and not (use_4bit or use_8bit):
|
|
441
|
+
model = model.merge_and_unload()
|
|
442
|
+
|
|
360
443
|
return model, tokenizer
|
|
@@ -51,6 +51,7 @@ from npcpy.memory.command_history import setup_chroma_db
|
|
|
51
51
|
from npcpy.memory.search import execute_rag_command, execute_brainblast_command
|
|
52
52
|
from npcpy.data.load import load_file_contents
|
|
53
53
|
from npcpy.data.web import search_web
|
|
54
|
+
from npcpy.data.image import capture_screenshot
|
|
54
55
|
|
|
55
56
|
|
|
56
57
|
import base64
|
|
@@ -1008,7 +1009,7 @@ def get_attachment(attachment_id):
|
|
|
1008
1009
|
@app.route("/api/capture_screenshot", methods=["GET"])
|
|
1009
1010
|
def capture():
|
|
1010
1011
|
|
|
1011
|
-
screenshot = capture_screenshot(
|
|
1012
|
+
screenshot = capture_screenshot(full=True)
|
|
1012
1013
|
|
|
1013
1014
|
|
|
1014
1015
|
if not screenshot:
|
|
@@ -1895,10 +1896,26 @@ def finetune_diffusers():
|
|
|
1895
1896
|
'output_dir': output_dir,
|
|
1896
1897
|
'epochs': num_epochs,
|
|
1897
1898
|
'current_epoch': 0,
|
|
1899
|
+
'current_batch': 0,
|
|
1900
|
+
'total_batches': 0,
|
|
1901
|
+
'current_loss': None,
|
|
1902
|
+
'loss_history': [],
|
|
1903
|
+
'step': 0,
|
|
1898
1904
|
'start_time': datetime.datetime.now().isoformat()
|
|
1899
1905
|
}
|
|
1900
1906
|
print(f"🌋 Finetuning job {job_id} initialized. Output directory: {output_dir}")
|
|
1901
|
-
|
|
1907
|
+
|
|
1908
|
+
def progress_callback(progress_data):
|
|
1909
|
+
"""Callback to update job progress from training loop."""
|
|
1910
|
+
finetune_jobs[job_id]['current_epoch'] = progress_data.get('epoch', 0)
|
|
1911
|
+
finetune_jobs[job_id]['epochs'] = progress_data.get('total_epochs', num_epochs)
|
|
1912
|
+
finetune_jobs[job_id]['current_batch'] = progress_data.get('batch', 0)
|
|
1913
|
+
finetune_jobs[job_id]['total_batches'] = progress_data.get('total_batches', 0)
|
|
1914
|
+
finetune_jobs[job_id]['step'] = progress_data.get('step', 0)
|
|
1915
|
+
finetune_jobs[job_id]['current_loss'] = progress_data.get('loss')
|
|
1916
|
+
if progress_data.get('loss_history'):
|
|
1917
|
+
finetune_jobs[job_id]['loss_history'] = progress_data['loss_history']
|
|
1918
|
+
|
|
1902
1919
|
def run_training_async():
|
|
1903
1920
|
print(f"🌋 Finetuning job {job_id}: Starting asynchronous training thread...")
|
|
1904
1921
|
try:
|
|
@@ -1908,16 +1925,15 @@ def finetune_diffusers():
|
|
|
1908
1925
|
learning_rate=learning_rate,
|
|
1909
1926
|
output_model_path=output_dir
|
|
1910
1927
|
)
|
|
1911
|
-
|
|
1928
|
+
|
|
1912
1929
|
print(f"🌋 Finetuning job {job_id}: Calling train_diffusion with config: {config}")
|
|
1913
|
-
# Assuming train_diffusion might print its own progress or allow callbacks
|
|
1914
|
-
# For more granular logging, you'd need to modify train_diffusion itself
|
|
1915
1930
|
model_path = train_diffusion(
|
|
1916
1931
|
expanded_images,
|
|
1917
1932
|
captions,
|
|
1918
|
-
config=config
|
|
1933
|
+
config=config,
|
|
1934
|
+
progress_callback=progress_callback
|
|
1919
1935
|
)
|
|
1920
|
-
|
|
1936
|
+
|
|
1921
1937
|
finetune_jobs[job_id]['status'] = 'complete'
|
|
1922
1938
|
finetune_jobs[job_id]['model_path'] = model_path
|
|
1923
1939
|
finetune_jobs[job_id]['end_time'] = datetime.datetime.now().isoformat()
|
|
@@ -1947,21 +1963,32 @@ def finetune_diffusers():
|
|
|
1947
1963
|
def finetune_status(job_id):
|
|
1948
1964
|
if job_id not in finetune_jobs:
|
|
1949
1965
|
return jsonify({'error': 'Job not found'}), 404
|
|
1950
|
-
|
|
1966
|
+
|
|
1951
1967
|
job = finetune_jobs[job_id]
|
|
1952
|
-
|
|
1968
|
+
|
|
1953
1969
|
if job['status'] == 'complete':
|
|
1954
1970
|
return jsonify({
|
|
1971
|
+
'status': 'complete',
|
|
1955
1972
|
'complete': True,
|
|
1956
|
-
'outputPath': job.get('model_path', job['output_dir'])
|
|
1973
|
+
'outputPath': job.get('model_path', job['output_dir']),
|
|
1974
|
+
'loss_history': job.get('loss_history', [])
|
|
1957
1975
|
})
|
|
1958
1976
|
elif job['status'] == 'error':
|
|
1959
|
-
return jsonify({
|
|
1960
|
-
|
|
1977
|
+
return jsonify({
|
|
1978
|
+
'status': 'error',
|
|
1979
|
+
'error': job.get('error_msg', 'Unknown error')
|
|
1980
|
+
})
|
|
1981
|
+
|
|
1961
1982
|
return jsonify({
|
|
1962
|
-
'
|
|
1963
|
-
'
|
|
1964
|
-
'
|
|
1983
|
+
'status': 'running',
|
|
1984
|
+
'epoch': job.get('current_epoch', 0),
|
|
1985
|
+
'total_epochs': job.get('epochs', 0),
|
|
1986
|
+
'batch': job.get('current_batch', 0),
|
|
1987
|
+
'total_batches': job.get('total_batches', 0),
|
|
1988
|
+
'step': job.get('step', 0),
|
|
1989
|
+
'loss': job.get('current_loss'),
|
|
1990
|
+
'loss_history': job.get('loss_history', []),
|
|
1991
|
+
'start_time': job.get('start_time')
|
|
1965
1992
|
})
|
|
1966
1993
|
|
|
1967
1994
|
@app.route("/api/ml/train", methods=["POST"])
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: npcpy
|
|
3
|
-
Version: 1.3.
|
|
3
|
+
Version: 1.3.15
|
|
4
4
|
Summary: npcpy is the premier open-source library for integrating LLMs and Agents into python systems.
|
|
5
5
|
Home-page: https://github.com/NPC-Worldwide/npcpy
|
|
6
6
|
Author: Christopher Agostino
|
|
@@ -305,7 +305,7 @@ ggm = NPC(
|
|
|
305
305
|
isabel = NPC(
|
|
306
306
|
name='Isabel Allende',
|
|
307
307
|
primary_directive='You are Isabel Allende, weaving stories with emotion and history. Analyze texts and provide insight.',
|
|
308
|
-
model='llama3.2
|
|
308
|
+
model='llama3.2',
|
|
309
309
|
provider='ollama',
|
|
310
310
|
|
|
311
311
|
)
|
|
@@ -359,7 +359,7 @@ LLM responses can be obtained without NPCs as well.
|
|
|
359
359
|
|
|
360
360
|
```python
|
|
361
361
|
from npcpy.llm_funcs import get_llm_response
|
|
362
|
-
response = get_llm_response("Who was the celtic Messenger god?", model='
|
|
362
|
+
response = get_llm_response("Who was the celtic Messenger god?", model='qwen3:4b', provider='ollama')
|
|
363
363
|
print(response['response'])
|
|
364
364
|
```
|
|
365
365
|
|
|
@@ -400,7 +400,7 @@ Return structured outputs by specifying `format='json'` or passing a Pydantic sc
|
|
|
400
400
|
|
|
401
401
|
```python
|
|
402
402
|
from npcpy.llm_funcs import get_llm_response
|
|
403
|
-
response = get_llm_response("What is the sentiment of the american people towards the repeal of Roe v Wade? Return a json object with `sentiment` as the key and a float value from -1 to 1 as the value", model='
|
|
403
|
+
response = get_llm_response("What is the sentiment of the american people towards the repeal of Roe v Wade? Return a json object with `sentiment` as the key and a float value from -1 to 1 as the value", model='deepseek-chat', provider='deepseek', format='json')
|
|
404
404
|
|
|
405
405
|
print(response['response'])
|
|
406
406
|
```
|