Files
labs/Notebooks-CSAI/Lab8B_GPU_GenAI_Visual.ipynb

282 KiB

Module 8 — Nhìn tận mắt AI tạo sinh trên GPU: huấn luyện, chạy, và bị lừa — kèm đồng hồ đo GPU / CPU

Cách làm: bấm vào một ô code rồi nhấn Shift + Enter để chạy ô đó và sang ô kế tiếp. Chạy lần lượt từ trên xuống. Kernel phải là Python (lớp CSAI – GPU) (góc trên bên phải). Ô có dòng ✏️ SỬA là chỗ mời bạn đổi số / chữ rồi chạy lại. Sửa hỏng? Menu Kernel → Restart Kernel and Run All Cells. Cả notebook chạy hết khoảng 5–10 phút. GPU dùng chung cả lớp: số % GPU bạn thấy là của cả card, gồm cả bạn bên cạnh. Chạy xong nhớ chạy ô dọn dẹp cuối bài để trả bộ nhớ GPU.

🎯 Mục tiêu của bài

Học xong bài này, bạn:

  1. Thấy GAN học vẽ qua từng vòng: từ vệt nhiễu thành chữ số — cuộc chạy đua Generator vs Discriminator (Phần 2).
  2. Đo được GPU nhanh hơn CPU bao nhiêu lần trên cùng một việc, và vì sao (Phần 1).
  3. Tạo một ảnh deepfake của người không tồn tại trong chưa tới 1 giây, và đo nó tốn gì (Phần 3).
  4. Thấy AI bị lừa bởi nhiễu mắt thường không nhìn thấy (Phần 4).
  5. So huấn luyện với chạy (suy luận): cái nào ngốn thời gian, cái nào ngốn bộ nhớ GPU (Phần 5).

🗺️ Bài này: bài toán gì, mô hình nào, vì sao?

  • Bài toán: hiểu AI tạo sinh (GAN, diffusion) đủ để biết kẻ tấn công làm được gì và tốn bao nhiêu tài nguyên — và biết AI phòng thủ cũng bị lừa.
  • Dữ liệu: ảnh chữ số viết tay MNIST (Phần 1–2), câu mô tả bằng chữ (Phần 3), một ảnh con mèo (Phần 4).
  • Vì vậy chọn: DCGAN (GAN nhỏ, học được trong vài chục giây trên GPU), sd-turbo (mô hình diffusion đã học sẵn, chỉ chạy), ResNet-18 (mô hình nhận dạng ảnh đã học sẵn) để thử lừa.
  • Đồng hồ đo: mọi bước đều chạy kèm một "đồng hồ" ghi lại mỗi 0,2 giây: GPU bận bao nhiêu %, bộ nhớ GPU (VRAM), điện năng GPU, CPU của chính bạn.

📚 Mô hình và dữ liệu dùng trong bài

Mô hình Là gì Kích thước Học ở đâu / từ dữ liệu gì Dùng ở
DCGAN (GAN tích chập) 2 mạng đấu nhau: Generator vẽ ảnh giả từ nhiễu, Discriminator chấm thật / giả G ≈ 534 nghìn tham số, D ≈ 139 nghìn (in ra ở Phần 1) học ngay trong bài, từ đầu, trên MNIST Phần 1, 2, 2b
sd-turbo (Stability AI) mô hình diffusion tạo ảnh từ câu chữ, bản "chưng cất" của Stable Diffusion 2.1 để chạy 1 bước ≈ 1,3 tỷ tham số, ≈ 2,5 GB VRAM; gồm 4 khối (in ra ở Phần 3) học sẵn bởi nhà phát triển; ta chỉ chạy Phần 3
ResNet-18 (torchvision) mạng nhận dạng ảnh 1.000 loại vật (ImageNet) 11,7 triệu tham số; đúng 69,8% (top-1) trên ImageNet theo thông tin đi kèm học sẵn trên ImageNet-1K; ta chỉ chạy và tấn công Phần 4
Dữ liệu Là gì Lấy ở đâu
MNIST 60.000 ảnh chữ số viết tay 28 × 28 (có nhãn, nhưng GAN không dùng nhãn) bộ công khai kinh điển, tải sẵn ở /opt/csai/data/m8_gpu
Câu mô tả (prompt) chữ tiếng Anh bạn gõ do bạn viết — chỉ người hư cấu / đồ vật
Ảnh con mèo "chelsea" 1 ảnh mẫu đi kèm thư viện scikit-image
Số đo GPU / CPU GPU %, VRAM, điện năng, CPU của bạn — mỗi 0,1–0,2 giây đồng hồ DongHo đo trực tiếp máy lớp trong lúc bạn chạy

Pipeline — 7 bước của mọi bài toán AI trong khóa. Mỗi phần ghi 📍 Bước ở đầu:

Bước Câu hỏi phải trả lời Phần
1. Thu thập dữ liệu dữ liệu gì, có nhãn không? Phần 0
2. Làm sạch dữ liệu đưa ảnh về dạng mô hình học được Phần 0
3. Chọn mô hình phù hợp với bài toán GAN để học tạo ảnh; mô hình học sẵn để chạy Phần 0, 3, 4
4. Coding huấn luyện GAN, chạy diffusion, tấn công đối kháng Phần 1, 2, 3, 4
5. Lưu ý khi coding GPU "đói" dữ liệu, dùng chung GPU, hết bộ nhớ GPU, quên trả bộ nhớ Phần 2b, 3, 6
6. Đánh giá các mô hình nhìn ảnh + đo thời gian, VRAM, GPU % Phần 1–5
7. Quy tắc bàn tay phải ✋ ứng dụng SOC, tổng hợp Phần 5b, 6

📖 Thuật ngữ

Thuật ngữ Nghĩa
GPU card đồ họa: hàng nghìn lõi nhỏ làm cùng lúc rất nhiều phép nhân — đúng thứ mạng nơ-ron cần
VRAM bộ nhớ riêng của GPU. Mô hình + dữ liệu đang tính phải nằm trong VRAM; hết VRAM là lỗi "out of memory"
GPU % (utilization) trong 1 giây vừa qua, GPU bận tính bao nhiêu phần thời gian
Huấn luyện (training) mô hình học: lặp lại hàng nghìn lần "đoán → đo sai → sửa"
Suy luận / chạy (inference) mô hình đã học xong, chỉ dùng để trả lời — không sửa gì
epoch một lượt học hết toàn bộ dữ liệu
batch một nhóm ảnh đưa vào GPU cùng lúc (ví dụ 128 ảnh)
Generator (G) / Discriminator (D) G vẽ ảnh giả từ nhiễu ngẫu nhiên; D đoán ảnh thật hay giả. Hai bên cùng giỏi lên
loss con số "sai bao nhiêu" — mô hình học để giảm nó
nhiễu đối kháng thay đổi rất nhỏ trên ảnh, được tính toán để đánh lừa mô hình

🐍 Cú pháp hay gặp trong bài

