スタディサプリ Product Team Blog

株式会社リクルートが開発するスタディサプリのプロダクトチームのブログです

Argo Workflows で構築する、生成 AI 字幕生成ワークフロー

こんにちは、動画基盤チームの @kumackey です。 本記事では、Argo Workflows と生成 AI を組み合わせて構築した字幕生成ワークフローについて、その実装方法と技術的なポイントをご紹介します。

字幕生成について

背景

『スタディサプリ』では、2025年2月に講義動画における字幕再生機能を導入しています:

2025年2月のリリースでは計3講座のみを対象と絞っていましたが、対象講座を大幅に拡大することとなりました。 字幕生成の技術は既に確立しており、Whisper による文字起こしと GPT-4o による校正を組み合わせたスクリプトを、開発者の PC を用いて実行していました。

参考: 講義動画における生成 AI を活用した字幕生成

対象講座を拡大するにあたり、字幕生成をワークフロー化することで、字幕生成の効率化を狙いました。

実現したワークフロー

字幕生成ワークフローは、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 ノードについては後述します。

Whisper による音声認識処理

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 イメージで実行しています。

OCR モデルの 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 による校正) が実行されます。

字幕生成ワークフロー(DAG)

中間生成物管理

字幕生成ワークフローでは、動画ファイル、音声ファイル、生字幕ファイル、校正後の字幕ファイルなど、様々な中間生成物が生成されます。 生成 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

以下に図を示します。

Karpenter によるノードの動的起動

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 内で共有される一時ボリューム

以下に図を示します。

S3 initContainer による AI モデルの準備

まとめ

本記事では、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 共同創業者 代表取締役社長)※ご登壇順
===