Danh sách bài viết

Bài 5: Git workflow cá nhân cho dự án AI

Git workflow thực tế cho AI Engineer: feature branch, Conventional Commits, .gitignore cho ML project, atomic commits, rebase vs merge, track experiment với commit hash.

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

Mục Tiêu Bài Học

Sau khi hoàn thành bài này, bạn sẽ:

  • Biết khi nào dùng main-only, khi nào dùng feature branch
  • Viết commit message theo Conventional Commits — kể cả cho experiment ML
  • Cấu hình .gitignore chặn data, model weight, và secret
  • Hiểu atomic commits và tần suất commit hợp lý
  • Track experiment MLflow/W&B liên kết với git commit hash
  • Biết cách xử lý khi lỡ commit secret vào repo

Bài này giả định bạn đã biết git init, git add, git commit, git push, git pull, git checkout -b. Nếu chưa, nên xem qua Git basics trước.

2

Tại Sao Git Quan Trọng Với AI Engineer

Git không chỉ là nơi lưu code. Với AI project, nó còn giải quyết 4 vấn đề thực tế:

Experiment tracking qua code state

Khi chạy experiment, model đạt accuracy=0.84. Hai tuần sau bạn thử cấu hình khác — accuracy xuống còn 0.79. Nếu không commit đúng thời điểm, bạn không biết code state nào tương ứng với con số 0.84 kia. Commit hash giải quyết vấn đề này: mỗi experiment ghi lại SHA, sau đó git checkout <sha> để reproduce.

Reproducibility sau 6 tháng

AI project thường có vòng đời dài. Bạn có thể cần quay lại project sau nửa năm để demo cho employer, hoặc để so sánh baseline với approach mới. Nếu không có version control rõ ràng, thường phải bắt đầu lại từ đầu.

Collaboration trong team nhỏ

Ngay cả project 2 người cũng cần tránh overwrite code của nhau. Branch + PR workflow giải quyết điều này mà không cần công cụ phức tạp.

GitHub history là bằng chứng

Recruiter nhìn commit history để đánh giá workflow. Một repo có commit history đều đặn (nhiều commit nhỏ theo thời gian) khác hẳn repo chỉ có 1 commit "initial commit" chứa 200 file. Cái sau cho thấy code được dump lên sau khi xong, không phải develop iteratively.

3

Workflow Solo Project

a) Main branch only

Tất cả commit đẩy thẳng lên main. Phù hợp cho:

  • Prototype nhanh trong 1–2 ngày
  • Experiment cá nhân không có người khác đọc code

Nhược điểm: không có safety net — commit lỗi lên thẳng main. CI không được trigger per-feature. Không dùng cho portfolio repo vì không có evidence của iterative development.

b) Feature branch workflow (khuyến nghị)

Đây là pattern phù hợp nhất cho portfolio project và project cá nhân nghiêm túc:

  • main: stable, deployable bất kỳ lúc nào. Không commit trực tiếp.
  • feature/x, experiment/y: develop trên đây, merge vào main khi xong và đã test.
# Bắt đầu 1 feature mới
git checkout main
git pull origin main
git checkout -b feature/add-rag-retrieval

# ... develop, commit nhiều lần trên branch này ...

# Merge khi xong
git checkout main
git pull origin main
git merge feature/add-rag-retrieval
git push origin main

# Xóa branch sau merge
git branch -d feature/add-rag-retrieval
git push origin --delete feature/add-rag-retrieval

Với portfolio repo, mở PR trên GitHub trước khi merge: CI sẽ chạy, bạn có thể self-review diff trước khi code vào main. GitHub sẽ giữ lại PR history — recruiter nhìn thấy bạn có workflow có tổ chức.

4

Conventional Commits

Conventional Commits là spec commit message được dùng rộng rãi trong open source và team engineering. Format:

<type>(<scope>): <description>

Các type chuẩn

Type Khi nào dùng
feat Thêm tính năng mới
fix Sửa bug
docs Thay đổi docs, README — không đụng code
refactor Tái cấu trúc code, không thêm feature / không fix bug
test Thêm hoặc sửa test
chore Bảo trì: cập nhật dependency, config, CI
perf Cải thiện performance — không thêm feature
style Formatting, linting — không đổi logic

Ví dụ thực tế trong AI project

# Thêm model mới
feat(model): add XGBoost baseline classifier

# Fix bug xử lý input rỗng
fix(api): handle empty input in /predict endpoint

# Cập nhật README
docs(readme): add quick start section and architecture diagram

# Tách data_loader ra module riêng
refactor(data): split data_loader into separate module

# Cập nhật version PyTorch
chore(deps): bump torch to 2.3.0

