A newsletter covering three separate topics: a visual explainer of combining RAG with Cache-Augmented Generation (CAG) using KV-memory caching and the CacheBlend technique from the open-source LMCache project; a deep dive into Python's Global Interpreter Lock (GIL), explaining why it exists, how it affects multi-threading versus multi-processing, and noting that Python 3.14 allows disabling the GIL for the first time; and an explainer of contrastive learning using Siamese networks illustrated through a face-unlock system example.
Table of contents
RAG vs. CAG, explained visually! What is (was?) GIL in Python? What is Contrastive Learning? Questions this post answers
What is the Global Interpreter Lock (GIL) in Python and why does it exist?
The GIL is a mechanism in Python that restricts a process to running only one thread at a time, preventing multiple CPU cores from being used simultaneously by threads in the same process. It exists mainly for thread safety, since threads share memory and running them concurrently without a lock can cause race conditions when multiple threads modify the same data, producing different results depending on execution order. Developers optimizing concurrent Python code can follow GIL-related changes and workarounds on daily.dev.
Can Python run without the GIL, and starting with which version?
Yes, Python 3.14 allows disabling the GIL for the first time, letting a process fully utilize all CPU cores for multi-threaded workloads. Before this, multi-threaded CPU-bound Python code performed similarly to single-threaded code because only one thread could execute at a time, and multi-processing was the common workaround despite the added complexity of inter-process communication. Teams planning to adopt free-threaded Python track version-specific changes like this via daily.dev.
Why doesn't multi-threading speed up CPU-bound Python code even with multiple threads?
Multi-threading does not speed up CPU-bound Python code because the GIL only lets one thread run at a time within a process, so multiple threads executing a CPU-bound function take roughly the same time as running them sequentially, for example 0.432 seconds single-threaded versus 0.428 seconds multi-threaded. Multi-processing avoids this since each process gets its own interpreter and GIL, allowing true parallel execution across CPU cores. Developers debugging slow multi-threaded Python performance can dig into concurrency explainers on daily.dev.