From 18f8f44980eaf93f40f5dab69185b6a6029319fa Mon Sep 17 00:00:00 2001 From: SilasMarvin <19626586+SilasMarvin@users.noreply.github.com> Date: Tue, 11 Jun 2024 10:57:01 -0700 Subject: [PATCH 01/20] Preliminary draft of semantic search in postgres in 15 minutes --- ...mantic-search-in-postgres-in-15-minutes.md | 404 ++++++++++++++++++ 1 file changed, 404 insertions(+) create mode 100644 pgml-cms/blog/semantic-search-in-postgres-in-15-minutes.md diff --git a/pgml-cms/blog/semantic-search-in-postgres-in-15-minutes.md b/pgml-cms/blog/semantic-search-in-postgres-in-15-minutes.md new file mode 100644 index 000000000..bcd067dc1 --- /dev/null +++ b/pgml-cms/blog/semantic-search-in-postgres-in-15-minutes.md @@ -0,0 +1,404 @@ + +--- +description: >- + Dive into the world of Postgres and learn how to implement semantic search in nothing but SQL. +featured: true +image: ".gitbook/assets/image (2) (2).png" +tags: ["Engineering"] +--- + +# Semantic Search in Postgres in 15 Minutes + +
+ +
Author
+ +
+ +Silas Marvin + +June 15, 2024 + +PostgresML gives the ability to generate, store, and search over embeddings directly in your Postgres database. + +

PostgresML is a composition engine that provides advanced AI capabilities.

+ +## What is and is not Semantic Search + +Semantic search is a new form of machine learning powered search that doesn’t rely on any form of keyword matching, but transforms text into embeddings and performs nearest neighbors search. + +It is not a complete replacement for full text search. In many cases full text search is capable of outperforming semantic search. Specifically, if a user knows the exact phrase in a document they want to match, full text search is faster and guaranteed to return the correct result while semantic search is only likely to return the correct result. Full text search and semantic search can be combined to create powerful and robust search systems. + +Semantic search is not just for machine learning engineers. The actual system behind semantic search is relatively easy to implement and thanks to new Postgres extensions like pgml and pgvector, is readily available to SQL developers. Just as it is expected for modern SQL developers to be familiar with and capable of implementing full text search, soon SQL developers will be expected to implement semantic search. + +## Embeddings 101 + +Semantic search is powered by embeddings. To understand how semantic search works, we must have a basic understanding of embeddings. + +Embeddings are vectors. Given some text and some embedding model, we can convert text to vectors: + +!!! generic + +!!! code block time="10.493 ms" + +```postgresql +SELECT pgml.embed('mixedbread-ai/mxbai-embed-large-v1', 'test'); +``` + +!!! + +!!! + +Here we are using the pgml.embed SQL function to generate an embedding using the `mixedbread-ai/mxbai-embed-large-v1` model. + +The output size of the vector varies per model. This specific model outputs vectors with 1024 dimensions. This means each vector contains 1024 floating point numbers. + +The vector this model outputs is not random. It is designed to capture the semantic meaning of the text. What this really means, is that sentences that are closer together in meaning will be closer together in vector space. + +Let’s look at a more simple example. Assume we have a model called `simple-embedding-model`, and it outputs vectors with 2 dimensions. Let’s embed the following three phrases: `I like Postgres`, `I like SQL`, `Rust is the best`. + +!!! generic + +!!! code block time="10.493 ms" + +```postgresql +SELECT pgml.embed('simple-embedding-model', 'I like Postgres') as embedding; + +SELECT pgml.embed('simple-embedding-model', 'I like SQL') as embedding; + +SELECT pgml.embed('simple-embedding-model', 'Rust is the best') as embedding; +``` + +!!! + +!!! results + +embedding +--------- +[0.1, 0.2] + +embedding +--------- +[0.12, 0.25] + +embedding +--------- +[-0.8, -0.9] + +!!! + +!!! + +Notice how similar the vectors produced by the text `I like Postgres` and `I like SQL` are compared to `Rust is the best`. + +This is a simple example, but the same idea holds true when translating to real models like `mixedbread-ai/mxbai-embed-large-v1`. + +## What Does it Mean to be Close? + +We can use the idea that text that is more similar in meaning will be closer together in the vector space to perform search. + +For instance let’s say that we have the following documents: + +```text +Document1: The pgml.transform function is a PostgreSQL function for calling LLMs in the database. + +Document2: I think tomatos are incredible on burgers. +``` + +And a user is looking for the answer to the question: `What is the pgml.transform function?`. If we embed the user query and all of the documents using a model like `mixedbread-ai/mxbai-embed-large-v1`, we can compare the query embedding to all of the document embeddings, and select the document that has the closest embedding as the answer. + +These are big embeddings, and we can’t simply eyeball which one is the closest. How do we actually measure the similarity / distance between different vectors? There are four popular methods for measuring the distance between vectors available in PostgresML: +- L2 distance +- (negative) inner product +- cosine distance +- L1 distance + +For most use cases we recommend using the cosine distance as defined by the formula: + +INSERT IMAGE + +Where A and B are two vectors. + +This is a somewhat confusing formula but lucky for us pgvector provides an operator that computes this for us. + +!!! generic + +!!! code block + +```postgresql +SELECT '[1,2,3]'::vector <=> '[2,3,4]'::vector; +``` + +!!! + +!!! results + + cosine_distance +---------------------- + 0.007416666029069763 +(1 row) + +!!! + +!!! + +The other distance functions have similar formulas and also provide convenient operators to use. It may be worth testing the other operators and seeing which performs better for your use case. For more information on the other distance functions see our guide on [embeddings](https://postgresml.org/docs/guides/embeddings/vector-similarity). + +Back to our search example outlined above, we can compute the cosine distance between our query embedding and our documents. + +!!! generic + +!!! code block + +```postgresql +SELECT pgml.embed('mixedbread-ai/mxbai-embed-large-v1', 'What is the pgml.transform function?')::vector <=> pgml.embed('mixedbread-ai/mxbai-embed-large-v1', 'The pgml.transform function is a PostgreSQL function for calling LLMs in the database.')::vector as cosine_distance; +SELECT pgml.embed('mixedbread-ai/mxbai-embed-large-v1', 'What is the pgml.transform function?')::vector <=> pgml.embed('mixedbread-ai/mxbai-embed-large-v1', 'I think tomatos are incredible on burgers.')::vector as cosine_distance; +``` + +!!! + +!!! results + + cosine_distance +-------------------- + 0.1114425936213167 + + cosine_distance +-------------------- + 0.7383001059221699 + +!!! + +!!! + +Notice that the cosine distance between `What is the pgml.transform function?` and `The pgml.transform function is a PostgreSQL function for calling LLMs in the database.` is much smaller than the cosine distance between `What is the pgml.transform function?` and `I think tomatos are incredible on burgers.`. + +## Making it Fast! + +It is inefficient to compute the embeddings for our documents for every search request. Instead, we want to embed our documents once, and search against our stored embeddings. + +We can store our embedding vectors with the vector type given by pgvector. + +!!! generic + +!!! code block + +```postgresql +CREATE TABLE text_and_embeddings ( + id SERIAL PRIMARY KEY, + text text, + embedding vector (1024) +); +INSERT INTO text_and_embeddings(text, embedding) +VALUES + ('The pgml.transform function is a PostgreSQL function for calling LLMs in the database.', pgml.embed('mixedbread-ai/mxbai-embed-large-v1', 'The pgml.transform function is a PostgreSQL function for calling LLMs in the database.')), + ('I think tomatos are incredible on burgers.', pgml.embed('mixedbread-ai/mxbai-embed-large-v1', 'I think tomatos are incredible on burgers.')) +; +``` + +!!! + +!!! + +We can search this table using the following query: + +!!! generic + +!!! code block time="10.493 ms" + +```postgresql +WITH embedded_query AS ( + SELECT + pgml.embed('mixedbread-ai/mxbai-embed-large-v1', 'What is the pgml.transform function?', '{"prompt": "Represent this sentence for searching relevant passages: "}')::vector embedding +) +SELECT + text, + ( + SELECT + embedding + FROM embedded_query) <=> text_and_embeddings.embedding cosine_distance +FROM + text_and_embeddings +ORDER BY + text_and_embeddings.embedding <=> ( + SELECT + embedding + FROM embedded_query) +LIMIT 1; +``` + +!!! + +!!! results + + text | cosine_distance +----------------------------------------------------------------------------------------+--------------------- + The pgml.transform function is a PostgreSQL function for calling LLMs in the database. | 0.13467974993681486 +(1 row) + +!!! + +!!! + +This query is fast for now, but as the table scales it will greatly slow down because we have not indexed the vector column. + + +!!! generic + +!!! code block time="10.493 ms" + +```postgresql +INSERT INTO text_and_embeddings (text, embedding) +SELECT md5(random()::text), pgml.embed('mixedbread-ai/mxbai-embed-large-v1', md5(random()::text)) +FROM generate_series(1, 10000); + +WITH embedded_query AS ( + SELECT + pgml.embed('mixedbread-ai/mxbai-embed-large-v1', 'What is the pgml.transform function?', '{"prompt": "Represent this sentence for searching relevant passages: "}')::vector embedding +) +SELECT + text, + ( + SELECT + embedding + FROM embedded_query) <=> text_and_embeddings.embedding cosine_distance +FROM + text_and_embeddings +ORDER BY + text_and_embeddings.embedding <=> ( + SELECT + embedding + FROM embedded_query) +LIMIT 1; +``` + +!!! + +!!! results + + text | cosine_distance +----------------------------------------------------------------------------------------+--------------------- + The pgml.transform function is a PostgreSQL function for calling LLMs in the database. | 0.13467974993681486 +(1 row) + +!!! + +!!! + +This somewhat less than ideal performance can be fixed by indexing the vector column. There are two types of indexes available in pgvector: IVFFlat and HNSW. + +IVFFlat indexes clusters the table into sublists, and when searching, only searches over a fixed number of the sublists. For example in the case above, if we were to add an IVFFlat index with 10 lists: + +!!! generic + +!!! code block time="10.493 ms" + +```postgresql +CREATE INDEX ON text_and_embeddings USING ivfflat (embedding vector_cosine_ops) WITH (lists = 10); +``` + +!!! + +!!! + +Now let's try searching again. + +!!! generic + +!!! code block time="10.493 ms" + +```postgresql +WITH embedded_query AS ( + SELECT + pgml.embed('mixedbread-ai/mxbai-embed-large-v1', 'What is the pgml.transform function?', '{"prompt": "Represent this sentence for searching relevant passages: "}')::vector embedding +) +SELECT + text, + ( + SELECT + embedding + FROM embedded_query) <=> text_and_embeddings.embedding cosine_distance +FROM + text_and_embeddings +ORDER BY + text_and_embeddings.embedding <=> ( + SELECT + embedding + FROM embedded_query) +LIMIT 1; +``` + +!!! + +!!! results + + text | cosine_distance +----------------------------------------------------------------------------------------+--------------------- + The pgml.transform function is a PostgreSQL function for calling LLMs in the database. | 0.13467974993681486 +(1 row) + +!!! + +!!! + +We can see it is about a 10x speedup because we are only searching over 1/10th of the original vectors. + +HNSW indexes are a bit more complicated. It is essentially a graph with edges linked by proximity in the vector space. For more information you can check out this [writeup](https://www.pinecone.io/learn/series/faiss/hnsw/). + +HNSW indexes typically have better and faster recall but require more compute when inserting. We recommend using HNSW indexes for most use cases. + +!!! generic + +!!! code block time="10.493 ms" + +```postgresql +DROP index text_and_embeddings_embedding_idx; + +CREATE INDEX ON text_and_embeddings USING hnsw (embedding vector_cosine_ops); +``` + +!!! + +!!! + +Now let's try searching again. + +!!! generic + +!!! code block time="10.493 ms" + +```postgresql +WITH embedded_query AS ( + SELECT + pgml.embed('mixedbread-ai/mxbai-embed-large-v1', 'What is the pgml.transform function?', '{"prompt": "Represent this sentence for searching relevant passages: "}')::vector embedding +) +SELECT + text, + ( + SELECT + embedding + FROM embedded_query) <=> text_and_embeddings.embedding cosine_distance +FROM + text_and_embeddings +ORDER BY + text_and_embeddings.embedding <=> ( + SELECT + embedding + FROM embedded_query) +LIMIT 1; +``` + +!!! + +!!! results + + text | cosine_distance +----------------------------------------------------------------------------------------+--------------------- + The pgml.transform function is a PostgreSQL function for calling LLMs in the database. | 0.13467974993681486 +(1 row) + +!!! + +!!! + +That was even faster! From 00bd75de924b8eec1b1e4a11ec6c7c779979eb06 Mon Sep 17 00:00:00 2001 From: SilasMarvin <19626586+SilasMarvin@users.noreply.github.com> Date: Wed, 12 Jun 2024 10:02:07 -0700 Subject: [PATCH 02/20] Cleanups --- pgml-cms/blog/SUMMARY.md | 1 + ...mantic-search-in-postgres-in-15-minutes.md | 74 ++++++++++++------- 2 files changed, 49 insertions(+), 26 deletions(-) diff --git a/pgml-cms/blog/SUMMARY.md b/pgml-cms/blog/SUMMARY.md index 3abd4242e..33dd2db48 100644 --- a/pgml-cms/blog/SUMMARY.md +++ b/pgml-cms/blog/SUMMARY.md @@ -1,6 +1,7 @@ # Table of contents * [Home](README.md) +* [Semantic Search in Postgres in 15 Minutes](semantic-search-in-postgres-in-15-minutes.md) * [Announcing the Release of our Rust SDK](announcing-the-release-of-our-rust-sdk.md) * [Serverless LLMs are dead; Long live Serverless LLMs](serverless-llms-are-dead-long-live-serverless-llms.md) * [Speeding up vector recall 5x with HNSW](speeding-up-vector-recall-5x-with-hnsw.md) diff --git a/pgml-cms/blog/semantic-search-in-postgres-in-15-minutes.md b/pgml-cms/blog/semantic-search-in-postgres-in-15-minutes.md index bcd067dc1..359cf61c9 100644 --- a/pgml-cms/blog/semantic-search-in-postgres-in-15-minutes.md +++ b/pgml-cms/blog/semantic-search-in-postgres-in-15-minutes.md @@ -1,7 +1,7 @@ --- description: >- - Dive into the world of Postgres and learn how to implement semantic search in nothing but SQL. + Learn how to implement semantic search in postgres with nothing but SQL. featured: true image: ".gitbook/assets/image (2) (2).png" tags: ["Engineering"] @@ -11,7 +11,7 @@ tags: ["Engineering"]
-
Author
+
Author
@@ -19,10 +19,6 @@ Silas Marvin June 15, 2024 -PostgresML gives the ability to generate, store, and search over embeddings directly in your Postgres database. - -

PostgresML is a composition engine that provides advanced AI capabilities.

