ようやく試せた Amazon Bedrock の Latency Optimize 

## tl;dr

latency optimize は実験の条件下(※)において

input  : 176 tokens/msec -> 301 tokens/msec (x1.71 高速)
output :  58 tokens/msec -> 109 tokens/msec (x1.88 高速)

という高速化を実現しています。

(※input token 109, output token 301, Claude 3.5 haiku)

## 今年も終わりですね。

仕事が一段落したので、ようやく少し遊ぶ時間が出来ました。

Latency optimizeが出たので、試してみます。

https://aws.amazon.com/jp/about-aws/whats-new/2024/12/latency-optimized-inference-foundation-models-amazon-bedrock/


## Latency Optimize？
Latency Optimize はいろいろ最適化して速くなったよ！以上のことがブログには書かれていません。

re:Invent のキーノートで発表が合った時は Trainium(trn2)で推論することで速くなったよ、という解説がありました。
私見ではありますが、GPU は Graphic Processing Unit であり、機械学習専用に作ったコンピューティングリソースを利用すればそりゃ速くなるし、推論はトレーニングからバックプロパゲーションだけを抜いた行為なのだからその高速化された環境でやるのもそりゃ速いですよね、というところです。

https://youtu.be/vx36tyJ47ps?si=FkuzRHUf34o9pIWl&t=3917

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/504112/b07b4f85-c705-ed4f-b43a-f700ea7920ee.png)

そして claude 3.5 haiku も対応しているよ、とのことです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/504112/ba9ad601-6cb8-d633-9097-644a4464bad0.png)


## Latency Optimize を試してみよう

さて、Latency Optimize を試してみましょう。

