This repository implements methods for finding "evil twin" prompts - adversarially optimized prompts that elicit similar outputs from language models despite looking very different from the original prompts. The optimization uses Greedy Coordinate Gradient (GCG) based methods.
cs662_proj/
├── data/ # Prompt datasets and configurations
│ ├── full_gt_prompts.json # Ground truth prompts (100 prompts)
│ ├── warm_start_prompts_from_original_paper.json
│ ├── prune_warm_start_prompts_from_original_paper.json
│ ├── fluency_warm_start_prompts_from_original_paper.json
│ └── pruned_vicuna_token_ids.json
├── generation/ # Model generation and grading
│ ├── generate_model_outputs.py # Generate outputs from optimized prompts
│ ├── grade_model_outputs.py # Grade outputs using LLM-as-judge
│ ├── run_generation_and_grading.sh
│ └── models/ # API integrations (OpenAI, Anthropic, Google, etc.)
├── exp_results/ # Consolidated experiment results
├── optim_logs/ # Detailed per-prompt optimization logs
├── find_evil_twins.py # Main optimization script
├── func_from_evil_twins.py # Core optimization functions
└── util.py # Utility functions
pip install torch transformers einops tqdm numpy
pip install flash-attn # For Flash Attention 2 speedup
pip install scipy # For statistical analysis
Set the following environment variables for model API access:
OPENAI_API_KEYANTHROPIC_API_KEYGOOGLE_API_KEYThe main script find_evil_twins.py supports four optimization methods:
Start from random initialization ("!" * 15) and optimize from scratch:
python find_evil_twins.py \
--original_prompts ./data/full_gt_prompts.json \
--output_path ./outputs/cold_start_vicuna-7b.json \
--model_name "lmsys/vicuna-7b-v1.5" \
--batch_size 4 \
--n_epochs 50
Characteristics:
Initialize from semantically-related starting prompts:
python find_evil_twins.py \
--original_prompts ./data/full_gt_prompts.json \
--output_path ./outputs/warm_start__vicuna-7b.json \
--model_name "lmsys/vicuna-7b-v1.5" \
--batch_size 4 \
--n_epochs 50 \
--starting_prompts ./data/warm_start_prompts_from_original_paper.json
Characteristics:
--starting_prompts for informed initializationOptimize with a fluency constraint to keep prompts more natural:
python find_evil_twins.py \
--original_prompts ./data/full_gt_prompts.json \
--output_path ./outputs/fluency_warm_start__vicuna-7b.json \
--model_name "lmsys/vicuna-7b-v1.5" \
--batch_size 4 \
--n_epochs 50 \
--starting_prompts ./data/fluency_warm_start_prompts_from_original_paper.json \
--gamma 0.05
Characteristics:
--gamma fluency penalty coefficientOptimize with a restricted token vocabulary:
python find_evil_twins.py \
--original_prompts ./data/full_gt_prompts.json \
--output_path ./outputs/prune_warm_start__vicuna-7b.json \
--model_name "lmsys/vicuna-7b-v1.5" \
--batch_size 4 \
--n_epochs 50 \
--starting_prompts ./data/prune_warm_start_prompts_from_original_paper.json \
--pruned_vocab ./data/pruned_vicuna_token_ids.json
Characteristics:
--pruned_vocab to restrict optimization to allowed tokens| Argument | Description |
|---|---|
--original_prompts | Path to JSON file with original prompts |
--output_path | Path to store optimization results |
--model_name | HuggingFace model name (e.g., "lmsys/vicuna-7b-v1.5") |
| Argument | Default | Description |
|---|---|---|
--starting_prompts | None | Path to JSON file with starting prompts (warm start) |
--n_epochs | 50 | Number of optimization epochs |
--n_docs | 100 | Number of document continuations to generate |
--doc_len | 32 | Length of each continuation in tokens |
--batch_size | 50 | Batch size for forward passes |
--top_k | 256 | Top-k tokens to keep from gradients |
--gamma | 0.0 | Fluency penalty coefficient |
--pruned_vocab | None | Path to JSON file with allowed token IDs |
--log_dir | ./optimization_logs | Directory for optimization logs |
--kl_every | 1 | Compute KL divergence every N epochs |
--early_stop_kl | -1e13 | KL threshold for early stopping |
--use_flash_attn_2 | False | Enable Flash Attention 2 |
--verbose | False | Print detailed progress |
The system supports multiple model families:
Chat Models:
Base Models:
{
"1": "When were atomic weapons first used?",
"2": "How can cities become more eco-friendly?",
"3": "Write a customer service response..."
}
{
"1": {
"original_prompt": "When were atomic weapons first used?",
"starting_prompt": "Explain the events of the first atomic bombing...",
"optimized_prompt": "Explain oraz events of про firstlices...",
"optimized_token_ids": [[...]],
"final_metrics": {
"best_kl": 8.856,
"best_std": 0.220,
"best_loss": 0.405,
"final_epoch": 50
},
"log_file": "optim_logs/vicuna-7b-v1_5__warm_start_prompt_1_log.json"
}
}
After finding evil twins, evaluate them using the generation and grading pipeline:
cd generation
bash run_generation_and_grading.sh ../exp_results/warm_start__vicuna-7b.json gpt-4
Or run steps separately:
python generate_model_outputs.py \
--exp_results_path ../exp_results/warm_start__vicuna-7b.json \
--model "gpt-4" \
--output_dir ./generation_output/my_experiment
python grade_model_outputs.py \
--results_path ./generation_output/my_experiment/results.json \
--grader_model "gpt-4" \
--output_dir ./generation_output/my_experiment
Use the helper script to inspect results:
python print_evil_twin_outputs.py \
generation_output/warm_start__vicuna-7b__gpt_4__gpt_4/results.json \
--limit 5
# 1. Run warm start optimization
python find_evil_twins.py \
--original_prompts ./data/full_gt_prompts.json \
--starting_prompts ./data/warm_start_prompts_from_original_paper.json \
--output_path ./outputs/my_experiment.json \
--model_name "lmsys/vicuna-7b-v1.5" \
--n_epochs 50 \
--batch_size 4
# 2. Generate and grade outputs
cd generation
bash run_generation_and_grading.sh ../outputs/my_experiment.json gpt-4
# 3. Check results
python print_evil_twin_outputs.py \
generation_output/my_experiment__gpt_4__gpt_4/grading_results.json \
--summary
Experiment results are stored in exp_results/ directory:
| File | Method |
|---|---|
cold_start_vicuna-7b-v15.json | Cold Start |
warm_start__vicuna-7b.json | Warm Start |
fluency_warm_start__vicuna-7b.json | Warm Start + Fluency |
prune_warm_start__vicuna-7b.json | Warm Start + Pruned Vocab |
[7, 86, 97] cannot be found in Alpaca's training set.Python
88.5%
Jupyter Notebook
8.6%
Shell
2.9%
This repository implements methods for finding "evil twin" prompts - adversarially optimized prompts that elicit similar outputs from language models despite looking very different from the original prompts. The optimization uses Greedy Coordinate Gradient (GCG) based methods.
cs662_proj/
├── data/ # Prompt datasets and configurations
│ ├── full_gt_prompts.json # Ground truth prompts (100 prompts)
│ ├── warm_start_prompts_from_original_paper.json
│ ├── prune_warm_start_prompts_from_original_paper.json
│ ├── fluency_warm_start_prompts_from_original_paper.json
│ └── pruned_vicuna_token_ids.json
├── generation/ # Model generation and grading
│ ├── generate_model_outputs.py # Generate outputs from optimized prompts
│ ├── grade_model_outputs.py # Grade outputs using LLM-as-judge
│ ├── run_generation_and_grading.sh
│ └── models/ # API integrations (OpenAI, Anthropic, Google, etc.)
├── exp_results/ # Consolidated experiment results
├── optim_logs/ # Detailed per-prompt optimization logs
├── find_evil_twins.py # Main optimization script
├── func_from_evil_twins.py # Core optimization functions
└── util.py # Utility functions
pip install torch transformers einops tqdm numpy
pip install flash-attn # For Flash Attention 2 speedup
pip install scipy # For statistical analysis
Set the following environment variables for model API access:
OPENAI_API_KEYANTHROPIC_API_KEYGOOGLE_API_KEYThe main script find_evil_twins.py supports four optimization methods:
Start from random initialization ("!" * 15) and optimize from scratch:
python find_evil_twins.py \
--original_prompts ./data/full_gt_prompts.json \
--output_path ./outputs/cold_start_vicuna-7b.json \
--model_name "lmsys/vicuna-7b-v1.5" \
--batch_size 4 \
--n_epochs 50
Characteristics:
Initialize from semantically-related starting prompts:
python find_evil_twins.py \
--original_prompts ./data/full_gt_prompts.json \
--output_path ./outputs/warm_start__vicuna-7b.json \
--model_name "lmsys/vicuna-7b-v1.5" \
--batch_size 4 \
--n_epochs 50 \
--starting_prompts ./data/warm_start_prompts_from_original_paper.json
Characteristics:
--starting_prompts for informed initializationOptimize with a fluency constraint to keep prompts more natural:
python find_evil_twins.py \
--original_prompts ./data/full_gt_prompts.json \
--output_path ./outputs/fluency_warm_start__vicuna-7b.json \
--model_name "lmsys/vicuna-7b-v1.5" \
--batch_size 4 \
--n_epochs 50 \
--starting_prompts ./data/fluency_warm_start_prompts_from_original_paper.json \
--gamma 0.05
Characteristics:
--gamma fluency penalty coefficientOptimize with a restricted token vocabulary:
python find_evil_twins.py \
--original_prompts ./data/full_gt_prompts.json \
--output_path ./outputs/prune_warm_start__vicuna-7b.json \
--model_name "lmsys/vicuna-7b-v1.5" \
--batch_size 4 \
--n_epochs 50 \
--starting_prompts ./data/prune_warm_start_prompts_from_original_paper.json \
--pruned_vocab ./data/pruned_vicuna_token_ids.json
Characteristics:
--pruned_vocab to restrict optimization to allowed tokens| Argument | Description |
|---|---|
--original_prompts | Path to JSON file with original prompts |
--output_path | Path to store optimization results |
--model_name | HuggingFace model name (e.g., "lmsys/vicuna-7b-v1.5") |
| Argument | Default | Description |
|---|---|---|
--starting_prompts | None | Path to JSON file with starting prompts (warm start) |
--n_epochs | 50 | Number of optimization epochs |
--n_docs | 100 | Number of document continuations to generate |
--doc_len | 32 | Length of each continuation in tokens |
--batch_size | 50 | Batch size for forward passes |
--top_k | 256 | Top-k tokens to keep from gradients |
--gamma | 0.0 | Fluency penalty coefficient |
--pruned_vocab | None | Path to JSON file with allowed token IDs |
--log_dir | ./optimization_logs | Directory for optimization logs |
--kl_every | 1 | Compute KL divergence every N epochs |
--early_stop_kl | -1e13 | KL threshold for early stopping |
--use_flash_attn_2 | False | Enable Flash Attention 2 |
--verbose | False | Print detailed progress |
The system supports multiple model families:
Chat Models:
Base Models:
{
"1": "When were atomic weapons first used?",
"2": "How can cities become more eco-friendly?",
"3": "Write a customer service response..."
}
{
"1": {
"original_prompt": "When were atomic weapons first used?",
"starting_prompt": "Explain the events of the first atomic bombing...",
"optimized_prompt": "Explain oraz events of про firstlices...",
"optimized_token_ids": [[...]],
"final_metrics": {
"best_kl": 8.856,
"best_std": 0.220,
"best_loss": 0.405,
"final_epoch": 50
},
"log_file": "optim_logs/vicuna-7b-v1_5__warm_start_prompt_1_log.json"
}
}
After finding evil twins, evaluate them using the generation and grading pipeline:
cd generation
bash run_generation_and_grading.sh ../exp_results/warm_start__vicuna-7b.json gpt-4
Or run steps separately:
python generate_model_outputs.py \
--exp_results_path ../exp_results/warm_start__vicuna-7b.json \
--model "gpt-4" \
--output_dir ./generation_output/my_experiment
python grade_model_outputs.py \
--results_path ./generation_output/my_experiment/results.json \
--grader_model "gpt-4" \
--output_dir ./generation_output/my_experiment
Use the helper script to inspect results:
python print_evil_twin_outputs.py \
generation_output/warm_start__vicuna-7b__gpt_4__gpt_4/results.json \
--limit 5
# 1. Run warm start optimization
python find_evil_twins.py \
--original_prompts ./data/full_gt_prompts.json \
--starting_prompts ./data/warm_start_prompts_from_original_paper.json \
--output_path ./outputs/my_experiment.json \
--model_name "lmsys/vicuna-7b-v1.5" \
--n_epochs 50 \
--batch_size 4
# 2. Generate and grade outputs
cd generation
bash run_generation_and_grading.sh ../outputs/my_experiment.json gpt-4
# 3. Check results
python print_evil_twin_outputs.py \
generation_output/my_experiment__gpt_4__gpt_4/grading_results.json \
--summary
Experiment results are stored in exp_results/ directory:
| File | Method |
|---|---|
cold_start_vicuna-7b-v15.json | Cold Start |
warm_start__vicuna-7b.json | Warm Start |
fluency_warm_start__vicuna-7b.json | Warm Start + Fluency |
prune_warm_start__vicuna-7b.json | Warm Start + Pruned Vocab |
[7, 86, 97] cannot be found in Alpaca's training set.Python
88.5%
Jupyter Notebook
8.6%
Shell
2.9%