<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Aditya Raut</title>
    <description>The latest articles on DEV Community by Aditya Raut (@rautaditya2606).</description>
    <link>https://dev.to/rautaditya2606</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2747140%2F696730cd-b32e-4fc6-ad1b-d6c8e7bf9df7.png</url>
      <title>DEV Community: Aditya Raut</title>
      <link>https://dev.to/rautaditya2606</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/rautaditya2606"/>
    <language>en</language>
    <item>
      <title>I Got 28 TPS Out of Free Kaggle GPUs. Here's What It Took.</title>
      <dc:creator>Aditya Raut</dc:creator>
      <pubDate>Sun, 23 Aug 2026 12:20:19 +0000</pubDate>
      <link>https://dev.to/rautaditya2606/i-got-28-tps-out-of-free-kaggle-gpus-heres-what-it-took-5dpl</link>
      <guid>https://dev.to/rautaditya2606/i-got-28-tps-out-of-free-kaggle-gpus-heres-what-it-took-5dpl</guid>
      <description>&lt;p&gt;I want to be upfront about something: this whole project runs on free Kaggle T4 notebooks, an AWS EC2 t3.micro relay that costs almost nothing, and public internet. No A100s. No private datacenter network. No budget.&lt;/p&gt;

&lt;p&gt;And yet, ShardFlow v2.1 hits 28.10 TPS peak on Qwen2.5-7B across two separate cloud regions over WAN.&lt;/p&gt;

&lt;p&gt;This is the story of how that happened, and specifically the one fix in v2.1 that I did not see coming.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem: Running a 7B Model When You Have No Money
&lt;/h2&gt;

&lt;p&gt;A 7B parameter model in FP16 needs roughly 15 GB of VRAM. A single Kaggle T4 has 16 GB. Technically it fits, barely, with nothing left over for a KV cache.&lt;/p&gt;

&lt;p&gt;The solution is tensor parallelism: split the model across two machines. Node 0 (Iowa) handles layers 0 to 14. Node 1 (Oregon) handles layers 14 to 28, plus the LM head and final verification. They talk to each other through a TCP relay running on an EC2 t3.micro in Ohio.&lt;/p&gt;

&lt;p&gt;The baseline throughput with this setup and no tricks: 4.92 TPS. Usable, but not fast.&lt;/p&gt;




&lt;h2&gt;
  
  
  Speculative Decoding: The Idea
&lt;/h2&gt;

&lt;p&gt;LLM inference is slow because it's sequential. You generate one token, wait, generate another, wait. Each round trip across WAN costs you ~86ms RTT. At 1 token per round trip, you're fighting the network the whole time.&lt;/p&gt;

&lt;p&gt;Speculative decoding flips this. Instead of sending one token at a time, you run a tiny draft model locally to guess the next K tokens ahead. Then you send all K guesses to the verifier in one shot. If the big model agrees with M of them, you've committed M tokens in a single round trip instead of one.&lt;/p&gt;

&lt;p&gt;ShardFlow uses Qwen2.5-0.5B as the draft model, running on cuda:1 of Node 0 while the 7B target slice runs on cuda:0. Zero VRAM contention. The drafter proposes 8 candidates, Node 1 verifies them all in parallel, and you get an average of 4.07 tokens per round trip instead of 1.&lt;/p&gt;

&lt;p&gt;With speculative decoding in eager mode: 14.3 TPS peak. 3x better.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Wall I Hit
&lt;/h2&gt;

&lt;p&gt;I thought 14.3 was the ceiling. The network was the obvious bottleneck: two Kaggle instances in different states, an EC2 relay in between, public internet routing. What else could you do?&lt;/p&gt;

&lt;p&gt;Then I looked more carefully at what the draft model was actually doing.&lt;/p&gt;

&lt;p&gt;Every round, generating 8 candidate tokens meant running 8 separate forward passes through the 0.5B model. Each forward pass launched roughly 1,500 CUDA kernels, one by one, from a Python loop.&lt;/p&gt;

&lt;p&gt;Here's the problem: each CUDA kernel executes in 2 to 5 microseconds on the GPU. But Python needs 8 to 10 microseconds just to issue the launch call. The GPU was sitting idle for more time than it was actually computing. Draft generation per round: 112ms. The GPU idle rate: 65%.&lt;/p&gt;

