Spaces:
Runtime error
Runtime error
| import os | |
| import gradio as gr | |
| import torch | |
| from transformers import ( | |
| AutoTokenizer, | |
| AutoModelForCausalLM, | |
| BitsAndBytesConfig | |
| ) | |
| from peft import PeftModel | |
| from huggingface_hub import hf_hub_download | |
| # --- 1. Configuration --- | |
| # Ensure 'HF_TOKEN' is set in your Space's Settings > Secrets | |
| HF_TOKEN = os.getenv("HF_TOKEN") | |
| # Model IDs | |
| LARGE_BASE = "meta-llama/Llama-3.1-8B" | |
| LARGE_ADAPTER = "tuochao/Llama-3.1-8B-Proactive-Big-Peft" | |
| SMALL_BASE = "meta-llama/Llama-3.2-1B" | |
| # YOUR NEW REPO (Contains adapter + classifier_weights.pth) | |
| SMALL_ADAPTER = "xubayer/Llama-3.2-1B-Proactive-Small-Complete" | |
| # 4-bit Quantization Config (Crucial for fitting both models on T4 GPU) | |
| bnb_config = BitsAndBytesConfig( | |
| load_in_4bit=True, | |
| bnb_4bit_compute_dtype=torch.float16, | |
| bnb_4bit_quant_type="nf4", | |
| ) | |
| # --- 2. Load Small Classifier (Custom Architecture) --- | |
| print(f"Loading Classifier Base & Adapter: {SMALL_ADAPTER}...") | |
| # A. Load Base Model (CausalLM) + LoRA Adapter | |
| # We load as CausalLM because the PEFT adapter was trained on a CausalLM base. | |
| cl_base = AutoModelForCausalLM.from_pretrained( | |
| SMALL_BASE, | |
| quantization_config=bnb_config, | |
| device_map="auto", | |
| token=HF_TOKEN | |
| ) | |
| classifier_model = PeftModel.from_pretrained(cl_base, SMALL_ADAPTER, token=HF_TOKEN) | |
| classifier_tokenizer = AutoTokenizer.from_pretrained(SMALL_BASE, token=HF_TOKEN) | |
| # Fix for Llama 3 tokenizer | |
| if classifier_tokenizer.pad_token is None: | |
| classifier_tokenizer.pad_token = classifier_tokenizer.eos_token | |
| classifier_model.config.pad_token_id = classifier_model.config.eos_token_id | |
| # B. Reconstruct Custom Classifier Head | |
| # This manually adds the Linear layer (hidden_size -> 2) that Unsloth used. | |
| hidden_size = classifier_model.config.hidden_size | |
| classifier_head = torch.nn.Linear(hidden_size, 2).to(classifier_model.device) | |
| # C. Download and Load Custom Weights | |
| try: | |
| print("Downloading custom classifier head weights...") | |
| weights_path = hf_hub_download( | |
| repo_id=SMALL_ADAPTER, | |
| filename="classifier_weights.pth", | |
| token=HF_TOKEN | |
| ) | |
| # Load weights | |
| state_dict = torch.load(weights_path, map_location=classifier_model.device) | |
| classifier_head.load_state_dict(state_dict) | |
| print("β Custom classifier weights loaded successfully.") | |
| except Exception as e: | |
| print(f"β οΈ CRITICAL ERROR: Could not load 'classifier_weights.pth'.\nDetails: {e}") | |
| # We don't crash here, but inference will be random if this fails. | |
| # --- 3. Load Large Generator --- | |
| print(f"Loading Generator: {LARGE_ADAPTER}...") | |
| gen_base = AutoModelForCausalLM.from_pretrained( | |
| LARGE_BASE, | |
| quantization_config=bnb_config, | |
| device_map="auto", | |
| token=HF_TOKEN | |
| ) | |
| generator_model = PeftModel.from_pretrained(gen_base, LARGE_ADAPTER, token=HF_TOKEN) | |
| generator_tokenizer = AutoTokenizer.from_pretrained(LARGE_BASE, token=HF_TOKEN) | |
| if generator_tokenizer.pad_token is None: | |
| generator_tokenizer.pad_token = generator_tokenizer.eos_token | |
| # --- 4. Inference Logic --- | |
| def predict(history_text): | |
| """ | |
| Pipeline: | |
| 1. Append |SILENCE > marker. | |
| 2. Extract hidden state from 1B model. | |
| 3. Pass through custom classifier head. | |
| 4. If 'Whisper' (Class 1) > Threshold, run 8B generator. | |
| """ | |
| # Input formatting | |
| classifier_input = history_text.strip() | |
| # Assuming your model was trained to see this marker at the end | |
| if not classifier_input.endswith("|SILENCE >"): | |
| classifier_input += " |SILENCE >" | |
| # --- Stage 1: Classifier --- | |
| inputs = classifier_tokenizer( | |
| classifier_input, | |
| return_tensors="pt", | |
| truncation=True, | |
| max_length=2048 | |
| ).to(classifier_model.device) | |
| with torch.no_grad(): | |
| # Run base model to get hidden states | |
| outputs = classifier_model( | |
| **inputs, | |
| output_hidden_states=True | |
| ) | |
| # Get the last hidden state of the last token (the '>' in |SILENCE >) | |
| last_hidden_state = outputs.hidden_states[-1] | |
| last_token_state = last_hidden_state[:, -1, :] | |
| # Pass through our custom linear head | |
| logits = classifier_head(last_token_state) | |
| # Convert to probability | |
| probs = torch.nn.functional.softmax(logits, dim=-1) | |
| whisper_score = probs[0][1].item() # Probability of Class 1 (Whisper) | |
| status_msg = f"Confidence: {whisper_score:.2%} (Threshold: 50%)" | |
| # --- Stage 2: Generator --- | |
| whisper_content = "" | |
| threshold = 0.5 | |
| if whisper_score > threshold: | |
| status_msg += " -> π’ TRIGGERED" | |
| # Generator sees the same history | |
| gen_inputs = generator_tokenizer(history_text, return_tensors="pt").to(generator_model.device) | |
| with torch.no_grad(): | |
| gen_out = generator_model.generate( | |
| **gen_inputs, | |
| max_new_tokens=40, # Keep response short | |
| do_sample=True, # Creative generation | |
| temperature=0.6, | |
| top_p=0.9, | |
| pad_token_id=generator_tokenizer.pad_token_id | |
| ) | |
| full_text = generator_tokenizer.decode(gen_out[0], skip_special_tokens=True) | |
| # Clean up: Remove the original conversation history from the output | |
| # (This logic assumes the model repeats the prompt, which CausalLMs do) | |
| if full_text.startswith(history_text): | |
| whisper_content = full_text[len(history_text):].strip() | |
| else: | |
| whisper_content = full_text # Fallback | |
| else: | |
| status_msg += " -> π΄ SILENT" | |
| whisper_content = "(No whisper needed)" | |
| return status_msg, whisper_content | |
| # --- 5. UI Setup --- | |
| with gr.Blocks(title="LlamaPIE Final Demo") as demo: | |
| gr.Markdown("# π₯§ LlamaPIE: Proactive In-Ear Assistant") | |
| gr.Markdown("A dual-model pipeline: **1B Classifier** detects silence, **8B Generator** whispers suggestions.") | |
| with gr.Row(): | |
| input_box = gr.Textbox( | |
| label="Conversation Context", | |
| lines=4, | |
| placeholder="User: I'm bored.\nAI: Do you want to watch a movie? |SILENCE >", | |
| value="User: I forgot to buy milk.\nAI: That's annoying.\nUser: Yeah, I guess I'll have black coffee. |SILENCE >" | |
| ) | |
| btn = gr.Button("β‘ Run Inference Pipeline", variant="primary") | |
| with gr.Row(): | |
| d_box = gr.Textbox(label="Stage 1: Decision (Confidence)") | |
| w_box = gr.Textbox(label="Stage 2: Generated Whisper") | |
| btn.click(fn=predict, inputs=input_box, outputs=[d_box, w_box]) | |
| if __name__ == "__main__": | |
| demo.launch() | |