<?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: Pranav Raj</title>
    <description>The latest articles on DEV Community by Pranav Raj (@pranav_raj_dae81effb8b57d).</description>
    <link>https://dev.to/pranav_raj_dae81effb8b57d</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%2F3989114%2F81b0e663-512a-4ba4-96ab-889b3cdd8c78.png</url>
      <title>DEV Community: Pranav Raj</title>
      <link>https://dev.to/pranav_raj_dae81effb8b57d</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/pranav_raj_dae81effb8b57d"/>
    <language>en</language>
    <item>
      <title>Kimi K3 explained in plain terms, and why "open" is getting complicated</title>
      <dc:creator>Pranav Raj</dc:creator>
      <pubDate>Fri, 17 Jul 2026 08:12:25 +0000</pubDate>
      <link>https://dev.to/pranav_raj_dae81effb8b57d/kimi-k3-explained-in-plain-terms-and-why-open-is-getting-complicated-481g</link>
      <guid>https://dev.to/pranav_raj_dae81effb8b57d/kimi-k3-explained-in-plain-terms-and-why-open-is-getting-complicated-481g</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; Moonshot AI released Kimi K3 on July 16, a 2.8 trillion parameter open-weight model with a 1 million token context window. This post explains what those numbers actually mean, goes one level into the architecture (MoE, Kimi Delta Attention), and ends with my take: the era of dirt-cheap Chinese models may be ending, and "open" now means something you can inspect but probably never run.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;I have not used K3 myself yet. This is me reading the launch material and independent coverage carefully so you get the picture in ten minutes instead of an afternoon.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What just happened
&lt;/h2&gt;

&lt;p&gt;A Google News notification about "the largest open-source model ever" is what pulled me in. The claim sounded big enough to be worth checking properly.&lt;/p&gt;

&lt;p&gt;Moonshot AI, the Beijing startup behind the Kimi models, released Kimi K3. The headline claims from their &lt;a href="https://www.kimi.com/blog/kimi-k3" rel="noopener noreferrer"&gt;launch post&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;2.8 trillion total parameters, which they call the first open model in the 3T class&lt;/li&gt;
&lt;li&gt;A 1 million token context window&lt;/li&gt;
&lt;li&gt;Native multimodality: it processes images and video in the same model, not through a bolted-on vision encoder&lt;/li&gt;
&lt;li&gt;Benchmarks that, by Moonshot's own admission, still trail Claude Fable 5 and GPT 5.6 Sol, but beat everything else they tested, including Claude Opus 4.8 and GLM-5.2&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The weights are not out yet. They are promised by July 27. Until then, nobody outside Moonshot can independently verify or run the model. Keep that in mind for everything below: these are launch-day numbers from the vendor, partially confirmed by early independent testing from Artificial Analysis.&lt;/p&gt;

&lt;h2&gt;
  
  
  The numbers, translated
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;2.8 trillion parameters does not mean 2.8 trillion at once.&lt;/strong&gt; K3 is a Mixture of Experts (MoE) model. Think of it as 896 specialist sub-networks ("experts") sitting behind a router. For each token, the router picks 16 of those 896 to actually do the work. So the model has a huge library of knowledge on disk, but only a small slice of it fires per token. That is how you make a 2.8T model affordable to serve at all. This is the same broad trick behind most large open models today; K3 just pushes the sparsity further than usual.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A 1 million token context window&lt;/strong&gt; means roughly 700,000+ words of input. That is an entire large codebase, or several books, in one prompt. The catch with long context has never been "can you fit it" but "can the model actually use token 900,000 when answering," and that is exactly what K3's attention changes are aimed at (next section).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Open" here means open weights, later.&lt;/strong&gt; You will be able to download the model and run it yourself. But at this scale, "yourself" means a serious GPU cluster. Moonshot recommends deploying on nodes with 64 or more accelerators. So it is open in the sense that researchers and companies can inspect, fine-tune, and self-host it. It is not open in the sense that you or I will run it on anything we own.&lt;/p&gt;

&lt;h2&gt;
  
  
  One level deeper: what is actually new in the architecture
&lt;/h2&gt;

