AIP-C01 07: コスト・性能・オブザーバビリティ
Mix.install([
{:kino, "~> 0.19"},
{:kino_vega_lite, "~> 0.1"}
])
概要
最終確認日: 2026-09-21
対応範囲: 4.1〜4.3, 1.2, 2.2, 5.2
このノートで身につけること
- トークン、モデル、キャッシュ、バッチ、スループットのコストを比較する
- TTFT、end-to-end latency、throughput、error rate を区別する
- 技術メトリクスと品質・ビジネスメトリクスを結びつける
- invocation logging の価値と機密データリスクを評価する
最新仕様の要点
Bedrock Runtime は CloudWatch の AWS/Bedrock namespace に呼び出し数、レイテンシー、トークン、エラー、Prompt Cache などのメトリクスを出します。Model Invocation Logging は既定で無効で、設定すると request / response / metadata を CloudWatch Logs または S3 に送れます。全文ログは診断に有用ですが、PII や秘密情報、保持、アクセス制御、コストのリスクがあります。
Prompt Caching は長く繰り返す prefix の入力コストとレイテンシーを下げる候補です。Batch inference は非同期の大量処理、Provisioned Throughput は安定した高負荷、cross-Region inference は利用可能容量の拡大、Prompt Router は品質とコストの動的選択に使います。
ハンズオン1: 単価を変数としてコストを比較する
価格は変更されるため、現在の Bedrock Pricing から対象リージョン・モデルの「1,000 トークン当たり単価」を入力してください。
input_price = Kino.Input.number("入力 $ / 1K tokens", default: 0.0001, step: 0.0001)
output_price = Kino.Input.number("出力 $ / 1K tokens", default: 0.0005, step: 0.0001)
requests_input = Kino.Input.number("月間リクエスト数", default: 100_000)
avg_input = Kino.Input.number("平均入力 tokens", default: 800)
avg_output = Kino.Input.number("平均出力 tokens", default: 200)
Kino.Layout.grid([input_price, output_price, requests_input, avg_input, avg_output], columns: 3)
requests = Kino.Input.read(requests_input)
input_tokens = requests * Kino.Input.read(avg_input)
output_tokens = requests * Kino.Input.read(avg_output)
input_cost = input_tokens / 1_000 * Kino.Input.read(input_price)
output_cost = output_tokens / 1_000 * Kino.Input.read(output_price)
%{
monthly_input_tokens: input_tokens,
monthly_output_tokens: output_tokens,
input_cost_usd: Float.round(input_cost, 2),
output_cost_usd: Float.round(output_cost, 2),
total_usd: Float.round(input_cost + output_cost, 2)
}
ハンズオン2: 最適化シナリオ
baseline = %{requests: 100_000, input_tokens: 800, output_tokens: 200}
scenarios = [
%{name: "baseline", request_ratio: 1.0, input_ratio: 1.0, output_ratio: 1.0},
%{name: "prompt compression", request_ratio: 1.0, input_ratio: 0.7, output_ratio: 1.0},
%{name: "semantic cache 25% hit", request_ratio: 0.75, input_ratio: 1.0, output_ratio: 1.0},
%{name: "maxTokens tuning", request_ratio: 1.0, input_ratio: 1.0, output_ratio: 0.65},
%{name: "combined", request_ratio: 0.75, input_ratio: 0.7, output_ratio: 0.65}
]
unit_input = Kino.Input.read(input_price)
unit_output = Kino.Input.read(output_price)
cost_rows =
scenarios
|> Enum.map(fn scenario ->
request_count = baseline.requests * scenario.request_ratio
in_tokens = request_count * baseline.input_tokens * scenario.input_ratio
out_tokens = request_count * baseline.output_tokens * scenario.output_ratio
cost = in_tokens / 1_000 * unit_input + out_tokens / 1_000 * unit_output
%{scenario: scenario.name, input_tokens: trunc(in_tokens), output_tokens: trunc(out_tokens), cost_usd: Float.round(cost, 2)}
end)
cost_chart =
VegaLite.new(width: 680, height: 270, title: "最適化シナリオ別の月額推定")
|> VegaLite.data_from_values(cost_rows)
|> VegaLite.mark(:bar, tooltip: true)
|> VegaLite.encode_field(:y, "scenario", type: :nominal, title: "シナリオ", sort: "-x")
|> VegaLite.encode_field(:x, "cost_usd", type: :quantitative, title: "月額推定 (USD)")
|> Kino.VegaLite.new()
Kino.Layout.tabs([
{"コスト比較", cost_chart},
{"トークン内訳", Kino.DataTable.new(cost_rows)}
])
キャッシュは「同じ意味なら過去回答を返す」ため、権限、tenant、鮮度、モデル/prompt version、Guardrail version を cache key と invalidation に含めます。コスト削減だけで品質事故を起こさないようにします。
ハンズオン3: SLO とアラーム判定
slo = %{
success_rate: 0.995,
p95_latency_ms: 3_000,
grounded_rate: 0.97,
cost_per_success_usd: 0.01
}
window = %{
requests: 20_000,
successes: 19_860,
p95_latency_ms: 2_850,
grounded_responses: 19_100,
evaluated_responses: 19_500,
cost_usd: 170.0
}
observed = %{
success_rate: window.successes / window.requests,
p95_latency_ms: window.p95_latency_ms,
grounded_rate: window.grounded_responses / window.evaluated_responses,
cost_per_success_usd: window.cost_usd / window.successes
}
Enum.map(observed, fn {metric, value} ->
target = Map.fetch!(slo, metric)
direction = if metric in [:p95_latency_ms, :cost_per_success_usd], do: :max, else: :min
pass = if direction == :max, do: value <= target, else: value >= target
%{metric: metric, observed: value, target: target, pass: pass}
end)
|> Kino.DataTable.new()
成功率だけでは「高速に誤答する」サービスを見逃します。運用、品質、安全、コスト、ビジネス成果の指標を並べます。
ハンズオン4: invocation log を安全に要約する
log_record = %{
request_id: "req-123",
model_id: "example-model",
input_tokens: 820,
output_tokens: 190,
latency_ms: 2_340,
status: 200,
prompt: "顧客 taro@example.com の契約を要約して",
response: "taro@example.com の契約は..."
}
safe_log =
log_record
|> Map.drop([:prompt, :response])
|> Map.put(:content_logged, false)
|> Map.put(:prompt_fingerprint, :crypto.hash(:sha256, log_record.prompt) |> Base.encode16(case: :lower))
safe_log
全文が必要な調査環境では、別ロググループ、KMS、厳格な IAM、短い保持、マスキング、監査を使います。CloudTrail は API 操作の監査、CloudWatch はメトリクス/ログ/アラーム、X-Ray はサービス境界のトレースという役割を区別します。
監視する代表指標
| 分類 | 指標例 |
|---|---|
| 利用 | Invocations、input/output/cache tokens |
| 信頼性 | throttles、4xx/5xx、fallback rate、queue depth |
| 性能 | TTFT、p50/p95/p99 latency、tokens/sec |
| RAG | retrieval latency、Recall@k、empty retrieval、rerank gain |
| 品質・安全 | grounded rate、hallucination rate、Guardrail intervention、schema failure |
| Agent | task completion、tool error、steps、loop termination、approval rate |
| ビジネス | containment、conversion、time saved、cost per successful task |
判断問題
- オンデマンドから Provisioned Throughput に切り替える前に、何を測定しますか。
- Prompt Caching と semantic response cache の違いを説明してください。
totalTokensが一定でもコストやレイテンシーが変わる理由を挙げてください。- invocation logging を有効化しない判断が妥当になる条件は何ですか。