☰
从零手搓AI工程学习路线:Python、TypeScript、Rust三语言实战
2026/11/4 18:53:16 网站建设 项目流程

1. 为什么我要从零手搓一套AI工程学习路线

第一次听到“ai-engineering-from-scratch”这个说法,是在一个做后端的朋友群里。有人甩了个链接,说现在AI工程师的岗位JD越来越离谱,既要懂模型微调,又要会写推理服务,还得能搭RAG管道,最好再顺手把前端界面也撸了。底下有人回了一句:“那不就是全栈加炼丹?”然后群里就沉默了。

这个沉默很真实。市面上讲AI的教程,要么是纯理论,一上来就是反向传播公式糊脸;要么是调包侠速成,三行代码调用API,跑通了但完全不知道背后发生了什么。真正能把“从零搭建”这件事讲清楚的资料,少之又少。而“ai-engineering-from-scratch”这个标题,恰好戳中了这个痛点——它想做的事情,是把AI工程化落地所需的整条链路,从最底层的环境配置到最上层的应用交付,一层一层剥开给你看。

我自己在这个方向上踩了差不多两年的坑。最开始用Python写脚本调模型,后来发现服务化部署绕不开TypeScript做前端交互,再后来遇到性能瓶颈又开始啃Rust写推理后端。这一路走下来,最大的感受是:AI工程不是单一语言能搞定的事,它更像是一个多语言协作的流水线。Python负责实验和训练,TypeScript负责界面和编排,Rust负责高性能计算和底层优化。三者各司其职,缺一不可。

这篇文章适合谁看?如果你已经会写一点Python,但对怎么把模型变成真正能用的产品毫无头绪;或者你是前端出身,想搞清楚AI应用的后端到底在干什么;又或者你是个纯新手,想找一条不那么容易放弃的学习路径——那这篇内容应该能帮到你。我不会只给你一堆链接让你自己啃,而是会把每个阶段该做什么、为什么这么做、容易在哪里翻车,都掰开揉碎了讲。

2. 整体学习路线的设计与选型逻辑

2.1 为什么是Python、TypeScript、Rust这三件套

先说说语言选型这件事。很多人会问:AI工程为什么不能只用Python?答案很简单——Python能跑模型,但跑不快;能写服务,但扛不住高并发;能做界面,但做出来的东西没人愿意用。

Python在AI领域的地位不用多说,PyTorch、TensorFlow、JAX这些框架全是Python优先。数据清洗、特征工程、模型训练、实验管理,这一整套流程用Python是最顺手的。但Python的GIL锁和解释执行特性决定了它不适合做高吞吐的在线推理服务。你可以用FastAPI搭个Demo,但真到了生产环境,延迟和并发一上来就露馅。

TypeScript的价值在于它把前端的交互能力和后端的编排能力统一了。现在的AI应用早就不只是命令行工具了,用户期望的是网页端对话、实时流式输出、可视化调试。这些用TypeScript配合React或者Vue来做,开发效率和可维护性都远超Python的Gradio或Streamlit。而且TypeScript的类型系统能在编译期就帮你抓出很多低级错误,这在多人协作的AI项目里特别重要。

Rust则是解决性能天花板问题的终极武器。当你需要自己写推理引擎、做模型量化、优化内存布局、处理大规模并发请求的时候,Rust的零成本抽象和内存安全特性就体现出来了。现在很多主流的推理框架,比如Candle、Burn,都是Rust写的。学会Rust,你就能深入到模型执行的底层,做真正意义上的性能优化。

注意:不要试图同时学这三门语言。正确的顺序是Python打底,TypeScript做应用,Rust做优化。每一步都建立在前一步的基础上,跳步只会让你崩溃。

2.2 从“能跑”到“能用”的四个阶段

我把整个学习路线拆成了四个阶段,每个阶段都有明确的产出目标。第一个阶段是环境搭建和基础能力建设,目标是能在本地跑通一个完整的模型推理流程。第二个阶段是服务化封装,把模型变成API,能通过网络调用。第三个阶段是应用层开发,做一个有界面的AI应用,支持流式输出和多轮对话。第四个阶段是性能优化和工程化,包括并发处理、缓存策略、监控告警、持续集成。

这四个阶段不是线性的,而是螺旋上升的。你在做应用层开发的时候,可能会发现服务端的响应格式需要调整;在做性能优化的时候,可能会回头修改Python端的预处理逻辑。这种反复迭代的过程,才是AI工程的真实状态。

