5 điểm bởi GN⁺ 2023-10-22 | 1 bình luận | Chia sẻ qua WhatsApp
  • GPU có kiến trúc ưu tiên thông lượng xử lý song song quy mô lớn hơn độ trễ thấp cho từng lệnh đơn lẻ, nên rất mạnh trong các tác vụ thực hiện hàng loạt phép toán cùng loại như deep learning, đồ họa và tính toán số
  • Trong khi CPU giảm độ trễ thực thi tuần tự bằng pipelining, thực thi ngoài thứ tự, thực thi suy đoán và cache nhiều cấp, GPU che giấu độ trễ và tăng thông lượng bằng nhiều ALU và thread
  • Ở độ chính xác 32-bit, Nvidia Ampere A100 đạt 19,5 TFLOPS, còn bộ xử lý Intel 24 nhân năm 2021 đạt 0,66 TFLOPS; khoảng cách về thông lượng tính toán số tiếp tục nới rộng
  • CUDA kernel có host code trên CPU đảm nhận phần chuẩn bị thực thi, còn device code trên GPU chạy theo cấu trúc grid·block·thread; các thread được gom theo đơn vị 32 thành warp và xử lý theo kiểu SIMT
  • Hiệu năng thực tế phụ thuộc lớn vào cách phân chia register, shared memory, block slot và thread slot của SM; nếu occupancy thấp thì khó che giấu độ trễ và có thể không đạt được thông lượng tối đa

Khác biệt về mục tiêu thiết kế giữa CPU và GPU

  • CPU chủ yếu được thiết kế để xử lý nhanh việc thực thi lệnh tuần tự
    • Để giảm độ trễ thực thi lệnh, CPU dùng các tính năng như instruction pipelining, out-of-order execution, speculative execution và multilevel cache
    • Với một phép toán đơn lẻ như cộng hai số hoặc một luồng tính toán ngắn, CPU có thể xử lý với độ trễ thấp hơn GPU
  • GPU được thiết kế xoay quanh tính song song quy mô lớn và thông lượng cao
    • Các tác vụ cần thực hiện nhanh nhiều phép toán đại số tuyến tính và tính toán số, như video game, đồ họa, tính toán số và deep learning, rất phù hợp với kiến trúc này
    • Với hàng triệu hoặc hàng tỷ phép toán cùng loại, GPU có thể xử lý nhanh hơn CPU rất nhiều nhờ tính song song quy mô lớn
  • Hiệu năng tính toán số được đo bằng FLOPS, tức số phép toán dấu phẩy động mỗi giây
    • Nvidia Ampere A100 cung cấp thông lượng 19,5 TFLOPS ở độ chính xác 32-bit
    • Tính đến năm 2021, bộ xử lý Intel 24 nhân đạt khoảng 0,66 TFLOPS ở độ chính xác 32-bit
    • Khoảng cách thông lượng giữa GPU và CPU ngày càng lớn qua từng năm

Cách GPU che giấu độ trễ

  • Ngay cả khi độ trễ của từng lệnh cao, GPU vẫn đạt được khả năng chịu độ trễ bằng cách tận dụng nhiều thread và tài nguyên tính toán
  • Trong khi một thread đang chờ kết quả của lệnh, GPU sẽ thực thi các thread khác không phải chờ
  • Nhờ cách lập lịch này, các đơn vị tính toán có thể tiếp tục hoạt động nhiều nhất có thể và duy trì thông lượng cao

Kiến trúc GPU compute

  • GPU gồm một mảng nhiều streaming multiprocessor (SM)
  • Mỗi SM chứa nhiều streaming processor, core và thread
    • Nvidia H100 có 132 SM, mỗi SM có 64 core, tổng cộng 8.448 core
  • Mỗi SM có một vùng bộ nhớ on-chip giới hạn được tất cả core dùng chung
    • Bộ nhớ này được gọi là shared memory hoặc scratchpad
  • Tài nguyên của control unit trong SM cũng được các core dùng chung
  • Mỗi SM có thread scheduler dựa trên phần cứng để thực thi thread
  • Tùy workload, GPU cũng có thể bao gồm các đơn vị chức năng chuyên biệt hoặc đơn vị tính toán tăng tốc như tensor core và ray tracing unit