# Tối ưu inference
perf(inference): cache tokenizer initialization to reduce latency 40ms

Scope là gì

Scope (trong dấu ngoặc) là phần code bị ảnh hưởng: model, api, data, config, training, eval... Không bắt buộc nhưng giúp history dễ scan hơn. Bỏ qua nếu change nhỏ và rõ ràng:

fix: correct typo in error message
docs: add license file

Breaking change

Nếu commit phá vỡ backward compatibility, thêm ! hoặc ghi BREAKING CHANGE: ở footer:

feat(api)!: change /predict response schema to include confidence scores

BREAKING CHANGE: field "result" renamed to "prediction", field "confidence" added.
5

Commit Type Mở Rộng Cho AI/ML

Conventional Commits cho phép team tự thêm type. Trong AI/ML project, 2 type sau hữu ích:

experiment

Dành cho commit thử nghiệm hyperparameter hoặc architecture — commit này thường không merge vào main, chỉ tồn tại trên nhánh experiment riêng để ghi lại code state tương ứng với 1 run.

experiment(model): try learning_rate=1e-4 with cosine scheduler
experiment(model): test LoRA rank=16 vs rank=32 on llama3-8b
experiment(retrieval): compare BM25 vs dense retrieval on 100 queries

data

Dành cho pipeline xử lý dữ liệu: cleaning step, feature engineering, schema change.

data(prep): add cleaning step to remove rows with missing age
data(features): add rolling 7-day average feature for churn model
data(schema): add "source" column to track data origin

Hai type này không nằm trong spec chính thức của Conventional Commits nhưng được chấp nhận khi team đã thống nhất. Nếu làm solo, dùng chúng nhất quán trong project.

6

Atomic Commits — Tần Suất Commit

Atomic commit nghĩa là 1 commit = 1 thay đổi có ý nghĩa độc lập — có thể revert mà không ảnh hưởng đến commit khác, và message mô tả đủ để hiểu mà không cần đọc diff.

Tránh hai thái cực

Commit quá to:

# Xấu — không biết thay đổi nào gây ra bug nào
git commit -m "did a lot of stuff"
# diff: 15 file, 600 dòng thay đổi, gộp cả feature mới + fix bug + refactor

Commit quá nhỏ:

# Spam — lịch sử vô nghĩa
git commit -m "fix typo"
git commit -m "fix typo again"
git commit -m "oops"
git commit -m "ok now"

Sweet spot cho feature development

Khoảng 30–60 phút work = 1 commit. Một commit tốt:

  • Code có thể chạy hoặc test được (không commit broken state vào main)
  • Message tóm tắt được cái gì thay đổi trong 1 dòng
  • Không gộp nhiều concern không liên quan (ví dụ: không vừa fix bug vừa thêm feature trong 1 commit)

Khi đang spike / explore

Trong giai đoạn EDA hay thử nghiệm model, commit thường xuyên hơn — mỗi checkpoint đáng ghi lại (thêm visualization mới, thử 1 model mới, phát hiện data issue). Dùng experiment/ branch và commit type experiment như đã nói ở trên.

7

Branch Naming Convention

Branch name cần đủ mô tả để 6 tháng sau bạn biết branch đó làm gì.

Prefix Khi nào dùng Ví dụ
feature/ Feature mới, sẽ merge vào main feature/add-rag-retrieval
fix/ Sửa bug cụ thể fix/api-empty-input-crash
experiment/ Thử nghiệm, thường không merge experiment/larger-context-window
refactor/ Tái cấu trúc code không thêm feature refactor/extract-config-class
docs/ Chỉ chỉnh docs / README docs/add-architecture-diagram

Tránh: branch1, test, wip, tmp, new-branch. Những tên này không mô tả được gì và tích tụ thành "nợ" trong repo.

Quy tắc viết tên: lowercase, dùng - thay khoảng trắng, ngắn gọn nhưng đủ nghĩa. Tối đa 4–5 từ sau prefix là đủ.

8

.gitignore Cho AI Project

Tạo .gitignore ngay khi git init, không chờ sau. Một khi file đã được commit, xóa khỏi index tốn công hơn nhiều.

Template cho AI/ML project (Git 2.40+):

# Python
__pycache__/
*.pyc
*.pyo
.venv/
venv/
*.egg-info/
dist/
build/

# Jupyter
.ipynb_checkpoints/
*.ipynb_checkpoints

# Environment / Secrets
.env
.env.*
*.key
*.pem

# Data — không commit data lớn vào git
data/raw/
data/processed/
*.csv
*.parquet
*.tsv
*.json.gz
!data/sample.csv  # ngoại lệ: sample nhỏ để reproduce

