こんにちは、動画基盤チームの @kumackey です。 本記事では、Argo Workflows と生成 AI を組み合わせて構築した字幕生成ワークフローについて、その実装方法と技術的なポイントをご紹介します。
字幕生成について
背景
『スタディサプリ』では、2025年2月に講義動画における字幕再生機能を導入しています:
- 『スタディサプリ高校講座』、講義動画に字幕表示 音声文字起こし生成AI活用で、聴覚障がい者の方々も利用しやすく まずは「数学Ⅰ/A」「情報Ⅰ」で
- 再エンコード無しで、HLS動画への字幕の追加を実現する
2025年2月のリリースでは計3講座のみを対象と絞っていましたが、対象講座を大幅に拡大することとなりました。 字幕生成の技術は既に確立しており、Whisper による文字起こしと GPT-4o による校正を組み合わせたスクリプトを、開発者の PC を用いて実行していました。
対象講座を拡大するにあたり、字幕生成をワークフロー化することで、字幕生成の効率化を狙いました。
実現したワークフロー
字幕生成ワークフローは、3つの主要なタスクで構成されています:
- 生字幕生成: 動画の音声を入力として、Whisper により文字起こしします。
- 校正データ生成: 後の校正に使うための LaTeX 形式のデータを、OCR モデルにより出力します。
- 校正: 文字起こししたデータを Gemini により校正します。
それぞれのタスクでは生成 AI ツールを活用しており、順に説明します。

Whisper (生字幕生成)
Whisper は、OpenAI が開発した音声認識モデルです。 多言語対応で高精度な文字起こしを実現し、特に日本語の認識精度が比較的高いことで知られています。
今回のユースケースでは、講義動画の音声から生字幕 (WebVTT 形式) を生成するために、Whisper large-v3-turbo モデルを使用しています。 動画から抽出した音声ファイルを Whisper で処理し、タイムスタンプ付きの字幕データを生成します。
以下は生成された字幕の一例です。 ただし、文字起こししただけだと、数式がうまく表現できてないことが分かります。
01:13.980 --> 01:28.980 例えば、3x4乗の係数。さあ、これもですね、確かに3とxが4個掛け合わされております。さあ、これの係数は数の部分ですので、3となりますし、 01:28.980 --> 01:43.980 先ほどの例で言うと、4となります。例えば、xですと、これは前に数字1が隠されていますので、係数は1という風になるのでした。さあ、続きまして、次数とは。 01:43.980 --> 01:54.760 この単項式の中で、掛け合わされた文字の個数でございます。文字の個数というところを注目をお願いします。 01:55.980 --> 02:05.700 例えば、例。マイナス4x2乗、y3乗。紛れもなく単項式です。さあ、これをバラバラに書くと、このような状態になっています。
Whisper は GPU ノード上での実行により高速化されるため、NVIDIA CUDA ランタイムイメージをベースにした Docker イメージで実行しています。 GPU ノードについては後述します。

OCR モデル (校正データ生成)
講義動画に表示される PDF 教材には数式や記号が多く含まれており、これらを正確に認識することが字幕の品質向上に不可欠です。 OCR モデルを使用して、PDF 教材から LaTeX 形式の数式を含むテキストデータを抽出し、後の校正処理で使用します。
以下は OCR モデルによって抽出されたデータの一例です。 数式が LaTeX 形式で正確に表現されています。
# 第 1 講 式の計算と展開 # PART1 単項式と多項式 # 要点 ① 単項式 「単項式」… 2, $x$ , $4 a ^ { 2 } b$ のように,数,文字,および数や文字を掛け合わせて作られる式「係数」… 単項式の の部分 # $3 x ^ { 4 }$ の係数は 「次数」… 単項式 $\oslash$ 中で,掛け合わせた文字の $- 4 x ^ { 2 } y ^ { 3 } = - 4 \times x \times x \times y \times y \times y$ よ $\mathfrak { V }$ , $- 4 x ^ { 2 } y ^ { 3 }$ の次数は 2など,数だけの単項式 $\oslash$ 次数は0である。ただし,数 $0 \textcircled { / 2 }$ 次数は考えない(0だけの項は,通常は書かないため)。
Whisper 同様、OCR モデルは GPU ノード上での実行により高速化されるため、NVIDIA CUDA ランタイムイメージをベースにした Docker イメージで実行しています。