&lt;p&gt;Two named components carry the release, and both are about how information flows through the model.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Kimi Delta Attention (KDA).&lt;/strong&gt; Standard attention gets expensive as context grows, because every token can look at every other token. KDA is Moonshot's answer for scaling attention efficiently to very long sequences. They also mention it breaks conventional prefix caching, and that they contributed a fix to vLLM, which tells you they expect people to actually serve this thing, not just benchmark it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Attention Residuals (AttnRes).&lt;/strong&gt; In a deep network, information from early layers has to survive dozens or hundreds of layers to influence the output. AttnRes, per Moonshot, lets the model selectively retrieve representations from earlier depths instead of accumulating everything uniformly. Intuition: rather than a message being whispered down a long chain of people, later layers can go back and re-read earlier notes directly.&lt;/p&gt;

&lt;p&gt;On top of that, a stack of training tricks with names like Quantile Balancing (keeping the 896 experts evenly loaded without a fragile hyperparameter) and quantization-aware training in MXFP4/MXFP8 (so the released weights run efficiently on a wide range of hardware). Moonshot claims all of this together gives roughly 2.5x better scaling efficiency than their previous K2 generation, meaning more capability per unit of compute.&lt;/p&gt;

&lt;p&gt;The full technical report is not out yet, so the honest status of every claim above is "vendor-described, plausible, unverified." The July 27 weights release is when the community gets to check.&lt;/p&gt;

&lt;h2&gt;
  
  
  The parts of the launch post worth raising an eyebrow at
&lt;/h2&gt;

&lt;p&gt;The demos are genuinely impressive on paper: a Triton-like GPU compiler built from scratch, a chip designed in a 48-hour autonomous run, a research pipeline reproducing astrophysics results in two hours. But launch-day case studies are always the best runs, selected by the vendor. &lt;a href="https://the-decoder.com/kimis-open-model-k3-nears-gpt-5-6-sol-and-fable-5-while-signaling-the-end-of-super-cheap-chinese-ai/" rel="noopener noreferrer"&gt;The Decoder's coverage&lt;/a&gt; notes that independent testing broadly confirmed the benchmark story, but also that K3's hallucination rate went up compared to its predecessor. Moonshot's own limitations section admits the model can be "excessively proactive," making decisions on your behalf when instructions are ambiguous. For an agent meant to run unsupervised for hours, that cuts both ways.&lt;/p&gt;

&lt;h2&gt;
  
  
  My take: two quiet shifts hiding in this release
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;First, cheap Chinese models may be over.&lt;/strong&gt; K3 costs $3 per million input tokens and $15 per million output tokens. That is not DeepSeek-style pricing; that is Western mid-tier pricing. The old story was that Chinese labs compete on being 10x cheaper. K3's story is "we are nearly frontier, pay accordingly." If that holds, the pricing gap that made a lot of hobby projects viable is closing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Second, "open source AI" is splitting into two different things.&lt;/strong&gt; There is open-as-in-runnable, models like the smaller Llama and Qwen variants that a student with a decent GPU can actually use. And there is open-as-in-inspectable, 2.8T monsters that are technically downloadable but practically only serve as public research artifacts and enterprise self-hosting options. Both are valuable. But when headlines say "the largest open model ever," it is worth being clear that its openness will be experienced by almost everyone through an API, same as a closed model.&lt;/p&gt;

&lt;p&gt;Where does that leave me? Honestly, nowhere near K3. It is far too big to run or experiment with locally, and I do not currently have a project that needs an AI API. Which is kind of the point: a model this "open" changes nothing about my day to day, and I suspect that is true for most individual developers reading this.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I would watch next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;July 27: do the weights actually land, and what does the technical report reveal about KDA and AttnRes?&lt;/li&gt;
&lt;li&gt;Whether independent benchmarks hold up once anyone can run it.&lt;/li&gt;
&lt;li&gt;DeepSeek's response, which reporting suggests is close.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you have already tried K3 through the API or Kimi Code, I would genuinely like to hear how it compares to whatever you use now. That is the data point launch posts can't give us.&lt;/p&gt;

