<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>pdfcrun.ch</title>
    <description></description>
    <link>https://pdfcrun.ch/</link>
    <atom:link href="https://pdfcrun.ch/feed.xml" rel="self" type="application/rss+xml"/>
    <pubDate>Mon, 21 Oct 2024 09:29:18 +0000</pubDate>
    <lastBuildDate>Mon, 21 Oct 2024 09:29:18 +0000</lastBuildDate>
    <generator>Jekyll v4.3.2</generator>
    
      <item>
        <title>Neuro-evolutionary Tetris</title>
        <description>&lt;p&gt;I want a teach a neural network how to play Tetris.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;source code: &lt;a href=&quot;https://github.com/opyate/neuro-evolved-tetris&quot;&gt;https://github.com/opyate/neuro-evolved-tetris&lt;/a&gt;&lt;/p&gt;

  &lt;p&gt;video: &lt;a href=&quot;https://youtu.be/rji6zOQgJZs&quot;&gt;https://youtu.be/rji6zOQgJZs&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;What are the possible approaches?&lt;/p&gt;

&lt;p&gt;A supervised learning approach could be that I record a Tetris world champion playing lots and lots of games, then create a large labelled dataset, then train a model on said dataset. However, that’s a lot of work, and the model might generalise to play exactly like that world champion and potentially miss out on novel Tetris techniques and play styles.&lt;/p&gt;

&lt;p&gt;A reinforcement learning (RL) approach could be that the AI agent learns by playing the game, receiving rewards for scoring and staying alive longer, and penalties for filling up the Tetris grid without clearing lines. It adjusts its actions (move a piece left or right) to maximise its score. That involves defining a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;policy&lt;/code&gt; (which is a strategy that maximizes its cumulative rewards over time) and a corresponding &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;reward function&lt;/code&gt; to provide feedback for adjusting the policy. In fact, RL works quite well for game playing and robotics!&lt;/p&gt;

&lt;p&gt;But, I want to try something a bit different, like genetic algorithms &lt;a href=&quot;#footnote-a&quot;&gt;ᵃ&lt;/a&gt;. Specifically, &lt;a href=&quot;http://www.scholarpedia.org/article/Neuroevolution&quot;&gt;neuroevolution&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Like reinforcement learning, neuroevolution allows an AI to learn through interaction with an environment, but instead of directly adjusting actions, neuroevolution focuses on evolving the neural network. Different neural networks with varying weights &lt;a href=&quot;#footnote-b&quot;&gt;ᵇ&lt;/a&gt; are tested in the game, and the ones that achieve higher scores are selected and “bred” to create new generations of better-performing Tetris players.&lt;/p&gt;

&lt;p&gt;Crucially, this approach will differ from the other approaches in that it doesn’t use backpropagation to update the network weights and biases (during which the network uses gradient descent to calculate the gradient of the error with respect to each weight and bias, with the gradient indicating the direction and magnitude of change needed to reduce the error). If you look at the code, you’ll only see the forward pass implemented.&lt;/p&gt;

&lt;p&gt;So, neuroevolution it is!&lt;/p&gt;

&lt;h1 id=&quot;overview-of-experiment&quot;&gt;Overview of experiment&lt;/h1&gt;

