Guest User

Untitled

a guest
Aug 13th, 2025
100
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 14.13 KB | None | 0 0
  1. We are going to benchmark perplexity using llama.cpp.
  2. Perplexity is a common metric for language models, and it's calculated based on the probability of a given text.
  3. In the context of llama.cpp, we can compute the perplexity of a text by running the model and computing the log probabilities for each token (or word) and then exponentiating the average negative log probability.
  4.  
  5. Steps:
  6.  
  7. We need a pre-compiled version of llama.cpp (or build it from source).
  8. We will use the perplexity executable from the repo, which computes perplexity on a given text.
  9.  
  10.  
  11. However, note that the perplexity executable in llama.cpp is designed to compute perplexity on a fixed set of tokens (the training set or validation set) and is not directly for general texts.
  12.  
  13. Alternatively, we can use the main executable in an interactive mode to compute the log probabilities for a given text and then extract the perplexity.
  14.  
  15. But there's a built-in way: the perplexity binary in the repo (if available) or we can use the quantize benchmark script? Actually, let me check.
  16.  
  17. Actually, in the llama.cpp repository, there is a perplexity example that we can use.
  18.  
  19. However, the standard approach in llama.cpp for perplexity is to use the perplexity executable. But note that this executable is part of the examples and may not be built by default.
  20.  
  21. How to proceed:
  22.  
  23. Option 1: Use the perplexity executable (if available in the build)
  24.  
  25. It's in the examples directory? Actually, I think it's in the bin directory after building.
  26.  
  27.  
  28. Option 2: Use the main executable in an interactive mode to compute perplexity on a given text.
  29.  
  30. Let me describe Option 1:
  31.  
  32. Step 1: Make sure you have built the perplexity executable. It might be that you have to build it specifically.
  33.  
  34. In the llama.cpp repository, after running make, you can find the perplexity binary in the build directory (or in the bin directory). If it's not there, you can try to build it by:
  35.  
  36.  
  37.  
  38. The perplexity example is in the examples folder? Actually, I think it's built as part of the main make. But if not, you can try:
  39.  
  40. make clean
  41. make perplexity
  42.  
  43.  
  44. Step 2: Prepare your test text (a corpus of text for which you want to compute perplexity). The perplexity executable expects a text file.
  45.  
  46. Step 3: Run the perplexity benchmark:
  47. ./perplexity -m /path/to/model.gguf -f /path/to/text_file -c 2048 -b 512
  48.  
  49. Where:
  50.  
  51. -m: model file (in GGUF format)
  52. -f: file containing the text
  53. -c: context window size (max)
  54. -b: batch size for processing
  55.  
  56.  
  57. However, note that this will compute the perplexity over the entire text.
  58.  
  59. But wait: the perplexity computation in language models is typically done by conditioning on the previous tokens. The perplexity executable in llama.cpp does the following:
  60.  
  61. It splits the text into chunks that fit in the context window (with sliding window?).
  62. It computes the log probabilities for each token (conditioned on the previous tokens) and then averages the negative log probabilities over the total number of tokens (excluding the first token which is a BOS if present).
  63.  
  64.  
  65. Step 4: The output will give you the perplexity.
  66.  
  67. However, if the perplexity executable is not available, we can use the main program in a different way.
  68.  
  69. Option 2: Using the main executable to generate the text and then compute the log probabilities.
  70.  
  71. But this is more manual. Alternatively, we can use the quantize benchmark script which can also do perplexity? Actually, the quantize script has a benchmark function that runs perplexity.
  72.  
  73. However, I think the perplexity executable is the intended way.
  74.  
  75. But note: the perplexity computation in the perplexity executable might be done in a sliding window manner, which is important for long texts.
  76.  
  77. Let me check the exit status and output:
  78.  
  79. If the program runs successfully, it will print the perplexity at the end.
  80.  
  81.  
  82. Example output:
  83. perplexity: x.xx
  84.  
  85. Step 5: Record the perplexity value.
  86.  
  87. However, note that the perplexity benchmark might be sensitive to:
  88.  
  89. The batch size (-b) and context window size (-c). You should set the context window size to at least as large as the model's context window (or the maximum you want to use). But note that the text will be broken into chunks of (context window size - overlap) or something? Actually, the perplexity executable uses a sliding window with a stride of the batch size? Not exactly.
  90.  
  91.  
  92. Looking at the source code of the perplexity executable (if available) might help, but we can rely on the documentation.
  93.  
  94. Alternatively, we can use the quantize script which has a --perplexity option? Actually, the quantize script also has a benchmark function that includes perplexity. But let's stick to the dedicated executable.
  95.  
  96. Important: The perplexity value is reported as the exponential of the average negative log likelihood (with base e) over the tokens. So:
  97. perplexity = exp( (1/N) * sum_{i=1}^{N} -log(p_i) )
  98.  
  99. where p_i is the probability of the i-th token given the previous tokens.
  100.  
  101. But note: the model might use a different base (like base 2) in some contexts? The exponential function is natural by default.
  102.  
  103. Also, the perplexity computation might exclude the first token because it's a BOS (beginning of sentence) token? Or it might be included? Actually, the perplexity executable in llama.cpp uses a BOS token at the beginning of each chunk and then computes the probability of each token in the chunk (so the first token is conditioned on nothing? and then the next on that token). So the first token in the chunk is predicted with a non-sense context? Actually, the BOS token is added at the start of each chunk.
  104.  
  105. How to run:
  106.  
  107.  
  108.  
  109. The perplexity executable expects a text file that should not include any special tokens? It will add BOS at the start of each chunk.
  110.  
  111.  
  112. It breaks the text into chunks of (n_ctx - 1) tokens? Because the BOS token is added, so the context can hold up to n_ctx tokens, which includes the BOS and then the next (n_ctx-1) tokens. Then each chunk is of length (n_ctx - 1). But note: the sliding window might have an overlap? Actually, the code in perplexity uses a non-overlapping chunking? Or overlapping? I think it uses a sliding window with step size of (batch_size) but only if batch_size is less than the chunk size? Actually, the perplexity executable uses:
  113.  
  114. chunk_size = min(n_ctx, max_tokens) ???
  115.  
  116.  
  117. Actually, the source code of perplexity (if available) would clarify. But let me assume that we can trust the implementation.
  118.  
  119. Step 6: We might want to run it multiple times (if there's randomness) but note that the perplexity computation is deterministic because we are not sampling for the probabilities? Actually, the perplexity computation is deterministic because we are greedy sampling? Or we are using the log probabilities of the next token without sampling? Actually, we are computing the log probability for the next token at each step (greedy decoding). So it's deterministic.
  120.  
  121. However, note: the perplexity computation in llama.cpp uses a greedy approach (selecting the token with the highest probability) for the next token? Actually, no: it's not choosing a token, it's computing the probability of the actual token in the text. So it's deterministic.
  122.  
  123. But wait: the model might have dropout? The perplexity computation in inference is typically done without dropout. So we are safe.
  124.  
  125. How to interpret the perplexity:
  126.  
  127. Lower perplexity is better.
  128.  
  129.  
  130. Example command:
  131.  
  132. ./perplexity -m models/7B/ggml-model-q4_0.gguf -f test.txt -c 2048 -b 512
  133.  
  134. The test.txt should contain the text you want to evaluate on.
  135.  
  136. But note: if the text is very long, it will be broken into chunks of (context window size - 1) tokens? Actually, the context window includes the BOS token and then the tokens of the text. So the first chunk will be BOS + the first (n_ctx-1) tokens. Then the next chunk will be BOS + tokens starting from the (n_ctx-1)th token? Or it uses a sliding window? The implementation in perplexity uses:
  137.  
  138.  
  139.  
  140. It starts by tokenizing the entire text.
  141.  
  142.  
  143. Then it breaks the tokens into chunks of (n_ctx - 1) tokens per chunk? Because we add BOS at the beginning, so each chunk can have at most (n_ctx - 1) tokens.
  144.  
  145.  
  146. Then, for each chunk, it runs the forward pass starting with BOS and then the tokens in the chunk (except the last token, because the last token is used to condition the next? Actually, no: we run the forward pass for the entire chunk, and then we compute the log probability for each token (from the second to the last) given the prefix (which includes the BOS and the tokens before).
  147.  
  148.  
  149. So for a chunk of T tokens (which is the BOS and then (T-1) tokens from the text), we have T-1 tokens for which we compute the probability.
  150.  
  151.  
  152. The chunking is non-overlapping? Actually, the implementation in perplexity (if we look at the source) uses:
  153.  
  154. for (int i = 0; i < n_tokens; i += n_tokens_in_each_forward) {
  155. // process a chunk starting at token i
  156. }
  157.  
  158. where n_tokens_in_each_forward = n_ctx - 1 (because we have BOS and then (n_ctx-1) tokens)
  159.  
  160.  
  161. But note: the tokens are processed in a sliding window? Actually, no: it's a non-overlapping chunking. So the division is by integer steps? But that might leave the last chunk incomplete. Actually, the code in perplexity (if available) would be:
  162.  
  163. int n_tokens_in_each_forward = n_ctx - 1;
  164.  
  165. for (int i = 0; i < n_tokens; i += n_tokens_in_each_forward) {
  166. // process a chunk starting at token i for n_ctx tokens (BOS and then n_tokens_in_each_forward tokens)
  167. // But note: we have to take at most n_tokens_in_each_forward tokens from the text for this chunk.
  168. }
  169.  
  170.  
  171. However, if the tokenized text is longer than the context window, then the chunks are broken down, but there is no overlap. So the context for the next chunk is only the BOS token. This means that the model is not conditioned on the previous chunk? So the perplexity is computed independently per chunk? That might not be ideal.
  172.  
  173.  
  174. Actually, the implementation of perplexity in llama.cpp (as of the time of writing) uses a sliding window that is non-overlapping? Or overlapping? I think it is non-overlapping. But that is not the standard way to compute perplexity for long texts. The standard way is to use a sliding window with step size 1? But that would be very inefficient.
  175.  
  176.  
  177. Alternatively, we can use a large context window and run the entire text in one pass if possible? But if the text is longer than the context window, we have to break it.
  178.  
  179.  
  180. The standard approach in perplexity computation for long texts is to use a sliding window where each token (except the first) is conditioned on the previous tokens, and we compute the probability for each token independently. The model context window is used by taking the last (n_ctx-1) tokens for each token? But that would be inefficient because we would have to run the model for each token in the text independently? Actually, we use a sliding window that moves one token at a time? But that is too slow.
  181.  
  182.  
  183. The perplexity executable in llama.cpp uses a more efficient method: it breaks the text into chunks that fit in the context window, and it uses the BOS token at the start of each chunk. So the tokens in the middle of a long text that are near the boundary of a chunk are conditioned on a context that does not include tokens from the previous chunk? That would be incorrect.
  184.  
  185.  
  186. This is a known issue in perplexity computation for long texts with a limited context window. The common solution is to use a large context window so that the entire text (or the vast majority) can be processed in one go.
  187.  
  188.  
  189. Therefore, for accurate perplexity computation, it is best to use a context window that is at least as large as the entire text? But that is not always possible.
  190.  
  191.  
  192. Alternatively, we can use a sliding window that moves one token at a time? But that is computationally expensive.
  193.  
  194.  
  195. However, note that the perplexity executable in llama.cpp uses a chunking that is independent per chunk. This means that the perplexity value is computed conditionally on the BOS token at the beginning of each chunk. Therefore, for chunks that are not at the beginning, the context is missing the previous chunks.
  196.  
  197.  
  198. This is a limitation. So if your text is longer than the context window, the perplexity computed for tokens after the first chunk is not conditioned on the entire prefix, and the result will be inflated (worse perplexity).
  199.  
  200.  
  201. Therefore, for long texts, it is important to use a context window that is large enough to cover the entire text, or at least a significant portion of the text (so that the chunks are large and the missing context is negligible).
  202.  
  203.  
  204. Alternatively, we can use a sliding window with an overlap? But the perplexity executable does not do that.
  205.  
  206.  
  207. So if you are evaluating on a long text, make sure to use a context window that is as large as possible (and your hardware allows) to minimize the error.
  208.  
  209.  
  210. For short text (within the context window), the perplexity is computed correctly.
  211.  
  212.  
  213. How to get the perplexity executable:
  214.  
  215.  
  216.  
  217. In the llama.cpp repository, after running make, there should be a binary named perplexity in the build directory? Or it might be named perplexity-bench? Actually, in the repository, there is a file examples/perplexity? Or is it integrated in the main build?
  218.  
  219.  
  220. Let me check: in the llama.cpp source, there is a file called examples/perplexity.cpp. This is a separate example program. So you can build it by:
  221. make perplexity
  222.  
  223.  
  224. Then it will create an executable.
  225.  
  226.  
  227. Alternatively, you can run:
  228. make clean && make
  229.  
  230. and then look for the perplexity executable in the build directory.
  231.  
  232.  
  233. If you are building on Windows, the executable might be named perplexity.exe.
  234.  
  235.  
  236. Step 7: Run the benchmark and record the perplexity. [...]
Advertisement
Add Comment
Please, Sign In to add comment