//  [python]
def forward(self, rays_o, rays_d, viewdirs, global_step=None, **render_kwargs):
        '''Volume rendering'''

        if global_step is not None:
            if global_step in [1, 100, 500, 1000, 2000, 3000, 5000, 10000, 15000, 16000, 17000, 18000, 19000, 20000] or global_step % self.set_sphere_freq == 0:
                self._set_sphere_nonempty_mask()

        ret_dict = {}
        N = len(rays_o)

        # sample points on rays
        # inner_pts, inner_ray_id, inner_step_id, mask_outbbox, N_steps = self.sample_ray_cuda(
        #         rays_o=rays_o, rays_d=rays_d, is_train=global_step is not None, **render_kwargs)
        inner_pts, mask_outbbox, step = self.sample_ray_ori(
            rays_o=rays_o, rays_d=rays_d, is_train=global_step is not None, use_bg=False, **render_kwargs)
        inner_ray_id, inner_step_id = create_full_step_id(inner_pts.shape[:2])
            
        pts_norm = torch.linalg.norm(inner_pts, ord=2, dim=-1, keepdim=True)
        inside_sphere = (pts_norm < 1.0).view(-1)
        inner_pts, inner_ray_id, inner_step_id = \
            inner_pts.view(-1, 3)[inside_sphere], inner_ray_id[inside_sphere], inner_step_id[inside_sphere]
            
        bg_render_kwargs = copy.deepcopy(render_kwargs)
        ''' old sample ray '''
        outer_pts_org, bg_mask_outbbox, bg_step = self.sample_ray_ori(
            rays_o=rays_o, rays_d=rays_d, is_train=global_step is not None, use_bg=False, **bg_render_kwargs)
        outer_ray_id, outer_step_id = create_full_step_id(outer_pts_org.shape[:2])

        bg_pts_norm = torch.linalg.norm(outer_pts_org, ord=2, dim=-1, keepdim=True)
        bg_inside_sphere = (bg_pts_norm < 1.0)[...,0]
        outer_pts = self.outside_sphere_trans(outer_pts_org, bg_pts_norm, filtered=True)
        bg_mask = ~bg_inside_sphere
        dist_thres = self.voxel_size * render_kwargs['stepsize'] * 0.5
        dist = (outer_pts[:, 1:] - outer_pts[:, :-1]).norm(dim=-1)
        dist_mask = ub360_utils_cuda.cumdist_thres(dist, dist_thres)
        bg_mask[:,1:] &= dist_mask
        outer_pts, outer_ray_id, outer_step_id = \
            outer_pts[bg_mask], outer_ray_id[bg_mask.view(-1)], outer_step_id[bg_mask.view(-1)]
        outer_pts_org = outer_pts_org[bg_mask]

        if self.smooth_sdf:
            sdf_grid = self.smooth_conv(self.sdf.grid)
        else:
            sdf_grid = self.sdf.grid
        sdf, gradient, feat = self.grid_sampler(inner_pts, sdf_grid, sample_ret=True, sample_grad=True, displace=1.0)
        self.gradient = self.neus_sdf_gradient(sdf=sdf_grid)

        dist = render_kwargs['stepsize'] * self.voxel_size
        s_val, in_alpha = self.neus_alpha_from_sdf_scatter(viewdirs, inner_ray_id, dist, sdf, gradient, global_step=global_step,
                                                    is_train=global_step is not None, use_mid=True)

        in_weights, in_alphainv_last = Alphas2Weights.apply(in_alpha, inner_ray_id, N)
        

        if self.fast_color_thres > 0:
            mask = in_weights > self.fast_color_thres
            in_weights = in_weights[mask]
            inner_pts = inner_pts[mask]
            inner_ray_id = inner_ray_id[mask]
            inner_step_id = inner_step_id[mask]
            in_alpha = in_alpha[mask]
            gradient = gradient[mask]
            sdf = sdf[mask]

        # assert self.separate_modelling
        bg_interval = bg_render_kwargs['stepsize'] * self.voxel_size_ratio
        bg_density = self.bg_density(outer_pts)
        bg_alpha = self.activate_density(bg_density, bg_interval)

        bg_weights, bg_alphainv_last = Alphas2Weights.apply(bg_alpha, outer_ray_id, N)
        
        
        if self.bg_fast_color_thres > 0:	
            mask = bg_weights > self.fast_color_thres
            outer_pts = outer_pts[mask]	
            outer_ray_id = outer_ray_id[mask]	
            outer_step_id = outer_step_id[mask]	
            bg_alpha = bg_alpha[mask]	
            outer_pts_org = outer_pts_org[mask]
            bg_weights = bg_weights[mask]
        
        rgb_feat = []
        k_rgb_feat = []
        if self.k0_dim > 0:
            k0 = self.k0(inner_pts)
            if self.use_rgbnet_k0:
                rgb_feat.append(k0)
            k_rgb_feat.append(k0)
                
        all_grad_inds = list(set(self.grad_feat + self.k_grad_feat))
        all_sdf_inds = list(set(self.sdf_feat + self.k_sdf_feat))
        
        assert all_grad_inds == all_sdf_inds
        
        if len(all_grad_inds) > 0: 
            all_grad_inds = sorted(all_grad_inds)
            all_grad_inds_ = deepcopy(all_grad_inds)
            all_feat, all_grad = self.sample_sdfs(inner_pts, sdf_grid, displace_list=all_grad_inds_, use_grad_norm=self.use_grad_norm)
            rgb_feat.append(all_feat)
            rgb_feat.append(all_grad)
        else:
            all_feat, all_grad = None, None

        if self.use_xyz:
            rays_xyz = (inner_pts - self.xyz_min) / (self.xyz_max - self.xyz_min)
            xyz_emb = (rays_xyz.unsqueeze(-1) * self.posfreq).flatten(-2)
            xyz_emb = torch.cat([rays_xyz, xyz_emb.sin(), xyz_emb.cos()], -1)
            rgb_feat.append(xyz_emb)
        if self.use_view:
            viewdirs_emb = (viewdirs.unsqueeze(-1) * self.viewfreq).flatten(-2)
            viewdirs_emb = torch.cat(
                [viewdirs, viewdirs_emb.sin(), viewdirs_emb.cos()], -1)
            rgb_feat.append(viewdirs_emb.flatten(0, -2)[inner_ray_id])
        if self.center_sdf:
            rgb_feat.append(sdf[:, None])
        rgb_feat = torch.cat(rgb_feat, -1)
        rgb_logit = self.rgbnet(rgb_feat)
        rgb = torch.sigmoid(rgb_logit)
        
	    # outside
        bg_rgb_feat = []
        if self.bg_k0_dim > 0:
            bg_k0 = self.bg_k0(outer_pts)
            bg_rgb_feat.append(bg_k0)
        if self.bg_use_xyz:
            bg_rays_xyz = (outer_pts_org - self.xyz_min) / (self.xyz_max - self.xyz_min)
            bg_xyz_emb = (bg_rays_xyz.unsqueeze(-1) * self.bg_posfreq).flatten(-2)
            bg_xyz_emb = torch.cat(
                [bg_rays_xyz, bg_xyz_emb.sin(), bg_xyz_emb.cos()], -1)
            bg_rgb_feat.append(bg_xyz_emb)
        if self.bg_use_view:
            bg_viewdirs_emb = (viewdirs.unsqueeze(-1) * self.bg_viewfreq).flatten(-2)
            bg_viewdirs_emb = torch.cat(
                [viewdirs, bg_viewdirs_emb.sin(), bg_viewdirs_emb.cos()], -1)
            bg_rgb_feat.append(bg_viewdirs_emb.flatten(0, -2)[outer_ray_id])
        bg_rgb_feat = torch.cat(bg_rgb_feat, -1)
        bg_rgb_logit = self.bg_rgbnet(bg_rgb_feat)
        bg_rgb = torch.sigmoid(bg_rgb_logit)
        
        in_marched = segment_coo(
            src=(in_weights.unsqueeze(-1) * rgb),
            index=inner_ray_id, out=torch.zeros([N, 3]), reduce='sum')
        bg_marched = segment_coo(
            src=(bg_weights.unsqueeze(-1) * bg_rgb),
            index=outer_ray_id, out=torch.zeros([N, 3]), reduce='sum')
        cum_in_weights = segment_coo(
            src=(in_weights.unsqueeze(-1)),
            index=inner_ray_id, out=torch.zeros([N, 1]), reduce='sum')
        bg_marched = bg_marched.clamp(0, 1)
        in_marched = in_marched.clamp(0, 1)
        rgb_marched = in_marched + (1 - cum_in_weights) * bg_marched
        rgb_marched = rgb_marched.clamp(0, 1)
        in_marched = in_marched

        if self.use_rgb_k:
            if self.k_use_xyz:
                k_rays_xyz = (inner_pts - self.xyz_min) / (self.xyz_max - self.xyz_min)
                k_xyz_emb = (k_rays_xyz.unsqueeze(-1) * self.k_posfreq).flatten(-2)
                k_xyz_emb = torch.cat([k_rays_xyz, k_xyz_emb.sin(), k_xyz_emb.cos()], -1)
                k_rgb_feat.append(k_xyz_emb)
            if self.k_use_view:
                k_viewdirs_emb = (viewdirs.unsqueeze(-1) * self.k_viewfreq).flatten(-2)
                k_viewdirs_emb = torch.cat(
                    [viewdirs, k_viewdirs_emb.sin(), k_viewdirs_emb.cos()], -1)
                k_rgb_feat.append(k_viewdirs_emb.flatten(0, -2)[inner_ray_id])
            if self.k_center_sdf:
                k_rgb_feat.append(sdf[:, None])
                
            k_rgb_feat.append(gradient)
            if self.k_res:
                color_feat = rgb_logit
                if self.k_detach_1:
                    k_rgb_feat.append(color_feat.detach())
                else:
                    k_rgb_feat.append(color_feat)
            k_rgb_feat = torch.cat(k_rgb_feat, -1)
            k_rgb_logit = self.k_rgbnet(k_rgb_feat)
            if self.k_detach_2:
                k_rgb_logit = rgb_logit.detach() + k_rgb_logit
            else:
                k_rgb_logit = rgb_logit + k_rgb_logit
            k_rgb = torch.sigmoid(k_rgb_logit)
            k_in_marched = segment_coo(
                src=(in_weights.unsqueeze(-1) * k_rgb),
                index=inner_ray_id, out=torch.zeros([N, 3]), reduce='sum')
            k_in_marched = k_in_marched.clamp(0, 1)
            k_rgb_marched = k_in_marched + (1 - cum_in_weights) * bg_marched
            k_rgb_marched = k_rgb_marched.clamp(0, 1)
            in_marched = k_in_marched
        else:
            k_rgb_marched = None

        # Ray marching
        if gradient is not None and render_kwargs.get('render_grad', False):
            normal = gradient / (gradient.norm(2, -1, keepdim=True) + 1e-6)
            normal_marched = segment_coo(
                src=(in_weights.unsqueeze(-1) * normal),
                index=inner_ray_id, out=torch.zeros([N, 3]), reduce='sum')
        else:
            normal_marched = None

        out_marched = bg_marched

        if render_kwargs.get('render_depth', False):
            with torch.no_grad():
                depth = segment_coo(
                    src=(in_weights * inner_step_id * dist),
                    index=inner_ray_id, out=torch.zeros([N]), reduce='sum')
                disp = 1 / depth
        else:
            depth = None
            disp = 0
            
        ret_dict.update({
            'alphainv_cum': (1 - cum_in_weights)[..., 0],
            'weights': in_weights,
            'bg_weights': bg_weights,
            'pts_norm': pts_norm,
            'rgb_marched': rgb_marched,
            # 'k_rgb_marched': k_rgb_marched,
            'in_marched': in_marched,
            'out_marched': out_marched,
            'normal_marched': normal_marched,
            'raw_alpha': in_alpha,
            'raw_rgb': rgb,
            'depth': depth,
            'disp': disp,
            'mask': mask,
            'mask_outbbox':mask_outbbox,
            'gradient': gradient,
            "gradient_error": None,
            "s_val": s_val,
        })
        if self.use_rgb_k:
            ret_dict.update({
            'rgb_marched': k_rgb_marched,
            'rgb_marched0': rgb_marched,
            })
        return ret_dict