&lt;p&gt;Sources: &lt;a href="https://www.kimi.com/blog/kimi-k3" rel="noopener noreferrer"&gt;Moonshot's K3 launch post&lt;/a&gt;, &lt;a href="https://venturebeat.com/technology/chinas-moonshot-ai-releases-kimi-k3-the-largest-open-source-model-ever-rivaling-top-u-s-systems" rel="noopener noreferrer"&gt;VentureBeat&lt;/a&gt;, &lt;a href="https://the-decoder.com/kimis-open-model-k3-nears-gpt-5-6-sol-and-fable-5-while-signaling-the-end-of-super-cheap-chinese-ai/" rel="noopener noreferrer"&gt;The Decoder&lt;/a&gt;, &lt;a href="https://techcrunch.com/2026/07/16/moonshots-upcoming-kimi-3-is-expected-to-close-the-gap-with-anthropics-opus-4-8/" rel="noopener noreferrer"&gt;TechCrunch&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>machinelearning</category>
      <category>llm</category>
      <category>opensource</category>
    </item>
    <item>
      <title>I built vector search by hand out of curiosity, and my toy caught a mistake I did not know I had made</title>
      <dc:creator>Pranav Raj</dc:creator>
      <pubDate>Tue, 14 Jul 2026 14:20:24 +0000</pubDate>
      <link>https://dev.to/pranav_raj_dae81effb8b57d/i-built-vector-search-by-hand-out-of-curiosity-and-my-toy-caught-a-mistake-i-did-not-know-i-had-lk4</link>
      <guid>https://dev.to/pranav_raj_dae81effb8b57d/i-built-vector-search-by-hand-out-of-curiosity-and-my-toy-caught-a-mistake-i-did-not-know-i-had-lk4</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; I shipped a "second brain" app at a hackathon that searched notes by meaning, and I did not understand the search part. So I rebuilt it by hand in numpy, 20 fake products in 8 dimensions. The mechanism turned out to be about ten lines. The interesting part was that the toy quietly told me two of my four categories overlapped, which I had done to myself and never noticed. That is the thing real embeddings will never let you see.&lt;/p&gt;

&lt;h2&gt;
  
  
  The question that started it
&lt;/h2&gt;

&lt;p&gt;At a hackathon I built &lt;a href="https://github.com/CoffeeAurCode/second-brain-ai_MVP" rel="noopener noreferrer"&gt;second-brain&lt;/a&gt;, which stores notes as memories and searches them by meaning instead of exact words. It runs sentence-transformers (&lt;code&gt;all-MiniLM-L6-v2&lt;/code&gt;) to turn text into 384-dimensional vectors, and Qdrant to store and search them. It worked. You could type a rough idea and get back the note you meant, with none of the same words in it.&lt;/p&gt;

&lt;p&gt;But I had wired that up without knowing what it did. It was an &lt;code&gt;embed()&lt;/code&gt; call and a Qdrant query, and it felt like magic. Magic in my own project bothers me, so I wrote down the actual question:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What happens between &lt;code&gt;embed(query)&lt;/code&gt; and the results coming back?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Not "what is an embedding". I could recite that. I wanted the mechanism, the part that takes a pile of vectors and picks the close ones. So I built one. No model, no vector database, no framework. Just numpy, 20 made-up products across four categories (electronics, clothing, cooking, stationary), each clustered around a hand-picked center in 8-dimensional space with noise added so they were not identical.&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="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;numpy&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;

&lt;span class="c1"&gt;# each category is a cluster center in 8-dimensional space
&lt;/span&gt;&lt;span class="n"&gt;electronics_center&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;array&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mf"&gt;0.9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.08&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.79&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.2&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="n"&gt;clothing_center&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;array&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.09&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.09&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.89&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="n"&gt;cooking_center&lt;/span&gt;     &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;array&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mf"&gt;0.2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.95&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="n"&gt;stationary_center&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;array&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mf"&gt;0.8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.86&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.08&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="c1"&gt;# real embeddings vary, so simulate that with noise around each center
&lt;/span&gt;&lt;span class="n"&gt;noise&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;
&lt;span class="n"&gt;electronics&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;electronics_center&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;noise&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;randn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# ...same for the other three categories
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Using fake vectors was the whole point, not a shortcut. I did not want to learn how a transformer produces a vector. I wanted to isolate the half I did not understand, which is what you do with the vectors once you have them. Remember the four centers above. They come back later and they are the best part of this post.&lt;/p&gt;
&lt;h2&gt;
  
  
  Question 1: how do you actually compare two vectors?
&lt;/h2&gt;

