Replies: 2 comments
-
Hi @BDoignies
There is nothing specific to samplers, as far as I know. In short, Dr.Jit will "read"/"trace" through the entire path tracer source code with a single thread. Once it has established the computation graph that will be compiled into LLVM it executes it in parallel with many threads.
I think this is mostly because we were trying to be cautious. We've never tried it ourselves and haven't fully thought through all the implications of trying to differentiate the
We've been aware of issues in the task (thread pool) manager, but have never figured out the root cause. If you're willing to share more of your code we might be able to finally narrow down this long lasting issue.
That is one option. The easiest would be to replace the line you found in the |
Beta Was this translation helpful? Give feedback.
-
Thanks for your reply ! I will try to check later with finite difference methods if the magnitude and sign are correct. It is indeed true that the convergence seems to be very slow (even with higher lr), but I thought it was just the targets which have very small gradients. I have followed the tutorial here, but if you find anything wrong, please let me know. import mitsuba as mi
import drjit as dr
import numpy as np
import matplotlib.pyplot as plt
from tqdm import tqdm
# Some parameters for sampler, global
# so it can be shared outside the class
MAX_SAMPLES = 22
SAMPLE_NAMES = [f"samples_{i}" for i in range(MAX_SAMPLES)]
mi.set_variant('llvm_ad_rgb')
def get_ref(spp=128):
scene = mi.cornell_box()
mscene = mi.load_dict(scene)
return mi.render(mscene, spp=spp)
def mse(img, ref):
return dr.mean(dr.sqr(img - ref))
class SamplerOptim(mi.Sampler):
def __init__(self, props):
super().__init__(props)
self.shape = (1, MAX_SAMPLES)
# Instantiate variables independantly
self.samples = []
# Starts in the middle of the cube (arbitrary)
for _ in range(np.prod(self.shape)):
self.samples.append(mi.Float(0.5))
self.sample_id = 0
def next_1d(self: mi.Sampler, active: bool = True) -> mi.Float:
id = self.sample_id
self.sample_id = (self.sample_id + 1) % self.shape[1]
return self.samples[id]
def next_2d(self: mi.Sampler, active: bool = True) -> mi.Point2f:
x = self.next_1d(active)
y = self.next_1d(active)
return mi.Point2f(x, y)
def traverse(self, callback):
# Trick to enable differentiability
f_before = callback.flags
callback.flags = int(mi.ParamFlags.Differentiable)
for i, sp in enumerate(self.samples):
callback.put_parameter(SAMPLE_NAMES[i], sp, mi.ParamFlags.Differentiable | mi.ParamFlags.Discontinuous)
# Restore flags
callback.flags = f_before
mi.register_sampler("sampler_optim", lambda props: SamplerOptim(props))
ref = get_ref()
scene = mi.cornell_box()
scene["sensor"]["sampler"]["type"] = "sampler_optim"
scene["integrator"]["max_depth"] = 2
mscene = mi.load_dict(scene)
params = mi.traverse(mscene)
keys = ["sensor.sampler." + sname for sname in SAMPLE_NAMES]
params.keep(keys)
# Optimizer
opt = mi.ad.Adam(lr=1e-4)
for k in keys:
opt[k] = params[k]
dr.enable_grad(params[k])
params.update(opt)
image = mi.render(mscene, params, spp=1)
bimg = mi.Bitmap(image)
mi.util.write_bitmap("init.png", bimg)
# mi.util.write_bitmap("init.exr", bimg)
errors = []
for it in tqdm(range(2500)):
image = mi.render(mscene, params, spp=1)
loss = mse(image, ref)
dr.backward(loss)
opt.step()
for k in keys:
opt[k] = dr.clamp(opt[k], 0.0, 1.0)
params.update(opt)
errors.append(loss)
# Save regularly because of crashes
if it % 50 == 0:
# Retrieve samples values
out_samples = []
for k in keys:
out_samples.append(opt[k])
# print(errors[0], errors[-1])
bimg = mi.Bitmap(image)
plt.plot(errors)
plt.savefig("errors.png")
plt.close()
np.savetxt("errors.txt", errors)
np.savetxt("samples.txt", out_samples)
mi.util.write_bitmap("out.png", bimg)
# mi.util.write_bitmap("out.exr", bimg) |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
Hello,
I've been playing with Mitsuba3 (in python) lately and I'm interested in the differentiable aspect of the renderer. In particular, I'm interested in differentiation with respect to samples. Fortunately, the code allows you to register custom plugins (samplers), override functionality and register parameters. Unfortunately for me, however, differentiation with any sampler parameter is disabled (if I'm not mistaken this is the line that disables it).
It is possible to trick the system and here is a simple and minimal plugin (for a single spp sampler) I used:
And... surprisingly, it roughly works. With a simple MSE it changes the sample position and reduces the loss. But... But sometimes the code crashes. The exact error is : "Assertion failed in /project/ext/drjit-core/ext/nanothread/src/queue.cpp:354: remain == 1" which I think is due to multi-threading. I do not know if I am using the plugin correctly, but forcing some sample values in the initialisation loop gave coherent results.
I know this is hacky. And to be honest, I did not expect the code to work at all. But it seems to work; at least partially. So I have a few questions :
I have not been able to dig into the code and answer these questions myself (especially for Q2/Q3). If any information is missing, please let me know.
Thank you !
I'm sorry if I've missed any previous discussion on this. A search with the keywords
sample
/sampler
did not return any similar problems. Here is some system information if it helps:mi.cornell_box()
Beta Was this translation helpful? Give feedback.
All reactions