| import streamlit as st |
| import torch |
| import torch.nn.functional as F |
| from torchvision import transforms |
| from PIL import Image |
| import numpy as np |
| from io import BytesIO |
| import requests |
|
|
| from adavit_model import AdaViTDynamic |
|
|
| |
| |
| |
|
|
| st.set_page_config( |
| page_title="PatchWise — Adaptive Sparse ViT", |
| layout="wide", |
| initial_sidebar_state="collapsed", |
| ) |
|
|
| |
| |
| |
|
|
| st.markdown(""" |
| <style> |
| /* ── Fonts ────────────────────────────────────────────────────────────────── */ |
| @import url('https://fonts.googleapis.com/css2?family=DM+Mono:ital,wght@0,300;0,400;0,500;1,300&family=DM+Sans:ital,opsz,wght@0,9..40,300;0,9..40,400;0,9..40,500;1,9..40,300&display=swap'); |
| |
| /* ── Reset & base ─────────────────────────────────────────────────────────── */ |
| html, body, [class*="css"] { |
| font-family: 'DM Sans', sans-serif; |
| color: #e8e6e1; |
| } |
| |
| .stApp { |
| background-color: #0a0a0b; |
| background-image: |
| radial-gradient(ellipse 80% 50% at 50% -10%, rgba(56, 189, 248, 0.06) 0%, transparent 70%), |
| linear-gradient(180deg, #0a0a0b 0%, #0c0d10 100%); |
| } |
| |
| .block-container { |
| max-width: 1240px; |
| padding: 0 2rem 6rem 2rem; |
| margin: 0 auto; |
| } |
| |
| /* ── Hide streamlit chrome ────────────────────────────────────────────────── */ |
| #MainMenu, footer, header { visibility: hidden; } |
| [data-testid="stDecoration"] { display: none; } |
| section[data-testid="stSidebar"] { display: none; } |
| |
| /* ── Divider line ─────────────────────────────────────────────────────────── */ |
| .rule { |
| height: 1px; |
| background: linear-gradient(90deg, transparent, rgba(248,248,242,0.08) 20%, rgba(248,248,242,0.08) 80%, transparent); |
| margin: 0; |
| border: none; |
| } |
| |
| /* ── Nav bar ──────────────────────────────────────────────────────────────── */ |
| .nav { |
| display: flex; |
| align-items: center; |
| justify-content: space-between; |
| padding: 1.5rem 0 1.5rem 0; |
| margin-bottom: 0; |
| } |
| |
| .nav-wordmark { |
| font-family: 'DM Mono', monospace; |
| font-weight: 500; |
| font-size: 1rem; |
| letter-spacing: 0.18em; |
| color: #f8f8f2; |
| text-transform: uppercase; |
| } |
| |
| .nav-tag { |
| font-family: 'DM Mono', monospace; |
| font-size: 0.68rem; |
| color: rgba(248,248,242,0.35); |
| letter-spacing: 0.12em; |
| text-transform: uppercase; |
| } |
| |
| /* ── Hero ─────────────────────────────────────────────────────────────────── */ |
| .hero-wrap { |
| padding: 5rem 0 4.5rem 0; |
| } |
| |
| .hero-eyebrow { |
| font-family: 'DM Mono', monospace; |
| font-size: 0.7rem; |
| letter-spacing: 0.22em; |
| text-transform: uppercase; |
| color: #38bdf8; |
| margin-bottom: 1.5rem; |
| display: flex; |
| align-items: center; |
| gap: 0.75rem; |
| } |
| |
| .hero-eyebrow::before { |
| content: ''; |
| display: inline-block; |
| width: 2rem; |
| height: 1px; |
| background: #38bdf8; |
| } |
| |
| .hero-h1 { |
| font-family: 'DM Sans', sans-serif !important; |
| font-size: clamp(2.8rem, 6vw, 5rem) !important; |
| font-weight: 300 !important; |
| line-height: 1.05 !important; |
| letter-spacing: -0.03em !important; |
| color: #f8f8f2 !important; |
| margin-bottom: 1rem !important; |
| } |
| |
| .hero-h1 em { |
| font-style: italic; |
| color: rgba(248,248,242,0.38); |
| } |
| |
| .hero-sub { |
| font-size: 1.05rem; |
| font-weight: 300; |
| color: rgba(248,248,242,0.55); |
| line-height: 1.75; |
| max-width: 640px; |
| margin-bottom: 3rem; |
| } |
| |
| .hero-sub strong { |
| color: rgba(248,248,242,0.85); |
| font-weight: 400; |
| } |
| |
| /* ── Stat strip ───────────────────────────────────────────────────────────── */ |
| .stat-strip { |
| display: grid; |
| grid-template-columns: repeat(4, 1fr); |
| gap: 1px; |
| background: rgba(248,248,242,0.07); |
| border: 1px solid rgba(248,248,242,0.07); |
| border-radius: 2px; |
| overflow: hidden; |
| margin-bottom: 5rem; |
| } |
| |
| .stat-cell { |
| background: #0c0d10; |
| padding: 1.5rem 1.75rem; |
| transition: background 0.2s; |
| } |
| |
| .stat-cell:hover { background: #111215; } |
| |
| .stat-num { |
| font-family: 'DM Mono', monospace; |
| font-size: 2rem; |
| font-weight: 500; |
| color: #f8f8f2; |
| line-height: 1; |
| margin-bottom: 0.4rem; |
| letter-spacing: -0.03em; |
| } |
| |
| .stat-num span { |
| font-size: 1.1rem; |
| font-weight: 300; |
| color: #38bdf8; |
| } |
| |
| .stat-label { |
| font-size: 0.72rem; |
| letter-spacing: 0.12em; |
| text-transform: uppercase; |
| color: rgba(248,248,242,0.35); |
| font-family: 'DM Mono', monospace; |
| } |
| |
| /* ── Section heading ──────────────────────────────────────────────────────── */ |
| .sec-head { |
| display: flex; |
| align-items: baseline; |
| gap: 1rem; |
| margin-bottom: 1.75rem; |
| } |
| |
| .sec-num { |
| font-family: 'DM Mono', monospace; |
| font-size: 0.65rem; |
| color: rgba(248,248,242,0.25); |
| letter-spacing: 0.1em; |
| } |
| |
| .sec-title { |
| font-size: 0.75rem; |
| font-weight: 400; |
| letter-spacing: 0.18em; |
| text-transform: uppercase; |
| color: rgba(248,248,242,0.45); |
| font-family: 'DM Mono', monospace; |
| } |
| |
| /* ── Upload zone ──────────────────────────────────────────────────────────── */ |
| [data-testid="stFileUploader"] { |
| background: transparent !important; |
| border: 1px solid rgba(248,248,242,0.09) !important; |
| border-radius: 2px !important; |
| padding: 0.5rem 1rem !important; |
| transition: border-color 0.2s; |
| } |
| |
| [data-testid="stFileUploader"]:hover { |
| border-color: rgba(56,189,248,0.35) !important; |
| } |
| |
| [data-testid="stFileUploader"] * { |
| color: rgba(248,248,242,0.55) !important; |
| font-family: 'DM Mono', monospace !important; |
| font-size: 0.82rem !important; |
| } |
| |
| [data-testid="stFileUploader"] button { |
| border: 1px solid rgba(56,189,248,0.4) !important; |
| background: transparent !important; |
| color: #38bdf8 !important; |
| border-radius: 1px !important; |
| font-size: 0.75rem !important; |
| letter-spacing: 0.08em !important; |
| padding: 0.35rem 1rem !important; |
| } |
| |
| /* ── Sample thumbnails ────────────────────────────────────────────────────── */ |
| .thumb-label { |
| font-family: 'DM Mono', monospace; |
| font-size: 0.65rem; |
| letter-spacing: 0.12em; |
| text-transform: uppercase; |
| color: rgba(248,248,242,0.3); |
| margin-top: 0.5rem; |
| text-align: center; |
| } |
| |
| /* ── Streamlit image border ───────────────────────────────────────────────── */ |
| [data-testid="stImage"] img { |
| border: 1px solid rgba(248,248,242,0.08); |
| border-radius: 2px; |
| } |
| |
| /* ── Streamlit button ─────────────────────────────────────────────────────── */ |
| .stButton button { |
| width: 100% !important; |
| background: transparent !important; |
| border: 1px solid rgba(248,248,242,0.1) !important; |
| color: rgba(248,248,242,0.45) !important; |
| font-family: 'DM Mono', monospace !important; |
| font-size: 0.68rem !important; |
| letter-spacing: 0.1em !important; |
| text-transform: uppercase !important; |
| padding: 0.45rem 0.75rem !important; |
| border-radius: 1px !important; |
| transition: all 0.15s !important; |
| margin-top: 0.35rem !important; |
| } |
| |
| .stButton button:hover { |
| border-color: rgba(56,189,248,0.45) !important; |
| color: #38bdf8 !important; |
| background: rgba(56,189,248,0.04) !important; |
| } |
| |
| /* ── Result panel ─────────────────────────────────────────────────────────── */ |
| .result-panel { |
| border: 1px solid rgba(248,248,242,0.08); |
| border-radius: 2px; |
| padding: 2rem; |
| background: #0c0d10; |
| height: 100%; |
| } |
| |
| .result-class { |
| font-family: 'DM Mono', monospace; |
| font-size: 0.65rem; |
| letter-spacing: 0.2em; |
| text-transform: uppercase; |
| color: rgba(248,248,242,0.3); |
| margin-bottom: 0.5rem; |
| } |
| |
| .result-label { |
| font-size: 2.6rem; |
| font-weight: 300; |
| letter-spacing: -0.03em; |
| color: #f8f8f2; |
| line-height: 1; |
| margin-bottom: 0.25rem; |
| text-transform: capitalize; |
| } |
| |
| .result-conf { |
| font-family: 'DM Mono', monospace; |
| font-size: 3.5rem; |
| font-weight: 500; |
| letter-spacing: -0.04em; |
| color: #38bdf8; |
| line-height: 1; |
| margin-bottom: 0.5rem; |
| } |
| |
| .result-conf-label { |
| font-family: 'DM Mono', monospace; |
| font-size: 0.65rem; |
| letter-spacing: 0.15em; |
| text-transform: uppercase; |
| color: rgba(248,248,242,0.25); |
| margin-bottom: 2rem; |
| } |
| |
| .metric-row { |
| display: flex; |
| flex-direction: column; |
| gap: 0.75rem; |
| margin-top: 1.5rem; |
| padding-top: 1.5rem; |
| border-top: 1px solid rgba(248,248,242,0.06); |
| } |
| |
| .metric-line { |
| display: flex; |
| justify-content: space-between; |
| align-items: baseline; |
| } |
| |
| .metric-key { |
| font-family: 'DM Mono', monospace; |
| font-size: 0.65rem; |
| letter-spacing: 0.1em; |
| text-transform: uppercase; |
| color: rgba(248,248,242,0.3); |
| } |
| |
| .metric-val { |
| font-family: 'DM Mono', monospace; |
| font-size: 0.82rem; |
| font-weight: 500; |
| color: rgba(248,248,242,0.75); |
| } |
| |
| .bar-wrap { |
| height: 2px; |
| background: rgba(248,248,242,0.07); |
| border-radius: 1px; |
| overflow: hidden; |
| margin-top: 0.25rem; |
| } |
| |
| .bar-fill { |
| height: 100%; |
| background: #38bdf8; |
| border-radius: 1px; |
| transition: width 0.6s ease; |
| } |
| |
| .bar-fill-warn { |
| background: #fb923c; |
| } |
| |
| /* ── Patch map label ──────────────────────────────────────────────────────── */ |
| .map-label { |
| font-family: 'DM Mono', monospace; |
| font-size: 0.65rem; |
| letter-spacing: 0.15em; |
| text-transform: uppercase; |
| color: rgba(248,248,242,0.25); |
| margin-bottom: 0.5rem; |
| } |
| |
| /* ── Architecture table ───────────────────────────────────────────────────── */ |
| .arch-table { |
| width: 100%; |
| border-collapse: collapse; |
| font-size: 0.83rem; |
| } |
| |
| .arch-table tr { |
| border-bottom: 1px solid rgba(248,248,242,0.05); |
| } |
| |
| .arch-table tr:last-child { |
| border-bottom: none; |
| } |
| |
| .arch-table td { |
| padding: 0.8rem 0; |
| line-height: 1.4; |
| } |
| |
| .arch-table td:first-child { |
| font-family: 'DM Mono', monospace; |
| font-size: 0.68rem; |
| letter-spacing: 0.08em; |
| text-transform: uppercase; |
| color: rgba(248,248,242,0.3); |
| width: 42%; |
| padding-right: 1.5rem; |
| } |
| |
| .arch-table td:last-child { |
| color: rgba(248,248,242,0.75); |
| font-weight: 300; |
| } |
| |
| /* ── Contribution items ───────────────────────────────────────────────────── */ |
| .contrib-item { |
| display: flex; |
| gap: 1.25rem; |
| align-items: flex-start; |
| padding: 1rem 0; |
| border-bottom: 1px solid rgba(248,248,242,0.05); |
| } |
| |
| .contrib-item:last-child { border-bottom: none; } |
| |
| .contrib-num { |
| font-family: 'DM Mono', monospace; |
| font-size: 0.65rem; |
| color: rgba(56,189,248,0.6); |
| letter-spacing: 0.05em; |
| padding-top: 0.1rem; |
| min-width: 1.5rem; |
| } |
| |
| .contrib-text { |
| font-size: 0.88rem; |
| font-weight: 300; |
| color: rgba(248,248,242,0.65); |
| line-height: 1.65; |
| } |
| |
| .contrib-text strong { |
| color: rgba(248,248,242,0.88); |
| font-weight: 400; |
| } |
| |
| /* ── Phase timeline ───────────────────────────────────────────────────────── */ |
| .phase-wrap { |
| display: flex; |
| flex-direction: column; |
| gap: 0; |
| } |
| |
| .phase-item { |
| display: grid; |
| grid-template-columns: 7rem 1fr; |
| gap: 1.5rem; |
| padding: 1.5rem 0; |
| border-bottom: 1px solid rgba(248,248,242,0.05); |
| align-items: start; |
| } |
| |
| .phase-item:last-child { border-bottom: none; } |
| |
| .phase-tag { |
| font-family: 'DM Mono', monospace; |
| font-size: 0.65rem; |
| letter-spacing: 0.1em; |
| text-transform: uppercase; |
| color: rgba(248,248,242,0.2); |
| padding-top: 0.1rem; |
| } |
| |
| .phase-name { |
| font-size: 0.88rem; |
| font-weight: 400; |
| color: rgba(248,248,242,0.85); |
| margin-bottom: 0.3rem; |
| } |
| |
| .phase-desc { |
| font-size: 0.8rem; |
| font-weight: 300; |
| color: rgba(248,248,242,0.4); |
| line-height: 1.6; |
| } |
| |
| .phase-pill { |
| display: inline-block; |
| margin-top: 0.5rem; |
| padding: 0.2rem 0.6rem; |
| font-family: 'DM Mono', monospace; |
| font-size: 0.58rem; |
| letter-spacing: 0.08em; |
| text-transform: uppercase; |
| border-radius: 1px; |
| } |
| |
| .pill-resolved { |
| background: rgba(56,189,248,0.08); |
| color: #38bdf8; |
| border: 1px solid rgba(56,189,248,0.2); |
| } |
| |
| .pill-limited { |
| background: rgba(251,146,60,0.08); |
| color: #fb923c; |
| border: 1px solid rgba(251,146,60,0.2); |
| } |
| |
| /* ── Upload box placeholder ───────────────────────────────────────────────── */ |
| .upload-placeholder { |
| border: 1px solid rgba(248,248,242,0.07); |
| border-radius: 2px; |
| padding: 3.5rem 2rem; |
| text-align: center; |
| background: #0c0d10; |
| } |
| |
| .upload-placeholder p { |
| font-family: 'DM Mono', monospace; |
| font-size: 0.72rem; |
| letter-spacing: 0.12em; |
| text-transform: uppercase; |
| color: rgba(248,248,242,0.2); |
| line-height: 2; |
| margin: 0; |
| } |
| |
| /* ── Footer ───────────────────────────────────────────────────────────────── */ |
| .footer { |
| margin-top: 6rem; |
| padding-top: 2rem; |
| border-top: 1px solid rgba(248,248,242,0.06); |
| display: flex; |
| justify-content: space-between; |
| align-items: center; |
| } |
| |
| .footer-left { |
| font-family: 'DM Mono', monospace; |
| font-size: 0.65rem; |
| letter-spacing: 0.1em; |
| text-transform: uppercase; |
| color: rgba(248,248,242,0.2); |
| } |
| |
| .footer-right { |
| font-size: 0.75rem; |
| color: rgba(248,248,242,0.18); |
| font-weight: 300; |
| } |
| |
| /* ── Streamlit image override ─────────────────────────────────────────────── */ |
| [data-testid="stImage"] { |
| border-radius: 2px; |
| overflow: hidden; |
| } |
| |
| /* ── Hide streamlit label gaps ────────────────────────────────────────────── */ |
| .stFileUploader > label { display: none !important; } |
| div[data-testid="stVerticalBlock"] > div:empty { display: none; } |
| |
| /* ── Expander ─────────────────────────────────────────────────────────────── */ |
| details { |
| border: 1px solid rgba(248,248,242,0.07) !important; |
| border-radius: 2px !important; |
| background: #0c0d10 !important; |
| } |
| |
| summary { |
| font-family: 'DM Mono', monospace !important; |
| font-size: 0.72rem !important; |
| letter-spacing: 0.1em !important; |
| text-transform: uppercase !important; |
| color: rgba(248,248,242,0.4) !important; |
| padding: 1rem 1.25rem !important; |
| } |
| |
| </style> |
| """, unsafe_allow_html=True) |
|
|
|
|
| |
| |
| |
|
|
| device = torch.device("cpu") |
|
|
| @st.cache_resource |
| def load_model(): |
| model = AdaViTDynamic( |
| image_size=32, |
| patch_size=4, |
| num_classes=10, |
| dim=256, |
| depth=6, |
| heads=8, |
| mlp_dim=512, |
| ) |
| checkpoint = torch.load("best_model.pth", map_location=device) |
| model.load_state_dict(checkpoint, strict=False) |
| model.eval() |
| return model |
|
|
| model = load_model() |
|
|
| CLASSES = [ |
| "airplane", "automobile", "bird", "cat", "deer", |
| "dog", "frog", "horse", "ship", "truck" |
| ] |
|
|
| transform = transforms.Compose([ |
| transforms.Resize((32, 32)), |
| transforms.ToTensor(), |
| ]) |
|
|
|
|
| |
| |
| |
|
|
| def run_inference(image: Image.Image): |
| """Returns (predicted_class, confidence, keep_rate, flops_saved, patch_mask_8x8).""" |
| tensor = transform(image).unsqueeze(0) |
| with torch.no_grad(): |
| outputs = model(tensor) |
|
|
| |
| if isinstance(outputs, tuple): |
| raw, extras = outputs[0], outputs[1] if len(outputs) > 1 else {} |
| else: |
| raw = outputs |
|
|
| if isinstance(raw, dict): |
| logits = raw["logits"] |
| mask_flat = raw.get("mask", None) |
| else: |
| logits = raw |
| mask_flat = extras.get("mask", None) if isinstance(extras, dict) else None |
|
|
| probs = F.softmax(logits, dim=1) |
| pred_idx = torch.argmax(probs, dim=1).item() |
| confidence = probs[0][pred_idx].item() |
|
|
| if mask_flat is not None: |
| mask = mask_flat[0].cpu().numpy() |
| keep_rate = float(mask.mean()) * 100 |
| patch_grid = mask.reshape(8, 8) |
| else: |
| keep_rate = 100.0 |
| patch_grid = np.ones((8, 8)) |
|
|
| flops_saved = 100.0 - keep_rate |
| return CLASSES[pred_idx], confidence * 100, keep_rate, flops_saved, patch_grid |
|
|
|
|
| |
| |
| |
|
|
| |
| |
| |
| |
| |
| DEMO_SAMPLES = [ |
| ("Frog", [ |
| "https://images.pexels.com/photos/2062316/pexels-photo-2062316.jpeg", |
| "https://images.pexels.com/photos/70083/frog-macro-amphibian-green-70083.jpeg", |
| "https://images.pexels.com/photos/145683/pexels-photo-145683.jpeg", |
| "https://images.pexels.com/photos/1490908/pexels-photo-1490908.jpeg", |
| ]), |
| ("Automobile", [ |
| "https://images.pexels.com/photos/170811/pexels-photo-170811.jpeg", |
| "https://images.pexels.com/photos/1149831/pexels-photo-1149831.jpeg", |
| "https://images.pexels.com/photos/707046/pexels-photo-707046.jpeg", |
| "https://images.pexels.com/photos/3802510/pexels-photo-3802510.jpeg", |
| ]), |
| ] |
|
|
| @st.cache_resource(show_spinner=False) |
| def load_demo_samples(): |
| """For each demo class, try each fallback URL until one succeeds. |
| Downscales to 32x32 for inference, upscales to 256x256 for display.""" |
| import requests as _req |
|
|
| headers = { |
| "User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 " |
| "(KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", |
| "Referer": "https://www.pexels.com/", |
| "Accept": "image/webp,image/apng,image/*,*/*;q=0.8", |
| } |
|
|
| samples = [] |
| for label, urls in DEMO_SAMPLES: |
| img_32 = None |
| for url in urls: |
| try: |
| resp = _req.get(url, headers=headers, timeout=10) |
| if resp.status_code == 200: |
| img = Image.open(BytesIO(resp.content)).convert("RGB") |
| img_32 = img.resize((32, 32), Image.LANCZOS) |
| break |
| except Exception: |
| continue |
|
|
| if img_32 is None: |
| |
| img_32 = _make_synthetic(label) |
|
|
| img_256 = img_32.resize((256, 256), Image.NEAREST) |
| samples.append((label, img_32, img_256)) |
|
|
| return samples |
|
|
|
|
| def _make_synthetic(label: str) -> Image.Image: |
| """Generates a simple recognisable 32x32 stand-in if all URLs fail.""" |
| from PIL import ImageDraw, ImageFilter |
| img = Image.new("RGB", (32, 32), (20, 20, 28)) |
| draw = ImageDraw.Draw(img) |
| if label == "Dog": |
| draw.rectangle([0, 0, 32, 32], fill=(200, 170, 120)) |
| draw.ellipse([8, 8, 24, 24], fill=(160, 120, 70)) |
| draw.ellipse([10, 5, 22, 15], fill=(170, 130, 80)) |
| draw.ellipse([7, 4, 13, 12], fill=(130, 90, 50)) |
| draw.ellipse([19, 4, 25, 12], fill=(130, 90, 50)) |
| elif label == "Frog": |
| draw.rectangle([0, 0, 32, 32], fill=(60, 120, 50)) |
| draw.ellipse([6, 10, 26, 28], fill=(50, 160, 45)) |
| draw.ellipse([8, 4, 24, 18], fill=(60, 175, 55)) |
| draw.ellipse([7, 3, 14, 11], fill=(220, 220, 60)) |
| draw.ellipse([18, 3, 25, 11], fill=(220, 220, 60)) |
| draw.ellipse([8, 4, 13, 10], fill=(20, 70, 20)) |
| draw.ellipse([19, 4, 24, 10], fill=(20, 70, 20)) |
| elif label == "Ship": |
| draw.rectangle([0, 0, 32, 18], fill=(100, 150, 210)) |
| draw.rectangle([0, 18, 32, 32], fill=(40, 90, 160)) |
| draw.polygon([(3, 18), (29, 18), (27, 25), (5, 25)], fill=(230, 230, 230)) |
| draw.rectangle([8, 11, 24, 18], fill=(200, 200, 200)) |
| draw.rectangle([12, 6, 20, 11], fill=(210, 210, 210)) |
| draw.rectangle([15, 2, 17, 6], fill=(180, 50, 50)) |
| elif label == "Automobile": |
| draw.rectangle([0, 0, 32, 32], fill=(160, 185, 160)) |
| draw.rectangle([0, 24, 32, 32], fill=(70, 70, 70)) |
| draw.rectangle([3, 15, 29, 24], fill=(200, 40, 40)) |
| draw.polygon([(8, 9), (24, 9), (28, 15), (4, 15)], fill=(175, 30, 30)) |
| draw.rectangle([9, 10, 15, 14], fill=(140, 200, 230)) |
| draw.rectangle([17, 10, 23, 14], fill=(140, 200, 230)) |
| draw.ellipse([4, 20, 12, 28], fill=(25, 25, 25)) |
| draw.ellipse([20, 20, 28, 28], fill=(25, 25, 25)) |
| return img.filter(ImageFilter.GaussianBlur(0.3)) |
|
|
|
|
| |
| if "selected_sample_idx" not in st.session_state: |
| st.session_state.selected_sample_idx = None |
|
|
|
|
| |
| |
| |
|
|
| st.markdown(""" |
| <div class="nav"> |
| <span class="nav-wordmark">PatchWise</span> |
| <span class="nav-tag">UCE Osmania University · CSE 2025–26</span> |
| </div> |
| <div class="rule"></div> |
| """, unsafe_allow_html=True) |
|
|
|
|
| |
| |
| |
|
|
| st.markdown(""" |
| <div class="hero-wrap"> |
| <div class="hero-eyebrow">Adaptive Sparse Vision Transformers</div> |
| <h1 class="hero-h1"> |
| Not every patch<br><em>deserves</em> attention. |
| </h1> |
| <p class="hero-sub"> |
| PatchWise uses a reinforcement-learned policy to decide, per image, |
| which patches are worth computing — and physically drops the rest. |
| <strong>Same accuracy. Half the FLOPs.</strong> |
| </p> |
| </div> |
| """, unsafe_allow_html=True) |
|
|
| |
| st.markdown(""" |
| <div class="stat-strip"> |
| <div class="stat-cell"> |
| <div class="stat-num">54.2<span>%</span></div> |
| <div class="stat-label">Attention FLOPs saved</div> |
| </div> |
| <div class="stat-cell"> |
| <div class="stat-num">79.4<span>%</span></div> |
| <div class="stat-label">CIFAR-10 accuracy</div> |
| </div> |
| <div class="stat-cell"> |
| <div class="stat-num">2033<span>fps</span></div> |
| <div class="stat-label">Jetson AGX Orin</div> |
| </div> |
| <div class="stat-cell"> |
| <div class="stat-num">0.49<span>ms</span></div> |
| <div class="stat-label">Inference latency</div> |
| </div> |
| </div> |
| <div class="rule"></div> |
| """, unsafe_allow_html=True) |
|
|
|
|
| |
| |
| |
|
|
| st.markdown("<div style='height:3rem'></div>", unsafe_allow_html=True) |
| st.markdown(""" |
| <div class="sec-head"> |
| <span class="sec-num">01</span> |
| <span class="sec-title">Live Sparse Inference</span> |
| </div> |
| """, unsafe_allow_html=True) |
|
|
| |
| with st.spinner("Fetching samples…"): |
| demo_samples = load_demo_samples() |
|
|
| |
| uploaded = st.file_uploader( |
| "Upload", |
| type=["png", "jpg", "jpeg"], |
| label_visibility="collapsed", |
| ) |
|
|
| st.markdown("<div style='height:1.5rem'></div>", unsafe_allow_html=True) |
|
|
| |
| st.markdown(""" |
| <p style="font-family:'DM Mono',monospace;font-size:0.65rem; |
| letter-spacing:0.15em;text-transform:uppercase; |
| color:rgba(248,248,242,0.25);margin-bottom:0.85rem;"> |
| Or try a sample — processed at CIFAR-10 resolution (32×32) |
| </p> |
| """, unsafe_allow_html=True) |
|
|
| s_cols = st.columns(len(demo_samples), gap="medium") |
|
|
| for i, (label, img_32, img_256) in enumerate(demo_samples): |
| with s_cols[i]: |
| st.image(img_256, use_container_width=True) |
|
|
| if st.button(label, key=f"sample_{i}"): |
| st.session_state.selected_sample_idx = i |
| uploaded = None |
|
|
| |
|
|
| active_image = None |
| active_label = None |
|
|
| if uploaded is not None: |
| active_image = Image.open(uploaded).convert("RGB") |
| active_label = "uploaded" |
| st.session_state.selected_sample_idx = None |
| elif st.session_state.selected_sample_idx is not None: |
| idx = st.session_state.selected_sample_idx |
| active_label = demo_samples[idx][0] |
| active_image = demo_samples[idx][1] |
|
|
| |
|
|
| st.markdown("<div style='height:1.75rem'></div>", unsafe_allow_html=True) |
|
|
| if active_image is not None: |
| pred_class, confidence, keep_rate, flops_saved, patch_grid = run_inference(active_image) |
|
|
| out_img_col, out_patch_col, out_result_col = st.columns([1, 1, 1.4], gap="large") |
|
|
| with out_img_col: |
| st.markdown('<p class="map-label">Input image</p>', unsafe_allow_html=True) |
| st.image(active_image, use_container_width=True) |
|
|
| with out_patch_col: |
| st.markdown('<p class="map-label">Active patch map</p>', unsafe_allow_html=True) |
| |
| patch_vis = np.zeros((patch_grid.shape[0], patch_grid.shape[1], 3), dtype=np.uint8) |
| patch_vis[patch_grid == 1] = [56, 189, 248] |
| patch_vis[patch_grid == 0] = [18, 18, 22] |
| patch_img = Image.fromarray(patch_vis).resize((128, 128), Image.NEAREST) |
| st.image(patch_img, use_container_width=True) |
|
|
| with out_result_col: |
| st.markdown(f""" |
| <div class="result-panel"> |
| <div class="result-class">Prediction</div> |
| <div class="result-label">{pred_class}</div> |
| |
| <div style="height:1.25rem"></div> |
| |
| <div class="result-class">Confidence</div> |
| <div class="result-conf">{confidence:.1f}<span style="font-size:1.4rem;color:rgba(56,189,248,0.6)">%</span></div> |
| |
| <div class="metric-row"> |
| <div> |
| <div class="metric-line"> |
| <span class="metric-key">Patches kept</span> |
| <span class="metric-val">{keep_rate:.1f}%</span> |
| </div> |
| <div class="bar-wrap"> |
| <div class="bar-fill" style="width:{keep_rate:.1f}%"></div> |
| </div> |
| </div> |
| <div> |
| <div class="metric-line"> |
| <span class="metric-key">FLOPs saved</span> |
| <span class="metric-val">{flops_saved:.1f}%</span> |
| </div> |
| <div class="bar-wrap"> |
| <div class="bar-fill bar-fill-warn" style="width:{flops_saved:.1f}%"></div> |
| </div> |
| </div> |
| <div> |
| <div class="metric-line"> |
| <span class="metric-key">Routing policy</span> |
| <span class="metric-val">A3C · EMA-controlled</span> |
| </div> |
| </div> |
| </div> |
| </div> |
| """, unsafe_allow_html=True) |
|
|
| else: |
| st.markdown(""" |
| <div class="upload-placeholder"> |
| <p>Upload an image or select a sample above<br>to observe adaptive patch routing live.</p> |
| </div> |
| """, unsafe_allow_html=True) |
|
|
|
|
| |
| |
| |
|
|
| st.markdown("<div style='height:4rem'></div>", unsafe_allow_html=True) |
| st.markdown('<div class="rule"></div>', unsafe_allow_html=True) |
| st.markdown("<div style='height:3rem'></div>", unsafe_allow_html=True) |
|
|
| st.markdown(""" |
| <div class="sec-head"> |
| <span class="sec-num">02</span> |
| <span class="sec-title">Three-Phase Development</span> |
| </div> |
| |
| <div class="phase-wrap"> |
| |
| <div class="phase-item"> |
| <div class="phase-tag">Phase 01</div> |
| <div> |
| <div class="phase-name">Gumbel-Softmax differentiable approximation</div> |
| <div class="phase-desc"> |
| Embedded a lightweight decision network into DeiT-Small. Used the Gumbel-Softmax |
| reparameterization trick to allow gradient flow through discrete keep/drop decisions. |
| Dual-objective loss: cross-entropy + MSE usage penalty toward a target keep rate. |
| </div> |
| <span class="phase-pill pill-limited">Training–inference gap · soft probs vs hard actions</span> |
| </div> |
| </div> |
| |
| <div class="phase-item"> |
| <div class="phase-tag">Phase 02</div> |
| <div> |
| <div class="phase-name">Hybrid single-agent A2C with Bernoulli policy</div> |
| <div class="phase-desc"> |
| Transitioned to true discrete decisions. Actor outputs one logit per patch sampled |
| via a Bernoulli distribution. Patches physically removed before attention blocks using |
| <code style="font-family:'DM Mono',monospace;font-size:0.78em;color:rgba(248,248,242,0.55)">x.detach()</code> |
| gating. Critic reduces variance via advantage estimation. |
| </div> |
| <span class="phase-pill pill-limited">High variance · correlated sequential data · gradient conflict</span> |
| </div> |
| </div> |
| |
| <div class="phase-item"> |
| <div class="phase-tag">Phase 03</div> |
| <div> |
| <div class="phase-name">Asynchronous A3C · Categorical policy · EMA controller</div> |
| <div class="phase-desc"> |
| Four parallel Hogwild! workers decorrelate training data. Categorical distribution |
| with two independent logits per patch eliminates gradient tug-of-war. EMA controller |
| enforces keep-rate budget. Attention-guided dense rewards from early transformer layers. |
| Curriculum warmup stabilises joint backbone + policy training. |
| </div> |
| <span class="phase-pill pill-resolved">Stable convergence · 79.4% accuracy @ ≈47.7% keep rate</span> |
| </div> |
| </div> |
| |
| </div> |
| """, unsafe_allow_html=True) |
|
|
|
|
| |
| |
| |
|
|
| st.markdown("<div style='height:4rem'></div>", unsafe_allow_html=True) |
| st.markdown('<div class="rule"></div>', unsafe_allow_html=True) |
| st.markdown("<div style='height:3rem'></div>", unsafe_allow_html=True) |
|
|
| contrib_col, arch_col = st.columns([1, 1], gap="large") |
|
|
| with contrib_col: |
| st.markdown(""" |
| <div class="sec-head"> |
| <span class="sec-num">03</span> |
| <span class="sec-title">Contributions</span> |
| </div> |
| |
| <div class="contrib-item"> |
| <span class="contrib-num">01</span> |
| <span class="contrib-text"> |
| <strong>Attention-Guided Reward Mechanism</strong> — dense, spatially-aware |
| feedback extracted from early transformer layers, replacing sparse end-of-episode signals. |
| </span> |
| </div> |
| <div class="contrib-item"> |
| <span class="contrib-num">02</span> |
| <span class="contrib-text"> |
| <strong>EMA Controller</strong> — exponential moving average of batch-wise keep rate |
| enforces a strict computational budget, eliminating keep-rate collapse. |
| </span> |
| </div> |
| <div class="contrib-item"> |
| <span class="contrib-num">03</span> |
| <span class="contrib-text"> |
| <strong>Curriculum Warmup</strong> — RL policy disabled during early epochs so the |
| backbone learns stable representations before pruning activates. |
| </span> |
| </div> |
| <div class="contrib-item"> |
| <span class="contrib-num">04</span> |
| <span class="contrib-text"> |
| <strong>Categorical → Bernoulli upgrade</strong> — dual-logit formulation decouples |
| accuracy and sparsity gradients, resolving the Phase 2 tug-of-war. |
| </span> |
| </div> |
| <div class="contrib-item"> |
| <span class="contrib-num">05</span> |
| <span class="contrib-text"> |
| <strong>Hogwild! A3C parallelisation</strong> — four asynchronous workers update a |
| shared global model, decorrelating data and accelerating convergence. |
| </span> |
| </div> |
| <div class="contrib-item"> |
| <span class="contrib-num">06</span> |
| <span class="contrib-text"> |
| <strong>Edge validation on NVIDIA Jetson AGX Orin</strong> — 2033 FPS, 0.49 ms |
| latency, confirming real-world deployment viability. |
| </span> |
| </div> |
| """, unsafe_allow_html=True) |
|
|
| with arch_col: |
| st.markdown(""" |
| <div class="sec-head"> |
| <span class="sec-num">04</span> |
| <span class="sec-title">Model Configuration</span> |
| </div> |
| |
| <table class="arch-table"> |
| <tr><td>Backbone</td><td>Custom Vision Transformer (DeiT-inspired)</td></tr> |
| <tr><td>RL Controller</td><td>Asynchronous A3C · 4 workers</td></tr> |
| <tr><td>Policy distribution</td><td>Categorical (Softmax, 2 logits / patch)</td></tr> |
| <tr><td>Dataset</td><td>CIFAR-10 · CIFAR-100 stress test</td></tr> |
| <tr><td>Input resolution</td><td>32 × 32 px</td></tr> |
| <tr><td>Patch size</td><td>4 × 4 → 64 tokens total</td></tr> |
| <tr><td>Embedding dim</td><td>256</td></tr> |
| <tr><td>Depth</td><td>6 transformer layers</td></tr> |
| <tr><td>Attention heads</td><td>8</td></tr> |
| <tr><td>Target keep rate</td><td>50% (EMA-enforced)</td></tr> |
| <tr><td>Edge hardware</td><td>NVIDIA Jetson AGX Orin</td></tr> |
| <tr><td>CIFAR-100 stability</td><td>~57% accuracy @ 300 epochs</td></tr> |
| </table> |
| """, unsafe_allow_html=True) |
|
|
|
|
| |
| |
| |
|
|
| st.markdown(""" |
| <div class="rule" style="margin-top:5rem"></div> |
| <div class="footer"> |
| <span class="footer-left">PatchWise · Adaptive Sparse Vision Transformers for Edge AI</span> |
| <span class="footer-right"> |
| Srivarsha S · Aila Kaushik · Adla Sahithi — |
| UCE Osmania University · CSE · May 2026 |
| </span> |
| </div> |
| """, unsafe_allow_html=True) |