&lt;p&gt;The answer is short enough to be annoying. Normalize every vector to unit length, then rank by dot product. For unit vectors, the dot product &lt;strong&gt;is&lt;/strong&gt; cosine similarity, because the denominator in the cosine formula is just the two magnitudes, and you already made both of them 1.&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;normalize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vectors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ndarray&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ndarray&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;norms&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;linalg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;norm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vectors&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;axis&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;keepdims&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;norms&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;norms&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1e-10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;norms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# avoid divide by zero
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;vectors&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;norms&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;VectorIndex&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;vectors&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;labels&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;vectors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ndarray&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;labels&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;vectors&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;normalize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vectors&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# normalize once, at index time
&lt;/span&gt;        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;labels&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;labels&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;query_vector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ndarray&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;top_k&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;normalize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query_vector&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reshape&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="n"&gt;scores&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;vectors&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;flatten&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  &lt;span class="c1"&gt;# cosine similarity
&lt;/span&gt;        &lt;span class="n"&gt;top&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;argsort&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scores&lt;/span&gt;&lt;span class="p"&gt;)[::&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;][:&lt;/span&gt;&lt;span class="n"&gt;top_k&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;labels&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nf"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scores&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;top&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;That is the entire engine. It ran, and every query came back with the right category on top:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Query: 'Cooking'
  1. [0.9948] Ceramic pan
  2. [0.9816] Iron cast pan
  3. [0.9749] wodden spoon

Query: 'Clothing'
  1. [0.9935] Silk saree
  2. [0.9808] Oversized black t-shirt
  3. [0.9775] Women's crop top
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Two things clicked here that no amount of reading had done. Normalizing is not a ritual, it is what buys you the cheap dot product later. And it belongs in &lt;code&gt;add()&lt;/code&gt;, not &lt;code&gt;search()&lt;/code&gt;, because you index once and you query forever.&lt;/p&gt;

&lt;p&gt;The other thing I finally understood is why it is cosine and not plain Euclidean distance. Cosine only looks at direction, so it ignores magnitude. For text that matters, because a long document and a short one about the same topic can differ a lot in magnitude while pointing the same way. Distance would call them far apart. Cosine calls them the same thing.&lt;/p&gt;

&lt;p&gt;Qdrant adds a great deal on top of this, and I will get to what, but the core of what my second-brain runs on is those ten lines.&lt;/p&gt;
&lt;h2&gt;
  
  
  Question 2: can I see the space?
&lt;/h2&gt;

&lt;p&gt;8 dimensions, so no. Not directly. I projected down to 2D with PCA and plotted the products as dots and the queries as stars. The four clusters separated cleanly and each query star landed inside its cluster. Very satisfying.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fa9y6xrhwy128jqmr7rbq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fa9y6xrhwy128jqmr7rbq.png" alt=" " width="800" height="582"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then I printed how much of the variance those two components actually captured:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PC1: 45.92%
PC2: 26.45%
Total (PC1 + PC2): 72.36%
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;So the picture I was happily trusting is showing me about 72 percent of the structure. More than a quarter of what distinguishes these vectors is not on that plot at all, and 8 dimensions is a toy. Real embeddings are 384 or 1536. Whatever a t-SNE or UMAP plot of those looks like, the honest reading is that it is a heavily lossy sketch, and two points sitting on top of each other in the picture may not be near each other in the space.&lt;/p&gt;

&lt;p&gt;That was the first thing I got out of the build that I did not expect: a healthy distrust of every embedding visualization I had ever nodded along to, including my own.&lt;/p&gt;
&lt;h2&gt;
  
  
  Question 3: is a high score proof the result is right?
&lt;/h2&gt;

&lt;p&gt;Top-3 always looked perfect, which is exactly the problem, because top-3 is all a search UI ever shows you. So I stopped looking at the top and scored the query against &lt;strong&gt;all twenty&lt;/strong&gt; products, then sorted.&lt;/p&gt;

&lt;p&gt;That is when the run got interesting. Here is the tail of each query, the highest-scoring &lt;strong&gt;wrong-category&lt;/strong&gt; item:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Query&lt;/th&gt;
&lt;th&gt;Worst in-category score&lt;/th&gt;
&lt;th&gt;Best wrong-category score&lt;/th&gt;
&lt;th&gt;Margin&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Clothing&lt;/td&gt;
&lt;td&gt;0.9572&lt;/td&gt;
&lt;td&gt;0.4457 (wodden spoon)&lt;/td&gt;
&lt;td&gt;0.51&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Electronics&lt;/td&gt;
&lt;td&gt;0.9441&lt;/td&gt;
&lt;td&gt;0.6542 (Parker pen)&lt;/td&gt;
&lt;td&gt;0.29&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cooking&lt;/td&gt;
&lt;td&gt;0.9649&lt;/td&gt;
&lt;td&gt;0.6689 (Glue stick)&lt;/td&gt;
&lt;td&gt;0.30&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stationary&lt;/td&gt;
&lt;td&gt;0.9704&lt;/td&gt;
&lt;td&gt;0.7110 (Nakiri knives)&lt;/td&gt;
&lt;td&gt;0.26&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A cutoff around 0.8 separates right from wrong for every query here, so the toy did not fail. But look at the margins. Clothing has twice the breathing room that stationary does. Same code, same noise, same everything. Why would one category be twice as easy as another?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F82yvph410x28kn8ctnct.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F82yvph410x28kn8ctnct.png" alt=" " width="800" height="558"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  The thing I did not go looking for
&lt;/h2&gt;

&lt;p&gt;The queries were generated near the cluster centers. So if some categories are getting confused with each other, maybe the centers themselves are the problem. I had never thought to check them, because I picked those numbers by hand, in about thirty seconds, to "look different". So I measured the cosine similarity between the four centers:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;electronics  vs clothing     0.3318
electronics  vs cooking      0.5287
electronics  vs stationary   0.6202
clothing     vs cooking      0.3872
clothing     vs stationary   0.2911
cooking      vs stationary   0.6475
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;There it is. Clothing sits between 0.29 and 0.39 from everything else, off in its own corner. Stationary is 0.62 from electronics and 0.65 from cooking, wedged right between them. Go back and look at the centers I hand-wrote: stationary is high on dimension 0 (0.8), and so is electronics (0.9). Stationary is also high on dimension 2 (0.86), and so is cooking (0.95). I built a category that shares a strong axis with two others without noticing, and then I built the two other categories it overlaps with.&lt;/p&gt;

&lt;p&gt;And the search told me. The two most-confused pairs in the search results, stationary and cooking bleeding into each other at 0.71 and 0.67, are exactly the two highest-similarity pairs in that table. The retrieval quality was a direct readout of a geometry mistake I had made and could not see.&lt;/p&gt;

&lt;p&gt;That is the payoff of using fake vectors, and it is the opposite of what I expected. I thought the toy would teach me the algorithm and the algorithm was the boring part. What the toy actually gave me is the only setting where I get to see the ground truth of the space &lt;strong&gt;and&lt;/strong&gt; the search behavior at the same time, and compare them. With &lt;code&gt;all-MiniLM-L6-v2&lt;/code&gt; I get 384 numbers I cannot interpret, and if two of my note categories overlap in that space, nothing tells me. The search just quietly gets worse and I blame the threshold.&lt;/p&gt;
&lt;h2&gt;
  
  
  What this changed about my actual app
&lt;/h2&gt;

&lt;p&gt;The retrieval in second-brain is not pure similarity, and building the naive version showed me why it cannot be:&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;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;vector&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;embed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;         &lt;span class="c1"&gt;# semantic similarity
&lt;/span&gt;    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;limit&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;score_threshold&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;         &lt;span class="c1"&gt;# drop "near but irrelevant" hits
&lt;/span&gt;    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;with_payload&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;filter&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;                     &lt;span class="c1"&gt;# hard constraints, not similarity
&lt;/span&gt;        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;must&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;memory_type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;match&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;value&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;knowledge&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}},&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;importance&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;range&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gte&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.3&lt;/span&gt;&lt;span class="p"&gt;}},&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;QDRANT_HOST&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/collections/knowledge_memory/points/search&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Cosine similarity does exactly one thing: find the nearest vector. Nearest is not "correct", and it is definitely not "allowed". So the similarity finds candidates and everything else enforces the rules. "Notes from last week tagged work" is a database query wearing a search box, not a similarity question, and no amount of embedding quality will turn it into one.&lt;/p&gt;

