|
import gradio |
|
import torch |
|
from transformers import LogitsProcessor |
|
import numpy as np |
|
|
|
from modules import shared |
|
|
|
params = { |
|
'color_by_perplexity': False, |
|
'color_by_probability': False, |
|
'ppl_scale': 15.0, |
|
|
|
} |
|
|
|
class PerplexityLogits(LogitsProcessor): |
|
def __init__(self, verbose=False): |
|
self.generated_token_ids = [] |
|
self.selected_probs = [] |
|
self.top_token_ids_list = [] |
|
self.top_probs_list = [] |
|
self.perplexities_list = [] |
|
self.last_probs = None |
|
self.verbose = verbose |
|
|
|
def __call__(self, input_ids, scores): |
|
probs = torch.softmax(scores, dim=-1, dtype=torch.float) |
|
log_probs = torch.nan_to_num(torch.log(probs)) |
|
entropy = -torch.sum(probs*log_probs) |
|
entropy = entropy.cpu().numpy() |
|
perplexity = round(float(np.exp(entropy)), 4) |
|
self.perplexities_list.append(perplexity) |
|
last_token_id = int(input_ids[0][-1].cpu().numpy().item()) |
|
|
|
self.generated_token_ids.append(last_token_id) |
|
|
|
if len(self.selected_probs) > 0: |
|
|
|
if self.verbose: |
|
print(shared.tokenizer.decode(last_token_id)) |
|
print([shared.tokenizer.decode(token_id) for token_id in self.top_token_ids_list[-1]]) |
|
print(self.top_probs_list[-1]) |
|
if last_token_id in self.top_token_ids_list[-1]: |
|
idx = self.top_token_ids_list[-1].index(last_token_id) |
|
self.selected_probs.append(self.top_probs_list[-1][idx]) |
|
else: |
|
self.top_token_ids_list[-1].append(last_token_id) |
|
last_prob = round(float(self.last_probs[last_token_id]), 4) |
|
self.top_probs_list[-1].append(last_prob) |
|
self.selected_probs.append(last_prob) |
|
else: |
|
self.selected_probs.append(1.0) |
|
|
|
if self.verbose: |
|
pplbar = "-" |
|
if not np.isnan(perplexity): |
|
pplbar = "*"*round(perplexity) |
|
print(f"{last_token}\t{perplexity:.2f}\t{pplbar}") |
|
|
|
|
|
top_tokens_and_probs = torch.topk(probs, 5) |
|
top_probs = top_tokens_and_probs.values.cpu().numpy().astype(float).tolist() |
|
top_token_ids = top_tokens_and_probs.indices.cpu().numpy().astype(int).tolist() |
|
|
|
self.top_token_ids_list.append(top_token_ids) |
|
self.top_probs_list.append(top_probs) |
|
|
|
probs = probs.cpu().numpy().flatten() |
|
self.last_probs = probs |
|
|
|
|
|
return scores |
|
|
|
|
|
ppl_logits_processor = None |
|
|
|
def logits_processor_modifier(logits_processor_list, input_ids): |
|
global ppl_logits_processor |
|
ppl_logits_processor = PerplexityLogits() |
|
logits_processor_list.append(ppl_logits_processor) |
|
|
|
def output_modifier(text): |
|
global ppl_logits_processor |
|
|
|
|
|
|
|
perplexities = ppl_logits_processor.perplexities_list[:-1] |
|
top_token_ids_list = ppl_logits_processor.top_token_ids_list[:-1] |
|
top_tokens_list = [[shared.tokenizer.decode(token_id) for token_id in top_token_ids] for top_token_ids in top_token_ids_list] |
|
top_probs_list = ppl_logits_processor.top_probs_list[:-1] |
|
|
|
gen_token_ids = ppl_logits_processor.generated_token_ids[1:] |
|
gen_tokens = [shared.tokenizer.decode(token_id) for token_id in gen_token_ids] |
|
sel_probs = ppl_logits_processor.selected_probs[1:] |
|
|
|
end_part = '</span>' |
|
in_code = False |
|
|
|
if params['color_by_probability'] and params['color_by_perplexity']: |
|
i = 0 |
|
for token, prob, ppl, top_tokens, top_probs in zip(gen_tokens, sel_probs, perplexities, top_tokens_list, top_probs_list): |
|
if '`' in token: |
|
in_code = not in_code |
|
continue |
|
if in_code: |
|
continue |
|
color = probability_perplexity_color_scale(prob, ppl) |
|
if token in text[i:]: |
|
text = text[:i] + text[i:].replace(token, add_color_html(token, color), 1) |
|
i += text[i:].find(end_part) + len(end_part) |
|
elif params['color_by_perplexity']: |
|
i = 0 |
|
for token, ppl, top_tokens, top_probs in zip(gen_tokens, perplexities, top_tokens_list, top_probs_list): |
|
if '`' in token: |
|
in_code = not in_code |
|
continue |
|
if in_code: |
|
continue |
|
color = perplexity_color_scale(ppl) |
|
if token in text[i:]: |
|
text = text[:i] + text[i:].replace(token, add_color_html(token, color), 1) |
|
i += text[i:].find(end_part) + len(end_part) |
|
elif params['color_by_probability']: |
|
i = 0 |
|
for token, prob, top_tokens, top_probs in zip(gen_tokens, sel_probs, top_tokens_list, top_probs_list): |
|
if '`' in token: |
|
in_code = not in_code |
|
continue |
|
if in_code: |
|
continue |
|
color = probability_color_scale(prob) |
|
if token in text[i:]: |
|
text = text[:i] + text[i:].replace(token, add_color_html(token, color), 1) |
|
i += text[i:].find(end_part) + len(end_part) |
|
|
|
print('Average perplexity:', round(np.mean(perplexities), 4)) |
|
return text |
|
|
|
|
|
def probability_color_scale(prob): |
|
rv = 0 |
|
gv = 0 |
|
if prob <= 0.5: |
|
rv = 'ff' |
|
gv = hex(int(255*prob*2))[2:] |
|
if len(gv) < 2: |
|
gv = '0'*(2 - len(gv)) + gv |
|
else: |
|
rv = hex(int(255 - 255*(prob - 0.5)*2))[2:] |
|
gv = 'ff' |
|
if len(rv) < 2: |
|
rv = '0'*(2 - len(rv)) + rv |
|
return rv + gv + '00' |
|
|
|
|
|
def perplexity_color_scale(ppl): |
|
value = hex(max(int(255.0 - params['ppl_scale']*(float(ppl)-1.0)), 0))[2:] |
|
if len(value) < 2: |
|
value = '0'*(2 - len(value)) + value |
|
return 'ff' + value + value |
|
|
|
|
|
def probability_perplexity_color_scale(prob, ppl): |
|
rv = 0 |
|
gv = 0 |
|
bv = hex(min(max(int(params['ppl_scale']*(float(ppl)-1.0)), 0), 255))[2:] |
|
if len(bv) < 2: |
|
bv = '0'*(2 - len(bv)) + bv |
|
if prob <= 0.5: |
|
rv = 'ff' |
|
gv = hex(int(255*prob*2))[2:] |
|
if len(gv) < 2: |
|
gv = '0'*(2 - len(gv)) + gv |
|
else: |
|
rv = hex(int(255 - 255*(prob - 0.5)*2))[2:] |
|
gv = 'ff' |
|
if len(rv) < 2: |
|
rv = '0'*(2 - len(rv)) + rv |
|
return rv + gv + bv |
|
|
|
def add_color_html(token, color): |
|
return f'<span style="color: #{color}">{token}</span>' |
|
|
|
""" |
|
# This is still very broken at the moment, needs CSS too but I'm not very good at CSS (and neither is GPT-4 apparently) so I still need to figure that out. |
|
def add_dropdown_html(token, color, top_tokens, top_probs): |
|
html = f'<span class="hoverable" style="color: #{color}">{token}<div class="dropdown"><table class="dropdown-content">' |
|
for token, prob in zip(top_tokens, top_probs): |
|
# TODO: Background color? Bold for selected token? |
|
# Bigger issue: Why is there a newline after the first token, and the dropdown fails there? |
|
# The HTML ends up like <p><span>word</span></p><div>...</div>, |
|
# even though for all other tokens it shows up correctly. |
|
row_color = probability_color_scale(prob) |
|
html += f'<tr><td style="color: #{row_color}">{token}</td><td style="color: #{row_color}">{prob}</td></tr>' |
|
html += '</table></div></span>' |
|
return html |
|
""" |
|
|
|
def ui(): |
|
color_by_ppl_check = gradio.Checkbox(value=False, label="Color by perplexity", info="Higher perplexity is more red. If also showing probability, higher perplexity has more blue component.") |
|
def update_color_by_ppl_check(x): |
|
params.update({'color_by_perplexity': x}) |
|
color_by_ppl_check.change(update_color_by_ppl_check, color_by_ppl_check, None) |
|
|
|
color_by_prob_check = gradio.Checkbox(value=False, label="Color by probability", info="Green-yellow-red linear scale, with 100% green, 50% yellow, 0% red.") |
|
def update_color_by_prob_check(x): |
|
params.update({'color_by_probability': x}) |
|
color_by_prob_check.change(update_color_by_prob_check, color_by_prob_check, None) |
|
|
|
|
|
""" |
|
prob_dropdown_check = gradio.Checkbox(value=False, label="Probability dropdown") |
|
def update_prob_dropdown_check(x): |
|
params.update({'probability_dropdown': x}) |
|
prob_dropdown_check.change(update_prob_dropdown_check, prob_dropdown_check, None) |
|
""" |
|
|