EdwardConstantine/bengali-empathy-llama
Bengali Empathetic LLaMA š§š©
Fine-tuned LLaMA 3.1-8B-Instruct for empathetic Bengali conversations using LoRA (Low-Rank Adaptation).
š Table of Contents
- Model Description
- Training Details
- Evaluation Results
- Sample Responses
- Design Decisions & Trade-offs
- Architecture & OOP Design
- Usage
- Challenges Faced
- Future Improvements
- Files in This Repository
Model Description
This model is a LoRA fine-tuned version of Meta's LLaMA 3.1-8B-Instruct, specifically trained to generate compassionate and empathetic responses in Bengali.
What This Model Does
- Input: Bengali text expressing emotions (sadness, happiness, frustration, etc.)
- Output: Empathetic Bengali response with emotional understanding
Example
Input: ą¦ą¦®ą¦æ ą¦ą§ą¦¬ ą¦ą¦ą¦¾ ą¦
ą¦Øą§ą¦ą¦¬ ą¦ą¦°ą¦ą¦æą„¤ (I feel very lonely)
Output: ą¦¹ą§ą¦Æą¦¾ą¦, ą¦ą¦ą¦¾ ą¦ą§ą¦¬ ą¦ą¦ ą¦æą¦Øą„¤ ą¦ą¦æą¦Øą§ą¦¤ą§ ą¦ą¦®ą¦æ ą¦ą¦¶ą¦¾ ą¦ą¦°ą¦æ ą¦ą¦Ŗą¦Øą¦æ ą¦¶ą§ą¦ą§ą¦°ą¦ ą¦ą¦ą¦ą¦Ø ą¦¬ą¦Øą§ą¦§ą§ ą¦Ŗą¦¾ą¦¬ą§ą¦Øą„¤
(Yes, this is very hard. But I hope you will find a friend soon.)Training Details
Training History
ā First Attempt (Interrupted - Progress Lost)
Our initial training with optimal settings was interrupted at 66% completion due to Kaggle session timeout:
Loss Progression (Before Interruption): | Step | Training Loss | Validation Loss | |------|---------------|-----------------| | 500 | 0.4459 | - | | 1000 | 0.3869 | - | | 2000 | 0.3292 | 0.3281 | | 3000 | 0.2450 | - | | 4000 | 0.2351 | 0.2642 | | 5000 | 0.2093 | - | | 5329 | Session Timeout | - |
ā ļø If completed, this training would have achieved ~0.18-0.20 final loss with significantly better quality. The checkpoint was lost because saves were configured every 2000 steps, and the session crashed before the next save.
ā Second Attempt (Completed Successfully)
With remaining GPU quota (~3 hours), we completed a condensed training:
Final Results: | Metric | Value | |--------|-------| | Training Loss | 0.4190 | | Validation Loss | 0.3651 |
LoRA Configuration
Training Hyperparameters
Evaluation Results
Why Are BLEU/ROUGE Scores Low?
This is expected and normal for empathetic response generation. Here's why:
- Multiple Valid Responses: There are many ways to express empathy
- Reference: "ą¦ą¦®ą¦æ ą¦¦ą§ą¦ą¦ą¦æą¦¤" (I'm sorry)
- Generated: "ą¦ą¦ą¦¾ ą¦ą¦ িন হবą§" (This must be hard)
- Both are empathetic but share no words ā BLEU = 0
- Creative Generation: The model generates contextually appropriate but not verbatim responses
- Bengali Language: Morphologically rich language with many word forms
- Perplexity is Good: 1.95 indicates the model is confident in its predictions
Human Evaluation Framework
We created a human evaluation template with these criteria (1-5 scale):
š File: human_evaluation_sheet.csv (20 samples for manual evaluation)
Sample Responses
Design Decisions & Trade-offs
1ļøā£ Sequence Length: 256 vs Full Length
What "Sequence Length" Means:
- Maximum number of tokens (words/subwords) the model processes at once
- Original conversations may have 500-1000+ tokens
- We truncated to 256 tokens
Why We Reduced It:
Problem: Kaggle T4 GPU has only 16GB VRAM
Full Length (512+ tokens):
- Memory needed: ~18-20GB ā Doesn't fit
- Batch size: 1 (very slow)
- Training time: 20+ hours
Reduced Length (256 tokens):
- Memory needed: ~12GB ā
Fits
- Batch size: 4 (faster)
- Training time: 3 hoursImpact:
- ~15% of conversations get truncated
- Model may miss context in very long conversations
- Core empathetic learning still happens (most empathy is expressed in first 256 tokens)
What Could Be Done:
- Use A100 GPU (40GB VRAM) ā Can use 512-1024 tokens
- Use Unsloth library ā 2x memory efficiency
- Use gradient accumulation with batch_size=1 ā Slower but full length
- Use QLoRA with more aggressive quantization
2ļøā£ Strategy Pattern: LoRA vs Unsloth
What "Strategy Pattern" Means:
# Strategy Pattern = Swappable algorithms
class FineTuningStrategy: # Abstract strategy
def apply(self, model): pass
class LoRAStrategy(FineTuningStrategy): # Strategy 1 ā
Implemented
def apply(self, model):
return get_peft_model(model, lora_config)
class UnslothStrategy(FineTuningStrategy): # Strategy 2 ā Not implemented
def apply(self, model):
return FastLanguageModel.get_peft_model(model)
# Usage: Can swap strategies easily
strategy = LoRAStrategy() # or UnslothStrategy()
model = strategy.apply(base_model)Why We Only Used LoRA:
What Could Be Done:
# Full Strategy Pattern Implementation
from abc import ABC, abstractmethod
class FineTuningStrategy(ABC):
@abstractmethod
def apply(self, model, config):
pass
@abstractmethod
def get_name(self):
pass
class LoRAStrategy(FineTuningStrategy):
def apply(self, model, config):
from peft import get_peft_model, LoraConfig
lora_config = LoraConfig(
r=config.lora_r,
lora_alpha=config.lora_alpha,
target_modules=config.target_modules,
)
return get_peft_model(model, lora_config)
def get_name(self):
return "LoRA"
class UnslothStrategy(FineTuningStrategy):
def apply(self, model, config):
from unsloth import FastLanguageModel
model, tokenizer = FastLanguageModel.from_pretrained(
model_name=config.model_name,
max_seq_length=config.max_length,
load_in_4bit=True,
)
return FastLanguageModel.get_peft_model(model)
def get_name(self):
return "Unsloth"
# Usage
class LLAMAFineTuner:
def __init__(self, config, strategy: FineTuningStrategy):
self.config = config
self.strategy = strategy
def prepare_model(self, base_model):
print(f"Using {self.strategy.get_name()} strategy")
return self.strategy.apply(base_model, self.config)3ļøā£ Data Sampling: 40% vs 100%
Impact:
- Model sees less variety of conversations
- May not generalize as well to rare emotions
- Still learns core empathetic patterns
What Could Be Done:
- Train for longer with full dataset
- Use data augmentation to increase variety
- Prioritize diverse samples over random sampling
Architecture & OOP Design
Class Diagram
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā MAIN PIPELINE ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¤
ā ā
ā āāāāāāāāāāāāāāāāāāāāāāā āāāāāāāāāāāāāāāāāāāāāāā ā
ā ā DatasetProcessor ā ā LLAMAFineTuner ā ā
ā āāāāāāāāāāāāāāāāāāāāāā⤠āāāāāāāāāāāāāāāāāāāāāā⤠ā
ā ā + TEMPLATE ā ā + model ā ā
ā ā + train_dataset ā ā + tokenizer ā ā
ā ā + val_dataset ā ā + trainer ā ā
ā āāāāāāāāāāāāāāāāāāāāāā⤠āāāāāāāāāāāāāāāāāāāāāā⤠ā
ā ā + load() ā ā + load_model() ā ā
ā ā + process() ā ā + setup_trainer() ā ā
ā ā + _format() ā ā + train() ā ā
ā ā + _tokenize() ā ā + save_final() ā ā
ā āāāāāāāāāāāāāāāāāāāāāāā ā + generate() ā ā
ā āāāāāāāāāāāāāāāāāāāāāāā ā
ā ā
ā āāāāāāāāāāāāāāāāāāāāāāā āāāāāāāāāāāāāāāāāāāāāāā ā
ā ā Evaluator ā ā ExperimentLogger ā ā
ā āāāāāāāāāāāāāāāāāāāāāā⤠āāāāāāāāāāāāāāāāāāāāāā⤠ā
ā ā + model ā ā + db_path ā ā
ā āāāāāāāāāāāāāāāāāāāāāā⤠āāāāāāāāāāāāāāāāāāāāāā⤠ā
ā ā + calculate_bleu() ā ā + log() ā ā
ā ā + calculate_rouge() ā ā + log_response() ā ā
ā ā + calculate_ppl() ā ā + _init_db() ā ā
ā ā + test_samples() ā āāāāāāāāāāāāāāāāāāāāāāā ā
ā āāāāāāāāāāāāāāāāāāāāāāā ā
ā ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāDatabase Schema
-- Stores training experiment metadata
CREATE TABLE LLAMAExperiments (
id INTEGER PRIMARY KEY AUTOINCREMENT,
model_name TEXT, -- e.g., "meta-llama/Llama-3.1-8B-Instruct"
lora_config TEXT, -- JSON: {"r": 16, "alpha": 32}
train_loss REAL, -- e.g., 0.4190
val_loss REAL, -- e.g., 0.3651
duration_hours REAL, -- e.g., 3.26
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP
);
-- Stores generated responses for analysis
CREATE TABLE GeneratedResponses (
id INTEGER PRIMARY KEY AUTOINCREMENT,
experiment_id INTEGER, -- Links to LLAMAExperiments
input_text TEXT, -- User input
response_text TEXT, -- Model response
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (experiment_id) REFERENCES LLAMAExperiments(id)
);Usage
Installation
pip install transformers peft bitsandbytes accelerate torchLoad and Use the Model
from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig
from peft import PeftModel
import torch
# Quantization config (required for 4-bit loading)
bnb_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_quant_type="nf4",
bnb_4bit_compute_dtype=torch.float16,
bnb_4bit_use_double_quant=True,
)
# Load base model
base_model = AutoModelForCausalLM.from_pretrained(
"meta-llama/Llama-3.1-8B-Instruct",
quantization_config=bnb_config,
device_map="auto",
torch_dtype=torch.float16,
)
# Load LoRA adapter
model = PeftModel.from_pretrained(
base_model,
"EdwardConstantine/bengali-empathy-llama"
)
tokenizer = AutoTokenizer.from_pretrained(
"EdwardConstantine/bengali-empathy-llama"
)
# Generate empathetic response
def generate_response(prompt, max_tokens=200):
full_prompt = f"""<|begin_of_text|><|start_header_id|>system<|end_header_id|>
You are a compassionate Bengali conversational AI. Respond with empathy. Reply in Bengali.<|eot_id|><|start_header_id|>user<|end_header_id|>
{prompt}<|eot_id|><|start_header_id|>assistant<|end_header_id|>
"""
inputs = tokenizer(full_prompt, return_tensors="pt").to(model.device)
outputs = model.generate(
**inputs,
max_new_tokens=max_tokens,
temperature=0.7,
top_p=0.9,
do_sample=True,
pad_token_id=tokenizer.eos_token_id,
)
response = tokenizer.decode(outputs[0][inputs['input_ids'].shape[1]:], skip_special_tokens=True)
return response.strip()
# Example usage
response = generate_response("ą¦ą¦®ą¦æ ą¦ą§ą¦¬ ą¦ą¦ą¦¾ ą¦
ą¦Øą§ą¦ą¦¬ ą¦ą¦°ą¦ą¦æą„¤")
print(response)Challenges Faced
Future Improvements
Files in This Repository
Citation
@misc{bengali-empathy-llama-2024,
author = {EdwardConstantine},
title = {Bengali Empathetic LLaMA: Fine-tuned LLaMA 3.1-8B for Empathetic Bengali Conversations},
year = {2024},
publisher = {HuggingFace},
url = {https://huggingface.co/EdwardConstantine/bengali-empathy-llama}
}License
This model is released under the Apache 2.0 License, subject to Meta's LLaMA license terms.
Acknowledgments
- Meta AI for LLaMA 3.1-8B-Instruct base model
- Hugging Face for transformers and PEFT libraries
- Kaggle for free GPU access
- Bengali Empathetic Conversations Dataset creators
