Post

HKUDS026: GraphGPT 作为 Graph Instruction Tuning 与 Graph-Language Alignment Layer

HKUDS026: GraphGPT 作为 Graph Instruction Tuning 与 Graph-Language Alignment Layer

这是 PENGYI_HKUDS_STUDYMAP 的第二十七篇。

1
HKUDS026 -> GraphGPT

图智能主线已经连续三篇:

1
2
3
HKUDS024 GraphAgent -> agentic graph language assistant
HKUDS025 OpenGraph  -> open graph foundation model
HKUDS026 GraphGPT   -> graph instruction tuning for LLMs

这三篇的关系很清楚。

GraphAgent 关注:

1
agent 如何规划任务、构图、调用 graph-language model。

OpenGraph 关注:

1
graph model 本身如何跨图 zero-shot generalize。

GraphGPT 关注:

1
如何把图结构知识对齐到 LLM 的语言空间,并通过 instruction tuning 让 LLM 能做图任务。

也就是说,GraphGPT 是这条线里的 graph-language alignment layer。

对我们来说,这篇非常关键,因为未来的 Research OS / Quant OS 不只是要把图存起来,也不只是要让图模型给 embedding,而是要让 LLM 能以自然语言方式理解和解释图任务:

1
2
3
4
5
这篇论文属于哪个方向?
这两个节点之间是否应该有边?
这个因子为什么可能失效?
这个事件会沿着哪些资产/行业关系传播?
这个 project 和我们的 Quant OS 哪个模块最相关?

GraphGPT 的目标就是:

1
让 LLM 通过图 instruction tuning 学会处理 graph-structured data。

Local Snapshot

这次阅读的是本地 HKUDS 工作区里的 GraphGPT

ItemValue
repoGraphGPT
remotehttps://github.com/HKUDS/GraphGPT.git
branchmain
local headdb25a66
full commitdb25a66fd23b861156e6d7324f9ee8bc91c6ce7c
latest local commit date2024-06-25 21:27:00 +0800
latest local commitUpdate README.md
statusclean, synced with origin/main after fetch
paperGraphGPT: Graph Instruction Tuning for Large Language Models, SIGIR 2024 full paper
authorsJiabin Tang, Yuhao Yang, Wei Wei, Lei Shi, Suqi Cheng, Dawei Yin, Chao Huang
tracked files by git ls-files111
Python files76
markdown files4
YAML files3
JSON files2
shell scripts7
main foldersgraphgpt, text-graph-grounding, scripts, playground, tests, assets, images
main modelGraphLlamaForCausalLM
graph encoder choicesMPNN, clip_gcn_arxiv, clip_gt, clip_gt_arxiv, clip_gt_arxiv_pub
base LLMVicuna 7B v1.1 / v1.5 in README examples
released checkpointJiabin99/GraphGPT-7B-mix-all
released graph encoderJiabin99/Arxiv-PubMed-GraphCLIP-GT
released dataArxiv-PubMed-mix-NC-LP, GraphGPT-eval-instruction, All_pyg_graph_data, graph-matching
validationpy -m compileall -q graphgpt text-graph-grounding scripts tests playground passed
runtime checknot run locally because full run needs Vicuna checkpoint, graph data, graph encoder checkpoint, CUDA, and large dependencies

一句话先行:

1
2
3
4
GraphGPT 把 LLaVA-style multimodal alignment 从 image-language 迁移到 graph-language:
graph encoder 负责把子图编码成 graph tokens,
graph projector 把 graph tokens 对齐到 LLM hidden size,
instruction tuning 让 Vicuna 学会用自然语言完成 node classification / link prediction 等图任务。

它的关键词是:

1
2
3
4
5
text-graph grounding
dual-stage graph instruction tuning
CoT distillation
GraphLlama
graph tokens

它解决什么问题

普通 LLM 很擅长文本,但图任务有两个问题:

1
2
1. 图结构不是自然语言序列。
2. 图任务需要利用 node / edge / neighborhood / topology。

如果只把图转成一段文本描述,LLM 会遇到几个限制:

1
2
3
4
子图太大,prompt 很快爆上下文。
拓扑关系容易丢。
node features 和 edge_index 很难自然表达。
link prediction / node classification 很难只靠文字稳定完成。

GraphGPT 的思路是:

1
2
3
4
不要只把 graph 描述成文字。
先用 graph encoder 得到 graph node embeddings,
再通过 graph projector 注入 LLM embedding space,
最后用 instruction tuning 教 LLM 如何回答图任务。

这和 LLaVA 很像:

1
2
3
4
5
LLaVA:
  image encoder -> visual tokens -> LLM

GraphGPT:
  graph encoder -> graph tokens -> LLM

它的核心价值是把 graph structure 从外部数据变成 LLM 可消费的内部 token。

总体结构

GraphGPT 可以拆成六层:

LayerCodeRole
Text-Graph Groundingtext-graph-grounding训练图编码器,让 graph embedding 对齐文本语义
Graph Encodergraphgpt/model/graph_layersMPNN / GCN / graph transformer,对子图编码
GraphLlamagraphgpt/model/GraphLlama.pyLlama/Vicuna + graph tower + graph projector
Data Pipelinegraphgpt/train/train_graph.py从 instruction JSON 加载子图、展开 graph tokens
Two-Stage Tuningscripts/tune_script / scripts/tune_script_lightstage 1 graph matching,stage 2 NC/LP 任务调优
Evaluation / Servinggraphgpt/eval, graphgpt/serve评估 NC/LP,提供 CLI/Web/API serving

主链路可以写成:

1
2
3
4
5
6
7
8
9
10
instruction JSON
  -> read graph field
  -> sample subgraph node_list and edge_index
  -> load node features from all_graph_data.pt
  -> Data(graph_node, edge_index, target_node)
  -> graph encoder
  -> graph_projector
  -> <g_start> <g_patch> ... <g_end>
  -> Vicuna / GraphLlama
  -> natural-language answer

其中 graph patch token 是:

1
2
3
4
<graph>
<g_patch>
<g_start>
<g_end>

<graph> 会根据子图节点数展开成一串 <g_patch>,如果启用 start/end,则变成:

1
<g_start><g_patch><g_patch>...<g_end>

然后模型 forward 时,用 graph embeddings 替换这些 token 位置。

Component 1: Text-Graph Grounding

目录:

1
text-graph-grounding

这是 GraphGPT 的底层对齐步骤。

它做的事情类似 CLIP:

1
2
3
graph encoder output
  <-> node text embedding
  <-> neighbor text embedding

训练代码在:

1
text-graph-grounding/main_train.py

核心 loss 有三项:

1
2
3
node_loss: graph feature vs source node text
gt_loss:   graph feature vs target neighbor text
tt_loss:   source node text vs target neighbor text

总 loss:

1
all_loss = node_loss + edge_coef * gt_loss + edge_coef * tt_loss

这一步的意义是:

1
让 graph encoder 输出的向量和自然语言语义处在可对齐的空间。

如果没有这一步,graph encoder 可能只懂拓扑,不懂文本语义;LLM 可能只懂文本,不懂拓扑。Text-graph grounding 是桥。

对我们来说,这很关键。因为 Research OS / Quant OS 里的节点不是纯 ID,而是带文本语义的:

1
2
3
4
5
6
7
paper title / abstract
repo README
factor formula
backtest diagnosis
event description
asset business description
risk explanation

所以未来如果做自己的 graph-language layer,也需要类似:

1
node text <-> graph neighborhood <-> task label

的对齐。

Component 2: Graph Encoder

GraphGPT 支持多种 graph tower:

1
2
3
4
5
MPNN
clip_gcn_arxiv
clip_gt
clip_gt_arxiv
clip_gt_arxiv_pub

主要代码在:

1
graphgpt/model/graph_layers

其中 clip_graph.py 里有 CLIP-style text-graph model,graph_transformer.py 里有 graph transformer。

graph_transformer 的输入是 PyG-style graph:

1
2
3
4
5
Data(
  graph_node = node_features,
  edge_index = edge_index,
  target_node = target_node
)

它先把 node features 投影到 attention dimension:

1
W_P: gnn_input -> att_d_model

再经过若干 GTLayerGTLayer 在图边上做 attention:

1
2
3
4
5
6
7
rows, cols = g.edge_index
q = row embeddings
k = col embeddings
v = col embeddings
edge-wise attention
aggregate back to row nodes
residual + layer norm

最后再投回:

1
inverW_P: att_d_model -> gnn_output

这和 OpenGraph 的 graph transformer 不完全一样。OpenGraph 更关注跨图泛化和 anchor-sampled global attention;GraphGPT 这里更关注把 graph encoder 作为 LLM 的 graph tower,服务 instruction tuning。

Component 3: GraphLlama

核心文件:

1
graphgpt/model/GraphLlama.py

关键类:

1
2
3
GraphLlamaConfig
GraphLlamaModel
GraphLlamaForCausalLM

它的结构是:

