Kimi K3 ಜೊತೆ Lulucat Notes ರೆಂಡರಿಂಗ್ ಪೈಪ್ಲೈನ್ ಅನ್ನು ಮರುನಿರ್ಮಿಸುವುದು
ನಮ್ಮ Core Graphics tile pipeline ಆರು ಸಣ್ಣ ಹಂತಗಳಲ್ಲಿ Metal point-sprite pipeline ಆಗಿ ಬದಲಾಯಿತು; ಪ್ರತಿಯೊಂದು ಹಂತವನ್ನೂ ನಿಜವಾದ iPad ನಲ್ಲಿ ಪರಿಶೀಲಿಸಲಾಯಿತು. ಕೋಡ್ ಅನ್ನು Fireworks ಮೇಲಿನ Kimi K3 ಜೊತೆ pair programming ಮೂಲಕ ಬರೆಯಲಾಯಿತು.
ಕಳೆದ ವಾರ, Lulucat Notes ನಲ್ಲಿ lasso selection ಅನ್ನು ಎಳೆದಾಗ ಒಂದು ಗೋಚರ ಅಲೆ ಕಾಣಿಸಿತು: ಒಂದೇ frame ನಲ್ಲಿ ಕೆಲವು screen tiles selection ಅನ್ನು ಹೊಸ position ನಲ್ಲಿ ತೋರಿಸಿದರೆ, ಇತರವು ಇನ್ನೂ ಹಳೆಯ position ನಲ್ಲಿಯೇ ತೋರಿಸುತ್ತಿದ್ದವು. ಇಡೀ rendering pipeline ಅನ್ನು — Core Graphics bitmap ಮತ್ತು CATiledLayer — Metal ನಿಂದ ಬದಲಿಸಿದೆವು. ಆರು ಸಣ್ಣ ಹಂತಗಳಲ್ಲಿ ಈ ಬದಲಾವಣೆಯನ್ನು ಮಾಡಿದೆವು; ಮುಂದಿನ ಹಂತ ಆರಂಭಿಸುವ ಮೊದಲು ಪ್ರತಿಯೊಂದು ಹಂತವನ್ನೂ ನಿಜವಾದ iPad ಮೇಲೆ ಪರಿಶೀಲಿಸಲಾಯಿತು.
ಕೋಡ್ ಅನ್ನು Fireworks ಮೇಲೆ ನಡೆಯುತ್ತಿದ್ದ Moonshot ನ open model Kimi K3 ಜೊತೆ pair-program ಮಾಡಲಾಯಿತು. ಮನುಷ್ಯನು ದಿಕ್ಕು ತೋರಿಸಿದ, ನಿರ್ಧರಿಸಿದ ಮತ್ತು ಪರೀಕ್ಷಿಸಿದ; model ಬಹುತೇಕ ಪ್ರತಿಯೊಂದು line ಅನ್ನೂ ಬರೆದಿತು.
ಪೈಪ್ಲೈನ್
ಹಳೆಯ pipeline ನಲ್ಲಿ ನಿಧಾನವಾಗಿ ಪರಸ್ಪರ ದೂರವಾದ drawing ನ ಎರಡು ವಿಧಗಳಿದ್ದವು: ಕಡಿಮೆ ವೆಚ್ಚದ display ಗಾಗಿ strokes ಅನ್ನು bitmap ಒಳಗೆ bake ಮಾಡಲಾಗುತ್ತಿತ್ತು, ಮತ್ತು tiles ಗೆ ಹೆಚ್ಚಿನ detail ಬೇಕಾದಾಗಲೆಲ್ಲಾ vectors ಆಗಿ ಮರುಚಿತ್ರಿಸಲಾಗುತ್ತಿತ್ತು. ಹೊಸ pipeline ನಲ್ಲಿ ಒಂದೇ ಒಂದು ಕಲ್ಪನೆ ಇದೆ: ink ನಂತೆ ಕಾಣುವ ಎಲ್ಲವೂ point sprite. Pen stroke, highlighter sweep ಮತ್ತು eraser dab — ಇವೆಲ್ಲವೂ ಒಂದೇ 32-byte vertex — position, diameter, color — ಆಗಿದ್ದು, stroke ನ arc length ಉದ್ದಕ್ಕೂ ಒಂದು point ಅಂತರದಲ್ಲಿ ಇರಿಸಲಾದ GPU-rasterized circles ಆಗಿ ಅದೇ shader pair ನಿಂದ draw ಆಗುತ್ತವೆ.
![]()
ಒಂದು point ಅಂತರದಲ್ಲಿ circles ಸರಪಳಿ ಗಣಿತೀಯವಾಗಿ ಪರಿಪೂರ್ಣ capsule ನಿಂದ ಸುಮಾರು 0.075 points ದೂರ ಸರಿಯುತ್ತದೆ — ನಮ್ಮ canvas density ಯಲ್ಲಿ ಒಂದು pixel ನ ಐದನೇ ಒಂದು ಭಾಗ. ಬದಲಿಗೆ, ಮೂರು tools ಒಂದೇ code path ಗೆ ಸೇರುತ್ತವೆ ಮತ್ತು GPU ತಾನು ಅತ್ಯುತ್ತಮವಾಗಿ ಮಾಡುವ ಕೆಲಸವನ್ನೇ ಮಾಡುತ್ತದೆ.
ಆ ಕಲ್ಪನೆಯ ಸುತ್ತ architecture ಸರಳವಾಗಿದೆ. Committed ink ಒಂದೇ 4096² texture ನಲ್ಲಿ ಇರುತ್ತದೆ. UIScrollView ಉಳಿದಿದೆ, ಆದರೆ pure gesture engine ಆಗಿ ಅದರ ಪಾತ್ರವನ್ನು ಇಳಿಸಲಾಗಿದೆ: ಅದರ contentOffset ಮತ್ತು zoomScale ಪ್ರತಿ frame ನಲ್ಲಿ viewport uniform ಗೆ feed ಆಗುತ್ತವೆ, ಆದ್ದರಿಂದ panning ಮತ್ತು zooming ಏನನ್ನೂ write ಮಾಡುವುದಿಲ್ಲ. ಪ್ರತಿ frame ನಲ್ಲಿ ಐದು draws ಇರುತ್ತವೆ:
flowchart TB
subgraph frame["Every frame: five draws"]
direction TB
paper["1 · paper blit"] --> ink["2 · committed ink"]
ink --> live["3 · live stroke"]
live --> sel["4 · selection"]
sel --> dash["5 · lasso dashes"]
end
commit["stroke commit<br/>append stamps"] --> tex[("ink texture<br/>4096² render target")]
replay["regional replay<br/>erase · delete · move · undo"] --> tex
tex -. "sampled or re-drawn" .-> ink
ತಿದ್ದುಪಡಿಗಳು texture ಗೆ ನೇರವಾಗಿ ಬರೆಯುತ್ತವೆ. Stroke ಅನ್ನು commit ಮಾಡಿದಾಗ ಅದರ stamps ಸೇರಿಸಲಾಗುತ್ತದೆ. Erase, delete, move ಮತ್ತು undo ಗಳು ಪರಿಣಾಮಿತ region ಅನ್ನು scissor rectangle ಹಿಂದೆ replay ಮಾಡುತ್ತವೆ: region ಅನ್ನು clear ಮಾಡಿ, ಅದನ್ನು intersect ಮಾಡುವ strokes ಅನ್ನು ಮರುಚಿತ್ರಿಸಿ, ಮುಗಿಯಿತು. Partial erasure ಹಿಂದಿನ post ನ ownership semantics ಅನ್ನು ಉಳಿಸುತ್ತದೆ — ಒಂದು erasure ಅದು ink ಅನ್ನು ತೆಗೆದುಹಾಕುವ stroke ಗೆ ಸೇರಿದೆ — ಪ್ರತಿಯೊಂದು erased stroke ಅನ್ನು scratch texture ಒಳಗೆ draw ಮಾಡಿ, destination-out blending (
ಈ ಎಲ್ಲಕ್ಕೂ ಕಾರಣವಾದ selection ಈಗ point sprites ಆಗಿಯೂ draw ಆಗುತ್ತದೆ. ಅದನ್ನು ಎಳೆದಾಗ ಒಂದು uniform offset update ಆಗುತ್ತದೆ. Zero texture writes, zero tile invalidations — ಅಲೆ mitigation ಆಗಿಲ್ಲ; ಅದು ರಚನಾತ್ಮಕವಾಗಿ ಮಾಯವಾಗಿದೆ.
ಗೇಟ್ಕೀಪರ್: ಒಂದು ಪಿಕ್ಸೆಲ್ ವ್ಯತ್ಯಾಸ
Core Graphics renderer ಅನ್ನು ನಾವು delete ಮಾಡಲಿಲ್ಲ. ಅದನ್ನು offline reference implementation ಆಗಿ ಇಳಿಸಿದೆವು, ಮತ್ತು ಪ್ರತಿಯೊಂದು Metal change ಕೂಡ device ನಲ್ಲಿ capture ಮಾಡಿದ ನಿಜವಾದ stroke data ಮೇಲೆ ಅದರ ವಿರುದ್ಧ pixel comparison ಅನ್ನು pass ಮಾಡಬೇಕು. Acceptance criterion “identical pixels” ಅಲ್ಲ — ಎರಡು ಸರಿಯಾದ rasterizers anti-aliased edges ಉದ್ದಕ್ಕೂ ಕೆಲವು gray levels ನಷ್ಟು ನ್ಯಾಯಸಮ್ಮತವಾಗಿ ಭಿನ್ನವಾಗಬಹುದು. Gate ರಚನಾತ್ಮಕವಾಗಿದೆ: missing ink ಇಲ್ಲ, offset ಇಲ್ಲ, color drift ಇಲ್ಲ, ಮತ್ತು ink ನಿಂದ ದೂರವಿರುವ ಎಲ್ಲೆಡೆ ಯಾವುದೇ ದೊಡ್ಡ difference ಇಲ್ಲ.
![]()
Offline renderer ಅನ್ನು ನಿರ್ಮಿಸುವಾಗ ಎದುರಿಸಿದ ಐದು bugs ನಲ್ಲಿ ನಾಲ್ಕನ್ನು ಈ harness ಹಿಡಿಯಿತು. ಎಲ್ಲವನ್ನೂ output ಅನ್ನು pixel level ಗೆ crop ಮಾಡಿ diagnose ಮಾಡಲಾಯಿತು: Swift/Metal struct stride mismatch (28 bytes ವಿರುದ್ಧ 32, ಏಕೆಂದರೆ Metal float4 ಅನ್ನು 16 ಗೆ align ಮಾಡುತ್ತದೆ — screen color blocks ನಿಂದ ತುಂಬಿತು), fragment shader ನಲ್ಲಿ [[point_size]] ಅನ್ನು varying ಆಗಿ ಓದಲು ಸಾಧ್ಯವಾಗದದ್ದು (ಪ್ರತಿ stamp square ಆಯಿತು), ಒಂದೇ command buffer ನಲ್ಲಿ ಎರಡು render encoders ಸಹಬಾಳ್ವೆ ನಡೆಸಿದ್ದು (ಎಲ್ಲವೂ black), ಮತ್ತು quick strokes ನ ಮೊದಲ millimeter ಕಾಣದಂತೆ ಮಾಡಿದ missing start dab.
ಆರು ಹಂತಗಳು, ಒಂದು ಮರುಬರವಣಿಗೆಯಲ್ಲ
Migration plan ಸ್ವತಂತ್ರವಾಗಿ ship ಮಾಡಬಹುದಾದ ಆರು ಹಂತಗಳಾಗಿತ್ತು: pixel diff pass ಮಾಡುವ offline renderer; zero visual change ಇರುವ display shell; GPU ಮೇಲಿನ live stroke; GPU ಮೇಲಿನ selection; texture ಗೆ ನೇರವಾಗಿ ಬರೆಯುವ mutations; high-zoom vector re-drawing (“zero visual change” ಅದನ್ನು ಬೇಡಿಕೊಂಡಿದ್ದರಿಂದ step two ಒಳಗೆ ಸೇರಿಸಲಾಯಿತು). ಪ್ರತಿಯೊಂದು ಹಂತವೂ simulator ಅಥವಾ screenshot diff ಅಲ್ಲ, ಮೇಜಿನ ಮೇಲಿದ್ದ iPad Pro ನಲ್ಲಿ ಮನುಷ್ಯನು ಬರೆಯುವುದು, ಅಳಿಸುವುದು, zoom ಮಾಡುವುದು ಮತ್ತು drag ಮಾಡುವುದರೊಂದಿಗೆ ಮುಗಿಯಿತು.
Device ಪ್ರತಿಯೊಂದು automated check ತಪ್ಪಿಸಿಕೊಂಡ ಮೂರು bugs ಅನ್ನು ಹಿಡಿಯಿತು. 100% zoom ಗಿಂತ ಮೇಲೆ strokes ಎರಡು ಬಾರಿ draw ಆದವು — ಕೆಳಗೆ soft texture, ಮೇಲೆ sharp sprites — ಮನುಷ್ಯನು ಕೆಲವು ಕ್ಷಣಗಳಲ್ಲೇ ಗಮನಿಸಿದ faint blur ಆಗಿ ಅದು ಕಾಣಿಸಿತು. Stroke commit ಮಾಡಿದಾಗ ಒಂದು frame ಗೆ flicker ಆಯಿತು, ಏಕೆಂದರೆ ಹಳೆಯ overlay texture update ನೊಂದಿಗೆ sync ತಪ್ಪಿ cross-fade out ಆಯಿತು. 170% zoom ಗಿಂತ ಮೇಲೆ ಪ್ರತಿಯೊಂದು note ಮಾಯವಾಯಿತು: visibility-culling rectangle ತನ್ನ scaled coordinate space ನಲ್ಲಿ contentOffset ಬಳಸಿತು, ಆದ್ದರಿಂದ zoom ಮಾಡುವಂತೆ ಅದು strokes ನಿಂದ ದೂರ drift ಆಯಿತು. ಮೂರೂ ಒಂದು function ನಲ್ಲಿ ಒಂದು line ಬದಲಿಸುವಷ್ಟು ಸಣ್ಣ fixes ಆಗಿದ್ದವು; ಮೊದಲೇ ಬರೆಯಬಹುದಾಗಿದ್ದ ಯಾವುದೇ test ಕೂಡ ಅವುಗಳನ್ನು ಹಿಡಿಯುತ್ತಿರಲಿಲ್ಲ, ಏಕೆಂದರೆ ಅವುಗಳಿಗಾಗಿ ನೋಡಬೇಕು ಎಂಬುದೇ ನಮಗೆ ತಿಳಿದಿರಲಿಲ್ಲ. UI-first consumer app ನಲ್ಲಿ ಮನುಷ್ಯನನ್ನು loop ಒಳಗೆ ಇಡುವುದು ಏಕೆ ಅಗತ್ಯ ಎಂಬುದು ಇದೇ.
Kimi K3 ಜೊತೆ ಕೆಲಸ ಮಾಡುವುದು ಹೇಗಿದೆ
ಮೊದಲನೆಯದಾಗಿ, ಇದು ವೇಗವಾಗಿದೆ. “discuss, write, build, install, look” loop ನಿಮಿಷಗಳಲ್ಲಿ ನಡೆಯಿತು, ಮತ್ತು ವೇಗವಾಗಿ ಉತ್ತರಿಸುವ model ಒಂದು ದಿನದಲ್ಲಿ ನೀವು ಎಷ್ಟು loops ನಡೆಸಬಹುದು ಎಂಬುದನ್ನೇ ಬದಲಾಯಿಸುತ್ತದೆ.
ಎರಡನೆಯದಾಗಿ, ಇದು over-engineer ಮಾಡುವುದಿಲ್ಲ. ಈ codebase ಸ್ಪಷ್ಟವಾದ house rules ಮೇಲೆ ನಡೆಯುತ್ತದೆ — launch ಗಿಂತ ಮೊದಲು backward-compatibility scaffolding ಇಲ್ಲ, device ಅಗತ್ಯವೆಂದು ಸಾಬೀತುಪಡಿಸಿದಾಗ ಮಾತ್ರ complexity — ಮತ್ತು K3 ನೆನಪಿಸಬೇಕಾಗದೆ ಅವುಗಳನ್ನು ಪಾಲಿಸುತ್ತದೆ. ಇದು “later” ಗಾಗಿ spatial indexes ಸೇರಿಸಲಿಲ್ಲ, ಪ್ರತಿಯೊಂದು call ಅನ್ನು defensive checks ನಲ್ಲಿ wrap ಮಾಡಲಿಲ್ಲ, speculative abstraction ಮಾಡಲಿಲ್ಲ. ಇದಕ್ಕೆ prompt ನೀಡುವುದು house rules ಓದಿ ಅವುಗಳನ್ನು ನಿಜವಾಗಿಯೂ ನಂಬುವ ಸಮರ್ಥ ಸಹೋದ್ಯೋಗಿಯೊಂದಿಗೆ ಕೆಲಸ ಮಾಡುವಂತಿದೆ.
ಮೂರನೆಯದಾಗಿ, tools ಕೊಟ್ಟರೆ ಇದು ಅವುಗಳನ್ನು ಉತ್ಸಾಹದಿಂದ ಬಳಸುತ್ತದೆ. ನಾವು image utilities — view, pixel region ಗೆ crop, resize — ಅನ್ನು ಜೋಡಿಸಿದೆವು; ನಂತರ model ಮೇಲಿನ ಐದು harness bugs ಅನ್ನು diagnose ಮಾಡಲು ತನ್ನ renderer output ಅನ್ನು proactively crop ಮಾಡಲು ಆರಂಭಿಸಿತು. ಅಂಥ tool ಇದ್ದದ್ದೇ ಅದಕ್ಕೆ ನೋಡಬೇಕೆಂದು ನೆನಪಿಸಿತು.
ಇನ್ನೊಂದು ಅರ್ಧ: ಈ ಕಥೆಯ ಬಹುತೇಕ bugs ಅನ್ನು K3 ಬರೆದಿದೆ, notes ಮಾಯವಾಗುವಂತೆ ಮಾಡಿದ coordinate-space bug ಕೂಡ ಅದರಲ್ಲಿ ಸೇರಿದೆ. ಅದರ ಮಿತಿಗಳು ನಿಜ. ಕೆಲಸವನ್ನು ಸುರಕ್ಷಿತಗೊಳಿಸಿದ್ದು model ಯಾವಾಗಲೂ ಸರಿಯಾಗಿರುವುದು ಅಲ್ಲ; rendering drift ಅನ್ನು ಹಿಡಿದ harness ಮತ್ತು feel ಅನ್ನು ಹಿಡಿದ ಮನುಷ್ಯ. ಆದರೂ, ದಿನನಿತ್ಯದ ಕೆಲಸದಲ್ಲಿ, ನಾವು ಬಳಸುವ frontier closed models — Opus-class systems — ಇವುಗಳಿಂದ ಅದನ್ನು ವಿಶ್ವಾಸಾರ್ಹವಾಗಿ ಬೇರ್ಪಡಿಸಲು ನನಗೆ ಸಾಧ್ಯವಾಗಲಿಲ್ಲ. ಕೆಲವು ಅಂಶಗಳಲ್ಲಿ ಅದು ಸ್ಪಷ್ಟವಾಗಿ ಉತ್ತಮವಾಗಿತ್ತು: ಹೆಚ್ಚು ವೇಗವಾಗಿ, ಮತ್ತು codebase ಅನ್ನು defensive design ನಿಂದ ತುಂಬುವ ಒಲವು ಬಹಳ ಕಡಿಮೆ.
ಇನ್ನು ಮುಂದೆ ನಾವು ಹೇಗೆ ನಿರ್ಮಿಸಲು ಬಯಸುತ್ತೇವೆ
ದೊಡ್ಡ specification ಆಧಾರಿತ agentic development ನೊಂದಿಗೆ ನಾವು ಮುಗಿಸಿದ್ದೇವೆ — model ಗೆ ದೊಡ್ಡ specification ಕೊಟ್ಟು, ಅದರಿಂದ ಬಂದದ್ದನ್ನೆಲ್ಲ ಸ್ವೀಕರಿಸುವ ಶೈಲಿ. ಅದರ failure mode ಕೆಟ್ಟ code ಅಲ್ಲ; ಯಾರಿಗೂ ಅರ್ಥವಾಗದ code.
ಇಲ್ಲಿ ಕೆಲಸ ಮಾಡಿದದ್ದು, ಮತ್ತು ನಾವು ಮುಂದುವರಿಸುವುದು: ಸಣ್ಣ ಹಂತಗಳು; ಪ್ರತಿಯೊಂದನ್ನೂ ಆರಂಭಿಸುವ ಮೊದಲು ಚರ್ಚಿಸುವುದು, build ಮಾಡುವ ಮೊದಲು ಮನುಷ್ಯನಿಗೆ ಅರ್ಥವಾಗಿರುವುದು, ಮತ್ತು ಅದು ವಾಸಿಸುವ device ಮೇಲೆ verify ಮಾಡುವುದು. Model ನ ಕೆಲಸ ತ್ವರಿತವಾಗಿರುವುದು, ನಿಖರವಾಗಿರುವುದು ಮತ್ತು uncertainty ಬಗ್ಗೆ ಪ್ರಾಮಾಣಿಕವಾಗಿರುವುದು. ಮನುಷ್ಯನ ಕೆಲಸ judgment, taste ಮತ್ತು e2e verification — ವಿಶೇಷವಾಗಿ UI-first consumer software ನಲ್ಲಿ, ಅಲ್ಲಿ “right” ಹೇಗೆ ಅನಿಸುತ್ತದೆ ಎಂಬುದನ್ನು specification ವಿವರಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ. Fireworks ಮೇಲಿನ Kimi K3 ಈ loop ಗೆ ಸರಿಯಾಗಿ ಹೊಂದಿಕೊಳ್ಳುತ್ತದೆ ಎಂಬುದು ತಿಳಿದುಬಂತು: loop ಅನ್ನು tight ಆಗಿಡಲು ಸಾಕಷ್ಟು ವೇಗ, ಹಂತಗಳನ್ನು ಸಣ್ಣದಾಗಿ ಮತ್ತು ಸ್ವಚ್ಛವಾಗಿ ಇಡಲು ಸಾಕಷ್ಟು ಬುದ್ಧಿವಂತಿಕೆ.
ಅಲೆ ಮಾಯವಾಗಿದೆ, pipeline ಎರಡು ಬದಲು ಒಂದೇ ಕಲ್ಪನೆಯಾಗಿದೆ, ಮತ್ತು ನಮ್ಮನ್ನು ಅಲ್ಲಿಗೆ ತಂದ ಪ್ರಕ್ರಿಯೆ ಮುಂದುವರಿಯುತ್ತದೆ.