Skip to content

Commit 2ccf3cb

Browse files
committed
Save game
1 parent 4e032be commit 2ccf3cb

8 files changed

Lines changed: 496 additions & 364 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
- Reductions (`sum`/`product`/`min`/`max`/`argmin`/`argmax`) consume non-contiguous views in place instead of gathering a dense copy
1111
- `set()` and `sort()` reject read-only views
1212
- `asBuffer()` materialises a dense copy on view decorators
13+
- Column and row broadcast kernels read a non-contiguous vector operand through its view instead of gathering a dense copy
14+
- Fixed `softmax()` (and other row-wise kernels) reading only the first element of a non-contiguous 1-D operand such as a `columnAsVector()` view
1315

1416
- 4.1.0
1517
- Added `softplus()` and `erf()` special functions

‎ext/include/arithmetic.c‎

Lines changed: 240 additions & 234 deletions
Large diffs are not rendered by default.

‎ext/include/comparison.c‎

Lines changed: 117 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -188,63 +188,60 @@ TENSOR_SCALAR_DISPATCH(less_equal_scalar, x <= ab ? 1.0 : 0.0)
188188
* that the optimizer can vectorize the elementwise mapping instead of being
189189
* blocked by an indirect call. */
190190

191-
#define TENSOR_COL_DISPATCH_BODY(op) \
192-
tensor_double_view v_a; \
193-
zend_long m = 0; \
194-
double * owned_b = NULL; \
195-
const double * vb = NULL; \
196-
\
197-
if (UNEXPECTED(!tensor_view_resolve_arg(a, &v_a) || \
198-
!tensor_view_doubles(b, &vb, &m, &owned_b))) { \
199-
return; \
200-
} \
201-
\
202-
zend_long total = v_a.len; \
203-
zend_long nHat = zephir_get_intval(n_zval); \
204-
\
205-
if (UNEXPECTED(nHat < 1)) { \
206-
zephir_throw_exception_string(spl_ce_LengthException, \
207-
SL("Matrix and vector dimensions must agree.")); \
208-
if (owned_b != NULL) { efree(owned_b); } \
209-
return; \
210-
} \
211-
\
212-
zval c; \
213-
\
191+
#define TENSOR_COL_DISPATCH_BODY(op) \
192+
tensor_double_view v_a, v_b; \
193+
\
194+
if (UNEXPECTED(!tensor_view_resolve_arg(a, &v_a) || \
195+
!tensor_view_resolve_arg(b, &v_b))) { \
196+
return; \
197+
} \
198+
\
199+
zend_long m = v_b.len; \
200+
zend_long total = v_a.len; \
201+
zend_long nHat = zephir_get_intval(n_zval); \
202+
\
203+
if (UNEXPECTED(nHat < 1)) { \
204+
zephir_throw_exception_string(spl_ce_LengthException, \
205+
SL("Matrix and vector dimensions must agree.")); \
206+
return; \
207+
} \
208+
\
209+
zval c; \
210+
\
214211
if (UNEXPECTED(tensor_tensorbuffer_create(return_value, total, &c) == FAILURE)) { \
215-
if (owned_b != NULL) { efree(owned_b); } \
216-
return; \
217-
} \
218-
\
219-
double * restrict vc = zephir_buffer_doubles(&c); \
220-
\
221-
zend_long i, j; \
222-
\
223-
if (v_a.contiguous) { \
224-
const double * restrict va = v_a.base + v_a.offset; \
225-
\
226-
for (i = 0; i < m; ++i) { \
227-
for (j = 0; j < nHat; ++j) { \
228-
double x = va[i * nHat + j]; \
229-
double y = vb[i]; \
230-
vc[i * nHat + j] = op; \
231-
} \
232-
} \
233-
} else { \
234-
tensor_view_reader ra; \
235-
tensor_view_reader_init(&ra, &v_a); \
236-
\
237-
for (i = 0; i < m; ++i) { \
238-
for (j = 0; j < nHat; ++j) { \
239-
double x = tensor_view_reader_value(&ra); \
240-
double y = vb[i]; \
241-
vc[i * nHat + j] = op; \
242-
tensor_view_reader_advance(&ra); \
243-
} \
244-
} \
245-
} \
246-
\
247-
if (owned_b != NULL) { efree(owned_b); } \
212+
return; \
213+
} \
214+
\
215+
double * restrict vc = zephir_buffer_doubles(&c); \
216+
\
217+
zend_long i, j; \
218+
\
219+
if (v_a.contiguous) { \
220+
const double * restrict va = v_a.base + v_a.offset; \
221+
\
222+
for (i = 0; i < m; ++i) { \
223+
double y = tensor_double_view_get(&v_b, i); \
224+
\
225+
for (j = 0; j < nHat; ++j) { \
226+
double x = va[i * nHat + j]; \
227+
vc[i * nHat + j] = op; \
228+
} \
229+
} \
230+
} else { \
231+
tensor_view_reader ra; \
232+
tensor_view_reader_init(&ra, &v_a); \
233+
\
234+
for (i = 0; i < m; ++i) { \
235+
double y = tensor_double_view_get(&v_b, i); \
236+
\
237+
for (j = 0; j < nHat; ++j) { \
238+
double x = tensor_view_reader_value(&ra); \
239+
vc[i * nHat + j] = op; \
240+
tensor_view_reader_advance(&ra); \
241+
} \
242+
} \
243+
} \
244+
\
248245
zval_ptr_dtor(&c);
249246

