跳至内容

属性

基础¤

形状 property ¤

shape: tuple[sint, ...]

数据类型 property ¤

dtype: DType

设备 property ¤

device: str | tuple[str, ...]

维度数 property ¤

ndim: int

返回张量的维度数。

t = Tensor([[1, 2], [3, 4]])
print(t.ndim)
2

元素总数 ¤

numel() -> sint

返回张量中的元素总数。

t = Tensor([[[1, 2], [3, 4]], [[5, 6], [7, 8]]])
print(t.numel())
8
Source code in tinygrad/tensor.py
3925
3926
3927
3928
3929
3930
3931
3932
3933
3934
def numel(self) -> sint:
  """
  Returns the total number of elements in the tensor.

  ```python exec="true" source="above" session="tensor" result="python"
  t = Tensor([[[1, 2], [3, 4]], [[5, 6], [7, 8]]])
  print(t.numel())
  ```
  """
  return prod(self.shape)

元素大小 ¤

element_size() -> int

返回张量中单个元素的大小(以字节为单位)。

t = Tensor([5], dtype=dtypes.int16)
print(t.element_size())
2
Source code in tinygrad/tensor.py
3936
3937
3938
3939
3940
3941
3942
3943
3944
3945
def element_size(self) -> int:
  """
  Returns the size in bytes of an individual element in the tensor.

  ```python exec="true" source="above" session="tensor" result="python"
  t = Tensor([5], dtype=dtypes.int16)
  print(t.element_size())
  ```
  """
  return self.dtype.itemsize

字节数 ¤

nbytes() -> int

返回张量中所有元素的总字节数。

t = Tensor([8, 9], dtype=dtypes.float)
print(t.nbytes())
8
Source code in tinygrad/tensor.py
3947
3948
3949
3950
3951
3952
3953
3954
3955
3956
def nbytes(self) -> int:
  """
  Returns the total number of bytes of all elements in the tensor.

  ```python exec="true" source="above" session="tensor" result="python"
  t = Tensor([8, 9], dtype=dtypes.float)
  print(t.nbytes())
  ```
  """
  return self.numel() * self.element_size()

是否为浮点类型 ¤

is_floating_point() -> bool

如果张量包含浮点类型则返回True,即属于dtype.float64dtype.float32dtype.float16dtype.bfloat16之一。

t = Tensor([8, 9], dtype=dtypes.float32)
print(t.is_floating_point())
True
Source code in tinygrad/tensor.py
3958
3959
3960
3961
3962
3963
3964
3965
3966
3967
3968
def is_floating_point(self) -> bool:
  """
  Returns `True` if the tensor contains floating point types, i.e. is one of `dtype.float64`, `dtype.float32`,
  `dtype.float16`, `dtype.bfloat16`.

  ```python exec="true" source="above" session="tensor" result="python"
  t = Tensor([8, 9], dtype=dtypes.float32)
  print(t.is_floating_point())
  ```
  """
  return dtypes.is_float(self.dtype)

大小 ¤

size(dim: int | None = None) -> sint | tuple[sint, ...]

返回张量的大小。如果指定了dim,则返回沿维度dim的长度。否则返回张量的形状。

t = Tensor([[4, 5, 6], [7, 8, 9]])
print(t.size())
(2, 3)
print(t.size(dim=1))
3

Source code in tinygrad/tensor.py
3970
3971
3972
3973
3974
3975
3976
3977
3978
3979
3980
3981
3982
def size(self, dim:int|None=None) -> sint|tuple[sint, ...]:
  """
  Return the size of the tensor. If `dim` is specified, return the length along dimension `dim`. Otherwise return the shape of the tensor.

  ```python exec="true" source="above" session="tensor" result="python"
  t = Tensor([[4, 5, 6], [7, 8, 9]])
  print(t.size())
  ```
  ```python exec="true" source="above" session="tensor" result="python"
  print(t.size(dim=1))
  ```
  """
  return self.shape if dim is None else self.shape[dim]

数据访问¤

数据 ¤

data() -> memoryview