&lt;p&gt;The same logic explains the failure I have hit most in practice: exact matches get &lt;strong&gt;worse&lt;/strong&gt;, not better. Search a file name, an ID, or a tag, and semantic search will confidently hand you something close in meaning and bury the exact hit. Plain string matching wins there and it is not close.&lt;/p&gt;

&lt;p&gt;I also now think that &lt;code&gt;score_threshold: 0.5&lt;/code&gt; is a number I made up. It works, in the sense that the app stopped returning nonsense. I have no principled reason for it, and the margin table above is why that bothers me. The gap between right and wrong moved by 2x across four categories in a dataset I built myself. In a real embedding space I have no idea what it is.&lt;/p&gt;
&lt;h2&gt;
  
  
  The questions I left with
&lt;/h2&gt;

&lt;p&gt;The mechanism took an afternoon. These did not:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How do you pick a score threshold honestly?&lt;/strong&gt; Mine is a vibe. Doing it properly seems to need labelled query and result pairs and a look at where the score distributions actually cross, which means I need ground truth for my own notes, which I do not have. This is the one I want to solve next.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How does the geometry change at 384 dimensions?&lt;/strong&gt; Everything above is 8 dimensions, where I can reason about a single axis being shared. I have read that similarity scores bunch up as dimensions increase, so everything starts looking moderately similar to everything. I have not tested it, and my whole intuition here was built in a space small enough to be unrepresentative.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How does a real index avoid comparing everything?&lt;/strong&gt; My &lt;code&gt;search()&lt;/code&gt; is a dot product against every vector, O(n) per query, which is fine for 20 products and absurd for a million. Qdrant uses HNSW to approximate. So it can be wrong, on purpose, for speed. I would like to build that next and find out what "approximate" actually costs, because right now I take that trade on faith.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Filtering and approximate search must fight each other.&lt;/strong&gt; If HNSW is walking a graph to find near neighbors, and I also demand &lt;code&gt;importance &amp;gt;= 0.3&lt;/code&gt;, does it filter before it walks or after? Filter after and you can get back fewer results than you asked for. Filter before and it is not clear how the graph still works. I know Qdrant solves this. I do not know how.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How do you route a query to the right search?&lt;/strong&gt; The exact-match problem has a known answer, hybrid search: run keyword and vector search, merge the rankings. What I do not have is a clean way to decide which path a query deserves, or whether you just always run both and fuse. This is where I am stuck.&lt;/p&gt;

