@@ -188,6 +188,12 @@ impl LoraController {
188188 . and_then ( Value :: as_str)
189189 . filter ( |uri| !uri. trim ( ) . is_empty ( ) )
190190 . ok_or_else ( || "'source.uri' is required in request" . to_string ( ) ) ?;
191+ let load_inplace = match request. get ( "load_inplace" ) {
192+ None => false ,
193+ Some ( value) => value
194+ . as_bool ( )
195+ . ok_or_else ( || "'load_inplace' must be a boolean" . to_string ( ) ) ?,
196+ } ;
191197 validate_adapter_name ( & self . reserved_names , & name) ?;
192198
193199 let operation = self . operation_lock ( & name) ;
@@ -214,7 +220,8 @@ impl LoraController {
214220 name : name. clone ( ) ,
215221 path : path. to_string_lossy ( ) . into_owned ( ) ,
216222 } ;
217- if let Some ( existing) = self . loaded . lock ( ) . await . get ( & name) . cloned ( ) {
223+ let existing = { self . loaded . lock ( ) . await . get ( & name) . cloned ( ) } ;
224+ if let Some ( existing) = existing {
218225 if existing. adapter == adapter {
219226 if !existing. published {
220227 self . discovery
@@ -230,9 +237,41 @@ impl LoraController {
230237 }
231238 return Ok ( success ( & existing. adapter , "already loaded" ) ) ;
232239 }
233- return Err ( format ! (
234- "LoRA adapter '{name}' conflicts with the loaded source path"
235- ) ) ;
240+ if !load_inplace {
241+ return Err ( format ! (
242+ "LoRA adapter '{name}' conflicts with the loaded source path"
243+ ) ) ;
244+ }
245+ let adapter = self
246+ . engine
247+ . load_lora_inplace ( adapter)
248+ . await
249+ . map_err ( |error| error. to_string ( ) ) ?;
250+ let published = if existing. published {
251+ true
252+ } else {
253+ match self . discovery . attach ( & adapter) . await {
254+ Ok ( ( ) ) => true ,
255+ Err ( error) => {
256+ self . loaded . lock ( ) . await . insert (
257+ name,
258+ ManagedLora {
259+ adapter,
260+ published : false ,
261+ } ,
262+ ) ;
263+ return Err ( format ! ( "failed to register replaced LoRA model: {error}" ) ) ;
264+ }
265+ }
266+ } ;
267+ self . loaded . lock ( ) . await . insert (
268+ name,
269+ ManagedLora {
270+ adapter : adapter. clone ( ) ,
271+ published,
272+ } ,
273+ ) ;
274+ return Ok ( success ( & adapter, "replaced successfully" ) ) ;
236275 }
237276
238277 let adapter = self
@@ -488,6 +527,13 @@ mod tests {
488527 Ok ( adapter)
489528 }
490529
530+ async fn load_lora_inplace (
531+ & self ,
532+ adapter : LoraAdapter ,
533+ ) -> Result < LoraAdapter , DynamoError > {
534+ self . load_lora ( adapter) . await
535+ }
536+
491537 async fn unload_lora ( & self , name : & str ) -> Result < LoraAdapter , DynamoError > {
492538 self . events . lock ( ) . await . push ( format ! ( "unload:{name}" ) ) ;
493539 if self . fail_unload . load ( Ordering :: SeqCst ) {
@@ -664,6 +710,42 @@ mod tests {
664710 ) ;
665711 }
666712
713+ #[ tokio:: test]
714+ async fn load_inplace_replaces_a_stable_name_with_a_new_source ( ) {
715+ let root = tempfile:: tempdir ( ) . unwrap ( ) ;
716+ let first = root. path ( ) . join ( "step-1" ) ;
717+ let second = root. path ( ) . join ( "step-2" ) ;
718+ std:: fs:: create_dir ( & first) . unwrap ( ) ;
719+ std:: fs:: create_dir ( & second) . unwrap ( ) ;
720+ let engine = MockEngine :: new ( Vec :: new ( ) ) ;
721+ let discovery = Arc :: new ( MockDiscovery :: default ( ) ) ;
722+ let controller = LoraController :: new_with (
723+ engine. clone ( ) ,
724+ discovery. clone ( ) ,
725+ Some ( downloader ( & root. path ( ) . join ( "cache" ) ) ) ,
726+ HashSet :: new ( ) ,
727+ )
728+ . await
729+ . unwrap ( ) ;
730+
731+ for ( path, load_inplace) in [ ( & first, false ) , ( & second, true ) ] {
732+ controller
733+ . load ( json ! ( {
734+ "lora_name" : "adapter-a" ,
735+ "source" : { "uri" : format!( "file://{}" , path. display( ) ) } ,
736+ "load_inplace" : load_inplace,
737+ } ) )
738+ . await
739+ . unwrap ( ) ;
740+ }
741+
742+ assert_eq ! (
743+ engine. loaded. lock( ) . await [ "adapter-a" ] . path,
744+ std:: fs:: canonicalize( second) . unwrap( ) . to_string_lossy( )
745+ ) ;
746+ assert_eq ! ( discovery. attached. lock( ) . await . as_slice( ) , [ "adapter-a" ] ) ;
747+ }
748+
667749 #[ tokio:: test]
668750 async fn load_rejects_base_model_name_before_engine_mutation ( ) {
669751 let dir = tempfile:: tempdir ( ) . unwrap ( ) ;
0 commit comments