2026年-腾讯游戏-第十一届游戏安全技术竞赛-比赛笔记

进了决赛,可惜折戟决赛,未能获得名次。

由于手头GPU不足,故采用了一些方法去减轻GPU的沉重负担,比如GBDT蒸馏LLM,比如小模型打草稿大模型来润色的思路。

也不知道决赛会不会因为使用了树模型和非完整end to end生成式训练而被“一票否决”。

初赛

任务

任务定义:构建一个生成式模型,输入20秒游戏片段的所有玩家信息,预测接下来 5s 的:

(1)主玩家决策(选择交战/避战)

(2)行为变化(离散行为:救援队友、避战后撤、开火、丢雷、靠近远离等等)

解法

我的解决方案分为4层:

  1. 日志解析与摘要压缩
    把原始 20 秒日志整理成尽量保留关键信息的摘要文本。
  2. 善于利用结构化信息的teacher(GBDT,在代码中我们使用的是Catboost)
    先训练一个传统分类器,输出 6 类动作概率分布。
  3. 生成式 student(Qwen3-8B QLoRA)
    用生成式模型学习日志到行为的映射。
  4. 蒸馏
    先分别训练teacher模型和student模型。再让 student 在 6 类候选分数上拟合 teacher 分布进行蒸馏训练。

我们会固定 6 个候选答案,并计算它们的预测概率,再选最高者。(类似于qwen3guard-stream的处理)。

数据处理与摘要策略

训练集中的原始日志包含“决策”行。代码会先找到第一条带决策含义的行,并把:

  • 决策行之前的日志作为输入
  • 决策对应的动作作为标签

测试集没有决策行,因此直接把完整 20 秒日志读入,再做摘要:

摘要构造不是简单截断,而是有结构地保留关键信息。summarize_lines 的主要策略是:

  1. 保留开局玩家信息
  2. 保留非基础事件
  3. 对“玩家基础信息”按关键时间点抽样
    • 默认关键时刻:0, 5, 10, 15, 18, 19, 20
  4. 如果事件过多,对中间做省略
  5. 如果整体字符数过长,再做头尾保留的中间裁剪

最终摘要会尽量保住:

  • 开局阵容
  • 关键事件
  • 关键时间点的状态变化
  • 后段临近决策时刻的局势

这种处理方式的目的,是让长日志尽量转成对行为预测有判别力的文本。

生成式模型的基本预测方式

我们一开始是打算采取让模型直接生成预测。但是这样出现两个问题:

  1. 输出格式不稳定。这样会导致我们对答案的正确读取的难度也会加深。
  2. 生成出不在 6 类里的动作表达。

所以当前方法把输出空间收缩成 6 个固定候选JSON。

每个动作都对应一个固定 JSON:

1
{"意图决策":"交战/避战","动作行为":"具体动作"}

对每条输入摘要,模型不会只生成一个答案,而是对 6 个候选答案分别计算:

  • 条件对数概率
  • 再按目标 token 数做平均

最后取平均分最高的动作作为预测结果。

这一步的好处:

  • 输出严格受控
  • 任务从“自由生成”变成“受控的生成式分类生成”
  • 能直接得到6类分数,便于后续蒸馏、校准和分析

蒸馏方案的详细介绍

为了快速训练和引入善于利用结构化数据的模型,我们选用GBDT作为我们的teacher模型。

当前蒸馏使用的是:

  • teacher:GBDT 输出的 6 类动作概率分布
  • student:Qwen3-8B 量化基座 + LoRA adapter

二者之间不是通过中间文本传信息,而是通过6类分数分布对齐。

GBDT模型使用了以下特征进行初始训练。
(1). 文本特征
对 summary 做:

  • HashingVectorizer
    • analyzer=’char’
    • ngram_range=(2,4)
    • n_features=16000
    • alternate_sign=False
    • norm=None
  • 再接 TfidfTransformer(sublinear_tf=True)
  • 再接 TruncatedSVD
    • n_components=160
    • n_iter=8