[ドキュメント](https://docs.aws.amazon.com/bedrock/latest/userguide/latency-optimized-inference.html)によると、Claude 3.5 haiku と Llama 3.1 70b/405b で以下の引数を設定すれば使えるよ、とのことです。

```python
"performanceConfig" : {
    "latency" : "optimized"
}
```

また注意として以下があります。

>Latency optimized inference is available for Meta’s Llama 3.1 70B and 405B, as well as Anthropic’s Claude 3.5 Haiku in the US East (Ohio) Region via cross-region inference. For more information about pricing, visit the pricing page.

:::note info
Latency optimized inference for Llama 3.1 405B currently supports requests with total input and output token count up to 11K. For larger token count requests, we will fall back to the standard mode.
:::

* Ohio でクロスリージョンしなさい
* 入出力トークンの上限(11k)があるよ

とのことです。

## まずは標準で時間計測

早速やってみましょう。

今回は boto3 から [converse](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/bedrock-runtime/client/converse.html) API を使ってみることとします。

```python
import boto3

brt = boto3.client( # Ohio を指定しましょう
    service_name='bedrock-runtime',
    region_name='us-east-2'
)

model_id = 'us.anthropic.claude-3-5-haiku-20241022-v1:0'
prompt = '''あなたは大学受験生です。
問題として円周率が 3.05 以上であることの証明を求められました。
円に内接する正八角形の面積を求めることで円周率が 3.05 以上であることを実際に計算して証明してください。
私は TeX の表記を読むことができるので TeX で表現してください。'''
```

### 301 tokens 出力

速度差を出すために出力を長くなるようなプロンプトを用意してみました。(2003年の東大の問題)
使用するモデルは 3.5 haiku sonnet です。
このプロンプトのレスポンスはだいたい 400-500 output tokens でした。今回の目的は出力内容はどうでもいいので、 maxTokens を 301 で cut off して 3 回計測します。
そして、まずは Latency Optimize されていない(=たぶん GPU)状態で計測してみます。

```python
response = brt.converse(
    modelId = model_id,
    messages = [
        {
            'role':'user',
            'content':[
                {'text':prompt}
            ]
        }
    ],
    inferenceConfig={
        'maxTokens':301,
        'temperature':0,
    }
)
print(response)
```
1 回目 5641 msec
```text:出力
{(前略)
 'output': {'message': {'role': 'assistant',
   'content': [{'text': '以下に、円周率が3.05以上であることを正八角形の面積を用いて証明します。\n\n証明:\n\n1) 半径 $r = 1$ の円を考える。\n\n2) 正八角形の内接円の面積を計算する。\n\n3) 正八角形の各頂点の座標は以下のように表せる：\n   $(\\cos(\\frac{k\\pi}{4}), \\sin(\\frac{k\\pi}{4}))$ where $k = 0, 1, 2, ..., 7$\n\n4) 正八角形の面積 $A_8$ は：\n   $A_8 = 2r^2 \\sin(\\frac{\\pi}{4}) \\cdot 8 = 2 \\sin(\\frac{\\pi}{4}) = 2 \\cdot \\frac{\\sqrt{2}}{2} = \\sqrt{2}$\n\n5) 円の面積は $\\pi r^2 = \\pi$\n\n6) $\\frac{A_8}{\\pi} = \\frac{\\sqrt{2}}{\\pi} \\approx 0.9$\n\n7) したがって、$\\pi'}]}},
 'stopReason': 'max_tokens',
 'usage': {'inputTokens': 109, 'outputTokens': 301, 'totalTokens': 410},
 'metrics': {'latencyMs': 5641}}
```
2 回目 5613 msec
```text:出力
{(前略)
 'output': {'message': {'role': 'assistant',
   'content': [{'text': '以下に、円周率が3.05以上であることを正八角形の面積を用いて証明します。\n\n証明:\n\n1) 半径 $r = 1$ の円を考える。\n\n2) 正八角形の内接円の面積を計算する。\n\n3) 正八角形の各頂点の座標は以下のように表せる：\n   $(\\cos(\\frac{k\\pi}{4}), \\sin(\\frac{k\\pi}{4}))$ where $k = 0, 1, 2, ..., 7$\n\n4) 正八角形の面積 $A_8$ は：\n   $A_8 = 2r^2 \\sin(\\frac{\\pi}{4}) \\cdot 8 = 2 \\sin(\\frac{\\pi}{4}) = 2 \\cdot \\frac{\\sqrt{2}}{2} = \\sqrt{2}$\n\n5) 円の面積は $\\pi r^2 = \\pi$\n\n6) $\\frac{A_8}{\\pi} = \\frac{\\sqrt{2}}{\\pi} \\approx 0.9$\n\n7) したがって、$\\pi'}]}},
 'stopReason': 'max_tokens',
 'usage': {'inputTokens': 109, 'outputTokens': 301, 'totalTokens': 410},
 'metrics': {'latencyMs': 5613}}
```
3 回目 5961 msec
```text:出力
{(前略)
 'output': {'message': {'role': 'assistant',
   'content': 
 'output': {'message': {'role': 'assistant',
   'content': [{'text': '以下に、円周率が3.05以上であることを正八角形の面積を用いて証明します。\n\n証明:\n\n1) 半径 $r = 1$ の円を考える。\n\n2) 正八角形の内接円の面積を計算する。\n\n3) 正八角形の各頂点の座標は以下のように表せる：\n   $(\\cos(\\frac{k\\pi}{4}), \\sin(\\frac{k\\pi}{4}))$ where $k = 0, 1, 2, ..., 7$\n\n4) 正八角形の面積 $A_8$ は：\n   $A_8 = 2r^2 \\sin(\\frac{\\pi}{4}) \\cdot 8 = 2 \\sin(\\frac{\\pi}{4}) = 2 \\cdot \\frac{\\sqrt{2}}{2} = \\sqrt{2}$\n\n5) 円の面積は $\\pi r^2 = \\pi$\n\n6) $\\frac{A_8}{\\pi} = \\frac{\\sqrt{2}}{\\pi} \\approx 0.9$\n\n7) したがって、$\\pi'}]}},
 'stopReason': 'max_tokens',
 'usage': {'inputTokens': 109, 'outputTokens': 301, 'totalTokens': 410},
 'metrics': {'latencyMs': 5961}}
```

latencyMs は 5641 msec, 5613 msec, 5961 msec でした。
これは API 側が出力しているメトリクスなので、リクエストを受け取ってから返すまでの時間のはずです。

### 1 tokens 出力

ここに対して、`maxTokens:1` の LatencyMs を計測することで、ほぼ GPU (だと思われる) に token を載せる時間を得て、301 token 出力するのにかかった時間から引き算することで、output token の時間を計測できるはずです。同様に 3 回計測してみましょう。


```python
response = brt.converse(
    modelId = model_id,
    messages = [
        {
            'role':'user',
            'content':[
                {'text':prompt}
            ]
        }
    ],
    inferenceConfig={
        'maxTokens':1,
        'temperature':1,
    }
)
```

1 回目 730 msec
```text:出力
{(前略)
 'output': {'message': {'role': 'assistant',
   'content': 
 'output': {'message': {'role': 'assistant', 'content': []}},
 'stopReason': 'max_tokens',
 'usage': {'inputTokens': 109, 'outputTokens': 1, 'totalTokens': 110},
 'metrics': {'latencyMs': 730}}
```

2 回目 514 msec
```text:出力
{(前略)
 'output': {'message': {'role': 'assistant',
   'content': 
 'output': {'message': {'role': 'assistant', 'content': []}},
 'stopReason': 'max_tokens',
 'usage': {'inputTokens': 109, 'outputTokens': 1, 'totalTokens': 110},
 'metrics': {'latencyMs': 514}}
```

3 回目 621 msec
```text:出力
{(前略)
 'output': {'message': {'role': 'assistant',
   'content': 
 'output': {'message': {'role': 'assistant', 'content': []}},
 'stopReason': 'max_tokens',
 'usage': {'inputTokens': 109, 'outputTokens': 1, 'totalTokens': 110},
 'metrics': {'latencyMs': 621}}
```

たった 3 回の計測ですが、雑に平均を取ってから計算してみましょう。

```
    109 tokens req して 301 token を得るまでの平均時間 : (5641+5613+5961)/3 = 5738 msec

-)  109 tokens req して   1 token を得るまでの平均時間 : (730+514+621)/3    =  622 msec
--------------------------------------------------------------------------------------
                                                                          5116 msec
109 /  622 * 1000 = 176 tokens/sec(input)
300 / 5116 * 1000 =  58 tokens/sec(output)
```

input に 176 tokens/sec, output に 58 tokens/sec かかりました。（条件や token 長さによって変わることに注意)

