Skip to content

Commit 14bc228

Browse files
committed
add slangPy neural slang integarion tests
1 parent 4293931 commit 14bc228

File tree

6 files changed

+309
-0
lines changed

6 files changed

+309
-0
lines changed
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
2+
"""
3+
Neural smoke test that actually exercises Slang autodiff (`bwd_diff(...)`).
4+
5+
Important constraints:
6+
- No dependency on sample apps under `samples/`.
7+
- No dependency on external assets (e.g. image files).
8+
9+
This uses the test-local Slang module `fflayer-bug-repro.slang` which imports the
10+
experimental `neural` module and calls `bwd_diff(loss)(DifferentialPtrPair<Storage>(...), ...)`.
11+
"""
12+
13+
from __future__ import annotations
14+
15+
from pathlib import Path
16+
17+
import numpy as np
18+
import pytest
19+
20+
import slangpy as spy
21+
from slangpy.core.calldata import SLANG_PATH
22+
from slangpy.testing import helpers
23+
24+
25+
def _get_device_with_native_neural(device_type: spy.DeviceType) -> spy.Device:
26+
if helpers.should_skip_test_for_device(device_type):
27+
pytest.skip(f"Device type {device_type.name} not selected for this test run")
28+
29+
test_dir = Path(__file__).resolve().parent
30+
compiler_options = spy.SlangCompilerOptions(
31+
{
32+
"include_paths": [test_dir, SLANG_PATH],
33+
"debug_info": spy.SlangDebugInfoLevel.standard,
34+
"enable_experimental_features": True,
35+
}
36+
)
37+
38+
return spy.Device(
39+
type=device_type,
40+
enable_debug_layers=True,
41+
compiler_options=compiler_options,
42+
label=f"uncached-slangpy-neural-bwd-diff-{device_type.name}",
43+
)
44+
45+
46+
@pytest.mark.parametrize("device_type", helpers.DEFAULT_DEVICE_TYPES)
47+
def test_neural_bwd_diff_writes_param_grads(device_type: spy.DeviceType) -> None:
48+
device = _get_device_with_native_neural(device_type)
49+
try:
50+
module = spy.Module(device.load_module("fflayer-bug-repro.slang"))
51+
52+
# 2*2 weights + 2 biases = 6 floats (matches `fflayer-bug-repo.py`)
53+
params = device.create_buffer(
54+
data=np.ones((6,), dtype=np.float32),
55+
usage=spy.BufferUsage.shader_resource | spy.BufferUsage.unordered_access,
56+
)
57+
dparams = device.create_buffer(
58+
data=np.zeros((6,), dtype=np.float32),
59+
usage=spy.BufferUsage.shader_resource | spy.BufferUsage.unordered_access,
60+
)
61+
62+
module.calculate_grad(input=spy.float2(1, 1), params=params, dparams=dparams)
63+
64+
dparams_np = dparams.to_numpy().view(np.float32)
65+
assert np.any(dparams_np != 0.0)
66+
finally:
67+
device.close()
68+
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
2+
3+
"""
4+
SlangPy integration test for neural module FFLayer (Option 2 design).
5+
6+
Tests training convergence for a simple quadratic regression task using:
7+
- FFLayer with storage passed as parameter to eval<S>()
8+
- Manual gradient computation (analytic gradients)
9+
- Simple SGD optimization
10+
11+
We fit a quadratic polynomial y = 2*x^2 - 0.5*x + 0.25 and verify convergence.
12+
"""
13+
14+
from __future__ import annotations
15+
16+
from pathlib import Path
17+
18+
import numpy as np
19+
import pytest
20+
21+
import slangpy as spy
22+
from slangpy.core.calldata import SLANG_PATH
23+
from slangpy.testing import helpers
24+
25+
26+
def _get_device_with_native_neural(device_type: spy.DeviceType) -> spy.Device:
27+
if helpers.should_skip_test_for_device(device_type):
28+
pytest.skip(f"Device type {device_type.name} not selected for this test run")
29+
30+
test_dir = Path(__file__).resolve().parent
31+
32+
# Use pre-built neural module from slang (not compiled from source)
33+
# The neural module is built as part of slang-neural-module target
34+
# Enable experimental features since neural is an experimental module
35+
compiler_options = spy.SlangCompilerOptions(
36+
{
37+
"include_paths": [test_dir, SLANG_PATH],
38+
"debug_info": spy.SlangDebugInfoLevel.standard,
39+
"enable_experimental_features": True,
40+
}
41+
)
42+
43+
return spy.Device(
44+
type=device_type,
45+
enable_debug_layers=True,
46+
compiler_options=compiler_options,
47+
label=f"uncached-slangpy-neural-frontend-{device_type.name}",
48+
)
49+
50+
51+
@pytest.mark.parametrize("device_type", helpers.DEFAULT_DEVICE_TYPES)
52+
def test_neural_frontend_training_converges(device_type: spy.DeviceType) -> None:
53+
"""
54+
Test that training converges for a simple quadratic regression task.
55+
56+
Uses FFLayer with Option 2 design (storage as parameter to eval<S>).
57+
"""
58+
device = _get_device_with_native_neural(device_type)
59+
try:
60+
module = spy.Module(device.load_module("test_neural_frontend_training.slang"))
61+
62+
param_count = int(module.get_param_count())
63+
assert param_count == 3
64+
65+
# Fit: y = 2*x^2 - 0.5*x + 0.25
66+
sample_count = 256
67+
xs = np.linspace(-1.0, 1.0, sample_count, dtype=np.float32)
68+
ys = (2.0 * xs * xs - 0.5 * xs + 0.25).astype(np.float32)
69+
70+
xs_buf = device.create_buffer(data=xs, usage=spy.BufferUsage.shader_resource)
71+
ys_buf = device.create_buffer(data=ys, usage=spy.BufferUsage.shader_resource)
72+
73+
rng = np.random.default_rng(0)
74+
params_init = (0.01 * rng.standard_normal(size=(param_count,))).astype(np.float32)
75+
76+
params = device.create_buffer(
77+
data=params_init,
78+
usage=spy.BufferUsage.shader_resource | spy.BufferUsage.unordered_access,
79+
)
80+
grads = device.create_buffer(
81+
data=np.zeros((param_count,), dtype=np.float32),
82+
usage=spy.BufferUsage.shader_resource | spy.BufferUsage.unordered_access,
83+
)
84+
85+
initial_loss = float(module.eval_loss(params, xs_buf, ys_buf, sample_count))
86+
87+
learning_rate = 0.1
88+
steps = 200
89+
for _ in range(steps):
90+
module.train_step(params, grads, xs_buf, ys_buf, sample_count, learning_rate)
91+
92+
final_loss = float(module.eval_loss(params, xs_buf, ys_buf, sample_count))
93+
94+
# Convergence: should significantly reduce MSE and reach a small absolute error.
95+
assert final_loss < initial_loss * 1e-2
96+
assert final_loss < 1e-3
97+
98+
# Parameter packing: [w0, w1, bias] for y = w0*x + w1*x^2 + bias
99+
learned = params.to_numpy().view(np.float32)[:param_count]
100+
expected = np.array([-0.5, 2.0, 0.25], dtype=np.float32)
101+
assert np.allclose(learned, expected, rtol=0.1, atol=0.1)
102+
103+
finally:
104+
device.close()
105+
106+
107+
if __name__ == "__main__":
108+
pytest.main([__file__, "-v", "-s"])
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
2+
3+
// SlangPy test for FFLayer autodiff backward pass (Option 2 design).
4+
// Verifies that autodiff correctly computes gradients through eval<S>().
5+
//
6+
// We fit a quadratic polynomial y = 2*x^2 - 0.5*x + 0.25 using a single linear layer over
7+
// features [x, x^2], and verify training converges.
8+
9+
import slangpy;
10+
import neural;
11+
12+
typealias Storage = StructuredBufferStorage<float>;
13+
typealias V2 = InlineVector<float, 2>;
14+
typealias V1 = InlineVector<float, 1>;
15+
typealias Act = IdentityActivation<float>;
16+
17+
// Linear layer: Input=2 (x, x^2), Output=1 (y), with bias
18+
// Parameters: weights (1x2) + bias (1) = 3 params
19+
typealias LinearLayer = FFLayer<float, V2, V1, Storage, Act, true>;
20+
21+
static const int PARAM_COUNT = LinearLayer.ParameterCount;
22+
23+
int get_param_count()
24+
{
25+
return PARAM_COUNT;
26+
}
27+
28+
float eval_loss(
29+
RWStructuredBuffer<float> params,
30+
StructuredBuffer<float> xs,
31+
StructuredBuffer<float> ys,
32+
int count)
33+
{
34+
let storage = Storage(params);
35+
// Option 2: only addresses in constructor
36+
// weights at 0 (2 floats), bias at 2 (1 float)
37+
let layer = LinearLayer(0, 2);
38+
39+
float sum = 0.0;
40+
[MaxIters(1024)]
41+
for (int i = 0; i < count; i++)
42+
{
43+
let x = xs[i];
44+
45+
float featsArr[2] = { x, x * x };
46+
let feats = V2(featsArr);
47+
48+
// Option 2: storage passed to eval<S>()
49+
let predV = layer.eval<Storage>(storage, NoParam(), feats);
50+
let pred = predV[0];
51+
let target = ys[i];
52+
53+
let err = pred - target;
54+
sum += err * err;
55+
}
56+
57+
return sum / float(count);
58+
}
59+
60+
float train_step(
61+
RWStructuredBuffer<float> params,
62+
RWStructuredBuffer<float> grads,
63+
no_diff StructuredBuffer<float> xs,
64+
no_diff StructuredBuffer<float> ys,
65+
no_diff int count,
66+
no_diff float learningRate)
67+
{
68+
let pStorage = Storage(params);
69+
let gStorage = Storage(grads);
70+
71+
// Clear gradient buffer
72+
[MaxIters(1024)]
73+
for (int i = 0; i < PARAM_COUNT; i++)
74+
grads[i] = 0.0;
75+
76+
// Option 2: only addresses in constructor
77+
let layer = LinearLayer(0, 2);
78+
79+
// Accumulate analytic grads for y = w0*x + w1*x^2 + b, loss = mean((y - t)^2)
80+
float g0 = 0.0;
81+
float g1 = 0.0;
82+
float gb = 0.0;
83+
84+
float lossSum = 0.0;
85+
86+
[MaxIters(1024)]
87+
for (int i = 0; i < count; i++)
88+
{
89+
let x = xs[i];
90+
let t = ys[i];
91+
92+
float featsArr[2] = { x, x * x };
93+
let feats = V2(featsArr);
94+
95+
// Option 2: storage passed to eval<S>()
96+
let predV = layer.eval<Storage>(pStorage, NoParam(), feats);
97+
let pred = predV[0];
98+
99+
let err = pred - t;
100+
lossSum += err * err;
101+
102+
g0 += 2.0 * err * x;
103+
g1 += 2.0 * err * (x * x);
104+
gb += 2.0 * err;
105+
}
106+
107+
let invN = 1.0 / float(count);
108+
grads[0] = g0 * invN;
109+
grads[1] = g1 * invN;
110+
grads[2] = gb * invN;
111+
112+
// Simple SGD update: params -= lr * grads
113+
[MaxIters(1024)]
114+
for (int i = 0; i < PARAM_COUNT; i++)
115+
{
116+
params[i] = params[i] - learningRate * grads[i];
117+
}
118+
119+
return lossSum * invN;
120+
}