# Model weights
*.pkl
*.pth
*.pt
*.h5
*.bin
*.safetensors
*.onnx
*.engine
models/checkpoints/

# ML tracking
mlruns/
wandb/
.neptune/

# OS
.DS_Store
Thumbs.db

# IDE
.vscode/
.idea/
*.swp

# Logs
logs/
*.log

3 nguyên tắc cốt lõi

  1. Không commit data lớn. Dataset thực tế thường hàng trăm MB đến GB. Git không xử lý tốt binary lớn — repo sẽ phình to và clone chậm. Dùng DVC hoặc lưu ngoài (S3, GCS) và track bằng pointer.
  2. Không commit model weight. File .pth hoặc .safetensors vài trăm MB sẽ làm repo unusable. Dùng Git LFS hoặc Hugging Face Hub.
  3. Không commit secret. .env, API key, private key — một khi đã vào git history, chúng tồn tại ngay cả sau khi xóa file. Cần rewrite history để xóa thực sự (xem mục 15).

Kiểm tra .gitignore đang hoạt động

# Xem file nào sẽ bị tracked nếu bạn git add .
git status

# Kiểm tra 1 file cụ thể có bị ignore không
git check-ignore -v data/raw/train.csv
# output: .gitignore:15:data/raw/    data/raw/train.csv

# Xem list tất cả file đang bị ignore
git ls-files --others --ignored --exclude-standard
9

Data Và Model Lớn — LFS Và DVC

Khi cần version control cho file lớn, có 2 lựa chọn chính:

Git LFS (Large File Storage)

Git LFS thay thế file lớn bằng pointer text trong git history, file thực tế lưu trên LFS server (GitHub, GitLab hỗ trợ sẵn). Phù hợp cho asset binary không thay đổi thường xuyên: ảnh dataset, font, model checkpoint nhỏ.

# Cài Git LFS
git lfs install

# Track file .pth
git lfs track "*.pth"
git lfs track "*.safetensors"

# File .gitattributes tự động được tạo
git add .gitattributes
git commit -m "chore: configure git lfs for model weights"

DVC (Data Version Control)

DVC track data và model qua pipeline, lưu file thực tế ra remote storage (S3, GCS, Azure Blob, local NFS). Phù hợp cho ML project vì:

  • Track pipeline (data → features → model) chứ không chỉ file
  • Reproduce experiment với dvc repro
  • Share dataset qua team mà không commit vào git

DVC được giới thiệu chi tiết trong Series 6 (MLOps). Ở bài này, điều quan trọng là biết không commit file lớn trực tiếp, và DVC là công cụ chuẩn cho vấn đề này trong ML workflow.

Khuyến nghị

  • Asset binary tĩnh (ảnh mẫu, font) → Git LFS
  • Dataset và model weight trong ML project → DVC
  • File nhỏ (<1MB) dùng để reproduce (sample data, config) → commit trực tiếp vào git
10

.gitattributes — Line Ending

AI project thường có contributor dùng cả Windows, macOS, và Linux. Line ending khác nhau (CRLF trên Windows, LF trên Unix) gây ra diff giả — git thấy file thay đổi dù nội dung không đổi thực chất.

File .gitattributes đặt ở root repo kiểm soát vấn đề này:

* text=auto
*.py text eol=lf
*.md text eol=lf
*.ipynb text eol=lf
*.csv text eol=lf
*.yml text eol=lf
*.yaml text eol=lf
*.json text eol=lf
*.sh text eol=lf
*.png binary
*.jpg binary
*.jpeg binary
*.gif binary
*.pkl binary
*.pth binary
*.h5 binary

text=auto cho phép git tự phát hiện text file và normalize. Các dòng tiếp theo override cho từng loại file cụ thể. Dòng binary nói git không cố chuyển đổi line ending và không diff các file này.

Commit .gitattributes ngay khi tạo repo, cùng lúc với .gitignore:

git add .gitignore .gitattributes
git commit -m "chore: add gitignore and gitattributes"
11

Rebase Vs Merge

Đây là câu hỏi hay gây tranh luận. Hiểu trade-off để chọn đúng context:

Merge

Tạo merge commit, giữ nguyên history graph với cả 2 branch. History phản ánh đúng những gì thực sự xảy ra — khi nào tách branch, khi nào merge lại.

A---B---C---M  (main)
     \     /
      D---E    (feature/add-rag)

Ưu điểm: dễ hiểu, an toàn. Nhược điểm: history "noisy" nếu có nhiều branch nhỏ merge liên tục.

Rebase

Replay các commit của feature branch lên đỉnh main — tạo ra linear history.

