Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -453,8 +453,8 @@ MODEL_CHOICES = [
|
|
| 453 |
# Note: nf4 (NormalFloat4) is specifically designed for QLoRA and provides
|
| 454 |
# better quality than standard int4 at the same bitwidth
|
| 455 |
PRECISION_MAP = {
|
| 456 |
-
"float32": 4.0,
|
| 457 |
-
"fp32": 4.0,
|
| 458 |
"bf16": 2.0, # BFloat16 - preferred for training (better range than fp16)
|
| 459 |
"fp16": 2.0, # Float16 - standard for inference
|
| 460 |
"nf4": 0.5625, # NormalFloat4 - QLoRA's quantization format
|
|
@@ -612,7 +612,7 @@ GPU_DATABASE = [
|
|
| 612 |
# Based on real-world benchmarks from MLPerf, vendor data, and community testing
|
| 613 |
# Tokens per second per GPU for different model sizes
|
| 614 |
#
|
| 615 |
-
# Last Updated:
|
| 616 |
# Sources:
|
| 617 |
# - MLPerf Training v3.1 (November 2023)
|
| 618 |
# - NVIDIA TensorRT-LLM benchmarks (Q4 2024)
|
|
@@ -1504,7 +1504,9 @@ def calculate_vram(
|
|
| 1504 |
weights_gb = calculate_model_weights(spec, precision)
|
| 1505 |
|
| 1506 |
# Calculate KV cache
|
| 1507 |
-
|
|
|
|
|
|
|
| 1508 |
|
| 1509 |
if task == "Inference":
|
| 1510 |
# Inference: weights + KV cache + framework overhead
|
|
@@ -1514,7 +1516,9 @@ def calculate_vram(
|
|
| 1514 |
|
| 1515 |
else: # Training
|
| 1516 |
# Training: weights + KV + activations + optimizer + gradients
|
| 1517 |
-
|
|
|
|
|
|
|
| 1518 |
optimizer_gb, _ = calculate_optimizer_states(weights_gb, ft_method or "Full Fine-Tuning", rank or 64, spec)
|
| 1519 |
gradients_gb, _ = calculate_gradients(weights_gb, ft_method or "Full Fine-Tuning", rank or 64, spec)
|
| 1520 |
|
|
@@ -1540,6 +1544,255 @@ def calculate_vram(
|
|
| 1540 |
# Hardware Recommendation Engine
|
| 1541 |
# =================================================================================================
|
| 1542 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1543 |
def recommend_hardware(
|
| 1544 |
required_vram: float,
|
| 1545 |
task: str,
|
|
@@ -1622,6 +1875,7 @@ def recommend_hardware(
|
|
| 1622 |
"cost": [],
|
| 1623 |
"vram_util": [],
|
| 1624 |
"cost_efficiency": [], # tokens per rupee
|
|
|
|
| 1625 |
}
|
| 1626 |
|
| 1627 |
for config in chart_configs:
|
|
@@ -1636,7 +1890,22 @@ def recommend_hardware(
|
|
| 1636 |
chart_data["cost"].append(price)
|
| 1637 |
chart_data["vram_util"].append(vram_util)
|
| 1638 |
# Cost efficiency: tokens per rupee per hour
|
| 1639 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1640 |
|
| 1641 |
# Generate recommendation cards
|
| 1642 |
def make_card(title: str, emoji: str, config: Optional[GPUConfig]) -> str:
|
|
@@ -1708,14 +1977,14 @@ def process_request(
|
|
| 1708 |
output_tokens: float,
|
| 1709 |
dataset_tier: str,
|
| 1710 |
manufacturers: list[str],
|
| 1711 |
-
) -> Tuple[str, str, str, Any]:
|
| 1712 |
"""
|
| 1713 |
Main request processing function.
|
| 1714 |
|
| 1715 |
Orchestrates model resolution, VRAM calculation, and hardware recommendation.
|
| 1716 |
|
| 1717 |
Returns:
|
| 1718 |
-
Tuple of (report, budget_rec, runner_up_rec, combined_chart)
|
| 1719 |
"""
|
| 1720 |
try:
|
| 1721 |
# Validate inputs
|
|
@@ -1773,7 +2042,7 @@ def process_request(
|
|
| 1773 |
)
|
| 1774 |
|
| 1775 |
if error:
|
| 1776 |
-
return error, "", "", None
|
| 1777 |
|
| 1778 |
# Generate report
|
| 1779 |
report = f"""
|
|
@@ -1843,23 +2112,6 @@ def process_request(
|
|
| 1843 |
)
|
| 1844 |
])
|
| 1845 |
|
| 1846 |
-
# combined_chart.update_layout(
|
| 1847 |
-
# title_text="GPU Comparison (Top 10 by Cost)",
|
| 1848 |
-
# xaxis_title="GPU Configuration",
|
| 1849 |
-
# yaxis_title="Normalized Value (% of max)",
|
| 1850 |
-
# barmode='group',
|
| 1851 |
-
# height=450,
|
| 1852 |
-
# xaxis_tickangle=-45,
|
| 1853 |
-
# margin=dict(b=120),
|
| 1854 |
-
# legend=dict(
|
| 1855 |
-
# orientation="h",
|
| 1856 |
-
# yanchor="bottom",
|
| 1857 |
-
# y=1.02,
|
| 1858 |
-
# xanchor="center",
|
| 1859 |
-
# x=0.5
|
| 1860 |
-
# )
|
| 1861 |
-
# )
|
| 1862 |
-
|
| 1863 |
combined_chart.update_layout(
|
| 1864 |
title_text="GPU Comparison (Top 10 by Cost)",
|
| 1865 |
xaxis_title="GPU Configuration",
|
|
@@ -1882,7 +2134,48 @@ def process_request(
|
|
| 1882 |
title_font=dict(size=14),
|
| 1883 |
)
|
| 1884 |
|
| 1885 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1886 |
|
| 1887 |
except Exception as e:
|
| 1888 |
error_msg = f"""
|
|
@@ -1891,7 +2184,7 @@ def process_request(
|
|
| 1891 |
<p>{str(e)}</p>
|
| 1892 |
</div>
|
| 1893 |
"""
|
| 1894 |
-
return error_msg, "", "", None
|
| 1895 |
|
| 1896 |
# =================================================================================================
|
| 1897 |
# UI Event Handlers
|
|
@@ -2053,6 +2346,16 @@ def create_interface() -> gr.Blocks:
|
|
| 2053 |
with gr.Column(elem_classes=["runner-box"]):
|
| 2054 |
rec_out_2 = gr.Markdown()
|
| 2055 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2056 |
# Bar chart comparison section
|
| 2057 |
with gr.Accordion("📊 GPU Comparison (Top 10 by Cost)", open=False):
|
| 2058 |
combined_plot = gr.Plot(label="GPU Comparison")
|
|
@@ -2091,7 +2394,7 @@ def create_interface() -> gr.Blocks:
|
|
| 2091 |
ft_method, rank, batch, sample_count,
|
| 2092 |
input_tokens, output_tokens, dataset_tier, manufacturers,
|
| 2093 |
],
|
| 2094 |
-
outputs=[report_out, rec_out_1, rec_out_2, combined_plot]
|
| 2095 |
)
|
| 2096 |
|
| 2097 |
return demo
|
|
|
|
| 453 |
# Note: nf4 (NormalFloat4) is specifically designed for QLoRA and provides
|
| 454 |
# better quality than standard int4 at the same bitwidth
|
| 455 |
PRECISION_MAP = {
|
| 456 |
+
# "float32": 4.0,
|
| 457 |
+
# "fp32": 4.0,
|
| 458 |
"bf16": 2.0, # BFloat16 - preferred for training (better range than fp16)
|
| 459 |
"fp16": 2.0, # Float16 - standard for inference
|
| 460 |
"nf4": 0.5625, # NormalFloat4 - QLoRA's quantization format
|
|
|
|
| 612 |
# Based on real-world benchmarks from MLPerf, vendor data, and community testing
|
| 613 |
# Tokens per second per GPU for different model sizes
|
| 614 |
#
|
| 615 |
+
# Last Updated: 17 December 2025
|
| 616 |
# Sources:
|
| 617 |
# - MLPerf Training v3.1 (November 2023)
|
| 618 |
# - NVIDIA TensorRT-LLM benchmarks (Q4 2024)
|
|
|
|
| 1504 |
weights_gb = calculate_model_weights(spec, precision)
|
| 1505 |
|
| 1506 |
# Calculate KV cache
|
| 1507 |
+
# For training, KV cache is always at compute precision (bf16/fp16), not quantized
|
| 1508 |
+
kv_precision = "bf16" if task == "Training" else precision
|
| 1509 |
+
kv_cache_gb = calculate_kv_cache(spec, batch_size, seq_len, kv_precision)
|
| 1510 |
|
| 1511 |
if task == "Inference":
|
| 1512 |
# Inference: weights + KV cache + framework overhead
|
|
|
|
| 1516 |
|
| 1517 |
else: # Training
|
| 1518 |
# Training: weights + KV + activations + optimizer + gradients
|
| 1519 |
+
# IMPORTANT: Activations are always stored at compute precision (bf16/fp16)
|
| 1520 |
+
# even for QLoRA, because the forward pass computes at full precision
|
| 1521 |
+
activations_gb = calculate_activations(spec, batch_size, seq_len, "bf16")
|
| 1522 |
optimizer_gb, _ = calculate_optimizer_states(weights_gb, ft_method or "Full Fine-Tuning", rank or 64, spec)
|
| 1523 |
gradients_gb, _ = calculate_gradients(weights_gb, ft_method or "Full Fine-Tuning", rank or 64, spec)
|
| 1524 |
|
|
|
|
| 1544 |
# Hardware Recommendation Engine
|
| 1545 |
# =================================================================================================
|
| 1546 |
|
| 1547 |
+
def generate_analysis_csv(
|
| 1548 |
+
# Input parameters
|
| 1549 |
+
model_name: str,
|
| 1550 |
+
task: str,
|
| 1551 |
+
quant: str,
|
| 1552 |
+
framework: str,
|
| 1553 |
+
batch_size: int,
|
| 1554 |
+
seq_len: int,
|
| 1555 |
+
ft_method: Optional[str],
|
| 1556 |
+
rank: Optional[int],
|
| 1557 |
+
sample_count: int,
|
| 1558 |
+
input_tokens: int,
|
| 1559 |
+
output_tokens: int,
|
| 1560 |
+
pricing_tier: str,
|
| 1561 |
+
manufacturers: List[str],
|
| 1562 |
+
# Calculated values
|
| 1563 |
+
spec: 'ModelSpec',
|
| 1564 |
+
weights_gb: float,
|
| 1565 |
+
variable_gb: float,
|
| 1566 |
+
total_vram: float,
|
| 1567 |
+
actual_precision: str,
|
| 1568 |
+
# GPU recommendations
|
| 1569 |
+
gpu_recommendations: List[Dict[str, Any]],
|
| 1570 |
+
source: str,
|
| 1571 |
+
) -> str:
|
| 1572 |
+
"""
|
| 1573 |
+
Generate a detailed CSV containing all analysis parameters, formulas, and recommendations.
|
| 1574 |
+
|
| 1575 |
+
Returns:
|
| 1576 |
+
CSV content as a string
|
| 1577 |
+
"""
|
| 1578 |
+
import csv
|
| 1579 |
+
import io
|
| 1580 |
+
from datetime import datetime
|
| 1581 |
+
|
| 1582 |
+
output = io.StringIO()
|
| 1583 |
+
writer = csv.writer(output)
|
| 1584 |
+
|
| 1585 |
+
# ==========================================
|
| 1586 |
+
# SECTION 1: HEADER
|
| 1587 |
+
# ==========================================
|
| 1588 |
+
writer.writerow(["IndiaAI GPU Infrastructure Recommender - Analysis Report"])
|
| 1589 |
+
writer.writerow(["Generated", datetime.now().strftime("%Y-%m-%d %H:%M:%S")])
|
| 1590 |
+
writer.writerow([])
|
| 1591 |
+
|
| 1592 |
+
# ==========================================
|
| 1593 |
+
# SECTION 2: INPUT PARAMETERS
|
| 1594 |
+
# ==========================================
|
| 1595 |
+
writer.writerow(["*" * 50])
|
| 1596 |
+
writer.writerow(["INPUT PARAMETERS"])
|
| 1597 |
+
writer.writerow(["*" * 50])
|
| 1598 |
+
writer.writerow(["Parameter", "Value", "Description"])
|
| 1599 |
+
writer.writerow(["Model Name", model_name, "User-specified model identifier"])
|
| 1600 |
+
writer.writerow(["Task", task, "Inference or Training"])
|
| 1601 |
+
writer.writerow(["Quantization", quant, "Precision format for model weights"])
|
| 1602 |
+
writer.writerow(["Framework", framework, "Inference/training framework"])
|
| 1603 |
+
writer.writerow(["Batch Size", batch_size, "Number of samples processed together"])
|
| 1604 |
+
writer.writerow(["Sequence Length", seq_len, "Maximum context length (tokens)"])
|
| 1605 |
+
if task == "Training":
|
| 1606 |
+
writer.writerow(["Fine-tuning Method", ft_method or "N/A", "Training strategy (QLoRA/LoRA/Full FT)"])
|
| 1607 |
+
writer.writerow(["LoRA Rank", rank if ft_method in ["LoRA", "QLoRA"] else "N/A", "Adapter rank for LoRA/QLoRA"])
|
| 1608 |
+
writer.writerow(["Sample Count", sample_count, "Total samples to process"])
|
| 1609 |
+
writer.writerow(["Input Tokens", input_tokens, "Average input tokens per sample"])
|
| 1610 |
+
writer.writerow(["Output Tokens", output_tokens, "Average output tokens per sample"])
|
| 1611 |
+
writer.writerow(["Pricing Tier", pricing_tier, "Selected pricing model"])
|
| 1612 |
+
writer.writerow(["GPU Manufacturers", ", ".join(manufacturers), "Filtered GPU vendors"])
|
| 1613 |
+
writer.writerow([])
|
| 1614 |
+
|
| 1615 |
+
# ==========================================
|
| 1616 |
+
# SECTION 3: MODEL ARCHITECTURE (Derived)
|
| 1617 |
+
# ==========================================
|
| 1618 |
+
writer.writerow(["*" * 50])
|
| 1619 |
+
writer.writerow(["MODEL ARCHITECTURE (Derived)"])
|
| 1620 |
+
writer.writerow(["*" * 50])
|
| 1621 |
+
writer.writerow(["Parameter", "Value", "Formula / Source"])
|
| 1622 |
+
writer.writerow(["Source", source, "How model info was obtained"])
|
| 1623 |
+
writer.writerow(["Parameters (Billions)", f"{spec.params_bn:.2f}", "From model config or estimated from name"])
|
| 1624 |
+
writer.writerow(["Parameters (Exact)", f"{spec.params:,}", "params_bn × 1,000,000,000"])
|
| 1625 |
+
writer.writerow(["Layers", spec.layers, "Number of transformer layers"])
|
| 1626 |
+
writer.writerow(["Attention Heads", spec.heads, "Number of query attention heads"])
|
| 1627 |
+
writer.writerow(["KV Heads", spec.kv_heads, "Number of key/value heads (GQA)"])
|
| 1628 |
+
writer.writerow(["Head Dimension", spec.head_dim, "Dimension per attention head"])
|
| 1629 |
+
writer.writerow(["Hidden Size", spec.heads * spec.head_dim, "heads × head_dim"])
|
| 1630 |
+
writer.writerow(["Max Context Length", spec.context, "Maximum supported sequence length"])
|
| 1631 |
+
writer.writerow([])
|
| 1632 |
+
|
| 1633 |
+
# ==========================================
|
| 1634 |
+
# SECTION 4: PRECISION PARAMETERS
|
| 1635 |
+
# ==========================================
|
| 1636 |
+
writer.writerow(["*" * 50])
|
| 1637 |
+
writer.writerow(["PRECISION PARAMETERS"])
|
| 1638 |
+
writer.writerow(["*" * 50])
|
| 1639 |
+
writer.writerow(["Parameter", "Value", "Formula / Explanation"])
|
| 1640 |
+
|
| 1641 |
+
bytes_per_param = PRECISION_MAP.get(quant, 2.0)
|
| 1642 |
+
writer.writerow(["Selected Precision", quant, "User-selected quantization format"])
|
| 1643 |
+
writer.writerow(["Bytes per Parameter", bytes_per_param, f"From PRECISION_MAP['{quant}']"])
|
| 1644 |
+
writer.writerow(["Actual Precision Used", actual_precision, "May differ for LoRA/Full FT (requires bf16)"])
|
| 1645 |
+
|
| 1646 |
+
if actual_precision != quant:
|
| 1647 |
+
actual_bytes = PRECISION_MAP.get(actual_precision, 2.0)
|
| 1648 |
+
writer.writerow(["Actual Bytes per Param", actual_bytes, f"Overridden to {actual_precision} for training"])
|
| 1649 |
+
writer.writerow([])
|
| 1650 |
+
|
| 1651 |
+
# ==========================================
|
| 1652 |
+
# SECTION 5: VRAM CALCULATION BREAKDOWN
|
| 1653 |
+
# ==========================================
|
| 1654 |
+
writer.writerow(["*" * 50])
|
| 1655 |
+
writer.writerow(["VRAM CALCULATION BREAKDOWN"])
|
| 1656 |
+
writer.writerow(["*" * 50])
|
| 1657 |
+
writer.writerow(["Component", "Value (GB)", "Formula"])
|
| 1658 |
+
|
| 1659 |
+
# Model weights calculation
|
| 1660 |
+
actual_bytes_per_param = PRECISION_MAP.get(actual_precision, 2.0)
|
| 1661 |
+
weights_formula = f"{spec.params_bn:.2f}B params × {actual_bytes_per_param} bytes / 1024³"
|
| 1662 |
+
writer.writerow(["Model Weights", f"{weights_gb:.2f}", weights_formula])
|
| 1663 |
+
|
| 1664 |
+
# KV Cache calculation
|
| 1665 |
+
# For training, KV cache uses bf16 (compute precision), not quantized
|
| 1666 |
+
if task == "Training":
|
| 1667 |
+
kv_bytes_per_elem = 2.0 # Always bf16 for training
|
| 1668 |
+
kv_cache_gb = calculate_kv_cache(spec, batch_size, seq_len, "bf16")
|
| 1669 |
+
kv_formula = f"2 × {spec.layers} layers × {batch_size} batch × {seq_len} seq × {spec.kv_heads} kv_heads × {spec.head_dim} head_dim × {kv_bytes_per_elem} bytes (bf16) / 1024³"
|
| 1670 |
+
else:
|
| 1671 |
+
kv_bytes_per_elem = 2.0 if quant in ['nf4', '4bit', 'int4'] else PRECISION_MAP.get(quant, 2.0)
|
| 1672 |
+
kv_cache_gb = calculate_kv_cache(spec, batch_size, seq_len, quant)
|
| 1673 |
+
kv_formula = f"2 × {spec.layers} layers × {batch_size} batch × {seq_len} seq × {spec.kv_heads} kv_heads × {spec.head_dim} head_dim × {kv_bytes_per_elem} bytes / 1024³"
|
| 1674 |
+
writer.writerow(["KV Cache", f"{kv_cache_gb:.2f}", kv_formula])
|
| 1675 |
+
|
| 1676 |
+
if task == "Training":
|
| 1677 |
+
# Activations - always at bf16 compute precision, even for QLoRA
|
| 1678 |
+
# The forward pass computes at full precision, so activations are stored at bf16
|
| 1679 |
+
hidden_size = spec.heads * spec.head_dim
|
| 1680 |
+
activations_gb = calculate_activations(spec, batch_size, seq_len, "bf16")
|
| 1681 |
+
act_formula = f"{batch_size} × {seq_len} × {hidden_size} hidden × {spec.layers} layers × 12 (checkpointing) × 2 bytes (bf16) / 1024³"
|
| 1682 |
+
writer.writerow(["Activations", f"{activations_gb:.2f}", act_formula])
|
| 1683 |
+
|
| 1684 |
+
# Optimizer states
|
| 1685 |
+
optimizer_gb, opt_desc = calculate_optimizer_states(weights_gb, ft_method or "Full Fine-Tuning", rank or 64, spec)
|
| 1686 |
+
if ft_method == "Full Fine-Tuning":
|
| 1687 |
+
opt_formula = f"{weights_gb:.2f} GB weights × 4 (Adam: 2 states × fp32)"
|
| 1688 |
+
else:
|
| 1689 |
+
adapter_params = 2 * (rank or 64) * hidden_size * spec.layers
|
| 1690 |
+
opt_formula = f"LoRA adapters ({adapter_params:,} params) × 4 (Adam states)"
|
| 1691 |
+
writer.writerow(["Optimizer States", f"{optimizer_gb:.2f}", opt_formula])
|
| 1692 |
+
|
| 1693 |
+
# Gradients
|
| 1694 |
+
gradients_gb, grad_desc = calculate_gradients(weights_gb, ft_method or "Full Fine-Tuning", rank or 64, spec)
|
| 1695 |
+
if ft_method == "Full Fine-Tuning":
|
| 1696 |
+
grad_formula = f"{weights_gb:.2f} GB weights × 2 (fp32 gradients)"
|
| 1697 |
+
else:
|
| 1698 |
+
grad_formula = f"LoRA adapter gradients (fp16)"
|
| 1699 |
+
writer.writerow(["Gradients", f"{gradients_gb:.2f}", grad_formula])
|
| 1700 |
+
|
| 1701 |
+
# LoRA adapters
|
| 1702 |
+
if ft_method in ["LoRA", "QLoRA"]:
|
| 1703 |
+
adapter_params = 2 * (rank or 64) * hidden_size * spec.layers
|
| 1704 |
+
adapter_gb = (adapter_params * 2) / (1024**3)
|
| 1705 |
+
adapter_formula = f"2 × {rank} rank × {hidden_size} hidden × {spec.layers} layers × 2 bytes / 1024³"
|
| 1706 |
+
writer.writerow(["LoRA Adapters", f"{adapter_gb:.4f}", adapter_formula])
|
| 1707 |
+
else:
|
| 1708 |
+
# Framework overhead for inference
|
| 1709 |
+
overhead_gb = FRAMEWORK_OVERHEAD.get(framework, 2.0)
|
| 1710 |
+
writer.writerow(["Framework Overhead", f"{overhead_gb:.2f}", f"FRAMEWORK_OVERHEAD['{framework}']"])
|
| 1711 |
+
|
| 1712 |
+
writer.writerow([])
|
| 1713 |
+
raw_total = weights_gb + variable_gb
|
| 1714 |
+
buffer_amount = raw_total * 0.1
|
| 1715 |
+
total_with_buffer = raw_total * 1.1
|
| 1716 |
+
writer.writerow(["TOTAL VRAM (calculated)", f"{raw_total:.2f}", "Sum of all components"])
|
| 1717 |
+
writer.writerow(["Safety Buffer (10%)", f"{buffer_amount:.2f}", "Total × 0.10 (used for GPU selection)"])
|
| 1718 |
+
writer.writerow(["TOTAL VRAM + BUFFER", f"{total_with_buffer:.2f}", "Total × 1.10 (GPUs must have >= this VRAM)"])
|
| 1719 |
+
writer.writerow([])
|
| 1720 |
+
|
| 1721 |
+
# ==========================================
|
| 1722 |
+
# SECTION 6: GPU RECOMMENDATIONS (Top 10)
|
| 1723 |
+
# ==========================================
|
| 1724 |
+
writer.writerow(["*" * 50])
|
| 1725 |
+
writer.writerow(["GPU RECOMMENDATIONS (Top 10 by Cost)"])
|
| 1726 |
+
writer.writerow(["*" * 50])
|
| 1727 |
+
writer.writerow([
|
| 1728 |
+
"Rank", "GPU Configuration", "Total VRAM (GB)", "VRAM/GPU (GB)", "GPU Count",
|
| 1729 |
+
"VRAM Utilization (%)", "Throughput (tok/s)", "Price (₹/hr)",
|
| 1730 |
+
"Cost Efficiency (tok/₹)", "TFLOPS", "Bandwidth (GB/s)"
|
| 1731 |
+
])
|
| 1732 |
+
|
| 1733 |
+
for i, gpu in enumerate(gpu_recommendations, 1):
|
| 1734 |
+
writer.writerow([
|
| 1735 |
+
i,
|
| 1736 |
+
gpu["name"],
|
| 1737 |
+
gpu["vram"],
|
| 1738 |
+
gpu["vram_per_gpu"],
|
| 1739 |
+
gpu["gpu_count"],
|
| 1740 |
+
f"{gpu['vram_util']:.1f}",
|
| 1741 |
+
f"{gpu['throughput']:.0f}",
|
| 1742 |
+
f"{gpu['price']:.2f}",
|
| 1743 |
+
f"{gpu['cost_efficiency']:.2f}",
|
| 1744 |
+
f"{gpu['tflops']:.0f}",
|
| 1745 |
+
f"{gpu['bandwidth']:.0f}",
|
| 1746 |
+
])
|
| 1747 |
+
|
| 1748 |
+
writer.writerow([])
|
| 1749 |
+
|
| 1750 |
+
# ==========================================
|
| 1751 |
+
# SECTION 7: FORMULAS REFERENCE
|
| 1752 |
+
# ==========================================
|
| 1753 |
+
writer.writerow(["*" * 50])
|
| 1754 |
+
writer.writerow(["FORMULAS REFERENCE"])
|
| 1755 |
+
writer.writerow(["*" * 50])
|
| 1756 |
+
writer.writerow(["Calculation", "Formula"])
|
| 1757 |
+
writer.writerow(["Model Weights (GB)", "num_parameters × bytes_per_param / 1024³"])
|
| 1758 |
+
writer.writerow(["KV Cache (GB)", "2 × layers × batch × seq_len × kv_heads × head_dim × bytes_per_elem / 1024³"])
|
| 1759 |
+
writer.writerow(["Activations (GB)", "batch × seq_len × hidden_size × layers × multiplier × bytes / 1024³"])
|
| 1760 |
+
writer.writerow(["Optimizer States (GB)", "trainable_params × 8 bytes (Adam: momentum + variance at fp32)"])
|
| 1761 |
+
writer.writerow(["Gradients (GB)", "trainable_params × bytes_per_grad"])
|
| 1762 |
+
writer.writerow(["LoRA Adapter Size", "2 × rank × hidden_dim × layers × 2 bytes"])
|
| 1763 |
+
writer.writerow(["VRAM Utilization (%)", "(required_vram / gpu_vram) × 100"])
|
| 1764 |
+
writer.writerow(["Cost Efficiency", "throughput (tok/s) / price (₹/hr)"])
|
| 1765 |
+
writer.writerow([])
|
| 1766 |
+
|
| 1767 |
+
# ==========================================
|
| 1768 |
+
# SECTION 8: PRECISION MAP REFERENCE
|
| 1769 |
+
# ==========================================
|
| 1770 |
+
writer.writerow(["*" * 50])
|
| 1771 |
+
writer.writerow(["PRECISION MAP REFERENCE"])
|
| 1772 |
+
writer.writerow(["*" * 50])
|
| 1773 |
+
writer.writerow(["Format", "Bytes per Parameter", "Notes"])
|
| 1774 |
+
for fmt, bytes_val in PRECISION_MAP.items():
|
| 1775 |
+
notes = {
|
| 1776 |
+
# "fp32": "Full precision - maximum accuracy",
|
| 1777 |
+
# "float32": "Full precision - maximum accuracy",
|
| 1778 |
+
"bf16": "Brain Float16 - preferred for training",
|
| 1779 |
+
"fp16": "Half precision - standard for inference",
|
| 1780 |
+
"nf4": "NormalFloat4 - QLoRA format",
|
| 1781 |
+
"4bit": "4-bit quantization",
|
| 1782 |
+
"int4": "Integer 4-bit",
|
| 1783 |
+
"int8": "Integer 8-bit - good accuracy/size tradeoff",
|
| 1784 |
+
"awq": "Activation-aware Weight Quantization (inference-only)",
|
| 1785 |
+
"gptq": "GPTQ quantization (inference-only)",
|
| 1786 |
+
}.get(fmt, "")
|
| 1787 |
+
writer.writerow([fmt, bytes_val, notes])
|
| 1788 |
+
writer.writerow([])
|
| 1789 |
+
|
| 1790 |
+
writer.writerow(["*" * 50])
|
| 1791 |
+
writer.writerow(["END OF REPORT"])
|
| 1792 |
+
writer.writerow(["*" * 50])
|
| 1793 |
+
|
| 1794 |
+
return output.getvalue()
|
| 1795 |
+
|
| 1796 |
def recommend_hardware(
|
| 1797 |
required_vram: float,
|
| 1798 |
task: str,
|
|
|
|
| 1875 |
"cost": [],
|
| 1876 |
"vram_util": [],
|
| 1877 |
"cost_efficiency": [], # tokens per rupee
|
| 1878 |
+
"gpu_details": [], # Full details for CSV export
|
| 1879 |
}
|
| 1880 |
|
| 1881 |
for config in chart_configs:
|
|
|
|
| 1890 |
chart_data["cost"].append(price)
|
| 1891 |
chart_data["vram_util"].append(vram_util)
|
| 1892 |
# Cost efficiency: tokens per rupee per hour
|
| 1893 |
+
cost_eff = total_tps / price if price > 0 else 0
|
| 1894 |
+
chart_data["cost_efficiency"].append(cost_eff)
|
| 1895 |
+
|
| 1896 |
+
# Full GPU details for CSV export
|
| 1897 |
+
chart_data["gpu_details"].append({
|
| 1898 |
+
"name": config.name,
|
| 1899 |
+
"vram": config.vram,
|
| 1900 |
+
"vram_per_gpu": config.vram_per_gpu,
|
| 1901 |
+
"gpu_count": config.count,
|
| 1902 |
+
"vram_util": vram_util,
|
| 1903 |
+
"throughput": total_tps,
|
| 1904 |
+
"price": price,
|
| 1905 |
+
"cost_efficiency": cost_eff,
|
| 1906 |
+
"tflops": config.tflops,
|
| 1907 |
+
"bandwidth": config.bandwidth,
|
| 1908 |
+
})
|
| 1909 |
|
| 1910 |
# Generate recommendation cards
|
| 1911 |
def make_card(title: str, emoji: str, config: Optional[GPUConfig]) -> str:
|
|
|
|
| 1977 |
output_tokens: float,
|
| 1978 |
dataset_tier: str,
|
| 1979 |
manufacturers: list[str],
|
| 1980 |
+
) -> Tuple[str, str, str, Any, Optional[str]]:
|
| 1981 |
"""
|
| 1982 |
Main request processing function.
|
| 1983 |
|
| 1984 |
Orchestrates model resolution, VRAM calculation, and hardware recommendation.
|
| 1985 |
|
| 1986 |
Returns:
|
| 1987 |
+
Tuple of (report, budget_rec, runner_up_rec, combined_chart, csv_filepath)
|
| 1988 |
"""
|
| 1989 |
try:
|
| 1990 |
# Validate inputs
|
|
|
|
| 2042 |
)
|
| 2043 |
|
| 2044 |
if error:
|
| 2045 |
+
return error, "", "", None, None
|
| 2046 |
|
| 2047 |
# Generate report
|
| 2048 |
report = f"""
|
|
|
|
| 2112 |
)
|
| 2113 |
])
|
| 2114 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2115 |
combined_chart.update_layout(
|
| 2116 |
title_text="GPU Comparison (Top 10 by Cost)",
|
| 2117 |
xaxis_title="GPU Configuration",
|
|
|
|
| 2134 |
title_font=dict(size=14),
|
| 2135 |
)
|
| 2136 |
|
| 2137 |
+
# Generate CSV content and save to file
|
| 2138 |
+
csv_filepath = None
|
| 2139 |
+
if chart_data and chart_data.get("gpu_details"):
|
| 2140 |
+
csv_content = generate_analysis_csv(
|
| 2141 |
+
model_name=model_name,
|
| 2142 |
+
task=task,
|
| 2143 |
+
quant=quant,
|
| 2144 |
+
framework=fw,
|
| 2145 |
+
batch_size=batch,
|
| 2146 |
+
seq_len=seq_len,
|
| 2147 |
+
ft_method=ft_method,
|
| 2148 |
+
rank=rank,
|
| 2149 |
+
sample_count=sample_count,
|
| 2150 |
+
input_tokens=input_tokens,
|
| 2151 |
+
output_tokens=output_tokens,
|
| 2152 |
+
pricing_tier=dataset_tier,
|
| 2153 |
+
manufacturers=manufacturers,
|
| 2154 |
+
spec=spec,
|
| 2155 |
+
weights_gb=weights_gb,
|
| 2156 |
+
variable_gb=variable_gb,
|
| 2157 |
+
total_vram=total_vram,
|
| 2158 |
+
actual_precision=actual_precision,
|
| 2159 |
+
gpu_recommendations=chart_data["gpu_details"],
|
| 2160 |
+
source=source,
|
| 2161 |
+
)
|
| 2162 |
+
|
| 2163 |
+
# Save CSV to a temporary file
|
| 2164 |
+
import tempfile
|
| 2165 |
+
import os
|
| 2166 |
+
|
| 2167 |
+
# Create a filename based on model and timestamp
|
| 2168 |
+
from datetime import datetime
|
| 2169 |
+
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
| 2170 |
+
safe_model_name = model_name.replace("/", "_").replace(" ", "_")[:30]
|
| 2171 |
+
filename = f"gpu_analysis_{safe_model_name}_{timestamp}.csv"
|
| 2172 |
+
|
| 2173 |
+
# Save to temp directory
|
| 2174 |
+
csv_filepath = os.path.join(tempfile.gettempdir(), filename)
|
| 2175 |
+
with open(csv_filepath, 'w', newline='', encoding='utf-8') as f:
|
| 2176 |
+
f.write(csv_content)
|
| 2177 |
+
|
| 2178 |
+
return report, budget_rec, runner_up_rec, combined_chart, csv_filepath
|
| 2179 |
|
| 2180 |
except Exception as e:
|
| 2181 |
error_msg = f"""
|
|
|
|
| 2184 |
<p>{str(e)}</p>
|
| 2185 |
</div>
|
| 2186 |
"""
|
| 2187 |
+
return error_msg, "", "", None, None
|
| 2188 |
|
| 2189 |
# =================================================================================================
|
| 2190 |
# UI Event Handlers
|
|
|
|
| 2346 |
with gr.Column(elem_classes=["runner-box"]):
|
| 2347 |
rec_out_2 = gr.Markdown()
|
| 2348 |
|
| 2349 |
+
# Download button for CSV export
|
| 2350 |
+
with gr.Row():
|
| 2351 |
+
csv_download = gr.File(
|
| 2352 |
+
label="📥 Download Analysis (CSV)",
|
| 2353 |
+
visible=True,
|
| 2354 |
+
file_count="single",
|
| 2355 |
+
type="filepath",
|
| 2356 |
+
interactive=False,
|
| 2357 |
+
)
|
| 2358 |
+
|
| 2359 |
# Bar chart comparison section
|
| 2360 |
with gr.Accordion("📊 GPU Comparison (Top 10 by Cost)", open=False):
|
| 2361 |
combined_plot = gr.Plot(label="GPU Comparison")
|
|
|
|
| 2394 |
ft_method, rank, batch, sample_count,
|
| 2395 |
input_tokens, output_tokens, dataset_tier, manufacturers,
|
| 2396 |
],
|
| 2397 |
+
outputs=[report_out, rec_out_1, rec_out_2, combined_plot, csv_download]
|
| 2398 |
)
|
| 2399 |
|
| 2400 |
return demo
|