Emotional RLAIF PPO Meta-Llama-3-8B-Instruct

This repository contains a LoRA/PEFT adapter trained from meta-llama/Meta-Llama-3-8B-Instruct with LLaMA-Factory using Proximal Policy Optimization (PPO) for emotional response alignment.

The adapter was trained as part of an emotional RLAIF pipeline using the mario-rc/aif-emotional-generation dataset, with dialogues used for SFT and PPO, and aif_annotations preferences used for RM preference alignment.

Project repository: Mario-RC/aif-emotional-model.

Intended Use

This adapter is intended for research and experimentation with emotionally aligned dialogue generation. It should be loaded on top of the corresponding base model using PEFT.

Model Details

  • Base model: meta-llama/Meta-Llama-3-8B-Instruct
  • Adapter repository: mario-rc/emotional-rlaif-ppo-meta-llama-3-8b-instruct
  • Adapter type: LoRA / PEFT
  • Alignment method: PPO
  • Training framework: LLaMA-Factory
  • Prompt template: llama3
  • Dataset: mario-rc/aif-emotional-generation

Released Emotional RLAIF Models

The released emotional RLAIF adapters are available on Hugging Face:

Training Procedure

Key hyperparameters for this adapter:

  • Learning rate: 1e-5
  • Epochs: 1
  • Scheduler: cosine
  • Warmup ratio: 0.1
  • SFT data: dialogues
  • PPO prompt source: dialogues
  • Reward model data: aif_annotations preference pairs
  • Precision: bfloat16
  • Optimizer: AdamW (torch)

Evaluation

The released adapters were evaluated on the held-out emotional dialogue generation split with automatic reference-overlap metrics. These metrics measure similarity to reference responses and should be complemented with human or judge-based evaluation for emotional quality.

All released adapters:

Model Alignment BLEU-4 ROUGE-1 ROUGE-2 ROUGE-L
emotional-rlaif-dpo-gemma-2-2b-it DPO 38.0977 39.3142 19.5753 33.7190
emotional-rlaif-ppo-gemma-2-9b-it PPO 44.4333 42.5233 22.7153 37.7601
emotional-rlaif-dpo-gemma-2-9b-it DPO 41.9904 40.8481 21.2884 35.8110
emotional-rlaif-ppo-glm-4-9b-chat-1m PPO 45.0070 42.0043 22.8903 37.9557
emotional-rlaif-dpo-glm-4-9b-chat-1m DPO 40.0157 39.5339 19.6798 34.3553
emotional-rlaif-ppo-meta-llama-3-8b-instruct PPO 44.3123 43.1426 23.3597 38.3680
emotional-rlaif-dpo-meta-llama-3-8b-instruct DPO 41.0271 41.0354 21.1472 36.0910
emotional-rlaif-ppo-llama-3.2-1b-instruct PPO 41.2577 39.9133 20.1419 34.4066
emotional-rlaif-dpo-llama-3.2-3b-instruct DPO 41.7391 40.7582 20.9527 35.5167
emotional-rlaif-ppo-mistral-7b-instruct-v0.3 PPO 45.9741 44.2755 25.5357 40.8223
emotional-rlaif-ppo-phi-3-small-8k-instruct PPO 45.6238 44.3653 25.4530 40.1762

Framework Versions

  • PEFT: 0.11.1
  • Transformers: 4.42.x / 4.45.x training environments
  • PyTorch: bfloat16 CUDA training
  • LLaMA-Factory: LoRA/PEFT training workflow

Usage Example

import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
from peft import PeftModel

base_model_id = "meta-llama/Meta-Llama-3-8B-Instruct"
adapter_id = "mario-rc/emotional-rlaif-ppo-meta-llama-3-8b-instruct"

tokenizer = AutoTokenizer.from_pretrained(base_model_id)
model = AutoModelForCausalLM.from_pretrained(
    base_model_id,
    device_map="auto",
    torch_dtype=torch.bfloat16,
)
model = PeftModel.from_pretrained(model, adapter_id)
model.eval()

messages = [
    {"role": "user", "content": "I feel overwhelmed today. Can you respond with empathy?"}
]

inputs = tokenizer.apply_chat_template(
    messages,
    add_generation_prompt=True,
    return_tensors="pt",
).to(model.device)

with torch.no_grad():
    outputs = model.generate(
        inputs,
        max_new_tokens=256,
        do_sample=True,
        temperature=0.7,
        top_p=0.9,
    )

print(tokenizer.decode(outputs[0][inputs.shape[-1]:], skip_special_tokens=True))

How to Use

The following example loads this adapter and runs an interactive emotional dialogue loop. Use exit to stop the chat.

import random
import sys

import torch
from peft import AutoPeftModelForCausalLM
from transformers import AutoTokenizer

MODEL_ID = "mario-rc/emotional-rlaif-ppo-meta-llama-3-8b-instruct"


def get_turn_markers():
    return {
        'bos': '<|begin_of_text|>',
        'user_start': '<|start_header_id|>user<|end_header_id|>\n\n',
        'user_end': '<|eot_id|>',
        'assistant_start': '<|start_header_id|>assistant<|end_header_id|>\n\n',
        'assistant_end': '<|eot_id|>',
    }