A---B---C---D'---E'  (main, sau rebase + merge)

Ưu điểm: history sạch, dễ đọc với git log --oneline. Nhược điểm: viết lại commit hash — nếu branch đã được push và người khác đang dùng, rebase sẽ gây conflict.

Pattern khuyến nghị cho solo/small team

  • Feature branch local: git rebase main thường xuyên để cập nhật với main, tránh conflict lớn khi merge.
  • Merge PR vào main: dùng merge (hoặc squash merge) để giữ context của PR trên GitHub.

Squash merge

Squash merge gom toàn bộ commit của feature branch thành 1 commit trên main. Giúp main history sạch, mỗi entry = 1 feature/fix hoàn chỉnh:

git checkout main
git merge --squash feature/add-rag-retrieval
git commit -m "feat(retrieval): implement RAG with ChromaDB and text-embedding-3-small"

GitHub UI cũng có nút "Squash and merge" khi merge PR. Đây là lựa chọn tốt cho portfolio project.

12

Pull Request Workflow Cho Solo Project

Mở PR ngay cả khi làm một mình có 3 lợi ích cụ thể:

  1. Trigger CI: GitHub Actions chạy test tự động mỗi khi có PR — phát hiện broken code trước khi vào main.
  2. Self-review: Nhìn diff trên GitHub UI thường phát hiện issue mà nhìn trong editor bỏ sót.
  3. History: GitHub lưu PR history — recruiter/collaborator sau này có thể xem reasoning đằng sau mỗi thay đổi lớn.

Quy trình đầy đủ:

# 1. Tạo branch
git checkout -b feature/add-rag-retrieval

# 2. Develop, commit nhiều lần
git add src/retrieval.py
git commit -m "feat(retrieval): implement vector search with ChromaDB"

git add tests/test_retrieval.py
git commit -m "test(retrieval): add unit tests for chunk and embed pipeline"

# 3. Push branch lên remote
git push origin feature/add-rag-retrieval

# 4. Mở PR trên GitHub
# GitHub sẽ gợi ý tạo PR khi detect branch mới

# 5. CI chạy, tự review diff, merge (squash recommended)
# 6. Xóa branch sau merge (GitHub có nút "Delete branch")

PR description nên có: mô tả ngắn về thay đổi, cách test, screenshot nếu có UI change. Không cần dài — 3–5 dòng là đủ cho solo project.

13

Tagging Release

Tag đánh dấu 1 commit là "phiên bản" — hữu ích khi bạn deploy v1.0 và cần rollback sau này, hoặc khi muốn gắn release note cho 1 milestone.

# Tạo annotated tag (khuyến nghị — có message và author)
git tag -a v1.0.0 -m "Initial release: RAG chatbot on internal docs"
git push origin v1.0.0

# Hoặc push tất cả tag
git push --tags

# Xem danh sách tag
git tag -l

# Checkout về code ở 1 tag cụ thể
git checkout v1.0.0

Trên GitHub, tag tự động tạo trang Releases. Thêm changelog và attach artifact (model config, requirements.txt snapshot) để người khác biết cách reproduce đúng phiên bản đó.

Dùng Semantic Versioning: vMAJOR.MINOR.PATCH. MAJOR: breaking change. MINOR: feature mới backward-compatible. PATCH: bug fix.

14

Track Experiment Với Commit Hash

Khi dùng MLflow hoặc W&B để log experiment, luôn include git commit SHA trong metadata của run. Điều này đảm bảo: nhìn vào bất kỳ run nào cũng biết code state nào đã tạo ra nó.

MLflow

import subprocess
import mlflow

def get_git_sha() -> str:
    try:
        return subprocess.check_output(
            ["git", "rev-parse", "HEAD"]
        ).strip().decode()
    except subprocess.CalledProcessError:
        return "unknown"

with mlflow.start_run():
    mlflow.set_tag("git_sha", get_git_sha())
    mlflow.log_param("learning_rate", 1e-4)
    mlflow.log_metric("f1", 0.84)

W&B (Weights & Biases)

import wandb
import subprocess

git_sha = subprocess.check_output(
    ["git", "rev-parse", "HEAD"]
).strip().decode()

run = wandb.init(
    project="churn-prediction",
    config={
        "learning_rate": 1e-4,
        "epochs": 10,
    },
    notes=f"git_sha={git_sha}",
)
run.config.update({"git_sha": git_sha})

W&B cũng có tính năng tự động detect git repo nếu chạy trong thư mục có .git — check wandb.run.metadata sau khi init để xem git info đã được capture chưa.