Gemini (校正)
Gemini は、Google が開発している大規模言語モデルで、Google Cloud の Generative AI の API 経由で利用できます。 前述の校正データおよび生字幕を入力とし、校正するようにプロンプトを構築して LLM にリクエストします。
以下は校正された字幕の一例です。 数式が校正データを元に修正されていることが分かるかと思います。
00:01:13.980 --> 00:01:28.980 例えば、3x⁴ の係数 さあ、これもですね、確かに3とxが4個掛け合わされております さあ、これの係数は数の部分ですので、3となりますし 00:01:28.980 --> 00:01:43.979 先ほどの例で言うと、4となります 例えば、xですと、これは前に数字1が隠されていますので、係数は1という風になるのでした さあ、続きまして、次数とは 00:01:43.979 --> 00:01:54.759 この単項式の中で、掛け合わされた文字の個数でございます 文字の個数というところを注目をお願いします 00:01:55.979 --> 00:02:05.700 例えば、例 -4x²y³ 紛れもなく単項式です さあ、これをバラバラに書くと、このような状態になっています
生成 AI ワークフロー基盤としての Argo Workflows
Argo Workflows とは
Argo Workflows とは、Kubernetes 上で並列ジョブを調整するためのオープンソースのワークフローエンジンです。 Kubernetes CRD として実装されており、各タスクがコンテナとして動作します。 『スタディサプリ』ではジョブ基盤として Argo Workflows を利用しており、今回の字幕生成ワークフローも Argo Workflows で実装しています。
参考: JenkinsからArgo Workflowsへの移行話
Argo Workflows は、生成 AI ワークフローを構築する上でも有用な機能を提供しています。 本記事では、今回の字幕生成ワークフローで活用した機能を紹介します。
DAG
字幕生成ワークフローでは、複数のタスクの並列実行およびその同期が必要となる場面があります。 DAG(有向非巡環グラフ)は、タスク間の依存関係を明示的に指定してワークフローを定義する機能で、並列処理と同期を簡単に記述できます。
- name: main dag: tasks: - name: generate-proofreading-data # 校正データ生成 template: generate-proofreading-data - name: generate-raw-subtitles # 生字幕生成 template: generate-raw-subtitles - name: proofread-subtitles # 校正 # 並列処理している2つのタスクが成功したら、校正に進む depends: "generate-proofreading-data.Succeeded && generate-raw-subtitles.Succeeded" template: proofread-subtitles
この記述により、generate-proofreading-data (OCR モデルによる校正データ生成) と generate-raw-subtitles (Whisper による生字幕生成) が並列実行されます。
両方が完了した後に proofread-subtitles (Gemini による校正) が実行されます。