- ## What is and is not Semantic Search Semantic search is a new form of machine learning powered search that doesn’t rely on any form of keyword matching, but transforms text into embeddings and performs nearest neighbors search. @@ -39,17 +35,25 @@ Embeddings are vectors. Given some text and some embedding model, we can convert !!! generic -!!! code block time="10.493 ms" +!!! code_block time="10.493 ms" ```postgresql SELECT pgml.embed('mixedbread-ai/mxbai-embed-large-v1', 'test'); ``` +!!! + +!!! results + +```text +{0.100442916,0.19116051,0.031572577,0.4392428,-0.4684339,0.2671335,0.000183642,0.402029,0.52400684,0.9102567,0.3289586,0.13825876,0.060483586,-0.9315942,-0. 36723328,-0.34614784,-0.5203485,-0.0026301902,-0.4359727,-0.2078317,-0.3624561,0.151347,-1.0850769,0.03073607,-0.38463902,0.5746146,-0.065853976,-0.02959722, 0.3181093,0.60477436,-0.20975977,-0.112029605,0.32066098,-0.92783266,0.17003687,-0.6294021,0.94078255,-0.32587636,-0.06733026,-0.41903132,-0.29940385,-0.0473 7147,0.7173805,-0.4461215,-1.2713308,-0.44129986,-0.46632472,-0.89888424,-0.22231846,-0.34233224,0.09881798,0.17341912,0.27128124,-0.7020756,0.113429464,-0.2 2964618,-0.22798245,0.1105072,-0.8441625,1.238711,0.8674123,-0.14600402,0.391594,-0.9928256,0.24864249,-0.11477054,0.23513256,-0.366138,-0.13302355,-0.449127 64,-0.45309332,0.4775117,-0.19158679,-0.6634198,-0.21402365,0.2285473,0.09665201,0.47793895,-0.456355,0.33732873,0.2820914,0.17230554,0.14925064,0.23560016,- 1.2823433,-0.8188313,0.07958572,0.758208,0.39241728,-0.021326406,-0.0026611753,0.4960972,-0.5743201,-0.10779899,-0.53800243,0.11743014,0.17272875,-0.537756,0 .15774709,-0.024241826,0.75601554,0.5569049,-0.098995246,1.0593386,-0.90425104,0.3956237,0.024354521,-0.32476613,-0.5950871,-0.75371516,-0.31561607,0.0696320 8,0.6516349,0.5434117,-0.7673086,0.7324196,0.15175763,1.1354275,-0.56910944,-0.09738582,0.35705066,0.018214416,-0.091416046,-0.19074695,-0.34592274,-0.115972 71,-0.5033031,0.6735635,-0.05835747,-0.21572702,-0.58285874,0.095334634,0.8742985,0.6349386,0.4706169,-0.029405594,-0.50637966,0.4569466,0.2924249,-0.9321604 ,0.34013036,1.1258447,-0.28096777,1.2910426,0.32090122,0.5956652,0.22290495,0.08063537,-0.3783538,0.71436244,-0.90230185,-0.4399799,0.24639784,0.3069413,-0.4 8032463,0.27206388,-0.43469447,-0.2339563,0.12732148,0.22685277,-0.7924011,0.3359629,-0.30172998,0.43736732,-0.521733,1.324045,-0.28834093,-0.15974034,0.2684 1715,-0.33593872,0.73629487,-0.1049053,0.16749644,0.3264093,-0.101803474,0.22606595,1.2974273,0.22830595,0.39088526,0.4486965,-0.57037145,-0.09293561,-0.0394 99372,0.47220317,0.74698365,0.2392712,0.23049277,-0.52685314,-0.5007666,-0.03302433,-0.2098883,0.47145832,-0.6392486,0.58358306,-0.15019979,0.32308426,-0.506 2344,-0.16731891,-0.55598915,-1.7701503,-0.3798853,0.54786783,-0.71652645,-0.1773671,0.2289979,-1.0015582,0.5309544,0.81240565,-0.17937969,-0.3966378,0.60281 52,0.8962739,-0.176342,-0.010436469,0.02249392,0.09129296,-0.105494745,0.970157,-0.26875457,0.10241943,0.6148784,-0.35458624,0.5211534,0.61402124,0.48477444, -0.16437246,-0.28179103,1.2025942,-0.22813843,-0.09890138,0.043852188,1.0050704,-0.17958593,1.3325024,0.59157765,0.4212076,1.0721352,0.095619194,0.26288888,0 .42549643,0.2535346,0.35668525,0.82613224,0.30157906,-0.567903,0.32422608,-0.046756506,0.08393835,-0.31040233,0.7402205,0.7880251,0.5210311,1.0603857,0.41067 ,-0.3616352,-0.25297007,0.97518605,0.85333014,0.16857263,0.040858276,0.09388767,-0.19449936,0.38802677,0.164354,-0.017545663,0.15570965,-0.31904343,0.2223094 4,0.6248201,-0.5483591,-0.36983973,-0.38050756,-1.925645,-1.037537,-0.6157122,-0.53581315,0.2836596,-0.643354,0.07323658,-0.93136156,-0.20392789,-0.72027314, -0.33667037,0.91866046,0.23589604,0.9972664,-0.29671007,0.08811699,0.24376874,0.82748646,-0.604533,-0.67664343,-0.32924688,-0.37375167,0.33761302,-0.19614917 ,-0.21015668,0.46505967,-0.28253073,-1.0112746,1.1360632,0.8825793,-1.0680563,0.0655969,-1.034352,0.5267044,0.91949135,-0.031119794,0.60942674,0.54940313,-0. 3630888,0.44943437,0.66361815,0.073895305,-0.59853613,0.18480797,0.49640504,-0.13335773,-0.66213644,0.08816239,-0.52057326,-0.48232892,-0.2665552,-0.10339267 ,-0.30988455,0.46449667,-0.022207903,-1.6161236,0.27622652,-0.5909109,-1.0504522,0.052266315,-0.66712016,1.038967,-0.21038829,-0.30632204,-0.63056785,-0.0326 83205,0.8322449,0.43663988,0.8234027,-0.69451404,-0.29506078,0.8947272,0.36536238,-0.06769319,-0.21281374,0.1542073,-1.0177101,0.1798313,-0.38755146,0.353291 33,-0.1736927,0.2708998,0.36253256,0.55142975,-0.25388694,0.2749728,1.0570686,0.14571312,0.14165263,-0.18871498,0.2701316,0.6352345,-0.1975502,-1.0767723,-0. 0899109,0.06417123,0.16973273,-1.4618999,0.75780666,-0.37219298,0.34675384,-0.21044762,0.3230924,-0.59562063,0.57655936,-0.24317324,0.4706862,-1.0036217,0.27 595782,-0.18632303,-0.024258574,0.36281094,0.72106606,0.4534661,0.10037945,0.49504414,-0.9208432,-0.8387544,-0.17667033,0.44228357,0.36593667,-0.3061421,-1.2 638136,-1.1484053,0.5236616,0.020920075,0.2590868,-0.017210491,0.48833278,-0.34420222,0.35703135,1.0728164,-0.51129043,0.0902225,-0.42320332,0.19660714,-0.28 81061,-0.15664813,-0.99245757,0.06579208,-1.5574676,0.16405283,0.46488675,-0.15788008,-1.01791,0.84872925,0.035253655,0.40218765,-0.59924084,-0.2960986,-0.27 4478,-0.17835106,0.6479293,-0.42014578,-0.15515843,-0.62578845,0.2247606,1.153755,-0.033114456,-0.8774578,-0.021032173,-0.54359645,-1.0827168,-0.4298837,0.39 979023,-0.031404667,-0.25790605,-0.55900896,0.85690576,-0.23558116,-0.64585954,-0.18867598,-0.016098155,-0.021867584,0.5298315,0.65620464,-0.45029733,-1.0737 212,-0.25292996,-1.8820043,0.78425264,0.049297262,0.033368483,-0.13924618,-0.08540384,0.26575288,0.3641497,-0.5929729,0.012706397,-0.14115371,0.7092089,-0.29 87519,-0.50846523,1.1529989,-0.007935073,-0.39666718,0.66540664,-0.43792737,-0.14657505,0.013367422,0.59577924,-0.31825122,0.3546381,0.11212899,0.5804333,-0. 72722685,-0.58012086,-0.25618848,-0.3021478,0.3090123,0.39833462,-0.1964222,-1.0031091,-0.7377774,-0.37093028,-0.268894,-0.16332497,0.8644577,0.5592706,0.175 96069,-0.28468367,-0.11259709,-0.3321775,0.12905857,-0.4623798,-0.2466813,-0.39571014,0.8273027,0.3286372,-0.42084447,-0.6982525,0.51819134,-0.4211214,-0.450 2746,-0.58659184,0.9362978,-0.24028268,-0.07863556,0.03276802,0.31117234,-0.61217594,0.29426676,0.5394515,0.096639164,-0.17290285,-0.100368135,-1.1184332,0.6 5379685,0.21017732,-0.48588675,-0.42309326,0.78154176,0.11492857,0.9659768,0.85164833,-0.510996,-0.4957692,-1.0045063,0.41195333,-0.25961345,-0.06390433,-0.8 0765647,-0.5750627,-0.004215756,0.6570266,0.021791002,-0.2851547,0.33010367,-1.0438038,0.64198446,-0.3170377,-0.21503314,-0.7744095,0.34140953,-0.123576835,1 .2228137,0.3193732,0.097345576,0.013826016,0.490495,0.16021223,0.3592192,-0.64754117,-1.2467206,0.20728661,-0.040293045,0.18149051,-0.3889212,-1.2567233,-0.2 7512074,-0.8875311,-0.4562278,-0.14274459,-0.7154212,-0.9517362,-0.42942467,0.34255636,-0.25662944,-0.071650766,-0.2570997,0.97032154,0.55209476,0.9512633,-0 .78840256,-0.87641865,-0.31667447,-1.1845096,0.61095214,-0.4934745,-0.090470426,-0.8589016,0.16191132,1.3353379,0.36014295,0.6354017,1.6015769,-0.15028308,-0 .35953638,-0.46233898,0.7056889,0.44098303,-0.2561036,-0.38414526,-0.85254925,-0.35759622,0.32756907,-1.1055855,-0.9486638,-0.75697213,-0.18819816,0.91543293 ,0.046453375,0.8660134,-0.7937197,-0.72757536,-0.8235348,0.8263684,0.84975964,0.6188537,1.0370533,0.8713266,-0.17223643,0.74872315,-0.087729014,0.027644658,- 0.41663802,0.86366785,0.45966265,-0.7807239,0.72492,0.7516153,-1.4882706,-0.7965106,0.44769654,0.04745266,-0.3665682,-1.1761265,-0.16592331,-0.49482864,-0.18 829915,0.079323344,0.5283898,-0.25911674,0.49787715,0.040334962,0.6457638,-0.9161095,-0.52021873,0.3950836,-0.8869649,0.61957175,-0.8694589,-0.14945404,0.331 69168,0.2645687,0.45321828,-0.20752133,-0.00011348105,0.7114366,0.36253646,0.94113743,0.27327093,-0.279275,0.74158365,-0.7394054,-0.9920889,-0.5790354,0.4460 0168,0.6965152,0.055897847,-0.7247457,-0.23232944,1.0741904,-0.103388265,0.3405134,-0.6539511,-0.51377046,-0.7043648,-0.61793834,-0.7072252,-0.34909388,-0.05 701723,0.6294965,-0.30765653,0.03854165,0.032257613,0.8844775,-0.12016908,0.45807433,-0.8181472,0.5738447,-0.08459999,-0.5052286,-0.322389,0.16923045,-0.5340 384,0.82369304,-0.6654957,0.09066754,0.23323251,0.75676244,-0.07526736,0.18891658,-0.58411753,-0.5459881,0.31472534,0.22671345,0.15036865,0.5497431,0.6759999 4,-0.17044614,0.3315073,-0.07908476,0.3493545,-1.3477355,0.56133074,0.6158089,-0.15612105,-0.15391739,-1.6920619,-0.45604506,-0.9460573,0.1832847,-0.9812012, -0.037437357,0.23665366,0.20942298,0.12745716,0.3055677,0.4899028,0.1521983,-0.4412764,0.44380093,-0.24363151,0.049277242,-0.03479184,0.34719813,0.34336445,0 .44446415,-0.2509871,-0.07174216,0.16965394,0.40415984,-0.50963897,-0.4655299,0.59960693,-0.3961361,0.17242691,0.71643007,-0.012265541,0.07691683,1.2442924,0 .22043933,-1.2103993,0.61401594,-0.541842,-0.33357695,0.3074923,0.065326504,-0.27286193,0.6154859,-0.69564784,-0.11709106,-0.1545567,-0.11896704,-0.007217975 ,0.23488984,0.5601741,0.4612949,-0.28685024,-0.01752333,0.09766184,1.3614978,-0.9316589,-0.62082577,-0.17708167,-0.14922938,0.6017379,0.20790131,-0.17358595, 0.51986843,-0.8632079,-0.23630512,0.5615771,0.12942453,-0.55579686,-0.28877118,-0.023886856,0.6346819,0.11919484,0.112735756,-0.2105418,-1.0274605,-0.2215069 7,0.6296189,0.528352,-0.27940798,0.5474754,0.14160539,0.38373166,0.5457794,-0.7958526,-0.53057015,1.2145486,0.12005539,0.9229809,0.11178251,0.35618028,0.8680 126,-0.14047255,-0.022312704,0.6335968,0.22576317,0.63063693,0.077043116,-0.3592758,0.14797379,0.37010187,-0.14920035,-0.303325,-0.68384075,-0.22196415,-0.48 251563,0.085435614,1.0682561,-0.28910154,0.0547357,-0.49188855,0.07103363,0.23165464,0.7919816,-0.31917652,-0.11256474,0.22344519,0.202349,-0.042141877,0.487 33395,-0.6330437,0.18770827,-0.8534354,0.24361372,0.05912281,-0.14594407,-0.3065622,-0.13557081,-1.4080516,0.60802686,0.7874556,-0.8090863,0.5354539,-0.86377 89,-0.2529881,-0.76151496,0.39836842,-0.3637328,0.16363671,0.5599722,-0.24072857,0.09546083,0.831411,0.09562837,0.31388548,0.103111275,1.1427172,0.694476,0.9 3155265,0.64801776,-0.33954978,-0.0988641,0.473648,-0.2811673,-0.3996959,-0.33468047,-0.21153395,0.886874,-0.8678805,-0.10753187,-0.19310957,0.4603335,-0.122 70494,-1.0267254,-0.53114897,0.004987782,-0.7938769,0.40439928,0.4829653,1.5288875,0.6414294,-0.6214873,-0.65656304,0.47653323,0.16301247,-0.12008583,1.03255 62,0.13527338,-0.927417,-0.35502926,-0.17070319,-0.0011159402,0.15795147,-0.3817831,-0.99539477,0.44974712,0.623257,0.032141224,0.20115706,-0.753747,-0.03541 0736,0.317427,0.7414546,-0.41621342,1.4412553,0.088434115,-0.29406205,0.019276256,-0.66831887,0.39378297,-0.15091878,-0.33501017,0.012463322,0.26902023,-0.85 676277,-0.08205583,-0.13279751,0.8540507,-0.07071759,0.67416996,-1.0808998,-0.7537985,-1.1090854,-0.42881688,-0.545489,1.0022873,-0.34716064,-0.3511107,0.611 6534,-1.0079868,3.7511525,0.4171535,0.504542,-0.051603127,-0.071831375,0.44832432,-0.21127303,-0.57512856,-0.19024895,0.23094098,0.16914046,0.21540225,-0.077 53263,0.19773084,0.8750281,0.55822086,-0.46648705,-0.44413725,0.23833762,-0.6311006,-0.5150255,0.014071045,-0.043874096,0.40925947,-0.082470596,0.4262907,1.2 440436,-0.123832524,-0.09172271,-0.42539525,1.0193819,-0.20638897,-0.055872787,-0.12540375,-0.058966316,0.73125196,0.3050278,0.25579217,0.118471175,-0.148029 91,-0.33583203,0.11730125,1.5576597,-0.17712794,-0.2750745,0.11848973,-0.48632467,0.8594597,0.21705948,-0.04919338,0.8793258,-0.6851242,1.2830902,-0.226695,- 1.6696168,-0.4619705,-0.080957085,-0.53974324,-0.77588433,0.103437446,0.015129212,0.2896572,-0.28889287,-0.266523,-0.5023567,-0.0604841,0.57056016,0.5261334, -0.18631883,-0.5122663,-0.055830136,0.56574637,-0.5704402,-0.4263674,0.24019304,0.082071595,-0.31298077,0.30196336,-0.011113114,-0.5608543,0.3951217,-0.26592 582,0.41811758,-0.7411703,0.30873746,0.5664615,-0.98191136,-0.49090472,-1.0648257,0.97027993,0.9559882,-0.019431114,-0.07921166,-0.120092966,-0.13082835} +``` + !!! !!! -Here we are using the pgml.embed SQL function to generate an embedding using the `mixedbread-ai/mxbai-embed-large-v1` model. +Above we used the pgml.embed SQL function to generate an embedding using the `mixedbread-ai/mxbai-embed-large-v1` model. The output size of the vector varies per model. This specific model outputs vectors with 1024 dimensions. This means each vector contains 1024 floating point numbers. @@ -59,7 +63,7 @@ Let’s look at a more simple example. Assume we have a model called `simple-emb !!! generic -!!! code block time="10.493 ms" +!!! code_block ```postgresql SELECT pgml.embed('simple-embedding-model', 'I like Postgres') as embedding; @@ -73,17 +77,19 @@ SELECT pgml.embed('simple-embedding-model', 'Rust is the best') as embedding; !!! results -embedding +```text +embedding for 'I like Postgres' --------- [0.1, 0.2] -embedding +embedding for 'I like SQL' --------- [0.12, 0.25] -embedding +embedding for 'Rust is the best' --------- [-0.8, -0.9] +``` !!! @@ -99,12 +105,21 @@ We can use the idea that text that is more similar in meaning will be closer tog For instance let’s say that we have the following documents: + +!!! generic + +!!! code_block + ```text Document1: The pgml.transform function is a PostgreSQL function for calling LLMs in the database. Document2: I think tomatos are incredible on burgers. ``` +!!! + +!!! + And a user is looking for the answer to the question: `What is the pgml.transform function?`. If we embed the user query and all of the documents using a model like `mixedbread-ai/mxbai-embed-large-v1`, we can compare the query embedding to all of the document embeddings, and select the document that has the closest embedding as the answer. These are big embeddings, and we can’t simply eyeball which one is the closest. How do we actually measure the similarity / distance between different vectors? There are four popular methods for measuring the distance between vectors available in PostgresML: @@ -123,7 +138,7 @@ This is a somewhat confusing formula but lucky for us pgvector provides an opera !!! generic -!!! code block +!!! code_block ```postgresql SELECT '[1,2,3]'::vector <=> '[2,3,4]'::vector; @@ -133,10 +148,11 @@ SELECT '[1,2,3]'::vector <=> '[2,3,4]'::vector; !!! results +```text cosine_distance ---------------------- 0.007416666029069763 -(1 row) +``` !!! @@ -148,7 +164,7 @@ Back to our search example outlined above, we can compute the cosine distance be !!! generic -!!! code block +!!! code_block ```postgresql SELECT pgml.embed('mixedbread-ai/mxbai-embed-large-v1', 'What is the pgml.transform function?')::vector <=> pgml.embed('mixedbread-ai/mxbai-embed-large-v1', 'The pgml.transform function is a PostgreSQL function for calling LLMs in the database.')::vector as cosine_distance; @@ -159,6 +175,7 @@ SELECT pgml.embed('mixedbread-ai/mxbai-embed-large-v1', 'What is the pgml.transf !!! results +```text cosine_distance -------------------- 0.1114425936213167 @@ -166,6 +183,7 @@ SELECT pgml.embed('mixedbread-ai/mxbai-embed-large-v1', 'What is the pgml.transf cosine_distance -------------------- 0.7383001059221699 +``` !!! @@ -181,7 +199,7 @@ We can store our embedding vectors with the vector type given by pgvector. !!! generic -!!! code block +!!! code_block ```postgresql CREATE TABLE text_and_embeddings ( @@ -204,7 +222,7 @@ We can search this table using the following query: !!! generic -!!! code block time="10.493 ms" +!!! code_block time="10.493 ms" ```postgresql WITH embedded_query AS ( @@ -231,10 +249,11 @@ LIMIT 1; !!! results +``` text | cosine_distance ----------------------------------------------------------------------------------------+--------------------- The pgml.transform function is a PostgreSQL function for calling LLMs in the database. | 0.13467974993681486 -(1 row) +``` !!! @@ -245,7 +264,7 @@ This query is fast for now, but as the table scales it will greatly slow down be !!! generic -!!! code block time="10.493 ms" +!!! code_block time="10.493 ms" ```postgresql INSERT INTO text_and_embeddings (text, embedding) @@ -276,10 +295,11 @@ LIMIT 1; !!! results +``` text | cosine_distance ----------------------------------------------------------------------------------------+--------------------- The pgml.transform function is a PostgreSQL function for calling LLMs in the database. | 0.13467974993681486 -(1 row) +``` !!! @@ -291,7 +311,7 @@ IVFFlat indexes clusters the table into sublists, and when searching, only searc !!! generic -!!! code block time="10.493 ms" +!!! code_block time="10.493 ms" ```postgresql CREATE INDEX ON text_and_embeddings USING ivfflat (embedding vector_cosine_ops) WITH (lists = 10); @@ -305,7 +325,7 @@ Now let's try searching again. !!! generic -!!! code block time="10.493 ms" +!!! code_block time="10.493 ms" ```postgresql WITH embedded_query AS ( @@ -332,10 +352,11 @@ LIMIT 1; !!! results +``` text | cosine_distance ----------------------------------------------------------------------------------------+--------------------- The pgml.transform function is a PostgreSQL function for calling LLMs in the database. | 0.13467974993681486 -(1 row) +``` !!! @@ -349,7 +370,7 @@ HNSW indexes typically have better and faster recall but require more compute wh !!! generic -!!! code block time="10.493 ms" +!!! code_block time="10.493 ms" ```postgresql DROP index text_and_embeddings_embedding_idx; @@ -365,7 +386,7 @@ Now let's try searching again. !!! generic -!!! code block time="10.493 ms" +!!! code_block time="10.493 ms" ```postgresql WITH embedded_query AS ( @@ -392,10 +413,11 @@ LIMIT 1; !!! results +``` text | cosine_distance ----------------------------------------------------------------------------------------+--------------------- The pgml.transform function is a PostgreSQL function for calling LLMs in the database. | 0.13467974993681486 -(1 row) +``` !!! From 068af92a9e2fb3ea44912e812109eb0dcfc50673 Mon Sep 17 00:00:00 2001 From: SilasMarvin <19626586+SilasMarvin@users.noreply.github.com> Date: Fri, 14 Jun 2024 09:58:00 -0700 Subject: [PATCH 03/20] Ready for review --- .../.gitbook/assets/cosine_similarity.png | Bin 0 -> 140149 bytes ...mantic-search-in-postgres-in-15-minutes.md | 84 +++++++++--------- 2 files changed, 44 insertions(+), 40 deletions(-) create mode 100644 pgml-cms/blog/.gitbook/assets/cosine_similarity.png diff --git a/pgml-cms/blog/.gitbook/assets/cosine_similarity.png b/pgml-cms/blog/.gitbook/assets/cosine_similarity.png new file mode 100644 index 0000000000000000000000000000000000000000..7704ac84bdb5c27c469cf6d9fd93dab47a3b4554 GIT binary patch literal 140149 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%0Dl(OjndE)DQj5?Pd`$fu< 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 Learn how to implement semantic search in postgres with nothing but SQL. featured: true -image: ".gitbook/assets/image (2) (2).png" tags: ["Engineering"] --- @@ -35,7 +34,7 @@ Embeddings are vectors. Given some text and some embedding model, we can convert !!! generic -!!! code_block time="10.493 ms" +!!! code_block time="19.080 ms" ```postgresql SELECT pgml.embed('mixedbread-ai/mxbai-embed-large-v1', 'test'); @@ -53,7 +52,7 @@ SELECT pgml.embed('mixedbread-ai/mxbai-embed-large-v1', 'test'); !!! -Above we used the pgml.embed SQL function to generate an embedding using the `mixedbread-ai/mxbai-embed-large-v1` model. +Above we used the `pgml.embed` SQL function to generate an embedding of the word `test` using the `mixedbread-ai/mxbai-embed-large-v1` model. The output size of the vector varies per model. This specific model outputs vectors with 1024 dimensions. This means each vector contains 1024 floating point numbers. @@ -120,7 +119,7 @@ Document2: I think tomatos are incredible on burgers. !!! -And a user is looking for the answer to the question: `What is the pgml.transform function?`. If we embed the user query and all of the documents using a model like `mixedbread-ai/mxbai-embed-large-v1`, we can compare the query embedding to all of the document embeddings, and select the document that has the closest embedding as the answer. +And a user is looking for the answer to the question: `What is the pgml.transform function?`. If we embed the user query and all of the documents using a model like `mixedbread-ai/mxbai-embed-large-v1`, we can compare the query embedding to all of the document embeddings, and select the document that has the closest embedding to the answer. These are big embeddings, and we can’t simply eyeball which one is the closest. How do we actually measure the similarity / distance between different vectors? There are four popular methods for measuring the distance between vectors available in PostgresML: - L2 distance @@ -130,11 +129,11 @@ These are big embeddings, and we can’t simply eyeball which one is the closest For most use cases we recommend using the cosine distance as defined by the formula: -INSERT IMAGE +

