<?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>DEV Community: Joul</title>
    <description>The latest articles on DEV Community by Joul (@joul_dev).</description>
    <link>https://dev.to/joul_dev</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4063924%2Fcc1cc157-ff85-430e-96ab-627aaccc5c93.png</url>
      <title>DEV Community: Joul</title>
      <link>https://dev.to/joul_dev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/joul_dev"/>
    <language>en</language>
    <item>
      <title>Shipping MiniMax H3 three days after the weights dropped</title>
      <dc:creator>Joul</dc:creator>
      <pubDate>Mon, 10 Aug 2026 15:28:29 +0000</pubDate>
      <link>https://dev.to/joul_dev/shipping-minimax-h3-three-days-after-the-weights-dropped-2dp8</link>
      <guid>https://dev.to/joul_dev/shipping-minimax-h3-three-days-after-the-weights-dropped-2dp8</guid>
      <description>&lt;p&gt;MiniMax released the open weights for H3, their omni-modal video model, on August 3. By August 6 it was running in production on our own GPUs — text-to-video and image-to-video, with joint audio, served through &lt;a href="https://gen-image.com" rel="noopener noreferrer"&gt;gen-image&lt;/a&gt;, the &lt;a href="https://imference.com" rel="noopener noreferrer"&gt;imference API&lt;/a&gt; and Imference Desktop. We're two developers working on this part-time.&lt;/p&gt;

&lt;p&gt;This post is about the unglamorous middle: what it actually takes to serve a frontier video model three days after the weights drop, when you don't write custom kernels and don't have a lab's GPU budget.&lt;/p&gt;

&lt;h2&gt;
  
  
  Riding an unmerged PR
&lt;/h2&gt;

&lt;p&gt;The honest version of "we use Hugging Face diffusers" is: H3 support lives in &lt;a href="https://github.com/huggingface/diffusers/pull/14355" rel="noopener noreferrer"&gt;diffusers PR #14355&lt;/a&gt;, which is not merged and not released. We pin the exact commit we validated. The PR imports &lt;code&gt;torch.nn.functional.ScalingType&lt;/code&gt;, which only exists since torch 2.10 — so the stack is torch 2.11.0+cu128, torchao 0.18, and a diffusers build that officially doesn't exist yet.&lt;/p&gt;

&lt;p&gt;The price of that speed: the H3 worker can't share a venv with the rest of our fleet, which is pinned to diffusers 0.39.0. Dedicated pod, dedicated stack, and we'll converge when the PR ships in a release. We don't write inference kernels — we ride Hugging Face's work while it's still warm. That's the whole strategy, and the commit-level pin is what it costs.&lt;/p&gt;

&lt;h2&gt;
  
  
  The actual three days: converting checkpoints
&lt;/h2&gt;

&lt;p&gt;Here's what nobody tells you about day-3 support: the weights that exist aren't the weights you need.&lt;/p&gt;

&lt;p&gt;The only usable int8 quantization of H3 was published by Comfy-Org in their ConvRot format — block Hadamard rotations with per-channel scales, laid out for ComfyUI. Diffusers expects a different world. So the real work of the three days was a converter: de-quantize ConvRot, re-serialize as torchao int8, resynthesize the audio VAE's &lt;code&gt;weight_g&lt;/code&gt;/&lt;code&gt;weight_v&lt;/code&gt; parameters (the published file has weight_norm fused), and rebuild the whole thing as a diffusers model tree.&lt;/p&gt;

&lt;p&gt;My favorite detail: H3's text encoder is a Qwen3-VL truncated to 50 layers, but the loader guard insists on more than 50. The converter synthesizes a fake 51st layer — norms at 1, projections near zero — purely to walk past the check. It does nothing. It exists to satisfy an assertion.&lt;/p&gt;

&lt;p&gt;The output is a ~63 GB tree across 27 files, verified against the original safetensors headers via HTTP range requests — you can check tensor shapes and dtypes without downloading 63 GB, which felt like cheating in the best way. We pushed the tree to our own R2 mirror; production pods run with &lt;code&gt;HF_HUB_OFFLINE=1&lt;/code&gt; and never touch Hugging Face at runtime.&lt;/p&gt;

&lt;p&gt;Was int8 worth the trouble? We A/B'd it against bf16 on the same seed: the frames are visually identical. int8 is the production profile, no debate. (int4 was attempted and abandoned — torchao 0.18 removed its old int4 path and the new one depends on a library that isn't published yet. Comfy's "pruned" bf16 variant turned out to be a modified architecture, low-rank AdaLN and all — not convertible, rejected.)&lt;/p&gt;

&lt;h2&gt;
  
  
  What it runs on
&lt;/h2&gt;

&lt;p&gt;The surprise of H3 isn't the VRAM. With block-level offload it peaks around 21 GB; with leaf-level offload, 10.7 GB — and since throughput is bound by memory bandwidth rather than compute, the aggressive offload barely costs you. A 12-16 GB gaming card can run this model.&lt;/p&gt;