## Latency Optimize で試す

同じことを latency Optimize でやってみましょう。

### 301 tokens 出力

converse API で引数に `performanceConfig={'latency': 'optimized'}` を設定するだけです。簡単ですね。

```python
response = brt.converse(
    modelId = model_id,
    messages = [
        {
            'role':'user',
            'content':[
                {'text':prompt}
            ]
        }
    ],
    inferenceConfig={
        'maxTokens':301,
        'temperature':0,
    },
    performanceConfig={
        'latency': 'optimized'
    }
)
print(response)
```

1 回目 : 3184 msec
```text:出力
{(前略)
 'output': {'message': {'role': 'assistant',
   'content': [{'text': '以下に、円周率が3.05以上であることを正八角形の面積を用いて証明します。\n\n\\begin{proof}\n半径 $r = 1$ の円を考える。\n\n正八角形の内接円の面積を計算する手順：\n\n1. 正八角形の一辺の長さ $a$ を求める\n   $a = 2r \\sin(\\frac{\\pi}{8}) = 2 \\sin(\\frac{\\pi}{8})$\n\n2. 正八角形の面積 $S$ を計算\n   $S = 8 \\cdot \\frac{1}{2} a^2 \\cot(\\frac{\\pi}{8})$\n\n3. 計算\n   $S = 8 \\cdot \\frac{1}{2} (2 \\sin(\\frac{\\pi}{8}))^2 \\cot(\\frac{\\pi}{8})$\n   $= 8 \\sin^2(\\frac{\\pi}{8}) \\cot(\\frac{\\pi}{8})$\n\n4. 数値計算\n   $S \\approx 3.0615$\n\n5. 円の面積 $\\pi r^'}]}},
 'stopReason': 'max_tokens',
 'usage': {'inputTokens': 109, 'outputTokens': 301, 'totalTokens': 410},
 'metrics': {'latencyMs': 3184},
 'performanceConfig': {'latency': 'optimized'}}
```

2 回目 : 3129 msec
```text:出力
{(前略)
 'output': {'message': {'role': 'assistant',
   'content': [{'text': '以下に、円周率が3.05以上であることを正八角形の面積を用いて証明します。\n\n\\begin{proof}\n半径 $r = 1$ の円を考える。\n\n正八角形の内接円の面積を計算する手順：\n\n1. 正八角形の一辺の長さ $a$ を求める\n   $a = 2r \\sin(\\frac{\\pi}{8}) = 2 \\sin(\\frac{\\pi}{8})$\n\n2. 正八角形の面積 $S$ を計算\n   $S = 8 \\cdot \\frac{1}{2} a^2 \\cot(\\frac{\\pi}{8})$\n\n3. 計算\n   $S = 8 \\cdot \\frac{1}{2} (2 \\sin(\\frac{\\pi}{8}))^2 \\cot(\\frac{\\pi}{8})$\n   $= 8 \\sin^2(\\frac{\\pi}{8}) \\cot(\\frac{\\pi}{8})$\n\n4. 数値計算\n   $S \\approx 3.0615$\n\n5. 円の面積 $\\pi r^'}]}},
 'stopReason': 'max_tokens',
 'usage': {'inputTokens': 109, 'outputTokens': 301, 'totalTokens': 410},
 'metrics': {'latencyMs': 3129},
 'performanceConfig': {'latency': 'optimized'}}
```