Cú pháp Nghĩa
x.to(THIET_BI) chuyển dữ liệu / mô hình lên GPU (cuda:0) hoặc CPU (cpu)
with torch.no_grad(): chỉ chạy, không học — nhanh hơn, ít bộ nhớ hơn
with DongHo("tên") as dh: bật đồng hồ đo GPU / CPU trong suốt khối lệnh bên trong

Phần 0 — Thu thập dữ liệu và chiếc máy bạn đang dùng

📍 Bước 1 + 2 + 3 — Thu thập, làm sạch, chọn mô hình.

5 câu hỏi về dữ liệu Trả lời
Dữ liệu gì? MNIST: 60.000 ảnh chữ số viết tay 0–9, mỗi ảnh 28 × 28 điểm ảnh xám
Lấy ở đâu? bộ dữ liệu công khai kinh điển, đã tải sẵn vào máy lớp ở /opt/csai/data/m8_gpu
Thu thập thế nào? chữ số do học sinh và nhân viên điều tra dân số Mỹ viết tay, được quét và cắt về 28 × 28
Có nhãn chưa? có (mỗi ảnh ghi là số mấy), nhưng GAN không dùng nhãn: nó chỉ học "ảnh chữ số trông như thế nào"
Có cần làm sạch? chỉ cần đưa về thang −1 … 1 (Generator xuất ra đúng thang này) — không có dòng lỗi

Chọn mô hình: muốn tạo ảnh mới giống thật → mô hình tạo sinh. GAN nhỏ học được ngay trên lớp; mô hình tạo ảnh lớn (Phần 3) phải dùng bản đã học sẵn vì tự học tốn hàng nghìn giờ GPU.

Ô dưới nạp thư viện, dựng đồng hồ đo và cho bạn xem máy đang có gì.

In [ ]:
%matplotlib inline
import os, warnings
warnings.filterwarnings("ignore")         # ẩn cảnh báo kỹ thuật của thư viện (không ảnh hưởng kết quả)
os.environ["TQDM_DISABLE"] = "1"         # tắt thanh tiến trình khi nạp mô hình
import time, threading, gc
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import torch, torch.nn as nn
import torchvision as tv
import psutil, pynvml
from IPython.display import display, clear_output

pynvml.nvmlInit()
SO_GPU = pynvml.nvmlDeviceGetCount()

def gpu_trong_nhat():
    # Chọn GPU còn nhiều bộ nhớ trống nhất (cả lớp dùng chung 2 GPU)
    trong = [pynvml.nvmlDeviceGetMemoryInfo(pynvml.nvmlDeviceGetHandleByIndex(i)).free for i in range(SO_GPU)]
    return int(np.argmax(trong)), max(trong) / 2**30

for i in range(SO_GPU):
    h = pynvml.nvmlDeviceGetHandleByIndex(i)
    mem = pynvml.nvmlDeviceGetMemoryInfo(h)
    print(f"GPU {i}: {pynvml.nvmlDeviceGetName(h)} | VRAM {mem.total/2**30:.1f} GB, đang trống {mem.free/2**30:.1f} GB "
          f"| bận {pynvml.nvmlDeviceGetUtilizationRates(h).gpu}%")
CHI_SO_GPU, _ = gpu_trong_nhat()
THIET_BI = f"cuda:{CHI_SO_GPU}"
print(f"CPU: kernel của bạn được dùng {torch.get_num_threads()} luồng | bài này chạy trên {THIET_BI}")


class DongHo:
    """Đồng hồ đo: mỗi 0,2 giây ghi GPU %, VRAM, điện năng GPU, CPU % của chính bạn, RAM của bạn."""
    def __init__(self, ten, chi_so_gpu=None, chu_ky=0.2):
        self.ten, self.chu_ky = ten, chu_ky
        self.h = pynvml.nvmlDeviceGetHandleByIndex(CHI_SO_GPU if chi_so_gpu is None else chi_so_gpu)
        self.tien_trinh = psutil.Process()
        self.dong = []
    def _do(self):
        t0 = time.time(); self.tien_trinh.cpu_percent(None)
        while not self._dung.is_set():
            time.sleep(self.chu_ky)
            self.dong.append({"giây": time.time() - t0,
                              "GPU %": pynvml.nvmlDeviceGetUtilizationRates(self.h).gpu,
                              "VRAM cả card (GB)": pynvml.nvmlDeviceGetMemoryInfo(self.h).used / 2**30,
                              "điện GPU (W)": pynvml.nvmlDeviceGetPowerUsage(self.h) / 1000,
                              "CPU của bạn (%)": self.tien_trinh.cpu_percent(None),
                              "RAM của bạn (GB)": self.tien_trinh.memory_info().rss / 2**30})
    def __enter__(self):
        self._dung = threading.Event(); self.bat_dau = time.time()
        self.luong = threading.Thread(target=self._do, daemon=True); self.luong.start(); return self
    def __exit__(self, *a):
        self._dung.set(); self.luong.join(); self.thoi_gian = time.time() - self.bat_dau
        self.bang = pd.DataFrame(self.dong)
    def tom_tat(self):
        b = self.bang
        return {"việc": self.ten, "thời gian (s)": round(self.thoi_gian, 1),
                "GPU % trung bình": int(b["GPU %"].mean()) if len(b) else None,
                "VRAM cả card cao nhất (GB)": round(float(b["VRAM cả card (GB)"].max()), 2) if len(b) else None,
                "điện GPU cao nhất (W)": int(b["điện GPU (W)"].max()) if len(b) else None,
                "CPU của bạn trung bình (%)": int(b["CPU của bạn (%)"].mean()) if len(b) else None}

def ve_dong_ho(*cac_dong_ho, tieu_de=""):
    # Vẽ 4 đồng hồ theo thời gian; mỗi đồng hồ (mỗi việc) một màu
    cot = ["GPU %", "VRAM cả card (GB)", "điện GPU (W)", "CPU của bạn (%)"]
    fig, truc = plt.subplots(1, 4, figsize=(16, 3.2))
    for dh in cac_dong_ho:
        for t, c in zip(truc, cot):
            t.plot(dh.bang["giây"], dh.bang[c], label=dh.ten)
    for t, c in zip(truc, cot):
        t.set_title(c); t.set_xlabel("giây"); t.grid(alpha=0.3)
    truc[0].legend(fontsize=8); fig.suptitle(tieu_de); plt.tight_layout(); plt.show()

# Dữ liệu: MNIST đã tải sẵn, đưa ảnh về thang -1..1
bien_doi = tv.transforms.Compose([tv.transforms.ToTensor(), tv.transforms.Normalize([0.5], [0.5])])
mnist = tv.datasets.MNIST("/opt/csai/data/m8_gpu", train=True, download=False, transform=bien_doi)
# 📦 mnist = 60.000 cặp (ảnh 1×28×28 thang -1..1, nhãn 0-9); GAN chỉ dùng phần ảnh
print(f"MNIST: {len(mnist)} ảnh, mỗi ảnh {tuple(mnist[0][0].shape)}, giá trị từ {mnist[0][0].min():.0f} đến {mnist[0][0].max():.0f}")
fig, truc = plt.subplots(1, 10, figsize=(12, 1.5))
for k in range(10):
    truc[k].imshow(mnist[k][0][0], cmap="gray"); truc[k].set_title(str(mnist[k][1])); truc[k].axis("off")
