<?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: Kasper</title>
    <description>The latest articles on DEV Community by Kasper (@nibor1896).</description>
    <link>https://dev.to/nibor1896</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%2F4027621%2Ffc171ea8-e1e3-4189-8ab6-82d003df70e6.png</url>
      <title>DEV Community: Kasper</title>
      <link>https://dev.to/nibor1896</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/nibor1896"/>
    <language>en</language>
    <item>
      <title>284B model on one graphics card, because most of it is asleep</title>
      <dc:creator>Kasper</dc:creator>
      <pubDate>Sat, 08 Aug 2026 11:50:16 +0000</pubDate>
      <link>https://dev.to/nibor1896/284b-model-on-one-graphics-card-because-most-of-it-is-asleep-47hc</link>
      <guid>https://dev.to/nibor1896/284b-model-on-one-graphics-card-because-most-of-it-is-asleep-47hc</guid>
      <description>&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%2F83q66ndqc9yk51koheeq.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%2F83q66ndqc9yk51koheeq.png" alt=" " width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A mixture-of-experts model only wakes a fraction of itself per token. Six experts out of 256, in each of 43 layers. So the question stops being how to fit 96 GiB into 32 GiB of VRAM and becomes how fast you can read the missing pieces off a drive.&lt;/p&gt;

&lt;p&gt;Crow keeps the always-active part on the card, holds the 64 most useful experts per layer beside it, and streams whatever is missing while the GPU works. Peak host memory is 1.28 GiB. Decode is around 12 tok/s at a 200k context.&lt;/p&gt;

&lt;p&gt;The interesting part is not the throughput. It is that 79% of a request is the decode thread waiting on the drive, so this is a latency problem wearing a bandwidth costume, and everything that helped acts on latency.&lt;/p&gt;

&lt;p&gt;Model: DeepSeek V4 Flash&lt;br&gt;
Full details on Git: &lt;a href="https://github.com/nibor1896/Crow" rel="noopener noreferrer"&gt;Crow on Github&lt;/a&gt;&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>ai</category>
      <category>opensource</category>
      <category>python</category>
    </item>
    <item>
      <title>Giving Claude a brain</title>
      <dc:creator>Kasper</dc:creator>
      <pubDate>Sat, 01 Aug 2026 18:05:12 +0000</pubDate>
      <link>https://dev.to/nibor1896/giving-claude-a-brain-14mj</link>
      <guid>https://dev.to/nibor1896/giving-claude-a-brain-14mj</guid>
      <description>&lt;p&gt;I use Claude across a bunch of projects. Every one of them needs some place for Claude to write down stuff that isn't in the code. A decision I made three weeks ago, why a bug actually happened, a workflow nobody ever wrote down. I kept solving that by hand, per project, and after a while none of my setups looked the same and I trusted none of them fully.&lt;/p&gt;

&lt;p&gt;So I built claude-obsidian-vault-kit. It's not a plugin, it's basically a setup contract disguised as a Markdown file. You hand it to Claude, answer a few questions, and it builds you a proper Obsidian vault: folders, an index, and some tools that check everything stays sane.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it works
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Download &lt;a href="https://github.com/nibor1896/claude-obsidian-vault-kit/blob/master/claude-obsidian-vault-kit.md" rel="noopener noreferrer"&gt;claude-obsidian-vault-kit.md&lt;/a&gt;, one file, that's it.&lt;/li&gt;
&lt;li&gt;Drop it into a Claude conversation.&lt;/li&gt;
&lt;li&gt;Say "set this up for me."&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Claude then just asks you stuff first, like whether you already use Obsidian, if you want a throwaway test vault to poke around in before it's real, which projects you have, how you want to back things up. Only after that does it actually write anything to your disk.&lt;/p&gt;

&lt;p&gt;What you end up with is completely empty. No fake example notes, nothing invented, just the folder structure, three small tools, and a few pages explaining how it all works.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part I actually built this for
&lt;/h2&gt;

&lt;p&gt;Every note gets a small header at the top (title, summary, project, topic, date) and the tool that builds your index only reads that header. It never opens the note itself. That's all: your index can't get cluttered with random text because there's no code path that lets it happen. You never edit the index by hand either, you edit the note and rerun the tool, and if a note's header is broken or missing, it doesn't just get quietly skipped. It tells you, loudly.&lt;/p&gt;

&lt;p&gt;There's also a /vaultkit command that gets installed for you and running it rebuilds the index and checks for broken links, duplicate notes, and stale scheduled stuff. All in one go.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where it stands right now
&lt;/h2&gt;

&lt;p&gt;I've tested pretty thoroughly on Windows with PowerShell and Git Bash. macOS hasn't been tried ones (I don't a Macbook), and Linux was only tested once on an older version of the kit, so treat both as "probably fine, not promised." If you try either, opening an issue with what happened would genuinely help.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;git clone &lt;a href="https://github.com/nibor1896/claude-obsidian-vault-kit" rel="noopener noreferrer"&gt;https://github.com/nibor1896/claude-obsidian-vault-kit&lt;/a&gt;&lt;br&gt;
or just grab the &lt;a href="https://github.com/nibor1896/claude-obsidian-vault-kit/blob/master/claude-obsidian-vault-kit.md" rel="noopener noreferrer"&gt;claude-obsidian-vault-kit.md&lt;/a&gt; file straight from the repo and drop it into a chat with Claude. &lt;/p&gt;

&lt;h2&gt;
  
  
  Result
&lt;/h2&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%2F3mws88ywmjnd76u3look.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%2F3mws88ywmjnd76u3look.png" alt="Obsidian Vault" width="800" height="497"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>claude</category>
      <category>obsidian</category>
      <category>productivity</category>
      <category>opensource</category>
    </item>
    <item>
      <title>F5-TTS in .NET, v0.3.0: streaming, numbers that read right, and the feature I measured away</title>
      <dc:creator>Kasper</dc:creator>
      <pubDate>Sun, 19 Jul 2026 09:46:50 +0000</pubDate>
      <link>https://dev.to/nibor1896/f5-tts-in-net-v030-streaming-numbers-that-read-right-and-the-feature-i-measured-away-3jdk</link>
      <guid>https://dev.to/nibor1896/f5-tts-in-net-v030-streaming-numbers-that-read-right-and-the-feature-i-measured-away-3jdk</guid>
      <description>&lt;p&gt;&lt;a href="https://github.com/nibor1896/Horus.F5Tts.Onnx" rel="noopener noreferrer"&gt;Horus.F5Tts.Onnx&lt;/a&gt;&lt;br&gt;
Here's what landed in &lt;strong&gt;v0.3.0&lt;/strong&gt;:&lt;/p&gt;
&lt;h2&gt;
  
  
  Streaming: hear the first sentence sooner
&lt;/h2&gt;

&lt;p&gt;For a paragraph, &lt;code&gt;SynthesizeLongAsync&lt;/code&gt; gives you nothing until every sentence is done. The wait that actually hurts in an interactive app is &lt;em&gt;time-to-first-audio&lt;/em&gt; — and that's the one v0.3.0 fixes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;chunk&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SynthesizeStreamAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reference&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;refText&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;paragraph&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;player&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Write&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="n"&gt;Samples&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// sentence chunk.Index + 1 of chunk.Count&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The bit I care about: it's &lt;strong&gt;not a second rendering&lt;/strong&gt;. The chunks are the same sentence-level pieces, cross-faded the same way, so concatenating every streamed chunk is &lt;strong&gt;byte-for-byte identical&lt;/strong&gt; to &lt;code&gt;SynthesizeLongAsync&lt;/code&gt; for the same inputs and seed. I proved that twice — as pure cross-fade math with no models, and end-to-end against the model — and then confirmed it live: the streamed and non-streamed WAVs share one SHA-256.&lt;/p&gt;

&lt;p&gt;It's chunk-granularity, not frame-level (F5 renders each chunk as a whole), so the win is the &lt;em&gt;first&lt;/em&gt; chunk arriving early, and it grows with text length.&lt;/p&gt;

&lt;h2&gt;
  
  
  Text normalizers: numbers that read right
&lt;/h2&gt;

&lt;p&gt;Checkpoints are trained on &lt;em&gt;normalized&lt;/em&gt; text. Feed the model a raw &lt;code&gt;50 %&lt;/code&gt;, &lt;code&gt;1.000 €&lt;/code&gt;, &lt;code&gt;z.B.&lt;/code&gt;, &lt;code&gt;3.8.2026&lt;/code&gt; or &lt;code&gt;14:30&lt;/code&gt; and it swallows or mumbles them — they're out-of-distribution. v0.3.0 ships ready normalizers for German and English:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TextNormalizer&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;GermanTextNormalizer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Normalize&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// "am 3.8.2026 um 14:30 Uhr" -&amp;gt; "am dritten August zweitausendsechsundzwanzig um vierzehn Uhr dreißig"&lt;/span&gt;

&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TextNormalizer&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;EnglishTextNormalizer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Normalize&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// "I saved $1,000 (50%) by the 3rd" -&amp;gt; "I saved one thousand dollars (fifty percent) by the third"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;They cover cardinal numbers, percent, currency with cents, decimals and thousands (the &lt;code&gt;,&lt;/code&gt;/&lt;code&gt;.&lt;/code&gt; convention is swapped between the two languages), dates, clock times, ordinals (German inflects from the leading word — &lt;code&gt;am&lt;/code&gt; → &lt;em&gt;-ten&lt;/em&gt;, &lt;code&gt;der&lt;/code&gt; → &lt;em&gt;-te&lt;/em&gt;, none → &lt;em&gt;-ter&lt;/em&gt;), abbreviations and a few symbols. Only recognized patterns are touched; prose — and the name "Max." — is left alone.&lt;/p&gt;

&lt;p&gt;The difference is audible. The same German sentence ran &lt;strong&gt;5.2 s raw vs 8.3 s normalized&lt;/strong&gt; — the extra seconds are the numbers actually being spoken instead of skipped.&lt;/p&gt;

&lt;p&gt;And where a reading would be a &lt;em&gt;guess&lt;/em&gt;, I left it alone on purpose: German bare ordinals at a sentence boundary stay cardinals, and English numeric dates (&lt;code&gt;3/8/2026&lt;/code&gt; — is that March 8 or 3 August?) are untouched. A wrong reading is worse than a plain one.&lt;/p&gt;

&lt;h2&gt;
  
  
  PreparedVoice — and the feature I measured away
&lt;/h2&gt;

&lt;p&gt;When one voice speaks many lines, you can now bind the reference once:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;voice&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;PrepareVoiceFromWav&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"reference.wav"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;refText&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;voice&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SynthesizeAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"First line."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;voice&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SynthesizeLongAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;wholeParagraph&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I originally wanted this as a &lt;em&gt;performance&lt;/em&gt; feature — cache the reference processing across calls. Then I measured one synthesis (NFE 32):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;phase&lt;/th&gt;
&lt;th&gt;time&lt;/th&gt;
&lt;th&gt;share&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;preprocess (the only reference-dependent step)&lt;/td&gt;
&lt;td&gt;50 ms&lt;/td&gt;
&lt;td&gt;0.3 %&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;transformer denoising loop (31 steps)&lt;/td&gt;
&lt;td&gt;16,156 ms&lt;/td&gt;
&lt;td&gt;99.5 %&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;decode&lt;/td&gt;
&lt;td&gt;36 ms&lt;/td&gt;
&lt;td&gt;0.2 %&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Caching the reference would save ~0.3 % — and it can't even be cached, because F5 fuses the reference into the same graph pass as the generated text. So &lt;code&gt;PreparedVoice&lt;/code&gt; ships as &lt;strong&gt;ergonomics only&lt;/strong&gt;, and its docs say exactly that. The genuine latency win this release is streaming (time-to-first-audio), not throughput.&lt;/p&gt;

&lt;p&gt;That's the rule I keep for this library: measure before you build, and don't sell a number you don't have.&lt;/p&gt;

&lt;h2&gt;
  
  
  Also in 0.3.0
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;IProgress&amp;lt;F5TtsProgress&amp;gt;&lt;/code&gt; progress reporting that spans the whole request, and &lt;strong&gt;238 tests&lt;/strong&gt; (the streaming guarantee and ~124 normalizer cases included).&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;📦 NuGet: &lt;code&gt;dotnet add package Horus.F5Tts.Onnx&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;📝 &lt;a href="https://github.com/nibor1896/Horus.F5Tts.Onnx/blob/main/CHANGELOG.md#030---2026-07-19" rel="noopener noreferrer"&gt;Changelog&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;🤗 Models: &lt;a href="https://huggingface.co/nibor1896/F5-TTS-German-ONNX" rel="noopener noreferrer"&gt;German&lt;/a&gt; · &lt;a href="https://huggingface.co/nibor1896/F5-TTS-English-ONNX" rel="noopener noreferrer"&gt;English&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Feedback, issues and PRs welcome. 🙌&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>ai</category>
      <category>opensource</category>
      <category>tts</category>
    </item>
    <item>
      <title>v0.2.0 - Horus.F5Tts.Onnx - MASSIVE improvements</title>
      <dc:creator>Kasper</dc:creator>
      <pubDate>Thu, 16 Jul 2026 14:26:20 +0000</pubDate>
      <link>https://dev.to/nibor1896/v020-horusf5ttsonnx-107c</link>
      <guid>https://dev.to/nibor1896/v020-horusf5ttsonnx-107c</guid>
      <description>&lt;p&gt;&lt;a href="//nibor1896.github.io/Horus.F5Tts.Onnx/"&gt;HORUS.F5TTS.ONNX&lt;/a&gt;&lt;br&gt;
&lt;a href="https://www.nuget.org/packages/Horus.F5Tts.Onnx#readme-body-tab" rel="noopener noreferrer"&gt;HORUS.F5TTS.ONNX - NuGet&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;v0.2.0 ships FP16 → 10× faster on GPU.&lt;br&gt;
60 ms per denoising step, down from 617. Measured, not vibes.&lt;/p&gt;

&lt;p&gt;Plus: text of any length, real cancellation, resampling built in.&lt;/p&gt;

&lt;p&gt;MIT · 90 tests&lt;br&gt;
dotnet add package Horus.F5Tts.Onnx&lt;/p&gt;

</description>
      <category>ai</category>
      <category>csharp</category>
      <category>dotnet</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Shipping the first .NET F5-TTS library — and the ONNX bug I had to fix first</title>
      <dc:creator>Kasper</dc:creator>
      <pubDate>Mon, 13 Jul 2026 17:24:49 +0000</pubDate>
      <link>https://dev.to/nibor1896/shipping-the-first-net-f5-tts-library-and-the-onnx-bug-i-had-to-fix-first-22dc</link>
      <guid>https://dev.to/nibor1896/shipping-the-first-net-f5-tts-library-and-the-onnx-bug-i-had-to-fix-first-22dc</guid>
      <description>&lt;p&gt;F5-TTS is one of the better open text-to-speech and voice-cloning models. There's just one catch if you live in the .NET world: &lt;strong&gt;running it means Python and PyTorch.&lt;/strong&gt; No native story, no NuGet package, nothing you can drop into a desktop app without shipping an interpreter alongside it.&lt;/p&gt;

&lt;p&gt;I wanted German (and any-language) TTS inside a native Windows app — so I set out to run F5-TTS on &lt;strong&gt;ONNX Runtime&lt;/strong&gt;, which .NET already has first-class bindings for. This is the story of the bug that stood in the way, and the library that came out of fixing it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The garbled-German problem
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/DakeQQ/F5-TTS-ONNX" rel="noopener noreferrer"&gt;DakeQQ/F5-TTS-ONNX&lt;/a&gt; is an excellent project that exports F5-TTS to three ONNX graphs (preprocess → transformer → decode). It works great — for the &lt;strong&gt;default (v1)&lt;/strong&gt; base checkpoint.&lt;/p&gt;

&lt;p&gt;Point it at a non-English community fine-tune, though, and the output is &lt;em&gt;fluent-but-wrong&lt;/em&gt; speech: the right voice, the right language, complete nonsense words. A classic sign that the text conditioning is broken while the acoustic model is fine.&lt;/p&gt;

&lt;h2&gt;
  
  
  Root cause: v0 vs v1
&lt;/h2&gt;

&lt;p&gt;Many non-English fine-tunes (like &lt;code&gt;hvoss-techfak/F5-TTS-German&lt;/code&gt;) are built on the older &lt;strong&gt;F5TTS_Base (v0)&lt;/strong&gt; architecture, which differs from v1 in two config flags:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;param&lt;/th&gt;
&lt;th&gt;v1 (default)&lt;/th&gt;
&lt;th&gt;v0&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pe_attn_head&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;None&lt;/code&gt; (RoPE on all heads)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;1&lt;/code&gt; (RoPE on the &lt;strong&gt;first head only&lt;/strong&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;text_mask_padding&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;True&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The exporter's model reimplementation was hard-wired for v1: it applied RoPE to every attention head and always zeroed out padded text positions. On a v0 checkpoint, both corrupt the conditioning — and you get word-salad.&lt;/p&gt;

&lt;p&gt;The fix was small and I contributed it upstream in &lt;a href="https://github.com/DakeQQ/F5-TTS-ONNX/pull/74" rel="noopener noreferrer"&gt;PR #74&lt;/a&gt; (merged): honor &lt;code&gt;pe_attn_head&lt;/code&gt; in the attention processor, and honor &lt;code&gt;mask_padding&lt;/code&gt; in the text embedding.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping it in a .NET API
&lt;/h2&gt;

&lt;p&gt;With correct ONNX models in hand, the wrapper is tiny — all the heavy signal processing (STFT, the diffusion transformer, the vocoder) lives inside the graphs. The library just marshals tensors and runs the NFE loop:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Horus.F5Tts.Onnx&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;F5TtsModel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"F5_Preprocess.onnx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"F5_Transformer.onnx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"F5_Decode.onnx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"vocab.txt"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;configureSession&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AppendExecutionProvider_DML&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; &lt;span class="c1"&gt;// CPU / DirectML / CUDA&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reference&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;WavAudio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReadPcm16&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"voice.wav"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 24 kHz mono&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Synthesize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reference&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Transcript of the clip."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Hello from .NET!"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteAllBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"out.wav"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToWav&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Design choices worth calling out:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No forced runtime.&lt;/strong&gt; The package only references the ONNX Runtime &lt;em&gt;managed&lt;/em&gt; API; the consumer adds the CPU / DirectML / CUDA native package and picks the provider via a session hook.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Character-level tokenizer.&lt;/strong&gt; For Latin-script languages you don't need jieba/pinyin at all — a plain char→vocab-index mapping is enough (verified end-to-end).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dependency-free WAV helpers&lt;/strong&gt;, so the surface stays tiny.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Verifying it actually works
&lt;/h2&gt;

&lt;p&gt;Compiling isn't shipping. I ran the whole pipeline against real models and transcribed the output with &lt;code&gt;faster-whisper large-v3&lt;/code&gt;: correct language, exact transcript, confidence 1.00. (The smoke test caught a real runtime bug too — the NFE loop ran one step too many and overran the transformer's time-step table. CI + a smoke test earn their keep.)&lt;/p&gt;

&lt;h2&gt;
  
  
  Shipping it
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;NuGet:&lt;/strong&gt; &lt;code&gt;dotnet add package Horus.F5Tts.Onnx&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/nibor1896/Horus.F5Tts.Onnx" rel="noopener noreferrer"&gt;nibor1896/Horus.F5Tts.Onnx&lt;/a&gt; (MIT)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Model:&lt;/strong&gt; &lt;a href="https://huggingface.co/nibor1896/F5-TTS-German-ONNX" rel="noopener noreferrer"&gt;nibor1896/F5-TTS-German-ONNX&lt;/a&gt; (CC-BY-NC-4.0)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Site:&lt;/strong&gt; &lt;a href="https://nibor1896.github.io/Horus.F5Tts.Onnx/" rel="noopener noreferrer"&gt;nibor1896.github.io/Horus.F5Tts.Onnx&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As far as I can tell, it's the first library that runs F5-TTS natively from .NET. If you're building something that needs local, offline, natural-sounding TTS on Windows/.NET, give it a spin — and open an issue if you hit anything.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>ai</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