每个阶段我都会给出具体的工具链推荐和版本号,因为AI领域的依赖管理是个大坑。Python的包版本冲突、Node.js的node_modules黑洞、Rust的crate兼容性,每一个都能让你浪费一整天。我会把我验证过的组合写出来,你直接抄作业就行。

2.3 学习路径的避坑原则

第一条原则:不要一上来就搞大模型。很多人觉得AI工程就是调GPT-4,结果连Transformer的基本结构都说不清楚。我的建议是从小模型开始,比如TinyLlama或者Qwen的0.5B版本,在本地跑通了,再去碰大模型。小模型跑得快,调试成本低,能让你把注意力放在工程流程上,而不是等API响应。

第二条原则:不要跳过环境配置。我见过太多人因为Python环境搞不定就放弃了。conda、venv、poetry、pipenv,选一个用熟就行。我个人的习惯是用conda做环境隔离,pip做包管理,因为conda在处理CUDA相关的依赖时更省心。TypeScript这边用pnpm,比npm快,比yarn稳。Rust就用官方的rustup,配合国内镜像源,下载速度能快不少。

第三条原则:不要只写代码不写文档。AI项目的迭代速度极快,今天能跑的代码下周可能就因为依赖更新挂了。养成写README和注释的习惯,把每个模块的输入输出、依赖版本、已知问题都记下来。这不是浪费时间,这是在给未来的自己省时间。

3. 核心细节解析与实操要点

3.1 Python环境搭建:从零到跑通第一个推理脚本

Python环境是整条链路的地基。我推荐用Miniconda而不是完整的Anaconda,因为后者预装了大量用不到的包,会让环境变得臃肿。下载Miniconda的安装包,一路下一步就行,注意勾选“Add to PATH”那个选项,虽然安装程序会警告你不推荐,但加了之后在终端里用起来方便很多。

装完之后,创建一个专门用于AI工程的环境:

conda create -n ai-eng python=3.11 conda activate ai-eng

为什么选3.11而不是最新的3.12或3.13?因为很多AI框架的预编译轮子还没跟上最新版本。3.11是目前兼容性最好的选择,PyTorch、TensorFlow、ONNX Runtime都有稳定的wheel包。

接下来装核心依赖。这里有个技巧:先装PyTorch,再装其他。因为PyTorch的CUDA版本需要和你的显卡驱动匹配,如果先装了其他包,可能会把numpy的版本搞乱,导致PyTorch安装失败。

pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121

cu121代表CUDA 12.1,你需要根据自己显卡支持的CUDA版本调整。用nvidia-smi命令可以查看驱动支持的CUDA版本。如果用的是Mac的M系列芯片,把index-url换成https://download.pytorch.org/whl/cpu就行,MPS加速会自动启用。

装完PyTorch之后,验证一下:

import torch print(torch.__version__) print(torch.cuda.is_available()) print(torch.cuda.get_device_name(0) if torch.cuda.is_available() else "CPU mode")

如果cuda.is_available()返回False,先别急着怀疑人生。检查一下显卡驱动版本,然后确认安装的PyTorch是不是CUDA版本。用pip list | grep torch看看版本号后面有没有+cu121这样的后缀。没有的话就是装成CPU版了,卸载重装。

实操心得:国内下载PyTorch的wheel包经常超时,可以加上清华源或者阿里源的镜像。但注意,PyTorch的CUDA版本包不在PyPI上,所以镜像源只对普通包有效。CUDA版本的PyTorch还是得从官方源下载,建议挂个代理或者用离线安装包。

3.2 TypeScript工程化配置:避开那些弃用警告的坑

TypeScript这两年的版本更新很快,很多以前能用的配置项现在都标了弃用。比如baseUrl和moduleResolution: node10,在TypeScript 7.0里就要被移除了。如果你现在写的tsconfig.json里还有这些配置,编辑器会一直给你弹黄色警告,虽然不影响运行,但看着很烦。

正确的做法是迁移到bundler模式。如果你用的是Vite或者Next.js,它们已经帮你处理好了大部分配置。如果是手动配置,参考这个tsconfig.json:

{ "compilerOptions": { "target": "ES2022", "module": "ESNext", "moduleResolution": "bundler", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, "resolveJsonModule": true, "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", "paths": { "@/*": ["./src/*"] } }, "include": ["src/**/*"], "exclude": ["node_modules", "dist"] }