plt.suptitle("10 ảnh THẬT đầu tiên (số trên đầu = nhãn)"); plt.show()

Kết quả (đo trên máy lớp, 2 GPU đang trống): GPU 0 / GPU 1: NVIDIA GeForce RTX 3060 Laptop GPU | VRAM 12.0 GB, đang trống 11.5 GB | bận 0% · CPU: kernel của bạn được dùng 2 luồng · MNIST: 60000 ảnh, mỗi ảnh (1, 28, 28), giá trị từ -1 đến 1 + một hàng 10 chữ số thật kèm nhãn.

👉 Hiểu:

  • Máy lớp có 2 GPU, mỗi cái 12 GB VRAM, cả lớp dùng chung. Bài tự chọn GPU đang trống nhiều nhất.
  • CPU của bạn chỉ có 2 luồng (để 20 bạn không giành nhau) — nhớ con số này, Phần 1 và 2b sẽ thấy nó quan trọng thế nào.
  • Ảnh đã ở thang −1 … 1: đó là bước làm sạch duy nhất bài này cần.

Phần 1 — Cùng một việc: CPU hay GPU?

📍 Bước 4 + 6 — Coding và đo: dựng một GAN nhỏ rồi cho nó học cùng số bước trên CPU và trên GPU, đồng hồ bật suốt.

GAN gồm 2 mạng:

  • Generator (G): nhận 64 số ngẫu nhiên → vẽ ra một ảnh 28 × 28.
  • Discriminator (D): nhận một ảnh → chấm "thật" hay "giả".

Mỗi bước học: D xem 128 ảnh thật + 128 ảnh G vừa vẽ và học phân biệt; rồi G học vẽ sao cho D tin là thật.

gan_loop

In [ ]:
SO_NHIEU = 64                                        # G nhận 64 số ngẫu nhiên

def tao_gan(thiet_bi):
    G = nn.Sequential(nn.ConvTranspose2d(SO_NHIEU, 128, 7, 1, 0), nn.BatchNorm2d(128), nn.ReLU(True),
                      nn.ConvTranspose2d(128, 64, 4, 2, 1), nn.BatchNorm2d(64), nn.ReLU(True),
                      nn.ConvTranspose2d(64, 1, 4, 2, 1), nn.Tanh()).to(thiet_bi)        # 64 số -> ảnh 1x28x28
    D = nn.Sequential(nn.Conv2d(1, 64, 4, 2, 1), nn.LeakyReLU(0.2, True),
                      nn.Conv2d(64, 128, 4, 2, 1), nn.BatchNorm2d(128), nn.LeakyReLU(0.2, True),
                      nn.Flatten(), nn.Linear(128 * 7 * 7, 1)).to(thiet_bi)               # ảnh -> 1 điểm "thật?"
    oG = torch.optim.Adam(G.parameters(), 2e-4, betas=(0.5, 0.999))
    oD = torch.optim.Adam(D.parameters(), 2e-4, betas=(0.5, 0.999))
    return G, D, oG, oD

bce = nn.BCEWithLogitsLoss()

def mot_buoc(G, D, oG, oD, anh_that, thiet_bi):
    # Một bước học của GAN: D học phân biệt, rồi G học đánh lừa D. Trả về (loss D, loss G)
    b = anh_that.size(0)
    gia = G(torch.randn(b, SO_NHIEU, 1, 1, device=thiet_bi))
    loss_D = bce(D(anh_that), torch.ones(b, 1, device=thiet_bi)) + bce(D(gia.detach()), torch.zeros(b, 1, device=thiet_bi))
    oD.zero_grad(); loss_D.backward(); oD.step()
    loss_G = bce(D(gia), torch.ones(b, 1, device=thiet_bi))      # G muốn D nói "thật" (1)
    oG.zero_grad(); loss_G.backward(); oG.step()
    return loss_D.item(), loss_G.item()

nap = torch.utils.data.DataLoader(mnist, batch_size=128, shuffle=True, num_workers=1, drop_last=True)
G, D, oG, oD = tao_gan("cpu")
print("Số tham số (núm vặn) của G:", sum(p.numel() for p in G.parameters()), "| của D:", sum(p.numel() for p in D.parameters()))

Kết quả: Số tham số (núm vặn) của G: 534081 | của D: 138817

👉 Hiểu: G có khoảng 534 nghìn "núm vặn", D khoảng 139 nghìn. Mỗi bước học, máy phải tính lại tất cả các núm này — hàng trăm nghìn phép nhân cho mỗi ảnh. Đó chính là loại việc GPU sinh ra để làm.

In [ ]:
# ✏️ SỬA: số bước học dùng để so (mỗi bước = 128 ảnh)
SO_BUOC_SO_SANH = 100

def hoc_n_buoc(thiet_bi, so_buoc):
    torch.manual_seed(0)
    G, D, oG, oD = tao_gan(thiet_bi)
    buoc = 0
    for anh, _ in nap:
        mot_buoc(G, D, oG, oD, anh.to(thiet_bi), thiet_bi)
        buoc += 1
        if buoc == so_buoc: break
    if thiet_bi.startswith("cuda"): torch.cuda.synchronize()     # chờ GPU làm xong hết rồi mới bấm giờ

with DongHo("P1: CPU") as dh_cpu:
    hoc_n_buoc("cpu", SO_BUOC_SO_SANH)
with DongHo("P1: GPU") as dh_gpu:
    hoc_n_buoc(THIET_BI, SO_BUOC_SO_SANH)

so_sanh = pd.DataFrame([dh_cpu.tom_tat(), dh_gpu.tom_tat()])
# 📦 so_sanh = bảng tóm tắt 2 lần đo (thời gian, GPU %, VRAM, điện, CPU)
print(so_sanh.to_string(index=False))
print(f"\nGPU nhanh hơn CPU khoảng {dh_cpu.thoi_gian / dh_gpu.thoi_gian:.0f} lần cho {SO_BUOC_SO_SANH} bước học")
ve_dong_ho(dh_cpu, dh_gpu, tieu_de=f"Cùng {SO_BUOC_SO_SANH} bước học GAN: CPU vs GPU")

Kết quả (đo 2 lần trên máy lớp):

   việc  thời gian (s)  GPU % trung bình  VRAM cả card cao nhất (GB)  điện GPU cao nhất (W)  CPU của bạn trung bình (%)
P1: CPU           37.5                 0                        0.51                     17                         166
P1: GPU            3.8                19                        0.65                     20                          43