&lt;p&gt;The floor isn't VRAM. It's host RAM: you need roughly 75 GB of it to hold the weights for offloading. That single number explains most of our deployment decisions — including why Desktop gets H3 as a cloud model (more on that below).&lt;/p&gt;

&lt;p&gt;We validated on a rented RTX 6000 Ada (48 GB, ~14.9 s/step at SD resolution) and serve production on RTX 5090 pods (~8.1 s/step — nearly 2× the Ada, GDDR7 bandwidth doing the work). Our wider fleet spans A4000s to 5090s, which forced one careful choice: CUDA 12.8 is the last channel whose builds cover the entire range in a single image, Turing through Blackwell. torch 2.12 dropped it. So &lt;code&gt;pytorch:2.11.0-cuda12.8&lt;/code&gt; is, as far as we can tell, the newest "runs everywhere" combo that exists.&lt;/p&gt;

&lt;p&gt;One deployment lesson worth passing on: our GPU preflight used to check the device's compute capability against a supported list. A 4090 passed the check and then failed in ways the metadata said were impossible. The fix was embarrassingly simple — run a kernel, add 1 to &lt;code&gt;zeros(8)&lt;/code&gt;, assert the sum is 8. Test behavior, not metadata.&lt;/p&gt;

&lt;h2&gt;
  
  
  Latency, honestly
&lt;/h2&gt;

&lt;p&gt;Real numbers, RTX 5090, 6.58-second clip at 24 fps, fixed seed: SD (960×544) at 20 steps takes about 4 minutes. HD (native 1344×768) takes about 9. HD at 30 steps takes 14 — and was rejected, because in a blind A/B I couldn't see the difference from 20 steps. Attention cost scales with the square of the token count, so resolution is brutally expensive.&lt;/p&gt;

&lt;p&gt;This is why we serve 540p and 720p, and nothing above: we don't have the GPU budget to serve more at a latency we'd accept. SD is the default; HD is opt-in and priced accordingly. (The 2K module never entered the discussion — MiniMax kept it API-only.) Cold start on a fresh pod is 2 min 15 — 63 GB pulled from the CDN mirror plus load.&lt;/p&gt;

&lt;p&gt;One operational footnote: queue timeouts tuned for image jobs don't survive contact with video. A ceiling that's generous for an SDXL render is fatal for a nine-minute clip — our video rails now run with a much longer active timeout than the image ones. A video job is a different animal, and every layer of the stack needs to know it.&lt;/p&gt;

&lt;h2&gt;
  
  
  One engine, one queue, three products
&lt;/h2&gt;

&lt;p&gt;H3 now runs on the same rail as everything else we serve: one inference engine, one queue (&lt;a href="https://github.com/Publikey/runqy" rel="noopener noreferrer"&gt;runqy&lt;/a&gt;, our open-source Go task queue), fanned out to gen-image, the imference API, and Imference Desktop. On Desktop it's a cloud model — the local stack stays on stable diffusers, and 75 GB of host RAM is not a laptop.&lt;/p&gt;

&lt;p&gt;One more first for us, almost in passing: H3 generates audio jointly with the video, in the same DiT — the mp4 comes out with AAC muxed in. It's the first audio+video rail in the house, and it cost us nothing extra to ship.&lt;/p&gt;

&lt;p&gt;Three days, one converter, one fake layer. The model is live — go make something with it.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>machinelearning</category>
      <category>go</category>
      <category>devops</category>
    </item>
    <item>
      <title>I translated my app into Chinese. I don't speak Chinese.</title>
      <dc:creator>Joul</dc:creator>
      <pubDate>Wed, 05 Aug 2026 09:55:19 +0000</pubDate>
      <link>https://dev.to/joul_dev/i-translated-my-app-into-chinese-i-dont-speak-chinese-4g60</link>
      <guid>https://dev.to/joul_dev/i-translated-my-app-into-chinese-i-dont-speak-chinese-4g60</guid>
      <description>&lt;p&gt;The translation itself took minutes. Verifying it found a bug that had been sitting in my app all along — in English too.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why bother
&lt;/h2&gt;

&lt;p&gt;While researching where local AI inference is actually popular, I kept running into the same answer: China. It makes sense — capable consumer GPUs, a strong self-hosting culture, a healthy distrust of cloud services. It's also a market that's structurally out of reach for Western cloud SaaS... but not for an open-source desktop app. Nothing to host, nothing to bill, nothing to block.&lt;/p&gt;