&lt;p&gt;Python was quietly murdering GPU utilization and I had no idea.&lt;/p&gt;




&lt;h2&gt;
  
  
  CUDA Graphs: What They Are and Why They Helped
&lt;/h2&gt;

&lt;p&gt;A CUDA Graph is a way to capture a sequence of GPU operations once and replay them as a single driver call.&lt;/p&gt;

&lt;p&gt;Normally, every time your model does a forward pass, Python issues hundreds or thousands of individual kernel launches. Each one is a separate call to the CUDA driver. That overhead adds up fast, especially when you're doing it in a loop.&lt;/p&gt;

&lt;p&gt;With CUDA Graphs, you capture the entire forward pass of the 0.5B draft model: all 24 transformer layers, the LM head, the argmax for the next token. You do this once. After that, replaying the whole thing costs one driver call. No Python in the hot path at all.&lt;/p&gt;

&lt;p&gt;Draft generation: 112ms to 25ms. 4.5x faster.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why It Kept Breaking (and How I Fixed It)
&lt;/h2&gt;

&lt;p&gt;Every time I tried CUDA Graphs, the model started looping: "the the the the the". Clearly something was wrong.&lt;/p&gt;

&lt;p&gt;CUDA Graphs capture exact GPU memory addresses at record time. If any tensor gets reallocated during replay, the graph reads from a stale address and you get garbage output.&lt;/p&gt;

&lt;p&gt;HuggingFace's default KV cache (DynamicCache) calls &lt;code&gt;torch.cat&lt;/code&gt; every single token step. That allocates a new buffer every time. The graph had captured the old address. Replay read from it. Output: garbage.&lt;/p&gt;

&lt;p&gt;Four changes fixed this:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. StaticCache instead of DynamicCache.&lt;/strong&gt; StaticCache pre-allocates fixed-size buffers for the KV cache. No reallocations during generation. The addresses the graph captured stay valid.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. In-place tensor mutation.&lt;/strong&gt; Instead of creating new tensors for intermediate values, everything gets written in-place. Same memory, same address, graph stays happy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Explicit position_ids updates.&lt;/strong&gt; The graph needs to know which position each token is at. With dynamic allocation, this was implicit. With a static graph, you have to update position_ids manually before each replay.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. In-place KV rewind.&lt;/strong&gt; When the speculative verifier rejects some draft tokens, the KV cache needs to roll back to the last accepted position. This rewind has to happen in-place, not by creating a new cache object.&lt;/p&gt;

&lt;p&gt;Once all four were in place: no more loops. Clean output. 25ms draft generation.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Numbers
&lt;/h2&gt;

&lt;p&gt;On Qwen2.5-7B across 2 Kaggle T4s over WAN:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Version&lt;/th&gt;
&lt;th&gt;TPS&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;v1.0 (REST relay)&lt;/td&gt;
&lt;td&gt;2.27&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;v2.0 baseline&lt;/td&gt;
&lt;td&gt;4.92&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;v2.0 + neural drafter (eager)&lt;/td&gt;
&lt;td&gt;14.3 peak&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;v2.1 + CUDA graphs&lt;/td&gt;
&lt;td&gt;28.10 peak / 20.31 avg&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Also tested on Qwen2.5-14B with 4-bit NF4 quantization, same two T4s: 14.43 TPS average over WAN. A 14.7B parameter model. Free GPUs.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I Actually Learned
&lt;/h2&gt;

&lt;p&gt;The network was not the bottleneck. I spent a lot of time assuming the WAN latency was the hard ceiling, that there was nothing left to squeeze. The real bottleneck was Python kernel launch overhead, and it was invisible until I looked at GPU idle time.&lt;/p&gt;

&lt;p&gt;Profiling matters more than intuition. "The network is slow" is an easy assumption to make. "Python is launching 1,500 kernels from a loop and the GPU is idle 65% of the time" requires actually measuring.&lt;/p&gt;

&lt;p&gt;CUDA Graphs are not magic. They are very specific. Captured addresses must stay valid. Any dynamic allocation breaks them. The StaticCache + in-place mutation combination is what makes them work for autoregressive generation.&lt;/p&gt;




&lt;h2&gt;
  
  
  Try It Yourself
&lt;/h2&gt;

