avatarAlex Tantos

Free AI web copilot to create summaries, insights and extended knowledge, download it at here

8630

Abstract

">=</span> <span class="hljs-number">1.2</span>) prompt <span class="hljs-operator">=</span> """ [INST] <> You are a helpful, respectful and honest assistant. Always answer as helpfully as possible, while being safe. Your answers should not include any harmful, unethical, racist, sexist, toxic, dangerous, or illegal content. Please ensure that your responses are socially unbiased and positive in nature.

If a question does not make any sense, or is not factually coherent, explain why instead of answering something not correct. If you don't know the answer to a question, please don't share false information.
&lt;&gt;

$instruction [/INST]

"""
text_token <span class="hljs-operator">=</span> generate_text(textenc, model, prompt; max_length, k, temperature)
<span class="hljs-keyword">for</span> (i, token) <span class="hljs-keyword">in</span> enumerate(text_token)
    m <span class="hljs-operator">=</span> <span class="hljs-keyword">match</span>(r"&lt;0x([0-9A-F]{2})&gt;", token)
    if <span class="hljs-operator">!</span>isnothing(m)
        c <span class="hljs-operator">=</span> parse(UInt8, m.captures[]; base<span class="hljs-operator">=</span><span class="hljs-number">16</span>)
        text_token[i] <span class="hljs-operator">=</span> StringView([c])
    <span class="hljs-keyword">end</span>
<span class="hljs-keyword">end</span>
gen_text <span class="hljs-operator">=</span> replace(<span class="hljs-keyword">join</span>(text_token), <span class="hljs-string">'▁'</span><span class="hljs-operator">=</span><span class="hljs-operator">&gt;</span><span class="hljs-string">' '</span>)
println(gen_text)

<span class="hljs-keyword">end</span>

generate(textenc, model, "Can you explain to me briefly what is the Julia programming language?")</pre></div><h1 id="2b37">Importing Packages</h1><p id="cae2">Here are all the necessary packages, including the two <code>Transformers.jl</code> modules, <code>HuggingFace</code> and <code>TextEncoders</code>, utilized in Peter’s notebook. I will reference them as they become relevant in the code below.</p><div id="dc07"><pre><span class="hljs-keyword">using</span> Transformers <span class="hljs-keyword">using</span> CUDA <span class="hljs-keyword">using</span> Transformers.HuggingFace <span class="hljs-keyword">using</span> Flux <span class="hljs-keyword">using</span> StatsBase <span class="hljs-keyword">using</span> Transformers.TextEncoders <span class="hljs-keyword">using</span> StringViews <span class="hljs-keyword">using</span> HuggingFaceApi</pre></div><h1 id="4e2e">Setting up the GPU devices</h1><div id="019c"><pre>CUDA<span class="hljs-selector-class">.devices</span>() CUDA<span class="hljs-selector-class">.device</span>!(<span class="hljs-number">1</span>)</pre></div><p id="a39a">The first line lists all available <code>GPU</code> devices of your machine or service and the second line selects the first one.</p><h1 id="7c1d">Disabling Scalar Indexing</h1><div id="b382"><pre>CUDA<span class="hljs-selector-class">.allowscalar</span>(false) <span class="hljs-built_in">enable_gpu</span>(true)</pre></div><p id="aa02">To leverage the full potential of Machine Learning, as is usually done in these use cases, Peter employs <code>CUDA</code>, a parallel computing platform and API model created by <code>NVIDIA</code>. This section of the code is aimed at optimizing the settings for the <code>GPU</code>(s):</p><ol><li><code>CUDA.allowscalar(false)</code>: This command ensures that our operations are vectorized. In essence, by disabling scalar indexing, which means work on individual data elements (scalars), computations are performed on multiple data elements on vectors or matrices simultaneously. This is ideal for the <code>GPU</code>'s architecture, because <code>GPU</code>s are designed to perform operations in parallel on large sets of data, and scalar operations can lead to performance bottlenecks.</li><li><code>enable_gpu(true)</code>: With this function call, we direct our computing tasks to the <code>GPU</code>.</li></ol><p id="70cc">In summary, these lines of code set up our environment to utilize <code>GPU</code> acceleration, enabling parallel processing for demanding tasks like text generation.</p><h1 id="0a71">Accessing the Llama-2 Model with an Access Token</h1><div id="e2a6"><pre>access_token = <span class="hljs-string">""</span> HuggingFaceApi.save_token(access_token)</pre></div><p id="3b76">This snippet sets up authentication with the <code>HuggingFace</code> API.</p><ol><li><code>access_token = ""</code>: Here is where you’ll insert your unique personal access token from <code>HuggingFace</code>. This token is a unique identifier that grants access to the API, acting as a password.</li><li><code>HuggingFaceApi.save_token(access_token)</code>: This line calls the method <code>save_token()</code> from the module named <code>HuggingFaceApi</code>, passing in the <code>access_token</code> variable as an argument. The purpose of this method is to save the access token in the configuration of the API client. This is necessary because the token tells the <code>HuggingFace</code> service who is accessing the API and ensures that the user has the correct permissions.</li></ol><p id="ef86">Without proper authentication, the API would reject your requests to ensure the security and privacy of its users and resources.</p><h1 id="6480">Loading the Model and Tokenizer</h1><div id="cc01"><pre><span class="hljs-attr">textenc</span> = hgf<span class="hljs-string">"meta-llama/Llama-2-7b-chat-hf:tokenizer"</span> <span class="hljs-attr">model</span> = todevice(hgf<span class="hljs-string">"meta-llama/Llama-2-7b-chat-hf:ForCausalLM"</span>)</pre></div><p id="2665">The tokenizer and the model are loaded from <code>HuggingFace</code>'s model hub, which is a well-known repository of pre-trained models. The identifier <code>meta-llama/Llama-2-7b-chat-hf:tokenizer</code> specifies which tokenizer to load. <code>todevice()</code> moves the model to the selected <code>GPU</code> device. The <code>ForCausalLM</code> suffix suggests that the <code>Llama-2</code> model is a type of LLM designed for generating text based on a given input (i.e., <i>causal language modeling</i>).</p><h1 id="19f2">Helper Functions for Text Generation</h1><h2 id="2fdb">The temp_softmax() softmax function</h2><div id="b6de"><pre><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">temp_softmax</span><span class="hljs-params">(logits; temperature = 1.2)</span></span> <span class="hljs-keyword">return</span> softmax(logits ./ temperature) <span class="hljs-keyword">end</span></pre></div><p id="dbb5"><code>temp_softmax()</code> is a custom softmax function with a temperature parameter scaling the logits of the model to adjust the <i>creativity</i> of the model's outputs. The softmax function is a common tool used in Machine Learning to convert logits (raw prediction scores) into probabilities that sum to 1 and <i>tunes</i> the model’s prediction confidence. Above 1, it promotes varied responses; below 1, it favors more certain predictions.</p><h2 id="6dee">The top_k_sample() function</h2><div id="f038"><pre><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">top_k_sample</span>(<span class="hljs-params">probs; k = <span class="hljs-number">1</span></span>) <span class="hljs-title">sorted</span> = <span class="hljs-title">sort</span>(<span class="hljs-params">probs, rev = <span class="hljs-literal">true</span></span>) <span class="hljs-title">indexes</span> = <span class="hljs-title">partialsortperm</span>(<span class="hljs-params">probs, <span class="hljs-number">1</span>:k, rev=<span class="hljs-literal">true</span></span>) <span class="hljs-title">index</span> = <span class="hljs-title">sample</span>(<span class="hljs-params">indexes, ProbabilityWeights(<span class="hljs-params">sorted[<span class="hljs-number">1</span>:k]</span>), <span class="hljs-number">1</span></span>) <span class="hljs-title">return</span> <span class="hljs-title">index</span> <span class="hljs-title">end</span></span></pre></div><p id="0fca">The <code>top_k_sample()</code> function implements top-k sampling, which restricts the model's choices to the <code>k</code> most likely next tokens. When <code>k</code> is set to 1, the function performs greedy sampling, always choosing the most likely next token. If <code>k</code> is larger, the sampling becomes more varied as it will choose from the top <code>k</code> most like

Options

ly next tokens.</p><p id="711f">Both of these functions are techniques for controlling the output of generative models to balance between randomness (or <i>creativity</i>) and the likelihood of the text. The use of these methods can significantly affect the style of the text generated by the model.</p><h1 id="3ccd">Generating Text</h1><div id="908b"><pre>function <span class="hljs-title function_">generate_text</span><span class="hljs-params">(textenc, model, context = <span class="hljs-string">""</span>; max_length = <span class="hljs-number">512</span>, k = <span class="hljs-number">1</span>, temperature = <span class="hljs-number">1.2</span>, ends = textenc.endsym)</span> encoded = encode(textenc, context).<span class="hljs-type">token</span> <span class="hljs-variable">ids</span> <span class="hljs-operator">=</span> encoded.<span class="hljs-type">onehots</span> <span class="hljs-variable">ends_id</span> <span class="hljs-operator">=</span> lookup(textenc.vocab, ends) <span class="hljs-keyword">for</span> i in <span class="hljs-number">1</span>:<span class="hljs-type">max_length</span> <span class="hljs-variable">input</span> <span class="hljs-operator">=</span> (; token = encoded) |> <span class="hljs-type">todevice</span> <span class="hljs-variable">outputs</span> <span class="hljs-operator">=</span> model(input) logits = <span class="hljs-meta">@view</span> outputs.logit[:, end, <span class="hljs-number">1</span>] probs = temp_softmax(logits; temperature) new_id = top_k_sample(collect(probs); k)[<span class="hljs-number">1</span>] push!(ids, new_id) new_id == ends_id && <span class="hljs-keyword">break</span> end <span class="hljs-keyword">return</span> decode(textenc, encoded) end</pre></div><p id="c7bf">The <code>generate_text()</code>function generates text by taking a prompt as input and running the model to produce output, respecting a maximum length and using the above helper functions for sampling.</p><p id="5a79">Let's break down the function to understand how it works:</p><ol><li><code>generate_text(textenc, model, context = ""; max_length = 512, k = 1, temperature = 1.2, ends = textenc.endsym)</code>: The signature of the function has several parameters:</li></ol><ul><li><code>textenc</code>: The tokenizer for encoding text to a format the model can process.</li><li><code>model</code>: The actual language model that will generate the predictions.</li><li><code>context</code>: An optional parameter that serves as a starting point or prompt for the text generation.</li><li><code>max_length</code>: The maximum length of the generated text sequence.</li><li><code>k</code>: The parameter for the top-k sampling strategy.</li><li><code>temperature</code>: The temperature for setting the softmax function.</li><li><code>ends</code>: The end-of-sequence symbol used to determine when to stop generating text.</li></ul><p id="3639">2. <code>encoded = encode(textenc, context).token</code>: The input <code>context</code> is encoded into a format that the model can understand (usually a list of token IDs).</p><p id="58e8">3. <code>ids = encoded.onehots</code>: The encoded tokens are further processed into one-hot encoded vectors, although this is not a typical step for language models which usually operate directly on token IDs.</p><p id="94ec">4. <code>ends_id = lookup(textenc.vocab, ends)</code>: This looks up the token ID for the end-of-sequence symbol in the tokenizer's vocabulary.</p><p id="7554">5. <code>for i in 1:max_length</code>: The function enters a loop that will continue until it either reaches the maximum length for the generated text or it encounters the end-of-sequence symbol.</p><p id="c2c0">6. <code>input = (; token = encoded) |> todevice</code>: The current input is prepared for the model, which includes the tokens generated so far. It is then moved to the device, the <code>GPU</code>, for computation.</p><p id="7176">7. <code>outputs = model(input)</code>: The model generates logits (raw output scores) for the next token in the sequence based on the input.</p><p id="7797">8. <code>logits = @view outputs.logit[:, end, 1]</code>: A slice of the logits for the last token in the sequence is extracted.</p><p id="f188">9. <code>probs = temp_softmax(logits; temperature)</code>: The custom softmax function defined above is applied to the logits using the specified temperature, to get a probability distribution.</p><p id="8888">10. <code>new_id = top_k_sample(collect(probs); k)[1]</code>: The top-k sampling function is applied to the probabilities to select the next token ID.</p><p id="b97b">11. <code>push!(ids, new_id)</code>: The new token ID is added to the list of generated token IDs.</p><p id="6f93">12. <code>new_id == ends_id && break</code>: If the generated token is the end-of-sequence symbol, the loop is exited.</p><p id="4f56">13. <code>return decode(textenc, encoded)</code>: Finally, the list of token IDs is decoded back into text and returned as the output of the function.</p><p id="da96">Essentially, the function generates one token at a time and appends it to the output until it reaches the maximum length or the end-of-sequence symbol.</p><h1 id="8b4d">Processing the Output</h1><p id="d0e2">The final code snippet of the notebook defines a new custom function <code>generate()</code> that formats the generated text and prints the output.</p><div id="1efd"><pre><span class="hljs-keyword">function</span> generate(textenc, model, instruction; max_length <span class="hljs-operator">=</span> <span class="hljs-number">512</span>, k <span class="hljs-operator">=</span> <span class="hljs-number">1</span>, temperature <span class="hljs-operator">=</span> <span class="hljs-number">1.2</span>) prompt <span class="hljs-operator">=</span> """ [INST] <> You are a helpful, respectful and honest assistant. Always answer as helpfully as possible, while being safe. Your answers should not include any harmful, unethical, racist, sexist, toxic, dangerous, or illegal content. Please ensure that your responses are socially unbiased and positive in nature.

