<?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: Mena Mahany</title>
    <description>The latest articles on DEV Community by Mena Mahany (@mena_mahany_1d49c5103fdce).</description>
    <link>https://dev.to/mena_mahany_1d49c5103fdce</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%2F4057991%2F3d3cdfb2-6036-4fec-b9ea-07cb176c0277.jpg</url>
      <title>DEV Community: Mena Mahany</title>
      <link>https://dev.to/mena_mahany_1d49c5103fdce</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mena_mahany_1d49c5103fdce"/>
    <language>en</language>
    <item>
      <title>How I Cut AI Transcription Costs Below $0.10/Hour with AWS Spot Instances and Whisper Large-v3 Turbo</title>
      <dc:creator>Mena Mahany</dc:creator>
      <pubDate>Sat, 01 Aug 2026 12:55:03 +0000</pubDate>
      <link>https://dev.to/mena_mahany_1d49c5103fdce/how-i-cut-ai-transcription-costs-below-010hour-with-aws-spot-instances-and-whisper-large-v3-turbo-2od1</link>
      <guid>https://dev.to/mena_mahany_1d49c5103fdce/how-i-cut-ai-transcription-costs-below-010hour-with-aws-spot-instances-and-whisper-large-v3-turbo-2od1</guid>
      <description>&lt;p&gt;A deep dive into building a cost-optimized, self-hosted AI transcription pipeline on AWS - using Spot Instances, SQS queue-based autoscaling, and Whisper Large-v3 Turbo - that beats the OpenAI Whisper API on price.&lt;/p&gt;

&lt;p&gt;Most teams building on top of speech-to-text quietly accept the API bill. I didn't. When a side project started costing me &lt;strong&gt;$180/month in transcription API fees&lt;/strong&gt;, I rebuilt the entire pipeline on AWS and got my processing cost &lt;strong&gt;below $0.10/hour&lt;/strong&gt; - cheaper than the OpenAI Whisper API itself.&lt;/p&gt;

&lt;p&gt;This post walks through the architecture, the trade-offs, and the specific decisions that made it work. Everything here powers &lt;a href="https://www.strikescribe.com" rel="noopener noreferrer"&gt;StrikeScribe&lt;/a&gt;, the AI transcription platform I built as a solo founder, so these numbers come from real production workloads, not a benchmark toy.&lt;/p&gt;

&lt;p&gt;If you're building anything on top of Whisper, Deepgram, AssemblyAI, or the OpenAI audio API, this is the arbitrage most people miss.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem: managed transcription APIs don't scale on cost
&lt;/h2&gt;

&lt;p&gt;Managed APIs are fantastic for getting started. You send audio, you get text. But the pricing model punishes volume:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;OpenAI Whisper API:&lt;/strong&gt; ~$0.36/hour (at $0.006/min)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Otter, Fireflies, and similar:&lt;/strong&gt; subscription tiers that cap your hours and charge steep overages&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AssemblyAI / Deepgram:&lt;/strong&gt; competitive, but still per-minute billing that grows linearly with usage&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The moment you're processing hundreds of hours a month — meetings, podcasts, research calls, IoT audio streams — that linear cost curve becomes the dominant line item. You're paying a margin on GPU compute you could rent directly.&lt;/p&gt;

&lt;p&gt;The insight: &lt;strong&gt;the underlying model (Whisper) is open source.&lt;/strong&gt; You're paying for someone else's convenience layer. If you can run the inference yourself efficiently, the economics flip.&lt;/p&gt;

&lt;h2&gt;
  
  
  The architecture
&lt;/h2&gt;

&lt;p&gt;Here's the high-level pipeline:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Upload → S3 → SQS Queue → Autoscaling Worker Group (GPU Spot Instances)
   → Whisper Large-v3 Turbo → Pyannote (diarization) → Postgres → Client
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let me break down the parts that actually matter for cost.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Whisper Large-v3 Turbo for the quality/speed sweet spot
&lt;/h3&gt;

