Danh sách bài viết

Bài 11: Khi nào dùng Gradio, khi nào dùng Streamlit

So sánh kỹ thuật Gradio và Streamlit theo execution model, state management, layout, chatbot UI, tốc độ build và deploy — phân tích theo use case để chọn đúng công cụ cho từng bài toán.

27/05/2026
1 lượt xem
1

Mục tiêu bài học

Sau bài này bạn sẽ:

  • Hiểu rõ sự khác biệt execution model giữa Gradio và Streamlit và tại sao nó ảnh hưởng đến cách thiết kế app.
  • Biết framework nào phù hợp với use case nào dựa trên tiêu chí kỹ thuật cụ thể.
  • Tránh chọn sai framework rồi mới phát hiện giới hạn ở giữa chừng dự án.
  • Nhận ra khi nào cả hai đều không phù hợp.

Bài này không dạy lại syntax của hai framework — xem bài 9 (Gradio) và bài 10 (Streamlit) nếu cần ôn lại. Bài này tập trung hoàn toàn vào so sánh kỹ thuật và quyết định chọn lựa.

2

Execution model — khác biệt cốt lõi

Đây là điểm khác biệt nền tảng nhất — ảnh hưởng đến mọi thứ từ cách viết code đến performance.

Gradio: event-driven

Gradio chạy theo mô hình event-driven. Mỗi component đăng ký handler riêng. Khi user click button, chỉ callback tương ứng được gọi — phần còn lại của app không chạy lại.

import gradio as gr

def process(text: str, threshold: float) -> str:
    return f"Processed: {text} (threshold={threshold})"

with gr.Blocks() as demo:
    inp = gr.Textbox(label="Input")
    slider = gr.Slider(0.0, 1.0, value=0.5)
    btn = gr.Button("Run")
    out = gr.Textbox(label="Output")

    # Chỉ hàm process() chạy khi click btn
    btn.click(fn=process, inputs=[inp, slider], outputs=out)

demo.launch()

Streamlit: script rerun

Streamlit chạy lại toàn bộ file Python từ đầu đến cuối mỗi lần user tương tác. Click button, kéo slider, gõ text input — đều trigger toàn bộ script chạy lại.

import streamlit as st

# Script này chạy lại từ đầu mỗi lần user tương tác

text = st.text_input("Input")
threshold = st.slider("Threshold", 0.0, 1.0, 0.5)

if st.button("Run"):
    # Chỉ đoạn code này phụ thuộc vào button
    # nhưng toàn bộ script đã chạy đến đây rồi
    st.write(f"Processed: {text} (threshold={threshold})")

Điều gì xảy ra khi user click button

Hành vi Gradio Streamlit
Code Python được chạy Chỉ callback đã đăng ký (btn.click) Toàn bộ script từ dòng 1 đến EOF
Biến Python thông thường Giữ nguyên trạng thái giữa các call Reset về giá trị ban đầu mỗi rerun
Model load bên ngoài cache Không bị load lại Load lại mỗi rerun nếu không cache
Phù hợp khi script dài Tốt — code ngoài callback không bị gọi Cần cẩn thận — mỗi dòng đều chạy lại

Hệ quả thực tế: Streamlit yêu cầu lập trình viên phải thiết kế cho việc chạy lại — cache, session_state, st.form để giảm rerun không cần thiết. Gradio không có vấn đề này vì chỉ chạy handler được gọi.

3

Layout và UX

Gradio

gr.Interface tạo layout cố định: input bên trái, output bên phải, nút Submit ở giữa. Phù hợp cho kiểu app "nhập vào → nhận kết quả". Layout này quen thuộc với người dùng cuối không có kỹ thuật.

gr.Blocks cho phép tự khai báo layout với gr.Row, gr.Column, gr.Tab. Tuy nhiên thiết kế vẫn nghiêng về dạng form: input → trigger → output. Khó xây dashboard nhiều tầng từ top đến bottom vì cấu trúc layout của Gradio không tự nhiên với scroll dài.

Theme: Gradio hỗ trợ gr.themes.Base(), gr.themes.Soft(), gr.themes.Monochrome(). Có thể custom qua theme builder tại gradio.app/guides/theming-guide.

