<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: PEPPERCORN</title>
    <description>The latest articles on Forem by PEPPERCORN (@peppercorn_llm).</description>
    <link>https://forem.com/peppercorn_llm</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3910738%2F8084bbca-3641-4d19-85b2-f53a184e1f84.jpg</url>
      <title>Forem: PEPPERCORN</title>
      <link>https://forem.com/peppercorn_llm</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/peppercorn_llm"/>
    <language>en</language>
    <item>
      <title>[Day 3] I Had a Local LLM Analyze a Year of My Credit Card Statements</title>
      <dc:creator>PEPPERCORN</dc:creator>
      <pubDate>Tue, 05 May 2026 22:52:50 +0000</pubDate>
      <link>https://forem.com/peppercorn_llm/day-3-i-had-a-local-llm-analyze-a-year-of-my-credit-card-statements-4eab</link>
      <guid>https://forem.com/peppercorn_llm/day-3-i-had-a-local-llm-analyze-a-year-of-my-credit-card-statements-4eab</guid>
      <description>&lt;h1&gt;
  
  
  [Day 3] I Had a Local LLM Analyze a Year of My Credit Card Statements
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Intro
&lt;/h2&gt;

&lt;p&gt;Day 3: I'm going to hand a year of credit card statements over to a local LLM and see what it can do.&lt;/p&gt;

&lt;p&gt;This is experiment #3.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What I'm using today: DGX Spark + &lt;a href="https://ollama.com/" rel="noopener noreferrer"&gt;Ollama&lt;/a&gt; + &lt;a href="https://qwenlm.github.io/" rel="noopener noreferrer"&gt;Qwen2.5&lt;/a&gt; (comparing 7B vs 72B). Ollama is the de-facto local-LLM runtime, and Qwen2.5 is a multilingual model from Alibaba (China) that handles Japanese reasonably well, apparently.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Today's setup
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Data&lt;/strong&gt;: 12 months of credit card statements from a single card.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Volume&lt;/strong&gt;: 383 transactions, ¥2,761,555 in total spend.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Goal&lt;/strong&gt;: get the AI to spot waste patterns and propose savings.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Comparison axes&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Model size&lt;/strong&gt;: 7B (light) vs 72B (heavy)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Input format&lt;/strong&gt;: raw CSV vs pandas-aggregated summary&lt;/li&gt;
&lt;li&gt;→ &lt;strong&gt;4 patterns&lt;/strong&gt; total&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Takeaway&lt;/strong&gt;: "If you ask an AI to aggregate raw data, the numbers come out way off." / "If you pre-aggregate with a spreadsheet tool first and then feed the AI, you get fast and accurate results." A small but practical finding.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Get the CSVs onto the DGX
&lt;/h2&gt;

&lt;p&gt;Log into the credit card company's web statements page on myPC1 (my Windows laptop), download 12 months of CSVs, then push them to the DGX.&lt;/p&gt;

&lt;p&gt;I deliberately skipped GitHub for the transfer this time — once you push something, it's in the history forever, and credit card data shouldn't be there even briefly. Instead, I used &lt;strong&gt;direct PC-to-PC transfer over SSH&lt;/strong&gt; (one command, finishes in seconds; details in the collapsibles at the end). The &lt;code&gt;.gitignore&lt;/code&gt; excludes &lt;code&gt;private-data/&lt;/code&gt; too, so accidental commits are ruled out.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Install Ollama
&lt;/h2&gt;

&lt;p&gt;Ollama is the de-facto runtime for local LLMs. One command should be enough.&lt;/p&gt;

&lt;p&gt;There was a small password hiccup during install (details below), but eventually it was up and running.&lt;/p&gt;

&lt;p&gt;The DGX Spark specs really show through:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Memory: 121 GB&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Default context window: ~262,144 tokens&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In other words: "throw a whole book at it, no problem" territory. Reassuring.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Two model sizes: Qwen2.5 7B vs 72B
&lt;/h2&gt;

&lt;p&gt;The strategy: &lt;strong&gt;same model family, different sizes&lt;/strong&gt;. That way the differences come from size, not architecture.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;7B (light)&lt;/strong&gt;: ~4.7 GB, downloads in 5 minutes. Fast.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;72B (heavy)&lt;/strong&gt;: ~47 GB, 25 minutes to download. Slow but smart.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;What does "B" mean?&lt;/strong&gt; Short for &lt;em&gt;Billion&lt;/em&gt;. It's the number of "weights" inside the AI — more weights, more it remembers, basically. So &lt;strong&gt;7B has 7 billion weights, 72B has 72 billion&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Loading both onto the DGX simultaneously, memory usage looks like:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;AI model&lt;/th&gt;
&lt;th&gt;Memory occupied&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;qwen2.5:72b&lt;/td&gt;
&lt;td&gt;61 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;qwen2.5:7b&lt;/td&gt;
&lt;td&gt;8.2 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Total&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;69 GB&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;69 GB. Spacious!&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Prepping the CSVs
&lt;/h2&gt;

&lt;p&gt;Once I had the CSVs in hand, &lt;strong&gt;three small headaches&lt;/strong&gt; before they were ready for the AI:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Headache 1&lt;/strong&gt;: An older encoding (Windows Japanese flavor) → needs converting to modern UTF-8&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Headache 2&lt;/strong&gt;: Some merchant names contain commas, which breaks naive CSV parsing&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Headache 3&lt;/strong&gt;: Each file has a "monthly total" line at the end that isn't actually data&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Details in the collapsible. After cleanup, the 12 files merge into a single dataset:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Item&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Transactions&lt;/td&gt;
&lt;td&gt;383&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Period&lt;/td&gt;
&lt;td&gt;12 months (1 year)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Total spend&lt;/td&gt;
&lt;td&gt;¥2,761,555&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Avg per tx&lt;/td&gt;
&lt;td&gt;¥7,210&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Median per tx&lt;/td&gt;
&lt;td&gt;¥3,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Largest single tx&lt;/td&gt;
&lt;td&gt;¥209,283 (overseas flight)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Smallest&lt;/td&gt;
&lt;td&gt;¥-3,980 (refund)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Now to feed this to 7B and 72B and see what each of them says.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Experiment 1: Throw the raw CSV at the AI
&lt;/h2&gt;

&lt;p&gt;No tricks: &lt;strong&gt;all 383 rows, straight at the AI&lt;/strong&gt;. Prompt is the full ask: "As a household budget consultant, output category breakdown / monthly trend / waste patterns / savings suggestions / lifestyle hypothesis."&lt;/p&gt;

&lt;h3&gt;
  
  
  7B's answer (75 seconds)
&lt;/h3&gt;

