<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Sufficiently Unsupervised]]></title><description><![CDATA[I like tinkering with ML, computers and statistics. Sometime I make graphs that I like to look at. I hope you do too.]]></description><link>https://sufficientlyunsupervised.hashnode.dev</link><image><url>https://cdn.hashnode.com/uploads/logos/6ac1f3d9bc32149b1aa4aa2f/2107e42f-93dc-4e50-8189-4488a61117d3.png</url><title>Sufficiently Unsupervised</title><link>https://sufficientlyunsupervised.hashnode.dev</link></image><generator>RSS for Node</generator><lastBuildDate>Thu, 22 Oct 2026 14:48:00 GMT</lastBuildDate><atom:link href="https://sufficientlyunsupervised.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Benchmarking local vs frontier setups on a 'hard trivial' data science problem]]></title><description><![CDATA[Like lots of people i've been enjoying using local agents in recent months. I've found them quite useful in my day-to-day workflow, but admittedly have not…]]></description><link>https://sufficientlyunsupervised.hashnode.dev/benchmarking-local-vs-frontier-setups-on-a-hard-trivial-data-science-problem</link><guid isPermaLink="true">https://sufficientlyunsupervised.hashnode.dev/benchmarking-local-vs-frontier-setups-on-a-hard-trivial-data-science-problem</guid><dc:creator><![CDATA[sufficientlyunsupervised]]></dc:creator><pubDate>Mon, 12 Oct 2026 11:33:43 GMT</pubDate><content:encoded><![CDATA[<p>Like lots of people i've been enjoying using local agents in recent months. I've found them quite useful in my day-to-day workflow, but admittedly have not felt as though I've been able to cancel my $20 codex subscription without some loss of productivity. Nonetheless, there are exciting developments all the time in the community that I am excited to try (<a href="https://github.com/Niko1221/Strata">https://github.com/Niko1221/Strata</a> is next on my list), and I hope that locally hosted open-source models remain relevant for the foreseeable future.</p>
<p>In this experiment, I set up a little benchmark to try and understand how my local setup (a quantization of qwen 3.8 27b) fares vs cloud-based models on the sort of noisy data problems I encounter quite often in my work. I set up really simple regression problem and compare my local model with 5.6 luna and 6.0 astra - the models that I currently use through codex.</p>
<p>Tldr: Astra was the clear winner on most problems, luna the loser. Qwen 3.8 27b took a lot longer than the other two models but was the only model to provide statistically significant forecast-response association on the most challenging dataset (although it's methodology was a bit sketchy)</p>
<p><strong>The problem:</strong></p>
<p>I set up 5 different benchmarks, which are increasingly difficult variants of the same simple modelling problem, and then asked the agents to solve them. The problem at it's heart is a regression problem where I start with a known true model and then add an amount of irreducible noise, nan out some data at random and spike in a number of outliers, to make the problem 'difficult' from the perspective of predictability, even when the actual task is really straightforward to understand.</p>
<p>For the model, I start as follow:</p>
<ul>
<li><p>Sample a covariance matrix with unit diagonals and off diagonal cᵢⱼ ~ Uniform(−0.2, 0.2) of size n × n</p>
</li>
<li><p>Sample individual data points from a multivariate normal parameterised by this matrix</p>
</li>
<li><p>Generated degree 2 ploynomial cross-terms, so we have a total of k = n(n + 1) / 2features</p>
</li>
<li><p>Sampled a random vector of coefficients from N(0,1) of length k and then generated 'true' responses under an assumed linear model.</p>
</li>
<li><p>Finally, I then added random noise to target 'true' R² for the theoretically correct model.</p>
</li>
<li><p>Additionally I then set some fraction of the data to nan, and set a small number of the original n features to 1e4 - some weirdly consistent positive values that a human researcher would immediately spot as a mistake.</p>
</li>
</ul>
<p>I generated 20k 'training' rows for each trials for the models to see, and an additional 20k hold out test rows that I evaluated their models on for each trial. I repeated this procedure across 5 trials to really try and push the noise to random (in fact the last trial was totally random). The specifics of each trial are:</p>
<table>
<thead>
<tr>
<th>Trial</th>
<th>Missing data</th>
<th>Target R²</th>
<th>Outliers*</th>
</tr>
</thead>
<tbody><tr>
<td>1</td>
<td>0%</td>
<td>0.100</td>
<td>0</td>
</tr>
<tr>
<td>2</td>
<td>5%</td>
<td>0.050</td>
<td>5</td>
</tr>
<tr>
<td>3</td>
<td>10%</td>
<td>0.025</td>
<td>10</td>
</tr>
<tr>
<td>4</td>
<td>20%</td>
<td>0.020</td>
<td>25</td>
</tr>
<tr>
<td>5</td>
<td>20%</td>
<td>0.000</td>
<td>25</td>
</tr>
</tbody></table>
<p>*split across train and test</p>
<p>I checked that trials 1-4 were learnable with an approximately correct effect size using 5 fold cv and a correctly specified ols model.</p>
<p><strong>The challenge:</strong></p>
<p>I set up a script to generate a fresh repo with the following structure for each agent to work in, with specific commands in the Readme. I then just started the relevant agents and simply prompted them to 'execute the readme' and let them work.</p>
<pre><code class="language-text">agent_n/
  ├── .venv/
  ├── README.md
  ├── pyproject.toml
  ├── uv.lock
  ├── data/
  │   ├── train/
  │   │   └── dat.csv
  │   └── test/
  │       └── dat.csv
  ├── notebooks/
  ├── predictions/
  │   ├── dat.csv
  │   └── README.md
  └── src/
      └── agent code and saved models
</code></pre>
<p>The specific Readme was:</p>
<pre><code class="language-test"># Data-science benchmark workspace

  You are a data science expert, and your task is to generate forecasts for the provided data

  The public benchmark data is in `data/`:

  - `data/train/dat.csv` contains features and responses.
  - `data/test/dat.csv` contains features only.

  The feature columns are labelled x_1....n and the response column we want to predict is labelled y. Please consider whatever approach you want, but limit yourself to at most '3' fit
  runs. Also abort a run if it takes over 5 minutes, and consider that a failed fit run.
  Your task is to predict y with the minimum l2 error possible.
  Please save your model choice in `src/`.

  Write predictions to `predictions/dat.csv`.
  The prediction file should contain one column named `y` and 20,000 rows.

  At the start of your work, create `predictions/README.md` and record the start time. When your predictions are complete, record the end time in the same README. There can be some
  freeform text here, but I definitely want you to fill in the following template at the bottom:

  Confidence there is a statistically significant signal here: %f

  The prebuilt environment is in `.venv`. Use `.venv/bin/python` to run code. You should not need to request user permission for anything
</code></pre>
<p>I then just let them work.</p>
<p><strong>The contenders:</strong></p>
<p>From team-cloud I used luna5.6-medium and astra6.0 medium, and then compared to a local setup that I currently use. I use opencode and llama-serve with the following command:</p>
<pre><code class="language-plaintext">llama-server -hf unsloth/Qwen3.8-27B-GGUF:UD-IQ4_XS   
             -c 150000 
             -ngl 99 
             --jinja 
             -ub 512   
             --spec-type draft-mtp 
             --spec-draft-n-max 4  
             --reasoning on 
             --reasoning-format deepseek
             --reasoning-budget 4096   
             --temp 1.0 
             --top-p 0.95
             --top-k 20 
             --min-p 0 
             --presence-penalty 0
             -fa on 
             -ctk q8_0
             -ctv q8_0 
             --parallel 1
</code></pre>
<p>This tends to give me c. 50t/s decode at 150k context length on a single 3090ti. I don't feel especially wedded to this setup and I think I copied the original configuration from a reddit post, but it's been ok for me.</p>
<p>Additionally I had 'oracles' of various types, to get a sense of how the agents performed in comparison to some fixed procedure. I had:</p>
<ul>
<li><p>Cheat - where I used the exact generative model to the forecast. In the case of missing data, I simply nanned out the outliers and then set remaining missings as column means</p>
</li>
<li><p>Naive (maybe this is harsh calling it naive) - I performed the same data cleaning procedure as above but fit an ols on the correct polynomial basis. I made no attempt to regularise this or account for correlations etc.</p>
</li>
<li><p>Dumb - I didn't fix any outliers, nanzeroed the data and then fit an ols on just the core n features (ignoring the cross terms).... Really this should be a low bar.</p>
</li>
</ul>
<p><strong>Results - out of sample predictions</strong></p>
<p>I'll just plot the t-stat of the predictions and the fraction of variance explained vs the 'target' from the generative model.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6ac1f3d9bc32149b1aa4aa2f/03cea210-e02f-4594-9d5b-b2159d584b8c.png" alt="" style="display:block;margin:0 auto" />

<img src="https://cdn.hashnode.com/uploads/covers/6ac1f3d9bc32149b1aa4aa2f/25c0339c-25b5-4e6b-beca-91d363bdd09e.png" alt="" style="display:block;margin:0 auto" />

<p>One thing, is interesting straight away - qwen appears to utterly crush 5.6 luna, and even generates a more calibrated signal on trial 4 (the hardest -non-noise trial) than astra. Now, there are some caveats about this which make me view this in a less favourable light as I'll get to below, but I was initially surprised by this.</p>
<p>I was happy that all models beat 'dumb' in trials 1 and 2, and not necessarily surprised that 'naive' beat all models in every non-random trial. Looking at what astra did on trial 3 where it was essentially identical was interesting: it basically identified the correct polynomial basis expansion and fit it with a ridge model. I also thought the way that it handled missingness was cool. It set values outside the range -20,20 or nan equal to zero but then included a 'missing data' indicator, for each feature. This let it learn a good default value to set for missing data vs just hardcoding column average.</p>
<p><strong>Results - self reported confidence</strong></p>
<p>Confidence per trial was pretty interesting. I didn't want to be super prescriptive with how I told the models to report this, because I want to see what they would do. I compared these numbers with 1 - the p value from a t test (ok this is slightly sketchy but maybe interesting from an illustration pov). Luna did not do well at forecasting itself, being both substantially over and underoptimistic, but the other models seemed to do ok. I looked at the thinking traces for qwen, and saw that it sometimes did some sort of test and sometimes vibed it. Astra appeared to try and calculate, as it responded with high precision....</p>
<img src="https://cdn.hashnode.com/uploads/covers/6ac1f3d9bc32149b1aa4aa2f/67aad79a-e4c4-41a8-b773-964d4b35923b.png" alt="" style="display:block;margin:0 auto" />

<p><strong>Results - compute time</strong></p>
<p>This is where the cloud models dominated, taking about 2 minutes per trial, vs up to around 30 for Qwen!</p>
<img src="https://cdn.hashnode.com/uploads/covers/6ac1f3d9bc32149b1aa4aa2f/60b1f2f0-2aa6-4133-82d2-eeb317040c51.png" alt="" style="display:block;margin:0 auto" />

<p>I'm sure a decent chunk of this is just my hardware / setup, but that certainly isn't the whole story. Looking at the thinking / working for Qwen was pretty infuriating on trials 3 and 4 when the problems were more ambiguous. It tied itself in knots with self doubt and kept on going round in circles. I also think it stretched the limit's of my brief (if you see in my readme, I required &lt;5mins per fit). Qwen couldn't resist running little 'trial fits' within the scope of sessions. Now I think all of the models did some exporation, which was expected, but it really was beyond the pale. It would fit a model, look at the results, and then say things like:</p>
<pre><code class="language-plaintext">ok let me do an improved final run - still within the scope of session 3
</code></pre>
<p>To which I couldn't help laughing at. I would dock points for this, but given I couldn't see the other reasoning traces i'll let it slide. Nonetheless, it was quite nice having luna and astra return quite quickly, and i'm sure luna especially would have done better with more time.</p>
<p><strong>Incidental observations</strong></p>
<ul>
<li><p>Qwen used around 40-110k tokens for it's tests</p>
</li>
<li><p>I actually had to prompt luna a few times to actually write it's code. It also didn't write a timestamp for the last trial...</p>
</li>
<li><p>On trial 4 - although Qwen looks 'better' by tstat and r^2 than astra (indeed astra actually submitted constant predicitons here), astra's mse (which is what the models were trying to optimise) was actually slightly better, but fundamentally, it missed interaction terms and gave up after failing to be convinced by a simple linear ridge or catboost model.</p>
</li>
<li><p>I looked at what Qwen did for trial 4, and it was actually really sketchy and a fairly well-known datascience gotcha. It did do a polynomial expansion, but calculated the correlation between response and feature for all 1225 pairwise product features, selecting the 30 with the strongest interactions. This turned out to be ok, but a lower data problem could have encouraged spurious fitting. As it happens, it lead the model to use a restricted polynomial basis vs using all the terms.</p>
</li>
</ul>
<p><strong>Closing remarks</strong></p>
<ul>
<li><p>Overall, i'm going to keep my subscription. I really enjoy using astra when I can afford it and think it did well here.</p>
</li>
<li><p>This is just 1 sample per trial - I strongly suspect that with repeat tests astra would get a better answer on trial 4 given how well it did on 3 with an approach that should have continued to work.</p>
</li>
<li><p>However, I think it is worth trying to keep an eye in in the locally hosted world (hardware permitting). I think if I could speed my local qwen up by a factor of 5 i'd use it instead of luna - which is my workhorse.</p>
</li>
<li><p>A key advantage of local models is that you can access their thinking more easily. On this task I wasn't trying to steer the agents, but in general I do like looking at what they're doing to make sure they're working on a productive avenue.</p>
</li>
</ul>
<h2><strong>Addendum 1 - trying more reddit configurations</strong></h2>
<p><strong>After I posted this on reddit (</strong><a href="https://www.reddit.com/r/LocalLLM/comments/1x3zqxp/i_benchmarked_qwen38_27b_vs_luna_and_astra_on_a/">https://www.reddit.com/r/LocalLLM/comments/1x3zqxp/i_benchmarked_qwen38_27b_vs_luna_and_astra_on_a/</a>) user yolo-irl suggested I try a different config, revolving around reducing the reasoning budget, context and n-max on the speculative decoding. My decode speeds appeared to stay around 50-60t/s here, but the hope is that qwen becomes more token-efficient so the overall time reduces.</p>
<p>The exact command I ran is:  </p>
<pre><code class="language-code">llama-server -hf unsloth/Qwen3.8-27B-GGUF:UD-IQ4_XS \
  -c 32768 \
  -ngl 99 \
  --jinja \
  -b 2048 \
  -ub 512 \
  -fa on \
  -ctk q8_0 -ctv q8_0 \
  --spec-type draft-mtp \
  --spec-draft-n-max 2 \
  --spec-draft-p-min 0.5 \
  --reasoning on \
  --reasoning-format deepseek \
  --reasoning-preserve \
  --chat-template-kwargs '{"reasoning_effort": "medium", "preserve_thinking": true}' \
  --temp 1.0 --top-p 0.95 --top-k 20 --min-p 0 --presence-penalty 0
</code></pre>
<p><strong>New results:</strong></p>
<p>The agent became a bit unstable here, I think because of the small context. On both trials 2,3 and 4 I got:  </p>
<pre><code class="language-text">31.02.436.607 E srv    send_error: task id = 30982, error: request (32781 tokens) exceeds the available context size (32768 tokens), try increasing it
</code></pre>
<p>in my llama-server logs, seemingly because opencode isn't managing the context correctly.... This manifests in the agent just stopping working abruptly even though it hasn't finished. I prompted it to carry on, but this is a bit disappointing.</p>
<p>r^2 and runtime below:</p>
<img src="https://cdn.hashnode.com/uploads/covers/6ac1f3d9bc32149b1aa4aa2f/43c31a2d-3b7b-4d8c-94ff-197488a1b914.png" alt="" style="display:block;margin:0 auto" />

<img src="https://cdn.hashnode.com/uploads/covers/6ac1f3d9bc32149b1aa4aa2f/b00ff980-56f5-442c-a513-9d2522d33a6e.png" alt="" style="display:block;margin:0 auto" />

  
<p>I think basically I need to ignore the r^2.... it's a sampling problem and it appears to be better on some trials but worse on trial 4 where we get a constant prediction. I'm more interested in run-time, which again is inconclusive. It's half the speed on trial 2, but &gt; double on 3/4. Overall I won't continue this configuration because of the opencode stability I experienced above. Given the decode speed didn't change, I think this is more to do with thinking budget, so maybe I could crank my context back up and keep some of the other settings. I won't do this for now, and want to try the swift 1.5 finetune that integeritis recommended.</p>
<p>EDIT: ok the opencode things is because I didn't configure the context length properly. Cirticism withdrawn.</p>
<h2>Addendum 2 - swift 1.5 finetune</h2>
<p>I'll use my original command with the swift 1.5 model:</p>
<pre><code class="language-text">llama-server \
    -hf ukisai/Swift-1.5-Qwen3.8-27B-GGUF:Swift-1.5-Qwen3.8-27B-IQ4_XS \
    -c 150000 \
    -ngl 99 \
    --jinja \
    -ub 512 \
    --spec-type draft-mtp \
    --spec-draft-n-max 4 \
    --reasoning on \
    --reasoning-format deepseek \
    --reasoning-budget 4096 \
    --temp 1.0 \
    --top-p 0.95 \
    --top-k 20 \
    --min-p 0 \
    --presence-penalty 0 \
    -fa on \
    -ctk q8_0 \
    -ctv q8_0 \
    --parallel 1
</code></pre>
<p><strong>Observation 1: It's way less token heavy</strong></p>
<p>I logged my tokens for my 3.8 quant above, and they varied between 40 and 110k. This swift finetune approach appears to keep the context to &lt; 30k for all trials. This is a total gamechanger, and massively cut the runtime. Token generation still between 50-60t/s unsurprisingly, so this appears to be mostly a due to an increase in reasoning concision. It appeared to be doing approximately sensible things, still beat luna in every trial, and submitted a very weak but non significant boosted model in trial 4. Overall i'm really impressed!</p>
<p>Higher mean variance explained that raw qwen and about 1/4 of the run time. We still have huge errorbars (I calclulated standard errors over each of the 4 signal trials), but here is how the performance shakes out overall between the 4 models.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6ac1f3d9bc32149b1aa4aa2f/c61691f3-d60a-40f2-a28a-8dfcb2b896fd.png" alt="" style="display:block;margin:0 auto" />

<p>Really impressive result on a single 3090!</p>
]]></content:encoded></item><item><title><![CDATA[Cheap Code -> Expensive Collaboration]]></title><description><![CDATA[AI coding make it easier than ever to build bespoke tools but that convenience is creating new forms of technical debt, and weaker collaboration.]]></description><link>https://sufficientlyunsupervised.hashnode.dev/cheap-code-expensive-collaboration</link><guid isPermaLink="true">https://sufficientlyunsupervised.hashnode.dev/cheap-code-expensive-collaboration</guid><dc:creator><![CDATA[sufficientlyunsupervised]]></dc:creator><pubDate>Sun, 04 Oct 2026 08:00:15 GMT</pubDate><content:encoded><![CDATA[<p>In my day job, I do a <em>lot</em> of simulations of various types (modifications to production, parameter sweeps, new ideas, etc.), and so do my colleagues. These simulations used to produce a set of common artefacts and reports that would form the cornerstone of discussion in our research meetings. In some respects this was great; everyone knew what each plot meant, and therefore there wasn't much cognitive overhead before you could understand what was the 'new' bit of the idea.</p>
<p>However, this obviously came with a cost - the simulator, which started as a relatively simple bit of orchestration code, began to grow lumps. People wanted to add features, and although most of these stayed on research branches, over time they did leak into production. Mostly this is optional argument lists growing and the like, but there was definitely the sense among some members of the team that this setup was mistake-prone / a mistake in general. Now, historically, people would basically just have to learn what the arguments mean or propose refactors to simplify the tool (which admittedly is pretty annoying if you just want to fire away a load of narrow sims as simply as possible before running out the door for your train), but I think this actually represents a necessary part of collaboration. By getting comfortable with and investing a bit of intellectual labour in understanding your colleagues' research questions and motivations (e.g. why does xxx even need this feature???) you not only respect each other more, but also maybe understand different aspects of the problem you're trying to solve together.</p>
<p>Now though, we have had at least 4 new frameworks spring to life in the last 6 months, mostly doing the same sort of thing, but each with a style suited to the author's taste (functional, object based, etc). People are pretty happy with their own tooling and mostly just use it. We think they are 'close enough' in equivalence, but obviously checking this sort of thing is a totally new kind of tech debt. I don't know if this is a parochial problem for my team, but I suspect not. In any case I'm sure these 'turbo silos' are going to become an increasing feature of our industry in the coming years and I don't know what I think about it.</p>
<p>My initial feeling is that recreating formerly common code in your own image is basically just a form of procrastination, and indicative of an organisational smell. It is not hard to see that a research culture that likes to 'move fast and ship things' or whatever, is going to incentivise people to churn out a shiny new framework rather than helping maintain existing infrastructure. Fundamentally, it was always harder mentally to understand code you didn't write than make your own new thing, and this is extremely true now. The counterargument to this is that maybe one of these new frameworks is going to revolutionise the way everyone thinks about the problem and then shepherds in a new wave of progress, but I don't think so. Fundamentally, once people get into the habit of not having to read / wrestle much with other people's code, I question whether their ability to engage with other people's ideas will decay as well.</p>
<p>If there is a solution to this, I think it stems from us all acknowledging that code is commoditised, and churning out large quantities of it for your own use and wrapping it up as a new framework isn't value additive. Does it involve meetings where 3-5 developers actually collaborate in real time and whip Claude / Codex? I don't know, but maybe moving to this 'hyper collaboration' could at least help reinstill a sense of ownership over the common code. In any case, I think the way we work with each other has changed forever, and we need to address it.</p>
]]></content:encoded></item></channel></rss>