def update_prompt(dialogues):
    """Build the prompt for the model based on the dialogue history."""
    markers = get_turn_markers()

    system = (
        f"{markers['bos']}You are an expert at creating dialogues.\n\n"
        "Dialogue and emotional structure:\n"
    )

    human_prompts = [d[0] for d in dialogues]
    chatbot_responses = [d[1] for d in dialogues]

    p_emo = [h[0] for h in human_prompts]
    p_utt = [h[1] for h in human_prompts]
    r1_utt = [c[1] for c in chatbot_responses]
    r2_emo = [c[2] for c in chatbot_responses]
    r2_utt = [c[3] for c in chatbot_responses]
    r3_utt = [c[5] for c in chatbot_responses]

    context = (
        "Human: (HAPPINESS) PROMPT.\n"
        "Chatbot: (HAPPINESS) RESPONSE_1. (HAPPINESS) RESPONSE_2. (NEUTRAL) RESPONSE_3.\n"
    )
    for p_e, _, _, r2_e, _, _ in zip(p_emo, p_utt, r1_utt, r2_emo, r2_utt, r3_utt):
        context += f"Human: ({p_e}) PROMPT.\n"
        context += f"Chatbot: ({p_e}) RESPONSE_1. ({r2_e}) RESPONSE_2. (NEUTRAL) RESPONSE_3.\n"
    context += "\n"

    rules = (
        "Dialogue rules:\n"
        "The response must be open-domain curated. The response should be coherent, empathetic, engaging and proactive.\n"
        "The chatbot RESPONSE is composed of 3 different sentences (RESPONSE_1, RESPONSE_2 and RESPONSE_3), separated by a period.\n"
        "Between RESPONSE_1, RESPONSE_2 and RESPONSE_3 should be a max length of 20-25 words.\n"
        "RESPONSE_3 must be open-ended to follow-up the conversation, so the Human is encouraged to answer with a full long sentence. Avoid yes/no questions.\n\n"
        "Emotional response rules:\n"
        f"RESPONSE_1 must contain a {p_emo[-1]} tone.\n"
        f"RESPONSE_2 must contain a {r2_emo[-1]} tone.\n"
        "RESPONSE_3 must contain a NEUTRAL tone.\n\n"
        "Answer in a single turn to Human. Follow exactly the emotional structure and the emotional and dialogue rules."
        f"{markers['user_start']}"
    )

    completion = ""
    for idx, (p_e, p_u, r1_u, r2_e, r2_u, r3_u) in enumerate(zip(p_emo, p_utt, r1_utt, r2_emo, r2_utt, r3_utt)):
        completion += f"({p_e}) {p_u}{markers['user_end']}{markers['assistant_start']}"
        if idx != len(p_emo) - 1:
            completion += f"({p_e}) {r1_u} ({r2_e}) {r2_u} (NEUTRAL) {r3_u}{markers['assistant_end']}{markers['user_start']}"

    return system + context + rules + completion


