""" Train a single expert adapter for smart contract vulnerability detection. Usage: python train_expert.py --expert Reentrancy python train_expert.py --expert "Access Control" python train_expert.py --expert "Integer Overflow/Underflow" python train_expert.py --expert "Timestamp Dependence" python train_expert.py --expert "Unchecked Low-Level Calls" Each expert is a LoRA adapter on Qwen2.5-Coder-3B-Instruct, trained to answer: "Is this contract vulnerable with MY specific vulnerability type?" Positives: contracts with the expert's vulnerability type Negatives: safe contracts + contracts with OTHER vulnerability types """ import argparse import os import torch from datasets import load_dataset from peft import LoraConfig from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig from trl import SFTConfig, SFTTrainer from huggingface_hub import HfApi import trackio BASE_MODEL = "Qwen/Qwen2.5-Coder-3B-Instruct" # Expert → dataset mapping EXPERT_DATASETS = { "Reentrancy": "jhsu12/solidity-vuln-expert-reentrancy", "Access Control": "jhsu12/solidity-vuln-expert-access-control", "Integer Overflow/Underflow": "jhsu12/solidity-vuln-expert-integer-overflow-underflow", "Timestamp Dependence": "jhsu12/solidity-vuln-expert-timestamp-dependence", "Unchecked Low-Level Calls": "jhsu12/solidity-vuln-expert-unchecked-low-level-calls", } def parse_args(): parser = argparse.ArgumentParser() parser.add_argument("--expert", type=str, required=True, choices=list(EXPERT_DATASETS.keys()), help="Vulnerability type to train expert for") parser.add_argument("--output_dir", type=str, default=None) parser.add_argument("--lora_r", type=int, default=32) parser.add_argument("--epochs", type=int, default=5) parser.add_argument("--batch_size", type=int, default=2) parser.add_argument("--grad_accum", type=int, default=4) parser.add_argument("--lr", type=float, default=2e-4) parser.add_argument("--max_seq_len", type=int, default=1536) parser.add_argument("--push_to_hub", action="store_true", default=True) return parser.parse_args() def main(): args = parse_args() expert_name = args.expert dataset_id = EXPERT_DATASETS[expert_name] hub_model_id = f"jhsu12/solidity-vuln-expert-{expert_name.lower().replace(' ', '-').replace('/', '-')}-v1" output_dir = args.output_dir or f"./expert-{expert_name.lower().replace(' ', '-').replace('/', '-')}" print(f"=" * 60) print(f" Training Expert: {expert_name}") print(f" Base Model: {BASE_MODEL}") print(f" Dataset: {dataset_id}") print(f" Hub Model: {hub_model_id}") print(f"=" * 60) # GPU config HAS_BF16 = torch.cuda.is_bf16_supported() if torch.cuda.is_available() else False GPU_MEM = torch.cuda.get_device_properties(0).total_memory / 1e9 if torch.cuda.is_available() else 0 print(f"\nšŸ–„ļø GPU: {torch.cuda.get_device_name(0) if torch.cuda.is_available() else 'CPU'}") print(f"šŸ’¾ VRAM: {GPU_MEM:.1f} GB") print(f"šŸ”¢ BF16: {HAS_BF16}") compute_dtype = torch.bfloat16 if HAS_BF16 else torch.float16 # Trackio monitoring trackio.init( project=f"solidity-expert-{expert_name.lower().replace(' ', '-').replace('/', '-')}", name=f"{expert_name.lower().replace(' ', '-').replace('/', '-')}-3b-v1", ) # Load dataset print(f"\nšŸ“¦ Loading dataset...") dataset = load_dataset(dataset_id) train_dataset = dataset["train"] eval_dataset = dataset["test"] print(f" Train: {len(train_dataset)} Eval: {len(eval_dataset)}") # Load model with 4-bit quantization bnb_config = BitsAndBytesConfig( load_in_4bit=True, bnb_4bit_quant_type="nf4", bnb_4bit_compute_dtype=compute_dtype, bnb_4bit_use_double_quant=True, ) print(f"\nšŸ¤– Loading {BASE_MODEL}...") model = AutoModelForCausalLM.from_pretrained( BASE_MODEL, quantization_config=bnb_config, device_map="auto", dtype=compute_dtype, trust_remote_code=True, attn_implementation="sdpa", ) tokenizer = AutoTokenizer.from_pretrained(BASE_MODEL, trust_remote_code=True) if tokenizer.pad_token is None: tokenizer.pad_token = tokenizer.eos_token tokenizer.padding_side = "right" print(" āœ… Model loaded") # LoRA config peft_config = LoraConfig( r=args.lora_r, lora_alpha=16, lora_dropout=0.05, bias="none", task_type="CAUSAL_LM", target_modules="all-linear", ) # Training config training_args = SFTConfig( output_dir=output_dir, num_train_epochs=args.epochs, per_device_train_batch_size=args.batch_size, per_device_eval_batch_size=1, gradient_accumulation_steps=args.grad_accum, eval_accumulation_steps=1, learning_rate=args.lr, bf16=HAS_BF16, fp16=not HAS_BF16, gradient_checkpointing=True, gradient_checkpointing_kwargs={"use_reentrant": False}, max_length=args.max_seq_len, packing=False, optim="paged_adamw_8bit", warmup_steps=20, lr_scheduler_type="cosine", weight_decay=0.01, max_grad_norm=0.3, logging_steps=10, logging_first_step=True, logging_strategy="steps", disable_tqdm=True, report_to=["trackio"], save_strategy="epoch", eval_strategy="epoch", load_best_model_at_end=True, metric_for_best_model="eval_loss", push_to_hub=False, # We push manually at the end seed=42, ) # Train print(f"\nšŸ‹ļø Initializing trainer...") trainer = SFTTrainer( model=model, args=training_args, train_dataset=train_dataset, eval_dataset=eval_dataset, processing_class=tokenizer, peft_config=peft_config, ) trainable = sum(p.numel() for p in model.parameters() if p.requires_grad) total = sum(p.numel() for p in model.parameters()) print(f" Trainable: {trainable:,} ({100*trainable/total:.2f}%)") print(f"\nšŸš€ Starting training for {expert_name} expert...") train_result = trainer.train() print(f"\nāœ… Training complete!") print(f" Train loss: {train_result.training_loss:.4f}") # Get best eval loss from training (eval already ran each epoch) best_eval_loss = trainer.state.best_metric print(f"\nšŸ“Š Best eval loss (from training): {best_eval_loss:.4f}") # Save save_dir = os.path.join(output_dir, "best_model") print(f"\nšŸ’¾ Saving to {save_dir}...") trainer.save_model(save_dir) tokenizer.save_pretrained(save_dir) # Push to hub if args.push_to_hub: print(f"\nšŸš€ Pushing to {hub_model_id}...") api = HfApi() api.upload_folder( folder_path=save_dir, repo_id=hub_model_id, ignore_patterns=["optimizer*", "scheduler*", "training_args*", "trainer_state*", "rng_state*"], commit_message=f"Expert adapter for {expert_name} vulnerability detection (3B base)", ) print(f" āœ… Pushed to https://hf.co/{hub_model_id}") print(f"\n{'='*60}") print(f" Expert {expert_name} Complete!") print(f" Base Model: {BASE_MODEL}") print(f" Train loss: {train_result.training_loss:.4f}") print(f" Eval loss: {best_eval_loss:.4f}") print(f"{'='*60}") if __name__ == "__main__": main()