moduleResolution: bundler是专门为现代打包工具设计的,它支持package.json里的exports字段,能正确处理条件导出。paths配置配合baseUrl的替代方案,直接用相对路径的别名,不需要再设置baseUrl。

包管理器我推荐pnpm。安装很简单:

npm install -g pnpm

然后在项目目录里pnpm init,再pnpm add你需要的依赖。pnpm的硬链接机制能节省大量磁盘空间,而且安装速度比npm快很多。对于AI应用来说,你可能需要这些包:ai(Vercel的AI SDK)、openai、@langchain/core、zod(做输出格式校验)、eventsource-parser(处理流式响应)。

注意:TypeScript的版本建议锁定在5.4以上,但不要用beta版。5.4对using声明和NoInfer类型有更好的支持,这些在写AI相关的类型定义时很有用。

3.3 Rust开发环境:从安装到跑通第一个推理Demo

Rust的安装比Python简单,官方脚本一行搞定:

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

但在国内,下载crate的速度可能会让你想砸键盘。配置镜像源是必须的。在~/.cargo/config.toml里加上:

[source.crates-io] replace-with = 'ustc' [source.ustc] registry = "sparse+https://mirrors.ustc.edu.cn/crates.io-index/"

中科大的镜像同步频率高,速度也稳定。配置完之后,cargo build的速度会有质的提升。

Rust在AI工程里的切入点主要是推理侧。我建议从candle这个框架入手,它比tch-rs更轻量,纯Rust实现,不依赖libtorch。创建一个新项目:

cargo new rust-inference-demo cd rust-inference-demo

在Cargo.toml里加上依赖:

[dependencies] candle-core = "0.4" candle-nn = "0.4" candle-transformers = "0.4" tokenizers = "0.15" anyhow = "1.0"

然后写一个最简单的模型加载和推理脚本。这里以加载一个HuggingFace上的小模型为例:

use candle_core::{Device, Tensor}; use candle_transformers::models::bert::{BertModel, Config}; use tokenizers::Tokenizer; fn main() -> anyhow::Result<()> { let device = Device::Cpu; let tokenizer = Tokenizer::from_file("tokenizer.json")?; let config = Config::default(); let model = BertModel::load("model.safetensors", &config, &device)?; let encoding = tokenizer.encode("Hello, AI engineering!", true)?; let input_ids = Tensor::new(encoding.get_ids(), &device)?.unsqueeze(0)?; let output = model.forward(&input_ids)?; println!("Output shape: {:?}", output.shape()); Ok(()) }

这段代码跑通之后,你就有了一个纯Rust的推理基础。接下来可以尝试量化、批处理、多线程并发这些优化手段。

实操心得:Rust的编译错误信息非常详细,但有时候会一次性报几十个错误,看着很吓人。我的习惯是从第一个错误开始改,改完重新编译,不要试图一次性解决所有问题。另外,cargo clippy是个好东西,能帮你发现很多代码风格和潜在性能问题,建议每次提交前都跑一遍。

4. 实操过程与核心环节实现

4.1 用Python搭建一个完整的RAG管道

RAG(检索增强生成)是当前AI应用最主流的架构之一。它的核心思路是:先把文档切片、向量化、存入向量数据库,然后在用户提问时检索最相关的片段,拼接到提示词里送给大模型生成回答。

先装依赖:

pip install langchain langchain-community chromadb sentence-transformers pypdf

文档加载和切片:

from langchain_community.document_loaders import PyPDFLoader from langchain.text_splitter import RecursiveCharacterTextSplitter loader = PyPDFLoader("technical_manual.pdf") pages = loader.load() splitter = RecursiveCharacterTextSplitter( chunk_size=512, chunk_overlap=64, separators=["\n\n", "\n", "。", "!", "?", ";", ",", " ", ""] ) chunks = splitter.split_documents(pages) print(f"Total chunks: {len(chunks)}")

chunk_size设为512是因为大多数embedding模型的最大输入长度是512个token。chunk_overlap设为64是为了保证切片之间的语义连续性,避免一个完整的句子被切成两半。分隔符列表里把中文标点放在前面,是因为中文文档的句子边界通常由这些标点界定。

向量化和存储:

from langchain_community.embeddings import HuggingFaceEmbeddings from langchain_community.vectorstores import Chroma embedding = HuggingFaceEmbeddings( model_name="BAAI/bge-small-zh-v1.5", model_kwargs={"device": "cuda"}, encode_kwargs={"normalize_embeddings": True} ) vectorstore = Chroma.from_documents( documents=chunks, embedding=embedding, persist_directory="./chroma_db" ) vectorstore.persist()