&lt;p&gt;ShardFlow is open source and designed to reproduce on free Kaggle notebooks. You need two Kaggle accounts and an EC2 t3.micro (or any machine with a public IP).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/rautaditya2606/Shardflow" rel="noopener noreferrer"&gt;github.com/rautaditya2606/Shardflow&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The README has step-by-step instructions for reproducing the exact benchmark. 583 people have already cloned it. I'd love to know if you get different numbers on different hardware.&lt;/p&gt;

&lt;p&gt;v3 is whenever someone sponsors me actual GPUs. Until then, free T4s and Ohio relays it is.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>gpu</category>
      <category>inference</category>
      <category>python</category>
    </item>
    <item>
      <title>Production RAG Optimization: Practical Techniques That Improved Performance</title>
      <dc:creator>Aditya Raut</dc:creator>
      <pubDate>Wed, 01 Jul 2026 15:46:12 +0000</pubDate>
      <link>https://dev.to/rautaditya2606/how-we-reduced-rag-latency-by-40-and-token-costs-by-60-in-production-1okj</link>
      <guid>https://dev.to/rautaditya2606/how-we-reduced-rag-latency-by-40-and-token-costs-by-60-in-production-1okj</guid>
      <description>&lt;p&gt;Building a Retrieval-Augmented Generation (RAG) system is relatively straightforward. Building one that performs well in production is a different challenge altogether.&lt;/p&gt;

&lt;p&gt;Once a RAG pipeline starts handling real users and large document collections, common bottlenecks begin to appear: increasing response latency, rising LLM costs, and slower document ingestion.&lt;/p&gt;

&lt;p&gt;It's tempting to solve these problems by switching to a larger model or waiting for cheaper APIs. In practice, many of the biggest improvements come from optimizing the architecture around the model rather than changing the model itself.&lt;/p&gt;

&lt;p&gt;This article covers several engineering techniques that proved effective while optimizing a production RAG system. None of them are particularly novel on their own, but together they produced meaningful improvements in latency, cost, and overall system efficiency.&lt;/p&gt;




&lt;h1&gt;
  
  
  1. Route Queries Before Retrieving
&lt;/h1&gt;

&lt;p&gt;One of the easiest ways to waste latency and tokens is to perform semantic retrieval for every user request.&lt;/p&gt;

&lt;p&gt;Many queries don't require vector search at all.&lt;/p&gt;

&lt;p&gt;Examples include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Greetings&lt;/li&gt;
&lt;li&gt;Questions about uploaded documents or metadata&lt;/li&gt;
&lt;li&gt;General conversational interactions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Rather than embedding every query and performing retrieval, a lightweight intent classification stage can determine whether semantic search is actually necessary.&lt;/p&gt;

&lt;p&gt;If retrieval isn't required, the request can be answered directly through conversational logic or structured backend data.&lt;/p&gt;

&lt;p&gt;Besides reducing unnecessary work, this also frees retrieval resources for queries where semantic search genuinely improves answer quality.&lt;/p&gt;




&lt;h1&gt;
  
  
  2. Rerank Before Building the Prompt
&lt;/h1&gt;

&lt;p&gt;Most retrieval systems are optimized for recall.&lt;/p&gt;

&lt;p&gt;Returning 10–20 potentially relevant chunks is often desirable during retrieval, but sending every retrieved chunk to the LLM usually isn't.&lt;/p&gt;

&lt;p&gt;Larger prompts increase:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Input token usage&lt;/li&gt;
&lt;li&gt;First-token latency&lt;/li&gt;
&lt;li&gt;Overall inference time&lt;/li&gt;
&lt;li&gt;The likelihood of distracting the model with irrelevant context&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A lightweight reranking stage can identify the small subset of retrieved passages most likely to answer the user's question.&lt;/p&gt;

&lt;p&gt;Only those passages are forwarded to prompt construction.&lt;/p&gt;

&lt;p&gt;This keeps prompts focused while significantly reducing unnecessary context.&lt;/p&gt;




&lt;h1&gt;
  
  
  3. Reduce Context Instead of Reducing Recall
&lt;/h1&gt;

&lt;p&gt;Even after reranking, retrieved chunks frequently contain far more text than necessary.&lt;/p&gt;

&lt;p&gt;A document chunk might contain several hundred tokens while only a few sentences are actually relevant.&lt;/p&gt;

