简介:面向希望系统掌握AI大模型学习方法并尝试独立搭建模型的开发者和学习者,这份docx学习笔记围绕基础理论、经典论文与实战落地三条主线展开。资源为单个docx文档,压缩包仅11KB,内容高度凝练,已有878人学习。笔记系统梳理了神经网络、反向传播、损失函数与优化器等核心基础,同时深入到Transformer、BERT、GPT系列、T5等关键论文的设计原理与训练策略;结合“训练自己的AI模型”实操项目,清晰呈现数据预处理、模型设计、超参数设置、训练过程监控、过拟合与欠拟合应对、评估指标及部署优化等完整流程,并附有知乎与CSDN相关讨论和实操链接。对于刚接触大模型、需要快速建立知识框架并落到实践的学习者而言,这份高密度笔记提供了从理论到工程的有效指引。
1. 从“会用”到“能自己搭”:这份资源到底让你学到哪一步
市面上的 AI 大模型教程,绝大多数只教你怎么打开网页、怎么调 prompt,真正敢碰“自己搭建”的少之又少。这份资源不一样,它把学习路径拆成了两条线:一条是搞懂大模型的基础理论,另一条是实打实把模型跑起来、封装成自己的服务。我从里面最直接的收获是——你不需要从零训练一个模型,但你需要知道权重文件怎么加载、量化参数怎么调、推理服务怎么起,以及前端怎么把流式输出渲染成逐字蹦出来的对话框。适合两类人:一种是刚接触 AI 大模型、想系统建立认知的开发者;另一种是已经调过 API、但想知道底层到底发生了什么的后端或全栈工程师。如果你是想研究 LLM 底层的算法原理,这份资源可能不够深,但如果你是想“在自己的机器上跑起来一个能对话的大模型”,它的路径相当完整。
2. 大模型基础理论:从 Token 到 Transformer,先搞清楚模型在算什么
2.1 先理解 Token 和上下文窗口,再看模型参数才有意义
很多人一上来就看模型参数量,70B、13B、7B,以为参数越多越强,但实际上更影响你部署决策的,是 Token 和上下文窗口这两个概念。Token 是模型处理文本的最小单位,既不是一个字也不是一个词,而是一个被切分出来的片段。英文里一个单词常常就是一个 Token,中文里一个汉字通常会被切成一到两个 Token。模型的所有计算都是基于 Token 序列进行的,输入一段话、输出一段话,本质上都是在对 Token 做概率预测。
上下文窗口决定了模型一次能“看到”多少 Token。窗口越大,模型能记住的对话历史越长,但显存占用也随之上涨。你部署本地模型时,如果显存只有 8GB,强行把上下文窗口开到 8192,很可能直接爆显存。我一般建议先按 2048 跑通流程,确认推理速度正常,再逐步往上调。资源的理论部分把 Token 和窗口的关系讲得比较清楚,理解这一点后,你在配置推理参数时就不会盲目改数值了。
2.2 Transformer 的核心机制:注意力机制不是黑匣子,但决定显存
Transformer 架构里,最影响你部署体验的是注意力机制的计算方式。标准的多头自注意力(Multi-Head Self-Attention)在计算时,需要对输入序列中的每个 Token 与其他所有 Token 做相关性计算,所以显存和计算量随序列长度呈平方级增长。这也是为什么长上下文窗口在本地部署时那么吃显存。当前主流模型大多使用优化后的注意力变体,比如 GQA(分组查询注意力)或 Flash Attention,就是为了缓解这个问题。
你不需要去手推完整的前向传播公式,但至少要能看懂这张图:输入经过嵌入层映射成向量 → 经过多层 Transformer Block(里面是注意力层+前馈网络)→ 最后经过 LM Head 映射成词表概率。资源的理论部分对这个流程做了流程化讲解,配合简单的图示,新手也能在半小时内建立起完整认知。
2.3 预训练与微调:为什么你不该从零训练大模型
预训练的目标是让模型学会“接话”,也就是根据前文预测下一个 Token。这个过程需要海量数据和巨大算力,个人开发者完全没有必要自己去走一遍。你真正需要掌握的是微调(Fine-tuning),也就是在预训练好的权重基础上,用特定领域的数据继续训练。微调又分为全参微调和 LoRA(低秩适配)微调两种。全参微调会更新所有权重参数,显存需求极高,一张 24GB 显存的卡也只能跑很小的模型。LoRA 只训练一小部分新增参数,原模型权重冻结不动,显存占用大幅下降。
资源里给出的学习顺序是:先搞懂预训练的目标函数是交叉熵损失,再跑一个 LoRA 微调的小例子,体会“冻结原权重、只练新增参数”这个核心思想。这一步一旦走通,你对“大模型是怎么学会特定领域知识”的理解会比只看文章深得多,也能为后续做“模型怎么用”打下基础。
3. 本地部署与微调:从选模型到跑通推理,完整走一遍流程
3.1 硬件评估与模型选型:先把显存预算算清楚
部署本地大模型,第一步不是下载模型,是算账。你需要明确自己的显存上限,然后反推能跑哪个尺寸的模型。这里有一个快速估算的经验值:7B 量级的模型做 4-bit 量化后,大约需要 4-6GB 显存;13B 模型的 4-bit 量化大约需要 8-10GB;70B 模型即使量化到 4-bit,也需要 35GB 以上。这个值不是精确值,但足够你在下载前做出判断。我的建议是:显存 8GB 以内的,选 7B 量化模型;显存 16-24GB 的,直接上 13B 或 14B 量化模型;如果你是 4090 这类 24GB 显存的卡,可以尝试 30B 级别的量化模型,但推理速度会明显下降。
3.2 用 Ollama 快速验证推理,再切换到 vLLM 做服务化
Ollama 是目前最友好的本地模型运行方式。它把模型下载、权重加载、推理服务封装成了几条命令,非常适合第一步跑通流程。安装完成后的启动命令是:
ollama run qwen2.5:7b这条命令的完整流程是:Ollama 先从模型仓库拉取 qwen2.5 的 7B 版本权重,加载到显存,然后启动一个交互式的对话界面。你在终端输入问题,它返回答案,整个过程不用写一行代码。我第一次跑通的时候,最大的感受是“原来本地模型的唯一门槛只是显存”。如果你想把 Ollama 变成一个可以被程序调用的服务,只需执行:
ollama serve这会在本地 11434 端口启动一个 OpenAI 兼容的 API 服务。你之后写的任何脚本,都可以用 HTTP 请求直接与模型交互,这为后续封装 Web 服务打下了基础。而当你需要更高的并发吞吐量时,就换到 vLLM 这类专业推理框架,它能通过 PagedAttention 技术大幅提升显存利用效率。
3.3 动手做一次 LoRA 微调:用 HuggingFace 的 PEFT 库实现
微调不需要你重新写训练循环,HuggingFace 的transformers和peft两个库已经帮你封装好了。下面是一个最小可运行的 LoRA 微调流程:
from transformers import AutoModelForCausalLM, AutoTokenizer from peft import LoraConfig, get_peft_model from datasets import load_dataset # 加载底座模型和分词器,使用 4-bit 量化节省显存 model = AutoModelForCausalLM.from_pretrained( "Qwen/Qwen2.5-7B", load_in_4bit=True, device_map="auto" ) tokenizer = AutoTokenizer.from_pretrained("Qwen/Qwen2.5-7B") # 配置 LoRA 参数 lora_config = LoraConfig( r=8, # 低秩矩阵的维度,越大表示可学习的参数越多 lora_alpha=32, # 缩放系数,控制 LoRA 权重的影响力度 target_modules=["q_proj", "k_proj", "v_proj", "o_proj"], # 只对注意力层的投影矩阵做适配 lora_dropout=0.1, # 防止过拟合 bias="none" # 不训练偏置项 ) # 用 PEFT 包装模型,冻结原始权重 peft_model = get_peft_model(model, lora_config) peft_model.print_trainable_parameters()这段代码的关键在于load_in_4bit=True和target_modules两个参数。前者让底座模型以 4-bit 量化方式加载,减少显存占用,使你能够在单卡上微调 7B 模型;后者决定了 LoRA 插到模型的哪些层——一般只需要改造注意力层的四个线性投影矩阵,前馈网络层可以不动。打印出来的可训练参数占比通常在 1% 以下,这正是 LoRA 的节省之处。
训练循环可以直接使用transformers自带的Trainer:
from transformers import Trainer, TrainingArguments training_args = TrainingArguments( output_dir="./lora_output", # 训练产物保存路径 per_device_train_batch_size=1, # 批量大小为 1,进一步降低显存峰值 gradient_accumulation_steps=8, # 梯度累积 8 步等效 batch size 8 num_train_epochs=3, learning_rate=2e-4, logging_steps=50, save_strategy="epoch", fp16=True # 使用半精度训练 ) trainer = Trainer( model=peft_model, args=training_args, train_dataset=dataset, tokenizer=tokenizer ) trainer.train()这一段里最容易踩坑的是fp16=True。如果你的显卡比较旧,比如 GTX 16 系列,半精度训练会出 NaN loss。新卡如 30 系、40 系基本没问题。训练完成后,LoRA 权重会保存到output_dir,只有几十 MB 大小。之后推理时,你只需要加载原始权重和这个 LoRA 适配器,就能获得微调后的模型能力。
3.4 量化模型:用 GPTQ 和 AWQ 把显存需求再压一档
如果你跑完上面的流程发现显存还是不够,或者想让推理速度更快,就需要对模型做量化。量化就是把权重从 FP16(16 位浮点数)压缩到 INT8 或 INT4,用精度换显存。当前主流的两种量化方法是 GPTQ 和 AWQ。GPTQ 基于二阶近似逐层校准,AWQ 则通过分析激活值分布来保护重要权重通道,二者效果接近,AWQ 在低比特位下通常更稳一点。
from transformers import AutoModelForCausalLM, AutoTokenizer, GPTQConfig quant_config = GPTQConfig( bits=4, # 量化到 4 bit dataset="c4", tokenizer=tokenizer, group_size=128, # 每 128 个权重共享一个缩放因子 desc_act=False # 是否按激活值降序排列 ) quantized_model = AutoModelForCausalLM.from_pretrained( "Qwen/Qwen2.5-7B", quantization_config=quant_config, device_map="auto" )量化后的模型可以直接用save_pretrained保存成本地权重文件。需要说明的是,量化不是完全没有代价的。模型在复杂推理、数学计算上的能力会有轻微下降,这是精度损失导致的必然结果。如果你做的场景对输出准确性要求极高,比如医疗诊断辅助、法律文书生成,建议优先保留 FP16 原模型,只在考虑部署成本时才做量化。
4. 搭建中的避坑与常见问题:五个最容易翻车的环节
4.1 模型下载卡住不动:先确认是不是网络和端口问题
现象:用 Ollama 或 HuggingFace 下载模型时,进度条长时间不动,或者下载到一半断掉重来。原因:HuggingFace 的下载节点在国外,网络不稳定是常态;Ollama 的模型仓库在大陆也存在访问波动。解决:Ollama 用户可以设置环境变量OLLAMA_HOST指向国内镜像源,常见做法是配置成https://ollama.example.com这类镜像地址。HuggingFace 用户则可以通过设置镜像站来绕过:
export HF_ENDPOINT=https://hf-mirror.com之后用huggingface-cli download或直接在代码里from_pretrained,下载速度会明显提升。从那以后我每次下载模型前都会先确认环境变量是否设置,几乎不再因为下载问题卡流程。
4.2 显存溢出(OOM):你的模型和上下文窗口至少有一个太大
现象:加载模型或输入长文本时,程序直接退出,报错信息里出现CUDA out of memory。原因:要么是模型尺寸本身超出显存上限,要么是上下文窗口设置过大。解决:先按 3.1 的公式算一遍硬件的承载能力。在使用 Transformers 库加载模型时,设置max_memory参数:
model = AutoModelForCausalLM.from_pretrained( "Qwen/Qwen2.5-7B", device_map="auto", max_memory={0: "8GiB", "cpu": "16GiB"} )这样可以防止模型直接溢出到崩溃,剩余的层会自动分配到 CPU 上计算,代价是推理速度会慢。管理层建议是:优先降低上下文窗口到 2048 以下,再考虑换更小的模型。
4.3 LoRA 训练 loss 不下降:八成是学习率和数据格式的问题
现象:训练循环跑了好几个 step,loss 一直不变,或者反复抖动。原因:最常见的是学习率设置不合理,LoRA 微调的学习率通常在2e-4到5e-4之间,远高于全参微调的1e-5。另一个常见坑是数据没有按模型的对话模板格式化,导致模型学到的是无效的上下文模式。解决:检查你的数据是否包含指令、输入、输出三段结构,并用模型自带的apply_chat_template方法完成格式化:
messages = [{"role": "user", "content": "请写一段Python代码"}] input_text = tokenizer.apply_chat_template(messages, tokenize=False)这一步处理完成后,再分词、喂给模型训练。如果 loss 依然不降,就把学习率逐步调低到1e-4再试一次。
4.4 推理速度慢到没法用:计算量瓶颈在于重复的显存读写
现象:模型答一句话要等十几秒,体验比在线 API 差很多。原因:本地模型的推理速度受显存带宽限制,生成的每个 Token 都要读取一次全部模型权重。7B 模型在 3090 上大约每秒生成 20-30 个 Token,在 4060 上可能只有 10 个左右。解决:先确认是否开了flash_attention_2(如果有相应实现),这个能在不损失精度的情况下加速 20% 以上:
model = AutoModelForCausalLM.from_pretrained( "Qwen/Qwen2.5-7B", torch_dtype=torch.float16, attn_implementation="flash_attention_2" )再一个有效手段是降低生成长度限制,max_new_tokens从 1024 降到 512,用户感知等待时间直接减半。如果仍然不满足,就需要考虑换推理框架,vLLM 通常能比 Transformers 原生实现快 3 到 5 倍。
4.5 部署到 Linux 服务器起不来服务:端口占用和环境变量丢失
现象:ollama serve或 FastAPI 服务启动后,外网完全访问不到,或者curl测试直接拒绝连接。原因:大概率是端口被防火墙拦截,或者是启动时没有把CUDA_VISIBLE_DEVICES等环境变量带上。解决:先本地curl http://localhost:11434/api/generate确认服务本身正常,再用ss -lntp查看端口监听状态。如果是防火墙问题,执行:
sudo ufw allow 11434/tcp如果是在 systemd 服务里启动的,记得在[Service]段下加入Environment="CUDA_VISIBLE_DEVICES=0"。这些细节单看都很小,但把它们全部串起来,部署过程中的大多数翻车事故都能绕开。
5. 从本地模型到 Web 服务:SSE 流式输出与交互逻辑的完整封装
5.1 为什么用 SSE 而不是 WebSocket
封装大模型交互逻辑时,最容易踩的坑就是前后端通信方式选错。不少人一上来就用 WebSocket,但这个方案有一个天然痛点:WebSocket 是全双工的长连接,做实时对话确实没问题,但它的逻辑比 HTTP 复杂,且所有主流大模型 API(OpenAI、Anthropic、以及基于 Ollama 的服务端)都以 SSE 作为标准流式协议。SSE(Server-Sent Events)是单向的服务器推流,客户端只需要用fetch接收流式数据,省去了 WebSocket 的连接管理和心跳维护。搜索引擎里大家都在讨论“通过 sse 流式输出实现大模型回答实时渲染”,就是这个原因——SSE 是兼容性最好、侵入性最低的选择。
5.2 后端:用 FastAPI 实现 GPT 风格的流式接口
我习惯用 FastAPI 做后端,因为它的异步支持非常干净。显然,核心逻辑不复杂:接收前端请求 → 组装 prompt → 调用本地模型 → 以 SSE 格式不断推送大模型产出的内容给前端。
import json from fastapi import FastAPI from fastapi.responses import StreamingResponse app = FastAPI() # 封装一个 chat 接口,支持 messages 数组格式,与 OpenAI API 兼容 @app.post("/v1/chat/completions") async def chat_completions(request: dict): messages = request.get("messages", []) # 把消息数组拼接成模型能识别的 prompt 文本 prompt = "" for msg in messages: prompt += f"{msg['role']}: {msg['content']}\n" prompt += "assistant:" # 这里调用 Ollama 或其他本地推理服务 # 用 requests 库发起流式请求 ollama_url = "http://localhost:11434/api/generate" # 注意:真正的实现里用 httpx 的 stream 方法会更合适 # 这里用 requests 的 stream 展示核心流程 import requests resp = requests.post(ollama_url, json={ "model": "qwen2.5:7b", "prompt": prompt, "stream": True }, stream=True) # 用生成器把 Ollama 的流式输出转成 SSE 格式 def event_generator(): for line in resp.iter_lines(): if not line: continue # Ollama 返回的是 JSON 行 data = json.loads(line) token = data.get("response", "") if token: chunk = { "choices": [{ "delta": {"content": token}, "index": 0 }] } yield f"data: {json.dumps(chunk)}\n\n" if data.get("done", False): # 放一个结束标记,前端据此中断接收 yield "data: [DONE]\n\n" break return StreamingResponse(event_generator(), media_type="text/event-stream")这段代码把 Ollama 的流式输出中转成了 OpenAI 风格的 SSE 格式。核心逻辑在event_generator里:它逐行读取 Ollama 的响应,把每个 token 包装成 OpenAI 兼容的 chunk,再以data:前缀推给前端。为什么用text/event-stream作为media_type?因为浏览器端的EventSource和fetch都能识别这个 MIME 类型。前端拿到这个流之后,逐块解析就能实现打字机效果。
实际生产环境里,推荐把requests换成httpx,它的异步接口更适合 FastAPI 的事件循环。我在资源落地时也把这一点做了标注,避免你在压测时发现请求阻塞。
5.3 前端:用 fetch + abort 实现流式渲染与中断
前端的核心职责是:接收 SSE 流,实时渲染增量内容,并且支持用户点击“停止”按钮时中断生成。很多初次实践的朋友以为必须用EventSource,但EventSource只支持 GET 请求,无法携带自定义请求体,所以不适合 POST 方式的对话接口。更好的方案是用fetch配合ReadableStream手动解析:
async function fetchChatResponse(messages, onUpdate) { const controller = new AbortController(); // 保存 controller 供外部调用 abort() 中断 window.currentAbortController = controller; const resp = await fetch('/v1/chat/completions', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ messages }), signal: controller.signal }); // 读取响应体的流式数据 const reader = resp.body.getReader(); const decoder = new TextDecoder('utf-8'); let buffer = ''; while (true) { const { done, value } = await reader.read(); if (done) break; buffer += decoder.decode(value, { stream: true }); // SSE 数据以 \n\n 分割,逐块解析 const lines = buffer.split('\n\n'); buffer = lines.pop() || ''; for (const line of lines) { if (!line.startsWith('data:')) continue; const payload = line.replace('data:', '').trim(); if (payload === '[DONE]') { // 生成完毕,结束会话 return; } try { const json = JSON.parse(payload); const delta = json.choices?.[0]?.delta?.content || ''; if (delta) { // 把增量传给 UI 层做渲染 onUpdate(delta); } } catch (e) { console.error('解析 SSE 数据失败:', e); } } } }AbortController是整个中断机制的核心。当你调用controller.abort()时,fetch的signal会立即中断网络请求,浏览器的reader.read()会抛出一个AbortError。你需要在外层捕获这个错误,做界面回滚处理。常见做法是把controller存在全局变量里,这样“停止生成”按钮可以直接调用它。这里最容易踩的坑是:SSE 流的每一块数据包不一定以\n\n结尾,跨包的半行数据如果被直接丢弃,就会出现丢字、断句的问题。正确的处理方式是把buffer = lines.pop()留到下一轮循环继续拼接,这也是上面代码里buffer变量的意义所在。
5.4 整体调用链路与参数调优
在完整的 Web 应用中,前端点击发送后,执行fetchChatResponse(messages, updateUI)开始接收数据;后端 FastAPI 接口把请求转发给 Ollama 的/api/generate,Ollama 加载模型后逐 Token 返回;后端的生成器再把 Token 包装成 SSE 格式推给前端。整条链路里,影响用户体验的关键参数集中在两个地方:后端 Ollama 的temperature和top_p,以及前端max_tokens的控制。
在 Ollama 的请求体里加一个options字段就能控制生成参数:
resp = requests.post(ollama_url, json={ "model": "qwen2.5:7b", "prompt": prompt, "stream": True, "options": { "temperature": 0.7, "top_p": 0.9, "max_tokens": 1024 } }, stream=True)如果你的场景是客服问答、代码生成,temperature建议调低到0.3左右,减少随机性;如果是创意写作、头脑风暴,再拉高到0.8。max_tokens在本地部署场景是必要的保护阀门,我见过不少因为忘记限制生成长度导致显存被打满的案例。
从那以后,我每次新起一个对话项目,都强制自己先走一遍“理论 → 部署 → 微调 → 封装”这个完整链路,不做任何跳过。这样不仅能把注意力机制、LoRA、流式传输这些名词真正变成手上的能力,还能在出问题时用最直接的思路去定位,而不是面对黑匣子式的 Model API 求助无门。这套方法已经帮我处理过三个实际项目,希望帮到你。
本文还有配套的精品资源,点击获取