GPU nhanh hơn CPU khoảng 10 lần cho 100 bước học (lần đo khác: 32,1 s vs 4,0 s → khoảng 8 lần).

👉 Hiểu:

  • Cùng 100 bước học: CPU mất hơn nửa phút, GPU chưa tới 4 giây → nhanh hơn khoảng 8–10 lần.
  • Nhìn đồng hồ CPU của bạn: khi học trên CPU, nó kịch trần ~170–190% (2 luồng chạy hết công suất) suốt cả quá trình; GPU nằm im (0%).
  • Khi học trên GPU, GPU chỉ bận ~20% — card còn rảnh rất nhiều. Ghi nhớ điều lạ này: Phần 2b sẽ giải thích.

Phần 2 — Xem GAN học vẽ, từng vòng một (bảng điều khiển trực tiếp)

📍 Bước 4 + 6 — Huấn luyện thật và đo: cho GAN học vài epoch trên GPU. Sau mỗi epoch, bảng bên dưới tự vẽ lại:

  • trái: 32 chữ số G đang vẽ (luôn từ cùng 32 dãy nhiễu, để thấy cùng một "nét" tiến bộ dần);
  • giữa: loss của D và G;
  • phải: GPU % và CPU % của bạn trong lúc học.

Đọc loss thế nào? Không mong loss về 0. GAN là cuộc chạy đua: nếu D thắng hẳn (loss D ≈ 0) thì G không học được gì; nếu hai đường dao động quanh một mức thì hai bên đang ngang tài — đó là lúc ảnh đẹp dần.

In [ ]:
# ✏️ SỬA: số epoch (mỗi epoch = 60.000 ảnh)
SO_EPOCH = 5

torch.manual_seed(0)
G, D, oG, oD = tao_gan(THIET_BI)
nhieu_co_dinh = torch.randn(32, SO_NHIEU, 1, 1, device=THIET_BI)   # 32 dãy nhiễu cố định để so qua các epoch
lich_su_loss, anh_moi_epoch = [], []
# 📦 lich_su_loss = danh sách (loss D, loss G) từng bước · anh_moi_epoch = ảnh G vẽ sau mỗi epoch

with DongHo("P2: huấn luyện GAN") as dh_hoc:
    for ep in range(1, SO_EPOCH + 1):
        t_ep = time.time()
        for anh, _ in nap:
            lich_su_loss.append(mot_buoc(G, D, oG, oD, anh.to(THIET_BI), THIET_BI))
        with torch.no_grad():
            anh_moi_epoch.append(G(nhieu_co_dinh).cpu())
        # --- vẽ lại bảng điều khiển ---
        clear_output(wait=True)
        fig, truc = plt.subplots(1, 3, figsize=(16, 4.2))
        luoi = tv.utils.make_grid(anh_moi_epoch[-1], nrow=8, normalize=True)
        truc[0].imshow(luoi.permute(1, 2, 0)); truc[0].axis("off"); truc[0].set_title(f"G vẽ sau epoch {ep}/{SO_EPOCH}")
        l = np.array(lich_su_loss)
        truc[1].plot(l[:, 0], label="loss D", alpha=0.7); truc[1].plot(l[:, 1], label="loss G", alpha=0.7)
        truc[1].set_xlabel("bước học"); truc[1].legend(); truc[1].set_title("loss"); truc[1].grid(alpha=0.3)
        b = pd.DataFrame(dh_hoc.dong)
        truc[2].plot(b["giây"], b["GPU %"], label="GPU %"); truc[2].plot(b["giây"], b["CPU của bạn (%)"], label="CPU của bạn %")
        truc[2].set_xlabel("giây"); truc[2].legend(); truc[2].set_title("đồng hồ"); truc[2].grid(alpha=0.3)
        plt.tight_layout(); plt.show()
        print(f"epoch {ep}: {time.time()-t_ep:.1f} s | VRAM PyTorch của bạn đang giữ {torch.cuda.memory_reserved(THIET_BI)/2**20:.0f} MB")

Kết quả: bảng điều khiển vẽ lại 5 lần; cuối cùng epoch 5: 14.0 s | VRAM PyTorch của bạn đang giữ 108 MB.

👉 Hiểu:

  • Ảnh trái: sau epoch 1 đã thấy dáng chữ số nhưng nhiều nét nhòe; đến epoch 5 phần lớn đọc được.
  • Loss: loss D dao động quanh ~0,6–0,8, loss G quanh ~1,5 và không về 0 — đúng như mong đợi của một cuộc chạy đua: không bên nào thắng hẳn.
  • Đồng hồ: GPU chỉ bận khoảng 20–30%, và tụt về 0 ở đầu mỗi epoch (vạch rơi đều đặn ~14 giây một lần). Mỗi epoch mất ~14 giây, mà GPU phần lớn thời gian ngồi chờ.
  • VRAM của bạn chỉ khoảng 0,1 GB: GAN này nhỏ, 20 bạn cùng học thoải mái.
In [ ]:
# Cả chặng đường trên một hình: cùng 8 dãy nhiễu, sau từng epoch
fig, truc = plt.subplots(len(anh_moi_epoch), 8, figsize=(10, 1.4 * len(anh_moi_epoch)))
for e, anh in enumerate(anh_moi_epoch):
    for k in range(8):
        truc[e, k].imshow(anh[k, 0], cmap="gray"); truc[e, k].axis("off")
    truc[e, 0].set_title(f"epoch {e+1}", fontsize=8, loc="left")
plt.suptitle("Cùng một dãy nhiễu → G vẽ ngày càng giống chữ số"); plt.tight_layout(); plt.show()
print(pd.DataFrame([dh_hoc.tom_tat()]).to_string(index=False))
ve_dong_ho(dh_hoc, tieu_de="Toàn bộ quá trình huấn luyện GAN")

Kết quả:

              việc  thời gian (s)  GPU % trung bình  VRAM cả card cao nhất (GB)  điện GPU cao nhất (W)  CPU của bạn trung bình (%)
P2: huấn luyện GAN           71.6                23                        0.65                     20                          38

👉 Hiểu:

  • Đọc hình theo cột: cùng một dãy nhiễu, mỗi hàng là một epoch → nét chữ rõ dần. G chưa bao giờ thấy nhãn "đây là số 5": nó chỉ học sao cho D tin là thật.
  • Vài ô vẫn méo sau 5 epoch: GAN cần học lâu hơn — và đó là lý do mô hình tạo ảnh thật (Phần 3) tốn hàng nghìn giờ GPU.
  • 72 giây, GPU bận trung bình chỉ 23%: tốn thời gian mà card thì nhàn. Chuyển sang 2b để chẩn đoán.

2b. Đọc đồng hồ → chẩn đoán → sửa: GPU đang "đói"

📍 Bước 6 → Bước 5 — Đánh giá rồi sửa cách code: nhìn lại đồng hồ ở trên: trong lúc huấn luyện, GPU chỉ bận khoảng một phần tư thời gian, và tụt về 0 ở đầu mỗi epoch. Card mạnh nhưng ngồi chơi — vì sao?