&lt;p&gt;If you have built hybrid retrieval in production, I would like to know how you make that routing decision, and whether you calibrated your score threshold or picked it the way I did. That is the honest state of it.&lt;/p&gt;

&lt;p&gt;Second-brain is here: &lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/CoffeeAurCode" rel="noopener noreferrer"&gt;
        CoffeeAurCode
      &lt;/a&gt; / &lt;a href="https://github.com/CoffeeAurCode/second-brain-ai_MVP" rel="noopener noreferrer"&gt;
        second-brain-ai_MVP
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;second-brain-ai (qdrant-based)&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;this project is a minimal Second brain AI system using qdrant as the primary vector memory store&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;How to Run&lt;/h2&gt;
&lt;/div&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Start Qdrant locally
docker run -p 6333:6333 qdrant/qdrant&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Install dependencies:
pip install -r requirements.txt&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Run the any of the demo file:
python Demo_1.py/Demo_2.py/Demo_3.py&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The demo will store knowledge, retrieve relevant memories,
apply memory evolution, and retrieve again.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Dependencies&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;This project uses Qdrant as the primary vector search engine.
Qdrant is run locally using Docker, which is the official and recommended
deployment method.&lt;/p&gt;
&lt;p&gt;Docker is required only to start the Qdrant service.
All application logic runs in Python.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Memory Architecture&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;The system uses Qdrant as the primary memory store.&lt;/p&gt;
&lt;p&gt;Three typed memory collections are defined:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;knowledge_memory: long-term semantic knowledge(hard disk)&lt;/li&gt;
&lt;li&gt;context_memory: short-lived working context(RAM)&lt;/li&gt;
&lt;li&gt;interaction_memory: conversation traces and user intent(EQ)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All future retrieval and memory evolution operates exclusively on these collections.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Embeddings&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;Text is converted into semantic vector representations…&lt;/p&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/CoffeeAurCode/second-brain-ai_MVP" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


</description>
      <category>ai</category>
      <category>embeddings</category>
      <category>python</category>
      <category>beginners</category>
    </item>
    <item>
      <title>The paper reported jumps like 21% to 97%. My replication got +2%.</title>
      <dc:creator>Pranav Raj</dc:creator>
      <pubDate>Tue, 07 Jul 2026 14:43:50 +0000</pubDate>
      <link>https://dev.to/pranav_raj_dae81effb8b57d/the-paper-reported-jumps-like-21-to-97-my-replication-got-2-44jj</link>
      <guid>https://dev.to/pranav_raj_dae81effb8b57d/the-paper-reported-jumps-like-21-to-97-my-replication-got-2-44jj</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; I ran a small replication of the paper "Prompt Repetition Improves Non-Reasoning LLMs": 100 MMLU questions, one non-reasoning model, prompt sent once versus twice. Baseline 59%, repetition 61%. Probably not statistically significant, and that turned out to be the interesting part. The gap between the paper's headline numbers and my +2% taught me more about transformer attention than the trick itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I tried this