Phân cấp bộ nhớ GPU

  • Register

    • Mỗi SM có số lượng register rất lớn
    • Nvidia A100 và H100 có 65.536 register trên mỗi SM
    • Register được các core dùng chung và được cấp phát động theo nhu cầu của thread
    • Register được cấp phát cho một thread cụ thể trong khi thực thi là riêng của thread đó, nên thread khác không thể đọc hoặc ghi
  • constant cache

    • Cache dữ liệu hằng số được mã chạy trên SM sử dụng
    • Lập trình viên phải khai báo rõ đối tượng là hằng số trong code thì GPU mới có thể lưu nó trong constant cache
  • shared memory

    • Đây là SRAM on-chip nhỏ, nhanh, độ trễ thấp và có thể lập trình, nằm trong mỗi SM
    • Được chia sẻ bởi các thread block chạy trên cùng một SM
    • Khi nhiều thread dùng cùng một mảnh dữ liệu, chỉ một thread cần đọc từ global memory rồi chia sẻ cho các thread còn lại, nhờ đó giảm các lần load trùng lặp
    • Cũng được dùng làm cơ chế đồng bộ giữa các thread bên trong thread block
  • L1 cache và L2 cache

    • Mỗi SM có L1 cache để cache dữ liệu thường được truy cập từ L2 cache
    • L2 cache được tất cả SM dùng chung, cache dữ liệu thường được truy cập từ global memory để giảm độ trễ
    • L1 và L2 cache hoạt động trong suốt với SM, nên từ góc nhìn của SM, dữ liệu trông như được nhận từ global memory
  • global memory

    • GPU có global memory nằm off-chip, là DRAM dung lượng lớn và băng thông cao
    • Nvidia H100 có 80GB HBM và băng thông 3000GB/s
    • global memory nằm xa SM nên có độ trễ cao, nhưng phân cấp bộ nhớ on-chip và nhiều đơn vị tính toán giúp che giấu độ trễ này

CUDA kernel và cấu trúc thread

  • CUDA là giao diện lập trình để viết chương trình cho GPU Nvidia
  • Phần tính toán chạy trên GPU được biểu diễn dưới dạng kernel, có hình thức tương tự hàm C/C++
    • Ví dụ là một kernel cộng vector: nhận hai vector làm đầu vào, cộng từng phần tử và ghi kết quả vào vector thứ ba
  • Khi chạy kernel, nhiều thread được khởi động; toàn bộ tập hợp này được gọi là grid
    • grid gồm một hoặc nhiều thread block
    • Mỗi thread block gồm một hoặc nhiều thread
  • Số block và số thread thay đổi tùy theo kích thước dữ liệu và mức song song mong muốn
    • Với phép cộng vector 256 chiều, có thể tạo một block gồm 256 thread để mỗi thread xử lý một phần tử của vector
    • Với bài toán lớn hơn, số thread sẵn có của GPU có thể không đủ, nên có thể để mỗi thread xử lý nhiều điểm dữ liệu
  • Triển khai CUDA được chia thành hai phần
    • host code chạy trên CPU, chịu trách nhiệm tải dữ liệu, cấp phát bộ nhớ GPU và chạy kernel với thread grid đã cấu hình
    • device code chạy trên GPU và định nghĩa hàm kernel thực tế

