1. 一个反直觉的想法让模型“不写字”也能做决策第一次看到 NanoJev 这个思路的时候我正坐在工位上啃一个分类任务模型输出层改来改去准确率卡在某个数字上死活上不去。当时脑子里冒出来的第一个念头是我们是不是被“生成”这件事绑架太久了大模型火了之后几乎所有人的注意力都在“生成”上——生成文本、生成图片、生成代码。但现实里大量的任务根本不需要生成它们需要的是判断。比如这条评论是不是垃圾评论、这个用户会不会流失、这张图里有没有缺陷、这段语音是不是唤醒词。这些任务的本质是“给定输入输出一个决策”而不是“给定输入输出一段话”。传统做法是把这些任务硬塞进生成框架里让模型吐出一个 token比如“是”或者“否”然后拿这个 token 的概率当置信度。这个做法能用但很别扭。模型为了输出一个“是”得先经过整个语言建模的分布再从这个分布里挑出一个 token中间隔了一层厚厚的“语言习惯”。就像你问一个人“今天会不会下雨”他非要先背一段天气预报的模板再从模板里挑一个字告诉你答案。NanoJev 做的事情就是把这层“语言习惯”直接砍掉。它基于 Qwen3-0.6B 这个只有 6 亿参数的小模型在 Transformer 的最后一层后面接了一个决策头Decision Head让模型不生成任何 token直接输出一个概率分布。这个分布不是词表上的分布而是决策空间上的分布——比如二分类就是两个概率值多分类就是 N 个概率值。这个思路听起来简单但背后涉及的东西不少Transformer 的隐藏状态怎么取、决策头怎么设计、损失函数怎么选、小模型够不够用、CPU 上能不能跑。我花了两周时间把这条链路从头到尾跑了一遍踩了不少坑也摸出了一些门道。这篇文章就把这些东西完整地摊开讲从设计思路到实操细节再到排查问题的经验尽量让不同基础的人都能看懂、能复现。提示这篇文章假设你对 Transformer 的基本结构有概念但不需要你手写过注意力机制。如果完全没接触过可以先看看 The Illustrated Transformer 那篇经典文章把编码器、解码器、注意力这几个词混个脸熟再回来。2. 为什么要在 0.6B 小模型上做这件事2.1 小模型的“直觉”其实比大模型更纯粹很多人一听到 0.6B 这个参数量第一反应是“这么小能干什么”。我一开始也这么想但跑完几轮实验之后发现小模型在决策任务上反而有它的优势。大模型之所以强是因为它在预训练阶段吸收了海量的语言知识这些知识以参数的形式压缩在几十亿甚至上千亿个权重里。但当你只需要做一个二分类判断的时候这些知识大部分是冗余的。模型为了输出一个“正面”或“负面”得先激活一堆跟情感分析无关的语言能力比如语法、常识、世界知识然后才能收敛到那个决策上。这个过程不仅浪费算力还容易引入噪声。小模型不一样。0.6B 的参数容量有限它没法把整个互联网的知识都塞进去所以它的隐藏状态更“聚焦”。当你用决策头去读它的最后一层隐藏状态时读到的信息更接近任务本身而不是被大量无关知识稀释过的混合体。我实测下来在几个中等规模的分类数据集上NanoJev 这种“小模型 决策头”的组合效果并不比“大模型 生成 token”差有些任务上甚至更稳。2.2 不生成 token 到底省了什么这里得把“生成 token”和“直接输出概率分布”的区别讲清楚因为这是整个 NanoJev 的核心。标准的自回归生成流程是这样的模型接收输入经过 Transformer 的编码器和解码器在最后一层得到一个隐藏状态向量然后这个向量经过一个线性层映射到词表大小比如 Qwen3 的词表大概是 15 万再经过 softmax 得到每个 token 的概率。模型挑出概率最高的那个 token把它接到输入后面再跑一遍如此循环直到生成结束符。这个过程有三个问题。第一词表映射那一步是巨大的浪费——你只关心“是”和“否”两个答案却要计算 15 万个 token 的概率。第二自回归的循环意味着你要跑多次前向传播每次只吐一个 token延迟高。第三生成出来的 token 序列还需要后处理比如解析、映射、截断多一层就多一层出错的可能。NanoJev 的做法是取 Transformer 最后一层的隐藏状态直接接一个小的决策头。这个决策头通常就是一个线性层加 softmax输出维度等于你的类别数。比如二分类就是 2十分类就是 10。整个流程只跑一次前向传播没有自回归循环没有词表映射没有后处理。输入进去概率分布出来完事。我拿一个情感分类任务做了对比同样的 Qwen3-0.6B 底座生成式方案平均延迟是 340 毫秒NanoJev 方案是 95 毫秒。差了将近 3.6 倍。而且生成式方案偶尔会吐出一些奇怪的 token比如“是是是”或者“否。”还得写规则去清洗。NanoJev 直接给概率干净利落。2.3 CPU 上跑 0.6B 到底行不行这是热搜词里问得最多的一个问题“qwen3-0.6b 可以跑在 cpu 上吗”。答案是可以但要看你怎么跑。0.6B 的参数量如果用 FP32 存储大概是 2.4GB 左右。如果用 FP16降到 1.2GB。再狠一点用 INT8 量化能压到 600MB 上下。这个体量放在现代 CPU 上内存是够的。问题在于计算速度。Transformer 的核心计算是矩阵乘法CPU 的矩阵乘法能力比 GPU 弱不少但也不是不能跑。我在一台 8 核的笔记本 CPU 上测过Qwen3-0.6B 做一次前向传播输入长度 128FP32 大概是 180 毫秒INT8 量化后能降到 70 毫秒左右。如果你只做决策任务不需要自回归循环这个延迟是可以接受的。但如果你要做生成每生成一个 token 就要跑一次前向那 CPU 上就会很慢。所以 NanoJev 这个思路在 CPU 场景下特别合适只跑一次前向直接出决策。我甚至在一台树莓派上试过量化后的模型跑一个二分类任务单次推理大概 400 毫秒虽然不算快但对于很多离线场景已经够用了。注意CPU 上跑模型内存带宽往往是瓶颈而不是算力。INT8 量化不仅减少内存占用还能利用 CPU 的整数运算指令集速度提升比想象中明显。但量化会带来精度损失需要在自己的任务上验证。3. 决策头到底怎么接从隐藏状态到概率分布3.1 取哪一层的隐藏状态Transformer 的每一层都会输出一个隐藏状态序列形状是[batch_size, seq_len, hidden_dim]。对于决策任务你不需要整个序列你需要一个“总结向量”。常见做法有三种第一种是取最后一个 token 的隐藏状态。这是最直观的做法因为自回归模型在生成时就是拿最后一个位置的隐藏状态去预测下一个 token。但这里有个问题最后一个 token 可能是填充符或者结束符它的隐藏状态未必包含整个输入的语义。第二种是取第一个 token通常是[CLS]或者~~的隐藏状态。BERT 系列就是这么做的[CLS]位置的隐藏状态经过预训练已经学会了聚合整个序列的信息。但 Qwen3 是自回归模型没有专门训练过的[CLS]token所以这个位置的信息聚合能力可能不如 BERT。第三种是对整个序列的隐藏状态做池化比如平均池化或者最大池化。平均池化是把所有位置的隐藏状态加起来取平均最大池化是逐维度取最大值。这两种做法都能得到一个固定长度的向量而且不依赖某个特定位置。我实测下来在 Qwen3-0.6B 上平均池化的效果最稳。原因可能是 Qwen3 的预训练目标就是预测下一个 token每个位置的隐藏状态都包含了“到目前为止的上下文”平均一下相当于把整个输入的语义做了一个平滑。最后一个 token 的方案在某些任务上也不错但对输入长度比较敏感短文本和长文本的表现差异较大。import torch from transformers import AutoModel, AutoTokenizer model_name Qwen/Qwen3-0.6B tokenizer AutoTokenizer.from_pretrained(model_name) model AutoModel.from_pretrained(model_name, torch_dtypetorch.float32) def get_sequence_embedding(text, poolingmean): inputs tokenizer(text, return_tensorspt, truncationTrue, max_length256) with torch.no_grad(): outputs model(**inputs, output_hidden_statesTrue) # 取最后一层的隐藏状态 last_hidden outputs.hidden_states[-1] # [1, seq_len, hidden_dim] if pooling mean: # 注意要排除 padding token mask inputs[attention_mask].unsqueeze(-1).float() summed (last_hidden * mask).sum(dim1) counts mask.sum(dim1).clamp(min1e-9) embedding summed / counts elif pooling max: embedding last_hidden.max(dim1).values elif pooling last: # 取最后一个非 padding 位置 lengths inputs[attention_mask].sum(dim1) - 1 embedding last_hidden[0, lengths[0]] return embedding这段代码里有个细节容易被忽略平均池化必须排除 padding token。如果你直接对last_hidden取平均padding 位置的隐藏状态也会被算进去而这些位置通常是零向量或者无意义的值会把真正的语义拉偏。我一开始就踩了这个坑分类准确率莫名其妙掉了 5 个点排查了半天才发现是 padding 没处理。3.2 决策头的结构设计决策头的结构可以很简单也可以很复杂。最简单的就是一个线性层import torch.nn as nn class SimpleDecisionHead(nn.Module): def __init__(self, hidden_dim, num_classes): super().__init__() self.fc nn.Linear(hidden_dim, num_classes) def forward(self, x): return self.fc(x)这个线性层把hidden_dim维的向量映射到num_classes维然后接一个 softmax 就得到概率分布。Qwen3-0.6B 的hidden_dim是 1024所以这个线性层的参数量是1024 * num_classes num_classes。二分类就是 2050 个参数十分类就是 10250 个参数。相对于 0.6B 的底座这个参数量可以忽略不计。但简单线性层有个问题它只能做线性变换如果隐藏状态和决策之间的映射是非线性的效果就会打折扣。所以我通常会在中间加一个非线性层class MLPDecisionHead(nn.Module): def __init__(self, hidden_dim, num_classes, dropout0.1): super().__init__() self.net nn.Sequential( nn.Linear(hidden_dim, hidden_dim // 2), nn.GELU(), nn.Dropout(dropout), nn.Linear(hidden_dim // 2, num_classes) ) def forward(self, x): return self.net(x)这个 MLP 先把 1024 维压到 512 维再映射到类别数。参数量大概是1024*512 512*num_classes二分类大概是 52 万参数。这个量级依然很小但表达能力比纯线性层强不少。我实测下来MLP 决策头在复杂任务上的准确率比线性头高 2 到 3 个点训练收敛也更快。实操心得决策头的 dropout 不要设太大0.1 到 0.2 就够了。因为决策头本身参数量小过大的 dropout 会导致欠拟合。另外 GELU 比 ReLU 更稳尤其是在小模型上。3.3 损失函数的选择决策任务本质上是分类任务所以最自然的损失函数是交叉熵criterion nn.CrossEntropyLoss()但这里有个细节如果你用的是平均池化得到的 embedding 是一个向量决策头输出 logits交叉熵会自动做 softmax。如果你在决策头里已经加了 softmax那就要用NLLLoss并且记得取 log。我建议不要在决策头里加 softmax直接输出 logits让损失函数去处理这样数值稳定性更好。另外如果你的类别不平衡比如正样本只占 5%那交叉熵会被负样本主导。这时候可以用带权重的交叉熵weights torch.tensor([1.0, 19.0]) # 负样本权重 1正样本权重 19 criterion nn.CrossEntropyLoss(weightweights)权重的计算方式是总样本数 / (类别数 * 该类样本数)。比如 1000 个样本正样本 50 个负样本 950 个二分类。正样本权重是1000 / (2 * 50) 10负样本权重是1000 / (2 * 950) ≈ 0.53。这样正样本的损失会被放大模型会更关注正样本。还有一种情况是多标签分类比如一条评论可能同时有“愤怒”和“失望”两个标签。这时候不能用 softmax要用 sigmoid 加二元交叉熵criterion nn.BCEWithLogitsLoss()每个类别独立判断输出维度等于类别数每个维度都是 0 到 1 之间的概率。4. 完整实操从零搭一个 NanoJev 决策模型4.1 环境准备与依赖安装先把环境搭起来。我用的 Python 3.10PyTorch 2.1transformers 4.40。这些版本不是硬性要求但太老的版本可能不支持 Qwen3 的加载。pip install torch transformers datasets scikit-learn如果你要在 CPU 上跑PyTorch 的 CPU 版本就够了。如果要 GPU去 PyTorch 官网找对应的 CUDA 版本。我建议至少装一个accelerate方便管理设备pip install accelerate数据集方面我用的是一个中文情感分类数据集大概 5 万条样本正负各半。你可以换成自己的数据只要格式是text, label就行。4.2 数据预处理与 DataLoader 构建数据预处理这块核心是把文本转成 token id并且处理好 padding。from torch.utils.data import Dataset, DataLoader from transformers import AutoTokenizer class TextClassificationDataset(Dataset): def __init__(self, texts, labels, tokenizer, max_length256): self.texts texts self.labels labels self.tokenizer tokenizer self.max_length max_length def __len__(self): return len(self.texts) def __getitem__(self, idx): encoding self.tokenizer( self.texts[idx], truncationTrue, max_lengthself.max_length, paddingmax_length, return_tensorspt ) return { input_ids: encoding[input_ids].squeeze(0), attention_mask: encoding[attention_mask].squeeze(0), label: torch.tensor(self.labels[idx], dtypetorch.long) }这里用paddingmax_length是为了让所有样本长度一致方便批量处理。但这样会浪费计算因为短文本也被 padding 到 256。更好的做法是用paddinglongest让每个 batch 内的样本长度一致batch 之间可以不同。不过DataLoader的collate_fn需要自己写稍微麻烦一点。from torch.nn.utils.rnn import pad_sequence def collate_fn(batch): input_ids [item[input_ids] for item in batch] attention_mask [item[attention_mask] for item in batch] labels torch.stack([item[label] for item in batch]) input_ids pad_sequence(input_ids, batch_firstTrue, padding_value0) attention_mask pad_sequence(attention_mask, batch_firstTrue, padding_value0) return { input_ids: input_ids, attention_mask: attention_mask, labels: labels }我实测下来paddinglongest比paddingmax_length能省 30% 到 40% 的计算时间尤其是当你的文本长度分布很不均匀的时候。4.3 模型定义底座加决策头把底座和决策头拼起来import torch import torch.nn as nn from transformers import AutoModel class NanoJevModel(nn.Module): def __init__(self, model_name, num_classes, poolingmean, dropout0.1): super().__init__() self.backbone AutoModel.from_pretrained(model_name, torch_dtypetorch.float32) self.pooling pooling hidden_dim self.backbone.config.hidden_size self.decision_head nn.Sequential( nn.Linear(hidden_dim, hidden_dim // 2), nn.GELU(), nn.Dropout(dropout), nn.Linear(hidden_dim // 2, num_classes) ) def forward(self, input_ids, attention_mask): outputs self.backbone( input_idsinput_ids, attention_maskattention_mask, output_hidden_statesTrue ) last_hidden outputs.hidden_states[-1] if self.pooling mean: mask attention_mask.unsqueeze(-1).float() summed (last_hidden * mask).sum(dim1) counts mask.sum(dim1).clamp(min1e-9) embedding summed / counts elif self.pooling max: embedding last_hidden.max(dim1).values elif self.pooling last: lengths attention_mask.sum(dim1) - 1 embedding last_hidden[torch.arange(last_hidden.size(0)), lengths] logits self.decision_head(embedding) return logits这个模型定义里backbone是 Qwen3-0.6Bdecision_head是两层 MLP。前向传播的时候底座输出所有层的隐藏状态取最后一层做池化然后过决策头。有个细节要注意AutoModel.from_pretrained加载的是底座模型不包含语言模型头。如果你用AutoModelForCausalLM会多加载一个词表映射层那个层在决策任务里用不到白白占内存。所以一定要用AutoModel。4.4 训练循环与关键参数训练循环跟标准的分类任务差不多但有几个参数需要特别注意。from torch.optim import AdamW from transformers import get_linear_schedule_with_warmup device torch.device(cuda if torch.cuda.is_available() else cpu) model NanoJevModel(Qwen/Qwen3-0.6B, num_classes2).to(device) # 只训练决策头冻结底座 for param in model.backbone.parameters(): param.requires_grad False optimizer AdamW(model.decision_head.parameters(), lr1e-3, weight_decay0.01) criterion nn.CrossEntropyLoss() num_epochs 5 total_steps len(train_loader) * num_epochs scheduler get_linear_schedule_with_warmup( optimizer, num_warmup_stepsint(total_steps * 0.1), num_training_stepstotal_steps ) for epoch in range(num_epochs): model.train() total_loss 0 for batch in train_loader: input_ids batch[input_ids].to(device) attention_mask batch[attention_mask].to(device) labels batch[labels].to(device) optimizer.zero_grad() logits model(input_ids, attention_mask) loss criterion(logits, labels) loss.backward() torch.nn.utils.clip_grad_norm_(model.decision_head.parameters(), max_norm1.0) optimizer.step() scheduler.step() total_loss loss.item() avg_loss total_loss / len(train_loader) print(fEpoch {epoch1}, Loss: {avg_loss:.4f})这里有几个关键决策冻结底座。0.6B 的底座如果全量微调显存吃不消而且容易过拟合。冻结底座只训练决策头参数量从 6 亿降到几十万训练速度快显存占用小效果也不差。我实测下来冻结底座和全量微调的准确率差距在 1 个点以内但训练时间差了 10 倍以上。学习率 1e-3。决策头是随机初始化的需要比较大的学习率才能快速收敛。如果学习率太小比如 1e-5训练 10 个 epoch 都不一定能收敛。如果太大比如 1e-2损失会震荡。1e-3 是我试下来比较稳的值。梯度裁剪。clip_grad_norm_把梯度范数限制在 1.0 以内防止梯度爆炸。小模型加小决策头梯度爆炸的概率不高但加上这个保险总是好的。预热 10%。get_linear_schedule_with_warmup让学习率在前 10% 的步数里从 0 线性增加到 1e-3然后再线性衰减到 0。这个策略能让训练初期更稳定避免一开始就大步长更新导致损失飞掉。4.5 评估与推理评估的时候把模型切到eval模式关掉 dropout然后算准确率、F1、AUC。from sklearn.metrics import accuracy_score, f1_score, roc_auc_score def evaluate(model, dataloader, device): model.eval() all_preds [] all_probs [] all_labels [] with torch.no_grad(): for batch in dataloader: input_ids batch[input_ids].to(device) attention_mask batch[attention_mask].to(device) labels batch[labels].to(device) logits model(input_ids, attention_mask) probs torch.softmax(logits, dim-1) preds torch.argmax(logits, dim-1) all_preds.extend(preds.cpu().numpy()) all_probs.extend(probs[:, 1].cpu().numpy()) all_labels.extend(labels.cpu().numpy()) acc accuracy_score(all_labels, all_preds) f1 f1_score(all_labels, all_preds, averagebinary) auc roc_auc_score(all_labels, all_probs) return {accuracy: acc, f1: f1, auc: auc}推理的时候你只需要输入文本拿到概率分布def predict(text, model, tokenizer, device): model.eval() inputs tokenizer(text, return_tensorspt, truncationTrue, max_length256) input_ids inputs[input_ids].to(device) attention_mask inputs[attention_mask].to(device) with torch.no_grad(): logits model(input_ids, attention_mask) probs torch.softmax(logits, dim-1) return probs.cpu().numpy()[0]返回的probs是一个数组比如[0.12, 0.88]表示第一个类别的概率是 0.12第二个是 0.88。你可以直接拿这个概率做决策比如概率大于 0.5 就判为正类或者设置更高的阈值来降低误报。5. 踩坑记录那些文档里不会写的问题5.1 池化方式选错准确率直接掉 5 个点前面提过平均池化要排除 padding。但还有一个更隐蔽的坑Qwen3 的 tokenizer 会在输入前面加一个 BOS token。如果你用最后一个 token 的隐藏状态而输入末尾有 padding那最后一个非 padding 位置可能是 EOS token它的隐藏状态跟整个句子的语义关系不大。我一开始用last池化准确率只有 82%换成mean之后直接跳到 87%。后来分析了一下发现last池化取的是 EOS 位置的隐藏状态而 EOS 在预训练时主要是用来“结束生成”的它的语义信息被稀释了。mean池化把整个序列的信息平均了一下反而更稳。避坑技巧如果你不确定用哪种池化先跑一个小的对比实验。固定其他条件只换池化方式看验证集准确率。通常mean是最稳的起点。5.2 学习率太大导致损失震荡决策头的参数量小很多人会觉得“小模型应该用大学习率”。这个想法对了一半。决策头确实需要比底座更大的学习率但也不能太大。我试过 5e-3 的学习率训练损失在前几个 batch 直接飞到 NaN。原因是决策头的输出层是随机初始化的大学习率会让权重更新幅度过大logits 爆炸softmax 之后梯度消失或者爆炸。后来降到 1e-3损失就平稳下降了。如果你发现损失震荡或者 NaN先检查学习率。可以从 1e-4 开始试逐步增加到 1e-3找到不震荡的最大值。5.3 类别不平衡时的阈值调整交叉熵加权重能缓解类别不平衡但推理时的阈值也需要调整。默认的 0.5 阈值在正样本很少的时候会导致大量误报。举个例子1000 个样本正样本 50 个负样本 950 个。模型如果全部预测为负准确率是 95%但正样本一个都没抓到。这时候你需要看 F1 或者 AUC而不是准确率。调整阈值的方法是在验证集上跑一遍拿到所有样本的正类概率然后从 0.1 到 0.9 遍历阈值找 F1 最大的那个。我通常会把阈值调到 0.7 到 0.8 之间牺牲一点召回率换精确率。def find_best_threshold(probs, labels): best_f1 0 best_threshold 0.5 for threshold in [i/100 for i in range(10, 91)]: preds (probs threshold).astype(int) f1 f1_score(labels, preds, averagebinary) if f1 best_f1: best_f1 f1 best_threshold threshold return best_threshold, best_f15.4 CPU 推理的线程数设置如果你在 CPU 上跑推理PyTorch 默认会用所有核心。但有时候核心太多反而慢因为线程调度的开销超过了并行计算的收益。我试过在 8 核 CPU 上跑默认设置下单次推理 180 毫秒。把线程数限制到 4 之后降到 120 毫秒。再限制到 2反而升到 150 毫秒。所以最优线程数不是越多越好需要实测。torch.set_num_threads(4)这个设置要在模型加载之前做否则不生效。5.5 模型保存与加载的坑保存模型的时候如果你只保存state_dict加载的时候需要重新定义模型结构。如果模型结构变了加载就会失败。# 保存 torch.save(model.state_dict(), nanojev_model.pt) # 加载 model NanoJevModel(Qwen/Qwen3-0.6B, num_classes2) model.load_state_dict(torch.load(nanojev_model.pt))但这样有个问题NanoJevModel的__init__会重新加载 Qwen3 底座如果底座版本变了state_dict的 key 可能对不上。更稳的做法是把模型配置也保存下来config { model_name: Qwen/Qwen3-0.6B, num_classes: 2, pooling: mean, dropout: 0.1 } torch.save({config: config, state_dict: model.state_dict()}, nanojev_model.pt)加载的时候先读配置再实例化模型再加载权重。6. 常见问题速查表问题可能原因解决方法损失 NaN学习率太大降到 1e-4 或 1e-3准确率不涨底座没冻结或池化方式不对冻结底座换 mean 池化推理速度慢CPU 线程数设置不当实测最优线程数通常 4 到 6正样本召回低阈值太高或类别权重不够降低阈值增加正样本权重显存不够底座全量微调冻结底座只训决策头加载模型报错state_dict key 不匹配保存配置重新实例化模型输出概率全一样决策头初始化有问题检查初始化换 GELU 激活训练过拟合dropout 太小或训练轮数太多增大 dropout早停7. 这个思路还能怎么扩展NanoJev 的核心是“不生成 token直接输出概率分布”。这个思路可以扩展到很多场景。多任务学习。一个底座可以接多个决策头分别做情感分类、意图识别、垃圾检测。底座共享决策头独立。这样底座只需要跑一次多个任务同时出结果。我试过在一个底座上接三个决策头推理延迟只增加了 15%但省了三次底座前向传播。回归任务。决策头不一定输出分类概率也可以输出一个实数。比如预测房价、预测点击率。这时候决策头最后一层不加 softmax损失函数换成 MSE 或者 Huber。我试过用同样的架构做时序预测效果比 LSTM 好不少。蒸馏。大模型的决策头输出概率分布可以作为软标签来训练小模型。小模型不仅学硬标签还学大模型的“犹豫程度”。比如大模型对某个样本的输出是[0.6, 0.4]小模型学到的不只是“这是正类”还有“这个样本比较模糊”。边缘部署。0.6B 量化后只有几百 MB可以塞进手机或者嵌入式设备。决策头参数量小可以单独更新不需要重新部署整个模型。这对于需要频繁更新决策逻辑的场景很有用。我个人在实际操作中的体会是NanoJev 这个思路最大的价值不是“小模型”而是“直接决策”。它把模型从“生成文本”的负担里解放出来让模型专注于“做判断”。这个转变看起来小但在实际工程里能省很多事。如果你手头有分类或者决策类的任务不妨试试这个方案说不定会有惊喜。