with gr.Blocks(theme=gr.themes.Soft()) as demo:
    ...

Streamlit

Streamlit dùng mô hình vertical scroll — component được render từ trên xuống dưới theo thứ tự trong code. st.columns() tạo multi-column, st.sidebar tạo panel trái cố định, st.tabs() tạo tab ngang. Phù hợp với dashboard layout: sidebar filter + nội dung chính với nhiều chart.

Theme: cấu hình qua file .streamlit/config.toml:

[theme]
primaryColor = "#1f77b4"
backgroundColor = "#ffffff"
secondaryBackgroundColor = "#f0f2f6"
textColor = "#262730"
font = "sans serif"

Kết luận layout

Gradio phù hợp khi UI là form input/output đơn giản. Streamlit phù hợp khi cần dashboard phức tạp với filter sidebar, nhiều chart và bố cục nhiều cột.

4

Tốc độ build nguyên mẫu

Demo 1 model classifier

Gradio — 5 dòng:

import gradio as gr

def classify(text: str) -> dict:
    # giả sử model đã load ở module level
    return {"positive": 0.82, "negative": 0.18}

gr.Interface(fn=classify, inputs="text", outputs=gr.Label()).launch()

Streamlit — 10–15 dòng:

import streamlit as st

st.title("Text Classifier")

text = st.text_area("Input text")
if st.button("Classify"):
    # giả sử model đã load với @st.cache_resource
    result = {"positive": 0.82, "negative": 0.18}
    for label, score in result.items():
        st.metric(label=label, value=f"{score:.0%}")

Với use case demo 1 model, Gradio ít boilerplate hơn rõ ràng. gr.Interface tự tạo label, layout, nút Submit và hiển thị output.

Dashboard 5 metric + filter + chart

Streamlit — ~30 dòng: sidebar filter, 3 metric card, 1 line chart, 1 dataframe — có thể xây gọn trong 30 dòng vì component model của Streamlit thiên về top-down composition.

Gradio — ~60–80 dòng: cần gr.Blocks + nhiều gr.Row/gr.Column + quản lý event handler riêng cho mỗi filter. Sidebar là khái niệm không có trong Gradio — phải tự tạo bằng column layout.

5

Tốc độ runtime và chi phí

Gradio — nhanh hơn khi click đơn

Vì Gradio chỉ gọi callback đã đăng ký, mỗi lần click chỉ tốn thời gian của hàm đó. Với app đơn giản (1 button, 1 callback), latency phía Python rất thấp — phần lớn thời gian là inference model.

Streamlit — ảnh hưởng bởi độ dài script

Mỗi rerun chạy toàn bộ script. Nếu script có nhiều bước khởi tạo nặng không được cache, mỗi click đều chậm. Ví dụ: nếu quên @st.cache_resource trên model loader, mỗi rerun load lại model 500MB.

Với cache đúng cách (@st.cache_data, @st.cache_resource), Streamlit runtime tương đương Gradio cho các tác vụ thông thường.

Vấn đề với DataFrame lớn trong Streamlit

Streamlit serialize DataFrame sang JSON để gửi về browser. 100k dòng thường mất vài giây và render chậm. Nên luôn giới hạn số dòng hiển thị:

# Tránh st.table() với data lớn — render cả bảng tĩnh
# Dùng st.dataframe() với limit
st.dataframe(df.head(500))
st.caption(f"Hiển thị 500 / {len(df):,} dòng")

Gradio cũng có component gr.Dataframe nhưng cũng gặp vấn đề tương tự với data lớn. Cả hai đều không phải công cụ phù hợp để hiển thị dataset nhiều triệu dòng.

6

State management

Gradio: gr.State() per session

gr.State(default_value) tạo một ô nhớ per-session. Giá trị được truyền vào function như argument bình thường và trả về để cập nhật.

import gradio as gr

def add_to_history(user_msg: str, history: list) -> tuple[list, str]:
    history = history + [user_msg]   # không mutate list gốc
    return history, f"Đã thêm: {user_msg}. Tổng: {len(history)}"

