<?xml version="1.0" encoding="UTF-8" ?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Alt-text</title>
    <description>Dries Buytaert on Alt-text.</description>
    <link>https://dri.es/tag/alt-text</link>
    <atom:link href="https://dri.es/tag/alt-text/rss.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Comparing local LLMs for alt-text generation, round 2</title>
      <link>https://dri.es/comparing-local-llms-for-alt-text-generation-round-2</link>
      <guid>https://dri.es/comparing-local-llms-for-alt-text-generation-round-2</guid>
      <pubDate>Tue, 27 May 2025 14:04:35 -0400</pubDate>
      <description><![CDATA[<p>Four months ago, I <a href="https://dri.es/comparing-local-llms-for-alt-text-generation">tested 10 local vision LLMs</a> and compared them against the top cloud models. <em>Vision models</em> can analyze images and describe their content, making them useful for <code>alt</code>-text generation.</p>
<p>The result? The local models missed important details or introduced hallucinations. So <a href="https://dri.es/automating-alt-text-generation-ai">I switched to using cloud models</a>, which produced better results but meant sacrificing privacy and offline capability.</p>
<p>Two weeks ago, <a href="https://ollama.com/">Ollama</a> released <a href="https://github.com/ollama/ollama/releases/tag/v0.7.0">version 0.7.0</a> with improved support for vision models. They added support for three vision models I hadn't tested yet: <a href="https://huggingface.co/mistralai/Mistral-Small-3.1-24B-Instruct-2503">Mistral 3.1</a>, <a href="https://huggingface.co/Qwen/Qwen2.5-VL-32B-Instruct">Qwen 2.5 VL</a> and <a href="https://huggingface.co/google/gemma-3-27b-it">Gemma 3</a>.</p>
<p>I decided to evaluate these models to see whether they've caught up to GPT-4 and Claude 3.5 in quality. Can local models now generate accurate and reliable <code>alt</code>-text?</p>
<table>
  <thead>
  <tr>
  <th>Model</th>
  <th>Provider</th>
  <th>Release date</th>
  <th>Model size</th>
</tr>
</thead>
  <tbody>
  <tr>
  <td>
  <a href="https://huggingface.co/google/gemma-3-27b-it">Gemma 3 (27B)</a>
</td>
  <td>Google DeepMind</td>
  <td>March 2025</td>
  <td>27B</td>
</tr>
  <tr>
  <td>
  <a href="https://huggingface.co/Qwen/Qwen2.5-VL-32B-Instruct">Qwen 2.5 VL (32B)</a>
</td>
  <td>Alibaba</td>
  <td>March 2025</td>
  <td>32B</td>
</tr>
  <tr>
  <td>
  <a href="https://huggingface.co/mistralai/Mistral-Small-3.1-24B-Instruct-2503">Mistral 3.1 (24B)</a>
</td>
  <td>Mistral AI</td>
  <td>March 2025</td>
  <td>24B</td>
</tr>
</tbody>
</table>
<h2>Updating my <code>alt</code>-text script</h2>
<p>For my earlier experiments, I created <a href="https://github.com/dbuytaert/image-caption">an open-source script</a> that generates <code>alt</code>-text descriptions. The script is a Python wrapper around <a href="https://github.com/simonw/llm">Simon Willison's <code>llm</code> tool</a>, which provides a unified interface to LLMs. It supports models from Ollama, Hugging Face and various cloud providers.</p>
<p>To test the new models, I added 3 new entries to my script's <a href="https://github.com/dbuytaert/image-caption/blob/v2/models.yaml"><code>models.yaml</code></a>, which defines each model's prompt, temperature, and token settings. Once configured, generating <code>alt</code>-text is simple. Here is an example using the three new vision models:</p>
<pre><code class="language-shell">$ ./caption.py test-images/image-1.jpg –model mistral-3.1-24b gemma3-27b qwen2.5vl-32b
</code></pre>
<p>Which outputs something like:</p>
<pre><code class="language-shell">{
  &quot;image&quot;: &quot;test-images/image-1.jpg&quot;,
  &quot;captions&quot;: {
    &quot;mistral-3.1-24b&quot;: &quot;A bustling intersection at night filled with pedestrians crossing in all directions.&quot;
    &quot;gemma3-27b&quot;: &quot;A high-angle view shows a crowded Tokyo street filled with pedestrians and brightly lit advertising billboards at night.&quot;,
    &quot;qwen2.5vl-32b&quot;: &quot;A bustling city intersection at night, crowded with people crossing the street, surrounded by tall buildings with bright, colorful billboards and advertisements.&quot;,
  }
}
</code></pre>
<h2>Evaluating the models</h2>
<p>To keep the results consistent, I used the same test images and the same evaluation method as in <a href="https://dri.es/comparing-local-llms-for-alt-text-generation#model-evaluation">my earlier blog post</a>. The details results are in this <a href="https://docs.google.com/spreadsheets/d/1LttMys6cEkse6LrRZoznvDdvBiz_WkVyauNBFV2jIXg/edit">Google spreadsheet</a>.</p>
<p>Each <code>alt</code>-text was scored from 0 to 5 based on three criteria: how well it identified the most important elements in the image, how effectively it captured the mood or atmosphere, and whether it avoided repetition, grammar issues or hallucinated details. I then converted each score into a letter grade from A to F.</p>
<p>For comparison, the cloud models received the following scores: GPT-4o earned an average of 4.8 out of 5 (grade A), and Claude 3.5 Sonnet received a perfect 5 out of 5 (grade A).</p>
<h3>Test image 1: Shibuya Crossing, Tokyo</h3>
<div class="large">
  <figure><img src="https://dri.es/files/cache/japan-2024/shibuya-crossing-1280w.jpg" alt="Areal view of an intersection, capturing the evening commute with pedestrians, traffic and electronic billboards." width="1280" height="850" />
</figure>
</div>
<table>
  <thead>
  <tr>
  <th>Model</th>
  <th>Description</th>
  <th>Grade</th>
</tr>
</thead>
  <tbody>
  <tr>
  <td>Mistral 3.1 (24B)</td>
  <td>A bustling intersection at night filled with pedestrians crossing in all directions.</td>
  <td>C</td>
</tr>
  <tr>
  <td>Gemma 3 (27B)</td>
  <td>A high-angle view shows a crowded Tokyo <mark>street</mark> filled with pedestrians and brightly lit advertising billboards.</td>
  <td>B</td>
</tr>
  <tr>
  <td>Qwen 2.5 VL (32B)</td>
  <td>A bustling city intersection at night, crowded with people crossing the street, surrounded by brightly lit billboards and tall buildings.</td>
  <td>B</td>
</tr>
</tbody>
</table>
<p>None earned an A because they failed to identify this as Shibuya Crossing in Tokyo, while cloud models do. Mistral also missed the billboards.</p>
<h3>Test image 2: Isabella Stewart Gardner Museum, Boston</h3>
<div class="large">
  <figure><img src="https://dri.es/files/cache/miscellaneous-2023/isabella-stewart-gardner-museum-1280w.jpg" alt="An empty picture frame in a room with ornate wallpaper, candles, old paintings, and antique furniture." width="1280" height="850" />
</figure>
</div>
<table>
  <thead>
  <tr>
  <th>Model</th>
  <th>Description</th>
  <th>Grade</th>
</tr>
</thead>
  <tbody>
  <tr>
  <td>Mistral 3.1 (24B)</td>
  <td>An ornate wall features a large empty picture frame.</td>
  <td>B</td>
</tr>
  <tr>
  <td>Gemma 3 (27B)</td>
  <td>An empty, ornate gold frame hangs on a patterned green wall between two framed portraits and a candle sconce.</td>
  <td>A</td>
</tr>
  <tr>
  <td>Qwen 2.5 VL (32B)</td>
  <td>A vintage-style room features ornate wallpaper, a framed empty canvas, a lit candelabra, and a decorative vase on a table, with portraits on either side.</td>
  <td>A</td>
</tr>
</tbody>
</table>
<p>The vision models in my previous post often mistook the empty frame for a framed painting. All three models in this test correctly identified it as empty. Gemma and Qwen captured valuable details about the scene, while Mistral's description felt sparse.</p>
<h3>Test image 3: wakeboarding in Vermont, USA</h3>
<div class="large">
  <figure><img src="https://dri.es/files/cache/vermont-2024/wakeboarding-1280w.jpg" alt="Two men in swim shorts on the back of a boat watching another person wakeboarding behind the boat." width="1280" height="850" />
</figure>
</div>
<table>
  <thead>
  <tr>
  <th>Model</th>
  <th>Description</th>
  <th>Grade</th>
</tr>
</thead>
  <tbody>
  <tr>
  <td>Mistral 3.1 (24B)</td>
  <td>Two shirtless men on a boat watch another person <mark>water skiing</mark> on a lake.</td>
  <td>B</td>
</tr>
  <tr>
  <td>Gemma 3 (27B)</td>
  <td>Two people on a boat watch a <mark>waterskier</mark> speeding across the lake on a sunny day.</td>
  <td>B</td>
</tr>
  <tr>
  <td>Qwen 2.5 VL (32B)</td>
  <td>Two shirtless men on a boat watch a person <mark>water skiing</mark> in the distance on a calm lake.</td>
  <td>B</td>
</tr>
</tbody>
</table>
<p>All three described a wakeboarding scene as &quot;water skiing&quot;, while the cloud models correctly identified it as wakeboarding.</p>
<h3>Test image 4: hiking in the Dolomites, Italy</h3>
<div class="large">
  <figure><img src="https://dri.es/files/cache/italy-2022/santa-maddalena-church-in-funes-2-1280w.jpg" alt="Santa maddalena church in funes" width="1280" height="846" />
</figure>
</div>
<table>
  <thead>
  <tr>
  <th>Model</th>
  <th>Description</th>
  <th>Grade</th>
</tr>
</thead>
  <tbody>
  <tr>
  <td>Mistral 3.1 (24B)</td>
  <td>A wooden statue of a <mark>saint</mark> is mounted on a post with directional signs pointing to various locations.</td>
  <td>C</td>
</tr>
  <tr>
  <td>Gemma 3 (27B)</td>
  <td>A small wooden shrine with a statue of Mary stands beside a signpost indicating hiking trails in a grassy field.</td>
  <td>B</td>
</tr>
  <tr>
  <td>Qwen 2.5 VL (32B)</td>
  <td>A wooden shrine with a statue of <mark>a figure</mark> stands on a tree stump, surrounded by a scenic mountain landscape with directional signs in the foreground.</td>
  <td>B</td>
</tr>
</tbody>
</table>
<p>Only Gemma recognized the statue as Mary. Both Mistral and Gemma missed the mountains in the background, which seems important.</p>
<h3>Test image 5: backgammon by candlelight</h3>
<div class="large">
  <figure><img src="https://dri.es/files/cache/vermont-2023/backgammon-by-candlelight-1280w.jpg" alt="A backgammon board on a wooden table, accompanied by candles that cast a warm glow." width="1280" height="850" />
</figure>
</div>
<table>
  <thead>
  <tr>
  <th>Model</th>
  <th>Description</th>
  <th>Grade</th>
</tr>
</thead>
  <tbody>
  <tr>
  <td>Mistral 3.1 (24B)</td>
  <td>A lit candle and a glass of liquid are on a wooden table next to a wooden board game.</td>
  <td>B</td>
</tr>
  <tr>
  <td>Gemma 3 (27B)</td>
  <td>A lit candle and glass votive sit on a wooden table, creating a warm, inviting glow in a dimly lit space.</td>
  <td>B</td>
</tr>
  <tr>
  <td>Qwen 2.5 VL (32B)</td>
  <td>A cozy scene with a lit candle on a wooden table, next to a backgammon board and a glass of liquid, creating a warm and inviting atmosphere.</td>
  <td>A</td>
</tr>
</tbody>
</table>
<p>Neither Mistral nor Gemma recognized the backgammon board. Only Qwen identified it correctly. Mistral also failed to capture the photo's mood.</p>
<h3 id="model-accuracy">Model accuracy</h3>
<div class="large">
  <table>
  <tr>
  <th>Model</th>
  <th>Repetitions</th>
  <th>Hallucinations</th>
  <th>Moods</th>
  <th>Average score</th>
  <th>Grade</th>
</tr>
  <tr>
  <td>Mistral 3.1 (24B)</td>
  <td style="background-color: #ccffcc">Never</td>
  <td style="background-color: #ccffcc">Never</td>
  <td style="background-color: #ffeb99">Fair</td>
  <td>3.4/5</td>
  <td>C</td>
</tr>
  <tr>
  <td>Gemma 3 (27B)</td>
  <td style="background-color: #ccffcc">Never</td>
  <td style="background-color: #ccffcc">Never</td>
  <td style="background-color: #ccffcc">Good</td>
  <td>4.2/5</td>
  <td>B</td>
</tr>
  <tr>
  <td>Qwen 2.5 VL (32B)</td>
  <td style="background-color: #ccffcc">Never</td>
  <td style="background-color: #ccffcc">Never</td>
  <td style="background-color: #ccffcc">Good</td>
  <td>4.4/5</td>
  <td>B</td>
</tr>
</table>
</div>
<p>Qwen 2.5 VL performed best overall, with Gemma 3 not far behind.</p>
<p>Needless to say, these results are based on a small set of test images. And while I used a structured scoring system, the evaluation still involves subjective judgment. This is not a definitive ranking, but it's enough to draw some conclusions.</p>
<p>It was nice to say that all three LLMs avoided repetition and hallucinations, and generally captured the mood of the images.</p>
<p>Local models still make mistakes. All three described wakeboarding as &quot;water skiing&quot;, most failed to recognize the statue as Mary or place the intersection in Japan. Cloud models get these details right, as I showed in <a href="https://dri.es/comparing-local-llms-for-alt-text-generation">my previous blog post</a>.</p>
<h2>Conclusion</h2>
<p>I ran my original experiment four months ago, and at the time, none of the models I tested felt accurate enough for large-scale <code>alt</code>-text generation. Some, like Llama 3, showed promise but still fell short in overall quality.</p>
<p>Newer models like Qwen 2.5 VL and Gemma 3 have matched the performance I saw earlier with Llama 3. Both performed well in my latest test. They produced relevant, grounded descriptions without hallucinations or repetition, which earlier local models often struggled with.</p>
<p>Still, the quality is not yet at the level where I would trust these models to generate thousands of <code>alt</code>-texts without human review. They make more mistakes than GPT-4 or Claude 3.5.</p>
<p>My main question was: are local models now good enough for practical use? While Qwen 2.5 VL performed best overall, it still needs human review. I've started using it for small batches where manual checking is manageable. For large-scale, fully automated use, I continue using cloud models as they remain the most reliable option.</p>
<p>That said, local vision-language models continue to improve. My long-term goal is to return to a 100% local-first workflow that gives me more control and keeps my data private. While we're not there yet, these results show real progress.</p>
<p>My plan is to wait for the next generation of local vision models (or upgrade my hardware to run larger models). When those become available, I'll test them and report back.</p>
]]></description>
    </item>
    <item>
      <title>Trusting AI with my images wasn&#039;t easy</title>
      <link>https://dri.es/trusting-ai-with-my-images-was-not-easy</link>
      <guid>https://dri.es/trusting-ai-with-my-images-was-not-easy</guid>
      <pubDate>Mon, 24 Feb 2025 09:11:42 -0500</pubDate>
      <description><![CDATA[<p>I did it. I just finished generating <code>alt</code>-text for 9,000 images on my website.</p>
<p>What began as a simple task evolved into a four-part series where I <a href="https://dri.es/comparing-local-llms-for-alt-text-generation">compared different LLMs</a>, <a href="https://dri.es/i-want-to-run-ai-locally-here-is-why-i-am-not-yet">evaluated local versus cloud processing</a>, and <a href="https://dri.es/automating-alt-text-generation-ai">built an automated workflow</a>.</p>
<p>But this final step was different. It wasn't about technology. It was about trust and letting things go.</p>
<h2>My AI tool in action</h2>
<p>In my last blog post, I shared <a href="https://github.com/dbuytaert/image-caption">scripts</a> to <a href="https://dri.es/automating-alt-text-generation-ai">automate <code>alt</code>-text generation for a single image</a>. The final step? Running my scripts on the 9,000 images missing <code>alt</code>-text. This covers over 20 years of images in <a href="https://dri.es/albums">photo albums</a> and <a href="https://dri.es/blog">blog posts</a>.</p>
<p>Here is my tool in action:</p>
<figure><img src="https://dri.es/files/images/blog/ai-generating-alt-text.gif" alt="A terminal displays AI generating image descriptions, showing suggested title and alt-text for each photo that scrolls by." width="960" height="540" />
</figure>
<p>And yes, AI generated the <code>alt</code>-text for this GIF. AI describing AI, a recursion that should have ripped open the space-time continuum. Sadly, no portals appeared. At best, it might have triggered a stack overflow in a distant dimension. Meanwhile, I just did the evening dishes.</p>
<p>ChatGPT-4o processed all 9,000 images at half a cent each, for less than $50 in total. And despite hammering their service for a couple days, I never hit a rate limit or error. Very impressive.</p>
<h2>AI is better than me</h2>
<p>Trusting a script to label 9,000 images made me nervous. What if mistakes in auto-generated descriptions made my website less accessible? What if future AI models trained on any mistakes?</p>
<p>I started cautiously, stopping after <a href="https://dri.es/albums">each album</a> to check every <code>alt</code>-text. After reviewing 250 images, I noticed something: I wasn't fixing errors, I was just tweaking words.</p>
<p>Then came the real surprise. I tested my script on albums I had manually described five years ago. The result was humbling. AI wrote better <code>alt</code>-text: spotting details I missed, describing scenes more clearly, and capturing nuances I overlooked. Turns out, past me wasn't so great at writing <code>alt</code>-text.</p>
<p>Not just that. The LLM understood Japanese restaurant menus, decoded Hungarian text, interpreted German Drupal books, and read Dutch street signs. It recognized conference badges and correctly labeled events. It understood cultural contexts across countries. It picked up details about my photos that I had forgotten or didn't even know existed.</p>
<p>I was starting to understand this wasn't about AI's ability to describe images; it was about me accepting that AI often described them better than I could.</p>
<h2>Conclusion</h2>
<p>AI isn't perfect, but it can be very useful. People worry about hallucinations and inaccuracy, and I did too. But after generating <code>alt</code>-text for 9,000 images, I saw something different: real, practical value.</p>
<p>It didn't just make my site more accessible; it challenged me. It showed me that sometimes, the best way to improve is to step aside and let a tool do the job better.</p>
]]></description>
    </item>
    <item>
      <title>Automating alt-text generation with AI</title>
      <link>https://dri.es/automating-alt-text-generation-ai</link>
      <guid>https://dri.es/automating-alt-text-generation-ai</guid>
      <pubDate>Thu, 20 Feb 2025 06:22:29 -0500</pubDate>
      <description><![CDATA[<p>Billions of images on the web lack proper <code>alt</code>-text, making them inaccessible to millions of users who rely on screen readers.</p>
<p>My own website is no exception, so <a href="https://dri.es/comparing-local-llms-for-alt-text-generation">a few weeks ago</a>, I set out to add missing <code>alt</code>-text to about 9,000 images on this website.</p>
<p>What seemed like a simple fix became a multi-step challenge. I needed to <a href="https://dri.es/comparing-local-llms-for-alt-text-generation">evaluate different AI models</a> and <a href="https://dri.es/i-want-to-run-ai-locally-here-is-why-i-am-not-yet">decide between local and cloud processing</a>.</p>
<p>To make the web better, a lot of websites need to add <code>alt</code>-text to their images. So I decided to document my progress here on <a href="https://dri.es/">my blog</a> so others can learn from it – or offer suggestions. This third post dives into the technical details of how I built an automated pipeline to generate <code>alt</code>-text at scale.</p>
<h2>High-level architecture overview</h2>
<p>My automation process follows three steps for each image:</p>
<ol>
<li>Check if <code>alt</code>-text exists for a given image</li>
<li>Generate new <code>alt</code>-text using AI when missing</li>
<li>Update the database record for the image with the new <code>alt</code>-text</li>
</ol>
<p>The rest of this post goes into more detail on each of these steps. If you're interested in the implementation, you can find most of the <a href="https://github.com/dbuytaert/image-caption">source code on GitHub</a>.</p>
<h2>Retrieving image metadata</h2>
<p>To systematically process 9,000 images, I needed a structured way to identify which ones were missing <code>alt</code>-text.</p>
<p>Since my site runs on <a href="https://www.drupal.org/">Drupal</a>, I built two REST API endpoints to interact with the image metadata:</p>
<ul>
<li><code>GET /album/{album-name}/{image-name}/get</code> – Retrieves metadata for an image, including title, <code>alt</code>-text, and caption.</li>
<li><code>PATCH /album/{album-name}/{image-name}/patch</code> – Updates specific fields, such as adding or modifying <code>alt</code>-text.</li>
</ul>
<p>I've built similar APIs before, including one for my <a href="https://dri.es/building-my-own-temperature-and-humidity-monitor">basement's temperature and humidity monitor</a>. That post provides a more detailed breakdown of how I build endpoints like this.</p>
<p>This API uses separate URL paths (<code>/get</code> and <code>/patch</code>) for different operations, rather than using a single resource URL. I'd prefer to follow RESTful principles, but this approach avoids caching problems, including content negotiation issues in CDNs.</p>
<p>Anyway, with the new endpoints in place, fetching metadata for an image is simple:</p>
<pre><code class="language-shell">curl -H &quot;Authorization: test-token&quot; \
  &quot;https://dri.es/album/isle-of-skye-2024/journey-to-skye/get&quot;
</code></pre>
<p>Every request requires an authorization token. And no, <code>test-token</code> isn't the real one. Without it, anyone could edit my images. While crowdsourced <code>alt</code>-text might be an interesting experiment, it's not one I'm looking to run today.</p>
<p>This request returns a JSON object with image metadata:</p>
<pre><code class="language-shell">{
  &quot;title&quot;: &quot;Journey to Skye&quot;,
  &quot;alt&quot;: &quot;&quot;,
  &quot;caption&quot;: &quot;Each year, Klaas and I pick a new destination for our outdoor adventure. In 2024, we set off for the Isle of Skye in Scotland. This stop was near Glencoe, about halfway between Glasgow and Skye.&quot;
}

</code></pre>
<p>Because the <code>alt</code>-field is empty, the next step is to generate a description using AI.</p>
<h2>Generating and refining <code>alt</code>-text with AI</h2>
<div class="large">
  <figure><img src="https://dri.es/files/cache/isle-of-skye-2024/journey-to-skye-1280w.jpg" alt="A person stands by a small lake surrounded by grassy hills and mountains under a cloudy sky in the Scottish Highlands." width="1280" height="850" />
</figure>
</div>
<p>In <a href="https://dri.es/comparing-local-llms-for-alt-text-generation">my first post on AI-generated <code>alt</code>-text</a>, I wrote a Python script to compare 10 different local <a href="https://en.wikipedia.org/wiki/Large_language_model">Large Language Models</a> (LLMs). The script uses <a href="https://pytorch.org/">PyTorch</a>, a widely used machine learning framework for AI research and deep learning. This implementation was a great learning experience.</p>
<p>The original script takes an image as input and generates <code>alt</code>-text using multiple LLMs:</p>
<pre><code class="language-shell">./caption.py journey-to-skye.jpg
{
  &quot;image&quot;: &quot;journey-to-skye.jpg&quot;,
  &quot;captions&quot;: {
    &quot;vit-gpt2&quot;: &quot;A man standing on top of a lush green field next to a body of water with a bird perched on top of it.&quot;,
    &quot;git&quot;: &quot;A man stands in a field next to a body of water with mountains in the background and a mountain in the background.&quot;,
    &quot;blip&quot;: &quot;This is an image of a person standing in the middle of a field next to a body of water with a mountain in the background.&quot;,
    &quot;blip2-opt&quot;: &quot;A man standing in the middle of a field with mountains in the background.&quot;,
    &quot;blip2-flan&quot;: &quot;A man is standing in the middle of a field with a river and mountains behind him on a cloudy day.&quot;,
    &quot;minicpm-v&quot;: &quot;A person standing alone amidst nature, with mountains and cloudy skies as backdrop.&quot;,
    &quot;llava-13b&quot;: &quot;A person standing alone in a misty, overgrown field with heather and trees, possibly during autumn or early spring due to the presence of red berries on the trees and the foggy atmosphere.&quot;,
    &quot;llava-34b&quot;: &quot;A person standing alone on a grassy hillside with a body of water and mountains in the background, under a cloudy sky.&quot;,
    &quot;llama32-vision-11b&quot;: &quot;A person standing in a field with mountains and water in the background, surrounded by overgrown grass and trees.&quot;
  }
}
</code></pre>
<p>My original plan was to run everything locally for full control, no subscription costs, and optimal privacy. But after testing 10 local LLMs, I changed my mind.</p>
<p>I knew cloud-based models would be better, but wanted to see if local models were good enough for <code>alt</code>-texts. Turns out, they're not quite there. You can read the <a href="https://dri.es/comparing-local-llms-for-alt-text-generation">full comparison</a>, but I gave the best local models a B, while cloud models earned an A.</p>
<p>While local processing aligned with my principles, it compromised the primary goal: creating the best possible descriptions for screen reader users. So I abandoned my local-only approach and decided to use cloud-based LLMs.</p>
<p>To automate <code>alt</code>-text generation for 9,000 images, I needed programmatic access to cloud models rather than relying on their browser-based interfaces – though <a href="https://dri.es/i-gave-an-ai-agent-edit-access-to-my-website">browser-based AI can be tons of fun</a>.</p>
<p>Instead of expanding my script with cloud LLM support, I switched to <a href="https://simonwillison.net/">Simon Willison</a>'s <code>llm</code> tool: <a href="https://llm.datasette.io/">https://llm.datasette.io/</a>. <code>llm</code> is a command-line tool and Python library that supports both local and cloud-based models. It takes care of installation, dependencies, API key management, and uploading images. Basically, all the things I didn't want to spend time maintaining myself.</p>
<p>Despite enjoying my PyTorch explorations with vision-language models and multimodal encoders, I needed to focus on results. My weekly progress goal meant prioritizing working <code>alt</code>-text over building homegrown inference pipelines.</p>
<p>I also considered you, my readers. If this project inspires you to make your own website more accessible, you're better off with a script built on a well-maintained tool like <code>llm</code> rather than trying to adapt my custom implementation.</p>
<p>Scrapping my PyTorch implementation stung at first, but building on a more mature and active open-source project was far better for me and for you. So I rewrote my script, now in the <a href="https://github.com/dbuytaert/image-caption">v2 branch</a>, with the original PyTorch version preserved in v1.</p>
<p>The new version of my script keeps the same simple interface but now supports cloud models like ChatGPT and Claude:</p>
<pre><code class="language-shell">./caption.py journey-to-skye.jpg --model chatgpt-4o-latest claude-3-sonnet --context &quot;Location: Glencoe, Scotland&quot;
{
  &quot;image&quot;: &quot;journey-to-skye.jpg&quot;,
  &quot;captions&quot;: {
    &quot;chatgpt-4o-latest&quot;: &quot;A person in a red jacket stands near a small body of water, looking at distant mountains in Glencoe, Scotland.&quot;,
    &quot;claude-3-sonnet&quot;: &quot;A person stands by a small lake surrounded by grassy hills and mountains under a cloudy sky in the Scottish Highlands.&quot;
  }
}
</code></pre>
<p>The <code>--context</code> parameter improves <code>alt</code>-text quality by adding details the LLM can't determine from the image alone. This might include GPS coordinates, album titles, or even <a href="https://dri.es/van-life-on-the-isle-of-skye">a blog post about the trip</a>.</p>
<p>In this example, I added <code>&quot;Location: Glencoe, Scotland&quot;</code>. Notice how ChatGPT-4o mentions Glencoe directly while Claude-3 Sonnet references the Scottish Highlands. This contextual information makes descriptions more accurate and valuable for users. For maximum accuracy, use all available information!</p>
<h2>Updating image metadata</h2>
<p>With <code>alt</code>-text generated, the final step is updating each image. The <code>PATCH</code> endpoint accepts only the fields that need changing, preserving other metadata:</p>
<pre><code class="language-shell">curl -X PATCH \
  -H &quot;Authorization: test-token&quot; \
  &quot;https://dri.es/album/isle-of-skye-2024/journey-to-skye/patch&quot; \
  -d '{
    &quot;alt&quot;: &quot;A person stands by a small lake surrounded by grassy hills and mountains under a cloudy sky in the Scottish Highlands.&quot;,
  }'

</code></pre>
<p>That's it. This completes the automation loop for one image. It checks if <code>alt</code>-text is needed, creates a description using a cloud-based LLM, and updates the image if necessary. Now, I just need to do this about 9,000 times.</p>
<h2>Tracking AI-generated <code>alt</code>-text</h2>
<p>Before running the script on all 9,000 images, I added a label to the database that marks each <code>alt</code>-text as either human-written or AI-generated. This makes it easy to:</p>
<ul>
<li>Re-run AI-generated descriptions without overwriting human-written ones</li>
<li>Upgrade AI-generated <code>alt</code>-text as better models become available</li>
</ul>
<p>With this approach I can update the AI-generated <code>alt</code>-text when a future ChatGPT model is released. And eventually, it might allow me to return to my original principles: to use a high-quality local LLM trained on public domain data. In the meantime, it helps me make the web more accessible today while building toward a better long-term solution tomorrow.</p>
<h2>Next steps</h2>
<p>Now that the process is automated for a single image, the last step is to run the script on all 9,000. And honestly, it makes me nervous. The perfectionist in me wants to review every single AI-generated <code>alt</code>-text, but that is just not feasible. So I have to trust AI. I'll probably write one more post to share the results and what I learned from this final step.</p>
<p>Stay tuned.</p>
]]></description>
    </item>
    <item>
      <title>I want to run AI locally. Here is why I&#039;m not (yet).</title>
      <link>https://dri.es/i-want-to-run-ai-locally-here-is-why-i-am-not-yet</link>
      <guid>https://dri.es/i-want-to-run-ai-locally-here-is-why-i-am-not-yet</guid>
      <pubDate>Tue, 11 Feb 2025 07:47:55 -0500</pubDate>
      <description><![CDATA[<p>Last week, I wrote about my plan to <a href="https://dri.es/comparing-local-llms-for-alt-text-generation">use AI to generate 9,000 <code>alt</code>-texts</a> for images on my website. I tested 12 LLMs – 10 running locally and 2 cloud-based – to assess their accuracy in generating <code>alt</code>-text for images. I ended that post with two key questions:</p>
<ol>
<li>Should I use AI-generated <code>alt</code>-texts, even if it they are not perfect?</li>
<li>Should I generate these <code>alt</code>-texts with local LLMs or in the cloud?</li>
</ol>
<p>Since then, I've received dozens of emails and LinkedIn comments. The responses were all over the place. Some swore by local models because they align with open-source values. Others championed cloud-based LLMs for better accuracy. A couple of people even ran tests using different models to help me out.</p>
<p>I appreciate every response. It's a great reminder of why building in the open is so valuable: it brings in diverse perspectives.</p>
<p>But one comment stood out. A visually impaired reader put it simply: <q>Imperfect <code>alt</code>-text is better than no <code>alt</code>-text</q>.</p>
<p>That comment made the first decision easy: AI-generated <code>alt</code>-text, even if not perfect, is better than nothing.</p>
<p>The harder question was which AI models to use. As a long-term open-source evangelist, I <em>really</em> want to run my own LLMs. Local AI aligns with my values: no privacy concerns, no API quotas, more transparency, and more control. They also align with my wallet: no subscription fees. And, let's be honest: running your own LLMs earns you some bragging rights at family parties.</p>
<p>But here is the problem: local models aren't as good as cloud models.</p>
<p>Most laptops and consumer desktops have 16–32GB of RAM, which limits them to small, lower-accuracy models. Even maxing out an Apple Mac Studio with 192GB of RAM doesn't change that. Gaming GPUs? Also a dead end, at least for me. Even high-end cards with 24GB of VRAM struggle with the larger models unless you stack multiple cards together.</p>
<p>The gap between local and cloud hardware is big. It's like racing a bicycle against a jet engine.</p>
<p>I could wait. Apple will likely release a new <a href="https://www.apple.com/mac-studio/">Mac Studio</a> this year, and I'm hoping it supports more than 192GB of RAM. NVIDIA's <a href="https://www.nvidia.com/en-us/project-digits/">Digits project</a> could make consumer-grade LLM hardware even more viable.</p>
<p>Local models are also improving fast. Just in the past few weeks:</p>
<ul>
<li>Alibaba released <a href="https://huggingface.co/collections/Qwen/qwen25-vl-6795ffac22b334a837c0f9a5">Qwen 2.5 VL</a>, which performs well in benchmarks.</li>
<li>DeepSeek launched <a href="https://github.com/deepseek-ai/DeepSeek-VL2">DeepSeek-VL2</a>, a strong new open model.</li>
<li>Mark Zuckerberg shared that Meta's Llama 4 is in testing and might be released in the next few months.</li>
</ul>
<p>Consumer hardware and local models will continue to improve. But even when they do, cloud models will still be ahead. So, I am left with this choice:</p>
<ol>
<li>Prioritize accessibility: use the best AI models available today, even if they're cloud-based.</li>
<li>Stick to Open Source ideals: run everything locally, but accept worse accuracy.</li>
</ol>
<p>A reader, Kris, put it well: <q>Prioritize users while investing in your values</q>. That stuck with me.</p>
<p>I'd love to run everything locally, but making my content accessible and ensuring its accuracy matters more. So, for now, I'm moving forward with cloud-based models, even if it means compromising on my open-source ideals.</p>
<p>It's not the perfect answer, but it's the <em>practical</em> one. Prioritizing accessibility and end-user needs over my own principles feels like the right choice.</p>
<p>That doesn't mean I'm giving up on local LLMs. I'll keep testing models, tracking improvements, and looking for the right hardware upgrades. The moment local AI is good enough for generating <code>alt</code>-text, I'll switch. In my next post, I'll share my technical approach to making this work.</p>
]]></description>
    </item>
    <item>
      <title>Comparing local large language models for alt-text generation</title>
      <link>https://dri.es/comparing-local-llms-for-alt-text-generation</link>
      <guid>https://dri.es/comparing-local-llms-for-alt-text-generation</guid>
      <pubDate>Mon, 03 Feb 2025 11:45:10 -0500</pubDate>
      <description><![CDATA[<p>I have <a href="https://dri.es/photos">10,000 photos</a> on my website. About 9,000 have no <code>alt</code>-text. I'm not proud of that, and it has bothered me for a long time.</p>
<p>When I started my blog nearly 20 years ago, I didn't think much about <code>alt</code>-texts. Over time, I realized its importance for visually impaired users who rely on screen readers.</p>
<p>The past 5+ years, I diligently added <code>alt</code>-text to every new image I uploaded. But that only covers about 1,000 images, leaving most older photos without descriptions.</p>
<p>Writing 9,000 <code>alt</code>-texts manually would take ages. Of course, AI could do this much faster, but is it good enough?</p>
<p>To see what AI can do, I tested 12 <em>Large Language Models</em> (LLMs): 10 running locally and 2 in the cloud. My goal was to test their accuracy and determine whether they can generate accurate <code>alt</code>-text.</p>
<p>The TL;DR is that, not surprisingly, cloud models (GPT-4, Claude Sonnet 3.5) set the benchmark with A-grade performance, though not 100% perfect. I prefer local models for privacy, cost, and offline use. Among local options, the Llama variants and MiniCPM-V perform best. Both earned a B grade: they work reliably but sometimes miss important details.</p>
<p>I know I'm not the only one. Plenty of people – entire organizations even – have massive backlogs of images without <code>alt</code>-text. I'm determined to fix that for my blog and share what I learn along the way. This blog post is just step one – <a href="https://buttondown.com/dries-buytaert-blog">subscribe by email</a> or <a href="https://dri.es/rss.xml">RSS</a> to get future posts.</p>
<h2>Models evaluated</h2>
<p>I tested <code>alt</code>-text generation using 12 AI models: 9 on my MacBook Pro with 32GB RAM, 1 on a higher-RAM machine (thanks to Jeremy Andrews, a friend and long-time Drupal contributor), and 2 cloud-based services.</p>
<p>The table below lists the models I tested, with details like links to research papers, release dates, parameter sizes (in billions), memory requirements, some architectural details and more:</p>
<div class="large">
  <table>
  <thead>
  <tr>
   <th></th>
   <th>Model</th>
   <th>Launch date</th>
   <th>Type</th>
   <th>Vision encoder</th>
   <th>Language encoder</th>
   <th>Model size (billions of parameters)</th>
   <th>RAM</th>
   <th>Deployment</th>
</tr>
</thead>
  <tbody>
  <tr>
   <td>1</td>
   <td>
    <a href="https://huggingface.co/nlpconnect/vit-gpt2-image-captioning">VIT-GPT2</a>
 </td>
   <td>2021</td>
   <td>Image-to-text</td>
   <td>ViT (Vision Transformer)</td>
   <td>GPT-2</td>
   <td>0.4B</td>
   <td>~8GB</td>
   <td>Local, Dries</td>
</tr>
  <tr>
   <td>2</td>
   <td>
    <a href="https://huggingface.co/microsoft/git-base">Microsoft GIT</a>
 </td>
   <td>2022</td>
   <td>Image-to-text</td>
   <td>Swin Transformer</td>
   <td>Transformer Decoder</td>
   <td>1.2B</td>
   <td>~8GB</td>
   <td>Local, Dries</td>
</tr>
  <tr>
   <td>3</td>
   <td>
    <a href="https://huggingface.co/Salesforce/blip-image-captioning-large">BLIP Large</a>
 </td>
   <td>2022</td>
   <td>Image-to-text</td>
   <td>ViT</td>
   <td>BERT</td>
   <td>0.5B</td>
   <td>~8GB</td>
   <td>Local, Dries</td>
</tr>
  <tr>
   <td>4</td>
   <td>
    <a href="https://huggingface.co/Salesforce/blip2-opt-2.7b">BLIP-2 OPT</a>
 </td>
   <td>2023</td>
   <td>Image-to-text</td>
   <td>CLIP ViT</td>
   <td>OPT</td>
   <td>2.7B</td>
   <td>~8GB</td>
   <td>Local, Dries</td>
</tr>
  <tr>
   <td>5</td>
   <td>
    <a href="https://huggingface.co/Salesforce/blip2-flan-t5-xl">BLIP-2 FLAN-T5</a>
 </td>
   <td>2023</td>
   <td>Image-to-text</td>
   <td>CLIP ViT</td>
   <td>FLAN-T5 XL</td>
   <td>3B</td>
   <td>~8GB</td>
   <td>Local, Dries</td>
</tr>
  <tr>
   <td>6</td>
   <td>
    <a href="https://ollama.com/library/minicpm-v">MiniCPM-V</a>
 </td>
   <td>2024</td>
   <td>Multi-modal</td>
   <td>SigLip-400M</td>
   <td>Qwen2-7B</td>
   <td>8B</td>
   <td>~16GB</td>
   <td>Local, Dries</td>
</tr>
  <tr>
   <td>7</td>
   <td>
    <a href="https://ollama.com/library/llava">LLaVA 13B</a>
 </td>
   <td>2024</td>
   <td>Multi-modal</td>
   <td>CLIP ViT</td>
   <td>Vicuna 13B</td>
   <td>13B</td>
   <td>~16GB</td>
   <td>Local, Dries</td>
</tr>
  <tr>
   <td>8</td>
   <td>
    <a href="https://ollama.com/library/llava">LLaVA 34B</a>
 </td>
   <td>2024</td>
   <td>Multi-modal</td>
   <td>CLIP ViT</td>
   <td>Vicuna 34B</td>
   <td>34B</td>
   <td>~32GB</td>
   <td>Local, Dries</td>
</tr>
  <tr>
   <td>9</td>
   <td>
    <a href="https://ollama.com/library/llama3.2-vision">Llama 3.2 Vision 11B</a>
 </td>
   <td>2024</td>
   <td>Multi-modal</td>
   <td>Custom Vision Encoder</td>
   <td>Llama 3.2</td>
   <td>11B</td>
   <td>~20GB</td>
   <td>Local, Dries</td>
</tr>
  <tr>
   <td>10</td>
   <td>
    <a href="https://ollama.com/library/llama3.2-vision">Llama 3.2 Vision 90B</a>
 </td>
   <td>2024</td>
   <td>Multi-modal</td>
   <td>Custom Vision Encoder</td>
   <td>Llama 3.2</td>
   <td>90B</td>
   <td>~128GB</td>
   <td>Local, Jeremy</td>
</tr>
  <tr>
   <td>11</td>
   <td>
    <a href="https://chat.openai.com">OpenAI GPT-4o</a>
 </td>
   <td>2023</td>
   <td>Multi-modal</td>
   <td>Custom Vision Encoder</td>
   <td>GPT-4</td>
   <td>&gt;150B</td>
   <td>
 </td>
   <td>Cloud</td>
</tr>
  <tr>
   <td>12</td>
   <td>
    <a href="https://claude.ai">Anthropic Claude 3.5 Sonnet</a>
 </td>
   <td>2024</td>
   <td>Multi-modal</td>
   <td>Custom Vision Encoder</td>
   <td>Claude 3.5</td>
   <td>&gt;150B</td>
   <td>
 </td>
   <td>Cloud</td>
</tr>
</tbody>
</table>
</div>
<h2>How image-to-text models work (in less than 30 seconds)</h2>
<p>LLMs come in many forms, but for this project, I focused on <em>image-to-text</em> and <em>multi-modal</em> models. Both types of models can analyze images and generate text, either by describing images or answering questions about them.</p>
<p>Image-to-text models follow a two-step process: <strong>vision encoding</strong> and <strong>language decoding</strong>:</p>
<ol>
<li><strong>Vision encoding</strong>: First, the model breaks an image down into <em>patches</em>. You can think of these as &quot;puzzle pieces&quot;. The patches are converted into mathematical representations called <em>embeddings</em>, which summarize their visual details. Next, an <a href="https://en.wikipedia.org/wiki/Attention_(machine_learning)">attention mechanism</a> filters out the most important patches (e.g. the puzzle pieces with the cat's outline or fur texture) and eliminates less relevant details (e.g. puzzle pieces with plain blue skies).</li>
<li><strong>Language encoding</strong>: Once the model has summarized the most important visual features, it uses a <em>language model</em> to translate those features into words. This step is where the actual text (image captions or Q&amp;A answers) is generated.</li>
</ol>
<p>In short, the vision encoder <em>sees</em> the image, while the language encoder <em>describes</em> it.</p>
<p>If you look at the table above, you'll see that each row pairs a <em>vision encoder</em> (e.g., ViT, CLIP, Swin) with a <em>language encoder</em> (e.g., GPT-2, BERT, T5, Llama).</p>
<p>For a more in-depth explanation, I recommend <a href="https://sebastianraschka.com/">Sebastian Raschka</a>'s article <a href="https://sebastianraschka.com/blog/2024/understanding-multimodal-llms.html">Understanding Multi-modal LLMs</a>, which also covers how image encoders work. It's fantastic!</p>
<h2>Comparing different AI models</h2>
<p>I wrote a Python script that generates <code>alt</code>-texts for images using nine different local models. You can find it in my <a href="https://github.com/dbuytaert/image-caption">GitHub repository</a>. It takes care of installing models, running them, and generating <code>alt</code>-texts. It supports both <a href="https://huggingface.co/">Hugging Face</a> and <a href="https://ollama.ai/">Ollama</a> and is built to be easily extended as new models come out.</p>
<p>You can run the script as follows:</p>
<pre><code class="language-shell">$ ./caption.py ./test-images/image-1.jpg
</code></pre>
<p>The first time you run the script, it will download all models, which requires significant disk space and bandwidth – expect to download over 50GB of model data.</p>
<p>The script outputs a JSON response, making it easy to integrate or analyze programmatically. Here is an example output:</p>
<pre>
  <code class="language-json">{
  "image": "test-images/image-1.jpg",
  "<code>alt</code>-texts": {
  "vit-gpt2": "A city at night with skyscrapers and a traffic light on the side of the street in front of a tall building.",
  "git": "A busy city street is lit up at night, with the word qroi on the right side of the sign.",
  "blip": "This is an aerial view of a busy city street at night with lots of people walking and cars on the side of the road.",
  "blip2-opt": "An aerial view of a busy city street at night.",
  "blip2-flan": "An aerial view of a busy street in tokyo, japanese city at night with large billboards.",
  "minicpm-v": "A bustling cityscape at night with illuminated billboards and advertisements, including one for Michael Kors.",
  "llava-13b": "A bustling nighttime scene from Tokyo's famous Shibuya Crossing, characterized by its bright lights and dense crowds of people moving through the intersection.",
  "llava-34b": "A bustling city street at night, filled with illuminated buildings and numerous pedestrians.",
  "llama32-vision-11b": "A bustling city street at night, with towering skyscrapers and neon lights illuminating the scene."
  }
  }
</code>
</pre>
<h2>Test images</h2>
<p>With the script ready, I decided to test it on some of <a href="https://dri.es/photos">my 10,000 photos</a>. Not all of them at once. I picked five that I consider non-standard. Instead of simple portraits or landscapes, I picked photos with elements that might confuse or challenge the models.</p>
<p>One photo is from the <a href="https://en.wikipedia.org/wiki/Isabella_Stewart_Gardner_Museum_theft">Isabella Stewart Gardner Museum</a> in Boston and features an empty gold frame. The frame once held a masterpiece stolen in the infamous 1990 heist, one of the biggest art thefts in history. I wanted to see if the models would recognize it as empty or mistake it for a framed painting.</p>
<p>Another photo, taken last summer in Vermont, shows a wakeboarder. Though he is the main subject, he is relatively small in the frame. I was curious to see if the models could still recognize him as the focal point.</p>
<p>In another photo, a backgammon game is set in a dark but cozy atmosphere. I was curious to see if the models could recognize partially visible objects and capture the mood of the scene.</p>
<p>To ensure a fair test, I stripped all <a href="https://en.wikipedia.org/wiki/Exif">EXIF metadata</a> from the images. This includes any embedded captions, GPS coordinates, or other details that could inadvertently help the models.</p>
<p>Yes, I <em>know</em> that a test set of five images is small, but it's sufficient to identify the top models for further evaluation. With 12 models generating <code>alt</code>-texts for each photo, I had to <a href="https://docs.google.com/spreadsheets/d/1LttMys6cEkse6LrRZoznvDdvBiz_WkVyauNBFV2jIXg/edit">manually evaluate 60 <code>alt</code>-texts</a>, which was tedious to do fairly and accurately. For now, these five images are enough to filter out weaker models and pinpoint the best 2-3 LLMs for more detailed testing.</p>
<p>Next, I'll share the detailed results for each of the five photos. It's a lot to go through, so feel free to skip ahead to the <a href="#model-accuracy">summary</a>.</p>
<h3>Test image 1: Shibuya Crossing, Tokyo</h3>
<div class="large">
  <figure><img src="https://dri.es/files/cache/japan-2024/shibuya-crossing-1280w.jpg" alt="Areal view of an intersection, capturing the evening commute with pedestrians, traffic and electronic billboards." width="1280" height="850" />
</figure>
</div>
<p>Benchmark LLMs:</p>
<table>
  <thead>
  <tr>
  <th>Model</th>
  <th>Description</th>
  <th>Grade</th>
</tr>
</thead>
  <tbody>
  <tr>
  <td>GPT-4o</td>
  <td>A vibrant city intersection with crowds crossing, surrounded by illuminated billboards and towering buildings, likely Shibuya, Tokyo.</td>
  <td>A</td>
</tr>
  <tr>
  <td>Claude</td>
  <td>Hundreds of people cross Tokyo's famous Shibuya intersection as neon signs light up the surrounding skyscrapers.</td>
  <td>A</td>
</tr>
</tbody>
</table>
<p>Local LLMs:</p>
<table>
  <thead>
  <tr>
  <th>Model</th>
  <th>Description</th>
  <th>Grade</th>
</tr>
</thead>
  <tbody>
  <tr>
  <td>VIT-GPT2</td>
  <td>A city at night with skyscrapers and a <mark>traffic light</mark> on the side of the street in front of a tall <mark>building</mark>.</td>
  <td>F</td>
</tr>
  <tr>
  <td>GIT</td>
  <td>A busy city street is lit up at night, with the word <mark>qroi</mark> on the right side of the sign.</td>
  <td>E</td>
</tr>
  <tr>
  <td>BLIP</td>
  <td>This is an aerial view of a busy city street at night with lots of people walking and cars on the side of the road.</td>
  <td>D</td>
</tr>
  <tr>
  <td>BLIP-2 with OPT</td>
  <td>An aerial view of a busy city street at night.</td>
  <td>E</td>
</tr>
  <tr>
  <td>BLIP-2 with FLAN</td>
  <td>An aerial view of a busy street in <mark>Tokyo, Japanese city</mark> at night with large billboards.</td>
  <td>E</td>
</tr>
  <tr>
  <td>LLaVA (13B)</td>
  <td>A bustling nighttime scene of Tokyo's Shibuya Crossing, with neon lights and people walking across the intersection.</td>
  <td>A</td>
</tr>
  <tr>
  <td>LLaVA (34B)</td>
  <td>A bustling city street at night, with illuminated buildings and numerous pedestrians.</td>
  <td>C</td>
</tr>
  <tr>
  <td>MiniCPM-V</td>
  <td>A bustling cityscape at night with various advertisements, including one for Michael Kors and another for Yakult.</td>
  <td>C</td>
</tr>
  <tr>
  <td>Llama 3.2 Vision (11B)</td>
  <td>A bustling city street at night, with numerous billboards and advertisements lining the buildings.</td>
  <td>C</td>
</tr>
  <tr>
  <td>Llama 3.2 Vision (90B)</td>
  <td>A bustling city street with many people and buildings.</td>
  <td>D</td>
</tr>
</tbody>
</table>
<h3>Test image 2: Isabella Stewart Gardner Museum, Boston</h3>
<div class="large">
  <figure><img src="https://dri.es/files/cache/miscellaneous-2023/isabella-stewart-gardner-museum-1280w.jpg" alt="An empty picture frame in a room with ornate wallpaper, candles, old paintings, and antique furniture." width="1280" height="850" />
</figure>
</div>
<p>Benchmark LLMs:</p>
<table>
  <thead>
  <tr>
  <th>Model</th>
  <th>Description</th>
  <th>Grade</th>
</tr>
</thead>
  <tbody>
  <tr>
  <td>GPT-4o</td>
  <td>
   <mark>Elegant</mark> wallpaper, an empty gold frame, candelabra, and framed painting in a classic room.</td>
   <td>B</td>
</tr>
  <tr>
   <td>Claude</td>
   <td>Ornate Victorian room with damask wallpaper, candelabra, and gold frames on wall.</td>
   <td>B</td>
</tr>
</tbody>
</table>
<p>Local LLMs:</p>
<table>
  <thead>
  <tr>
  <th>Model</th>
  <th>Description</th>
  <th>Grade</th>
</tr>
</thead>
  <tbody>
  <tr>
  <td>VIT-GPT2</td>
  <td>A living room with a <mark>mirror</mark>, candles, and a vase of <mark>flowers</mark> on a table in front of a <mark>mirror</mark>.</td>
  <td>F</td>
</tr>
  <tr>
  <td>GIT</td>
  <td>A picture frame is hanging on a wall next to a vase <mark>and a vase</mark> with <mark>the word tulips on it</mark>.</td>
  <td>E</td>
</tr>
  <tr>
  <td>BLIP</td>
  <td>A room with a painting on the wall and <mark>two vases</mark> on the table in front of it.</td>
  <td>E</td>
</tr>
  <tr>
  <td>BLIP-2 with OPT</td>
  <td>A room with a painting on the wall, a picture frame, and a chandelier.</td>
  <td>C</td>
</tr>
  <tr>
  <td>BLIP-2 with FLAN</td>
  <td>A room with a <mark>painting</mark> on the wall and a couple of framed pictures hanging on the wall next to it.</td>
  <td>D</td>
</tr>
  <tr>
  <td>LLaVA (13B)</td>
  <td>An <mark>empty</mark> wall with ornate decorations, including candlesticks and framed artwork, suggesting a formal or historical setting.
</td>
  <td>D</td>
</tr>
  <tr>
  <td>LLaVA (34B)</td>
  <td>An ornate room with framed pictures on the wall, a chandelier <mark>hanging from the ceiling</mark>, and a <mark>fireplace mantel</mark> adorned with decorative items.</td>
  <td>E</td>
</tr>
  <tr>
  <td>MiniCPM-V</td>
  <td>A room with ornate wallpaper, candlesticks and framed portraits of historical figures is displayed.</td>
  <td>
</td>
</tr>
  <tr>
  <td>Llama 3.2 Vision (11B)</td>
  <td>An empty gold frame on a wall with ornate wallpaper, surrounded by other decorative items.</td>
  <td>A</td>
</tr>
  <tr>
  <td>Llama 3.2 Vision (90B)</td>
  <td>An ornate room with a gold frame, a painting of a man, and a vase on a <mark>table</mark>.</td>
  <td>B</td>
</tr>
</tbody>
</table>
<h3>Test image 3: wakeboarding in Vermont, USA</h3>
<div class="large">
  <figure><img src="https://dri.es/files/cache/vermont-2024/wakeboarding-1280w.jpg" alt="Two men in swim shorts on the back of a boat watching another person wakeboarding behind the boat." width="1280" height="850" />
</figure>
</div>
<p>Benchmark LLMs:</p>
<table>
  <thead>
  <tr>
  <th>Model</th>
  <th>Description</th>
  <th>Grade</th>
</tr>
</thead>
  <tbody>
  <tr>
  <td>GPT-4o</td>
  <td>Two shirtless boys on a boat watch a person wakeboarding in the distance on a cloudy day.</td>
  <td>A</td>
</tr>
  <tr>
  <td>Claude</td>
  <td>People watching from a boat as a person is wakeboarding on a lake with a wooded shoreline.</td>
  <td>B</td>
</tr>
</tbody>
</table>
<p>Local LLMs:</p>
<table>
  <thead>
  <tr>
  <th>Model</th>
  <th>Description</th>
  <th>Grade</th>
</tr>
</thead>
  <tbody>
  <tr>
  <td>VIT-GPT2</td>
  <td>A man standing on top of a boat next to another man <mark>holding a surfboard in one hand</mark> and <mark>a surfboard in the other</mark>.</td>
  <td>E</td>
</tr>
  <tr>
  <td>GIT</td>
  <td>Two men are in a boat, one of them is wearing an orange hat <mark>and the other is wearing an orange hat</mark>.</td>
  <td>D</td>
</tr>
  <tr>
  <td>BLIP</td>
  <td>There are two men riding on the back of a boat in the water, <mark>one of them is on a surfboard and the other is on a board</mark>.</td>
  <td>D</td>
</tr>
  <tr>
  <td>BLIP-2 with OPT</td>
  <td>
   <mark>Three young men</mark> sitting on the back of a boat.</td>
   <td>D</td>
</tr>
  <tr>
   <td>BLIP-2 with FLAN</td>
   <td>
    <mark>A group of people</mark> sitting on a boat watching a man ride a <mark>surfboard</mark> in the middle of the water.</td>
    <td>C</td>
 </tr>
   <tr>
    <td>LLaVA (13B)</td>
    <td>Two shirtless men on a boat, watching another man <mark>surfing</mark> in the water.</td>
    <td>B</td>
 </tr>
   <tr>
    <td>LLaVA (34B)</td>
    <td>Two shirtless men on a boat, watching another person <mark>water skiing</mark>.</td>
    <td>B</td>
 </tr>
   <tr>
    <td>MiniCPM-V</td>
    <td>Two shirtless men on a boat watch as another person <mark>water skis away from them</mark>, with trees and sky visible across the lake behind him.</td>
    <td>B</td>
 </tr>
   <tr>
    <td>Llama 3.2 Vision (11B)</td>
    <td>Two shirtless men on a boat watching <mark>people</mark> wakeboarding or <mark>surfing</mark> behind it.</td>
    <td>B</td>
 </tr>
   <tr>
    <td>Llama 3.2 Vision (90B)</td>
    <td>Two men on a boat watching another man wakeboarding.</td>
    <td>A</td>
 </tr>
</tbody>
</table>
<h3>Test image 4: hiking in the Dolomites, Italy</h3>
<div class="large">
  <figure><img src="https://dri.es/files/cache/italy-2022/santa-maddalena-church-in-funes-2-1280w.jpg" alt="Santa maddalena church in funes" width="1280" height="846" />
</figure>
</div>
<p>Benchmark LLMs:</p>
<table>
  <thead>
  <tr>
  <th>Model</th>
  <th>Description</th>
  <th>Grade</th>
</tr>
</thead>
  <tbody>
  <tr>
  <td>GPT-4o</td>
  <td>A wooden shrine with a statue of Mary and directional signs, set against mountains and green hills.</td>
  <td>A</td>
</tr>
  <tr>
  <td>Claude</td>
  <td>Wooden shrine of St. Magdalena with trail signs in the Dolomites mountains of Italy.</td>
  <td>A</td>
</tr>
</tbody>
</table>
<p>Local LLMs:</p>
<table>
  <thead>
  <tr>
  <th>Model</th>
  <th>Description</th>
  <th>Grade</th>
</tr>
</thead>
  <tbody>
  <tr>
  <td>VIT-GPT2</td>
  <td>A <mark>bird</mark> perched on top of a wooden post in front of a mountain range with a view of <mark>a mountain range and mountains</mark>.</td>
  <td>F</td>
</tr>
  <tr>
  <td>GIT</td>
  <td>A <mark>wooden sign</mark> in front of a mountain with a statue on it that says <mark>"Meise museum"</mark> on the top.</td>
  <td>D</td>
</tr>
  <tr>
  <td>BLIP</td>
  <td>There is <mark>a birdhouse with a statue</mark> on top of it in the middle of a field with trees and mountains in the background.</td>
  <td>D</td>
</tr>
  <tr>
  <td>BLIP-2 with OPT</td>
  <td>A statue of Mary on a wooden post in the middle of a field with mountains in the background.</td>
  <td>B</td>
</tr>
  <tr>
  <td>BLIP-2 with FLAN</td>
  <td>A statue of the Virgin Mary sits on top of a wooden post <mark>in the middle of a mountain</mark>.</td>
  <td>C</td>
</tr>
  <tr>
  <td>LLaVA (13B)</td>
  <td>A picturesque scene of a small wooden chapel with a statue of <mark>Jesus</mark>, set against a backdrop of majestic mountains and lush greenery.</td>
  <td>C</td>
</tr>
  <tr>
  <td>LLaVA (34B)</td>
  <td>A wooden shrine with religious iconography, surrounded by alpine scenery and signposts.</td>
  <td>B</td>
</tr>
  <tr>
  <td>MiniCPM-V</td>
  <td>A wooden shrine with a statue of Mary and several directional signs pointing to various locations such as Kirchsteig, Magdalena, St.</td>
  <td>B</td>
</tr>
  <tr>
  <td>Llama 3.2 Vision (11B)</td>
  <td>A wooden shrine with a statue of Mary and a signpost in front of mountains.</td>
  <td>A</td>
</tr>
  <tr>
  <td>Llama 3.2 Vision (90B)</td>
  <td>A statue of Mary in a wooden shrine with a signpost pointing to various locations, including Rundweg St.</td>
  <td>B</td>
</tr>
</tbody>
</table>
<h3>Test image 5: backgammon by candlelight</h3>
<div class="large">
  <figure><img src="https://dri.es/files/cache/vermont-2023/backgammon-by-candlelight-1280w.jpg" alt="A backgammon board on a wooden table, accompanied by candles that cast a warm glow." width="1280" height="850" />
</figure>
</div>
<p>Benchmark LLMs:</p>
<table>
  <thead>
  <tr>
  <th>Model</th>
  <th>Description</th>
  <th>Grade</th>
</tr>
</thead>
  <tbody>
  <tr>
  <td>GPT-4o</td>
  <td>A cozy, dimly lit room with <mark>a candle</mark> on a wooden table, next to a backgammon board, creating a warm, rustic ambiance.
</td>
  <td>B</td>
</tr>
  <tr>
  <td>Claude</td>
  <td>Two candles light up a game board for backgammon on a wooden table at night.</td>
  <td>A</td>
</tr>
</tbody>
</table>
<p>Local LLMs:</p>
<table>
  <thead>
  <tr>
  <th>Model</th>
  <th>Description</th>
  <th>Grade</th>
</tr>
</thead>
  <tbody>
  <tr>
  <td>VIT-GPT2</td>
  <td>A candle is lit on a wooden table in front of a <mark>fire place</mark> with candles and other items on top of it.</td>
  <td>E</td>
</tr>
  <tr>
  <td>GIT</td>
  <td>Two candles are lit next to each other on a table, <mark>one of them is lit up and the other is lit up</mark>.
</td>
  <td>D</td>
</tr>
  <tr>
  <td>BLIP</td>
  <td>There is a lit <mark>candle</mark> sitting on top of a wooden table next to a game board and <mark>a glass of wine</mark> on the table.
</td>
  <td>D</td>
</tr>
<tr>
  <td>BLIP-2 with OPT</td>
  <td>A candle sits on top of a wooden table.</td>
  <td>C</td>
</tr>
<tr>
  <td>BLIP-2 with FLAN</td>
  <td>A candle sits on a wooden table next to a backgammon board and a <mark>glass of wine</mark>.</td>
  <td>D</td>
</tr>
<tr>
  <td>LLaVA (13B)</td>
  <td>A cozy indoor setting with candles, <mark>a guitar</mark>, and a warm ambiance.</td>
  <td>D</td>
</tr>
<tr>
  <td>LLaVA (34B)</td>
  <td>A candlelit wooden table with <mark>musical instruments</mark> and a cozy ambiance.</td>
  <td>C</td>
</tr>
<tr>
  <td>MiniCPM-V</td>
  <td>A dimly lit room with candles and backgammon pieces on a wooden table, creating an atmosphere of relaxation or leisure activity.</td>
  <td>A</td>
</tr>
<tr>
  <td>Llama 3.2 Vision (11B)</td>
  <td>A dimly lit room with a wooden table, featuring a backgammon board and two candles.
</td>
<td>A</td>
</tr>
<tr>
  <td>Llama 3.2 Vision (90B)</td>
  <td>A candle and backgammon board on a wooden table.
</td>
<td>B</td>
</tr>
</tbody>
</table>
<h3 id="model-accuracy">Model accuracy</h3>
<p>I evaluated each description using <a href="https://docs.google.com/spreadsheets/d/1LttMys6cEkse6LrRZoznvDdvBiz_WkVyauNBFV2jIXg/edit">a structured but subjective scoring system</a>. For each image, I identified the two or three most important objects the AI should recognize and include in its description. I also assessed whether the model captured the photo's mood, which can be important for visually impaired users. Finally, I deducted points for repetition, grammar errors, or hallucinations (invented details). Each <code>alt</code>-text received a score from 0 to 5, which I then converted to a letter grade from A to F.</p>
<div class="large">
  <table>
  <tr>
  <th>Model</th>
  <th>Repetitions</th>
  <th>Hallucinations</th>
  <th>Moods</th>
  <th>Average score</th>
  <th>Grade</th>
</tr>
  <tr>
  <td>VIT-GPT2</td>
  <td style="background-color: #ffcccc">Often</td>
  <td style="background-color: #ffcccc">Often</td>
  <td style="background-color: #ffcccc">Poor</td>
  <td>0.4/5</td>
  <td>F</td>
</tr>
  <tr>
  <td>GIT</td>
  <td style="background-color: #ffcccc">Often</td>
  <td style="background-color: #ffcccc">Often</td>
  <td style="background-color: #ffcccc">Poor</td>
  <td>1.6/5</td>
  <td>D</td>
</tr>
  <tr>
  <td>BLIP</td>
  <td style="background-color: #ffcccc">Often</td>
  <td style="background-color: #ffcccc">Often</td>
  <td style="background-color: #ffcccc">Poor</td>
  <td>1.8/5</td>
  <td>D</td>
</tr>
  <tr>
  <td>BLIP2 w/OPT</td>
  <td style="background-color: #ccffcc">Rarely</td>
  <td style="background-color: #ffeb99">Sometimes</td>
  <td style="background-color: #ffeb99">Fair</td>
  <td>2.6/5</td>
  <td>C</td>
</tr>
  <tr>
  <td>BLIP2 w/FLAN</td>
  <td style="background-color: #ccffcc">Rarely</td>
  <td style="background-color: #ffeb99">Sometimes</td>
  <td style="background-color: #ffeb99">Fair</td>
  <td>2.2/5</td>
  <td>D</td>
</tr>
  <tr>
  <td>LLaVA 13B</td>
  <td style="background-color: #ccffcc">Never</td>
  <td style="background-color: #ffeb99">Sometimes</td>
  <td style="background-color: #ccffcc">Good</td>
  <td>3.2/5</td>
  <td>C</td>
</tr>
  <tr>
  <td>LLaVA 34B</td>
  <td style="background-color: #ccffcc">Never</td>
  <td style="background-color: #ffeb99">Sometimes</td>
  <td style="background-color: #ccffcc">Good</td>
  <td>3.2/5</td>
  <td>C</td>
</tr>
  <tr>
  <td>MiniCPM-V</td>
  <td style="background-color: #ccffcc">Never</td>
  <td style="background-color: #ccffcc">Never</td>
  <td style="background-color: #ccffcc">Good</td>
  <td>3.8/5</td>
  <td>B</td>
</tr>
  <tr>
  <td>Llama 11B</td>
  <td style="background-color: #ccffcc">Never</td>
  <td style="background-color: #ccffcc">Rarely</td>
  <td style="background-color: #ccffcc">Good</td>
  <td>4.4/5</td>
  <td>B</td>
</tr>
  <tr>
  <td>Llama 90B</td>
  <td style="background-color: #ccffcc">Never</td>
  <td style="background-color: #ccffcc">Rarely</td>
  <td style="background-color: #ccffcc">Good</td>
  <td>3.8/5</td>
  <td>B</td>
</tr>
  <tr>
  <td>GPT-4o</td>
  <td style="background-color: #ccffcc">Never</td>
  <td style="background-color: #ccffcc">Never</td>
  <td style="background-color: #ccffcc">Good</td>
  <td>4.8/5</td>
  <td>A</td>
</tr>
  <tr>
  <td>Claude 3.5 Sonnet</td>
  <td style="background-color: #ccffcc">Never</td>
  <td style="background-color: #ccffcc">Never</td>
  <td style="background-color: #ccffcc">Good</td>
  <td>5/5</td>
  <td>A</td>
</tr>
</table>
</div>
<p>The cloud-based models, GPT-4o and Claude 3.5 Sonnet, performed nearly perfectly on my small test of five images, with no major errors, hallucinations, repetitions and excellent mood detection.</p>
<p>Among local models, both Llama variants and MiniCPM-V show the strongest performance.</p>
<p>Repetition in descriptions frustrates users of screen readers. Early models like VIT-GPT2, GIT, BLIP, and BLIP2 frequently repeat content, making them unsuitable.</p>
<p>Hallucinations can be a serious issue in my opinion. Describing nonexistent objects or actions misleads visually impaired users and erodes trust. Among the best-performing local models, MiniCPM-V did not hallucinate, while Llama 11B and Llama 90B each made one mistake. Llama 90B misidentified a cabinet at the museum as a table, and Llama 11B described multiple people wakeboarding instead of just one. While these errors aren't dramatic, they are still frustrating.</p>
<p>Capturing mood is essential for giving visually impaired users a richer understanding of images. While early models struggled in this area, all recent models all performed well. This includes both LLaVA variants and MiniCPM-V.</p>
<p>From a practical standpoint, Llama 11B and MiniCPM-V ran smoothly on my 32GB RAM laptop, but Llama 90B needed more memory. Long story short, this means that Llama 11B and MiniCPM-V are my best candidates for additional testing.</p>
<h2>Possible next steps</h2>
<p>The results raise a tough question: is a &quot;B&quot;-level <code>alt</code>-text better than none at all? Many human-written <code>alt</code>-texts probably aren't perfect either. Should I wait for local models to hit an &quot;A&quot;-grade, or is an imperfect description still better than no <code>alt</code>-text at all?</p>
<p>Here are four possible next steps:</p>
<ol>
<li><strong>Combine AI outputs</strong> – Run the same image through different models and merge their results to try and create more accurate descriptions.</li>
<li><strong>Wait and upgrade</strong> – Use the best local model for now, tag AI-generated <code>alt</code>-texts in the database, and refresh them in 6–12 months when new and better local models are available.</li>
<li><strong>Go cloud-based</strong> – Get the best quality with a cloud model, even if it means uploading 65GB of photos. I can't explain why, or if the feeling is even justified, but it feels like giving in.</li>
<li><strong>Hybrid approach</strong> – Use AI to generate <code>alt</code>-texts but review them manually. With 9,000 images, that is not practical. I'd need a way to flag <code>alt</code>-texts most likely to be wrong. Can LLMs give me a reliably confidence score?</li>
</ol>
<p>Each option comes with trade-offs. Some options are quick but imperfect, others take work but might be worth it. Going cloud-based is the easiest but it feels like giving in. Waiting for better models is effortless but means delaying progress. Merging AI outputs or assigning a confidence score takes more effort but might be the best balance of speed and accuracy.</p>
<p>Maybe the solution is a combination of these options? I could go cloud-based now, tag the AI-generated <code>alt</code>-texts in my database, and regenerate them in 6–12 months when LLMs got even better.</p>
<p>It also comes down to pragmatism versus principle. Should I stick to local models because I believe in data privacy and Open Source, or should I prioritize accessibility by providing the best possible <code>alt</code>-text for users? The local-first approach better aligns with my values, but it might come at the cost of a worse experience for visually impaired users.</p>
<p>I'll be weighing these options over the next few weeks. What would you do? I'd love to hear your thoughts!</p>
<p><strong>Update:</strong> My thoughts on using AI for <code>alt</code>-text has evolved across several blog posts. First, I <a href="https://dri.es/i-want-to-run-ai-locally-here-is-why-i-am-not-yet">chose a cloud-based LLM</a> after all. Then, I <a href="https://dri.es/automating-alt-text-generation-ai">built an automated system</a> to generate and update descriptions for just one image. Finally, I <a href="https://dri.es/trusting-ai-with-my-images-was-not-easy">scaled it to 9,000 images</a> and learned to trust AI in the process.</p>
]]></description>
    </item>
  </channel>
</rss>