Chẩn đoán: mỗi bước học, CPU (chỉ 2 luồng của bạn) phải đọc 128 ảnh, đổi sang số rồi chở lên GPU. GPU tính xong trong nháy mắt rồi chờ chuyến hàng tiếp theo. Giống một đầu bếp giỏi (GPU) mà chỉ có một người phụ bếp (CPU) chạy đi chợ từng mớ rau.

Cách sửa: chở cả kho lên GPU một lần (60.000 ảnh nhỏ chỉ khoảng 180 MB VRAM), rồi mỗi bước lấy thẳng trong VRAM. Thử thêm: mỗi lần nấu nhiều hơn (batch 512 thay vì 128).

Ô dưới đo 1 epoch theo 3 cách, đồng hồ bật suốt.

gpu_starved

In [ ]:
# ✏️ SỬA: kích thước batch cho cách thứ 3
BATCH_LON = 512

X_vram = (mnist.data.float() / 255 - 0.5) / 0.5            # cả 60.000 ảnh, cùng thang -1..1 như bien_doi
X_vram = X_vram.unsqueeze(1).to(THIET_BI)                    # chở cả kho lên GPU một lần
# 📦 X_vram = tensor 60000×1×28×28 nằm sẵn trong VRAM
print(f"Cả kho MNIST trong VRAM: {X_vram.numel() * 4 / 2**20:.0f} MB")

def mot_epoch_tu_vram(batch):
    G, D, oG, oD = tao_gan(THIET_BI)
    thu_tu = torch.randperm(X_vram.size(0), device=THIET_BI)  # xáo trộn ngay trên GPU
    for i in range(0, X_vram.size(0) - batch + 1, batch):
        mot_buoc(G, D, oG, oD, X_vram[thu_tu[i:i + batch]], THIET_BI)
    torch.cuda.synchronize()

def mot_epoch_dataloader():
    G, D, oG, oD = tao_gan(THIET_BI)
    for anh, _ in nap:
        mot_buoc(G, D, oG, oD, anh.to(THIET_BI), THIET_BI)
    torch.cuda.synchronize()

torch.manual_seed(0)
with DongHo("2b: CPU chở từng batch 128") as dh_a: mot_epoch_dataloader()
with DongHo("2b: cả kho trong VRAM, batch 128") as dh_b: mot_epoch_tu_vram(128)
with DongHo(f"2b: cả kho trong VRAM, batch {BATCH_LON}") as dh_c: mot_epoch_tu_vram(BATCH_LON)
print(pd.DataFrame([dh_a.tom_tat(), dh_b.tom_tat(), dh_c.tom_tat()]).to_string(index=False))
ve_dong_ho(dh_a, dh_b, dh_c, tieu_de="Cùng 1 epoch: GPU đói hay no?")

Kết quả:

                            việc  thời gian (s)  GPU % trung bình  VRAM cả card cao nhất (GB)  điện GPU cao nhất (W)  CPU của bạn trung bình (%)
      2b: CPU chở từng batch 128           13.6                24                        0.83                     20                          32
2b: cả kho trong VRAM, batch 128            7.0                41                        0.83                     27                          98
2b: cả kho trong VRAM, batch 512            2.6                94                        1.10                     39                          94

Cả kho MNIST trong VRAM: 179 MB

👉 Hiểu:

  • Chẩn đoán đúng: chở cả kho lên VRAM (chỉ 179 MB) → 1 epoch từ 13,6 s xuống 7,0 s, GPU từ 24% lên 41%.
  • Nấu mẻ to hơn (batch 512) → 2,6 s, GPU 94%, điện GPU tăng từ 20 W lên 39 W: lần này card làm việc thật. Nhanh hơn khoảng 5 lần so với cách đầu, cùng một máy.
  • ⚠️ Đánh đổi: batch 512 nghĩa là ít bước học hơn trong một epoch (117 bước thay vì 468), nên chất lượng ảnh sau 1 epoch không tự động tốt hơn. Nhanh hơn ≠ học tốt hơn — vẫn phải nhìn ảnh và đo.
  • 💡 Bài học vận hành: đồng hồ "GPU %" thấp không có nghĩa là "GPU yếu" — thường là bị nghẽn ở chỗ khác (CPU, đọc dữ liệu). Đọc đồng hồ trước khi đòi mua GPU to hơn.

Phần 3 — Chạy (suy luận): tạo ảnh "người không tồn tại" trong chưa tới 1 giây

📍 Bước 3 + 4 + 6 — Chọn mô hình học sẵn, chạy, đo: sd-turbo (Stability AI) là mô hình diffusion đã được học sẵn — ta chỉ chạy nó: gõ một câu mô tả → ra ảnh.

Mô hình nào đang chạy ở đây? sd-turbo thật ra là 4 khối nối nhau (ô dưới in ra tên và số tham số của từng khối):

  1. Tokenizer + bộ mã hóa chữ (CLIP): đổi câu mô tả thành một dãy số "ý nghĩa".
  2. UNet: khối lớn nhất — bắt đầu từ nhiễu ngẫu nhiên và "gột nhiễu" thành ảnh, theo hướng của dãy số trên (làm trong một không gian ảnh nén nhỏ).
  3. VAE: "giải nén" kết quả thành ảnh 512 × 512 thật.
  4. Bộ lập lịch (scheduler): quyết định gột nhiễu bao nhiêu bước — ở đây 1 bước.

Liên hệ với GAN ở Phần 2: theo thẻ mô hình của nhà phát triển, sd-turbo được "chưng cất" từ Stable Diffusion 2.1 (vốn cần nhiều chục bước) bằng phương pháp Adversarial Diffusion Distillation — trong đó có một Discriminator chấm thật / giả, đúng ý tưởng GAN. Nhờ vậy nó vẽ được trong 1 bước.

⚠️ Quy tắc của lớp: chỉ mô tả người hư cấu hoặc đồ vật / cảnh. Không tên người thật, không logo, không giấy tờ tùy thân. Ô dưới tự chặn các từ đó.

📍 Bước 5 — Lưu ý khi coding: mô hình này chiếm khoảng 3 GB VRAM. Cả lớp dùng chung 2 GPU, nên ô dưới kiểm tra bộ nhớ trống trước khi nạp; thiếu thì báo bạn chờ một lúc chứ không làm sập máy của cả lớp.

sd_turbo_blocks

In [ ]:
from diffusers import AutoPipelineForText2Image
from diffusers.utils import logging as diffusers_log
from huggingface_hub.utils import logging as hub_log
diffusers_log.set_verbosity_error(); hub_log.set_verbosity_error()      # chỉ hiện lỗi thật, ẩn thông báo kỹ thuật