with gr.Blocks() as demo:
    state = gr.State([])   # list rỗng ban đầu, per session

    msg_input = gr.Textbox(label="Message")
    btn = gr.Button("Add")
    summary = gr.Textbox(label="Summary")

    btn.click(
        fn=add_to_history,
        inputs=[msg_input, state],
        outputs=[state, summary],   # state được cập nhật, không hiển thị ra UI
    )

demo.launch()

Pattern này rõ ràng: state đi vào function, state mới đi ra function. Không có side effect ngầm.

Streamlit: st.session_state dict

st.session_state là dict tồn tại xuyên suốt các rerun trong cùng session. Truy cập trực tiếp từ bất kỳ đâu trong script.

import streamlit as st

if "history" not in st.session_state:
    st.session_state.history = []

msg = st.text_input("Message")
if st.button("Add"):
    st.session_state.history.append(msg)

st.write(f"Tổng: {len(st.session_state.history)}")

Tiện lợi hơn vì truy cập trực tiếp, nhưng dễ có side effect nếu script phức tạp — state bị đọc và ghi từ nhiều nơi. Gradio buộc state đi qua function signature, rõ ràng hơn về data flow.

So sánh nhanh

Tiêu chí Gradio (gr.State) Streamlit (session_state)
Cú pháp truy cập Qua function args/return Dict toàn cục st.session_state["key"]
Data flow Tường minh qua signature Ngầm — đọc/ghi từ bất kỳ đâu
Dễ debug Hơn khi nhiều handler Cần trace flow thủ công khi phức tạp
Tích hợp widget Qua inputs/outputs list Widget có thể tự bind vào key: st.text_input(key="x")
7

Multi-page

Streamlit: native multi-page

Streamlit hỗ trợ multi-page từ v1.10 qua thư mục pages/ — Streamlit tự tạo sidebar navigation. Từ v1.36, st.navigation() cho phép cấu hình tường minh hơn:

my_app/
├── app.py
└── pages/
    ├── 01_Dashboard.py
    └── 02_Chatbot.py

st.session_state persist khi user chuyển giữa các page — không cần truyền data qua query param hay database.

Gradio: không có "page" thực sự

Gradio có gr.TabbedInterfacegr.Tab bên trong gr.Blocks — nhưng đây là tab trong cùng một page, không phải routing giữa các URL khác nhau.

import gradio as gr

demo1 = gr.Interface(fn=classify, inputs="text", outputs="text")
demo2 = gr.Interface(fn=generate, inputs="text", outputs="text")

# Tab interface — cùng URL, chuyển qua tab
app = gr.TabbedInterface(
    [demo1, demo2],
    ["Classifier", "Generator"],
)
app.launch()

Gradio không thay đổi URL khi chuyển tab. Nếu cần routing thực sự (/app/dashboard, /app/chatbot), phải mount vào FastAPI và tự xử lý routing ở tầng FastAPI.

Kết luận

Nếu app cần nhiều trang riêng biệt với URL và sidebar navigation, Streamlit là lựa chọn tự nhiên hơn. Gradio phù hợp khi các "trang" đơn giản có thể đặt trong tab.

8

Chatbot UI

Gradio: gr.ChatInterface — ít code nhất

gr.ChatInterface tự render message list, nút Retry, Undo, Clear và quản lý history. Chỉ cần viết hàm nhận message + history:

import gradio as gr
from openai import OpenAI

client = OpenAI()

def chat_stream(message: str, history: list):
    messages = []
    for user_msg, assistant_msg in history:
        messages.append({"role": "user", "content": user_msg})
        messages.append({"role": "assistant", "content": assistant_msg})
    messages.append({"role": "user", "content": message})

    stream = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=messages,
        stream=True,
    )
    partial = ""
    for chunk in stream:
        partial += chunk.choices[0].delta.content or ""
        yield partial  # streaming: yield token từng phần

gr.ChatInterface(fn=chat_stream).launch()

Toàn bộ chatbot với streaming trong ~20 dòng. History được Gradio quản lý — không cần session_state.

Streamlit: st.chat_input + tự quản history

Streamlit có st.chat_input, st.chat_message, st.write_stream nhưng phải tự quản lý history trong session_state:

import streamlit as st
from openai import OpenAI

