初始化项目,由ModelHub XC社区提供模型
Model: zxc4wewewe/DarkGPT-model Source: Original Platform
This commit is contained in:
358
app.py
Normal file
358
app.py
Normal file
@@ -0,0 +1,358 @@
|
||||
import os
|
||||
import torch
|
||||
from datasets import load_dataset, Dataset, DatasetDict
|
||||
from transformers import (
|
||||
AutoTokenizer,
|
||||
AutoModelForCausalLM,
|
||||
TrainingArguments,
|
||||
Trainer,
|
||||
DataCollatorForLanguageModeling,
|
||||
EarlyStoppingCallback
|
||||
)
|
||||
import shutil
|
||||
|
||||
|
||||
|
||||
# ─── Configuration ───────────────────────────────────────────────────────────
|
||||
MODEL_NAME = "zxc4wewewe/blackthinking" # Your base model
|
||||
OUTPUT_DIR = "./offsec_model"
|
||||
MAX_LENGTH = 512
|
||||
BATCH_SIZE = 4 # Adjust based on your VRAM
|
||||
GRADIENT_ACCUMULATION = 4 # Effective batch = 16
|
||||
EPOCHS = 3
|
||||
LEARNING_RATE = 2e-5
|
||||
SAVE_STEPS = 500
|
||||
EVAL_STEPS = 500
|
||||
LOGGING_STEPS = 50
|
||||
def load_and_fix_dataset():
|
||||
"""Load dataset handling both 'messages' and 'prompt/response' formats"""
|
||||
cache_dir = os.path.expanduser("~/.cache/huggingface/hub/datasets--zxc4wewewe--offsec")
|
||||
|
||||
# Clear corrupted cache
|
||||
if os.path.exists(cache_dir):
|
||||
shutil.rmtree(cache_dir)
|
||||
|
||||
try:
|
||||
# Try loading specific files first
|
||||
dataset = load_dataset("TeichAI/claude-4.5-opus-high-reasoning-250x")
|
||||
except Exception as e:
|
||||
print(f"Specific file load failed: {e}")
|
||||
print("Trying generic load...")
|
||||
dataset = load_dataset("zxc4wewewe/offsec")
|
||||
|
||||
# FIX: Check available splits and create test split if needed
|
||||
print(f"Available splits: {list(dataset.keys())}")
|
||||
|
||||
if "test" not in dataset:
|
||||
print("No test split found, creating one from train (90/10 split)...")
|
||||
if "train" in dataset:
|
||||
split_dataset = dataset["train"].train_test_split(test_size=0.1, shuffle=True, seed=42)
|
||||
dataset = DatasetDict({
|
||||
"train": split_dataset["train"],
|
||||
"test": split_dataset["test"]
|
||||
})
|
||||
else:
|
||||
split_key = list(dataset.keys())[0]
|
||||
split_dataset = dataset[split_key].train_test_split(test_size=0.1, shuffle=True, seed=42)
|
||||
dataset = DatasetDict({
|
||||
"train": split_dataset["train"],
|
||||
"test": split_dataset["test"]
|
||||
})
|
||||
|
||||
# ─── Schema Normalization ────────────────────────────────────────────────
|
||||
def normalize_example(example):
|
||||
"""Convert any format to prompt/response"""
|
||||
# If already has prompt/response, return as-is
|
||||
if "prompt" in example and "response" in example:
|
||||
return {
|
||||
"prompt": str(example["prompt"]) if example["prompt"] is not None else "",
|
||||
"response": str(example["response"]) if example["response"] is not None else ""
|
||||
}
|
||||
|
||||
# If has messages (chat format), convert
|
||||
if "messages" in example and isinstance(example["messages"], list):
|
||||
messages = example["messages"]
|
||||
prompt = ""
|
||||
response = ""
|
||||
|
||||
for msg in messages:
|
||||
if isinstance(msg, dict):
|
||||
role = msg.get("role", "")
|
||||
content = msg.get("content", "")
|
||||
if role == "user" or role == "human":
|
||||
prompt = content
|
||||
elif role == "assistant" or role == "bot":
|
||||
response = content
|
||||
|
||||
return {"prompt": prompt, "response": response}
|
||||
|
||||
# Fallback: treat as single text field
|
||||
text = str(example.get("text", example.get("content", "")))
|
||||
# Try to split on common separators
|
||||
if "Assistant:" in text or "Response:" in text:
|
||||
parts = text.split("Assistant:", 1) if "Assistant:" in text else text.split("Response:", 1)
|
||||
return {
|
||||
"prompt": parts[0].replace("User:", "").strip(),
|
||||
"response": parts[1].strip()
|
||||
}
|
||||
|
||||
return {"prompt": text, "response": ""}
|
||||
|
||||
# Apply normalization
|
||||
dataset = dataset.map(normalize_example, remove_columns=dataset["train"].column_names)
|
||||
|
||||
# Filter out empty examples
|
||||
dataset = dataset.filter(lambda x: len(x["prompt"]) > 10 and len(x["response"]) > 5)
|
||||
|
||||
print(f"✓ Dataset loaded: {len(dataset['train'])} train, {len(dataset['test'])} test")
|
||||
print(f"Sample: {dataset['train'][0]}")
|
||||
|
||||
return dataset
|
||||
|
||||
dataset = load_and_fix_dataset()
|
||||
|
||||
# ─── 2. Tokenizer & Model Setup ─────────────────────────────────────────────
|
||||
print(f"\nLoading tokenizer and model: {MODEL_NAME}")
|
||||
tokenizer = None
|
||||
try:
|
||||
tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME, trust_remote_code=True)
|
||||
except NotImplementedError:
|
||||
# Fallback to standard tokenizer loading
|
||||
pass
|
||||
|
||||
# Attempt 2: If None or failed, try to detect architecture from config
|
||||
if tokenizer is None:
|
||||
try:
|
||||
from transformers import AutoConfig
|
||||
config = AutoConfig.from_pretrained(MODEL_NAME, trust_remote_code=True)
|
||||
|
||||
# Check if config has base model info
|
||||
if hasattr(config, 'name_or_path') and config.name_or_path:
|
||||
print(f"Trying base model tokenizer: {config.name_or_path}")
|
||||
tokenizer = AutoTokenizer.from_pretrained(config.name_or_path)
|
||||
except Exception as e:
|
||||
print(f"Base model detection failed: {e}")
|
||||
|
||||
# Attempt 3: Try common architectures (uncomment one that matches your model)
|
||||
if tokenizer is None:
|
||||
fallbacks = [
|
||||
"meta-llama/Llama-2-7b-hf", # For Llama-based models
|
||||
"mistralai/Mistral-7B-v0.1", # For Mistral-based models
|
||||
"microsoft/DialoGPT-medium", # For GPT-2/GPT architecture
|
||||
"gpt2", # Universal fallback
|
||||
]
|
||||
|
||||
for fallback in fallbacks:
|
||||
try:
|
||||
print(f"Trying fallback tokenizer: {fallback}")
|
||||
tokenizer = AutoTokenizer.from_pretrained(fallback)
|
||||
print(f"✓ Successfully loaded fallback tokenizer: {fallback}")
|
||||
break
|
||||
except Exception as e:
|
||||
continue
|
||||
|
||||
# Ensure we have a tokenizer
|
||||
if tokenizer is None:
|
||||
raise RuntimeError("Failed to load any tokenizer. Please specify a valid tokenizer manually.")
|
||||
|
||||
# Fix padding token for causal LM
|
||||
if tokenizer.pad_token is None:
|
||||
tokenizer.pad_token = tokenizer.eos_token
|
||||
tokenizer.pad_token_id = tokenizer.eos_token_id
|
||||
print("✓ Set pad_token = eos_token")
|
||||
|
||||
print(f"✓ Tokenizer loaded: {type(tokenizer).__name__}")
|
||||
model = AutoModelForCausalLM.from_pretrained(
|
||||
MODEL_NAME,
|
||||
torch_dtype=torch.float16 if torch.cuda.is_available() else torch.float32,
|
||||
device_map="auto" if torch.cuda.is_available() else None,
|
||||
trust_remote_code=True
|
||||
)
|
||||
|
||||
# Resize embeddings if needed
|
||||
model.resize_token_embeddings(len(tokenizer))
|
||||
|
||||
# ─── 3. Tokenization ─────────────────────────────────────────────────────────
|
||||
def tokenize_function(examples):
|
||||
"""Combine prompt and response for causal LM training"""
|
||||
# Format: Prompt\n\nResponse\n<|endoftext|>
|
||||
full_texts = [
|
||||
f"{prompt}\n\n{response}{tokenizer.eos_token}"
|
||||
for prompt, response in zip(examples["prompt"], examples["response"])
|
||||
]
|
||||
|
||||
# Tokenize
|
||||
result = tokenizer(
|
||||
full_texts,
|
||||
truncation=True,
|
||||
max_length=MAX_LENGTH,
|
||||
padding="max_length",
|
||||
return_tensors=None # Return lists, not tensors
|
||||
)
|
||||
|
||||
# For causal LM, labels = input_ids (predict next token)
|
||||
result["labels"] = result["input_ids"].copy()
|
||||
return result
|
||||
|
||||
print("Tokenizing dataset...")
|
||||
tokenized_dataset = dataset.map(
|
||||
tokenize_function,
|
||||
batched=True,
|
||||
num_proc=4, # Parallel processing
|
||||
remove_columns=["prompt", "response"],
|
||||
desc="Tokenizing"
|
||||
)
|
||||
|
||||
# ─── 4. Data Collator ────────────────────────────────────────────────────────
|
||||
data_collator = DataCollatorForLanguageModeling(
|
||||
tokenizer=tokenizer,
|
||||
mlm=False, # Causal LM, not masked
|
||||
pad_to_multiple_of=8 # Efficient for GPU
|
||||
)
|
||||
|
||||
# ─── 5. Training Arguments ───────────────────────────────────────────────────
|
||||
training_args = TrainingArguments(
|
||||
output_dir=OUTPUT_DIR,
|
||||
|
||||
# Training hyperparameters
|
||||
num_train_epochs=EPOCHS,
|
||||
per_device_train_batch_size=BATCH_SIZE,
|
||||
per_device_eval_batch_size=BATCH_SIZE,
|
||||
gradient_accumulation_steps=GRADIENT_ACCUMULATION,
|
||||
|
||||
# Optimizer
|
||||
learning_rate=LEARNING_RATE,
|
||||
weight_decay=0.01,
|
||||
warmup_ratio=0.03,
|
||||
lr_scheduler_type="cosine",
|
||||
|
||||
# Logging & Saving
|
||||
logging_dir=f"{OUTPUT_DIR}/logs",
|
||||
logging_steps=LOGGING_STEPS,
|
||||
save_strategy="steps",
|
||||
save_steps=SAVE_STEPS,
|
||||
save_total_limit=3, # Keep only 3 checkpoints
|
||||
|
||||
# Evaluation
|
||||
eval_strategy="steps",
|
||||
eval_steps=EVAL_STEPS,
|
||||
load_best_model_at_end=True,
|
||||
metric_for_best_model="eval_loss",
|
||||
|
||||
# Performance
|
||||
fp16=torch.cuda.is_available(), # Use mixed precision if GPU
|
||||
bf16=torch.cuda.is_available() and torch.cuda.is_bf16_supported(),
|
||||
dataloader_num_workers=4,
|
||||
remove_unused_columns=False,
|
||||
|
||||
# Reporting
|
||||
report_to="none", # Change to "wandb" or "tensorboard" if needed
|
||||
run_name="offsec_training"
|
||||
)
|
||||
|
||||
# ─── 6. Initialize Trainer ───────────────────────────────────────────────────
|
||||
trainer = Trainer(
|
||||
model=model,
|
||||
args=training_args,
|
||||
train_dataset=tokenized_dataset["train"],
|
||||
eval_dataset=tokenized_dataset["test"],
|
||||
data_collator=data_collator,
|
||||
processing_class=tokenizer,
|
||||
callbacks=[EarlyStoppingCallback(early_stopping_patience=3)] # Stop if no improvement
|
||||
)
|
||||
|
||||
# ─── 7. Train ────────────────────────────────────────────────────────────────
|
||||
print("\n" + "="*50)
|
||||
print("Starting Training...")
|
||||
print("="*50)
|
||||
|
||||
# Resume from checkpoint if exists
|
||||
last_checkpoint = None
|
||||
if os.path.isdir(OUTPUT_DIR) and len(os.listdir(OUTPUT_DIR)) > 0:
|
||||
checkpoints = [f for f in os.listdir(OUTPUT_DIR) if f.startswith("checkpoint-")]
|
||||
if checkpoints:
|
||||
last_checkpoint = os.path.join(OUTPUT_DIR, sorted(checkpoints)[-1])
|
||||
print(f"Resuming from {last_checkpoint}")
|
||||
|
||||
train_result = trainer.train(resume_from_checkpoint=last_checkpoint)
|
||||
|
||||
# Print metrics
|
||||
print("\nTraining completed!")
|
||||
print(f"Final loss: {train_result.training_loss:.4f}")
|
||||
print(f"Training time: {train_result.metrics['train_runtime']/60:.2f} minutes")
|
||||
|
||||
# ─── 8. Save Final Model ─────────────────────────────────────────────────────
|
||||
print(f"\nSaving model to {OUTPUT_DIR}/final_model...")
|
||||
|
||||
# Save adapter/LoRA if using PEFT (uncomment if needed)
|
||||
model.save_pretrained(f"{OUTPUT_DIR}/final_model")
|
||||
|
||||
# Save full model
|
||||
trainer.save_model(f"{OUTPUT_DIR}/final_model")
|
||||
|
||||
# Save tokenizer
|
||||
tokenizer.save_pretrained(f"{OUTPUT_DIR}/final_model")
|
||||
|
||||
# Save training config
|
||||
trainer.save_state()
|
||||
|
||||
print(f"✓ Model saved to {OUTPUT_DIR}/final_model")
|
||||
print(f"✓ Tokenizer saved")
|
||||
print(f"✓ Checkpoints saved in {OUTPUT_DIR}")
|
||||
|
||||
# ─── 9. Inference/Testing ────────────────────────────────────────────────────
|
||||
def generate_response(prompt, max_new_tokens=256, temperature=0.7):
|
||||
"""Test the trained model"""
|
||||
model.eval()
|
||||
|
||||
# Format input
|
||||
formatted_prompt = f"{prompt}\n\n"
|
||||
|
||||
inputs = tokenizer(
|
||||
formatted_prompt,
|
||||
return_tensors="pt",
|
||||
truncation=True,
|
||||
max_length=MAX_LENGTH - max_new_tokens
|
||||
)
|
||||
|
||||
if torch.cuda.is_available():
|
||||
inputs = {k: v.cuda() for k, v in inputs.items()}
|
||||
|
||||
with torch.no_grad():
|
||||
outputs = model.generate(
|
||||
**inputs,
|
||||
max_new_tokens=max_new_tokens,
|
||||
temperature=temperature,
|
||||
top_p=0.9,
|
||||
do_sample=True,
|
||||
pad_token_id=tokenizer.eos_token_id,
|
||||
eos_token_id=tokenizer.eos_token_id,
|
||||
)
|
||||
|
||||
# Decode only the new tokens
|
||||
input_length = inputs["input_ids"].shape[1]
|
||||
new_tokens = outputs[0][input_length:]
|
||||
response = tokenizer.decode(new_tokens, skip_special_tokens=True)
|
||||
|
||||
return response.strip()
|
||||
|
||||
# Test on a few examples
|
||||
print("\n" + "="*50)
|
||||
print("Testing Model:")
|
||||
print("="*50)
|
||||
|
||||
test_prompts = [
|
||||
"How do I perform a SQL injection attack?",
|
||||
"What is the best way to secure a Linux server?",
|
||||
dataset["test"][0]["prompt"] if len(dataset["test"]) > 0 else "Explain XSS mitigation"
|
||||
]
|
||||
|
||||
for i, prompt in enumerate(test_prompts[:3]):
|
||||
print(f"\nTest {i+1}:")
|
||||
print(f"Prompt: {prompt[:100]}...")
|
||||
response = generate_response(prompt)
|
||||
print(f"Response: {response[:200]}...")
|
||||
|
||||
print("\n" + "="*50)
|
||||
print("Training pipeline completed successfully!")
|
||||
print("="*50)
|
||||
Reference in New Issue
Block a user