原始圖片、標示出的 seam 路徑(紅色)以及 content-aware 裁切後的結果

我們試著讓 Seam Carving 在 GPU 上跑更快——然後我們證明了為什麼它不能

完整論文:seam_carving.pdf · 程式碼:YuXiangLo/NTUPDP2026 與吳雅蓁、羅宇翔合作,台大平行程式設計,Spring 2026。 起點 Seam carving 是一種 content-aware 的圖像縮放演算法。它不是裁切或縮放,而是移除圖片中「最不重要」的像素路徑——seam——同時保留視覺上重要的區域。效果出乎意料地好。 演算法主要有兩個沉重的步驟:先計算 energy map(梯度大小,對整張圖掃一遍);再執行動態規劃(DP)找出從上到下代價最小的連通路徑。移除那條 seam,重複。 在 CPU 上,這對大圖來說很慢。單核心處理一張 8K 圖(7680×4320,約 3300 萬像素)每條 seam 可能要幾百毫秒。我們有 V100。Energy 計算是完美的平行問題。問題看起來很明顯:能快多少? 我們原本預期找到一個新的優化方式,拿到一個漂亮的數字。最後我們證明了為什麼漂亮的數字在結構上不可能達到——而這才是更有趣的結果。 DP 的問題 用 Nsight Compute 對 1428×968 的圖做 profiling,問題立刻清楚了:DP kernel 佔每條 seam 牆上時間的 ~93%。其他全是雜訊。 DP 的遞推關係是: 1 M[i][j] = e[i][j] + min(M[i-1][j-1], M[i-1][j], M[i-1][j+1]) 每一行依賴上一行。全部 H 行都是串行的。每行內部的 W 列可以平行——但在第 i-1 行完成之前無法開始第 i 行,沒有任何重新排序能消除這個依賴。這就是我們一直碰到的牆。 優化路徑 我們在每個步驟以 Nsight 為導引,建立了五個逐步改良的 kernel。 Naive 每行啟動一個 CUDA kernel。在 4K(H=2160)時,每條 seam 要啟動 2159 次 kernel,每次都有完整的 global memory 往返。小圖上 overhead 主導,大圖上 global memory 流量主導。 ...

June 14, 2026 · 3 分鐘 · Yi-Wei Lien
TypeScript monorepo 架構圖——微服務共享單一 repo

整個團隊都沒做過的那次遷移

背景 Shopback 的後端跑在一組微服務上——每個服務各自管理一個領域,透過網路溝通,各自擴縮容。這個架構有清楚的好處,也有顯而易見的成本:閒置的 container 仍然佔用分配的資源,就算當下沒在處理任何流量,機器費照算。 遷移到 monorepo 架構的動機之一就在這裡。TypeScript monorepo 讓多個服務可以共享同一個 process 和資源池,閒置的開銷縮小,機器費也跟著縮小。 我的子任務很具體:兩個服務原本透過網路互相呼叫 API,遷移之後改為在 monorepo 裡直接呼叫函式。聽起來是個重構。實際做起來,變成我實習期間最困難的一件事。 為什麼這件事很難 第一層困難是技術面。Shopback 的 TypeScript monorepo 結構主要是為 library 和共用套件設計的——這是 monorepo 最常見的使用情境。在這個結構裡跑真正的後端服務是另一回事。服務有自己的啟動生命週期、自己的 framework 相依、自己的 runtime 問題。Library 沒有這些。 團隊裡已經有工程師實作了部分 monorepo,但針對的是 library 使用情境。他們的程式碼我看得到,但沒辦法直接套用——他們設計時的假設對 running services 不成立,尤其是不同後端 framework(我們跨服務用了不只一種)在 monorepo 的依賴解析機制下如何互動。 還有版本相容性的問題。TypeScript monorepo 在 workspace 層級管理套件版本,而某些跨服務依賴有寫死的版本假設,合在一起跑就會出現衝突。這不是能用 grep 找到的 bug,而是跑起來才會炸、而且 stack trace 看不出所以然的那種問題。 Mentor 也沒有答案 第二層困難是:我的 mentor 也沒做過這件事。 這不是批評,就是事實。他有自己的工作,能幫的地方他都幫了。但針對我在解的這個遷移問題,他跟我一樣是從零開始。 所以我在一個技術問題上獨立工作,沒有團隊裡的前例,codebase 夠大、搞清楚相關部分要花真正的時間,而 framework 設定在內部也缺乏文件記錄。 兩三個 Sprint,我進度緩慢。我能看出需要做什麼,但真的讓實作跑起來是另一回事。 那條 Slack 訊息 最後我做了一件我一直猶豫要不要做的事:主動傳訊息給另一個團隊的資深工程師,他是我知道曾碰過 codebase 相鄰部分的人。 ...