1
2
3
4
LlamaModel
  + graph_tower
  + graph_projector
  + graph special tokens

GraphLlamaModel.forward 的核心逻辑:

1
2
3
4
5
6
1. input_ids 先变成 text embeddings。
2. graph_tower 对 graph_data 编码,得到 node-level graph features。
3. graph_projector 把 graph features 投到 LLM hidden size。
4. 在 input_ids 中找到 <g_patch> 或 <g_start>/<g_end> 的位置。
5. 用 graph features 替换这些 token embeddings。
6. 送入 Llama decoder。

也就是说,它不是把图写成一段 prompt,而是真的把图向量插入 LLM 的 embedding 序列。

这一步就是 GraphGPT 的核心工程点。

用一句话概括:

1
GraphGPT 把 graph encoder 输出伪装成 LLM 能读的连续 token。

这也是它和纯文本 graph prompting 的区别。

Component 4: Instruction Data Pipeline

训练数据格式大致是:

1
2
3
4
5
6
7
8
9
10
11
12
{
  "id": "dataset_split_nodeidx_tasktype",
  "graph": {
    "node_idx": 0,
    "edge_index": [[...], [...]],
    "node_list": [...]
  },
  "conversations": [
    {"from": "human", "value": "Given a citation graph: <graph> ..."},
    {"from": "gpt", "value": "..."}
  ]
}

如果是 link prediction,数据里会有两个图:

1
2
3
4
5
6
7
edge_index_1
node_list_1
node_idx_1

edge_index_2
node_list_2
node_idx_2

训练时:

1
2
graph_type = id.split("_")[0]
graph_node_rep = graph_data_all[graph_type].x[node_list]

然后构造:

1
Data(graph_node=graph_node_rep, edge_index=edge_index, target_node=target_node)

如果是 LP,则构造:

1
2
3
4
{
  "graph_1": Data(...),
  "graph_2": Data(...)
}

这说明 GraphGPT 的 instruction data 不是纯 JSON 文本,而是:

1
instruction text + graph pointer + graph tensor store

这对我们很有启发。未来我们自己的 Research OS 也可以这样组织:

1
2
3
4
5
6
7
8
9
10
11
12
instruction:
  "判断这个 factor 是否属于 momentum family,并解释原因"

graph pointer:
  factor node id
  local factor-neighborhood edge list
  related backtest / risk / data source nodes

tensor store:
  factor features
  node text embeddings
  graph embeddings

这样自然语言任务和结构化图数据就能连起来。

Component 5: Dual-Stage Graph Instruction Tuning

GraphGPT 的 tuning paradigm 有两阶段。

Stage 1:

1
self-supervised instruction tuning

README 里用的是 graph matching:

1
graph_matching.json

训练脚本:

1
scripts/tune_script/graphgpt_stage1.sh

主要目标是训练:

1
2
graph_projector
special graph token embeddings

让 LLM 初步学会把 graph tokens 和文本任务对齐。

Stage 2:

1
task-specific instruction tuning

数据包括:

1
2
3
4
node classification
link prediction
mixing data for multitasking
CoT instruction data

训练脚本:

1
scripts/tune_script/graphgpt_stage2.sh

Stage 2 会加载 Stage 1 提取出来的 projector:

1
pretrain_graph_mlp_adapter

并在具体任务上继续 tuning。

这个设计很像:

1
2
Stage 1: learn modality alignment
Stage 2: learn task behavior

对我们未来做 Quant GraphGPT 也很自然:

1
2
3
4
5
6
7
8
9
Stage 1:
  factor / paper / repo / asset graph matching

Stage 2:
  factor family classification
  factor failure diagnosis
  asset-event link prediction
  next experiment recommendation
  portfolio risk explanation

Component 6: CoT Distillation

README 特别提到:

1
Chain-of-Thought (CoT) Distillation

原因是 graph data 有 distribution shift:

1
2
3
4
不同图的类别数不同
结构模式不同
节点语义不同
任务形式不同

如果模型只输出答案,很容易不稳。CoT 的作用是让模型生成:

1
step-by-step reasoning

这对图任务尤其重要,因为图任务的正确答案通常需要解释:

1
2
3
4
为什么这个节点属于某个类别?
为什么这两个节点应该连接?
哪几个邻居支持这个判断?
哪些结构证据和文本证据共同导致结论?

这和我们的 PM review / human audit 非常接近。

Quant Research OS 里也不应该只输出:

1
2
3
buy / sell
factor good / bad
class = momentum

而要输出:

1
2
3
4
5
evidence
reasoning path
risk caveat
failure mode
next validation