class Chatbot:
    def __init__(self, dialogue_language="es"):
        self.dialogue_language = dialogue_language
        self.device = "cuda" if torch.cuda.is_available() else "cpu"

        self.tokenizer = AutoTokenizer.from_pretrained(MODEL_ID)
        self.model = AutoPeftModelForCausalLM.from_pretrained(
            MODEL_ID,
            torch_dtype=torch.bfloat16 if torch.cuda.is_available() else torch.float32,
            device_map="auto" if torch.cuda.is_available() else None,
        )
        if not torch.cuda.is_available():
            self.model = self.model.to(self.device)
        self.model.eval()

    def split_emo_chatbot(sentence):
        """Extract emotions and utterances from a chatbot response."""
        response_pos_ini = [i for i, c in enumerate(sentence) if c == "("]
        response_pos_end = [i for i, c in enumerate(sentence) if c == ")"]
        response_r1_utt = sentence[response_pos_end[0] + 2:response_pos_ini[1]].strip()
        response_r2_utt = sentence[response_pos_end[1] + 2:response_pos_ini[2]].strip()
        response_r3_utt = sentence[response_pos_end[2] + 2:].lstrip()
        return response_r1_utt, response_r2_utt, response_r3_utt

    def select_dialogue(self, dialogue_language):
        """Return a list of example dialogues for the given language."""
        if dialogue_language == "en":
            dialogue_base = [
                [["HAPPINESS", "Hi, who are you?"],
                 ["HAPPINESS", "Hi! I'm Ray, a social personal assistant robot with emotions.", "HAPPINESS", "I'm here to chat with you about anything you'd like.", "NEUTRAL", "What would you like to talk about?"]],
                [["HAPPINESS", "I'm interested in talking about you, tell me more."],
                 ["HAPPINESS", "Great! I'm glad you want to get to know me!", "NEUTRAL", "I'm designed to help and talk with people about any topic.", "NEUTRAL", "I can talk about science, technology, history, or just have a pleasant conversation. What interests you?"]],
            ]
            dialogue = [
                [["HAPPINESS", "Nice to meet you, Ray. I'd like to know more about you."],
                 ["HAPPINESS", "The pleasure is mine!", "HAPPINESS", "I'm a chatbot designed to chat and learn with you.", "NEUTRAL", "Would you like to talk about a specific topic?"]],
                [["HAPPINESS", "I love talking to you, you're very interesting."],
                 ["HAPPINESS", "That's so nice to hear! I'm glad you enjoy talking to me.", "NEUTRAL", "I'm designed to have meaningful and empathetic conversations.", "NEUTRAL", "Would you like to talk about emotions, artificial intelligence, or something more personal?"]],
            ]
        else:
            dialogue_base = [
                [["HAPPINESS", "Hola, ¿quién eres?"],
                 ["HAPPINESS", "¡Hola! Soy Ray y soy un robot social asistente personal con emociones.", "HAPPINESS", "Estoy aquí para charlar contigo sobre cualquier tema.", "NEUTRAL", "¿Sobre qué te gustaría hablar?"]],
                [["HAPPINESS", "Me interesa hablar sobre ti, cuéntame más detalles."],
                 ["HAPPINESS", "¡Genial, me encanta que quieras conocerme!", "NEUTRAL", "Estoy diseñado para ayudar y hablar con la gente sobre cualquier tema.", "NEUTRAL", "Puedo hablar de ciencia, tecnología, historia o simplemente tener una charla amena. ¿Qué te interesa?"]],
            ]
            dialogue = [
                [["HAPPINESS", "Mucho gusto, Ray. Me gustaría saber más sobre ti."],
                 ["HAPPINESS", "¡El gusto es mío!", "HAPPINESS", "Soy un chatbot diseñado para conversar y aprender contigo.", "NEUTRAL", "¿Quieres hablar de algún tema en específico?"]],
                [["HAPPINESS", "Me encanta hablar contigo, eres muy interesante."],
                 ["HAPPINESS", "¡Qué lindo escuchar eso! Me alegra que disfrutes hablar conmigo.", "NEUTRAL", "Estoy diseñado para tener conversaciones significativas y empáticas.", "NEUTRAL", "¿Te gustaría que hablemos sobre emociones, inteligencia artificial, o algo más personal?"]],
            ]
        return dialogue_base + dialogue

    def chat_with_model(self, dialogues, max_new_tokens=96):
        prompt_text = update_prompt(dialogues)
        inputs = self.tokenizer(prompt_text, return_tensors="pt").to(self.model.device)
        with torch.no_grad():
            outputs = self.model.generate(
                **inputs,
                max_new_tokens=max_new_tokens,
                do_sample=True,
                temperature=0.7,
                top_p=0.9,
                eos_token_id=self.tokenizer.eos_token_id,
            )
        generated = outputs[0][inputs["input_ids"].shape[-1]:]
        response = self.tokenizer.decode(generated, skip_special_tokens=True).splitlines()[0].strip()
        print("Response:", response, "\n")
        return response

    def main(self):
        emotions = ["ANGER", "FEAR", "SADNESS", "DISGUST", "HAPPINESS", "SURPRISE", "NEUTRAL"]
        dialogue = self.select_dialogue(self.dialogue_language)

        while True:
            if len(dialogue) > 7:
                dialogue.pop(2)

            p_emo = random.choice(emotions)
            user_sentence = input(f"Enter your sentence: ({p_emo}) ")
            if user_sentence.strip().lower() == "exit":
                break

            r2_emo = random.choice(emotions)
            dialogue.append([[p_emo, user_sentence], [p_emo, "", r2_emo, "", "NEUTRAL", ""]])
            response = self.chat_with_model(dialogue)

            try:
                r1_utt, r2_utt, r3_utt = self.split_emo_chatbot(response)
            except Exception:
                if self.dialogue_language == "en":
                    r1_utt, r2_utt, r3_utt = "I'm sorry.", "I didn't understand you.", "Could you repeat?"
                else:
                    r1_utt, r2_utt, r3_utt = "Lo siento.", "No te he entendido.", "¿Podrías repetirme?"

            dialogue[-1][1] = [p_emo, r1_utt, r2_emo, r2_utt, "NEUTRAL", r3_utt]


if __name__ == "__main__":
    language = sys.argv[1] if len(sys.argv) > 1 else "en"
    chatbot = Chatbot(dialogue_language=language)
    chatbot.main()

Limitations

  • This repository contains an adapter, not a standalone merged model; use requires access to the corresponding base model and its terms.
  • The model is optimized for the emotional dialogue format used in the project dataset.
  • Automatic BLEU/ROUGE scores do not fully capture empathy, safety, coherence, or emotional appropriateness.
  • Outputs should be evaluated for the target deployment setting and reviewed before user-facing use.
Downloads last month
94
Inference Providers NEW
This model isn't deployed by any Inference Provider. 🙋 Ask for provider support

Model tree for mario-rc/emotional-rlaif-ppo-meta-llama-3-8b-instruct

Adapter
(1171)
this model

Dataset used to train mario-rc/emotional-rlaif-ppo-meta-llama-3-8b-instruct

Collection including mario-rc/emotional-rlaif-ppo-meta-llama-3-8b-instruct