在真实的代码开发场景里,一个功能往往跨越多个文件,一段 bug 修复也经常涉及调用链上下游。代码大模型如果只能在单文件片段上做预测,就很难真正理解仓库层面的依赖关系。OctoLong 给出了一条值得关注的技术路线:在通用预训练之后、下游微调之前,插入一个 mid-training 中间训练阶段,用跨仓库代码上下文继续训练模型,从而增强长上下文建模能力。文章把这条路线拆开来讲,包括 mid-training 为什么能补长上下文短板、跨仓库上下文数据如何构造、训练时有哪些工程注意点、评测怎样设计才有说服力,以及复现这类工作经常遇到哪些坑。
1. 先从代码模型的长上下文痛点说起
1.1 上下文窗口和有效上下文是两件事
很多人在选代码模型时,习惯先把“上下文窗口”当核心指标:32K、128K、200K,数字越大似乎越强。但上下文窗口只代表模型“最多能接收多长的输入”,并不代表模型“能在这个长度内有效利用信息”。一个模型在 128K 窗口下,可能依然只从最近的 2000 个 token 里获取关键信息,窗口前段的函数定义、仓库注释、配置信息早就被注意力机制忽略了。
代码场景比自然语言场景更依赖远距离信息。比如你要理解一个函数handle_payment(order),它的类型定义可能在models/order.py,订单状态常量在constants/payment.py,数据库查询逻辑在repositories/order_repo.py。如果模型只读到函数体片段,缺少对依赖符号和返回类型的感知,生成的代码大概率在类型、边界条件或异常处理上出错。所以长上下文建模的核心问题不是“窗口有多大”,而是“模型在窗口内是否真的会跨片段建立引用关系”。
这也是 mid-training 这类方法存在的理由。位置编码外推和注意力优化解决的是“能不能容纳长序列”,而训练数据决定“模型在长序列中该学什么”。如果训练数据永远是把互不相关的短文本拼在一起,模型即使有长窗口,也学不会有效定位和聚合信息。
1.2 现有长上下文方案在代码场景的短板
当前让代码模型支持长上下文,常见做法有四类。每一类都有作用,但都有特定盲区。
| 方案类型 | 典型实现 | 主要解决问题 | 代码场景短板 |
|---|---|---|---|
| 位置编码外推 | RoPE 扩展、NTK、YaRN、ALiBi | 让模型接收超出训练长度的序列 | 只解决长度限制,不解决信息利用率 |
| 长上下文续训 | 在长文档上继续预训练 | 让模型适应长序列的数据分布 | 网页文本堆叠多,仓库结构信息弱 |
| 长上下文 SFT | 用长指令数据微调 | 让模型学会按指令消费长输入 | 依赖人工标注或高质量长任务数据 |
| 检索增强 RAG | 向量召回、BM25、GraphRAG | 从外部候选里找回相关片段 | 召回质量决定上限,打断整体结构 |
细看会发现一个共性问题:这些方案要么只改模型结构,要么只用“长但不一定相关”的文本做训练。代码仓库本身有极强的结构信息,比如 import、require、include、目录层次、符号引用、commit 依赖,这些结构比普通长文本更适合训练长上下文建模。OctoLong 选择跨仓库代码上下文做 mid-training,本质上是把代码仓库的结构优势变成训练信号。
1.3 mid-training 在训练流程中的定位
LLM 的常规流水线可以概括为三个阶段:通用预训练、监督微调、偏好对齐。mid-training 被放在预训练之后、微调之前,也可以理解为“领域定向继续预训练”。
它和 SFT 的区别很明显:SFT 会改变模型输出的格式,让模型学会“用户提问 -> 模型回答”的模式,而 mid-training 通常不做指令格式转换,而是继续用 next-token prediction 的方式,把模型推向某个特定领域。它和普通继续预训练的区别则在于目标选择:继续预训练的目标是继续累积领域知识,mid-training 的目标往往更聚焦,比如提升长上下文能力、增强多文件代码理解、适应特定领域术语。
OctoLong 这个名字拆开看,Octo 可以联想到“八进制”“八爪鱼”“多分支结构”的意象,Long 指向 long-context。标题已经把它要做的事情讲清楚了:用跨仓库代码上下文做 mid-training,目的是增强长上下文建模。也就是说,这套方法不改变模型的下游任务输出方式,而是在模型能力矩阵里专门补“长上下文代码理解”这块短板。
2. OctoLong 为什么选择跨仓库代码上下文
2.1 单文件样本撑不起仓库级理解
单文件数据构造很简单:把文件按长度截断,或者按 tokenizer 的max_length切块。这种样本的优点是干净、易处理,缺点是天然丢失了仓库里的横向依赖。一个函数被截掉一半,或者一个文件里反复出现的工具类信息只出现一次,模型要从这些碎片里学会完整代码逻辑,本质上是在做“读残卷猜全貌”。
跨仓库代码上下文改变了这个状态。一个训练样本可以同时包含多个文件内容,文件之间依靠真实代码结构产生关联。例如入口函数src/api.py引入了models/item.py,样本里就同时出现这两个文件,模型在预测src/api.py后续内容时,有机会去models/item.py里寻找类型定义。这种训练迫使注意力机制做远距离检索和聚合,而不是只看局部窗口。
这里需要解释“跨仓库”的含义。它不一定是“把多个完全不相关的 GitHub 仓库随机拼在一起”,而是指数据组织跨出了单文件边界。一个 pull request 中同时改动的多个文件、一个服务模块和它依赖的 SDK 源码、一个仓库中相近目录下的核心实现与测试文件,都可以构成跨文件、甚至跨仓库依赖的训练单元。
2.2 一个训练样本长什么样
在没有官方数据生成工具的情况下,可以按下面这种结构设计样本。核心思路是把仓库快照转成带分隔符的长文本,然后用这些长文本做 next-token prediction。
{ "id": "cross-repo-context-0001", "repo": "example/checkout-system", "base_ref": "main", "context": [ { "path": "src/api.py", "content": "from models.item import Item\nfrom constants.status import OrderStatus\n\ndef create_order(item: Item):\n ...", "sep": "<file path=\"src/api.py\">" }, { "path": "models/item.py", "content": "class Item:\n name: str\n price: decimal.Decimal\n ...", "sep": "<file path=\"models/item.py\">" }, { "path": "constants/status.py", "content": "class OrderStatus:\n PENDING = 'PENDING'\n PAID = 'PAID'\n ...", "sep": "<file path=\"constants/status.py\">" } ], "target": "def mark_paid(order_id):\n ..." }context是按语义关联挑选出的多文件内容,target是要生成的目标片段。如果做纯续训风格,可以将context里的文件依次拼接成一个大字符串,去掉target,直接做语言建模;如果做指令风格,可以在target前加一句以上代码来自多个文件,请根据实现补全缺失逻辑之类的话,形成监督信号。
def build_training_text(sample: dict) -> str: segments = [] for file_obj in sample["context"]: segments.append(file_obj["sep"]) segments.append(file_obj["content"]) segments.append("<task>") segments.append(sample.get("target", "")) return "\n".join(segments)上面的sep并不一定需要保留,是否添加文件路径标记取决于模型在推理时是否会使用这类格式。如果打算把 mid-training 的产物继续做下游代码任务微调,保留路径标记可以帮助模型理解文件边界。
2.3 为什么跨仓库数据能增强长上下文建模
第一,样本长度自然变长。单文件样本通常只有几百到几千 token,把几个有关联的文件拼在一起后,样本很容易超过一万 token。模型被迫在更长的序列上反复计算,对长序列的数值稳定性、注意力分布和梯度传播都会更适应。
第二,信息密度更高。代码里的符号引用是显式的约束。模型要预测OrderStatus.PAID后面的内容,就要先看到constants/status.py里的定义。这种长距离依赖是天然的训练目标,比人工构造的“长文本里找一句话”更丰富。
第三,保留了通用语言的特性。代码数据里包含注释、文档字符串、commit message、配置说明和自然语言描述,这些内容仍然是通用语料的一部分。因此基于代码仓库的 mid-training 不会像纯合成文本那样导致过度的领域偏离。
值得提醒的是,跨仓库数据不能无脑拼接。如果只是把语料库里所有文件随机拼一大段,那模型学到的依然是“长而无序”的文本,甚至可能因为虚假关联而扰乱注意力。真正有效的前提是:文件之间通过 import、函数调用、类型引用、commit 共现等关系连接。
3. 训练环境和工程准备
3.1 硬件资源要按“最长样本”规划
长上下文训练对显存的消耗不是线性增长,而是接近二次增长。即使使用 FlashAttention,激活值、梯度、优化器状态依然会随着序列长度快速膨胀。在常见实践中,复现这类方法至少需要 8 卡 A100 或同级别显卡,代码模型规模在 7B 到 13B 之间时,32K 上下文属于可以接受的起步配置。
| 资源类型 | 小规模验证 | 正式复现 | 生产级迭代 |
|---|---|---|---|
| GPU | 1-2 张 24G 以上 | 8 张 A100 80G | 多机多卡集群 |
| 系统内存 | 128G | 256G | 512G 以上 |
| 存储 | 1-2T | 10T 以上 | 对象存储 + 快文件系统 |
| 训练框架 | transformers + PEFT | DeepSpeed / Megatron-LM | 自定义分布式数据管道 |
| 注意力实现 | 标准 attention | FlashAttention 2 | 定制 kernel + 序列并行 |
学习阶段的验证不一定要完整复现。可以先用 1K 上下文跑通数据管道,再用 8K 上下文训练一个很小的模型,确认数据和代码都没有问题后,再上完整规模。
3.2 基础模型和位置编码选型
标题没有指定 OctoLong 使用哪个基础模型,从开源生态看,这类方法通常会选择支持长窗口、代码理解能力较强的模型。选型时建议重点检查三点。
- 位置编码是否支持扩展。RoPE 系列模型通常可以通过调整
rope_scaling或 base 频率来扩展,但扩展后的稳定性需要自己验证。 - tokenizer 对代码的压缩率。一个过度拆分代码符号的 tokenizer 会让长样本更快触达长度上限,浪费有效上下文。
- 特殊 token 是否一致。文件分隔符、任务标记、结束标记都要在训练和推理时保持一致。
# 一个可参考的模型加载配置示例 model_name_or_path: Qwen/Qwen2.5-Coder-7B torch_dtype: bfloat16 attn_implementation: flash_attention_2 rope_scaling: type: yarn factor: 2.0实际使用时,rope_scaling的配置需要结合基础模型的原始训练长度和你的目标长度来计算。不要盲目调大factor,否则位置编码外推会降低模型的局部精度。
3.3 训练框架的关键参数
下面是 DeepSpeed 场景下启动训练时常见的一组参数,用来解释它们各自的作用。
deepspeed --num_gpus=8 train.py \ --model_name_or_path Qwen/Qwen2.5-Coder-7B \ --data_path ./data/train.jsonl \ --block_size 32768 \ --bf16 true \ --per_device_train_batch_size 1 \ --gradient_accumulation_steps 8 \ --gradient_checkpointing true \ --learning_rate 2e-5 \ --lr_scheduler_type cosine \ --warmup_ratio 0.05 \ --deepspeed ds_z3_config.json| 参数 | 含义 | 常见设置 | 调大/调小影响 |
|---|---|---|---|
| block_size | 训练序列最大长度 | 8192~32768 | 越大显存越高,对长上下文收益越大 |
| per_device_train_batch_size | 单卡 batch 大小 | 1~4 | 长序列下通常只能设 1 |
| gradient_accumulation_steps | 梯度累积步数 | 4~16 | 越大越接近大 batch,但训练更慢 |
| gradient_checkpointing | 重计算激活值 | 开启 | 明显降低显存,增加训练时间 |
| learning_rate | 学习率 | 1e-5~5e-5 | 过大导致灾难性遗忘,过小收敛慢 |
| warmup_ratio | 预热比例 | 0.03~0.1 | 长序列训练不稳定时可调大 |
不要把单卡 batch size 调大作为优化目标,长上下文训练中优先保证样本长度覆盖目标窗口,再通过梯度累积控制有效 batch size。
4. 从数据构造到最小训练闭环
4.1 仓库数据收集和过滤
第一步是确定数据源。可以基于公开代码语料,也可以自建私有仓库合集。需要遵守各平台的许可协议,并对仓库做基础过滤。
收集过程中的常见过滤条件:
- 文件大小超过阈值或低于阈值,比如小于 100 字节的配置碎片和大于 2MB 的生成文件;
- 明显是锁文件或构建产物的路径,例如
package-lock.json、dist/、node_modules/; - 非目标语言的源文件,如果只想训练 Python 相关代码,就过滤掉大部分不相关内容。
- 测试文件和样例目录是否需要保留,取决于目标能力。如果想增强“修改代码后同步修改测试”的能力,就保留测试文件。
# 一个简单的过滤函数示例 import json from pathlib import Path def is_valid_path(path: str) -> bool: block_substrings = ["node_modules", "dist", "build", ".git", "__pycache__"] return not any(part in block_substrings for part in path.split("/")) def build_sample(repo_path: Path, candidates: list) -> dict: valid = [] for rel_path in candidates: full_path = repo_path / rel_path if not full_path.exists(): continue if not is_valid_path(rel_path): continue content = full_path.read_text(encoding="utf-8", errors="ignore") if len(content) < 200 or len(content) > 500_000: continue valid.append({"path": rel_path, "content": content}) return {"repo": repo_path.name, "context": valid[:8]}这里的candidates需要根据 import 图或 commit 关系统计得出。如果只是把一个仓库的前 8 个文件按字典序放入样本,模型很难学到真正的跨文件依赖。
4.2 文件关联度计算
一个可以直接落地的思路是:先用 AST 或正则解析 import 语句,再构建“文件 -> 被引用文件”的图,最后从某个入口文件出发做 BFS。这个做法虽然简单,但比随机挑文件更能体现跨文件依赖。
# 基于 import 关系选择相关文件 from collections import deque def get_related_files(import_graph: dict, entry: str, depth: int = 2): seen = {entry} queue = deque([(entry, 0)]) while queue: node, d = queue.popleft() if d >= depth: continue for nxt in import_graph.get(node, []): if nxt not in seen: seen.add(nxt) queue.append((nxt, d + 1)) return list(seen)如果目标是跨仓库依赖,还要把子模块之间共享的基础库也纳入。例如主项目 import 了内部的common-lib,那么构造样本时可以把主项目入口文件、相关业务文件和common-lib的实现文件放入同一个样本。这种跨仓库上下文对模型理解真实工程结构非常有帮助。
需要注意,BFS 这个策略只适合“快速验证”。如果做正式训练,建议使用更丰富的关联信号:函数调用关系、同名符号引用、commit 历史里同时修改的文件、README 和文档指向的模块。关联信号越丰富,样本的信息结构越接近真实开发场景。
4.3 tokenization 和序列切分
长样本在 tokenize 后可能超过block_size。这里有两种处理方式。
切分式:把一个超长样本按block_size切多段,每段独立作为训练样本。缺点是可能把一个跨文件上下文从中间切开,损失长距离依赖。
重试式:如果一个样本超过max_length,先通过仓库依赖信息找到更核心的入口文件,重新选择相关文件,直到样本长度落在目标范围。
def trim_to_target_length(sample: dict, tokenizer, max_len: int) -> dict: text = build_training_text(sample) tokens = tokenizer.encode(text, add_special_tokens=False) if len(tokens) <= max_len: return sample # 重新按依赖优先级选择文件,优先保留入口和核心依赖 ordered_files = sorted(sample["context"], key=lambda f: f.get("priority", 0), reverse=True) new_context = [] total = 0 for f in ordered_files: tokens_so_far = tokenizer.encode(f["sep"] + f["content"], add_special_tokens=False) if total + len(tokens_so_far) > max_len - 128: continue new_context.append(f) total += len(tokens_so_far) sample["context"] = new_context return sample这里预留 128 个 token 给后续的 task 标记和 target,是一种常见做法。如果预留不足,拼接后仍然会超长,并产生截断噪声。
4.4 训练数据配比
mid-training 阶段的数据配比会直接影响效果。一个相对稳妥的起步方案是:
| 数据来源 | 建议比例 | 作用 |
|---|---|---|
| 跨仓库/跨文件代码上下文 | 50%~70% | 强化长距离代码依赖建模 |
| 普通单文件代码语料 | 15%~25% | 保持短代码能力,防止局部建模退化 |
| 通用文本和文档数据 | 10%~20% | 减少通用能力遗忘 |
不同项目的基础模型不同,配比也要调。判断标准就是评测:如果通用代码能力下降,说明单文件代码语料占比太低;如果长上下文评测没有提升,说明跨仓库数据的构造质量有问题,而不是比例问题。
5. 评测:如何判断长上下文能力真的变强
5.1 评测任务要覆盖三种能力
长上下文代码能力的评测至少要覆盖定位、理解、生成三层。
| 能力层 | 代表任务 | 示例指标 |
|---|---|---|
| 定位能力 | 在仓库上下文中找到某个符号定义 | 命中率 |
| 理解能力 | 基于跨文件信息回答代码逻辑问题 | 准确率、F1 |
| 生成能力 | 根据上下文生成缺失函数或修复代码 | CodeBLEU、Pass@k |
只跑一个总指标不够。比如语言模型的困惑度下降,不一定会带来代码生成准确率提升;长文档问答提升,也不代表模型在跨文件补全任务上更强。建议按能力层分别记录结果。
5.2 用 needle-in-a-haystack 做快速体检
针尖测试是长上下文能力的快速体检方法。做法很简单:在一个长上下文里随机插入一段关键信息,然后构造一个只有读过这段信息才能回答的问题。
needle = "The flag value is OCTOLONG_NEEDLE_7." haystack = build_training_text(sample) position = len(haystack) // 2 haystack_with_needle = haystack[:position] + needle + haystack[position:] prompt = f"{haystack_with_needle}\n\nQuestion: What is the flag value?\nAnswer:"如果是代码场景,针尖信息可以替换成函数签名、配置项或类型定义。例如在长代码上下文中插入一行REPO_FLAG_42 = "cross_repo_pass",然后问模型REPO_FLAG_42是多少。
评测时要注意:
- 针尖位置要有覆盖,不能只放中间位置;
- 同一个问题要在不同上下文长度下测试;
- 上下文长度要覆盖训练长度和推理目标长度;
- 避免针尖信息出现在训练数据里。
5.3 代码仓库级评测需要注意数据隔离
跨仓库训练数据的最大风险是“训练集包含评测仓库快照”。如果一个仓库的 main 分支代码参与了训练,评测时再拿同仓库其他 commit 或任务做评估,很容易高估效果。
建议按时间切分:训练时只使用某个时间点之前的 commit 快照,评测任务使用后续 commit 或专门构造的跨文件任务。这样能更接近真实场景,也避免数据泄漏导致的虚高分数。
消融实验至少应该包含四组:
- 完整方法:跨仓库代码上下文 + mid-training;
- 去掉跨仓库结构:只用单文件代码语料做 mid-training;
- 去掉 mid-training:直接进入下游任务微调;
- 改变训练窗口:相同数据量下对比 8K、16K、32K。
这样能回答三个问题:跨仓库结构是否带来增益,mid-training 是否比直接 SFT 更合适,训练窗口大小对效果的影响有多大。
6. 复现 OctoLong 类方法时的高频问题排查
6.1 训练时显存溢出
现象:启动训练后很快报CUDA out of memory,程序直接退出。
排查顺序:
- 将
per_device_train_batch_size降到 1; - 确认
gradient_checkpointing已开启; - 确认
attn_implementation是flash_attention_2或等价实现; - 检查模型是否加载到了 GPU 0,优化器状态是否被 ZeRO 切分;
- 如果依然 OOM,把
block_size减半,先跑通流程再逐步加长。
错误日志里如果出现torch.OutOfMemoryError,不要先堆 GPU,先看显存到底被谁占满。可以用nvidia-smi观察显存分布,也可以在训练脚本里打印每个 batch 的输入形状。
6.2 训练 loss 下降但长上下文评测没提升
现象:训练阶段 loss 很稳定地下降,但 needle 测试命中率没有变化,跨文件问答也看不到提升。
可能原因:
- 数据构造里的长距离依赖不足。模型能从最近上下文猜出下一个 token,根本没有被迫看远处文件;
- 评测任务格式与训练任务格式不一致;
- 训练序列长度虽然标为 32K,但实际大部分样本远短于 32K,模型并没有真正见到长样本。
检查方式:
# 统计训练数据长度分布 python - <<'PY' import json from transformers import AutoTokenizer tokenizer = AutoTokenizer.from_pretrained("Qwen/Qwen2.5-Coder-7B") lengths = [] with open("data/train.jsonl") as f: for line in f: sample = json.loads(line) text = build_training_text(sample) lengths.append(len(tokenizer.encode(text))) lengths.sort() print(f"min={lengths[0]}, median={lengths[len(lengths)//2]}, max={lengths[-1]}") PY如果中位数远低于目标长度,说明样本长度分布有问题。应该优先增加多文件关联样本,而不是直接调大模型窗口。
6.3 mid-training 后出现灾难性遗忘
现象:训练结束后长上下文代码理解有所提升,但普通代码补全、通用对话、指令跟随能力下降。
原因通常有两类:一是学习率过大,模型在 mid-training 数据上过度拟合;二是数据配比中通用语料占比太低。
建议调整顺序:
- 将学习率降到原来的 1/3 或 1/5;
- 在训练集中增加 10%~20% 的通用代码和通用文本;
- 训练结束后做一次轻量 SFT 或 adapter 回滚,恢复指令能力;
- 保存 mid-training checkpoint 时保留原始模型权重,便于做权重平均。
6.4 评测结果异常偏高或偏低
评测分数异常偏高,先怀疑数据泄漏。检查训练样本是否包含评测任务相关的 commit、文件或问题答案。最容易忽略的是仓库里的test/目录,如果评测任务直接来自某个测试文件,而这部分测试文件参与了训练,效果就会失真。
评测分数异常偏低,则先检查上下文长度和位置编码。如果训练时 max length 是 16K,评测时突然给模型 64K 输入,位置编码外推不稳定会导致分数大幅下降。评测长度应该控制在训练长度附近,或者提前做好外推配置。
| 异常现象 | 优先检查 | 处理方向 |
|---|---|---|
| 分数异常高 | 训练集和评测集是否有重叠文件 | 按 commit 时间切分、按路径过滤 |
| 分数异常低 | 评测长度和训练长度是否匹配 | 调整测试长度或外推配置 |
| 结果不稳定 | 解码参数、评测样本顺序 | 固定 seed、多次评测取均值 |
7. 从实验到落地:工程化建议和扩展方向
7.1 建立 mid-training 的迭代流程
mid-training 不是一次性实验,而应该成为模型迭代流水线中的一个固定环节。一个可复用的流程如下:
- 代码仓库数据采集,设定许可证过滤和敏感信息过滤;
- 构建文件依赖图和 commit 关联数据;
- 生成跨仓库上下文样本,做长度分布统计;
- 配置训练脚本和监控指标;
- 在 1B 或 3B 小模型上验证数据管道;
- 在目标模型上做完整训练;
- 跑长上下文评测、通用能力评测、安全评测;
- 如果通过,进入 SFT 或下游任务微调阶段。
每一步都要有检查点。比如第 3 步完成后,必须要看样本长度分布和文件数量分布;第 5 步完成后,要确认 loss 能正常下降;第 7 步完成后,要记录基线对比表,而不是只看一两个指标。
7.2 推理侧还需要配套优化
即使模型已经被 mid-training 增强了长上下文能力,部署时仍然不建议把整个仓库无脑塞进输入。实际开发中,仓库可能包含几十万行代码,全部放进上下文既不经济,也容易导致注意力分散。
比较好的做法是结合一个仓库级检索层:先用符号索引或向量索引召回与当前任务相关的文件,再把这部分文件放入上下文。这样 mid-training 模型负责“在相关长上下文里精读”,检索层负责“从仓库里找相关文件”,两者分工明确。
推理侧还需要关注 KV cache 大小。如果系统同时服务多个用户,长上下文的 KV cache 会占用大量显存,可以考虑 Prefix Caching、token 级别的 prompt 压缩或分段缓存。模型上下文能力强并不等于部署时一定要用满,服务成本也要纳入设计。
7.3 可以继续延伸的方向
mid-training 在代码长上下文上的思路,可以继续向几个方向延伸。
第一,把 mid-training 和 agent 轨迹结合。代码智能体经常需要在上下文中维护“用户需求、当前文件、测试结果、历史修改”等信息,这些信息天然跨文件、跨步骤,符合 mid-training 的训练结构。
第二,把跨仓库数据和 RAG 的检索策略结合。训练数据里的文件关联关系,可以作为检索排序的监督信号,改进仓库级代码检索。
第三,在多语言混合仓库上做实验。一个仓库里可能同时有 Python 后端、TypeScript 前端、YAML 配置和 Markdown 文档,跨仓库上下文如果覆盖这些异构文件,能让模型学会在多种语言之间切换和联动。
如果打算在自己的代码模型上开始尝试,建议从一个小型仓库集和 8K 上下文开始,先建立完整的数据-训练-评测闭环,再逐步扩大仓库数量和上下文窗口。跑通闭环之后再判断,到底是数据关联度、上下文长度还是模型规模,才是限制长上下文能力的主要瓶颈。