optillm 0.0.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- optillm/__init__.py +0 -0
- optillm/bon.py +50 -0
- optillm/cot_decoding.py +162 -0
- optillm/cot_reflection.py +64 -0
- optillm/entropy_decoding.py +234 -0
- optillm/leap.py +180 -0
- optillm/mcts.py +187 -0
- optillm/moa.py +94 -0
- optillm/plansearch.py +138 -0
- optillm/pvg.py +194 -0
- optillm/reread.py +44 -0
- optillm/rstar.py +334 -0
- optillm/rto.py +76 -0
- optillm/self_consistency.py +92 -0
- optillm/z3_solver.py +310 -0
- optillm-0.0.2.dist-info/LICENSE +201 -0
- optillm-0.0.2.dist-info/METADATA +326 -0
- optillm-0.0.2.dist-info/RECORD +20 -0
- optillm-0.0.2.dist-info/WHEEL +5 -0
- optillm-0.0.2.dist-info/top_level.txt +1 -0
optillm/__init__.py
ADDED
|
File without changes
|
optillm/bon.py
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
logger = logging.getLogger(__name__)
|
|
4
|
+
|
|
5
|
+
def best_of_n_sampling(system_prompt: str, initial_query: str, client, model: str, n: int = 3) -> str:
|
|
6
|
+
bon_completion_tokens = 0
|
|
7
|
+
|
|
8
|
+
messages = [{"role": "system", "content": system_prompt},
|
|
9
|
+
{"role": "user", "content": initial_query}]
|
|
10
|
+
|
|
11
|
+
completions = []
|
|
12
|
+
|
|
13
|
+
response = client.chat.completions.create(
|
|
14
|
+
model=model,
|
|
15
|
+
messages=messages,
|
|
16
|
+
max_tokens=4096,
|
|
17
|
+
n=n,
|
|
18
|
+
temperature=1
|
|
19
|
+
)
|
|
20
|
+
completions = [choice.message.content for choice in response.choices]
|
|
21
|
+
logger.info(f"Generated {len(completions)} initial completions. Tokens used: {response.usage.completion_tokens}")
|
|
22
|
+
bon_completion_tokens += response.usage.completion_tokens
|
|
23
|
+
|
|
24
|
+
# Rate the completions
|
|
25
|
+
rating_messages = messages.copy()
|
|
26
|
+
rating_messages.append({"role": "system", "content": "Rate the following responses on a scale from 0 to 10, where 0 is poor and 10 is excellent. Consider factors such as relevance, coherence, and helpfulness. Respond with only a number."})
|
|
27
|
+
|
|
28
|
+
ratings = []
|
|
29
|
+
for completion in completions:
|
|
30
|
+
rating_messages.append({"role": "assistant", "content": completion})
|
|
31
|
+
rating_messages.append({"role": "user", "content": "Rate the above response:"})
|
|
32
|
+
|
|
33
|
+
rating_response = client.chat.completions.create(
|
|
34
|
+
model=model,
|
|
35
|
+
messages=rating_messages,
|
|
36
|
+
max_tokens=256,
|
|
37
|
+
n=1,
|
|
38
|
+
temperature=0.1
|
|
39
|
+
)
|
|
40
|
+
bon_completion_tokens += rating_response.usage.completion_tokens
|
|
41
|
+
try:
|
|
42
|
+
rating = float(rating_response.choices[0].message.content.strip())
|
|
43
|
+
ratings.append(rating)
|
|
44
|
+
except ValueError:
|
|
45
|
+
ratings.append(0)
|
|
46
|
+
|
|
47
|
+
rating_messages = rating_messages[:-2]
|
|
48
|
+
|
|
49
|
+
best_index = ratings.index(max(ratings))
|
|
50
|
+
return completions[best_index], bon_completion_tokens
|
optillm/cot_decoding.py
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import torch
|
|
2
|
+
from transformers import PreTrainedModel, PreTrainedTokenizer
|
|
3
|
+
from typing import List, Tuple, Dict, Optional
|
|
4
|
+
import numpy as np
|
|
5
|
+
|
|
6
|
+
def get_device():
|
|
7
|
+
if torch.backends.mps.is_available():
|
|
8
|
+
return torch.device("mps")
|
|
9
|
+
elif torch.cuda.is_available():
|
|
10
|
+
return torch.device("cuda")
|
|
11
|
+
else:
|
|
12
|
+
return torch.device("cpu")
|
|
13
|
+
|
|
14
|
+
def calculate_confidence(logits: List[torch.Tensor], answer_ids: torch.Tensor) -> float:
|
|
15
|
+
"""
|
|
16
|
+
Calculate the confidence score (Δ) as specified in the paper.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
logits: List of logits for each decoding step
|
|
20
|
+
answer_ids: Tensor of token ids for the answer
|
|
21
|
+
|
|
22
|
+
Returns:
|
|
23
|
+
Confidence score (Δ)
|
|
24
|
+
"""
|
|
25
|
+
confidence_sum = 0.0
|
|
26
|
+
valid_tokens = 0
|
|
27
|
+
for t, token_id in enumerate(answer_ids):
|
|
28
|
+
if t >= len(logits):
|
|
29
|
+
break
|
|
30
|
+
token_logits = logits[t]
|
|
31
|
+
probs = torch.softmax(token_logits, dim=-1)
|
|
32
|
+
if probs.size(-1) > 1:
|
|
33
|
+
top_2_probs, _ = torch.topk(probs, min(2, probs.size(-1)))
|
|
34
|
+
if top_2_probs.size(-1) > 1:
|
|
35
|
+
confidence_sum += (top_2_probs[-1][0] - top_2_probs[-1][1]).item()
|
|
36
|
+
else:
|
|
37
|
+
confidence_sum += 1.0 # Max confidence if there's only one token
|
|
38
|
+
else:
|
|
39
|
+
confidence_sum += 1.0 # Max confidence if there's only one token
|
|
40
|
+
valid_tokens += 1
|
|
41
|
+
|
|
42
|
+
return confidence_sum / valid_tokens if valid_tokens > 0 else 0.0
|
|
43
|
+
|
|
44
|
+
def aggregate_paths_based_on_scores(paths: List[Tuple[str, float]]) -> Tuple[str, float]:
|
|
45
|
+
"""Aggregate multiple paths based on their confidence scores."""
|
|
46
|
+
answer_scores = {}
|
|
47
|
+
for answer, delta in paths:
|
|
48
|
+
answer_scores[answer] = answer_scores.get(answer, 0) + delta
|
|
49
|
+
best_answer = max(answer_scores, key=answer_scores.get)
|
|
50
|
+
return best_answer, answer_scores[best_answer]
|
|
51
|
+
|
|
52
|
+
def cot_decode(
|
|
53
|
+
model: PreTrainedModel,
|
|
54
|
+
tokenizer: PreTrainedTokenizer,
|
|
55
|
+
messages: List[Dict[str, str]],
|
|
56
|
+
k: int = 10,
|
|
57
|
+
num_beams: int = 1,
|
|
58
|
+
max_new_tokens: int = 512,
|
|
59
|
+
temperature: float = 1.0,
|
|
60
|
+
top_p: float = 1.0,
|
|
61
|
+
repetition_penalty: float = 1.0,
|
|
62
|
+
length_penalty: float = 1.0,
|
|
63
|
+
no_repeat_ngram_size: int = 0,
|
|
64
|
+
early_stopping: bool = False,
|
|
65
|
+
aggregate_paths: bool = False,
|
|
66
|
+
) -> Tuple[str, float]:
|
|
67
|
+
"""
|
|
68
|
+
Implement CoT-decoding for a given chat input.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
model: The Hugging Face transformer model.
|
|
72
|
+
tokenizer: The associated tokenizer.
|
|
73
|
+
messages: List of chat messages in the format [{"role": "user", "content": "..."}]
|
|
74
|
+
k: The number of alternative tokens to consider at the first step.
|
|
75
|
+
num_beams: Number of beams for beam search.
|
|
76
|
+
max_new_tokens: Maximum number of new tokens to generate.
|
|
77
|
+
temperature: Sampling temperature.
|
|
78
|
+
top_p: Nucleus sampling probability.
|
|
79
|
+
repetition_penalty: Repetition penalty factor.
|
|
80
|
+
length_penalty: Length penalty factor.
|
|
81
|
+
no_repeat_ngram_size: Size of n-grams to avoid repeating.
|
|
82
|
+
early_stopping: Whether to stop generation when all beams are finished.
|
|
83
|
+
aggregate_paths: Whether to aggregate multiple paths.
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
A tuple containing the best path (or aggregated result) and its confidence score.
|
|
87
|
+
"""
|
|
88
|
+
device = get_device()
|
|
89
|
+
model.to(device)
|
|
90
|
+
|
|
91
|
+
# Use the chat template to format the input
|
|
92
|
+
if tokenizer.chat_template:
|
|
93
|
+
input_text = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
|
|
94
|
+
else:
|
|
95
|
+
# Fallback for tokenizers without chat templates
|
|
96
|
+
input_text = "\n".join([f"{msg['role']}: {msg['content']}" for msg in messages])
|
|
97
|
+
input_text += "\nassistant:"
|
|
98
|
+
|
|
99
|
+
input_ids = tokenizer.encode(input_text, return_tensors="pt").to(device)
|
|
100
|
+
attention_mask = torch.ones_like(input_ids).to(device)
|
|
101
|
+
|
|
102
|
+
# Set pad_token_id if it's not set
|
|
103
|
+
if tokenizer.pad_token_id is None:
|
|
104
|
+
tokenizer.pad_token_id = tokenizer.eos_token_id
|
|
105
|
+
|
|
106
|
+
# Get the top-k tokens for the first decoding step
|
|
107
|
+
with torch.no_grad():
|
|
108
|
+
outputs = model(input_ids, attention_mask=attention_mask)
|
|
109
|
+
first_token_logits = outputs.logits[0, -1, :]
|
|
110
|
+
top_k_logits, top_k_indices = torch.topk(first_token_logits, k)
|
|
111
|
+
|
|
112
|
+
paths = []
|
|
113
|
+
for idx in top_k_indices:
|
|
114
|
+
# Generate sequence starting with the selected token
|
|
115
|
+
start_ids = torch.cat([input_ids, idx.unsqueeze(0).unsqueeze(0)], dim=-1)
|
|
116
|
+
start_mask = torch.cat([attention_mask, torch.ones((1, 1), dtype=torch.long, device=device)], dim=-1)
|
|
117
|
+
|
|
118
|
+
output = model.generate(
|
|
119
|
+
start_ids,
|
|
120
|
+
attention_mask=start_mask,
|
|
121
|
+
max_new_tokens=max_new_tokens,
|
|
122
|
+
num_beams=num_beams,
|
|
123
|
+
temperature=temperature,
|
|
124
|
+
top_p=top_p,
|
|
125
|
+
repetition_penalty=repetition_penalty,
|
|
126
|
+
length_penalty=length_penalty,
|
|
127
|
+
no_repeat_ngram_size=no_repeat_ngram_size,
|
|
128
|
+
early_stopping=early_stopping,
|
|
129
|
+
pad_token_id=tokenizer.pad_token_id,
|
|
130
|
+
eos_token_id=tokenizer.eos_token_id,
|
|
131
|
+
output_scores=True,
|
|
132
|
+
return_dict_in_generate=True,
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
generated_sequence = output.sequences[0]
|
|
136
|
+
answer_ids = generated_sequence[len(input_ids[0]):]
|
|
137
|
+
answer_text = tokenizer.decode(answer_ids, skip_special_tokens=True)
|
|
138
|
+
|
|
139
|
+
# Calculate confidence score (Δ)
|
|
140
|
+
confidence = calculate_confidence(output.scores, answer_ids)
|
|
141
|
+
paths.append((answer_text, confidence))
|
|
142
|
+
|
|
143
|
+
if aggregate_paths:
|
|
144
|
+
return aggregate_paths_based_on_scores(paths)
|
|
145
|
+
else:
|
|
146
|
+
return max(paths, key=lambda x: x[1])
|
|
147
|
+
|
|
148
|
+
# Usage example
|
|
149
|
+
# from transformers import AutoModelForCausalLM, AutoTokenizer
|
|
150
|
+
|
|
151
|
+
# model_name = "Qwen/Qwen2.5-0.5B-Instruct"
|
|
152
|
+
# model = AutoModelForCausalLM.from_pretrained(model_name, attn_implementation="eager")
|
|
153
|
+
# tokenizer = AutoTokenizer.from_pretrained(model_name)
|
|
154
|
+
|
|
155
|
+
# messages = [
|
|
156
|
+
# {"role": "user", "content": "In a dance class of 20 students, 20% enrolled in contemporary dance, 25% of the remaining enrolled in jazz dance, and the rest enrolled in hip-hop dance. What percentage of the entire students enrolled in hip-hop dance?"}
|
|
157
|
+
# ]
|
|
158
|
+
|
|
159
|
+
# # Generate the response using CoT decoding
|
|
160
|
+
# print(f"Using device: {get_device()}")
|
|
161
|
+
# result, confidence = cot_decode(model, tokenizer, messages, aggregate_paths=True, max_new_tokens=512)
|
|
162
|
+
# print(f"CoT Decoding:\n {result}")
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import re
|
|
2
|
+
import logging
|
|
3
|
+
|
|
4
|
+
logger = logging.getLogger(__name__)
|
|
5
|
+
|
|
6
|
+
def cot_reflection(system_prompt, initial_query, client, model: str, return_full_response: bool=False):
|
|
7
|
+
cot_completion_tokens = 0
|
|
8
|
+
cot_prompt = f"""
|
|
9
|
+
{system_prompt}
|
|
10
|
+
|
|
11
|
+
You are an AI assistant that uses a Chain of Thought (CoT) approach with reflection to answer queries. Follow these steps:
|
|
12
|
+
|
|
13
|
+
1. Think through the problem step by step within the <thinking> tags.
|
|
14
|
+
2. Reflect on your thinking to check for any errors or improvements within the <reflection> tags.
|
|
15
|
+
3. Make any necessary adjustments based on your reflection.
|
|
16
|
+
4. Provide your final, concise answer within the <output> tags.
|
|
17
|
+
|
|
18
|
+
Important: The <thinking> and <reflection> sections are for your internal reasoning process only.
|
|
19
|
+
Do not include any part of the final answer in these sections.
|
|
20
|
+
The actual response to the query must be entirely contained within the <output> tags.
|
|
21
|
+
|
|
22
|
+
Use the following format for your response:
|
|
23
|
+
<thinking>
|
|
24
|
+
[Your step-by-step reasoning goes here. This is your internal thought process, not the final answer.]
|
|
25
|
+
<reflection>
|
|
26
|
+
[Your reflection on your reasoning, checking for errors or improvements]
|
|
27
|
+
</reflection>
|
|
28
|
+
[Any adjustments to your thinking based on your reflection]
|
|
29
|
+
</thinking>
|
|
30
|
+
<output>
|
|
31
|
+
[Your final, concise answer to the query. This is the only part that will be shown to the user.]
|
|
32
|
+
</output>
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
# Make the API call
|
|
36
|
+
response = client.chat.completions.create(
|
|
37
|
+
model=model,
|
|
38
|
+
messages=[
|
|
39
|
+
{"role": "system", "content": cot_prompt},
|
|
40
|
+
{"role": "user", "content": initial_query}
|
|
41
|
+
],
|
|
42
|
+
temperature=0.7,
|
|
43
|
+
max_tokens=4096
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
# Extract the full response
|
|
47
|
+
full_response = response.choices[0].message.content
|
|
48
|
+
cot_completion_tokens += response.usage.completion_tokens
|
|
49
|
+
logger.info(f"CoT with Reflection :\n{full_response}")
|
|
50
|
+
|
|
51
|
+
# Use regex to extract the content within <thinking> and <output> tags
|
|
52
|
+
thinking_match = re.search(r'<thinking>(.*?)</thinking>', full_response, re.DOTALL)
|
|
53
|
+
output_match = re.search(r'<output>(.*?)(?:</output>|$)', full_response, re.DOTALL)
|
|
54
|
+
|
|
55
|
+
thinking = thinking_match.group(1).strip() if thinking_match else "No thinking process provided."
|
|
56
|
+
output = output_match.group(1).strip() if output_match else full_response
|
|
57
|
+
|
|
58
|
+
logger.info(f"Final output :\n{output}")
|
|
59
|
+
|
|
60
|
+
if return_full_response:
|
|
61
|
+
return full_response, cot_completion_tokens
|
|
62
|
+
else:
|
|
63
|
+
return output, cot_completion_tokens
|
|
64
|
+
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import torch
|
|
2
|
+
import torch.nn.functional as F
|
|
3
|
+
from transformers import PreTrainedModel, PreTrainedTokenizer
|
|
4
|
+
from typing import List, Tuple, Dict, Optional
|
|
5
|
+
import logging
|
|
6
|
+
|
|
7
|
+
# Set up logging
|
|
8
|
+
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
|
9
|
+
|
|
10
|
+
# Device selection
|
|
11
|
+
if torch.backends.mps.is_available():
|
|
12
|
+
device = torch.device("mps")
|
|
13
|
+
elif torch.cuda.is_available():
|
|
14
|
+
device = torch.device("cuda")
|
|
15
|
+
else:
|
|
16
|
+
device = torch.device("cpu")
|
|
17
|
+
|
|
18
|
+
logging.info(f"Using device: {device}")
|
|
19
|
+
|
|
20
|
+
LN_2 = 0.69314718056 # ln(2)
|
|
21
|
+
|
|
22
|
+
def calculate_varentropy_logsoftmax(logits: torch.Tensor, axis: int = -1) -> Tuple[torch.Tensor, torch.Tensor]:
|
|
23
|
+
log_probs = F.log_softmax(logits, dim=axis)
|
|
24
|
+
probs = torch.exp(log_probs)
|
|
25
|
+
entropy = -torch.sum(probs * log_probs, dim=axis) / LN_2 # Convert to base-2
|
|
26
|
+
varentropy = torch.sum(probs * (log_probs / LN_2 + entropy.unsqueeze(-1))**2, dim=axis)
|
|
27
|
+
return entropy, varentropy
|
|
28
|
+
|
|
29
|
+
def calculate_attention_metrics(attention_scores: torch.Tensor) -> Dict[str, torch.Tensor]:
|
|
30
|
+
attention_probs = F.softmax(attention_scores, dim=-1)
|
|
31
|
+
attn_entropy = -torch.sum(attention_probs * torch.log2(torch.clamp(attention_probs, 1e-10, 1.0)), dim=-1)
|
|
32
|
+
attn_varentropy = torch.var(attn_entropy, dim=-1)
|
|
33
|
+
|
|
34
|
+
attn_varentropy = torch.where(torch.isnan(attn_varentropy), torch.zeros_like(attn_varentropy), attn_varentropy)
|
|
35
|
+
mean_attention = torch.mean(attention_probs, dim=1)
|
|
36
|
+
agreement = torch.mean(torch.abs(attention_probs - mean_attention.unsqueeze(1)), dim=(1, 2))
|
|
37
|
+
|
|
38
|
+
interaction_strength = torch.mean(torch.abs(attention_scores), dim=(1, 2, 3))
|
|
39
|
+
|
|
40
|
+
return {
|
|
41
|
+
"attn_entropy": torch.mean(attn_entropy),
|
|
42
|
+
"attn_varentropy": torch.mean(attn_varentropy),
|
|
43
|
+
"agreement": torch.mean(agreement),
|
|
44
|
+
"interaction_strength": interaction_strength
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
def _sample(logits: torch.Tensor, temperature=0.666, top_p=0.90, top_k=27, min_p: float = 0.0, generator: torch.Generator = None) -> torch.Tensor:
|
|
48
|
+
bsz = logits.shape[0]
|
|
49
|
+
logit = logits[:, -1]
|
|
50
|
+
probs = F.softmax(logit / temperature, dim=-1)
|
|
51
|
+
|
|
52
|
+
if min_p > 0.0:
|
|
53
|
+
p_max = torch.max(probs, dim=-1, keepdim=True).values
|
|
54
|
+
indices_to_remove = probs < (min_p * p_max)
|
|
55
|
+
logit = torch.where(indices_to_remove, torch.full_like(logit, float('-inf')), logit)
|
|
56
|
+
|
|
57
|
+
top_k_probs, top_k_indices = torch.topk(probs, k=min(top_k, probs.shape[-1]))
|
|
58
|
+
probs_sort = torch.flip(top_k_probs, dims=[-1])
|
|
59
|
+
probs_idx = torch.flip(top_k_indices, dims=[-1])
|
|
60
|
+
probs_sum = torch.cumsum(probs_sort, dim=-1)
|
|
61
|
+
mask = torch.where(probs_sum - probs_sort > top_p, torch.tensor(1.0, device=device), torch.tensor(0.0, device=device))
|
|
62
|
+
probs_sort = probs_sort * (1 - mask)
|
|
63
|
+
probs_sort = probs_sort / torch.sum(probs_sort, dim=-1, keepdim=True)
|
|
64
|
+
next_token = torch.multinomial(probs_sort, 1, generator=generator)
|
|
65
|
+
next_token_g = torch.gather(probs_idx, -1, next_token.reshape(bsz, 1).to(torch.int64))
|
|
66
|
+
return next_token_g.to(torch.int32)
|
|
67
|
+
|
|
68
|
+
def adaptive_sample(logits: torch.Tensor, metrics: Dict[str, torch.Tensor],
|
|
69
|
+
gen_tokens: torch.Tensor, n_samples: int,
|
|
70
|
+
base_temp: float = 0.666, base_top_p: float = 0.90, base_top_k: int = 40, base_min_p: float = 0.03,
|
|
71
|
+
generator: torch.Generator = None) -> torch.Tensor:
|
|
72
|
+
logits_uncertainty = metrics["logits_entropy"] + metrics["logits_varentropy"]
|
|
73
|
+
attn_uncertainty = metrics["attn_entropy"] + metrics["attn_varentropy"]
|
|
74
|
+
|
|
75
|
+
temperature = base_temp * (1 + 0.3 * logits_uncertainty + 0.2 * attn_uncertainty - 0.2 * metrics["agreement"])
|
|
76
|
+
top_p = torch.clamp(base_top_p * (1 + 0.1 * metrics["attn_varentropy"]), 0.1, 1.0)
|
|
77
|
+
top_k = int(torch.clamp(
|
|
78
|
+
torch.round(torch.tensor(base_top_k) * (1 + 0.3 * metrics["interaction_strength"].item() - 0.2 * metrics["agreement"].item())),
|
|
79
|
+
min=1,
|
|
80
|
+
max=100
|
|
81
|
+
).item())
|
|
82
|
+
min_p = torch.clamp(base_min_p * (1 - 0.5 * logits_uncertainty), 0.01, 0.5)
|
|
83
|
+
|
|
84
|
+
# Convert tensor values to Python scalars for logging
|
|
85
|
+
logging.debug(f"Adaptive sampling params: temp={temperature.item():.3f}, top_p={top_p.item():.3f}, top_k={top_k}, min_p={min_p.item():.3f}")
|
|
86
|
+
|
|
87
|
+
samples = []
|
|
88
|
+
for _ in range(n_samples):
|
|
89
|
+
sample = _sample(logits, temperature=temperature.item(), top_p=top_p.item(), top_k=top_k, min_p=min_p.item(), generator=generator)
|
|
90
|
+
samples.append(sample)
|
|
91
|
+
|
|
92
|
+
def score_sample(sample):
|
|
93
|
+
sample_flat = sample.flatten().to(torch.long)
|
|
94
|
+
one_hot = F.one_hot(sample_flat, logits.shape[-1])
|
|
95
|
+
log_probs = F.log_softmax(logits, dim=-1).view(-1, logits.shape[-1])
|
|
96
|
+
log_prob = torch.sum(log_probs * one_hot)
|
|
97
|
+
|
|
98
|
+
confidence_score = (
|
|
99
|
+
(1 - metrics["logits_entropy"]) * 0.1 +
|
|
100
|
+
(1 - metrics["attn_entropy"]) * 0.2 +
|
|
101
|
+
(1 - metrics["logits_varentropy"]) * 0.3 +
|
|
102
|
+
(1 - metrics["attn_varentropy"]) * 0.4 +
|
|
103
|
+
metrics["agreement"] * 0.5 +
|
|
104
|
+
metrics["interaction_strength"] * 0.6
|
|
105
|
+
)
|
|
106
|
+
return log_prob + confidence_score
|
|
107
|
+
|
|
108
|
+
sample_scores = torch.stack([score_sample(sample) for sample in samples])
|
|
109
|
+
best_sample_idx = torch.argmax(sample_scores)
|
|
110
|
+
return samples[best_sample_idx]
|
|
111
|
+
|
|
112
|
+
def entropy_decode(
|
|
113
|
+
model: PreTrainedModel,
|
|
114
|
+
tokenizer: PreTrainedTokenizer,
|
|
115
|
+
messages: List[Dict[str, str]],
|
|
116
|
+
max_new_tokens: int = 512,
|
|
117
|
+
temperature: float = 0.666,
|
|
118
|
+
top_p: float = 0.90,
|
|
119
|
+
top_k: int = 27,
|
|
120
|
+
min_p: float = 0.03,
|
|
121
|
+
generator: torch.Generator = torch.Generator(device=device).manual_seed(1337)
|
|
122
|
+
) -> str:
|
|
123
|
+
model.to(device)
|
|
124
|
+
logging.info("Starting entropy decoding")
|
|
125
|
+
|
|
126
|
+
if hasattr(tokenizer, 'chat_template') and tokenizer.chat_template:
|
|
127
|
+
input_text = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
|
|
128
|
+
else:
|
|
129
|
+
input_text = "\n".join([f"{msg['role']}: {msg['content']}" for msg in messages])
|
|
130
|
+
input_text += "\nassistant:"
|
|
131
|
+
|
|
132
|
+
input_ids = tokenizer.encode(input_text, return_tensors="pt").to(device)
|
|
133
|
+
attention_mask = torch.ones_like(input_ids).to(device)
|
|
134
|
+
|
|
135
|
+
if tokenizer.pad_token_id is None:
|
|
136
|
+
tokenizer.pad_token_id = tokenizer.eos_token_id
|
|
137
|
+
|
|
138
|
+
generated_tokens = []
|
|
139
|
+
gen_tokens = input_ids
|
|
140
|
+
past_key_values = None
|
|
141
|
+
stop = torch.tensor([tokenizer.eos_token_id], device=device, dtype=torch.int32)
|
|
142
|
+
|
|
143
|
+
for step in range(max_new_tokens):
|
|
144
|
+
logging.debug(f"Generation step: {step + 1}")
|
|
145
|
+
with torch.no_grad():
|
|
146
|
+
outputs = model(
|
|
147
|
+
input_ids if past_key_values is None else input_ids[:, -1:],
|
|
148
|
+
attention_mask=attention_mask,
|
|
149
|
+
past_key_values=past_key_values,
|
|
150
|
+
use_cache=True,
|
|
151
|
+
output_attentions=True,
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
logits = outputs.logits[:, -1:, :]
|
|
155
|
+
attention_scores = outputs.attentions[-1]
|
|
156
|
+
past_key_values = outputs.past_key_values
|
|
157
|
+
|
|
158
|
+
entropy, varentropy = calculate_varentropy_logsoftmax(logits)
|
|
159
|
+
attention_metrics = calculate_attention_metrics(attention_scores)
|
|
160
|
+
metrics = {
|
|
161
|
+
"logits_entropy": entropy,
|
|
162
|
+
"logits_varentropy": varentropy,
|
|
163
|
+
**attention_metrics
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
logging.debug(f"Metrics: entropy={entropy.item():.3f}, varentropy={varentropy.item():.3f}")
|
|
167
|
+
|
|
168
|
+
if entropy < 0.1 and varentropy < 0.1:
|
|
169
|
+
next_token = torch.argmax(logits[:, -1], dim=-1, keepdim=True).to(torch.int32)
|
|
170
|
+
logging.debug("Using greedy sampling")
|
|
171
|
+
elif entropy > 3.0 and varentropy < 0.1:
|
|
172
|
+
if not torch.isin(gen_tokens[:,-1], torch.tensor([2564], device=device)).any():
|
|
173
|
+
next_token = torch.tensor([[2564]], dtype=torch.int32, device=device)
|
|
174
|
+
logging.debug("Inserting clarification token")
|
|
175
|
+
else:
|
|
176
|
+
temp_adj = 1.3 + 0.2 * attention_metrics["attn_entropy"].item()
|
|
177
|
+
next_token = _sample(logits, temperature=min(1.5, temperature * temp_adj), top_p=top_p, top_k=top_k, min_p=min_p, generator=generator)
|
|
178
|
+
logging.debug(f"Using adjusted temperature sampling: {temp_adj:.3f}")
|
|
179
|
+
elif entropy < 5.0 and varentropy > 5.0:
|
|
180
|
+
temp_adj = 1.2 + 0.3 * attention_metrics["interaction_strength"].item()
|
|
181
|
+
top_k_adj = max(5, int(top_k * (1 + 0.5 * (1 - attention_metrics["agreement"].item()))))
|
|
182
|
+
next_token = _sample(logits, temperature=min(1.5, temperature * temp_adj), top_p=top_p, top_k=top_k_adj, min_p=min_p, generator=generator)
|
|
183
|
+
logging.debug(f"Using exploration sampling: temp={temp_adj:.3f}, top_k={top_k_adj}")
|
|
184
|
+
elif entropy > 5.0 and varentropy > 5.0:
|
|
185
|
+
temp_adj = 2.0 + 0.5 * attention_metrics["attn_varentropy"].item()
|
|
186
|
+
top_p_adj = max(0.5, top_p - 0.2 * attention_metrics["attn_entropy"].item())
|
|
187
|
+
next_token = _sample(logits, temperature=max(2.0, temperature * temp_adj), top_p=top_p_adj, top_k=top_k, min_p=min_p, generator=generator)
|
|
188
|
+
logging.debug(f"Using high uncertainty sampling: temp={temp_adj:.3f}, top_p={top_p_adj:.3f}")
|
|
189
|
+
else:
|
|
190
|
+
next_token = adaptive_sample(
|
|
191
|
+
logits,
|
|
192
|
+
metrics,
|
|
193
|
+
gen_tokens,
|
|
194
|
+
n_samples=5,
|
|
195
|
+
base_temp=temperature,
|
|
196
|
+
base_top_p=top_p,
|
|
197
|
+
base_top_k=top_k,
|
|
198
|
+
base_min_p=min_p,
|
|
199
|
+
generator=generator
|
|
200
|
+
)
|
|
201
|
+
logging.debug("Using adaptive sampling")
|
|
202
|
+
|
|
203
|
+
generated_tokens.append(next_token.item())
|
|
204
|
+
gen_tokens = torch.cat((gen_tokens, next_token), dim=1)
|
|
205
|
+
input_ids = torch.cat([input_ids, next_token], dim=-1)
|
|
206
|
+
attention_mask = torch.cat([attention_mask, torch.ones((1, 1), device=device, dtype=torch.long)], dim=-1)
|
|
207
|
+
|
|
208
|
+
logging.debug(f"Generated token: {tokenizer.decode([next_token.item()])}")
|
|
209
|
+
|
|
210
|
+
if torch.isin(next_token, stop).any():
|
|
211
|
+
logging.info("Reached stop token. Ending generation.")
|
|
212
|
+
break
|
|
213
|
+
|
|
214
|
+
generated_text = tokenizer.decode(generated_tokens, skip_special_tokens=True)
|
|
215
|
+
logging.info("Finished entropy decoding")
|
|
216
|
+
logging.info(f"Generated text: {generated_text}")
|
|
217
|
+
|
|
218
|
+
return generated_text
|
|
219
|
+
|
|
220
|
+
# Usage example
|
|
221
|
+
# from transformers import AutoModelForCausalLM, AutoTokenizer
|
|
222
|
+
|
|
223
|
+
# model_name = "Qwen/Qwen2.5-0.5B-Instruct"
|
|
224
|
+
# model = AutoModelForCausalLM.from_pretrained(model_name, attn_implementation="eager")
|
|
225
|
+
# tokenizer = AutoTokenizer.from_pretrained(model_name)
|
|
226
|
+
|
|
227
|
+
# messages = [
|
|
228
|
+
# {"role": "user", "content": "In a dance class of 20 students, 20% enrolled in contemporary dance, 25% of the remaining enrolled in jazz dance, and the rest enrolled in hip-hop dance. What percentage of the entire students enrolled in hip-hop dance?"}
|
|
229
|
+
# ]
|
|
230
|
+
|
|
231
|
+
# logging.info("Starting entropy decoding process")
|
|
232
|
+
# result = entropy_decode(model, tokenizer, messages)
|
|
233
|
+
# print(f"Entropy Decoding Result:\n{result}")
|
|
234
|
+
# logging.info("Entropy decoding process completed")
|