&lt;/h2&gt;

&lt;p&gt;The claim sounds like a joke: paste your prompt twice and a non-reasoning LLM gets better. No chain-of-thought, no fine-tuning, no extra instructions. The paper, &lt;a href="https://arxiv.org/abs/2512.14982" rel="noopener noreferrer"&gt;Prompt Repetition Improves Non-Reasoning LLMs&lt;/a&gt; from Google Research, tested Gemini, GPT, Claude, and Deepseek models across seven benchmarks and found repetition won 47 of 70 model-benchmark combinations with zero losses, including improvements as large as 21% to 97% on long-context tasks like NameIndex, where the model has to pull one item out of a long list.&lt;/p&gt;

&lt;p&gt;A trick that cheap deserves a replication, and I wanted to know two things. Does it show up on ordinary short questions? And if it does, why does duplicating text change anything at all inside a transformer?&lt;/p&gt;

&lt;h2&gt;
  
  
  The setup
&lt;/h2&gt;

&lt;p&gt;I kept it deliberately small and fixed everything I could:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Dataset:&lt;/strong&gt; 100 multiple-choice questions sampled from MMLU science and math subjects (high school and college math, physics, chemistry, biology, abstract algebra, astronomy). Four options, one correct answer. The dataset was built once with a fixed seed and frozen to &lt;code&gt;questions.json&lt;/code&gt; so both conditions saw identical questions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Model:&lt;/strong&gt; &lt;code&gt;llama-3.1-8b-instant&lt;/code&gt; via the Groq API. A small, fast, genuinely non-reasoning model, which is the population the paper is about.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Conditions:&lt;/strong&gt; the baseline sends the question once with "Answer with one letter." The repetition condition sends the exact same block twice (n = 2), nothing else changed.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# baseline                      # repetition (n = 2)
&amp;lt;Question&amp;gt;                      &amp;lt;Question&amp;gt;
A. ...                          A. ...
B. ...                          B. ...
C. ...                          C. ...
D. ...                          D. ...
Answer with one letter          Answer with one letter
                                &amp;lt;Question&amp;gt;
                                A. ...
                                ...
                                Answer with one letter
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  Results
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;Correct&lt;/th&gt;
&lt;th&gt;Accuracy&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Baseline (n = 1)&lt;/td&gt;
&lt;td&gt;59 / 100&lt;/td&gt;
&lt;td&gt;59%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Repeated prompt (n = 2)&lt;/td&gt;
&lt;td&gt;61 / 100&lt;/td&gt;
&lt;td&gt;61%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two more questions correct. Before calling that a win, the honest check: for paired correct/incorrect outcomes on the same questions, the right test is McNemar's, which looks only at the questions where the two conditions disagree. With 100 questions and a swing this small, the improvement is likely not statistically significant. I am not claiming the effect; I am reporting what 100 questions showed.&lt;/p&gt;

&lt;p&gt;So the replication "worked" in the least satisfying way possible: a positive direction, too small to trust on its own. The useful part was figuring out why the effect should be small here when the paper's numbers are so large.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why my effect was small and the paper's was big
&lt;/h2&gt;

&lt;p&gt;The paper's dramatic gains come from long-context retrieval tasks. Give a model a list of 50 names and ask for the 25th, and attention has to hold position across hundreds of tokens. Attention weight spreads thin across a long sequence, the signal for any single token gets diluted, and the model misses. Repeating the prompt puts a second copy of every important token in the context, doubling the chances that the answer-relevant tokens get attended to. On tasks that stress attention, that rescue is worth a lot.&lt;/p&gt;

&lt;p&gt;My questions were short. A four-option MCQ barely stresses attention at all; the model can already see everything clearly. Repetition amplifies a signal that was never weak, so there is not much headroom to gain. The effect size is not a property of the trick, it is a property of how starved the task is for attention. That is the actual takeaway, and it is also a testable prediction: rerun this on long-context questions and the gap should open up.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why repetition changes anything at all
&lt;/h2&gt;

