From 821d331700eb274fb24f470b7e38ac85e2854213 Mon Sep 17 00:00:00 2001 From: brian Date: Mon, 14 Jul 2025 11:22:36 +0800 Subject: [PATCH 1/3] add kan files --- MindFlow/mindflow/cell/__init__.py | 3 +- MindFlow/mindflow/cell/kan.py | 485 ++++++++++++++++++ .../st/mindflow/cell/kan/data/curve2coef.npz | Bin 0 -> 1758 bytes .../st/mindflow/cell/kan/data/init_output.npz | Bin 0 -> 838 bytes tests/st/mindflow/cell/kan/data/init_x.npy | Bin 0 -> 1328 bytes tests/st/mindflow/cell/kan/data/kan.ckpt | Bin 0 -> 551 bytes tests/st/mindflow/cell/kan/data/kan.npz | Bin 0 -> 48340 bytes tests/st/mindflow/cell/kan/data/kan.pt | Bin 0 -> 2925 bytes tests/st/mindflow/cell/kan/data/subset.npz | Bin 0 -> 662 bytes .../mindflow/cell/kan/data/update_output.npz | Bin 0 -> 742 bytes tests/st/mindflow/cell/kan/test_kan.py | 141 +++++ 11 files changed, 628 insertions(+), 1 deletion(-) create mode 100644 MindFlow/mindflow/cell/kan.py create mode 100644 tests/st/mindflow/cell/kan/data/curve2coef.npz create mode 100644 tests/st/mindflow/cell/kan/data/init_output.npz create mode 100644 tests/st/mindflow/cell/kan/data/init_x.npy create mode 100644 tests/st/mindflow/cell/kan/data/kan.ckpt create mode 100644 tests/st/mindflow/cell/kan/data/kan.npz create mode 100644 tests/st/mindflow/cell/kan/data/kan.pt create mode 100644 tests/st/mindflow/cell/kan/data/subset.npz create mode 100644 tests/st/mindflow/cell/kan/data/update_output.npz create mode 100644 tests/st/mindflow/cell/kan/test_kan.py diff --git a/MindFlow/mindflow/cell/__init__.py b/MindFlow/mindflow/cell/__init__.py index d71670098..69fab7c56 100644 --- a/MindFlow/mindflow/cell/__init__.py +++ b/MindFlow/mindflow/cell/__init__.py @@ -18,6 +18,7 @@ from .basic_block import LinearBlock, ResBlock, InputScale, FCSequential, MultiS from .neural_operators import FNO1D, FNO2D, FNO3D, KNO1D, KNO2D, PDENet, PeRCNN, SNO, SNO1D, SNO2D, SNO3D from .attention import Attention, MultiHeadAttention, TransformerBlock from .vit import ViT +from .kan import KANLayer, curve2coef from .unet2d import UNet2D from .sno_utils import poly_data, get_poly_transform, interpolate_1d_dataset, interpolate_2d_dataset from .diffusion import DiffusionScheduler, DiffusionTrainer, DDPMScheduler, DDIMScheduler, DDPMPipeline, DDIMPipeline @@ -26,6 +27,6 @@ from .diffusion_transformer import DiffusionTransformer, ConditionDiffusionTrans __all__ = ["get_activation", "FNO1D", "FNO2D", "FNO3D", "KNO1D", "KNO2D", "PDENet", "UNet2D", "PeRCNN", "SNO", "SNO1D", "SNO2D", "SNO3D", "Attention", "MultiHeadAttention", "TransformerBlock", "ViT", "DDPMPipeline", "DDIMPipeline", "DiffusionTrainer", "DiffusionScheduler", "DDPMScheduler", - "DDIMScheduler", "DiffusionTransformer", "ConditionDiffusionTransformer"] + "DDIMScheduler", "DiffusionTransformer", "ConditionDiffusionTransformer", "KANLayer", "curve2coef"] __all__.extend(basic_block.__all__) __all__.extend(sno_utils.__all__) diff --git a/MindFlow/mindflow/cell/kan.py b/MindFlow/mindflow/cell/kan.py new file mode 100644 index 000000000..9f5021a1e --- /dev/null +++ b/MindFlow/mindflow/cell/kan.py @@ -0,0 +1,485 @@ +# Copyright 2025 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""KAN api""" +from mindspore import Tensor, Parameter, ops, nn, mint +from mindspore.scipy.linalg import lstsq +from mindspore.common import dtype as mstype +import numpy as np +# pylint: disable=C0103 +# pylint: disable=W0102 + + +def sparse_mask(in_dim, out_dim): + ''' + get sparse mask + ''' + in_coord = ops.arange(in_dim) * 1/in_dim + 1/(2*in_dim) + out_coord = ops.arange(out_dim) * 1/out_dim + 1/(2*out_dim) + + dist_mat = ops.abs(out_coord[:, None] - in_coord[None, :]) + in_nearest = ops.argmin(dist_mat, axis=0) + in_connection = ops.stack([ops.arange(in_dim), in_nearest]).T + out_nearest = ops.argmin(dist_mat, axis=1) + out_connection = ops.stack([out_nearest, ops.arange(out_dim)]).T + all_connection = ops.cat([in_connection, out_connection], axis=0) + mask = ops.zeros((in_dim, out_dim), mstype.float32) + mask[all_connection[:, 0], all_connection[:, 1]] = 1.0 + + return mask + + +def B_batch(x, grid, k=0): + ''' + evaludate x on B-spline bases + + Args: + ----- + x : 2D Tensor + inputs, shape (number of splines, number of samples) + grid : 2D Tensor + grids, shape (number of splines, number of grid points) + k : int + the piecewise polynomial order of splines. + extend : bool + If True, k points are extended on both ends. If False, no extension (zero boundary condition). + Default: True + + Returns: + -------- + spline values : 3D Tensor + shape (batch, in_dim, G+k). G: the number of grid intervals, k: spline order. + + Example + ------- + >>> from kan.spline import B_batch + >>> x = ops.rand(100,2) + >>> grid = ops.linspace(-1, 1, steps=11)[None, :].expand(2, 11) + >>> B_batch(x, grid, k=3).shape + ''' + + x = x.expand_dims(axis=2) + grid = grid.expand_dims(axis=0) + + if k == 0: + value = (x >= grid[:, :, :-1]) * (x < grid[:, :, 1:]) + else: + B_km1 = B_batch(x[:, :, 0], grid=grid[0], k=k - 1) + term1 = (x - grid[:, :, :-(k + 1)]) / (grid[:, :, k:-1] - + grid[:, :, :-(k + 1)] + 1e-8) * B_km1[:, :, :-1] + term2 = (grid[:, :, k + 1:] - x) / (grid[:, :, k + 1:] - + grid[:, :, 1:(-k)] + 1e-8) * B_km1[:, :, 1:] + value = term1 + term2 + + return ops.nan_to_num(value) + + +def coef2curve(x_eval, grid, coef, k): + ''' + converting B-spline coefficients to B-spline curves. + Evaluate x on B-spline curves (summing up B_batch results over B-spline basis). + + Args: + ----- + x_eval : 2D Tensor + shape (batch, in_dim) + grid : 2D Tensor + shape (in_dim, G+2k). G: the number of grid intervals; k: spline order. + coef : 3D Tensor + shape (in_dim, out_dim, G+k) + k : int + the piecewise polynomial order of splines. + + Returns: + -------- + y_eval : 3D Tensor + shape (batch, in_dim, out_dim) + + ''' + b_splines = B_batch(x_eval, grid, k=k) + y_eval = mint.einsum('ijk,jlk->ijl', b_splines, coef) + return y_eval + + +def curve2coef(x_eval, y_eval, grid, k): + ''' + converting B-spline curves to B-spline coefficients using least squares. + + Args: + ----- + x_eval : 2D Tensor + shape (batch, in_dim) + y_eval : 3D Tensor + shape (batch, in_dim, out_dim) + grid : 2D Tensor + shape (in_dim, grid+2*k) + k : int + spline order + lamb : float + regularized least square lambda + + Returns: + -------- + coef : 3D Tensor + shape (in_dim, out_dim, G+k) + ''' + batch = x_eval.shape[0] + in_dim = x_eval.shape[1] + out_dim = y_eval.shape[2] + n_coef = grid.shape[1] - k - 1 + + mat = B_batch(x_eval, grid, k) + mat = mat.transpose(1, 0, 2).expand_dims( + axis=1).broadcast_to((in_dim, out_dim, batch, n_coef)) + y_eval = y_eval.transpose(1, 2, 0).expand_dims(axis=3) + coef = lstsq(mat, y_eval)[0][:, :, :, 0] + return coef + + +def extend_grid(grid, k_extend=0): + ''' + extend grid + ''' + h = (grid[:, [-1]] - grid[:, [0]]) / (grid.shape[1] - 1) + + for _ in range(k_extend): + grid = ops.cat([grid[:, [0]] - h, grid], axis=1) + grid = ops.cat([grid, grid[:, [-1]] + h], axis=1) + + return grid + + +class KANLayer(nn.Cell): + """ + KANLayer class + + + Attributes: + ----------- + in_dim: int + input dimension + out_dim: int + output dimension + num: int + the number of grid intervals + k: int + the piecewise polynomial order of splines + noise_scale: float + spline scale at initialization + coef: 2D Tensor + coefficients of B-spline bases + scale_base_mu: float + magnitude of the residual function b(x) is drawn from N(mu, sigma^2), mu = sigma_base_mu + scale_base_sigma: float + magnitude of the residual function b(x) is drawn from N(mu, sigma^2), mu = sigma_base_sigma + scale_sp: float + mangitude of the spline function spline(x) + base_fun: fun + residual function b(x) + mask: 1D Tensor + mask of spline functions. + setting some element of the mask to zero means setting the corresponding activation to zero function. + grid_eps: float in [0,1] + a hyperparameter used in update_grid_from_samples. + When grid_eps = 1, the grid is uniform; when grid_eps = 0, + the grid is partitioned using percentiles of samples. + 0 < grid_eps < 1 interpolates between the two extremes. + the id of activation functions that are locked + + Args: + ----- + x : 2D Tensor + inputs, shape (number of samples, input dimension) + + Returns: + -------- + y : 2D Tensor + outputs, shape (number of samples, output dimension) + preacts : 3D Tensor + fan out x into activations, shape (number of sampels, output dimension, input dimension) + postacts : 3D Tensor + the outputs of activation functions with preacts as inputs + postspline : 3D Tensor + the outputs of spline functions with preacts as inputs + + Example + ------- + >>> from mindflow.cell import KANLayer + >>> model = KANLayer(in_dim=3, out_dim=5) + >>> x = ops.randn(0, 1, size=(100,3)) + >>> y, preacts, postacts, postspline = model(x) + >>> y.shape, preacts.shape, postacts.shape, postspline.shape + """ + + def __init__(self, in_dim=3, out_dim=2, num=5, k=3, noise_scale=0.5, scale_base_mu=0.0, + scale_base_sigma=1.0, scale_sp=1.0, base_fun=nn.SiLU(), grid_eps=0.02, + grid_range=[-1, 1], sp_trainable=True, sb_trainable=True, sparse_init=False): + super().__init__() + self.out_dim = out_dim + self.in_dim = in_dim + self.num = num + self.k = k + self.grid_eps = grid_eps + self.base_fun = base_fun + + # Initialize grid + grid_init = np.linspace(grid_range[0], grid_range[1], num=num+1) + grid_init = np.tile(grid_init, (in_dim, 1)) + grid = Tensor(grid_init, mstype.float32) + grid = extend_grid(grid, k_extend=k) + self.grid = Parameter(grid, requires_grad=False, name="grid") + + # Initialize coefficients with noise + noises = (np.random.rand(num+1, in_dim, out_dim) - 0.5) * \ + noise_scale / num + noises = Tensor(noises, mstype.float32) + + # Use fixed grid for initial coef calculation + static_grid = self.grid[:, k:-k].transpose(1, 0) + self.coef = Parameter( + curve2coef(static_grid, noises, self.grid, k), + name="coef" + ) + + # Initialize mask + if sparse_init: + mask_val = sparse_mask(in_dim, out_dim) + else: + mask_val = ops.ones((in_dim, out_dim), mstype.float32) + self.mask = Parameter(mask_val, requires_grad=False, name="mask") + + # Initialize scales + scale_base_init = scale_base_mu / np.sqrt(in_dim) + \ + scale_base_sigma * (np.random.rand(in_dim, out_dim) + * 2 - 1) / np.sqrt(in_dim) + self.scale_base = Parameter( + Tensor(scale_base_init, mstype.float32), + requires_grad=sb_trainable, + name="scale_base" + ) + + scale_sp_init = np.ones((in_dim, out_dim)) * \ + scale_sp / np.sqrt(in_dim) * mask_val.asnumpy() + self.scale_sp = Parameter( + Tensor(scale_sp_init, mstype.float32), + requires_grad=sp_trainable, + name="scale_sp" + ) + + def construct(self, x): + """construct""" + batch = x.shape[0] + preacts = x.expand_dims(axis=1).broadcast_to( + (batch, self.out_dim, self.in_dim)) + + base = self.base_fun(x) + y = coef2curve(x, self.grid, self.coef, self.k) + postspline = y.transpose(0, 2, 1) + + # Combine base and spline + scale_base_term = self.scale_base.expand_dims( + axis=0) * base.expand_dims(axis=2) + scale_sp_term = self.scale_sp.expand_dims(axis=0) * y + y = scale_base_term + scale_sp_term + y = self.mask.expand_dims(axis=0) * y + + postacts = y.transpose(0, 2, 1) + y = ops.sum(y, dim=1) # Sum over input dimension + + return y, preacts, postacts, postspline + + def update_grid_from_samples(self, x, mode='sample'): + ''' + update grid from samples + + Args: + ----- + x : 2D Tensor + inputs, shape (number of samples, input dimension) + + Returns: + -------- + None + + Example + ------- + >>> model = KANLayer(in_dim=1, out_dim=1, num=5, k=3) + >>> print(model.grid.data) + >>> x = ops.linspace(-3,3,steps=100)[:,None] + >>> model.update_grid_from_samples(x) + >>> print(model.grid.data) + ''' + x_pos, _ = ops.sort(x, axis=0) + y_eval = coef2curve(x_pos, self.grid, self.coef, self.k) + num_interval = self.grid.shape[1] - 1 - 2 * self.k + + def get_grid(num_interval): + ids = [int(x.shape[0] / num_interval * i) for i in range(num_interval)] + [-1] + grid_adaptive = x_pos[ids].transpose(1, 0) + margin = 0.00 + h = (grid_adaptive[:, [-1]] - grid_adaptive[:, [0]] + 2 * margin) / num_interval + grid_uniform = grid_adaptive[:, [0]] - margin + \ + h * ops.arange(num_interval + 1).expand_dims(0) + grid = self.grid_eps * grid_uniform + \ + (1 - self.grid_eps) * grid_adaptive + return grid + + grid = get_grid(num_interval) + + if mode == 'grid': + sample_grid = get_grid(2 * num_interval) + x_pos = sample_grid.transpose() + y_eval = coef2curve(x_pos, self.grid.asnumpy(), + self.coef.asnumpy(), self.k) + + self.grid.set_data(extend_grid(grid, k_extend=self.k)) + self.coef.set_data(curve2coef(x_pos, y_eval, self.grid, self.k)) + + def initialize_grid_from_parent(self, parent, x, mode='sample'): + ''' + Initialize grid from a parent KANLayer & samples + + Args: + ----- + parent : KANLayer + a parent KANLayer (whose grid is usually coarser than the current model) + x : 2D Tensor + inputs, shape (number of samples, input dimension) + + Returns: + -------- + None + + Example + ------- + >>> batch = 100 + >>> parent_model = KANLayer(in_dim=1, out_dim=1, num=5, k=3) + >>> print(parent_model.grid.data) + >>> model = KANLayer(in_dim=1, out_dim=1, num=10, k=3) + >>> x = ops.randn((batch, 1)) + >>> model.initialize_grid_from_parent(parent_model, x) + >>> print(model.grid.data) + ''' + x_pos = ops.sort(x, axis=0)[0] + y_eval = coef2curve(x_pos, parent.grid, parent.coef, parent.k) + + def get_grid(): + # Create a temporary KANLayer for interpolation + sp2 = KANLayer( + in_dim=1, + out_dim=self.in_dim, + k=1, + num=parent.grid.shape[1] - 2 * parent.k - + 1, # G = num_grid_points - 2k -1 + scale_base_mu=0.0, + scale_base_sigma=0.0 + ) + + # Get parent grid points + x_pos = parent.grid[:, parent.k:-parent.k] + + # Prepare input for curve2coef + static_grid = sp2.grid[:, sp2.k:-sp2.k].transpose(1, 0) + static_grid = static_grid.broadcast_to( + (static_grid.shape[0], self.in_dim)) + + # Compute coefficients for interpolation + sp2_coef = curve2coef( + static_grid, + x_pos.transpose(1, 0).expand_dims(axis=2), + sp2.grid, + k=1 + ).transpose(1, 0, 2) + + sp2.coef.set_data(sp2_coef) + + # Generate new grid points using interpolation + percentile = Tensor(np.linspace(-1, 1, self.num + 1), + mstype.float32).expand_dims(axis=1) + grid_new = sp2(percentile)[0].transpose(1, 0) + return grid_new + + if mode == 'grid': + sample_grid = get_grid() + x_pos = sample_grid.permute(1, 0) + y_eval = coef2curve(x_pos, parent.grid, parent.coef, parent.k) + + # Set current grid and coefficients + grid = get_grid() + grid_extended = extend_grid(grid, k_extend=self.k) + self.grid.set_data(grid_extended) + self.coef.set_data(curve2coef( + x_pos, + y_eval, + self.grid, + self.k + )) + + def get_subset(self, in_id, out_id): + ''' + get a smaller KANLayer from a larger KANLayer (used for pruning) + + Args: + ----- + in_id : list + id of selected input neurons + out_id : list + id of selected output neurons + + Returns: + -------- + spb : KANLayer + + Example + ------- + >>> kanlayer_large = KANLayer(in_dim=10, out_dim=10, num=5, k=3) + >>> kanlayer_small = kanlayer_large.get_subset([0,9],[1,2,3]) + >>> kanlayer_small.in_dim, kanlayer_small.out_dim + (2, 3) + ''' + spb = KANLayer(len(in_id), len(out_id), self.num, + self.k, base_fun=self.base_fun) + spb.grid.set_data(self.grid[in_id]) + spb.coef.set_data(self.coef[in_id][:, out_id]) + spb.scale_base.set_data(self.scale_base[in_id][:, out_id]) + spb.scale_sp.set_data(self.scale_sp[in_id][:, out_id]) + spb.mask.set_data(self.mask[in_id][:, out_id]) + return spb + + def swap(self, i1, i2, mode='in'): + '''Helper function to swap tensor elements''' + def swap_tensor(data, i1, i2, mode): + if mode == 'in': + # Swap entire rows + row1 = data[i1].copy() + row2 = data[i2].copy() + data[i1] = row2 + data[i2] = row1 + elif mode == 'out': + # Swap entire columns + col1 = data[:, i1].copy() + col2 = data[:, i2].copy() + data[:, i1] = col2 + data[:, i2] = col1 + return data + + # Swap grid if mode is 'in' + if mode == 'in': + grid_data = self.grid.value() + self.grid.set_data(swap_tensor(grid_data, i1, i2, 'in')) + + # Swap other parameters + for param in [self.coef, self.scale_base, self.scale_sp, self.mask]: + param_data = param.value() + param.set_data(swap_tensor(param_data, i1, i2, mode)) diff --git a/tests/st/mindflow/cell/kan/data/curve2coef.npz b/tests/st/mindflow/cell/kan/data/curve2coef.npz new file mode 100644 index 0000000000000000000000000000000000000000..69d4c3c2710e37db6ec3cd4edbf795a46a65dacd GIT binary patch literal 1758 zcmWIWW@Zs#fB;2?*48v>2Sx@45N2l(VaP8n(aS5SWMmKk3xSk@q(ES@U#M?DBqKu^ zL$!KJYH@Orx|M>uO`3_ij)Hnxeo;wLVqScHQA#RE+$}MuI29;foRL_N3gl}T>nIrM zC|GFfDAX#D2e{mp&e;=G)wlOa?SowjhAMj%BD?l{*_vf5I%D4MxT-C?!$LClSWZ#f zyY8N~ZI$0UYhxKLTi1KJwuMW*_B8Lmxto994{Q4!$E`Q4?%KP;G}-oK@VW?a!GaAZa&z)H|z1SJs~`AttGq|ZOb)9 z_ng|haPPfdU)zcNZF_6WAKGZVi?%tJB4*QnMB8T8%+|gC4^6dQRyT3)T-M;dsf`x4 zO3o=Z0Vn~lx?QX41Ta)Vm=zfC6|i6j34j2MrYP9WbQFv!2y6{`;f35kPXH_d3zWJ>a&MoRPS30<%bFY6BY=Ayk80PK1curU^Kznp$HaWbzcf!lvx>r})_*zQsekM47Pwvw*Hf4AD_BwCT-0S;1Wlyinr#;L; zU-m?C&$m&?T5q$B#oe}ej`dzG->Wufi#hkGvB=rlexJAJfc(qdm-kBADA^hB970P)8p~5t zVZq1&494`L%oLi0BryLPQWB29gmm@{&w;eGXZwwfH-i%tC?P=;kR2$&fD#H&&K{`7 z9;nuV*6L9cWc04f)-Awv2g0BPnGFj@kN^n4XzU3xlel7#f|Lj?HMy99ZVB*aWD)^7 z5(3amai}nmY5->@Xqk?#3AF@5)&wm6A>|Re7Svpetfhj90g{K&wVrhr0p6@^APH6=WC1$(2vCB70RSv9%69+& literal 0 HcmV?d00001 diff --git a/tests/st/mindflow/cell/kan/data/init_output.npz b/tests/st/mindflow/cell/kan/data/init_output.npz new file mode 100644 index 0000000000000000000000000000000000000000..e4b0decef55cf973199c028d3a87b12decbffa71 GIT binary patch literal 838 zcmWIWW@Zs#fB;1XjpeDS0*nj{Ak4ua!jN8+nWC3hP|3(302TwO1IdBFWWP|~fJjD$ zGKOmPl+@znB6TYTb(=I3bsYuuwEUuyqQt!T{Gyapkhoi7PH`$wyf`DVAQi~hFxF8p zG}6>js8t{raGgEFb0974*?wc=&HEV`8urhcDY^g58J~SXYzJi91I6rta`r$q_CU1` zv{oO05|V$V@7;9(h7<^cLNYl&H4PS$ATbbt(UgXyk&c1|1!2kcwT#&|S#`6m*1BVR z+ceT`+x)NYJ-x2Zc3YvPE!U=1*7N6G+x_Jn-(EKfm%T@eWcGSEPu+RLp~jY(*=kRn zz=z$*`xFGyPsrH{_1fBY6wlxLYu~{= zI};nMzxG|SiJIeSYo?uT^VY;|?~hU^8y3aRy`5VY?g{W_WD)^}F$AEeVyG~XY5<2j iEKQ^9LX9zGU0y(au=oq`W@Q7(F#%x?kUqi$;sF37EAw~& literal 0 HcmV?d00001 diff --git a/tests/st/mindflow/cell/kan/data/init_x.npy b/tests/st/mindflow/cell/kan/data/init_x.npy new file mode 100644 index 0000000000000000000000000000000000000000..a76d58c18a5d5c40453f1270ece179b9061af6fb GIT binary patch literal 1328 zcmbWr`#;rp9KdlUIf_I%qQyZuiuQ2HC7ttmpGOx`=%i_-I^obXqdF2(9y#JsJH<)5 za5%1uP9iIX^!>cgF^?FNluJU{Bxl_gNv&F2`xo~5?e)wK3=R-(ouYF<=O8yCGEOYv zdULqEUCX#m9PX|?lKqmf*qwVM5t0A7PuO>Hk)J&-Dr|4$=XY>%c6Q>pEq3B0bN**D z=K1v!>jnkn=zFL`S%&yNUkZV3hfy(D!`Mt!(YC@l%q8=DP+LDIG2>ozZ?q6nL==49 zlt93x9oYJ4Exk~%9CtYDk>lE0sL30k=l|IX!Mqe=Id{4sXjwI+ziXg7LxfCqQV9{Q zor{v7C-m}@Fg%(x1XU~}Nbg!fE;dU*_sVU!+1q0-};Nc>0!wjNu6>GgZQ)6XQWG*>*Ximr(AQv9L^yc<(5Eu zQzGaqS10J{S5f(}3AoxUL`M%ZR5hun%j^NnlpBI7Zgl&CB~hl2fkpiQyfJNO zG7{K)ZLAk;anQqO;xklGeut!u3Jr7c`$Ke4a%KfQF0v9wV*CDT4Ov+Ci;R1s}=9b%iQmJjE*^|}q zuzni!`O0`V%RbQfDLQcb4-Hv;o2@SF*3i+vWRO35iC|@23tUODq#r!2h_7NPR2Qs; zB#R;tn^nT}Rt1tvdN@m@3r0KBfQbu$xL*`h;(VL9^UgyXO~v+N0cv&6lL~3DQTQ1nj48ZGkCTPmUj9%;rKhr?U zH{AG%Np4t7!Y6V`mt!ZUOpeeEC9d>cvoSf4r=;(7&B5VSJoL4xXrbR_a56c|?~0bg zqkBzYDhS?A_h>{9IQItJcj@z51fNuwur y@w4C9v9sdP4!)rGH literal 0 HcmV?d00001 diff --git a/tests/st/mindflow/cell/kan/data/kan.ckpt b/tests/st/mindflow/cell/kan/data/kan.ckpt new file mode 100644 index 0000000000000000000000000000000000000000..580fd170136cc3abd311bd1ca955284197d37f04 GIT binary patch literal 551 zcmd;D$;ic$UX+<4w1AO=nS)1&-7P0SvBcO&Y69cgGdu^<(w^-%Hr~9SfuUjlteKMg z&z$ku2gG(jwmneH9w=uIRAUcR>p*MuT(^O4P0mkE6S@R)D-#C?!nFq&zmzfCCaZ3? z)mnFKZ?i_aZL|N?y{FgJ+3qN`v}N41%6iSbYr8+4Y;j;Hwk<8vxPE&W@bf~dq zW478;E%0G?`o3j*rscHmt=`XP8-8A9FWb4ay}Nnd+Wc|*y+={lY>%kLY^$OeDVu+7 zihJIi-?%q9eu1q+b()P1_w+p4jZN>BV z{@HhM&-=s%>!*E}Y!c^q+Ujd(+q^Y#+xxoI$%av}b8q*Sg?qU4xma=&i?fB)L4HH{ zOac_l*f5tV7gup|Voqv&Qetr`hPHbPL+l=HT)r=h;gy|blBC^>i;VlW*uB}yWz59^ Q(Np3ust>m0I8qX@Bjb+ literal 0 HcmV?d00001 diff --git a/tests/st/mindflow/cell/kan/data/kan.npz b/tests/st/mindflow/cell/kan/data/kan.npz new file mode 100644 index 0000000000000000000000000000000000000000..aa5f4454dc8d198362d8bc50d1f619cf23e48182 GIT binary patch literal 48340 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 literal 0 HcmV?d00001 diff --git a/tests/st/mindflow/cell/kan/data/kan.pt b/tests/st/mindflow/cell/kan/data/kan.pt new file mode 100644 index 0000000000000000000000000000000000000000..7cce7eda21c0d525429ec5eb302767066f5d5d46 GIT binary patch literal 2925 zcmcImeP|nH7=P1rX_lt5Yg}z>tyWh}*DkqyHf?iBIme-#vKSqPA%yGY@?q1YZ|+h* zYG<*mV~R2m6?IJakAlc>Zs?@BcKXKy1pvKhg& zI~U_~d~8>W6HO3whM~?HLHr<*OT}OnCuVb8(iY8&skC5<=J>&UDjkc8d`8ITqN7d| z)LX5Q8ssk^Pd35BY6W7V(^*!0SXnf|O%WaPA)q0mN8j8?9@a41MQD780gRq8Pr=QM znbA!_)4?Q;p;m4YWOY*Ealo6BH4GubdIev_7!Sa$Og&>{bO&IA z!eJ5N&0)Ar<``y$A=s$laKc*;Snd+x_Au;-;O$aagb{eRhQkf-Q8=t6j!uPRhr-b%!tOBK znIDI?$Y#V79p*(AZ!)yY*&?hFv0Q)B$~&j{PfES z1i9nb8EMj*ASZW!C4G5pA9)J)lDZGy>wo{qh3P-eZISM4+9jRIwMt)5uYB_Lz%!&l z-&Yu`|7|)k^G@N_^nPhJElhfaIKJV`{i{zz?ho%C|B4v%9n8^Urw5;mh*-8 zcP+!b9Ss*dn-C|qjd+O_UKdSuSF{h`9(^S6!|``#9tTT--ofU;;(~7GWZ<%d^$%~= zeO95qyHfqWvzFjmhV|W+g{AB3*8;oz{-rM1RY?L|Cakn@B(dlE{ig+ZWhEQ#_I82K zrBDZan#DJFG!>&MXC=bL2ITNMorKrf>mhm^KEmzm@pwFhvzKrXZm)~*IXpc+2jO#h zaFic?uqR|iwSXBh}5{qCxFN3K&^-&y*9Wn|GszU}2eH z)s0ckp>Do)8jdg;QE(8Yre6+KcPl&{tUMQrP&G9WP!H~=OY7$^=I9R&*t!iG!KVzyOLjFip4HpM+} z&TrhC9KXQUp*qb*hkN>-k4??H-zPR$Kkd6@lQ_rIR$n{Y=ByiTMgN0v!H!B-RjtK~JfV2n`hz9_( CI;SH5 literal 0 HcmV?d00001 diff --git a/tests/st/mindflow/cell/kan/data/update_output.npz b/tests/st/mindflow/cell/kan/data/update_output.npz new file mode 100644 index 0000000000000000000000000000000000000000..50cb2ce13b2c9f30ef8e42a5c7bebfc5d5f68043 GIT binary patch literal 742 zcmWIWW@Zs#fB;2?Z(MsAHvl;x%)ubSkY1FTqL)`t$;co876YjR$$`LRzfj+RNJfS- zhHCYc)Z*kKbt?sRn=})39R>BY{GyVg#Ju?YqLfsSxLaaQaVk)}I3uwj70A~x)KM@r z($rC?RUj8|85v(baOTXK18He(2N)O}4rn}lvVVSy@P7WCO7;oWZ|s3$4nR2vpc)6D zT897>pUzlf+U&pxR0G5ypC;$0rons)5(5DkO_5KHbrg(r6f7w4D_6u{0sF}-x%REL zJz$$v;I}u%>#og*H9a;r4s!3EcvZ{p?{x{g2SLhvmxT!0KHIosZ-zmiZI4{=-tDbD zwgP*m?kn?E*k}7m$d+}>m+pX93f9<5b@-Cb<2@Ha}oh|R|4)A7V5&?!W1fZufs4$Rf k0Eas)&7tc;jWJ|huYep_`~`TkvVr87fG`J0r!awd08uo}1poj5 literal 0 HcmV?d00001 diff --git a/tests/st/mindflow/cell/kan/test_kan.py b/tests/st/mindflow/cell/kan/test_kan.py new file mode 100644 index 000000000..07dea767d --- /dev/null +++ b/tests/st/mindflow/cell/kan/test_kan.py @@ -0,0 +1,141 @@ +"""test KAN""" +import sys +import os +import pytest + +from mindspore import ops, Tensor, load_checkpoint, load_param_into_net, context +import numpy as np +from mindflow.cell import curve2coef, KANLayer + +# pylint: disable=C0413 +PROJECT_ROOT = os.path.abspath(os.path.join( + os.path.dirname(__file__), "../../../")) +sys.path.append(PROJECT_ROOT) +from common.cell import compare_output + +CKPT_PATH = './data/kan.ckpt' + + +def load_kan_data(): + data = np.load('./data/kan.npz') + y_gt = data['y'] + x = data['x'] + preacts_gt, postacts_gt, postspline_gt = data['preacts'], data['postacts'], data['postspline'] + return x, y_gt, preacts_gt, postacts_gt, postspline_gt + + +@pytest.mark.level0 +@pytest.mark.platform_arm_ascend910b_training +@pytest.mark.env_onecard +@pytest.mark.parametrize('mode', [context.GRAPH_MODE, context.PYNATIVE_MODE]) +def test_curve2coef(mode): + """ + Feature: curve2coef + Description: test curve2coef + Expectation: success + """ + context.set_context(mode=mode) + func = curve2coef + + def load_data(): + data = np.load('./data/curve2coef.npz') + x, y, grid, k, out_gt = data['x'], data['y'], data['grid'], data['k'], data['out'] + return x, y, grid, k, out_gt + x, y, grid, k, out_gt = load_data() + k = k.item() + out = func(Tensor(x), Tensor(y), Tensor(grid), k) + assert compare_output(out_gt, out.numpy()) + print('test_curve2coef pass') + + +@pytest.mark.level0 +@pytest.mark.platform_arm_ascend910b_training +@pytest.mark.env_onecard +@pytest.mark.parametrize('mode', [context.GRAPH_MODE, context.PYNATIVE_MODE]) +def test_kan_forward(mode): + """ + Feature: KAN + Description: test KAN forwardresult + Expectation: success + """ + context.set_context(mode=mode) + x, y_gt, preacts_gt, postacts_gt, postspline_gt = load_kan_data() + net = KANLayer() + params = load_checkpoint(CKPT_PATH) + load_param_into_net(net, params) + y, preacts, postacts, postspline = net(Tensor(x)) + assert compare_output(y.numpy(), y_gt) + assert compare_output(preacts.numpy(), preacts_gt) + assert compare_output(postacts.numpy(), postacts_gt) + assert compare_output(postspline.numpy(), postspline_gt) + print('test_kan_forward pass') + + +def load_grid_coef(npz_file): + data = np.load(npz_file) + return data['grid'], data['coef'] + + +@pytest.mark.level0 +@pytest.mark.platform_arm_ascend910b_training +@pytest.mark.env_onecard +@pytest.mark.parametrize('mode', [context.GRAPH_MODE, context.PYNATIVE_MODE]) +def test_update_grid_from_samples(mode): + """ + Feature: KAN update_grid_from_samples + Description: test KAN update_grid_from_samples + Expectation: success + """ + context.set_context(mode=mode) + layer = KANLayer() + load_param_into_net(layer, load_checkpoint(CKPT_PATH)) + x = ops.linspace(-3, 3, steps=100)[:, None] + layer.update_grid_from_samples(x) + grid, coef = load_grid_coef('./data/update_output.npz') + assert compare_output(layer.grid.data.numpy(), grid) + assert compare_output(layer.coef.data.numpy(), coef) + print('test_update_grid_from_samples pass') + + +@pytest.mark.level0 +@pytest.mark.platform_arm_ascend910b_training +@pytest.mark.env_onecard +@pytest.mark.parametrize('mode', [context.GRAPH_MODE, context.PYNATIVE_MODE]) +def test_initialize_grid_from_parent(mode): + """ + Feature: initialize_grid_from_parent + Description: test KAN initialize_grid_from_parent + Expectation: success + """ + context.set_context(mode=mode) + layer = KANLayer() + load_param_into_net(layer, load_checkpoint(CKPT_PATH)) + parent = KANLayer() + load_param_into_net(parent, load_checkpoint(CKPT_PATH)) + x = Tensor(np.load('./data/init_x.npy')) + layer.initialize_grid_from_parent(parent, x) + # print('initialize_grid_from_parent', layer.grid.data, layer.coef.data) + grid, coef = load_grid_coef('./data/init_output.npz') + assert compare_output(layer.grid.data.numpy(), grid) + assert compare_output(layer.coef.data.numpy(), coef) + print('test_initialize_grid_from_parent pass') + + +@pytest.mark.level0 +@pytest.mark.platform_arm_ascend910b_training +@pytest.mark.env_onecard +@pytest.mark.parametrize('mode', [context.GRAPH_MODE, context.PYNATIVE_MODE]) +def test_get_subset(mode): + """ + Feature: KAN get_subset + Description: test KAN get_subset + Expectation: success + """ + context.set_context(mode=mode) + layer = KANLayer() + load_param_into_net(layer, load_checkpoint(CKPT_PATH)) + child = layer.get_subset([1, 2], [1]) + grid, coef = load_grid_coef('./data/subset.npz') + assert compare_output(child.grid.data.numpy(), grid) + assert compare_output(child.coef.data.numpy(), coef) + print('test_get_subset pass') -- Gitee From d6d5ca93d6386385ff844293c58ad3120ecc9994 Mon Sep 17 00:00:00 2001 From: brian Date: Mon, 14 Jul 2025 14:19:20 +0800 Subject: [PATCH 2/3] mod version --- .jenkins/test/config/flow_config/dependent_packages.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.jenkins/test/config/flow_config/dependent_packages.yaml b/.jenkins/test/config/flow_config/dependent_packages.yaml index 9e70df642..d2d1a21e9 100644 --- a/.jenkins/test/config/flow_config/dependent_packages.yaml +++ b/.jenkins/test/config/flow_config/dependent_packages.yaml @@ -1,2 +1,2 @@ mindspore: - '/mindspore/mindspore/version/202503/20250326/master_20250326010019_b91eca2945e61641319f9887aa76a1ccb38604d3_newest/' \ No newline at end of file + '/mindspore/mindspore/version/202505/20250501/master_20250501010016_2c14fbc769a3d096872fa8692ad3b6a997d04a07_newest/' -- Gitee From ba4dea292044f8b6ddd70a44a11263a339a5fbe2 Mon Sep 17 00:00:00 2001 From: brian Date: Mon, 21 Jul 2025 17:23:46 +0800 Subject: [PATCH 3/3] dd --- .jenkins/test/config/flow_config/dependent_packages.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.jenkins/test/config/flow_config/dependent_packages.yaml b/.jenkins/test/config/flow_config/dependent_packages.yaml index d2d1a21e9..574dc9b2f 100644 --- a/.jenkins/test/config/flow_config/dependent_packages.yaml +++ b/.jenkins/test/config/flow_config/dependent_packages.yaml @@ -1,2 +1,2 @@ mindspore: - '/mindspore/mindspore/version/202505/20250501/master_20250501010016_2c14fbc769a3d096872fa8692ad3b6a997d04a07_newest/' + '/mindspore/mindspore/version/202506/20250603/master_20250603091707_20e3faa947757ae90617a0300d5dc8d70398ec63_newest/' -- Gitee