&lt;p&gt;Instead of embedding smaller chunks—which can hurt retrieval quality—another approach is to trim context after retrieval.&lt;/p&gt;

&lt;p&gt;For factual questions, relevant sections can be extracted by locating query-related keywords and selecting a window surrounding those matches.&lt;/p&gt;

&lt;p&gt;For summarization tasks, preserving a larger leading section often produces better results than aggressive trimming.&lt;/p&gt;

&lt;p&gt;This approach maintains retrieval quality while substantially reducing prompt size.&lt;/p&gt;




&lt;h1&gt;
  
  
  4. Parallelize Document Processing
&lt;/h1&gt;

&lt;p&gt;Ingestion pipelines are frequently limited by I/O rather than CPU.&lt;/p&gt;

&lt;p&gt;Tasks such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;PDF extraction&lt;/li&gt;
&lt;li&gt;Office document parsing&lt;/li&gt;
&lt;li&gt;OCR&lt;/li&gt;
&lt;li&gt;Text cleaning&lt;/li&gt;
&lt;li&gt;Chunk preparation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;can usually be processed independently.&lt;/p&gt;

&lt;p&gt;Instead of processing uploaded files sequentially, these operations can be executed concurrently using a worker pool.&lt;/p&gt;

&lt;p&gt;Parallel preprocessing improves indexing throughput and reduces the time required before newly uploaded documents become searchable.&lt;/p&gt;




&lt;h1&gt;
  
  
  5. Perform OCR Before Retrieval
&lt;/h1&gt;

&lt;p&gt;Supporting scanned PDFs and image-heavy documents is a common production requirement.&lt;/p&gt;

&lt;p&gt;One option is to send these documents directly to multimodal vision models.&lt;/p&gt;

&lt;p&gt;While effective, this increases both inference cost and latency.&lt;/p&gt;

&lt;p&gt;An alternative approach is to perform OCR during preprocessing.&lt;/p&gt;

&lt;p&gt;Pages without selectable text are detected automatically and converted into plain text before entering the indexing pipeline.&lt;/p&gt;

&lt;p&gt;Performing OCR locally offers several practical advantages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reduces reliance on multimodal inference&lt;/li&gt;
&lt;li&gt;Produces standard text suitable for embedding models&lt;/li&gt;
&lt;li&gt;Lowers preprocessing costs&lt;/li&gt;
&lt;li&gt;Makes downstream retrieval pipelines simpler&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To improve retrieval quality, very small or low-confidence OCR outputs should also be discarded before indexing, preventing noisy embeddings from entering the vector store.&lt;/p&gt;




&lt;h1&gt;
  
  
  6. Measure Every Optimization
&lt;/h1&gt;

&lt;p&gt;Performance improvements should be measured rather than assumed.&lt;/p&gt;

&lt;p&gt;Useful metrics include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;End-to-end latency&lt;/li&gt;
&lt;li&gt;Retrieval latency&lt;/li&gt;
&lt;li&gt;Generation latency&lt;/li&gt;
&lt;li&gt;Prompt token usage&lt;/li&gt;
&lt;li&gt;Completion token usage&lt;/li&gt;
&lt;li&gt;Document ingestion time&lt;/li&gt;
&lt;li&gt;Indexing throughput&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Collecting these metrics throughout the pipeline makes it possible to compare changes before and after each optimization.&lt;/p&gt;

&lt;p&gt;Without instrumentation, it's difficult to know whether an optimization actually improved the system or simply shifted the bottleneck elsewhere.&lt;/p&gt;




&lt;h1&gt;
  
  
  Trade-offs
&lt;/h1&gt;

&lt;p&gt;Every optimization introduces trade-offs.&lt;/p&gt;

&lt;p&gt;Intent routing adds an additional classification step but avoids unnecessary retrieval.&lt;/p&gt;

&lt;p&gt;Reranking introduces another model call but often reduces the total generation cost by producing much smaller prompts.&lt;/p&gt;

&lt;p&gt;Local OCR shifts computation from cloud APIs to the application server, reducing inference costs while increasing CPU utilization.&lt;/p&gt;

&lt;p&gt;The right choice depends on workload characteristics, latency requirements, infrastructure, and operational costs.&lt;/p&gt;




