☰
从零构建AI工程能力:Python、TypeScript、Rust三语言实战指南
2026/10/3 19:11:45 网站建设 项目流程

1. 从零搭建AI工程能力:为什么我选择用三种语言同时开工

很多人第一次看到"ai-engineering-from-scratch"这个项目名,第一反应是"又一个教你调API的教程仓库"。我最初也是这么想的,直到真正把仓库拉下来跑了一遍,才发现它想做的事情完全不是"教你用现成框架搭个Demo",而是从最底层的工程视角,把AI系统拆成可以独立理解、独立实现、独立验证的模块。这个区别很关键:前者让你会用一个工具,后者让你在工具失效、文档缺失、线上出问题时还能自己造轮子。

这个项目最吸引我的地方,是它同时用Python、TypeScript、Rust三种语言去实现同一套AI工程概念。这不是为了炫技,而是因为三种语言在AI工程链路里承担的角色完全不同。Python是算法验证和数据处理的主战场,生态最全,numpy、pytorch、transformers这些库几乎垄断了研究和原型阶段;TypeScript负责的是前端交互层和Node服务层,尤其是现在大量AI应用需要做流式输出、WebSocket通信、浏览器端推理,TS的类型系统能帮你在编译期就拦住一大批低级错误;Rust则是性能和可靠性的兜底,推理服务、tokenizer、向量检索这些对延迟和内存敏感的部分,用Rust重写往往能带来数量级的提升。

我自己的判断是,一个真正合格的AI工程师,不应该只会写model.fit()和openai.chat.completions.create()。你需要知道一次推理请求从HTTP层进来之后,经过tokenizer、embedding、attention计算、采样、detokenize,每一步的耗时和内存开销大概是多少;你需要知道为什么流式输出要用SSE而不是普通轮询;你需要知道向量数据库在做ANN检索时,HNSW和IVF两种索引结构在召回率和延迟上的取舍。这些东西,光看框架文档是学不到的,必须自己从零实现一遍。

这个项目适合谁?我认为有三类人收益最大。第一类是有一定编程基础但没系统做过AI工程的后端或前端开发者,想补齐AI这一环;第二类是在做AI应用但总感觉"知其然不知其所以然"的工程师,想搞清楚底层到底发生了什么;第三类是想在面试或技术分享中讲清楚AI系统设计的人,需要一套能自己跑起来、能改、能验证的代码作为支撑。如果你只是想快速调个API做个聊天机器人,这个项目可能有点"重";但如果你想真正理解AI工程,它值得你花时间。

接下来我会按照我自己实际跑这个项目的顺序,把环境搭建、三种语言的模块设计、核心实现细节、踩过的坑,以及一些文档里不会写的经验,完整地讲一遍。

2. 环境准备:三种语言工具链的安装与版本对齐

2.1 Python环境:别用系统自带的,用pyenv或conda隔离

Python这块我踩过的坑最多。很多人图省事直接用系统自带的Python,或者从python官网下载一个安装包一路下一步,结果后面装依赖时各种版本冲突。我的建议是,永远不要在系统Python上装项目依赖,用pyenv或者conda做环境隔离。

如果你用pyenv,安装流程大概是这样的:

# macOS/Linux curl https://pyenv.run | bash # 配置shell环境变量后 pyenv install 3.11.7 pyenv virtualenv 3.11.7 ai-eng pyenv activate ai-eng

为什么选3.11而不是最新的3.12或3.13?因为AI生态里很多库对Python版本的跟进有延迟,3.11是目前兼容性最好的版本,numpy、torch、transformers这些主流库都有成熟的wheel包,不需要自己编译。我试过在3.12上装某些库,结果因为C扩展没跟上,pip直接开始从源码编译,等了二十分钟还报错。

Windows用户我建议直接用conda,因为conda能同时管理Python版本和二进制依赖,省去很多编译麻烦:

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

装完Python之后,先升级pip,再装基础依赖:

python -m pip install --upgrade pip pip install numpy scipy matplotlib jupyter

这里有个细节:numpy的版本要和你的Python版本、操作系统架构匹配。如果你在M1/M2的Mac上,确保装的是arm64版本的numpy,否则性能会差很多。可以用python -c "import numpy; print(numpy.__version__)"验证,再用numpy.show_config()看底层BLAS库是不是Accelerate或OpenBLAS。

2.2 TypeScript环境:Node版本管理和tsconfig的关键配置

TypeScript这块,第一件事是装Node。我推荐用nvm管理Node版本,因为不同项目对Node版本要求不一样,全局装一个很容易出问题:

curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash nvm install 20 nvm use 20

Node 20是目前LTS版本,对ES模块和顶层await支持都很好。装完Node之后,全局装TypeScript和ts-node:

npm install -g typescript ts-node

然后是tsconfig.json的配置,这里有几个关键点。项目里如果用到了现代模块系统,module和moduleResolution要配对设置。我见过太多人在这两个选项上踩坑,报出"选项'moduleResolution=node10'已弃用"这类警告。正确的做法是:

{ "compilerOptions": { "target": "ES2022", "module": "NodeNext", "moduleResolution": "NodeNext", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, "outDir": "./dist", "rootDir": "./src" }, "include": ["src/**/*"], "exclude": ["node_modules", "dist"] }

strict: true这个选项我强烈建议打开,虽然初期会报一堆类型错误,但它能帮你养成写类型的好习惯。AI工程里数据流很复杂,一个any传下去,后面出问题你根本不知道在哪。skipLibCheck: true是为了跳过node_modules里第三方库的类型检查,加快编译速度,这个在实际项目里几乎是必开的。

2.3 Rust环境:镜像源配置和工具链选择

Rust的安装相对简单,用rustup一条命令搞定:

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

但国内网络环境下,cargo拉依赖会很慢,需要配置镜像源。在~/.cargo/config.toml里加上:

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

配置完之后,cargo build的速度会有明显提升。工具链方面,默认装的是stable,但如果你要用一些nightly特性(比如某些SIMD指令),可以用rustup toolchain install nightly装一个nightly,然后用rustup override set nightly在特定项目里切换。

Rust的IDE支持,我用的是VS Code加rust-analyzer插件。装完之后记得在设置里把rust-analyzer.checkOnSave打开,这样每次保存都会跑一遍类型检查,比等到cargo build才发现错误效率高很多。如果你用IntelliJ IDEA,也有Rust插件,但rust-analyzer的体验目前还是最好的。

三种语言的环境都装好之后,我建议先各自跑一个hello world验证一下,别急着上项目。Python跑python -c "print('ok')",TypeScript跑ts-node -e "console.log('ok')",Rust跑cargo new hello && cd hello && cargo run。三个都通了,再开始看项目代码。

3. 项目结构拆解:三种语言各自负责什么

3.1 目录组织逻辑与模块边界

这个项目的目录结构不是按语言简单分三个文件夹就完事,而是按功能模块划分,每个模块下再分语言实现。我理解这种设计的意图是:让你在学一个概念时,能同时看到三种语言的实现,对比它们各自的表达方式和性能特征。

大致的结构是这样的:

ai-engineering-from-scratch/ ├── 01-tokenization/ │ ├── python/ │ ├── typescript/ │ └── rust/ ├── 02-embeddings/ │ ├── python/ │ ├── typescript/ │ └── rust/ ├── 03-attention/ │ ├── python/ │ ├── typescript/ │ └── rust/ ├── 04-inference-server/ │ ├── python/ │ ├── typescript/ │ └── rust/ └── ...

每个模块都是一个独立的、可运行的最小示例。这种"一个概念三种实现"的组织方式,好处是你能直观感受到:同样一个tokenizer,Python写出来可能就几十行,Rust写出来要考虑所有权和生命周期,TypeScript写出来要处理类型定义。这种对比本身就是一种学习。

3.2 Python模块:算法验证和数据处理的主力

Python模块在这个项目里承担的是"参考实现"的角色。它的代码通常最简洁、最接近数学公式,因为Python的语法表达力强,numpy又能把矩阵运算写得像数学一样直观。

比如在attention模块里,Python版本的实现可能就是这样几行:

import numpy as np def scaled_dot_product_attention(Q, K, V, mask=None): d_k = Q.shape[-1] scores = Q @ K.transpose(-2, -1) / np.sqrt(d_k) if mask is not None: scores = np.where(mask, scores, -1e9) weights = softmax(scores, axis=-1) return weights @ V

这段代码几乎就是论文里公式的直接翻译。它的价值在于可读性和可验证性:你可以拿它当基准,去验证TypeScript和Rust的实现结果是否一致。我在实际做的时候,会先用Python生成一组随机输入和对应的输出,存成JSON,然后让另外两种语言的实现去读同样的输入,对比输出差异。如果差异在1e-5以内,就认为实现正确。

Python模块的另一个作用是快速实验。你想改个参数、换个激活函数、试试不同的初始化方式,在Python里改一行就能跑,不用等编译。这种迭代速度在研究阶段是无价的。

3.3 TypeScript模块:前端交互和服务编排

TypeScript模块在这个项目里的定位比较特殊。它不只是"把Python代码翻译成TS",而是承担了服务编排和前端交互的职责。很多AI应用的实际形态是:前端页面收集用户输入,通过HTTP或WebSocket发给后端,后端调用模型推理,再把结果流式返回给前端。这个链路里,TypeScript出现在两端。

在项目里,TypeScript模块通常会实现一个轻量的HTTP服务,用Node的http模块或者Express/Fastify,把推理逻辑包成API。同时它会实现一个简单的客户端,演示怎么处理流式响应。比如处理SSE(Server-Sent Events)的代码:

const response = await fetch('/api/chat', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ prompt }) }); const reader = response.body?.getReader(); const decoder = new TextDecoder(); while (true) { const { done, value } = await reader!.read(); if (done) break; const chunk = decoder.decode(value, { stream: true }); // 解析SSE格式的chunk for (const line of chunk.split('\n')) { if (line.startsWith('data: ')) { const data = JSON.parse(line.slice(6)); process.stdout.write(data.token); } } }

这段代码看起来简单,但里面有几个坑:TextDecoder的stream: true选项必须加,否则多字节字符(比如中文)在chunk边界处会被截断成乱码;SSE的chunk不一定按行对齐,需要自己维护一个buffer。这些细节在文档里往往一笔带过,但实际写的时候不注意就会出问题。

3.4 Rust模块:性能敏感路径的重写

Rust模块是这个项目里最有"工程味"的部分。它通常用来实现那些对性能要求高的组件:tokenizer、矩阵运算、向量检索、推理调度。Rust的优势在于零成本抽象和内存安全,你可以写出接近C的性能,同时不用担心内存泄漏和空指针。

比如tokenizer的BPE实现,Python版本可能用字典和循环,跑一万条文本要几秒;Rust版本用HashMap和迭代器,同样的数据可能只要几十毫秒。这个差距在离线处理时无所谓,但在线服务里就是能不能扛住并发的问题。

Rust模块的代码通常会长一些,因为要处理所有权、生命周期、错误类型。比如一个简单的矩阵乘法:

pub fn matmul(a: &[f32], b: &[f32], m: usize, k: usize, n: usize) -> Vec<f32> { let mut c = vec![0.0f32; m * n]; for i in 0..m { for j in 0..n { let mut sum = 0.0f32; for p in 0..k { sum += a[i * k + p] * b[p * n + j]; } c[i * n + j] = sum; } } c }

这段代码没有用任何外部库,就是最朴素的三重循环。它的意义在于让你理解矩阵乘法的内存访问模式:为什么按行优先存储时,内层循环要遍历k而不是n?因为这样能更好地利用CPU缓存。这种底层认知,是调库调不出来的。

4. 核心实现细节:从tokenizer到推理服务的完整链路

4.1 Tokenizer:BPE算法的三种实现对比

Tokenizer是AI工程链路的第一环,也是很多人容易忽略的一环。你可能觉得tokenizer就是个分词工具,调一下就行,但实际上tokenizer的实现质量直接影响模型的输入质量和推理速度。

BPE(Byte Pair Encoding)是目前主流的分词算法,GPT系列、LLaMA系列都用它。核心思想是:从字符级别开始,不断合并出现频率最高的相邻字符对,直到词表达到目标大小。训练过程大概是:

  1. 把所有文本拆成字符序列,统计每个字符的频率
  2. 统计所有相邻字符对的频率
  3. 合并频率最高的字符对,形成新的token
  4. 重复2-3,直到达到目标词表大小或没有可合并的对

Python实现这个算法,用collections.Counter和循环就能写出来,代码大概一百行。但它的性能瓶颈很明显:每次合并都要重新统计所有对,时间复杂度是O(n²)级别。处理大规模语料时,Python版本可能要跑几个小时。

TypeScript实现BPE,性能比Python好一些,因为V8的JIT优化不错,但依然受限于单线程。而且TS里没有原生的优先队列,要实现高效的频率统计得自己写堆或者用排序模拟。

Rust实现BPE,可以用HashMap做频率统计,用BinaryHeap做优先队列,再配合多线程并行处理不同文本块,性能能比Python快几十倍。但Rust的代码量也最大,光是处理UTF-8字符边界就要写不少代码。

我在实际对比三种实现时发现一个有意思的现象:小规模数据下,三种语言的差异不明显;但数据量上去之后,Rust的优势是指数级放大的。这提醒我们,技术选型不能只看"能不能跑通",要看"在目标数据规模下能不能跑得动"。

4.2 Embedding与向量检索:从余弦相似度到HNSW

Embedding是把文本、图像等非结构化数据转成稠密向量的过程。在AI工程里,embedding的质量决定了检索、推荐、聚类等下游任务的效果。这个项目里,embedding模块会实现几个核心功能:向量归一化、余弦相似度计算、以及一个简单的向量索引。

余弦相似度的公式是:

similarity(A, B) = (A · B) / (||A|| * ||B||)

如果向量已经归一化(模长为1),那分母就是1,相似度就退化成点积。所以实际工程里,通常先把所有向量归一化,然后用点积算相似度,这样能省掉一次开方和除法。

Python实现用numpy一行就能算:

def cosine_similarity(a, b): return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))

但如果要做大规模检索,比如一百万条向量里找最相似的十条,暴力计算就是一百万次点积,延迟可能到几百毫秒。这时候就需要ANN(Approximate Nearest Neighbor)索引。项目里会实现一个简化版的HNSW(Hierarchical Navigable Small World)或者IVF(Inverted File Index)。

HNSW的核心思想是构建一个多层图结构,上层稀疏、下层稠密,检索时从上层开始快速定位到大致区域,再逐层下沉精确查找。它的优点是召回率高、查询快,缺点是构建索引慢、内存占用大。IVF则是先把向量聚类成若干簇,检索时只查最近的几个簇,优点是构建快、内存省,缺点是召回率受聚类质量影响。

我在实际项目里选型时的经验是:数据量在十万级以下,暴力检索就够了,别过早优化;十万到千万级,用HNSW或IVF;千万级以上,考虑量化压缩或者分布式方案。这个项目里两种索引都会实现,你可以自己跑benchmark对比。

4.3 Attention机制:从数学公式到高效实现

Attention是Transformer的核心,也是AI工程里计算量最大的部分。标准的scaled dot-product attention公式是:

Attention(Q, K, V) = softmax(QK^T / sqrt(d_k)) V

Python实现直接翻译公式就行,但要注意数值稳定性。softmax在输入值很大时容易溢出,标准做法是先减去最大值:

def softmax(x, axis=-1): x_max = np.max(x, axis=axis, keepdims=True) exp_x = np.exp(x - x_max) return exp_x / np.sum(exp_x, axis=axis, keepdims=True)

这个x - x_max的操作看起来多余,但它是防止np.exp(1000)变成inf的关键。我在早期实现时忽略过这一步,结果在长序列上直接出NaN,排查了半天才发现是数值溢出。

TypeScript实现attention,如果没有WebGL或WASM加速,纯JS跑矩阵乘法会很慢。项目里可能会用typed array(Float32Array)来优化内存布局,再配合循环展开提升性能。但说实话,TS做attention计算不是它的强项,它的价值更多在于演示和教学。

Rust实现attention,可以用ndarray库或者手写SIMD指令。手写SIMD的版本性能最好,但代码可读性差,而且不同CPU架构的指令集不一样,要做运行时检测。项目里可能会提供一个基础版本和一个优化版本,让你对比性能差异。

这里有个经验:attention的计算瓶颈通常在内存带宽而不是计算单元。因为QK^T会产生一个n×n的矩阵,n是序列长度,这个矩阵的读写量是O(n²)。当n很大时,GPU的显存带宽会成为瓶颈。这也是为什么FlashAttention这类算法能提速:它通过分块计算和重计算,减少了显存读写。

4.4 推理服务:HTTP层、批处理和流式输出

推理服务是把模型能力暴露给外部调用的最后一环。这个项目里,推理服务模块会实现一个完整的HTTP服务,包括请求解析、批处理调度、推理执行、流式返回。

HTTP层用Python的话,FastAPI是首选,因为它原生支持async和SSE。用TypeScript的话,Fastify或Express都行,但要注意Node的单线程模型,CPU密集的推理会阻塞事件循环,需要用worker_threads或者子进程。用Rust的话,axum是目前比较流行的选择,配合tokio做异步运行时。

批处理是提升吞吐量的关键。单个请求推理时,GPU利用率可能只有10%,因为大部分时间在等数据传输。把多个请求攒成一批一起推理,GPU利用率能到80%以上。但批处理会引入延迟:第一个请求要等后面的请求凑够一批才能执行。所以实际系统里要在吞吐量和延迟之间做权衡,常见的策略是设置一个最大等待时间(比如10ms),超时就用当前攒到的请求执行。

流式输出是现在AI应用的标配。用户不想等模型生成完整回复才看到结果,而是希望token一个一个蹦出来。实现流式输出,后端要用SSE或WebSocket,前端要处理chunk边界。这里有个坑:SSE的每个chunk不一定是一个完整的token,可能一个token被拆到两个chunk里,也可能一个chunk里有多个token。所以前端要维护一个buffer,按SSE的\n\n分隔符切分事件,再解析data:字段。

5. 踩坑实录:那些文档里不会写的经验

5.1 Python依赖冲突:numpy版本和torch的兼容问题

我遇到最头疼的问题,是numpy和torch的版本冲突。torch在安装时会锁定一个numpy版本范围,如果你先装了最新版numpy,再装torch,pip可能会把numpy降级,然后你之前写的代码因为numpy API变化跑不了。

解决办法是先装torch,再装其他依赖,让pip自己解决版本约束。或者用pip install torch --no-deps跳过依赖检查,然后手动装兼容的numpy版本。但后者风险大,除非你很清楚自己在做什么。

另一个坑是M1/M2 Mac上的架构问题。有些库只有x86版本,在arm64上装会走Rosetta转译,性能差很多。装之前用pip debug --verbose看支持的平台标签,确保装的是arm64版本。

5.2 TypeScript类型体操:泛型约束和条件类型

TypeScript的类型系统很强大,但也很容易写过头。我在实现一个向量检索的泛型接口时,写了这样的类型:

type VectorLike<T> = T extends number[] ? T : never; type DistanceFn<T> = (a: VectorLike<T>, b: VectorLike<T>) => number;

结果编译器报了一堆错,因为T在VectorLike里没有被正确约束。正确的写法应该是:

type VectorLike = number[] | Float32Array; type DistanceFn<T extends VectorLike> = (a: T, b: T) => number;

这个坑的教训是:类型体操要适度,能用简单类型解决的就别上条件类型。AI工程里数据流复杂,类型定义太花哨反而会增加维护成本。

5.3 Rust所有权:在推理循环里避免不必要的clone

Rust的所有权系统是它的优势,也是初学者的噩梦。我在写一个批处理推理循环时,一开始每个请求都clone一份输入数据,结果性能比Python还差。后来发现,大部分clone是不必要的,用引用和切片就能解决。

比如这个函数签名:

fn process_batch(inputs: Vec<Vec<f32>>) -> Vec<Vec<f32>> { inputs.iter().map(|x| x.clone()).collect() }

这里的x.clone()完全可以避免,改成返回引用或者用into_iter消费所有权:

fn process_batch(inputs: Vec<Vec<f32>>) -> Vec<Vec<f32>> { inputs.into_iter().map(|x| x).collect() }

into_iter会把Vec的所有权转移进来,不需要clone。这个改动在数据量大时能省掉大量内存分配。

5.4 跨语言数据一致性:浮点数精度和序列化格式

三种语言实现同一套算法,最大的挑战是保证结果一致。浮点数在不同语言、不同硬件上的精度可能不一样,比如Python的float是双精度,Rust的f32是单精度,TypeScript的number是双精度。如果混用,结果会有微小差异。