中間生成物管理
字幕生成ワークフローでは、動画ファイル、音声ファイル、生字幕ファイル、校正後の字幕ファイルなど、様々な中間生成物が生成されます。 生成 AI による出力は非決定的で不安定であるため、生成物を逐一確認できることは重要です。
Artifacts 機能により、各タスクの中間生成物を S3 に自動的に保存できます。
- name: main dag: tasks: - name: download-video # 講義動画のダウンロード template: download-video - name: extract-audio # 音声のみの抽出 template: extract-audio depends: "download-video.Succeeded" arguments: artifacts: # download-video の出力したファイルを利用 - name: video-file from: "{{tasks.download-video.outputs.artifacts.video-file}}" - name: generate-raw-subtitle # 生字幕生成 template: generate-raw-subtitle depends: "extract-audio.Succeeded" arguments: artifacts: # extract-audio の出力したファイルを利用 - name: audio-file from: "{{tasks.extract-audio.outputs.artifacts.audio-file}}" - name: proofread-subtitle # 校正 template: proofread-subtitle depends: "generate-raw-subtitle.Succeeded" arguments: artifacts: # generate-raw-subtitle の出力したファイルを利用 - name: raw-subtitle from: "{{tasks.generate-raw-subtitle.outputs.artifacts.raw-subtitle}}"
ワークフロー実行後、中間生成物は Amazon S3 に保存され、Argo Workflows の GUI からもアクセス・ダウンロードできるようになります。動作確認やデバッグが容易となりました。 以下の図に、各タスクでの中間生成物の Amazon S3 への保存を示します。

Loops
『スタディサプリ』では1つの講座には複数の動画が含まれています。 講座に含まれる複数の動画に対して、その動画ごとにタスクを並列実行したいです。
withParam を使って動的なループ処理を実現できます。
前のタスクで生成された JSON 配列を受け取り、その要素ごとにタスクを並列実行できます。
- name: main dag: tasks: - name: extract-videos # 動画一覧を取得 template: extract-videos - name: task-by-video # 各動画に対して繰り返し処理 depends: "extract-videos.Succeeded" template: task-by-video arguments: parameters: - name: video-id value: "{{item}}" withParam: "{{tasks.extract-videos.outputs.parameters.video-ids}}"
extract-videos のタスクで生成された動画 ID 一覧の JSON 配列(例: ["video1", "video2", "video3"])を受け取り、各動画に対して task-by-video テンプレートが並列実行されます。

並列数制限
Gemini といった LLM API にはレートリミットが設定されていることが多く、無制限に並列実行するとレートリミットにひっかかりがちです。 また、GPU ノードの同時に立ち上がるインスタンス数に制限があることなどもあります。 Synchronization を利用することで並列実行数を制限し、過度な並列実行を抑制することができます。
spec: synchronization: semaphores: - configMapKeyRef: name: my-config key: workflow
ConfigMap で並列数の上限を設定します。
apiVersion: v1 kind: ConfigMap metadata: name: my-config data: workflow: "2" # 最大2つまでなら並列実行できる
以下に図を示します。

GPU の活用
Whisper などのモデルは GPU を使うことで高速に処理できます。 Argo Workflows で GPU ノードを活用する方法などを紹介します。
GPU ノードの指定
『スタディサプリ』では、Karpenter を活用してノードを動的に起動しています。
参考: KarpenterでEKSクラスタのオートスケーリングを実現する
Karpenter は AWS が開発したオープンソースのツールで、Pod のリソース要求に基づいて適切なサイズのノードを自動的に起動してくれる仕組みです。 Karpenter では NodePool という設定リソースを使って、プロビジョニングするノードの要件を定義します。 今回のケースでは、GPU ノード専用の NodePool を以下のように定義しています。
apiVersion: karpenter.sh/v1 kind: NodePool metadata: name: my-gpu-nodepool spec: template: spec: requirements: - key: karpenter.k8s.aws/instance-gpu-manufacturer operator: In values: - nvidia # NVIDIA GPU を搭載したインスタンスを指定 - key: karpenter.sh/capacity-type operator: In values: - spot - on-demand # Spot と On-Demand の両方を許可 taints: - effect: NoSchedule key: quipper.dev/karpenter value: my-gpu-nodepool
ワークフロー側では、NodePool を指定するだけで、Karpenter が必要に応じて GPU インスタンスを自動的に起動してくれます。 例えば以下のように記述することで、G4 の g4dn.xlarge インスタンス上で実行されます。
- name: generate-raw-subtitles-by-whisper container: image: my-whisper-image affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: karpenter.sh/nodepool operator: In values: - my-gpu-nodepool # NodePool を指定 - key: node.kubernetes.io/instance-type operator: In values: - g4dn.xlarge # インスタンスタイプを指定 tolerations: - key: quipper.dev/karpenter operator: Equal value: my-gpu-nodepool effect: NoSchedule
以下に図を示します。

