scratch.py 2.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384
  1. """Shared caller-owned scratch plan helpers."""
  2. from __future__ import annotations
  3. from collections.abc import Mapping, Sequence
  4. from dataclasses import dataclass
  5. import torch
  6. @dataclass(frozen=True, kw_only=True)
  7. class ScratchBufferSpec:
  8. name: str
  9. shape: tuple[int, ...]
  10. dtype: torch.dtype
  11. device: torch.device
  12. @property
  13. def nbytes(self) -> int:
  14. numel = 1
  15. for dim in self.shape:
  16. numel *= int(dim)
  17. return numel * torch.empty((), dtype=self.dtype).element_size()
  18. def scratch_buffer_spec(
  19. name: str,
  20. *,
  21. nbytes: int,
  22. device: torch.device,
  23. ) -> ScratchBufferSpec:
  24. return ScratchBufferSpec(
  25. name=name,
  26. shape=(max(int(nbytes), 1),),
  27. dtype=torch.uint8,
  28. device=device,
  29. )
  30. def scratch_tensor(
  31. scratch: torch.Tensor | Mapping[str, torch.Tensor] | Sequence[torch.Tensor],
  32. specs: tuple[ScratchBufferSpec, ...],
  33. *,
  34. owner: str,
  35. ) -> torch.Tensor:
  36. if len(specs) != 1:
  37. raise RuntimeError(
  38. f"{owner} scratch plans currently expect exactly one scratch buffer"
  39. )
  40. spec = specs[0]
  41. if isinstance(scratch, torch.Tensor):
  42. tensor = scratch
  43. elif isinstance(scratch, Mapping):
  44. if spec.name not in scratch:
  45. raise KeyError(f"scratch mapping is missing {spec.name!r}")
  46. tensor = scratch[spec.name]
  47. else:
  48. if len(scratch) != 1:
  49. raise ValueError(
  50. f"scratch sequence must contain exactly one tensor, got {len(scratch)}"
  51. )
  52. tensor = scratch[0]
  53. if tensor.dtype != spec.dtype:
  54. raise TypeError(
  55. f"{spec.name} scratch must have dtype {spec.dtype}, got {tensor.dtype}"
  56. )
  57. if tensor.device != spec.device:
  58. raise ValueError(
  59. f"{spec.name} scratch device {tensor.device} does not match {spec.device}"
  60. )
  61. if int(tensor.numel()) < int(spec.shape[0]):
  62. raise ValueError(
  63. f"{spec.name} scratch has {int(tensor.numel())} bytes, requires {int(spec.shape[0])}"
  64. )
  65. if not tensor.is_contiguous():
  66. raise ValueError(f"{spec.name} scratch must be contiguous")
  67. return tensor.reshape(-1)
  68. __all__ = [
  69. "ScratchBufferSpec",
  70. "scratch_buffer_spec",
  71. "scratch_tensor",
  72. ]