CAN_TRONG_GB = 3.5                                  # sd-turbo cần ~3 GB, chừa thêm 0,5 GB
gpu_tao_anh, trong_gb = gpu_trong_nhat()
print(f"GPU {gpu_tao_anh} đang trống {trong_gb:.1f} GB")
may_tao_anh = None
if trong_gb < CAN_TRONG_GB:
    print("⏳ GPU đang đầy (các bạn khác đang dùng). Chờ 1–2 phút rồi chạy lại ô này.")
else:
    with DongHo("P3: nạp mô hình", gpu_tao_anh, chu_ky=0.1) as dh_nap:
        may_tao_anh = AutoPipelineForText2Image.from_pretrained("stabilityai/sd-turbo", dtype=torch.float16,
                                                                variant="fp16").to(f"cuda:{gpu_tao_anh}")
        may_tao_anh.set_progress_bar_config(disable=True)
    print(f"Nạp mô hình: {dh_nap.thoi_gian:.1f} s | VRAM PyTorch của bạn: {torch.cuda.memory_reserved(gpu_tao_anh)/2**30:.2f} GB")
    print("\nCác khối bên trong sd-turbo:")
    for ten_khoi, khoi in may_tao_anh.components.items():
        if khoi is None: continue
        so_tham_so = sum(p.numel() for p in khoi.parameters()) if hasattr(khoi, "parameters") else 0
        print(f"  {ten_khoi:<14} {type(khoi).__name__:<24} {so_tham_so/1e6:8.1f} triệu tham số" if so_tham_so else f"  {ten_khoi:<14} {type(khoi).__name__}")

TU_CAM = ["logo", "passport", "hộ chiếu", "căn cước", "id card", "license", "biden", "trump", "obama", "putin", "elon",
          "taylor", "celebrity", "người nổi tiếng", "nude", "khỏa thân"]

def tao_anh(cau_mo_ta, hat_giong=0):
    # Tạo 1 ảnh 512x512 từ câu mô tả (tiếng Anh cho kết quả tốt nhất). Chặn các từ cấm của lớp.
    if any(t in cau_mo_ta.lower() for t in TU_CAM):
        raise ValueError("Câu mô tả có từ bị cấm trong lớp (người thật / logo / giấy tờ). Hãy mô tả người hư cấu hoặc đồ vật.")
    g = torch.Generator(f"cuda:{gpu_tao_anh}").manual_seed(hat_giong)
    return may_tao_anh(prompt=cau_mo_ta, num_inference_steps=1, guidance_scale=0.0, generator=g).images[0]

Kết quả: GPU 0 đang trống 11.2 GB · Nạp mô hình: 1.3 s | VRAM PyTorch của bạn: 2.47 GB

Các khối bên trong sd-turbo:
  vae            AutoencoderKL                83.7 triệu tham số
  text_encoder   CLIPTextModel               340.4 triệu tham số
  tokenizer      CLIPTokenizer
  unet           UNet2DConditionModel        865.9 triệu tham số
  scheduler      EulerDiscreteScheduler

👉 Hiểu — mô hình nào đang chạy ở đây?

  • UNet (866 triệu tham số) là "bộ não" gột nhiễu thành ảnh; CLIP (340 triệu) hiểu câu chữ; VAE (84 triệu) giải nén thành ảnh. Cộng lại khoảng 1,3 tỷ tham số — gấp khoảng 2.000 lần GAN tự học ở Phần 2 (≈ 0,7 triệu).
  • Tokenizer và scheduler không có tham số: một cái cắt chữ thành từng mẩu, một cái lên lịch gột nhiễu (ở đây 1 bước).
  • Mô hình đã tải sẵn trên máy lớp nên nạp chỉ mất ~1–2 giây và chiếm ~2,5 GB VRAM (cả card lên ~3,1 GB). Một GPU 12 GB chỉ chứa được khoảng 3 bạn cùng nạp — vì vậy ô trên kiểm tra trước. Nếu bạn thấy "⏳ GPU đang đầy", hãy chờ bạn khác chạy ô dọn dẹp cuối bài.
In [ ]:
# ✏️ SỬA: câu mô tả (người HƯ CẤU hoặc đồ vật), bằng tiếng Anh
CAC_CAU = ["a photo of a middle-aged business executive in a suit, office background, realistic",
           "a photo of a young IT technician in a server room, realistic",
           "a photo of a laptop on a desk with a cup of coffee, realistic",
           "a photo of a security camera on a building wall, realistic"]

if may_tao_anh is None:
    print("Chưa nạp được mô hình (xem ô trên).")
else:
    tao_anh(CAC_CAU[0])                               # chạy nháp 1 lần (lần đầu luôn chậm hơn)
    thoi_gian_moi_anh, cac_anh = [], []
    with DongHo("P3: tạo 8 ảnh", gpu_tao_anh, chu_ky=0.1) as dh_chay:
        for k in range(8):                            # mỗi câu 2 ảnh, khác "hạt giống" -> khác ảnh
            t = time.time(); cac_anh.append(tao_anh(CAC_CAU[k % len(CAC_CAU)], hat_giong=k)); thoi_gian_moi_anh.append(time.time() - t)
    # 📦 cac_anh = 8 ảnh PIL 512x512 · thoi_gian_moi_anh = số giây mỗi ảnh
    fig, truc = plt.subplots(2, 4, figsize=(16, 8.6))
    for t, a, s in zip(truc.ravel(), cac_anh, thoi_gian_moi_anh):
        t.imshow(a); t.axis("off"); t.set_title(f"{s:.2f} s", fontsize=10)
    plt.suptitle("TẤT CẢ đều do AI tạo — không ai trong ảnh tồn tại"); plt.tight_layout(); plt.show()
    print(f"Trung bình {np.mean(thoi_gian_moi_anh):.2f} s / ảnh")
    ve_dong_ho(dh_nap, dh_chay, tieu_de="Nạp mô hình rồi chạy: GPU làm gì?")

Kết quả: 8 ảnh 512 × 512 (doanh nhân, kỹ thuật viên IT trong phòng máy chủ, laptop và cà phê, camera an ninh), mỗi ảnh ghi số giây trên đầu · Trung bình 0.23 s / ảnh.

👉 Hiểu:

  • Không ai trong ảnh tồn tại. Mỗi ảnh tốn chưa tới một phần tư giây trên một GPU laptop — một kẻ lừa đảo tạo được hàng nghìn ảnh mỗi giờ cho hồ sơ giả, tài khoản mạng xã hội giả.
  • Đồng hồ: lúc nạp, VRAM leo dần lên ~3,1 GB nhưng GPU gần như rảnh (chủ yếu là chép dữ liệu); lúc chạy, GPU vọt lên ~80–100% và điện tăng lên ~37 W.
  • So với Phần 2: huấn luyện GAN nhỏ tốn hàng chục giây; chạy mô hình lớn đã học sẵn chỉ tốn phần giây — nhưng chiếm VRAM gấp nhiều lần. Phần 5 đặt hai thứ cạnh nhau.
  • Phòng thủ (Module 8): đừng tin ảnh vì "trông thật" — xác minh qua kênh khác, Code Word, nguồn gốc ảnh (CAI).

