<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:media="http://search.yahoo.com/mrss/">
  <channel>
    <title>AI · Nhân Tài Đức</title>
    <link>https://jared.lynskey.co.nz/vi/tags/ai/</link>
    <description>Những câu chuyện ngắn về kỹ thuật phần mềm và lãnh đạo đội ngũ kỹ sư — performance review, tuyển dụng và mở rộng team, CI/CD, giám sát DevOps và AI agent. Viết từ Seoul.</description>
    <generator>Hugo</generator>
    <language>vi</language>
    <copyright>© 2026 Nhân Tài Đức</copyright>
    <lastBuildDate>Fri, 30 Jan 2026 00:00:00 +0000</lastBuildDate>
    <atom:link href="https://jared.lynskey.co.nz/vi/tags/ai/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>AI chạy ngay trên điện thoại trong React Native: Qwen 1.7B với llama.cpp</title>
      <link>https://jared.lynskey.co.nz/vi/posts/2026/2026-01-30-on-device-ai/</link>
      <guid isPermaLink="true">https://jared.lynskey.co.nz/vi/posts/2026/2026-01-30-on-device-ai/</guid>
      <pubDate>Fri, 30 Jan 2026 00:00:00 +0000</pubDate>
      <dc:creator>Jared Lynskey</dc:creator>
      <category>React Native</category>
      <category>AI</category>
      <category>LLM</category>
      <category>llama.cpp</category>
      <category>Qwen</category>
      <category>On-Device AI</category>
      <category>Privacy</category>
      <category>Mobile Development</category>
      <description>Cách tôi cho một trợ lý AI dùng Qwen 1.7B chạy hoàn toàn trên điện thoại, ngay trong ứng dụng báo giá rèm cửa của mình, bằng llama.rn và một giao thức gọi công cụ tự viết.</description>
      <content:encoded><![CDATA[<p>Trợ lý AI trong ứng dụng Curtain Estimator của tôi chạy hoàn toàn trên điện thoại. Không có dữ liệu nào rời khỏi máy. Người dùng chỉ cần gõ tiếng Anh thông thường là có thể tạo công việc (job), tìm khách hàng và quản lý dự án, và mọi thứ vẫn chạy ngon lành kể cả khi bật chế độ máy bay.</p>
<p>Trong bài này, tôi sẽ kể lại cách mình làm ra nó với <strong>llama.rn</strong> (thư viện kết nối React Native với llama.cpp) và <strong>Qwen 1.7B</strong>, một mô hình nhỏ nhưng hóa ra làm được nhiều hơn tôi tưởng.</p>
<blockquote><p><strong>📝 Cập nhật:</strong> Bản hiện tại đã chuyển sang <a href="https://huggingface.co/unsloth/Qwen3.5-2B-GGUF"  target="_blank" rel="noreferrer">Qwen3.5-2B-GGUF</a> thay cho Qwen3-1.7B ban đầu. Tôi cũng bỏ luôn mẹo chèn <code>/no_think</code> vào tin nhắn, giờ thì tắt chế độ suy nghĩ (thinking mode) đúng cách qua tham số <code>chat_template_kwargs</code>:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">chat_response</span> <span class="o">=</span> <span class="n">client</span><span class="o">.</span><span class="n">chat</span><span class="o">.</span><span class="n">completions</span><span class="o">.</span><span class="n">create</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">model</span><span class="o">=</span><span class="s2">&#34;Qwen/Qwen3.5-27B&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">messages</span><span class="o">=</span><span class="n">messages</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">max_tokens</span><span class="o">=</span><span class="mi">32768</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">temperature</span><span class="o">=</span><span class="mf">0.7</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">top_p</span><span class="o">=</span><span class="mf">0.8</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">presence_penalty</span><span class="o">=</span><span class="mf">1.5</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">extra_body</span><span class="o">=</span><span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;top_k&#34;</span><span class="p">:</span> <span class="mi">20</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;chat_template_kwargs&#34;</span><span class="p">:</span> <span class="p">{</span><span class="s2">&#34;enable_thinking&#34;</span><span class="p">:</span> <span class="kc">False</span><span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span></span></span></code></pre></div></div>
<p>Gọn gàng hơn nhiều so với kiểu lén nhét cờ điều khiển vào prompt.</p>
</blockquote>
<h2 class="relative group">Vì sao tôi không muốn dùng AI trên đám mây cho việc này
    <div id="vì-sao-tôi-không-muốn-dùng-ai-trên-đám-mây-cho-việc-này" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#v%c3%ac-sao-t%c3%b4i-kh%c3%b4ng-mu%e1%bb%91n-d%c3%b9ng-ai-tr%c3%aan-%c4%91%c3%a1m-m%c3%a2y-cho-vi%e1%bb%87c-n%c3%a0y" aria-label="Cọc dấu">#</a>
    </span>
    
</h2>
<p>Cách làm tính năng AI quen thuộc trong ứng dụng di động là: người dùng gõ tin nhắn, ứng dụng gửi nó sang OpenAI, Anthropic hoặc Google, nhận câu trả lời về, và hóa đơn lại nhích lên một chút.</p>
<p>Nhưng với một ứng dụng mà thợ lắp rèm dùng để điều hành việc làm ăn của chính họ, cách làm đó có vấn đề. Tên, địa chỉ, số điện thoại của khách hàng bị gửi cho bên thứ ba. Chi tiết dự án, giá cả và ghi chú thì nằm trên máy chủ của người khác. Rồi còn những câu hỏi về GDPR và nơi lưu trữ dữ liệu mà thật lòng tôi không muốn mất thời gian vào. Chưa kể hóa đơn API cứ tăng dần theo số người dùng.</p>
<p>Cách còn lại là chạy mô hình ngay trên điện thoại của người dùng, và tôi đã chọn cách đó.</p>

<h2 class="relative group">Vì sao đến giờ chuyện này mới khả thi
    <div id="vì-sao-đến-giờ-chuyện-này-mới-khả-thi" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#v%c3%ac-sao-%c4%91%e1%ba%bfn-gi%e1%bb%9d-chuy%e1%bb%87n-n%c3%a0y-m%e1%bb%9bi-kh%e1%ba%a3-thi" aria-label="Cọc dấu">#</a>
    </span>
    
</h2>
<p>Nếu là một năm trước thì tôi đã chẳng buồn thử. Nhưng có ba thứ thay đổi gần như cùng lúc. Thứ nhất, mô hình lượng tử hóa (quantized) đã đủ nhỏ: Qwen 1.7B ở mức Q4_K_M chỉ nặng 1.1 GB, nhẹ hơn phần lớn các game, tải về một lần là nằm luôn trong bộ lưu trữ của ứng dụng. Thứ hai, GPU trên điện thoại đã đủ nhanh: llama.cpp dùng Metal trên iOS và Vulkan trên Android, và trên iPhone 14 Pro tôi đo được khoảng 15 token mỗi giây, thừa sức để truyền dần câu trả lời (streaming) theo thời gian thực. Thứ ba, mô hình nhỏ giờ đã thực sự dùng được: Qwen 1.7B làm theo chỉ dẫn có cấu trúc, phân tích được JSON và suy luận qua nhiều bước. Logic nghiệp vụ cần đúng những khả năng đó. Có ai bắt nó làm thơ đâu.</p>

<h2 class="relative group">Chọn mô hình
    <div id="chọn-mô-hình" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#ch%e1%bb%8dn-m%c3%b4-h%c3%acnh" aria-label="Cọc dấu">#</a>
    </span>
    
</h2>
<p>Tôi đã thử bốn mô hình nhỏ trước khi chốt Qwen3-1.7B.</p>
<p><strong>TinyLlama 1.1B</strong> (637 MB) chạy nhanh nhất, nhưng cứ bịa ra ID khách hàng và bỏ sót các trường bắt buộc, nên đầu ra có cấu trúc thì không tin được. <strong>Phi-3-mini</strong> (1.8 GB) suy luận tốt nhưng nói nhiều không chịu nổi; hỏi một câu đơn giản, nó trả về cả bài văn 200 chữ trong khi tôi chỉ cần 20. <strong>Gemma-2B</strong> (1.2 GB) nhanh, phân loại chính xác, nhưng gọi hàm lại yếu: nó không sinh ra đều đặn được các thẻ <code>&lt;action&gt;</code> mà hệ thống công cụ của tôi cần. <strong>Qwen3-1.7B</strong> (1.1 GB) thì vừa khéo: đầu ra có cấu trúc ổn định, làm theo chỉ dẫn chính xác, lại hỗ trợ chuỗi suy luận (chain-of-thought) qua thẻ <code>&lt;think&gt;</code>.</p>
<p>Bản lượng tử hóa Q4_K_M dùng trọng số 4-bit kết hợp phân cụm k-means, nhỏ hơn khoảng 75% so với bản độ chính xác đầy đủ (full precision) mà chất lượng chỉ giảm chừng 5%.</p>

<h2 class="relative group">Cài đặt llama.rn
    <div id="cài-đặt-llamarn" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#c%c3%a0i-%c4%91%e1%ba%b7t-llamarn" aria-label="Cọc dấu">#</a>
    </span>
    
</h2>
<p>llama.rn bọc llama.cpp lại để dùng được trong React Native, và cài đặt là phần dễ nhất:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">npm install llama.rn
</span></span><span class="line"><span class="cl"><span class="nb">cd</span> ios <span class="o">&amp;&amp;</span> pod install</span></span></code></pre></div></div>
<p>Còn bản thân mô hình thì được tải xuống ở lần dùng đầu tiên:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-typescript" data-lang="typescript"><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">MODEL_URL</span> <span class="o">=</span> <span class="s2">&#34;https://huggingface.co/unsloth/Qwen3-1.7B-GGUF/resolve/main/Qwen3-1.7B-Q4_K_M.gguf&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">MODEL_PATH</span> <span class="o">=</span> <span class="nx">FileSystem</span><span class="p">.</span><span class="nx">documentDirectory</span> <span class="o">+</span> <span class="s2">&#34;llama-models/Qwen3-1.7B-Q4_K_M.gguf&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">downloadModel</span> <span class="o">=</span> <span class="kr">async</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">downloadResumable</span> <span class="o">=</span> <span class="nx">FileSystem</span><span class="p">.</span><span class="nx">createDownloadResumable</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="nx">MODEL_URL</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nx">MODEL_PATH</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">{},</span>
</span></span><span class="line"><span class="cl">    <span class="p">(</span><span class="nx">progress</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="kr">const</span> <span class="nx">pct</span> <span class="o">=</span> <span class="nx">progress</span><span class="p">.</span><span class="nx">totalBytesWritten</span> <span class="o">/</span> <span class="nx">progress</span><span class="p">.</span><span class="nx">totalBytesExpectedToWrite</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="nx">setDownloadProgress</span><span class="p">(</span><span class="nx">pct</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">await</span> <span class="nx">downloadResumable</span><span class="p">.</span><span class="nx">downloadAsync</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span></span></span></code></pre></div></div>
<p>Qua WiFi thì mất 2-3 phút, qua LTE thì khoảng 5-8 phút. Khi tệp đã nằm trên máy, bạn nạp nó lên và bật tăng tốc bằng GPU:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-typescript" data-lang="typescript"><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">ctx</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">initLlama</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">  <span class="nx">model</span>: <span class="kt">MODEL_PATH</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nx">n_ctx</span>: <span class="kt">8192</span><span class="p">,</span>      <span class="c1">// 8K context window
</span></span></span><span class="line"><span class="cl">  <span class="nx">n_gpu_layers</span>: <span class="kt">99</span><span class="p">,</span> <span class="c1">// Use GPU for all layers
</span></span></span><span class="line"><span class="cl"><span class="p">});</span></span></span></code></pre></div></div>
<p>Đừng bỏ qua <code>n_gpu_layers: 99</code>. Đẩy toàn bộ việc tính toán sang Metal/Vulkan thay vì để CPU gánh sẽ giúp tốc độ tăng khoảng 5 lần.</p>

<h2 class="relative group">Suy luận và truyền dần kết quả
    <div id="suy-luận-và-truyền-dần-kết-quả" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#suy-lu%e1%ba%adn-v%c3%a0-truy%e1%bb%81n-d%e1%ba%a7n-k%e1%ba%bft-qu%e1%ba%a3" aria-label="Cọc dấu">#</a>
    </span>
    
</h2>
<p>Chẳng ai muốn ngồi nhìn biểu tượng đang tải xoay vòng suốt mười giây, nên quá trình suy luận (inference) trả câu trả lời ra dần từng token một:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-typescript" data-lang="typescript"><span class="line"><span class="cl"><span class="kd">let</span> <span class="nx">fullResponse</span> <span class="o">=</span> <span class="s2">&#34;&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">await</span> <span class="nx">llamaContext</span><span class="p">.</span><span class="nx">completion</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">messages</span><span class="o">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">      <span class="p">{</span> <span class="nx">role</span><span class="o">:</span> <span class="s2">&#34;system&#34;</span><span class="p">,</span> <span class="nx">content</span>: <span class="kt">systemPrompt</span> <span class="p">},</span>
</span></span><span class="line"><span class="cl">      <span class="p">{</span> <span class="nx">role</span><span class="o">:</span> <span class="s2">&#34;user&#34;</span><span class="p">,</span> <span class="nx">content</span><span class="o">:</span> <span class="s2">&#34;Create a job for John Smith&#34;</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">],</span>
</span></span><span class="line"><span class="cl">    <span class="nx">n_predict</span>: <span class="kt">512</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nx">temperature</span>: <span class="kt">0.7</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nx">top_p</span>: <span class="kt">0.8</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="p">(</span><span class="nx">data</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// Called for each token
</span></span></span><span class="line"><span class="cl">    <span class="nx">fullResponse</span> <span class="o">+=</span> <span class="nx">data</span><span class="p">.</span><span class="nx">token</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="nx">setStreamingText</span><span class="p">(</span><span class="nx">fullResponse</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">);</span></span></span></code></pre></div></div>
<p>Trên điện thoại đời mới, token đầu tiên xuất hiện sau khoảng 200ms (đó là thời gian xử lý prompt), sau đó mỗi token mất 50-80ms. So với bất cứ thứ gì phải chờ mạng, cảm giác gần như tức thì.</p>

<h2 class="relative group">Gọi hàm theo cách mô hình nhỏ làm được thật
    <div id="gọi-hàm-theo-cách-mô-hình-nhỏ-làm-được-thật" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#g%e1%bb%8di-h%c3%a0m-theo-c%c3%a1ch-m%c3%b4-h%c3%acnh-nh%e1%bb%8f-l%c3%a0m-%c4%91%c6%b0%e1%bb%a3c-th%e1%ba%adt" aria-label="Cọc dấu">#</a>
    </span>
    
</h2>
<p>Cơ chế gọi hàm (function calling) kiểu GPT-4 dựa vào JSON schema, và mô hình nhỏ gặp nó là &ldquo;ngã&rdquo;: JSON sai định dạng, thiếu trường, đủ cả. Vì vậy tôi dùng một giao thức đơn giản hơn, dựa trên XML:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-typescript" data-lang="typescript"><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">systemPrompt</span> <span class="o">=</span> <span class="sb">`You are an AI assistant. When you need to take an action, output:
</span></span></span><span class="line"><span class="cl"><span class="sb">&lt;action&gt;{&#34;type&#34;:&#34;search_customers&#34;,&#34;query&#34;:&#34;Smith&#34;}&lt;/action&gt;
</span></span></span><span class="line"><span class="cl"><span class="sb">
</span></span></span><span class="line"><span class="cl"><span class="sb">Available actions:
</span></span></span><span class="line"><span class="cl"><span class="sb">- search_customers: {&#34;type&#34;:&#34;search_customers&#34;,&#34;query&#34;:&#34;John&#34;}
</span></span></span><span class="line"><span class="cl"><span class="sb">- create_job: {&#34;type&#34;:&#34;create_job&#34;,&#34;customer_id&#34;:5,&#34;alias&#34;:&#34;Living Room&#34;}
</span></span></span><span class="line"><span class="cl"><span class="sb">- update_job: {&#34;type&#34;:&#34;update_job&#34;,&#34;job_id&#34;:&#34;J-0042&#34;,&#34;status&#34;:&#34;quoting&#34;}
</span></span></span><span class="line"><span class="cl"><span class="sb">
</span></span></span><span class="line"><span class="cl"><span class="sb">Rules:
</span></span></span><span class="line"><span class="cl"><span class="sb">1. ONE action tag per response, at the very end
</span></span></span><span class="line"><span class="cl"><span class="sb">2. When searching/creating → end with &lt;action&gt;
</span></span></span><span class="line"><span class="cl"><span class="sb">3. When just chatting → no action tag
</span></span></span><span class="line"><span class="cl"><span class="sb">`</span><span class="p">;</span></span></span></code></pre></div></div>
<p>Thẻ XML thắng vì mấy lý do rất bình thường: <code>&lt;action&gt;</code> và <code>&lt;/action&gt;</code> là dấu phân cách rõ ràng, phân tích chỉ cần một biểu thức chính quy (regex), các ví dụ trong prompt kiêm luôn vai trò tài liệu, và cách này vẫn chạy được khi mô hình nói thêm vài câu lan man quanh thẻ.</p>
<p>Phần phân tích thì đơn giản đúng như vẻ ngoài của nó:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-typescript" data-lang="typescript"><span class="line"><span class="cl"><span class="kd">function</span> <span class="nx">parseAction</span><span class="p">(</span><span class="nx">text</span>: <span class="kt">string</span><span class="p">)</span><span class="o">:</span> <span class="nx">Action</span> <span class="o">|</span> <span class="kc">null</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">match</span> <span class="o">=</span> <span class="nx">text</span><span class="p">.</span><span class="nx">match</span><span class="p">(</span><span class="sr">/&lt;action&gt;([\s\S]*?)&lt;\/action&gt;/</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">match</span><span class="p">)</span> <span class="k">return</span> <span class="kc">null</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">try</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="nx">JSON</span><span class="p">.</span><span class="nx">parse</span><span class="p">(</span><span class="nx">match</span><span class="p">[</span><span class="mi">1</span><span class="p">].</span><span class="nx">trim</span><span class="p">());</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span> <span class="k">catch</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="kc">null</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
<p>Khi mô hình trả về một hành động (action), tôi thực thi nó rồi đưa kết quả ngược lại vào cuộc hội thoại:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-typescript" data-lang="typescript"><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">action</span> <span class="o">=</span> <span class="nx">parseAction</span><span class="p">(</span><span class="nx">modelResponse</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="p">(</span><span class="nx">action</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">result</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">executeAction</span><span class="p">(</span><span class="nx">action</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1">// Inject result as a system message
</span></span></span><span class="line"><span class="cl">  <span class="nx">conversationHistory</span><span class="p">.</span><span class="nx">push</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">    <span class="nx">role</span><span class="o">:</span> <span class="s2">&#34;user&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nx">content</span><span class="o">:</span> <span class="sb">`[TOOL_RESULT] </span><span class="si">${</span><span class="nx">result</span><span class="p">.</span><span class="nx">message</span><span class="si">}</span><span class="err">\</span><span class="sb">n</span><span class="err">\</span><span class="sb">n→ NEXT: Tell the user what happened.`</span>
</span></span><span class="line"><span class="cl">  <span class="p">});</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1">// Continue generation
</span></span></span><span class="line"><span class="cl">  <span class="k">await</span> <span class="nx">generateNextResponse</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
<p>Chừng đó đã đủ để chạy các luồng xử lý nhiều bước đúng nghĩa:</p>
<p><strong>Người dùng</strong>: &ldquo;Create a job for Smith&rdquo;</p>
<ol>
<li><strong>Mô hình</strong>: <code>&lt;action&gt;{&quot;type&quot;:&quot;search_customers&quot;,&quot;query&quot;:&quot;Smith&quot;}&lt;/action&gt;</code></li>
<li><strong>Hệ thống</strong>: <code>[TOOL_RESULT] Found: Jane Smith (id:42), Bob Smith (id:89)</code></li>
<li><strong>Mô hình</strong>: &ldquo;I found two Smiths—Jane and Bob. Which one?&rdquo;</li>
<li><strong>Người dùng</strong>: &ldquo;Jane&rdquo;</li>
<li><strong>Mô hình</strong>: <code>&lt;action&gt;{&quot;type&quot;:&quot;create_job&quot;,&quot;customer_id&quot;:42}&lt;/action&gt;</code></li>
<li><strong>Hệ thống</strong>: <code>[TOOL_RESULT] Job J-0073 created</code></li>
<li><strong>Mô hình</strong>: &ldquo;Done! Created job J-0073 for Jane Smith.&rdquo;</li>
</ol>

<h2 class="relative group">Chuỗi suy luận với thẻ &lt;think&gt;
    <div id="chuỗi-suy-luận-với-thẻ-think" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#chu%e1%bb%97i-suy-lu%e1%ba%adn-v%e1%bb%9bi-th%e1%ba%bb-think" aria-label="Cọc dấu">#</a>
    </span>
    
</h2>
<p>Mô hình nhỏ làm tốt hơn thấy rõ khi bạn bắt nó suy nghĩ thành lời. Qwen có sẵn chế độ suy nghĩ:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-typescript" data-lang="typescript"><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">systemPrompt</span> <span class="o">=</span> <span class="sb">`Before each response, wrap your reasoning in &lt;think&gt; tags:
</span></span></span><span class="line"><span class="cl"><span class="sb">
</span></span></span><span class="line"><span class="cl"><span class="sb">&lt;think&gt;
</span></span></span><span class="line"><span class="cl"><span class="sb">INTENT: what does the user want?
</span></span></span><span class="line"><span class="cl"><span class="sb">HAVE: what data do I already have?
</span></span></span><span class="line"><span class="cl"><span class="sb">NEED: what&#39;s still missing?
</span></span></span><span class="line"><span class="cl"><span class="sb">DECISION: call tool | ask user | just respond
</span></span></span><span class="line"><span class="cl"><span class="sb">&lt;/think&gt;
</span></span></span><span class="line"><span class="cl"><span class="sb">
</span></span></span><span class="line"><span class="cl"><span class="sb">Then output your actual response.`</span><span class="p">;</span></span></span></code></pre></div></div>
<p>Bên trong, mô hình sẽ sinh ra nội dung trông như thế này:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">&lt;think&gt;
</span></span><span class="line"><span class="cl">INTENT: create a job
</span></span><span class="line"><span class="cl">HAVE: customer query &#34;Smith&#34;
</span></span><span class="line"><span class="cl">NEED: exact customer_id
</span></span><span class="line"><span class="cl">DECISION: search first
</span></span><span class="line"><span class="cl">&lt;/think&gt;
</span></span><span class="line"><span class="cl">&lt;action&gt;{&#34;type&#34;:&#34;search_customers&#34;,&#34;query&#34;:&#34;Smith&#34;}&lt;/action&gt;</span></span></code></pre></div></div>
<p>Tôi lọc bỏ các thẻ <code>&lt;think&gt;</code> trước khi hiển thị bất cứ thứ gì lên giao diện, nhưng trong lúc phát triển thì vẫn để chúng hiện ra. Độ ổn định khác nhau một trời một vực: mô hình tự lý luận qua từng bước rồi mới chốt một hành động.</p>

<h2 class="relative group">Giữ các cuộc hội thoại dài nằm gọn trong 8K
    <div id="giữ-các-cuộc-hội-thoại-dài-nằm-gọn-trong-8k" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#gi%e1%bb%af-c%c3%a1c-cu%e1%bb%99c-h%e1%bb%99i-tho%e1%ba%a1i-d%c3%a0i-n%e1%ba%b1m-g%e1%bb%8dn-trong-8k" aria-label="Cọc dấu">#</a>
    </span>
    
</h2>
<p>Một khi kết quả từ công cụ bắt đầu dồn vào, cửa sổ ngữ cảnh (context window) 8K đầy nhanh hơn bạn tưởng. Tầm lượt thứ 30 là tôi chạm trần.</p>
<p>Cách tôi xử lý là nén cuộc hội thoại lại, bằng cách nhờ chính mô hình tự tóm tắt:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-typescript" data-lang="typescript"><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">compactPrompt</span> <span class="o">=</span> <span class="sb">`Summarize this conversation in under 200 words.
</span></span></span><span class="line"><span class="cl"><span class="sb">Include: user&#39;s goal, customers/jobs created (with IDs), and any unfinished tasks.
</span></span></span><span class="line"><span class="cl"><span class="sb">
</span></span></span><span class="line"><span class="cl"><span class="si">${</span><span class="nx">conversationHistory</span><span class="p">.</span><span class="nx">map</span><span class="p">(</span><span class="nx">m</span> <span class="o">=&gt;</span> <span class="sb">`</span><span class="si">${</span><span class="nx">m</span><span class="p">.</span><span class="nx">role</span><span class="si">}</span><span class="sb">: </span><span class="si">${</span><span class="nx">m</span><span class="p">.</span><span class="nx">content</span><span class="si">}</span><span class="sb">`</span><span class="p">).</span><span class="nx">join</span><span class="p">(</span><span class="s1">&#39;\n\n&#39;</span><span class="p">)</span><span class="si">}</span><span class="sb">`</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">summary</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">chat</span><span class="p">([{</span> <span class="nx">role</span><span class="o">:</span> <span class="s2">&#34;user&#34;</span><span class="p">,</span> <span class="nx">content</span>: <span class="kt">compactPrompt</span> <span class="p">}]);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// Replace history with summary
</span></span></span><span class="line"><span class="cl"><span class="nx">conversationHistory</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">  <span class="p">{</span> <span class="nx">role</span><span class="o">:</span> <span class="s2">&#34;user&#34;</span><span class="p">,</span> <span class="nx">content</span><span class="o">:</span> <span class="sb">`[CONTEXT SUMMARY]</span><span class="err">\</span><span class="sb">n</span><span class="si">${</span><span class="nx">summary</span><span class="si">}</span><span class="err">\</span><span class="sb">n[END SUMMARY]`</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">];</span></span></span></code></pre></div></div>
<p>Một cuộc hội thoại 50 tin nhắn được ép xuống còn khoảng 150 token, mà mô hình vẫn tiếp tục bình thường, không quên mình đã tạo những công việc nào, với ID khách hàng nào.</p>

<h2 class="relative group">Lưu phiên trò chuyện
    <div id="lưu-phiên-trò-chuyện" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#l%c6%b0u-phi%c3%aan-tr%c3%b2-chuy%e1%bb%87n" aria-label="Cọc dấu">#</a>
    </span>
    
</h2>
<p>Các phiên trò chuyện được lưu xuống máy chủ Django của tôi:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-typescript" data-lang="typescript"><span class="line"><span class="cl"><span class="kr">interface</span> <span class="nx">SessionMessage</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nx">role</span><span class="o">:</span> <span class="s2">&#34;user&#34;</span> <span class="o">|</span> <span class="s2">&#34;assistant&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="nx">content</span>: <span class="kt">string</span><span class="p">;</span>  <span class="c1">// Stripped for display
</span></span></span><span class="line"><span class="cl">  <span class="nx">raw</span>: <span class="kt">string</span><span class="p">;</span>      <span class="c1">// Full with &lt;think&gt; and &lt;action&gt; tags
</span></span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">await</span> <span class="nx">api</span><span class="p">.</span><span class="nx">createAIChatSession</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">  <span class="nx">organization_id</span>: <span class="kt">orgId</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nx">title</span>: <span class="kt">firstUserMessage.slice</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">60</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="nx">messages</span><span class="o">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span> <span class="nx">role</span><span class="o">:</span> <span class="s2">&#34;user&#34;</span><span class="p">,</span> <span class="nx">content</span>: <span class="kt">userText</span><span class="p">,</span> <span class="nx">raw</span>: <span class="kt">userText</span> <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span> <span class="nx">role</span><span class="o">:</span> <span class="s2">&#34;assistant&#34;</span><span class="p">,</span> <span class="nx">content</span>: <span class="kt">cleanedResponse</span><span class="p">,</span> <span class="nx">raw</span>: <span class="kt">fullModelOutput</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="p">});</span></span></span></code></pre></div></div>
<p>Nhờ lưu cả <code>content</code> lẫn <code>raw</code>, lịch sử trò chuyện hiển thị lại gọn gàng trên giao diện, còn khi mở lại một phiên cũ thì mô hình được nạp lại đầy đủ ngữ cảnh, kể cả các hành động. Người dùng chuyển qua lại giữa các cuộc hội thoại bằng một danh sách thả xuống chứa lịch sử, na ná như ChatGPT.</p>

<h2 class="relative group">Số đo hiệu năng trên môi trường production
    <div id="số-đo-hiệu-năng-trên-môi-trường-production" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#s%e1%bb%91-%c4%91o-hi%e1%bb%87u-n%c4%83ng-tr%c3%aan-m%c3%b4i-tr%c6%b0%e1%bb%9dng-production" aria-label="Cọc dấu">#</a>
    </span>
    
</h2>
<p><strong>iPhone 14 Pro</strong> (A16 Bionic, 6GB RAM):</p>
<ul>
<li>Nạp mô hình: ~3 giây</li>
<li>Token đầu tiên: 180-220ms</li>
<li>Tốc độ sinh: 14-16 token/giây</li>
<li>Bộ nhớ: ~1.8 GB</li>
</ul>
<p><strong>Samsung Galaxy S23</strong> (Snapdragon 8 Gen 2, 8GB RAM):</p>
<ul>
<li>Nạp mô hình: ~4 giây</li>
<li>Token đầu tiên: 250-300ms</li>
<li>Tốc độ sinh: 10-12 token/giây</li>
<li>Bộ nhớ: ~2.1 GB</li>
</ul>
<p><strong>iPhone 11</strong> (A13, 4GB RAM):</p>
<ul>
<li>Nạp mô hình: ~6 giây</li>
<li>Token đầu tiên: 400-500ms</li>
<li>Tốc độ sinh: 6-8 token/giây</li>
<li>Bộ nhớ: ~2.2 GB (thỉnh thoảng bị sập vì thiếu bộ nhớ)</li>
</ul>
<p>Kinh nghiệm của tôi là cần từ 6GB RAM trở lên thì mới chạy mượt. Máy 4GB vẫn chạy được, nhưng nhiều khả năng bạn sẽ phải giải phóng mô hình mỗi khi ứng dụng chuyển xuống chạy nền.</p>

<h2 class="relative group">Quản lý bộ nhớ
    <div id="quản-lý-bộ-nhớ" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#qu%e1%ba%a3n-l%c3%bd-b%e1%bb%99-nh%e1%bb%9b" aria-label="Cọc dấu">#</a>
    </span>
    
</h2>
<p>iOS sẵn sàng buộc đóng ứng dụng của bạn nếu nó ôm quá nhiều bộ nhớ, nên cứ mỗi lần ứng dụng rời khỏi màn hình chính (không còn chạy ở tiền cảnh) là tôi giải phóng mô hình:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-typescript" data-lang="typescript"><span class="line"><span class="cl"><span class="nx">useEffect</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">subscription</span> <span class="o">=</span> <span class="nx">AppState</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="s2">&#34;change&#34;</span><span class="p">,</span> <span class="p">(</span><span class="nx">nextAppState</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="nx">nextAppState</span> <span class="o">===</span> <span class="s2">&#34;background&#34;</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nx">llamaContext</span><span class="o">?</span><span class="p">.</span><span class="nx">release</span><span class="p">();</span> <span class="c1">// Free ~2 GB
</span></span></span><span class="line"><span class="cl">    <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="nx">nextAppState</span> <span class="o">===</span> <span class="s2">&#34;active&#34;</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nx">loadModel</span><span class="p">();</span> <span class="c1">// Reload when foregrounded
</span></span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">});</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">return</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="nx">subscription</span><span class="p">.</span><span class="nx">remove</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"><span class="p">},</span> <span class="p">[]);</span></span></span></code></pre></div></div>

<h2 class="relative group">Ảnh hưởng tới pin
    <div id="ảnh-hưởng-tới-pin" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#%e1%ba%a3nh-h%c6%b0%e1%bb%9fng-t%e1%bb%9bi-pin" aria-label="Cọc dấu">#</a>
    </span>
    
</h2>
<p>Chạy suy luận ngay trên máy thì cũng tốn pin chứ không phải miễn phí. Đây là số liệu tôi đo được khi dùng thực tế:</p>
<table>
	<thead>
			<tr>
					<th>Mức độ sử dụng</th>
					<th>Hao pin thêm</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>Nhẹ (5-10 truy vấn/ngày)</td>
					<td>&lt;1% mỗi ngày</td>
			</tr>
			<tr>
					<td>Vừa (20-30 truy vấn/ngày)</td>
					<td>~3-4% mỗi ngày</td>
			</tr>
			<tr>
					<td>Nặng (50+ truy vấn/ngày)</td>
					<td>~6-8% mỗi ngày</td>
			</tr>
	</tbody>
</table>
<p>Suy luận bằng GPU tốn điện hơn CPU nhưng xong nhanh hơn nhiều, mà người dùng thì rõ ràng thích được trả lời tức thì, nên tôi vẫn giữ GPU.</p>

<h2 class="relative group">Sau hai tháng, với khoảng 200 người dùng bản beta
    <div id="sau-hai-tháng-với-khoảng-200-người-dùng-bản-beta" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#sau-hai-th%c3%a1ng-v%e1%bb%9bi-kho%e1%ba%a3ng-200-ng%c6%b0%e1%bb%9di-d%c3%b9ng-b%e1%ba%a3n-beta" aria-label="Cọc dấu">#</a>
    </span>
    
</h2>
<p>Cơ cấu các truy vấn làm tôi hơi bất ngờ:</p>
<ol>
<li>&ldquo;Create a job for [customer name]&rdquo; - 68% số truy vấn</li>
<li>&ldquo;Find jobs for [customer]&rdquo; - 15%</li>
<li>&ldquo;Update job [ID] to [status]&rdquo; - 9%</li>
<li>Câu hỏi chung chung về ứng dụng - 8%</li>
</ol>
<p>91% truy vấn thành công ngay lần đầu. Số còn lại thất bại vì ba lý do: không tìm thấy khách hàng do người dùng gõ sai tên (4%), tràn ngữ cảnh trong những cuộc hội thoại rất dài (3%), và mô hình tự bịa ra ID khách hàng (2%).</p>
<p>Lỗi cuối cùng này trước đây còn tệ hơn nhiều. Chuyện bịa ID gần như biến mất sau khi tôi trả kết quả của công cụ về ở dạng tường minh, kiểu <code>customer_id=42</code>, thay vì một câu văn. Với mô hình nhỏ, bạn phải dựng sẵn &ldquo;giàn giáo&rdquo; đó ra thật rõ ràng.</p>

<h2 class="relative group">Chi phí bao nhiêu
    <div id="chi-phí-bao-nhiêu" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#chi-ph%c3%ad-bao-nhi%c3%aau" aria-label="Cọc dấu">#</a>
    </span>
    
</h2>
<p>Chạy trên máy: $0/tháng tiền hạ tầng, $0 cho mỗi người dùng ngoài lần tải 1.1 GB duy nhất, và có bao nhiêu người muốn cài ứng dụng thì nó cũng mở rộng được bấy nhiêu.</p>
<p>Còn nếu làm bản chạy trên đám mây, ước tính sơ sơ thế này: một cuộc hội thoại trung bình khoảng 2,000 token, 200 người dùng mỗi người 30 cuộc hội thoại một tháng là 6,000 cuộc hội thoại, tức 12M token/tháng. Tính ra khoảng $18/tháng với GPT-3.5, $360/tháng với GPT-4, hoặc $180/tháng với Claude. Ở quy mô này thì chưa đến mức phá sản, nhưng chạy trên máy thì hoàn vốn ngay từ ngày đầu, và kể cả khi lên tới 10,000 người dùng cũng vẫn không tốn đồng nào.</p>

<h2 class="relative group">Những giới hạn nên biết
    <div id="những-giới-hạn-nên-biết" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#nh%e1%bb%afng-gi%e1%bb%9bi-h%e1%ba%a1n-n%c3%aan-bi%e1%ba%bft" aria-label="Cọc dấu">#</a>
    </span>
    
</h2>
<p><strong>Năng lực của mô hình.</strong> Qwen 1.7B làm các tác vụ có cấu trúc rất tốt, nhưng lại rất dở ở suy luận phức tạp qua nhiều bước, nhớ kiến thức (nó không phải công cụ tìm kiếm), viết sáng tạo và những việc cần sự tinh tế. Cứ thiết kế tính năng xoay quanh những gì mô hình nhỏ làm giỏi là ổn.</p>
<p><strong>Yêu cầu thiết bị.</strong> Tối thiểu là 3 GB RAM và 2 GB bộ nhớ trống; thực tế thì bạn nên có 6 GB RAM, chip 64-bit và hỗ trợ GPU. Máy đời cũ như iPhone 8 hay Galaxy S9 sẽ chạy rất chật vật, nên hãy kiểm tra khả năng của thiết bị trước và có phương án dự phòng cho êm.</p>
<p><strong>Cập nhật mô hình.</strong> Mỗi lần phát hành mô hình mới là người dùng lại phải tải lại 1.1 GB. Tôi đánh số phiên bản cho mô hình ngay trong đường dẫn lưu trữ:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-typescript" data-lang="typescript"><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">MODEL_PATH</span> <span class="o">=</span> <span class="sb">`</span><span class="si">${</span><span class="nx">FileSystem</span><span class="p">.</span><span class="nx">documentDirectory</span><span class="si">}</span><span class="sb">llama-models/v2/Qwen3-1.7B-Q4_K_M.gguf`</span><span class="p">;</span></span></span></code></pre></div></div>
<p>Mỗi phiên bản mô hình mới thì tăng số trong đường dẫn lên. Mô hình cũ sẽ tự được dọn đi khi người dùng gỡ ứng dụng.</p>

<h2 class="relative group">iOS vs Android
    <div id="ios-vs-android" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#ios-vs-android" aria-label="Cọc dấu">#</a>
    </span>
    
</h2>
<p>Trên iOS, Metal chạy nhanh hơn Android khoảng 20%, bộ nhớ được quản lý tốt hơn, và mặc định mô hình sẽ được sao lưu lên iCloud (tắt đi bằng <code>NSURLIsExcludedFromBackupKey</code>, trừ khi bạn muốn ngốn dung lượng sao lưu của người dùng). Android dùng Vulkan/OpenCL thì chênh lệch giữa các máy lớn hơn nhiều: một số GPU đời cũ hoàn toàn không hỗ trợ Vulkan và phải chạy bằng CPU, còn dữ liệu lưu trữ thì không được sao lưu tự động. Hãy kiểm thử trên cả hai nền tảng; khoảng cách giữa một máy Android tốt và một máy Android kém còn lớn hơn cả khoảng cách giữa hai nền tảng.</p>

<h2 class="relative group">Mẹo gỡ lỗi
    <div id="mẹo-gỡ-lỗi" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#m%e1%ba%b9o-g%e1%bb%a1-l%e1%bb%97i" aria-label="Cọc dấu">#</a>
    </span>
    
</h2>
<p><strong>1. Bật ghi nhật ký chi tiết (verbose logging)</strong></p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-typescript" data-lang="typescript"><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">ctx</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">initLlama</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">  <span class="nx">model</span>: <span class="kt">MODEL_PATH</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nx">n_ctx</span>: <span class="kt">8192</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nx">n_gpu_layers</span>: <span class="kt">99</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nx">verbose</span>: <span class="kt">true</span><span class="p">,</span> <span class="c1">// Logs every token + timings
</span></span></span><span class="line"><span class="cl"><span class="p">});</span></span></span></code></pre></div></div>
<p><strong>2. Theo dõi số token</strong></p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-typescript" data-lang="typescript"><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">tokens</span> <span class="o">=</span> <span class="nx">fullResponse</span><span class="p">.</span><span class="nx">split</span><span class="p">(</span><span class="sr">/\s+/</span><span class="p">).</span><span class="nx">length</span> <span class="o">*</span> <span class="mf">1.3</span><span class="p">;</span> <span class="c1">// Rough estimate
</span></span></span><span class="line"><span class="cl"><span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="sb">`Generated </span><span class="si">${</span><span class="nx">tokens</span><span class="si">}</span><span class="sb"> tokens in </span><span class="si">${</span><span class="nx">duration</span><span class="si">}</span><span class="sb">ms`</span><span class="p">);</span></span></span></code></pre></div></div>
<p><strong>3. Giám sát bộ nhớ</strong></p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-typescript" data-lang="typescript"><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">MemoryInfo</span> <span class="p">}</span> <span class="kr">from</span> <span class="s1">&#39;react-native-device-info&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">memoryUsage</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">MemoryInfo</span><span class="p">.</span><span class="nx">getUsedMemory</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"><span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="sb">`Memory: </span><span class="si">${</span><span class="p">(</span><span class="nx">memoryUsage</span> <span class="o">/</span> <span class="mi">1024</span> <span class="o">/</span> <span class="mi">1024</span><span class="p">).</span><span class="nx">toFixed</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span><span class="si">}</span><span class="sb"> MB`</span><span class="p">);</span></span></span></code></pre></div></div>
<p><strong>4. Kiểm thử khi ngoại tuyến (offline)</strong></p>
<p>Bật chế độ máy bay rồi dùng ứng dụng như thật. Mô hình phải nạp được từ bộ nhớ đệm (cache), quá trình suy luận phải chạy xong, và chỉ có phần lưu lên máy chủ là được phép lỗi, mà có lỗi thì cũng phải lỗi êm.</p>

<h2 class="relative group">Tôi muốn đưa nó đi xa hơn thế nào
    <div id="tôi-muốn-đưa-nó-đi-xa-hơn-thế-nào" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#t%c3%b4i-mu%e1%bb%91n-%c4%91%c6%b0a-n%c3%b3-%c4%91i-xa-h%c6%a1n-th%e1%ba%bf-n%c3%a0o" aria-label="Cọc dấu">#</a>
    </span>
    
</h2>
<p>llama.cpp đã hỗ trợ mô hình thị giác (LLaVA, Qwen2-VL), và một tính năng kiểu &ldquo;đây là ảnh vải rèm, thêm vào công việc này giúp tôi&rdquo; rõ ràng rất hợp với ứng dụng này. Tôi cũng muốn thử tinh chỉnh (fine-tune) một LoRA adapter dựa trên các mô tả công việc cũ; tôi đoán độ chính xác với các truy vấn trong lĩnh vực rèm cửa sẽ tăng 20-30%. Xa hơn nữa là Whisper.cpp để có giao diện giọng nói chạy hoàn toàn ngoại tuyến, và có thể là học liên kết (federated learning), để mô hình tốt dần lên nhờ nhiều người dùng mà không có chút dữ liệu thô nào rời khỏi điện thoại của họ.</p>

<h2 class="relative group">Bạn có nên làm theo không?
    <div id="bạn-có-nên-làm-theo-không" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#b%e1%ba%a1n-c%c3%b3-n%c3%aan-l%c3%a0m-theo-kh%c3%b4ng" aria-label="Cọc dấu">#</a>
    </span>
    
</h2>
<p>Nếu bạn đang làm ứng dụng doanh nghiệp có dữ liệu nhạy cảm, quy trình cần chạy được khi ngoại tuyến, hay các tác vụ có cấu trúc như phân loại và nhập liệu, và nhất là nếu bạn lo chi phí API khi mở rộng quy mô, thì câu trả lời là có, cách này dùng được ngay từ hôm nay.</p>
<p>Còn nếu bạn cần trò chuyện tự do về đủ mọi chủ đề, cứ dùng Claude hoặc GPT-4. Tương tự với tác vụ đòi hỏi nhiều kiến thức (mô hình nhỏ đơn giản là không biết nhiều), khi bạn buộc phải hỗ trợ máy cấu hình thấp, hoặc khi kiến thức chuyên ngành của bạn thay đổi nhanh hơn tốc độ bạn phát hành bản cập nhật mô hình.</p>
<p>Toàn bộ hệ thống này tôi làm mất khoảng 12 tiếng. Chi phí vận hành là $0 mỗi tháng, dùng được cả trên máy bay, và dữ liệu khách hàng không bao giờ rời khỏi tay khách hàng. Khó mà chê được.</p>
<hr>
<p><em>Có câu hỏi hay góp ý? Bạn tìm tôi ở <a href="https://twitter.com/jaredlynskey"  target="_blank" rel="noreferrer">@jaredlynskey</a>. Thư viện llama.rn nằm ở <a href="https://github.com/mybigday/llama.rn"  target="_blank" rel="noreferrer">github.com/mybigday/llama.rn</a>.</em></p>
]]></content:encoded>
      <media:content url="https://jared.lynskey.co.nz/en/posts/2026/2026-01-30-on-device-ai/featured.jpg" medium="image" type="image/jpeg" />
    </item>
  </channel>
</rss>
