<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Jeremy Maslanko</title><link>https://jeremymaslanko.com/</link><description/><atom:link href="/rss.xml" rel="self"/><lastBuildDate>Sun, 01 Feb 2026 12:00:00 -0400</lastBuildDate><item><title>I Published a Book</title><link>https://jeremymaslanko.com/i-published-a-book.html</link><description>&lt;p&gt;I can now add 'Published Author' to my resume and if you look at my &lt;a href="https://www.amazon.com/dp/B0GLFN5DTH"&gt;book&lt;/a&gt; on Amazon, I'm sure you will be impressed! Not everyone is able to churn out a book with OVER 40 pages like I can, and I didn't even need to include any pictures! Hopefully by now you realize I am being facetious. My 'book' that I published has less than 300 words. It would be more appropriate to call it a coloring book, however, that is exactly how I want it to be used.&lt;/p&gt;
&lt;p&gt;I try to read a fair amount. Any given year, I will read between 15-20 books which does not account for the countless articles I read on HackerNews. I typically read non-fiction, but am drawn to the books that read as if they are fiction. Gripping stories with plenty of character development help me imagine the scenes in my head. And I think that this is one of the most important parts of reading. Simply processing the words on the page does nothing if you are not able to build a world in your head that they represent. That is where &lt;em&gt;Imagine That!&lt;/em&gt; comes into play.&lt;/p&gt;
&lt;p&gt;For years I have had the idea of creating a book that has simple prompts on the left side of the book with a blank page on the right side. But while the page is blank to start, it should not end that way. After reading a prompt, it is time for the child to take out their crayons/markers/colored pencils and draw what they picture in their head. My hope is that by reading the text and then immediately drawing a picture to go with it, kids will be able to quickly learn to associate the words on the page with more than black letters on white paper.&lt;/p&gt;
&lt;p&gt;Now I can admit, this is not the most polished book. There were points in the process that I rushed and I am okay with that. Reason being, I wanted to &lt;em&gt;actually&lt;/em&gt; produce this. As I said, I have had this idea for years and never spent any actual time figuring out how to produce it. &lt;/p&gt;
&lt;p&gt;Through my research, I landed on self-publishing through Amazon Kindle Direct Publishing. This is an easy way to self-publish a book without having to go through the full publishing process. The perfect approach for a project like this. I doubt any publisher would even respond to my requests to publish something like this, and if they did, I would have an even tougher time trying to convince them to give me an advance. Quite frankly, it wouldn't be deserving of one.&lt;/p&gt;
&lt;p&gt;While I would love for this to go viral and make me thousands of dollars a month in royalties, I am realistic. This book has a very high chance of selling less than one copy, and I am okay with that. When I have kids of my own, I will be happy to purchase an author copy for them to work through and bring their imaginations to the page.&lt;/p&gt;
&lt;p&gt;For those curious, AI was used throughout the process. But don't worry, an LLM did not help with any of the text outside of the Amazon description. Yes, you heard that right. The prompts in this book are the result of my authentic, human creativity! But LLM's were great for me introducing me to things I have never even thought of coming from a technical background. Things like what should go through a publisher vs. self-publishing, dimensions of the book, dimensions for front/back cover and spine, and a plethora of other things.&lt;/p&gt;
&lt;p&gt;As I have been blogging more, I have started to enjoy writing more and more. It is a new skill that I have plenty of room to improve. With that, I have had other ideas for actual books to write and after going through the process of publishing this 'book', I am in a much more confident position to try and tackle those ideas. In the off chance you have a new reader in your life and you purchase this, please share with me how they liked it and if you have any suggestions!&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Jeremy Maslanko</dc:creator><pubDate>Sun, 01 Feb 2026 12:00:00 -0400</pubDate><guid>tag:jeremymaslanko.com,2026-02-01:/i-published-a-book.html</guid><category>misc</category></item><item><title>Diagnosing a Faulty Breaker with ChatGPT</title><link>https://jeremymaslanko.com/diagnosing-a-faulty-breaker-with-chatgpt.html</link><description>&lt;p&gt;Let me start with a disclaimer. This is not a how-to guide on how to replace a breaker. Working on breakers, live wires, and electrical panels can be extremely dangerous. If you are not confident in your handyman abilities, the money spent on an electrician will be well worth it. With that out of the way, let us move on to my situation.&lt;/p&gt;
&lt;p&gt;My wife and I bought our house in June of 2026. Shortly after moving, we noticed that the dishwasher was not completing its cycles, leaving an unwanted residue on the dishes from the soap that hadn't been fully rinsed off. We actually didn't know that the dishwasher wasn't completing its cycles at first. We saw that it was not running, felt some steam coming out from the vent, and simply assumed it was done. On top of that, we were using a different soap. "Hmmm, maybe it's just the soap." we thought. But that was debunked quickly when we would go and run the dishwasher again, notice nothing happening, and then see that the breaker had tripped.&lt;/p&gt;
&lt;p&gt;Great. We just moved into a new house, and now we have to shell out $500-1000 for a new dishwasher. Since there were so many things to do while getting settled in, we (read: I) decided to blissfully ignore the issue and simply walk downstairs every so often and turn the breaker back on. I am not an electrician, but you probably don't want to ignore this as long as I did. Clearly, something isn't working the way it is supposed to, which can lead to bigger problems.&lt;/p&gt;
&lt;p&gt;The weird thing was that it actually stopped happening as frequently. A few weeks went by at a time before it would get tripped again. At this point, I have been naively assuming that it is just an issue with a dishwasher, so I let it continue because I am pretty cheap and didn't want to buy a new dishwasher. But all good things must come to an end, and the issue started happening more frequently. Not just when we would run the dishwasher, but every few hours, I would notice that the breaker was switched (the only other thing on this breaker is the garbage disposal, which I would run to test if the breaker was triggered). Fine, I will stop procrastinating and get this figured out.&lt;/p&gt;
&lt;p&gt;I have done my fair share of home remodeling. I have done electrical (replaced outlets, switches, and lights), plumbing (shower and sink faucets, garbage disposal replacement), flooring, cabinet, countertop installation and more. You name it, I have likely done it or am confident I can learn how to do it. However, one thing I have not touched is an electrical panel. Whenever I do electrical work, I usually just turn off the main and be done with it. That is the extent of my electrical panel knowledge.&lt;/p&gt;
&lt;p&gt;Even though I suspected the issue was with my dishwasher pulling too much power and tripping the breaker, replacing it felt like an expensive way to test that suspicion. Enter ChatGPT. Throughout a back-and-forth dialogue, I described the problem, shared details about what was connected to the breaker, and that it was an AFCI/GFCI dual-purpose breaker. Throughout the conversation, I was guided through a variety of checks to isolate the problem so that I could actually fix the issue.&lt;/p&gt;
&lt;p&gt;It was quite a blow to my ego when the first thing I was prompted to do was to unplug the dishwasher and see if the issue continued. So I did that, and it still tripped. What a relief! I don't have to go buy a new dishwasher. Next, I unplugged the garbage disposal and reached the same result. My hunch is starting to be that the issue is the breaker. At least I hoped that was the case because if there was an issue somewhere in the walls, I would definitely be calling an electrician, and that would get pricey. One last thing to check was to make sure that the outlet didn't have any noticeable issues. I turned the power off, pulled it out, and inspected it for any signs of corrosion or water damage. From what I could tell, everything looked normal.&lt;/p&gt;
&lt;p&gt;At this point, it is either an issue with the breaker or somewhere in the walls. I was confident I could do this electrical work because I knew that it was pretty straightforward. I wasn't adding anything new; I was simply going to be removing the wires from the existing breaker, taking it out, reconnecting the wires to the new breaker, and finally inserting the breaker back into the panel. But how do I know what breaker to get?&lt;/p&gt;
&lt;p&gt;From earlier research, I knew I needed to get the same one that I have. After consulting with my assistant, they gave me a list of things to verify. First, I had to figure out if my panel was BR or CH. I didn't really know the difference, but when I looked into it, it appears that it is just two different lines of products that Eaton offers, with the CH line being the more premium in parts vs. the BR line being more economical. Not surprisingly, my new build (2017) home has the economical version! But had I not been prompted to check for this one, I also wouldn't have known that the two are not interchangeable. I was able to learn which line to use from the sticker on my panel.&lt;/p&gt;
&lt;p&gt;The next thing I had to match was the amperage. This was easier to see as the number 20 was on the switch. The last thing I needed to check was that the breaker was also dual-rated for AFCI/GFCI. These are easy to find in the store because, unlike regular breakers packed in boxes, these were hanging with anti-theft devices on them due to the ~$60 price difference ($70 compared to $10 for the regular breaker).&lt;/p&gt;
&lt;p&gt;Being in tech, I skeptically listen to LLMs. I understand they are probabilistic and that they have many limitations. It is actually pretty easy to recognize ("You are absolutely correct, that the solution is wrong!" - ChatGPT when I say that the code it provided didn't work). With that in mind, I didn't use ChatGPT to tell me how to actually change the breaker. Instead, I relied on good 'ol YouTube University.&lt;/p&gt;
&lt;p&gt;I swapped out the breaker and let it sit for a few hours before plugging anything in. All seemed well, so I plugged the dishwasher in and ran a cycle. A complete cycle ran, incredible! Lastly, I plugged in the disposal, and things still seem to be operating as expected.&lt;/p&gt;
&lt;p&gt;In the past, I would have iteratively googled and likely would have gotten to the same result. But that approach can be quite frustrating as I have to piece together different bits of information and try and form a coherent story and path forward. This is where I find LLMs to be most valuable. I can provide information specific to my situation and work through the problem. "AI" didn't magically complete this task for me, but it did help me solve the problem quicker and more confidently.&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Jeremy Maslanko</dc:creator><pubDate>Mon, 19 Jan 2026 12:00:00 -0400</pubDate><guid>tag:jeremymaslanko.com,2026-01-19:/diagnosing-a-faulty-breaker-with-chatgpt.html</guid><category>misc</category></item><item><title>Rethinking generational wealth</title><link>https://jeremymaslanko.com/rethinking-generational-wealth.html</link><description>&lt;p&gt;When I hear generational wealth mentioned, there is a certain image that I get in my head. Private jets, multiple houses, and never having to think about what something costs. There is probably a staff consisting of landscapers, housekeepers, nannies, lawyers, and accountants just to name a few. It is what I think about when I think of how the ultra-wealthy live their lives. With this image in mind, how do we actually define generational wealth?&lt;/p&gt;
&lt;p&gt;I am sure there are many definitions, but &lt;a href="https://www.investopedia.com/generational-wealth-definition-5189580"&gt;Investopedia&lt;/a&gt; defines generational wealth as "...generational wealth refers to financial assets passed from one generation of a family to another". That makes sense to me. It is one thing for someone to make a lot of money and spend it all, but that wouldn't be generational wealth. Money needs to survive past a single generation. After all, it is in the name! If you poll people though, it's not as simple as passing down a small sum of money in an inheritance. Looking at this thread on &lt;a href="https://www.reddit.com/r/CalebHammer/comments/18sufwm/at_what_point_do_you_consider_someone_to_have/"&gt;Reddit&lt;/a&gt;, you can see that the general consensus is that the amount of money needed to be "generational" is enough to retire as soon as you get access to that money. This lines up with the image I have in my head.&lt;/p&gt;
&lt;p&gt;Now that is a fair definition, but the problem I have with it is that it is not achievable for 99% of the population. Very few families will be in this situation. But I like to create goals and work towards them, so I like to define generational wealth closer to that of the Investopedia definition and hopefully people can start to think about it that way too.&lt;/p&gt;
&lt;p&gt;You may not be able to give your children an estate that has tens of millions of dollars for them to retire with, but you can give them money for tuition. If tens of thousands of dollars for tuition is too much, maybe you can give them a few grand toward their first house. If a few grand for a first house is too much, then maybe you can let them live with you when they graduate so that they can pay off loans, save for a home/apartment, or maybe both. Generational wealth shouldn't be thought of solely as an option for the ultra-wealthy and there are many ways to set up future generations for greater financial success.&lt;/p&gt;
&lt;p&gt;The power of investing is compound interest. Ten thousand dollars invested for 30 years with a 6% return gets you ~$57,000. That is pretty powerful if you make that investment at 30 years old for your retirement. But that same investment for 60 years is ~$329,000! A one-time investment when a child is born leads to a large sum of money for their retirement. Little things like this can lay the foundation for generational wealth and are very achievable.&lt;/p&gt;
&lt;p&gt;I recognize that not everybody values generational wealth the same. Many people may want to spend the money on themselves which is perfectly okay! You did work hard to save it, there is nothing wrong with enjoying the money too. My only hope is that we can all think a little looser about the definition of generational wealth and think of it as something you can work toward should you choose to.&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Jeremy Maslanko</dc:creator><pubDate>Fri, 02 Jan 2026 12:00:00 -0400</pubDate><guid>tag:jeremymaslanko.com,2026-01-02:/rethinking-generational-wealth.html</guid><category>misc</category></item><item><title>A simple LRU Cache in Python</title><link>https://jeremymaslanko.com/a-simple-lru-cache-in-python.html</link><description>&lt;p&gt;In a previous &lt;a href="https://jeremymaslanko.com/how-do-caches-work.html"&gt;post&lt;/a&gt;, I explained some of the basic concepts of how caches work in hardware at the CPU level. As I mentioned to start that post, caching is used in many different areas of computer science. So how can you use this new knowledge you have gained? One way you might want to implement a cache is if you have expensive database calls. &lt;/p&gt;
&lt;p&gt;Let's say that you have a web app where you are displaying movie information. When a user searches for a movie, your backend will query your database for the information related to that movie. For a simple web app with a small number of active users, you probably won't run into big performance issues. But we're going to imagine that we have millions of active users because we have created the best movie web app known to mankind. &lt;/p&gt;
&lt;p&gt;With so many users, you start to look into where your costs are coming from and realized there is a big line item for I/O requests. Well that makes sense, we do have millions of active users! But you pulled up another dashboard and notice that every time a new movie is released, an overwhelming majority of the requests are for that movie. Armed with your knowledge of caching, you decide to implement a cache to try and reduce the number of calls your backend makes to the database.&lt;/p&gt;
&lt;h3&gt;Implementation&lt;/h3&gt;
&lt;p&gt;Caches can become quite complex, especially as the number of cores increases in order to keep all of the data up to date. For our example, we will be looking at a very simplified version. We won't be writing any data to the database, so our cache will only contain data that we have read. Additionally, the cache will be in memory and we will also provide a &lt;code&gt;ttl&lt;/code&gt; parameter when we instantiate the class.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;LRUCache&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ttl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="sd"&gt;        Docstring for __init__&lt;/span&gt;

&lt;span class="sd"&gt;        :param size: Number of entries in cache&lt;/span&gt;
&lt;span class="sd"&gt;        :param ttl: Time To Live (minutes) - Length of time for object in cache&lt;/span&gt;
&lt;span class="sd"&gt;            to be active. Once ttl is past, item is now a cache miss&lt;/span&gt;
&lt;span class="sd"&gt;        &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cache&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ttl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ttl&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="c1"&gt;# The timedelta is expressed in seconds, so multiply by 60 to get minutes&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;diff&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;time&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;diff&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;seconds&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ttl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;
            &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="fm"&gt;__len__&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;time&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;data&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;lru_counter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;time&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;key&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;time&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;lru_counter&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;time&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
                    &lt;span class="n"&gt;lru_counter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;time&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;time&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;key&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="k"&gt;del&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;lru_counter&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;key&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;time&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;data&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Our cache is pretty simple, but let's break it down. We start by creating a class called &lt;code&gt;LRUCache&lt;/code&gt;. This class takes two required parameters. The first parameter is the size of the cache. If we didn't have this, we would end up creating a cache that continuously grows which would eventually slow down our program and run out of memory. How big you make this is dependent on your use case. For our movie app, we might make the size be close to the number of movies released each week as these will be the most accessed movies.&lt;/p&gt;
&lt;p&gt;The second parameter is &lt;code&gt;ttl&lt;/code&gt; which is short for Time To Live. Common in networking as well as a caching strategy, &lt;code&gt;ttl&lt;/code&gt; tells us how long our data is "active". Basically, at some point we will update our data so we want to make sure that we don't continue to access stale data in our cache. We might have users rate the movie so we want to make sure that when we cache the data, we periodically pull our data again so that we have updated data. If we are okay not having real time data, we might make this number 24 hours long so that it is refreshed daily.&lt;/p&gt;
&lt;p&gt;In order to create a cache we will need some sort of unique key to index into our data structure. In CPU's we use index bits of the memory location we are accessing. For something like this, we can use a unique value that we might be querying with. In our movie example, we will probably have some sort of &lt;code&gt;movie_id&lt;/code&gt; in our database that can be used.&lt;/p&gt;
&lt;p&gt;Looking at the methods we have created, the first one is a &lt;code&gt;check&lt;/code&gt; method. This simply checks to see if the data is in the cache or not and outputs a boolean value. Additionally, if the data is in the cache, we verify that the data is not older than the &lt;code&gt;ttl&lt;/code&gt; value that we have set. If it is, we will return &lt;code&gt;False&lt;/code&gt; as if it was a normal cache miss.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;get&lt;/code&gt; method... well it gets the data based on the key.&lt;/p&gt;
&lt;p&gt;Last but not least, the &lt;code&gt;add&lt;/code&gt; method is where a lot of the heavy lifting is done. We have a set size we are allowing our cache to be so that we don't run into any memory issues, so the first thing we do is check to see if our cache is at our max size already. If it is not, then we simply add our data to the cache using a key. If we have hit our max size, we now have to kick out the Least Recently Used item. We first create an &lt;code&gt;lru_counter&lt;/code&gt; dictionary with the current time and the key (which is &lt;code&gt;None&lt;/code&gt;) to start. Then we will iterate through our cache and compare the time for each item to the time in our &lt;code&gt;lru_counter&lt;/code&gt;. If it less than the time in the &lt;code&gt;lru_counter&lt;/code&gt;, we will update the &lt;code&gt;lru_counter&lt;/code&gt; to be the time of our item and then set the key equal to our items key. Once we have iterated through all of the items in our cache, we will delete the item based on the key in our &lt;code&gt;lru_counter&lt;/code&gt; and then add in the data we want.&lt;/p&gt;
&lt;h3&gt;Walk through&lt;/h3&gt;
&lt;p&gt;First, we will create a cache with three lines and a &lt;code&gt;ttl&lt;/code&gt; of two minutes and add two lines of data:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;cache&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;LRUCache&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;abc&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;123&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;def&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;456&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;If we call &lt;code&gt;cache.cache&lt;/code&gt;, we can see the current contents of our cache:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="o"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;abc&amp;#39;&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;time&amp;#39;&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;datetime.datetime&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;2025&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;12&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;14&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;19&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;59&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;32&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;216167&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;,&lt;span class="s1"&gt;&amp;#39;data&amp;#39;&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;123&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;,
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;def&amp;#39;&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;time&amp;#39;&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;datetime.datetime&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;2025&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;12&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;14&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;20&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;9&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;898178&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;,&lt;span class="s1"&gt;&amp;#39;data&amp;#39;&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;456&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;,
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;ghi&amp;#39;&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;time&amp;#39;&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;datetime.datetime&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;2025&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;12&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;14&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;20&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;31&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;427328&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;,&lt;span class="s1"&gt;&amp;#39;data&amp;#39;&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;789&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;We see that &lt;code&gt;abc&lt;/code&gt; was the first the item in the cache and our cache is at our capacity. Now, if we were to add a line with the key of &lt;code&gt;zzz&lt;/code&gt;, we can see that the cache has kicked out &lt;code&gt;abc&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="o"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;def&amp;#39;&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;time&amp;#39;&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;datetime.datetime&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;2025&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;12&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;14&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;20&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;9&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;898178&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;,&lt;span class="s1"&gt;&amp;#39;data&amp;#39;&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;456&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;,
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;ghi&amp;#39;&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;time&amp;#39;&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;datetime.datetime&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;2025&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;12&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;14&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;20&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;31&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;427328&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;,&lt;span class="s1"&gt;&amp;#39;data&amp;#39;&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;789&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;,
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;zzz&amp;#39;&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;time&amp;#39;&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;datetime.datetime&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;2025&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;12&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;14&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;20&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;8&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;24&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;557737&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;,&lt;span class="s1"&gt;&amp;#39;data&amp;#39;&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;999&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;Suggestions&lt;/h3&gt;
&lt;p&gt;We now have a basic functioning LRU Cache in Python. I glossed over A LOT with this implementation, so here are some things to consider if you actually need to implement this.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It is very likely that you have your application deployed to more than one server. This muddies the water a bit, just as if it were a multi-core cache in hardware. You may want to deploy the cache to its own container that each server calls. You will also probably have to add in some locking functionality so that if you are also using the cache for writes, you keep the data accesses sequential and coherent.&lt;/li&gt;
&lt;li&gt;I wrote the code quickly. You might want to create a &lt;code&gt;CacheLine&lt;/code&gt; class instead of just a nested dictionary to keep things tidier.&lt;/li&gt;
&lt;li&gt;As it is now, you will have to call the methods in order starting with &lt;code&gt;check&lt;/code&gt; and then either &lt;code&gt;get&lt;/code&gt; or &lt;code&gt;add&lt;/code&gt;. A better implementation might have these all rolled into one.&lt;/li&gt;
&lt;li&gt;The time for each item is the time when the line was inserted into the cache. If our &lt;code&gt;ttl&lt;/code&gt; is set to expire daily, this could lead to problems. We know that when a movie is released, people will go all weekend. If we are not updating any data like ratings, then everytime we read the data from the cache, it might be beneficial to update the time as well.&lt;/li&gt;
&lt;/ul&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Jeremy Maslanko</dc:creator><pubDate>Sun, 14 Dec 2025 12:00:00 -0400</pubDate><guid>tag:jeremymaslanko.com,2025-12-14:/a-simple-lru-cache-in-python.html</guid><category>misc</category></item><item><title>I finally learned how to study</title><link>https://jeremymaslanko.com/i-finally-learned-how-to-study.html</link><description>&lt;!-- Status: skip --&gt;

&lt;p&gt;At the age of 30, I have finally found the most effective way for me to learn a subject. I am currently in Georgia Tech's OMSCS program and have been learning about computer architecture. With online lectures, it can sometimes be challenging for me to stay engaged. The vidoes may be playing and I am looking at the screen, but I zone out and end up not knowing what was just taught. This snowballs as I then go on to the next video where I get even more confused because I do not know the concepts that we are building off of.&lt;/p&gt;
&lt;p&gt;I have never really been good at taking notes and typically when I study, I simply review slides or re-read textbook chapters. With online lectures, I have rewatched the lectures which has been helpful. But when I rewatch lectures I am still at risk of zoning out. This has largely worked, but I always end up feeling like I do not have a good understanding of the material and quickly lose that knowledge after I am done cramming.&lt;/p&gt;
&lt;p&gt;I have had this blog for a couple years but never really got in a rhythm of posting. But I recently was reading comments on a HackerNews post about why people blog and there was a theme I started to notice. People blog so that they can learn or share what they learn. I often read blogs about topics that are new to me and then I thought that it would be nice to do something similar. I decided to write about how &lt;a href="https://jeremymaslanko.com/how-do-caches-work.html"&gt;caches&lt;/a&gt; work at a basic level. By the time I was finished writing, I had such a greater understanding of the material!&lt;/p&gt;
&lt;p&gt;As a way to prepare for my final, I began writing blog posts on all of the topics I was reviewing. This helped me immensely and I felt much more confident about the material. It is similar to the idea that the best way to learn something is to teach it. When you do that, you have to make sure you understand the topic so that you can effectively teach it. With an online program, I have less interactions with other students. But writing blog posts gives me the opportunity to teach anyone. I do not intend to publish most of the posts I have written since they are not very polished. However, I still write them as if I were publishing them. This forces me to understand the topic in a way that will allow me to communicate it differently.&lt;/p&gt;
&lt;p&gt;Right now I have only been doing this for a few weeks while reviewing material and it is a lot of work. Watching lecture videos while also writing a blog post on it is time consuming, so I do not recommend doing this as a last minute study option. But going forward, I plan to do this from the beginning. My hope is that this will make it easier to understand the material the first time I am introduced to it.&lt;/p&gt;
&lt;p&gt;I encourage you to try this method as well. If you need to cram for an exam, it probably won't be the best method. If there is a single topic you are not confident on, try to write a blog post. Hopefully you will find it effective as I have.&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Jeremy Maslanko</dc:creator><pubDate>Mon, 08 Dec 2025 12:00:00 -0400</pubDate><guid>tag:jeremymaslanko.com,2025-12-08:/i-finally-learned-how-to-study.html</guid><category>misc</category></item><item><title>How do caches work?</title><link>https://jeremymaslanko.com/how-do-caches-work.html</link><description>&lt;p&gt;I think it is fair to say that most people have an idea of what caching does at a variety of levels. To someone not in tech, "clearing your cache" (also those yummy cookies!) is a way to get your browser to update. So it is understood that some data is stored pretty close to you. To someone in tech, you may understand that caching data is a way to keep it accessible so that you don't have to go and retrieve it. Content Delivery Networks (CDN's) cache webpages (and other data that is distributed) at different locations so that your performance as a user is faster. Great, makes sense!&lt;/p&gt;
&lt;p&gt;For both of these examples, the ideas are based on what comes from CPU hardware. When a program is running and fetching instructions/data, accessing memory can be costly (in terms of cycle time). &lt;/p&gt;
&lt;h2&gt;Locality&lt;/h2&gt;
&lt;p&gt;When we fetch data from memory, there are two different themes that emerge. Data that was just accessed is likely to be accessed again, and data that is accessed will be close to the data that was accessed in the prior instruction. Those are two examples of temporal locality and spatial locality, respectively. A good way to think about this is in loops. Let's look at the code below.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;For each value between 0 and 9, we add to the sum the value from &lt;code&gt;arr&lt;/code&gt; at location &lt;code&gt;i&lt;/code&gt;. Each time we iterate through the loop, we are accessing the &lt;code&gt;sum&lt;/code&gt; (temporal locality) and we are accessing the next position in the array (spatial locality).&lt;/p&gt;
&lt;p&gt;Specifically looking at temporal locality, accessing the value of &lt;code&gt;sum&lt;/code&gt; from memory each time we use it can become quite a bottleneck in our program, especially when the loop becomes much larger. This is where cacheing comes in! Instead of going to the main memory every iteration, what if had a smaller memory module on the chip? It would be closer to all of the work that is being done which would make it much faster.&lt;/p&gt;
&lt;p&gt;For now, I am only going to focus on a single core with a single cache. Caches in hardware can become more complex with multiple levels (L1, L2, or L3) and multiple cores (how does one core know what is in the cache of a different core?).&lt;/p&gt;
&lt;h2&gt;Hit or Miss?&lt;/h2&gt;
&lt;p&gt;Whether we have a cache hit or cache miss, it is pretty straightforward. If the data we are trying to get is in the cache, it's a hit! And if it is not, then it's a miss. The data we are tying to get is based on the address of the location of the data in memory.&lt;/p&gt;
&lt;h2&gt;So what is the actual cache?&lt;/h2&gt;
&lt;p&gt;The cache is basically just a lookup table. In order to keep it small to fit on the chip and provide quick lookups, there are only so many records of data in that table. Now that makes sense, but how do we know if the data is in the table? We will use the address of the data that we are trying to access to index into this table. The "records" that I mentioned are actually called blocks or lines, and the length of it is called the block/line size. The block size is typically going to be 32-128 bytes.&lt;/p&gt;
&lt;p&gt;We know that blocks are the records in the cache, how do we know how many blocks are in the cache? To calculate this, we just take the size of the cache and divide by the block size. So if we have a 32KB cache and we have 64B block size, then we know that 512 blocks!&lt;/p&gt;
&lt;p&gt;The blocks in the cache will have different locations along that block where we can put data. To figure out which block and which section of that block we will put the data, we look at the block offset and block number. We find both of these values from the address.&lt;/p&gt;
&lt;p&gt;If we have a 16B block size, the 4 least significant bits of the address will be the block offset and the the remaining bits are the block number. Why 4 bits? $2^4$ is 16, so we get 4 bits. If we had a block size of 32B, then we would have 5 bits used for the offset. Say we had the address of &lt;code&gt;0x123&lt;/code&gt;, which is &lt;code&gt;000100100011&lt;/code&gt; in binary, the block offset would be &lt;code&gt;0011&lt;/code&gt; and the block number would be &lt;code&gt;00010010&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;We have said that the cache is indexed by the address and that the data is stored in the cache. But we also said that the cache can contain more than one section of data, denoted by the offset. So how do we know if the data we are looking for is actually in that line? In addition to the data, the cache also keeps track of the cache tags which will keep track of the tags that are in the line.&lt;/p&gt;
&lt;p&gt;Until this point, we never really touched on what the values are when the cache is instantiated. The cache may have some random data in it as well as random tags. To know if the data we have in it is actual data or some random gibberish, we will also keep track of a valid bit. This valid bit simply says whether the data is real or not. So, to get a cache hit, we need the tag to match our block number AND the valid bit to be 1.&lt;/p&gt;
&lt;h2&gt;Types of caches&lt;/h2&gt;
&lt;p&gt;There are three types of caches:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Fully Associative&lt;/li&gt;
&lt;li&gt;Set-Associative&lt;/li&gt;
&lt;li&gt;Direct-Mapped&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;When an address can go to any block, that is an example of a fully associative cache. This is what we have talked about so far. When an address can go to only one block, that is a direct mapped cache. And in the middle, if an address can go to &lt;code&gt;n&lt;/code&gt; number of blocks, that is a set associative cache.&lt;/p&gt;
&lt;h3&gt;Direct-Mapped Cache&lt;/h3&gt;
&lt;p&gt;Let's say that we have a cache with 4 lines. Of course, we will have more than 4 blocks in memory. In a direct-mapped cache, each block in memory will map to one of the specific lines in the cache. But how? Well just like before, the first few bits will be used for the block address ($2^n$ where &lt;code&gt;n&lt;/code&gt; is the value that gets you to the block size). However, instead of all the remaining bits being used for the block tag, we will take the next few bits for the index and the remaining bits after that will be the tag. How many bits for the index? Similarily to the offset, it will be $2^n$ where &lt;code&gt;n&lt;/code&gt; is the number that gets you equal to the number of lines in the cache. In our example with 4 lines, then we would have $2^2=4$.&lt;/p&gt;
&lt;p&gt;For a more concrete example, consider the address &lt;code&gt;0x12345678&lt;/code&gt; that is trying to get inserted into a direct mapped cache with 16 lines and has a block size of 32B.&lt;/p&gt;
&lt;p&gt;Number of bits: &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Number of offset bits = $2^n=32$, with $n=5$&lt;/li&gt;
&lt;li&gt;Number of index bits = $2^n=16$, with $n=4$&lt;/li&gt;
&lt;li&gt;Number of tag bits = $32$ bits - offset bits - index bits&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;With the bits above, now we can find the bits that correspond to each chunk. Our hex value in binary is&lt;/p&gt;
&lt;p&gt;&lt;code&gt;00010010001101000101011001111000&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;and we can split it up into tag, index, offset like this&lt;/p&gt;
&lt;p&gt;&lt;code&gt;00010010001101000101011|0011|11000&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Looking specificially at the 4 bits for the offset, we can easily figure out which line the address maps to in our cache!&lt;/p&gt;
&lt;h3&gt;Set-Associative Cache&lt;/h3&gt;
&lt;p&gt;Similarly to a direct-mapped cache, a set-associative cache only has specific lines that the address is able to access. When we say set, we are talking about a set (or group) of lines in the cache that we are able to put the data for a specific address. For example, if we had a cache with 16 lines and the lines were in sets of 4, we would call that a 4 way set associative cache. There would be 4 sets each with 4 lines.&lt;/p&gt;
&lt;p&gt;To find which set our address can be inserted into, we first find the number of sets that are in the cache. The general formula is the number of lines in the cache divided by the $n$ ways in each set, where ways is the number of lines in each set. If you had 32 lines in your cache and 2 ways in each set, then you would have 16 sets.&lt;/p&gt;
&lt;p&gt;Just like the direct-mapped cache, we use the index bits to find which set to access. In our 32 line, 2 way cache that gave us 16 sets, which would be 5 bits for the offset ($2^5=16$).&lt;/p&gt;
&lt;p&gt;The direct-mapped cache is simply a 1-way set-associative cache!&lt;/p&gt;
&lt;h3&gt;Fully-Associtive Cache&lt;/h3&gt;
&lt;p&gt;A fully-associative cache is a cache where any address can go to any line in the cache. When that is the case, we have no offset bits and just have the tag and offset bits because there is no need for indexing.&lt;/p&gt;
&lt;h2&gt;Replacement Policies&lt;/h2&gt;
&lt;p&gt;Now that we know how caches are organized and the different types of caches, we have to decide what to do when we are trying to write to the cache and it is full. A few different options could be to randomly kick out an entry, choose whatever was first in the cache (FIFO), or choose whatever was Least Recently Used (LRU).&lt;/p&gt;
&lt;h3&gt;Least Recently Used&lt;/h3&gt;
&lt;p&gt;Like the name implies, in an LRU cache we will replace the item in the cache that has least recently been used. To keep track of what has been used when, we will add an LRU counter. Let's say that we have a cache with just 4 lines. Each line in the LRU counter will have a value between 0 and 3, with none repeating. 0 is the line that was least recently used and 3 is the line that was most recently used. When we need to replace a line, we will place it in the line that has the LRU counter value of 0. When we do this, we will change the LRU counter of that line to 3 and then decrement all of the other values.&lt;/p&gt;
&lt;p&gt;If we are accessing the most recenlty used line (LRU counter of 3), then we do not need to change any of the values of the LRU counter because they are just the same as before.&lt;/p&gt;
&lt;p&gt;But what happens if we want to access something that is in our cache, but say it has an LRU counter of 1? Well that line that we access will have its LRU value change to 3 and then anything with a value larger than the value just accessed (2 and 3 in this case) will be decremented. The line with the LRU counter value of 0 will stay the same.&lt;/p&gt;
&lt;h2&gt;Write Policy&lt;/h2&gt;
&lt;p&gt;The last topic I will touch on is the write policy. First we have to decide what to do when there is a write miss. In a write miss, we want to write a value to the cache but it does not exist. At that point, do we write it to the cache or not? Simiply put, in a write allocate cache, we will write that value to the cache and in a no-write allocated cache we do not. Write allocate caches are what is commonly used and the reasoning is related to temporal locality. For any item we are accessing, we are likely to access that again so we put it in the cache to make it quicker to access.&lt;/p&gt;
&lt;p&gt;In addition to the allocation, we also have to decide if we will write the value to memory. In a write-through cache, we will update the memory immediately on cache write. The other option is a a write-back cache, where we only write to memory when that value is being replaced. This reduces the total number of times we have to access the memory. As you can imagine, write-back caches are much more popular.&lt;/p&gt;
&lt;h3&gt;Write Back Caches&lt;/h3&gt;
&lt;p&gt;Further exploring write back caches, consider two scenarios: we wrote a block to the cache and read block from memory into the cache. In both scenarios, the cache has a value. These two scenarios work differently when we have to replace that block in the cache. In the first scenario, the cache has the newest data value so when we replace it, we will have to write the current value in the cache to memory. But in the second scenario, the cached value is the same value that is currently in memory, so we do not have to write it to memory. How do we know which scenario we have? We use what is called a dirty bit. When the dirty bit is 0, we know that the value is "clean" or that we don't need to write to memory. If the dirty bit is 1, the value is "dirty" and we have to write it to memory.&lt;/p&gt;
&lt;p&gt;Hopefully after reading this you have a better understanding of how caches work. This article only scratched the surface, and most notably only talked about single caches in a single core chip. As you add in additional levels of caches and multiple cores, the complexity of managing caches increases.&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Jeremy Maslanko</dc:creator><pubDate>Wed, 12 Nov 2025 12:00:00 -0400</pubDate><guid>tag:jeremymaslanko.com,2025-11-12:/how-do-caches-work.html</guid><category>misc</category></item><item><title>Understanding Python Relative Imports</title><link>https://jeremymaslanko.com/understanding-python-relative-imports.html</link><description>&lt;p&gt;One thing that has always tripped me up with Python is the import system. Everytime I think I understand how 
it works, I run some code and get more import errors. But I think I now have a complete understanding of how it 
works, so let's dive in.&lt;/p&gt;
&lt;h3&gt;What is &lt;code&gt;sys.path&lt;/code&gt;?&lt;/h3&gt;
&lt;p&gt;To see how the Python import system works, we have to start by understanding where Python looks for code 
when there is an import. This is in the module search path, which is inititialized when Python starts. By 
printing the &lt;code&gt;sys.path&lt;/code&gt;, we can see the list of paths that Python will be searching for modules. I created 
a folder named &lt;code&gt;package&lt;/code&gt;, and we can see the repo structure below:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &lt;span class="n"&gt;package&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;
    &lt;span class="o"&gt;|--&lt;/span&gt; &lt;span class="n"&gt;fruits&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;
    &lt;span class="o"&gt;|&lt;/span&gt;   &lt;span class="o"&gt;|--&lt;/span&gt; &lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt;
    &lt;span class="o"&gt;|&lt;/span&gt;   &lt;span class="o"&gt;|--&lt;/span&gt; &lt;span class="n"&gt;apple&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;
    &lt;span class="o"&gt;|&lt;/span&gt;       &lt;span class="o"&gt;--&lt;/span&gt; &lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt;
    &lt;span class="o"&gt;|&lt;/span&gt;       &lt;span class="o"&gt;--&lt;/span&gt; &lt;span class="nb"&gt;slice&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt;
    &lt;span class="o"&gt;|--&lt;/span&gt; &lt;span class="n"&gt;vegetables&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;
        &lt;span class="o"&gt;|--&lt;/span&gt; &lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt;
        &lt;span class="o"&gt;|--&lt;/span&gt; &lt;span class="n"&gt;celery&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;
            &lt;span class="o"&gt;--&lt;/span&gt; &lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt;
            &lt;span class="o"&gt;--&lt;/span&gt; &lt;span class="n"&gt;dice&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Here is a sample output from &lt;code&gt;print(sys.path)&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;/Users/Jeremy/Documents/Projects/package&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;/Library/Frameworks/Python.framework/Versions/3.13/lib/python313.zip&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;/Library/Frameworks/Python.framework/Versions/3.13/lib/python3.13&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;/Library/Frameworks/Python.framework/Versions/3.13/lib/python3.13/lib-dynload&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;/Library/Frameworks/Python.framework/Versions/3.13/lib/python3.13/site-packages&amp;#39;&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;There are five different paths that are listed. The first one is associated with the file I was running, 
and the remaining are different standard paths. Additionally, if you were to manually edit the &lt;code&gt;PYTHONPATH&lt;/code&gt;, 
then those would be listed as well.&lt;/p&gt;
&lt;p&gt;Let me also share what is in the two Python files.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# dice.py&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;sys&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;fruits.apple.slice&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;slice_apple&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;dice_celery&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Celery has been diced.&amp;quot;&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;__main__&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# slice.py&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;sys&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;slice_apple&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Apple has been sliced.&amp;quot;&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;__main__&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;Module vs. Script&lt;/h3&gt;
&lt;p&gt;From the root of the project in the &lt;code&gt;package/&lt;/code&gt; folder, the above &lt;code&gt;sys.path&lt;/code&gt; output was from 
&lt;code&gt;python3 -m vegetables.celery.dice&lt;/code&gt;. When I run that command with the &lt;code&gt;-m&lt;/code&gt; option, the code is 
ran as a module. When you do that, the path that is added to &lt;code&gt;sys.path[0]&lt;/code&gt; is from where the code is 
executed from. So if I run &lt;code&gt;python3 -m apple.slice&lt;/code&gt; from the &lt;code&gt;fruits/&lt;/code&gt; directory, then &lt;code&gt;sys.path[0]&lt;/code&gt; 
will be:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="s1"&gt;&amp;#39;/Users/Jeremy/Documents/Projects/package/fruits&amp;#39;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;But what happens if I run &lt;code&gt;python3 -m celery.dice&lt;/code&gt; from the &lt;code&gt;vegetables/&lt;/code&gt;? We get an import error!&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;Traceback&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;most&lt;/span&gt; &lt;span class="n"&gt;recent&lt;/span&gt; &lt;span class="n"&gt;call&lt;/span&gt; &lt;span class="n"&gt;last&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="n"&gt;File&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&amp;lt;frozen runpy&amp;gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="mi"&gt;198&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;_run_module_as_main&lt;/span&gt;
&lt;span class="n"&gt;File&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&amp;lt;frozen runpy&amp;gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="mi"&gt;88&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;_run_code&lt;/span&gt;
&lt;span class="n"&gt;File&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;/Users/Jeremy/Documents/Projects/package/vegetables/celery/dice.py&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;module&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;fruits.apple.slice&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;slice_apple&lt;/span&gt;
&lt;span class="ne"&gt;ModuleNotFoundError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;No&lt;/span&gt; &lt;span class="n"&gt;module&lt;/span&gt; &lt;span class="n"&gt;named&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;fruits&amp;#39;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now why is that? We ran it as a module and it worked from the &lt;code&gt;package/&lt;/code&gt; directory and it worked. Well, 
if we comment out the import statement and see what the &lt;code&gt;sys.path&lt;/code&gt; is, we get:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="s1"&gt;&amp;#39;/Users/Jeremy/Documents/Projects/package/vegetables&amp;#39;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Because our &lt;code&gt;sys.path&lt;/code&gt; is in the &lt;code&gt;vegetables/&lt;/code&gt; directory, that is where we are telling Python our 
project root is. With that as the root location, Python will only look at folders that are downstream 
of the root path and will not infer anything upstream is part of the module.&lt;/p&gt;
&lt;p&gt;What is important is that we are running the code as a module. If we run a progam just as a Python script, 
we have slightly different behavior. With the import still commented out in &lt;code&gt;dice.py&lt;/code&gt;, regardless of where 
we run the code from, the value in &lt;code&gt;sys.path[0]&lt;/code&gt; will be the location of the file itself.&lt;/p&gt;
&lt;p&gt;Running &lt;code&gt;python3 dice.py&lt;/code&gt; from the &lt;code&gt;celery/&lt;/code&gt; directory gives us:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="s1"&gt;&amp;#39;/Users/Jeremy/Documents/Projects/package/vegetables/celery&amp;#39;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;If we ran &lt;code&gt;python3 vegetables/celery/dice.py&lt;/code&gt;, we get the same outuput!&lt;/p&gt;
&lt;p&gt;One thing to remember, your import has to be relative to where Python thinks your root directory is. 
This can be easy to forget as your project grows and you rework your folder structure.&lt;/p&gt;
&lt;p&gt;Hopefully this will help you navigate the Python import system when working on your projects!&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Jeremy Maslanko</dc:creator><pubDate>Sun, 26 Oct 2025 12:00:00 -0400</pubDate><guid>tag:jeremymaslanko.com,2025-10-26:/understanding-python-relative-imports.html</guid><category>misc</category></item><item><title>Similarity Search Explained</title><link>https://jeremymaslanko.com/similarity-search-explained.html</link><description>&lt;p&gt;Large Language Models (LLM's) have exploded in popularity in the last year.  As such, I 
have found there to be many new "AI Practitioners".  These are people who I have found to 
not have the strongest understanding of some of the technical details surrounding these 
models and their various applications.  This is not to generalize any one individual as 
not being technical.  A software engineer who creates a Retrieval Augmented Generation (RAG) 
model is certainly a technical person, even if they don't understand all of the math behind 
it.  But if you are new to the world of AI/ML and are looking for a better understanding 
of some of the math that makes these tools so powerful, continue reading.&lt;/p&gt;
&lt;p&gt;In this post, we will look at a few of the similarity measures that are used for retrieval 
in vector databases.  I mentioned the RAG model above, but what exactly is it?  And why is 
it so commonly used?  The basics of the model are as follows:  a user types a question, that 
question is compared to a database of documents, and then the document in that database that 
is most similar to the question is returned.  After that document is returned, you combine 
it with the original question and run that complete text through the LLM.  In fact, I would 
say that the similarity search is the most important part!  Sure the LLM's that we are using 
are very impressive, but all we are really doing is having it jumble up the 
words that we enter into the prompt.  The hard part, answering the question, comes from the 
document that was returned from the vector database.&lt;/p&gt;
&lt;p&gt;What makes this such a great application for companies to implement, is the ease in which 
you are able to now have a custom chat application on domain specific knowledge.  There is 
no training of a transformer from scratch on some large corpus of private data, or even any 
finetuning.  Simply load the documents into a vector database, query it based on some given text, 
and there you go, you have the answer you were &lt;em&gt;hopefully&lt;/em&gt; looking for!  There are plenty of 
tutorials on how to implement these solutions, so I will leave that for others to explain.&lt;/p&gt;
&lt;h3&gt;What's a vector embedding?&lt;/h3&gt;
&lt;p&gt;Before diving into the various approaches for calculating the similarity, a brief overview of 
what vector embeddings are would be helpful.  In simple terms, it is a numerical representation 
of words.  There are a variety of ways to come up with the values that represent each word or token,
but that is not important right now.  Using libriaries like LangChain and HuggingFace can allow 
you to easily convert the text that you have into numbers.  And once that is done, you can load 
them into a database.&lt;/p&gt;
&lt;h3&gt;So, how do they work?&lt;/h3&gt;
&lt;p&gt;The document that is returned is the one that has the smallest distance between the document and 
the question that was asked.  There are several ways to compute this distance between documents. 
As a reminder, when I refer to the documents, I am referring to the numerical representation of 
these documents (i.e. their embeddings).&lt;/p&gt;
&lt;p&gt;There are three main ways to compute the distance and they are as follows:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Euclidean Distance&lt;/li&gt;
&lt;li&gt;Manhattan Distnace&lt;/li&gt;
&lt;li&gt;Cosine Similarity&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Euclidean Distance&lt;/h3&gt;
&lt;p&gt;Below is the formula for the Euclidean distance:&lt;/p&gt;
&lt;center&gt; $d = \sqrt{\sum_{i=1}^{n}(x_i - y_i)^2}$ &lt;/center&gt;

&lt;p&gt;It may look familiar from a middle school math class.  If we were comparing two, 2-dimensional vectors, 
this formula would be the same as the Pythagorean theorem to find the length of the hypotenuse of a right 
triangle.  When we have vectors with more than 2 dimensions, we are calculating the same thing, just in 
a multi-dimensional space.&lt;/p&gt;
&lt;h3&gt;Manhattan Distance&lt;/h3&gt;
&lt;p&gt;If we are in a city, we can think of the Euclidean as the direct distance between two points as if 
we were able to walk through buildings.  The Manhattan distance essentially takes the buildings 
into account and assumes we can go only in two directions (if we were dealing with two 
dimensional vectors).  For this reason, the Manhattan distance is also called the Taxicab distance. 
The formula is as follows:&lt;/p&gt;
&lt;center&gt; $d = \sum_{i=1}^{n} |x_i - y_i|$ &lt;/center&gt;

&lt;p&gt;As we can see, the Manhattan distance is just the sum of the absolute difference between each element of 
the two vectors.&lt;/p&gt;
&lt;h3&gt;Cosine Similarity&lt;/h3&gt;
&lt;p&gt;The last distance measurement we will discuss is the Cosine Similarity.  This metric focuses on the 
angle between two vectors and not the magnitude.  This is beneficial when the magnitudes of two vectors 
are different which may sway the Euclidean distance to not classify them as similar.  But if for our 
data we do not care about the magnitude, the Cosine Similarity may show that the two are truly 
very similar.&lt;/p&gt;
&lt;center&gt; $cos(\theta) = \frac{\sum_{i=1}^{n} x_i \cdot y_i}{\sqrt{\sum_{i=1}^{n}(x_i)^2} \cdot \sqrt{\sum_{i=1}^{n}(y_i)^2}}$ &lt;/center&gt;

&lt;p&gt;In the numerator, we simply multiply each element pair of the two vectors and then sum their results. 
The denominator has a bit more to it.  Here we are multiplying the L2 norm of each vector.  The L2 Norm 
is calculated by taking the square root of the sum of the squares of each element in the vector.&lt;/p&gt;
&lt;h3&gt;Python Implementation&lt;/h3&gt;
&lt;p&gt;Now that we have an understanding of the formulas, we can implement these in Python.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;numpy&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nn"&gt;np&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;VectorSimilarity&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;#39;&amp;#39;&amp;#39;&lt;/span&gt;
&lt;span class="sd"&gt;    Class for vector similarity search.&lt;/span&gt;

&lt;span class="sd"&gt;    - x, y: equal length numpy arrays&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;#39;&amp;#39;&amp;#39;&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;array&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;array&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;

        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_validate_params&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_validate_params&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ndarray&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="nb"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ndarray&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;pass&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="ne"&gt;TypeError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Input arrays must be equal length ndarrays!&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;euclidean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="sd"&gt;&amp;#39;&amp;#39;&amp;#39;Returns the euclidean distance between the two vectors&amp;#39;&amp;#39;&amp;#39;&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sqrt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;square&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;))))&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;manhattan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="sd"&gt;&amp;#39;&amp;#39;&amp;#39;Returns the manhattan distance between the two vectors&amp;#39;&amp;#39;&amp;#39;&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;absolute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;cosine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="sd"&gt;&amp;#39;&amp;#39;&amp;#39;Returns the cosine similarity between the two vectors&amp;#39;&amp;#39;&amp;#39;&lt;/span&gt;

        &lt;span class="n"&gt;num&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;denom&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sqrt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;square&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sqrt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;square&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
        &lt;span class="n"&gt;cos_theta&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;num&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;denom&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;cos_theta&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Hopefully now you have a better understanding of some of the similarity metrics used in vector retrieval. This functionality is at the core of RAG models when using LLM's on domain specific documentation!&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Jeremy Maslanko</dc:creator><pubDate>Sun, 14 Jan 2024 12:00:00 -0400</pubDate><guid>tag:jeremymaslanko.com,2024-01-14:/similarity-search-explained.html</guid><category>misc</category></item><item><title>I'm Sick of LLM's</title><link>https://jeremymaslanko.com/im-sick-of-llms.html</link><description>&lt;p&gt;A few hours after Google announced their new family of language models named Gemini, I was 
asked what I thought about them.  I gave an honest answer, that I hadn't really looked into 
them.  I saw a headline that they had were released, and shrugged it off.  Why?  Well, I'm 
kind of sick of them..&lt;/p&gt;
&lt;p&gt;When I say this, it is not meant to discredit or downplay how impressive the models are. 
The ability to ask a question about linear algebra followed by a prompt to write a poem in 
the tone of pirate and confidently get a response that is &lt;em&gt;generally&lt;/em&gt; accurate is incredible. 
I remember less than two years ago messing around with BERT and its variants, but being dissapointed 
in respones that were nonsensical.&lt;/p&gt;
&lt;p&gt;But with that said, I've become a bit unimpressed with new models.  Most of them tell you 
that they are some percentage point better than other models on a variety of different metrics. 
And while that is progress, it's just not that exciting to me.  On top of that, the architecture 
of the models are largely the same, which means the differences stem from variations in training 
data.  And if your model is only performing better because your model has ___ billion parameters 
more than the last, great.  I am happy that you and your team have access to more compute.&lt;/p&gt;
&lt;p&gt;What has been impressive to me, are the smaller models.  In fact, I think most of the productivity 
gains we see will be from these smaller models.  Retrieval Augmented Generation (RAG) models are 
one of the more common ways to use LLM's on domain specific data.  The magic sauce for those is 
not the LLM, but the similarity search returning the context.  As long as the LLM is able to 
produce coherent text, all it needs to do is mix up the input to provide the answer.  In simple 
experiments, I have found that 3 bit quantized models are more than sufficient compared to 8 bit 
or even full base models.  Perhaps I should run an actual experiment, I am a data scientist 
after all.&lt;/p&gt;
&lt;p&gt;Maybe I'm not sick of LLM's.  Maybe it's just that I am sick of people talking about "AI" as 
if it's something magical new inventions.  AI is not some sentient thing, its a vague way to 
refer to complex applied math.  Or maybe I am just a jaded data scientist sick of hearing 
people talk about AI who couldn't tell you how to multiply two vecotrs or explain what a 
p-value is.  It's probably the later.&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Jeremy Maslanko</dc:creator><pubDate>Sat, 16 Dec 2023 12:00:00 -0400</pubDate><guid>tag:jeremymaslanko.com,2023-12-16:/im-sick-of-llms.html</guid><category>misc</category></item><item><title>My First Post</title><link>https://jeremymaslanko.com/my-first-post.html</link><description>&lt;p&gt;Welcome to my blog!  I am very excited to begin this project as I expect to be able to learn a lot from it.  My hope is that you will be able to pick something up along the way, whether it is a better understanding of a specific topic or an appreciation for a different viewpoint.&lt;/p&gt;
&lt;p&gt;As I am sure you may have noticed, this isn't the prettiest site at the moment.  I wanted to build this blog from scratch, so I have some work to do.  But at the same time, my motivation to work on formatting HTML/CSS remains low.&lt;/p&gt;
&lt;p&gt;I have no set plans on how often I will post.  My hope is that I will find a blend of short, quick posts that are easier to churn out mixed in with some longer posts that are more thoughtful and take more time.  The topics will vary from tech to math to finance and business.  These are all topics that I find very interesting.  Additionally, I am sure I will write about other things that are not part of those broader topics.  That said, I do hope to post with some sort of regularity.&lt;/p&gt;
&lt;p&gt;If I write about anything and make an error, please reach out so that I can correct it.  My intention will never be to lead anyone astray.  Feel free to reach out if you would like to discuss anything or talk about something I have written about.  Just as I hope you can learn something from my posts, I am sure there is more that I can learn from you.&lt;/p&gt;
&lt;p&gt;And with that, I can't think of anything else to share at this moment.&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Jeremy Maslanko</dc:creator><pubDate>Wed, 13 Sep 2023 00:00:00 -0400</pubDate><guid>tag:jeremymaslanko.com,2023-09-13:/my-first-post.html</guid><category>misc</category></item></channel></rss>