अनुक्रमणिका
7 मिनट पढ़ने में

570,000 Lines का LLM Code Compile हुआ। Performance SQLite से 20,171 गुना धीमी थी।

किसी ने LLM से लिखे Rust reimplementation of SQLite को benchmark किया। Code जो सही दिखता है और code जो सच में सही है, उनके बीच का अंतर पांच orders of magnitude निकला।

एक LLM ने पूरा SQLite का Rust reimplementation लिखा। Compile हुआ। Tests pass हुए। Code clean था, well-structured था, idiomatic Rust था। और एक basic primary key lookup पर यह SQLite से 20,171 गुना धीमा था।

यह number पढ़कर मैं रुक गया। इसलिए नहीं कि LLM-generated code का slow होना कोई बड़ी बात है, बल्कि इसलिए कि slowness कहां से आ रही थी। Code में कोई ऐसी गलती नहीं थी जो compiler या test suite पकड़ सके। B-tree सही implement था। Query planner था। Storage engine काम करता था। हर piece अपनी जगह defensible था। लेकिन पूरा system मिलकर लगभग unusable था।

मैंने benchmark analysis और source code दोनों ध्यान से पढ़े। जो patterns मिले वो बार-बार LLM-generated projects में दिखते हैं, और मुझे लगता है ये इन models के code लिखने के तरीके के बारे में कुछ fundamental बताते हैं।

B-tree था। Query Planner ने उसे ignore किया।

SQLite में PRIMARY KEY lookup B-tree path लेता है और O(log n) time में खत्म होता है। where.c में चार lines iPKey check करती हैं और query को सीधे tree पर route करती हैं। यह एक micro-optimization है जो तभी sense देती है जब आपको पूरे system की समझ हो।

LLM-generated version में B-tree implementation भी था। Isolation में सही काम करता था। Problem यह थी कि query planner ने primary key lookups के लिए उसे कभी call ही नहीं किया। is_rowid_ref() function सिर्फ तीन literal strings पहचानता था: “rowid”, “rowid”, और “oid”। अगर आपने column id INTEGER PRIMARY KEY declare किया था, तो planner उसे rowid alias नहीं मानता था। हर query full table scan पर जाती थी।

इसका math बहुत brutal है। 100 rows को 100 बार query करने पर B-tree path लगभग 700 comparison steps लेता है। Full scan path 10,000 से ज़्यादा। लेकिन असली नुकसान algorithmic complexity से आता है: O(log n) per lookup, O(n) बन जाता है, और पूरे benchmark suite में यह compound होकर 20,171x का gap बनाता है।

यह वो bug है जो कोई unit test नहीं पकड़ता जब तक आप specifically benchmark न लिखें। B-tree काम करता है। Scan काम करता है। Planner गलत एक choose करता है। सब pass होता रहता है।

Safe Defaults compound interest की तरह बढ़ते हैं

इस case को single routing bug से ज़्यादा interesting बनाने वाली बात यह थी। Query planner issue account करने के बाद भी reimplementation लगभग 2,900 गुना धीमा था। बाकी का gap अलग-अलग individually reasonable decisions की layer से आया।

हर query execution पूरा AST clone करता था और उसे bytecode में recompile करता था। SQLite prepared statement handles reuse करता है। दोनों approaches valid हैं, लेकिन हर execution पर AST clone करना scale पर expensive है।

हर page read heap पर fresh 4KB buffer allocate करता था। SQLite का page cache already-loaded memory का direct pointer return करता है। LLM version ने safe, obvious path चुना: allocate, read, return। काम करता है। बस thousands of pages per query read करते वक्त orders of magnitude धीमा है।

हर commit पूरा schema scratch से rebuild करता था। SQLite एक single integer cookie value compare करता है। अगर cookie नहीं बदला, तो schema valid है। Reimplementation में यह concept था ही नहीं, इसलिए हर बार पूरा काम होता था।

हर statement एक sync_all() call trigger करता था जो सारे file metadata को disk पर flush करे। SQLite fdatasync() use करता है जो सिर्फ file data flush करता है और metadata sync skip करता है। Write-heavy workloads पर यह फर्क बहुत मायने रखता है।

इसे मैं defensive defaults का compound effect कहता हूं। हर choice की अपनी isolated justification है। AST clone करने से Rust में ownership complexity avoid होती है। Fresh buffer allocate करने से use-after-free bugs नहीं होते। Schema rebuild करने से stale cache issues नहीं आते। sync_all() call करने से strongest durability guarantee मिलती है।

लेकिन performance costs add नहीं होते, multiply होते हैं। जब चार 10x penalties stack होती हैं, तो 40x slower नहीं होता। 10,000x slower होता है। LLM इस compounding के बारे में नहीं सोचता क्योंकि वो हर function relatively isolation में generate करता है। Local optimize करता है और globally pay करता है।