&lt;p&gt;Here are some high level steps that describes the simulation.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;spawn thousands of bots from the simulation runner&lt;/li&gt;
  &lt;li&gt;each bot contains
    &lt;ul&gt;
      &lt;li&gt;a brain, aka a &lt;a href=&quot;#the-pytorch-network&quot;&gt;PyTorch network&lt;/a&gt;, which has
        &lt;ul&gt;
          &lt;li&gt;10x10 inputs which represents the Tetris grid&lt;/li&gt;
          &lt;li&gt;a hidden layer&lt;/li&gt;
          &lt;li&gt;7 outputs which represents the possible player moves (left, right, etc)&lt;/li&gt;
          &lt;li&gt;random initial weights&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
      &lt;li&gt;a &lt;a href=&quot;#the-tetris-engine&quot;&gt;Tetris engine&lt;/a&gt;, which
        &lt;ul&gt;
          &lt;li&gt;maintains the internal game state&lt;/li&gt;
          &lt;li&gt;allows 7 possible moves to modify said game state&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;the simulation loop now starts, and every bot predicts their next move&lt;/li&gt;
  &lt;li&gt;after each bot made their move, they get fitness points
    &lt;ul&gt;
      &lt;li&gt;for staying alive,&lt;/li&gt;
      &lt;li&gt;for scoring (i.e. clearing lines on the Tetris grid)&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;after each bot made their move, they might also be in a game over state&lt;/li&gt;
  &lt;li&gt;after all bots reach game over, they evolve
    &lt;ul&gt;
      &lt;li&gt;evolution step 1: parents are chosen based on a &lt;a href=&quot;#weighted-selection&quot;&gt;weighted selection&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;evolution step 2: the parents reproduce via &lt;a href=&quot;#crossover&quot;&gt;crossover&lt;/a&gt;, to produce a child&lt;/li&gt;
      &lt;li&gt;evolution step 3: the child is slightly &lt;a href=&quot;#mutation&quot;&gt;mutated&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;the children becomes the brains for the next cohort of bots, with a fresh game state&lt;/li&gt;
  &lt;li&gt;the simulation loops again&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;the-pytorch-network&quot;&gt;The PyTorch network&lt;/h2&gt;

&lt;p&gt;I use PyTorch to create a network with 10x10 inputs to represent the Tetris grid &lt;a href=&quot;#footnote-c&quot;&gt;ᶜ&lt;/a&gt;, and 7 outputs to represent the possible moves, which are:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;up&lt;/li&gt;
  &lt;li&gt;down&lt;/li&gt;
  &lt;li&gt;left&lt;/li&gt;
  &lt;li&gt;right&lt;/li&gt;
  &lt;li&gt;rotate clockwise&lt;/li&gt;
  &lt;li&gt;rotate counter clockwise&lt;/li&gt;
  &lt;li&gt;no operation (do nothing)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Given any grid state (e.g. an empty grid with a starting piece at the top, or a mid-game grid with some stacks at the bottom), the model will run a classification task, i.e. which is the best move to make given the current state.&lt;/p&gt;

&lt;h2 id=&quot;the-tetris-engine&quot;&gt;The Tetris Engine&lt;/h2&gt;

&lt;p&gt;I built a Tetris engine from scratch that was completely decoupled from any game ticks and rendering logic. This allows me to run a simulation as fast as my CPU &lt;a href=&quot;#footnote-d&quot;&gt;ᵈ&lt;/a&gt; cores will allow, thereby not having to wait for one game tick every 16ms (if we assume 60FPS)&lt;/p&gt;

&lt;p&gt;Scoring is &lt;a href=&quot;https://tetris.wiki/Scoring&quot;&gt;standard Tetris scoring&lt;/a&gt; and I even implemented rotation using the recommended &lt;a href=&quot;https://tetris.wiki/Super_Rotation_System&quot;&gt;SRS (Super Rotation System)&lt;/a&gt; strategy, which allows for &lt;a href=&quot;https://tetris.wiki/Wall_kick&quot;&gt;wall kicks&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The engine also detects if the bot plays many repeat moves (that aren’t &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;up&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;down&lt;/code&gt;), upon which is forces game over.&lt;/p&gt;

&lt;h2 id=&quot;weighted-selection&quot;&gt;Weighted selection&lt;/h2&gt;

&lt;p&gt;Our weighted selection function uses a relay-race technique for giving a fair shot to all members of a population, while still increasing the chances of selection for those with higher fitness scores.&lt;/p&gt;

