【3D-AICG 系列-13】Trellis 2 的 SC-VAE 的 Training Loss 细节

系列文章目录

文章目录

  • 系列文章目录
      • 一、符号与变量对应
      • [二、第一阶段损失 L s 1 \mathcal{L}{s1} Ls1(公式 6)](#二、第一阶段损失 L s 1 \mathcal{L}{s1} Ls1(公式 6))
        • [1. λ v ∥ v ^ − v ∥ 2 2 \lambda_v \|\hat{v}-v\|_2^2 λv∥v^−v∥22(对偶顶点 MSE)](#1. λ v ∥ v ^ − v ∥ 2 2 \lambda_v |\hat{v}-v|_2^2 λv∥v^−v∥22(对偶顶点 MSE))
        • [2. λ δ BCE ( δ ^ , δ ) \lambda_\delta \text{BCE}(\hat{\delta},\delta) λδBCE(δ^,δ)(对偶面标志 BCE)](#2. λ δ BCE ( δ ^ , δ ) \lambda_\delta \text{BCE}(\hat{\delta},\delta) λδBCE(δ^,δ)(对偶面标志 BCE))
        • [3. λ ρ BCE ( ρ ^ , ρ ) \lambda_\rho \text{BCE}(\hat{\rho},\rho) λρBCE(ρ^,ρ)(剪枝/细分掩码 BCE)](#3. λ ρ BCE ( ρ ^ , ρ ) \lambda_\rho \text{BCE}(\hat{\rho},\rho) λρBCE(ρ^,ρ)(剪枝/细分掩码 BCE))
        • [4. λ mat ∥ f ^ mat − f mat ∥ 1 \lambda_{\text{mat}}\|\hat{f}^{\text{mat}}-f^{\text{mat}}\|1 λmat∥f^mat−fmat∥1(材质 L1)](#4. λ mat ∥ f ^ mat − f mat ∥ 1 \lambda{\text{mat}}|\hat{f}{\text{mat}}-f{\text{mat}}|_1 λmat∥f^mat−fmat∥1(材质 L1))
        • [5. λ K L L K L \lambda_{KL}\mathcal{L}{KL} λKLLKL](#5. λ K L L K L \lambda{KL}\mathcal{L}_{KL} λKLLKL)
      • [三、第二阶段损失 L s 2 = L s 1 + L render \mathcal{L}{s2} = \mathcal{L}{s1} + \mathcal{L}{\text{render}} Ls2=Ls1+Lrender(公式 7)](#三、第二阶段损失 L s 2 = L s 1 + L render \mathcal{L}{s2} = \mathcal{L}{s1} + \mathcal{L}{\text{render}} Ls2=Ls1+Lrender(公式 7))
      • 四、相机策略(浅近裁剪平面)
      • [五、解耦潜在空间:材质以 shape 的细分结构为条件](#五、解耦潜在空间:材质以 shape 的细分结构为条件)
      • 六、汇总表

本文是论文 3.2.2 VAE Training 与代码的逐项对应说明。


一、符号与变量对应

论文符号 含义 代码变量 / 说明
v v v 对偶顶点位置 vertices(dual grid 顶点偏移,3 维)
δ \delta δ 对偶面/边标志 intersected(体素 3 条边是否被面穿过,3 维 bool)
ρ \rho ρ 剪枝/细分掩码 subdivision :上采样时"是否在该 coarse 下生成 8 个子体素"的 mask,即 subs_gt / subs
f mat f^{\text{mat}} fmat 材质属性 PBR VAE 的 x.feats(base_color, metallic, roughness, alpha 等)
v ^ \hat{v} v^ 预测值 decoder 输出:pred_vertice, pred_intersected, subs, 以及 PBR 的 y.feats

二、第一阶段损失 L s 1 \mathcal{L}_{s1} Ls1(公式 6)

论文:

1. λ v ∥ v ^ − v ∥ 2 2 \lambda_v \|\hat{v}-v\|_2^2 λv∥v^−v∥22(对偶顶点 MSE)

代码: ShapeVaeTrainerlambda_vertice(默认 0.01)

189:190:repo/TRELLIS.2/trellis2/trainers/vae/shape_vae.py 复制代码
        if self.lambda_vertice > 0:
            terms["direct/vertice"] = F.mse_loss(pred_vertice.feats, vertices.feats)
            terms["loss"] = terms["loss"] + self.lambda_vertice * terms["direct/vertice"]
  • 论文 v v v ↔ vertices.feats, v ^ \hat{v} v^ ↔ pred_vertice.feats
  • 实现就是 MSE,等价于 ∥ v ^ − v ∥ 2 2 \|\hat{v}-v\|_2^2 ∥v^−v∥22 的 mean
2. λ δ BCE ( δ ^ , δ ) \lambda_\delta \text{BCE}(\hat{\delta},\delta) λδBCE(δ^,δ)(对偶面标志 BCE)

代码: lambda_intersected(默认 0.1)

186:188:repo/TRELLIS.2/trellis2/trainers/vae/shape_vae.py 复制代码
        if self.lambda_intersected > 0:
            terms["direct/intersected"] = F.binary_cross_entropy_with_logits(pred_intersected.feats.flatten(), intersected.feats.flatten().float())
            terms["loss"] = terms["loss"] + self.lambda_intersected * terms["direct/intersected"]
  • 论文 δ \delta δ ↔ intersected, δ ^ \hat{\delta} δ^ ↔ pred_intersected(logits)
  • binary_cross_entropy_with_logits 即 BCE
3. λ ρ BCE ( ρ ^ , ρ ) \lambda_\rho \text{BCE}(\hat{\rho},\rho) λρBCE(ρ^,ρ)(剪枝/细分掩码 BCE)

代码: subdivision 预测的 BCE,lambda_subdiv(默认 0.1)

193:195:repo/TRELLIS.2/trellis2/trainers/vae/shape_vae.py 复制代码
        for i, (sub_gt, sub) in enumerate(zip(subs_gt, subs)):
            terms[f"bce_sub{i}"] = F.binary_cross_entropy_with_logits(sub.feats, sub_gt.float())
            terms["loss"] = terms["loss"] + self.lambda_subdiv * terms[f"bce_sub{i}"]
  • 论文 ρ \rho ρ ↔ 每层上采样的 GT subdivision subs_gt(来自 encoder 下采样时的 spatial cache)
  • ρ ^ \hat{\rho} ρ^ ↔ decoder 每层预测的 subs
  • 多层累加,每层权重都是 lambda_subdiv
4. λ mat ∥ f ^ mat − f mat ∥ 1 \lambda_{\text{mat}}\|\hat{f}^{\text{mat}}-f^{\text{mat}}\|_1 λmat∥f^mat−fmat∥1(材质 L1)

代码:PBR VAE 里,对应 direct regression 的 L1

175:181:repo/TRELLIS.2/trellis2/trainers/vae/pbr_vae.py 复制代码
        if self.loss_type == 'l1':
            terms["l1"] = l1_loss(x.feats, y.feats)
            terms["loss"] = terms["loss"] + terms["l1"]
        elif self.loss_type == 'l2':
            terms["l2"] = l2_loss(x.feats, y.feats)
            terms["loss"] = terms["loss"] + terms["l2"]
  • f mat f^{\text{mat}} fmat ↔ x.feats, f ^ mat \hat{f}^{\text{mat}} f^mat ↔ y.feats
  • 论文写的是 L1,配置里用 loss_type='l1' 时就是 ∥ f ^ mat − f mat ∥ 1 \|\hat{f}^{\text{mat}}-f^{\text{mat}}\|_1 ∥f^mat−fmat∥1
5. λ K L L K L \lambda_{KL}\mathcal{L}_{KL} λKLLKL

代码: 两个 VAE 都用同一形式的 KL

Shape VAE:

211:213:repo/TRELLIS.2/trellis2/trainers/vae/shape_vae.py 复制代码
        terms["kl"] = 0.5 * torch.mean(mean.pow(2) + logvar.exp() - logvar - 1)
        terms["loss"] = terms["loss"] + self.lambda_kl * terms["kl"]

PBR VAE:

217:219:repo/TRELLIS.2/trellis2/trainers/vae/pbr_vae.py 复制代码
        terms["kl"] = 0.5 * torch.mean(mean.pow(2) + logvar.exp() - logvar - 1)
        terms["loss"] = terms["loss"] + self.lambda_kl * terms["kl"]
  • 即标准高斯先验的 KL: 1 2 ( μ 2 + e log ⁡ σ 2 − log ⁡ σ 2 − 1 ) \frac{1}{2}(\mu^2 + e^{\log\sigma^2} - \log\sigma^2 - 1) 21(μ2+elogσ2−logσ2−1)
  • Shape 配置里 lambda_kl=1e-6,很小,符合"第一阶段以重建为主"的写法

三、第二阶段损失 L s 2 = L s 1 + L render \mathcal{L}{s2} = \mathcal{L}{s1} + \mathcal{L}_{\text{render}} Ls2=Ls1+Lrender(公式 7)

论文:第二阶段在 L s 1 \mathcal{L}_{s1} Ls1 上再加基于渲染的感知损失。

代码: 没有拆成两个训练阶段,而是在同一个 training_losses() 里同时算 direct + 渲染,对应"始终在用 L s 2 \mathcal{L}_{s2} Ls2":

197:209:repo/TRELLIS.2/trellis2/trainers/vae/shape_vae.py 复制代码
        # rendering loss
        cameras = self._randomize_camera(len(mesh))
        gt_renders = self._render_batch(mesh, **cameras, return_types=['mask', 'normal', 'depth'])
        pred_renders = self._render_batch(recon, **cameras, return_types=['mask', 'normal', 'depth'])
        terms['render/mask'] = l1_loss(pred_renders['mask'], gt_renders['mask'])
        terms['render/depth'] = l1_loss(pred_renders['depth'], gt_renders['depth'])
        terms['render/normal/l1'] = l1_loss(pred_renders['normal'], gt_renders['normal'])
        terms['render/normal/ssim'] = 1 - ssim(pred_renders['normal'], gt_renders['normal'])
        terms['render/normal/lpips'] = lpips(pred_renders['normal'], gt_renders['normal'])
        terms['loss'] = terms["loss"] + \
                        self.lambda_mask * terms['render/mask'] + \
                        self.lambda_depth * terms['render/depth'] + \
                        self.lambda_normal * (terms['render/normal/l1'] + self.lambda_ssim * terms['render/normal/ssim'] + self.lambda_lpips * terms['render/normal/lpips'])

对应关系:

论文描述 代码
渲染 mask return_types=['mask', ...],L1,权重 lambda_mask=1
渲染 depth return_types=['depth', ...],L1,权重 lambda_depth=10
法线 L1 terms['render/normal/l1'],权重 lambda_normal=1
法线 SSIM terms['render/normal/ssim'],权重 lambda_normal * lambda_ssim(0.2)
法线 LPIPS terms['render/normal/lpips'],权重 lambda_normal * lambda_lpips(0.2)

若要"只做第一阶段",可在配置里把 lambda_masklambda_depthlambda_normal 设为 0,即退化为 L s 1 \mathcal{L}_{s1} Ls1。

材质 VAE 的"第二阶段"是渲染感知损失(base_color / MRA 的 SSIM+LPIPS):

208:214:repo/TRELLIS.2/trellis2/trainers/vae/pbr_vae.py 复制代码
            terms['render/base_color/ssim'] = 1 - ssim(pred_base_color, gt_base_color)
            terms['render/base_color/lpips'] = lpips(pred_base_color, gt_base_color)
            terms['render/mra/ssim'] = 1 - ssim(pred_mra, gt_mra)
            terms['render/mra/lpips'] = lpips(pred_mra, gt_mra)
            terms['loss'] = terms["loss"] + \
                            self.lambda_render * (self.lambda_ssim * ... + self.lambda_lpips * ...)

对应论文里"材质也通过感知损失监督"。


四、相机策略(浅近裁剪平面)

论文:随机放置相机,并用浅的近裁剪平面"切开"物体表面,以建模内外结构。

代码: ShapeVaeTrainer._randomize_camera

113:126:repo/TRELLIS.2/trellis2/trainers/vae/shape_vae.py 复制代码
        r_min, r_max = self.camera_randomization_config['radius_range']
        k_min = 1 / r_max**2
        k_max = 1 / r_min**2
        ks = torch.rand(num_samples, device=self.device) * (k_max - k_min) + k_min
        radius = 1 / torch.sqrt(ks)
        fov = 2 * torch.arcsin(0.5 / radius)
        origin = radius.unsqueeze(-1) * F.normalize(torch.randn(num_samples, 3, device=self.device), dim=-1)
        extrinsics = utils3d.torch.extrinsics_look_at(origin, torch.zeros_like(origin), ...)
        intrinsics = utils3d.torch.intrinsics_from_fov_xy(fov, fov)
        near = [np.random.uniform(r - 1, r) for r in radius.tolist()]
        return { 'extrinsics': ..., 'intrinsics': ..., 'near': near }
  • radius_range: [2, 100]:相机到原点的距离在 [2, 100] 内随机(在 1/k 空间均匀)。
  • near 取在 [radius - 1, radius],即近平面紧贴相机、远平面 far = near + 2,形成很薄的视锥,相当于用近平面"切"过物体,对应论文的 shallow near plane 和"切开表面、看到内外结构"。

五、解耦潜在空间:材质以 shape 的细分结构为条件

论文:两个解耦的 SC-VAE(shape + material),材质 VAE 上采样时以 shape VAE 的 subdivision structures 为条件。

代码: 推理时,纹理 decoder 显式接收 shape 的 subdivision 作为 guide_subs

507:507:repo/TRELLIS.2/trellis2/pipelines/trellis2_image_to_3d_ours.py 复制代码
        ret = self.models['tex_slat_decoder'](slat, guide_subs=subs) * 0.5 + 0.5
  • subs 来自 shape 的 decoder 在上采样时返回的 subdivision 列表(每层"哪些 coarse 体素被细分为 8 个子体素")。
  • 纹理 decoder 若 pred_subdiv=False,则使用 guide_subs 决定在哪些位置做 channel→spatial 上采样,从而与 shape 的几何结构对齐。

Decoder 接口(支持 guide_subs):

478:479:repo/TRELLIS.2/trellis2/models/sc_vaes/sparse_unet_vae.py 复制代码
    def forward(self, x: sp.SparseTensor, guide_subs: Optional[List[sp.SparseTensor]] = None, return_subs: bool = False) -> sp.SparseTensor:
        assert guide_subs is None or self.pred_subdiv == False, "Only decoders with pred_subdiv=False can be used with guide_subs"

因此:论文里的 "subdivision structures" = 代码里的 subs / `guide_subs;材质/纹理 decoder 在推理(和可选的训练)时按 shape 的细分结构做条件上采样。


六、汇总表

论文公式/概念 代码位置 配置/权重
λ v ∣ v ^ − v ∣ 2 2 \lambda_v |\hat{v}-v|_2^2 λv∣v^−v∣22 shape_vae.py L189-190 lambda_vertice=0.01
λ δ BCE ( δ ^ , δ ) \lambda_\delta \text{BCE}(\hat{\delta},\delta) λδBCE(δ^,δ) shape_vae.py 第186-188行 lambda_intersected=0.1
λ ρ BCE ( ρ ^ , ρ ) \lambda_\rho \text{BCE}(\hat{\rho},\rho) λρBCE(ρ^,ρ) shape_vae.py 第193-195行 lambda_subdiv=0.1
λ mat ∣ f ^ mat − f mat ∣ 1 \lambda_{\text{mat}}|\hat{f}^{\text{mat}}-f^{\text{mat}}|_1 λmat∣f^mat−fmat∣1 pbr_vae.py 第175-177行 loss_type='l1'
λ K L L K L \lambda_{KL}\mathcal{L}_{KL} λKLLKL shape_vae.py 第211-213行;pbr_vae.py 第217-219行 lambda_kl=1e-6
L render \mathcal{L}_{\text{render}} Lrender(mask/depth 损失) shape_vae.py 第200-206行 lambda_mask=1lambda_depth=10
法线 L1 + SSIM + LPIPS shape_vae.py L202-208 lambda_normal=1, lambda_ssim/lpips=0.2
材质渲染感知损失 pbr_vae.py L208-214 lambda_render, lambda_ssim, lambda_lpips
随机相机 + 浅 near shape_vae.py L113-126,camera_randomization_config radius_range=[2,100], near ∈ [r-1,r]
材质以 subdivision 为条件 tex_slat_decoder(..., guide_subs=subs),decoder forward(..., guide_subs=...) pipeline / sparse_unet_vae

这样就把论文 3.2.2 的公式、两阶段、相机和"材质条件于 shape 细分结构"都和代码对上去了。

相关推荐
njsgcs2 小时前
专业名词写在rag里而不是skill里
人工智能
love530love2 小时前
解决微软登录错误 0xCAA82EE2 & 身份验证故障排查指南
运维·人工智能·microsoft·onedrive·microsoft 365·teams·microsoftonline
Aaron15882 小时前
RFSOC与ADRV9009、AD9026、AD9361技术指标及应用场景对比分析
人工智能·算法·fpga开发·硬件工程·信息与通信·信号处理·基带工程
A小码哥2 小时前
MCP-Atlas:首个大规模 AI 模型工具使用基准测试详解
人工智能
_Twink1e2 小时前
[算法竞赛]四、树
数据结构·笔记·算法
东坡肘子2 小时前
春晚、机器人、AI 与 LLM -- 肘子的 Swift 周报 #124
人工智能·swiftui·swift
AC赳赳老秦2 小时前
2026云原生AI规模化趋势预测:DeepSeek在K8s集群中的部署与运维实战
运维·人工智能·云原生·架构·kubernetes·prometheus·deepseek
大鹏的NLP博客2 小时前
Rust + PyTorch 实现 BGE 向量检索系统
人工智能·pytorch·rust
一个努力编程人2 小时前
计算机视觉CV领域————Swin Transformer
人工智能·计算机视觉·transformer