Skip to content

4. Vector Math with Tokens

Updated
17 Sept 2026
4 min read
6 sections
Page 25 of 32
Deep Understanding of AI Large Language Models
Adapted from A deep understanding of AI language model mechanisms — https://udemy.com/course/dullms_x

1. Setup and Initialization

We'll start by importing our standard data science stack and loading the GPT-2 model and tokenizer to extract the embedding weights.

python
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.gridspec import GridSpec

# highres plots
import matplotlib_inline.backend_inline
matplotlib_inline.backend_inline.set_matplotlib_formats('svg')

Import GPT-2 model and extract its embeddings matrix

python
from transformers import GPT2Model,GPT2Tokenizer

# pretrained GPT-2 model and tokenizer
gpt2 = GPT2Model.from_pretrained('gpt2')
tokenizer = GPT2Tokenizer.from_pretrained('gpt2')

embeddings = gpt2.wte.weight.detach().numpy()
Out · stderr
/Users/drippy/.pyenv/versions/3.12.6/lib/python3.12/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html
  from .autonotebook import tqdm as notebook_tqdm

2. Converting Numbers to Tokens

How does a tokenizer handle numbers? Let's iterate through a range of integers to see which token IDs are assigned to them and how they are decomposed.

python
# create some numbers
numbers = np.arange(11)
numbers = np.concatenate( (numbers,10*numbers[2:],100*numbers[2:]), axis=0)

# initialize token vector
numTokenLabels = np.zeros(len(numbers))

# get and report the tokens
for i,n in enumerate(numbers):

  # get the first token for this number
  numTokenLabels[i] = tokenizer.encode(str(n))[0]

  # try /2
  print(f'The number {n:5} is token(s) {tokenizer.encode(str(n))}')
Out · stdout
The number     0 is token(s) [15]
The number     1 is token(s) [16]
The number     2 is token(s) [17]
The number     3 is token(s) [18]
The number     4 is token(s) [19]
24 more lines
The number     5 is token(s) [20]
The number     6 is token(s) [21]
The number     7 is token(s) [22]
The number     8 is token(s) [23]
The number     9 is token(s) [24]
The number    10 is token(s) [940]
The number    20 is token(s) [1238]
The number    30 is token(s) [1270]
The number    40 is token(s) [1821]
The number    50 is token(s) [1120]
The number    60 is token(s) [1899]
The number    70 is token(s) [2154]
The number    80 is token(s) [1795]
The number    90 is token(s) [3829]
The number   100 is token(s) [3064]
The number   200 is token(s) [2167]
The number   300 is token(s) [6200]
The number   400 is token(s) [7029]
The number   500 is token(s) [4059]
The number   600 is token(s) [8054]
The number   700 is token(s) [9879]
The number   800 is token(s) [7410]
The number   900 is token(s) [12865]
The number  1000 is token(s) [12825]

Token Value Visualization

By plotting the number against its token ID, we can see if there is any linear relationship or pattern in the way the tokenizer assigns IDs to numeric strings.

python
plt.figure(figsize=(10,4))
plt.plot(numbers,numTokenLabels,color=[.5,.5,.5],linewidth=.5)
plt.scatter(numbers,numTokenLabels,c=np.arange(len(numbers)),s=100,marker='s',cmap='plasma_r',zorder=10)

plt.gca().set(xlabel='Number (as string)',ylabel='Token value',xticks=numbers,xlim=[numbers[0]-15,numbers[-1]+15])
# plt.gca().set(xscale='log')
plt.show()
Out · plot
Output 1

3. Token Length Analysis

Integers and floating-point numbers are decomposed differently. We'll analyze the number of tokens required to represent various numeric types.

python
# tokenize integers and floating-point numbers

# initialize
numnums = 99_999
int_toklens = np.zeros(numnums,dtype=int)
float_toklens = np.zeros(numnums,dtype=int)

# random numbers
ra = 5*np.random.randn(numnums)

for i in range(numnums):

  # integers
  int_toklens[i] = len(tokenizer.encode(str(i)))

  # and the random numbers
  float_toklens[i] = len(tokenizer.encode(str(ra[i])))
python
_,axs = plt.subplots(1,2,figsize=(12,4))

axs[0].plot(int_toklens+np.random.randn(numnums)/30,'s',markerfacecolor=[.7,.7,.9],alpha=.4)
axs[1].plot(ra,float_toklens+np.random.randn(numnums)/50,'o',markerfacecolor=[.7,.9,.7],alpha=.4)

axs[0].set(xlabel='Number',ylabel='Token length',yticks=range(int_toklens.max()+2),title='Token lengths of integers')
axs[1].set(xlabel='Number',ylabel='Token length',title='Token lengths of floating-point numbers')

plt.tight_layout()
plt.show()
Out · plot
Output 2

4. Arithmetic on Token IDs

Does multiplying two token IDs result in a meaningful new token? This exercise demonstrates that token IDs are arbitrary indices and do not follow mathematical logic.

python
# the equation and its tokens
eq = '5 x 3 ='
tokens = tokenizer.encode(eq)

# try the math
print(f'{eq} -> {tokens}')
print(f'Product of tokens = {np.prod(tokens)}')
# Updated line in cell 7
print(f'   which is "{tokenizer.decode(int(np.prod(tokens))) if np.prod(tokens) < tokenizer.vocab_size else "[OUT OF VOCAB]"}"')
Out · stdout
5 x 3 = -> [20, 2124, 513, 796]
Product of tokens = 17346623040
Out · error
TypeError: 'NoneType' object is not iterable
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[7], line 8
      6 print(f'{eq} -> {tokens}')
