Not able to update Bokeh bar plot based on button click which updates source.data

Hi all,
I am new to using Panel. As a project I need to create text generating app which also shows the probabilities for each generated word. I managed to create word generating part where I can control different parameters with widgets. But I am struggling to update the probabilities plot, even though the source.data is updated with every button click. Below is the code for the project:

import tensorflow as tf
from transformers import TFGPT2LMHeadModel, GPT2Tokenizer
from transformers import tf_top_k_top_p_filtering
from IPython.display import clear_output
import matplotlib.pyplot as plt
import numpy as np

import panel as pn
pn.extension()

import panel.widgets as pnw
from bokeh.models import CustomJS
from math import pi
from bokeh.plotting import figure
from bokeh.layouts import column, row
from bokeh.models import ColumnDataSource, Slider, TextInput

#tokenizer and model for word generation
tokenizer = GPT2Tokenizer.from_pretrained("gpt2")
model = TFGPT2LMHeadModel.from_pretrained("gpt2", pad_token_id=tokenizer.eos_token_id)

# function to get the predicted words and logits to derive the probabilities for each prediction
def get_pred(sequence='Please input some text', model=model, tokenizer=tokenizer, temperature=0.7, top_k=50, top_p=0.95):
    tf.random.set_seed(1234)
    input_ids = tokenizer.encode(sequence, return_tensors="tf")
    # get logits of last hidden state
    next_token_logits = model(input_ids)[0][:, -1, :]
    # apply a temperature coefficient and filter
    next_token_logits = next_token_logits / temperature
    # filter
    filtered_next_token_logits = tf_top_k_top_p_filtering(next_token_logits, top_k, top_p)
    # sample
    next_token = tf.random.categorical(filtered_next_token_logits, dtype=tf.int32, num_samples=1)
    resulting_string = tokenizer.decode(next_token.numpy().tolist()[0])
    return resulting_string, filtered_next_token_logits

# function to update source data
def plot_data(filtered_next_token_logits):
    probabilities = tf.nn.softmax(filtered_next_token_logits)
    k = tf.math.count_nonzero(probabilities).numpy()
    k = min(100, k)
    probs_filter = tf.math.top_k(probabilities[0], k)
    probability_list = probs_filter.values.numpy()
    word_list = list()
    for i in probs_filter.indices.numpy():
        word_list.append(tokenizer.decode([i]))
    return probability_list, word_list


tf.random.set_seed(1234)


# widgets to control the model output for the predictions
temperature_pn = pnw.FloatSlider(name='Temperature', value = 1.0, start=0.0, end=1.0, step=0.01)
top_k_pn = pnw.IntSlider(name='Top K', value = 0, start=0, end=100)
top_p_pn = pnw.FloatSlider(name='Top p', value = 1.0, start=0.0, end=1.0, step=0.01)

widgets = pn.Column("<br>Parameters", temperature_pn, top_k_pn, top_p_pn)

text_input = pn.widgets.TextInput(placeholder='Enter a string here...')
generated_text = pn.pane.HTML(object = text_input.value, background='#f0f0f0', width=500, height=200 )
text_input.link(generated_text, value = 'object')

pn.Row(text_input, generated_text)

button = pn.widgets.Button(name="Generate", button_type="primary")

# Bokeh bar plot initialization
word_list = list(["test"])
probability_list = list([0.5])
source = ColumnDataSource(data=dict(word_list=word_list, probability_list=probability_list))
plot = figure(x_range=source.data['word_list'], height=250, title="Probabilities",
              toolbar_location=None, tools="")
plot.vbar(x = 'word_list', top = 'probability_list',width=0.8, source=source)
plot.xaxis.major_label_orientation = pi/2

# function to update data by button click
def click_cb(event):
    pred, filtered_next_token_logits = get_pred(generated_text.object, 
                                                model, 
                                                tokenizer, 
                                                temperature_pn.value, 
                                                top_k_pn.value, 
                                                top_p_pn.value)
    generated_text.object +=pred
    probabilities, word_list = plot_data(filtered_next_token_logits)
    probability_list = probabilities.tolist()
    source.data = dict(word_list = word_list, probability_list = probability_list)
     
# update data by button click
button.on_click(click_cb)

text_part = pn.Column(text_input,button,generated_text)

bokeh_plot = pn.pane.Bokeh(plot, width=800)
pn.io.push_notebook(bokeh_plot)

# call back function in case the text input changes 
def text_change_cb(event):
    generated_text.object = event.new

# tying the callback function to the text_input widget
text_input.param.watch(text_change_cb, 'value')

text_generation = pn.Row(text_part, widgets)

app = pn.Column(text_generation, bokeh_plot)
app.show()

Below image is the output while generating new word:

Hi @Vusal