GraphGPT 的 CoT distillation 给了这个方向。

Evaluation 与 Serving

评估代码在:

1
graphgpt/eval

主要包括:

1
2
3
4
run_graphgpt.py
run_graphgpt_LP.py
run_vicuna.py
cal_metric_arxiv.py

评估脚本会:

1
2
3
4
5
6
7
1. 读取 eval instruction JSON。
2. 根据 instruction 里的 graph 字段加载子图。
3. 把 <graph> 替换成 graph patch tokens。
4. 加载 GraphLlamaForCausalLM。
5. 手动加载 graph tower。
6. generate 输出。
7. 写入 JSON 结果。

Serving 代码在:

1
graphgpt/serve

它大量继承 FastChat/LLaVA 风格,包含:

1
2
3
4
5
6
7
controller
model_worker
openai_api_server
gradio web server
CLI
gateway
monitor

这说明 GraphGPT 不只是离线训练代码,也有部署和 demo 形态。

不过从本地阅读看,真正最关键的还是:

1
graph instruction tuning pipeline

Serving 层更多是沿用 FastChat 的服务框架。

和 GraphAgent / OpenGraph 的区别

三者可以这样分工:

Project更像什么关键问题
GraphAgentagentic graph-language assistantagent 如何规划、构图、执行 graph task?
OpenGraphgraph foundation modelgraph model 如何跨图 zero-shot generalize?
GraphGPTgraph instruction tuningLLM 如何通过 instruction tuning 学会图任务?

如果组合成一个系统:

1
2
3
4
5
6
7
8
GraphAgent:
  任务规划和图生成

OpenGraph:
  更开放的图表示和跨图泛化

GraphGPT:
  图结构和自然语言任务对齐

对我们的 Research OS / Quant OS,三者可以对应:

1
2
3
4
5
6
7
8
Planner:
  GraphAgent-style task planning

Graph Encoder:
  OpenGraph-style graph representation

LLM Interface:
  GraphGPT-style instruction tuning and explanation

这是非常完整的一条路线。

对 Pengyi Research OS 的意义

Research OS 需要把大量研究对象组织成图:

1
2
3
4
5
6
7
8
9
10
paper
repo
method
dataset
benchmark
author
lab
project
PR opportunity
research question

但只存图还不够。我们需要能问:

1
2
3
4
5
这个 repo 应该归到哪条研究主线?
这个 paper 和哪个 project 最相关?
这个 idea 应该连接哪些已有技术?
这个 PR opportunity 是否值得做?
这个 project 能否支撑 RA / PhD application narrative?

GraphGPT 给我们的启发是:

1
把这些问题做成 graph instruction data。

例如:

1
2
3
4
5
{
  "instruction": "Given a research project graph: <graph>, classify the target repo into one research track and explain why.",
  "graph": "local neighborhood around repo node",
  "answer": "Graph / Knowledge Graph, because ..."
}

这就能把我们现在写的 HKUDS 学习地图变成训练数据源。

也就是说,我们的网站文章不只是输出成果,还可以反过来成为:

1
Research OS graph instruction dataset

对 Pengyi Quant Research OS 的意义

Quant OS 更适合做 graph instruction tuning。

可以构造很多任务:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Node classification:
  给一个 factor node,判断它属于哪个 factor family。

Link prediction:
  给两个 asset / event / factor nodes,判断是否存在有效关系。

Failure diagnosis:
  给一个 backtest-neighborhood graph,解释 alpha 为什么失效。

Risk explanation:
  给一个 portfolio exposure graph,解释主要风险来源。

Next experiment planning:
  给一个 factor research graph,生成下一轮实验计划。

这些任务都可以写成:

1
2
Given a quant research graph: <graph>
...

然后让模型输出:

1
2
3
4
5
classification
prediction
diagnosis
reasoning
next action

这和我们一直说的 R&D Agent 完全接上:

1
2
3
4
5
6
自动提出因子假设
自动实现
自动回测
自动诊断偏差
自动生成下一轮研究计划
人类 PM 审核

GraphGPT 对应的是:

1
让 LLM 看懂 factor / asset / event / backtest graph,并能用语言解释和规划。

我们怎么吸收

短期不复刻 GraphGPT-7B。成本太高。

我们可以做轻量版:

1
2
3
4
5
1. 把 HKUDS / LLMQuant 学习文章抽成 graph JSON。
2. 为每个 graph neighborhood 生成 instruction-answer pairs。
3. 先用普通 LLM 做 graph-to-text prompt,不训练模型。
4. 做一个 small benchmark:project classification / relation prediction / PR opportunity ranking。
5. 积累足够数据后,再考虑 LoRA / projector / graph encoder。

