<?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: yashasvi.shukla</title>
    <description>The latest articles on DEV Community by yashasvi.shukla (@yash_127).</description>
    <link>https://dev.to/yash_127</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%2F4039020%2Fafadd3a8-e531-4de7-a3dc-ab1c611f1e65.jpg</url>
      <title>DEV Community: yashasvi.shukla</title>
      <link>https://dev.to/yash_127</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/yash_127"/>
    <language>en</language>
    <item>
      <title>Product Quantization Explained</title>
      <dc:creator>yashasvi.shukla</dc:creator>
      <pubDate>Tue, 21 Jul 2026 08:54:02 +0000</pubDate>
      <link>https://dev.to/yash_127/product-quantization-explained-e3l</link>
      <guid>https://dev.to/yash_127/product-quantization-explained-e3l</guid>
      <description>&lt;p&gt;While building my agentic architecture, I had to choose between pgvector, ChromaDB, Qdrant, and Pinecone. I started studying where each one performs well and where it falls short.&lt;/p&gt;

&lt;p&gt;One thing I kept coming across was that pgvector starts consuming a lot of memory once your dataset grows to millions of vectors. That's when people often switch to Qdrant.&lt;/p&gt;

&lt;p&gt;I became curious: what is Qdrant doing under the hood that makes it so much more memory-efficient?&lt;/p&gt;

&lt;p&gt;That question led me to Product Quantization (PQ). &lt;/p&gt;

&lt;p&gt;pgvector works well for smaller datasets, but once you have millions of vectors, memory usage increases rapidly because every vector is stored in full precision. Qdrant handles the same scale much more efficiently using a compression technique called Product Quantization (PQ). Understanding how PQ works took me much longer than I expected.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;1st step&lt;/strong&gt;&lt;br&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%2F7dgsfoybva5g8cwesbip.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%2F7dgsfoybva5g8cwesbip.png" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What an embedding actually is. One document → 1 &lt;strong&gt;vector&lt;/strong&gt; of N numbers (e.g. 1536). Each number is a coordinate along a learned direction — not spatial like x/y/z, but the mathematical generalization of it.&lt;/p&gt;

&lt;p&gt;Why compression is needed at all. we have now  &lt;strong&gt;1 million vectors&lt;/strong&gt; × 1536 floats × 4 bytes = ~6GB just for one field,  in memory its a large number . This is the "why should I care" bridge before you go into HOW.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2nd step&lt;/strong&gt;&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%2Fca6vapkr9mi1c96nffto.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%2Fca6vapkr9mi1c96nffto.png" alt=" " width="800" height="1047"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;we pick 1st vector and slice it into  96 sub-vectors of 16 numbers&lt;br&gt;
This split is arbitrary—it isn't intrinsic to the data. It's simply a design choice that makes clustering computationally tractable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;keep in mind&lt;/strong&gt;&lt;br&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%2Fon0b1v2ejg33qxoujkf8.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%2Fon0b1v2ejg33qxoujkf8.png" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;keep in mind 1st vector's chunk 1 is in dimension 1-16, &lt;br&gt;
similarly 2nd vector's chunk 1 is also in dimension 1-16,&lt;br&gt;
it goes on 1MNth vector - chunk 1 is in dimension 1-16 &lt;br&gt;
i.e chunk 1 of each vector is in same dimension i.e (1-16)&lt;/p&gt;

&lt;p&gt;similarly 1st vector's chunk 2 is in dimension 17-32&lt;br&gt;
 2nd vector's chunk 2 is also in dimension 17-32&lt;br&gt;
till 1millionTH vector chunk 2 will be in dimension 17-32 &lt;br&gt;
.&lt;br&gt;
.&lt;br&gt;
.&lt;br&gt;
similarly 1st  vector's chunk 96 is in dimension 1521-1536&lt;br&gt;
2nd vector's chunk 2 is also in dimension 1521-1536&lt;br&gt;
till 1millionTH vector chunk 2 will be in dimension 1521-1536&lt;/p&gt;

&lt;p&gt;idea here is each similar chunk from each vector lives in similar dimensional space &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3rd step&lt;/strong&gt;&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%2F9pc0t92rxxa8i6pvbnhp.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%2F9pc0t92rxxa8i6pvbnhp.png" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, we take Chunk 1 from every vector. Since each Chunk 1 represents dimensions 1–16, we now have 1 million 16-dimensional chunks. We then run k-means clustering on these chunks.&lt;/p&gt;

&lt;p&gt;K-means groups together chunks that are close to each other in the vector space (i.e., the distance between them is small). For each group, it computes the average of all the chunks in that group. This average becomes the cluster centroid.&lt;/p&gt;

&lt;p&gt;We repeat this process until 256 cluster centroids are created for Chunk 1.&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%2F087yskfd9bhm26p5gvgc.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%2F087yskfd9bhm26p5gvgc.png" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;now we repeat the same for chunk 2 of ALL 1M vectors &lt;br&gt;
then for chunk 3 ........till chunk 96&lt;/p&gt;

&lt;p&gt;we have 256 cluster for each 96 chunks &lt;/p&gt;

&lt;p&gt;so far we had 1 million vectors , each vector of 1536 dimension , &lt;br&gt;
we each vector into 96chunks * 16spaces&lt;/p&gt;