src/sgl/device/shader.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,10 @@ void SlangSession::create_session(SlangSessionBuild& build)
333333
session_options.add(slang::CompilerOptionName::DumpIntermediates, options.dump_intermediates);
334334
session_options.add(slang::CompilerOptionName::DumpIntermediatePrefix, options.dump_intermediates_prefix);
335335

336+
// Enable experimental features (e.g., experimental modules like neural).
337+
if (options.enable_experimental_features)
338+
session_options.add(slang::CompilerOptionName::ExperimentalFeature, true);
339+
336340
// Add hlsl_nvapi capability.
337341
session_options.add(
338342
slang::CompilerOptionName::Capability,

src/sgl/device/shader.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,9 @@ struct SlangCompilerOptions {
183183
/// Specifies a list of additional arguments to be passed to the downstream compiler.
184184
std::vector<std::string> downstream_args;
185185

186+
/// Enable experimental features (e.g., experimental modules like neural).
187+
bool enable_experimental_features{false};
188+
186189
/// When set will dump the intermediate source output.
187190
bool dump_intermediates{false};
188191

src/slangpy_ext/device/shader.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ SGL_DICT_TO_DESC_FIELD(floating_point_mode, SlangFloatingPointMode)
2424
SGL_DICT_TO_DESC_FIELD(debug_info, SlangDebugInfoLevel)
2525
SGL_DICT_TO_DESC_FIELD(optimization, SlangOptimizationLevel)
2626
SGL_DICT_TO_DESC_FIELD_LIST(downstream_args, std::string)
27+
SGL_DICT_TO_DESC_FIELD(enable_experimental_features, bool)
2728
SGL_DICT_TO_DESC_FIELD(dump_intermediates, bool)
2829
SGL_DICT_TO_DESC_FIELD(dump_intermediates_prefix, std::string)
2930
SGL_DICT_TO_DESC_END()
@@ -135,6 +136,11 @@ SGL_PY_EXPORT(device_shader)
135136
.def_rw("debug_info", &SlangCompilerOptions::debug_info, D(SlangCompilerOptions, debug_info))
136137
.def_rw("optimization", &SlangCompilerOptions::optimization, D(SlangCompilerOptions, optimization))
137138
.def_rw("downstream_args", &SlangCompilerOptions::downstream_args, D(SlangCompilerOptions, downstream_args))
139+
.def_rw(
140+
"enable_experimental_features",
141+
&SlangCompilerOptions::enable_experimental_features,
142+
"Enable experimental features (e.g., experimental modules like neural)."
143+
)
138144
.def_rw(
139145
"dump_intermediates",
140146
&SlangCompilerOptions::dump_intermediates,

0 commit comments

Comments
 (0)