Điều kiện để reproduce hoạt động

  • Commit code trước khi chạy experiment — nếu code uncommitted thì SHA hiện tại không phản ánh đúng state chạy.
  • Không rebase / force push branch đã chứa experiment SHA đó.
  • Lưu cả requirements.txt snapshot tương ứng với SHA (hoặc dùng virtual environment + lock file).
15

Khi Lỡ Commit Secret

Secret đã commit vào git history vẫn tồn tại ngay cả sau khi bạn xóa file đó và commit lại. Lý do: git lưu toàn bộ object history — commit cũ vẫn accessible qua SHA.

Bước 1 — Revoke key ngay lập tức. Đây là bước quan trọng nhất và phải làm trước. Vào dashboard của service tương ứng (OpenAI, AWS, GitHub) và rotate / revoke key. Xem key đó là compromised — không chờ cleanup git xong rồi mới revoke.

Bước 2 — Rewrite history với git filter-repo.

# Cài git-filter-repo (pip install git-filter-repo)
pip install git-filter-repo

# Xóa file chứa secret khỏi toàn bộ history
git filter-repo --invert-paths --path config/secrets.yml

# Hoặc xóa 1 pattern cụ thể (ví dụ: key string)
git filter-repo --replace-text replacements.txt
# replacements.txt nội dung:
# sk-proj-xxxxxxxxxxxx==>REDACTED_KEY

# Force push tất cả branches
git push origin --force --all
git push origin --force --tags

Bước 3 — Verify trên GitHub. Vào Security tab → Secret scanning alerts (nếu repo public, GitHub sẽ alert nếu phát hiện pattern API key). Confirm secret đã không còn xuất hiện trong history.

Lưu ý khi làm trong team: Force push lên shared branch yêu cầu tất cả member xóa local clone và clone lại. Thông báo trước khi thực hiện để tránh mất commit của người khác.

Cách tốt nhất: đừng để xảy ra ngay từ đầu. Dùng pre-commit với hook detect-secrets để chặn commit secret trước khi vào history.

16

Anti-Pattern Thường Gặp

Các lỗi này hay gặp ở người mới chuyển sang AI/ML từ ngành khác, hoặc người chưa quen với Git trong project thực tế:

  • "Initial commit" với 50+ file — dump toàn bộ code sau khi xong project. History không cho thấy development process. Recruiter nhìn vào thấy 1 commit duy nhất ngay lập tức biết đây không phải workflow thực tế.
  • Commit message không có nghĩa. "update", "fix", "changes", "final", "final2" — những message này không trả lời được "cái gì thay đổi và tại sao".
  • Commit model weight vào git. File .pth 200MB sẽ làm repo phình to — git clone mất nhiều phút, GitHub có thể từ chối push nếu vượt 100MB/file.
  • git push --force lên main trong team. Force push lên shared branch có thể xóa commit của người khác nếu họ đã pull và làm việc trên đó. Chỉ force push lên branch của bạn và chỉ khi thực sự cần thiết.
  • Quên .gitignore từ đầu. Commit .env lần đầu rồi mới xóa — secret vẫn còn trong history. Cleanup tốn thêm nhiều bước (xem mục 15).
  • Commit khi code chưa chạy được. main branch bị broken, CI fail liên tục — người clone repo sau không clone được code working.
  • Quên git pull trước khi push. Kết quả: conflict khi push, hoặc push lên một base cũ làm mất commit của người khác khi không dùng merge strategy đúng.
  • Branch tên không mô tả. test1, dev, wip tích tụ mà không bao giờ merge hoặc xóa. Sau 3 tháng không ai biết branch đó để làm gì.
17

Tóm Tắt

  • Feature branch workflow là lựa chọn tốt cho portfolio project: main stable, develop trên nhánh riêng, merge qua PR.
  • Conventional Commits (feat, fix, refactor, chore...) giúp history có ý nghĩa và dễ scan. Thêm experimentdata cho ML-specific change.
  • Atomic commits: 1 commit = 1 thay đổi có ý nghĩa, không quá to cũng không spam.
  • Branch naming: feature/, fix/, experiment/, refactor/ + tên mô tả.
  • .gitignore phải có trước khi git init hoàn tất. Chặn data lớn, model weight, secret, .env.
  • File lớn (dataset, model): dùng DVC cho ML pipeline, Git LFS cho asset binary tĩnh.
  • .gitattributes kiểm soát line ending — tránh diff giả giữa Windows/Mac/Linux.
  • Track experiment: log git SHA vào MLflow/W&B metadata — đảm bảo mọi run đều biết code state nào tạo ra nó.
  • Lỡ commit secret: revoke key ngay, rồi dùng git filter-repo để rewrite history.