21 more lines
      7 print(f'Product of tokens = {np.prod(tokens)}')
----> 8 print(f'   which is "{tokenizer.decode(np.prod(tokens))}"')

File ~/.pyenv/versions/3.12.6/lib/python3.12/site-packages/transformers/tokenization_utils_base.py:3870, in PreTrainedTokenizerBase.decode(self, token_ids, skip_special_tokens, clean_up_tokenization_spaces, **kwargs)
   3867 # Convert inputs to python lists
   3868 token_ids = to_py_obj(token_ids)
-> 3870 return self._decode(
   3871     token_ids=token_ids,
   3872     skip_special_tokens=skip_special_tokens,
   3873     clean_up_tokenization_spaces=clean_up_tokenization_spaces,
   3874     **kwargs,
   3875 )

File ~/.pyenv/versions/3.12.6/lib/python3.12/site-packages/transformers/tokenization_utils.py:1104, in PreTrainedTokenizer._decode(self, token_ids, skip_special_tokens, clean_up_tokenization_spaces, spaces_between_special_tokens, **kwargs)
   1102 current_sub_text = []
   1103 # TODO @ArthurZ in version 5, special tokens should be handled in convert_tokens_to_string, while _convert_tokens_to_string
-> 1104 for token in filtered_tokens:
   1105     if skip_special_tokens and token in self.all_special_tokens:
   1106         continue

TypeError: 'NoneType' object is not iterable
python
# maybe just the numbers?
t5 = tokenizer.encode('5')
t3 = tokenizer.encode('3')

# let's see...
print(f'"5" and "3" have tokens {t5} and {t3}.')
print(f'Their product is {t5[0]*t3[0]}, which is "{tokenizer.decode(int(t5[0]*t3[0])) if t5[0]*t3[0] < tokenizer.vocab_size else "[OUT OF VOCAB]"}"')
Out · stdout
"5" and "3" have tokens [20] and [18].
Their product is 360, which is " D"

5. Vector Math with Embeddings

Unlike token IDs, the high-dimensional embedding vectors are designed to capture semantic meaning. We can perform vector addition and multiplication to see how they interact.

python
# isolate the embedding vectors
e5 = embeddings[t5,:].squeeze()
e3 = embeddings[t3,:].squeeze()

# math
theirSum  = e3+e5
theirProd = e3*e5

# plot the vectors
plt.figure(figsize=(12,4))

plt.plot(e3,label='3')
plt.plot(e5,label='5')
plt.plot(theirSum,label='3+5')
plt.plot(theirProd,label='3x5')

plt.gca().set(xlabel='Embeddings dimension',ylabel='Value',xlim=[0,len(e3)])
plt.legend()
plt.show()
Out · plot
Output 3

Unembedding Result

To decode a vector back into a token, we use unembedding. This process finds the token in the vocabulary that is most similar to our calculated vector vv:

tidx=arg⁡max⁡(vET)t_{idx} = \arg\max(v E^T)

  • vv: The target vector (e.g., $e3 + e5$).
  • ETE^T: Transpose of the embedding matrix. Multiplying vv by ETE^T calculates the dot product (similarity) with every token in the vocabulary.
  • arg⁡max⁡\arg\max: Returns the index of the token with the highest similarity score.
GPT models use Weight Tying, meaning the same matrix EE is used for both input embedding and output unembedding.
python
# Project back to vocab space using matrix transpose (dot product similarity)
sumUnembedding  = theirSum @ embeddings.T
prodUnembedding = theirProd @ embeddings.T

# Check dimensions: (embedding_dim) @ (embedding_dim, vocab_size) -> (vocab_size)
print(f'Summed vector X embeddings matrix = unembeddings vector')
print(f'    {theirSum.shape}    X    {embeddings.shape}   =  {sumUnembedding.shape}\n')

# Use argmax to find the most similar token index and decode to string
print(f'Max embedding of 5+3 = "{tokenizer.decode(np.argmax(sumUnembedding))}"')
print(f'Max embedding of 5x3 = "{tokenizer.decode(np.argmax(prodUnembedding))}"')
Out · stdout
Summed vector X embeddings matrix = unembeddings vector
    (768,)    X    (50257, 768)   =  (50257,)

Max embedding of 5+3 = "5"
Max embedding of 5x3 = " Weinstein"

Why does 5 + 3 unembed as "5"? Even though we added the vector for "3", the resulting vector remains most similar to the original "5" token. Because the cosine similarity is still highest with "5", the argmax operation selects it as the output. This highlights that simple vector addition doesn't perform mathematical arithmetic; it just shifts the position in high-dimensional space.

python
# curious to see how the embeddings for '3' and '5' compare
plt.plot(e5,e3,'.')
plt.show()
Out · plot
Output 4

Key Take-home Points

  • Arbitrary Mapping: Token IDs are purely indices and have no mathematical relationship to the value they represent (e.g., token ID 20 times 18 is not token ID 360).
  • Numerical Tokenization: Tokenizers often break large numbers or floats into multiple sub-tokens, which can make mathematical reasoning difficult for LLMs.
  • Vector Logic: Performing math on embedding vectors (like addition) results in a new vector that can be "unembedded" to see which token it is most similar to.
  • Semantic Space: While vector math is the basis for attention mechanisms, simple arithmetic on embeddings rarely results in precise mathematical outcomes.