&lt;p&gt;So the math looked like this: potential upside — access to one of the largest local-inference communities in the world; cost — an afternoon. I don't speak a word of Chinese. I did it anyway.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/Publikey/imference-desktop" rel="noopener noreferrer"&gt;Imference Desktop&lt;/a&gt; is a local-first image generation app. There isn't that much text in it — buttons, labels, settings, error messages. I asked Claude to translate the locale file. That's the part everyone imagines is hard. It isn't.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Foh2hypiwlumrqcp6bmuf.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Foh2hypiwlumrqcp6bmuf.png" alt="Imference Desktop — the same main screen in English (top) and Chinese (bottom)" width="800" height="835"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Same screen, two audiences — English on top, Chinese below.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The parts that actually needed thought
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Some strings don't live where you think they do.&lt;/strong&gt; My format labels ("Portrait Large", "Landscape Ultra-Wide"...) come from the server's model catalog, not from the app's locale files. Result: the format bar showed up half-translated — three Chinese labels followed by six English ones — because only the strings that existed as i18n keys got translated. Localization coverage is an architecture question before it's a language question.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Some things must NOT be translated.&lt;/strong&gt; Model names stay in English — even Chinese users say "Flux" and "SDXL". Translating them would have made the app &lt;em&gt;harder&lt;/em&gt; to use. Knowing what to leave alone matters as much as translating the rest.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Localization is adaptation, not conversion.&lt;/strong&gt; The Chinese prompt placeholder doesn't say what the English one says. It adds a piece of advice the English version doesn't need: "describe the image you want (English recommended), e.g. ...". Most image models are trained predominantly on English captions; a Chinese user typing a Chinese prompt gets worse results through no fault of their own. The translation that serves the user diverges from the source on purpose.&lt;/p&gt;

&lt;h2&gt;
  
  
  Verifying what you can't read
&lt;/h2&gt;

&lt;p&gt;Here's the uncomfortable part. When the translation came back, I was looking at strings I could not evaluate at all. For all I knew, my "Cancel" button said something embarrassing.&lt;/p&gt;

&lt;p&gt;My review pipeline, fully honest version:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;I took paired screenshots of every screen — English and Chinese side by side.&lt;/li&gt;
&lt;li&gt;I fed them to a &lt;em&gt;fresh&lt;/em&gt; Claude session, in incognito mode, with no memory of my app and no stake in defending its own translation, and asked for an element-by-element comparison.&lt;/li&gt;
&lt;li&gt;It came back with a structured review: most of the translation judged idiomatic and faithful, the half-translated format bar flagged, two wording refinements suggested (a clearer per-generation credits label, a VRAM-vs-memory inconsistency in a settings subtitle), and one question — "your Chinese placeholder diverges from the English one, is that intentional?"&lt;/li&gt;
&lt;li&gt;I fed that review to Claude Code, which traced each finding to the actual cause in the codebase and implemented the fixes.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;My job in all this was arbitration, not translation: keep the divergent placeholder (intentional), pick the Chinese naming scheme for the extended formats, decide where to fix what. The pipeline was AI end to end — translator, reviewer, implementer — with a human making the three or four judgment calls that actually required one.&lt;/p&gt;

&lt;h2&gt;
  
  
  The review found a bug in my app
&lt;/h2&gt;

&lt;p&gt;This is the part I didn't see coming. Comparing the two languages element by element, the reviewer noticed something off that had nothing to do with Chinese: my aspect ratios were backwards. A 896×1152 portrait format was labeled "9:7" — a landscape ratio. Checked against the API: systematic, every image format in the catalog ships its ratio string inverted. In production. In both languages. Nobody had ever reported it — including me, who had looked at that format bar hundreds of times.&lt;/p&gt;

&lt;p&gt;The fix was a ten-line client-side normalization (trust the dimensions, flip the string when its orientation disagrees). The lesson is bigger: &lt;strong&gt;a careful localization review is a free QA pass.&lt;/strong&gt; Fresh eyes — even artificial ones — forced to compare two renderings of the same UI element by element will catch things you've stopped seeing. The Chinese translation improved my English app.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd tell other indie devs
&lt;/h2&gt;

&lt;p&gt;AI has collapsed the cost of localization. What used to require an agency, a budget and weeks now takes an afternoon — &lt;em&gt;for the right kind of product&lt;/em&gt;. Two honest caveats:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;This works because my app has little text and low stakes. I would not ship an AI-only translation for legal copy, medical content, or marketing where tone is everything.&lt;/li&gt;
&lt;li&gt;The translation is the cheap part. The verification pipeline and the public honesty are what make it shippable. My Chinese README says it plainly: translated with AI assistance by a dev who doesn't speak Chinese — corrections welcome via PR. That one line turns my biggest weakness into a contribution funnel.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The asymmetry is what matters: an afternoon of work for access to a market I couldn't otherwise touch. Even if it only ever brings a handful of users and a few translation-fixing PRs, it already paid for itself — it found a bug my own eyes never would have.&lt;/p&gt;

</description>
      <category>i18n</category>
      <category>ai</category>
      <category>indiedev</category>
      <category>showdev</category>
    </item>
  </channel>
</rss>