&lt;p&gt;Whisper Large-v3 Turbo gives you near-Large-v3 accuracy at a fraction of the inference time. For a cost-sensitive pipeline, inference &lt;em&gt;speed&lt;/em&gt; is cost, because you're paying for GPU-seconds. Turbo dramatically cuts the seconds-per-audio-hour ratio.&lt;/p&gt;

&lt;p&gt;For faster throughput, I run it via &lt;code&gt;faster-whisper&lt;/code&gt; (CTranslate2 backend), which is significantly more memory- and compute-efficient than the reference implementation:&lt;br&gt;
&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;faster_whisper&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;WhisperModel&lt;/span&gt;

&lt;span class="c1"&gt;# int8_float16 keeps VRAM low while preserving accuracy
&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;WhisperModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;large-v3&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cuda&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;compute_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;int8_float16&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="n"&gt;segments&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;info&lt;/span&gt; &lt;span class="o"&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;transcribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;audio.wav&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;beam_size&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;vad_filter&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="c1"&gt;# skip silence, save compute
&lt;/span&gt;    &lt;span class="n"&gt;vad_parameters&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;min_silence_duration_ms&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;500&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;segment&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;segments&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;[&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;segment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; -&amp;gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;segment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;] &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;segment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two cheap wins here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;vad_filter=True&lt;/code&gt;&lt;/strong&gt; — voice activity detection skips silent regions entirely. Real-world recordings (meetings, calls) are full of dead air. This alone cut my compute meaningfully.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;int8_float16&lt;/code&gt; quantization&lt;/strong&gt; — lets you run Large-v3 on cheaper GPUs with less VRAM without a noticeable accuracy hit for most audio.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. SQS + queue-depth autoscaling
&lt;/h3&gt;

&lt;p&gt;The naive approach is one always-on GPU instance. That's the expensive approach — you pay for idle time.&lt;/p&gt;

&lt;p&gt;Instead, I decouple upload from processing with an &lt;strong&gt;SQS queue&lt;/strong&gt;. A lightweight Node.js controller polls the queue depth and scales the worker fleet based on backlog:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;SQSClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;GetQueueAttributesCommand&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@aws-sdk/client-sqs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;AutoScalingClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;SetDesiredCapacityCommand&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@aws-sdk/client-auto-scaling&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sqs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;SQSClient&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;region&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;us-east-1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;asg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;AutoScalingClient&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;region&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;us-east-1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;scaleWorkers&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Attributes&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;sqs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;GetQueueAttributesCommand&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;QueueUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;QUEUE_URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;AttributeNames&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ApproximateNumberOfMessages&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;backlog&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parseInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Attributes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ApproximateNumberOfMessages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// one worker per N queued jobs, capped to control spend&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;desired&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ceil&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;backlog&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="nx"&gt;MAX_WORKERS&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;asg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;SetDesiredCapacityCommand&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;AutoScalingGroupName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ASG_NAME&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;DesiredCapacity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;desired&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the queue is empty, the fleet scales to zero GPU workers. You pay for GPU time &lt;strong&gt;only when there's audio to process.&lt;/strong&gt; That's the single biggest structural cost lever.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Spot Instances (and surviving interruptions)
&lt;/h3&gt;

&lt;p&gt;GPU Spot Instances are 60–90% cheaper than on-demand. The catch: AWS can reclaim them with a 2-minute warning. For a stateless batch workload like transcription, this is a perfect fit — &lt;em&gt;if&lt;/em&gt; you handle interruptions gracefully.&lt;/p&gt;

&lt;p&gt;The worker listens for the Spot interruption notice and requeues its in-flight job so nothing is lost:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Poll the instance metadata endpoint for the interruption signal&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;checkForInterruption&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;http://169.254.169.254/latest/meta-data/spot/instance-action&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AbortSignal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&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;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// Reclaim imminent — put the current job back on the queue&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;requeueCurrentJob&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;gracefulShutdown&lt;/span&gt;&lt;span class="p"&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;catch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// 404 = not interrupted, carry on&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;setInterval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;checkForInterruption&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because jobs are idempotent and queue-backed, an interrupted job simply gets picked up by another worker. No lost work, no manual recovery. This resilience is what makes Spot viable for production, not just for hobby batch jobs.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Diarization with Pyannote
&lt;/h3&gt;

&lt;p&gt;Raw transcript is only half the value — knowing &lt;em&gt;who&lt;/em&gt; said &lt;em&gt;what&lt;/em&gt; is what turns a wall of text into usable meeting notes. Pyannote handles speaker diarization:&lt;br&gt;
&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;pyannote.audio&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Pipeline&lt;/span&gt;

&lt;span class="n"&gt;pipeline&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Pipeline&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_pretrained&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pyannote/speaker-diarization-3.1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;use_auth_token&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;HF_TOKEN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;diarization&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;pipeline&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;audio.wav&lt;/span&gt;&lt;span class="sh"&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;turn&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="n"&gt;speaker&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;diarization&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;itertracks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;yield_label&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="nf"&gt;print&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;speaker&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;turn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;s - &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;turn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;s&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You then align diarization segments with Whisper's timestamps to attribute each line to a speaker. It adds compute, but it's the difference between "a transcript" and "meeting intelligence."&lt;/p&gt;

&lt;h2&gt;
  
  
  The results
&lt;/h2&gt;

&lt;p&gt;Running this pipeline in production:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Processing cost: below $0.10/hour&lt;/strong&gt; of audio — beating the OpenAI Whisper API&lt;/li&gt;
&lt;li&gt;One stress test: &lt;strong&gt;216 hours of audio&lt;/strong&gt; (from 9 Raspberry Pi devices recording 24/7) processed for &lt;strong&gt;under $3 total&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Overall infrastructure cost reduction of &lt;strong&gt;~60%&lt;/strong&gt; versus the naive always-on / managed-API baseline&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The compounding effect of the four levers — Turbo model, VAD silence-skipping, scale-to-zero autoscaling, and Spot pricing — is multiplicative, not additive. Each one alone helps; stacked, they change the entire unit economics.&lt;/p&gt;

&lt;h2&gt;
  
  
  When you should NOT self-host
&lt;/h2&gt;

&lt;p&gt;To be fair, this isn't free lunch. Self-hosting made sense for me because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Volume was high enough that API fees dwarfed engineering time&lt;/li&gt;
&lt;li&gt;The workload is &lt;strong&gt;batch&lt;/strong&gt;, not real-time (Spot + queue latency is fine)&lt;/li&gt;
&lt;li&gt;I was comfortable owning AWS infra, GPU drivers, and model updates&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you're doing low volume, need sub-second live captioning, or don't want to babysit infrastructure, a managed API is genuinely the right call. The break-even point is usually somewhere in the low hundreds of hours per month.&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaways
&lt;/h2&gt;

&lt;p&gt;The arbitrage model — &lt;strong&gt;find what competitors overprice, then build it cheaper on primitives you control&lt;/strong&gt; — is very real in AI infrastructure right now. A huge amount of "AI product" pricing is just margin on top of GPU compute and open models.&lt;/p&gt;

&lt;p&gt;If you're processing meaningful audio volume, the stack that worked for me was:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;faster-whisper&lt;/code&gt; + Large-v3 Turbo with &lt;code&gt;int8_float16&lt;/code&gt; and VAD&lt;/li&gt;
&lt;li&gt;SQS queue-depth autoscaling that scales to zero&lt;/li&gt;
&lt;li&gt;GPU Spot Instances with interruption-safe, idempotent jobs&lt;/li&gt;
&lt;li&gt;Pyannote for diarization to add real product value&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I baked all of this into &lt;a href="https://www.strikescribe.com" rel="noopener noreferrer"&gt;StrikeScribe&lt;/a&gt; — upload audio or video, get a searchable transcript plus structured AI insights, no signup and no meeting bot required. If you want to see the output side of this pipeline in action, that's the easiest way.&lt;/p&gt;

&lt;p&gt;Happy to answer architecture questions in the comments — especially around Spot interruption handling and diarization alignment, which are the two things people usually get stuck on.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>architecture</category>
      <category>aws</category>
      <category>infrastructure</category>
    </item>
  </channel>
</rss>