एक cron one-liner को replace करने के लिए 82,000 Lines

उसी developer के दूसरे LLM-generated project में यही pattern अलग तरीके से दिखा। Problem: Rust का target/ directory time के साथ disk space खाता रहता है। LLM का solution: एक 82,000-line Rust daemon जिसमें सात dashboards हैं और एक Bayesian scoring engine है जो decide करे कि कौन से artifacts clean up होने चाहिए।

Existing solution है find ./target -type f -atime +30 -delete, एक cron job में single line। Zero dependencies। या cargo-sweep, एक official community tool जो already exist करता है और उन edge cases handle करता है जो daemon नहीं करता।

LLM-generated project ने 192 dependencies pull किए। Reference के लिए: ripgrep, Rust ecosystem के सबसे sophisticated search tools में से एक, 61 use करता है।

यही pattern बार-बार दिखता है: LLMs वो build करते हैं जो आपने मांगा, वो नहीं जो आपको चाहिए। अगर आप prompt करें “build a system that intelligently manages Rust build artifacts with monitoring and scoring,” तो exactly वही मिलेगा। Model के पास यह पूछने का कोई mechanism नहीं है कि problem को system की ज़रूरत है भी या नहीं। उसे नहीं पता कि target/ directory size Rust community में एक perennial complaint है जिसके well-known solutions मौजूद हैं। वो 192 dependencies बनाम zero का maintenance cost नहीं सोचता।

Research भी यही दिशा दिखाती है

मैं जानना चाहता था कि ये दोनों projects outliers हैं या नहीं, इसलिए broader research देखी। हैं नहीं।

METR ने 16 experienced open-source developers के साथ randomized controlled trial किया। AI tools use करने वाले group ने tasks 19% धीमे complete किए control group की तुलना में। जो बात मेरे मन में रह गई: experiment खत्म होने के बाद, AI group को लगा कि वो 20% faster थे। Productivity का subjective experience measured reality से उलटा था।

GitClear ने 210 million lines of code analyze किए और पाया कि copy-pasted code ने refactored code को पहली बार overtake किया। यह trend directly AI coding tool adoption के साथ correlate करता है। Code जितनी तेज़ी से add हो रहा है, उतनी तेज़ी से improve नहीं हो रहा।

Google के DORA 2024 report ने पाया कि AI adoption में 25% की बढ़ोतरी deployment stability में 7.2% की गिरावट के साथ correlate करती थी। ज़्यादा AI-generated code production में जा रहा है, ज़्यादा incidents आ रहे हैं।

NeurIPS 2024 के Mercury benchmark ने standard coding benchmarks में efficiency metrics add किए। जब आप सिर्फ “does it produce correct output” नहीं बल्कि “does it produce correct output without wasting resources” measure करते हैं, तो pass rates 50% से नीचे गिर जाते हैं।

इसका मतलब यह नहीं कि LLMs coding के लिए useless हैं। मैं खुद उन्हें constantly use करता हूं। लेकिन इसका मतलब यह ज़रूर है कि “compiles और tests pass” एक dangerously low bar है। Plausible code और correct code के बीच का gap वही जगह है जहां real engineering होती है।

Developers से यह असल में क्या मांगता है

Core problem यह नहीं है कि LLMs बुरा code लिखते हैं। वो ऐसा code लिखते हैं जो locally coherent है और globally incoherent है। हर function sense देता है। System नहीं देता। यही वो failure mode है जो traditional testing miss कर देती है, क्योंकि tests local behavior verify करते हैं।

ज़रूरत है ऐसे evaluation की जो gaps को target करे। Benchmarks, न सिर्फ tests। CI में performance budgets, न सिर्फ correctness checks। Architectural review जो पहले पूछे “यह module exists क्यों है” उससे पहले कि यह काम करता है या नहीं। Dependency audits जो solution की complexity की तुलना problem की complexity से करें।

सवाल यह नहीं है कि “क्या यह code सही दिखता है?” सवाल यह है कि “हम कैसे prove करें कि यह सही है?” और prove करने के लिए वो systems-level thinking चाहिए जो LLMs में अभी नहीं है।

आपने जो मांगा और production जो demand करता है, उनके बीच का gap वही जगह है जहां engineering judgment रहती है। Measurement के बिना, code generation सिर्फ token generation है।

न्यूज़लेटर से जुड़ें

मेरे नवीनतम प्रोजेक्ट्स, लेखों और AI तथा वेब डेवलपमेंट प्रयोगों के बारे में अपडेट प्राप्त करें।