หัวใจข้อแรก: รันไฟล์ int8 บน PC วัด accuracy + latency ตัวเลขนี้คือ ground truth ที่ NPU ต้องได้ตรง
def bench_int8(model_path, X, y, z):
it = Interpreter(model_path=model_path); it.allocate_tensors()
inp, out = it.get_input_details()[0], it.get_output_details()[0]
in_scale, in_zero = inp["quantization"]
out_scale, out_zero = out["quantization"]
correct, total_ms = 0, 0.0
for i in range(len(X)):
x = (X[i:i+1] - z["mean"]) / z["std"] # เติม 1 · normalize
q = np.clip(np.round(x/in_scale + in_zero), -128, 127).astype(np.int8) # เติม 2
it.set_tensor(inp["index"], q)
t0 = time.perf_counter(); it.invoke() # เติม 3 · จับเวลาอนุมาน
total_ms += (time.perf_counter() - t0) * 1000.0
o = (it.get_tensor(out["index"])[0].astype(np.float32) - out_zero) * out_scale
correct += int(o.argmax() == y[i]) # เติม 4 · นับคลาสถูก
return correct/len(X), total_ms/len(X)
in_scale/in_zeroอ่านมาจากตัวโมเดล ไม่ใช่เดา — เพราะ PTQ ฝังคู่ scale/zero ไว้ในทุก tensor แล้ว เราแค่หยิบมาใช้ให้ตรง
หัวใจข้อห้า: เรียก quantize_vela.sh ให้ Vela คอมไพล์ int8 → ไฟล์ที่บอร์ดรันได้
def run_vela(int8_path):
try:
# เติม 5: subprocess.run(["./quantize_vela.sh", int8_path], check=True)
subprocess.run(["./quantize_vela.sh", int8_path], check=True)
except (subprocess.CalledProcessError, FileNotFoundError):
print("ยังไม่มี vela ในเครื่องนี้ (อยู่ใน Docker image ของ บทเรียน 5.3–5.5) — ข้ามไปก่อนได้")
return None
base = os.path.splitext(os.path.basename(int8_path))[0]
out = os.path.join("output", base + "_vela.tflite")
return out if os.path.exists(out) else None
try/except เพราะ vela อาจไม่ได้ลงในเครื่องนี้ (มันอยู่ใน Docker) — สคริปต์ต้องไม่ตายทั้งตัวเพราะขั้นเดียวขาดcheck=True = ถ้า vela คืน exit code ผิด ให้โยน error ขึ้นมา ไม่กลืนเงียบนี่คือ ขั้นเดียวในทั้งคอร์ส ที่ MCU ต้องทำต่างจากคนอื่น — จำภาพนี้ไว้ เว็บ/PC/Cortex-A ข้ามขั้นนี้ทั้งหมด
หัวใจข้อสุดท้าย: เอา accuracy กับ latency มาวางเทียบสามเป้าหมายในตารางเดียว
def print_table(pc_acc, pc_ms, mcu_ms):
mcu_lat = "%.2f" % mcu_ms if mcu_ms is not None else "อ่านจากบอร์ด"
print("PC/Docker | model_int8.tflite | %.3f | %.2f ms | ground truth" % (pc_acc, pc_ms))
print("MCU + NPU | model_int8_vela.tflite| %.3f | %s ms | Vela + Ethos-U55" % (pc_acc, mcu_lat))
print("Web | model_web.tflite | ~%.3f | (เบราว์เซอร์) | float I/O" % pc_acc)
pc_acc ไว้ก่อน (Vela ไม่แก้คณิต) แล้วยืนยันด้วย verdict จริงบนบอร์ด--mcu-ms เข้ามาตารางนี้แหละคือ ชัยชนะที่วัดได้ ของชุดบทเรียน — ไม่ใช่แค่ "รันได้" แต่ตอบได้ว่าแต่ละเป้าหมายเร็ว/แม่น/ประหยัดต่างกันแค่ไหน
ตัวเลข MCU มาจากไหน? จาก MicroPython บนบอร์ดจริง — พอ wrap โมเดลเสร็จ มันโผล่ใน edge_ai.models() แล้วเราอ่าน latency ได้แบบเดียวกับโมเดลอื่น:
import edge_ai, time
for m in edge_ai.models():
print(m['index'], m['name']) # โมเดล gesture ของเราโผล่เป็นชื่อใหม่
edge_ai.select(N) # N = index ที่เจอ
time.sleep_ms(500)
r = edge_ai.result()
print("บน NPU:", r['label'], r['latency_ms'], "ms") # <- เลขนี้ไปเติมช่อง MCU
edge_ai ตัวเดียวกับที่เราใช้ตั้งแต่ บทเรียน 1.1–1.3 — วันนี้ต่างแค่โมเดลในทะเบียนเป็นของเราเองr['latency_ms'] คือเวลาที่ NPU ใช้อนุมานจริง เอาไปเทียบกับ pc_ms ในตารางได้เลยคอร์สนี้เดินจาก "เรียก
edge_aiที่คนอื่นเตรียมไว้" (บทเรียน 1.1–1.3) มาถึง "ใส่โมเดลของเราเข้าไปในedge_aiเอง" (บทเรียน 5.8–5.9) — วงกลมปิดครบตรงนี้
ก่อนพาโมเดลขึ้นบอร์ดจริง ลองดูหน้าตาที่เราเล็งไว้บน BENTO Edge AI Emulator — จอ emulator ที่รันได้จริงในเบราว์เซอร์ ให้เห็น verdict + latency แบบเดียวกับบนบอร์ด โดยยังไม่ต้องเสียบสาย