返回该张量的数据作为内存视图。

t = Tensor([1, 2, 3, 4])
print(np.frombuffer(t.data(), dtype=np.int32))
[1 2 3 4]
Source code in tinygrad/tensor.py
293
294
295
296
297
298
299
300
301
302
303
304
def data(self) -> memoryview:
  """
  Returns the data of this tensor as a memoryview.

  ```python exec="true" source="above" session="tensor" result="python"
  t = Tensor([1, 2, 3, 4])
  print(np.frombuffer(t.data(), dtype=np.int32))
  ```
  """
  if 0 in self.shape: return memoryview(bytearray(0)).cast(self.dtype.base.fmt)
  assert all_int(self.shape), f"no data if shape is symbolic, {self.shape=}"
  return self._buffer().as_typed_buffer(self.shape)

项目 ¤

item() -> ConstType

返回该张量的值作为一个标准的Python数值。

t = Tensor(42)
print(t.item())
42
Source code in tinygrad/tensor.py
306
307
308
309
310
311
312
313
314
315
316
def item(self) -> ConstType:
  """
  Returns the value of this tensor as a standard Python number.

  ```python exec="true" source="above" session="tensor" result="python"
  t = Tensor(42)
  print(t.item())
  ```
  """
  assert self.numel() == 1, "must have one element for item"
  return self.data()[(0,) * len(self.shape)]

转换为列表 ¤

tolist() -> Sequence[ConstType] | ConstType

返回该张量的值作为一个嵌套列表。 对于常量张量则返回单个值。

t = Tensor([1, 2, 3, 4])
print(t.tolist())
[1, 2, 3, 4]
t = Tensor(5)
print(t.tolist())
5

Source code in tinygrad/tensor.py
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
def tolist(self) -> Sequence[ConstType]|ConstType:
  """
  Returns the value of this tensor as a nested list.
  Returns single value for const tensor.

  ```python exec="true" source="above" session="tensor" result="python"
  t = Tensor([1, 2, 3, 4])
  print(t.tolist())
  ```
  ```python exec="true" source="above" session="tensor" result="python"
  t = Tensor(5)
  print(t.tolist())
  ```
  """
  return self.data().tolist()

numpy ¤

numpy() -> 'np.ndarray'

将此张量的值作为numpy.ndarray返回。

t = Tensor([1, 2, 3, 4])
print(repr(t.numpy()))
array([1, 2, 3, 4], dtype=int32)
Source code in tinygrad/tensor.py
336
337
338
339
340
341
342
343
344
345
346
347
348
349
def numpy(self) -> 'np.ndarray':  # type: ignore [name-defined] # noqa: F821
  """
  Returns the value of this tensor as a `numpy.ndarray`.

  ```python exec="true" source="above" session="tensor" result="python"
  t = Tensor([1, 2, 3, 4])
  print(repr(t.numpy()))
  ```
  """
  assert all_int(self.shape), f"no data if shape is symbolic, {self.shape=}"
  import numpy as np
  if self.dtype.base == dtypes.bfloat16: return self.float().numpy()
  if 0 in self.shape: return np.empty(self.shape, dtype=_to_np_dtype(self.dtype.base))
  return self._buffer().numpy().reshape(self.shape)

tinygrad 操作¤

schedule_with_vars ¤

schedule_with_vars(
    *lst: Tensor,
) -> tuple[list[ScheduleItem], dict[Variable, int]]

创建实现这些带有变量的Tensor(s)所需的调度计划。

注意

一个张量只能被调度一次。