&lt;p&gt;...this is where &lt;strong&gt;the numbers go wildly off&lt;/strong&gt;.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Item&lt;/th&gt;
&lt;th&gt;What 7B said&lt;/th&gt;
&lt;th&gt;Real data&lt;/th&gt;
&lt;th&gt;Match?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Amazon total&lt;/td&gt;
&lt;td&gt;¥2,014,386 (257 tx)&lt;/td&gt;
&lt;td&gt;¥693,663 (166 tx)&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Amazon Downloads&lt;/td&gt;
&lt;td&gt;¥2,014,386 (257 tx)&lt;/td&gt;
&lt;td&gt;¥80,323 (50 tx)&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Outdoor brand&lt;/td&gt;
&lt;td&gt;¥495,740&lt;/td&gt;
&lt;td&gt;¥154,820&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A local recreation venue&lt;/td&gt;
&lt;td&gt;"¥49,574" cited&lt;/td&gt;
&lt;td&gt;(a different small charge actually exists)&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;None of the numbers line up. Amazon total is roughly 3× off, Amazon Downloads about 25× off, and the cited venue context is a different charge entirely.&lt;/p&gt;

&lt;p&gt;Reading 383 rows of CSV and computing totals turned out to be a heavy lift for the 7B model.&lt;/p&gt;

&lt;h3&gt;
  
  
  72B's answer (12m 9s)
&lt;/h3&gt;

&lt;p&gt;What if we throw size at the problem? After 12 minutes of patience:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Item&lt;/th&gt;
&lt;th&gt;What 72B said&lt;/th&gt;
&lt;th&gt;Real data&lt;/th&gt;
&lt;th&gt;Match?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Amazon total&lt;/td&gt;
&lt;td&gt;¥635,792 (104 tx)&lt;/td&gt;
&lt;td&gt;¥693,663 (166 tx)&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AI/dev tools&lt;/td&gt;
&lt;td&gt;¥193,629 (21 tx)&lt;/td&gt;
&lt;td&gt;¥176,850 (24 tx)&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Travel&lt;/td&gt;
&lt;td&gt;¥487,555 (43 tx)&lt;/td&gt;
&lt;td&gt;¥416,268 (8 tx)&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Not exact, but the off-by amounts are within ~10%, and there are no fabricated venues. A real improvement.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;However — when asked about the monthly trend, here's what 72B said:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Month 1: ¥316,789 → Month 2: ¥229,600 → Month 3: ¥237,500 → ... → Month 12: ¥291,500&lt;br&gt;
(Gradually increasing.)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The actual range is ¥69,961 (low) to ¥493,072 (high) — a chaotic up-and-down waveform. "Gradually increasing" isn't quite right. Even 72B isn't great at aggregating distributed data over a long CSV.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. Experiment 2: Aggregate first, then feed the AI
&lt;/h2&gt;

&lt;p&gt;If the AI struggles with aggregation, do the aggregation in a different tool first and only hand the AI the result.&lt;/p&gt;

&lt;p&gt;The flow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;📥 Raw CSV (22,132 chars, 383 rows)
       ↓
