<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Dat Viet Thanh Nguyen</title><link>https://datvtn.github.io/</link><atom:link href="https://datvtn.github.io/index.xml" rel="self" type="application/rss+xml"/><description>Dat Viet Thanh Nguyen</description><generator>Hugo Blox Builder (https://hugoblox.com)</generator><language>en-us</language><lastBuildDate>Mon, 24 Oct 2022 00:00:00 +0000</lastBuildDate><image><url>https://datvtn.github.io/media/icon_hu82c06b08a282889123d30d9b11b05ced_13465_512x512_fill_lanczos_center_3.png</url><title>Dat Viet Thanh Nguyen</title><link>https://datvtn.github.io/</link></image><item><title>What I Learned Shipping Computer Vision Models to 10+ Edge Targets</title><link>https://datvtn.github.io/writing/deploying-cv-models-on-edge-socs/</link><pubDate>Fri, 14 Aug 2026 00:00:00 +0000</pubDate><guid>https://datvtn.github.io/writing/deploying-cv-models-on-edge-socs/</guid><description>&lt;p>Over four years at VinAI I deployed computer vision models onto NVIDIA Xavier, Qualcomm QCS610 and SA8255P, Ambarella CV25, KingFisher boards, and VinSmart phones — through TensorRT, SNPE, QNN, CVFlow, NCNN, MNN, and TFLite. Different silicon, different toolchains, same recurring lessons. These are the ones I would tell my earlier self.&lt;/p>
&lt;h2 id="the-bottleneck-is-almost-never-the-network">The bottleneck is almost never the network&lt;/h2>
&lt;p>The instinct when a model is too slow on device is to make the model smaller. Often that is the wrong lever. Before touching the architecture, it is worth checking:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Are all your operations actually running on the accelerator?&lt;/strong> A single unsupported op in the middle of a graph can force a fallback to CPU, and the round trip costs more than the op saves. The profile will show a suspiciously expensive layer that looks trivial on paper.&lt;/li>
&lt;li>&lt;strong>Where is the pre- and post-processing running?&lt;/strong> Resize, colour conversion, letterboxing, and NMS are easy to leave in Python or in unoptimised CPU code. On a real pipeline these can rival inference time.&lt;/li>
&lt;li>&lt;strong>Is the pipeline saturated?&lt;/strong> A model that takes 15 ms but sits idle waiting for frames is not a 15 ms pipeline.&lt;/li>
&lt;/ul>
&lt;p>Measuring end to end, on the target, before optimising anything, saves a lot of wasted architecture work.&lt;/p>
&lt;h2 id="flops-are-not-latency">FLOPs are not latency&lt;/h2>
&lt;p>A model with fewer multiply-accumulates is not automatically faster on a given SoC. Depthwise separable convolutions are the classic example: excellent FLOP economy, but memory-bandwidth bound, and on some accelerators they run &lt;em>worse&lt;/em> than a denser convolution with more nominal work.&lt;/p>
&lt;p>This is why designing against the target beats shrinking after the fact. When we built a Neural Architecture Search pipeline to generate object detection architectures for CPU-constrained devices, the useful part was not NAS itself — it was that the search was scored against measured on-device latency rather than a proxy like FLOPs or parameter count. The architectures it found were ones we would not have chosen by intuition.&lt;/p>
&lt;h2 id="ptq-first-qat-when-you-have-to">PTQ first, QAT when you have to&lt;/h2>
&lt;p>Going from FP32 to INT8 is usually the single largest win available, and post-training quantization gets you most of it for very little effort. My default order of operations:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Post-training quantization&lt;/strong> with a representative calibration set. &amp;ldquo;Representative&amp;rdquo; is doing real work in that sentence — a calibration set drawn from clean, well-lit training images will produce a model that falls apart on the hard inputs it will actually see.&lt;/li>
&lt;li>&lt;strong>Look at where the accuracy actually went.&lt;/strong> Quantization error is rarely spread evenly. It concentrates in a few layers — often the first and last, and anything with a wide dynamic activation range.&lt;/li>
&lt;li>&lt;strong>Selectively keep those layers at higher precision&lt;/strong> before reaching for anything more elaborate.&lt;/li>
&lt;li>&lt;strong>Quantization-aware training&lt;/strong> only when the above is not enough. It works, but it costs a training cycle every time the model changes, which changes how quickly you can iterate.&lt;/li>
&lt;/ol>
&lt;p>For face and detection models we compressed substantially with minimal accuracy loss this way. The failure mode to watch for is evaluating the quantized model only on your clean benchmark: INT8 degradation shows up disproportionately on the tail — small faces, low light, motion blur — which is exactly the part of the distribution the product is judged on.&lt;/p>
&lt;h2 id="every-runtime-has-opinions">Every runtime has opinions&lt;/h2>
&lt;p>Supporting many targets means accepting that &amp;ldquo;export the model&amp;rdquo; is not a single step. Each runtime has its own supported operator set, its own layout preferences, its own quantization semantics, and its own quiet behavioural differences. Things that helped:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Keep one source model and a per-target export path&lt;/strong>, not a fork per target. Forks drift, and then a bug fixed in one place lives on in four others.&lt;/li>
&lt;li>&lt;strong>Test numerical parity at every hop.&lt;/strong> Compare FP32 PyTorch against exported FP32 against quantized on-device output, on the same inputs. Catching a discrepancy at the export step is minutes of work; catching it after integration is days.&lt;/li>
&lt;li>&lt;strong>Constrain the architecture to the intersection of what your targets support.&lt;/strong> An exotic operator that only one runtime handles well will cost more in maintenance than it earns in accuracy.&lt;/li>
&lt;/ul>
&lt;h2 id="accuracy-is-not-the-product">Accuracy is not the product&lt;/h2>
&lt;p>A driver monitoring system that is correct 99% of the time but stutters is worse, to the person using it, than one that is slightly less accurate and smooth. Frame-to-frame stability, latency consistency, and graceful behaviour under thermal throttling are all part of whether the thing is any good — and none of them appear in a validation metric.&lt;/p>
&lt;p>That gap between the benchmark and the product is, to me, the interesting part of this work.&lt;/p></description></item><item><title>SEMACS: Semantic–Acoustic Speech Coding for Ultra-Low Token Rates</title><link>https://datvtn.github.io/publication/semacs/</link><pubDate>Thu, 01 Jan 2026 00:00:00 +0000</pubDate><guid>https://datvtn.github.io/publication/semacs/</guid><description>&lt;p>This manuscript is &lt;strong>currently under review&lt;/strong>. A link to the preprint, code, and full details will be added here once it is publicly available.&lt;/p></description></item><item><title>Personality-Driven AI Avatar Bot</title><link>https://datvtn.github.io/project/personality-driven-ai-bot/</link><pubDate>Wed, 01 Jan 2025 00:00:00 +0000</pubDate><guid>https://datvtn.github.io/project/personality-driven-ai-bot/</guid><description>&lt;p>&lt;strong>Dec 2023 – Jan 2025&lt;/strong>&lt;/p>
&lt;h2 id="problem">Problem&lt;/h2>
&lt;p>Group chat applications are text-first. Adding an AI participant that feels like a &lt;em>character&lt;/em> rather than a chat completion means giving it a face, a voice, and a consistent personality — and generating all of that fast enough that it can take part in a live conversation rather than replying minutes later.&lt;/p>
&lt;h2 id="approach">Approach&lt;/h2>
&lt;p>I built the avatar generation side of the product: turning a character definition into a speaking AI avatar that could be dropped into a group chat.&lt;/p>
&lt;p>The pipeline combines several pieces I had been building in parallel:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Identity generation&lt;/strong> — a consistent visual identity per character, stable across every clip the bot produces.&lt;/li>
&lt;li>&lt;strong>Audio-driven talking head&lt;/strong> — driving lip-sync and head motion from a single audio stream while preserving the character&amp;rsquo;s identity, head pose, and facial expression.&lt;/li>
&lt;li>&lt;strong>Expression control&lt;/strong> — routing the character&amp;rsquo;s intended emotion (joy, sadness, anger) into the generated video, so the avatar&amp;rsquo;s delivery matches the tone of what it is saying.&lt;/li>
&lt;li>&lt;strong>Real-time diffusion video&lt;/strong> — a Stable Diffusion video generation pipeline producing avatar speech video directly from audio input.&lt;/li>
&lt;/ul>
&lt;p>The engineering constraint throughout was latency: a conversational avatar that lags is worse than no avatar, so each stage was optimised for real-time or near-real-time throughput rather than best-possible offline quality.&lt;/p>
&lt;h2 id="tech-stack">Tech stack&lt;/h2>
&lt;p>&lt;code>PyTorch&lt;/code> · &lt;code>Stable Diffusion&lt;/code> · &lt;code>TensorRT&lt;/code> · &lt;code>Python&lt;/code> · &lt;code>Docker&lt;/code>&lt;/p>
&lt;h2 id="outcome">Outcome&lt;/h2>
&lt;p>Shipped as the avatar generation component of a group chat application.&lt;/p>
&lt;!-- TODO: add concrete numbers here if they can be shared — e.g. end-to-end latency per utterance, resolution/FPS of generated video, number of supported characters. --></description></item><item><title>Driver Attention Tracking and Analysis</title><link>https://datvtn.github.io/publication/driver-attention-tracking/</link><pubDate>Wed, 10 Apr 2024 00:00:00 +0000</pubDate><guid>https://datvtn.github.io/publication/driver-attention-tracking/</guid><description>&lt;h2 id="why-this-problem">Why this problem&lt;/h2>
&lt;p>Knowing &lt;em>what a driver is looking at&lt;/em> — not just where their head is pointing — is the difference between a monitoring system that can warn about a missed traffic sign and one that cannot. The usual answer is eye-tracking glasses, which are expensive, intrusive, and impractical for mass deployment. They also only tell you what the driver &lt;em>did&lt;/em> look at, never what they missed in the scene.&lt;/p>
&lt;h2 id="what-we-did">What we did&lt;/h2>
&lt;p>We built a dashboard-mounted system that needs only two ordinary cameras: one on the driver&amp;rsquo;s face, one on the road. The core is a convolutional network that reads both images at once and predicts the driver&amp;rsquo;s point-of-gaze directly in the scene frame.&lt;/p>
&lt;p>The hard part is that the geometry between driver and cameras drifts constantly — even within a single drive. Rather than asking the driver to recalibrate, the network includes a &lt;strong>camera calibration module&lt;/strong> that learns an embedding of the driver–camera spatial configuration and is trained end to end with the rest of the model. This module measurably improves gaze accuracy.&lt;/p>
&lt;p>We also released a large-scale &lt;strong>in-situ&lt;/strong> dataset: real urban driving sessions with synchronised face, scene, and gaze video, and over one hundred thousand image pairs annotated with points of gaze — addressing a gap no existing gaze dataset filled.&lt;/p>
&lt;h2 id="result">Result&lt;/h2>
&lt;p>Mean prediction error of &lt;strong>29.69 pixels&lt;/strong> against a 1280×720 scene camera, outperforming the baseline methods we compared against.&lt;/p></description></item><item><title>AI Photo Editing App</title><link>https://datvtn.github.io/project/ai-photo-editing/</link><pubDate>Mon, 01 Jan 2024 00:00:00 +0000</pubDate><guid>https://datvtn.github.io/project/ai-photo-editing/</guid><description>&lt;p>&lt;strong>Sep 2023 – Jan 2024&lt;/strong>&lt;/p>
&lt;h2 id="problem">Problem&lt;/h2>
&lt;p>The edits people actually want from a photo app — &lt;em>remove that person&lt;/em>, &lt;em>drop the background&lt;/em>, &lt;em>make the sky dramatic&lt;/em>, &lt;em>put him in a suit&lt;/em> — are each a different technical problem, but users expect all of them behind a single tap. Each feature needs an accurate mask, a plausible fill, and a result that survives being zoomed into.&lt;/p>
&lt;h2 id="approach">Approach&lt;/h2>
&lt;p>I built four editing features on a shared backbone of segmentation plus generative inpainting:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Object removal&lt;/strong> — segment the unwanted region, then inpaint it with surroundings-consistent content rather than a blur or clone-stamp patch.&lt;/li>
&lt;li>&lt;strong>Background removal&lt;/strong> — high-quality alpha matting around hard cases: hair, glasses, motion blur at the subject boundary.&lt;/li>
&lt;li>&lt;strong>Sky replacement&lt;/strong> — sky segmentation combined with colour harmonisation, so the relit foreground matches the new sky instead of looking pasted on.&lt;/li>
&lt;li>&lt;strong>Suit changer&lt;/strong> — garment-region segmentation and generative synthesis that keeps the subject&amp;rsquo;s identity and pose intact.&lt;/li>
&lt;/ul>
&lt;p>The common thread is that mask quality dominates perceived output quality: a strong generative model behind a sloppy mask still looks wrong at the edges, so most of the tuning effort went into the segmentation stage and its boundary refinement.&lt;/p>
&lt;h2 id="tech-stack">Tech stack&lt;/h2>
&lt;p>&lt;code>PyTorch&lt;/code> · &lt;code>Diffusion Models&lt;/code> · &lt;code>Segmentation networks&lt;/code> · &lt;code>OpenCV&lt;/code> · &lt;code>Python&lt;/code>&lt;/p>
&lt;h2 id="outcome">Outcome&lt;/h2>
&lt;p>Delivered as a set of consumer-facing editing features.&lt;/p>
&lt;!-- TODO: add before/after examples and any quality or runtime numbers that can be shared publicly. --></description></item><item><title>Experience</title><link>https://datvtn.github.io/experience/</link><pubDate>Tue, 24 Oct 2023 00:00:00 +0000</pubDate><guid>https://datvtn.github.io/experience/</guid><description/></item><item><title>QC-StyleGAN: Quality Controllable Image Generation and Manipulation</title><link>https://datvtn.github.io/publication/qc-stylegan/</link><pubDate>Mon, 28 Nov 2022 00:00:00 +0000</pubDate><guid>https://datvtn.github.io/publication/qc-stylegan/</guid><description>&lt;h2 id="why-this-problem">Why this problem&lt;/h2>
&lt;p>StyleGAN and its successors are trained on curated, high-quality datasets. That is fine for synthesis, but it breaks the moment you want to &lt;em>edit a real photo&lt;/em>: GAN inversion pushes a blurry, noisy, or JPEG-compressed input onto a high-quality manifold, so the model silently &amp;ldquo;restores&amp;rdquo; the image while you were only trying to change the hairstyle. You lose the ability to make an edit that preserves the original look.&lt;/p>
&lt;h2 id="what-we-did">What we did&lt;/h2>
&lt;p>We added an explicit &lt;strong>quality control code&lt;/strong> to the generator, so image content and image degradation become separately controllable. The same network can:&lt;/p>
&lt;ul>
&lt;li>synthesise images at any point along a continuous quality axis;&lt;/li>
&lt;li>invert and edit a low-quality image &lt;em>without&lt;/em> changing its quality;&lt;/li>
&lt;li>restore a sharp image by simply moving the quality code — giving blind restoration across noise, blur, compression artifacts, and their mixtures, without training a restoration model;&lt;/li>
&lt;li>synthesise, transfer, and interpolate degradations between images.&lt;/li>
&lt;/ul>
&lt;p>Published at NeurIPS 2022; the work also received a NeurIPS Scholar Award. Code is available in the &lt;a href="https://github.com/VinAIResearch/QC-StyleGAN" target="_blank" rel="noopener">official repository&lt;/a>.&lt;/p></description></item><item><title>Real-Time Portrait Segmentation on Mobile</title><link>https://datvtn.github.io/project/portrait-segmentation/</link><pubDate>Wed, 01 Dec 2021 00:00:00 +0000</pubDate><guid>https://datvtn.github.io/project/portrait-segmentation/</guid><description>&lt;p>&lt;strong>Aug 2021 – Dec 2021&lt;/strong>&lt;/p>
&lt;h2 id="problem">Problem&lt;/h2>
&lt;p>Portrait effects — background blur, replacement, relighting — need a person mask computed every frame, on a phone, inside the camera&amp;rsquo;s frame budget, without draining the battery. A model that is accurate but too slow is useless here, and so is a fast model that flickers between frames or cuts through someone&amp;rsquo;s hair.&lt;/p>
&lt;h2 id="approach">Approach&lt;/h2>
&lt;p>Two halves, and the data half mattered as much as the model:&lt;/p>
&lt;p>&lt;strong>The network.&lt;/strong> A deliberately small segmentation architecture designed against the mobile inference budget rather than trimmed down afterwards. Design decisions were driven by what the target runtime could actually execute efficiently, not just by parameter count — an operation that is cheap on paper is not cheap if the on-device runtime falls back to CPU for it.&lt;/p>
&lt;p>&lt;strong>The data pipeline.&lt;/strong> I built the full pipeline: collection, annotation, quality control, and augmentation. Boundary quality is where portrait segmentation visibly succeeds or fails, so the annotation standard around hair and edges drove most of the final result.&lt;/p>
&lt;p>The model was quantized and deployed through the mobile inference stack used for VinSmart devices, alongside the other lightweight face analysis networks (FaceID, Masked FaceID, camera effects) our team shipped.&lt;/p>
&lt;h2 id="tech-stack">Tech stack&lt;/h2>
&lt;p>&lt;code>PyTorch&lt;/code> · &lt;code>TFLite&lt;/code> · &lt;code>SNPE&lt;/code> · &lt;code>NCNN&lt;/code> · &lt;code>INT8 quantization&lt;/code> · &lt;code>Python&lt;/code> · &lt;code>C++&lt;/code>&lt;/p>
&lt;h2 id="outcome">Outcome&lt;/h2>
&lt;p>A portrait segmentation network running in real time on target mobile hardware.&lt;/p>
&lt;!-- TODO: add the model size, on-device latency, and IoU if these can be shared publicly. --></description></item><item><title>Industrial Surface Defect Detection</title><link>https://datvtn.github.io/project/surface-defect-detection/</link><pubDate>Thu, 01 Apr 2021 00:00:00 +0000</pubDate><guid>https://datvtn.github.io/project/surface-defect-detection/</guid><description>&lt;p>&lt;strong>Jan 2021 – Apr 2021&lt;/strong>&lt;/p>
&lt;h2 id="problem">Problem&lt;/h2>
&lt;p>Industrial defect inspection is an unbalanced detection problem with an unforgiving cost function: defects are rare, visually subtle, and vary in severity, while a false negative ships a bad part and a false positive stops a line. The output the customer needs is not just &lt;em>defect / no defect&lt;/em> but a &lt;strong>grade&lt;/strong> — how severe, and therefore what to do about it.&lt;/p>
&lt;h2 id="approach">Approach&lt;/h2>
&lt;p>I built the detection and grading models, and — the part that shaped the project most — the integration path.&lt;/p>
&lt;p>The customer&amp;rsquo;s existing inspection software was a .NET application, not a Python stack. Rather than standing up a separate inference service and forcing a network hop into a line-rate process, the model was packaged as a &lt;strong>.NET-consumable DLL&lt;/strong> so it could be called directly from the existing application. That removed a whole class of latency and deployment failure modes, at the cost of doing the native packaging and memory-management work properly.&lt;/p>
&lt;p>On the modelling side, the emphasis was on the severity grading step: getting a reliable ordering of defect severity out of a detector matters more to the operator than squeezing the last point of detection mAP.&lt;/p>
&lt;h2 id="tech-stack">Tech stack&lt;/h2>
&lt;p>&lt;code>PyTorch&lt;/code> · &lt;code>LibTorch&lt;/code> · &lt;code>C++&lt;/code> · &lt;code>.NET DLL integration&lt;/code> · &lt;code>OpenCV&lt;/code>&lt;/p>
&lt;h2 id="outcome">Outcome&lt;/h2>
&lt;p>Delivered as an integrated component of the customer&amp;rsquo;s existing inspection application.&lt;/p>
&lt;!-- TODO: add defect classes covered, grading accuracy, and throughput per part if these can be shared publicly. --></description></item><item><title>Autopilot Perception on Jetson TX2</title><link>https://datvtn.github.io/project/autopilot-perception/</link><pubDate>Fri, 01 Jan 2021 00:00:00 +0000</pubDate><guid>https://datvtn.github.io/project/autopilot-perception/</guid><description>&lt;p>&lt;strong>Jan 2020 – Jan 2021&lt;/strong>&lt;/p>
&lt;h2 id="problem">Problem&lt;/h2>
&lt;p>An autonomous vehicle has to see and react inside a control loop that does not wait. On a Jetson TX2, the whole perception stack — not just one model — has to fit in a few watts and a few tens of milliseconds, while staying reliable under changing light, weather, and road surfaces.&lt;/p>
&lt;h2 id="approach">Approach&lt;/h2>
&lt;p>I worked on the perception side of the vehicle: the models that turn camera frames into the lane, sign, and obstacle information the control stack acts on.&lt;/p>
&lt;p>The interesting constraint was that accuracy and latency traded against each other continuously. Time spent on a heavier detector is time the controller does not have to react, so the work was as much about &lt;strong>inference engineering&lt;/strong> — optimising the graph, choosing precisions, keeping the pipeline saturated — as about model architecture. The stack was built against TensorRT to get the most out of the TX2&amp;rsquo;s GPU.&lt;/p>
&lt;p>This was developed for the FPT Digital Race, where the system had to run a real course rather than a held-out test set — which surfaces a different class of failure than offline evaluation does.&lt;/p>
&lt;h2 id="tech-stack">Tech stack&lt;/h2>
&lt;p>&lt;code>PyTorch&lt;/code> · &lt;code>TensorRT&lt;/code> · &lt;code>NVIDIA Jetson TX2&lt;/code> · &lt;code>OpenCV&lt;/code> · &lt;code>Python&lt;/code> · &lt;code>C++&lt;/code>&lt;/p>
&lt;h2 id="outcome">Outcome&lt;/h2>
&lt;p>&lt;strong>Top 10 — FPT Digital Race 2020 Final.&lt;/strong>&lt;/p>
&lt;!-- TODO: add the perception pipeline's end-to-end FPS on TX2 and which specific tasks the stack covered. --></description></item></channel></rss>