Source code in tinygrad/tensor.py
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
def schedule_with_vars(self, *lst:Tensor) -> tuple[list[ScheduleItem], dict[Variable, int]]:
  """
  Creates the schedule needed to realize these Tensor(s), with Variables.

  NOTE: A Tensor can only be scheduled once.
  """
  big_sink = UOp.sink(*[x.lazydata for x in (self,)+lst])

  # TODO: move this to scheduler tensor_map pass
  if any(x.op is Ops.MULTI for x in big_sink.toposort):
    # multi fixup
    _apply_map_to_tensors(get_multi_map(big_sink), name="Apply Multi Map")
    big_sink = UOp.sink(*flatten([x.lazydata.src if x.lazydata.op is Ops.MULTI else [x.lazydata] for x in (self,)+lst]))

  # verify Tensors match the spec
  if __debug__: type_verify(list(big_sink.toposort), tensor_uop_spec)

  schedule, var_vals, becomes_map = create_schedule_with_vars(big_sink)
  _apply_map_to_tensors(becomes_map, name="Apply Schedule Map")
  return memory_planner(schedule), var_vals

调度 ¤

schedule(*lst: Tensor) -> list[ScheduleItem]

创建实现这些张量所需的调度计划。

Source code in tinygrad/tensor.py
247
248
249
250
251
def schedule(self, *lst:Tensor) -> list[ScheduleItem]:
  """Creates the schedule needed to realize these Tensor(s)."""
  schedule, var_vals = self.schedule_with_vars(*lst)
  assert len(var_vals) == 0
  return schedule

实现 ¤

realize(*lst: Tensor, do_update_stats=True) -> Tensor

触发创建这些张量所需的计算。

Source code in tinygrad/tensor.py
253
254
255
256
def realize(self, *lst:Tensor, do_update_stats=True) -> Tensor:
  """Triggers the computation needed to create these Tensor(s)."""
  run_schedule(*self.schedule_with_vars(*lst), do_update_stats=do_update_stats)
  return self

替换 ¤

replace(x: Tensor, allow_shape_mismatch=False) -> Tensor

将该张量的数据替换为另一个张量的数据。仅要求张量的形状必须匹配。

Source code in tinygrad/tensor.py
258
259
260
261
262
263
264
265
def replace(self, x:Tensor, allow_shape_mismatch=False) -> Tensor:
  """
  Replaces the data of this tensor with the data of another tensor. Only the shape of the tensors must match.
  """
  # used for replacing a Tensor with a new version of it (potentially with a different device and dtype)
  assert self.shape == x.shape or allow_shape_mismatch, f"replace shape mismatch {self.shape} != {x.shape}"
  self.lazydata = x.lazydata
  return self

赋值 ¤

assign(x) -> Tensor
Source code in tinygrad/tensor.py
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
def assign(self, x) -> Tensor:
  # TODO: this is a hack for writing to DISK. remove with working assign
  if isinstance(self.device, str) and self.device.startswith("DISK"):
    if x.__class__ is not Tensor: x = Tensor(x, device="CPU", dtype=self.dtype)
    self.contiguous().realize().lazydata.base.buffer.ensure_allocated().copyin(x._data())
    return self
  if x.__class__ is not Tensor: x = Tensor(x, device=self.device, dtype=self.dtype)
  if self.lazydata is x.lazydata: return self  # a self assign is a NOOP
  # NOTE: we allow cross device assign
  assert self.shape == x.shape, f"assign shape mismatch {self.shape} != {x.shape}"
  assert self.device == x.device, f"assign device mismatch {self.device} != {x.device}"
  assert self.dtype == x.dtype, f"assign dtype mismatch {self.dtype} != {x.dtype}"
  assert not x.requires_grad  # self requires_grad is okay?
  if not self.lazydata.is_realized: return self.replace(x)
  self.lazydata = self.lazydata.assign(x.lazydata)
  return self

分离 ¤

detach() -> Tensor

返回一个与此张量数据相同的新张量,但已从自动微分图中分离。

Source code in tinygrad/tensor.py
284
285
286
287
288
def detach(self) -> Tensor:
  """
  Returns a new tensor with the same data as this tensor, but detached from the autograd graph.
  """
  return Tensor(self.lazydata.detach(), device=self.device, requires_grad=False)

克隆 ¤

clone() -> Tensor

创建一个该张量的克隆,为数据分配单独的缓冲区。

