|
import gradio as gr |
|
import plotly.graph_objects as go |
|
import pandas as pd |
|
import numpy as np |
|
|
|
def calculate_runpod_cost_revised( |
|
requests_per_hour, |
|
execution_time_per_request, |
|
active_cost_per_second, |
|
flex_cost_per_second, |
|
cold_start_penalty, |
|
num_active_workers, |
|
concurrent_batch_size=1 |
|
): |
|
|
|
monthly_hours = 24 * 30 |
|
seconds_per_hour = 3600 |
|
monthly_requests = requests_per_hour * monthly_hours |
|
|
|
|
|
|
|
active_capacity_per_hour = (seconds_per_hour / execution_time_per_request) * num_active_workers * concurrent_batch_size |
|
|
|
|
|
active_requests_handled = min(monthly_requests, active_capacity_per_hour * monthly_hours) |
|
|
|
|
|
flex_requests = max(0, monthly_requests - active_requests_handled) |
|
|
|
|
|
active_cost = num_active_workers * active_cost_per_second * seconds_per_hour * monthly_hours |
|
|
|
|
|
|
|
avg_requests_per_cold_start = concurrent_batch_size * 2 |
|
cold_starts = flex_requests / avg_requests_per_cold_start if flex_requests > 0 else 0 |
|
|
|
|
|
flex_processing_cost = flex_requests * execution_time_per_request * flex_cost_per_second |
|
flex_cold_start_cost = cold_starts * cold_start_penalty * flex_cost_per_second |
|
flex_cost = flex_processing_cost + flex_cold_start_cost |
|
|
|
|
|
active_utilization = (active_requests_handled / (active_capacity_per_hour * monthly_hours) * 100) if num_active_workers > 0 else 0 |
|
|
|
total_monthly_cost = active_cost + flex_cost |
|
|
|
return { |
|
"monthly_requests": monthly_requests, |
|
"active_requests": active_requests_handled, |
|
"flex_requests": flex_requests, |
|
"active_cost": active_cost, |
|
"flex_cost": flex_cost, |
|
"total_monthly_cost": total_monthly_cost, |
|
"active_utilization": active_utilization |
|
} |
|
|
|
def generate_cost_projection( |
|
min_requests, |
|
max_requests, |
|
execution_time, |
|
active_cost, |
|
flex_cost, |
|
num_active_workers, |
|
cold_start, |
|
concurrent_batch_size |
|
): |
|
|
|
request_volumes = np.linspace(min_requests, max_requests, 20) |
|
results = [] |
|
|
|
for req_vol in request_volumes: |
|
result = calculate_runpod_cost_revised( |
|
req_vol, |
|
execution_time, |
|
active_cost, |
|
flex_cost, |
|
cold_start, |
|
num_active_workers, |
|
concurrent_batch_size |
|
) |
|
results.append({ |
|
'requests_per_hour': req_vol, |
|
'monthly_requests': result['monthly_requests'], |
|
'active_cost': result['active_cost'], |
|
'flex_cost': result['flex_cost'], |
|
'total_cost': result['total_monthly_cost'], |
|
'active_utilization': result['active_utilization'] |
|
}) |
|
|
|
df = pd.DataFrame(results) |
|
|
|
|
|
fig = go.Figure() |
|
fig.add_trace(go.Scatter( |
|
x=df['requests_per_hour'], |
|
y=df['active_cost'], |
|
name='Active Worker Cost', |
|
line=dict(color='blue') |
|
)) |
|
fig.add_trace(go.Scatter( |
|
x=df['requests_per_hour'], |
|
y=df['flex_cost'], |
|
name='Flex Worker Cost', |
|
line=dict(color='orange') |
|
)) |
|
fig.add_trace(go.Scatter( |
|
x=df['requests_per_hour'], |
|
y=df['total_cost'], |
|
name='Total Cost', |
|
line=dict(color='green', width=3) |
|
)) |
|
|
|
fig.update_layout( |
|
title='RunPod Serverless Monthly Cost Projection', |
|
xaxis_title='Requests per Hour', |
|
yaxis_title='Monthly Cost ($)', |
|
hovermode='x unified' |
|
) |
|
|
|
return fig |
|
|
|
def calculate_specific_cost( |
|
requests, |
|
execution_time, |
|
active_cost, |
|
flex_cost, |
|
num_active_workers, |
|
cold_start, |
|
concurrent_batch_size |
|
): |
|
result = calculate_runpod_cost_revised( |
|
requests, |
|
execution_time, |
|
active_cost, |
|
flex_cost, |
|
cold_start, |
|
num_active_workers, |
|
concurrent_batch_size |
|
) |
|
|
|
|
|
html = f""" |
|
<table style="width:100%; border-collapse: collapse;"> |
|
<tr style="background-color: #f2f2f2;"> |
|
<th style="padding: 12px; text-align: left; border: 1px solid #ddd;">Metric</th> |
|
<th style="padding: 12px; text-align: right; border: 1px solid #ddd;">Value</th> |
|
</tr> |
|
<tr> |
|
<td style="padding: 8px; border: 1px solid #ddd;">Monthly Requests</td> |
|
<td style="padding: 8px; text-align: right; border: 1px solid #ddd;">{result['monthly_requests']:,.0f}</td> |
|
</tr> |
|
<tr> |
|
<td style="padding: 8px; border: 1px solid #ddd;">Requests Handled by Active Workers</td> |
|
<td style="padding: 8px; text-align: right; border: 1px solid #ddd;">{result['active_requests']:,.0f}</td> |
|
</tr> |
|
<tr> |
|
<td style="padding: 8px; border: 1px solid #ddd;">Requests Handled by Flex Workers</td> |
|
<td style="padding: 8px; text-align: right; border: 1px solid #ddd;">{result['flex_requests']:,.0f}</td> |
|
</tr> |
|
<tr> |
|
<td style="padding: 8px; border: 1px solid #ddd;">Active Worker Cost</td> |
|
<td style="padding: 8px; text-align: right; border: 1px solid #ddd;">${result['active_cost']:,.2f}</td> |
|
</tr> |
|
<tr> |
|
<td style="padding: 8px; border: 1px solid #ddd;">Flex Worker Cost</td> |
|
<td style="padding: 8px; text-align: right; border: 1px solid #ddd;">${result['flex_cost']:,.2f}</td> |
|
</tr> |
|
<tr style="font-weight: bold;"> |
|
<td style="padding: 8px; border: 1px solid #ddd;">Total Monthly Cost</td> |
|
<td style="padding: 8px; text-align: right; border: 1px solid #ddd;">${result['total_monthly_cost']:,.2f}</td> |
|
</tr> |
|
<tr> |
|
<td style="padding: 8px; border: 1px solid #ddd;">Active Worker Utilization</td> |
|
<td style="padding: 8px; text-align: right; border: 1px solid #ddd;">{result['active_utilization']:.1f}%</td> |
|
</tr> |
|
</table> |
|
""" |
|
|
|
return html |
|
|
|
|
|
theme = gr.themes.Default( |
|
primary_hue=gr.themes.colors.red, |
|
secondary_hue=gr.themes.colors.red, |
|
neutral_hue=gr.themes.colors.slate, |
|
) |
|
|
|
|
|
with gr.Blocks(title="RunPod Serverless Cost Estimator", theme=theme) as demo: |
|
gr.Markdown("# RunPod Serverless Cost Estimator") |
|
gr.Markdown("Estimate your monthly RunPod Serverless costs based on request volume and execution time") |
|
|
|
with gr.Row(): |
|
with gr.Column(scale=1): |
|
min_requests = gr.Slider(minimum=100, maximum=50000, value=1000, step=1000, label="Minimum Requests/Hour") |
|
max_requests = gr.Slider(minimum=1000, maximum=100000, value=20000, step=1000, label="Maximum Requests/Hour") |
|
execution_time = gr.Slider(minimum=1, maximum=120, value=30, step=1, label="Execution Time per Request (seconds)") |
|
active_cost = gr.Number(value=0.00019, label="Active Worker Cost ($/second)", precision=5) |
|
flex_cost = gr.Number(value=0.00031, label="Flex Worker Cost ($/second)", precision=5) |
|
num_active_workers = gr.Slider(minimum=0, maximum=600, value=30, step=10, label="Number of Active Workers") |
|
cold_start = gr.Slider(minimum=0, maximum=60, value=1, step=1, label="Cold Start Penalty (seconds)") |
|
concurrent_batch_size = gr.Slider(minimum=1, maximum=10, value=2, step=1, label="Concurrent Requests per Worker") |
|
|
|
plot_button = gr.Button("Generate Cost Projection") |
|
|
|
with gr.Column(scale=2): |
|
plot_output = gr.Plot(label="Cost Projection") |
|
|
|
gr.Markdown("## Cost Breakdown for Specific Request Volume") |
|
|
|
with gr.Row(): |
|
with gr.Column(scale=1): |
|
specific_requests = gr.Number(value=10000, label="Requests per Hour", precision=0) |
|
calc_button = gr.Button("Calculate Cost") |
|
|
|
with gr.Column(scale=2): |
|
cost_table = gr.HTML() |
|
|
|
plot_button.click( |
|
generate_cost_projection, |
|
inputs=[min_requests, max_requests, execution_time, active_cost, flex_cost, num_active_workers, cold_start, concurrent_batch_size], |
|
outputs=plot_output |
|
) |
|
|
|
calc_button.click( |
|
calculate_specific_cost, |
|
inputs=[specific_requests, execution_time, active_cost, flex_cost, num_active_workers, cold_start, concurrent_batch_size], |
|
outputs=cost_table |
|
) |
|
|
|
|
|
if __name__ == "__main__": |
|
demo.launch() |