@@ -95,30 +95,44 @@ class ProgramCacheResource(abc.ABC):
9595 ``bytes``.
9696
9797 The values written are the compiled program bytes themselves --
98- cubin, PTX, LTO-IR, etc. Callers that compile via
99- :class:`~cuda.core.Program` typically pass the resulting
100- :class:`~cuda.core.ObjectCode` directly; the cache extracts
101- ``bytes(object_code.code)`` for storage. Reads return raw bytes so
102- cache files remain consumable by external NVIDIA tools
103- (``cuobjdump``, ``nvdisasm``, ``cuda-gdb``, ...). Callers
104- reconstruct an :class:`~cuda.core.ObjectCode` themselves when they
105- need one::
98+ cubin, PTX, LTO-IR, etc. Reads return raw bytes so cache files
99+ remain consumable by external NVIDIA tools (``cuobjdump``,
100+ ``nvdisasm``, ``cuda-gdb``, ...).
106101
102+ Most callers don't interact with this object directly. The
103+ recommended usage is :meth:`cuda.core.Program.compile`'s ``cache=``
104+ keyword, which derives the key, returns a fresh
105+ :class:`~cuda.core.ObjectCode` on hit, and stores the compile
106+ result on miss::
107+
108+ with FileStreamProgramCache() as cache:
109+ obj = program.compile("cubin", cache=cache)
110+
111+ The escape hatch -- only needed when the compile inputs require an
112+ ``extra_digest`` (header / PCH content fingerprints, NVVM
113+ libdevice) -- is to call :func:`make_program_cache_key` yourself
114+ and use the cache as a plain ``bytes`` mapping::
115+
116+ from cuda.core._module import ObjectCode
117+
118+ key = make_program_cache_key(
119+ code=source, code_type="c++", options=options,
120+ target_type="cubin", extra_digest=header_fingerprint(),
121+ )
107122 data = cache.get(key)
108123 if data is None:
109124 obj = program.compile("cubin")
110125 cache[key] = obj # extracts bytes(obj.code)
111- data = bytes(obj.code)
112126 else:
113127 obj = ObjectCode._init(data, "cubin")
114128
115129 The cache layer does no payload validation; bytes go in and come
116130 back out unchanged. Symbol-mapping metadata that
117131 :class:`~cuda.core.ObjectCode` carries when produced with NVRTC
118132 name expressions is **not** preserved across a cache round-trip --
119- the binary alone is stored. Callers that need symbol_mapping for
120- ``get_kernel(name_expression)`` should compile fresh, or look the
121- mangled symbol up by hand.
133+ the binary alone is stored. Callers that need `` symbol_mapping``
134+ for ``get_kernel(name_expression)`` should compile fresh, or look
135+ the mangled symbol up by hand.
122136 """
123137
124138 @abc .abstractmethod
@@ -170,6 +184,29 @@ def get(self, key: bytes | str, default: bytes | None = None) -> bytes | None:
170184 except KeyError :
171185 return default
172186
187+ def update (
188+ self ,
189+ items : (
190+ collections .abc .Mapping [bytes | str , bytes | bytearray | memoryview | ObjectCode ]
191+ | collections .abc .Iterable [
192+ tuple [bytes | str , bytes | bytearray | memoryview | ObjectCode ]
193+ ]
194+ ),
195+ / ,
196+ ) -> None :
197+ """Bulk ``__setitem__``.
198+
199+ Accepts a mapping or an iterable of ``(key, value)`` pairs. Each
200+ write goes through ``__setitem__`` so backend-specific value
201+ coercion (e.g. extracting bytes from an :class:`~cuda.core.ObjectCode`)
202+ and size-cap enforcement run on every entry. Not transactional --
203+ a failure mid-iteration leaves earlier writes committed.
204+ """
205+ if isinstance (items , collections .abc .Mapping ):
206+ items = items .items ()
207+ for key , value in items :
208+ self [key ] = value
209+
173210 def close (self ) -> None : # noqa: B027
174211 """Release backend resources. No-op by default."""
175212
@@ -516,18 +553,38 @@ def make_program_cache_key(
516553
517554 Examples
518555 --------
519- Wiring a cache around :class:`~cuda.core.Program` compile::
556+ For most workflows you should not call ``make_program_cache_key``
557+ yourself -- pass ``cache=`` to :meth:`cuda.core.Program.compile`,
558+ which derives the key, returns the cached
559+ :class:`~cuda.core.ObjectCode` on hit, and stores the compile
560+ result on miss::
520561
521562 from cuda.core import Program, ProgramOptions
522- from cuda.core._module import ObjectCode
523- from cuda.core.utils import FileStreamProgramCache, make_program_cache_key
563+ from cuda.core.utils import FileStreamProgramCache
524564
525565 source = 'extern "C" __global__ void k(int *a){ *a = 1; }'
526566 options = ProgramOptions(arch="sm_80")
527567
528- with FileStreamProgramCache("/var/cache/myapp/cuda") as cache:
568+ with FileStreamProgramCache() as cache:
569+ obj = Program(source, "c++", options=options).compile(
570+ "cubin", cache=cache
571+ )
572+
573+ Call ``make_program_cache_key`` directly when the compile inputs
574+ require an ``extra_digest`` (the cache cannot read external file
575+ content on the caller's behalf) -- ``Program.compile(cache=...)``
576+ refuses those inputs with a ``ValueError`` pointing here::
577+
578+ from cuda.core._module import ObjectCode
579+ from cuda.core.utils import FileStreamProgramCache, make_program_cache_key
580+
581+ with FileStreamProgramCache() as cache:
529582 key = make_program_cache_key(
530- code=source, code_type="c++", options=options, target_type="cubin"
583+ code=source,
584+ code_type="c++",
585+ options=options,
586+ target_type="cubin",
587+ extra_digest=fingerprint_headers(options.include_path),
531588 )
532589 data = cache.get(key)
533590 if data is None:
0 commit comments