&lt;p&gt;It works like this:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;imagine a relay race, where each bot runs a distance tied to its fitness&lt;/li&gt;
  &lt;li&gt;the higher the fitness, the farther they run&lt;/li&gt;
  &lt;li&gt;pick a random starting line (so, a random distance to the finish line)&lt;/li&gt;
  &lt;li&gt;the race begins with the first bot, and it runs a distance equal to its normalised fitness score&lt;/li&gt;
  &lt;li&gt;loop for every bot&lt;/li&gt;
  &lt;li&gt;the race ends when a bot cross the finish line&lt;/li&gt;
  &lt;li&gt;that bot is selected as a parent&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In essence, every bot has a shot at crossing the finish line, but those with higher fitness can run longer distances, thus have a better chance of being selected to be a parent.&lt;/p&gt;

&lt;p&gt;Alternatively, we could just select the bots with the highest fitness each time, but this decreases the amount of variety in the system, as lower-fitness bots might still have interesting play strategies up their sleeves.&lt;/p&gt;

&lt;h2 id=&quot;evolution&quot;&gt;Evolution&lt;/h2&gt;

&lt;h3 id=&quot;crossover&quot;&gt;Crossover&lt;/h3&gt;

&lt;p&gt;This is the reproduction part of evolution. We start with a blank child network, then for every weight in the network, select either parent A or parent B’s weight as determined by a virtual coin flip.&lt;/p&gt;

&lt;h3 id=&quot;mutation&quot;&gt;Mutation&lt;/h3&gt;

&lt;p&gt;This mimics real-world genetic mutations, which typically introduce minor changes rather than entirely new traits.&lt;/p&gt;

&lt;p&gt;We define a very small mutation rate (0.01) which means that only 1% of the weights in the child’s network will be changed slightly by a small Gaussian noise.&lt;/p&gt;

&lt;h1 id=&quot;results&quot;&gt;Results&lt;/h1&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/gen/blog/2024-10-17-neuro-evolutionary-tetris/sim.gif&quot; alt=&quot;Zoomed into a subset of the bots&quot; /&gt;&lt;/p&gt;

&lt;p&gt;After 2,000 rounds of Tetris, the bots are clearly on an upward trajectory and increasing their fitness.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/gen/blog/2024-10-17-neuro-evolutionary-tetris/plot.png&quot; alt=&quot;Plot showing increasing mean fitness&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The variance in the mean fitness plot can be explained by the weighted selection algorithm, in that we still sometimes pick less fit parents to reproduce, with the expectation that the mean fitness sometimes takes a hit. However, the moving average shows a clear upward trend in fitness.&lt;/p&gt;

&lt;h2 id=&quot;other-things-to-try&quot;&gt;Other things to try&lt;/h2&gt;

&lt;h3 id=&quot;try-alternate-fitness-allocation&quot;&gt;Try alternate fitness allocation&lt;/h3&gt;

&lt;p&gt;At the moment, fitness is&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;increased by 1 for every tick of staying alive&lt;/li&gt;
  &lt;li&gt;BUT increased by 100/300/500/800 (depending on number of lines cleared) for scoring in the game.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A bot might score by chance, but not necessarily have learnt a strategy for staying alive longer, which gives it an outsized benefit in the simulation.&lt;/p&gt;