cosine similarity formula
Where A and B are two vectors. -This is a somewhat confusing formula but lucky for us pgvector provides an operator that computes this for us. +This is a somewhat confusing formula but luckily for us pgvector provides an operator that computes the cosine distance for us. !!! generic @@ -158,8 +157,12 @@ SELECT '[1,2,3]'::vector <=> '[2,3,4]'::vector; !!! +!!! note + The other distance functions have similar formulas and also provide convenient operators to use. It may be worth testing the other operators and seeing which performs better for your use case. For more information on the other distance functions see our guide on [embeddings](https://postgresml.org/docs/guides/embeddings/vector-similarity). +!!! + Back to our search example outlined above, we can compute the cosine distance between our query embedding and our documents. !!! generic @@ -176,11 +179,11 @@ SELECT pgml.embed('mixedbread-ai/mxbai-embed-large-v1', 'What is the pgml.transf !!! results ```text - cosine_distance +cosine_distance -------------------- 0.1114425936213167 - cosine_distance +cosine_distance -------------------- 0.7383001059221699 ``` @@ -222,7 +225,7 @@ We can search this table using the following query: !!! generic -!!! code_block time="10.493 ms" +!!! code_block time="19.864 ms" ```postgresql WITH embedded_query AS ( @@ -237,11 +240,7 @@ SELECT FROM embedded_query) <=> text_and_embeddings.embedding cosine_distance FROM text_and_embeddings -ORDER BY - text_and_embeddings.embedding <=> ( - SELECT - embedding - FROM embedded_query) +ORDER BY cosine_distance LIMIT 1; ``` @@ -259,18 +258,31 @@ LIMIT 1; !!! -This query is fast for now, but as the table scales it will greatly slow down because we have not indexed the vector column. +This query is fast for now, but as the table scales it will greatly slow down because we have not indexed the embedding column. +Let's insert 100,000 embeddings. !!! generic -!!! code_block time="10.493 ms" +!!! code_block ```postgresql INSERT INTO text_and_embeddings (text, embedding) SELECT md5(random()::text), pgml.embed('mixedbread-ai/mxbai-embed-large-v1', md5(random()::text)) -FROM generate_series(1, 10000); +FROM generate_series(1, 100000); +``` + +!!! + +!!! + +Now let's try our search again. + +!!! generic + +!!! code_block time="105.917 ms" +```postgresql WITH embedded_query AS ( SELECT pgml.embed('mixedbread-ai/mxbai-embed-large-v1', 'What is the pgml.transform function?', '{"prompt": "Represent this sentence for searching relevant passages: "}')::vector embedding @@ -283,11 +295,7 @@ SELECT FROM embedded_query) <=> text_and_embeddings.embedding cosine_distance FROM text_and_embeddings -ORDER BY - text_and_embeddings.embedding <=> ( - SELECT - embedding - FROM embedded_query) +ORDER BY cosine_distance LIMIT 1; ``` @@ -305,13 +313,13 @@ LIMIT 1; !!! -This somewhat less than ideal performance can be fixed by indexing the vector column. There are two types of indexes available in pgvector: IVFFlat and HNSW. +This somewhat less than ideal performance can be fixed by indexing the embedding column. There are two types of indexes available in pgvector: IVFFlat and HNSW. -IVFFlat indexes clusters the table into sublists, and when searching, only searches over a fixed number of the sublists. For example in the case above, if we were to add an IVFFlat index with 10 lists: +IVFFlat indexes clusters the table into sublists, and when searching, only searches over a fixed number of the sublists. In the case above, if we were to add an IVFFlat index with 10 lists: !!! generic -!!! code_block time="10.493 ms" +!!! code_block time="4989.398 ms" ```postgresql CREATE INDEX ON text_and_embeddings USING ivfflat (embedding vector_cosine_ops) WITH (lists = 10); @@ -321,11 +329,11 @@ CREATE INDEX ON text_and_embeddings USING ivfflat (embedding vector_cosine_ops) !!! -Now let's try searching again. +And search again: !!! generic -!!! code_block time="10.493 ms" +!!! code_block time = "29.191" ```postgresql WITH embedded_query AS ( @@ -340,11 +348,7 @@ SELECT FROM embedded_query) <=> text_and_embeddings.embedding cosine_distance FROM text_and_embeddings -ORDER BY - text_and_embeddings.embedding <=> ( - SELECT - embedding - FROM embedded_query) +ORDER BY cosine_distance LIMIT 1; ``` @@ -362,7 +366,7 @@ LIMIT 1; !!! -We can see it is about a 10x speedup because we are only searching over 1/10th of the original vectors. +We can see it is a massive speedup because we are only searching over 1/10th of the original vectors! HNSW indexes are a bit more complicated. It is essentially a graph with edges linked by proximity in the vector space. For more information you can check out this [writeup](https://www.pinecone.io/learn/series/faiss/hnsw/). @@ -370,7 +374,7 @@ HNSW indexes typically have better and faster recall but require more compute wh !!! generic -!!! code_block time="10.493 ms" +!!! code_block ```postgresql DROP index text_and_embeddings_embedding_idx; @@ -386,7 +390,7 @@ Now let's try searching again. !!! generic -!!! code_block time="10.493 ms" +!!! code_block time="20.270 ms" ```postgresql WITH embedded_query AS ( @@ -401,11 +405,7 @@ SELECT FROM embedded_query) <=> text_and_embeddings.embedding cosine_distance FROM text_and_embeddings -ORDER BY - text_and_embeddings.embedding <=> ( - SELECT - embedding - FROM embedded_query) +ORDER BY cosine_distance LIMIT 1; ``` @@ -424,3 +424,7 @@ LIMIT 1; !!! That was even faster! + +There is a lot more that can go into semantic search. Stay tuned for a follow up post on hybrid search and re-ranking. + +If you have any questions, or just have an idea on how to make PostgresML better, we'd love to hear from you in our [Discord](https://discord.com/invite/DmyJP3qJ7U). We’re open source, and welcome contributions from the community, especially when it comes to the rapidly evolving ML/AI landscape. From a9148da8fcc56bfb40b25fda322c7c409d54bb30 Mon Sep 17 00:00:00 2001 From: SilasMarvin <19626586+SilasMarvin@users.noreply.github.com> Date: Mon, 17 Jun 2024 14:30:35 -0700 Subject: [PATCH 04/20] Cleanup first paragraph --- pgml-cms/blog/semantic-search-in-postgres-in-15-minutes.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pgml-cms/blog/semantic-search-in-postgres-in-15-minutes.md b/pgml-cms/blog/semantic-search-in-postgres-in-15-minutes.md index 6941b3762..a31fcc086 100644 --- a/pgml-cms/blog/semantic-search-in-postgres-in-15-minutes.md +++ b/pgml-cms/blog/semantic-search-in-postgres-in-15-minutes.md @@ -20,11 +20,11 @@ June 15, 2024 ## What is and is not Semantic Search -Semantic search is a new form of machine learning powered search that doesn’t rely on any form of keyword matching, but transforms text into embeddings and performs nearest neighbors search. +Semantic search uses machine learning to understand the meaning of text by converting it into numerical vectors, allowing for more accurate and context-aware search results. -It is not a complete replacement for full text search. In many cases full text search is capable of outperforming semantic search. Specifically, if a user knows the exact phrase in a document they want to match, full text search is faster and guaranteed to return the correct result while semantic search is only likely to return the correct result. Full text search and semantic search can be combined to create powerful and robust search systems. +It is not a complete replacement for full-text search. In many cases, full-text search can outperform semantic search. Specifically, if a user knows the exact phrase they want to match in a document, full-text search is faster and guaranteed to return the correct result, whereas semantic search is only likely to return the correct result. Full-text search and semantic search can be combined to create powerful and robust search systems. -Semantic search is not just for machine learning engineers. The actual system behind semantic search is relatively easy to implement and thanks to new Postgres extensions like pgml and pgvector, is readily available to SQL developers. Just as it is expected for modern SQL developers to be familiar with and capable of implementing full text search, soon SQL developers will be expected to implement semantic search. +Semantic search is not just for machine learning engineers. The system behind semantic search is relatively easy to implement, and thanks to new Postgres extensions like pgml and pgvector, it is readily available to SQL developers. Just as modern SQL developers are expected to be familiar with and capable of implementing full-text search, they will soon be expected to implement semantic search as well. ## Embeddings 101 From 3e0fa336870465514c6c8c40e4c9445b4ba98161 Mon Sep 17 00:00:00 2001 From: Lev Kokotov Date: Mon, 17 Jun 2024 15:25:51 -0700 Subject: [PATCH 05/20] A few suggestions (#1536) --- ...mantic-search-in-postgres-in-15-minutes.md | 177 +++++++++++------- 1 file changed, 106 insertions(+), 71 deletions(-) diff --git a/pgml-cms/blog/semantic-search-in-postgres-in-15-minutes.md b/pgml-cms/blog/semantic-search-in-postgres-in-15-minutes.md index a31fcc086..ce4abb919 100644 --- a/pgml-cms/blog/semantic-search-in-postgres-in-15-minutes.md +++ b/pgml-cms/blog/semantic-search-in-postgres-in-15-minutes.md @@ -1,7 +1,6 @@ - --- description: >- - Learn how to implement semantic search in postgres with nothing but SQL. + Learn how to implement semantic search in PostgreSQL with nothing but SQL. featured: true tags: ["Engineering"] --- @@ -18,13 +17,13 @@ Silas Marvin June 15, 2024 -## What is and is not Semantic Search +## What is and is not semantic search Semantic search uses machine learning to understand the meaning of text by converting it into numerical vectors, allowing for more accurate and context-aware search results. It is not a complete replacement for full-text search. In many cases, full-text search can outperform semantic search. Specifically, if a user knows the exact phrase they want to match in a document, full-text search is faster and guaranteed to return the correct result, whereas semantic search is only likely to return the correct result. Full-text search and semantic search can be combined to create powerful and robust search systems. -Semantic search is not just for machine learning engineers. The system behind semantic search is relatively easy to implement, and thanks to new Postgres extensions like pgml and pgvector, it is readily available to SQL developers. Just as modern SQL developers are expected to be familiar with and capable of implementing full-text search, they will soon be expected to implement semantic search as well. +Semantic search is not just for machine learning engineers. The system behind semantic search is relatively easy to implement, and thanks to new Postgres extensions like _pgml_ and _pgvector_, it is readily available to SQL developers. Just as modern SQL developers are expected to be familiar with and capable of implementing full-text search, they will soon be expected to implement semantic search as well. ## Embeddings 101 @@ -34,10 +33,10 @@ Embeddings are vectors. Given some text and some embedding model, we can convert !!! generic -!!! code_block time="19.080 ms" +!!! code_block time="14.125 ms" ```postgresql -SELECT pgml.embed('mixedbread-ai/mxbai-embed-large-v1', 'test'); +SELECT pgml.embed('mixedbread-ai/mxbai-embed-large-v1', 'Generating embeddings in Postgres is fun!'); ``` !!! @@ -45,31 +44,31 @@ SELECT pgml.embed('mixedbread-ai/mxbai-embed-large-v1', 'test'); !!! results ```text -{0.100442916,0.19116051,0.031572577,0.4392428,-0.4684339,0.2671335,0.000183642,0.402029,0.52400684,0.9102567,0.3289586,0.13825876,0.060483586,-0.9315942,-0. 36723328,-0.34614784,-0.5203485,-0.0026301902,-0.4359727,-0.2078317,-0.3624561,0.151347,-1.0850769,0.03073607,-0.38463902,0.5746146,-0.065853976,-0.02959722, 0.3181093,0.60477436,-0.20975977,-0.112029605,0.32066098,-0.92783266,0.17003687,-0.6294021,0.94078255,-0.32587636,-0.06733026,-0.41903132,-0.29940385,-0.0473 7147,0.7173805,-0.4461215,-1.2713308,-0.44129986,-0.46632472,-0.89888424,-0.22231846,-0.34233224,0.09881798,0.17341912,0.27128124,-0.7020756,0.113429464,-0.2 2964618,-0.22798245,0.1105072,-0.8441625,1.238711,0.8674123,-0.14600402,0.391594,-0.9928256,0.24864249,-0.11477054,0.23513256,-0.366138,-0.13302355,-0.449127 64,-0.45309332,0.4775117,-0.19158679,-0.6634198,-0.21402365,0.2285473,0.09665201,0.47793895,-0.456355,0.33732873,0.2820914,0.17230554,0.14925064,0.23560016,- 1.2823433,-0.8188313,0.07958572,0.758208,0.39241728,-0.021326406,-0.0026611753,0.4960972,-0.5743201,-0.10779899,-0.53800243,0.11743014,0.17272875,-0.537756,0 .15774709,-0.024241826,0.75601554,0.5569049,-0.098995246,1.0593386,-0.90425104,0.3956237,0.024354521,-0.32476613,-0.5950871,-0.75371516,-0.31561607,0.0696320 8,0.6516349,0.5434117,-0.7673086,0.7324196,0.15175763,1.1354275,-0.56910944,-0.09738582,0.35705066,0.018214416,-0.091416046,-0.19074695,-0.34592274,-0.115972 71,-0.5033031,0.6735635,-0.05835747,-0.21572702,-0.58285874,0.095334634,0.8742985,0.6349386,0.4706169,-0.029405594,-0.50637966,0.4569466,0.2924249,-0.9321604 ,0.34013036,1.1258447,-0.28096777,1.2910426,0.32090122,0.5956652,0.22290495,0.08063537,-0.3783538,0.71436244,-0.90230185,-0.4399799,0.24639784,0.3069413,-0.4 8032463,0.27206388,-0.43469447,-0.2339563,0.12732148,0.22685277,-0.7924011,0.3359629,-0.30172998,0.43736732,-0.521733,1.324045,-0.28834093,-0.15974034,0.2684 1715,-0.33593872,0.73629487,-0.1049053,0.16749644,0.3264093,-0.101803474,0.22606595,1.2974273,0.22830595,0.39088526,0.4486965,-0.57037145,-0.09293561,-0.0394 99372,0.47220317,0.74698365,0.2392712,0.23049277,-0.52685314,-0.5007666,-0.03302433,-0.2098883,0.47145832,-0.6392486,0.58358306,-0.15019979,0.32308426,-0.506 2344,-0.16731891,-0.55598915,-1.7701503,-0.3798853,0.54786783,-0.71652645,-0.1773671,0.2289979,-1.0015582,0.5309544,0.81240565,-0.17937969,-0.3966378,0.60281 52,0.8962739,-0.176342,-0.010436469,0.02249392,0.09129296,-0.105494745,0.970157,-0.26875457,0.10241943,0.6148784,-0.35458624,0.5211534,0.61402124,0.48477444, -0.16437246,-0.28179103,1.2025942,-0.22813843,-0.09890138,0.043852188,1.0050704,-0.17958593,1.3325024,0.59157765,0.4212076,1.0721352,0.095619194,0.26288888,0 .42549643,0.2535346,0.35668525,0.82613224,0.30157906,-0.567903,0.32422608,-0.046756506,0.08393835,-0.31040233,0.7402205,0.7880251,0.5210311,1.0603857,0.41067 ,-0.3616352,-0.25297007,0.97518605,0.85333014,0.16857263,0.040858276,0.09388767,-0.19449936,0.38802677,0.164354,-0.017545663,0.15570965,-0.31904343,0.2223094 4,0.6248201,-0.5483591,-0.36983973,-0.38050756,-1.925645,-1.037537,-0.6157122,-0.53581315,0.2836596,-0.643354,0.07323658,-0.93136156,-0.20392789,-0.72027314, -0.33667037,0.91866046,0.23589604,0.9972664,-0.29671007,0.08811699,0.24376874,0.82748646,-0.604533,-0.67664343,-0.32924688,-0.37375167,0.33761302,-0.19614917 ,-0.21015668,0.46505967,-0.28253073,-1.0112746,1.1360632,0.8825793,-1.0680563,0.0655969,-1.034352,0.5267044,0.91949135,-0.031119794,0.60942674,0.54940313,-0. 3630888,0.44943437,0.66361815,0.073895305,-0.59853613,0.18480797,0.49640504,-0.13335773,-0.66213644,0.08816239,-0.52057326,-0.48232892,-0.2665552,-0.10339267 ,-0.30988455,0.46449667,-0.022207903,-1.6161236,0.27622652,-0.5909109,-1.0504522,0.052266315,-0.66712016,1.038967,-0.21038829,-0.30632204,-0.63056785,-0.0326 83205,0.8322449,0.43663988,0.8234027,-0.69451404,-0.29506078,0.8947272,0.36536238,-0.06769319,-0.21281374,0.1542073,-1.0177101,0.1798313,-0.38755146,0.353291 33,-0.1736927,0.2708998,0.36253256,0.55142975,-0.25388694,0.2749728,1.0570686,0.14571312,0.14165263,-0.18871498,0.2701316,0.6352345,-0.1975502,-1.0767723,-0. 0899109,0.06417123,0.16973273,-1.4618999,0.75780666,-0.37219298,0.34675384,-0.21044762,0.3230924,-0.59562063,0.57655936,-0.24317324,0.4706862,-1.0036217,0.27 595782,-0.18632303,-0.024258574,0.36281094,0.72106606,0.4534661,0.10037945,0.49504414,-0.9208432,-0.8387544,-0.17667033,0.44228357,0.36593667,-0.3061421,-1.2 638136,-1.1484053,0.5236616,0.020920075,0.2590868,-0.017210491,0.48833278,-0.34420222,0.35703135,1.0728164,-0.51129043,0.0902225,-0.42320332,0.19660714,-0.28 81061,-0.15664813,-0.99245757,0.06579208,-1.5574676,0.16405283,0.46488675,-0.15788008,-1.01791,0.84872925,0.035253655,0.40218765,-0.59924084,-0.2960986,-0.27 4478,-0.17835106,0.6479293,-0.42014578,-0.15515843,-0.62578845,0.2247606,1.153755,-0.033114456,-0.8774578,-0.021032173,-0.54359645,-1.0827168,-0.4298837,0.39 979023,-0.031404667,-0.25790605,-0.55900896,0.85690576,-0.23558116,-0.64585954,-0.18867598,-0.016098155,-0.021867584,0.5298315,0.65620464,-0.45029733,-1.0737 212,-0.25292996,-1.8820043,0.78425264,0.049297262,0.033368483,-0.13924618,-0.08540384,0.26575288,0.3641497,-0.5929729,0.012706397,-0.14115371,0.7092089,-0.29 87519,-0.50846523,1.1529989,-0.007935073,-0.39666718,0.66540664,-0.43792737,-0.14657505,0.013367422,0.59577924,-0.31825122,0.3546381,0.11212899,0.5804333,-0. 72722685,-0.58012086,-0.25618848,-0.3021478,0.3090123,0.39833462,-0.1964222,-1.0031091,-0.7377774,-0.37093028,-0.268894,-0.16332497,0.8644577,0.5592706,0.175 96069,-0.28468367,-0.11259709,-0.3321775,0.12905857,-0.4623798,-0.2466813,-0.39571014,0.8273027,0.3286372,-0.42084447,-0.6982525,0.51819134,-0.4211214,-0.450 2746,-0.58659184,0.9362978,-0.24028268,-0.07863556,0.03276802,0.31117234,-0.61217594,0.29426676,0.5394515,0.096639164,-0.17290285,-0.100368135,-1.1184332,0.6 5379685,0.21017732,-0.48588675,-0.42309326,0.78154176,0.11492857,0.9659768,0.85164833,-0.510996,-0.4957692,-1.0045063,0.41195333,-0.25961345,-0.06390433,-0.8 0765647,-0.5750627,-0.004215756,0.6570266,0.021791002,-0.2851547,0.33010367,-1.0438038,0.64198446,-0.3170377,-0.21503314,-0.7744095,0.34140953,-0.123576835,1 .2228137,0.3193732,0.097345576,0.013826016,0.490495,0.16021223,0.3592192,-0.64754117,-1.2467206,0.20728661,-0.040293045,0.18149051,-0.3889212,-1.2567233,-0.2 7512074,-0.8875311,-0.4562278,-0.14274459,-0.7154212,-0.9517362,-0.42942467,0.34255636,-0.25662944,-0.071650766,-0.2570997,0.97032154,0.55209476,0.9512633,-0 .78840256,-0.87641865,-0.31667447,-1.1845096,0.61095214,-0.4934745,-0.090470426,-0.8589016,0.16191132,1.3353379,0.36014295,0.6354017,1.6015769,-0.15028308,-0 .35953638,-0.46233898,0.7056889,0.44098303,-0.2561036,-0.38414526,-0.85254925,-0.35759622,0.32756907,-1.1055855,-0.9486638,-0.75697213,-0.18819816,0.91543293 ,0.046453375,0.8660134,-0.7937197,-0.72757536,-0.8235348,0.8263684,0.84975964,0.6188537,1.0370533,0.8713266,-0.17223643,0.74872315,-0.087729014,0.027644658,- 0.41663802,0.86366785,0.45966265,-0.7807239,0.72492,0.7516153,-1.4882706,-0.7965106,0.44769654,0.04745266,-0.3665682,-1.1761265,-0.16592331,-0.49482864,-0.18 829915,0.079323344,0.5283898,-0.25911674,0.49787715,0.040334962,0.6457638,-0.9161095,-0.52021873,0.3950836,-0.8869649,0.61957175,-0.8694589,-0.14945404,0.331 69168,0.2645687,0.45321828,-0.20752133,-0.00011348105,0.7114366,0.36253646,0.94113743,0.27327093,-0.279275,0.74158365,-0.7394054,-0.9920889,-0.5790354,0.4460 0168,0.6965152,0.055897847,-0.7247457,-0.23232944,1.0741904,-0.103388265,0.3405134,-0.6539511,-0.51377046,-0.7043648,-0.61793834,-0.7072252,-0.34909388,-0.05 701723,0.6294965,-0.30765653,0.03854165,0.032257613,0.8844775,-0.12016908,0.45807433,-0.8181472,0.5738447,-0.08459999,-0.5052286,-0.322389,0.16923045,-0.5340 384,0.82369304,-0.6654957,0.09066754,0.23323251,0.75676244,-0.07526736,0.18891658,-0.58411753,-0.5459881,0.31472534,0.22671345,0.15036865,0.5497431,0.6759999 4,-0.17044614,0.3315073,-0.07908476,0.3493545,-1.3477355,0.56133074,0.6158089,-0.15612105,-0.15391739,-1.6920619,-0.45604506,-0.9460573,0.1832847,-0.9812012, -0.037437357,0.23665366,0.20942298,0.12745716,0.3055677,0.4899028,0.1521983,-0.4412764,0.44380093,-0.24363151,0.049277242,-0.03479184,0.34719813,0.34336445,0 .44446415,-0.2509871,-0.07174216,0.16965394,0.40415984,-0.50963897,-0.4655299,0.59960693,-0.3961361,0.17242691,0.71643007,-0.012265541,0.07691683,1.2442924,0 .22043933,-1.2103993,0.61401594,-0.541842,-0.33357695,0.3074923,0.065326504,-0.27286193,0.6154859,-0.69564784,-0.11709106,-0.1545567,-0.11896704,-0.007217975 ,0.23488984,0.5601741,0.4612949,-0.28685024,-0.01752333,0.09766184,1.3614978,-0.9316589,-0.62082577,-0.17708167,-0.14922938,0.6017379,0.20790131,-0.17358595, 0.51986843,-0.8632079,-0.23630512,0.5615771,0.12942453,-0.55579686,-0.28877118,-0.023886856,0.6346819,0.11919484,0.112735756,-0.2105418,-1.0274605,-0.2215069 7,0.6296189,0.528352,-0.27940798,0.5474754,0.14160539,0.38373166,0.5457794,-0.7958526,-0.53057015,1.2145486,0.12005539,0.9229809,0.11178251,0.35618028,0.8680 126,-0.14047255,-0.022312704,0.6335968,0.22576317,0.63063693,0.077043116,-0.3592758,0.14797379,0.37010187,-0.14920035,-0.303325,-0.68384075,-0.22196415,-0.48 251563,0.085435614,1.0682561,-0.28910154,0.0547357,-0.49188855,0.07103363,0.23165464,0.7919816,-0.31917652,-0.11256474,0.22344519,0.202349,-0.042141877,0.487 33395,-0.6330437,0.18770827,-0.8534354,0.24361372,0.05912281,-0.14594407,-0.3065622,-0.13557081,-1.4080516,0.60802686,0.7874556,-0.8090863,0.5354539,-0.86377 89,-0.2529881,-0.76151496,0.39836842,-0.3637328,0.16363671,0.5599722,-0.24072857,0.09546083,0.831411,0.09562837,0.31388548,0.103111275,1.1427172,0.694476,0.9 3155265,0.64801776,-0.33954978,-0.0988641,0.473648,-0.2811673,-0.3996959,-0.33468047,-0.21153395,0.886874,-0.8678805,-0.10753187,-0.19310957,0.4603335,-0.122 70494,-1.0267254,-0.53114897,0.004987782,-0.7938769,0.40439928,0.4829653,1.5288875,0.6414294,-0.6214873,-0.65656304,0.47653323,0.16301247,-0.12008583,1.03255 62,0.13527338,-0.927417,-0.35502926,-0.17070319,-0.0011159402,0.15795147,-0.3817831,-0.99539477,0.44974712,0.623257,0.032141224,0.20115706,-0.753747,-0.03541 0736,0.317427,0.7414546,-0.41621342,1.4412553,0.088434115,-0.29406205,0.019276256,-0.66831887,0.39378297,-0.15091878,-0.33501017,0.012463322,0.26902023,-0.85 676277,-0.08205583,-0.13279751,0.8540507,-0.07071759,0.67416996,-1.0808998,-0.7537985,-1.1090854,-0.42881688,-0.545489,1.0022873,-0.34716064,-0.3511107,0.611 6534,-1.0079868,3.7511525,0.4171535,0.504542,-0.051603127,-0.071831375,0.44832432,-0.21127303,-0.57512856,-0.19024895,0.23094098,0.16914046,0.21540225,-0.077 53263,0.19773084,0.8750281,0.55822086,-0.46648705,-0.44413725,0.23833762,-0.6311006,-0.5150255,0.014071045,-0.043874096,0.40925947,-0.082470596,0.4262907,1.2 440436,-0.123832524,-0.09172271,-0.42539525,1.0193819,-0.20638897,-0.055872787,-0.12540375,-0.058966316,0.73125196,0.3050278,0.25579217,0.118471175,-0.148029 91,-0.33583203,0.11730125,1.5576597,-0.17712794,-0.2750745,0.11848973,-0.48632467,0.8594597,0.21705948,-0.04919338,0.8793258,-0.6851242,1.2830902,-0.226695,- 1.6696168,-0.4619705,-0.080957085,-0.53974324,-0.77588433,0.103437446,0.015129212,0.2896572,-0.28889287,-0.266523,-0.5023567,-0.0604841,0.57056016,0.5261334, -0.18631883,-0.5122663,-0.055830136,0.56574637,-0.5704402,-0.4263674,0.24019304,0.082071595,-0.31298077,0.30196336,-0.011113114,-0.5608543,0.3951217,-0.26592 582,0.41811758,-0.7411703,0.30873746,0.5664615,-0.98191136,-0.49090472,-1.0648257,0.97027993,0.9559882,-0.019431114,-0.07921166,-0.120092966,-0.13082835} +{-0.12269165,0.79433846,0.1909454,-0.8607215,-0.5526149,-0.48317516,0.48356333,0.40197256,0.6542712,0.20637313,0.68719935,-0.11798598,0.3924242,-0.3669872,-0.37829298,-0.57285887,-0.42399693,-0.57672346,-0.5584913,-0.25157344,-0.26103315,0.8435066,-1.3652948,-0.060239665,0.053472117,0.61965233,0.70429814,0.21168475,2.1243148,0.54657197,0.44898787,0.5141667,0.25056657,-0.7296713,-0.21511579,-0.26193422,0.18050511,0.42497447,0.10701023,-0.47321296,0.88108975,-0.23380123,0.097806804,-0.7617625,-1.7238936,0.0734859,0.5393925,0.08824284,0.6490631,-0.6999467,-0.04020539,0.34580526,-0.22457539,-0.1596002,0.30769205,0.10054478,-0.21030527,-0.6795052,-0.49133295,0.64051557,0.729387,-0.28649548,0.6304755,-1.2938358,0.18542609,-0.1447736,0.26269862,-0.7243509,-0.3743654,0.32034853,-0.033665977,-0.101480104,-0.40238166,-0.13823868,-0.08293891,0.18822464,0.614725,-0.51620704,-0.9493647,0.34618157,-0.045119785,0.5292574,0.24998534,0.50182945,-0.66819376,-0.69498116,1.0365546,0.7618454,0.22734495,-0.3371644,0.18830177,0.65933335,0.90198004,0.62203044,-0.18297921,0.80193377,-0.3250604,0.7243765,0.42883193,0.21042423,-0.01517533,0.5617572,-0.1593908,0.25845265,-0.07747603,0.4637758,0.3156056,-0.8067281,0.20704024,0.26316988,0.26273122,-0.32277155,0.16489738,-0.025123874,-0.8421937,0.42238364,-0.20360216,0.7395353,-0.28297424,-0.58514386,-1.1276962,-0.57587785,0.7367427,-1.183229,-0.17403314,-1.3642671,0.06204233,0.83101535,-0.8367251,0.4434241,0.13569412,-0.5018109,-0.24702606,0.2925449,-0.30402657,0.30018607,-0.8272239,0.7552851,0.71613544,-0.5800097,0.4300131,-0.3769249,0.15121885,1.4300121,-0.70190847,-0.014502372,1.1501042,-0.91252214,-1.299539,1.5988679,0.29511172,-0.3301541,0.10612632,0.48639655,-0.67100185,-0.18592787,-0.0610746,-0.40246755,0.34081936,0.26820442,-0.1269026,-0.02156586,0.10375944,0.6626627,-0.18523005,0.96837664,-0.5868682,0.081125714,-0.62061644,-1.010315,-0.18992952,-0.034805447,0.3482115,0.10850326,0.7015801,1.181063,0.51085556,-0.3421162,1.1605215,0.34367874,-0.45851547,-0.23464307,0.22397688,0.5295375,-0.067920305,0.38869885,-0.764097,0.08183036,-0.74270236,0.1314034,-0.09241337,0.7889378,-0.4487391,0.2671574,-0.057286393,0.23383318,-0.64422816,0.31305853,-0.5284081,-0.8764228,-1.0072867,0.7426642,0.20632008,0.19519271,-0.20781143,-0.55022776,-0.7449971,0.8095787,-1.1823708,-0.12114787,0.7764435,-0.4102213,-0.5614735,-1.151166,0.453138,-0.124295816,-0.7787184,0.8213192,0.19523725,-0.3429081,-0.5960741,0.05939262,0.6634549,-0.10354193,-0.16674386,0.23894079,0.5281129,0.4417929,-0.052335966,0.26073328,-0.5175538,0.43219882,0.42117482,0.9145017,0.62297195,0.5059562,1.0199716,0.33026397,0.10540544,1.4194826,0.2387192,-0.24473047,-0.12635238,0.38584706,0.06950318,0.13178644,0.4950382,0.58716995,-0.22241667,0.28335956,-1.4205463,-0.37189013,-0.006335424,0.674547,-0.35189858,-0.06895771,0.33660728,0.6581518,-0.5726849,0.20706958,-0.63431185,0.55616635,-0.3150213,0.18246625,0.6179018,0.3199304,0.1705371,0.40476194,-0.49592853,-0.00519022,-0.98531955,-0.8100823,-0.58652925,0.10230886,-0.7235388,-0.6156084,0.2809807,-0.2967379,-0.3508671,-1.1141659,-0.22769807,0.08822136,-0.23333925,0.6282077,1.0215682,0.38222972,-1.1630126,0.4021485,-0.064744614,1.0170162,-0.6086199,0.32332307,0.3160495,0.37213752,0.23822482,-0.24534902,-0.35759526,0.16281769,0.20119011,-0.7505329,-0.53170776,0.52023965,0.34757367,-0.3365119,-1.090554,0.74303913,0.7576997,0.1850476,0.38377324,0.6341742,0.0035892723,0.17847057,-0.52225345,0.4744198,-0.7825479,0.85714924,1.2160783,0.05176344,-0.34153363,-0.9228027,-0.45701292,-0.31697652,0.18669243,-0.080539,-0.97618884,0.44975403,0.12266389,-1.5476696,0.10114262,0.2652986,-0.6647504,-0.11139665,0.09672374,0.3067969,0.124992974,-0.075039916,-0.945483,-0.08019136,0.33150327,0.79691124,0.32509813,-0.7345915,0.49151382,0.8019188,0.054724086,0.3824057,0.54616,-1.338427,-0.17915602,0.29255223,-0.1312647,0.17714119,0.9686431,0.5271556,-0.09237713,-0.14801571,-0.8311881,0.4603313,1.173417,-0.17329413,1.1544656,1.2609864,0.6680077,-0.7116551,-0.26211533,-0.6321865,-0.4512319,0.30350694,0.7740681,-1.0377058,0.5507171,0.08685625,-0.4665991,1.0912793,-0.4253514,-1.3324647,0.6247509,0.17459206,0.64427835,-0.1543753,-0.4854082,0.42142552,0.41042453,0.80998975,-0.025750212,0.8487763,0.29716644,-0.8283788,-0.702183,-0.15909031,-0.4065299,1.064912,-0.25737965,-0.22743805,-1.1570827,0.17145145,0.38430393,0.82506144,0.46196732,-0.101009764,0.7100557,0.37232363,0.2594003,0.19210479,0.36719602,0.75960565,-0.65713775,0.23913959,0.692282,-0.41791838,0.47484493,0.17821907,-0.60062724,0.29957938,-0.11593854,0.32937768,-0.45972684,0.01129646,0.18534593,0.62680054,-0.028435916,0.251009,-0.71900076,0.44056803,0.16914998,-1.0019057,-0.55680645,0.059508275,0.20963086,0.06784629,0.07168728,-0.93063635,-0.045650747,-0.007684426,-0.7944553,0.79666996,0.9232027,-0.0643565,0.6617379,-1.1071137,0.35533053,-0.5851006,0.7480103,0.18149409,0.42977095,0.28515843,-0.29686522,0.9553224,0.7197761,-0.6413751,-0.17099445,-0.544606,0.06221392,-0.24136083,-0.5460586,-0.40875596,-0.057024892,-0.31573594,-0.01389576,-0.010156465,0.5784532,-0.44803303,0.38007888,-0.38199085,-0.43404552,0.91768897,-0.09181415,-0.44456294,0.28143787,0.6168798,-0.34374133,0.43424013,0.39190337,-0.56925493,0.8975914,-0.27520975,0.82481575,-0.16046512,-0.21151508,0.013323051,-0.60130703,0.19633308,-0.07837379,-0.16391036,-0.80348927,-1.6232564,-0.123514965,-0.15926442,-0.9025081,0.47055957,-0.078078784,-0.30613127,1.0725194,-0.5127652,-0.26803625,0.2473333,-0.43352637,0.26197925,0.47239286,0.3917152,0.13200012,-0.021115797,-1.3560157,-0.15067065,-0.23412828,0.24189733,-0.7706759,-0.3094795,-0.17276037,0.11040486,-1.122779,-0.8549858,-0.8815358,0.36725566,0.4391438,0.14913401,-0.044919793,-0.90855205,-1.2868156,0.86806804,0.013447602,-1.3518908,-1.0878333,1.1056291,-0.6054898,0.8732615,0.090048715,0.3439396,-0.43436176,-1.4296948,0.21427931,-0.56683505,-0.7287918,-0.66875815,-1.2414092,0.14564492,0.14575684,1.6843026,-0.7691825,-0.8857156,-0.59383214,0.1526336,-0.40446484,-0.093765385,-0.57902026,0.7115043,-0.2987314,1.4434578,-0.7507225,-0.14864576,0.09993563,0.3642726,0.39022216,1.4126799,-0.39582014,-0.46609184,-0.119693935,-0.7797329,0.8846008,-0.008525363,-1.1169624,0.28791374,-0.64548826,-0.14354923,-0.9195319,0.5042809,-0.64800096,-0.566263,0.31473473,-1.3200041,0.066968784,-1.2279652,0.6596321,-0.22676139,0.05292237,-0.44841886,-0.14407255,-1.1879731,-0.9624812,0.3520917,-0.8199045,-0.23614404,0.057054248,0.2774532,0.56673276,-0.68772894,0.8464806,1.0946864,0.7181479,-0.08149687,-0.033113156,-0.45337513,0.6593971,0.040748913,0.25708768,0.2444611,-0.6291184,0.2154976,-1.0344702,-0.57461023,-0.22907877,0.20212884,1.5542895,-0.69493115,0.76096123,-0.27198875,-0.28636566,-0.80702794,-0.09504783,0.5880213,0.52442694,0.88963073,-0.113876544,0.44108576,0.5131936,-0.51199615,-0.5373556,-0.50712276,0.7119059,0.26809675,-0.624161,0.50190353,0.45905492,-0.7560234,-0.36166972,-0.11057704,-0.93385667,0.14702824,-0.5007164,0.062319282,0.14635088,-0.60926783,0.44830725,0.5508014,-0.18144712,0.8553549,0.4763656,-0.06791675,-0.7282673,0.5312333,0.29696235,-0.32435995,0.11339427,-0.3156661,0.21376118,0.101174735,0.49239466,0.31915516,0.7523039,0.015413809,1.1970537,1.2595433,0.7877007,-0.77948576,-0.07308315,-0.005401653,-0.9297423,-0.6518283,-0.5235209,-0.08294889,-0.32686272,0.81800294,0.28346354,0.23243074,1.211297,0.5740814,-0.23115727,-1.0199192,-0.11423441,-1.2686234,-0.3610325,-0.13443044,-0.09186939,-0.46258482,-0.2746501,0.039179135,-0.6018465,-0.8123009,0.65863043,-1.4951158,0.04137505,-0.39956668,-0.21086998,-0.16921428,-0.12892427,-0.07058203,0.22937924,0.1872652,0.24946518,0.06469146,0.69964784,-0.14188632,0.57223684,0.26891342,-0.27864167,-0.5591145,-0.79737157,-1.0706135,-0.2231602,-1.108503,-0.34735858,-0.032272782,-0.38188872,0.32032675,0.6364613,-0.38768604,-1.1507906,-0.913829,0.36491016,0.25496644,-0.06781126,-0.84842575,0.0793298,0.0049917502,0.07099934,-0.5054571,-0.55416757,-0.4953387,0.47616813,0.13400371,1.3912268,0.30719018,-0.16337638,0.18637846,-0.19401097,0.71916217,-0.21031788,0.61066073,-0.43263736,-0.54376316,-0.36609605,0.30756727,0.3625213,0.30662173,-0.109407134,-0.26726124,-0.10782864,-0.5728887,0.35624364,0.23127197,1.0006613,-0.18430339,0.24659279,-0.1414664,-0.9362831,-0.14328903,-0.76222867,-1.6322204,-0.23277596,1.1940688,-0.5248364,0.6987823,0.36069974,-0.38930154,0.31739354,0.8688939,0.25019056,-0.45539424,0.5829257,-0.35556546,-0.23837212,-0.74019665,-0.49967116,0.20733729,0.18190496,-0.84233344,-0.9670267,0.29291785,0.18208896,0.26272357,0.076004505,0.16490388,0.23035681,-0.05491554,-0.35777965,-0.06495173,0.84074193,-0.06649489,0.5308439,-0.27389482,0.52712023,-0.70385605,1.582289,0.3533609,0.6537309,-0.11627128,1.1282475,-0.12714477,0.61138934,1.0615714,0.6239467,0.54578096,-0.56903726,-0.09996867,0.29148775,0.4719238,0.52982926,-0.122312695,-0.59448034,1.1922164,-0.102847695,0.015887707,-0.46900386,0.9373753,0.5174408,0.107704684,0.33192438,-0.73113894,-0.07725855,-0.21073207,-0.53892136,-0.41692436,0.04440565,-0.7362955,-0.18671799,-0.617404,0.11175289,-0.03757055,-0.9091465,-0.4772941,0.115955085,-0.109630615,0.27334505,-0.15329921,-0.40542892,0.6577188,-0.14270602,0.028438624,0.7158844,-0.04260146,0.14211391,0.36379516,-0.16956282,-0.32750866,0.7697329,-0.31624234,-0.81320703,-0.18005963,0.6081982,0.23052801,-0.20143141,0.24865282,-0.5117264,-0.64896625,-0.664304,0.4412688,-0.74262285,0.31758395,1.0110188,-0.0542792,-0.12961724,0.038787734,-0.019657299,0.3522628,0.88944745,0.7572078,0.4543937,0.31338966,2.1305785,0.11285806,0.9827753,0.4258123,0.46003717,0.01849649,-0.050423466,-0.7171815,-0.31475943,-0.48302308,-1.342478,0.017705658,0.3137204,0.43893284,-0.31969646,0.26008397,0.86090857,-0.9084142,0.47359383,1.2101759,0.25754166,0.071290456,-0.19756663,-0.07539108,-0.6719409,0.404817,-0.992041,0.48930237,0.83036274,-1.0315892,-0.06564829,0.00026013568,-0.43265438,-0.55953914,-0.06504767,-0.6801495,0.57494533,0.6398298,0.46862775,0.04649162,-0.70052904,-0.24009219,0.52453166,0.79875654,-0.09534484,0.82706153,0.96052814,0.1742728,0.057494655,-0.21722038,0.21895333,-0.15573184,0.5323167,-0.11215742,0.23329657,-0.566671,-0.7952302,0.31211463,0.40420142,0.32071197,-0.9692792,-0.27738753,0.35658348,-0.23604108,-0.5778135,-1.2452201,0.18487398,0.28343126,0.034852847,-0.42560938,-0.87293553,3.3916373,0.37104064,0.95921576,0.30020702,0.43176678,0.4746065,0.8066563,0.02344249,0.6768376,-1.243408,0.013419566,0.26038718,0.052325014,0.40021995,0.69684315,0.17993873,-0.6125471,0.39728552,0.1287264,-0.821042,-0.6356886,0.04368836,0.58837336,0.2951825,0.80620193,-0.55552566,-0.27555013,-0.86757773,-0.33467183,0.07901353,0.20590094,0.095205106,0.5052767,-0.3156328,-0.054386012,0.29206502,-0.26267004,-1.1437016,0.037064184,0.5587826,-0.23018162,-0.9855164,0.007280944,-0.5550629,-0.46999946,0.58497715,-0.1522534,0.4508725,0.37664524,-0.72747505,-0.52117777,-0.8577786,0.77468944,-1.2249953,-0.85298705,-0.8583468,-0.5801342,-0.817326,0.16878682,1.3681034,-0.6309237,0.42270342,-0.11961653,0.36134583,0.459141,0.24535258,0.21466772,-0.45898587,-0.20054409,-0.92821646,-0.05238323,0.17994325,0.82358634,-1.1087554,0.55523217,-0.29262337,-0.7871331,0.7758087,-0.2988389,-0.14875472,-0.731297,-0.46911976,-0.5939936,0.39334157,-0.2833826,0.64205635,-0.21212497,0.31960186,0.25826675,0.94142056,-0.15007028,0.7186352,-0.13642757,0.4422678,-0.106289506} ``` !!! !!! -Above we used the `pgml.embed` SQL function to generate an embedding of the word `test` using the `mixedbread-ai/mxbai-embed-large-v1` model. +We used the [pgml.embed](/docs/api/sql-extension/pgml.embed) PostresML function to generate an embedding of the sentence "Generating embeddings in Postgres is fun!" using the [mixedbread-ai/mxbai-embed-large-v1](https://huggingface.co/mixedbread-ai/mxbai-embed-large-v1) model from mixedbread.ai. -The output size of the vector varies per model. This specific model outputs vectors with 1024 dimensions. This means each vector contains 1024 floating point numbers. +The output size of the vector varies per model, and in _mxbai-embed-large-v1_ outputs vectors with 1024 dimensions: each vector contains 1024 floating point numbers. -The vector this model outputs is not random. It is designed to capture the semantic meaning of the text. What this really means, is that sentences that are closer together in meaning will be closer together in vector space. +The vector this model outputs is not random. It is designed to capture the semantic meaning of the text. What this really means, is that sentences which are closer together in meaning will be closer together in vector space. -Let’s look at a more simple example. Assume we have a model called `simple-embedding-model`, and it outputs vectors with 2 dimensions. Let’s embed the following three phrases: `I like Postgres`, `I like SQL`, `Rust is the best`. +Let’s look at a more simple example. Let's assume we have a model called _simple-embedding-model_, and it outputs vectors with only 2 dimensions. Let’s embed the following three phrases: "I like Postgres", "I like SQL" and "Rust is the best": !!! generic !!! code_block ```postgresql -SELECT pgml.embed('simple-embedding-model', 'I like Postgres') as embedding; +SELECT pgml.embed('simple-embedding-model', 'I like Postgres') AS embedding; -SELECT pgml.embed('simple-embedding-model', 'I like SQL') as embedding; +SELECT pgml.embed('simple-embedding-model', 'I like SQL') AS embedding; -SELECT pgml.embed('simple-embedding-model', 'Rust is the best') as embedding; +SELECT pgml.embed('simple-embedding-model', 'Rust is the best') AS embedding; ``` !!! @@ -94,34 +93,26 @@ embedding for 'Rust is the best' !!! -Notice how similar the vectors produced by the text `I like Postgres` and `I like SQL` are compared to `Rust is the best`. - -This is a simple example, but the same idea holds true when translating to real models like `mixedbread-ai/mxbai-embed-large-v1`. +You'll notice how similar the vectors produced by the text "I like Postgres" and "I like SQL" are compared to "Rust is the best". This is a artificial example, but the same idea holds true when translating to real models like _mixedbread-ai/mxbai-embed-large-v1_. -## What Does it Mean to be Close? +## What does it mean to be "close"? -We can use the idea that text that is more similar in meaning will be closer together in the vector space to perform search. +We can use the idea that text that is more similar in meaning will be closer together in the vector space to build our semantic search engine. For instance let’s say that we have the following documents: +| Document ID | Document text | +-----|----------| +| 1 | The pgml.transform function is a PostgreSQL function for calling LLMs in the database. | +| 2 | I think tomatos are incredible on burgers. | -!!! generic - -!!! code_block - -```text -Document1: The pgml.transform function is a PostgreSQL function for calling LLMs in the database. - -Document2: I think tomatos are incredible on burgers. -``` -!!! +and a user is looking for the answer to the question: "What is the pgml.transform function?". If we embed the search query and all of the documents using a model like _mixedbread-ai/mxbai-embed-large-v1_, we can compare the query embedding to all of the document embeddings, and select the document that has the closest embedding in vector space, and therefore in meaning, to the to the answer. -!!! +These are big embeddings, so we can’t simply estimate which one is closest. So, how do we actually measure the similarity (distance) between different vectors? -And a user is looking for the answer to the question: `What is the pgml.transform function?`. If we embed the user query and all of the documents using a model like `mixedbread-ai/mxbai-embed-large-v1`, we can compare the query embedding to all of the document embeddings, and select the document that has the closest embedding to the answer. +_pgvector_ as of this writing supports four different measurements of vector similarity: -These are big embeddings, and we can’t simply eyeball which one is the closest. How do we actually measure the similarity / distance between different vectors? There are four popular methods for measuring the distance between vectors available in PostgresML: - L2 distance - (negative) inner product - cosine distance @@ -131,9 +122,9 @@ For most use cases we recommend using the cosine distance as defined by the form
cosine similarity formula
-Where A and B are two vectors. +where A and B are two vectors. -This is a somewhat confusing formula but luckily for us pgvector provides an operator that computes the cosine distance for us. +This is a somewhat confusing formula but luckily _pgvector_ provides an operator that computes the cosine distance for us: !!! generic @@ -157,21 +148,34 @@ SELECT '[1,2,3]'::vector <=> '[2,3,4]'::vector; !!! -!!! note +Other distance functions have similar formulas and provide convenient operators to use as well. It may be worth testing other operators and to see which performs better for your use case. For more information on the other distance functions, take a look at our [Embeddings guide](https://postgresml.org/docs/guides/embeddings/vector-similarity). -The other distance functions have similar formulas and also provide convenient operators to use. It may be worth testing the other operators and seeing which performs better for your use case. For more information on the other distance functions see our guide on [embeddings](https://postgresml.org/docs/guides/embeddings/vector-similarity). - -!!! - -Back to our search example outlined above, we can compute the cosine distance between our query embedding and our documents. +Going back to our searchn example, we can compute the cosine distance between our query embedding and our documents: !!! generic !!! code_block ```postgresql -SELECT pgml.embed('mixedbread-ai/mxbai-embed-large-v1', 'What is the pgml.transform function?')::vector <=> pgml.embed('mixedbread-ai/mxbai-embed-large-v1', 'The pgml.transform function is a PostgreSQL function for calling LLMs in the database.')::vector as cosine_distance; -SELECT pgml.embed('mixedbread-ai/mxbai-embed-large-v1', 'What is the pgml.transform function?')::vector <=> pgml.embed('mixedbread-ai/mxbai-embed-large-v1', 'I think tomatos are incredible on burgers.')::vector as cosine_distance; +SELECT pgml.embed( + 'mixedbread-ai/mxbai-embed-large-v1', + 'What is the pgml.transform function?' +)::vector + <=> +pgml.embed( + 'mixedbread-ai/mxbai-embed-large-v1', + 'The pgml.transform function is a PostgreSQL function for calling LLMs in the database.' +)::vector AS cosine_distance; + +SELECT pgml.embed( + 'mixedbread-ai/mxbai-embed-large-v1', + 'What is the pgml.transform function?' +)::vector + <=> +pgml.embed( + 'mixedbread-ai/mxbai-embed-large-v1', + 'I think tomatos are incredible on burgers.' +)::vector AS cosine_distance; ``` !!! @@ -192,13 +196,13 @@ cosine_distance !!! -Notice that the cosine distance between `What is the pgml.transform function?` and `The pgml.transform function is a PostgreSQL function for calling LLMs in the database.` is much smaller than the cosine distance between `What is the pgml.transform function?` and `I think tomatos are incredible on burgers.`. +You'll notice that the distance between "What is the pgml.transform function?" and "The pgml.transform function is a PostgreSQL function for calling LLMs in the database." is much smaller than the cosine distance between "What is the pgml.transform function?" and "I think tomatos are incredible on burgers". -## Making it Fast! +## Making it fast! -It is inefficient to compute the embeddings for our documents for every search request. Instead, we want to embed our documents once, and search against our stored embeddings. +It is inefficient to compute embeddings for all the documents every time we search the dataset. Instead, we should embed our documents once and search against precomputed embeddings. -We can store our embedding vectors with the vector type given by pgvector. +_pgvector_ provides us with the `vector` data type for storing embeddings in regular PostgreSQL tables: !!! generic @@ -210,34 +214,52 @@ CREATE TABLE text_and_embeddings ( text text, embedding vector (1024) ); -INSERT INTO text_and_embeddings(text, embedding) + +INSERT INTO text_and_embeddings (text, embedding) VALUES - ('The pgml.transform function is a PostgreSQL function for calling LLMs in the database.', pgml.embed('mixedbread-ai/mxbai-embed-large-v1', 'The pgml.transform function is a PostgreSQL function for calling LLMs in the database.')), - ('I think tomatos are incredible on burgers.', pgml.embed('mixedbread-ai/mxbai-embed-large-v1', 'I think tomatos are incredible on burgers.')) -; + ( + 'The pgml.transform function is a PostgreSQL function for calling LLMs in the database.', + pgml.embed( + 'mixedbread-ai/mxbai-embed-large-v1', + 'The pgml.transform function is a PostgreSQL function for calling LLMs in the database.' + ) + ), + + ( + 'I think tomatos are incredible on burgers.', + pgml.embed( + 'mixedbread-ai/mxbai-embed-large-v1', + 'I think tomatos are incredible on burgers.' + ) + ); ``` !!! !!! -We can search this table using the following query: +Once our table has some data, we can search it using the following query: !!! generic !!! code_block time="19.864 ms" ```postgresql -WITH embedded_query AS ( +WITH query_embedding AS ( SELECT - pgml.embed('mixedbread-ai/mxbai-embed-large-v1', 'What is the pgml.transform function?', '{"prompt": "Represent this sentence for searching relevant passages: "}')::vector embedding + pgml.embed( + 'mixedbread-ai/mxbai-embed-large-v1', + 'What is the pgml.transform function?', + '{"prompt": "Represent this sentence for searching relevant passages: "}' + )::vector embedding ) SELECT text, ( SELECT - embedding - FROM embedded_query) <=> text_and_embeddings.embedding cosine_distance + embedding + FROM query_embedding + ) <=> text_and_embeddings.embedding cosine_distance FROM text_and_embeddings ORDER BY cosine_distance @@ -258,9 +280,9 @@ LIMIT 1; !!! -This query is fast for now, but as the table scales it will greatly slow down because we have not indexed the embedding column. +This query is fast for now, but as we add more data to the the table, it will slow down because we have not indexed the embedding column. -Let's insert 100,000 embeddings. +Let's demonstrate this by inserting 100,000 additional embeddings: !!! generic @@ -268,7 +290,12 @@ Let's insert 100,000 embeddings. ```postgresql INSERT INTO text_and_embeddings (text, embedding) -SELECT md5(random()::text), pgml.embed('mixedbread-ai/mxbai-embed-large-v1', md5(random()::text)) +SELECT + md5(random()::text), + pgml.embed( + 'mixedbread-ai/mxbai-embed-large-v1', + md5(random()::text) + ) FROM generate_series(1, 100000); ``` @@ -276,7 +303,7 @@ FROM generate_series(1, 100000); !!! -Now let's try our search again. +Now trying our search engine again: !!! generic @@ -313,27 +340,29 @@ LIMIT 1; !!! -This somewhat less than ideal performance can be fixed by indexing the embedding column. There are two types of indexes available in pgvector: IVFFlat and HNSW. +This somewhat less than ideal performance can be fixed by indexing the embedding column. There are two types of indexes available in _pgvector_: IVFFlat and HNSW. -IVFFlat indexes clusters the table into sublists, and when searching, only searches over a fixed number of the sublists. In the case above, if we were to add an IVFFlat index with 10 lists: +IVFFlat indexes clusters the table into sublists, and when searching, only searches over a fixed number of sublists. In oour example, if we were to add an IVFFlat index with 10 lists: !!! generic !!! code_block time="4989.398 ms" ```postgresql -CREATE INDEX ON text_and_embeddings USING ivfflat (embedding vector_cosine_ops) WITH (lists = 10); +CREATE INDEX ON text_and_embeddings +USING ivfflat (embedding vector_cosine_ops) +WITH (lists = 10); ``` !!! !!! -And search again: +and search again, we would get much better perfomance: !!! generic -!!! code_block time = "29.191" +!!! code_block time="29.191 ms" ```postgresql WITH embedded_query AS ( @@ -368,9 +397,9 @@ LIMIT 1; We can see it is a massive speedup because we are only searching over 1/10th of the original vectors! -HNSW indexes are a bit more complicated. It is essentially a graph with edges linked by proximity in the vector space. For more information you can check out this [writeup](https://www.pinecone.io/learn/series/faiss/hnsw/). +HNSW indexes are a bit more complicated. It is essentially a graph with edges linked by proximity in vector space. For more information you can check out this [write-up](https://www.pinecone.io/learn/series/faiss/hnsw/). -HNSW indexes typically have better and faster recall but require more compute when inserting. We recommend using HNSW indexes for most use cases. +HNSW indexes typically have better and faster recall but require more compute when adding new vectors. That being said, we recommend using HNSW indexes for most use cases. !!! generic @@ -379,14 +408,15 @@ HNSW indexes typically have better and faster recall but require more compute wh ```postgresql DROP index text_and_embeddings_embedding_idx; -CREATE INDEX ON text_and_embeddings USING hnsw (embedding vector_cosine_ops); +CREATE INDEX ON text_and_embeddings +USING hnsw (embedding vector_cosine_ops); ``` !!! !!! -Now let's try searching again. +Now let's try searching again: !!! generic @@ -395,14 +425,19 @@ Now let's try searching again. ```postgresql WITH embedded_query AS ( SELECT - pgml.embed('mixedbread-ai/mxbai-embed-large-v1', 'What is the pgml.transform function?', '{"prompt": "Represent this sentence for searching relevant passages: "}')::vector embedding + pgml.embed( + 'mixedbread-ai/mxbai-embed-large-v1', + 'What is the pgml.transform function?', + '{"prompt": "Represent this sentence for searching relevant passages: "}' + )::vector embedding ) SELECT text, ( SELECT - embedding - FROM embedded_query) <=> text_and_embeddings.embedding cosine_distance + embedding + FROM embedded_query + ) <=> text_and_embeddings.embedding cosine_distance FROM text_and_embeddings ORDER BY cosine_distance From c71fcd2e68305eacfd3195fefec3864bb34a7b2d Mon Sep 17 00:00:00 2001 From: SilasMarvin <19626586+SilasMarvin@users.noreply.github.com> Date: Mon, 17 Jun 2024 15:28:20 -0700 Subject: [PATCH 06/20] Add reason on why to use semantic search --- pgml-cms/blog/semantic-search-in-postgres-in-15-minutes.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pgml-cms/blog/semantic-search-in-postgres-in-15-minutes.md b/pgml-cms/blog/semantic-search-in-postgres-in-15-minutes.md index ce4abb919..7bf770e2e 100644 --- a/pgml-cms/blog/semantic-search-in-postgres-in-15-minutes.md +++ b/pgml-cms/blog/semantic-search-in-postgres-in-15-minutes.md @@ -21,6 +21,8 @@ June 15, 2024 Semantic search uses machine learning to understand the meaning of text by converting it into numerical vectors, allowing for more accurate and context-aware search results. +When users are unsure of the exact terms to search for, semantic search can uncover relevant information that traditional keyword searches might miss. This capability is particularly valuable for discovering content based on the intent and context of the search query, rather than relying solely on precise word matches. + It is not a complete replacement for full-text search. In many cases, full-text search can outperform semantic search. Specifically, if a user knows the exact phrase they want to match in a document, full-text search is faster and guaranteed to return the correct result, whereas semantic search is only likely to return the correct result. Full-text search and semantic search can be combined to create powerful and robust search systems. Semantic search is not just for machine learning engineers. The system behind semantic search is relatively easy to implement, and thanks to new Postgres extensions like _pgml_ and _pgvector_, it is readily available to SQL developers. Just as modern SQL developers are expected to be familiar with and capable of implementing full-text search, they will soon be expected to implement semantic search as well. From 9b6e75fddad0cc8fa329c25aee304e5e60caf6ee Mon Sep 17 00:00:00 2001 From: SilasMarvin <19626586+SilasMarvin@users.noreply.github.com> Date: Mon, 17 Jun 2024 15:38:31 -0700 Subject: [PATCH 07/20] Clean up spelling errors --- ...mantic-search-in-postgres-in-15-minutes.md | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/pgml-cms/blog/semantic-search-in-postgres-in-15-minutes.md b/pgml-cms/blog/semantic-search-in-postgres-in-15-minutes.md index 7bf770e2e..4d2b2e0ef 100644 --- a/pgml-cms/blog/semantic-search-in-postgres-in-15-minutes.md +++ b/pgml-cms/blog/semantic-search-in-postgres-in-15-minutes.md @@ -106,7 +106,7 @@ For instance let’s say that we have the following documents: | Document ID | Document text | -----|----------| | 1 | The pgml.transform function is a PostgreSQL function for calling LLMs in the database. | -| 2 | I think tomatos are incredible on burgers. | +| 2 | I think tomatoes are incredible on burgers. | and a user is looking for the answer to the question: "What is the pgml.transform function?". If we embed the search query and all of the documents using a model like _mixedbread-ai/mxbai-embed-large-v1_, we can compare the query embedding to all of the document embeddings, and select the document that has the closest embedding in vector space, and therefore in meaning, to the to the answer. @@ -130,7 +130,7 @@ This is a somewhat confusing formula but luckily _pgvector_ provides an operato !!! generic -!!! code_block +!!! code_block time="64.643 ms" ```postgresql SELECT '[1,2,3]'::vector <=> '[2,3,4]'::vector; @@ -176,7 +176,7 @@ SELECT pgml.embed( <=> pgml.embed( 'mixedbread-ai/mxbai-embed-large-v1', - 'I think tomatos are incredible on burgers.' + 'I think tomatoes are incredible on burgers.' )::vector AS cosine_distance; ``` @@ -191,14 +191,14 @@ cosine_distance cosine_distance -------------------- - 0.7383001059221699 + 0.7328613577628744 ``` !!! !!! -You'll notice that the distance between "What is the pgml.transform function?" and "The pgml.transform function is a PostgreSQL function for calling LLMs in the database." is much smaller than the cosine distance between "What is the pgml.transform function?" and "I think tomatos are incredible on burgers". +You'll notice that the distance between "What is the pgml.transform function?" and "The pgml.transform function is a PostgreSQL function for calling LLMs in the database." is much smaller than the cosine distance between "What is the pgml.transform function?" and "I think tomatoes are incredible on burgers". ## Making it fast! @@ -228,10 +228,10 @@ VALUES ), ( - 'I think tomatos are incredible on burgers.', + 'I think tomatoes are incredible on burgers.', pgml.embed( 'mixedbread-ai/mxbai-embed-large-v1', - 'I think tomatos are incredible on burgers.' + 'I think tomatoes are incredible on burgers.' ) ); ``` @@ -282,7 +282,7 @@ LIMIT 1; !!! -This query is fast for now, but as we add more data to the the table, it will slow down because we have not indexed the embedding column. +This query is fast for now, but as we add more data to the table, it will slow down because we have not indexed the embedding column. Let's demonstrate this by inserting 100,000 additional embeddings: @@ -344,7 +344,7 @@ LIMIT 1; This somewhat less than ideal performance can be fixed by indexing the embedding column. There are two types of indexes available in _pgvector_: IVFFlat and HNSW. -IVFFlat indexes clusters the table into sublists, and when searching, only searches over a fixed number of sublists. In oour example, if we were to add an IVFFlat index with 10 lists: +IVFFlat indexes clusters the table into sublists, and when searching, only searches over a fixed number of sublists. In our example, if we were to add an IVFFlat index with 10 lists: !!! generic @@ -360,7 +360,7 @@ WITH (lists = 10); !!! -and search again, we would get much better perfomance: +and search again, we would get much better performance: !!! generic From b451c9b7cbdc9a17c7c3674e2ff39d0987c662bd Mon Sep 17 00:00:00 2001 From: SilasMarvin <19626586+SilasMarvin@users.noreply.github.com> Date: Mon, 17 Jun 2024 15:52:18 -0700 Subject: [PATCH 08/20] Fix more small spelling errors --- pgml-cms/blog/semantic-search-in-postgres-in-15-minutes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pgml-cms/blog/semantic-search-in-postgres-in-15-minutes.md b/pgml-cms/blog/semantic-search-in-postgres-in-15-minutes.md index 4d2b2e0ef..ff4869c01 100644 --- a/pgml-cms/blog/semantic-search-in-postgres-in-15-minutes.md +++ b/pgml-cms/blog/semantic-search-in-postgres-in-15-minutes.md @@ -152,7 +152,7 @@ SELECT '[1,2,3]'::vector <=> '[2,3,4]'::vector; Other distance functions have similar formulas and provide convenient operators to use as well. It may be worth testing other operators and to see which performs better for your use case. For more information on the other distance functions, take a look at our [Embeddings guide](https://postgresml.org/docs/guides/embeddings/vector-similarity). -Going back to our searchn example, we can compute the cosine distance between our query embedding and our documents: +Going back to our search example, we can compute the cosine distance between our query embedding and our documents: !!! generic From d418debfdb243b1059130a709b0801c76d6331b6 Mon Sep 17 00:00:00 2001 From: SilasMarvin <19626586+SilasMarvin@users.noreply.github.com> Date: Tue, 18 Jun 2024 08:52:08 -0700 Subject: [PATCH 09/20] Finish timings --- ...mantic-search-in-postgres-in-15-minutes.md | 33 +++++++++++++------ 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/pgml-cms/blog/semantic-search-in-postgres-in-15-minutes.md b/pgml-cms/blog/semantic-search-in-postgres-in-15-minutes.md index ff4869c01..ab83f816a 100644 --- a/pgml-cms/blog/semantic-search-in-postgres-in-15-minutes.md +++ b/pgml-cms/blog/semantic-search-in-postgres-in-15-minutes.md @@ -35,7 +35,7 @@ Embeddings are vectors. Given some text and some embedding model, we can convert !!! generic -!!! code_block time="14.125 ms" +!!! code_block ```postgresql SELECT pgml.embed('mixedbread-ai/mxbai-embed-large-v1', 'Generating embeddings in Postgres is fun!'); @@ -130,7 +130,7 @@ This is a somewhat confusing formula but luckily _pgvector_ provides an operato !!! generic -!!! code_block time="64.643 ms" +!!! code_block ```postgresql SELECT '[1,2,3]'::vector <=> '[2,3,4]'::vector; @@ -206,9 +206,10 @@ It is inefficient to compute embeddings for all the documents every time we sear _pgvector_ provides us with the `vector` data type for storing embeddings in regular PostgreSQL tables: + !!! generic -!!! code_block +!!! code_block time="12.547 ms" ```postgresql CREATE TABLE text_and_embeddings ( @@ -216,7 +217,19 @@ CREATE TABLE text_and_embeddings ( text text, embedding vector (1024) ); +``` + +!!! + +!!! + +Let's add some data to our table: + +!!! generic +!!! code_block time="72.156 ms" + +```postgresql INSERT INTO text_and_embeddings (text, embedding) VALUES ( @@ -240,11 +253,11 @@ VALUES !!! -Once our table has some data, we can search it using the following query: +Now that our table has some data, we can search over it using the following query: !!! generic -!!! code_block time="19.864 ms" +!!! code_block time="35.016 ms" ```postgresql WITH query_embedding AS ( @@ -288,7 +301,7 @@ Let's demonstrate this by inserting 100,000 additional embeddings: !!! generic -!!! code_block +!!! code_block time="3114242.499 ms" ```postgresql INSERT INTO text_and_embeddings (text, embedding) @@ -309,7 +322,7 @@ Now trying our search engine again: !!! generic -!!! code_block time="105.917 ms" +!!! code_block time="138.252 ms" ```postgresql WITH embedded_query AS ( @@ -364,7 +377,7 @@ and search again, we would get much better performance: !!! generic -!!! code_block time="29.191 ms" +!!! code_block time="44.508 ms" ```postgresql WITH embedded_query AS ( @@ -405,7 +418,7 @@ HNSW indexes typically have better and faster recall but require more compute wh !!! generic -!!! code_block +!!! code_block time="115564.303" ```postgresql DROP index text_and_embeddings_embedding_idx; @@ -422,7 +435,7 @@ Now let's try searching again: !!! generic -!!! code_block time="20.270 ms" +!!! code_block time="35.716 ms" ```postgresql WITH embedded_query AS ( From 84872ac4f9db8b6770ec94d4013d01a00611d814 Mon Sep 17 00:00:00 2001 From: Silas Marvin <19626586+SilasMarvin@users.noreply.github.com> Date: Tue, 18 Jun 2024 08:53:02 -0700 Subject: [PATCH 10/20] Update pgml-cms/blog/semantic-search-in-postgres-in-15-minutes.md Co-authored-by: Montana Low --- pgml-cms/blog/semantic-search-in-postgres-in-15-minutes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pgml-cms/blog/semantic-search-in-postgres-in-15-minutes.md b/pgml-cms/blog/semantic-search-in-postgres-in-15-minutes.md index ab83f816a..d97813c86 100644 --- a/pgml-cms/blog/semantic-search-in-postgres-in-15-minutes.md +++ b/pgml-cms/blog/semantic-search-in-postgres-in-15-minutes.md @@ -25,7 +25,7 @@ When users are unsure of the exact terms to search for, semantic search can unco It is not a complete replacement for full-text search. In many cases, full-text search can outperform semantic search. Specifically, if a user knows the exact phrase they want to match in a document, full-text search is faster and guaranteed to return the correct result, whereas semantic search is only likely to return the correct result. Full-text search and semantic search can be combined to create powerful and robust search systems. -Semantic search is not just for machine learning engineers. The system behind semantic search is relatively easy to implement, and thanks to new Postgres extensions like _pgml_ and _pgvector_, it is readily available to SQL developers. Just as modern SQL developers are expected to be familiar with and capable of implementing full-text search, they will soon be expected to implement semantic search as well. +Semantic search is not just for machine learning engineers. The system behind semantic search is relatively easy to implement, and thanks to new Postgres extensions like `pgml` and `pgvector`, it is readily available to SQL developers. Just as modern SQL developers are expected to be familiar with and capable of implementing full-text search, they will soon be expected to implement semantic search as well. ## Embeddings 101 From 1686f930c4908861e2217a5e02857389c0f3a350 Mon Sep 17 00:00:00 2001 From: Silas Marvin <19626586+SilasMarvin@users.noreply.github.com> Date: Tue, 18 Jun 2024 08:54:05 -0700 Subject: [PATCH 11/20] Update pgml-cms/blog/semantic-search-in-postgres-in-15-minutes.md Co-authored-by: Montana Low --- pgml-cms/blog/semantic-search-in-postgres-in-15-minutes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pgml-cms/blog/semantic-search-in-postgres-in-15-minutes.md b/pgml-cms/blog/semantic-search-in-postgres-in-15-minutes.md index d97813c86..ecd997149 100644 --- a/pgml-cms/blog/semantic-search-in-postgres-in-15-minutes.md +++ b/pgml-cms/blog/semantic-search-in-postgres-in-15-minutes.md @@ -23,7 +23,7 @@ Semantic search uses machine learning to understand the meaning of text by conve When users are unsure of the exact terms to search for, semantic search can uncover relevant information that traditional keyword searches might miss. This capability is particularly valuable for discovering content based on the intent and context of the search query, rather than relying solely on precise word matches. -It is not a complete replacement for full-text search. In many cases, full-text search can outperform semantic search. Specifically, if a user knows the exact phrase they want to match in a document, full-text search is faster and guaranteed to return the correct result, whereas semantic search is only likely to return the correct result. Full-text search and semantic search can be combined to create powerful and robust search systems. +It is not a replacement for full-text search. In many cases, full-text search can outperform semantic search. Specifically, if a user knows the exact keywords they want to match in a document, full-text search is faster and guaranteed to return the correct result, whereas semantic search is only likely to return the correct result. Full-text search and semantic search can be combined to create powerful and robust search systems. Semantic search is not just for machine learning engineers. The system behind semantic search is relatively easy to implement, and thanks to new Postgres extensions like `pgml` and `pgvector`, it is readily available to SQL developers. Just as modern SQL developers are expected to be familiar with and capable of implementing full-text search, they will soon be expected to implement semantic search as well. From b2b9d88bba1288d1ec358b33753872a7c06fc72e Mon Sep 17 00:00:00 2001 From: Silas Marvin <19626586+SilasMarvin@users.noreply.github.com> Date: Tue, 18 Jun 2024 08:56:00 -0700 Subject: [PATCH 12/20] Update pgml-cms/blog/semantic-search-in-postgres-in-15-minutes.md Co-authored-by: Montana Low --- pgml-cms/blog/semantic-search-in-postgres-in-15-minutes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pgml-cms/blog/semantic-search-in-postgres-in-15-minutes.md b/pgml-cms/blog/semantic-search-in-postgres-in-15-minutes.md index ecd997149..be7fbeb2e 100644 --- a/pgml-cms/blog/semantic-search-in-postgres-in-15-minutes.md +++ b/pgml-cms/blog/semantic-search-in-postgres-in-15-minutes.md @@ -59,7 +59,7 @@ The output size of the vector varies per model, and in _mxbai-embed-large-v1_ ou The vector this model outputs is not random. It is designed to capture the semantic meaning of the text. What this really means, is that sentences which are closer together in meaning will be closer together in vector space. -Let’s look at a more simple example. Let's assume we have a model called _simple-embedding-model_, and it outputs vectors with only 2 dimensions. Let’s embed the following three phrases: "I like Postgres", "I like SQL" and "Rust is the best": +Let’s look at a more simple example. Let's assume we have a model called `simple-embedding-model`, and it outputs vectors with only 2 dimensions. Let’s embed the following three phrases: "I like Postgres", "I like SQL" and "Rust is the best": !!! generic From b8766bd1cdff4802f6e7bcc2d195561119bfbbd8 Mon Sep 17 00:00:00 2001 From: Silas Marvin <19626586+SilasMarvin@users.noreply.github.com> Date: Tue, 18 Jun 2024 08:56:06 -0700 Subject: [PATCH 13/20] Update pgml-cms/blog/semantic-search-in-postgres-in-15-minutes.md Co-authored-by: Montana Low --- pgml-cms/blog/semantic-search-in-postgres-in-15-minutes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pgml-cms/blog/semantic-search-in-postgres-in-15-minutes.md b/pgml-cms/blog/semantic-search-in-postgres-in-15-minutes.md index be7fbeb2e..71f5e6b4f 100644 --- a/pgml-cms/blog/semantic-search-in-postgres-in-15-minutes.md +++ b/pgml-cms/blog/semantic-search-in-postgres-in-15-minutes.md @@ -355,7 +355,7 @@ LIMIT 1; !!! -This somewhat less than ideal performance can be fixed by indexing the embedding column. There are two types of indexes available in _pgvector_: IVFFlat and HNSW. +This somewhat less than ideal performance can be fixed by indexing the embedding column. There are two types of indexes available in `pgvector`: IVFFlat and HNSW. IVFFlat indexes clusters the table into sublists, and when searching, only searches over a fixed number of sublists. In our example, if we were to add an IVFFlat index with 10 lists: From 45741834757701d0cc410b0b343a42c42f4812c1 Mon Sep 17 00:00:00 2001 From: Silas Marvin <19626586+SilasMarvin@users.noreply.github.com> Date: Tue, 18 Jun 2024 08:56:12 -0700 Subject: [PATCH 14/20] Update pgml-cms/blog/semantic-search-in-postgres-in-15-minutes.md Co-authored-by: Montana Low --- pgml-cms/blog/semantic-search-in-postgres-in-15-minutes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pgml-cms/blog/semantic-search-in-postgres-in-15-minutes.md b/pgml-cms/blog/semantic-search-in-postgres-in-15-minutes.md index 71f5e6b4f..6f5ea393d 100644 --- a/pgml-cms/blog/semantic-search-in-postgres-in-15-minutes.md +++ b/pgml-cms/blog/semantic-search-in-postgres-in-15-minutes.md @@ -410,7 +410,7 @@ LIMIT 1; !!! -We can see it is a massive speedup because we are only searching over 1/10th of the original vectors! +We can see it is a massive speedup because we are only comparing our input to 1/10th of the original vectors, instead of all of them! HNSW indexes are a bit more complicated. It is essentially a graph with edges linked by proximity in vector space. For more information you can check out this [write-up](https://www.pinecone.io/learn/series/faiss/hnsw/). From 4db21493dacefd957e296d940f2bd58af33aa465 Mon Sep 17 00:00:00 2001 From: Silas Marvin <19626586+SilasMarvin@users.noreply.github.com> Date: Tue, 18 Jun 2024 08:56:19 -0700 Subject: [PATCH 15/20] Update pgml-cms/blog/semantic-search-in-postgres-in-15-minutes.md Co-authored-by: Montana Low --- pgml-cms/blog/semantic-search-in-postgres-in-15-minutes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pgml-cms/blog/semantic-search-in-postgres-in-15-minutes.md b/pgml-cms/blog/semantic-search-in-postgres-in-15-minutes.md index 6f5ea393d..a29897f64 100644 --- a/pgml-cms/blog/semantic-search-in-postgres-in-15-minutes.md +++ b/pgml-cms/blog/semantic-search-in-postgres-in-15-minutes.md @@ -414,7 +414,7 @@ We can see it is a massive speedup because we are only comparing our input to 1/ HNSW indexes are a bit more complicated. It is essentially a graph with edges linked by proximity in vector space. For more information you can check out this [write-up](https://www.pinecone.io/learn/series/faiss/hnsw/). -HNSW indexes typically have better and faster recall but require more compute when adding new vectors. That being said, we recommend using HNSW indexes for most use cases. +HNSW indexes typically have better and faster recall but require more compute when adding new vectors. That being said, we recommend using HNSW indexes for most use cases where writes are less frequent than reads. !!! generic From 68368e275bd700bb7324dbdc7f82d8fee7900d86 Mon Sep 17 00:00:00 2001 From: Silas Marvin <19626586+SilasMarvin@users.noreply.github.com> Date: Tue, 18 Jun 2024 08:56:42 -0700 Subject: [PATCH 16/20] Update pgml-cms/blog/semantic-search-in-postgres-in-15-minutes.md Co-authored-by: Montana Low --- pgml-cms/blog/semantic-search-in-postgres-in-15-minutes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pgml-cms/blog/semantic-search-in-postgres-in-15-minutes.md b/pgml-cms/blog/semantic-search-in-postgres-in-15-minutes.md index a29897f64..4b59e4888 100644 --- a/pgml-cms/blog/semantic-search-in-postgres-in-15-minutes.md +++ b/pgml-cms/blog/semantic-search-in-postgres-in-15-minutes.md @@ -55,7 +55,7 @@ SELECT pgml.embed('mixedbread-ai/mxbai-embed-large-v1', 'Generating embeddings i We used the [pgml.embed](/docs/api/sql-extension/pgml.embed) PostresML function to generate an embedding of the sentence "Generating embeddings in Postgres is fun!" using the [mixedbread-ai/mxbai-embed-large-v1](https://huggingface.co/mixedbread-ai/mxbai-embed-large-v1) model from mixedbread.ai. -The output size of the vector varies per model, and in _mxbai-embed-large-v1_ outputs vectors with 1024 dimensions: each vector contains 1024 floating point numbers. +The output size of the vector varies per model, and in `mxbai-embed-large-v1` outputs vectors with 1024 dimensions: each vector contains 1024 floating point numbers. The vector this model outputs is not random. It is designed to capture the semantic meaning of the text. What this really means, is that sentences which are closer together in meaning will be closer together in vector space. From af8dd3edcf97bdd4cb92c06a9e8a98105c7ab04c Mon Sep 17 00:00:00 2001 From: SilasMarvin <19626586+SilasMarvin@users.noreply.github.com> Date: Tue, 18 Jun 2024 08:59:07 -0700 Subject: [PATCH 17/20] Convert italics back to backticks --- .../blog/semantic-search-in-postgres-in-15-minutes.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pgml-cms/blog/semantic-search-in-postgres-in-15-minutes.md b/pgml-cms/blog/semantic-search-in-postgres-in-15-minutes.md index 4b59e4888..132238c89 100644 --- a/pgml-cms/blog/semantic-search-in-postgres-in-15-minutes.md +++ b/pgml-cms/blog/semantic-search-in-postgres-in-15-minutes.md @@ -95,7 +95,7 @@ embedding for 'Rust is the best' !!! -You'll notice how similar the vectors produced by the text "I like Postgres" and "I like SQL" are compared to "Rust is the best". This is a artificial example, but the same idea holds true when translating to real models like _mixedbread-ai/mxbai-embed-large-v1_. +You'll notice how similar the vectors produced by the text "I like Postgres" and "I like SQL" are compared to "Rust is the best". This is a artificial example, but the same idea holds true when translating to real models like `mixedbread-ai/mxbai-embed-large-v1`. ## What does it mean to be "close"? @@ -109,11 +109,11 @@ For instance let’s say that we have the following documents: | 2 | I think tomatoes are incredible on burgers. | -and a user is looking for the answer to the question: "What is the pgml.transform function?". If we embed the search query and all of the documents using a model like _mixedbread-ai/mxbai-embed-large-v1_, we can compare the query embedding to all of the document embeddings, and select the document that has the closest embedding in vector space, and therefore in meaning, to the to the answer. +and a user is looking for the answer to the question: "What is the pgml.transform function?". If we embed the search query and all of the documents using a model like `mixedbread-ai/mxbai-embed-large-v1`, we can compare the query embedding to all of the document embeddings, and select the document that has the closest embedding in vector space, and therefore in meaning, to the to the answer. These are big embeddings, so we can’t simply estimate which one is closest. So, how do we actually measure the similarity (distance) between different vectors? -_pgvector_ as of this writing supports four different measurements of vector similarity: +`pgvector` as of this writing supports four different measurements of vector similarity: - L2 distance - (negative) inner product @@ -126,7 +126,7 @@ For most use cases we recommend using the cosine distance as defined by the form where A and B are two vectors. -This is a somewhat confusing formula but luckily _pgvector_ provides an operator that computes the cosine distance for us: +This is a somewhat confusing formula but luckily `pgvector` provides an operator that computes the cosine distance for us: !!! generic @@ -204,7 +204,7 @@ You'll notice that the distance between "What is the pgml.transform function?" a It is inefficient to compute embeddings for all the documents every time we search the dataset. Instead, we should embed our documents once and search against precomputed embeddings. -_pgvector_ provides us with the `vector` data type for storing embeddings in regular PostgreSQL tables: +`pgvector` provides us with the `vector` data type for storing embeddings in regular PostgreSQL tables: !!! generic From 2c156aeda674d201ca1c200a27c93d9217a988c1 Mon Sep 17 00:00:00 2001 From: SilasMarvin <19626586+SilasMarvin@users.noreply.github.com> Date: Tue, 18 Jun 2024 09:04:32 -0700 Subject: [PATCH 18/20] Remove hnsw link out --- pgml-cms/blog/semantic-search-in-postgres-in-15-minutes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pgml-cms/blog/semantic-search-in-postgres-in-15-minutes.md b/pgml-cms/blog/semantic-search-in-postgres-in-15-minutes.md index 132238c89..8632fea84 100644 --- a/pgml-cms/blog/semantic-search-in-postgres-in-15-minutes.md +++ b/pgml-cms/blog/semantic-search-in-postgres-in-15-minutes.md @@ -412,7 +412,7 @@ LIMIT 1; We can see it is a massive speedup because we are only comparing our input to 1/10th of the original vectors, instead of all of them! -HNSW indexes are a bit more complicated. It is essentially a graph with edges linked by proximity in vector space. For more information you can check out this [write-up](https://www.pinecone.io/learn/series/faiss/hnsw/). +HNSW indexes are a bit more complicated. It is essentially a graph with edges linked by proximity in vector space. HNSW indexes typically have better and faster recall but require more compute when adding new vectors. That being said, we recommend using HNSW indexes for most use cases where writes are less frequent than reads. From faf0be14584cee95be2a32d913fa539015e5e96e Mon Sep 17 00:00:00 2001 From: SilasMarvin <19626586+SilasMarvin@users.noreply.github.com> Date: Tue, 18 Jun 2024 09:25:47 -0700 Subject: [PATCH 19/20] Alude to arrays --- pgml-cms/blog/semantic-search-in-postgres-in-15-minutes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pgml-cms/blog/semantic-search-in-postgres-in-15-minutes.md b/pgml-cms/blog/semantic-search-in-postgres-in-15-minutes.md index 8632fea84..d531070f2 100644 --- a/pgml-cms/blog/semantic-search-in-postgres-in-15-minutes.md +++ b/pgml-cms/blog/semantic-search-in-postgres-in-15-minutes.md @@ -31,7 +31,7 @@ Semantic search is not just for machine learning engineers. The system behind se Semantic search is powered by embeddings. To understand how semantic search works, we must have a basic understanding of embeddings. -Embeddings are vectors. Given some text and some embedding model, we can convert text to vectors: +Embeddings are vectors / arrays. Given some text and some embedding model, we can convert text to vectors: !!! generic From 27445f5b6b2b913eb41fb025347e882b7875c1f8 Mon Sep 17 00:00:00 2001 From: SilasMarvin <19626586+SilasMarvin@users.noreply.github.com> Date: Tue, 18 Jun 2024 15:11:14 -0700 Subject: [PATCH 20/20] Finalize post --- ...mantic-search-in-postgres-in-15-minutes.md | 28 ++++++++++++++----- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/pgml-cms/blog/semantic-search-in-postgres-in-15-minutes.md b/pgml-cms/blog/semantic-search-in-postgres-in-15-minutes.md index d531070f2..67cd8a6d5 100644 --- a/pgml-cms/blog/semantic-search-in-postgres-in-15-minutes.md +++ b/pgml-cms/blog/semantic-search-in-postgres-in-15-minutes.md @@ -1,11 +1,11 @@ --- description: >- - Learn how to implement semantic search in PostgreSQL with nothing but SQL. + How to implement semantic search in Postgres with nothing but SQL. featured: true tags: ["Engineering"] --- -# Semantic Search in Postgres in 15 Minutes +# Implementing Semantic Search in Postgres in 15 Minutes
@@ -15,7 +15,7 @@ tags: ["Engineering"] Silas Marvin -June 15, 2024 +June 18, 2024 ## What is and is not semantic search @@ -23,9 +23,11 @@ Semantic search uses machine learning to understand the meaning of text by conve When users are unsure of the exact terms to search for, semantic search can uncover relevant information that traditional keyword searches might miss. This capability is particularly valuable for discovering content based on the intent and context of the search query, rather than relying solely on precise word matches. -It is not a replacement for full-text search. In many cases, full-text search can outperform semantic search. Specifically, if a user knows the exact keywords they want to match in a document, full-text search is faster and guaranteed to return the correct result, whereas semantic search is only likely to return the correct result. Full-text search and semantic search can be combined to create powerful and robust search systems. +It is not a replacement for keyword search. In many cases, keyword search can outperform semantic search. Specifically, if a user knows the exact keywords they want to match in a document, keyword search is faster and guaranteed to return the correct result, whereas semantic search is only likely to return the correct result. The most robust search systems combine the two. This technique is called hybrid search, which ultimately delivers the most accurate search system and best user experience. -Semantic search is not just for machine learning engineers. The system behind semantic search is relatively easy to implement, and thanks to new Postgres extensions like `pgml` and `pgvector`, it is readily available to SQL developers. Just as modern SQL developers are expected to be familiar with and capable of implementing full-text search, they will soon be expected to implement semantic search as well. +Semantic search is not just for machine learning engineers. The system behind semantic search is relatively easy to implement, and thanks to new Postgres extensions like `pgml` and `pgvector`, it is readily available to SQL developers. Just as modern SQL developers are expected to be familiar with and capable of implementing keyword search, they will soon be expected to implement semantic search as well. + +For more on hybird search techniques check out our blog post, _[How to Improve Search Results with Machine Learning](https://postgresml.org/blog/how-to-improve-search-results-with-machine-learning)_. ## Embeddings 101 @@ -95,7 +97,7 @@ embedding for 'Rust is the best' !!! -You'll notice how similar the vectors produced by the text "I like Postgres" and "I like SQL" are compared to "Rust is the best". This is a artificial example, but the same idea holds true when translating to real models like `mixedbread-ai/mxbai-embed-large-v1`. +You'll notice how similar the vectors produced by the text "I like Postgres" and "I like SQL" are compared to "Rust is the best". This is an artificial example, but the same idea holds true when translating to real models like `mixedbread-ai/mxbai-embed-large-v1`. ## What does it mean to be "close"? @@ -202,7 +204,7 @@ You'll notice that the distance between "What is the pgml.transform function?" a ## Making it fast! -It is inefficient to compute embeddings for all the documents every time we search the dataset. Instead, we should embed our documents once and search against precomputed embeddings. +It is inefficient to compute embeddings for all the documents every time we search the dataset as it takes a few milliseconds to generate an embedding. Instead, we should embed our documents once and search against precomputed embeddings. `pgvector` provides us with the `vector` data type for storing embeddings in regular PostgreSQL tables: @@ -478,3 +480,15 @@ That was even faster! There is a lot more that can go into semantic search. Stay tuned for a follow up post on hybrid search and re-ranking. If you have any questions, or just have an idea on how to make PostgresML better, we'd love to hear from you in our [Discord](https://discord.com/invite/DmyJP3qJ7U). We’re open source, and welcome contributions from the community, especially when it comes to the rapidly evolving ML/AI landscape. + +## Closing thoughts / why PostgresQL? + +There are a host of benefits to performing machine learning tasks in your database. The hard part of AI & ML systems has always been managing data. Vastly more engineers have a full-time job managing data pipelines than models. Vastly more money is spent on data management systems than LLMs, and this will continue to be the case, because data is the bespoke differentiator. + +Getting the data to the models in a timely manner often spans multiple teams and multiple disciplines collaborating for multiple quarters. When the landscape is changing as quickly as modern AI & ML, many applications are out of date before they launch, and unmaintainable long term. + +Moving the models to the data rather than constantly pulling the data to the models reduces engineering overhead, the number of costly external network calls, and only enhances your ability to scale. Why not scale your data on a proven database handling millions of requests per second? That’s why we do machine learning in Postgres. + +For more on the benefits of in-database AI/ML see our blog post, [_LLMs are Commoditized, Data is the Differentiator_](https://postgresml.org/blog/llms-are-commoditized-data-is-the-differentiator). + +In this post we focused on SQL, but for those without SQL expertise, the benefits of in-database machine learning are still accessible. You can abstract away the SQL functions in [JS](https://postgresml.org/docs/api/client-sdk/), [Python](https://postgresml.org/docs/api/client-sdk/), [Rust](https://postgresml.org/docs/api/client-sdk/) or [C](https://postgresml.org/docs/api/client-sdk/). pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy