尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

AI Engineering from Scratch:构建高确定性AI生产系统的六项硬核实践

AI Engineering from Scratch:构建高确定性AI生产系统的六项硬核实践 1. 这不是“搭积木”而是重新理解AI工程的底层逻辑很多人看到“AI Engineering from Scratch”第一反应是“不就是用LangChain搭个RAG再接个LLM API”——这恰恰是当前最危险的认知偏差。我带过17个AI落地项目其中12个在第二个月就卡在“无法上线”上根本原因不是模型不好而是整个工程链路从第一天就建在流沙上。所谓“from scratch”不是从零写Transformer而是从零定义数据如何可信地流动、推理如何可验证地执行、服务如何可预期地降级。它解决的不是“能不能跑通”而是“上线后第37小时当GPU显存突然飙到98%、用户query里混进一段base64编码的恶意payload、下游数据库连接池耗尽时系统是否还能返回一条有业务意义的错误提示而不是直接500崩掉”。关键词里没有一个词是关于模型精度的全是关于工程确定性——AI Engineering的本质是把概率性输出装进确定性管道。你不需要会推导反向传播但必须能看懂Prometheus监控图里p99延迟突刺和CUDA OOM之间的因果链你不需要手写CUDA kernel但得知道为什么把batch_size从4调到8QPS没涨反而P95延迟翻了3倍。这篇文章不教你怎么调LoRA它讲的是当你删掉所有现成框架只留Linux内核、Python解释器和一块A100怎么用最原始的工具链一砖一瓦垒出一条能扛住真实业务流量的AI流水线。适合两类人一是刚跳出“调参侠”舒适区、想真正吃透AI系统全貌的工程师二是技术决策者需要判断团队到底缺的是算法博士还是能把LLM塞进银行核心交易链路的系统架构师。2. 为什么放弃LangChain/LLamaIndex是第一步也是最关键的一步我见过太多团队在需求评审会上信心满满地说“我们用LangChain一周就能上线”结果三个月后还在debug文档切分器把PDF表格撕成碎片的问题。这不是工具不行而是它们的设计哲学和生产环境存在根本性错配。LangChain本质是个教学框架——它的模块像乐高拼起来demo很炫但每个接口都默认信任上游输入、忽略下游负载、回避状态一致性。举个真实案例某金融客户要求“根据财报PDF生成风险摘要”我们初期用LangChainChroma本地测试完美。上线后第三天运维报警Chroma向量库内存泄漏每小时涨2GB48小时后OOM。排查发现LangChain的DocumentLoader默认把PDF每页转成独立Document对象而Chroma的add_documents()方法内部会为每个Document生成embedding并缓存中间结果。当用户上传一份200页的年报瞬间创建200个Python对象Chroma的内存管理器根本没做对象复用更别说批量embedding的CUDA stream优化。最终解决方案删掉LangChain改用PyMuPDF直接解析PDF获取文本块坐标用HuggingFace Transformers的pipeline接口批量处理显式控制batch_size16结果存入PostgreSQL的jsonb字段——内存稳定在1.2GBP95延迟从3.2s降到0.8s。这不是倒退是回归工程本质明确每一行代码的资源契约。from scratch的第一课就是亲手写一个比LangChain少80%代码、但多300%可控性的loader# 真实生产环境loader非示例已脱敏 import fitz # PyMuPDF from typing import List, Dict, Any class FinancialPDFLoader: def __init__(self, max_page_size_mb: float 15.0): self.max_page_size_mb max_page_size_mb def load(self, pdf_path: str) - List[Dict[str, Any]]: 严格按业务规则切分跳过封面/目录/附录保留表格结构标记 doc fitz.open(pdf_path) pages [] for page_num in range(len(doc)): # 关键跳过扫描件页面无text layer if not doc[page_num].get_text(blocks): continue # 关键识别财报特有结构如合并资产负债表标题下必有表格 blocks doc[page_num].get_text(blocks) text_blocks [] for block in blocks: if block[4].strip(): # block[4] is text content # 用正则识别表格行特征连续数字百分号货币符号 if re.search(r\d{1,3}(?:,\d{3})*(?:\.\d)?\s*%, block[4]): text_blocks.append({type: table_row, content: block[4]}) else: text_blocks.append({type: text, content: block[4]}) # 关键限制单页文本长度防超长页拖垮embedding full_text \n.join([b[content] for b in text_blocks]) if len(full_text.encode(utf-8)) self.max_page_size_mb * 1024 * 1024: # 按语义段落切分而非暴力截断 paragraphs full_text.split(\n) chunks self._semantic_chunk(paragraphs) pages.extend([{page: page_num, chunk: c} for c in chunks]) else: pages.append({page: page_num, full_text: full_text}) return pages def _semantic_chunk(self, paragraphs: List[str]) - List[str]: 基于财报语义的chunk确保资产总计和对应数值在同一chunk chunks [] current_chunk [] for para in paragraphs: if re.match(r^\s*(资产|负债|所有者权益|利润总额|净利润)\s*$, para.strip()): if current_chunk: chunks.append(\n.join(current_chunk)) current_chunk [] current_chunk.append(para) if current_chunk: chunks.append(\n.join(current_chunk)) return chunks提示这段代码里藏着三个生产级思维1max_page_size_mb参数不是随便写的它对应GPU显存中embedding batch的内存预算2_semantic_chunk不依赖NLTK或spaCy因为财报文本结构高度固定正则比模型更可靠、更快3type字段标记表格行后续embedding时可启用特殊tokenization策略。这些细节LangChain的抽象层里永远看不到。放弃高级框架不是回到石器时代而是为了看清地基上的每一道裂缝。当你亲手处理PDF坐标、手动管理内存、显式声明每个函数的资源消耗边界时“AI Engineering”的“Engineering”二字才真正有了重量。3. Embedding服务不用FAISS用PostgreSQLpgvector的硬核实践现在主流教程都在教你怎么用FAISS做向量检索但没人告诉你当你的向量库要支撑每天50万次查询、支持按时间范围业务标签置信度阈值三重过滤时FAISS的纯向量索引会变成性能黑洞。我们曾在一个医疗问答项目里踩过这个坑FAISS索引100万条医嘱embedding单次查询平均85ms但加上“只查2023年后的处方”这个条件就得先用PostgreSQL查出ID列表再用FAISS查向量——结果P99延迟飙升到2.3s。解决方案删掉FAISS用pgvector。不是因为它“新”而是因为它把向量检索变成了SQL问题而SQL引擎天生擅长复合条件过滤。pgvector的核心优势在于它把向量当作普通列索引和查询完全融入PostgreSQL的查询优化器。你可以写这样的SQLSELECT id, content, 1 - (embedding [0.1,0.2,...]) AS similarity FROM medical_embeddings WHERE created_at 2023-01-01 AND department_id IN (101, 102, 105) AND confidence_score 0.85 ORDER BY embedding [0.1,0.2,...] LIMIT 5;这条SQL同时完成时间过滤、部门ID过滤、置信度过滤、向量相似度排序——全部在一次查询中由PostgreSQL原生执行。我们实测对比A100 PostgreSQL 15 pgvector 0.7场景FAISS方案pgvector方案说明纯向量检索100万条85ms112msFAISS快但无业务过滤时间部门双过滤2300ms186msFAISS需两步pgvector一步加入置信度过滤3100ms192msFAISS无法在向量索引中嵌入标量条件并发100 QPSP95 3200msP95 210msFAISS锁竞争严重pgvector利用连接池注意pgvector的性能不是凭空来的。它依赖两个关键配置1CREATE INDEX ON medical_embeddings USING ivfflat (embedding vector_cosine_ops) WITH (lists 100);——lists参数必须根据数据量校准100万条用1001000万条需调到10002SET pgvector.enable_indexscan true;必须开启否则走全表扫描。这些参数背后是向量量化原理ivfflat将向量空间划分为lists个簇查询时只搜索最近的几个簇lists太小漏召回太大增延迟。更硬核的是embedding生成服务本身。我们不用HuggingFace的pipeline而是用ONNX Runtime直接加载量化后的all-MiniLM-L6-v2模型# 生产级embedding服务Flask ONNX import onnxruntime as ort import numpy as np from transformers import AutoTokenizer class ONNXEmbedder: def __init__(self, model_path: str): # 关键使用CPU provider避免GPU上下文切换开销 self.session ort.InferenceSession( model_path, providers[CPUExecutionProvider] # 不用CUDA ) self.tokenizer AutoTokenizer.from_pretrained(sentence-transformers/all-MiniLM-L6-v2) def encode(self, texts: List[str], batch_size: int 32) - np.ndarray: 显式批处理避免动态padding导致的显存碎片 all_embeddings [] for i in range(0, len(texts), batch_size): batch texts[i:ibatch_size] # 关键固定max_length64截断而非padding inputs self.tokenizer( batch, truncationTrue, max_length64, paddingFalse, # 禁用padding return_tensorsnp ) # 手动pad到batch内最长长度非全局max max_len max(len(x) for x in inputs[input_ids]) padded_inputs { input_ids: np.array([ np.pad(x, (0, max_len-len(x)), constant_values0) for x in inputs[input_ids] ]), attention_mask: np.array([ np.pad(x, (0, max_len-len(x)), constant_values0) for x in inputs[attention_mask] ]) } # ONNX inference ort_inputs { input_ids: padded_inputs[input_ids].astype(np.int64), attention_mask: padded_inputs[attention_mask].astype(np.int64) } ort_outputs self.session.run(None, ort_inputs) all_embeddings.append(ort_outputs[0]) # [batch, seq_len, hidden] # 取[CLS] token embedding并归一化 embeddings np.vstack(all_embeddings)[:, 0, :] # [total, hidden] return embeddings / np.linalg.norm(embeddings, axis1, keepdimsTrue) # 为什么不用GPU实测数据A100上ONNX CPU推理32文本耗时42msCUDA推理因context切换memory copy反而58ms这段代码揭示了from scratch的残酷真相性能优化不是堆硬件而是消灭隐式开销。禁用padding不是偷懒是避免GPU显存中产生大量无效token用CPU而非CUDA是因为小batch推理时GPU启动开销远超计算收益固定max_length64是因为医疗文本极少超长强行pad到512只会浪费90%显存带宽。这些选择没有“标准答案”只有你对着火焰图flame graph和nvidia-smi dmon日志一行行抠出来的血泪经验。4. LLM推理服务绕过vLLM用Triton Inference Server自建流水线vLLM确实快但它把“推理服务”封装成了黑盒。当你需要在LLM输出前插入风控规则引擎、在生成中途注入实时数据库查询结果、或对特定token序列强制终止生成时vLLM的API就变成了枷锁。我们为某政务热线做的AI坐席系统要求当用户说出“我要投诉”时必须立即停止生成转而调用工单系统创建投诉单——这个需求vLLM无法满足因为它不暴露生成过程中的token流。解决方案用NVIDIA Triton Inference Server自己组装推理流水线。Triton的核心价值在于模型编排ensemble。它允许你把多个模型或传统函数串成流水线每个stage可以是PyTorch模型、TensorRT引擎、Python后处理脚本、甚至curl调用外部API。我们的政务坐席流水线结构如下[HTTP Request] → [Stage 1: Text Preprocessor] → 清洗敏感词、标准化地址格式 → [Stage 2: Intent Classifier (TensorRT)] → 判断是否投诉/咨询/查询 → [Stage 3: Conditional Router] → 若intent投诉跳转到Stage 4否则到Stage 5 → [Stage 4: Ticket Creator (Python Backend)] → 调用工单API返回ticket_id → [Stage 5: LLM Generator (TensorRT)] → 基于promptticket_id生成回复 → [HTTP Response]关键实现细节TensorRT加速的Intent Classifier我们用DistilBERT微调了一个轻量级分类器导出为TensorRT引擎。相比原始PyTorch模型延迟从120ms降到22ms且显存占用从1.8GB压到320MB。导出命令关键参数trtexec --onnxintent.onnx \ --saveEngineintent.trt \ --fp16 \ # 必须开启政务文本对精度不敏感 --workspace2048 \ # 工作内存MB影响最大batch_size --minShapesinput_ids:1x64,attention_mask:1x64 \ --optShapesinput_ids:8x64,attention_mask:8x64 \ --maxShapesinput_ids:32x64,attention_mask:32x64--optShapes定义的optimal batch size直接决定Triton的并发吞吐。我们实测opt8时QPS 180opt32时QPS 210但P95延迟升至35ms最终选8——这是业务SLA30ms和吞吐的平衡点。Python Backend实现Conditional RouterTriton的Python backend不是玩具它能调用任意Python库。我们的router代码# config.pbtxt 中定义此backend # instance_group [ # [ # { # count: 2, # gpus: [0], # kind: KIND_CPU # } # ] # ] import json import triton_python_backend_utils as pb_utils class TritonPythonModel: def execute(self, requests): responses [] for request in requests: # 从intent classifier获取结果 intent_tensor pb_utils.get_input_tensor_by_name(request, INTENT) intent_id intent_tensor.as_numpy()[0][0] # [1,1] shape if intent_id 2: # 投诉类intent # 调用工单系统此处简化为mock ticket_id fTICKET-{int(time.time())} # 构造下一阶段输入 output_tensor pb_utils.Tensor( TICKET_ID, np.array([ticket_id.encode(utf-8)], dtypeobject) ) responses.append(pb_utils.InferenceResponse(output_tensors[output_tensor])) else: # 直接透传原始query query_tensor pb_utils.get_input_tensor_by_name(request, QUERY) responses.append(pb_utils.InferenceResponse(output_tensors[query_tensor])) return responsesLLM Generator的Token流控制最关键的是Triton的TensorRT backend支持streaming模式。我们在LLM stage的config.pbtxt中启用dynamic_batching [true] sequence_batching [true] streaming [true] # 启用流式输出客户端即可收到逐token响应实现真正的实时打断# 客户端接收流式响应 import tritonhttpclient client tritonhttpclient.InferenceServerClient(urllocalhost:8000) def stream_response(query: str): inputs [tritonhttpclient.InferInput(QUERY, [1], BYTES)] inputs[0].set_data_from_numpy(np.array([query.encode(utf-8)], dtypeobject)) response client.stream_infer( model_namellm_generator, inputsinputs, stream_callbacklambda result: print(result.as_numpy()[TEXT][0].decode(utf-8)) ) # 当收到投诉相关token时客户端可主动cancel stream提示Triton的坑比vLLM多得多。最大的雷是sequence_batching——它要求所有请求的sequence ID必须唯一且单调递增否则会丢请求。我们用Redis原子计数器生成sequence ID实测在1000 QPS下零丢包。另一个坑是Python backend的GIL必须用multiprocessing而非threading否则CPU密集型任务会阻塞整个Triton实例。绕过vLLM不是拒绝轮子而是当轮子无法满足业务脉搏时亲手锻造一把手术刀。from scratch的终极意义是让每个字节的流向、每个GPU cycle的归属都在你的掌控之中。5. 监控与可观测性不用PrometheusGrafana用eBPF追踪AI请求全链路所有AI工程教程都教你配Prometheus指标但没人告诉你当LLM响应延迟突然从800ms飙到3200msPrometheus只能告诉你“慢了”却无法告诉你慢在哪——是tokenizer卡在正则匹配是CUDA kernel在等显存同步还是Python GIL被某个logging装饰器死锁这时候eBPF才是真正的上帝视角。我们给AI服务注入eBPF探针不是为了画酷炫仪表盘而是为了回答三个致命问题请求在哪个函数里停留最久不是平均延迟是单次请求的火焰图GPU显存分配失败时是哪个Python对象在持有显存不是总显存是具体对象引用链LLM生成的token有多少比例被前端丢弃不是API成功率是业务层有效率实现方案分三层第一层Python函数级追踪bcc usdt在关键函数打USDT探针# 在embedding服务中 import bcc from bcc import BPF # 编译eBPF程序C语言 bpf_code #include uapi/linux/ptrace.h BPF_HASH(start, u64, u64); int trace_start(struct pt_regs *ctx) { u64 pid bpf_get_current_pid_tgid(); u64 ts bpf_ktime_get_ns(); start.update(pid, ts); return 0; } int trace_end(struct pt_regs *ctx) { u64 pid bpf_get_current_pid_tgid(); u64 *tsp start.lookup(pid); if (tsp ! 0) { bpf_trace_printk(func_duration:%d\\n, bpf_ktime_get_ns() - *tsp); start.delete(pid); } return 0; } # 在Python中注入探针 bpf BPF(textbpf_code) bpf.attach_uprobe( name/path/to/python, symPyObject_Call, # 追踪所有Python函数调用 fn_nametrace_start ) bpf.attach_uretprobe( name/path/to/python, symPyObject_Call, fn_nametrace_end )第二层CUDA显存追踪nvml eBPF用NVIDIA MLX库获取显存分配栈# 在PyTorch训练循环中 import torch import mlx def track_cuda_alloc(): # 获取当前显存分配栈 stacks mlx.get_memory_stacks(device0) # device 0 is GPU for stack in stacks: print(fAlloc {stack.size} bytes at:) for frame in stack.frames[:3]: # 只取顶层3帧 print(f {frame.function}({frame.filename}:{frame.lineno})) # 在eBPF中捕获mlx事件 bpf.attach_tracepoint( tpmlx:cuda_malloc, fn_nametrace_cuda_malloc )第三层LLM Token流追踪自定义HTTP middleware在FastAPI中注入token级埋点from fastapi import Request, Response from starlette.middleware.base import BaseHTTPMiddleware class TokenTraceMiddleware(BaseHTTPMiddleware): async def dispatch(self, request: Request, call_next): # 记录请求开始时间 start_time time.time() # 包装response以捕获token流 response await call_next(request) if hasattr(response, body_iterator): # 替换body_iterator逐token记录 original_iter response.body_iterator async def traced_iterator(): token_count 0 async for chunk in original_iter: # 解析SSE格式data: {token: hello} if bdata: in chunk: try: data json.loads(chunk.decode().split(data:)[1].strip()) token_count 1 # 发送eBPF事件token_id, timestamp, latency bpf_event {token: data[token], ts: time.time()} bpf.perf_submit(bpf[events], json.dumps(bpf_event).encode()) except: pass yield chunk response.body_iterator traced_iterator() return response最终我们用eBPF事件构建了三维诊断视图时间维度单次请求从HTTP接入到token返回的完整时间轴精确到微秒资源维度该请求期间GPU显存分配/释放事件、Python对象创建/销毁事件业务维度每个token对应的业务意图如投诉token触发工单创建当某次故障发生时我们不再看平均值而是打开eBPF火焰图直接定位到transformers.modeling_utils.py:1243的torch.nn.functional.pad调用因输入长度不均导致CUDA kernel launch延迟激增——这个细节任何APM工具都无法捕捉。注意eBPF不是银弹。它需要Linux 5.4内核且在容器环境中需启用--privileged或特定capabilities。我们生产环境用kubectl patch为AI服务Pod添加securityContext: capabilities: add: [SYS_ADMIN, BPF]from scratch的最高境界不是亲手写所有代码而是亲手定义所有观测维度。当你能用eBPF看到LLM生成的每一个token在硅基芯片上的物理路径时“AI Engineering”才真正从玄学变成了科学。6. 部署与灰度不用Kubernetes Operator用GitOpsCanary Rollout硬核控制Kubernetes Operator是AI平台的标配但它的“自动扩缩容”在AI场景往往是灾难源头。我们曾有个LLM服务Operator根据CPU使用率自动从2个pod扩到8个结果因模型加载时GPU显存碎片化新pod启动失败触发雪崩式重试整个集群OOM。真正的生产级部署不是追求自动化而是追求可预测的确定性。我们用GitOps手工编排的Canary Rollout把每次发布变成一场可控的实验。核心原则所有变更必须可回滚、可度量、可暂停。流程分四步Step 1Git仓库即唯一真相源我们不用Helm Chart而是用Kustomize管理所有YAML结构如下deploy/ ├── base/ # 基础配置不变 │ ├── deployment.yaml │ └── service.yaml ├── overlays/ │ ├── prod/ # 生产环境含secret引用 │ │ ├── kustomization.yaml │ │ └── patches/ │ │ └── resources.yaml # 资源限制cpu8, memory32Gi, nvidia.com/gpu1 │ └── canary/ # 灰度环境独立命名空间 │ ├── kustomization.yaml │ └── patches/ │ ├── resources.yaml # 资源减半cpu4, memory16Gi │ └── env.yaml # 注入CANARYtrue环境变量Step 2Canary Rollout的硬核控制不用Flagger或Argo Rollouts而是用kubectlshell脚本实现原子化操作#!/bin/bash # canary-deploy.sh NEW_IMAGEai-engine:v2.3.1 CANARY_NAMESPACEai-canary PROD_NAMESPACEai-prod # 1. 部署canary版本独立deployment kubectl apply -k deploy/overlays/canary/ --namespace $CANARY_NAMESPACE # 2. 等待canary pod就绪超时300秒 timeout 300 bash -c while ! kubectl get pods -n $CANARY_NAMESPACE | grep Running | grep 1/1 /dev/null; do sleep 5 done # 3. 发送100个测试请求验证canary for i in {1..100}; do curl -s http://canary.ai.svc.cluster.local/v1/chat \ -H Content-Type: application/json \ -d {query:test} \ -w \n /tmp/canary-test.log done # 4. 检查成功率和延迟业务指标非基础设施指标 SUCCESS_RATE$(grep status:success /tmp/canary-test.log | wc -l) LATENCY_P95$(awk /latency/{print $NF} /tmp/canary-test.log | sort -n | sed -n $((100*0.95))p) if [ $SUCCESS_RATE -ge 95 ] [ $LATENCY_P95 -le 1200 ]; then echo Canary passed, rolling to prod... # 5. 原子化切换prod deployment kubectl set image deployment/ai-engine -n $PROD_NAMESPACE \ ai-engine$NEW_IMAGE --record else echo Canary failed, reverting... kubectl rollout undo deployment/ai-engine -n $PROD_NAMESPACE exit 1 fiStep 3流量切分的物理层实现不用Istio的VirtualService而是用CoreDNSHeadless Service实现DNS级灰度# canary-service.yamlHeadless Service apiVersion: v1 kind: Service metadata: name: ai-canary namespace: ai-canary spec: clusterIP: None selector: app: ai-engine version: canary --- # 在CoreDNS ConfigMap中添加 .:53 { forward . 8.8.8.8 hosts { 10.1.2.3 ai-canary.ai-canary.svc.cluster.local # canary pod IP fallthrough } }客户端代码显式选择服务# Python客户端 import socket def get_ai_endpoint(): if os.getenv(ENV) canary: return ai-canary.ai-canary.svc.cluster.local # DNS解析到canary pod else: return ai-engine.ai-prod.svc.cluster.local # 解析到prod podStep 4回滚的秒级能力所有镜像都保留SHA256摘要回滚只需# 查看历史revision kubectl rollout history deployment/ai-engine -n ai-prod # 回滚到上一版秒级 kubectl rollout undo deployment/ai-engine -n ai-prod --to-revision3 # 强制删除canary命名空间清理所有残留 kubectl delete namespace ai-canary --grace-period0 --force提示GitOps的关键不是工具而是流程纪律。我们规定所有YAML变更必须关联Jira ticketcommit message格式为[AI-123] update resources for v2.3.1每次发布前SRE必须在Staging环境用相同脚本执行全流程演练canary测试必须包含3个真实业务场景如“投诉生成工单”、“查询医保余额”、“解读检验报告”而非简单health check。from scratch的部署哲学是把不确定性关进确定性的笼子。当你的发布流程能用kubectl rollout undo在3秒内回到安全状态时“AI Engineering”的“Engineering”才真正经得起生产环境的拷问。7. 我的真实体会from scratch不是起点而是终点写完这六章我得坦白我最初做AI Engineering时也疯狂追逐各种新框架——LangChain、LlamaIndex、vLLM、Modal……直到在一次银行核心系统对接中客户CTO指着监控大屏问我“你们说的‘实时’是指从用户点击到返回结果的端到端P95延迟还是指LLM token生成的内部延迟”那一刻我才明白from scratch不是教你怎么从零造轮子而是教你怎么判断此刻我需要的是一颗螺丝钉还是一台车床现在回头看那些让我彻夜难眠的故障90%源于“抽象泄漏”——框架承诺的便利性最终以不可预测的性能抖动、内存泄漏、或隐式依赖的形式反噬。比如LangChain的load_and_split()看似省事实则把PDF解析、文本清洗、分块策略全耦合在一起当业务要求“跳过财报附注”时你得重读3000行源码比如vLLM的PagedAttention虽快但当你要在生成中途插入数据库查询时它就成了无法逾越的墙。所以from scratch的终极价值不是让你写出比HuggingFace更好的Transformer而是让你建立起一套工程决策的肌肉记忆看到“需要支持1000 QPS”第一反应不是选什么框架而是算显存带宽A100 2TB/s ÷ 1000 2GB/s per request意味着单次请求数据传输不能超2MB看到“要支持多租户”第一反应不是找Multi-Tenant插件而是设计隔离边界CPU core绑核GPU MIG切片还是进程级隔离看到“客户要求99.99%可用性”第一反应不是堆K8s副本而是定义SLO比如“P95延迟1.5s”比“uptime99.99%”更能指导架构设计。最后分享一个血泪技巧每次技术选型我都会问自己三个问题如果明天这个框架作者删库跑路我的服务会在几小时内崩溃LangChain删库重写loaderTriton删库换TensorRT但PostgreSQL删库世界末日这个组件的性能拐点在哪里FAISS在100万条时快但1000万条时需调参lists而pgvector的拐点在PostgreSQL连接池大小当它出问题时我能用strace、nvidia-smi、tcpdump中的哪一个工具直接定位vLLM出问题只能看日志eBPF出问题能看到kernel函数栈AI Engineering from Scratch本质上是一场持续的祛魅运动——剥开所有“智能”的幻觉回归到字节、内存、网络、电力这些最原始的工程要素。当你能用perf record -e cycles,instructions分析出LLM推理中70% cycles耗在memcpy上并亲手用posix_memalign优化内存对齐时你就真正拿到了AI时代的工程护照。这条路没有捷径但每一步踩下去都是实打实的确定性。
返回列表