&lt;h1&gt;
  
  
  Key Takeaways
&lt;/h1&gt;

&lt;p&gt;One lesson became increasingly clear throughout this work:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Many production RAG bottlenecks are architectural rather than model-related.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Several practical optimizations consistently provided meaningful improvements:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Route requests intelligently so retrieval only runs when it adds value.&lt;/li&gt;
&lt;li&gt;Rerank retrieved results before prompt construction.&lt;/li&gt;
&lt;li&gt;Trim context after retrieval instead of stuffing entire document chunks into prompts.&lt;/li&gt;
&lt;li&gt;Parallelize document preprocessing and indexing.&lt;/li&gt;
&lt;li&gt;Prefer local OCR when multimodal reasoning is unnecessary.&lt;/li&gt;
&lt;li&gt;Instrument the pipeline so improvements can be measured objectively.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Modern language models continue to improve rapidly, but production performance often depends just as much on retrieval, preprocessing, prompt construction, and observability as it does on the model itself.&lt;/p&gt;

&lt;p&gt;I'm currently expanding on these ideas through an open-source diagnostics toolkit for Haystack pipelines. If you're working on production RAG systems or interested in collaborating, I'd be happy to connect and exchange ideas.&lt;/p&gt;

&lt;p&gt;Linkedin: &lt;a href="https://www.linkedin.com/in/aditya-raut-3b4bba31b/" rel="noopener noreferrer"&gt;https://www.linkedin.com/in/aditya-raut-3b4bba31b/&lt;/a&gt;&lt;br&gt;
Haystack Diagnostics: &lt;a href="https://github.com/rautaditya2606/haystack-diagnostics" rel="noopener noreferrer"&gt;https://github.com/rautaditya2606/haystack-diagnostics&lt;/a&gt;&lt;/p&gt;

</description>
      <category>rag</category>
      <category>ai</category>
      <category>chatgpt</category>
      <category>claude</category>
    </item>
    <item>
      <title>I Got Tired of Debugging Haystack RAG Pipelines Blind, So I Built a Diagnostics Engine</title>
      <dc:creator>Aditya Raut</dc:creator>
      <pubDate>Sat, 27 Jun 2026 11:40:13 +0000</pubDate>
      <link>https://dev.to/rautaditya2606/i-got-tired-of-debugging-haystack-rag-pipelines-blind-so-i-built-a-diagnostics-engine-2g0o</link>
      <guid>https://dev.to/rautaditya2606/i-got-tired-of-debugging-haystack-rag-pipelines-blind-so-i-built-a-diagnostics-engine-2g0o</guid>
      <description>&lt;p&gt;RAG pipelines fail in quiet ways.&lt;/p&gt;

&lt;p&gt;Retrieval drops. Documents go missing. Metadata gets corrupted somewhere between ingestion and query time. Your generator starts hallucinating and you don't know if it's the retriever, the document store, or something upstream.&lt;/p&gt;

&lt;p&gt;The debugging loop is always the same: check traces, grep logs, write a one-off script to inspect the document store, try to diff two runs manually. It works, but it's slow and it doesn't scale.&lt;/p&gt;

&lt;p&gt;I hit this enough times while working on a Haystack 2.x pipeline at my internship that I started building something to systematize it.&lt;/p&gt;

&lt;p&gt;That became &lt;a href="https://github.com/rautaditya2606/haystack-diagnostics" rel="noopener noreferrer"&gt;Haystack Diagnostics Engine&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  What it actually does
&lt;/h2&gt;

&lt;p&gt;Four things:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Document store validation&lt;/strong&gt; — checks your vector store for duplicate chunks, missing metadata fields, and short/malformed documents before they silently degrade retrieval quality.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pipeline introspection&lt;/strong&gt; — inspects your Haystack pipeline structure, flags misconfigurations, and can visualize the component graph. Useful when you're inheriting a pipeline someone else built.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Retrieval failure classification&lt;/strong&gt; — when a query returns garbage, this tells you &lt;em&gt;why&lt;/em&gt;. Six failure classes: empty results, low-score results, metadata filter mismatch, reranker collapse, score inversion, and retriever timeout. Each has a different fix. Uses Haystack's &lt;code&gt;include_outputs_from&lt;/code&gt; for single-pass retriever/reranker diagnostics without re-running the pipeline.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Debug bundle capture and diffing&lt;/strong&gt; — this is the one I've gotten the most feedback on.&lt;/p&gt;