Phần 4 — AI phòng thủ cũng bị lừa: nhiễu không nhìn thấy

📍 Bước 4 + 6 — Tấn công đối kháng và đo: ResNet-18 là mô hình nhận dạng ảnh đã học sẵn (1.000 loại vật). Ta tính một lớp nhiễu rất nhỏ (mỗi điểm ảnh đổi tối đa vài phần 255) sao cho mô hình nhìn con mèo thành cái máy nướng bánh mì.

Đây là cùng ý tưởng với "chèn từ an toàn" ở Lab 4: thay đổi nhỏ, có tính toán, đủ để lừa mô hình mà người không thấy.

adversarial

In [ ]:
from skimage import data, transform

# ✏️ SỬA: vật muốn mô hình "nhìn thấy" (tên tiếng Anh trong 1.000 loại của ImageNet) và độ lớn nhiễu (trên 255)
MUC_TIEU = "toaster"
DO_NHIEU = 2

trong_so = tv.models.ResNet18_Weights.IMAGENET1K_V1
nhan_dang = tv.models.resnet18(weights=trong_so).eval().to(THIET_BI)
ten_lop = trong_so.meta["categories"]                          # 📦 1.000 tên loại vật
tb = torch.tensor([0.485, 0.456, 0.406], device=THIET_BI).view(1, 3, 1, 1)
lc = torch.tensor([0.229, 0.224, 0.225], device=THIET_BI).view(1, 3, 1, 1)
xac_suat = lambda x: torch.softmax(nhan_dang((x - tb) / lc), 1)[0]

meo = torch.tensor(transform.resize(data.chelsea(), (224, 224), anti_aliasing=True), dtype=torch.float32,
                   device=THIET_BI).permute(2, 0, 1).unsqueeze(0)     # ảnh mèo kèm sẵn trong scikit-image
p0 = xac_suat(meo); goc = p0.argmax().item()
eps, dich = DO_NHIEU / 255, ten_lop.index(MUC_TIEU)
with DongHo("P4: tấn công đối kháng", chu_ky=0.1) as dh_lua:
    anh_lua = meo.clone()
    for _ in range(20):                                       # 20 bước nhỏ, mỗi bước đẩy về phía MUC_TIEU
        anh_lua.requires_grad_(True)
        loss = nn.functional.cross_entropy(nhan_dang((anh_lua - tb) / lc), torch.tensor([dich], device=THIET_BI))
        g, = torch.autograd.grad(loss, anh_lua)
        anh_lua = (anh_lua - (eps / 8) * g.sign()).detach()
        anh_lua = (meo + (anh_lua - meo).clamp(-eps, eps)).clamp(0, 1)   # giữ nhiễu trong giới hạn DO_NHIEU
p1 = xac_suat(anh_lua); moi = p1.argmax().item()
nhieu = (anh_lua - meo)[0].permute(1, 2, 0).cpu().numpy()
fig, truc = plt.subplots(1, 3, figsize=(13, 4.5))
truc[0].imshow(meo[0].permute(1, 2, 0).cpu()); truc[0].set_title(f"ảnh gốc: {ten_lop[goc]} {p0[goc]:.0%}")
truc[1].imshow((nhieu - nhieu.min()) / (nhieu.max() - nhieu.min() + 1e-9)); truc[1].set_title("lớp nhiễu (phóng đại cho dễ thấy)")
truc[2].imshow(anh_lua[0].permute(1, 2, 0).cpu()); truc[2].set_title(f"gốc + nhiễu {DO_NHIEU}/255: {ten_lop[moi]} {p1[moi]:.0%}")
for t in truc: t.axis("off")
plt.tight_layout(); plt.show()
print(f"Điểm ảnh bị đổi nhiều nhất: {(anh_lua - meo).abs().max().item()*255:.1f} / 255 | {dh_lua.thoi_gian:.1f} s")

# Mô hình "nghĩ" gì? 5 đáp án nó tin nhất, trước và sau khi thêm nhiễu
fig, truc = plt.subplots(1, 2, figsize=(13, 3.6))
for t, pp, ten, mau in [(truc[0], p0, "TRƯỚC: ảnh gốc", "#059669"), (truc[1], p1, f"SAU: + nhiễu {DO_NHIEU}/255", "#DC2626")]:
    gt, cs = pp.topk(5)
    t.barh([ten_lop[i] for i in cs.tolist()][::-1], (gt.detach().cpu().numpy() * 100)[::-1], color=mau)
    t.set_xlim(0, 100); t.set_xlabel("mô hình tin (%)"); t.set_title(ten); t.grid(alpha=0.3, axis="x")
plt.suptitle("5 đáp án ResNet-18 tin nhất"); plt.tight_layout(); plt.show()

Kết quả: ảnh gốc Egyptian cat 80% → ảnh + nhiễu 2/255 toaster 100% · Điểm ảnh bị đổi nhiều nhất: 2.0 / 255 | 0.4 s

Biểu đồ dưới: 5 đáp án ResNet-18 tin nhất — TRƯỚC: Egyptian cat ~80%, tabby, tiger cat (đều là mèo); SAU: toaster ~100%, các loại mèo biến mất khỏi top 5.

👉 Hiểu:

  • Hai ảnh trái và phải trông giống hệt — mỗi điểm ảnh đổi tối đa 2 trên 255 mức sáng. Vậy mà mô hình chắc chắn 100% đây là máy nướng bánh mì.
  • Lớp nhiễu ở giữa không ngẫu nhiên: nó được tính từ chính mô hình (đạo hàm), chỉ mất dưới nửa giây trên GPU.
  • ✏️ Thử sửa: đổi MUC_TIEU sang một loại vật khác (ví dụ "banana", "pizza"), hoặc DO_NHIEU = 1 rồi chạy lại.
  • Cùng ý tưởng với Lab 4 (chèn từ an toàn) và Lab 10, Phần 8.1: AI phòng thủ cũng bị lừa — luôn cần nhiều lớp phòng thủ và con người kiểm tra.

Phần 5 — Huấn luyện vs chạy: tốn gì, tốn bao nhiêu?

📍 Bước 6 — Đánh giá: gom tất cả đồng hồ của bài vào một bảng và một hình.

In [ ]:
cac_dong_ho = [dh_cpu, dh_gpu, dh_hoc, dh_a, dh_c, dh_lua] + ([dh_nap, dh_chay] if may_tao_anh is not None else [])
bang_tong = pd.DataFrame([d.tom_tat() for d in cac_dong_ho])
# 📦 bang_tong = mỗi việc trong bài 1 dòng: thời gian, GPU % trung bình, VRAM cao nhất, điện cao nhất, CPU của bạn
print(bang_tong.to_string(index=False))
fig, truc = plt.subplots(1, 3, figsize=(15, 3.8))
bang_tong.plot.barh(x="việc", y="thời gian (s)", ax=truc[0], legend=False, title="thời gian (s)")
bang_tong.plot.barh(x="việc", y="GPU % trung bình", ax=truc[1], legend=False, title="GPU % trung bình")
bang_tong.plot.barh(x="việc", y="VRAM cả card cao nhất (GB)", ax=truc[2], legend=False, title="VRAM cả card cao nhất (GB)")
for t in truc: t.grid(alpha=0.3, axis="x")
plt.tight_layout(); plt.show()