&lt;p&gt;we took each chunk 1 formed 256 clusters &lt;br&gt;
we took each chunk 2 formed 256 clusters &lt;br&gt;
.&lt;br&gt;
.&lt;br&gt;
we took chunk 96 formed 256 clusters &lt;/p&gt;

&lt;p&gt;now&lt;br&gt;&lt;br&gt;
the compression begins &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4th step&lt;/strong&gt;&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%2F9q6tty6znd2nm4sdiodh.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%2F9q6tty6znd2nm4sdiodh.png" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;remember at  second step we have divided each vector into 96 chunks &lt;/p&gt;

&lt;p&gt;now read carefully &lt;br&gt;
**&lt;/p&gt;

&lt;h2&gt;
  
  
  we pick up vector 1
&lt;/h2&gt;

&lt;p&gt;see step 2&lt;br&gt;
   we pick &lt;strong&gt;chunk 1&lt;/strong&gt; (from step 2) we compare it with chunk&lt;br&gt;&lt;br&gt;
   1's 256 centroids (which we made in prev step)&lt;br&gt;
   and check chunk 1 is closest to which centroid among these 256 &lt;br&gt;
   centroids&lt;/p&gt;

&lt;p&gt;say it was close to cluster 33 , we store this &lt;strong&gt;id 33&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;we are still at &lt;strong&gt;vector 1&lt;/strong&gt;&lt;br&gt;
   now we pick &lt;strong&gt;chunk 2&lt;/strong&gt; (from step 2 ) we we compare it with &lt;strong&gt;chunk&lt;br&gt;&lt;br&gt;
   2&lt;/strong&gt;'s  256 centroids (which we made in prev step) not any other chunk's &lt;br&gt;
   centroids &lt;/p&gt;

&lt;p&gt;we found it was close to say cluster 221 , we store this &lt;strong&gt;id 221&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;we repeat the same process till &lt;strong&gt;96th chunk&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;so we get a compressed pq 96 ids for vector 1 &lt;br&gt;
&lt;strong&gt;[33,221,88.......144]&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5th step **&lt;br&gt;
now we move on to vector 2 &lt;br&gt;
      we repeat the same process &lt;br&gt;
      we pick up **chunk 1&lt;/strong&gt; (from step 2) we compare it with chunk&lt;br&gt;&lt;br&gt;
   1's 256 centroids (which we made in step #3)&lt;br&gt;
   and check chunk 1 is closest to which centroid among these 256 &lt;br&gt;
   centroids&lt;/p&gt;

&lt;p&gt;say it was close to cluster 17 , we store this &lt;strong&gt;id 17&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;we are still at &lt;strong&gt;vector 2&lt;/strong&gt;&lt;br&gt;
   now we pick &lt;strong&gt;chunk 2&lt;/strong&gt; (from step 2 ) we we compare it with &lt;strong&gt;chunk&lt;br&gt;&lt;br&gt;
   2&lt;/strong&gt;'s  256 centroids (which we made in step 3) not any other chunk's &lt;br&gt;
   centroids &lt;/p&gt;

&lt;p&gt;we found it was close to say cluster 4 , we store this &lt;strong&gt;id 4&lt;/strong&gt;&lt;br&gt;
   we got ids for vector 2 &lt;br&gt;
   &lt;strong&gt;[17,4,190......,9]&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;we repeat the same process till &lt;strong&gt;96th chunk&lt;/strong&gt;&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%2Ff9ewkdwts6wjtds1y3sc.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%2Ff9ewkdwts6wjtds1y3sc.png" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;and we repeat this for  vector 3 then vector 4 till..... 1M vectors&lt;/p&gt;

&lt;p&gt;Compression = replace values with IDs. For a given vector, chunk 1's actual 16 numbers are replaced with the ID of the nearest centroid in chunk 1's codebook (using Euclidean distance). That ID is a number between 0 and 255, so it fits in a single byte instead of storing 16 float32 values (64 bytes). &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%2Fg42zurixmm1ngryo8aoi.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%2Fg42zurixmm1ngryo8aoi.png" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Repeat this for all 96 chunks, and the vector shrinks from &lt;strong&gt;6144 bytes&lt;/strong&gt; (1536 × 4) to &lt;strong&gt;96 bytes&lt;/strong&gt;—roughly &lt;strong&gt;64× smaller&lt;/strong&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;6144 bytes → 96 bytes (~64× compression)&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Of course, something is lost. Product Quantization is &lt;strong&gt;lossy&lt;/strong&gt;, so distance calculations become approximate rather than exact. The tradeoff is lower memory usage at the cost of some recall. Systems like Qdrant mitigate this by rescoring the top-K candidates using the original full-precision vectors, recovering much of the lost accuracy.&lt;/p&gt;

&lt;p&gt;in next blog i would talk about after compressing , how we are going to fetch fast if any querry comes in &lt;/p&gt;

&lt;p&gt;i have given my many hours understanding it don't expect you would understand in 1st go read it several times ,you would surely understand in 1/50th time i took to understand after reading this . &lt;/p&gt;

</description>
      <category>agentaichallenge</category>
      <category>llm</category>
      <category>ai</category>
    </item>
  </channel>
</rss>