&lt;h2&gt;
  
  
  Debug bundles: the part that actually changed my workflow
&lt;/h2&gt;

&lt;p&gt;The typical production debugging scenario: something worked last week, it doesn't work now, and you have no idea what changed.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;collect_debug_bundle(pipeline, query, ...)&lt;/code&gt; captures the full state of a single query execution as a structured JSON file:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pipeline graph, Haystack version, component &lt;code&gt;init_parameters&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Raw retriever top-k (pre-reranker) — scores, metadata, content previews&lt;/li&gt;
&lt;li&gt;Reranked top-k when a reranker is detected&lt;/li&gt;
&lt;li&gt;Prompt snapshot and generated answer&lt;/li&gt;
&lt;li&gt;Failure classification result&lt;/li&gt;
&lt;li&gt;Corpus health checks scoped to only the retrieved document IDs, not the full store&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Bundle filenames are &lt;code&gt;{query_slug}_{timestamp}.json&lt;/code&gt; — human-readable, sort naturally across runs of the same query. The UUID lives inside the JSON, not in the filename.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;diff_debug_bundles(bundle_a, bundle_b)&lt;/code&gt; compares two persisted bundles and reports:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Score deltas per document&lt;/li&gt;
&lt;li&gt;Docs that appeared or disappeared between runs&lt;/li&gt;
&lt;li&gt;Component config changes between the two pipeline states&lt;/li&gt;
&lt;li&gt;Character-level answer diff via &lt;code&gt;difflib&lt;/code&gt; (no tokenizer dependency)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Config diffs filter out known volatile fields by default — things like &lt;code&gt;InMemoryDocumentStore.index&lt;/code&gt;, which regenerates as a random UUID on instantiation and would create false positives on every diff. You can pass &lt;code&gt;ignore_config_paths=set()&lt;/code&gt; to disable filtering or extend the defaults with component-specific paths like &lt;code&gt;"retriever.session_id"&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;There's also a CLI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python &lt;span class="nt"&gt;-m&lt;/span&gt; diagnostics.debug_bundler diff bundle_a.json bundle_b.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The workflow this enables: run a query, persist the bundle, deploy a change, run the same query again, diff the two bundles. You get an exact record of what shifted — scores, docs, config, answer — without relying on memory or logs.&lt;/p&gt;




&lt;h2&gt;
  
  
  What it found on a real deployment
&lt;/h2&gt;

&lt;p&gt;I ran the validator against a live Weaviate-backed RAG instance with 823 chunks and OpenAI embeddings.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;195 duplicate chunks (23.7% of the corpus)&lt;/li&gt;
&lt;li&gt;14 documents missing required metadata keys&lt;/li&gt;
&lt;li&gt;8 anomalous short chunks under the minimum threshold&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of these were obvious from the outside. The pipeline was running, queries were returning results, everything looked fine. The duplicates were inflating retrieval scores for certain topics. The missing metadata was breaking a filter that wasn't catching the error gracefully.&lt;/p&gt;

&lt;p&gt;The MCP server benchmarks at ~0.95s for 15 concurrent graph-inspection requests.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why MCP
&lt;/h2&gt;

&lt;p&gt;I wanted this to be composable, not just another CLI tool you run once and forget.&lt;/p&gt;

&lt;p&gt;Wrapping it as an MCP server means you can call &lt;code&gt;validate_document_store&lt;/code&gt;, &lt;code&gt;inspect_pipeline&lt;/code&gt;, &lt;code&gt;diagnose_retrieval_failure&lt;/code&gt;, or &lt;code&gt;collect_debug_bundle&lt;/code&gt; directly from Claude Desktop or any MCP-compatible client during a debugging session. The context stays in one place instead of jumping between terminals and notebooks.&lt;/p&gt;




&lt;h2&gt;
  
  
  Current state
&lt;/h2&gt;

&lt;p&gt;Weaviate support is solid. Qdrant and Pinecone support is in progress. The project has been cloned by 90+ developers since I published it, which was surprising for something this niche.&lt;/p&gt;

&lt;p&gt;If you're using a different document store and want to add a backend, contributions are open.&lt;/p&gt;

&lt;p&gt;GitHub: &lt;a href="https://github.com/rautaditya2606/haystack-diagnostics" rel="noopener noreferrer"&gt;https://github.com/rautaditya2606/haystack-diagnostics&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Feedback welcome, especially if you hit a retrieval failure mode the engine doesn't classify correctly yet.&lt;/p&gt;

</description>
      <category>rag</category>
      <category>python</category>
      <category>opensource</category>
      <category>genai</category>
    </item>
    <item>
      <title>Building a Voice-Controlled Local AI Agent on a 4GB GPU</title>
      <dc:creator>Aditya Raut</dc:creator>
      <pubDate>Sun, 12 Apr 2026 20:57:55 +0000</pubDate>
      <link>https://dev.to/rautaditya2606/building-a-voice-controlled-local-ai-agent-on-a-4gb-gpu-emc</link>
      <guid>https://dev.to/rautaditya2606/building-a-voice-controlled-local-ai-agent-on-a-4gb-gpu-emc</guid>
      <description>&lt;p&gt;&lt;strong&gt;What I Built&lt;/strong&gt;&lt;br&gt;
I built a voice-controlled local AI agent that transcribes &lt;br&gt;
audio, classifies intent, and executes local tools — all &lt;br&gt;
visible through a transparent pipeline trace in a Gradio UI.&lt;br&gt;
The agent supports four intents: create file, write code, &lt;br&gt;
summarize text, and general chat.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Architecture&lt;/strong&gt;&lt;br&gt;
STT layer: Groq Whisper-large-v3 handles transcription via API.&lt;br&gt;
I chose Groq over local Whisper because my RTX 3050 (4GB VRAM) &lt;br&gt;
cannot run STT and an LLM simultaneously without OOM errors. &lt;br&gt;
Groq's API is actually faster (~300ms) than local whisper-small &lt;br&gt;
would have been.&lt;/p&gt;

&lt;p&gt;Intent layer: Ollama serves qwen2.5-coder:1.5b locally. The LLM &lt;br&gt;
returns a structured JSON intent that the tool router uses to &lt;br&gt;
decide which action to take.&lt;/p&gt;

&lt;p&gt;Tool layer: Four tools — create_file, write_code, summarize, &lt;br&gt;
general_chat. All file writes are sandboxed to output/.&lt;/p&gt;

&lt;p&gt;UI layer: Gradio displays transcription, detected intent, action &lt;br&gt;
taken, and a full pipeline trace with per-stage latency.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hardware Constraints and Decisions&lt;/strong&gt; &lt;br&gt;
My machine: Intel i5-12500H, RTX 3050 (4GB VRAM), 15GB RAM.&lt;/p&gt;

&lt;p&gt;The core constraint: 4GB VRAM cannot hold both a Whisper model &lt;br&gt;
and an LLM simultaneously.&lt;/p&gt;

&lt;p&gt;Decision 1 — STT via Groq API&lt;br&gt;
Running whisper-small locally uses ~1.5GB VRAM. That leaves &lt;br&gt;
only 2.5GB for the LLM, which isn't enough for a useful model. &lt;br&gt;
Offloading STT to Groq frees the entire 4GB for the LLM and &lt;br&gt;
actually improves latency.&lt;/p&gt;

&lt;p&gt;Decision 2 — qwen2.5-coder:1.5b via Ollama&lt;br&gt;
A 1.5B model at Q4 quantization fits comfortably in ~1.5GB VRAM.&lt;br&gt;
I initially tried the 7b variant but it exceeded available VRAM &lt;br&gt;
and caused Ollama to offload to RAM, significantly slowing &lt;br&gt;
inference.&lt;/p&gt;

&lt;p&gt;Decision 3 — Sequential pipeline&lt;br&gt;
STT completes before Ollama is called. This keeps peak VRAM &lt;br&gt;
usage under 2GB at any given time.&lt;/p&gt;

