1#![cfg_attr(not(feature = "std"), no_std)]
2#![warn(rust_2018_idioms)]
3
4mod gemm;
5
6#[cfg(feature = "f16")]
7pub use crate::gemm::f16;
8pub use crate::gemm::{c32, c64, gemm};
9pub use gemm_common::packed_cache;
10pub use gemm_common::Parallelism;
11
12pub use gemm_common::gemm::{
13 get_lhs_packing_threshold_multi_thread, get_lhs_packing_threshold_single_thread,
14 get_rhs_packing_threshold, get_threading_threshold, set_lhs_packing_threshold_multi_thread,
15 set_lhs_packing_threshold_single_thread, set_rhs_packing_threshold, set_threading_threshold,
16 DEFAULT_LHS_PACKING_THRESHOLD_MULTI_THREAD, DEFAULT_LHS_PACKING_THRESHOLD_SINGLE_THREAD,
17 DEFAULT_RHS_PACKING_THRESHOLD, DEFAULT_THREADING_THRESHOLD,
18};
19pub use gemm_common::{get_wasm_simd128, set_wasm_simd128, DEFAULT_WASM_SIMD128};
20
21#[cfg(test)]
22mod tests {
23 use super::*;
24 extern crate alloc;
25 use alloc::{vec, vec::Vec};
26 use num_traits::Float;
27
28 #[test]
31 fn test_packed_lhs_cache_hit_matches_miss() {
32 let cases = [
35 (64, 16, 128, false),
36 (96, 33, 100, false),
37 (512, 16, 384, true),
38 (1536, 16, 512, true),
39 (512, 128, 300, true),
40 ];
41 packed_cache::set_enabled(true);
42 packed_cache::set_budget_mb(1024);
43 packed_cache::clear();
44
45 for (m, n, k, row_major) in cases {
46 let (lhs_rs, lhs_cs) = if row_major { (k as isize, 1) } else { (1, m as isize) };
47 let a: Vec<f32> = (0..m * k).map(|_| rand::random()).collect();
48 let b: Vec<f32> = (0..k * n).map(|_| rand::random()).collect();
49 let mut want = vec![0f32; m * n];
50 let mut got = vec![0f32; m * n];
51 unsafe {
53 gemm::gemm_fallback(
54 m, n, k, want.as_mut_ptr(), m as isize, 1, false, a.as_ptr(), lhs_cs,
55 lhs_rs, b.as_ptr(), k as isize, 1, 0.0, 1.0,
56 );
57 for _ in 0..3 {
59 for parallelism in [
60 Parallelism::None,
61 #[cfg(feature = "rayon")]
62 Parallelism::Rayon(0),
63 ] {
64 gemm::gemm(
65 m, n, k, got.as_mut_ptr(), m as isize, 1, false, a.as_ptr(),
66 lhs_cs, lhs_rs, b.as_ptr(), k as isize, 1, 0.0, 1.0, false,
67 false, false, parallelism,
68 );
69 for (c, d) in got.iter().zip(want.iter()) {
70 assert_approx_eq::assert_approx_eq!(c, d, 1e-3);
71 }
72 }
73 }
74 }
75 assert!(packed_cache::stats().1 > 0, "the cached path was never taken");
76 }
77 packed_cache::clear();
78 packed_cache::set_enabled(false);
79
80 let (m, n, k) = (16, 512, 384);
84 let x: Vec<f32> = (0..m * k).map(|_| rand::random()).collect();
85 let w: Vec<f32> = (0..n * k).map(|_| rand::random()).collect();
86 let linear = |dst: &mut [f32]| unsafe {
87 gemm::gemm(
89 m, n, k, dst.as_mut_ptr(), 1, n as isize, false, x.as_ptr(), 1, k as isize,
90 w.as_ptr(), k as isize, 1, 0.0, 1.0, false, false, false, Parallelism::None,
91 )
92 };
93 let mut want = vec![0f32; m * n];
94 linear(&mut want);
95 assert_eq!(packed_cache::stats().1, 0, "nothing is cached undeclared");
96 let in_w = packed_cache::Constant::new(w.as_ptr(), w.len());
97 for _ in 0..3 {
98 let mut got = vec![0f32; m * n];
99 packed_cache::with_constant(in_w, || linear(&mut got));
100 assert_eq!(got, want, "a cached panel is a copy: bit for bit the same");
101 }
102 assert_eq!(packed_cache::stats().1, 1, "the declared weight is cached");
103
104 let (t, len, d) = (4, 256, 64);
108 let q: Vec<f32> = (0..t * d).map(|_| rand::random()).collect();
109 let mut keys: Vec<f32> = (0..len * d).map(|_| rand::random()).collect();
110 let scores = |keys: &[f32]| {
111 let mut dst = vec![0f32; t * len];
112 unsafe {
114 gemm::gemm(
115 t, len, d, dst.as_mut_ptr(), 1, len as isize, false, q.as_ptr(), 1,
116 d as isize, keys.as_ptr(), d as isize, 1, 0.0, 1.0, false, false, false,
117 Parallelism::None,
118 )
119 };
120 dst
121 };
122 let in_q = packed_cache::Constant::new(q.as_ptr(), q.len());
123 for step in 0..4 {
124 keys[d + step] += 1.0;
126 let got = packed_cache::with_constant(in_q, || scores(&keys));
127 assert_eq!(got, scores(&keys.clone()), "step {step} saw the write");
128 }
129 assert_eq!(packed_cache::stats().1, 1, "only the declared weight holds a panel");
130 packed_cache::clear();
131 }
132
133 #[test]
134 fn test_gemm_f16() {
135 let mut mnks = vec![];
136 mnks.push((4, 4, 4));
137 mnks.push((63, 2, 10));
138 mnks.push((16, 2, 1));
139 mnks.push((0, 0, 4));
140 mnks.push((16, 1, 1));
141 mnks.push((16, 3, 1));
142 mnks.push((16, 4, 1));
143 mnks.push((16, 1, 2));
144 mnks.push((16, 2, 2));
145 mnks.push((16, 3, 2));
146 mnks.push((16, 4, 2));
147 mnks.push((16, 16, 1));
148 mnks.push((64, 64, 0));
149 mnks.push((256, 256, 256));
150 mnks.push((4096, 4096, 4));
151 mnks.push((64, 64, 4));
152 mnks.push((0, 64, 4));
153 mnks.push((64, 0, 4));
154 mnks.push((8, 16, 1));
155 mnks.push((16, 8, 1));
156 mnks.push((1, 1, 2));
157 mnks.push((1024, 1024, 1));
158 mnks.push((1024, 1024, 4));
159 mnks.push((63, 1, 10));
160 mnks.push((63, 3, 10));
161 mnks.push((63, 4, 10));
162 mnks.push((1, 63, 10));
163 mnks.push((2, 63, 10));
164 mnks.push((3, 63, 10));
165 mnks.push((4, 63, 10));
166
167 mnks.push((1, 2048, 384));
170 mnks.push((2048, 1, 384));
171 for (m, n, k) in mnks {
172 #[cfg(feature = "std")]
173 dbg!(m, n, k);
174 for parallelism in [
175 Parallelism::None,
176 #[cfg(feature = "rayon")]
177 Parallelism::Rayon(0),
178 #[cfg(feature = "rayon")]
179 Parallelism::Rayon(128),
180 ] {
181 for alpha in [0.0, 1.0, 2.3] {
182 for beta in [0.0, 1.0, 2.3] {
183 #[cfg(feature = "std")]
184 dbg!(alpha, beta, parallelism);
185
186 for colmajor in [true, false] {
187 let alpha = f16::from_f32(alpha);
188 let beta = f16::from_f32(beta);
189 let a_vec: Vec<f16> = (0..(m * k))
190 .map(|_| f16::from_f32(rand::random()))
191 .collect();
192 let b_vec: Vec<f16> = (0..(k * n))
193 .map(|_| f16::from_f32(rand::random()))
194 .collect();
195 let mut c_vec: Vec<f16> = (0..(m * n))
196 .map(|_| f16::from_f32(rand::random()))
197 .collect();
198 let mut d_vec = c_vec.clone();
199
200 unsafe {
201 gemm::gemm(
202 m,
203 n,
204 k,
205 c_vec.as_mut_ptr(),
206 if colmajor { m } else { 1 } as isize,
207 if colmajor { 1 } else { n } as isize,
208 true,
209 a_vec.as_ptr(),
210 m as isize,
211 1,
212 b_vec.as_ptr(),
213 k as isize,
214 1,
215 alpha,
216 beta,
217 false,
218 false,
219 false,
220 parallelism,
221 );
222
223 gemm::gemm_fallback(
224 m,
225 n,
226 k,
227 d_vec.as_mut_ptr(),
228 if colmajor { m } else { 1 } as isize,
229 if colmajor { 1 } else { n } as isize,
230 true,
231 a_vec.as_ptr(),
232 m as isize,
233 1,
234 b_vec.as_ptr(),
235 k as isize,
236 1,
237 alpha,
238 beta,
239 );
240 }
241 let eps = f16::from_f32(1e-1);
242 for (c, d) in c_vec.iter().zip(d_vec.iter()) {
243 let eps_rel = c.abs() * eps;
244 let eps_abs = eps;
245 let eps = if eps_rel > eps_abs { eps_rel } else { eps_abs };
246 assert_approx_eq::assert_approx_eq!(c, d, eps);
247 }
248 }
249 }
250 }
251 }
252 }
253 }
254
255 #[test]
256 fn test_gemm_f32() {
257 set_wasm_simd128(true);
258
259 let mut mnks = vec![];
260 mnks.push((63, 2, 10));
261 mnks.push((1, 2, 10));
262 mnks.push((1, 63, 10));
263
264 mnks.push((2048, 255, 255));
266
267 mnks.push((256, 256, 256));
268 mnks.push((4096, 4096, 4));
269 mnks.push((64, 64, 4));
270 mnks.push((0, 64, 4));
271 mnks.push((64, 0, 4));
272 mnks.push((0, 0, 4));
273 mnks.push((64, 64, 0));
274 mnks.push((16, 1, 1));
275 mnks.push((16, 2, 1));
276 mnks.push((16, 3, 1));
277 mnks.push((16, 4, 1));
278 mnks.push((16, 1, 2));
279 mnks.push((16, 2, 2));
280 mnks.push((16, 3, 2));
281 mnks.push((16, 4, 2));
282 mnks.push((16, 16, 1));
283 mnks.push((8, 16, 1));
284 mnks.push((16, 8, 1));
285 mnks.push((1, 1, 2));
286 mnks.push((4, 4, 4));
287 mnks.push((1024, 1024, 1));
288 mnks.push((1024, 1024, 4));
289 mnks.push((63, 1, 10));
290 mnks.push((63, 3, 10));
291 mnks.push((63, 4, 10));
292 mnks.push((2, 63, 10));
293 mnks.push((3, 63, 10));
294 mnks.push((4, 63, 10));
295
296 mnks.push((1, 8192, 128));
299 mnks.push((8192, 1, 128));
300 for (m, n, k) in mnks {
301 #[cfg(feature = "std")]
302 dbg!(m, n, k);
303 for parallelism in [
304 Parallelism::None,
305 #[cfg(feature = "rayon")]
306 Parallelism::Rayon(0),
307 #[cfg(feature = "rayon")]
308 Parallelism::Rayon(128),
309 ] {
310 for alpha in [0.0, 1.0, 2.3] {
311 for beta in [0.0, 1.0, 2.3] {
312 #[cfg(feature = "std")]
313 dbg!(alpha, beta, parallelism);
314 for colmajor in [true, false] {
315 let a_vec: Vec<f32> = (0..(m * k)).map(|_| rand::random()).collect();
316 let b_vec: Vec<f32> = (0..(k * n)).map(|_| rand::random()).collect();
317 let mut c_vec: Vec<f32> =
318 (0..(m * n)).map(|_| rand::random()).collect();
319 let mut d_vec = c_vec.clone();
320
321 unsafe {
322 gemm::gemm(
323 m,
324 n,
325 k,
326 c_vec.as_mut_ptr(),
327 if colmajor { m } else { 1 } as isize,
328 if colmajor { 1 } else { n } as isize,
329 true,
330 a_vec.as_ptr(),
331 m as isize,
332 1,
333 b_vec.as_ptr(),
334 k as isize,
335 1,
336 alpha,
337 beta,
338 false,
339 false,
340 false,
341 parallelism,
342 );
343
344 gemm::gemm_fallback(
345 m,
346 n,
347 k,
348 d_vec.as_mut_ptr(),
349 if colmajor { m } else { 1 } as isize,
350 if colmajor { 1 } else { n } as isize,
351 true,
352 a_vec.as_ptr(),
353 m as isize,
354 1,
355 b_vec.as_ptr(),
356 k as isize,
357 1,
358 alpha,
359 beta,
360 );
361 }
362 for (c, d) in c_vec.iter().zip(d_vec.iter()) {
363 assert_approx_eq::assert_approx_eq!(c, d, 1e-3);
364 }
365 }
366 }
367 }
368 }
369 }
370 }
371
372 #[test]
373 fn test_gemm_f64() {
374 set_wasm_simd128(true);
375
376 let mut mnks = vec![];
377 mnks.push((63, 2, 10));
378 mnks.push((1, 2, 10));
379 mnks.push((1, 63, 10));
380
381 mnks.push((2048, 255, 255));
383
384 mnks.push((256, 256, 256));
385 mnks.push((4096, 4096, 4));
386 mnks.push((64, 64, 4));
387 mnks.push((0, 64, 4));
388 mnks.push((64, 0, 4));
389 mnks.push((0, 0, 4));
390 mnks.push((64, 64, 0));
391 mnks.push((16, 1, 1));
392 mnks.push((16, 2, 1));
393 mnks.push((16, 3, 1));
394 mnks.push((16, 4, 1));
395 mnks.push((16, 1, 2));
396 mnks.push((16, 2, 2));
397 mnks.push((16, 3, 2));
398 mnks.push((16, 4, 2));
399 mnks.push((16, 16, 1));
400 mnks.push((8, 16, 1));
401 mnks.push((16, 8, 1));
402 mnks.push((1, 1, 2));
403 mnks.push((4, 4, 4));
404 mnks.push((1024, 1024, 1));
405 mnks.push((1024, 1024, 4));
406 mnks.push((63, 1, 10));
407 mnks.push((63, 3, 10));
408 mnks.push((63, 4, 10));
409 mnks.push((2, 63, 10));
410 mnks.push((3, 63, 10));
411 mnks.push((4, 63, 10));
412
413 mnks.push((1, 1024, 1024));
416 mnks.push((1024, 1, 1024));
417 mnks.push((1, 128, 8192));
418 mnks.push((128, 1, 8192));
419 for (m, n, k) in mnks {
420 #[cfg(feature = "std")]
421 dbg!(m, n, k);
422 for parallelism in [
423 Parallelism::None,
424 #[cfg(feature = "rayon")]
425 Parallelism::Rayon(0),
426 #[cfg(feature = "rayon")]
427 Parallelism::Rayon(128),
428 ] {
429 for alpha in [0.0, 1.0, 2.3] {
430 for beta in [0.0, 1.0, 2.3] {
431 #[cfg(feature = "std")]
432 dbg!(alpha, beta, parallelism);
433 for colmajor in [true, false] {
434 let a_vec: Vec<f64> = (0..(m * k)).map(|_| rand::random()).collect();
435 let b_vec: Vec<f64> = (0..(k * n)).map(|_| rand::random()).collect();
436 let mut c_vec: Vec<f64> =
437 (0..(m * n)).map(|_| rand::random()).collect();
438 let mut d_vec = c_vec.clone();
439
440 unsafe {
441 gemm::gemm(
442 m,
443 n,
444 k,
445 c_vec.as_mut_ptr(),
446 if colmajor { m } else { 1 } as isize,
447 if colmajor { 1 } else { n } as isize,
448 true,
449 a_vec.as_ptr(),
450 m as isize,
451 1,
452 b_vec.as_ptr(),
453 k as isize,
454 1,
455 alpha,
456 beta,
457 false,
458 false,
459 false,
460 parallelism,
461 );
462
463 gemm::gemm_fallback(
464 m,
465 n,
466 k,
467 d_vec.as_mut_ptr(),
468 if colmajor { m } else { 1 } as isize,
469 if colmajor { 1 } else { n } as isize,
470 true,
471 a_vec.as_ptr(),
472 m as isize,
473 1,
474 b_vec.as_ptr(),
475 k as isize,
476 1,
477 alpha,
478 beta,
479 );
480 }
481 for (c, d) in c_vec.iter().zip(d_vec.iter()) {
482 assert_approx_eq::assert_approx_eq!(c, d);
483 }
484 }
485 }
486 }
487 }
488 }
489 }
490
491 #[test]
492 fn test_gemm_cplx32() {
493 let mut mnks = vec![];
494 mnks.push((4, 4, 4));
495 mnks.push((0, 64, 4));
496 mnks.push((64, 0, 4));
497 mnks.push((0, 0, 4));
498 mnks.push((64, 64, 4));
499 mnks.push((64, 64, 0));
500 mnks.push((6, 3, 1));
501 mnks.push((1, 1, 2));
502 mnks.push((128, 128, 128));
503 mnks.push((16, 1, 1));
504 mnks.push((16, 2, 1));
505 mnks.push((16, 3, 1));
506 mnks.push((16, 4, 1));
507 mnks.push((16, 1, 2));
508 mnks.push((16, 2, 2));
509 mnks.push((16, 3, 2));
510 mnks.push((16, 4, 2));
511 mnks.push((16, 16, 1));
512 mnks.push((8, 16, 1));
513 mnks.push((16, 8, 1));
514 mnks.push((1024, 1024, 4));
515 mnks.push((1024, 1024, 1));
516 mnks.push((63, 1, 10));
517 mnks.push((63, 2, 10));
518 mnks.push((63, 3, 10));
519 mnks.push((63, 4, 10));
520 mnks.push((1, 63, 10));
521 mnks.push((2, 63, 10));
522 mnks.push((3, 63, 10));
523 mnks.push((4, 63, 10));
524
525 mnks.push((1, 8192, 128));
528 mnks.push((8192, 1, 128));
529 for (m, n, k) in mnks {
530 #[cfg(feature = "std")]
531 dbg!(m, n, k);
532
533 let zero = c32::new(0.0, 0.0);
534 let one = c32::new(1.0, 0.0);
535 let arbitrary = c32::new(2.3, 4.1);
536 for alpha in [zero, one, arbitrary] {
537 for beta in [zero, one, arbitrary] {
538 #[cfg(feature = "std")]
539 dbg!(alpha, beta);
540 for conj_dst in [false, true] {
541 for conj_lhs in [false, true] {
542 for conj_rhs in [false, true] {
543 #[cfg(feature = "std")]
544 dbg!(conj_dst);
545 #[cfg(feature = "std")]
546 dbg!(conj_lhs);
547 #[cfg(feature = "std")]
548 dbg!(conj_rhs);
549 for colmajor in [true, false] {
550 let a_vec: Vec<f32> =
551 (0..(2 * m * k)).map(|_| rand::random()).collect();
552 let b_vec: Vec<f32> =
553 (0..(2 * k * n)).map(|_| rand::random()).collect();
554 let mut c_vec: Vec<f32> =
555 (0..(2 * m * n)).map(|_| rand::random()).collect();
556 let mut d_vec = c_vec.clone();
557
558 unsafe {
559 gemm::gemm(
560 m,
561 n,
562 k,
563 c_vec.as_mut_ptr() as *mut c32,
564 if colmajor { m } else { 1 } as isize,
565 if colmajor { 1 } else { n } as isize,
566 true,
567 a_vec.as_ptr() as *const c32,
568 m as isize,
569 1,
570 b_vec.as_ptr() as *const c32,
571 k as isize,
572 1,
573 alpha,
574 beta,
575 conj_dst,
576 conj_lhs,
577 conj_rhs,
578 #[cfg(feature = "rayon")]
579 Parallelism::Rayon(0),
580 #[cfg(not(feature = "rayon"))]
581 Parallelism::None,
582 );
583
584 gemm::gemm_cplx_fallback(
585 m,
586 n,
587 k,
588 d_vec.as_mut_ptr() as *mut c32,
589 if colmajor { m } else { 1 } as isize,
590 if colmajor { 1 } else { n } as isize,
591 true,
592 a_vec.as_ptr() as *const c32,
593 m as isize,
594 1,
595 b_vec.as_ptr() as *const c32,
596 k as isize,
597 1,
598 alpha,
599 beta,
600 conj_dst,
601 conj_lhs,
602 conj_rhs,
603 );
604 }
605 for (c, d) in c_vec.iter().zip(d_vec.iter()) {
606 assert_approx_eq::assert_approx_eq!(c, d, 1e-3);
607 }
608 }
609 }
610 }
611 }
612 }
613 }
614 }
615 }
616
617 #[test]
618 fn test_gemm_cplx64() {
619 let mut mnks = vec![];
620 mnks.push((4, 4, 4));
621 mnks.push((0, 64, 4));
622 mnks.push((64, 0, 4));
623 mnks.push((0, 0, 4));
624 mnks.push((64, 64, 4));
625 mnks.push((64, 64, 0));
626 mnks.push((6, 3, 1));
627 mnks.push((1, 1, 2));
628 mnks.push((128, 128, 128));
629 mnks.push((16, 1, 1));
630 mnks.push((16, 2, 1));
631 mnks.push((16, 3, 1));
632 mnks.push((16, 4, 1));
633 mnks.push((16, 1, 2));
634 mnks.push((16, 2, 2));
635 mnks.push((16, 3, 2));
636 mnks.push((16, 4, 2));
637 mnks.push((16, 16, 1));
638 mnks.push((8, 16, 1));
639 mnks.push((16, 8, 1));
640 mnks.push((1024, 1024, 4));
641 mnks.push((1024, 1024, 1));
642 mnks.push((63, 1, 10));
643 mnks.push((63, 2, 10));
644 mnks.push((63, 3, 10));
645 mnks.push((63, 4, 10));
646 mnks.push((1, 63, 10));
647 mnks.push((2, 63, 10));
648 mnks.push((3, 63, 10));
649 mnks.push((4, 63, 10));
650
651 mnks.push((1, 1024, 1024));
654 mnks.push((1024, 1, 1024));
655 mnks.push((1, 128, 8192));
656 mnks.push((128, 1, 8192));
657 for (m, n, k) in mnks {
658 #[cfg(feature = "std")]
659 dbg!(m, n, k);
660
661 let zero = c64::new(0.0, 0.0);
662 let one = c64::new(1.0, 0.0);
663 let arbitrary = c64::new(2.3, 4.1);
664 for alpha in [zero, one, arbitrary] {
665 for beta in [zero, one, arbitrary] {
666 #[cfg(feature = "std")]
667 dbg!(alpha, beta);
668 for conj_dst in [false, true] {
669 for conj_lhs in [false, true] {
670 for conj_rhs in [false, true] {
671 #[cfg(feature = "std")]
672 dbg!(conj_dst);
673 #[cfg(feature = "std")]
674 dbg!(conj_lhs);
675 #[cfg(feature = "std")]
676 dbg!(conj_rhs);
677 for colmajor in [true, false] {
678 let a_vec: Vec<f64> =
679 (0..(2 * m * k)).map(|_| rand::random()).collect();
680 let b_vec: Vec<f64> =
681 (0..(2 * k * n)).map(|_| rand::random()).collect();
682 let mut c_vec: Vec<f64> =
683 (0..(2 * m * n)).map(|_| rand::random()).collect();
684 let mut d_vec = c_vec.clone();
685
686 unsafe {
687 gemm::gemm(
688 m,
689 n,
690 k,
691 c_vec.as_mut_ptr() as *mut c64,
692 if colmajor { m } else { 1 } as isize,
693 if colmajor { 1 } else { n } as isize,
694 true,
695 a_vec.as_ptr() as *const c64,
696 m as isize,
697 1,
698 b_vec.as_ptr() as *const c64,
699 k as isize,
700 1,
701 alpha,
702 beta,
703 conj_dst,
704 conj_lhs,
705 conj_rhs,
706 #[cfg(feature = "rayon")]
707 Parallelism::Rayon(0),
708 #[cfg(not(feature = "rayon"))]
709 Parallelism::None,
710 );
711
712 gemm::gemm_cplx_fallback(
713 m,
714 n,
715 k,
716 d_vec.as_mut_ptr() as *mut c64,
717 if colmajor { m } else { 1 } as isize,
718 if colmajor { 1 } else { n } as isize,
719 true,
720 a_vec.as_ptr() as *const c64,
721 m as isize,
722 1,
723 b_vec.as_ptr() as *const c64,
724 k as isize,
725 1,
726 alpha,
727 beta,
728 conj_dst,
729 conj_lhs,
730 conj_rhs,
731 );
732 }
733 for (c, d) in c_vec.iter().zip(d_vec.iter()) {
734 assert_approx_eq::assert_approx_eq!(c, d);
735 }
736 }
737 }
738 }
739 }
740 }
741 }
742 }
743 }
744}