250247
#define TENSOR_COL_DISPATCH(name, op) \
@@ -295,65 +292,71 @@ TENSOR_COL_DISPATCH(less_equal_col_reverse, y <= x ? 1.0 : 0.0)
295292
* can vectorize the elementwise mapping instead of being blocked by an
296293
* indirect call. */
297294

298-
#define TENSOR_ROW_DISPATCH_BODY(op) \
299-
tensor_double_view v_a; \
300-
zend_long nb = 0; \
301-
double * owned_b = NULL; \
302-
const double * vb = NULL; \
303-
\
304-
if (UNEXPECTED(!tensor_view_resolve_arg(a, &v_a) || \
305-
!tensor_view_doubles(b, &vb, &nb, &owned_b))) { \
306-
return; \
307-
} \
308-
\
309-
zend_long total = v_a.len; \
310-
zend_long nHat = zephir_get_intval(n_zval); \
311-
\
312-
if (UNEXPECTED(nHat < 1 || total < nHat)) { \
313-
zephir_throw_exception_string(spl_ce_LengthException, \
314-
SL("Matrix and vector dimensions must agree.")); \
315-
if (owned_b != NULL) { efree(owned_b); } \
316-
return; \
317-
} \
318-
\
319-
zend_long m = total / nHat; \
320-
\
321-
zval c; \
322-
\
295+
#define TENSOR_ROW_DISPATCH_BODY(op) \
296+
tensor_double_view v_a, v_b; \
297+
\
298+
if (UNEXPECTED(!tensor_view_resolve_arg(a, &v_a) || \
299+
!tensor_view_resolve_arg(b, &v_b))) { \
300+
return; \
301+
} \
302+
\
303+
zend_long total = v_a.len; \
304+
zend_long nHat = zephir_get_intval(n_zval); \
305+
\
306+
if (UNEXPECTED(nHat < 1 || total < nHat)) { \
307+
zephir_throw_exception_string(spl_ce_LengthException, \
308+
SL("Matrix and vector dimensions must agree.")); \
309+
return; \
310+
} \
311+
\
312+
zend_long m = total / nHat; \
313+
\
314+
zval c; \
315+
\
323316
if (UNEXPECTED(tensor_tensorbuffer_create(return_value, total, &c) == FAILURE)) { \
324-
if (owned_b != NULL) { efree(owned_b); } \
325-
return; \
326-
} \
327-
\
328-
double * restrict vc = zephir_buffer_doubles(&c); \
329-
\
330-
zend_long i, j; \
331-
\
332-
if (v_a.contiguous) { \
333-
const double * restrict va = v_a.base + v_a.offset; \
334-
\
335-
for (i = 0; i < m; ++i) { \
336-
for (j = 0; j < nHat; ++j) { \
337-
double x = va[i * nHat + j]; \
338-
double y = vb[j]; \
339-
vc[i * nHat + j] = op; \
340-
} \
341-
} \
342-
} else { \
343-
tensor_view_reader ra; \
344-
tensor_view_reader_init(&ra, &v_a); \
345-
\
346-
for (i = 0; i < m; ++i) { \
347-
for (j = 0; j < nHat; ++j) { \
348-
double x = tensor_view_reader_value(&ra); \
349-
double y = vb[j]; \
350-
vc[i * nHat + j] = op; \
351-
tensor_view_reader_advance(&ra); \
352-
} \
353-
} \
354-
} \
355-
\
356-
if (owned_b != NULL) { efree(owned_b); } \
317+
return; \
318+
} \
319+
\
320+
double * restrict vc = zephir_buffer_doubles(&c); \
321+
\
322+
zend_long i, j; \
323+
\
324+
if (v_a.contiguous) { \
325+
const double * restrict va = v_a.base + v_a.offset; \
326+
\
327+
if (v_b.contiguous) { \
328+
const double * vb = v_b.base + v_b.offset; \
329+
\
330+
for (i = 0; i < m; ++i) { \
331+
for (j = 0; j < nHat; ++j) { \
332+
double x = va[i * nHat + j]; \
333+
double y = vb[j]; \
334+
vc[i * nHat + j] = op; \
335+
} \
336+
} \
337+
} else { \
338+
for (i = 0; i < m; ++i) { \
339+
for (j = 0; j < nHat; ++j) { \
340+
double x = va[i * nHat + j]; \
341+
double y = tensor_double_view_get(&v_b, j); \
342+
vc[i * nHat + j] = op; \
343+
} \
344+
} \
345+
} \
346+
} else { \
347+
tensor_view_reader ra; \
348+
tensor_view_reader_init(&ra, &v_a); \
349+
\
350+
for (i = 0; i < m; ++i) { \
351+
for (j = 0; j < nHat; ++j) { \
352+
double x = tensor_view_reader_value(&ra); \
353+
double y = tensor_double_view_get(&v_b, j); \
354+
vc[i * nHat + j] = op; \
355+
tensor_view_reader_advance(&ra); \
356+
} \
357+
} \
358+
} \
359+
\
357360
zval_ptr_dtor(&c);
358361