&lt;p&gt;*&lt;em&gt;Challenges I Faced *&lt;/em&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;VRAM management&lt;br&gt;
Loading two models simultaneously caused OOM errors. Solved &lt;br&gt;
by switching STT to Groq and keeping only the LLM local.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Intent JSON parsing&lt;br&gt;
Ollama sometimes returns malformed JSON or wraps it in &lt;br&gt;
markdown code fences. Solved with a robust parser that &lt;br&gt;
strips fences and falls back to keyword matching if JSON &lt;br&gt;
parsing fails entirely.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Output sandboxing&lt;br&gt;
Naive file creation allowed path traversal (e.g. &lt;br&gt;
../../etc/passwd). Solved with path normalization and &lt;br&gt;
checking that the resolved path starts with the output/ &lt;br&gt;
directory.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Gradio mic input format&lt;br&gt;
Gradio returns audio as a tuple (sample_rate, numpy_array) &lt;br&gt;
not a file path. Had to write it to a temp file before &lt;br&gt;
passing to Groq API.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;What I'd Do Differently at Scale&lt;/strong&gt;&lt;br&gt;
For a production version of this system, I would:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Replace Ollama with Triton Inference Server for proper 
model serving with batching and metrics endpoints.&lt;/li&gt;
&lt;li&gt;Add a message queue (Redis or RabbitMQ) between the UI 
and pipeline so multiple users don't block each other.&lt;/li&gt;
&lt;li&gt;Replace the flat logger with structured JSON logs shipped 
to an observability stack (Grafana + Loki).&lt;/li&gt;
&lt;li&gt;Add model versioning — config.yaml currently hardcodes 
model names. A proper MLOps setup uses a model registry.&lt;/li&gt;
&lt;li&gt;Containerize STT locally using a sidecar so the pipeline 
has no external API dependency in production.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Model Benchmarking
&lt;/h2&gt;

&lt;p&gt;I added a benchmarking tab — set models, prompt, iterations,&lt;br&gt;
get a latency table back.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Avg Latency&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;qwen2.5-coder:1.5b&lt;/td&gt;
&lt;td&gt;~3.2s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;qwen2.5-coder:7b&lt;/td&gt;
&lt;td&gt;~11.4s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For structured JSON intent extraction, the 1.5b model is&lt;br&gt;
3-4x faster with no meaningful accuracy difference. For a&lt;br&gt;
constrained task like this, bigger isn't better.&lt;/p&gt;
&lt;h2&gt;
  
  
  Persistent Memory
&lt;/h2&gt;

&lt;p&gt;Every pipeline run is stored in SQLite — transcription,&lt;br&gt;
intent, action, output, and trace. Surfaces in the UI as&lt;br&gt;
a recent runs panel.&lt;/p&gt;

&lt;p&gt;This matters for two reasons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Debugging&lt;/strong&gt; — if intent classification goes wrong, you
can see exactly what transcription and JSON the LLM
returned&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auditability&lt;/strong&gt; — every file written has a corresponding
memory entry with the voice command that triggered it&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Simple schema, append-only, no ORM:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;CREATE&lt;/span&gt; &lt;span class="n"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="n"&gt;NOT&lt;/span&gt; &lt;span class="n"&gt;EXISTS&lt;/span&gt; &lt;span class="nf"&gt;runs &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nb"&gt;id&lt;/span&gt;        &lt;span class="n"&gt;INTEGER&lt;/span&gt; &lt;span class="n"&gt;PRIMARY&lt;/span&gt; &lt;span class="n"&gt;KEY&lt;/span&gt; &lt;span class="n"&gt;AUTOINCREMENT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;timestamp&lt;/span&gt; &lt;span class="n"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;transcript&lt;/span&gt; &lt;span class="n"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;intent&lt;/span&gt;    &lt;span class="n"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;action&lt;/span&gt;    &lt;span class="n"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;output&lt;/span&gt;    &lt;span class="n"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;trace&lt;/span&gt;     &lt;span class="n"&gt;TEXT&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Links&lt;br&gt;
GitHub: &lt;a href="https://github.com/rautaditya2606/Aditya_Raut_Mem0_AI" rel="noopener noreferrer"&gt;https://github.com/rautaditya2606/Aditya_Raut_Mem0_AI&lt;/a&gt;&lt;br&gt;
Demo: &lt;a href="https://youtu.be/rhGIQvi4Y74" rel="noopener noreferrer"&gt;https://youtu.be/rhGIQvi4Y74&lt;/a&gt;  &lt;/p&gt;

</description>
      <category>agents</category>
      <category>ai</category>
      <category>llm</category>
      <category>showdev</category>
    </item>
  </channel>
</rss>