Kết quả:

                            việc  thời gian (s)  GPU % trung bình  VRAM cả card cao nhất (GB)  điện GPU cao nhất (W)  CPU của bạn trung bình (%)
                         P1: CPU           37.5                 0                        0.51                     17                         166
                         P1: GPU            3.8                19                        0.65                     20                          43
              P2: huấn luyện GAN           71.6                23                        0.65                     20                          38
      2b: CPU chở từng batch 128           13.6                24                        0.83                     20                          32
2b: cả kho trong VRAM, batch 512            2.6                94                        1.10                     39                          94
          P4: tấn công đối kháng            0.4                 4                        0.87                     17                          89
                 P3: nạp mô hình            1.3                33                        3.12                     17                          97
                   P3: tạo 8 ảnh            1.9                73                        4.30                     37                          99

👉 Hiểu — huấn luyện vs chạy:

Huấn luyện (P2, 2b) Chạy / suy luận (P3, P4)
Thời gian dài: hàng chục giây cho một GAN nhỏ; mô hình lớn thật = hàng nghìn giờ GPU ngắn: phần giây mỗi ảnh
VRAM GAN nhỏ ~0,1 GB mô hình lớn ~2,5–3 GB — thứ giới hạn số người dùng chung một GPU
GPU % dễ bị đói nếu CPU không cấp dữ liệu kịp (24% → 94% sau khi sửa) cao khi đang tạo ảnh (~80–100%)
Ai làm? kẻ tấn công ít khi tự huấn luyện mô hình lớn — họ tải bản có sẵn chạy được trên một GPU laptop → rẻ, nhanh, ai cũng làm được

💡 Với SOC: chi phí để tạo deepfake đã gần bằng 0; chi phí để phòng thủ nằm ở quy trình xác minh và con người.

Phần 5b — Bài này dùng gì trong SOC?

📍 Bước 7 — nối với công việc thật: mỗi phần của bài ứng với một việc trong trung tâm điều hành an ninh (SOC).

Phần trong bài Kẻ tấn công làm được gì SOC làm gì
Phần 3 — ảnh deepfake trong 0,2 giây ảnh / video giả người thật hoặc người không tồn tại để lừa chuyển tiền, mở tài khoản, lừa qua nhận diện khuôn mặt — vụ Arup (~25,6 triệu USD, Module 8) dùng cả một cuộc họp video giả không tin hình / video vì "trông thật": xác minh qua kênh khác, Code Word, quy trình 2 người duyệt chuyển tiền, đào tạo nhân viên nhận biết
Phần 4 — nhiễu đối kháng sửa nhẹ mẫu tấn công để mô hình phòng thủ (lọc thư rác, phát hiện mã độc, IDS) bỏ qua — cùng ý tưởng "chèn từ an toàn" ở Lab 4 và né IDS bằng GAN ở Module 8 nhiều lớp phòng thủ, không để một mô hình quyết định một mình; theo dõi tỉ lệ phát hiện và học lại với mẫu né (Lab 10, 8.3)
Phần 1, 2 — GAN học từ dữ liệu GAN học "thói quen" từ dữ liệu thật để sinh ra mẫu giống thật: PassGAN đoán mật khẩu theo thói quen người dùng (Module 8) chính sách mật khẩu tránh mẫu dễ đoán (Hanoi2024!), xác thực 2 lớp
Phần 1, 2b, 5 — đồng hồ GPU / CPU máy chủ bị cài đào tiền ảo trái phép (cryptomining) làm GPU / CPU kịch trần bất thường dùng chính các đồng hồ này: GPU 90–100% vào giờ không ai chạy AI = dấu hiệu đáng điều tra (Module 4: bất thường về tài nguyên)
Phần 2b, 3 — VRAM, GPU "đói" — vận hành AI trong SOC: một GPU 12 GB chỉ chứa ~3 người cùng chạy mô hình tạo ảnh; đọc đồng hồ để biết cần sửa code hay mua thêm GPU

Phần 6 — Dọn dẹp và quy tắc bàn tay ✋

📍 Bước 5 — Lưu ý khi coding: GPU dùng chung. Chạy ô dưới để trả bộ nhớ GPU cho các bạn khác (hoặc menu Kernel → Shut Down Kernel).

In [ ]:
for ten in ["may_tao_anh", "nhan_dang", "G", "D", "X_vram", "meo", "anh_lua"]:
    globals().pop(ten, None)
gc.collect(); torch.cuda.empty_cache()
print(f"VRAM PyTorch bạn còn giữ: {torch.cuda.memory_reserved(THIET_BI)/2**20:.0f} MB")
print("Phần nhỏ còn lại là nền của CUDA. Muốn trả hết 100%: menu Kernel → Shut Down Kernel.")

Quy tắc bàn tay ✋ áp vào bài này

Ngón Điều Câu hỏi phải tự trả lời Trong bài này Quên điều này thì…
👍 1. Ngón cái DỮ LIỆU sạch Dữ liệu đúng thang, đúng dạng chưa? ảnh đưa về −1 … 1 cho khớp đầu ra của G (Phần 0) G và D "nói hai thứ tiếng", GAN không học được
☝️ 2. Ngón trỏ ĐẶC TRƯNG đúng Mô hình nhìn vào đâu? ResNet nhìn mẫu điểm ảnh, không "hiểu" con mèo — nên nhiễu nhỏ đã đủ lừa (Phần 4) tin rằng AI "nhìn" như người
✋ 3. Ngón giữa CHỌN CÔNG CỤ Tự học hay dùng mô hình học sẵn? CPU hay GPU? GAN nhỏ tự học được trên GPU; mô hình tạo ảnh lớn phải dùng bản học sẵn (Phần 1, 3) đốt hàng giờ CPU cho việc GPU làm trong vài giây
💍 4. Ngón áp út ĐO trung thực Nhanh hơn bao nhiêu, tốn bao nhiêu — đo hay đoán? đồng hồ đo GPU %, VRAM, điện, CPU cho mọi bước (Phần 1–5); đọc đồng hồ thấy GPU "đói" rồi sửa (2b); synchronize trước khi bấm giờ mua GPU đắt mà để nó ngồi chờ CPU; so tốc độ sai vì GPU chạy bất đồng bộ
🤙 5. Ngón út GIẢI THÍCH & CẢNH GIÁC Ảnh này thật không? Mô hình phòng thủ có bị lừa không? ảnh người không tồn tại trong < 1 s (Phần 3); mèo thành máy nướng bánh mì (Phần 4) tin ảnh / video vì "trông thật"; tin AI phòng thủ không thể sai