Source code in tinygrad/tensor.py
351
352
353
354
355
356
357
def clone(self) -> Tensor:
  """
  Creates a clone of this tensor allocating a separate buffer for the data.
  """
  ret = Tensor(self.lazydata.clone(), self.device, requires_grad=self.requires_grad)
  if self.grad is not None: ret.grad = self.grad.clone()
  return ret

¤

to(device: str | tuple[str, ...] | None) -> Tensor

将张量移动到指定设备。

Source code in tinygrad/tensor.py
359
360
361
362
363
364
365
366
367
368
def to(self, device:str|tuple[str, ...]|None) -> Tensor:
  """
  Moves the tensor to the given device.
  """
  device = tuple(Device.canonicalize(x) for x in device) if isinstance(device, (tuple, list)) else Device.canonicalize(device)
  if device == self.device: return self
  if not isinstance(device, str): return self.shard(device)
  ret = Tensor(self.lazydata, device, requires_grad=self.requires_grad)
  if self.grad is not None: ret.grad = self.grad.to(device)
  return ret

转换为 ¤

to_(device: str | tuple[str, ...] | None) -> Tensor

将张量原地移动到指定设备。

Source code in tinygrad/tensor.py
370
371
372
373
374
375
376
def to_(self, device:str|tuple[str, ...]|None) -> Tensor:
  """
  Moves the tensor to the given device in place.
  """
  real = self.to(device)
  if self.grad is not None and real.grad is not None: self.grad.replace(real.grad)
  return self.replace(real)

分片 ¤

shard(
    devices: tuple[str, ...], axis: int | None = None
) -> Tensor

将张量分片到给定的设备上。可选地指定要在哪个轴上进行分片。

t = Tensor.empty(2, 4)
print(t.shard((t.device, t.device), axis=1).lazydata)
UOp(Ops.MULTI, dtypes.float, arg=(1, (True, True)), src=(
  UOp(Ops.CONTIGUOUS, dtypes.float, arg=None, src=(
    UOp(Ops.COPY, dtypes.float, arg=False, src=(
      x2:=UOp(Ops.DEVICE, dtypes.void, arg='CPU', src=()),
      UOp(Ops.SHRINK, dtypes.float, arg=((0, 2), (0, 2)), src=(
        x4:=UOp(Ops.RESHAPE, dtypes.float, arg=(2, 4), src=(
          UOp(Ops.BUFFER, dtypes.float, arg=8, src=(
             x2,
            UOp(Ops.UNIQUE, dtypes.void, arg=1169, src=()),)),)),)),)),)),
  UOp(Ops.CONTIGUOUS, dtypes.float, arg=None, src=(
    UOp(Ops.COPY, dtypes.float, arg=False, src=(
       x2,
      UOp(Ops.SHRINK, dtypes.float, arg=((0, 2), (2, 4)), src=(
         x4,)),)),)),))
Source code in tinygrad/tensor.py
378
379
380
381
382
383
384
385
386
387
388
389
390
def shard(self, devices:tuple[str, ...], axis:int|None=None) -> Tensor:
  """
  Shards the tensor across the given devices. Optionally specify which axis to shard on.

  ```python exec="true" source="above" session="tensor" result="python"
  t = Tensor.empty(2, 4)
  print(t.shard((t.device, t.device), axis=1).lazydata)
  ```
  """
  assert isinstance(self.device, str), "can't shard a MultiLazyBuffer"
  devices = tuple(Device.canonicalize(x) for x in devices)
  mlb = self.lazydata.shard(devices, self._resolve_dim(axis) if axis is not None else None)
  return Tensor(mlb, device=devices, requires_grad=self.requires_grad)

分片_ ¤

shard_(
    devices: tuple[str, ...], axis: int | None = None
) -> Tensor

将张量就地分片到给定的设备上。

Source code in tinygrad/tensor.py
392
393
394
395
396
def shard_(self, devices:tuple[str, ...], axis:int|None=None) -> Tensor:
  """
  Shards the tensor across the given devices in place.
  """
  return self.replace(self.shard(devices, axis))

连续 ¤

contiguous() -> Tensor

返回一个连续的张量。