第一版甚至可以不用 graph tokens,只做:

1
graph serialized as JSON / edge list / path list

但数据格式要按 GraphGPT 的思路设计:

1
instruction + graph pointer + answer + reasoning

这样以后可以平滑升级到真正的 graph-token model。

最重要的是:

1
从现在开始,我们写的每篇学习笔记都可以成为 future graph instruction data。

可以提 PR 的地方

GraphGPT 目前有一些具体、可落地的小改进点。

第一,README 的 code structure 里出现了:

1
pyproject.toml

但本地仓库没有这个文件。可以修正文档,或者补一个最小 pyproject.toml

第二,requirements.txt 里包含一些 OS/Ubuntu 绑定包:

1
2
3
python-apt==1.6.5+ubuntu0.7
PyGObject==3.26.1
unattended-upgrades==0.1

这些在普通 pip 环境里不一定能安装。可以拆成核心 requirements 和 platform-specific notes。

第三,README 和 scripts/tune_script/graphgpt_stage2.sh 里有一行:

1
--use_graph_start_end True\

True 和反斜杠之间缺少空格,shell 里可能会把它拼成异常参数。应该改成:

1
--use_graph_start_end True \

第四,评估脚本里硬编码了 graph tower 路径:

1
load_model_pretrained(CLIP, './clip_gt_arxiv_pub')

更合理的是加一个 CLI 参数:

1
--graph_tower_path

第五,评估和模型代码里大量直接 .cuda(),比如:

1
2
3
model.cuda()
input_ids.cuda()
graph_data.cuda()

这会限制 CPU/MPS/多 GPU device mapping。可以统一用 device 参数。

第六,GraphLlama_pl.py 里疑似有变量名问题:

1
2
target_modules=find_all_linear_names(model)
model = get_peft_model(model, lora_config)

但这个作用域里主要对象是 self.model,不是 model。如果启用 LoRA,可能触发错误。需要实际验证后提 PR。

第七,GraphLlama_pl.py 里:

1
self.model.config.pretrain_graph_model_path = self.model.config.pretrain_graph_model_path + model_args.graph_tower

这依赖 base config 里已经有 pretrain_graph_model_path,README FAQ 也提到过这个问题。更稳妥的是显式传参或检查属性存在。

第八,run_graphgpt.py / run_graphgpt_LP.py 中每个 worker 都在函数内部加载 graph_data_all = torch.load(graph_data_path),而 load_graph 每次调用都会加载一次。可以把 graph data 缓存在 worker 级别,避免重复 IO。

第九,run_graphgpt_LP.py 结果写入时总是使用:

1
2
"node_idx_1"
"node_idx_2"

但如果不是 LP 路径,可能和数据结构不完全匹配。代码里虽然按 task_type 分支加载图,但最后 append 逻辑没有完全分支化,值得检查。

这些都适合从 docs / scripts / eval ergonomics 入手,不需要碰核心算法。

和我们的主线连接

GraphGPT 对我们的最大启发是:

1
2
未来的 Quant Research OS 不能只让 LLM 读文本。
它应该让 LLM 读 graph-conditioned instruction。

这会把我们的研究系统从:

1
chat with notes

推进到:

1
chat with structured research graph

再推进到:

1
train / tune agent on graph-grounded research tasks

完整路线可以是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Stage 1: Graph Memory
  把文章、repo、factor、backtest 抽成 graph。

Stage 2: Graph Instruction Dataset
  为 graph neighborhood 生成 question-answer-reasoning pairs。

Stage 3: Graph-to-Text Reasoning
  用普通 LLM 做 graph prompt + answer。

Stage 4: Graph Token Alignment
  用 GraphGPT-style projector / graph tower 对齐 LLM。

Stage 5: Quant GraphGPT
  专门回答因子、事件、风险、回测诊断、下一步计划。

这条路非常长,但方向是对的。

下一步

图智能主线剩下:

1
HKUDS027 -> HiGPT

这会补上 heterogeneous graph intelligence。

目前三篇可以组成一个小闭环:

1
2
3
GraphAgent: graph agent workflow
OpenGraph:  graph foundation representation
GraphGPT:   graph-language instruction tuning

接下来 HiGPT 可以回答:

1
heterogeneous graph 怎么进入 LLM / graph intelligence?

这正好连接 QuantMind / factor graph / Research OS memory layer。

This post is licensed under CC BY 4.0 by the author.