emulator ช่วยให้ทุกคนเห็นภาพเดียวกันได้พร้อมกันโดยไม่ต้องมีบอร์ดครบทุกคน แต่จำไว้ว่า เลข latency ของ MCU ต้องมาจากบอร์ดจริงเท่านั้น — emulator ไม่มี NPU ให้จับเวลาแทนได้
เริ่มจากฝั่งที่ไม่ต้องมีบอร์ดก่อน เปิดเทอร์มินัลใน shared/training/:
pip install ai-edge-litert numpy (หรือเข้า Docker image ของ บทเรียน 5.3–5.5 ที่มีครบ + มี vela)python s14_tflite_board.py — จะเห็น accuracy + latency ของ int8 บน PCvela สคริปต์จะรัน Vela ต่อให้ ได้ output/model_int8_vela.tfliteถ้าเครื่องไม่มี
velaก็ไม่เป็นไร รันในเทอร์มินัลของ Docker image บทเรียน 5.3–5.5 ได้ (velaลงไว้ในนั้นแล้ว) — สคริปต์จะไม่ตายเพราะเราห่อtry/exceptไว้
ต่อไปพาโมเดลขึ้นบอร์ดจริง (สาย researcher — ต้องแก้และ build เฟิร์มแวร์เอง):
output/model_int8_vela.tflite เป็น AIM_GESTURE_* (Path A DEEPCRAFT converter ง่ายสุด)gesture ใน AI_MODELS · เพิ่ม GESTURE_ROW ใน s_models[] · วางไฟล์โมเดลrm -rf proj_cm55/build; make program EDGE_AI_MODEL=combo (ถ้าใช้ SDK สาธารณะ ข้อ 2 เปลี่ยนเป็นเรียก ai_engine_register() จากโค้ดของเรา เพราะ ai_engine.c อยู่ในไลบรารี prebuilt)ข้อ 4 ไม่ใช่พิธีกรรม — หลัง flash เซนเซอร์/NPU ต้อง init ใหม่จากไฟจริง ไม่งั้นผลที่อ่านได้อาจเป็นของรอบก่อน นี่คือนิสัย embedded ที่คอร์สย้ำตลอด
บนบอร์ด (BENTO IDE, REPL) รัน MPY สั้นๆ เพื่อดึงเลข MCU:
edge_ai.models() — ยืนยันว่าโมเดล gesture ของเราโผล่ (นับจำนวนโมเดลเพิ่มขึ้น)edge_ai.select(N) เลือกโมเดลเรา แล้วทำท่า idle/circle/shakingedge_ai.result() — ดู label ถูกไหม + จด latency_mspython s14_tflite_board.py --mcu-ms <ค่าที่จด> → ตารางเติมช่อง MCU ครบr = edge_ai.result()
print(r['label'], r['conf'], r['latency_ms']) # เช่น shaking 0.94 3.9
เมื่อ
labelบนบอร์ดตรงกับคลาสจริง และ accuracy ตรงกับ PC — แปลว่า front-end บนบอร์ด feed ถูก parity ครบสามเป้าหมาย นี่คือ MVP ของบทเรียน 5.8–5.9
เปิด s14_tflite_board.py มี pass/placeholder วางไว้ 5 จุด ตรงหัวใจของ pipeline:
| # | จุด | เติมด้วย | ถ้าลืม |
|---|---|---|---|
| 1 | bench_int8 |
x = (X[i:i+1] - z["mean"]) / z["std"] |
accuracy เพี้ยน (front-end ผิด) |
| 2 | bench_int8 |
q = np.clip(np.round(x/in_scale+in_zero),-128,127)... |
โมเดลเห็น input มั่ว |
| 3 | bench_int8 |
จับเวลา t0=perf_counter(); invoke(); total_ms+=... |
latency = 0 ตลอด |
| 4 | bench_int8 |
correct += int(o.argmax() == y[i]) |
accuracy = 0 เสมอ |
| 5 | run_vela |
subprocess.run(["./quantize_vela.sh", int8_path], check=True) |
ไม่มีไฟล์ _vela |
ขั้นตอน: ไล่หา # เติม: ทีละจุด แทน placeholder → python s14_tflite_board.py → ดูตาราง ถ้า accuracy ดูแปลก กลับไปเช็กช่อง 1/2 ก่อน (front-end คือจุดพังบ่อยสุด)
ห้าช่องนี้คือสอง verb หลักของชุดบทเรียน: quantize ให้ถูก (1-2) และ วัดให้เป็น (3-4) บวกขั้นพิเศษของ MCU (5)
อยากเข้าใจ quantization / NPU / Vela ให้ลึกกว่านี้ ลองตามลิงก์เหล่านี้ต่อ (เปิดดูได้ตามสะดวก):
วิดีโอ/เอกสารสอน (ของฟรี คุณภาพดี)
ภาพ/เอกสารอ้างอิง
วิดีโอ/ภาพภายนอกเป็นของเจ้าของต้นฉบับ ใช้เพื่อการศึกษา อ้างอิงลิงก์ต้นทาง — เราลิงก์ไปหา ไม่ได้ฝังหรือดัดแปลง
MVP ของบทเรียน 5.8–5.9 (เกณฑ์ผ่านของชุดบทเรียน): คุณทำโมเดลของตัวเองผ่าน quantize_vela.sh → wrap → รันบนบอร์ดผ่าน edge_ai ได้ verdict ที่คลาสถูก แล้ว เทียบสามเป้าหมาย (accuracy ตรง · latency ต่างกันอย่างมีเหตุผล)
"เทียบเป้าหมาย" ไม่ใช่แค่กรอกตัวเลข — ต้องตอบได้ว่างานแบบไหนควรอยู่เป้าหมายไหน และแลกอะไรกับอะไร
ถ้าติด ให้ไต่บันไดนี้ทีละขั้น อย่าเพิ่งกระโดดไปดูเฉลย เพราะของจะเข้าหัวตอนที่คุณพยายามเองก่อน:
# เติม: ทั้ง 5 จุดในไฟล์ฝึก + ตารางหน้าที่แล้ว บอกว่าแต่ละช่องเติมอะไรs14_tflite_board.py มีโครงครบทั้งไฟล์แล้ว เหลือ 5 จุดให้เติมs14_tflite_board.py เติมครบพร้อมคอมเมนต์อธิบายทุกช่อง (อ่านให้เข้าใจ ปิดไฟล์ แล้วพิมพ์เอง)s14_tflite_board_full.py ฉบับขัดเรียบร้อย เพิ่ม bench เส้นทาง Web + คอลัมน์ power + --show-mpyลองเขียนเองให้สุดก่อนนะ ถ้าติดจริงๆ ค่อยเปิดเฉลยดูทีละช่อง แล้วกลับมาพิมพ์เอง — เดี๋ยวเราค่อย ๆ แกะไปด้วยกัน
การพาโมเดลขึ้น NPU รวบยอดหลายชั้นของทั้งคอร์สมาไว้ในชุดบทเรียนเดียว:
ฝั่ง Edge AI / การ deploy
AIM_* — 4 ฟังก์ชันที่ทำให้ edge_ai รันโมเดลอะไรก็ได้ (registry shape-driven)ฝั่ง parity / วิธีทำงาน
edge_ai บนบอร์ดจริงทั้งหมดนี้ปิดวง Pillar 4: จากข้อมูลดิบ (บทเรียน 5.1–5.2) → เทรน (บทเรียน 5.3–5.5) → เว็บ (บทเรียน 5.6–5.7) → บนชิป (บทเรียน 5.8–5.9) โมเดลของเราเดินครบทุกเป้าหมายแล้ว
การเลือกเป้าหมายไม่ใช่เรื่องวิชาการ มันคือการตัดสินใจที่สินค้าจริงต้องตอบทุกวัน:
เพราะ
.tfliteไฟล์เดียวรันได้ทุกที่ เราจึง "เลื่อนการตัดสินใจ" ได้จนมีข้อมูลจริง — เทรนครั้งเดียว แล้ววัดว่าอยู่ที่ไหนดีที่สุด นี่คือพลังของ train once, run everywhere
งานทำเอง (ท้ายบทเรียน):
s14_tflite_board.py ให้ครบ 5 ช่อง รัน bench int8 บน PC ได้ accuracy + latency จริงquantize_vela.sh (ใน Docker บทเรียน 5.3–5.5) ให้ได้ output/model_int8_vela.tflite แล้วจดขนาดไฟล์เทียบกับ int8 เดิมedge_ai.latency() บนบอร์ด เอามาเติมช่อง MCU ด้วย --mcu-ms แล้วอธิบายว่าทำไม NPU เร็วกว่า PCใบ้ข้อ 3 — ถ้า accuracy บนบอร์ดไม่ตรง PC อย่าโทษ Vela ก่อน ให้ไล่ front-end (normalize/quantize) ที่ feed บนบอร์ดว่าตรงกับตอนเทรนไหม
วันนี้เราได้: เข้าใจว่าทำไม MCU ต้องมี Vela · quantize→Vela→wrap ด้วย AIM_* · รันโมเดลเราเองบน NPU ผ่าน edge_ai · เทียบสามเป้าหมายด้วยเลขจริง
ชุดบทเรียนถัดไป (บทเรียน 6.1–6.2) เราเข้าบล็อก Apps — เอาโมเดล (ของโรงงานหรือของเราเอง) มาทำเป็นแอปจริงต่อ verdict หนึ่งตัว หนึ่งงาน เจอกันครับ