282 KiB
282 KiB
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()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()))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")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")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")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?")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]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ì?")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()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()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.")