Các bước chạy kernel trên GPU

  • Sao chép dữ liệu từ host sang device

    • Trước khi chạy kernel, cần sao chép dữ liệu cần thiết từ bộ nhớ CPU sang GPU global memory
    • Trên phần cứng GPU hiện đại, cũng có thể dùng unified virtual memory để đọc trực tiếp từ host memory
  • Lập lịch thread block lên SM

    • Khi dữ liệu cần thiết đã sẵn sàng trong bộ nhớ GPU, thread block được gán cho SM
    • Tất cả thread trong một block được xử lý đồng thời trên cùng một SM
    • GPU phải đảm bảo tài nguyên SM cần thiết cho các thread đó trước khi thực thi
    • Trên thực tế, nhiều thread block có thể được gán đồng thời cho cùng một SM
    • Vì số SM có hạn và kernel lớn có thể có số block rất lớn, không phải tất cả block đều chạy ngay lập tức
    • GPU duy trì danh sách các block đang chờ; khi một block kết thúc, GPU gán một block đang chờ vào thực thi
  • SIMT và warp

    • Các thread được gán cho SM lại được gom thành từng nhóm 32; nhóm này được gọi là warp
    • Kích thước warp của GPU Nvidia thế hệ hiện tại là 32, nhưng có thể thay đổi ở phần cứng tương lai
    • SM lấy và phát cùng một lệnh cho tất cả thread trong một warp
    • Các thread thực thi cùng một lệnh đồng thời nhưng xử lý các phần dữ liệu khác nhau
    • Mô hình này được gọi là single instruction multiple threads(SIMT) và tương tự lệnh SIMD của CPU
    • Các GPU hiện đại từ Volta trở đi cũng có independent thread scheduling, cho phép tính đồng thời đầy đủ giữa các thread bất kể warp
  • Lập lịch warp và khả năng chịu độ trễ

    • Dù tất cả processing block trong SM xử lý warp, tại một thời điểm nhất định chỉ có một phần warp thực sự thực thi lệnh
    • Lý do là số execution unit của SM có hạn
    • Khi một warp chờ kết quả của một lệnh mất nhiều thời gian, SM đặt warp đó ở trạng thái chờ và thực thi warp khác không cần chờ
    • Mỗi thread trong mỗi warp có tập register riêng, nên việc chuyển đổi giữa các warp không có overhead riêng
    • Context switching giữa các process trên CPU tốn kém vì phải lưu register vào main memory và khôi phục trạng thái của process khác
  • Sao chép dữ liệu kết quả từ device về host

    • Khi tất cả thread của kernel thực thi xong, kết quả được sao chép lại về host memory

Phân chia tài nguyên và occupancy

  • Mức sử dụng tài nguyên GPU được đo bằng chỉ số occupancy
    • occupancy là tỷ lệ giữa số warp được gán cho SM và số warp tối đa mà SM đó có thể hỗ trợ
    • Để đạt thông lượng tối đa, occupancy 100% là mong muốn, nhưng không phải lúc nào cũng có thể do nhiều ràng buộc
  • SM có các tài nguyên thực thi cố định như register, shared memory, thread block slot và thread slot
    • Các tài nguyên này được phân chia động theo yêu cầu của thread và giới hạn của GPU
  • Ví dụ về Nvidia H100
    • Mỗi SM có thể xử lý 32 block, 64 warp, tức 2048 thread
    • Hỗ trợ tối đa 1024 thread trên mỗi block
    • Nếu chạy với kích thước block 1024 thread, 2048 thread slot sẽ được chia cho 2 block
  • Phân chia động có thể sử dụng tài nguyên tính toán hiệu quả hơn phân chia cố định
    • Trong phân chia cố định, mỗi thread block nhận một lượng tài nguyên thực thi cố định
    • Trong một số trường hợp, thread được cấp nhiều tài nguyên hơn mức cần thiết, gây lãng phí tài nguyên và giảm thông lượng
  • Ví dụ làm giảm occupancy
    • Nếu đặt kích thước block là 32 thread và cần tổng cộng 2048 thread, sẽ tạo ra 64 block
    • Tuy nhiên, mỗi SM chỉ có thể xử lý 32 block cùng lúc, nên thực tế chỉ 1024 thread được chạy và occupancy là 50%
    • Khi mỗi SM có 65.536 register, để chạy đồng thời 2048 thread thì mỗi thread chỉ có thể dùng tối đa 32 register
    • Nếu kernel cần 64 register cho mỗi thread, mỗi SM chỉ chạy được 1024 thread, nên occupancy lại là 50%
  • occupancy thấp khiến GPU không thể che giấu đủ độ trễ, đồng thời có thể làm giảm thông lượng tính toán cần thiết để đạt thông lượng tối đa của phần cứng
  • Để viết GPU kernel hiệu quả, cần phân bổ tài nguyên cẩn thận nhằm giảm độ trễ trong khi duy trì occupancy cao
    • Dùng nhiều register có thể làm bản thân code chạy nhanh hơn, nhưng cũng có thể làm occupancy thấp đi, nên việc cân bằng tối ưu hóa rất quan trọng

Tài liệu tham khảo thêm