🔧 Pre-aggregate with a spreadsheet tool (Python's pandas)
       ↓
📋 Aggregate summary (1,884 chars, ~90% smaller)
       ↓
🤖 Hand it to the AI (let it interpret and propose)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Python's &lt;strong&gt;pandas&lt;/strong&gt; = a spreadsheet-like library, but ~10,000× more powerful than Excel functions, used for tabular data analysis.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  7B + pre-aggregated input (50 seconds)
&lt;/h3&gt;

&lt;p&gt;Numbers are &lt;strong&gt;fully accurate&lt;/strong&gt; now.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Item&lt;/th&gt;
&lt;th&gt;What 7B said&lt;/th&gt;
&lt;th&gt;Real data&lt;/th&gt;
&lt;th&gt;Match?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Amazon total&lt;/td&gt;
&lt;td&gt;¥693,663&lt;/td&gt;
&lt;td&gt;¥693,663&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AI/dev tools&lt;/td&gt;
&lt;td&gt;¥176,850&lt;/td&gt;
&lt;td&gt;¥176,850&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Monthly max&lt;/td&gt;
&lt;td&gt;¥493,072&lt;/td&gt;
&lt;td&gt;¥493,072&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Monthly min&lt;/td&gt;
&lt;td&gt;¥69,961&lt;/td&gt;
&lt;td&gt;¥69,961&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Quoting straight from the pre-aggregated numbers, the hallucinations vanished.&lt;/p&gt;

&lt;p&gt;And 7B did this in 50 seconds — better quality than the 72B + raw CSV at 12 minutes. Quietly remarkable.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Before (raw CSV)&lt;/th&gt;
&lt;th&gt;After (aggregated)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Time&lt;/td&gt;
&lt;td&gt;75s&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;50s&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Numbers&lt;/td&gt;
&lt;td&gt;wildly off&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;exact&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Verdict&lt;/td&gt;
&lt;td&gt;not usable as-is&lt;/td&gt;
&lt;td&gt;quote directly&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  72B + pre-aggregated input (12m 13s)
&lt;/h3&gt;

&lt;p&gt;72B's numbers also match exactly (well, since they're being quoted from pre-aggregated data, that's expected). The proposal quality was the strongest of the four patterns:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Reduce Amazon dependency&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Current: online shopping (Amazon family) is 25.1% of total (¥693,663).&lt;/li&gt;
&lt;li&gt;Suggestion: stick to essentials only, regular review, avoid impulse buys.&lt;/li&gt;
&lt;li&gt;Expected savings: ¥57,805/month average (25% reduction) → ¥693,660/year&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;...wait, hold on. Annual Amazon spend was ¥693,663. The "savings" 72B suggests is ¥693,660. That's basically the &lt;strong&gt;same number&lt;/strong&gt;. So the proposal is effectively "stop buying on Amazon entirely (100%)" — definitely not 25%. Apparently 72B's percentage arithmetic isn't bulletproof either.&lt;/p&gt;

&lt;p&gt;That aside, the &lt;strong&gt;lifestyle hypothesis&lt;/strong&gt; section was kind of striking. Here's what 72B observed:&lt;/p&gt;

&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Heavy reliance on apps and subscriptions&lt;/strong&gt;: "App/subscription" category is 10.5% of total&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Frequent international travel&lt;/strong&gt;: "Travel/airline" is 15.1%, with notable overseas charges&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Frequent online shopping&lt;/strong&gt;: "Online (Amazon)" is 25.1% of total&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;It's just one card's data, so this isn't a complete picture — but if I fed an AI my full household financials, &lt;strong&gt;the analysis and advice would probably go a lot deeper&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Summary: 4 patterns
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Input&lt;/th&gt;
&lt;th&gt;Time&lt;/th&gt;
&lt;th&gt;Numerical accuracy&lt;/th&gt;
&lt;th&gt;Proposal quality&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;7B&lt;/td&gt;
&lt;td&gt;Raw CSV&lt;/td&gt;
&lt;td&gt;75s&lt;/td&gt;
&lt;td&gt;❌ Numbers way off&lt;/td&gt;
&lt;td&gt;△&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;72B&lt;/td&gt;
&lt;td&gt;Raw CSV&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;12m 9s&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;△ Misread monthly trend&lt;/td&gt;
&lt;td&gt;○&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;7B&lt;/td&gt;
&lt;td&gt;Aggregated&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;50s&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅ Exact&lt;/td&gt;
&lt;td&gt;○ Some repetition&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;72B&lt;/td&gt;
&lt;td&gt;Aggregated&lt;/td&gt;
&lt;td&gt;12m 13s&lt;/td&gt;
&lt;td&gt;✅ Exact&lt;/td&gt;
&lt;td&gt;◎ Best (mind the % math)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Quietly notable: &lt;strong&gt;72B takes ~12 minutes regardless of input size&lt;/strong&gt; (shrinking the prompt didn't change wall-clock time much). Output generation is the bottleneck. Which strengthens the case for "small model + pre-aggregate" as the cost-effective default.&lt;/p&gt;




&lt;h2&gt;
  
  
  7. Cross-check: the actual graphs
&lt;/h2&gt;

&lt;p&gt;Before trusting any of the AI output, let me put the real numbers on charts using the spreadsheet tool (pandas).&lt;/p&gt;

&lt;h3&gt;
  
  
  Monthly spending
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3wvfzqh0st6qv1323fgr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3wvfzqh0st6qv1323fgr.png" alt="Monthly spending" width="800" height="359"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Average ¥230,130/month, but the range is ¥69,961 (lowest) to ¥493,072 (highest) — about a 7× spread. The 72B's "gradually increasing" claim was a bit off the mark; the reality is bouncy.&lt;/p&gt;

&lt;h3&gt;
  
  
  Category share
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5wepa7rudozlx1igsp4o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5wepa7rudozlx1igsp4o.png" alt="Categories" width="800" height="339"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;"Other" being 32% is because my categorization rule is sloppy. I just wrote a simple "if the merchant name contains keyword X, bucket Y" rule, and lots of merchants didn't match any keyword and ended up in "Other." &lt;strong&gt;Reading meaning from a merchant name&lt;/strong&gt; is exactly the kind of thing AI is good at, so next time I'll let the AI do the categorization itself.&lt;/p&gt;

&lt;h3&gt;
  
  
  Top 15 merchants
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqynqrvxdlol28s3mr63m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqynqrvxdlol28s3mr63m.png" alt="Top merchants" width="800" height="429"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Amazon at ¥421,978 (105 tx) is far and away #1. Amazon really is too convenient...&lt;/p&gt;

&lt;h3&gt;
  
  
  Weekday rhythm
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftmwt0stf6hralf5vl8kp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftmwt0stf6hralf5vl8kp.png" alt="Weekday pattern" width="800" height="273"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Tuesday alone is ¥692,549 — way above the rest. Probably because that's when most of the subscription auto-charges land.&lt;/p&gt;




&lt;h2&gt;
  
  
  8. Today's takeaways
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Separate "aggregation" from "interpretation"
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;AI is bad at&lt;/th&gt;
&lt;th&gt;AI is good at&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Multi-row sum/average (numbers go wildly off)&lt;/td&gt;
&lt;td&gt;Categorization (interpreting fuzzy meaning)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Percentage math (saw "25% off → 100% off")&lt;/td&gt;
&lt;td&gt;Pattern recognition / hypothesis generation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Distributed aggregation like monthly totals&lt;/td&gt;
&lt;td&gt;Narrative interpretation, savings proposals&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;→ &lt;strong&gt;Aggregation is the spreadsheet tool's job; interpretation is the AI's.&lt;/strong&gt; When you split the work, things go fast and accurate. "Data prep matters before analysis" — yeah, that old saying really is true. Note to self.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sometimes input quality beats raw size
&lt;/h3&gt;

&lt;p&gt;"7B + pre-aggregated input in 50 seconds" outperformed "72B + raw CSV in 12 minutes". &lt;strong&gt;Sometimes you don't need a bigger model — you need cleaner input.&lt;/strong&gt; Felt that one today.&lt;/p&gt;

&lt;h3&gt;
  
  
  The local-LLM angle
&lt;/h3&gt;

&lt;p&gt;Feeding 12 months of raw credit card data to an AI without a single byte going to the cloud — it was surprisingly stress-free. This is one of the spots local LLMs really shine. Got personal info, or anything cloud-uncomfortable? This is the place for them.&lt;/p&gt;




&lt;h2&gt;
  
  
  9. Tech details (Claude explains)
&lt;/h2&gt;

&lt;p&gt;The technical bits, written up by my AI pair.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;SCP transfer to the DGX (mDNS, no IP needed)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;NVIDIA Sync auto-configures a Host alias in &lt;code&gt;~/AppData/Local/NVIDIA Corporation/Sync/config/ssh_config&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Host spark-XXXX.local
  Hostname spark-XXXX.local
  User [user]
  Port 22
  IdentityFile "...\\nvsync.key"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Which means I can SSH/SCP using &lt;code&gt;spark-XXXX.local&lt;/code&gt; without ever looking up an IP. The &lt;code&gt;.local&lt;/code&gt; suffix uses mDNS (Multicast DNS) for hostname resolution within the LAN.&lt;/p&gt;

&lt;p&gt;Transfer command (one line, from PowerShell on the Windows side):&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;scp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-r&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"C:\Users\[user]\Desktop\docs\dgx\csv"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;spark-XXXX.local:/home/&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="nx"&gt;/personal/dgx-100-experiments/private-data/credit-card-csv&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;Ollama install + the sudo-TTY catch + GPU detection log&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Ollama install:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://ollama.com/install.sh | sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Running this through Claude Code's Bash, it errored at the sudo password prompt — an interactive TTY is required there:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo: a terminal is required to read the password
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Reopened a separate SSH session, ran the same command manually, and it went through.&lt;/p&gt;

&lt;p&gt;Once installed, systemd auto-starts the service. The GPU detection log via &lt;code&gt;journalctl -u ollama&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;inference compute id=GPU-986c194b... name=CUDA0 description="NVIDIA GB10"
total="121.7 GiB" available="79.0 GiB"
default_num_ctx=262144
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;VRAM (DGX Spark unified memory): &lt;strong&gt;121.7 GiB&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Default context: &lt;strong&gt;262,144 tokens&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Compared with a typical RTX 4090 (24 GB VRAM, 8K–32K default context), the gap is significant.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Loading both models simultaneously&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ollama pull qwen2.5:7b   &lt;span class="c"&gt;# 4.7 GB&lt;/span&gt;
ollama pull qwen2.5:72b  &lt;span class="c"&gt;# 47 GB&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;After loading both, &lt;code&gt;ollama ps&lt;/code&gt; shows:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;NAME           SIZE      PROCESSOR    CONTEXT    
qwen2.5:72b    61 GB     100% GPU     32768
qwen2.5:7b     8.2 GB    100% GPU     32768
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Total ~69 GB used out of 79 GB available. Both models stay resident, switching between them is instant.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Custom CSV parser for the credit card data&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Three quirks needed handling: CP932 encoding, no quotes (commas in some merchant names break parsing), and a trailing summary row in each file.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;parse_line&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;gt&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;fields&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rstrip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\r\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;lt&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&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;None&lt;/span&gt;  &lt;span class="c1"&gt;# skip blank/summary rows
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;gt&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;merchant&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="n"&gt;fields&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;merchant&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;:]&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;fields&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;load_one&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;gt&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DataFrame&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;rows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cp932&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# skip header (cardholder metadata)
&lt;/span&gt;        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;parsed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parse_line&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;line&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;parsed&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parsed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DataFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;columns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;COLUMNS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;利用日&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_datetime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;利用日&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;%Y/%m/%d&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;利用金額&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;利用金額&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;astype&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="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;Japanese fonts in matplotlib&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;code&gt;japanize-matplotlib&lt;/code&gt; doesn't work on Python 3.12 — it imports &lt;code&gt;distutils&lt;/code&gt;, which was removed from the standard library.&lt;/p&gt;

&lt;p&gt;The modern replacement is &lt;code&gt;matplotlib-fontja&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;matplotlib-fontja
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;matplotlib_fontja&lt;/span&gt;  &lt;span class="c1"&gt;# noqa: F401  ← just importing it sets up IPAexGothic
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;Calling Ollama from Python&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The official &lt;code&gt;ollama&lt;/code&gt; Python client is straightforward:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ollama&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ollama&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;stream&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;qwen2.5:72b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;system&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;SYSTEM_PROMPT&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;user_prompt&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;temperature&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.3&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&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;chunk&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;message&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;flush&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Streaming makes long generation easier to watch unfold.&lt;/p&gt;




&lt;h2&gt;
  
  
  Tomorrow: Day 4
&lt;/h2&gt;

&lt;p&gt;Day 4 plan: &lt;strong&gt;let a local AI sort 20,000 iPhone photos&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The actual goal is to have a local image-recognition model (CLIP family?) clean up my photo library so I can stop paying iCloud for storage upgrades...!&lt;/p&gt;




&lt;h1&gt;
  
  
  100ExperimentsWithDGX #LocalLLM #Ollama
&lt;/h1&gt;

</description>
      <category>localllm</category>
      <category>ai</category>
      <category>dgxspark</category>
      <category>ollama</category>
    </item>
    <item>
      <title>[Day 2] I Trained an AI on 22 Photos of My Cat — Now It Draws Her in Any Scene</title>
      <dc:creator>PEPPERCORN</dc:creator>
      <pubDate>Tue, 05 May 2026 00:06:00 +0000</pubDate>
      <link>https://forem.com/peppercorn_llm/day-2-i-trained-an-ai-on-22-photos-of-my-cat-now-it-draws-her-in-any-scene-3a92</link>
      <guid>https://forem.com/peppercorn_llm/day-2-i-trained-an-ai-on-22-photos-of-my-cat-now-it-draws-her-in-any-scene-3a92</guid>
      <description>&lt;h1&gt;
  
  
  [Day 2] I Trained an AI on 22 Photos of My Cat — Now It Draws Her in Any Scene
&lt;/h1&gt;

&lt;h2&gt;
  
  
  So, yesterday I generated "some cat"
&lt;/h2&gt;

&lt;p&gt;Day 1 ended with "I made my DGX draw a cat" — but the cat that came out was just "a cat from somewhere". Today, the goal is to teach the AI about my actual cat (who's currently being looked after at my parents' place back in Japan).&lt;/p&gt;

&lt;p&gt;This is what people call LoRA training.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;LoRA: A technique that teaches an AI model "specific features" using a small set of images, without touching the base model itself. Apparently. The output is a small "diff" file (tens of MB).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is experiment #2.&lt;/p&gt;




&lt;h2&gt;
  
  
  The training data
&lt;/h2&gt;

&lt;p&gt;Source material: 22 photos of my cat.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy9tmru213ymne73f61pv.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy9tmru213ymne73f61pv.jpg" alt="Training photo collage" width="800" height="1058"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I picked a mix of angles — front-facing, full body, sleepy poses, varying lighting — to give the AI a fair shot at recognizing the cat's defining features (tuxedo black-and-white pattern, white socks, the black smudge on the nose).&lt;/p&gt;




&lt;h2&gt;
  
  
  Training pipeline
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Pre-processing
&lt;/h3&gt;

&lt;p&gt;iPhone HEIC files don't work directly with most AI tools, so first conversion to JPG. 10 of the 22 were HEIC.&lt;/p&gt;

&lt;p&gt;Then resize to 512px on the short side for training. &lt;strong&gt;This is where I tripped over a sneaky bug&lt;/strong&gt; — details in the collapsible section below.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Captions
&lt;/h3&gt;

&lt;p&gt;Every image gets a text description like "ohwx cat, sitting on a wooden floor, indoor, soft lighting". The four-letter &lt;code&gt;ohwx&lt;/code&gt; is a meaningless token that becomes the trigger word for "my specific cat" after training.&lt;/p&gt;

&lt;p&gt;Drafting 22 captions by hand would be tedious — but Claude can read images directly, so it drafted them while I just reviewed. The accuracy was uncanny. For example:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnmzgmz033je98xlpyilb.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnmzgmz033je98xlpyilb.jpg" alt="Cat on a kitchen counter" width="512" height="683"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;ohwx cat, walking on a metal kitchen counter, side profile, indoor kitchen with spice bottles and shelves in the background&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7nm6622l9qfb0py7z2ag.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7nm6622l9qfb0py7z2ag.jpg" alt="Mid-yawn cat" width="512" height="683"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;ohwx cat, in a loaf pose on a gray carpet, mouth open showing teeth, mid-yawn, indoor with shelves and warm lights in the background&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fisxhfub5fudklbriga1p.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fisxhfub5fudklbriga1p.jpg" alt="Cat by a window" width="683" height="512"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;ohwx cat, sitting on a wooden floor by a balcony window, viewed from behind, sharp sunlight casting long shadows, indoor&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;SUGOI.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Kohya_ss training
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;Kohya_ss&lt;/code&gt; is the de-facto LoRA training tool. Set up a TOML config, run one command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;accelerate launch train_network.py &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--config_file&lt;/span&gt; configs/train.toml &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--dataset_config&lt;/span&gt; configs/dataset.toml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Training logs scroll by, and the loss value gradually drops. Lower loss = the model is learning, apparently.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Done
&lt;/h3&gt;

&lt;p&gt;1100 steps in 13 minutes 3 seconds on the DGX Spark.&lt;/p&gt;




&lt;h2&gt;
  
  
  Result 1: just typing "ohwx cat" gives me my cat
&lt;/h2&gt;

&lt;p&gt;The first thing I tried was a "without LoRA vs with LoRA" comparison. Same prompt — "ohwx cat as a chef in a kitchen, ..." — first without the LoRA, then with it:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjzfjjk84dv92xns5nt3v.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjzfjjk84dv92xns5nt3v.jpg" alt="Without (left) vs With (right) LoRA" width="800" height="598"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Left: no LoRA. Right: with LoRA.&lt;/p&gt;

&lt;p&gt;Without LoRA, &lt;code&gt;ohwx&lt;/code&gt; is gibberish to the model, so it's ignored and only "a chef in a kitchen" carries weight. Result: a human chef. A nice woman cooking in a pink kitchen.&lt;/p&gt;

&lt;p&gt;With LoRA, &lt;code&gt;ohwx&lt;/code&gt; becomes a real token that points at my cat. Same prompt, but now my cat is the chef.&lt;/p&gt;

&lt;p&gt;This was the moment that hit.&lt;/p&gt;




&lt;h2&gt;
  
  
  Result 2: novel scene reproduction
&lt;/h2&gt;

&lt;p&gt;The training set has no photo of the cat sitting on a wooden floor in this exact composition. So I tried it:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fngsfgj9etl9pv39axg2z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fngsfgj9etl9pv39axg2z.png" alt="My cat sitting on a wooden floor" width="512" height="768"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;White socks: present. Nose smudge: present.&lt;/p&gt;




&lt;h2&gt;
  
  
  My cat, in places she's never been
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;ohwx cat&lt;/code&gt; in various scenes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sunny balcony
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxbhlskto67vvbgmx2hdm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxbhlskto67vvbgmx2hdm.png" alt="Cat on a sunny balcony" width="512" height="768"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Cozy.&lt;/p&gt;

&lt;h3&gt;
  
  
  Chef (reprise)
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2awj3mdsj8u788bedxhl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2awj3mdsj8u788bedxhl.png" alt="Cat as a chef" width="512" height="768"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The chef hat fits suspiciously well. Cooking ability unverified.&lt;/p&gt;

&lt;h3&gt;
  
  
  Autumn forest
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs1l3r9hwnvh3kqk8qc1n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs1l3r9hwnvh3kqk8qc1n.png" alt="Cat in an autumn forest" width="512" height="768"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A painterly take.&lt;/p&gt;

&lt;h3&gt;
  
  
  Astronaut
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frps74rdecajbrews1tz4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frps74rdecajbrews1tz4.png" alt="Cat as an astronaut" width="512" height="768"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A doppelgänger via the helmet glass — but sci-fi all the same.&lt;/p&gt;




&lt;h2&gt;
  
  
  Today's takeaway
&lt;/h2&gt;

&lt;p&gt;"Build your own AI from your own data" turned out to be way more accessible than I'd assumed.&lt;/p&gt;




&lt;h2&gt;
  
  
  Tech details (Claude explains)
&lt;/h2&gt;

&lt;p&gt;The technical bits, written up by my AI pair.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;HEIC → JPG conversion and the EXIF orientation trap&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Reading iPhone HEIC files in Python is straightforward with &lt;code&gt;pillow-heif&lt;/code&gt;. JPG conversion is a few lines:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;PIL&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ImageOps&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pillow_heif&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;register_heif_opener&lt;/span&gt;
&lt;span class="nf"&gt;register_heif_opener&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;IMG_1234.HEIC&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;oriented&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ImageOps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exif_transpose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# ← critical line
&lt;/span&gt;    &lt;span class="n"&gt;rgb&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;oriented&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;convert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;RGB&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;rgb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;IMG_1234.jpg&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;quality&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;95&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  What I tripped on
&lt;/h3&gt;

&lt;p&gt;My first version skipped &lt;code&gt;ImageOps.exif_transpose()&lt;/code&gt;. Result: 8 of 22 photos came out rotated 90° in the resized output.&lt;/p&gt;

&lt;p&gt;iPhones save portrait shots with the actual pixels stored landscape-ways, plus an EXIF Orientation tag saying "rotate 90° on display". Pillow's default &lt;code&gt;Image.open()&lt;/code&gt; ignores that tag — you have to call &lt;code&gt;exif_transpose()&lt;/code&gt; explicitly.&lt;/p&gt;

&lt;p&gt;Caught it before training started. If I hadn't, the LoRA would have learned "sideways cat" and generation would be weird.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Kohya_ss setup on ARM64 (DGX Spark)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;There are two repos commonly referred to as "Kohya_ss":&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;bmaltais/kohya_ss&lt;/code&gt; — GUI wrapper, xformers dependency (clashes with ARM64)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;kohya-ss/sd-scripts&lt;/code&gt; — the actual training engine, CLI/TOML driven&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;DGX Spark is ARM64, so I went with the latter:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone &lt;span class="nt"&gt;--depth&lt;/span&gt; 1 https://github.com/kohya-ss/sd-scripts.git ~/Kohya_ss
&lt;span class="nb"&gt;cd&lt;/span&gt; ~/Kohya_ss
python3 &lt;span class="nt"&gt;-m&lt;/span&gt; venv venv &amp;amp;amp&lt;span class="p"&gt;;&lt;/span&gt;&amp;amp;amp&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;source &lt;/span&gt;venv/bin/activate
pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--upgrade&lt;/span&gt; pip
pip &lt;span class="nb"&gt;install &lt;/span&gt;torch torchvision &lt;span class="nt"&gt;--index-url&lt;/span&gt; https://download.pytorch.org/whl/cu128
pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;DGX Spark uses CUDA 12.8 + ARM64 (sbsa), so the PyTorch &lt;code&gt;cu128&lt;/code&gt; channel works directly. Surprisingly painless.&lt;/p&gt;
&lt;h3&gt;
  
  
  Training config (TOML)
&lt;/h3&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="c"&gt;# train.toml (excerpt)&lt;/span&gt;
&lt;span class="py"&gt;pretrained_model_name_or_path&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;".../Realistic_Vision_V6.0_NV_B1.safetensors"&lt;/span&gt;
&lt;span class="py"&gt;vae&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;".../vae-ft-mse-840000-ema-pruned.safetensors"&lt;/span&gt;

&lt;span class="py"&gt;network_module&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"networks.lora"&lt;/span&gt;
&lt;span class="py"&gt;network_dim&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;
&lt;span class="py"&gt;network_alpha&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;

&lt;span class="py"&gt;optimizer_type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"AdamW8bit"&lt;/span&gt;
&lt;span class="py"&gt;unet_lr&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;1e-4&lt;/span&gt;
&lt;span class="py"&gt;text_encoder_lr&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;5e-5&lt;/span&gt;
&lt;span class="py"&gt;lr_scheduler&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"cosine_with_restarts"&lt;/span&gt;

&lt;span class="py"&gt;max_train_epochs&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;
&lt;span class="py"&gt;save_every_n_epochs&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;

&lt;span class="py"&gt;mixed_precision&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"bf16"&lt;/span&gt;
&lt;span class="py"&gt;sdpa&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="py"&gt;cache_latents&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="c"&gt;# dataset.toml&lt;/span&gt;
&lt;span class="nn"&gt;[general]&lt;/span&gt;
&lt;span class="py"&gt;shuffle_caption&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;span class="py"&gt;caption_extension&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;".txt"&lt;/span&gt;
&lt;span class="py"&gt;keep_tokens&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;

&lt;span class="nn"&gt;[[datasets]]&lt;/span&gt;
&lt;span class="py"&gt;resolution&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;512&lt;/span&gt;
&lt;span class="py"&gt;batch_size&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;span class="py"&gt;enable_bucket&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

  &lt;span class="nn"&gt;[[datasets.subsets]]&lt;/span&gt;
  &lt;span class="py"&gt;image_dir&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"/path/to/cat-photos-512"&lt;/span&gt;
  &lt;span class="py"&gt;num_repeats&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;22 photos × 10 repeats × 10 epochs ÷ batch 2 = 1100 steps. 13 minutes.&lt;/p&gt;

&lt;p&gt;Base model: Realistic Vision V6.0 B1 noVAE (a photo-realistic SD 1.5 derivative). External VAE: sd-vae-ft-mse-original. The combination is good at fur detail.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Hitting the ComfyUI HTTP API for batch generation&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Clicking through the GUI for one image at a time gets old fast. ComfyUI exposes an HTTP API that's easy to drive from Python — &lt;code&gt;urllib.request&lt;/code&gt; from the standard library is enough (no extra deps).&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;

&lt;span class="n"&gt;COMFY_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://127.0.0.1:8188&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;queue_prompt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;workflow&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;prompt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;workflow&lt;/span&gt;&lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;COMFY_URL&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/prompt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Content-Type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;application/json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;urlopen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;())[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;prompt_id&lt;/span&gt;&lt;span class="sh"&gt;"&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;wait_for_history&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;180&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;lt&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;urlopen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;COMFY_URL&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/history/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;prompt_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&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;prompt_id&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;prompt_id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;The workflow is ComfyUI's API format (a dict of node IDs with their connections). To use a LoRA, insert a &lt;code&gt;LoraLoader&lt;/code&gt; node between the checkpoint loader and KSampler.&lt;/p&gt;

&lt;p&gt;DGX Spark generates one 512×768 image in about 3 seconds. With seed/strength/prompt parametrized in a script, all 12 grid images came out in under a minute.&lt;/p&gt;




&lt;h2&gt;
  
  
  Tomorrow: Day 3
&lt;/h2&gt;

&lt;p&gt;Day 3 plan: have a local AI analyze my credit card history.&lt;/p&gt;

&lt;p&gt;The kind of data I'd rather not send to a cloud AI, but absolutely want to understand. Quintessential local-AI territory.&lt;/p&gt;




&lt;h1&gt;
  
  
  100ExperimentsWithDGX #LocalLLM
&lt;/h1&gt;

</description>
      <category>localllm</category>
      <category>ai</category>
      <category>dgxspark</category>
      <category>lora</category>
    </item>
    <item>
      <title>[Day 1] DGX Spark Came Home — I Made It Draw a Cat</title>
      <dc:creator>PEPPERCORN</dc:creator>
      <pubDate>Mon, 04 May 2026 03:20:48 +0000</pubDate>
      <link>https://forem.com/peppercorn_llm/day-1-dgx-spark-came-home-i-made-it-draw-a-cat-30f7</link>
      <guid>https://forem.com/peppercorn_llm/day-1-dgx-spark-came-home-i-made-it-draw-a-cat-30f7</guid>
      <description>&lt;h1&gt;
  
  
  [Day 1] DGX Spark Came Home — I Made It Draw a Cat
&lt;/h1&gt;

&lt;h2&gt;
  
  
  So... what is "local LLM" again?
&lt;/h2&gt;

&lt;p&gt;Honestly, I'm still figuring out what "local LLM" even means. But somehow, through a series of decisions I won't fully justify here, I ended up buying an NVIDIA DGX Spark — and now it's sitting in my house.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;DGX Spark: NVIDIA's "supercomputer for the home" — a small but seriously expensive box with the latest-gen AI chip inside. Apparently.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;What I really want to figure out is: when should I use local AI vs. cloud AI? Reading articles about it doesn't seem to help, so I'm going full hands-on. Goal: 100 experiments, one per day-ish, until I have an evidence-based answer.&lt;/p&gt;

&lt;p&gt;This is experiment#1.&lt;/p&gt;




&lt;h2&gt;
  
  
  First, the hardware
&lt;/h2&gt;

&lt;p&gt;So this is what showed up at my door — solidly packed in a sturdy cardboard box.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fetwrk2l7jv4q4qg6387t.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fetwrk2l7jv4q4qg6387t.jpg" alt="DGX Spark box" width="800" height="1067"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When I opened it, I was surprised at how small it actually is. "This is the AI machine?" kind of small.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqq8k08mii298z3qtorrr.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqq8k08mii298z3qtorrr.jpg" alt="DGX Spark hardware (mesh sides)" width="800" height="1067"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Boot up → Initial OS setup
&lt;/h2&gt;

&lt;p&gt;Power on, and an Ubuntu-based DGX OS 7.5.0 boots up.&lt;/p&gt;

&lt;h3&gt;
  
  
  Welcome screen
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqn3ibxlwj605xrrk9zfi.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqn3ibxlwj605xrrk9zfi.jpg" alt="Get started screen" width="800" height="470"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;"Get started" — yes, please.&lt;/p&gt;

&lt;h3&gt;
  
  
  Language and timezone
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkivl91tfq5u5wv55omq2.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkivl91tfq5u5wv55omq2.jpg" alt="Language and timezone" width="800" height="541"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Standard Linux installer territory — same as Ubuntu?&lt;/p&gt;

&lt;h3&gt;
  
  
  Privacy settings
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8v6rcegdhblxy68puwzv.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8v6rcegdhblxy68puwzv.jpg" alt="Privacy settings" width="800" height="755"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Diagnostic data sharing prompts.&lt;/p&gt;

&lt;h3&gt;
  
  
  System update
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foaogr37pvykiys42avjz.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foaogr37pvykiys42avjz.jpg" alt="Update started" width="800" height="438"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The moment I plugged it in, it started updating itself. Modern Linux being Linux.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setup complete
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fho3zwtbliqbvvabytjwt.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fho3zwtbliqbvvabytjwt.jpg" alt="Setup complete" width="712" height="413"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I picked a username and let the hostname auto-assign. DGX-side prep done.&lt;/p&gt;




&lt;h2&gt;
  
  
  Connecting from my Windows PC
&lt;/h2&gt;

&lt;p&gt;Plugging a monitor into the DGX every time would be tedious, so I want to SSH in from my regular Windows machine (which I've nicknamed "myPC1").&lt;/p&gt;

&lt;p&gt;NVIDIA provides a desktop app called NVIDIA Sync that's supposed to make SSH setup painless. So I install it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq5uezyis10h8hluz2xb0.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq5uezyis10h8hluz2xb0.jpg" alt="NVIDIA Sync install" width="643" height="287"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;…and that's where I fell into a trap big-time. Windows OpenSSH refused to connect with a "your SSH config has weird permissions, can't trust it" error.&lt;/p&gt;

&lt;p&gt;Full troubleshooting steps are in the collapsible "Tech details" section below.&lt;/p&gt;




&lt;h2&gt;
  
  
  Inside the DGX, finally
&lt;/h2&gt;

&lt;p&gt;After much wrestling, I made it inside. Here's the rough lay of the land:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Item&lt;/th&gt;
&lt;th&gt;Spec&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;GPU&lt;/td&gt;
&lt;td&gt;NVIDIA GB10 Grace Blackwell&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Memory&lt;/td&gt;
&lt;td&gt;128GB (unified between CPU and GPU)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Storage&lt;/td&gt;
&lt;td&gt;4TB SSD (basically empty)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CPU&lt;/td&gt;
&lt;td&gt;20 cores (perf + efficiency combo)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Idle power&lt;/td&gt;
&lt;td&gt;4W (yes, four)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;128GB of memory is apparently 8–16x what's in a typical laptop.&lt;/p&gt;




&lt;h2&gt;
  
  
  Setting up image generation → 🐱
&lt;/h2&gt;

&lt;p&gt;This is the main event. I'm setting up ComfyUI to generate the first cat from this DGX.&lt;/p&gt;

&lt;p&gt;The ComfyUI interface looks like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F53sxhno991z6dni6ozdv.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F53sxhno991z6dni6ozdv.jpg" alt="ComfyUI connected" width="800" height="403"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The "boxes connected by cables" view is intimidating at first, but the default workflow is pre-wired. You just type a prompt and hit Queue Prompt.&lt;/p&gt;

&lt;p&gt;So:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;a cute fluffy cat sitting on a sunny windowsill, photorealistic, high detail, beautiful lighting, soft fur, cinematic, masterpiece, best quality&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A few seconds later...&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flj7bnj1taf0oplft2itq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flj7bnj1taf0oplft2itq.png" alt="ComfyUI cat 1" width="512" height="512"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;🐱 There it is — the very first cat my DGX has ever drawn!&lt;/p&gt;

&lt;p&gt;Tweaked the prompt and made some more.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy5lb3ptrj1ue8cdejtno.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy5lb3ptrj1ue8cdejtno.png" alt="ComfyUI cat 2" width="512" height="512"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Eyes a bit unsettling but yeah, fluffy cat.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frrkiqc4px9sjeys77cms.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frrkiqc4px9sjeys77cms.png" alt="ComfyUI cat 3" width="512" height="512"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Going a touch dark there.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc6e77vf9oc2i6hem4fdm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc6e77vf9oc2i6hem4fdm.png" alt="ComfyUI cat 4" width="512" height="512"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;…is this a cat? It feels artistic though.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhwdpwdcz28b6dsmhsz51.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhwdpwdcz28b6dsmhsz51.png" alt="ComfyUI cat 5" width="512" height="512"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Distinctive composition.&lt;/p&gt;

&lt;p&gt;Each masterpiece takes a few to a dozen seconds. That speed means I can iterate on prompts without thinking about cost — which turned out to be quite addictive.&lt;/p&gt;




&lt;h2&gt;
  
  
  Tech details (let the AI explain it)
&lt;/h2&gt;

&lt;p&gt;The rest is the technical stuff. Read on if you're curious.&lt;/p&gt;

&lt;p&gt;I'm a non-engineer poking at this stuff for the first time, so I had Claude (my AI pair programmer for this challenge) write up the technical details. Hopefully useful for anyone walking the same path.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;How to actually get SSH working on Windows&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;NVIDIA Sync should generate an SSH keypair, register the public key on the DGX side at &lt;code&gt;~/.ssh/authorized_keys&lt;/code&gt;, and let you connect without a password.&lt;/p&gt;

&lt;p&gt;If it doesn't work, the cause is usually permissions on Windows SSH config files.&lt;/p&gt;
&lt;h3&gt;
  
  
  Symptom
&lt;/h3&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ ssh spark-XXXX.local
Bad permissions. Try removing permissions for user: [PC]\CodexSandboxUsers
on file C:/Users/[user]/.ssh/config.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;If you've installed Codex CLI or similar sandboxing tools in the past, the &lt;code&gt;[PC]\CodexSandboxUsers&lt;/code&gt; group may have inherited permissions on &lt;code&gt;~/.ssh/&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  Fix (run from an elevated PowerShell)
&lt;/h3&gt;

&lt;p&gt;Use environment variables to avoid hard-coding your username/PC name.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Take ownership&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;takeown&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;/f&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$&lt;/span&gt;&lt;span class="nn"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;USERPROFILE&lt;/span&gt;&lt;span class="s2"&gt;\.ssh\config"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;icacls&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$&lt;/span&gt;&lt;span class="nn"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;USERPROFILE&lt;/span&gt;&lt;span class="s2"&gt;\.ssh\config"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;/grant:r&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nv"&gt;$&lt;/span&gt;&lt;span class="nn"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;USERNAME&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;:F"&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c"&gt;# Disable inheritance and remove the bad user&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;icacls&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$&lt;/span&gt;&lt;span class="nn"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;USERPROFILE&lt;/span&gt;&lt;span class="s2"&gt;\.ssh\config"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;/inheritance:d&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;icacls&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$&lt;/span&gt;&lt;span class="nn"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;USERPROFILE&lt;/span&gt;&lt;span class="s2"&gt;\.ssh\config"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;/remove&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$&lt;/span&gt;&lt;span class="nn"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;COMPUTERNAME&lt;/span&gt;&lt;span class="s2"&gt;\CodexSandboxUsers"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Use &lt;code&gt;/inheritance:d&lt;/code&gt; rather than &lt;code&gt;/inheritance:r&lt;/code&gt; — &lt;code&gt;:r&lt;/code&gt; strips all permissions, locking yourself out.&lt;/p&gt;
&lt;h3&gt;
  
  
  NVIDIA Sync's internal config files need the same treatment
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;~/.ssh/config&lt;/code&gt; &lt;code&gt;Include&lt;/code&gt;s an NVIDIA Sync config file, and that one inherits the same problem.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$cfg&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="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$&lt;/span&gt;&lt;span class="nn"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;LOCALAPPDATA&lt;/span&gt;&lt;span class="s2"&gt;\NVIDIA Corporation\Sync\config\ssh_config"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;icacls&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$cfg&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;/inheritance:d&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;icacls&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$cfg&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;/remove&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$&lt;/span&gt;&lt;span class="nn"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;COMPUTERNAME&lt;/span&gt;&lt;span class="s2"&gt;\CodexSandboxUsers"&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="nv"&gt;$key&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="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$&lt;/span&gt;&lt;span class="nn"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;LOCALAPPDATA&lt;/span&gt;&lt;span class="s2"&gt;\NVIDIA Corporation\Sync\config\nvsync.key"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;icacls&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$key&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;/inheritance:d&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;icacls&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$key&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;/remove&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$&lt;/span&gt;&lt;span class="nn"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;COMPUTERNAME&lt;/span&gt;&lt;span class="s2"&gt;\CodexSandboxUsers"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  Ghost SIDs that icacls can't remove
&lt;/h3&gt;

&lt;p&gt;If you have SIDs from deleted user accounts lingering, &lt;code&gt;icacls /remove&lt;/code&gt; won't touch them. You need PowerShell ACL manipulation:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$cfg&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="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$&lt;/span&gt;&lt;span class="nn"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;LOCALAPPDATA&lt;/span&gt;&lt;span class="s2"&gt;\NVIDIA Corporation\Sync\config\ssh_config"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$acl&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;Get-Acl&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$cfg&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$badRules&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="nv"&gt;$acl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Access&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;Where-Object&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="bp"&gt;$_&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;IdentityReference&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Value&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-like&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"S-1-5-*"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-and&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="bp"&gt;$_&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;IdentityReference&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Translate&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;System.Security.Principal.NTAccount&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-isnot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;System.Security.Principal.NTAccount&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="nv"&gt;$badRules&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;ForEach-Object&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="nv"&gt;$acl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;RemoveAccessRule&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;$_&lt;/span&gt;&lt;span class="p"&gt;)&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;Out-Null&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;Set-Acl&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$cfg&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-AclObject&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$acl&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;After this, &lt;code&gt;ssh spark-XXXX.local&lt;/code&gt; connects on the first try (replace XXXX with your hostname).&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Commands to check DGX specs&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# GPU&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;nvidia-smi
NVIDIA-SMI 580.142    Driver Version: 580.142    CUDA Version: 13.0
GPU 0: NVIDIA GB10    36C    P8    4W / N/A

&lt;span class="c"&gt;# OS&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;uname&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt;
Linux spark-XXXX 6.17.0-1014-nvidia ... aarch64 GNU/Linux

&lt;span class="c"&gt;# Memory&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;free &lt;span class="nt"&gt;-h&lt;/span&gt;
Mem: 121Gi  2.6Gi  118Gi

&lt;span class="c"&gt;# Storage&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;df&lt;/span&gt; &lt;span class="nt"&gt;-h&lt;/span&gt;
/dev/nvme0n1p2  3.7T  47G  3.5T  2%  /

&lt;span class="c"&gt;# CPU&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;lscpu
Architecture:  aarch64
CPU&lt;span class="o"&gt;(&lt;/span&gt;s&lt;span class="o"&gt;)&lt;/span&gt;:        20
Model name:    Cortex-X925 + Cortex-A725
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Notable bits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CUDA 13.0 (latest)&lt;/li&gt;
&lt;li&gt;aarch64 (ARM64) architecture — yes, the DGX is ARM&lt;/li&gt;
&lt;li&gt;121Gi (≈128GB) unified memory&lt;/li&gt;
&lt;li&gt;20 cores in big.LITTLE layout (10 perf + 10 efficient)&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;ComfyUI installation steps&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Following the official NVIDIA &lt;a href="https://build.nvidia.com/spark" rel="noopener noreferrer"&gt;Comfy UI playbook&lt;/a&gt;.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Virtual environment&lt;/span&gt;
&lt;span class="nb"&gt;cd&lt;/span&gt; ~
python3 &lt;span class="nt"&gt;-m&lt;/span&gt; venv comfyui-env
&lt;span class="nb"&gt;source &lt;/span&gt;comfyui-env/bin/activate

&lt;span class="c"&gt;# PyTorch with CUDA 13.0&lt;/span&gt;
pip3 &lt;span class="nb"&gt;install &lt;/span&gt;torch torchvision &lt;span class="nt"&gt;--index-url&lt;/span&gt; https://download.pytorch.org/whl/cu130

&lt;span class="c"&gt;# ComfyUI itself&lt;/span&gt;
git clone https://github.com/comfyanonymous/ComfyUI.git
&lt;span class="nb"&gt;cd &lt;/span&gt;ComfyUI
pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt

&lt;span class="c"&gt;# Model (SD 1.5, ~2GB)&lt;/span&gt;
&lt;span class="nb"&gt;cd &lt;/span&gt;models/checkpoints/
wget https://huggingface.co/Comfy-Org/stable-diffusion-v1-5-archive/resolve/main/v1-5-pruned-emaonly-fp16.safetensors

&lt;span class="c"&gt;# Launch server&lt;/span&gt;
&lt;span class="nb"&gt;cd&lt;/span&gt; ~/ComfyUI
python main.py &lt;span class="nt"&gt;--listen&lt;/span&gt; 0.0.0.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Key packages installed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;torch 2.11.0+cu130&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;cuDNN 9.19&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;NCCL 2.28&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;transformers 5.7.0&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;comfyui-frontend-package 1.42.15&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Open &lt;code&gt;http://spark-XXXX.local:8188&lt;/code&gt; from your Windows PC's browser to access ComfyUI (XXXX is your hostname).&lt;/p&gt;
&lt;h3&gt;
  
  
  Download speed
&lt;/h3&gt;

&lt;p&gt;The 2GB model came down at 40.6 MB/s in 50 seconds from HuggingFace's CDN. About half of my home 1Gbps LAN.&lt;/p&gt;




&lt;h2&gt;
  
  
  Tomorrow: Day 2
&lt;/h2&gt;

&lt;p&gt;Day 2 plan: Train a LoRA on photos of my actual cat.&lt;/p&gt;

&lt;p&gt;Today's SD 1.5 only knows "some cat from somewhere". With LoRA fine-tuning, I should be able to teach it about my specific cat. That kind of personalization feels like the killer feature of running locally.&lt;/p&gt;




&lt;h1&gt;
  
  
  100ExperimentsWithDGX #LocalLLM
&lt;/h1&gt;

</description>
      <category>localllm</category>
      <category>ai</category>
      <category>dgxspark</category>
      <category>comfyui</category>
    </item>
  </channel>
</rss>
