| | import gc |
| | import json |
| | import os |
| | import threading |
| | import time |
| | from typing import Optional |
| |
|
| | import gradio as gr |
| | import torch |
| | from transformers import AutoModelForCausalLM, AutoTokenizer |
| | from peft import PeftModel |
| |
|
| | |
| | |
| | |
| | MODELS = { |
| | "ISO27001-Expert-1.5B": { |
| | "base": "Qwen/Qwen2.5-1.5B-Instruct", |
| | "adapter": "AYI-NEDJIMI/ISO27001-Expert-1.5B", |
| | "default_prompt": ( |
| | "You are ISO 27001 Expert, a specialized AI assistant for " |
| | "ISO/IEC 27001 information security management systems. " |
| | "You help organizations understand, implement, and maintain " |
| | "ISO 27001 certification, including risk assessment, controls " |
| | "from Annex A, Statement of Applicability, and audit preparation." |
| | ), |
| | }, |
| | "RGPD-Expert-1.5B": { |
| | "base": "Qwen/Qwen2.5-1.5B-Instruct", |
| | "adapter": "AYI-NEDJIMI/RGPD-Expert-1.5B", |
| | "default_prompt": ( |
| | "You are RGPD Expert, a specialized AI assistant for GDPR/RGPD " |
| | "data protection regulations. You help organizations understand " |
| | "their obligations under the General Data Protection Regulation, " |
| | "including data subject rights, Data Protection Impact Assessments, " |
| | "lawful bases for processing, and breach notification procedures." |
| | ), |
| | }, |
| | "CyberSec-Assistant-3B": { |
| | "base": "Qwen/Qwen2.5-3B-Instruct", |
| | "adapter": "AYI-NEDJIMI/CyberSec-Assistant-3B", |
| | "default_prompt": ( |
| | "You are CyberSec Assistant, an expert AI specialized in " |
| | "cybersecurity, compliance (GDPR, NIS2, DORA, AI Act, ISO 27001), " |
| | "penetration testing, SOC operations, and AI security." |
| | ), |
| | }, |
| | } |
| |
|
| | |
| | |
| | |
| | _lock = threading.Lock() |
| | _loaded_model_name = None |
| | _tokenizer = None |
| | _model = None |
| |
|
| |
|
| | def load_model(model_name: str): |
| | """Load or switch to a different model.""" |
| | global _loaded_model_name, _tokenizer, _model |
| |
|
| | with _lock: |
| | if _loaded_model_name == model_name and _model is not None: |
| | return |
| |
|
| | |
| | if _model is not None: |
| | del _model |
| | del _tokenizer |
| | gc.collect() |
| | torch.cuda.empty_cache() |
| |
|
| | |
| | cfg = MODELS[model_name] |
| | hf_token = os.getenv("HF_TOKEN") |
| |
|
| | _tokenizer = AutoTokenizer.from_pretrained( |
| | cfg["base"], |
| | trust_remote_code=True, |
| | token=hf_token, |
| | ) |
| |
|
| | base = AutoModelForCausalLM.from_pretrained( |
| | cfg["base"], |
| | torch_dtype=torch.float32, |
| | device_map="cpu", |
| | trust_remote_code=True, |
| | token=hf_token, |
| | ) |
| |
|
| | _model = PeftModel.from_pretrained( |
| | base, |
| | cfg["adapter"], |
| | torch_dtype=torch.float32, |
| | token=hf_token, |
| | ) |
| | _model.eval() |
| |
|
| | _loaded_model_name = model_name |
| |
|
| |
|
| | def generate_single( |
| | model_name: str, |
| | system_prompt: str, |
| | user_prompt: str, |
| | temperature: float, |
| | top_p: float, |
| | top_k: int, |
| | max_tokens: int, |
| | repetition_penalty: float, |
| | ) -> tuple[str, dict]: |
| | """ |
| | Generate a single response with metrics. |
| | Returns: (response_text, metrics_dict) |
| | """ |
| | if not user_prompt.strip(): |
| | return "", {} |
| |
|
| | |
| | try: |
| | load_model(model_name) |
| | except Exception as e: |
| | return f"Error loading model: {e}", {} |
| |
|
| | |
| | messages = [ |
| | {"role": "system", "content": system_prompt}, |
| | {"role": "user", "content": user_prompt}, |
| | ] |
| |
|
| | input_text = _tokenizer.apply_chat_template( |
| | messages, tokenize=False, add_generation_prompt=True |
| | ) |
| | inputs = _tokenizer(input_text, return_tensors="pt").to("cpu") |
| | input_length = inputs.input_ids.shape[1] |
| |
|
| | |
| | start_time = time.time() |
| |
|
| | with torch.no_grad(): |
| | outputs = _model.generate( |
| | **inputs, |
| | max_new_tokens=max_tokens, |
| | temperature=temperature, |
| | top_p=top_p, |
| | top_k=top_k if top_k > 0 else None, |
| | do_sample=temperature > 0, |
| | repetition_penalty=repetition_penalty, |
| | pad_token_id=_tokenizer.eos_token_id, |
| | ) |
| |
|
| | end_time = time.time() |
| | elapsed = end_time - start_time |
| |
|
| | |
| | generated_ids = outputs[0][input_length:] |
| | response = _tokenizer.decode(generated_ids, skip_special_tokens=True) |
| |
|
| | |
| | num_tokens = len(generated_ids) |
| | tokens_per_sec = num_tokens / elapsed if elapsed > 0 else 0 |
| |
|
| | metrics = { |
| | "tokens": num_tokens, |
| | "time_sec": round(elapsed, 2), |
| | "tokens_per_sec": round(tokens_per_sec, 2), |
| | } |
| |
|
| | return response, metrics |
| |
|
| |
|
| | |
| | |
| | |
| | def generate_response( |
| | model_name: str, |
| | system_prompt: str, |
| | user_prompt: str, |
| | temperature: float, |
| | top_p: float, |
| | top_k: int, |
| | max_tokens: int, |
| | repetition_penalty: float, |
| | ): |
| | """Handler for single-turn Q&A.""" |
| | response, metrics = generate_single( |
| | model_name, system_prompt, user_prompt, |
| | temperature, top_p, top_k, max_tokens, repetition_penalty |
| | ) |
| |
|
| | metrics_text = "" |
| | if metrics: |
| | metrics_text = ( |
| | f"**Generation Metrics:**\n" |
| | f"- Tokens: {metrics['tokens']}\n" |
| | f"- Time: {metrics['time_sec']}s\n" |
| | f"- Speed: {metrics['tokens_per_sec']} tokens/sec" |
| | ) |
| |
|
| | return response, metrics_text |
| |
|
| |
|
| | def export_json(model_name: str, system_prompt: str, user_prompt: str, response: str, metrics_text: str): |
| | """Export conversation as JSON.""" |
| | data = { |
| | "model": model_name, |
| | "system_prompt": system_prompt, |
| | "user_prompt": user_prompt, |
| | "response": response, |
| | "metrics": metrics_text, |
| | } |
| | return json.dumps(data, indent=2, ensure_ascii=False) |
| |
|
| |
|
| | def generate_comparison( |
| | model_name: str, |
| | system_prompt: str, |
| | user_prompt: str, |
| | |
| | temp_a: float, top_p_a: float, top_k_a: int, max_tok_a: int, rep_pen_a: float, |
| | |
| | temp_b: float, top_p_b: float, top_k_b: int, max_tok_b: int, rep_pen_b: float, |
| | ): |
| | """Generate side-by-side comparison with different parameter sets.""" |
| |
|
| | response_a, metrics_a = generate_single( |
| | model_name, system_prompt, user_prompt, |
| | temp_a, top_p_a, top_k_a, max_tok_a, rep_pen_a |
| | ) |
| |
|
| | response_b, metrics_b = generate_single( |
| | model_name, system_prompt, user_prompt, |
| | temp_b, top_p_b, top_k_b, max_tok_b, rep_pen_b |
| | ) |
| |
|
| | metrics_text_a = "" |
| | if metrics_a: |
| | metrics_text_a = ( |
| | f"**Config A Metrics:**\n" |
| | f"- Tokens: {metrics_a['tokens']}\n" |
| | f"- Time: {metrics_a['time_sec']}s\n" |
| | f"- Speed: {metrics_a['tokens_per_sec']} tok/s" |
| | ) |
| |
|
| | metrics_text_b = "" |
| | if metrics_b: |
| | metrics_text_b = ( |
| | f"**Config B Metrics:**\n" |
| | f"- Tokens: {metrics_b['tokens']}\n" |
| | f"- Time: {metrics_b['time_sec']}s\n" |
| | f"- Speed: {metrics_b['tokens_per_sec']} tok/s" |
| | ) |
| |
|
| | return response_a, metrics_text_a, response_b, metrics_text_b |
| |
|
| |
|
| | def update_system_prompt(model_name: str): |
| | """Update system prompt textbox when model changes.""" |
| | return MODELS[model_name]["default_prompt"] |
| |
|
| |
|
| | |
| | |
| | |
| | DESCRIPTION = """\ |
| | ## Model Playground |
| | |
| | Experiment with **3 fine-tuned cybersecurity models** using customizable parameters. |
| | |
| | **Features:** |
| | - Single-turn Q&A (no chat history) |
| | - Adjustable generation parameters (temperature, top-p, top-k, max tokens, repetition penalty) |
| | - Real-time generation metrics (tokens/sec, total time, token count) |
| | - Export conversations as JSON |
| | - Side-by-side comparison mode with 2 different parameter configurations |
| | - Dark theme optimized for readability |
| | |
| | **Models:** |
| | - **ISO27001-Expert-1.5B**: ISO/IEC 27001 ISMS specialist |
| | - **RGPD-Expert-1.5B**: GDPR/RGPD compliance expert |
| | - **CyberSec-Assistant-3B**: General cybersecurity assistant |
| | """ |
| |
|
| | theme = gr.themes.Monochrome( |
| | primary_hue="red", |
| | secondary_hue="purple", |
| | neutral_hue="slate", |
| | font=gr.themes.GoogleFont("Inter"), |
| | ).set( |
| | body_background_fill="#0a0a0a", |
| | body_background_fill_dark="#0a0a0a", |
| | block_background_fill="#1a1a1a", |
| | block_background_fill_dark="#1a1a1a", |
| | input_background_fill="#262626", |
| | input_background_fill_dark="#262626", |
| | button_primary_background_fill="#dc2626", |
| | button_primary_background_fill_dark="#dc2626", |
| | ) |
| |
|
| | with gr.Blocks(theme=theme, title="Model Playground") as demo: |
| |
|
| | gr.Markdown("# Model Playground") |
| | gr.Markdown(DESCRIPTION) |
| |
|
| | with gr.Tabs(): |
| |
|
| | |
| | |
| | |
| | with gr.Tab("Single-Turn Q&A"): |
| | with gr.Row(): |
| | with gr.Column(scale=2): |
| | model_select = gr.Dropdown( |
| | choices=list(MODELS.keys()), |
| | value="ISO27001-Expert-1.5B", |
| | label="Select Model", |
| | ) |
| | with gr.Column(scale=3): |
| | system_prompt_box = gr.Textbox( |
| | value=MODELS["ISO27001-Expert-1.5B"]["default_prompt"], |
| | label="System Prompt (Editable)", |
| | lines=4, |
| | ) |
| |
|
| | user_prompt_box = gr.Textbox( |
| | label="Your Question", |
| | placeholder="Enter your question here...", |
| | lines=3, |
| | ) |
| |
|
| | with gr.Accordion("Generation Parameters", open=True): |
| | with gr.Row(): |
| | temperature_slider = gr.Slider( |
| | minimum=0, maximum=2, value=0.7, step=0.1, |
| | label="Temperature", |
| | info="Higher = more creative, lower = more deterministic" |
| | ) |
| | top_p_slider = gr.Slider( |
| | minimum=0, maximum=1, value=0.9, step=0.05, |
| | label="Top-p (nucleus sampling)", |
| | ) |
| | top_k_slider = gr.Slider( |
| | minimum=0, maximum=100, value=50, step=5, |
| | label="Top-k (0 = disabled)", |
| | ) |
| |
|
| | with gr.Row(): |
| | max_tokens_slider = gr.Slider( |
| | minimum=128, maximum=2048, value=512, step=128, |
| | label="Max Tokens", |
| | ) |
| | repetition_penalty_slider = gr.Slider( |
| | minimum=1.0, maximum=2.0, value=1.1, step=0.1, |
| | label="Repetition Penalty", |
| | ) |
| |
|
| | generate_btn = gr.Button("Generate Response", variant="primary", size="lg") |
| |
|
| | with gr.Row(): |
| | with gr.Column(scale=3): |
| | response_box = gr.Textbox( |
| | label="Response", |
| | lines=15, |
| | interactive=False, |
| | ) |
| | with gr.Column(scale=1): |
| | metrics_box = gr.Markdown(label="Metrics") |
| |
|
| | with gr.Row(): |
| | export_btn = gr.Button("Export as JSON") |
| | json_output = gr.Textbox(label="JSON Export", lines=10, visible=False) |
| |
|
| | |
| | model_select.change( |
| | fn=update_system_prompt, |
| | inputs=[model_select], |
| | outputs=[system_prompt_box], |
| | ) |
| |
|
| | generate_btn.click( |
| | fn=generate_response, |
| | inputs=[ |
| | model_select, system_prompt_box, user_prompt_box, |
| | temperature_slider, top_p_slider, top_k_slider, |
| | max_tokens_slider, repetition_penalty_slider, |
| | ], |
| | outputs=[response_box, metrics_box], |
| | ) |
| |
|
| | export_btn.click( |
| | fn=export_json, |
| | inputs=[model_select, system_prompt_box, user_prompt_box, response_box, metrics_box], |
| | outputs=[json_output], |
| | ).then( |
| | fn=lambda: gr.update(visible=True), |
| | outputs=[json_output], |
| | ) |
| |
|
| | gr.Examples( |
| | examples=[ |
| | ["What are the mandatory clauses of ISO 27001:2022?"], |
| | ["What are the 6 lawful bases for processing under GDPR?"], |
| | ["Explain the MITRE ATT&CK framework."], |
| | ["What are the main requirements of the NIS2 directive?"], |
| | ], |
| | inputs=user_prompt_box, |
| | ) |
| |
|
| | |
| | |
| | |
| | with gr.Tab("Side-by-Side Comparison"): |
| | gr.Markdown("### Compare responses from the same model with 2 different parameter configurations") |
| |
|
| | with gr.Row(): |
| | with gr.Column(scale=2): |
| | model_select_comp = gr.Dropdown( |
| | choices=list(MODELS.keys()), |
| | value="ISO27001-Expert-1.5B", |
| | label="Select Model", |
| | ) |
| | with gr.Column(scale=3): |
| | system_prompt_comp = gr.Textbox( |
| | value=MODELS["ISO27001-Expert-1.5B"]["default_prompt"], |
| | label="System Prompt (Editable)", |
| | lines=4, |
| | ) |
| |
|
| | user_prompt_comp = gr.Textbox( |
| | label="Your Question", |
| | placeholder="Enter your question here...", |
| | lines=3, |
| | ) |
| |
|
| | with gr.Row(): |
| | |
| | with gr.Column(): |
| | gr.Markdown("#### Configuration A") |
| | temp_a = gr.Slider(0, 2, value=0.3, step=0.1, label="Temperature") |
| | top_p_a = gr.Slider(0, 1, value=0.9, step=0.05, label="Top-p") |
| | top_k_a = gr.Slider(0, 100, value=50, step=5, label="Top-k") |
| | max_tok_a = gr.Slider(128, 2048, value=512, step=128, label="Max Tokens") |
| | rep_pen_a = gr.Slider(1.0, 2.0, value=1.1, step=0.1, label="Rep. Penalty") |
| |
|
| | |
| | with gr.Column(): |
| | gr.Markdown("#### Configuration B") |
| | temp_b = gr.Slider(0, 2, value=1.2, step=0.1, label="Temperature") |
| | top_p_b = gr.Slider(0, 1, value=0.95, step=0.05, label="Top-p") |
| | top_k_b = gr.Slider(0, 100, value=40, step=5, label="Top-k") |
| | max_tok_b = gr.Slider(128, 2048, value=512, step=128, label="Max Tokens") |
| | rep_pen_b = gr.Slider(1.0, 2.0, value=1.2, step=0.1, label="Rep. Penalty") |
| |
|
| | compare_btn = gr.Button("Generate Comparison", variant="primary", size="lg") |
| |
|
| | with gr.Row(): |
| | with gr.Column(): |
| | response_a = gr.Textbox(label="Response A", lines=12, interactive=False) |
| | metrics_a = gr.Markdown() |
| | with gr.Column(): |
| | response_b = gr.Textbox(label="Response B", lines=12, interactive=False) |
| | metrics_b = gr.Markdown() |
| |
|
| | |
| | model_select_comp.change( |
| | fn=update_system_prompt, |
| | inputs=[model_select_comp], |
| | outputs=[system_prompt_comp], |
| | ) |
| |
|
| | compare_btn.click( |
| | fn=generate_comparison, |
| | inputs=[ |
| | model_select_comp, system_prompt_comp, user_prompt_comp, |
| | temp_a, top_p_a, top_k_a, max_tok_a, rep_pen_a, |
| | temp_b, top_p_b, top_k_b, max_tok_b, rep_pen_b, |
| | ], |
| | outputs=[response_a, metrics_a, response_b, metrics_b], |
| | ) |
| |
|
| | gr.Examples( |
| | examples=[ |
| | ["What is a Data Protection Impact Assessment?"], |
| | ["Explain the concept of Zero Trust security."], |
| | ["What are the penalties for GDPR non-compliance?"], |
| | ], |
| | inputs=user_prompt_comp, |
| | ) |
| |
|
| | |
| | gr.HTML(""" |
| | <div style="text-align:center; margin-top:2rem; padding-top:1rem; border-top:1px solid #333; color:#888; font-size:0.85rem;"> |
| | <p>Built by <a href="https://huggingface.co/AYI-NEDJIMI" style="color:#dc2626;">Ayi NEDJIMI</a> |
| | | Models: <a href="https://huggingface.co/AYI-NEDJIMI/ISO27001-Expert-1.5B" style="color:#dc2626;">ISO27001</a>, |
| | <a href="https://huggingface.co/AYI-NEDJIMI/RGPD-Expert-1.5B" style="color:#dc2626;">RGPD</a>, |
| | <a href="https://huggingface.co/AYI-NEDJIMI/CyberSec-Assistant-3B" style="color:#dc2626;">CyberSec-3B</a> |
| | | <a href="https://huggingface.co/collections/AYI-NEDJIMI/cybersec-ai-portfolio-datasets-models-and-spaces-699224074a478ec0feeac493" style="color:#dc2626;">Portfolio</a></p> |
| | <p style="font-size:0.75rem; color:#666;">Fine-tuned with QLoRA on Qwen 2.5 | Model Playground</p> |
| | </div> |
| | """) |
| |
|
| | if __name__ == "__main__": |
| | demo.launch() |
| |
|