&lt;p&gt;This is the part that got me to actually read about attention instead of nodding along to diagrams.&lt;/p&gt;

&lt;p&gt;When the model predicts its answer token, attention is distributed across every prompt token. A key concept might get a weight of, say, 0.07. With the prompt repeated, each copy might get 0.05 and 0.04, but the combined signal is stronger than before. Nothing about the model changed; the input just gave the mechanism two chances to find the same information.&lt;/p&gt;

&lt;p&gt;The KV cache view says the same thing: during inference every prompt token's key and value vectors sit in the cache, and repetition puts two copies of the relevant vectors in there. Retrieval by attention becomes more reliable when the target exists twice. Prompt repetition is signal amplification you perform from outside the model, which is also a decent one-line explanation of why prompt engineering works at all.&lt;/p&gt;

&lt;p&gt;The paper itself adds a framing I had not considered: causal masking. In a causal LLM, tokens can only attend backwards, so a token early in your question never gets to see the tokens that come after it. In the second copy of the prompt, every token effectively has the full query behind it. Repetition is the cheapest possible workaround for one-directional attention.&lt;/p&gt;
&lt;h2&gt;
  
  
  Limitations, plainly
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;100 questions is small, and the headline result is within noise.&lt;/li&gt;
&lt;li&gt;Short MCQs only, which the previous section argues is the worst case for this trick.&lt;/li&gt;
&lt;li&gt;One model, one repetition factor (n = 2).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  What I would run next
&lt;/h2&gt;

&lt;p&gt;The obvious follow-ups, roughly in order of what I am most curious about: long-context tasks like the paper's NameIndex setup where the prediction says the effect should be large, repetition factors n = 3 to 5 to find where returns diminish, and a comparison against chain-of-thought on the same frozen dataset, since both tricks spend extra tokens to buy accuracy.&lt;/p&gt;

&lt;p&gt;Repo with the dataset builder, notebook, and raw per-question CSVs:&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/CoffeeAurCode" rel="noopener noreferrer"&gt;
        CoffeeAurCode
      &lt;/a&gt; / &lt;a href="https://github.com/CoffeeAurCode/Prompt-Repetition-Improves-Non-Reasoning-LLMs" rel="noopener noreferrer"&gt;
        Prompt-Repetition-Improves-Non-Reasoning-LLMs
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      I tried replicating the paper “Prompt Repetition Improves Non-Reasoning LLMs.”
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="MD"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Prompt Repetition Improves Non-Reasoning LLMs&lt;/h1&gt;
&lt;/div&gt;

&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;A Small Replication Study&lt;/h3&gt;
&lt;/div&gt;

&lt;p&gt;This repository contains a small experiment replicating the core idea from the paper:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Prompt Repetition Improves Non-Reasoning LLMs&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The goal of this experiment was to understand whether &lt;strong&gt;repeating a prompt&lt;/strong&gt; improves accuracy on a custom multiple-choice dataset and to explore &lt;strong&gt;why such a simple trick can affect transformer behavior&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Rather than focusing on achieving large gains, the purpose of this project was to &lt;strong&gt;investigate the mechanics of prompt repetition and understand what it reveals about transformer attention.&lt;/strong&gt;&lt;/p&gt;




&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Motivation&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;Recent work has shown that &lt;strong&gt;repeating a prompt multiple times&lt;/strong&gt; can significantly improve the performance of non-reasoning LLMs on certain tasks.&lt;/p&gt;

&lt;p&gt;Example reported improvements from the paper:
21% -&amp;gt; 97% accuracy&lt;/p&gt;
&lt;p&gt;on tasks requiring long context reasoning such as &lt;strong&gt;NameIndex&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;This raises interesting questions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Why does prompt repetition help?&lt;/li&gt;
&lt;li&gt;Does it work on simple MCQ tasks?&lt;/li&gt;
&lt;li&gt;What does this reveal about…&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/CoffeeAurCode/Prompt-Repetition-Improves-Non-Reasoning-LLMs" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;If you have run prompt repetition on anything with real context length, I would like to know whether the effect showed up for you, and how large it was. And if you see a flaw in the setup that could explain the +2% besides the trick itself, that is exactly the kind of comment I am hoping for.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>machinelearning</category>
      <category>python</category>
    </item>
  </channel>
</rss>
