import gradio as gr
import tempfile
import shutil
import os
import subprocess
import re
import tensorflow as tf
import numpy as np
from PIL import Image
DEFAULT_CONFIG = "u55_eval_with_TA_config_400_and_200_MHz.ini"
def extract_summary_from_log(log_text):
summary_keys = [
"Accelerator configuration",
"Accelerator clock",
"Total SRAM used",
"Total On-chip Flash used",
"CPU operators",
"NPU operators",
"Batch Inference time"
]
summary = []
for key in summary_keys:
match = re.search(rf"{re.escape(key)}\s+(.+)", log_text)
if match:
value = match.group(1).strip()
if key == "Batch Inference time":
value = value.split(",")[0].strip()
key = "Inference time"
summary.append((key, value))
return summary
def run_vela(model_file):
accel = "ethos-u55-128"
optimise = "Size"
mem_mode = "Sram_Only"
sys_config = "Ethos_U55_400MHz_SRAM_3.2_GBs_Flash_0.05_GBs"
tmpdir = tempfile.mkdtemp()
try:
# Use the original uploaded model filename
original_model_name = os.path.basename(model_file)
model_path = os.path.join(tmpdir, original_model_name)
shutil.copy(model_file, model_path)
config_path = os.path.join(tmpdir, DEFAULT_CONFIG)
shutil.copy(DEFAULT_CONFIG, config_path)
output_dir = os.path.join(tmpdir, "vela_out")
os.makedirs(output_dir, exist_ok=True)
cmd = [
"vela",
f"--accelerator-config={accel}",
f"--optimise={optimise}",
f"--config={config_path}",
f"--memory-mode={mem_mode}",
f"--system-config={sys_config}",
model_path,
"--verbose-cycle-estimate",
"--verbose-performance",
f"--output-dir={output_dir}"
]
result = subprocess.run(cmd, capture_output=True, text=True, check=True)
vela_stdout = result.stdout
# Check for unsupported model patterns in logs
unsupported_patterns = [
"Warning: Unsupported TensorFlow Lite semantics",
"Network Tops/s nan Tops/s",
"Neural network macs 0 MACs/batch"
]
if any(pat in vela_stdout for pat in unsupported_patterns):
summary_html = (
'
'
''
'
'
'This model has unsupported layers and needs investigation based on layers.
'
'Please use Vela compiler on your Host Machine for further analysis.'
'
'
)
# Try to provide per-layer.csv if available for download
per_layer_csv = None
for log_fname in os.listdir(output_dir):
if log_fname.endswith("per-layer.csv"):
per_layer_csv = os.path.join("/tmp", log_fname)
shutil.copy(os.path.join(output_dir, log_fname), per_layer_csv)
break
return summary_html, None, per_layer_csv
model_filename = os.path.basename(model_file)
if model_filename:
vela_stdout = vela_stdout.replace(
"Network summary for",
f"Network summary for {model_filename} ("
)
summary_items = extract_summary_from_log(vela_stdout)
# Convert summary_items to dict for easy access
summary_dict = dict(summary_items) if summary_items else {}
# Build 4 cards for results
def clean_ops(val):
# Remove '=' and leading/trailing spaces
return val.lstrip("= ").strip() if isinstance(val, str) else val
summary_html = (
''
''
'
'
'
'
# Card 1: Accelerator
'
'
'
Accelerator
'
f'
Configuration: {summary_dict.get("Accelerator configuration","-")}
Clock: {summary_dict.get("Accelerator clock","-")}
'
'
'
# Card 2: Memory Usage
'
'
'
Memory Usage
'
f'
Total SRAM: {summary_dict.get("Total SRAM used","-")}
Total Flash: {summary_dict.get("Total On-chip Flash used","-")}
'
'
'
# Card 3: Operator Distribution
'
'
'
Operator Distribution
'
f'
CPU Operators: {clean_ops(summary_dict.get("CPU operators","-"))}
NPU Operators: {clean_ops(summary_dict.get("NPU operators","-"))}
'
'
'
# Card 4: Performance
'
'
'
Performance
'
f'
Inference time: {summary_dict.get("Inference time","-")}
'
'
'
'
'
) if summary_items else 'Summary info not found in log.
'
for fname in os.listdir(output_dir):
if fname.endswith("vela.tflite"):
final_path = os.path.join("/tmp", fname)
shutil.copy(os.path.join(output_dir, fname), final_path)
# Find per-layer.csv file for logs
per_layer_csv = None
for log_fname in os.listdir(output_dir):
if log_fname.endswith("per-layer.csv"):
per_layer_csv = os.path.join("/tmp", log_fname)
shutil.copy(os.path.join(output_dir, log_fname), per_layer_csv)
break
return summary_html, final_path, per_layer_csv
# If no tflite, still try to return per-layer.csv if present
per_layer_csv = None
for log_fname in os.listdir(output_dir):
if log_fname.endswith("per-layer.csv"):
per_layer_csv = os.path.join("/tmp", log_fname)
shutil.copy(os.path.join(output_dir, log_fname), per_layer_csv)
break
return summary_html, None, per_layer_csv
finally:
shutil.rmtree(tmpdir)
# Create Gradio interface
with gr.Blocks(
theme=gr.themes.Default(primary_hue="blue", neutral_hue="gray"),
title="SR Vela Compiler",
css="""
body {
background: #fafafa !important;
}
.gradio-container {
max-width: none !important;
margin: 0 !important;
background-color: #fafafa !important;
font-family: 'Inter', 'Segoe UI', -apple-system, sans-serif !important;
width: 100vw !important;
}
.main-header {
text-align: center;
margin: 0 !important;
color: #3b82f6 !important;
font-weight: 600;
font-size: 2.5rem;
letter-spacing: -0.025em;
}
.main-header h1 {
color: #3b82f6 !important;
margin: 0 !important;
}
/* Force all h1 to be blue */
h1 {
color: #3b82f6 !important;
}
/* Force all markdown h1 to be blue */
.gr-markdown h1 {
color: #3b82f6 !important;
}
.subtitle {
text-align: center;
color: #4b5563 !important;
font-size: 1.1rem;
margin-top: 0.5rem !important;
margin-bottom: 2rem !important;
}
.card {
background: #fafafa !important;
border-radius: 12px !important;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06) !important;
border: 1px solid #e5e7eb !important;
margin-bottom: 1.5rem !important;
transition: all 0.2s ease-in-out !important;
overflow: hidden !important;
}
/* Fix Downloads card styling */
.card .gr-form {
background: transparent !important;
border: none !important;
box-shadow: none !important;
padding: 0 !important;
}
/* Ensure gr.Group with card class looks like a proper card */
.gr-group.card {
background: #fafafa !important;
border-radius: 12px !important;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06) !important;
border: 1px solid #e5e7eb !important;
margin-bottom: 1.5rem !important;
overflow: hidden !important;
padding: 0 !important;
}
/* Remove inner container styling */
.gr-group.card > div:first-child {
background: transparent !important;
border: none !important;
padding: 0 !important;
}
/* Style download buttons like MobileNet's example buttons */
.card-content button {
background: #f1f5f9 !important;
border: 1px solid #cbd5e1 !important;
color: #4b5563 !important;
border-radius: 6px !important;
transition: all 0.2s ease !important;
margin: 0.5rem 0 !important;
width: 100% !important;
}
.card-content button:hover {
background: #1975cf !important;
border-color: #1975cf !important;
color: white !important;
}
.card > * {
padding: 0 !important;
margin: 0 !important;
}
.card:hover {
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05) !important;
transform: translateY(-1px) !important;
}
.card-header {
background: linear-gradient(135deg, #1975cf 0%, #1557b0 100%) !important;
color: white !important;
padding: 1rem 1.5rem !important;
border-radius: 12px 12px 0 0 !important;
font-weight: 600 !important;
font-size: 1.1rem !important;
}
.card-header * {
color: white !important;
}
.card-content {
padding: 1.5rem !important;
color: #4b5563 !important;
line-height: 1.6 !important;
background: #fafafa !important;
}
.stats-grid {
display: grid !important;
grid-template-columns: 1fr 1fr !important;
gap: 1.5rem !important;
margin-top: 1.5rem !important;
}
.stat-item {
background: #f8fafc !important;
padding: 1rem !important;
border-radius: 8px !important;
border-left: 4px solid #1975cf !important;
}
.stat-label {
font-weight: 600 !important;
color: #4b5563 !important;
font-size: 0.9rem !important;
margin-bottom: 0.5rem !important;
}
.stat-value {
color: #4b5563 !important;
font-size: 0.85rem !important;
}
.stat-value strong {
color: #4b5563 !important;
font-weight: 600 !important;
}
.btn-primary {
background: #1975cf !important;
border-color: #1975cf !important;
color: white !important;
}
.btn-primary:hover {
background: #1557b0 !important;
border-color: #1557b0 !important;
}
.markdown {
color: #374151 !important;
}
.results-text {
color: #4b5563 !important;
font-weight: 500 !important;
padding: 0 !important;
margin: 0 !important;
}
.results-text p {
color: #4b5563 !important;
margin: 0.5rem 0 !important;
}
.results-text * {
color: #4b5563 !important;
}
div[data-testid="markdown"] p {
color: #4b5563 !important;
}
.prose {
color: #4b5563 !important;
}
.prose * {
color: #4b5563 !important;
}
.card-header,
.card-header * {
color: white !important;
}
.error-header {
background: linear-gradient(135deg, #dc2626 0%, #b91c1c 100%) !important;
}
.custom-footer {
max-width: 800px !important;
margin: 2rem auto !important;
background: white !important;
border-radius: 12px !important;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06) !important;
border: 1px solid #e5e7eb !important;
padding: 1.5rem !important;
text-align: center !important;
}
.custom-footer a {
color: #1975cf !important;
text-decoration: none !important;
font-weight: 600 !important;
}
.custom-footer a:hover {
text-decoration: underline !important;
}
"""
) as demo:
gr.HTML("""
SR Vela Compiler
Upload your quantized TFLite model to get SR NPU performance estimates.
These estimates are being done in a Vela Toolchain Environment specific to SR110 configurations.
""")
with gr.Row():
with gr.Column(scale=1):
model_file = gr.File(
label="",
file_types=[".tflite"],
type="filepath",
height=280
)
compile_btn = gr.Button(
"Compile Model for SR110",
variant="primary",
size="lg",
elem_classes=["btn-primary"]
)
with gr.Group(elem_classes=["card"]):
gr.HTML('')
with gr.Column(elem_classes=["card-content"]):
download_btn = gr.DownloadButton(
label="Download Compiled Model",
visible=False,
size="sm"
)
download_logs_btn = gr.DownloadButton(
label="Download Logs Per Layer",
visible=False,
size="sm"
)
with gr.Column(scale=1):
summary_table = gr.HTML(elem_classes=["results-summary-box"])
# Footer
gr.HTML("""
""")
def wrapped_run_vela(model_file):
if model_file is None:
# Show error in a card-like box
return (
''
''
'
'
'No model file uploaded.'
'
',
gr.update(visible=False, value=None),
gr.update(visible=False, value=None)
)
summary_html, compiled_model_dl, per_layer_csv = run_vela(model_file)
return (
summary_html,
gr.update(visible=compiled_model_dl is not None, value=compiled_model_dl),
gr.update(visible=per_layer_csv is not None, value=per_layer_csv)
)
compile_btn.click(
wrapped_run_vela,
inputs=[model_file],
outputs=[summary_table, download_btn, download_logs_btn]
)
# Launch the demo
if __name__ == "__main__":
demo.launch(show_api=False)