(2). 数值统计特征
也是从 summary 里直接算的,不用原始结构化状态表。

具体包括:

  • summary_char_len
  • summary_line_count

10 个短语计数:

  • 玩家造成伤害
  • 技能生效
  • 换弹
  • 标点
  • 濒死掉血(第1次倒地)
  • 濒死掉血(第2次倒地)
  • 濒死掉血抑制
  • 快速救援
  • 救援中的标记
  • 救援后的标记

3 个比例特征:

  • ratio::combat
    • (玩家造成伤害 + 技能生效 + 换弹) / line_count
  • ratio::avoid
    • (3个倒地相关 + 3个救援相关) / line_count
  • ratio::marker
    • 标点 / line_count

所以数值特征总数是:

  • 2 + 10 + 3 = 15 维

最终给 GBDT 的输入是160 维文本 SVD特征 + 15 维数值特征 = 175 维

另外,模型结构不是单个6分类,而是两阶段:

  • intent_model:交战 / 避战
  • combat_model:交战内4分类
  • avoid_model:避战内2分类

最终 6 类分数是: log P(intent) + log P(action | intent)

为了模型具有很好的生成能力和判别能力,我们并不直接进行蒸馏。

而是先使用QLORA从头训练一个大模型作为初始LoRA权重,再继续训练LoRA参数做蒸馏。

对每个样本,student会输出6个候选动作分数。teacher则给出6类概率分布。

训练目标分成两部分:

  1. 监督分类损失

    • CE(student_scores, gold_action)
    • 让 student 对真实标签有区分能力
  2. teacher 蒸馏损失

    • KL(student_dist, teacher_probs_6)
    • 让 student 的分布尽量接近 teacher。

总损失定义为:

1
loss = 0.3 * CE + 0.7 * KL

为什么不直接把GBDT的输出拼接到提示词上

之前也做过“把 GBDT top3 候选和意图写进 prompt”的方案,比纯大模型要好,但是提升较小。原因可能是:

  • 模型仍然可能忽略提示,继续沿用旧边界
  • 文字先验和最终评测目标之间还有一层间接映射

当前蒸馏是直接在6类候选分数上对齐,更直接,也更符合最终预测机制。

不过由于比赛匆忙,并没有来得及计算的GBDT蒸馏大模型的评估分数。

决赛

任务

任务定义:构根据前 20 秒游戏日志,续写主玩家接下来 5 秒的行为文本。

最终不是输出分类标签,而是输出自然语言续写。因此整个方案不能只做动作分类,还必须解决两个问题:

  1. 结构正确:主决策、核心动作、后 5 秒阶段行为不能跑偏。
  2. 文本可读:最终续写要尽量自然,且接近标注文本的表达。

同时,面临任务的加重,初赛所使用的直接微调qwen会产生训练成本过重的问题。

围绕比赛目标和实际手头上的资源,最终形成了三层结构:

  1. GBDT:提供稳定的结构先验。
  2. mT5-large / mT5-xl:把结构先验写成三行结构化续写。
  3. Qwen-editor:在不重新理解全量长日志的前提下,对 mT5 草稿做双草稿融合与润色。

本来还花了一天来训练了一个byT5,可惜没来得及。

解法

整体方案演化

最初

最早的决赛赛主线是想类似初赛的解题方案,直接用 Qwen3-8B 做结构化续写,输出:

1
2
3
主决策:...
核心动作:...
续写:主玩家先……,随后……,最后……。

这条线在结果上是可用的,但有两个硬问题:

  1. decoder-only 模型的输入和输出共用长度预算。
  2. 决赛 prompt 太长,response-only 训练下会导致样本被删。

后续对缩短版 Qwen prompt 做了全训练集长度审计,结果是:

  • 训练集总数:88516
  • 2048 下可保留样本:44292
  • kept_ratio = 0.5004