Source code in tinygrad/tensor.py
2719
2720
2721
2722
2723
def contiguous(self) -> Tensor:
  """
  Returns a contiguous tensor.
  """
  return self._apply_uop(UOp.contiguous)

contiguous_backward ¤

contiguous_backward() -> Tensor

在反向传播过程中插入一个连续操作。

Source code in tinygrad/tensor.py
2734
2735
2736
2737
2738
def contiguous_backward(self) -> Tensor:
  """
  Inserts a contiguous operation in the backward pass.
  """
  return self._apply_uop(UOp.contiguous_backward)

梯度¤

梯度 ¤

gradient(
    *targets: Tensor,
    gradient: Tensor | None = None,
    materialize_grads=False
) -> list[Tensor]

计算目标相对于自身的梯度。

x = Tensor.eye(3)
y = Tensor([[2.0,0,-2.0]])
z = y.matmul(x).sum()
dx, dy = z.gradient(x, y)

print(dx.tolist())  # dz/dx
print(dy.tolist())  # dz/dy
[[2.0, 2.0, 2.0], [0.0, 0.0, 0.0], [-2.0, -2.0, -2.0]]
[[1.0, 1.0, 1.0]]
Source code in tinygrad/tensor.py
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
def gradient(self, *targets:Tensor, gradient:Tensor|None=None, materialize_grads=False) -> list[Tensor]:
  """
  Compute the gradient of the targets with respect to self.

  ```python exec="true" source="above" session="tensor" result="python"
  x = Tensor.eye(3)
  y = Tensor([[2.0,0,-2.0]])
  z = y.matmul(x).sum()
  dx, dy = z.gradient(x, y)

  print(dx.tolist())  # dz/dx
  print(dy.tolist())  # dz/dy
  ```
  """
  assert gradient is not None or self.shape == tuple(), "when no gradient is provided, backward must be called on a scalar tensor"
  if gradient is None: gradient = Tensor(1.0, dtype=self.dtype, device=self.device, requires_grad=False)
  rets = []
  target_uops = [x.lazydata for x in targets]
  grads = compute_gradient(self.lazydata, gradient.lazydata, set(target_uops))
  ret = []
  for x in target_uops:
    if (y:=grads.get(x)) is None:
      if materialize_grads: y = x.const_like(0)
      else: raise RuntimeError(f"{x}\n\nnot found in\n\n{self.lazydata}")
    ret.append(y)
  rets.append(ret)
  # create returned Tensors
  return [Tensor(u, device=t.device) for t,u in zip(targets, rets[0])]

反向传播 ¤

backward(gradient: Tensor | None = None) -> Tensor

将张量的梯度通过计算图反向传播。 如果未提供'gradient'参数,则该张量必须是标量,且梯度隐式设置为1.0。

t = Tensor([1.0, 2.0, 3.0, 4.0], requires_grad=True)
t.sum().backward()
print(t.grad.numpy())
[1. 1. 1. 1.]

Source code in tinygrad/tensor.py
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
def backward(self, gradient:Tensor|None=None) -> Tensor:
  """
  Propagates the gradient of a tensor backwards through the computation graph.
  If the 'gradient' argument is not provided, the tensor must be a scalar, and the gradient is implicitly set to 1.0.
  ```python exec="true" source="above" session="tensor" result="python"
  t = Tensor([1.0, 2.0, 3.0, 4.0], requires_grad=True)
  t.sum().backward()
  print(t.grad.numpy())
  ```
  """
  all_uops = self.lazydata.toposort
  tensors_need_grad: list[Tensor] = [t for tref in all_tensors if (t:=tref()) is not None and \
                                     t.lazydata in all_uops and t.requires_grad and not Tensor.no_grad]
  # clear contexts
  for t,g in zip(tensors_need_grad, self.gradient(*tensors_need_grad, gradient=gradient, materialize_grads=True)):
    assert g.shape == t.shape, f"grad shape must match tensor shape, {g.shape!r} != {t.shape!r}"
    t.grad = g if t.grad is None else (t.grad + g)
  return self