359362
#define TENSOR_ROW_DISPATCH(name, op) \

‎ext/include/linear_algebra.c‎

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1478,14 +1478,7 @@ void tensor_rank(zval * return_value, zval * a, zval * m, zval * n)
14781478
return;
14791479
}
14801480

1481-
if (v.ndim <= 1) {
1482-
v.ndim = 2;
1483-
v.shape[0] = (zend_long) ma;
1484-
v.shape[1] = (zend_long) na;
1485-
v.strides[0] = (zend_long) na;
1486-
v.strides[1] = 1;
1487-
v.contiguous = 1;
1488-
}
1481+
tensor_view_expand_matrix(&v, (zend_long) ma, (zend_long) na);
14891482

14901483
unsigned int rank = 0;
14911484
double * row = emalloc((na ? na : 1) * sizeof(double));
@@ -1630,14 +1623,7 @@ void tensor_covariance(zval * return_value, zval * a, zval * m, zval * n)
16301623
return;
16311624
}
16321625

1633-
if (va_view.ndim <= 1) {
1634-
va_view.ndim = 2;
1635-
va_view.shape[0] = ma;
1636-
va_view.shape[1] = na;
1637-
va_view.strides[0] = na;
1638-
va_view.strides[1] = 1;
1639-
va_view.contiguous = 1;
1640-
}
1626+
tensor_view_expand_matrix(&va_view, ma, na);
16411627

16421628
/* Centre the rows into a scratch block. This is a plain block rather than a
16431629
* TensorBuffer because it never escapes this call, and materialising the

‎ext/include/shape.c‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ int tensor_matrix_view(zval * obj, zend_long n_hat, tensor_double_view * v, zend
4242
*n = n_hat;
4343
*m = v->len / n_hat;
4444

45+
tensor_view_expand_matrix(v, *m, *n);
46+
4547
return 1;
4648
}
4749

‎ext/include/view.h‎

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,28 @@ static zend_always_inline zend_long tensor_view_row_length(const tensor_double_v
5858
return c;
5959
}
6060

61+
/* Give a flat 1-D view a logical `m` x `n` row-major shape so that row-wise
62+
* kernels can reach its rows. A plain dense matrix resolves to a 1-D view of
63+
* its whole allocation, and a Vector or ColumnVector resolves to a 1-D view
64+
* whose element stride need not be 1; both are interpreted as `m` rows of `n`
65+
* consecutive (in the logical sense) elements. A view that already has two or
66+
* more dimensions is left untouched. */
67+
static zend_always_inline void tensor_view_expand_matrix(tensor_double_view * v, zend_long m, zend_long n)
68+
{
69+
if (v->ndim > 1) {
70+
return;
71+
}
72+
73+
zend_long s = v->ndim == 1 ? v->strides[0] : 1;
74+
75+
v->shape[0] = m;
76+
v->shape[1] = n;
77+
v->strides[0] = n * s;
78+
v->strides[1] = s;
79+
v->ndim = 2;
80+
v->contiguous = (s == 1);
81+
}
82+
6183
/* Resolve a `Tensor\TensorBuffer` into a stride view, reading its
6284
* `buffer`/`offset`/`shape`/`strides` properties. Returns 1 on success and 0
6385
* on failure (throwing). Implemented in buffer.c. */

0 commit comments

Comments
 (0)