
三年前我第一次完整跑通一个3D资产生成模型的训练时最折磨我的并不是模型结构本身而是堆在数据管线里那批网格文件——有的法线反了有的模型悬浮在半空中有的自带一百万个三角面片。那时候我就意识到3D生成模型的训练真正决定上限的往往不是Transformer有多少层而是数据处理链路有没有把物理世界的形状翻译成能稳定求梯度的张量。Trellis.2这套方案在3DV3D Vision进阶圈子里被反复讨论核心也在于它把训练里最容易被低估的两个环节——数据处理与训练流程——用结构化的方式串了起来。这篇文章我打算从数据侧讲起一直讲到训练流程的每个阶段各自在练什么、为什么这么设计以及我在复现和改造这套流程时踩过的坑。1. 在3D生成里数据和流程为什么是双主角1.1 3D生成模型真正要学的不是形状而是形状的分布先说一个容易误解的地方。很多人以为3D生成模型的任务是记住形状其实不是。无论是文本生成3D、单图生成3D还是条件生成3D资产模型真正要学的是形状和纹理的条件分布。也就是说给它一个条件——一段文字、一张参考图、一组多视角截图——它要能从海量3D资产中采样出一个符合分布的新形状。这比2D图像生成难不少。图像本质上是规则的网格像素尺寸固定、通道固定、语义对齐直接丢进CNN或者ViT就能训。但3D数据太不乖了网格是离散的拓扑结构点云是稀疏无序的点集体素虽然规则但是内存爆炸NeRF这类隐式表示又没法直接当token用。所以3D生成的第一步不是搭模型而是选择一种能让模型稳定学习的数据表示。Trellis这套方案最有价值的贡献就是定义了一个结构化3D潜空间Structured Latent把乱七八糟的3D资产统一编码成token序列让后续生成模型像做2D生成一样去训练。1.2 从原始资产到训练样本的完整数据流动线在实际工程里我习惯把整个数据流动画成一条显式链路每个环节出了问题都能单独定位。Trellis.2的完整处理流程可以拆成下面几步原始网格资产 → 网格清洗与水密化 → 多视角渲染RGBalpha → 相机参数归一化与背景替换 → 稀疏体素化/场采样 → SLAT编码 → 结构化token序列 → 进入训练dataloader这条链路里的每一步都会直接影响最终生成质量。网格不水密后续场采样就会在内部产生空洞相机参数不统一模型会把不同尺度的物体当成同一种尺度来学背景不处理模型会把环境纹理误当成物体纹理场采样分辨率不够小车模型的轮子可能直接消失。我见过太多人把精力全花在调Transformer层数上结果生成出来永远是一个模糊的凳子——问题根本不在网络容量而在前面数据处理链路已经丢掉了细节。这也是为什么这篇文章要把数据处理放在训练流程前面来讲。2. 数据处理第一步网格清洗、相机归一化与多视角渲染2.1 网格清洗法线、尺度与朝向的强制统一拿到的原始3D资产很少是干净的。常见问题有法线方向不一致有些面片法线向内导致光照计算错误模型不在原点包围盒中心偏移单位不统一同一个数据集里有的模型是米制、有的是厘米制还有多余的内部面片和孤立顶点。清洗策略我一般分三步走。第一步统一坐标系把资产中心对齐到原点然后按包围盒最长边归一化到指定尺度。在Trellis.2这类方案里相机参数是严格对齐到归一化坐标系的所以这一步不做后面渲染出的多视角图和相机外参就是错的。第二步法线重计算强制所有面片法线朝外可以用邻接面片投票的方式做一致性翻转。第三步拓扑简化把超过几十万面的高模减到训练可接受的范围一般10万面以内足够面数过高只会拖慢渲染和体素化。做完这三步才能进渲染管线。不要跳过因为它们直接决定了多视角图集和后续场采样的质量。数据清洗占整个训练准备工作的比重在我个人经验里不会低于30%。2.2 多视角渲染视角数量、相机高度角与alpha通道Trellis.2训练依赖多视角RGB图作为条件信息。渲染这一步看着简单实际上有很多参数需要拍板而且不同参数对最终生成质量的影像差异巨大。视角数量我测试过8、12、24、32四种设置。8视角在简单物体上够用但遇到有复杂空洞结构的物体比如椅子扶手、耳机支架视角太少会导致3D编码器看不见背面结构。24视角是性价比最高的配置32视角对质量的提升边际效益明显下降但渲染耗时和数据体积增加不少。实际训练建议24蒸馏或者测试时可以用少量固定视角。相机分布相机不能只在赤道一圈均匀摆。Trellis类方法里常见的做法是从-30度到30度的高度角范围内随机采样加上绕Y轴均匀分布的方位角。这样能保证模型见过俯视和仰视视角不至于生成结果只在水平视角下好看。渲染输出格式需要RGBA四通道。alpha通道用来区分前景物体和背景。如果直接渲染纯白背景模型会把背景颜色学进纹理里后续生成结果会带着一圈白色光晕。alpha通道就是给模型一个明确的前景分割信号。2.3 背景替换、光照随机与视点扰动数据增强的正确姿势3D生成的数据增强和2D图像分类不太一样不能随意rotate/flip因为物体在三维空间里的朝向是有物理意义的。但有几类增强在Trellis.2的数据管线里效果很明显。第一类是背景替换。用alpha抠出前景后把背景替换成随机颜色渐变、噪声纹理或纯灰。这样模型学习时会把注意力放在前景几何和纹理上而不是拿背景当识别特征。第二类是光照随机。渲染时随机打几盏方向光强度、色温都在合理范围内抖动能让模型对光照不那么敏感生成结果在换环境后不至于崩坏。第三类是视点扰动。在相机标准位姿附近加小角度高斯扰动模拟采集误差提升对真实相机位姿的鲁棒性。这里有个经验之谈数据增强不是越多越好。3D生成任务里相机位置和物体姿态一旦扰动过大模型会分不清这个视角没见过和这个物体形状不同训练loss会出现很难收敛的抖动。合理的扰动范围我一般控制在角度标准差5度以内。3. 从mesh到token结构化3D潜空间的数据转化细节3.1 为什么不能直接把网格丢给Transformer网格数据由顶点坐标和面片索引组成拓扑结构是不规则的。两个形状相近的物体顶点数量、连接关系可能完全不同。Transformer要处理的是序列数据网格没法直接变成一个定长、有序、有语义的序列。点云也一样点序打乱后同一个物体看起来就像不同的序列。这就是为什么必须先建立一种结构化潜空间把任意拓扑的3D资产编码成统一的、有序的token序列。在Trellis.2的设计里这个潜空间不是单个全局向量而是一组排布在三维空间中的结构化潜在向量。每个潜在向量对应物体局部区域的一块信息包包含局部几何结构、表面特征、纹理信息等。这样做的好处很明显既能保留空间局部性又让生成模型只需要在这些token上做自回归或扩散采样可以复用2D生成模型里被验证过的成熟训练范式。3.2 从网格到稀疏体素场体素化与截断符号距离场网格不能直接进编码器所以要先把几何转成体素场。最常见的做法是计算截断符号距离场TSDF在归一化后的包围盒内划分规则网格对每个体素计算到最近表面的带符号距离正负表示在表面内外截断距离只在表面附近保留有效值。体素分辨率非常关键。256的三次方是很多3D生成模型的常用起点但直接存稠密的256^3float数组会爆内存。一个256^3的稠密TSDF场需要约67MB批量训练根本扛不住。Trellis.2走的路线是稀疏体素只有表面附近的有效体素才存特征其余空体素直接跳过。实际操作下来一个典型物体在256^3分辨率下的有效体素数通常在几万到几十万之间存成稀疏索引后非常可控。这一步本质上是把连续曲面离散化解决可微分问题——网格的拓扑突变没法求梯度但体素场是规则网格每个体素的值都能对输入求梯度模型就能用反向传播去优化了。3.3 SLAT编码器局部primitive、几何场与纹理场的分工拿到稀疏体素场之后接下来是SLAT编码。这里需要强调Trellis系列不是用一个全局VAE把整个物体压成一个向量而是把物体划分成很多局部primitive局部块每个primitive都编码成一个小型信息包。具体来说每个局部区域会被编码成多组结构化字段几何场geometry fields记录表面位置和法线纹理场texture fields记录颜色和材质属性有些实现还会额外加一个语义场。这些字段分开编码又共享空间位置索引解码时可以单独解码出几何和纹理也可以联合解码出带纹理的mesh。这种设计带来的直接好处是可编辑性。训练完成后如果你只想改局部纹理只需要把对应primitive的纹理字段换掉几何字段保持不变。这在传统全局潜空间里很难做到但在结构化潜空间里天然支持。3.4 2D tokenizer与3D token的统一空间Trellis.2的生成训练还有一个关键设计2D图像和3D资产并不是各训各的。多视角图像会被一个2D tokenizer编码成2D token序列然后与3D token在同一个特征空间里做attention。也就是说模型在生成3D token时可以看着参考图像的2D token做条件推理。这就像让一个雕塑家一边看设计图一边捏泥巴——2D视角提供外观约束3D潜空间提供体量结构。训练时2D tokenizer和3D编码器是联合优化的推理时从单张图提取2D token再通过生成模型采样3D token。统一空间的另一层意义是数据来源可以混用。网上有海量单张图片但高质量的3D资产很稀缺。2D tokenizer可以先在大量2D数据上预训练再用3D数据微调这能显著缓解3D数据不足的问题。4. 训练流程三个阶段先学听懂再学说4.1 阶段一2D图像tokenizer预训练整个训练流程不是一上来就直接训生成Transformer。第一个阶段要先训练2D图像tokenizer它负责把多视角RGB图编码成2D token序列同时还要能解码回图像。这个tokenizer本质是一个2D VAE或VQ-VAE。训练目标是重建输入一张多视角RGB图经过编码器压成token再经过解码器恢复原图损失函数是像素重建损失加上感知损失。训练数据可以先用大规模2D图像数据集不一定需要3D资产。我实际测试时发现2D tokenizer预训练的数据量充足与否对最终3D生成质量的影响甚至比3D数据量的影响还要明显因为它是整个模型理解像素长什么样的基础。这一阶段练的是看懂图像。如果跳过或者数据量不足后续生成模型会经常产生纹理混乱、颜色偏灰的问题而且很难通过加大3D训练来弥补。4.2 阶段二3D SLAT自重建训练第二阶段训练3D SLAT编码器和解码器目标是把稀疏体素场编码成结构化token再解码回体素场或直接提取mesh。这个阶段相当于让模型学会3D数据怎么压紧再还原。损失函数一般由三部分组成体素场的重建损失、表面法线的一致性损失、渲染图像的重建损失。渲染重建损失很关键——编码器压缩token后解码器生成的不只是一个隐式体素场还要能通过可微渲染得到多视角图像与原渲染图对比。这样强制token保留的是看得见的几何与纹理而不是一套模型自嗨的压缩格式。自重建训练不需要任何标注只需要成对的多视角渲染图和对应体素场。训练到验证集重建PSNR不再明显上涨时这个阶段就可以停了。4.3 阶段三生成Transformer训练有了可用的SLAT编码器和2D tokenizer第三阶段才是核心的生成模型训练。这个阶段固定或部分微调前面训练好的tokenizer训练一个条件生成模型输入2D图像token以及可选的文本或相机参数输出3D token序列。生成模型的训练目标和2D扩散模型/自回归模型本质上一致从潜空间里采样出与条件匹配的token分布。Trellis.2训练时通常采用mask-prediction的思路随机对部分3D token加噪声或做遮蔽让模型预测缺失token迭代训练后模型就学会了从条件到完整3D token序列的映射。Loss就是预测token和真实token之间的交叉熵或MSE。这一阶段对显存的压力最大因为输入既有2D token序列又有3D token序列还叠加了空间attention的计算。训练速度也明显比前两阶段慢我在单卡A100 80G上一套中等规模数据跑一个像样的生成模型往往需要一到两周。4.4 训练顺序为什么不能乱三个阶段的顺序是强依赖的不能随便调换。如果没有一个收敛的2D tokenizer你连图像条件都编码不进去如果没有训练好的3D SLAT编码器你根本没有合格的3D token作为生成目标如果直接端到端训所有模块网络会陷入多目标优化的困境——梯度信号互相竞争谁都没学好训练还会极其不稳定。在实际工程中前三阶段都是依次进行先训完一个再训下一个。阶段一和阶段二可以并行开展因为它们互不依赖数据也可以分别准备。等两者都收敛了再冻结参数进入阶段三。这种方法可能不是最花哨的却是最稳、最可复现的。5. 训练工程三座山显存、分布式并行与数值稳定5.1 显存估算token数量、batch size和梯度检查点3D生成训练最大的物理瓶颈就是显存。我踩过的第一个坑就是低估了内部token数量对显存的影响。一个256^3稀疏体素场在几十万个有效体素的情况下经过编码器降到结构化token后序列长度通常还有数千到上万级别。2D和3D token一起送进Transformer做attention显存占用是指数增长的。给个粗略估算思路以单样本2D token约576个、3D token约4096个为例自注意力部分的内存占用约等于序列长度的平方乘以头数和头维度这个量级在数十万到数百万浮点数之间。单卡跑一个小batch可能就要20-30GB显存。所以梯度检查点gradient checkpointing几乎必须开。它用前向时丢弃中间激活值、反向时重新计算的方式把显存从O(序列长度平方)降到O(序列长度)代价是训练时间增加20-30%。我通常的策略是先关掉检查点测试能接受的最大batch再打开检查点把batch翻倍。batch翻倍带来的收敛稳定性收益通常能覆盖掉重计算的时间损耗。5.2 分布式训练DDP、各阶段不同的并行策略阶段一和阶段二因为序列相对短用PyTorch DDPDistributedDataParallel就够了数据并行即可。但阶段三序列长、模型大纯数据并行会遇到同步梯度的通信瓶颈。我实测时4卡DDP在每步通信上的开销就占到了将近20%的时间。更合理的做法是结合张量并行或序列并行。把序列维度切到不同卡上每张卡只算一部分token的attention然后通过all-reduce合并。这个方案能显著降低单卡显存峰值但需要你对Transformer的attention实现有足够理解建议直接使用支持序列并行的成熟训练框架而不是自己写。分布式训练还有个容易忽略的坑数据shuffle的随机种子不一致会导致不同进程读到重复的增强样本等效于人为降低了数据多样性。正确做法是每个进程用全局rankepoch派生的种子保证同一条数据在不同进程上的增强方式不同。5.3 混合精度、梯度裁剪与loss稳定3D训练比2D训练更容易出现NaN。原因在于稀疏体素场的索引计算里如果某个体素正好落在稀疏区域边缘梯度容易出现极端值。我处理的办法有三个第一混合精度直接用bf16而不是fp16bf16的动态范围和fp32一致尾部梯度丢失更少第二梯度裁剪阈值从默认的1.0下调到0.5左右loss曲线明显更平滑第三给SLAT解码器的TSDF预测加一个小幅度的梯度缩放避免靠近截断边界时距离值的跳变被放大。如果loss突然出现NaN第一步不是回滚代码而是检查输入数据里是否存在退化的网格——比如面积接近零的薄片、完全共面的退化体素块。很多NaN都是脏数据引起的模型本身没问题。6. 实测中容易翻车的六个环节与排查方法6.1 相机坐标系不一致导致生成结果镜像这是我踩过最深的一个坑。Trellis类方法对相机坐标系是严格敏感的——如果你在渲染阶段用的OpenGL坐标系但训练配置里写的是OpenCV坐标系约定模型会把左右方向学反生成的3D模型在三维查看器里整个镜像。排查方法很简单训练前把渲染输出的多视角图按相机参数投影回3D空间和原网格叠加显示确认点云对齐。不要相信看起来差不多几乎都是差一点点但生成结果会差很多。6.2 数据类别不平衡3D数据集的类别分布天然是不均衡的椅子、桌子这类常见家具可能有几万个而乐器、灯具可能只有几十个。如果不做重采样模型最终只会生成高频类别。我的做法是类别分层采样每个batch内按类别均匀采样保证模型每个step都能见到低频类别。频率阈值太低的时候可以配合类别条件训练把类别embedding送进模型而不是删除数据。6.3 token截断导致输出细节丢失结构化token数量是动态的但Transformer训练要求固定序列长度。如果直接把超过长度上限的token截断小型物体的token被丢弃的不多但大型精细物体会丢大量局部细节生成结果会出现明显的断肢。我的做法是在数据阶段预处理时记录每个物体的token数量分布然后选择一个覆盖95%样本的序列长度阈值。剩余5%的超长样本用体素降采样把token压回阈值内而不是直接硬截断。6.4 解码器重建模糊如果你发现训练loss已经很低但解码器重建出的mesh表面很糊大概率是SLAT编码器的压缩率设置过高。结构化token的每个primitive能容纳的细节有限primitive数量不变时强行提高压缩率细节就会平均化。解决办法是增加primitive数量或增加码本大小而不是加Transformer层数。6.5 推理阶段提取mesh的参数陷阱训练时用的TSDF场在推理时要经过marching cubes提取mesh。这里的分割阈值iso level非常敏感默认的0.0不一定是最优值。由于解码器输出有偏差我经常把iso level在-0.05到0.05之间扫一遍取生成mesh与条件图像渲染loss最低的那个值。这一个小技巧能让生成结果的整体观感提升不少。6.6 验证集与训练集同源导致自信的错觉3D生成模型验证时很容易自欺欺人。如果验证集里的3D资产来自和训练集同一个数据源模型见过相同风格的渲染图验证指标会虚高。我建议验证集单独留出从不同渠道采集的资产至少保证类别不重叠才能真实评估模型的泛化能力。7. 从训练权重到生成结果推理与后处理管线7.1 推理流程拆解训练完成后推理流程和训练流程是不一样的实际跑通的链路是输入图像/文本 → 2D tokenizer编码 → 生成Transformer采样3D token → SLAT解码器重建体素场 → marching cubes提取mesh → 可选超分细化 → 纹理映射 → 输出资产这里的核心是采样环节。生成模型在token空间做迭代采样时有温度参数和采样步数两个关键旋钮。温度太高生成结果多样但容易出现畸形结构温度太低结果稳定但是平庸细节趋同。我常用的经验值是从1.0开始验证集上跑一组小批量生成观察结构完整性和多样性再做微调。7.2 生成结果的后处理与质量校验从marching cubes提取出的mesh通常带有很多小碎面和不规则拓扑必须先做清理移除孤立小连通片、平滑法线、重新三角化。如果生成结果要进入游戏或渲染引擎还需要做减面到目标面数然后重新生成UV和纹理贴图。这一步在Trellis2的管线里可以让SLAT解码器直接输出纹理字段也可以走传统工具的自动UV展开流程我实际测试下来两者结合效果最好。质量校验我也建议做成自动化对生成mesh做多视角渲染和输入条件图算PSNR、LPIPS再加一个几何指标如表面完整性mesh是否有非流形边。没有量化指标只看人眼很难在调试参数时做出可比较的决策。7.3 训练好的模型如何集成到现有工具链如果你不是只在学术环境里跑通demo而是想把这套流程集成到内容生产工具链里有几个额外的工程点要注意模型权重要转成半精度以降低推理显存2D tokenizer和3D解码器要封装成独立的、可缓存的服务因为它们是确定性模块同一输入图可以复用输出token生成Transformer的采样过程则要支持流式输出让用户先看到粗糙结果再逐步细化。另外建议把数据处理链路里用到的相机配置、体素分辨率、token序列长度等参数固化到一份可复现的配置文件中而不是散落在代码里。我吃过这个亏训练完一个效果好得多的模型想复现时才发现当时某个数据增强开关忘了记录前前后后花了一周才找回原来的训练设置。最后再分享一个我自己的习惯训练过程中除了保存最后的checkpoint每个阶段收敛时我还会单独把tokenizer和解码器各存一份快照。后期如果只想改进生成模型完全没有必要从头把前两个阶段也重训一遍。把训练流程拆成可独立迭代的模块才是让3D生成模型持续演进的正路。