migrate-max-v2-to-v3
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseMigrate a MAX model from V2 to ModuleV3
将 MAX 模型从 V2 迁移到 ModuleV3
Input: the V2 model source to migrate ().
$ARGUMENTSModuleV3 () is the current MAX model API. A V2 model
builds an explicit over values with layers from
; a ModuleV3 model declares a over values and
compiles with + . Read
references/v2-v3-basics.md first. It covers
, , dtype handling with ,
vs , the distributed mapping, and the import
rules.
max.experimental.nnGraphTensorValuemax.nnforward()TensorF.lazy()compile()F.lazy()compile(weights=...)auto_castforward()__call__()输入:待迁移的 V2 模型源代码()。
$ARGUMENTSModuleV3max.experimental.nnmax.nnTensorValueGraphTensorforward()F.lazy()compile()F.lazy()compile(weights=...)auto_castforward()__call__()Workflow
工作流
1. Confirm the model is V2
1. 确认模型采用 V2
Classify by imports, and read the file when grep and the imports disagree.
V2 code imports its layers from and builds an explicit
over ; ModuleV3 code imports and
subclasses its . Scoped greps shortcut the read:
max.nnGraphTensorValuemax.experimental.nnModulebash
grep -rn "max.experimental" <model-path>
grep -rEn "^\s*(from|import) max\.nn" <model-path>Hits from the first mean the model is already (at least partly) V3 and
needs review, not migration. Hits from the second mean V2. Imports from
alone don't decide: V3 code keeps and
from for input specs. A file that hits both is
mixed; read it before migrating anything.
max.graphTensorTypeDeviceRefmax.graph根据 imports 进行分类;当 grep 结果与 imports 不一致时,应阅读文件。
V2 代码从 导入层,并基于 显式构建 ;
ModuleV3 代码从 导入并继承其 。可使用
限定范围的 grep 快速判断,从而避免完整阅读文件:
max.nnTensorValueGraphmax.experimental.nnModulebash
grep -rn "max.experimental" <model-path>
grep -rEn "^\s*(from|import) max\.nn" <model-path>第一个 grep 有命中,表示模型已经至少部分采用 V3,需要审查而不是直接
迁移。第二个 grep 有命中,则表示模型采用 V2。仅导入 并不能
作为判断依据:V3 代码仍会从 引入 和
,用于定义输入规格。如果一个文件同时命中两者,说明它是混合
代码;迁移任何内容之前都应先阅读该文件。
max.graphmax.graphTensorTypeDeviceRef2. Keep the V2 code, add the V3 variant
2. 保留 V2 代码并添加 V3 变体
Leave the V2 implementation untouched so it keeps working while the V3
variant is under construction. Put the V3 code alongside it: a
directory when migrating a registered architecture, or a
new module file when the model lives in your own package. When migrating a
registered architecture, name the V3 variant with the suffix;
selects it while the V2 name keeps serving
by default.
<name>_modulev3_ModuleV3max serve --prefer-module-v3保持 V2 实现不变,使其在 V3 变体开发期间继续可用。将 V3 代码放在相邻
位置:迁移已注册架构时,创建 目录;模型位于你自己的
package 中时,则创建新的 module 文件。迁移已注册架构时,V3 变体名称应
使用 后缀; 会选择该变体,而
V2 名称默认仍继续提供服务。
<name>_modulev3_ModuleV3max serve --prefer-module-v33. Rewrite imports
3. 重写 imports
python
undefinedpython
undefinedREMOVE:
REMOVE:
from max.graph import Graph, TensorValue, BufferValue, ops
from max.nn.layer import Module, LayerList
from max.nn.linear import Linear
from max.nn.norm.rms_norm import RMSNorm
from max.graph import Graph, TensorValue, BufferValue, ops
from max.nn.layer import Module, LayerList
from max.nn.linear import Linear
from max.nn.norm.rms_norm import RMSNorm
ADD:
ADD:
from max.experimental import functional as F
from max.experimental.tensor import Tensor
from max.experimental.nn import Module
from max.experimental.nn.linear import Linear
from max.experimental.nn.norm import RMSNorm
from max.experimental.nn.sequential import ModuleList
from max.experimental import functional as F
from max.experimental.tensor import Tensor
from max.experimental.nn import Module
from max.experimental.nn.linear import Linear
from max.experimental.nn.norm import RMSNorm
from max.experimental.nn.sequential import ModuleList
KEEP (TensorValue-free, shared with V2):
KEEP (TensorValue-free, shared with V2):
from max.nn.kv_cache import KVCacheParams, KVCacheParamInterface
from max.nn.attention import MHAMaskVariant
- `TensorType` and `DeviceRef` stay from `max.graph`; they define input
specs, not graph values.
- `max.nn` imports are fine when the imported class doesn't use
`TensorValue`.
- `PagedCacheValues` comes from
`max.experimental.nn.common_layers.kv_cache` for the V3 type. `max.nn`
exports a `PagedCacheValues` of its own, an alias for the V2 input type
without `from_upstream()`; import from the `max.experimental` path so
the V3 type wins.from max.nn.kv_cache import KVCacheParams, KVCacheParamInterface
from max.nn.attention import MHAMaskVariant
- `TensorType` 和 `DeviceRef` 仍从 `max.graph` 导入;它们定义的是输入规格,
而不是 graph values。
- 如果导入的类不使用 `TensorValue`,则可以从 `max.nn` 导入。
- V3 类型的 `PagedCacheValues` 来自
`max.experimental.nn.common_layers.kv_cache`。`max.nn` 也导出自己的
`PagedCacheValues`,它是未使用 `from_upstream()` 的 V2 输入类型别名;
请从 `max.experimental` 路径导入,以确保使用 V3 类型。4. Rewrite the module surface
4. 重写模块接口
- becomes
__call__().forward() - and
TensorValuebecomeBufferValue.Tensor - takes the call signature:
Module. Prefer specific types; useclass MyModel(Module[[Tensor, ...], tuple[Tensor, ...]])only when...hasforward().*args - Layer constructors take keyword args and drop and
dtype=; V3 uses default dtype and device contexts, anddevice=moves the module to one device after construction.model.to(device) - becomes
LayerListfromModuleList.max.experimental.nn.sequentialsubclassesModuleList, so it takes a single iterable:list;ModuleList(layers)raisesModuleList(*layers).TypeError - Parameters are plain objects; create them with
Tensor,Tensor.zeros(), and friends.max.experimental.random.*
- 改为
__call__()。forward() - 和
TensorValue改为BufferValue。Tensor - 使用以下调用签名:
Module。请优先使用 具体类型;仅当class MyModel(Module[[Tensor, ...], tuple[Tensor, ...]])包含forward()时才使用*args。... - 层构造函数接受 keyword args,并移除 和
dtype=;V3 使用默认的 dtype 和 device 上下文,构建后可通过device=将模块移至单个 设备。model.to(device) - 改为
LayerList中的max.experimental.nn.sequential。ModuleList是ModuleList的子类,因此只接受一个 iterable:list;ModuleList(layers)会抛出ModuleList(*layers)。TypeError - Parameters 是普通的 对象;可使用
Tensor、Tensor.zeros()及相关方法创建。max.experimental.random.*
5. Rewrite operations
5. 重写 operations
ops.*F.*ops.gather()F.gather()Tensorreshape()transpose()permute()squeeze()unsqueeze()split()cast()flatten()F.flatten(tensor, start_dim)ops.*F.*ops.gather()F.gather()Tensorreshape()transpose()permute()squeeze()unsqueeze()split()cast()flatten()F.flatten(tensor, start_dim)6. Replace graph building with compile()
compile()6. 使用 compile() 替换 graph 构建
V2:
python
nn_model = MyModel(config)
nn_model.load_state_dict(state_dict)
with Graph("my_model", input_types=input_types) as graph:
outputs = nn_model(*graph.inputs)
graph.output(*outputs)
model = session.load(graph, weights_registry=state_dict)V3:
python
with F.lazy():
nn_model = MyModel(config)
nn_model.to(device)
model = nn_model.compile(*input_types, weights=state_dict)- wraps construction, recording weight tensors symbolically; the checkpoint passed to
F.lazy()replaces them.compile(weights=...) - Weight keys are strict: a mismatched key is an error.
- A dtype mismatch between a parameter and its loaded tensor raises by
default. Pass to permit safe dtype casts.
auto_cast=True - Call the compiled model directly (). Compiled outputs are
model(tokens, ...)objects; code that consumedTensoroutputs reads the buffer viaBuffer..driver_tensor
V2:
python
nn_model = MyModel(config)
nn_model.load_state_dict(state_dict)
with Graph("my_model", input_types=input_types) as graph:
outputs = nn_model(*graph.inputs)
graph.output(*outputs)
model = session.load(graph, weights_registry=state_dict)V3:
python
with F.lazy():
nn_model = MyModel(config)
nn_model.to(device)
model = nn_model.compile(*input_types, weights=state_dict)- 包裹模型构造过程,并以符号形式记录权重张量;传给
F.lazy()的 checkpoint 会替换这些权重。compile(weights=...) - 权重键采用严格匹配:不匹配的键会报错。
- 默认情况下,参数与加载张量之间的 dtype 不匹配会抛出错误。传入
可允许安全的 dtype 转换。
auto_cast=True - 直接调用编译后的模型()。编译输出是
model(tokens, ...)对象; 此前读取Tensor输出的代码应通过Buffer读取 buffer。.driver_tensor
7. Align weight names with the compiled hierarchy
7. 使权重名称与编译后的层级保持一致
compile(weights=...)self.language_model = ...language_model.Tied embeddings need explicit handling: conditionally create , and
multiply by in when
is set. Keep one copy of the shared weight in the
state dict.
lm_headembed_tokens.weight.Tforward()tie_word_embeddingscompile(weights=...)self.language_model = ...language_model.Tied embeddings 需要显式处理:设置 时,在
中有条件地创建 ,并乘以 。
state dict 中只保留一份共享权重。
tie_word_embeddingsforward()lm_headembed_tokens.weight.T8. Move the KV cache unflatten into forward()
forward()8. 将 KV cache unflatten 移入 forward()
The KV cache parameter types stay in ; the values type has a V3
home in . Use the methods on
plus :
max.nnmax.experimental.nn.common_layers.kv_cachekv_paramsPagedCacheValues.from_upstream()python
def forward(self, tokens: Tensor, ..., *variadic_args: Tensor):
kv_inputs = iter(arg._graph_value for arg in variadic_args)
symbolic_inputs = self.kv_params.unflatten_kv_inputs(kv_inputs)
kv = PagedCacheValues.from_upstream(symbolic_inputs, tokens.mapping)You need the extraction: arrive as
objects while the unflatten methods expect raw graph values. For
sliding plus global attention, declare and use
, wrapping each tree with its own
.
._graph_valuevariadic_argsTensorMultiKVCacheParamsunflatten_basic_kv_tree()PagedCacheValues.from_upstream()KV cache 的参数类型仍保留在 ;values 类型在 V3 中应从
导入。使用 上的
方法以及 :
max.nnmax.experimental.nn.common_layers.kv_cachekv_paramsPagedCacheValues.from_upstream()python
def forward(self, tokens: Tensor, ..., *variadic_args: Tensor):
kv_inputs = iter(arg._graph_value for arg in variadic_args)
symbolic_inputs = self.kv_params.unflatten_kv_inputs(kv_inputs)
kv = PagedCacheValues.from_upstream(symbolic_inputs, tokens.mapping)需要执行 提取: 到达时是 对象,
而 unflatten 方法需要原始 graph values。对于同时使用 sliding attention 和
global attention 的情况,请声明 并使用
,再分别使用各自的
包装每棵树。
._graph_valuevariadic_argsTensorMultiKVCacheParamsunflatten_basic_kv_tree()PagedCacheValues.from_upstream()9. Port sharding when the V2 model is sharded
9. 当 V2 模型已分片时移植 sharding
V2 sharding sets a on each layer and calls
to get a per-device module list. V3 puts a on
the model and a placement mapping on each weight:
ShardingStrategy.shard(devices)DeviceMeshpython
from max.experimental.nn.common_layers.linear import row_parallel
from max.experimental.sharding import DeviceMesh
model.mesh = DeviceMesh(devices, (n_devices,), ("tp",))
linear = row_parallel(Linear(in_dim, out_dim))col_parallel()row_parallel()default_device(mesh)model.to(mesh)Signals.shard()V2 sharding 会在每一层设置 ,并调用
得到按设备划分的 module list。V3 则在模型上设置 ,并为每个
权重设置 placement mapping:
ShardingStrategy.shard(devices)DeviceMeshpython
from max.experimental.nn.common_layers.linear import row_parallel
from max.experimental.sharding import DeviceMesh
model.mesh = DeviceMesh(devices, (n_devices,), ("tp",))
linear = row_parallel(Linear(in_dim, out_dim))col_parallel()row_parallel()default_device(mesh)model.to(mesh)Signals.shard()TensorValue10. Verify against V2
10. 与 V2 进行验证
Run both implementations on the same inputs and compare greedy tokens
(and logits where convenient); identical outputs confirm the migration.
Run mypy over the migrated module; V3 leans on the
signatures, so type errors surface real migration
bugs.
Module[[...], ...]让两个实现处理相同输入,并比较 greedy tokens(条件允许时也比较 logits);
输出完全一致即可确认迁移成功。对迁移后的模块运行 mypy;V3 依赖
签名,因此类型错误会暴露真实的迁移缺陷。
Module[[...], ...]Reference implementations
参考实现
The registered architectures ship as readable Python source in the
installed MAX package, under . The V3
migrations to read:
max/pipelines/architectures/- Single-GPU V3: ,
olmo3/,gpt_oss_modulev3/llama3_modulev3/ - Sharded V3: ,
kimik2_5_modulev3/,gemma3_modulev3/deepseekV3_modulev3/ - Hybrid attention with recurrent state:
nemotron_h_modulev3/ - V2 for comparison: ,
gpt_oss/llama3/
已注册架构以可读的 Python 源代码形式随已安装的 MAX package 提供,位于
下。可供阅读的 V3 迁移实现包括:
max/pipelines/architectures/- 单 GPU V3:、
olmo3/、gpt_oss_modulev3/llama3_modulev3/ - 分片 V3:、
kimik2_5_modulev3/、gemma3_modulev3/deepseekV3_modulev3/ - 带 recurrent state 的混合 attention:
nemotron_h_modulev3/ - 用于对比的 V2:、
gpt_oss/llama3/
When there is no Tensor
equivalent
Tensor没有 Tensor 等价实现时
Some V2 layers and ops have no equivalent yet. The options, and the
wrapper pattern for bridging a helper into a API, are
in references/v2-v3-basics.md.
TensorTensorValueTensor部分 V2 层和 ops 目前尚无 等价实现。相关选项,以及将
helper 桥接到 API 的 wrapper 模式,见
references/v2-v3-basics.md。
TensorTensorValueTensor