Nếu bạn đã từng làm việc với các dự án liên quan đến âm thanh, sáng tác nhạc tự động (Generative AI) hay xử lý ngôn ngữ tự nhiên (đặc biệt là các ngôn ngữ có thanh điệu như tiếng Việt), chắc hẳn bạn sẽ hiểu sự đáng sợ của “bùng nổ tổ hợp” (combinatorial explosion).
Hôm nay, tôi muốn chia sẻ câu chuyện về cách team Ayai của chúng tôi đã đụng độ với bài toán này trong dự án mới nhất, và cách thuật toán Tone Permutation Generator đã trở thành “vị cứu tinh” giúp hệ thống không bị sập.

Vấn Đề Chúng Tôi Gặp Phải: “Cơn Ác Mộng” Giai Thừa
Dự án của chúng tôi là một ứng dụng tự động phân tích và tạo ra các chuỗi âm điệu (tonal sequences) dựa trên một bộ nốt/thanh điệu đầu vào. Mục tiêu là tìm ra tất cả các biến thể có thể có của một chuỗi âm để AI có thể đánh giá và chọn ra giai điệu bắt tai nhất.
Ban đầu, mọi thứ rất trơn tru khi số lượng âm (tones) đầu vào nhỏ (khoảng 3 đến 4 âm). Tuy nhiên, khi người dùng nhập vào 8 hoặc 10 âm, hệ thống bắt đầu có dấu hiệu quá tải.
- Lý do: Số lượng hoán vị tăng theo giai thừa
O(n!). Với 10 âm khác nhau, chúng ta có 3,628,800 cách sắp xếp. - Hậu quả: Hàm đệ quy sinh hoán vị cũ của chúng tôi (chạy theo kiểu brute-force và lưu tất cả kết quả vào một mảng) đã ngốn sạch RAM, khiến server bị timeout và văng lỗi “Out of Memory”.
Chúng tôi cần một giải pháp thông minh hơn là chỉ tăng cấu hình máy chủ.
Giải Pháp: Tone Permutation Generator
Sau khi nghiên cứu, team quyết định đập đi xây lại module này và áp dụng thuật toán Tone Permutation Generator. Đây không chỉ là một thuật toán sinh hoán vị thông thường, mà được thiết kế đặc thù để xử lý dữ liệu âm thanh thông qua hai nguyên tắc cốt lõi:
1. Cơ Chế “Lazy Evaluation” (Đánh giá lười)
Thay vì sinh ra hàng triệu mảng chuỗi âm cùng lúc và lưu vào bộ nhớ, thuật toán sử dụng cấu trúc Generator (tương tự như yield trong Python hay function* trong JavaScript).
Mỗi lần hệ thống cần một chuỗi âm điệu mới để AI đánh giá, Generator mới tính toán và trả về chính xác một cấu hình tiếp theo. Bộ nhớ lúc này chỉ cần lưu trữ trạng thái hiện tại của vòng lặp, giúp RAM sử dụng gần như không đáng kể (từ vài GB giảm xuống chỉ còn vài MB).
2. Tích Hợp Ràng Buộc Âm Nghệ Thuật (Constraint-based Pruning)
Trong âm nhạc hoặc ngôn ngữ, không phải sự kết hợp thanh điệu nào cũng có ý nghĩa. Chúng tôi đã tinh chỉnh thuật toán sinh hoán vị (dựa trên thuật toán Heap) bằng cách thêm vào các điều kiện cắt tỉa (pruning) ngay trong lúc sinh nhánh:
- Bỏ qua ngay lập tức các chuỗi có 3 âm quá cao hoặc quá trầm đi liền nhau (tránh gây chói tai).
- Nhận diện và loại bỏ các hoán vị trùng lặp nếu đầu vào có các âm đồng nhất (Ví dụ: 2 nốt Đô giống hệt nhau).
Thành Quả Đạt Được
Việc áp dụng Tone Permutation Generator đã mang lại sự thay đổi ngoạn mục cho dự án:
| Tiêu chí | Trước khi tối ưu (Brute-force) | Sau khi dùng Tone Permutation Generator |
| Tiêu thụ RAM | ~4.5 GB (Với 10 tones) | ~15 MB (Chỉ lưu state của Generator) |
| Thời gian phản hồi | > 15 giây (Thường bị Timeout) | < 200 ms (Trả kết quả dạng stream) |
| Độ ổn định | App thường xuyên crash | Chạy mượt mà ngay cả với 12+ tones |
“Điểm mấu chốt không phải là xử lý dữ liệu nhanh hơn, mà là không tạo ra những dữ liệu thừa thãi ngay từ đầu.” – Lead Backend của team.
🛠 Lời Kết
Qua dự án này, team chúng tôi nhận ra rằng khi làm việc với các bài toán tổ hợp, việc quản lý bộ nhớ và tối ưu hóa thuật toán quan trọng hơn rất nhiều so với sức mạnh phần cứng thô. Việc biến một hàm sinh mảng thông thường thành một Generator kết hợp với logic cắt tỉa nhánh đã cứu sống cả hệ thống.
Nếu dự án của bạn cũng đang phải xử lý các bài toán sinh hoán vị cho âm thanh, bước sóng, hay chuỗi ngôn ngữ, hãy thử cân nhắc xây dựng một Tone Permutation Generator cho riêng mình nhé!
