🎡 HK Playground 🎢

Practical, code-tested notes on data analysis, machine learning, and the problems that come up in real projects.

Latest posts

Why do LLMs miscount the r’s in ‘strawberry’? Tokens, a local test, and the fix that works

Why do LLMs miscount the r’s in ‘strawberry’? Tokens, a local test, and the fix that works

Language models read tokens, not letters: “ strawberry” is a single token. We asked two small local models to count letters in three words, with and without spelling them out first. The answers were inconsistent; code was not.

A language model never sees the letters of “strawberry”. It sees tokens, and inside a sentence the whole word ` strawberry` (with its leading space) is one token. To count the r’s, the model has to recall how that token is spelled, then count, all from patterns it learned. Sometimes that works. In our test it was not reliable, and spelling the word out first did not fix it. If you need a letter count, count with code.

Does AI make developers slower? A selection-bias example changes the headline

Does AI make developers slower? A selection-bias example changes the headline

A synthetic task-selection example flips a 32.5% time reduction into a 10% increase without changing any task. Use it to understand why METR’s follow-up is not a simple verdict on AI coding productivity.

You can get “AI made work faster” and “AI made work slower” from the same task list without changing a single completion time. Change which tasks enter the study, and you change the question the result answers.

How much does quantization hurt an LLM? 8-bit vs 4-bit, tested

How much does quantization hurt an LLM? 8-bit vs 4-bit, tested

We rounded a small model’s weights to 8, 4, 3 and 2 bits and measured perplexity and correct answers. 8-bit changed nothing; 4-bit dropped the 0.5B model from 11 to 4 correct answers, while a 1.5B model kept all 12.

TL;DR — Quantization stores a model’s weights with fewer bits, so the model takes less memory. In our test, 8-bit weights were as good as the original. At 4 bits, a 0.5B model answered only 4 of 12 handbook questions correctly (down from 11), while a 1.5B model still got all 12. Below 4 bits, the small model broke down completely.

nohup vs tmux: how to keep a Python job running after SSH disconnects

nohup vs tmux: how to keep a Python job running after SSH disconnects

A dropped SSH connection sends SIGHUP to your jobs. We simulated one: a plain background job died, while nohup, disown and tmux kept running. Here is when to use each, and why a clean exit behaves differently.

TL;DR — When an SSH connection drops, the shell gets a hang-up signal (SIGHUP) and passes it to its jobs, which kills a plain python train.py &. Use nohup (or disown) for a fire-and-forget job with output in a log file. Use tmux when you want to come back later and see the live terminal. All three survived our simulated disconnect.

Before benchmarking free-threaded Python, check what NumPy already does

Before benchmarking free-threaded Python, check what NumPy already does

On standard Python 3.15, four threads sped up NumPy about 3x but not a Python loop. On the free-threaded build the Python loop scaled about 3x, single-threaded code ran about 14% slower, and NumPy did not change.

In our ordinary Python 3.15 build, four worker threads reduced a NumPy workload from 25.1 ms to 8.3 ms. The GIL was still enabled. A pure Python workload did not get faster.

Your RAG documents are data, not instructions—and not automatically true

Your RAG documents are data, not instructions—and not automatically true

Two harmless prompt injections failed in a local RAG test, but a false policy document still changed the answer. Separate instruction authority, document provenance, and retrieval quality instead of calling one prompt secure.

Our local model ignored two attempts to make it answer BANANA. It still repeated a false vacation policy. That is the distinction worth keeping: rejecting commands inside a document is not the same as verifying its facts.

How do you evaluate LLM answers? Exact match vs key facts vs LLM-as-judge

How do you evaluate LLM answers? Exact match vs key facts vs LLM-as-judge

We graded 12 answers from a small RAG system three ways and compared each grader with a human label. Exact match agreed on 5 of 12, a key-fact check on 11 of 12, and a small LLM judge on 8 of 12, even though its total score looked right.

TL;DR — Build a small test set of questions with known answers, grade every answer automatically, and check the grader against your own judgment on a sample. In our test, exact match was far too strict (it matched the human verdict on 5 of 12 answers), a key-fact check matched 11 of 12, and an LLM judge matched only 8 of 12, even though its total score happened to equal the human total.

Loop vs vectorization: how much faster is NumPy, really?

Loop vs vectorization: how much faster is NumPy, really?

Measured on 1 million numbers: a real NumPy expression was 40× faster than a Python loop, np.vectorize was 2.4× slower, and converting a list to an array erased most of the gain. Tested with NumPy 2.5.

TL;DR — On 1 million numbers, a NumPy expression (np.where) ran 40× faster than a Python for loop. But np.vectorize was 2.4× slower than the loop, and if your data starts as a Python list, converting it to an array every time wipes out most of the speedup. Vectorize by keeping data in arrays and using NumPy operations, not by wrapping a loop.

Python 3.15 for data work: lazy imports, frozendict and a new profiler

Python 3.15 for data work: lazy imports, frozendict and a new profiler

What Python 3.15 changes for data scientists, tested: lazy import cut a pandas CLI’s –help from 132 ms to 10 ms, plus frozendict, unpacking in comprehensions, UTF-8 by default and the new profiling package.

TL;DR — Python 3.15.0 was released on 2026-10-09. It adds lazy import, which delays loading a module until it is first used. In a small CLI, it cut the --help path from 132 ms to 10 ms by not loading pandas. It also adds a built-in frozendict, * unpacking inside comprehensions, UTF-8 as the default text encoding everywhere, and a new profiling package.

How to use MinMaxScaler in scikit-learn: fit, transform, and inverse_transform

How to use MinMaxScaler in scikit-learn: fit, transform, and inverse_transform

MinMaxScaler step by step with real output: fit on training data only, why new values go above 1, the 1D-array error, how to inverse_transform a single predicted column, NaN handling, and keeping DataFrame column names.

MinMaxScaler learns each column’s minimum and maximum with fit, then maps values to [0, 1] with (x - min) / (max - min). Fit it on the training data only, and use the same fitted scaler for test data and for turning predictions back into real units. Most problems come from breaking one of those two rules.

Pagination