| name | linguistic-regions-llm |
| description | Use this skill when working with linguistic region analysis in Large Language Models, including data preprocessing for multilingual training, region-based model training with DeepSpeed, and extracting/visualizing linguistic regions in transformer models |
Demo Scripts
scripts/extract_linguistic_regions.py
"""
Extract and Analyze Linguistic Regions in Large Language Models
This script demonstrates how to extract core linguistic regions and monolingual
regions from transformer-based language models. It provides utilities for
analyzing attention patterns and MLP activations across different languages.
Requires: torch, transformers, numpy, scipy
"""
import torch
import torch.nn as nn
import numpy as np
from typing import Dict, List, Tuple, Optional, Any
from transformers import AutoModel, AutoTokenizer
import json
from collections import defaultdict
import logging
from scipy.spatial.distance import jaccard
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
class LinguisticRegionExtractor:
"""
Extract and analyze linguistic regions in transformer models.
"""
def __init__(self, model_name_or_path: str, device: str = 'cuda'):
"""
Initialize the region extractor with a pre-trained model.
Args:
model_name_or_path: Path to the model or model identifier
device: Device to run the model on ('cuda' or 'cpu')
"""
self.device = torch.device(device if torch.cuda.is_available() else 'cpu')
self.model = AutoModel.from_pretrained(model_name_or_path).to(self.device)
self.tokenizer = AutoTokenizer.from_pretrained(model_name_or_path)
self.model.eval()
self.config = self.model.config
self.num_layers = self.config.num_hidden_layers
self.hidden_size = self.config.hidden_size
self.num_heads = self.config.num_attention_heads
self.activation_cache = defaultdict(list)
self.gradient_cache = defaultdict(list)
def register_hooks(self, layers_to_track: List[str]):
"""
Register forward and backward hooks to track activations and gradients.
Args:
layers_to_track: List of layer names to track (e.g., ['attention.o', 'mlp.down'])
"""
self.hooks = []
for name, module in self.model.named_modules():
for track_name in layers_to_track:
if track_name in name:
hook = module.register_forward_hook(
lambda m, inp, out, n=name: self._save_activation(n, out)
)
self.hooks.append(hook)
hook = module.register_backward_hook(
lambda m, grad_in, grad_out, n=name: self._save_gradient(n, grad_out)
)
self.hooks.append(hook)
def _save_activation(self, name: str, output: torch.Tensor):
"""Save activation values during forward pass."""
self.activation_cache[name].append(output.detach().cpu())
def _save_gradient(self, name: str, grad_output: Tuple[torch.Tensor]):
"""Save gradient values during backward pass."""
if grad_output[0] is not None:
self.gradient_cache[name].append(grad_output[0].detach().cpu())
def remove_hooks(self):
"""Remove all registered hooks."""
for hook in self.hooks:
hook.remove()
self.hooks = []
def extract_core_linguistic_regions(self,
texts: List[str],
languages: List[str],
top_percent: float = 0.05) -> Dict[str, torch.Tensor]:
"""
Extract core linguistic regions that are important across all languages.
Args:
texts: List of text samples
languages: List of corresponding languages
top_percent: Percentage of top regions to select (e.g., 0.05 for top 5%)
Returns:
Dictionary mapping layer names to boolean masks of core regions
"""
logger.info(f"Extracting core linguistic regions from {len(texts)} samples...")
self.activation_cache.clear()
self.gradient_cache.clear()
self.register_hooks(['attention.o_proj', 'mlp.down_proj'])
importance_scores = defaultdict(list)
for text, lang in zip(texts, languages):
inputs = self.tokenizer(text, return_tensors='pt', truncation=True, max_length=512)
inputs = {k: v.to(self.device) for k, v in inputs.items()}
with torch.enable_grad():
outputs = self.model(**inputs)
logits = outputs.last_hidden_state
importance = torch.norm(logits, dim=-1).mean()
importance.backward()
for name, activations in self.activation_cache.items():
if activations:
act = activations[-1]
score = torch.abs(act).mean(dim=(0, 1))
importance_scores[name].append(score)
self.remove_hooks()
core_regions = {}
for name, scores in importance_scores.items():
all_scores = torch.stack(scores).mean(dim=0)
k = int(all_scores.numel() * top_percent)
threshold = torch.topk(all_scores.flatten(), k).values[-1]
core_regions[name] = all_scores >= threshold
logger.info(f"Layer {name}: Selected {k} core regions (top {top_percent*100}%)")
return core_regions
def extract_monolingual_regions(self,
texts_by_language: Dict[str, List[str]],
reference_language: str = 'english',
threshold: float = 0.3) -> Dict[str, Dict[str, torch.Tensor]]:
"""
Extract language-specific regions in the model.
Args:
texts_by_language: Dictionary mapping languages to text samples
reference_language: Reference language for comparison
threshold: Jaccard similarity threshold for identifying unique regions
Returns:
Dictionary mapping languages to their specific regions
"""
logger.info(f"Extracting monolingual regions for {list(texts_by_language.keys())}...")
language_patterns = {}
self.register_hooks(['attention.q_proj'])
for lang, texts in texts_by_language.items():
self.activation_cache.clear()
patterns = []
for text in texts[:100]:
inputs = self.tokenizer(text, return_tensors='pt', truncation=True, max_length=512)
inputs = {k: v.to(self.device) for k, v in inputs.items()}
with torch.no_grad():
outputs = self.model(**inputs)
for name, activations in self.activation_cache.items():
if activations:
act = activations[-1]
pattern = (torch.abs(act) > torch.abs(act).mean()).float()
patterns.append(pattern.mean(dim=(0, 1)))
if patterns:
language_patterns[lang] = torch.stack(patterns).mean(dim=0)
self.remove_hooks()
monolingual_regions = {}
if reference_language in language_patterns:
reference_pattern = language_patterns[reference_language]
for lang, pattern in language_patterns.items():
if lang != reference_language:
diff = torch.abs(pattern - reference_pattern)
lang_specific = diff > diff.mean() + diff.std()
similarity = self._jaccard_similarity(lang_specific, reference_pattern > reference_pattern.mean())
if similarity < threshold:
monolingual_regions[lang] = {
'regions': lang_specific,
'similarity_to_reference': similarity,
'num_unique_dims': lang_specific.sum().item()
}
logger.info(f"Language {lang}: {lang_specific.sum().item()} unique dimensions, "
f"similarity to {reference_language}: {similarity:.3f}")
return monolingual_regions
def _jaccard_similarity(self, tensor1: torch.Tensor, tensor2: torch.Tensor) -> float:
"""
Calculate Jaccard similarity between two boolean tensors.
Args:
tensor1: First boolean tensor
tensor2: Second boolean tensor
Returns:
Jaccard similarity score
"""
intersection = (tensor1 & tensor2).float().sum()
union = (tensor1 | tensor2).float().sum()
if union == 0:
return 0.0
return (intersection / union).item()
def analyze_region_importance(self,
regions: Dict[str, torch.Tensor],
validation_texts: List[str]) -> Dict[str, float]:
"""
Analyze the importance of extracted regions using perturbation.
Args:
regions: Dictionary of regions to analyze
validation_texts: Texts to use for validation
Returns:
Dictionary mapping region names to importance scores
"""
logger.info("Analyzing region importance through perturbation...")
importance_scores = {}
for region_name, region_mask in regions.items():
original_perplexities = []
perturbed_perplexities = []
for text in validation_texts:
inputs = self.tokenizer(text, return_tensors='pt', truncation=True)
inputs = {k: v.to(self.device) for k, v in inputs.items()}
with torch.no_grad():
outputs = self.model(**inputs)
original_logits = outputs.last_hidden_state
perturbed_logits = original_logits.clone()
original_ppl = self._calculate_perplexity(original_logits)
perturbed_ppl = self._calculate_perplexity(perturbed_logits)
original_perplexities.append(original_ppl)
perturbed_perplexities.append(perturbed_ppl)
avg_original = np.mean(original_perplexities)
avg_perturbed = np.mean(perturbed_perplexities)
importance = abs(avg_perturbed - avg_original) / avg_original
importance_scores[region_name] = importance
logger.info(f"Region {region_name}: importance score = {importance:.4f}")
return importance_scores
def _calculate_perplexity(self, logits: torch.Tensor) -> float:
"""
Calculate perplexity from logits.
Args:
logits: Model output logits
Returns:
Perplexity value
"""
probs = torch.softmax(logits, dim=-1)
entropy = -torch.sum(probs * torch.log(probs + 1e-10), dim=-1)
perplexity = torch.exp(entropy.mean()).item()
return perplexity
def save_regions(self, regions: Dict, output_path: str):
"""
Save extracted regions to file.
Args:
regions: Dictionary of extracted regions
output_path: Path to save the regions
"""
serializable_regions = {}
for key, value in regions.items():
if isinstance(value, torch.Tensor):
serializable_regions[key] = value.cpu().numpy().tolist()
elif isinstance(value, dict):
serializable_regions[key] = {
k: v.cpu().numpy().tolist() if isinstance(v, torch.Tensor) else v
for k, v in value.items()
}
else:
serializable_regions[key] = value
with open(output_path, 'w') as f:
json.dump(serializable_regions, f, indent=2)
logger.info(f"Saved regions to {output_path}")
def main():
"""
Example usage of the linguistic region extractor.
"""
model_name = "meta-llama/Llama-2-7b-hf"
extractor = LinguisticRegionExtractor(model_name)
texts_by_language = {
'english': [
"The quick brown fox jumps over the lazy dog.",
"Artificial intelligence is transforming the world.",
"There are 365 days in a year and 12 months."
],
'chinese': [
"复旦大学位于上海市。",
"人工智能正在改变世界。",
"一年有365天,12个月。"
],
'arabic': [
"الذكاء الاصطناعي يغير العالم",
"هناك 365 يومًا في السنة",
"مرحبا بك في عالم التكنولوجيا"
]
}
all_texts = []
all_languages = []
for lang, texts in texts_by_language.items():
all_texts.extend(texts)
all_languages.extend([lang] * len(texts))
core_regions = extractor.extract_core_linguistic_regions(
texts=all_texts,
languages=all_languages,
top_percent=0.05
)
print("\nCore Linguistic Regions:")
for layer_name, mask in core_regions.items():
print(f" {layer_name}: {mask.sum().item()} active dimensions out of {mask.numel()}")
monolingual_regions = extractor.extract_monolingual_regions(
texts_by_language=texts_by_language,
reference_language='english',
threshold=0.3
)
print("\nMonolingual Regions:")
for lang, info in monolingual_regions.items():
print(f" {lang}: {info['num_unique_dims']} unique dimensions, "
f"similarity to English: {info['similarity_to_reference']:.3f}")
extractor.save_regions(
{'core': core_regions, 'monolingual': monolingual_regions},
'extracted_regions.json'
)
validation_texts = [
"Fudan University is located in Shanghai.",
"Machine learning models require data."
]
importance_scores = extractor.analyze_region_importance(
regions=core_regions,
validation_texts=validation_texts
)
print("\nRegion Importance Scores:")
for region, score in importance_scores.items():
print(f" {region}: {score:.4f}")
if __name__ == "__main__":
main()
scripts/preprocess_multilingual_data.py
"""
Multilingual Data Preprocessing for Linguistic Region Analysis
This script demonstrates how to preprocess multilingual text data for training
language models with region analysis capabilities. It uses the LLaMA-2 tokenizer
and supports multiple languages including Chinese, English, and others.
Requires: transformers, sentencepiece, numpy
"""
import json
import os
import numpy as np
from typing import List, Dict, Any, Optional, Tuple
from transformers import AutoTokenizer
import multiprocessing as mp
from functools import partial
import logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
class MultilingualDataProcessor:
"""
Process multilingual text data for LLM training with region analysis support.
"""
def __init__(self, tokenizer_path: str, seq_length: int = 512):
"""
Initialize the data processor with tokenizer and sequence parameters.
Args:
tokenizer_path: Path to the LLaMA-2 tokenizer files
seq_length: Maximum sequence length for tokenization
"""
self.tokenizer = AutoTokenizer.from_pretrained(tokenizer_path)
self.seq_length = seq_length
self.vocab_size = self.tokenizer.vocab_size
.language_configs = {
: {
: ,
:
},
: {
: ,
:
},
: {
: ,
:
},
: {
: ,
:
}
}
() -> []:
config = .language_configs.get(language, .language_configs[])
config[]:
text = text.replace(, )
tokens = .tokenizer.encode(text, add_special_tokens=, truncation=, max_length=.seq_length)
(tokens) < .seq_length:
tokens = tokens + [.tokenizer.pad_token_id] * (.seq_length - (tokens))
:
tokens = tokens[:.seq_length]
tokens
() -> []:
pad_token_id = .tokenizer.pad_token_id
[ token != pad_token_id token tokens]
():
logger.info()
(input_path, , encoding=) f:
lines = f.readlines()
process_func = partial(._process_single_line, language=language)
mp.Pool(num_workers) pool:
results = pool.(process_func, lines)
processed_data = [r r results r ]
._save_binary_data(processed_data, output_prefix)
logger.info()
() -> [[, ]]:
:
data = json.loads(line.strip())
text = data.get(, )
text:
tokens = .process_text(text, language)
attention_mask = .create_attention_mask(tokens)
{
: tokens,
: attention_mask,
: language,
: (text)
}
Exception e:
logger.warning()
():
input_ids = np.array([d[] d data], dtype=np.int32)
attention_masks = np.array([d[] d data], dtype=np.int8)
input_ids.tofile()
attention_masks.tofile()
metadata = {
: (data),
: .seq_length,
: .vocab_size,
: ((d[] d data))
}
(, ) f:
json.dump(metadata, f, indent=)
logger.info()
() -> [np.ndarray, np.ndarray, ]:
(, ) f:
metadata = json.load(f)
input_ids = np.fromfile(, dtype=np.int32)
input_ids = input_ids.reshape(-, metadata[])
attention_masks = np.fromfile(, dtype=np.int8)
attention_masks = attention_masks.reshape(-, metadata[])
logger.info()
input_ids, attention_masks, metadata
():
tokenizer_path =
input_file =
output_prefix =
language =
processor = MultilingualDataProcessor(
tokenizer_path=tokenizer_path,
seq_length=
)
os.path.exists(input_file):
processor.process_jsonl_file(
input_path=input_file,
output_prefix=output_prefix,
language=language,
num_workers=
)
input_ids, attention_masks, metadata = processor.load_binary_data(output_prefix)
()
()
:
sample_texts = {
: ,
: ,
: ,
:
}
lang, text sample_texts.items():
tokens = processor.process_text(text, language=lang)
mask = processor.create_attention_mask(tokens)
()
()
()
()
__name__ == :
main()