3 回目 : 3035 msec
```text:出力
{(前略)
 'output': {'message': {'role': 'assistant',
   'content': [{'text': '以下に、円周率が3.05以上であることを正八角形の面積を用いて証明します。\n\n\\begin{proof}\n半径 $r = 1$ の円を考える。\n\n正八角形の内接円の面積を計算する手順：\n\n1. 正八角形の一辺の長さ $a$ を求める\n   $a = 2r \\sin(\\frac{\\pi}{8}) = 2 \\sin(\\frac{\\pi}{8})$\n\n2. 正八角形の面積 $S$ を計算\n   $S = 8 \\cdot \\frac{1}{2} a^2 \\cot(\\frac{\\pi}{8})$\n\n3. 計算\n   $S = 8 \\cdot \\frac{1}{2} (2 \\sin(\\frac{\\pi}{8}))^2 \\cot(\\frac{\\pi}{8})$\n   $= 8 \\sin^2(\\frac{\\pi}{8}) \\cot(\\frac{\\pi}{8})$\n\n4. 数値計算\n   $S \\approx 3.0615$\n\n5. 円の面積 $\\pi r^'}]}},
 'stopReason': 'max_tokens',
 'usage': {'inputTokens': 109, 'outputTokens': 301, 'totalTokens': 410},
 'metrics': {'latencyMs': 3035},
 'performanceConfig': {'latency': 'optimized'}}
 ```

 5 秒後半だったのが 3 秒前半 と 2.5 秒くらい速くなりましたね。

### 1 tokens 出力
 
 先ほど同様、maxTokens を 1 にして input の計測をしてみましょう。

 ```python
 response = brt.converse(
    modelId = model_id,
    messages = [
        {
            'role':'user',
            'content':[
                {'text':prompt}
            ]
        }
    ],
    inferenceConfig={
        'maxTokens':1,
        'temperature':0,
    },
    performanceConfig={
        'latency': 'optimized'
    }
)
```

1 回目: 312 msec
```text:出力
{(前略)
 'output': {'message': {'role': 'assistant', 'content': []}},
 'stopReason': 'max_tokens',
 'usage': {'inputTokens': 109, 'outputTokens': 1, 'totalTokens': 110},
 'metrics': {'latencyMs': 312},
 'performanceConfig': {'latency': 'optimized'}}
```

2 回目 : 362 msec
```text:出力
{(前略)
 'output': {'message': {'role': 'assistant', 'content': []}},
 'stopReason': 'max_tokens',
 'usage': {'inputTokens': 109, 'outputTokens': 1, 'totalTokens': 110},
 'metrics': {'latencyMs': 362},
 'performanceConfig': {'latency': 'optimized'}}
```

3 回目 : 411 msec
```text:出力
{(前略)
 'output': {'message': {'role': 'assistant', 'content': []}},
 'stopReason': 'max_tokens',
 'usage': {'inputTokens': 109, 'outputTokens': 1, 'totalTokens': 110},
 'metrics': {'latencyMs': 411},
 'performanceConfig': {'latency': 'optimized'}}
```


```
    109 tokens req して 301 token を得るまでの平均時間 : (3184+3129+3035)/3 = 3116 msec

-)  109 tokens req して   1 token を得るまでの平均時間 : (312+362+411)/3    =  362 msec
--------------------------------------------------------------------------------------
                                                                          2754 msec
109 /  362 * 1000 = 301 tokens/sec(input)
300 / 2754 * 1000 = 109 tokens/sec(output)
```

input  : 176 tokens/msec -> 301 (x1.71 高速)
output :  58 tokens/msec -> 109 (x1.88 高速)

in/out 共に 1.7 倍以上高速化していますね。また出力を全部確認したわけではないですが、書き出しを見る限り latency optimize も標準と変わっていません。精度を維持したまま高速化したい、という要望にはお金で解決する、というオプションが加わったと考えることができます。

## おわりに
Amazon Bedrock はモデルだけじゃなく周辺の機能(Agents, KnowlesgeBases, Guardrails, etc...)が豊富でエンタープライズ用途で使いやすいところが売りでしたが、そこに新しいオプションが加わって嬉しい！（小並感）
1.25 倍のお金を払うと 1.8 倍くらい速いよ、というのは UX を売りにしているサービスだと嬉しいケースがありそうです。