Git và Quản Lý Kho Lưu Trữ¶
Git là cách các nhóm phần mềm cộng tác mà không ghi đè lên công việc của nhau. File này bao gồm mô hình tinh thần của Git, chiến lược rẽ nhánh (branching), hợp nhất (merging) và rebasing, giải quyết xung đột, pull request và quản lý các thách thức đặc thù ML như file lớn và theo dõi thử nghiệm.
-
Mọi dự án phần mềm nghiêm túc đều sử dụng kiểm soát phiên bản. Git là hệ thống thống trị, được sử dụng bởi hầu hết tất cả các dự án mã nguồn mở và công ty. Không có git, cộng tác là gửi file zip qua email và cầu nguyện không ai ghi đè thay đổi của bạn. Có git, mọi thay đổi đều được theo dõi, có thể hoàn tác và có thể quy kết.
-
Đối với kỹ sư ML: git theo dõi mã, cấu hình và script thử nghiệm của bạn. Kết hợp với các công cụ theo dõi thử nghiệm, nó mang lại khả năng tái tạo (reproducibility): "chính xác mã và cấu hình nào đã tạo ra mô hình này?"
Mô Hình Tinh Thần¶
-
Git theo dõi các ảnh chụp (snapshots) của dự án bạn. Mỗi commit là một ảnh chụp đầy đủ của tất cả các file được theo dõi tại thời điểm đó, không phải một bản diff (về nội bộ, git lưu trữ các diff cho hiệu quả, nhưng về mặt khái niệm mỗi commit là một trạng thái hoàn chỉnh).
-
Bốn "vị trí" cho file của bạn:
- Thư mục làm việc (Working directory): các file thực tế trên đĩa. Bạn chỉnh sửa chúng.
- Khu vực tổ chức (Staging area) (index): các file bạn đã đánh dấu cho commit tiếp theo.
git adddi chuyển các thay đổi vào đây. - Kho lưu trữ cục bộ (Local repository): lịch sử commit của bạn, được lưu trong
.git/.git commitlưu khu vực tổ chức như một ảnh chụp mới. - Kho lưu trữ từ xa (Remote repository) (ví dụ: GitHub): một bản sao dùng chung.
git pushtải các commit của bạn lên,git pulltải về các commit của người khác.
- Khu vực tổ chức là điều làm nên sức mạnh của git. Bạn có thể chỉnh sửa 10 file nhưng chỉ commit 3 file, giữ các thay đổi khác cho một commit riêng. Điều này cho phép các commit sạch sẽ, tập trung.
Các Lệnh Cần Thiết¶
git init # tạo một kho lưu trữ mới
git clone url # tải về một kho lưu trữ từ xa
git status # cái gì đã thay đổi? (lệnh được dùng nhiều nhất)
git add file.py # tổ chức một file cụ thể
git add . # tổ chức tất cả thay đổi (dùng thận trọng)
git commit -m "message mô tả" # commit các thay đổi đã tổ chức
git push # tải commit lên remote
git pull # tải về + hợp nhất thay đổi từ remote
git log --oneline # lịch sử commit dạng gọn
git diff # hiện các thay đổi chưa tổ chức
git diff --staged # hiện các thay đổi đã tổ chức
Rẽ Nhánh (Branching)¶
- Một nhánh (branch) là một con trỏ đến một commit. Nhánh mặc định là
main(hoặcmaster). Tạo một nhánh cho bạn một dòng phát triển độc lập: bạn có thể thực hiện các thay đổi mà không ảnh hưởng đếnmain.
git branch feature-x # tạo một nhánh
git checkout feature-x # chuyển sang nhánh đó
git checkout -b feature-x # tạo và chuyển nhánh trong một bước
git branch -d feature-x # xóa nhánh (sau khi đã hợp nhất)
git branch -a # liệt kê tất cả nhánh (local + remote)
- Khi nào nên rẽ nhánh: luôn luôn. Không bao giờ commit trực tiếp vào
main. Mỗi tính năng, sửa lỗi hoặc thử nghiệm đều có nhánh riêng. Điều này giữ chomainổn định và có thể triển khai.
Chiến Lược Rẽ Nhánh¶
-
Nhánh tính năng (Feature branches) (phổ biến nhất): mỗi tính năng/sửa lỗi có một nhánh từ
main. Khi xong, mở một pull request (PR) để hợp nhất trở lại. Đơn giản, hoạt động cho hầu hết các nhóm. -
Trunk-based development: các nhà phát triển commit vào
mainthường xuyên (nhiều lần mỗi ngày), sử dụng feature flags để che giấu công việc chưa hoàn thành. Được ưa chuộng bởi các nhóm triển khai liên tục (Google, Facebook). Yêu cầu CI/CD xuất sắc. -
Gitflow: các nhánh riêng biệt cho tính năng, bản phát hành (release) và hotfix. Phức tạp hơn, tốt hơn cho phần mềm có bản phát hành theo phiên bản (ứng dụng di động, phần mềm đóng gói). Quá mức cho hầu hết các dự án ML.
-
Đối với nhóm ML: nhánh tính năng với thời gian sống ngắn (hợp nhất trong vòng 1-3 ngày) là điểm ngọt (sweet spot). Các nhánh sống lâu phân kỳ khỏi
mainvà tạo ra các xung đột hợp nhất đau đớn.
Hợp Nhất (Merging) và Rebasing¶
- Merge tạo ra một "commit hợp nhất" mới kết hợp hai nhánh:
-
Điều này bảo tồn toàn bộ lịch sử: bạn có thể thấy rằng công việc đã xảy ra trên một nhánh và khi nó được hợp nhất. Commit hợp nhất có hai cha.
-
Rebase phát lại các commit của nhánh của bạn lên trên nhánh đích:
-
Điều này viết lại lịch sử: các commit của nhánh của bạn nhận được các hash mới, như thể bạn đã bắt đầu công việc từ đỉnh hiện tại của
main. Kết quả là một lịch sử tuyến tính (không có commit hợp nhất), dễ đọc hơn. -
Khi nào dùng cái nào:
- Rebase để cập nhật nhánh tính năng của bạn với các thay đổi mới nhất từ
main(giữ cho nhánh của bạn sạch và cập nhật). - Merge để tích hợp nhánh tính năng của bạn vào
main(bảo tồn lịch sử nhánh). - Không bao giờ rebase các commit đã được push và chia sẻ với người khác. Rebasing viết lại lịch sử; nếu người khác đã dựa trên các commit gốc, việc rebase gây ra hỗn loạn.
- Rebase để cập nhật nhánh tính năng của bạn với các thay đổi mới nhất từ
Giải Quyết Xung Đột¶
- Một xung đột (conflict) xảy ra khi hai nhánh sửa đổi cùng một dòng của cùng một file. Git không thể tự động quyết định thay đổi nào nên giữ và yêu cầu bạn giải quyết thủ công.
-
Giữa
<<<<<<< HEADvà=======là phiên bản của nhánh hiện tại. Giữa=======và>>>>>>> feature-xlà phiên bản của nhánh đến. Bạn quyết định cái nào nên giữ (hoặc kết hợp chúng), loại bỏ các dấu phân cách, lưu lại, vàgit addfile đã giải quyết. -
Cạm bẫy: không để dấu phân cách xung đột trong các file đã commit. Chúng là văn bản thô sẽ làm hỏng mã của bạn. Luôn tìm kiếm
<<<<<<<sau khi giải quyết. -
Giảm xung đột: giữ các nhánh tồn tại ngắn, thường xuyên merge
mainvào nhánh của bạn, và tránh nhiều người cùng chỉnh sửa một file đồng thời.
Viết Thông Điệp Commit Tốt¶
-
Một thông điệp commit dành cho bản thân tương lai của bạn và các đồng đội. "sua loi" chẳng cho bạn biết gì. "Fix off-by-one in batch size calculation that caused OOM on 8-GPU training" cho bạn biết mọi thứ.
-
Định dạng:
Tóm tắt ngắn (50 ký tự hoặc ít hơn, thể mệnh lệnh)
Mô tả dài hơn nếu cần. Giải thích TẠI SAO, không phải CÁI GÌ
(diff hiển thị cái gì đã thay đổi). Ngắt dòng ở 72 ký tự.
Fixes #123
-
Thể mệnh lệnh: "Add feature" chứ không phải "Added feature" hay "Adds feature." Đọc nó như đang hoàn thành câu: "Nếu được áp dụng, commit này sẽ thêm tính năng."
-
Commit nguyên tử (Atomic commits): mỗi commit nên làm một việc. "Add data loader" là một commit. "Add data loader and fix unrelated bug and update README" nên là ba commit. Điều này giúp cho
git bisect(tìm commit nào đã gây ra lỗi) khả thi.
Pull Request và Code Review¶
-
Một pull request (PR) đề xuất hợp nhất một nhánh vào
main. Nó là cửa ngõ cho code review: đồng đội đọc các thay đổi của bạn, đề xuất cải tiến và phê duyệt trước khi hợp nhất. -
Thực hành PR tốt:
- Giữ PR nhỏ (dưới 400 dòng thay đổi). PR lớn bj đóng dấu cao su vì không ai muốn review 2000 dòng.
- Viết mô tả rõ ràng: cái gì đã thay đổi, tại sao và cách kiểm tra nó.
- Liên kết đến issue hoặc ticket đã thúc đẩy thay đổi.
- Phản hồi các bình luận review kịp thời.
- Squash các commit vụn trước khi hợp nhất (để
maincó lịch sử sạch).
-
Code review không phải là để tìm lỗi (các bài kiểm tra làm điều đó). Nó để: chia sẻ kiến thức (người review học codebase), phản hồi thiết kế (đây có phải là cách tiếp cận đúng không?), và duy trì các tiêu chuẩn (đặt tên, phong cách, kiến trúc).
.gitignore¶
- File
.gitignorebảo git loại trừ những file nào khỏi việc theo dõi. Cho các dự án ML:
# Python
__pycache__/
*.pyc
*.egg-info/
.venv/
env/
# Data and models (too large for git)
data/
*.csv
*.parquet
models/
*.pt
*.onnx
*.bin
checkpoints/
# Secrets
.env
*.pem
credentials.json
# IDE
.vscode/
.idea/
*.swp
# OS
.DS_Store
Thumbs.db
# Jupyter
.ipynb_checkpoints/
# Experiment outputs
wandb/
mlruns/
outputs/
logs/
- Cạm bẫy: thêm một file vào
.gitignoresau khi nó đã được commit không loại bỏ nó khỏi kho lưu trữ. Bạn cũng phảigit rm --cached fileđể bỏ theo dõi nó. File vẫn ở trong lịch sử mãi mãi trừ khi bạn viết lại lịch sử (điều này lộn xộn).
Git cho ML¶
-
ML đưa ra những thách thức mà phần mềm truyền thống không gặp phải:
-
File lớn: các tập dữ liệu và trọng số mô hình có kích thước hàng GB hoặc hơn. Git được thiết kế cho file văn bản (mã nguồn), không phải các blob nhị phân. Giải pháp:
- Git LFS (Large File Storage): theo dõi các con trỏ trong git, lưu trữ file thực tế trên một máy chủ riêng. Đơn giản nhưng có giới hạn lưu trữ/băng thông trên GitHub.
- DVC (Data Version Control): quản lý dữ liệu và file mô hình riêng biệt với git, sử dụng bộ nhớ từ xa (S3, GCS). Hoạt động như git cho dữ liệu:
dvc add data.csv,dvc push,dvc pull.
-
Theo dõi thử nghiệm: commit nào + siêu tham số nào + dữ liệu nào đã tạo ra các chỉ số nào? Git theo dõi mã, nhưng không phải bối cảnh thử nghiệm đầy đủ.
- Weights & Biases (W&B): ghi log các chỉ số, siêu tham số, thông tin hệ thống và liên kết đến git commit. Cung cấp bảng điều khiển để so sánh các lần chạy.
- MLflow: theo dõi thử nghiệm mã nguồn mở với model registry. Ghi log tham số, chỉ số và artifacts.
- Cách tiếp cận đơn giản: ghi log git hash trong script huấn luyện của bạn:
git_hash = subprocess.check_output(['git', 'rev-parse', 'HEAD']).strip(). Lưu trữ nó cùng với kết quả của bạn.
-
Danh sách kiểm tra khả năng tái tạo (những gì cần theo dõi cho mỗi thử nghiệm):
- Git commit hash (phiên bản mã chính xác)
- File cấu hình / siêu tham số
- Các hạt giống ngẫu nhiên (random seeds)
- Phiên bản Python và thư viện (
pip freeze) - Phiên bản dữ liệu (DVC hash hoặc thẻ phiên bản tập dữ liệu)
- Phần cứng (loại GPU, số lượng GPU)
# Ảnh chụp nhanh khả năng tái tạo
echo "Commit: $(git rev-parse HEAD)" > experiment_info.txt
echo "Branch: $(git branch --show-current)" >> experiment_info.txt
echo "Dirty: $(git status --porcelain | wc -l) files" >> experiment_info.txt
pip freeze >> experiment_info.txt
nvidia-smi >> experiment_info.txt