1 bình luận

 
GN⁺ 2023-10-22
Ý kiến trên Hacker News
  • Có người đã gửi email khiếu nại về bài này: https://twitter.com/abhi9u/status/1715753871564476597
    Đây là vi phạm quy tắc HN. Thực tế, đây là mục duy nhất đủ quan trọng để xuất hiện cả trong hướng dẫn trang lẫn FAQ, và người dùng HN rất nhạy cảm với vấn đề này
    H: Tôi có thể nhờ mọi người upvote bài của mình không?
    Đ: Không. Người dùng chỉ nên bỏ phiếu khi bản thân họ thấy nội dung nào đó có giá trị trí tuệ, chứ không phải vì ai đó có thứ cần quảng bá. Nếu vi phạm quy tắc này, bài viết, tài khoản hoặc trang web có thể bị phạt hoặc chặn, nên đừng làm vậy
    https://news.ycombinator.com/newsfaq.html
    Đừng yêu cầu upvote, bình luận hoặc gửi bài. Người dùng nên bỏ phiếu và bình luận khi họ tự mình bắt gặp thứ gì đó mà họ thấy thú vị về mặt cá nhân, chứ không phải vì mục đích quảng bá
    https://news.ycombinator.com/newsguidelines.html

    • Tôi không biết quy tắc đó, và cũng không quen người đăng bài
      Giờ thì tôi biết rồi, nên sẽ không làm vậy nữa
  • Tôi khá ngạc nhiên vì phần “sao chép dữ liệu từ host sang thiết bị” không nhắc tới sao chép bất đồng bộ. Nếu muốn tận dụng GPU tối đa, bạn không thể để GPU ngồi không trong lúc sao chép dữ liệu giữa host và GPU
    Nhiều framework cung cấp cơ chế lên lịch sao chép bất đồng bộ có thể chạy cùng với việc gửi tác vụ bất đồng bộ. Bài viết này nghiêng về nhập môn GPU, nhưng trong lập trình GPU thực tế còn có vô số mẹo và kỹ thuật vượt xa mức đó nếu muốn vắt kiệt chiếc GPU đắt tiền đến giới hạn cuối cùng. Như đa số tối ưu hóa ngày nay, ở đây có rất nhiều vách đá ẩn và tính phi tuyến, nên công cụ profiling rất hữu ích

    • Có lẽ bạn sẽ dùng số thực dấu chấm động 64-bit (double), và nếu vậy thì không phải GPU nào cũng giúp được nhiều. Đặc biệt là khi so với một CPU mạnh
      Tuy vậy, dùng GPU có nhiều đơn vị FP64 vẫn có thể nhanh hơn đáng kể. Thường thì đó không phải GPU gaming, nhưng nếu bạn sẵn có một chiếc 4060 thì hiệu năng FP64 khoảng 300 GFLOPS, có khả năng vẫn cao hơn CPU. CPU hiện đại cũng rất mạnh ở mảng này, có thể phát hành nhiều phép toán FP64 mỗi xung nhịp trên mỗi lõi
  • Câu mở đầu “hầu hết lập trình viên đều hiểu sâu về CPU” rõ ràng sai đến mức, dù bài có thể rất hay, tôi vẫn khó mà nghiêm túc tiếp nhận phần còn lại

    • Thử sửa thành thế này thì sao: “một lượng đáng kể các nhà khoa học máy tính, kỹ sư máy tính, kỹ sư điện và lập trình viên nghiệp dư ...”
      Tôi từng học một lớp triết cho vui ở đại học, và ở đó tôi học được khả năng không vứt bỏ ngay một câu viết dở mà thay vào đó đọc nó như một phiên bản có thể sửa cho tốt hơn. Giờ não tôi tự động dịch những khái quát hóa quá mức hay điều sai hiển nhiên thành các mệnh đề gần đúng hợp lý. Khi lập luận phát triển, tôi tái cấu trúc những ý tưởng đó và có thể đánh giá toàn bài như một chỉnh thể nhất quán về mặt logic
      Nhờ vậy, ngay cả khi đọc một bài viết tệ, tôi vẫn giữ lại được những tiền đề và luận điểm đúng hoặc sai mới về chủ đề mình quan tâm, và nhờ đó thế giới tinh thần của tôi rộng ra thêm
    • Với đa số lập trình viên thì chắc chắn không đúng, nhưng có thể tác giả đang muốn nói đến kỹ sư được đào tạo CS. Nếu học chương trình khoa học máy tính bài bản, bạn thường sẽ hiểu CPU khá sâu, còn GPU thì thường chỉ được học hời hợt hơn nhiều
    • Tôi không hiểu vì sao dưới mọi bài viết trên internet luôn có ít nhất một bình luận kiểu “tôi ngừng đọc ở chỗ X”. Kiểu nói đó chẳng đóng góp gì cả
    • Có lẽ hơn nửa cuộc tranh luận này phụ thuộc vào việc định nghĩa hiểu sâu như thế nào
      Tôi đã học các sự thật cơ bản về kiến trúc CPU ở đại học, biết rất sơ lược về bức tranh tổng thể, và thỉnh thoảng cập nhật thêm một ít kiến thức giới hạn, nhưng tôi sẽ không gọi đó là “hiểu sâu”. Có lẽ nói là “hiểu cơ bản về cách CPU hoạt động, được thiết kế và được sử dụng” thì đúng hơn
      Nếu bạn thành thạo assembly thì có thể nói là bạn “hiểu sâu” cách dùng CPU ở mức thấp, nhưng nghe vẫn hơi cường điệu. Nó cũng khác với việc là chuyên gia thiết kế CPU/GPU
      Nên tôi đồng ý. Dù vậy, bài viết vẫn thú vị, đặc biệt là các sơ đồ minh họa rất tốt
    • Tôi đã học cả trong chương trình lấy bằng lẫn trong lớp Structure and Interpretation of Computer Programs, và tôi khuyên bất kỳ ai quan tâm đến điện toán mức thấp nên học lớp đó
  • Đoạn “các thanh ghi được cấp cho một luồng đang chạy là dành riêng cho luồng đó, nên luồng khác không thể đọc hoặc ghi” có ngoại lệ
    wave intrinsic của HLSL và các tính năng tương tự trong CUDA cho phép đọc thanh ghi của các luồng khác trong cùng wavefront hiện tại. Ngoài ra, ở đoạn về kiến trúc bộ nhớ cũng đáng nhắc tới rằng cache không đảm bảo tính nhất quán giữa các luồng trong cùng dispatch/grid, nhưng các khối chức năng đặc biệt tồn tại ở phạm vi toàn chip sẽ triển khai các phép toán nguyên tử trên bộ nhớ toàn cục

  • Lập trình SIMD thực sự rất khắc nghiệt
    Muốn chạy tính toán trên mọi pixel trên màn hình à? Không vấn đề
    Muốn thêm điều kiện rẽ nhánh à? Đau đấy

    • Muốn thêm eval à? Mọi thứ dừng hết
    • Nói công bằng thì điều này cũng hợp lý. Việc đưa ra quyết định thông minh “khó hơn” so với việc mở rộng các phép tính đơn giản ra cho rất nhiều worker
  • Tại sao vẫn còn gọi là GPU? PPU (thiết bị xử lý song song) nghe có vẻ là cái tên hay hơn

    • Vì ngoài khả năng GPU đa dụng, nó còn có silicon chuyên dụng cho đồ họa
    • Vì ai cũng hiểu GPU nghĩa là gì
      Quan hệ giữa drone và quad-copter cũng tương tự vậy
    • thiết bị xử lý vector có vẻ còn phù hợp hơn
    • CPU cũng là PPU
    • General Processing Unit
  • Bài viết rất hay. Và GPU đã phát triển vượt xa và có hiệu năng tốt hơn bất cứ thứ gì tôi có thể nghĩ ra đối với đúng loại công việc của nó.
    Nhưng với SIMD thì sau khi học các mô hình khác linh hoạt hơn, tôi muốn xếp nó vào nhóm không thực sự cần thiết. Tôi thích MIMD và cluster/transputer hơn, nhưng chúng dường như đã biến mất vào khoảng những năm 2000. Tình trạng hiện nay buộc lập trình viên phải tự di chuyển dữ liệu, viết shader dưới những giới hạn tùy tiện về số lượng vị trí bộ nhớ có thể truy cập đồng thời, lặp lại công việc bằng cách dùng ngôn ngữ riêng cho GPU/CPU, phải biết phần cứng nào có cho các tính năng như ray tracing, và bị trói vào những framework đầy tính chủ quan như OpenGL/Metal/Vulkan. GPU là một nhánh rẽ không bao giờ có thể đưa tôi đến nơi tôi muốn tới, nên 25 năm qua giống như trải nghiệm của một người đang sống trong một dòng thời gian sai lầm.
    Nói một cách tương đối, một CPU đa dụng có thể mở rộng trong các ràng buộc sau khi định luật Moore chấm dứt nên là dạng đa lõi với bộ nhớ cục bộ, chia sẻ dữ liệu bằng bộ nhớ định địa chỉ theo nội dung kiểu copy-on-write hoặc các cơ chế cache khác, và cung cấp một không gian địa chỉ hợp nhất duy nhất để người dùng có thể tự do khám phá mọi phương thức tính toán trong môi trường desktop computing. Nó dùng assembly tiêu chuẩn nhưng thường được lập trình bằng các ngôn ngữ lập trình hàm như Erlang/Go, Octave/MATLAB, lý tưởng nhất là Julia. 3D rendering và thư viện AI là các lớp nằm bên trên chứ không phải yếu tố nền tảng.
    Điều thú vị là GPU đã gần đạt tới cấu hình đa lõi mà tôi nói đến, nhưng driver lại tách người dùng khỏi quyền truy cập bare metal cần thiết cho MIMD đa dụng. Tôi từng nghĩ chỉ có FPGA mới là cách phá vỡ ưu thế của GPU, nhưng có lẽ vẫn có cơ hội viết driver để làm cho phần cứng GPU trông giống MIMD với bộ nhớ hợp nhất. Tôi không rõ các lõi GPU xử lý phép toán số nguyên tốt đến mức nào, nhưng có vẻ có thể xấp xỉ bằng phần số nguyên 32-bit của số thực dấu chấm động 64-bit. Vì những đánh đổi như vậy, một máy MIMD có thể chậm hơn GPU 10–100 lần nhưng vẫn nhanh hơn CPU 10–100 lần. Đồng thời nó vẫn có thể mở rộng mà không phụ thuộc quá mức vào cache lớn và bus tốc độ cao, những thứ đã làm CPU đình trệ từ khoảng năm 2007 khi thị trường di động nắm quyền chi phối và giá cùng hiệu quả điện năng được ưu tiên hơn hiệu năng. Máy MIMD cũng có thể được cluster hóa để tạo ra mạng tính toán phân tán kiểu SETI@home mà không cần thay đổi mã. Để hình dung nó có thể trao quyền cho người dùng phổ thông đến mức nào, có thể so sánh như BitTorrent so với FTP của tính toán thay vì dữ liệu.

  • Tôi không thực sự hiểu kiến trúc Apple Silicon khác NVIDIA như thế nào.
    Khi đọc câu “Nvidia H100 GPU có 132 SM, mỗi SM có 64 lõi, tổng cộng 8448 lõi”, thì con số 8448 lõi rõ ràng rất ấn tượng. Nhưng Apple M2 Ultra chỉ có 76 lõi?
    Làm sao NVIDIA H100 GPU lại có thể có số lõi nhiều hơn hơn 110 lần? Rõ ràng hiệu năng của nó không cao hơn M2 Ultra tới 110 lần, vậy ở đây thực chất đang diễn ra điều gì?

    • Nói chung thì SM của NVIDIA gần nhất với CU của GPU AMD hoặc lõi của GPU Apple. Ở đây “lõi” là các thành phần con của SM thực hiện các phép toán riêng lẻ.
      Tham khảo sơ đồ này từ blog NVIDIA: https://developer-blogs.nvidia.com/wp-content/uploads/2021/g...
      (https://developer.nvidia.com/blog/nvidia-ampere-architecture...)
    • NVIDIA gọi thứ về bản chất là vector lane là “lõi”, và cũng dùng “thread” trong SIMT để chỉ việc thực thi của một vector lane như vậy, điều này cố ý gây nhập nhằng và nói thật là không trung thực.
      Dĩ nhiên có thể lập luận rằng việc mỗi lane hỗ trợ program counter riêng khiến nó có lý do để được gọi là “thread”, nhưng rốt cuộc điều quan trọng vẫn là tốc độ và thông lượng của ALU.
    • H100 có thể dùng để sưởi ấm cả một căn phòng. Nó tiêu thụ điện nhiều hơn M2 Ultra hơn 10 lần.
  • Giờ thì đã hiểu vì sao machine learning dùng số dấu phẩy động cho độ chính xác. Không phải là một lựa chọn, mà là vì code đồ họa vốn dùng như vậy
    Đây là một mảnh ghép khác của câu đố “vì sao machine learning lại kém hiệu quả đến vậy”
    Tò mò không biết overhead sao chép bộ nhớ trong môi trường thực tế lớn đến mức nào. Nếu nó hoạt động như công việc thông thường thì sẽ rất khắc nghiệt. Người ta còn chuyển xử lý TCP sang phần cứng để tránh điều đó. Ở đây dữ liệu nhiều hơn nhiều, nhưng cũng được xử lý theo khối lớn hơn

    • Trong khá nhiều mạng lớn hiện đại, thời gian tính toán GPU cho việc tính gradient và backprop chậm đến mức việc sao chép dữ liệu dấu phẩy động qua bus PCIe không phải là nút thắt cổ chai
      Nói cách khác, sao chép một minibatch ảnh dấu phẩy động vẫn đủ nhanh. Vì vòng lặp gradient/SGD chậm và khối lượng tính toán rất lớn. Kể cả khi dùng mixed precision cũng vậy
      Với mạng nông, có thể có lợi khi chỉ sao chép dữ liệu nén ban đầu vào bộ nhớ GPU, rồi giải nén v.v. trên GPU. Nhưng việc GPU hiện đại vẫn chưa áp dụng PCIe 5 là vì hiệu năng tính toán thô quan trọng hơn
      Cuối cùng, Tensor Core cũng có ảnh hưởng lớn, và tùy mạng mà chúng có thể nhanh đến mức mức sử dụng trở nên rất thấp
    • Tôi không cho rằng việc chọn dùng số dấu phẩy động là đặc biệt kém hiệu quả. Nếu framework mặc định là fixed-point thì sẽ rất khó cân chỉnh dải động trên toàn bộ mạng
      Toán học của quá trình huấn luyện cũng giả định rằng các con số là liên tục
    • Dấu phẩy động lớn hơn, và phép toán của nó cũng khó hơn
      Tuy vậy, tôi từng thắc mắc vì sao LLM chạy trên CPU lại lượng tử hóa. Theo cách tôi hiểu, đó là quá trình hạ độ chính xác của trọng số để dùng ít bộ nhớ hơn
      Không rõ việc thiếu độ chính xác có tạo ra khác biệt hay không. Nếu vậy thì vì sao ngay từ đầu lại dùng dấu phẩy động? Nếu độ chính xác không quan trọng thì phần độ chính xác bổ sung chỉ khiến tiêu tốn thêm tài nguyên mà không có lý do thực sự, và có lẽ còn dùng nhiều tài nguyên hơn mức cần thiết tới vài bậc độ lớn
      Lĩnh vực này ban đầu không được khởi xướng bởi những người hiểu rõ hiệu năng. Họ dùng công cụ để tạo ra thứ gì đó, nhưng không có câu trả lời cho “vì sao”. Họ làm vậy vì công cụ vốn làm như thế
      Điều này quan trọng vì: ngay cả trên CPU phổ thông, chỉ một cách truy cập dữ liệu cũng có thể nhanh hơn cách khác tới vài bậc độ lớn, nhưng bạn phải biết điều đó. Chẳng phải bạn muốn giảm chi phí LLM đi vài bậc độ lớn sao?
    • Điều gì ở dấu phẩy động là kém hiệu quả? Machine learning dường như hưởng lợi rất lớn từ khả năng tiếp cận dải động trải rộng qua nhiều bậc độ lớn
  • Bài nói và slide này từ vài năm trước về những phần hóc búa của CPU và GPU cũng đáng xem
    Alexander Titov — Know your hardware: CPU memory hierarchy https://youtu.be/QOJ2hsop6hM
    https://github.com/alexander-titov/public/blob/master/confer...
    Know Your Hardware - CPU Memory Hierarchy -- Alexander Titov -- C%2B%2B Moscow Meetup March 2019.pdf
    https://github.com/alexander-titov/public/blob/master/confer...
    GPGPU - what it is and why you should care -- Alexander Titov -- CoreHard 2019.pdf