&lt;h3 id=&quot;try-other-weighted-selection-algorithms&quot;&gt;Try other weighted selection algorithms&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;Roulette Wheel Selection (&lt;a href=&quot;https://arxiv.org/abs/1109.3627&quot;&gt;Lipowski, 2011&lt;/a&gt;): Each individual is assigned a slice of a roulette wheel proportional to its fitness. A random “spin” of the wheel determines which individual is selected.&lt;/li&gt;
  &lt;li&gt;Rank-Based Selection (&lt;a href=&quot;https://www.researchgate.net/profile/Darrell-Whitley-2/publication/2527551_The_GENITOR_Algorithm_and_Selection_Pressure_Why_Rank-Based_Allocation_of_Reproductive_Trials_is_Best/links/5632149808ae3de9381e72c5/The-GENITOR-Algorithm-and-Selection-Pressure-Why-Rank-Based-Allocation-of-Reproductive-Trials-is-Best.pdf&quot;&gt;Whitley, D. 1989&lt;/a&gt;): Individuals are ranked based on their fitness, and selection probability is assigned based on rank rather than absolute fitness values.&lt;/li&gt;
  &lt;li&gt;Tournament Selection (&lt;a href=&quot;https://repository.ias.ac.in/82725/1/5-a.pdf&quot;&gt;Goldberg, D. E., &amp;amp; Deb, K., 1991&lt;/a&gt;): A subset of individuals is randomly chosen, and the individual with the highest fitness within that subset is selected.&lt;/li&gt;
  &lt;li&gt;Elitism (p101 of &lt;a href=&quot;https://deepblue.lib.umich.edu/bitstream/handle/2027.42/4507/bab6360.0001.001.pdf&quot;&gt;De Jong, K. A., 1975&lt;/a&gt;): A certain number of the best individuals from the previous generation are directly copied into the next generation.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h1&gt;

&lt;p&gt;At the time of writing this, I’m not yet seeing a bot which is clearly good at Tetris, so I’ll keep the simulation running, and revisit this post with an update.&lt;/p&gt;

&lt;p&gt;Meanwhile, please try the experiment yourself, make modifications, and let me know what you find!&lt;/p&gt;

&lt;h1 id=&quot;footnotes&quot;&gt;Footnotes&lt;/h1&gt;

&lt;p&gt;&lt;a id=&quot;footnote-a&quot;&gt;a&lt;/a&gt;. Genetic Algorithms (GAs) are a class of algorithms inspired by biological evolution. They operate on a population of candidate models (individuals), using selection (usually the fittest), crossover (recombination, or reproduction by two parent models), and mutation to iteratively improve their fitness for a given task. A battered copy of one of my uni textbooks from 1999 discusses genetic algorithms, and was one of the inspirations for this post! (That crack in the spine aligns with chapter 11, Neural Networks ;-)&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/gen/blog/2024-10-17-neuro-evolutionary-tetris/book.jpg&quot; alt=&quot;Nonlinear workbook&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;a id=&quot;footnote-b&quot;&gt;b&lt;/a&gt;. More elaborate neuro-evolutionary techniques also modify the network’s topology (see &lt;a href=&quot;https://en.wikipedia.org/wiki/Neuroevolution_of_augmenting_topologies&quot;&gt;NEAT&lt;/a&gt;, &lt;a href=&quot;https://medium.com/@eugenesh4work/hyperneat-approach-in-neuroevolution-d2ead10aad33&quot;&gt;HyperNEAT&lt;/a&gt;, ES-HyperNEAT) and even hyperparameters (&lt;a href=&quot;https://arxiv.org/abs/1703.00548&quot;&gt;CoDeepNEAT&lt;/a&gt;) but we’re keeping things simple for now.&lt;/p&gt;

&lt;p&gt;&lt;a id=&quot;footnote-c&quot;&gt;c&lt;/a&gt;. Standard Tetris has a 20x10 grid, but most of it is open space at the start of a game, so I halve it to get to learning quicker and make the underlying neural network smaller.&lt;/p&gt;

&lt;p&gt;&lt;a id=&quot;footnote-d&quot;&gt;d&lt;/a&gt;. I used CPU instead of GPU, because the networks were quite small and any gains in GPU acceleration would be impacted by copying data to and from the GPU. This opens the experiment up to others to tinker with. Note that evolutionary processes can sometimes be slow to converge to optimal solutions compared to gradient-based methods, so grab a cup of tea!&lt;/p&gt;
</description>
        <pubDate>Thu, 17 Oct 2024 11:22:42 +0000</pubDate>
        <link>https://pdfcrun.ch/blog/2024-10-17-neuro-evolutionary-tetris/</link>
        <guid isPermaLink="true">https://pdfcrun.ch/blog/2024-10-17-neuro-evolutionary-tetris/</guid>
        
        
        <category>Tech</category>
        
      </item>
    
      <item>
        <title>Local LLM to replace Github Copilot</title>
        <description>&lt;p&gt;We investigate using a local large language model (LLM) to replace Github Copilot. We’ll use the &lt;a href=&quot;https://huggingface.co/blog/starcoder2&quot;&gt;StarCoder2 model from HuggingFace&lt;/a&gt;, as it is quite new and shows great promise, and we’ll host it with &lt;a href=&quot;https://ollama.com/&quot;&gt;ollama&lt;/a&gt;, which just a few hours ago announced support for StarCoder2. Then we point VSCode to &lt;em&gt;ollama + StarCoder2&lt;/em&gt; using the &lt;a href=&quot;https://continue.dev/&quot;&gt;Continue VSCode extension&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;ollama&quot;&gt;Ollama&lt;/h2&gt;

&lt;p&gt;We start by downloading ollama.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;wget https://github.com/ollama/ollama/releases/download/v0.1.28/ollama-linux-amd64
&lt;span class=&quot;nb&quot;&gt;chmod&lt;/span&gt; +x ollama-linux-amd64
./ollama-linux-amd64
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;I tried LMStudio before, but they don’t support StarCoder2 yet, and it has a known issue with older versions of Ubuntu. Ollama does support StarCoder2 as of a few hours ago:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/gen/blog/2024-03-04-local-llm-to-replace-copilot/ollama-starcoder2-support.png&quot; alt=&quot;ollama supports StarCoder2&quot; /&gt;&lt;/p&gt;

&lt;!-- ## LMStudio

We start by downloading [LMStudio](https://lmstudio.ai/) (I&apos;m using the [Linux version](https://lmstudio.ai/beta-releases.html#linux-beta)) and then we can run the following commands to start the server:

```bash
wget https://releases.lmstudio.ai/linux/0.2.14/beta/LM_Studio-0.2.14-beta-1.AppImage
chmod +x LM_Studio-0.2.14-beta-1.AppImage
./LM_Studio-0.2.14-beta-1.AppImage
``` --&gt;

&lt;h2 id=&quot;starcoder2&quot;&gt;StarCoder2&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://huggingface.co/blog/starcoder2&quot;&gt;StarCoder2&lt;/a&gt; was released just a few days ago, so I’m pretty curious to try it out:&lt;/p&gt;

&lt;blockquote class=&quot;twitter-tweet&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;Introducing StarCoder 2 ⭐️ The most complete open Code-LLM 🤖 StarCoder 2 is the next iteration for StarCoder and comes in 3 sizes, trained 600+ programming languages on over 4 Trillion tokens on Stack v2. It outperforms StarCoder 1 by margin and has the best overall performance… &lt;a href=&quot;https://t.co/LVclRcq5ZM&quot;&gt;pic.twitter.com/LVclRcq5ZM&lt;/a&gt;&lt;/p&gt;&amp;mdash; Philipp Schmid (@_philschmid) &lt;a href=&quot;https://twitter.com/_philschmid/status/1762843489220296881?ref_src=twsrc%5Etfw&quot;&gt;February 28, 2024&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async=&quot;&quot; src=&quot;https://platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;

&lt;p&gt;From &lt;a href=&quot;https://arxiv.org/pdf/2402.19173.pdf&quot;&gt;the paper&lt;/a&gt;, it seems like StarCoder2 performs really well:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;We find that our small model, StarCoder2-3B, outperforms other Code LLMs of similar size on most benchmarks, and also outperforms StarCoderBase-15B. Our large model, StarCoder2- 15B, significantly outperforms other models of comparable size. In addition, it matches or outperforms CodeLlama-34B, a model more than twice its size. Although DeepSeekCoder- 33B is the best-performing model at code completion for high-resource languages, we find that StarCoder2-15B outperforms it on math and code reasoning benchmarks, as well as several low-resource languages.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;More specifically:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;StarCoder2-3B is the best in the 3B class of current 3B base models&lt;/li&gt;
  &lt;li&gt;StarCoder2-7B comes in second place only behind DeepSeekCoder-6.7B&lt;/li&gt;
  &lt;li&gt;StarCoder2-15B is the best in the 15B class of current 15B base models by a significant margin, and is even competitive with models that are more than twice its size, like CodeLlama-34B&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;However, some models perform better on some programming languages, so it might be worth consulting tables 10 to 12 in the paper to see which model is best for your use case. But for the team at PDFCrunch, Python is crucial, so we consult section 7.1.3 that discusses the models’ performance on Python data science tasks (using the &lt;a href=&quot;https://arxiv.org/abs/2211.11501&quot;&gt;DS-1000 benchmark&lt;/a&gt;):&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;StarCoder2-3B overall is the best-performing small model on DS-1000. Except for PyTorch and TensorFlow (where it is slightly worse than StableCode-3B), StarCoder2-3B achieves the best performance on all the other popular libraries.&lt;/li&gt;
  &lt;li&gt;StarCoder2-7B comes in second place out of the medium models, with a performance similar to DeepSeekCoder-6.7B.&lt;/li&gt;
  &lt;li&gt;StarCoder2-15B is the best-performing large model on DS-1000. It substantially outperforms both StarCoderBase-15B and CodeLlama-13B by large margins, and approaches the overall performance of CodeLlama-34B.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;which-size-model-to-use&quot;&gt;Which size model to use?&lt;/h3&gt;

&lt;p&gt;Which size model you select depends on your GPU’s VRAM. The token length of the code you want to prompt with and generate would normally also be a consideration, but all model sizes have a 16K context window, using a sliding window of 4K, with FlashAttention-2, and as the models &lt;a href=&quot;https://huggingface.co/blog/optimize-llm#2-flash-attention-a-leap-forward&quot;&gt;don’t use the default self-attention algorithm, large input contexts won’t exhaust your VRAM&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;From the paper, confirming the 16K context window:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;We start base model training with a 4k context window and subsequently fine-tune the model with a 16k context window&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;We further pre-trained each model for long-context on 200B tokens from the same pre-training corpus, using a 16,384 context length with a sliding window of 4,096, with FlashAttention-2&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Does the context include both the prompt tokens and prediction tokens? During the evaluation of RepoBench, for instance, they restricted the prompt context so that the prediction had a window of 128 tokens:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;We constrained the models to generate a maximum of 128 new tokens per prompt, and the first non-empty and non-comment line of the output was selected as the prediction.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;The maximum token count for prompts was set to 15,800 by truncating excess cross-file context&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I typically use OpenAI’s GPT-3.5 or GPT-4 for generating entire pages of code for &lt;a href=&quot;https://chat.openai.com/share/e93fbfe1-9069-49a6-8282-de7c9cad9093&quot;&gt;languages I don’t know&lt;/a&gt;, but for in-line use in VSCode, a couple hundred tokens is plenty.&lt;/p&gt;

&lt;!-- ## Using StarCoder2 in LMStudio

Find StarCoder2 in LMStudio:

![Find StarCoder2 in LMStudio](/assets/images/gen/blog/2024-03-04-local-llm-to-replace-copilot/starcoder2-in-lmstudio.png)

`dranger003` seems to offer a full-precision model for download, which we&apos;ll try first (at the bottom of this list):

![Download StarCoder2 from dranger003](/assets/images/gen/blog/2024-03-04-local-llm-to-replace-copilot/starcoder2-model-sizes-by-dranger003-v2.png)

(This is the point where I sadly have to wait for the download, as the electrician messed up the CAT6a connections to my garden office, so I&apos;m only getting 100Mbps of my 1200Mbps fibre broadband line 😂) --&gt;

&lt;h2 id=&quot;using-starcoder2-with-ollama-from-vscode&quot;&gt;Using StarCoder2 with ollama from VSCode&lt;/h2&gt;

&lt;h3 id=&quot;run-starcoder2&quot;&gt;Run StarCoder2&lt;/h3&gt;

&lt;p&gt;In a new terminal tab, run&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;./ollama-linux-amd64 run starcoder2:15b
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;(Depending on the amount of VRAM you have, you might need to run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;7b&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;3b&lt;/code&gt;, or pick a &lt;a href=&quot;https://ollama.com/library/starcoder2/tags&quot;&gt;quantised version of the model  &lt;/a&gt;.)&lt;/p&gt;

&lt;h3 id=&quot;install-the-continue-vscode-extension&quot;&gt;Install the Continue VSCode extension&lt;/h3&gt;

&lt;p&gt;While the model is downloading, install the &lt;a href=&quot;https://continue.dev/&quot;&gt;Continue VSCode extension&lt;/a&gt;. Once installed, click Continue’s gear icon, and in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;config.json&lt;/code&gt;, add the following snippet to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;models&lt;/code&gt; section:&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;title&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;StarCoder2 + Ollama&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;provider&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;ollama&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;model&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;starcoder2:15b&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;completionOptions&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;temperature&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;0.2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;topP&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;0.95&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;topK&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;40&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;presencePenalty&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;frequencyPenalty&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;stop&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;maxTokens&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;600&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;quick-test&quot;&gt;Quick test&lt;/h3&gt;

&lt;p&gt;As a quick test, I gave the chat interface the following prompt:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Write a quick little snippet in Python that opens a file in binary mode, and prints the hashed contents.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;em&gt;Continue&lt;/em&gt; by default points to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GPT-4 (Free Trial)&lt;/code&gt;, which responded with this bit of code:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;hashlib&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;hash_file&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;filename&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;h&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;hashlib&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;sha256&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;with&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;filename&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;rb&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;file&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;chunk&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;file&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;read&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;4096&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;chunk&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;break&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;h&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;update&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;chunk&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;nf&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sa&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;The SHA256 hash of file &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;filename&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; is: &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;h&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;hexdigest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;nf&quot;&gt;hash_file&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;your_file_path_here&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/gen/blog/2024-03-04-local-llm-to-replace-copilot/vscode-continue-with-gpt4.png&quot; alt=&quot;VSCode Continue with GPT-4&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Then I switched to the &lt;em&gt;StarCoder2 + Ollama&lt;/em&gt; model, and got this response:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;hashlib&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;with&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;file&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;rb&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;nf&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;hashlib&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;sha256&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;read&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;hexdigest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;But, in addition to the Python code, it also exhausted it’s output token budget with what looked like a Python tutorial (which is fine - I can ignore that).&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/gen/blog/2024-03-04-local-llm-to-replace-copilot/vscode-continue-with-starcoder2-via-ollama.png&quot; alt=&quot;VSCode Continue with StarCoder2 via ollama&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The functions output the same hash.&lt;/p&gt;

&lt;p&gt;It’s as if StarCoder2 got straight to the point, but GPT-4 went a step further and wrapped it in a function and read the file in chunks, which probably makes for more maintainable and correct code.&lt;/p&gt;

&lt;p&gt;However, not bad considering GPT-4 is SOTA, costs a lot more to run, and is (probably) a much larger (ensemble of) model(s). I’m excited to see what else StarCoder2 can do.&lt;/p&gt;

&lt;p&gt;What I’m also very interested in is using my own development data to fine-tune StarCoder2 (or any model I choose to use), and it seems as if &lt;a href=&quot;&apos;/home/opyate/Documents/code/pdfcrunch/pdfcrunch.github.io/404.jpg&apos;&quot;&gt;Continue has support for this&lt;/a&gt; (albeit possibly a paid feature?), but over here at PDFCrunch we’re quite confortable fine-tuning our own models anyway.&lt;/p&gt;

&lt;p&gt;Another great thing about &lt;em&gt;Continue&lt;/em&gt; is that it &lt;a href=&quot;https://continue.dev/docs/walkthroughs/codebase-embeddings&quot;&gt;indexes your entire codebase&lt;/a&gt;, so you can ask the model high-level questions about your codebase, like “Do I use X anywhere?” or “Is there any code written already that does X?”. Powerfull stuff, and I wonder if there are limitations - you might want to index your million-line monolith or mono-repo.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;I’ve just dropped $100 on a new year of Github Copilot earlier this week, but I’ll continue using &lt;em&gt;StarCoder2-15b + ollama + Continue&lt;/em&gt; for the foreseeable future, and see how it stacks up.&lt;/p&gt;

&lt;p&gt;This post was inspired by &lt;a href=&quot;https://www.youtube.com/watch?v=F1bXfnrzAxM&quot;&gt;this video&lt;/a&gt; which uses &lt;em&gt;Dolphin Mixtral + LMStudio + Continue&lt;/em&gt; instead:&lt;/p&gt;

&lt;iframe height=&quot;468&quot; width=&quot;100%&quot; src=&quot;https://www.youtube-nocookie.com/embed/F1bXfnrzAxM&quot; title=&quot;YouTube video player&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot; allowfullscreen=&quot;&quot;&gt;
&lt;/iframe&gt;

</description>
        <pubDate>Mon, 04 Mar 2024 09:15:02 +0000</pubDate>
        <link>https://pdfcrun.ch/blog/2024-03-04-local-llm-to-replace-copilot/</link>
        <guid isPermaLink="true">https://pdfcrun.ch/blog/2024-03-04-local-llm-to-replace-copilot/</guid>
        
        
        <category>Tech</category>
        
        <category>LLM</category>
        
      </item>
    
      <item>
        <title>OCR for PDF using Google Vision.</title>
        <description>&lt;p&gt;Here we show how to get Google Vision to run OCR on a PDF in Google Storage using NodeJS.&lt;/p&gt;

&lt;p&gt;Firstly, install the SDK.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;npm install --save @google-cloud/vision
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Here’s the code. It will grab the PDF (or TIFF - the other supported format) from the location &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gcsSourceUri&lt;/code&gt; and once completed, put the OCR JSON in the location &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gcsDestinationUri&lt;/code&gt;.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;const vision = require(&apos;@google-cloud/vision&apos;)

function ocr() {
  const gcsSourceUri = `gs://your-bucket-name/path/to/the.pdf`
  const gcsDestinationUri = `gs://your-bucket-name/path/to/ocr.json`

  const inputConfig = {
    // Supported mime_types are: &apos;application/pdf&apos; and &apos;image/tiff&apos;
    mimeType: &apos;application/pdf&apos;,
    gcsSource: {
      uri: gcsSourceUri
    }
  }
  const outputConfig = {
    gcsDestination: {
      uri: gcsDestinationUri
    }
  }
  const features = [{ type: &apos;DOCUMENT_TEXT_DETECTION&apos; }]
  const request = {
    requests: [
      {
        inputConfig: inputConfig,
        features: features,
        outputConfig: outputConfig
      }
    ]
  }

  return client.asyncBatchAnnotateFiles(request)
    .then(([operation]) =&amp;gt; operation.promise())
    .then(([filesResponse]) =&amp;gt; {
      return filesResponse
    })
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The response &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;filesResponse&lt;/code&gt; contains some more details about the completed operation, but you can now find your OCR result in Google Storage at the defined location.&lt;/p&gt;
</description>
        <pubDate>Thu, 13 Sep 2018 05:22:42 +0000</pubDate>
        <link>https://pdfcrun.ch/blog/2019-10-17-ocr-for-pdf-using-google-vision/</link>
        <guid isPermaLink="true">https://pdfcrun.ch/blog/2019-10-17-ocr-for-pdf-using-google-vision/</guid>
        
        
        <category>Tech</category>
        
      </item>
    
  </channel>
</rss>