BAAI/bge-small-zh-v1.5是一个中文效果不错的小型embedding模型,只有24M参数,在消费级显卡上跑得飞快。normalize_embeddings=True是为了让余弦相似度计算更准确。

检索和生成:

from langchain_community.llms import Ollama retriever = vectorstore.as_retriever(search_kwargs={"k": 3}) llm = Ollama(model="qwen2.5:7b", base_url="http://localhost:11434") def ask(question): docs = retriever.invoke(question) context = "\n\n".join([d.page_content for d in docs]) prompt = f"""基于以下上下文回答问题。如果上下文中没有相关信息,直接说不知道。 上下文: {context} 问题:{question} 回答:""" return llm.invoke(prompt) print(ask("这个设备的额定功率是多少?"))

这里用Ollama在本地跑Qwen2.5的7B模型,不需要联网,数据不出本地。k=3表示检索最相关的3个片段,这个数字可以根据文档的粒度调整。如果文档切片比较细,可以调到5;如果切片比较大,3就够了。

注意:RAG的效果很大程度上取决于切片质量。如果文档里有大量表格和图片,纯文本切片会丢失很多信息。这种情况需要考虑用多模态embedding或者专门的表格解析工具。

4.2 TypeScript实现流式对话界面

流式输出是AI应用的基本要求。用户不想等模型生成完整回答再看到结果,而是希望像打字机一样一个字一个字地显示。用TypeScript配合React和Vercel AI SDK,实现起来很顺手。

先装依赖:

pnpm add ai @ai-sdk/openai react react-dom pnpm add -D @types/react @types/react-dom typescript vite @vitejs/plugin-react

核心的流式处理逻辑:

import { createOpenAI } from '@ai-sdk/openai'; import { streamText } from 'ai'; const openai = createOpenAI({ baseURL: 'http://localhost:11434/v1', apiKey: 'ollama', }); export async function POST(req: Request) { const { messages } = await req.json(); const result = streamText({ model: openai('qwen2.5:7b'), messages, temperature: 0.7, }); return result.toDataStreamResponse(); }

前端组件:

import { useChat } from 'ai/react'; export default function Chat() { const { messages, input, handleInputChange, handleSubmit, isLoading } = useChat(); return ( <div className="chat-container"> {messages.map(m => ( <div key={m.id} className={`message ${m.role}`}> {m.content} </div> ))} <form onSubmit={handleSubmit}> <input value={input} onChange={handleInputChange} placeholder="输入你的问题..." disabled={isLoading} /> <button type="submit" disabled={isLoading}>发送</button> </form> </div> ); }

useChat这个hook帮你处理了消息历史管理、流式接收、加载状态这些琐事。toDataStreamResponse会把模型输出转换成SSE格式,前端自动解析并逐字渲染。

实操心得:流式输出在开发环境下可能会被Vite的代理缓冲,导致看起来不是逐字显示。解决办法是在Vite的配置里关掉代理的缓冲,或者直接用Next.js的API路由,它原生支持流式响应。

4.3 Rust做推理性能优化:量化和批处理

当你把模型部署到生产环境,第一个遇到的问题就是内存占用和推理速度。Rust在这方面有天然优势,因为你可以精确控制内存分配和线程调度。

量化是最直接的优化手段。以Candle为例,把FP16的模型转成INT8:

use candle_core::quantized::ggml_file; use candle_core::Device; let device = Device::Cpu; let model = ggml_file::qtensor_from_ggml( ggml_file::Content::read_file("model-q8_0.gguf")?, &device, )?;

INT8量化能把模型体积缩小一半,推理速度提升30%到50%,精度损失通常在1%以内。对于大多数应用场景来说,这个 trade-off 是值得的。

批处理是另一个优化点。当多个请求同时到达时,逐个处理会浪费大量计算资源。把请求攒成一批,一次性送给模型,能显著提高吞吐量:

use rayon::prelude::*; let inputs: Vec<Vec<u32>> = requests.iter().map(|r| r.token_ids.clone()).collect(); let batched = Tensor::new(inputs.as_slice(), &device)?; let outputs = inputs.par_iter() .map(|ids| { let input = Tensor::new(ids.as_slice(), &device)?.unsqueeze(0)?; model.forward(&input) }) .collect::<Result<Vec<_>, _>>()?;

