Hành Trình Của Tôi Với Mô Hình AI Client-Side: Bài Học Từ Transformer.js
Một câu chuyện chân thật, từng bước mô tả lý do tại sao AI client-side không thành công trong dự án cá nhân của tôi và cách chuyển sang phương pháp chỉ sử dụng server đã cứu tôi khỏi nhiều ngày thất vọng.
Giấc Mơ So Với Thực Tế
Khi tôi bắt đầu dự án này, tôi có một tầm nhìn đầy tham vọng: tạo ra một hệ thống AI chạy hoàn toàn trên client, bảo vệ dữ liệu người dùng. Các mục tiêu của tôi rất đơn giản:
- Viết lại văn bản thành những câu dễ phát âm hơn
- Phân tích độ lưu loát theo thời gian thực
- Đưa ra lời khuyên về tốc độ nói cho người nói lắp
- Cung cấp các cuộc trò chuyện tương tác
- Đảm bảo không có dữ liệu nào rời khỏi thiết bị của người dùng
Tôi tưởng tượng một hệ thống mà người dùng có thể nhận được sự hỗ trợ từ AI mà không cần gửi dữ liệu nhạy cảm về giọng nói hoặc văn bản đến server. Nghe có vẻ hoàn hảo. Tuy nhiên, thực tế lại rất khác. Sau nhiều tuần sửa lỗi về sự không tương thích cơ bản của trình duyệt, sự cố bộ nhớ và lỗi thư viện bên trong, tôi nhận ra rằng giấc mơ này chưa sẵn sàng. Cuối cùng, tôi phải xóa toàn bộ mã AI client-side và chuyển sang phương pháp chỉ sử dụng server.
Giai Đoạn 1: Thảm Họa Tải Xuống
Tôi bắt đầu với một cấu hình đơn giản sử dụng transformer.js:
javascript
import { pipeline } from '@xenova/transformers';
// Ví dụ tài liệu đơn giản
const generator = await pipeline('text-generation', 'Xenova/distilgpt2');
const response = await generator(
"Viết lại câu này để dễ phát âm hơn: những con cua đáng sợ có thể sáng tạo"
);
Có vẻ đơn giản. Tài liệu thư viện hứa hẹn AI dễ dàng trên trình duyệt, không cần server.
Nhưng trình duyệt của tôi ngay lập tức báo lỗi:
javascript
transformerService.js:29 Không thể khởi tạo mô hình transformer:
SyntaxError: Token không hợp lệ '<', "<!DOCTYPE "... không phải là JSON hợp lệ
Chuyện gì đã xảy ra ở đây?
Transformer.js đang cố gắng tải các tệp mô hình từ CDN của Hugging Face. Thay vì nhận được các tệp mô hình JSON, nó nhận được các trang HTML 404. Thư viện sau đó cố gắng phân tích HTML như JSON, gây ra lỗi cú pháp.
Tôi đã thử nhiều mô hình khác nhau, nghĩ rằng một số có thể hoạt động:
javascript
const modelsToTry = [
{ name: 'TinyLlama', task: 'text-generation', model: 'Xenova/TinyLlama-1.1B-Chat-v1.0' },
{ name: 'GPT-2', task: 'text-generation', model: 'gpt2' },
{ name: 'DistilGPT-2', task: 'text-generation', model: 'Xenova/distilgpt2' },
{ name: 'FLAN-T5-small', task: 'text2text-generation', model: 'Xenova/flan-t5-small' }
];
Tôi thậm chí đã cố gắng cấu hình CDN một cách thủ công:
javascript
import { pipeline, env } from '@xenova/transformers';
env.allowLocalModels = false;
env.allowRemoteModels = true;
env.remoteURL = 'https://cdn.huggingface.co/';
Không có gì hoạt động. Nhiều mô hình được quảng cáo là tương thích với trình duyệt thực sự không tồn tại—hoặc liên kết tải xuống của chúng bị hỏng. Tại thời điểm này, tôi đã mất hai ngày chỉ để cố gắng tải xuống một mô hình.
Giai Đoạn 2: Cuộc Tàn Sát Bộ Nhớ
Cuối cùng, tôi đã tải xuống một số mô hình. Tôi rất hy vọng, nhưng vấn đề tiếp theo xuất hiện ngay lập tức: sự cố bộ nhớ.
javascript
const output = await generator("những con cua đáng sợ có thể sáng tạo");
Lỗi console:
javascript
transformerService.js:89 Đã xảy ra lỗi trong quá trình thực thi mô hình: "RangeError: offset nằm ngoài phạm vi"
Điều này xảy ra ngay cả với đầu vào sáu từ.
Tôi đã thử mọi điều chỉnh tham số mà tôi có thể tưởng tượng:
javascript
// Cố gắng bảo thủ
const output = await generator(prompt, {
max_new_tokens: 100,
temperature: 0.7,
do_sample: true,
});
// Cố gắng bảo thủ hơn
const output = await generator(prompt, {
max_length: 150,
temperature: 0.1,
do_sample: false,
});
// Tối thiểu
const output = await generator(prompt, {
max_length: 80,
temperature: 0.1,
do_sample: false,
});
// Gọi mặc định
const output = await generator(prompt);
Không có sự kết hợp nào hoạt động. Giới hạn bộ nhớ của trình duyệt đã gây ra lỗi phân bổ tensor bên trong. Ngay cả các mô hình "nhỏ" cũng quá lớn để thực hiện bên client. Vấn đề này không thể khắc phục từ phía tôi.
Giai Đoạn 3: Nỗi Kinh Hoàng Tokenization
Đối với các mô hình đã tải thành công mà không bị treo, tôi gặp phải một vấn đề kỳ lạ khác: lỗi tokenizer.
javascript
transformerService.js:132 Thực thi mô hình thất bại: text.split không phải là một hàm
TypeError: text.split không phải là một hàm
Tôi đã kiểm tra ba lần đầu vào:
javascript
async generateText(prompt, options = {}) {
const stringPrompt = String(prompt || '').trim();
const truncatedPrompt = stringPrompt.length > 200
? stringPrompt.substring(0, 200) + "..."
: stringPrompt;
const finalPrompt = String(truncatedPrompt);
const output = await generator(finalPrompt);
}
Console xác nhận đầu vào của tôi luôn là một chuỗi:
javascript
Kiểu chuỗi prompt: string "Xử lý câu: những con cua đáng sợ có thể sáng tạo"
Kiểu prompt cuối cùng: string "Xử lý câu: những con cua đáng sợ có thể sáng tạo"
Tuy nhiên, transformer.js vẫn bị treo. Các lỗi bên trong có nghĩa là thư viện đang truyền các giá trị không phải chuỗi cho tokenizer của nó, bất kể những gì tôi cung cấp.
Ngay cả bài thử nghiệm đơn giản nhất cũng thất bại:
javascript
try {
const testOutput = await generator({
question: "Điều này là gì?",
context: "Đây là một bài kiểm tra."
});
} catch (testError) {
console.error('Thậm chí thử nghiệm tối thiểu cũng thất bại:', testError);
}
Tại thời điểm này, tôi nhận ra rằng thư viện này thực sự bị hỏng cho việc sử dụng client-side.
Giai Đoạn 4: Sự Chuyển Hướng Tuyệt Vọng
Tôi đã cố gắng chuyển sang các mô hình đơn giản hơn như phân loại hoặc nhúng:
javascript
const modelsToTry = [
{ name: 'Sentiment', task: 'sentiment-analysis', model: 'Xenova/distilbert-base-uncased-finetuned-sst-2-english' },
{ name: 'Embeddings', task: 'feature-extraction', model: 'Xenova/all-MiniLM-L6-v2' },
];
Chúng đã tải thành công, điều này đã mang lại cho tôi hy vọng. Có thể tôi có thể xây dựng logic của mình dựa trên phân tích cảm xúc.
Nhưng ngay khi tôi thực hiện chúng, những lỗi bộ nhớ tương tự lại xuất hiện:
javascript
transformerService.js:130 Đã xảy ra lỗi trong quá trình thực thi mô hình: "RangeError: offset nằm ngoài phạm vi"
Ngay cả một phương pháp dự phòng chuyển đổi cảm xúc thành hướng dẫn trị liệu giọng nói cũng không thể thực hiện:
javascript
convertSentimentToSpeech(sentimentOutput, originalPrompt) {
const phrase = this.extractPhrase(originalPrompt);
if (originalPrompt.includes('Xử lý câu')) {
return `Đối với "${phrase}": Thực hành thở chậm, kiểm soát. Chia câu thành các đoạn nhỏ hơn.`;
}
if (originalPrompt.includes('Viết lại để có phát âm mượt mà hơn')) {
const words = phrase.split(' ');
const alternatives = [
`1. ${words.slice(0, Math.ceil(words.length / 2)).join(' ')}`,
`2. ${phrase.toLowerCase()}`,
`3. ${phrase.replace(/\b(and|but)\b/g, ',')}`
];
return alternatives.join('\n');
}
return `Hướng dẫn cho "${phrase}": Sử dụng các chiến lược và kỹ thuật thở.`;
}
Bất kể tôi đã thử gì, các mô hình đều bị treo trước khi trả về bất kỳ đầu ra nào.
Giai Đoạn 5: Điểm Đột Phá
Tại thời điểm này, tôi đã đưa ra quyết định khó khăn: xóa transformer.js hoàn toàn.
Những gì đã từng là hơn 350 dòng mã client-side phức tạp với các phương pháp dự phòng, khởi tạo và xử lý lỗi giờ đây trở thành một cuộc gọi API đơn giản, đáng tin cậy chỉ sử dụng server:
javascript
class TransformerService {
constructor() {}
async rephrase(prompt, language = "en-US") {
const response = await fetch('http://localhost:8000/rephrase', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ prompt, language }),
});
const data = await response.json();
return data.response;
}
async fluencyCheck(inputText, language = "en-US") {
const response = await fetch('http://localhost:8000/fluency-check', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ input: inputText, language }),
});
const data = await response.json();
return data.response;
}
}
Và logic frontend đã đơn giản hóa đáng kể:
javascript
const getSuggestionsForPromptBox = useCallback(async () => {
try {
if (!prompt.trim()) return;
setLoadingPromptBox(true);
const [suggestionsResponse, pacingResponse] = await Promise.all([
transformerService.rephrase(prompt, selectedLanguage),
transformerService.pacingAdvice(prompt, selectedLanguage)
]);
const lines = suggestionsResponse.split('\n')
.filter(line => line.trim())
.map(line => line.trim());
setPromptSuggestions(lines);
setPromptPacingAdvice(pacingResponse);
} catch (error) {
console.error('Lỗi trong getSuggestionsForPromptBox:', error);
setPromptSuggestions([]);
setPromptPacingAdvice('');
} finally {
setLoadingPromptBox(false);
}
}, [prompt, selectedLanguage]);
Không còn các phương pháp dự phòng lai. Không còn sự cố. Không còn lỗi thư viện nội bộ.
Bài Học Rút Ra
- AI trên trình duyệt không sẵn sàng cho sản xuất
- Giới hạn bộ nhớ khiến ngay cả các mô hình nhỏ cũng bị treo
- Lỗi thư viện nội bộ không thể khắc phục từ mã người dùng
- Tính khả dụng của mô hình không đáng tin cậy
- Nhiều mô hình "tương thích với trình duyệt" không tồn tại
- Các liên kết tải xuống thường bị hỏng
- Tài liệu thường gây hiểu lầm
- Việc sửa lỗi gần như không thể
- Lỗi xảy ra sâu trong mã thư viện đã nén
- Các stack trace hiếm khi chỉ ra các sửa lỗi có thể hành động
- AI phía server hoạt động đáng tin cậy
- Hiệu suất cao và phản hồi nhất quán
- Không có giới hạn bộ nhớ trình duyệt
- Dễ dàng bảo trì và sửa lỗi
Giải Pháp Server-Side
Chuyển sang triển khai AI chỉ sử dụng server đã làm mọi thứ trở nên đơn giản và đáng tin cậy. Sử dụng FastAPI và một thư viện AI Python:
python
from fastapi import FastAPI, Request
import ollama
app = FastAPI()
@app.post("/rephrase")
async def rephrase_input(request: Request):
data = await request.json()
prompt = data.get("prompt")
system_prompt = (
f"Viết lại câu này để dễ phát âm hơn: \"{prompt}\""
)
response = ollama.generate(model="llama3", prompt=system_prompt)
return {"response": response["response"]}
Mười phút để triển khai. Hoạt động hoàn hảo mọi lúc.
Kết Luận
Sau nhiều tuần vật lộn với AI client-side:
- Các mô hình tạo văn bản: Thất bại hoàn toàn
- Các mô hình phân loại: Tải nhưng bị treo trong quá trình thực thi
- AI phía server: Hoạt động đáng tin cậy
Đôi khi quyết định kỹ thuật tốt nhất là biết khi nào nên từ bỏ công nghệ. Trong dự án của tôi, việc từ bỏ AI client-side và chuyển sang chỉ sử dụng server đã mang lại:
- Đơn giản
- Đáng tin cậy
- Dễ bảo trì
- Phản hồi AI nhất quán, chất lượng cao
Giấc mơ về AI hoàn toàn riêng tư, client-side vẫn chỉ là một giấc mơ. Nhưng AI thực tiễn mà hoạt động mọi lúc có thể đạt được khi chuyển sang server.