If a question does not make any sense, or is not factually coherent, explain why instead of answering something not correct. If you don't know the answer to a question, please don't share false information.
&lt;&gt;

$instruction [/INST]

"""
text_token <span class="hljs-operator">=</span> generate_text(textenc, model, prompt; max_length, k, temperature)
<span class="hljs-keyword">for</span> (i, token) <span class="hljs-keyword">in</span> enumerate(text_token)
    m <span class="hljs-operator">=</span> <span class="hljs-keyword">match</span>(r"&lt;0x([0-9A-F]{2})&gt;", token)
    if <span class="hljs-operator">!</span>isnothing(m)
        c <span class="hljs-operator">=</span> parse(UInt8, m.captures[]; base<span class="hljs-operator">=</span><span class="hljs-number">16</span>)
        text_token[i] <span class="hljs-operator">=</span> StringView([c])
    <span class="hljs-keyword">end</span>
<span class="hljs-keyword">end</span>
gen_text <span class="hljs-operator">=</span> replace(<span class="hljs-keyword">join</span>(text_token), <span class="hljs-string">'▁'</span><span class="hljs-operator">=</span><span class="hljs-operator">&gt;</span><span class="hljs-string">' '</span>)
println(gen_text)

<span class="hljs-keyword">end</span></pre></div><h2 id="d6b6">Example Generation</h2><div id="4de0"><pre><span class="hljs-built_in">generate</span>(textenc, model, "Can you explain to me briefly what is the Julia programming language?")</pre></div><p id="581a">The last line of the notebook executes the custom generation function <code>generate()</code>, demonstrating a practical application of the model. And there you have it! The final piece of code brings the <code>Llama-2</code> model to life, showcasing its practical utility. Execute the <code>generate()</code> function and watch the model unfolding its responses throughout the dialogue. Happy coding!</p><p id="a5ab"><i>If you’re interested in deepening your understanding of programming concepts in Julia, R and Python, consider clapping this post, following me here and even subscribing to my <a href="https://www.youtube.com/@alextantos658">YouTube channel</a>. I regularly share content that could be a valuable resource for your learning journey. Essentially, I channel my passion for these languages into sharing insights from my own learning and usage journey.</i></p></article></body>

Walking Through Text Generation with Llama-2 Using Transformers.jl

Photo by Greg Lippert on Unsplash

No medium membership? Read this article for free here.

Julia is no longer a niche language. It has flourished, thanks to a vibrant community that consistently releases new packages across a variety of fields. Among these packages, Transformers.jl has captured my attention for its simplicity and elegant way of approaching complex NLP tasks, such as text generation.

Peter, the main contributor of Transformers.jl, has prepared two very well-structured example Jupyter notebooks that serve as a primer for engaging with the Dolly and the Llama2 models in real-time. In this article, I offer a detailed walkthrough of the Llama-2 Jupyter notebook example with detailed commentary that extends beyond the scope of Peter’s original Jupyter notebook. To set the stage, let’s first explore an overview of Llama-2’s capabilities and limitations.

Llama-2 Model Overview

The Llama-2 Model stands at the forefront of the latest generation of open-source Large Language Models (LLMs) pre-trained and fine-tuned to excel in text generation. It comes in different sizes, with the 7B parameter model being particularly optimized for dialogue/chat applications. Developed by Meta, Llama-2 models are designed to be safe in interactions​​​​.

The model was trained in 2023 exclusively on data available to the public, ensuring that no Meta user data was included. The pre-training corpus consisted of 2 trillion tokens, while the fine-tuning process incorporated over one million human-annotated examples for better performance and safety alignment​​​​.

Performance and Safety

In terms of safety benchmarks, the Llama-2 models showed impressive results, with the 7B model achieving a 57.04% score on TruthfulQA and 0% on ToxiGen, reflecting a low likelihood of generating toxic and harmful content​​.

Intended Use and Limitations

However, despite these advances, Llama-2 comes with its set of limitations. Llama-2 is built for commercial and research applications in English and does not support other languages(!) Moreover, while testing has shown promising results, it’s important to note that Llama-2, like any AI model, can produce inaccurate or biased content, necessitating cautious use​​.

Let’s move on to explore the code!

The Llama2 Full Example Code

In the cell code below, you’ll find the complete code from Peter’s Llama2 example Jupyter notebook.

using Transformers
using CUDA
using Transformers.HuggingFace
using Flux
using StatsBase
using Transformers.TextEncoders
using StringViews
using HuggingFaceApi

CUDA.devices()

CUDA.devices(1)

CUDA.allowscalar(false)
enable_gpu(true)

access_token = ""

# This will save the access token to the disk, then all call to 
# download file from huggingface hub will use this token.
using HuggingFaceApi
HuggingFaceApi.save_token(access_token)

# or call those `load` function with `auth_token` keyword argument
# like this:
HuggingFace.load_tokenizer("meta-llama/Llama-2-7b-chat-hf"; auth_token = access_token)



textenc = hgf"meta-llama/Llama-2-7b-chat-hf:tokenizer"
model = todevice(hgf"meta-llama/Llama-2-7b-chat-hf:ForCausalLM") # move to gpu with `todevice` (or `Flux.gpu`)



function temp_softmax(logits; temperature = 1.2)
    return softmax(logits ./ temperature)
end

function top_k_sample(probs; k = 1)
    sorted = sort(probs, rev = true)
    indexes = partialsortperm(probs, 1:k, rev=true)
    index = sample(indexes, ProbabilityWeights(sorted[1:k]), 1)
    return index
end



function generate_text(textenc, model, context = ""; max_length = 512, k = 1, temperature = 1.2, ends = textenc.endsym)
    encoded = encode(textenc, context).token
    ids = encoded.onehots
    ends_id = lookup(textenc.vocab, ends)
    for i in 1:max_length
        input = (; token = encoded) |> todevice
        outputs = model(input)
        logits = @view outputs.logit[:, end, 1]
        probs = temp_softmax(logits; temperature)
        new_id = top_k_sample(collect(probs); k)[1]
        push!(ids, new_id)
        new_id == ends_id && break
    end
    return decode(textenc, encoded)
end



function generate(textenc, model, instruction; max_length = 512, k = 1, temperature = 1.2)
    prompt = """
    [INST] <>
    You are a helpful, respectful and honest assistant. Always answer as helpfully as possible, while being safe.  Your answers should not include any harmful, unethical, racist, sexist, toxic, dangerous, or illegal content. Please ensure that your responses are socially unbiased and positive in nature.

    If a question does not make any sense, or is not factually coherent, explain why instead of answering something not correct. If you don't know the answer to a question, please don't share false information.
    <>

    $instruction [/INST]
    
    """
    text_token = generate_text(textenc, model, prompt; max_length, k, temperature)
    for (i, token) in enumerate(text_token)
        m = match(r"<0x([0-9A-F]{2})>", token)
        if !isnothing(m)
            c = parse(UInt8, m.captures[]; base=16)
            text_token[i] = StringView([c])
        end
    end
    gen_text = replace(join(text_token), '▁'=>' ')
    println(gen_text)
end

generate(textenc, model, "Can you explain to me briefly what is the Julia programming language?")

Importing Packages

Here are all the necessary packages, including the two Transformers.jl modules, HuggingFace and TextEncoders, utilized in Peter’s notebook. I will reference them as they become relevant in the code below.

using Transformers
using CUDA
using Transformers.HuggingFace
using Flux
using StatsBase
using Transformers.TextEncoders
using StringViews
using HuggingFaceApi

Setting up the GPU devices

CUDA.devices()
CUDA.device!(1)

The first line lists all available GPU devices of your machine or service and the second line selects the first one.

Disabling Scalar Indexing

CUDA.allowscalar(false)
enable_gpu(true)

To leverage the full potential of Machine Learning, as is usually done in these use cases, Peter employs CUDA, a parallel computing platform and API model created by NVIDIA. This section of the code is aimed at optimizing the settings for the GPU(s):

  1. CUDA.allowscalar(false): This command ensures that our operations are vectorized. In essence, by disabling scalar indexing, which means work on individual data elements (scalars), computations are performed on multiple data elements on vectors or matrices simultaneously. This is ideal for the GPU's architecture, because GPUs are designed to perform operations in parallel on large sets of data, and scalar operations can lead to performance bottlenecks.
  2. enable_gpu(true): With this function call, we direct our computing tasks to the GPU.

In summary, these lines of code set up our environment to utilize GPU acceleration, enabling parallel processing for demanding tasks like text generation.

Accessing the Llama-2 Model with an Access Token

access_token = ""
HuggingFaceApi.save_token(access_token)

This snippet sets up authentication with the HuggingFace API.

  1. access_token = "": Here is where you’ll insert your unique personal access token from HuggingFace. This token is a unique identifier that grants access to the API, acting as a password.
  2. HuggingFaceApi.save_token(access_token): This line calls the method save_token() from the module named HuggingFaceApi, passing in the access_token variable as an argument. The purpose of this method is to save the access token in the configuration of the API client. This is necessary because the token tells the HuggingFace service who is accessing the API and ensures that the user has the correct permissions.

Without proper authentication, the API would reject your requests to ensure the security and privacy of its users and resources.

Loading the Model and Tokenizer

textenc = hgf"meta-llama/Llama-2-7b-chat-hf:tokenizer"
model = todevice(hgf"meta-llama/Llama-2-7b-chat-hf:ForCausalLM")

The tokenizer and the model are loaded from HuggingFace's model hub, which is a well-known repository of pre-trained models. The identifier meta-llama/Llama-2-7b-chat-hf:tokenizer specifies which tokenizer to load. todevice() moves the model to the selected GPU device. The ForCausalLM suffix suggests that the Llama-2 model is a type of LLM designed for generating text based on a given input (i.e., causal language modeling).

Helper Functions for Text Generation

The temp_softmax() softmax function

function temp_softmax(logits; temperature = 1.2)
    return softmax(logits ./ temperature)
end

temp_softmax() is a custom softmax function with a temperature parameter scaling the logits of the model to adjust the creativity of the model's outputs. The softmax function is a common tool used in Machine Learning to convert logits (raw prediction scores) into probabilities that sum to 1 and tunes the model’s prediction confidence. Above 1, it promotes varied responses; below 1, it favors more certain predictions.

The top_k_sample() function

function top_k_sample(probs; k = 1)
    sorted = sort(probs, rev = true)
    indexes = partialsortperm(probs, 1:k, rev=true)
    index = sample(indexes, ProbabilityWeights(sorted[1:k]), 1)
    return index
end

The top_k_sample() function implements top-k sampling, which restricts the model's choices to the k most likely next tokens. When k is set to 1, the function performs greedy sampling, always choosing the most likely next token. If k is larger, the sampling becomes more varied as it will choose from the top k most likely next tokens.

Both of these functions are techniques for controlling the output of generative models to balance between randomness (or creativity) and the likelihood of the text. The use of these methods can significantly affect the style of the text generated by the model.

Generating Text

function generate_text(textenc, model, context = ""; max_length = 512, k = 1, temperature = 1.2, ends = textenc.endsym)
    encoded = encode(textenc, context).token
    ids = encoded.onehots
    ends_id = lookup(textenc.vocab, ends)
    for i in 1:max_length
        input = (; token = encoded) |> todevice
        outputs = model(input)
        logits = @view outputs.logit[:, end, 1]
        probs = temp_softmax(logits; temperature)
        new_id = top_k_sample(collect(probs); k)[1]
        push!(ids, new_id)
        new_id == ends_id && break
    end
    return decode(textenc, encoded)
end

The generate_text()function generates text by taking a prompt as input and running the model to produce output, respecting a maximum length and using the above helper functions for sampling.

Let's break down the function to understand how it works:

  1. generate_text(textenc, model, context = ""; max_length = 512, k = 1, temperature = 1.2, ends = textenc.endsym): The signature of the function has several parameters:
  • textenc: The tokenizer for encoding text to a format the model can process.
  • model: The actual language model that will generate the predictions.
  • context: An optional parameter that serves as a starting point or prompt for the text generation.
  • max_length: The maximum length of the generated text sequence.
  • k: The parameter for the top-k sampling strategy.
  • temperature: The temperature for setting the softmax function.
  • ends: The end-of-sequence symbol used to determine when to stop generating text.

2. encoded = encode(textenc, context).token: The input context is encoded into a format that the model can understand (usually a list of token IDs).

3. ids = encoded.onehots: The encoded tokens are further processed into one-hot encoded vectors, although this is not a typical step for language models which usually operate directly on token IDs.

4. ends_id = lookup(textenc.vocab, ends): This looks up the token ID for the end-of-sequence symbol in the tokenizer's vocabulary.

5. for i in 1:max_length: The function enters a loop that will continue until it either reaches the maximum length for the generated text or it encounters the end-of-sequence symbol.

6. input = (; token = encoded) |> todevice: The current input is prepared for the model, which includes the tokens generated so far. It is then moved to the device, the GPU, for computation.

7. outputs = model(input): The model generates logits (raw output scores) for the next token in the sequence based on the input.

8. logits = @view outputs.logit[:, end, 1]: A slice of the logits for the last token in the sequence is extracted.

9. probs = temp_softmax(logits; temperature): The custom softmax function defined above is applied to the logits using the specified temperature, to get a probability distribution.

10. new_id = top_k_sample(collect(probs); k)[1]: The top-k sampling function is applied to the probabilities to select the next token ID.

11. push!(ids, new_id): The new token ID is added to the list of generated token IDs.

12. new_id == ends_id && break: If the generated token is the end-of-sequence symbol, the loop is exited.

13. return decode(textenc, encoded): Finally, the list of token IDs is decoded back into text and returned as the output of the function.

Essentially, the function generates one token at a time and appends it to the output until it reaches the maximum length or the end-of-sequence symbol.

Processing the Output

The final code snippet of the notebook defines a new custom function generate() that formats the generated text and prints the output.

function generate(textenc, model, instruction; max_length = 512, k = 1, temperature = 1.2)
    prompt = """
    [INST] <>
    You are a helpful, respectful and honest assistant. Always answer as helpfully as possible, while being safe.  Your answers should not include any harmful, unethical, racist, sexist, toxic, dangerous, or illegal content. Please ensure that your responses are socially unbiased and positive in nature.

    If a question does not make any sense, or is not factually coherent, explain why instead of answering something not correct. If you don't know the answer to a question, please don't share false information.
    <>

    $instruction [/INST]
    
    """
    text_token = generate_text(textenc, model, prompt; max_length, k, temperature)
    for (i, token) in enumerate(text_token)
        m = match(r"<0x([0-9A-F]{2})>", token)
        if !isnothing(m)
            c = parse(UInt8, m.captures[]; base=16)
            text_token[i] = StringView([c])
        end
    end
    gen_text = replace(join(text_token), '▁'=>' ')
    println(gen_text)
end

Example Generation

generate(textenc, model, "Can you explain to me briefly what is the Julia programming language?")

The last line of the notebook executes the custom generation function generate(), demonstrating a practical application of the model. And there you have it! The final piece of code brings the Llama-2 model to life, showcasing its practical utility. Execute the generate() function and watch the model unfolding its responses throughout the dialogue. Happy coding!

If you’re interested in deepening your understanding of programming concepts in Julia, R and Python, consider clapping this post, following me here and even subscribing to my YouTube channel. I regularly share content that could be a valuable resource for your learning journey. Essentially, I channel my passion for these languages into sharing insights from my own learning and usage journey.

AI
Julialang
Llama 2
Text Generation
Programming
Recommended from ReadMedium