rayon是Rust的并行计算库,par_iter()会自动把任务分配到多个线程上。配合批处理,单机的推理吞吐量能提升3到5倍。

注意:批处理的大小不是越大越好。太大的batch会导致显存溢出,反而降低吞吐量。需要根据你的硬件配置做压测,找到最优的batch size。一般来说,消费级显卡(8G显存)的batch size在4到8之间比较合适。

5. 常见问题与排查技巧实录

5.1 环境配置类问题速查

问题现象可能原因排查步骤解决方案
torch.cuda.is_available()返回FalsePyTorch装成了CPU版pip list | grep torch看版本号后缀卸载后从官方CUDA源重装
pnpm install卡住不动默认源在国外pnpm config get registry切换到国内镜像源
cargo build下载crate超时未配置镜像检查~/.cargo/config.toml添加中科大或清华源
Python包版本冲突依赖树太复杂pip check查看冲突用conda新建干净环境重装
TypeScript报baseUrl弃用TS版本≥5.0查看tsconfig.json改用paths+moduleResolution: bundler

5.2 模型推理类问题排查

问题一:推理结果乱码或者重复。

这种情况通常是tokenizer和模型不匹配导致的。比如用Llama的tokenizer去解码Qwen的输出,就会出现乱码。检查方法是打印tokenizer的vocab_size和模型的embedding层维度,两者必须一致。另外,生成参数里的repetition_penalty设得太低也会导致重复,一般设在1.1到1.2之间比较合适。

问题二:显存溢出(OOM)。

先看模型加载后的显存占用,用nvidia-smi或者torch.cuda.memory_allocated()。如果加载完就快满了,说明模型太大,需要量化或者换小模型。如果加载后还有余量,但推理时OOM,那就是batch size或者序列长度的问题。把batch size降到1,序列长度截断到512,先跑通再逐步往上加。

问题三:流式输出断断续续。

检查网络延迟和缓冲区设置。如果是本地部署,可能是推理速度本身就跟不上流式输出的节奏。可以在服务端加一个小的缓冲队列,攒够几个token再发。如果是通过API调用远程模型,检查SSE连接的keep-alive设置,有些代理会在60秒后自动断开。

5.3 我踩过的三个大坑

第一个坑:在Python里用多线程跑推理。Python的GIL会让多线程变成伪并行,不仅没有加速,反而因为线程切换增加了开销。正确的做法是用多进程,或者把推理逻辑放到Rust里用rayon做并行。

第二个坑:TypeScript的any类型滥用。AI应用的响应格式经常变,很多人图省事就用any接住所有数据。结果就是运行时各种undefined错误。我的建议是用zod定义schema,在API边界做运行时校验,这样类型安全和运行时安全都有了。

第三个坑:Rust的unwrap()满天飞。在原型阶段用unwrap()快速验证没问题,但到了生产环境,任何一个unwrap()都可能让整个服务崩溃。用anyhow和thiserror做错误处理,把错误往上抛,在顶层统一处理。虽然写起来麻烦一点,但稳定性提升是值得的。

实操心得:每次遇到问题,先问自己三个问题——最近改了什么?错误信息的第一行是什么?最小复现路径是什么?把这三个问题回答清楚,90%的问题都能自己解决。剩下的10%,再去搜或者问人。

6. 从学习到落地的几个关键决策

学完这套路线之后,你手里会有三个能跑的东西:一个Python的RAG管道、一个TypeScript的流式对话界面、一个Rust的量化推理服务。但怎么把它们串起来,变成一个真正的产品,这才是AI工程最考验人的地方。

我的建议是先用Python做原型验证。把整个流程跑通,确认业务逻辑没问题。然后逐步把性能敏感的部分迁移到Rust,把交互部分用TypeScript重写。不要一开始就追求完美的架构,先让东西跑起来,再慢慢优化。

另一个决策点是部署方式。如果只是内部使用,用Docker Compose把三个服务串起来就够了。如果要对外提供服务,需要考虑负载均衡、限流、监控这些。但那是另一个话题了,先把单机跑通再说。

最后说一个我自己的体会:AI工程这个领域,变化太快了。今天的最佳实践,可能下个月就被新的工具替代。所以不要死守某一种技术栈,保持学习的心态,但也要有自己的一套核心方法论。我的方法论就是:理解原理、动手验证、记录踩坑、持续迭代。这十六个字,比任何具体的工具都重要。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询