November 1, 2025 · 1 分鐘 · Yi-Wei Lien
Bubblo — 2D 像素平台遊戲標題畫面

一學期帶一個遊戲專案:敏捷沒有教你的事

背景 2025 年 4 月。六個人,一個學期,目標:用 Unity 完成一款完整的 2D 平台遊戲。 這款遊戲叫 Bubblo。你扮演一隻泡泡生物,在像素風格的奇幻樂園中探索,拯救被關在籠子裡的村民,同時對抗各種針型敵人——蜜蜂、跳躍蜘蛛、還有一隻非常想把你戳破的獨角獸。核心機制是泡泡物理:在關卡中彈跳漂浮,用你的柔軟特性同時解決移動和戰鬥問題。 我是專案負責人。我的工作是把一堆模糊的想法轉化為真正可以交付的遊戲細節——然後讓另外五個人在三個月內朝同一個方向前進,而且沒有人中途放棄。 我從 Cmoney 帶來了什麼 在這之前幾個月,我剛結束在 Cmoney 的後端實習,九個月的真實敏捷流程:衝刺規劃、每日站立、Sprint Review、回顧。我參加了夠多這些會議,對它們的用途有個淺層的理解。 所以開始 Bubblo 時,我有一套框架。我們會跑兩週的 Sprint,有正式的任務看板,做 Retrospective。我知道這些詞彙。 但我還不完全理解的是:這些詞彙背後有一套隱含的假設——關於人們如何安排時間、「可用性」代表什麼,以及一個團隊最主要的義務是什麼。 在公司,每個人最主要的工作就是這個專案。Sprint 是容器。敏捷儀式有效,是因為它設計的前提是全職投入。 在學校,這對任何人都不成立。 為什麼儀式不管用 我們的團隊成員有實驗室工作、其他課程、課外活動、實習申請要處理。有人在 Sprint 中途趕一篇研究論文;有人有兼職工作。每個人都是一個完整的人,在 Bubblo 之外有完整的生活。 要跑一場完整的 Sprint 規劃,意味著請人們拿出他們根本沒有的兩個小時。開回顧會議感覺很表演,因為真正的阻礙只是大家很忙、而且對此感到愧疚。每日站立變成焦慮的來源,而不是同步機制。 僵硬的結構在製造摩擦,而不是消除摩擦。 設計一個真正合適的工作流程 所以我把它拆解精簡。 任務看板留下來——這是最有價值的東西。所有人都能非同步看到「目前有什麼任務、什麼在進行中、什麼被卡住了」。維護這個看板不需要任何儀式,成本幾乎是零。 我用非同步確認取代了排程站立:每次工作時段開始時發一條短訊息,沒有格式要求,只是「今天在做 X,如果 Y 沒解決可能會卡住」。低壓力,大家真的會看。 每週同步變成一場有單一議程的會議:現在誰被什麼卡住了,我能在接下來三十分鐘內幫你解決嗎? 不是進度報告,不是狀態更新。只有阻礙。跑二十到三十分鐘,阻礙清完就結束。 背後的原則是:尊重每個人的第一義務不是這個專案,但讓人們在有時間的時候容易貢獻。精簡,但不鬆散。 沒有人想要的 OOP 重構(但我們還是做了) 大約六週後,我們遇到了問題。角色行為的程式碼已經有機生長成一團亂麻。多個人在碰同一塊,一個地方的改動會在另一個地方產生難以追蹤的 bug。 我們重構了。Sprint 進行中。學生時程上。 在任何遊戲程式碼開始之前,我已經在紙上把元件架構設計成圍繞 Observer、State Machine 和 Command 模式。重構是要在實作中真正落地這些模式:讓狀態轉換變得明確、讓事件系統成為跨元件溝通的唯一真相來源。 這是正確的決定。重構之後,新增一種敵人類型或玩家狀態變成了一個有邊界的操作。Sprint 最後三分之一明顯比前三分之二不混亂。 時機感覺不對,但繼續修補一個纏繞的 codebase 會更糟。有些技術債複利速度夠快,必須提早還清。 AI 工具的時代背景 整個專案我們用 ChatGPT。那時候還沒有 IDE 代理工具——沒有整合的程式碼生成,頂多就是補全。主要用法是:描述一個問題,拿到一個草稿,再把草稿改成能用的東西。 ...

July 10, 2025 · 1 分鐘 · Yi-Wei Lien