When I click the button I get the exception below.

  File "C:\repos\private\awesome-panel-introduction\.venv\lib\site-packages\bokeh\server\session.py", line 218, in _document_patched
    raise RuntimeError("_pending_writes should be non-None when we have a document lock, and we should have the lock when the document changes")
RuntimeError: _pending_writes should be non-None when we have a document lock, and we should have the lock when the document changes

This makes me think a few things

I will try to find a workaround below.

The below refactored code works

import tensorflow as tf
from transformers import TFGPT2LMHeadModel, GPT2Tokenizer
from transformers import tf_top_k_top_p_filtering
import matplotlib.pyplot as plt

import panel as pn
pn.extension()

import panel.widgets as pnw
from math import pi
from bokeh.plotting import figure
from bokeh.models import ColumnDataSource

#tokenizer and model for word generation
tokenizer = GPT2Tokenizer.from_pretrained("gpt2")
model = TFGPT2LMHeadModel.from_pretrained("gpt2", pad_token_id=tokenizer.eos_token_id)

# function to get the predicted words and logits to derive the probabilities for each prediction
def get_pred(sequence='Please input some text', model=model, tokenizer=tokenizer, temperature=0.7, top_k=50, top_p=0.95):
    tf.random.set_seed(1234)
    input_ids = tokenizer.encode(sequence, return_tensors="tf")
    # get logits of last hidden state
    next_token_logits = model(input_ids)[0][:, -1, :]
    # apply a temperature coefficient and filter
    next_token_logits = next_token_logits / temperature
    # filter
    filtered_next_token_logits = tf_top_k_top_p_filtering(next_token_logits, top_k, top_p)
    # sample
    next_token = tf.random.categorical(filtered_next_token_logits, dtype=tf.int32, num_samples=1)
    resulting_string = tokenizer.decode(next_token.numpy().tolist()[0])
    return resulting_string, filtered_next_token_logits

# function to update source data
def get_plot_data(filtered_next_token_logits):
    probabilities = tf.nn.softmax(filtered_next_token_logits)
    k = tf.math.count_nonzero(probabilities).numpy()
    k = min(100, k)
    probs_filter = tf.math.top_k(probabilities[0], k)
    probability_list = probs_filter.values.numpy()
    word_list = list()
    for i in probs_filter.indices.numpy():
        word_list.append(tokenizer.decode([i]))
    return probability_list, word_list

def get_plot(word_list, probability_list):
    source = ColumnDataSource(data=dict(word_list=word_list, probability_list=probability_list))
    plot = figure(x_range=source.data['word_list'], height=250, title="Probabilities",
                toolbar_location=None, tools="")
    plot.vbar(x = 'word_list', top = 'probability_list',width=0.8, source=source)
    plot.xaxis.major_label_orientation = pi/2
    return plot


tf.random.set_seed(1234)


# widgets to control the model output for the predictions
temperature_pn = pnw.FloatSlider(name='Temperature', value = 1.0, start=0.0, end=1.0, step=0.01)
top_k_pn = pnw.IntSlider(name='Top K', value = 0, start=0, end=100)
top_p_pn = pnw.FloatSlider(name='Top p', value = 1.0, start=0.0, end=1.0, step=0.01)

widgets = pn.Column("<br>Parameters", temperature_pn, top_k_pn, top_p_pn)

text_input = pn.widgets.TextInput(placeholder='Enter a string here...')
generated_text = pn.pane.HTML(object = text_input.value, background='#f0f0f0', width=500, height=200 )
text_input.link(generated_text, value = 'object')

pn.Row(text_input, generated_text)

button = pn.widgets.Button(name="Generate", button_type="primary")

# Bokeh bar plot initialization
word_list = list(["test"])
probability_list = list([0.5])
plot=get_plot(word_list, probability_list)

text_part = pn.Column(text_input,button,generated_text)

bokeh_plot = pn.pane.Bokeh(plot, width=800)

# function to update data by button click
def click_cb(event):
    bokeh_plot.loading=True
    pred, filtered_next_token_logits = get_pred(generated_text.object,
                                                model,
                                                tokenizer,
                                                temperature_pn.value,
                                                top_k_pn.value,
                                                top_p_pn.value)
    generated_text.object +=pred
    probabilities, word_list = get_plot_data(filtered_next_token_logits)
    probability_list = probabilities.tolist()
    bokeh_plot.object =get_plot(word_list, probability_list)
    bokeh_plot.loading=False

# update data by button click
button.on_click(click_cb)

# call back function in case the text input changes
def text_change_cb(event):
    generated_text.object = event.new

# tying the callback function to the text_input widget
text_input.param.watch(text_change_cb, 'value')

text_generation = pn.Row(text_part, widgets)

app = pn.Column(text_generation, bokeh_plot)
app.servable()
2 Likes

I made a slightly improved version here Hugging Face GPT2 Transformer Example (github.com)

1 Like

Hi @Marc. Thank you very much for your help.

2 Likes