@st.cache_resource
def get_client():
    return OpenAI()

if "messages" not in st.session_state:
    st.session_state.messages = []

# Render toàn bộ history mỗi lần rerun
for msg in st.session_state.messages:
    with st.chat_message(msg["role"]):
        st.markdown(msg["content"])

if prompt := st.chat_input("Nhắn gì đó..."):
    st.session_state.messages.append({"role": "user", "content": prompt})
    with st.chat_message("user"):
        st.markdown(prompt)

    with st.chat_message("assistant"):
        stream = get_client().chat.completions.create(
            model="gpt-4o-mini",
            messages=st.session_state.messages,
            stream=True,
        )
        response = st.write_stream(
            chunk.choices[0].delta.content or ""
            for chunk in stream
            if chunk.choices[0].delta.content
        )
    st.session_state.messages.append({"role": "assistant", "content": response})

Khoảng 30 dòng — nhiều hơn Gradio, nhưng linh hoạt hơn: dễ thêm sidebar cấu hình, hiển thị nguồn RAG bên cạnh chat, tích hợp vào dashboard multi-page.

Khi nào chọn cái nào cho chatbot

  • Chatbot thuần, standalone, deploy nhanh lên HF Spaces → Gradio gọn hơn.
  • Chatbot là 1 trang trong dashboard lớn, cần sidebar filter, multi-page → Streamlit phù hợp hơn.
9

Deploy

Gradio

  • Hugging Face Spaces (SDK = Gradio): native support — đây là deployment path được Gradio docs ưu tiên. Tỷ lệ lớn các Space trên HF là Gradio app. Push code lên repo Space là xong.
  • Modal: hỗ trợ deploy Gradio app với GPU on-demand.
  • Self-host với uvicorn: Gradio dùng ASGI (Starlette/uvicorn) nội bộ — có thể mount vào FastAPI app hiện có qua gr.mount_gradio_app().
import gradio as gr
from fastapi import FastAPI

app = FastAPI()
gradio_app = gr.Interface(fn=predict, inputs="text", outputs="text")

# Gradio mount vào /demo, FastAPI vẫn chạy các route còn lại
app = gr.mount_gradio_app(app, gradio_app, path="/demo")

Streamlit

  • Streamlit Community Cloud: link GitHub repo → deploy tự động. Free tier: 1 GB RAM, public repo.
  • Hugging Face Spaces (SDK = Streamlit): cũng hỗ trợ, phù hợp khi cần GPU Space.
  • Self-host: chạy streamlit run app.py --server.port 8501 --server.headless true trong container. Streamlit dùng Tornado (không phải ASGI) — không thể mount vào FastAPI.
# Docker entrypoint cho Streamlit
streamlit run app.py \
  --server.port 8501 \
  --server.headless true \
  --server.address 0.0.0.0

Điểm khác biệt quan trọng về deploy

Gradio có thể chạy cùng process và cùng port với FastAPI API server — hữu ích khi bạn muốn team QA có UI test trong khi production traffic đi qua JSON API. Streamlit không có khả năng này vì dùng Tornado, không ASGI.

10

Khi nào chọn Gradio

  • Demo 1 model cho người không-tech: PM, designer, client cần thử model mà không muốn gọi API. gr.Interface tạo UI rõ ràng với 5 dòng code.
  • Chatbot standalone: gr.ChatInterface với streaming, retry, undo built-in — không cần viết boilerplate quản lý history.
  • Public demo nhanh trên Hugging Face Spaces: Gradio là SDK native của HF Spaces, quy trình push lên Space nhanh và không có friction.
  • Mount cùng API FastAPI hiện có: thêm UI test cho team nội bộ mà không cần server riêng — gr.mount_gradio_app cho phép Gradio chạy tại /demo trong khi FastAPI chạy các route còn lại.
  • App nhỏ, input → output rõ ràng: text classification, image segmentation, audio transcription, code generation — pattern form input/output của Gradio ăn khớp tự nhiên với dạng task này.
11