我的做法是统一用f32做计算,用JSON做序列化,对比时允许1e-5的误差。JSON的浮点数表示虽然不完美,但跨语言兼容性好。如果对精度要求极高,可以用二进制格式比如MessagePack或Protobuf,但调试起来麻烦。

还有一个坑是字节序。不同CPU架构的字节序可能不同(虽然现在主流都是小端),如果直接传二进制数据,要注意统一。JSON和文本格式没这个问题,但性能差一些。

6. 从项目到生产:还需要补哪些工程能力

6.1 可观测性:日志、指标和追踪

这个项目作为学习材料,代码里基本没有可观测性。但真要把AI系统上生产,日志、指标、追踪三件套一个都不能少。

日志要结构化,用JSON格式,包含请求ID、用户ID、模型版本、输入长度、输出长度、耗时等字段。这样出问题时能快速定位是哪个请求、哪个环节出的问题。

指标要覆盖QPS、延迟分布(P50/P95/P99)、错误率、GPU利用率、显存占用、队列长度。这些指标用Prometheus采集,Grafana展示。延迟分布比平均延迟重要得多,因为用户感知的是P99,不是平均值。

追踪要用OpenTelemetry,把一次请求经过的每个组件(网关、tokenizer、推理引擎、后处理)都打上span,这样能看到时间花在哪。我见过很多系统优化了半天,结果发现瓶颈在日志写入或者序列化上。

6.2 模型版本管理和灰度发布

AI系统和传统软件最大的区别是模型是动态的。今天用v1模型,明天可能换v2,后天可能做A/B测试同时跑两个版本。所以模型版本管理是必须的。

基本要求是:每个模型有唯一版本号,推理请求要记录用了哪个版本,支持按流量比例灰度发布,出问题能快速回滚。这些用配置中心或者特征开关就能实现,但要在架构设计时就考虑进去,别等上线了再补。

6.3 成本控制:token计费和资源调度

AI推理是烧钱的,尤其是大模型。token计费是基本操作:记录每个请求的输入token数和输出token数,按定价算成本。但更重要的是资源调度:能不能把多个小请求合并成一批?能不能在低峰期用更便宜的实例?能不能对长请求做超时和截断?

我自己的经验是,成本优化里收益最大的是批处理和缓存。批处理能把GPU利用率从20%提到70%,直接省掉三分之二的机器。缓存则是对重复请求直接返回结果,比如系统提示词、常见问题,命中率能到30%以上。

6.4 安全与合规:输入过滤和输出审核

AI系统面临的安全风险包括提示词注入、越狱、敏感信息泄露等。工程上要做的是输入过滤和输出审核。输入侧检测异常模式,比如超长输入、特殊字符、已知的攻击模板;输出侧做敏感词过滤和内容分类,拦截不合规内容。

这些机制要在推理链路的哪个环节做,需要权衡。放在最前面能省算力,但可能误杀;放在最后面最准确,但已经消耗了算力。实际系统里通常是多层过滤,前面粗筛,后面精筛。

7. 我个人的学习路径建议

如果你打算认真跟完这个项目,我的建议是不要按目录顺序从头到尾看,而是按"数据流"的顺序学。先搞懂一条文本从输入到输出经过哪些环节,每个环节的核心算法是什么,然后再逐个模块深入。

具体来说,我推荐的顺序是:tokenizer → embedding → attention → 推理服务 → 向量检索。这个顺序符合数据流动的方向,每一步都是下一步的输入,学起来有连贯性。

每种语言的学习重点也不一样。Python重点看算法实现和数值计算,理解公式怎么变成代码;TypeScript重点看服务编排和异步处理,理解怎么把AI能力包装成API;Rust重点看性能优化和内存管理,理解怎么把慢代码变快。

最后说一个我自己的体会:这个项目最大的价值不是代码本身,而是它逼着你去思考"为什么"。为什么tokenizer要用BPE而不是按词分?为什么attention要除以sqrt(d_k)?为什么推理服务要做批处理?这些问题,你在调库的时候永远不会想,但自己实现一遍就全明白了。这种理解,才是AI工程师和"调包侠"之间的真正差距。

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

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

立即咨询