也就是说,即便已经把 prompt 改成 short 版,2048 下仍然会损失约一半训练样本。

这说明:初赛所使用的Qwen适合做短编辑任务,不适合继续扛长上下文主生成任务。

对应的基础提示词形态是:

系统提示词:

1
2
3
4
5
6
你是游戏安全竞赛的行为续写模型。根据给定的20秒游戏日志摘要,续写主玩家接下来5秒的行为。
你必须严格输出三行:主决策、核心动作、续写。
主决策只能是交战或避战。
核心动作只能从六类中选择:开火、开镜、放技能、丢雷、搜物资、救援队友。
续写必须使用“主玩家先……,随后……,最后……。”的格式。
不要输出额外解释。

用户提示词模板:

1
2
3
4
5
6
7
8
9
10
请根据下面的前20秒游戏日志,续写主玩家接下来5秒的行为。
主玩家:玩家xxxx
核心动作只能从以下六类中选择:开火、开镜、放技能、丢雷、搜物资、救援队友。
请严格输出三行:
主决策:<交战或避战>
核心动作:<六类动作之一>
续写:主玩家先……,随后……,最后……。

日志摘要:
...
转向 GBDT + mT5

为了把“结构预测”和“自然语言生成”拆开,方案转成两阶段:

  1. GBDT 先做结构预测
  2. mT5 再做结构化生成

这样做的核心收益是:

  • GBDT 提供动作与阶段子句先验,结构稳定
  • mT5encoder-decoder,输入输出长度分开,不会像 Qwen 一样因为 prompt 过长把答案挤掉

GBDT + mT5 基础上,继续观察到:

  • GBDT 文本很像模板,ROUGE 高,但不够自然
  • mT5 自然度更高,但仍会出现阶段偏差、表达僵硬或者 draft 间不一致

因此最终把 Qwen 改为 编辑器,而不是主生成器:

  • 输入:V1 先验 + mT5-large 草稿 + mT5-xl 草稿 + 主玩家近 2 秒焦点
  • 输出:一行最终续写

这样做有两个决定性好处:

  1. prompt 很短,2048 够用
  2. Qwen 被用在它更擅长的部分:中文融合、润色、句子组织

GBDT 结构先验层

GBDT 不直接生成最终文本,而是预测:

  • pred_action
  • pred_intent
  • pred_clause1
  • pred_clause2
  • pred_clause3

其中:

  • pred_action 为 6 分类:
    • 开火 / 开镜 / 放技能 / 丢雷 / 搜物资 / 救援队友
  • pred_intent 由动作映射:
    • 开火 / 开镜 / 放技能 / 丢雷 -> 交战
    • 搜物资 / 救援队友 -> 避战

最终模板化输出为:

1
2
3
主决策:{intent}
核心动作:{action}
续写:主玩家先{clause1},随后{clause2},最后{clause3}。

训练样本来源于决赛训练集,按决策行切分:

  • summary:前 20 秒摘要
  • focus_text:主玩家近 2 秒相关事件和状态
  • target_text:根据未来 5 秒规则生成的三行结构化目标

同时还会从 target_text 中拆出三段子句标签:

  • clause1_label
  • clause2_label
  • clause3_label

GBDT 用的是文本特征 + 数值特征混合方案。

文本特征:

  • summary
    • char 2-4 gram
    • HashingVectorizer + TF-IDF + SVD(160)
  • focus_text
    • char 2-4 gram
    • HashingVectorizer + TF-IDF + SVD(64)

数值特征包括:

  • summary_char_len
  • focus_char_len
  • summary_line_count
  • focus_line_count
  • 事件计数:
    • 玩家造成伤害
    • 技能生效
    • 换弹
    • 标点
    • 倒地 / 救援相关事件
  • focus_text 中主玩家动作/伤害/技能计数
  • scope 开镜/关镜计数
  • 主玩家状态位移量
  • 最后一帧是否处于开镜状态

框架:CatBoostClassifier

固定配置:

  • task_type = GPU
  • devices = 0:1(在 CUDA_VISIBLE_DEVICES=1,2 下对应双卡)
  • iterations = 1200
  • learning_rate = 0.05
  • depth = 8
  • l2_leaf_reg = 10
  • early_stopping_rounds = 100

action_model 先训练;随后把 action probs 拼到特征后面,再分别训练:

  • clause1_label_model
  • clause2_label_model
  • clause3_label_model

结果

训练耗时:

  • runtime_seconds = 1531.64
  • 25.5 分钟

验证结果:

Split action intent rouge_l
val_natural 0.8650 0.9755 0.8001
val_balanced 0.7733 0.9783 0.7758
ratio_val 0.8583 0.9700 0.8051

结论:

  • V1 的动作准确率不一定最好
  • ROUGE-L 最强
  • 说明结构模板非常稳定,是后续所有生成模型的可靠先验

V3:mT5-large / mT5-xl 结构化生成层

V3 不是直接看原始长日志,而是看:

  • V1主决策先验
  • V1核心动作 top3
  • V1阶段1/2/3先验
  • focus_text
  • 压缩后的 summary

输入模板如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
任务: 根据前20秒日志信息,生成主玩家后5秒结构化续写。

主玩家: 玩家xxxx
V1主决策先验: 交战/避战
V1核心动作候选1: ...
V1核心动作候选2: ...
V1核心动作候选3: ...
V1阶段1先验: ...
V1阶段2先验: ...
V1阶段3先验: ...

主玩家近2秒:
...

短摘要:
...

输出格式:
主决策:<交战或避战>
核心动作:<六类动作之一>
续写:主玩家先……,随后……,最后……。

目标文本固定是三行结构化文本。

训练配置

  • max_source_length = 1024
  • max_target_length = 128
  • num_train_epochs = 1
  • per_device_train_batch_size = 2
  • per_device_eval_batch_size = 2
  • gradient_accumulation_steps = 8
  • learning_rate = 1e-4
  • weight_decay = 0.01
  • bf16 = True
  • gradient_checkpointing = True

LoRA:

  • r = 16
  • alpha = 32
  • dropout = 0.05
  • target_modules = ["q", "v"]

训练结果:

  • train_runtime = 15518.79s
  • 4.31h
  • train_loss = 15.2807

相比 mT5-largemT5-xl 参数更多,配置更保守:

  • per_device_train_batch_size = 1
  • gradient_accumulation_steps = 16
  • 其它配置尽量与 large 保持一致

从已保存 checkpoint-2500 看,训练稳定进行到:

  • global_step = 2500
  • epoch ≈ 0.904

Qwen-Editor:双草稿融合层

系统提示词:

1
你是游戏行为续写编辑器。你会基于结构先验和两条候选续写,输出唯一一行最终续写。不要输出主决策、核心动作、解释或多余文本。

用户提示词模板:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
主玩家:玩家xxxx
V1主决策先验:交战/避战
V1核心动作候选1:...
V1核心动作候选2:...
V1核心动作候选3:...
V1阶段1先验:...
V1阶段2先验:...
V1阶段3先验:...

主玩家近2秒焦点:
...

mT5-large草稿:
...

mT5-xl草稿:
...

任务:综合两条草稿,输出一行更自然且更贴合先验的后5秒续写。
要求:
1. 只输出一行最终续写。
2. 必须保持“主玩家先……,随后……,最后……。”格式。
3. 不要输出主决策、核心动作、解释或多余文本。
4. 不要偏离V1先验的核心语义。

对 dual-draft editor prompt 做过长度检查。结论是:

  • 2048 基本够用
  • 少量样本会超预算

因此实际推理时采用:

  • max_seq_length = 2048
  • max_new_tokens = 128
  • 只对 focus_text 做裁剪
  • 不裁:
    • V1 priors
    • mT5-large/xl 草稿

最终 non-ICL dual-draft 结果里:

  • prompt_over_budget_before_fit_count = 3
  • prompt_over_budget_after_fit_count = 0

做过的失败尝试与挫折

失败一:把 Qwen 当主生成器

问题:

  • decoder-only
  • response-only
  • 决赛 prompt 太长

结果:

  • 2048 下训练集保留率只有 50.04%

结论:

  • 不再让 Qwen 直接扛“长上下文理解 + 主生成”
  • 转成 editor
失败二:Qwen-editor 做 1-shot retrieval ICL

实现方式:

  • val_balanced 做支持样本
  • V1 action / clause / draft 相似度 检索 1 条示例
  • 在 prompt 里加入 one-shot ICL

结果:

  • non-ICL dual-draft:0.8045
  • 1-shot ICL dual-draft:0.7792

也就是说:

  • ICL 仍优于 mt5-large/xl 基线
  • 但明显差于 non-ICL

结论:

  • 当前这题上,Qwen-editor 更适合短而硬的约束 prompt
  • 不适合在 2048 下继续加示例
失败三:尝试训练版 Qwen-editor

训练版 Qwen-editor 的思路是:

  1. 先对 train 全量生成 mT5-large/xl teacher 草稿
  2. 再训练 Qwen-editor 去学习融合这两条草稿

但这条线在工程上遇到了明显瓶颈:

  • train = 88516
  • teacher drafts 需要逐条生成
  • mT5-largemT5-xl 都要跑

估算进入 Qwen 正式训练前,teacher 生成就要数十小时到数天。

因此这条线最终没有跑成正式主线,只完成了:

  • teacher draft smoke
  • editor 数据集 smoke
  • Qwen-editor 训练 smoke

结论:

  • 在时间有限的条件下,不值得为 train 全量草稿付出这种代价
  • inference-only dual-draft editor 性价比更高

总结

模型 rouge_l_on_xuxie
mT5-large 0.7648
mT5-xl 0.7647
Qwen-editor(non-ICL, dual-draft) 0.8045
Qwen-editor(ICL, dual-draft) 0.7792

结论:

  • 当前最优不是单个 mT5
  • 而是:V1 GBDT 约束 + mT5-large/xl 双草稿 + non-ICL Qwen-editor`
graph TD

classDef largeModel fill:#e8f0fe,stroke:#448aff,stroke-width:2px,rx:10,ry:10,font-family:Arial, sans-serif;
classDef xlModel fill:#fff8e1,stroke:#f57c00,stroke-width:2px,rx:10,ry:10,font-family:Arial, sans-serif;
classDef rewardModel fill:#e0d6fa,stroke:#7c4dff,stroke-width:2px,rx:10,ry:10,font-family:Arial, sans-serif;
classDef ensembleModel fill:#e0f2f1,stroke:#009688,stroke-width:2px,rx:10,ry:10,font-family:Arial, sans-serif;


Large["<div style='color:black; text-align:center;'>1x ByT5-Large<br/>0.7648</div>"]:::largeModel
XL["<div style='color:black; text-align:center;'>1x ByT5-XL<br/>0.7647</div>"]:::xlModel
Reward["<div style='color:black; text-align:center;'><b>Pairwise Reward Model</b><br/><span style='color:grey'>Qwen3-8B</span>"]:::rewardModel
Ensemble["<div style='color:#009688; text-align:center;'><b>ENSEMBLE</b><br/>0.8045</div>"]:::ensembleModel


subgraph Top
Large ~~~ XL
end
style Top fill:none,stroke:none; 


Large --> Reward
XL --> Reward
Reward --> Ensemble


linkStyle default stroke:#999,stroke-width:2px;

2026年-腾讯游戏-第十一届游戏安全技术竞赛-比赛笔记
https://lijianxiong.space/2026/20260424/
作者
LJX
发布于
2026年4月24日
许可协议