Khi nào chọn Streamlit

  • Dashboard nhiều chart + filter + metric: sidebar filter, metric card ở trên, line chart ở giữa, dataframe ở dưới — Streamlit's vertical scroll + column layout phù hợp tự nhiên hơn.
  • Internal tool multi-page: data exploration (trang 1), model monitoring (trang 2), chatbot (trang 3) — native multi-page với pages/ hoặc st.navigation().
  • App có workflow tuần tự nhiều bước: upload file → cấu hình → preview → export. Vertical scroll của Streamlit thể hiện trình tự các bước rõ hơn.
  • Plot phức tạp (Plotly, Altair, matplotlib): cả hai framework đều support, nhưng Streamlit tích hợp tự nhiên hơn — st.plotly_chart(fig), st.altair_chart(chart), st.pyplot(fig) đều hoạt động không cần convert.
  • Chatbot tích hợp trong dashboard lớn: khi chatbot là 1 trong nhiều page, cần kết hợp với filter/context từ các page khác qua session_state.
12

Khi không nên dùng cả hai

Cả Gradio và Streamlit đều là Python UI framework chạy single-process. Dưới đây là những trường hợp nên dùng công cụ khác.

Cần auth phức tạp (RBAC, SSO, OAuth)

Gradio có basic auth auth=(username, password) hoặc callback function — đủ cho team nội bộ nhỏ. Streamlit có st.secrets nhưng không có auth built-in cho SSO hay role-based access. Nếu cần phân quyền theo role (admin xem tab A, user thường chỉ xem tab B) hay đăng nhập qua Google/Azure AD — nên dùng React/Next.js + FastAPI backend với auth library đầy đủ.

Cần routing tùy chỉnh, SEO, SSR

Cả hai framework render phía server Python và không cung cấp SSR theo nghĩa web truyền thống, không có sitemap tự động, không có meta tag per-page. Nếu app cần indexable bởi search engine hoặc cần URL tùy chỉnh sâu — dùng web framework đầy đủ.

Bài toán scaling nhiều user đồng thời

Gradio và Streamlit đều chạy single-process Python. Gradio 4.x có queue tích hợp và concurrency limit cấu hình được (default_concurrency_limit), nhưng không tự horizontal scale. Streamlit tương tự — mỗi instance server là 1 process. Khi có hàng trăm concurrent user thường xuyên, cần cân nhắc:

  • Scale bằng cách chạy nhiều instance đằng sau load balancer (nhưng session_state/gr.State không share giữa các instance — cần external store nếu cần share state).
  • Hoặc chuyển sang FastAPI + React khi UI đủ phức tạp để đầu tư frontend riêng.

Cần custom JS sâu hoặc realtime WebSocket phức tạp

Gradio hỗ trợ gr.HTML và custom JS ở mức cơ bản. Streamlit hỗ trợ st.components.v1.html() để nhúng HTML/JS tùy chỉnh. Nhưng nếu UI yêu cầu logic JavaScript phức tạp (canvas, WebGL, realtime collaboration) — đây không phải territory của hai framework này.

13

Bảng tổng hợp

Tiêu chí Gradio 4.x Streamlit 1.36+
Execution model Event-driven — chỉ chạy callback được gọi Script rerun — toàn bộ file chạy lại mỗi interact
State management gr.State per session, qua function args st.session_state dict, truy cập toàn cục
Layout mặc định Form input/output (trái–phải) Vertical scroll, sidebar, multi-column
Multi-page Tab trong cùng page, không có routing Native pages/ folder, st.navigation(), URL riêng
Chatbot built-in gr.ChatInterface — retry, undo, clear có sẵn st.chat_input + tự quản history trong session_state
Demo 1 model (LOC) ~5 dòng với gr.Interface ~10–15 dòng
Dashboard nhiều chart (LOC) ~60–80 dòng (Blocks + event) ~30 dòng
Tích hợp FastAPI Có — gr.mount_gradio_app(), ASGI Không — dùng Tornado riêng
Deploy native HF Spaces (Gradio SDK), Modal, self-host uvicorn Streamlit Community Cloud, HF Spaces (Streamlit SDK)
Auth built-in Basic auth (tuple hoặc callback) Không (cần custom hoặc third-party)
Plot libraries Plotly, matplotlib qua gr.Plot Plotly, Altair, matplotlib, Vega — tích hợp tự nhiên hơn