Spot Instance と RetryStrategy
GPU ノードは比較的高価なため、コスト削減のために Spot Instance を活用しています。 しかし Spot Instance は Spot Instance interruptions により突然終了することがあります。
RetryStrategy を設定することで、Spot Instance の中断が発生しても自動的にリトライされ、ワークフローの成功率を維持できました。 生成 AI の処理は失敗しても結果を捨てれば良いだけであり、すわなち冪等性を持っているためリトライしても問題はありません。
retryStrategy: retryPolicy: Always limit: "2" # 2回リトライされる
生成 AI モデルの転送
Whisper のような大規模モデルを使用する場合、モデルの容量・ダウンロードに注意する必要があります。 Whisper の場合、モデルだけで 3 GB 程度あり、この容量を毎回 NAT Gateway 経由で外部からダウンロードするのは大きなコストとなります。 そこで、モデルは S3 に置き、VPC エンドポイント 経由でダウンロードすることで、AWS 内部のネットワークのみを利用し効率的に転送できます。 S3 の転送コストは ゲートウェイ型の VPC エンドポイントであれば無料です。
initContainers を使って、メインコンテナの起動前にモデルをダウンロードします。 emptyDir ボリュームを共有することで、ダウンロードしたモデルをメインコンテナで利用できます。
initContainers: # メインコンテナの起動前 - name: download-whisper-models image: amazon/aws-cli:latest command: [sh, -c] args: - | mkdir -p /whisper_models/ aws s3 sync s3://my-ai-model-bucket/whisper_models/ /whisper_models/ volumeMounts: - name: whisper-models-volume mountPath: /whisper_models/ container: image: my-whisper-image command: [whisper] args: ["/input/audio.mp3", "--model_dir", "/whisper_models"] volumeMounts: - name: whisper-models-volume # initContainers と同じボリュームをマウント mountPath: /whisper_models/ volumes: - name: whisper-models-volume emptyDir: {} # 同じ Pod 内で共有される一時ボリューム
以下に図を示します。

まとめ
本記事では、Argo Workflows を活用した生成 AI 字幕生成ワークフローについて紹介しました。
- OCR モデル、Whisper、Gemini を組み合わせることで、校正データの生成、音声からの字幕生成、そして校正という3段階のプロセスを実現しました。
- DAG による並列処理と同期、Loops による動的なループ処理、Synchronization による並列数制限など、Argo Workflows の豊富な機能により、複雑なワークフローを簡潔に記述できました。
- Karpenter による自動ノード起動、Spot Instance によるコスト削減、Amazon S3 と VPC エンドポイント経由のモデル転送など、複数の工夫により GPU リソースとAIモデルの管理を効率化しました。
このワークフローにより、『スタディサプリ』の講義動画に対して、効率的に字幕を生成できるようになりました。
【お知らせ】RECRUIT TECH CONFERENCE 2026を開催します!(オンライン配信/参加無料)
リクルート主催の技術カンファレンス。第3回目となる今回は「AI×プロダクト開発」をテーマに、急速な技術進化の中で生まれた多様な領域のナレッジから、技術者の活躍を引き出す土壌づくりまで、豊富なセッションをお届けします。是非お気軽にご参加ください!
▼お申し込みはこちらから
https://recruit-event.connpass.com/event/371908/
===
RECRUIT TECH CONFERENCE 2026
・開催日時
2026年2月27日(金) 12:00~19:30 (オンライン配信/途中入退場自由)
・社外ゲスト
和田 卓人 氏(タワーズ・クエスト株式会社 取締役社長)/岡野原 大輔 氏(株式会社Preferred Networks 共同創業者 代表取締役社長)※ご登壇順
===